Урок 92
Часть 1
Датчик температуры DS18B20
Сегодня мы рассмотрим работу с датчиком температуры DS18B20, который не смотря на свою кажущуюся на первый взгляд простоту, обрёл очень широкое расспространение благодаря своим характеристикам. К положительным характеристикам данного датчика следует отнести его низкую энергопотребляемость, простоту конструкции благодаря наличию в своем корпусе TO-92 всего трёх ножек, ну и также недорогую стоимость, что также является немаловажным условием его широкого распространения.
С данным датчиком мы уже работали, подключая его к микроконтроллеру AVR, в уроке 20.
Затем посыпался шквал комментариев и просьб в группах и в личке с желанием увидеть урок с тонкостями подключения и программирования данного датчика в связке с контроллером STM32. Я долгое время не решался на этот шаг. Но в преддверии работы с технологиями беспроводной передачи данных более интересного решения для того, какие именно данные посредством данных технологий передавать, то есть какую именно полезную нагрузку, я не нашёл. Данная нагрузка полезна в буквальном смысле. Так как зачастую встают задачи собрать какой нибудь прибор, который сможет измерять температуру (а также влажность, давление и т.д.) и передать это на какой-то общий сервер. Конечно, существует немало таких устройств. Но есть очень много желающих вникнуть в суть этого процесса, а также организовать данный процесс по своему усмотрению. Поэтому приходится прибегнуть к программированию таких устройств.
Для этого и приходит к нам на помощь данный датчик и контроллер.
Данный датчик подключается посредством шины 1-WIRE, то есть передача данных осуществляется и в ту и в другую сторону всего по одному проводу, что конечно вносит в процесс написания кода определённые трудности. Чем меньше проводов, тем сложнее организовать передачу данных.
Мало того, на один провод можно повесить несколько таких устройств, причём не обязательно одинаковых, причём по этому поводу было ещё больше просьб написания мною урока.
И спешу похвалиться, что данный процесс я также освоил и обязательно поделюсь с вами своими наработками в одном из следующих занятий.
Теперь немного про датчик поподробнее.
Некоторые характеристики датчика:
Диапазон измерений — от -55 0C до +125 0C, но наивысшая точность показаний достигается в диапазоне от -10 0C до +85 0C.
Погрешность данного датчика — 0,5 градуса.
Дискретность измерения — от 9 до 12 бит данных.
Зависящая от установленной дискретности (разрешения) длительность измерения температуры — от 93,75 милисекунд до 750 милисекунд.
Уникальный 64-битный последовательный код, благодаря которому и можно использовать на одной шине несколько таких датчиков.
Подключать мы данный датчик будем к микроконтроллеру STM32F103C8T6, расположенному на недорогой отладочной плате, с которой мы уже немало последнее время поработали.
У данного контроллера нет аппаратной поддержки шины 1-wire, что делает наше занятие ещё более интересным, так как были вопросы, каким образом можно данный процесс организовать если нет аппаратной поддержки и ножки портов с использованием функций библиотеки HAL не так быстро реагируют на команды, как у МК AVR. Вот эту проблему нам также предстоит решить, и заодно и научиться пользоваться оперативным переключением ножек портов (ногодрыгом).
Данный датчик существует в двух видах корпуса — DIP и TO-92. Мы будем использовать второй тип.
Так как мы будем использовать только один датчик, то мы будем к нему обращаться другим способом, не используя ROM.
Мы подаём на датчик команду 44h последовательным кодом, тем самым заставляя датчик конвертировать температуру.
Посмотрим регистры, в которых хранится значение температуры после преобразования
В четырех младших битах младшего регистра хранятся доли градусов, в четырех старших, а также в трех младших битах старшего регистра — целые значения градусов.
В остальных битах — знак. Если 0, то плюс, если 1 — то минус.
Также в технической документации написано, что нужно обязательно подтянуть резистор на информационную ножку датчика к питанию. Рекомендуемый номинал — 4,7 килоом.
Также написан ряд временных и других характеристик, которые мы уже будем рассматривать по мере того, как будем писать код.
Вообщем схема у нас будет следующая. Поместим датчик в макетную плату. Между информационным выводом датчика и выводом питания подключим резистор на 4,7 килоома. Питание на выводы питания подадим 3,3 вольта с отладочной платы, а информационный вывод датчика подключим к ножке PB11 микроконтроллера. Также подключим модуль USART-USB, не подводя к нему плюсовой электрод питания и также подключим недорогой программатор ST-Link V2, с него и подадим питание на контроллер. Никаких отдельных источников питания мы использовать не будем, так как датчик почти ничего не потребляет в отличие, например от модуля LAN ENC28J60. Также к информационному выводу датчика подключим логический анализатор для исследования поведения данного вывода.
Получится вот такая сборка (нажмите на картинку для увеличения изображения)
Создадим новый проект в Cube MX, выбрав контроллер STM32F103C8Tx.
Настроим тактирование от кварцевого резонатора
Включим отладчик
Настроим USART
Включим ножку порта PC13, отвечающую за светодиод, на выход
Также включим на вход ножку PB11, к которой мы будем подсоединять информационный контакт датчика
В принципе, эту ножку можно было и не включать, все равно мы в проекте будем её инициализировать по-своему. Но всё же включим, чтобы о ней впоследствии не забыть и не захотеть её использовать ещё для чего-нибудь.
Перейдём в раздел Clock Configuration и настроим там частоты и переключатели следующим образом (нажмите на картинку для увеличения изображения)
Теперь перейдём в раздел Configuration, USART трогать не будем, оставим настройки по умолчанию (скорость 115200 кбпс), а вот ножкe порта PC13 настроим чуть побыстрее
Теперь зайдём в настройки проекта, выберем там в качестве среды программирования Keil 5, а также присвоим имя проекту по наименованию датчика
Применим настройки, соберём проект, откроем его в Keil, настроим программантор на авторезет, а также уровень оптимизации убавим до уровня 1.
Попробуем собрать проект.
Создадим два файла ds18b20.h и ds18b20.c следующего содержания
ds18b20.h:
#ifndef DS18B20_H_
#define DS18B20_H_
//--------------------------------------------------
#include "stm32f1xx_hal.h"
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
//--------------------------------------------------
#endif /* DS18B20_H_ */
ds18b20.c:
#include "ds18b20.h"
//--------------------------------------------------
Подключим нашу будущую библиотеку для датчика в файле main.h
/* USER CODE BEGIN Includes */
#include "ds18b20.h"
/* USER CODE END Includes */
В файле ds18b20.c добавим функцию задержки в микросекундах
#include "ds18b20.h"
//--------------------------------------------------
__STATIC_INLINE void DelayMicro(__IO uint32_t micros)
{
micros *= (SystemCoreClock / 1000000) / 9;
/* Wait till done */
while (micros--) ;
}
//--------------------------------------------------
Делитель 9 был выбран эксперементальным путём с помощью логического анализатора.
Теперь нам надо как-то научиться управлять ножкой PB11, создавая на ней при необходимости высокие и низкие состояния.
Но прежде чем этим заниматься, нам нужно эту ножку как следует настроить и инициализировать.
Добавим для этого функцию
//--------------------------------------------------
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;
}
//--------------------------------------------------
Давно мы не работали с библиотекой CMSIS, но нас это не пугает. Она чем-то похожа на стандартную библиотеку по работе с портами контроллеров AVR.
Сначала мы отменим уже существующую инициализацию, затем настроим конфигурационный регистр порта CRH. Вернее это не регистр, а только старшая половинка одного большого регистра CR. Младшая половина регистра служит для настройки ножек портов от 0 до 7, а старшая — от 8 до 15. Так как у нас ножка 11, то нам, соответственно, требуется CRH.
Вот как выглядит данный регистр в документации к контроллеру (Reference Manual)
Сначала инициализируем битовую пару MODE11 маской, вклюив высокий уроень в обоих битах, что будет соответствовать режиму на выход со скоростью 50 мегагерц
Затем в битовую пару CNF заносим [01], что будет соответствовать открытому коллектору в режиме выхода
Добавим на данную функцию прототип и вызовем её в main()
/* USER CODE BEGIN 2 */
port_init();
/* USER CODE END 2 */
Порт проинициализировали, теперь будем писать инициализацию датчика.
Данный процесс также описан в документации
Причём подобная процедура должна будет происходить не только при первоначальном включении датчика, а почти постоянно, при начале определённых действий, например запуска измерения температуры. Поэтому код для неё мы будем писать в следующей отдельной функции, котороую мы сейчас и добавим, вернувшись предварительно в файл ds18b20.c
//--------------------------------------------------
uint8_t ds18b20_Reset(void)
{
uint16_t status;
}
//----------------------------------------------------------
Судя по документации, видно что мы здесь притягиваем шину, дежим её в таком состоянии как минимум 480 микросекунд, затем отпускаем её, ждём максимум 60 мкс (мы подождём побольше — 65 мкс) ответа датчика, датчик должен нам ответить таким же притягиванием шины к земле за данное время. Если обнаружится ноль, то значит устройство на шине есть, а если шина так и останется висеть в воздухе, то данного датчика на шине нет.
Ну. и на остове вышепрочитанного продолжим нашу функцию определения наличия датчика на шине.
Выставляем мы определённый уровень на ножке с помощью соответствующего бита в регистре ODR, а проверяем — с помощью битав регистре IDR. Причём следует заметить, что перенастраивать ножку на вход вовсе не обязательно, что значительно сокращает время выполнения кода
uint8_t ds18b20_Reset(void)
{
uint16_t status;
GPIOB->ODR &= ~GPIO_ODR_ODR11;//низкий уровень
DelayMicro(485);//задержка как минимум на 480 микросекунд
GPIOB->ODR |= GPIO_ODR_ODR11;//высокий уровень
DelayMicro(65);//задержка как минимум на 60 микросекунд
status = GPIOB->IDR & GPIO_IDR_IDR11;//проверяем уровень
DelayMicro(500);//задержка как минимум на 480 микросекунд
//(на всякий случай подождём побольше, так как могут быть неточности в задержке)
return (status ? 1 : 0);//вернём результат
}
Прежде чем перейти к процедуре инициализации, нужно научиться отправлять данные в датчик, ну заодно, хоть это в инициализации не потребуется, и читать данные из датчика.
Посмотрим, как этот процесс должен происходить
А функции чтения и записи битов, а также и байтов мы будем уже писать в следующей части нашего занятия, в которой мы также познакомимся с организацией памяти датчика.
Предыдущий урок Программирование МК STM32 Следующая часть
Отладочную плату STM32F103C8T6 можно приобрести здесь STM32F103C8T6
Программатор недорогой можно купить здесь ST-Link V2
Логический анализатор 16 каналов можно приобрести здесь
Датчик температуры DS18B20 в экране с проводом можно приобрести здесь DS18B20
Переходник USB to TTL можно приобрести здесь ftdi ft232rl
Смотреть ВИДЕОУРОК (нажмите на картинку)
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;
}
не работает на stm32f407
На f407 регистры с другим названием, нужно смотреть в reference manual.
Разобрался с регистрами?
Для платы STM32F303VCT6, STMF3-Discovery, отсутствуют регистры CPIOx_CRH, следовательно, у меня такая же проблема как у streloc. Подскажите, пожалуйста, как быть?
А там в одном регистре все ноги умещаются. Для modeб speed и type предусмотрены отдельные регистры регистры.
Здесь неболшая ошибка в коде.
Посмотрите внимательно на этот код:
status = GPIOB->IDR & GPIO_IDR_IDR11;
Мы хотим узнать, равно ли значение бита GPIO_IDR_IDR11 единице или нет.
По факту, если это одиннадцатый бит мы получаем не единицу, а 0b1000000000;
Если попытаться затолкать это значение в uint8_t, оно обрежется и останется 0 в итоге.
Таким образом, status всегда равен нулю. Дальше в коде у вас эта ошибка исправлена
status = (GPIOB->IDR & GPIO_IDR_IDR11 ? 1 : 0);
Не понимаю в чем проблема, уже несколько раз делаю по шагу все операции сверяясь с reference manual своего контроллера stm32f303vc. Выводит постоянно неразборчивый мусор, снимал информационный вывод, код мусора тот же самый. Посмотрите, пожалуйста, правильная ли инициализация.
//—————————————————
void port_init(void)
{
HAL_GPIO_DeInit(GPIOB, GPIO_PIN_11);
GPIOB->MODER |= GPIO_MODER_MODER11_0;//01: General purpose output mode
GPIOB->MODER &= ~GPIO_MODER_MODER11_1;
GPIOB->OTYPER |= GPIO_OTYPER_OT_11;//1: Output open-drain
GPIOB->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR11;//11: High speed
}
//—————————————————
И последний вопрос, почему вы инициализировали UART и как определяете baud rate, чтобы пользоваться терминалом?
Так как у Вас другой контроллер, то может быть дело в задержке. Вы используете DelayMicro?
Да, использую, уже пробовал увеличивать задержку, эффекта ноль.
да тут лажа за лажей — где включено тактирование порта? что за чумовая запись —
GPIOB->ODR &= ~GPIO_ODR_ODR11? если можно просто GPIOB->BSRR =GPIO_BSRR_BR11;БЕЗ МОДИФИКАЦИИ ЧТЕНИЕ—ЗАПИСЬ-ЧТЕНИЕ. Народ с ума посходил со всякими Cube HAL..))вот и не знаем элементарных вещей
В stm32f4xx нету BRR BSRR в документации и скорее в проце, а так же некоторые stm32f7 не имеют этих регистров. Так что каждый чип программировать с помощью уникальных фич, можно позволить себе пока вы студент и не сталкиваетесь с поддержкой и 60-70 заказами в год, ну или 40-50 ))
ну не знаю про какой stm32f4x вы говорите вот выдержка из референса
rm0090
8.4.7 GPIO port bit set/reset register (GPIOx_BSRR) (x = A..I/J/K)
Address offset: 0x18
Reset value: 0x0000 0000
три урока прошёл
пока всё ок.
даже четыре
но правда с другими источниками сверялся.
спасибо.
наиболее полные уроки и более менее понятен.
совместим с stm32cubeide
пока у меня с cd card не входит.
status ? 1 : 0 а в каком уроке более детально рассказывалось про эту запись? Как это работает?
Почему перенастраивать ножку на вход вовсе не обязательно, почему скорость пина 1 Wire выбрана 50 мгц и почему выход с открытым коллектором настраивают.