Получил заказанные из Китая 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".

Теперь греем и охлаждаем датчик и в реальном времени наблюдаем как меняется температура. 

