Получил заказанные из Китая STM32F030F4P6 и начал их тестировать. Т. к. мигать светодиодом уже неинтересно, решил сделать что-то более полезное, а именно, считывание температуры с датчика DS18B20.
Схема получилась такой.
Глядя на нее может сложится впечатление что протокол 1Wire реализован программным методом, ведь аппаратного 1Wire модуля в этом МК нет, а для USART нужно два вывода, и диод на TXD. Но это не так. Используется USART, которому в полудуплексном режиме достаточно одного вывода для приема и передачи, а чтобы не было электрического конфликта, вывод работает в режиме открытого стока.
Основной код программы.
#include "stm32f0xx_conf.h" #include "onewire.h" volatile float tt; int main(void) { uint8_t buf[9]; GPIO_InitTypeDef GPIO_InitStruct; SystemCoreClockUpdate(); // Вычисление тактовой частоты ядра. SysTim_Init(100); // Инициализация системного таймера. OW_Init(); // Инициализация 1Wire библиотеки. RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE); // Включаем тактирование порта PB. GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1; // Настройка вывода PB.1. GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT; // Вывод работает как выход. GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; // Двухтактный выход (т. е. не открытый сток). GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL; // Подтягивающие резисторы отключены. GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStruct); while(1) { // Запуск измерения температуры. if (OW_Send(OW_SEND_RESET, "\xCC\x44", 2, 0, 0, OW_NO_READ) == OW_OK) { SysTim_DelayMS(800); // Ждем 800 миллисекунд. // Читаем данные с датчика if (OW_Send(OW_SEND_RESET, "\xCC\xBE\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 11, &buf[0], 9, 2) == OW_OK) { if (OW_CRC8(&buf[0], 8) == buf[8]) { tt = ((float)((buf[1]<<8)|buf[0]))/16.0; // Вычисляем температуру. GPIO_SetBits(GPIOB, GPIO_Pin_1); // Лог. 1 на выходе PB.1. SysTim_DelayMS(100); // Ждем 100 миллисекунд. GPIO_ResetBits(GPIOB, GPIO_Pin_1); // Лог. 0 на выходе PB.1. } } } } }
Вначале функции main создается массив buf под данные, получаемые из датчика при чтении температуры и экземпляр структуры GPIO_InitTypeDef, необходимый для настройки вывода порта под светодиод, который мигает при успешном получении данных из датчика. Затем вызывается несколько функций инициализации и конфигурации, а после находится цикл while, код которого МК выполняет большую часть времени. В нем, вызов функции OW_Send со вторым аргументом "\xCC\x44" запускает измерение температуры. Далее, после ожидания 800 миллисекунд, необходимых для измерения температуры датчиком, еще раз вызывается функция OW_Send, но на этот раз с командой чтения температуры. Множество xFF это байты отправляемые через USART и необходимые для чтения. Если процесс чтения прошел без ошибок то в массиве buf окажется 9 байт прочитанных с датчика и если контрольная сумма (CRC8) совпадет, то вычисляется температура и записывается в переменную tt, после чего на 100 миллисекунд на выводе PB1 устанавливается логическая единица для мигания светодиода, информирующего что процесс измерения температуры прошел удачно. А затем все начинается сначала (запуск измерения температуры, ее считывание и т. д.).
1Wire-функции находятся в файле onewire.c. Сам я его с нуля не писал, а взял готовый (файлы здесь).
Там версия для STM32F1, а у меня STM32F0 и без доработки не заработало. Как оказалось, несмотря на то что это более младшая и более дешевая модель, периферия у нее с большим функционалом. Например у USART появился выбор источника тактирования (внутренний RC генератор, выход умножителя частоты или тактирование от ядра МК), чего нет в STM32F1 (по крайней мере в STM32F103C8T6).Были и другие нюансы такие как отличающиеся выводы порта на который выведен USART, другие номера каналов DMA и т. д.
Теперь по порядку. Функция инициализации выглядит так.
//----------------------------------------------------------------------------- // инициализирует USART и DMA //----------------------------------------------------------------------------- uint8_t OW_Init() { GPIO_InitTypeDef GPIO_InitStruct; USART_InitTypeDef USART_InitStructure; RCC_USARTCLKConfig(RCC_USART1CLK_SYSCLK); // USART тактируется от ядра МК. RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); // Подаем тактирование на GPIOA, RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); // USART1, RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // и DMA1. // USART TX GPIO_InitStruct.GPIO_Pin = GPIO_Pin_2; // Настройка вывода PA.2. GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF; // Альтернативная функция вывода. GPIO_InitStruct.GPIO_OType = GPIO_OType_OD; // Выход с открытым стоком. GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL; // Подтягивающие резисторы отключены. GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStruct); GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_1); // GPIOA.2 это вывод USART1. // Настройка USART. USART_InitStructure.USART_BaudRate = 115200; // Скорость обмена. USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 8 бит данных. USART_InitStructure.USART_StopBits = USART_StopBits_1; // 1 стоп бит. USART_InitStructure.USART_Parity = USART_Parity_No; // Нет проверки четности. USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; // Настраиваем приемник и передатчик. USART_Init(USART1, &USART_InitStructure); USART_SetAddress(USART1, 0); // Адрес. Без этого не работает полудуплексный режим. USART_HalfDuplexCmd(USART1 , ENABLE); // Разрешение работы USART в полудуплексном режиме. USART_Cmd(USART1, ENABLE); // Включаем USART. return OW_OK; }
В ней выбирается источник тактирования, USART, подается тактирование на модули GPIOA, USART1 и DMA1, а затем настраивается порт PA2 как вывод USART, работающий в режиме с открытым стоком. После настраивается USART1, у которого устанавливается скорость обмена 115200 бод, 8 бит данный, 1 стоп-бит, отключается проверка четности и аппаратного управления потоком. Затем устанавливается адрес, включается полудуплексный режим и запускается USART1. Зачем в данном случае нужно устанавливать адрес, не знаю, т. к. это относится к многопроцессорному обмену и в данном случае не используется, но без этого полудуплексный режим не работает.
Функция приема/передачи 1Wire выглядит таким образом.
//----------------------------------------------------------------------------- // процедура общения с шиной 1-wire // sendReset - посылать RESET в начале общения. // OW_SEND_RESET или OW_NO_RESET // command - массив байт, отсылаемых в шину. Если нужно чтение - отправляем OW_READ_SLOTH // cLen - длина буфера команд, столько байт отошлется в шину // data - если требуется чтение, то ссылка на буфер для чтения // dLen - длина буфера для чтения. Прочитается не более этой длины // readStart - с какого символа передачи начинать чтение (нумеруются с 0) // можно указать OW_NO_READ, тогда можно не задавать data и dLen //----------------------------------------------------------------------------- uint8_t OW_Send(uint8_t sendReset, uint8_t *command, uint8_t cLen, uint8_t *data, uint8_t dLen, uint8_t readStart) { uint32_t times; uint8_t ret; // если требуется сброс - сбрасываем и проверяем на наличие устройств if (sendReset == OW_SEND_RESET) { ret = OW_Reset(); if (ret != OW_OK) { return ret; } } while (cLen > 0) { OW_toBits(*command, ow_buf); command++; cLen--; DMA_InitTypeDef DMA_InitStructure; // DMA на чтение DMA_DeInit(OW_DMA_CH_RX); DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) &(USART1->RDR); // Источник - регистр данных приема USART. DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t) &ow_buf[0]; // Приемник - массив в ОЗУ. DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; // Копирование данных из периферии в память. DMA_InitStructure.DMA_BufferSize = 8; // Размер буфера куда копировать данные. DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // Не увеличивать адрес периферии. DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // Увеличивать адрес памяти (в массив копируем же). DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // Размер данных периферии 1 байт. DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // Размер памяти 1 байт. DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; // Не циклический режим работы. DMA_InitStructure.DMA_Priority = DMA_Priority_Low; // Низкий приоритет. DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(OW_DMA_CH_RX, &DMA_InitStructure); // DMA на запись DMA_DeInit(OW_DMA_CH_TX); DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) &(USART1->TDR); // Источник - регистр данных передачи USART. DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; // Копирование данных из памяти в периферию. DMA_Init(OW_DMA_CH_TX, &DMA_InitStructure); // старт цикла отправки USART_ClearFlag(USART1, USART_FLAG_RXNE | USART_FLAG_TC | USART_FLAG_TXE); // Очистка флагов USATR USART_DMACmd(USART1, USART_DMAReq_Tx | USART_DMAReq_Rx, ENABLE); // Включаем DMA в USART. DMA_Cmd(OW_DMA_CH_RX, ENABLE); // Старт DMA. DMA_Cmd(OW_DMA_CH_TX, ENABLE); times = SysTim_GetTick(); // Текущее время с начала работы МК с миллисекундах. // Ждем, пока не примем 8 байт while (DMA_GetFlagStatus(OW_DMA_FLAG) == RESET) { if (SysTim_GetTick()-times > OW_TimeOut) // Превышен таймаут. { // отключаем DMA DMA_Cmd(OW_DMA_CH_TX, DISABLE); DMA_Cmd(OW_DMA_CH_RX, DISABLE); USART_DMACmd(USART1, USART_DMAReq_Tx | USART_DMAReq_Rx, DISABLE); return OW_Err_TimeOut; } #ifdef OW_GIVE_TICK_RTOS taskYIELD(); #endif } // отключаем DMA DMA_Cmd(OW_DMA_CH_TX, DISABLE); DMA_Cmd(OW_DMA_CH_RX, DISABLE); USART_DMACmd(USART1, USART_DMAReq_Tx | USART_DMAReq_Rx, DISABLE); // если прочитанные данные кому-то нужны - выкинем их в буфер if (readStart == 0 && dLen > 0) { *data = OW_toByte(ow_buf); data++; dLen--; } else { if (readStart != OW_NO_READ) { readStart--; } } } return OW_OK; }
В ней при необходимости вызывается функция OW_Reset() выполняющая сброс датчика, а затем в цикле передаются и принимаются данные. Для передачи одного байта по 1Wire нужно отправить 8 байт через USART. Прием и передача этих 8 байт осуществляется посредством модуля DMA (по русски это ПДП - прямой доступ к памяти, в данном случае, копирование данных из периферии в память и обратно без участия процессора).
Итак, давайте протестируем работу устройства. Для этого нужно скачать файлы и открыть проект (файл с расширением ebp) в программе EmBitz.
Для наблюдения температуры понадобится отладчик ST-Link или подобный, т. к. другого метода это сделать в данном коде не предусмотрено.
Подключаем отладчик в устройству и компьютеру и запускаем в программе EmBitz процесс отладки кликнув в меню "Отладка" по пункту "Start/Stop Dedug Session" или по аналогичному значку на панели инструментов.
Программа будет остановлена отладчиком. Ее нужно запустить кликнув в меню "Отладка" по пункту "Запустить" или нажав F5 на клавиатуре. При этом должен начать мигать светодиод примерно раз в секунду.
Если при этом не будет открыто окно "Watches", то открываем его из меню "Отладка" --> "Окна отладчика" --> "Наблюдать".
После находим к файле main.c переменную tt и кликаем по ней правой кнопкой мышки и в меню выбираем "Add wath 'tt'". В окне "Добавить наблюдение" ничего не меняем и нажимаем на ОК. Переменная добавится в окно "Watches". Чтобы ее значение регулярно обновлялось кликаем в окне "Watches" по переменной и в меню выбираем "Live updates".
Теперь греем и охлаждаем датчик и в реальном времени наблюдаем как меняется температура.