Продолжаем работать с модулем RMT и в прошлом занятии мы произвели инициализацию шины 1-Wire для работы с датчиком измерения температуры DS18B20.
На данном уроке мы попробуем использовать наш датчик по назначению, то есть для измерения температуры.
Лишний раз повторю, что с использованием других контроллеров мы всё это неоднократно проделывали, причём с STM32 мы в уроке 92 мы использовали один датчик DS18B20 на шине, а в уроке 94 у нас их было несколько. Поэтому, думаю, что с данной задачей мы легко справимся и с использование контроллера ESP32.
Схема урока пока будет такая же, как и в прошлом уроке — с одним датчиком, остальные мы также подключим чуть позже
Проект был сделан на основе проекта прошлого урока с именем RMT_DS18B20_INIT и получил новое имя RMT_DS18B20.
Откроем наш проект в Espressif IDE и в функции app_main файла main.c объявим массив для подсчёта количества ошибок, если таковые случатся, для каждого устройства, а также переменную для подсчёта количества измерений
1 2 3 4 |
owb_use_parasitic_power(owb, parasitic_power); int errors_count[MAX_DEVICES] = {0}; int sample_count = 0; |
Измерять мы будем один раз в секунду, поэтому объявим макрос с количеством милисекунд между измерениями
1 2 |
#define DS18B20_RESOLUTION (DS18B20_RESOLUTION_12_BIT) #define SAMPLE_PERIOD (1000) // milliseconds |
Затем организуем бесконечный цикл для измерений с использованием продвинутой задержки, которую мы изучали, когда работали над изучением ОС FreeRTOS с контроллером STM32
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
int sample_count = 0; if (num_devices > 0) { TickType_t last_wake_time = xTaskGetTickCount(); while (1) { vTaskDelayUntil(&last_wake_time, SAMPLE_PERIOD / portTICK_PERIOD_MS); } } else { printf("\nNo DS18B20 devices detected!\n"); } |
В файле DS18B20.c объявим макрос команды запуска конвертирования (аналого-цифрового преобразования) температуры датчиком
1 2 3 |
static const char *TAG = "ds18b20.c"; //--------------------------------------------------------------------- #define DS18B20_FUNCTION_TEMP_CONVERT 0x44 ///< Initiate a single temperature conversion |
Выше функции ds18b20_check_for_parasite_power добавим функцию, которая будет запускать конвертирование температуры
1 2 3 4 5 |
//--------------------------------------------------------------------- void ds18b20_convert_all(const OneWireBus * bus) { } //--------------------------------------------------------------------- |
На данную функцию объявим прототип в заголовочном файле и вызовем её в функции app_main файла main.c
1 2 3 4 |
TickType_t last_wake_time = xTaskGetTickCount(); while (1) { ds18b20_convert_all(owb); |
Перейдём в файл owb.c и выше функции owb_rmt_initialize добавим функцию, которая настроит жёстко ножку порта на притягивание к питанию
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
//--------------------------------------------------------------------- owb_status owb_set_strong_pullup(const OneWireBus * bus, bool enable) { owb_status status = OWB_STATUS_NOT_SET; if (!bus) { status = OWB_STATUS_PARAMETER_NULL; } else if (!_is_init(bus)) { status = OWB_STATUS_NOT_INITIALIZED; } else { if (bus->use_parasitic_power && bus->strong_pullup_gpio != GPIO_NUM_NC) { gpio_set_level(bus->strong_pullup_gpio, enable ? 1 : 0); ESP_LOGD(TAG, "strong pullup GPIO %d", enable); } // else ignore status = OWB_STATUS_OK; } return status; } //--------------------------------------------------------------------- |
Объявим прототип на данную функцию в заголовочном файле, вернёмся в файл ds18b20.c в функцию ds18b20_convert_all и в теле данной функции отправим в шину команду на конвертацию температуры и, используя функцию, которую мы только что добавили, притянем к питанию ножку
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
void ds18b20_convert_all(const OneWireBus * bus) { if (bus) { bool is_present = false; owb_reset(bus, &is_present); owb_write_byte(bus, OWB_ROM_SKIP); owb_write_byte(bus, DS18B20_FUNCTION_TEMP_CONVERT); owb_set_strong_pullup(bus, true); } else { ESP_LOGE(TAG, "bus is NULL"); } |
Далее нам надо как-то дождаться окончания конверсии температуры.
Добавим ниже функцию для этого
1 2 3 4 5 6 7 |
//--------------------------------------------------------------------- float ds18b20_wait_for_conversion(const DS18B20_Info * ds18b20_info) { float elapsed_time = 0.0f; return elapsed_time; } //--------------------------------------------------------------------- |
Конечно же, это пока только заготовка функции.
Объявим для данной функции прототип в заголовочном файле и вызовем её в файле main.c в функции app_main
1 2 |
ds18b20_convert_all(owb); ds18b20_wait_for_conversion(devices[0]); |
Вернёмся в файл ds18b20.c и объявим макрос времени, которое мы будем ждать до считывания температуры из скратчпада, в милисекундах. Данное время мы будем ожидать, если до этого обнаружились устройства на шине с паразитным питанием
1 2 3 4 |
#define DS18B20_FUNCTION_POWER_SUPPLY_READ 0xB4 ///< Determine if a device is using parasitic power //--------------------------------------------------------------------- static const int T_CONV = 750; // maximum conversion time at 12-bit resolution in milliseconds //--------------------------------------------------------------------- |
Выше функции _read_scratchpad добавим функцию для ожидания данного времени
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
//--------------------------------------------------------------------- static float _wait_for_duration(DS18B20_RESOLUTION resolution) { int64_t start_time = esp_timer_get_time(); if (_check_resolution(resolution)) { int divisor = 1 << (DS18B20_RESOLUTION_12_BIT - resolution); ESP_LOGD(TAG, "divisor %d", divisor); float max_conversion_time = (float)T_CONV / (float)divisor; int ticks = ceil(max_conversion_time / portTICK_PERIOD_MS); ESP_LOGD(TAG, "wait for conversion: %.3f ms, %d ticks", max_conversion_time, ticks); // wait at least this maximum conversion time vTaskDelay(ticks); } int64_t end_time = esp_timer_get_time(); return (float)(end_time - start_time) / 1000000.0f; } //--------------------------------------------------------------------- |
В функции ds18b20_wait_for_conversion в случае наличия на шине устройств с паразитным питанием вызовем данную функцию и заранее заготовим блок else для случая, когда таких устройств на шине не обнаружено
1 2 3 4 5 6 7 8 9 10 11 12 |
float elapsed_time = 0.0f; if (_is_init_ds(ds18b20_info)) { if (ds18b20_info->bus->use_parasitic_power) { elapsed_time = _wait_for_duration(ds18b20_info->resolution); } else { } } |
Выше функции _read_scratchpad добавим функцию, которая будет ждать результата конвертирования температуры от датчика, если устройств с паразитным питанием не обнаружено
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
//--------------------------------------------------------------------- static float _wait_for_device_signal(const DS18B20_Info * ds18b20_info) { float elapsed_time = 0.0f; if (_check_resolution(ds18b20_info->resolution)) { int divisor = 1 << (DS18B20_RESOLUTION_12_BIT - ds18b20_info->resolution); // allow for 10% overtime float max_conversion_time = (float)T_CONV / (float)divisor * 1.1; int max_conversion_ticks = ceil(max_conversion_time / portTICK_PERIOD_MS); ESP_LOGD(TAG, "wait for conversion: max %.0f ms, %d ticks", max_conversion_time, max_conversion_ticks); // wait for conversion to complete - all devices will pull bus low once complete TickType_t start_ticks = xTaskGetTickCount(); TickType_t duration_ticks = 0; uint8_t status = 0; do { vTaskDelay(1); owb_read_bit(ds18b20_info->bus, &status); duration_ticks = xTaskGetTickCount() - start_ticks; } while (status == 0 && duration_ticks < max_conversion_ticks); elapsed_time = duration_ticks * portTICK_PERIOD_MS; if (duration_ticks >= max_conversion_ticks) { ESP_LOGW(TAG, "conversion timed out"); } else { ESP_LOGD(TAG, "conversion took at most %.0f ms", elapsed_time); } } return elapsed_time; } //--------------------------------------------------------------------- |
Данная функция, хоть и выглядит громоздко, сложности никакой не представляет. Здесь мы в цикле считаем импульсы от датчика.
В функции ds18b20_wait_for_conversion в блоке противного случая вызовем нашу функцию
1 2 3 4 5 |
elapsed_time = _wait_for_duration(ds18b20_info->resolution); } else { elapsed_time = _wait_for_device_signal(ds18b20_info); |
Далее нам нужна будет функция считывания значения температуры. Для начала выше функции _read_scratchpad добавим функцию, которая будет конвертировать температуру из целого числа в удобочитаемое с плавающей точкой в градусах
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
//--------------------------------------------------------------------- static float _decode_temp(uint8_t lsb, uint8_t msb, DS18B20_RESOLUTION resolution) { float result = 0.0f; if (_check_resolution(resolution)) { // masks to remove undefined bits from result static const uint8_t lsb_mask[4] = { ~0x07, ~0x03, ~0x01, ~0x00 }; uint8_t lsb_masked = lsb_mask[resolution - DS18B20_RESOLUTION_9_BIT] & lsb; int16_t raw = (msb << 8) | lsb_masked; result = raw / 16.0f; } else { ESP_LOGE(TAG, "Unsupported resolution %d", resolution); } return result; } //--------------------------------------------------------------------- |
Выше функции ds18b20_check_for_parasite_power добавим функцию считывания температуры с датчика и преобразования результата в значение в градусах
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
//--------------------------------------------------------------------- DS18B20_ERROR ds18b20_read_temp(const DS18B20_Info * ds18b20_info, float * value) { DS18B20_ERROR err = DS18B20_ERROR_UNKNOWN; if (_is_init_ds(ds18b20_info)) { uint8_t temp_LSB = 0x00; uint8_t temp_MSB = 0x80; Scratchpad scratchpad = {0}; if ((err = _read_scratchpad(ds18b20_info, &scratchpad, 2)) == DS18B20_OK) { temp_LSB = scratchpad.temperature[0]; temp_MSB = scratchpad.temperature[1]; } // https://github.com/cpetrich/counterfeit_DS18B20#solution-to-the-85-c-problem if (scratchpad.reserved[1] == 0x0c && temp_MSB == 0x05 && temp_LSB == 0x50) { ESP_LOGE(TAG, "Read power-on value (85.0)"); err = DS18B20_ERROR_DEVICE; } float temp = _decode_temp(temp_LSB, temp_MSB, ds18b20_info->resolution); ESP_LOGD(TAG, "temp_LSB 0x%02x, temp_MSB 0x%02x, temp %f", temp_LSB, temp_MSB, temp); if (value) { *value = temp; } } return err; } //--------------------------------------------------------------------- |
Здесь происходит чтение скратчпада с последующим сбором в одну переменную младшего и старшего байта значения температуры. Затем при помощи функции преобразования в удобочитаемый вид значение превращается в значение в градусах и записывается по соответствующему адресу во входном параметре.
Объявим на данную функцию прототип в заголовочном файле и затем в функции app_main файла main.c мы вызовем данную функцию для каждого устройства
1 2 3 4 5 6 7 8 9 |
ds18b20_wait_for_conversion(devices[0]); float readings[MAX_DEVICES] = { 0 }; DS18B20_ERROR errors[MAX_DEVICES] = { 0 }; for (int i = 0; i < num_devices; ++i) { errors[i] = ds18b20_read_temp(devices[i], &readings[i]); } printf("\nTemperature readings (degrees C): sample %d\n", ++sample_count); |
Посчитаем ошибки, если таковые будут иметь место
1 2 3 4 5 6 7 8 9 |
printf("\nTemperature readings (degrees C): sample %d\n", ++sample_count); for (int i = 0; i < num_devices; ++i) { if (errors[i] != DS18B20_OK) { ++errors_count[i]; } printf(" %d: %.1f %d errors\n", i, readings[i], errors_count[i]); } |
У нас также может произойти ситуация, когда мы вообще не обнаружим устройств на шине. Соответственно, в данном случае мы не попадём в бесконечный цикл и провалимся вниз.
Давайте проработаем данный момент.
Перейдём в файл ds18b20.c и выше функции ds18b20_init добавим функцию освобождения памяти, запрошенного под переменную типа структуры со свойствами датчика
1 2 3 4 5 6 7 8 9 10 11 |
/--------------------------------------------------------------------- void ds18b20_free(DS18B20_Info ** ds18b20_info) { if (ds18b20_info != NULL && (*ds18b20_info != NULL)) { ESP_LOGD(TAG, "free %p", *ds18b20_info); free(*ds18b20_info); *ds18b20_info = NULL; } } //--------------------------------------------------------------------- |
Объявим на данную функцию прототип в заголовочном файле и вызовем её в функции app_main файла main.c для каждого датчика
1 2 3 4 5 6 |
printf("\nNo DS18B20 devices detected!\n"); } for (int i = 0; i < num_devices; ++i) { ds18b20_free(&devices[i]); } |
Вообще-то данный код добавлен только из эстетических соображений. По идее, у нас цикл будет нулевым, так как устройств на шине 0.
Проделаем ещё кое какие действия.
Для этого в файле owb.c в самом низу добавим функцию деинициализации шины
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
//--------------------------------------------------------------------- owb_status owb_uninitialize(OneWireBus * bus) { owb_status status = OWB_STATUS_NOT_SET; if (!_is_init(bus)) { status = OWB_STATUS_NOT_INITIALIZED; } else { bus->driver->uninitialize(bus); status = OWB_STATUS_OK; } return status; } //--------------------------------------------------------------------- |
Объявим на данную функцию прототип, вызовем её в функции app_main файла main.c и через секунду перезагрузим контроллер
1 2 3 4 5 6 7 |
ds18b20_free(&devices[i]); } owb_uninitialize(owb); printf("Restarting now.\n"); fflush(stdout); vTaskDelay(1000 / portTICK_PERIOD_MS); esp_restart(); |
Соберём код, прошьём контроллер и посмотрим результат в терминале
Отлично. Попробуем немного нагреть датчик рукой
Значение температуры возрастёт
Подключим остальные два датчика
Перезагрузим контроллер и посмотрим результат в терминале
Попробуем также нагреть рукой датчики
Посмотрим изменения показаний температуры
Показания увеличились.
Попробуем извлечь все датчики из схемы
Перезагрузим контроллер и посмотрим результат в терминале
Мы будем получать ошибки и перезагружаться, то есть нижний код также у нас работает.
Итак, на данном уроке нам удалось считать показания температуры с одного и нескольких датчиков DS18B20 с использование модуля RMT контроллера ESP32.
Всем спасибо за внимание!
Предыдущий урок Программирование МК ESP32 Следующий урок
Недорогие отладочные платы ESP32 можно купить здесь:
На AliExpress Недорогие отладочные платы ESP32
На Яндекс.Маркет Недорогие отладочные платы ESP32
Недорогие отладочные платы ESP32-S3 можно купить здесь:
Недорогие отладочные платы ESP32-S3
Датчик температуры в экране с проводом можно приобрести здесь:
На AliExpress DS18B20 в экране с проводом
На Яндекс.Маркет DS18B20 в экране с проводом
Логический анализатор 16 каналов можно приобрести (AliExpress) здесь
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в Дзен (нажмите на картинку)
Добавить комментарий