STM Урок 92. Датчик температуры DS18B20. Часть 3



 

Урок 92

 

Часть 3

 

Датчик температуры DS18B20

 

В предыдущей части урока мы познакомились с организацией памяти датчика, а также написали ещё несколько нужных функций.

 

Следующая функция, которая нам потребуется — это функция считывания памяти, нам же надо узнать температуру.

 

//----------------------------------------------------------

void ds18b20_ReadStratcpad(uint8_t mode, uint8_t *Data, uint8_t DevNum)

{

  uint8_t i;

  ds18b20_Reset();

  if(mode==SKIP_ROM)

  {

    //SKIP ROM

    ds18b20_WriteByte(0xCC);

  }

  //READ SCRATCHPAD

  ds18b20_WriteByte(0xBE);

  for(i=0;i<8;i++)

  {

    Data[i] = ds18b20_ReadByte();

  }

}

//----------------------------------------------------------

 

Функция данная тоже в особом объяснении не нуждается. Входные аргументы — это адрес массива, в который мы будем читать 8 байтов памяти, следующий — это режим, и последний — номер устройства.

Мы также сначала перезагружаем наш датчик, по условию режима даём команду пропустить чтение ROM, а затем подаём команду 0xBE, которая заставляет устройство передать нам байти памяти, которые мы затем читаем в цикле в наш массив.

Вот объяснение команды чтения памяти из технической документации

 

Image26

 

Добавим на эти две функции прототипы в заголовочном файле.

Перейдём в функцию main() и добавим там ещё ряд локальных переменных

 

uint8_t status;

uint8_t dt[8];

uint16_t raw_temper;

float temper;

char c;

 

 

Затем там добавим следующий код в бесконечном цикле

 

/* USER CODE BEGIN 3 */

ds18b20_MeasureTemperCmd(SKIP_ROM, 0);

HAL_Delay(800);

ds18b20_ReadStratcpad(SKIP_ROM, dt, 0);

sprintf(str1,"STRATHPAD: %02X %02X %02X %02X %02X %02X %02X %02X; ",

dt[0], dt[1], dt[2], dt[3], dt[4], dt[5], dt[6], dt[7]);

HAL_UART_Transmit(&huart1,(uint8_t*)str1,strlen(str1),0x1000);

sprintf(str1,"rn");

HAL_UART_Transmit(&huart1,(uint8_t*)str1,strlen(str1),0x1000);

HAL_Delay(150);

}

/* USER CODE END 3 */

 

Мы сначала даём команду на измерение и конвертацию температуры, ждём 800 милисекунд (на всякий случай, положено не менее 750 при нашей дискретности), Потом даём команду на чтении памяти и отображаем её в шестнадцатеричном виде в терминальной программе. Затем мы пока отдельно переносим строку и переводим каретку, а также ждём ещё немного до следующего чтения. Всё вместе получится приблизительно секунда. Для такого точного чтения температуры очень даже неплохо.

Соберём код, прошьём контроллер и посмотрим результат в терминале

 

Image27

 

Вот что мы прочитали. Первые два байта — это значение температуры. В нашем случае 0x9101. можно даже свободно вычислить температуру. В двоичном выражении эти 2 байта будут выглядеть как 0000000110010001. Получается что температура у нас положительная, так как первые пять старших бит у нас в нулях. В следующих семи битах у нас результат 0011001, что в десятично выражении будет 25, а в самых младших четырёх битах — 0001, это значт 1/16 доля градуса. получается что температура равна 25 и 1/16 градуса. У меня стоит термометр в часах, который приблизительно столько и показывает, можен немного побольше. Так что показания похожи на правду.

 

 

Посмотрим также считывание байтов в программе логического анализа. Вот наш Strathpad

 

Image28

 

Также посмотрим как именно выглядят считанные биты на графике

 

Image29

 

А выглядят они аналогично записываемых.

Здесь мы прочитали 0x93, что в двоичном выражении будет 10010011. Читаем справа налево. Так оно и есть. Единички опять у нас в виде коротеньких импульсов направленных вниз, а нолики — длинные.

Но мы же не будем каждый раз так вычислять температуру, поэтому вернёмся в файл ds18b20.c и добавим сначала функцию определения знака результата

 

//----------------------------------------------------------

uint8_t ds18b20_GetSign(uint16_t dt)

{

  //Проверим 11-й бит

  if (dt&(1<<11)) return 1;

  else return 0;

}

//----------------------------------------------------------

 

Здесь всё просто. Нам достаточно проверить любой из 5 старших битов. проверим 11-й. Тем более уж его-то проверять мы давно умеем, у нас ведь и ножка порта 11-я (магия, ничего не скажешь). И если он в единице, то её и вернём, иначе вернём ноль.

Также напишем ещё функцию преобразования самого значения температуры в результат с плавающей запятой

 

//----------------------------------------------------------

float ds18b20_Convert(uint16_t dt)

{

  float t;

  t = (float) ((dt&0x07FF)>>4); //отборосим знаковые и дробные биты

  //Прибавим дробную часть

  t += (float)(dt&0x000F) / 16.0f;

  return t;

}

//----------------------------------------------------------

 

Здесь также всё просто. Отключим все знаковые биты маской, сдвинем всю последовательность затем на 4 пункта вправо, отбробсив дробную часть, затем значение 4 младших бит разделим на 16 и прибавим к результату. Вот и всё. Очень даже легко.

Добавим на наши функции прототипы в заголовочный файл и перейдём в бесконечный цикл функции main().

Удалим там код перевода каретки и переноса строки и добавим вместо него следующий код

 

HAL_UART_Transmit(&huart1,(uint8_t*)str1,strlen(str1),0x1000);

raw_temper = ((uint16_t)dt[1]<<8)|dt[0];

if(ds18b20_GetSign(raw_temper)) c='-';

else c='+';

temper = ds18b20_Convert(raw_temper);

sprintf(str1,"Raw t: 0x%04X; t: %c%.2frn", raw_temper, c, temper);

HAL_UART_Transmit(&huart1,(uint8_t*)str1,strlen(str1),0x1000);

HAL_Delay(150);

 

Соберём код, прошьём контроллер и посмотрим результат в терминальной программе

 

Image30

 

Вот теперь всё понятно!

Я докоснулся до датчика пальцем руки и подержал. Температура начала расти.

Итак. Мы сегодня научились работать с датчиком температуры DS18B20, подключенному по шине 1-WIRE к контроллеру STM32 безо всякой аппаратной поддержки путём дрыганья ножкой порта. Также мы изучили ряд команд этого датчика, смогли считать с него температуру. Пока это единственный датчик на шине, но я надеюсь, что мы справимся и с несколькими. Тем более я уже подготовил такой датчик.

Ждите следующих уроков.

Всем спасибо за внимание!

 

 

Предыдущая часть Программирование МК STM32 Следующий урок

 

Исходный код

 

 

Отладочную плату можно приобрести здесь STM32F103C8T6

Логический анализатор можно приобрести здесь Логический анализатор на 8 каналов

Датчик температуры в экране с проводом можно приобрести здесь DS18B20 в экране с проводом

Переходник USB to TTL можно приобрести здесь ftdi ft232rl

 

 

Смотреть ВИДЕОУРОК (нажмите на картинку)

 

STM Датчик температуры DS18B20

24 комментария на “STM Урок 92. Датчик температуры DS18B20. Часть 3
  1. Александр:

    Добрый день.
    Столкнулся с проблемой — датчик не работает. Точнее не совсем верно. При работе считывается не верно карта памяти — адекватные показания первых 8-ми разрядов (правда они не соответствуют реальным) и соответственно выводит ахинею… Есть такое чувство что вопрос по временным задержкам. Может поможете, или подскажете как решить проблему…
    И еще. В одном из уроков проскочила фраза о возможности использования прерывания от таймера для формирования задержек. Может в одном из следующих уроков поднимется данный вопрос. Буду благодарен, и наверное не только я.
    Спасибо за вашу работу…

    • Александр:

      Купил на Алишке датчик в нержавеющем кожухе с проводом длиной 2м. Слизал ваш алгоритм, настроил временные задержки по осциллографу. Но стабильной работы так и не добился. Показания то четкие, то в какой-то момент сбиваются на больший/меньший показатель. Я так понимаю, идет какой-то сбой на линии между датчим и процессором (STM8S105K4T6C). Укоротил провод до нуля — не помогло.
      Где может быть проблема? На сколько обязательно считывать все 9 байт внутренней памяти и проверять CRC? Сейчас я беру только 2 байта с температурой и все.

  2. Зачем дробную часть делить на 16?

  3. И кстати если тип переменной значения температуры объявить как целое со знаком, то никаких манипуляций с определением знака не потребуется. Единица в старшем разряде для чисел со знаком автоматически считается знаком.

  4. Сергей:

    У меня DS18B20 в стальном, герметичном исполнении. При переходе к отрицательным температурам датчик переходит к инверсным данным. Т.е. если при 0 градусов первые два байта 00 00, то самый маленький минус это FF FE, при этом написанная программа преобразует их в -127 градусов. Если при проверке знака температуры, ещё и вычитать первые два байта при отрицательной температуре из FF FF, то температура преобразуется правильно.

  5. Сергей:

    неправильно написал не FF FE а FE FF, сперва младший байт, затем старший.

  6. dzanis:

    Не правельно конвертирует температуру ниже нуля
    Нашёл вот такой вариант, работает
    float ds18b20_Convert(uint16_t dt)
    {
    return dt * 0.0625;
    }

    • Игорь:

      Сделал так же, иначе при температуре ниже 16 градусов шли неверные показания. Да и когда-то писал для тини13 код, тоже сделал так же, как-то нагляднее, считаю.

      Еще в таймере отсчета интервалов (DelayMicro) поставил делитель на 8 вместо 9, иначе Master Reset Pulse получался около 410 мкс, вместо рекомендованных >480 мкс.

  7. Joe:

    Можете ли вы помочь с примером функции port_init для stm32f0 ?
    void port_init(void)
    {
    HAL_GPIO_DeInit(GPIOB, GPIO_PIN_11);
    GPIOB->CRH |= GPIO_CRH_MODE11;
    GPIOB->CRH |= GPIO_CRH_CNF11_0;
    GPIOB->CRH &= ~GPIO_CRH_CNF11_1;
    }

    • Serg:

      {
      HAL_GPIO_DeInit(GPIOC, DS18b_20_1_Pin); // сбрасываем настройки DS18b_20_1
      // настраиваем заново
      GPIOC->OSPEEDR |= GPIO_OSPEEDR_OSPEEDR0; // режим на выход с Max скоростью (бит — 11)
      GPIOC->OTYPER |= GPIO_OTYPER_OT_0; // режиму на выход open-drain (бит — 1)

  8. _12345_:

    Добрый день.
    Пробую
    port_init();
    status = ds18b20_init(NO_SKIP_ROM);
    Затем из Dev_ID и Dev_Cnt смотрю что считалось.
    Если датчик с номером 28 22 5d 5f 06 00 00 2c или 28 38 e3 5f 06 00 00 55, то в массиве он один и Dev_Cnt тоже один. Если 28 a3 7a 46 92 13 02 40, то он дублируется 8 раз, т.е. Dev_ID записан 8 раз, Dev_Cnt тоже 8. Датчик на линии один. В чём может быть проблема? Датчики исправны AVR их читает без проблем номера совпадают.

    • Serge:

      Пробовал подключить 8 датчиков. Корректно определяется два или три. Если подключаю по одному — работает нормально, определятся каждый. Пришлось по каждому определить ID, сделать массив и уже явно подставлять ID в цикле чтения. Второй косяк — периодически проскакивают ошибки в результате измерения и знаке температуры. Думаю добавить проверку CRC, чтобы исключать ошибки.

  9. _12345_:

    Так и не вышло у меня заставить правильно работать эту функцию. Из краткого изучения алгоритма работы подобных функций, пришел к выводу, что как правило, все они работают в несколько проходов, причём в каждом последующем проходе, они учитывают то по какому пути шли в предыдущем случае. Здесь же я этого не заметил, может плохо смотрел. Тем не менее, слегка доработанный пример из другого места заработал практически сразу и корректно. Для себя сделал вывод, что для урока выбран не самый удачный пример.
    На мой взгляд, для формирования задержек лучше использовать DWT. Судя по логическому анализатору, предложенная здесь функция, вместо 480 мк, формирует что-то в районе 580, точно не помню. Понятно, что это нельзя считать за абсолютную истину, но при использовании DWT получается 485, а это гораздо лучше, при этом же методе измерения. Функции ds18b20_ReadByte, ds18b20_WriteBit и другие, непосредственно работающие с шиной, по большому счёту не имеют к DS18B20 никакого отношения. Лучше их было бы назвать 1wire_ReadByte и т.д. Впоследствии их можно использовать для обращения к другим микросхемам по 1wire, например, DS1990, так будет более логично. Ну и в начале этих функций желательно запрещать прерывания, при выходе разрешать, этот пример то работать будет, а вот в дальнейшем, в более сложных программах, можно получить массу глюков.

  10. _12345_:

    И ещё один момент. Уж очень некрасиво задержка на 800мс. Тормозить все будет по-чёрному, ни нормальный обработчик кнопок сделать, ни что-либо ещё. Надо или по таймеру, а если совсем просто, можно считать состояние шины с помощью (спросить у датчика готовы ли показания) ds18b20_ReadByte или ds18b20_ReadBit, и если эти функции вернут соответственно 255 и 1, то можно читать. Правда, в этом случае, будут бесконечные обращения к шине, но для изучения работы вполне нормально.

  11. andrejs:

    да. ниже нуля неправильно показывал.оказалось если значение со знаком «-» то оно и в доп. коде. ))) нужно инвертировать и прибавить еденицу.

  12. александр:

    sprintf не выводит дробную (float temper) переменную на ПК.
    в str32cubeide for win10

    решил упростить вывод на ПК. в крайнем случае можно потом поделить на сто….
    //———————————————————-
    uint16_t ds18b20_int_Convert(uint16_t dt)
    {
    uint16_t t1,t2;
    t1 = ((dt&0x07FF)>>4); //отбросим знаковые и дробные биты
    t1 = t1*100;
    //Прибавим дробную часть
    t2 = (dt&0x000F)*25/4;

    return t1+t2;
    }
    //———————————————————-

    • ilya_gabdeev:

      в настройках линкера в озерфлаг надо прописать -u _printf_float
      «The float formatting support is not enabled, check your
      MCU Settings from «Project Properties > C/C++ Build > Settings > Tool
      Settings», or add manually «-u _printf_float» in linker flags.»

  13. ilya_gabdeev:

    а так все отлично. спасибо за урок. когда-то уже писал сам для пиков на ихнем асме, в Си только начинающий. все заработало, единственное сделал полность на HAL-е, ну их эти регистры, сыт ими и без надобности не полезу туда.

  14. Валерий:

    Для нормального отображения отрицательных температур добавьте строчку в функцию float ds18b20_Convert(uint16_t dt)
    t = (float) ((dt&0x07FF)>>4);
    if(dt>0x800) t=(127-t)*-1;

  15. Валерий:

    Переделанная версия ds18b20_Convert для работы с отрицательными температурами и выводом температуры в двух байтовый массив для дальнейшего хранения в архиве на микросхеме памяти (UINT8_t tem[0] байт целой температуры (+127 -127) и tem[1] для дробной части температуры.

    float ds18b20_Convert(uint16_t dt, int8_t *arx)
    {
    float t;
    uint16_t at, att;

    if(dt>0x800) {
    at=~dt&0x07FF;
    att=~dt&0x000F;
    t = (float) (((at)+1)>>4)*-1;
    t -= (float)((att)+1) / 16.0f;
    arx[0]=(uint8_t) (((at)+1)>>4)*-1;
    arx[1]=(uint8_t)(((att)+1)*100 / 16);
    }
    else {
    at=dt&0x07FF;
    att=dt&0x000F;
    t = (float) ((at)>>4); //отборосим знаковые и дробные биты
    t += (float)(att) / 16.0f; //ѕрибавим дробную часть
    arx[0]=(uint8_t) ((at)>>4);//отборосим знаковые и дробные биты
    arx[1]=(uint8_t)((att)*100 / 16); //ѕрибавим дробную часть
    }

    return t;
    }

  16. Валерий:

    Для вывода информации с отр. темп. для двух датчиков
    float temper[];
    int8_t ds_num_data[4]={0,};

    sprintf(str,»t1=%.2f t2=%.2f\r\n t1=%d,%02d t2=%d,%02d\r\n», temper[0], temper[1],ds_num_data[0],ds_num_data[1],ds_num_data[2],ds_num_data[3]);
    Писал для GSM управления теплицей на SIM 800 два датчика температуры программная регулировка и управление открытия проветривания. Вывод информации на LCD.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*