Регистры STM32 в структурах на битовых полях, UART (Tx, Rx-длина?) + DMA
Про меня
Приветствую! Меня зовут Александр, 33 года, на данный момент я работаю обычным электромонтёром, программирование микроконтроллеров и разработка различных электронных устройств для меня просто хобби. Учился всему чисто на энтузиазме и в ближайшем будущем есть желание теснее связать свою жизнь с миром IT. Долго решался написать статью на Habr и поделиться с миром своими решениями. Получить критику, советы того, что можно было сделать лучше или слова благодарности если статья была полезной. И вот моя первая публикация.
Вступление
Во время разработки программного обеспечения для микроконтроллеров или микросхем у которых присутствуют регистры при изменении любого бита, повсеместно используются битовые маски и битовые операции. В частности, речь пойдёт про микроконтроллеры STM32, в котором все регистры имеют 32 бита. Конечно, компания STMicroelectronics программой CubeMX и библиотекой HAL пытается максимально абстрагировать программистов от регистров. С одной стороны, это хорошо, мы в кротчайшие сроки получаем рабочую периферию, на котором даже можно что-то построить, вообще не читая документацию. Но рано или поздно всё равно придётся ковыряться в регистрах (думаю кто работал с библиотекой HAL меня поймёт). Библиотека CMSIS нам предоставляет весь необходимый инструмент для управления регистрами. Но всё равно, что-то не то, хочется работать с битами регистра как с обычными переменными. И вот в, таком случае к нам приходят структуры на битовых полях.
Цель статьи — рассказать, как получить удобный доступ к битам регистра STM32 без использования битовых операций и пользоваться битами микроконтроллера как обычными переменными. Чтобы не быть голословным, создадим проект UART+DMA с передачей и приёмом данных неизвестной длины, работающую чисто на структурах c битовыми полями, которые привязаны к адресам периферии STM32.
Созданный проект будет реализовывать обмен данными между микроконтроллером STM32 и Bluetooth модулем FSC-BT802 по протоколу UART с использованием DMA. На самом деле средствами библиотеки HAL у меня так и не получилось настроить нормальный обмен данными по UART. Большие проблемы доставляет то, что мы заранее не знаем сколько данных отправит bluetooth модуль и приходиться получать байты по одному и искать конец строки. Но спустя некоторое время всё это перестаёт работать. Поэтому и было решено, делать приём и передачу UART на регистрах.
У нас имеется:
Среда разработки STM32CubeIDE v1.9.0
FreeRTOS v9.0.0
Микроконтроллер STM32F405RGT6
Bluetooth модуль FSC-BT802
Связь между STM32 и Bluetooth по UART, скорость 115200
Вся инициализация: тактирование, UART, DMA, FreeRTOS и т.п. я оставил за библиотекой HAL, она с этим справляется очень хорошо и никаких проблем на этом фоне не возникало + очень удобно и быстро.
Генерация проекта в CubeIDE
Первое что необходимо сделать — это сгенерировать проект: настроить тактирование, выбрать необходимый UART, включить прерывания, DMA и желательно FreeRTOS.
Рис. 1 UART1 можно настроить как вам необходимо, на работу с регистрами это не влияетРис 2. Потоки DMA выберите те, которые доступны, остальные настройки для TX и RX потоков можно выставить как показано на изображенииРис. 3 Включить все глобальные прерывания
Далее, желательно включить FreeRTOS и добавить один поток для обработки кольцевого буфера принимаемых данных и добавить семафор для защиты передатчика UART DMA.
Рис. 4 Добавить поток. Можно сделать как указано на изображении, необходимый поток выделен синим и подчёркнут красной линией, на остальные потоки не обращайте внимание.Рис. 5 Добавить семафор как указано на изображении, выделен синим цветом и подчеркнут красной линией, на остальные потоки не обращайте внимание.
Настройки в визуальной части завершены, можно сгенерировать проект.
Создание структур регистров на битовых полях
Для создания структур я воспользовался довольно удобной возможностью языка C, это битовые поля. Не буду расписывать что это такое, если интересно найдёте информацию в интернете. Как мне кажется, для описания и работы с регистрами — это самая удобная функция.
И так, в проекте создаём 2 header файла, можно и один, но так проще структурировать, Register_UART.h и Register_DMA.h.
Далее идём в интернет, ищем Reference Manual на свой микроконтроллер (в частности, это RM0090 для STM32F405), скачиваем, открываем, находим раздел 30.6.8 USART register map страница 1018. И в файле Register_UART.h создаём структуру как указано на Рис. 6
Рис. 6 Создание структуры с битовыми полями на основе карты регистров UART, с остальными 3 мя регистрами поступаем точно так же (сюда просто не влезли)
Как видно из Рис. 6, структуры регистров с битовыми полями в точности повторяют карту регистров UART. Очень важно, расположение битов регистра должно быть строго как указано в карте и если в регистре присутствуют зарезервированные участки бит, их тоже необходимо указывать, также все эти битовые поля должны быть volatile так как они могут меняться без участия нашей программы и для защиты от оптимизации. Если всё сделать правильно, одна такая структура будет занимать ровно 32 бита.
После создания структур с битовыми полями из карты регистров, объединим всё в общую структуру «sUART1_TypeDef», можете назвать её как угодно.
Рис. 7 Объединение структур регистров с битовыми полями в общую структуру sUART1_TypeDefФайл Register_UART.h
#ifndef STM32_F405_UART1_REGISTER_H_
#define STM32_F405_UART1_REGISTER_H_
#include "stdio.h"
#define rUART1 ((sUART1_TypeDef *) USART1_BASE)
typedef struct{
volatile uint32_t PE :1; //0
volatile uint32_t FE :1; //1
volatile uint32_t NF :1; //2
volatile uint32_t ORE :1; //3
volatile uint32_t IDLE :1; //4
volatile uint32_t RXNE :1; //5
volatile uint32_t TC :1; //6
volatile uint32_t TXE :1; //7
volatile uint32_t LBD :1; //8
volatile uint32_t CTS :1; //9
volatile uint32_t Reserved :22; //10-31
} sUSART_SR;
typedef struct{
volatile uint32_t DR :9; //0-8
volatile uint32_t Reserved :23; //9-31
} sUSART_DR;
typedef struct{
volatile uint32_t DIV_Fraction :4; //0-3
volatile uint32_t DIV_Mantissa :12; //4-15
volatile uint32_t Reserved3 :16; //16-31
} sUSART_BRR;
typedef struct{
volatile uint32_t SBK :1; //0
volatile uint32_t RWU :1; //1
volatile uint32_t RE :1; //2
volatile uint32_t TE :1; //3
volatile uint32_t IDLEIE :1; //4
volatile uint32_t RXNEIEIE :1; //5
volatile uint32_t TCIE :1; //6
volatile uint32_t TXEIE :1; //7
volatile uint32_t PEIE :1; //8
volatile uint32_t PS :1; //9
volatile uint32_t PCE :1; //10
volatile uint32_t WAKE :1; //11
volatile uint32_t M :1; //12
volatile uint32_t UE :1; //13
volatile uint32_t Reserved1 :1; //14
volatile uint32_t OVER8 :1; //15
volatile uint32_t Reserved2 :16; //16-31
} sUSART_CR1;
typedef struct{
volatile uint32_t ADD :4; //0-3
volatile uint32_t Reserved1 :1; //4
volatile uint32_t LBDL :1; //5
volatile uint32_t LBDIE :1; //6
volatile uint32_t Reserved2 :1; //7
volatile uint32_t LBCL :1; //8
volatile uint32_t CPHA :1; //9
volatile uint32_t CPOL :1; //10
volatile uint32_t CLKEN :1; //11
volatile uint32_t STOP :2; //12-13
volatile uint32_t LINEN :1; //14
volatile uint32_t Reserved3 :17; //15-31
} sUSART_CR2;
typedef struct{
volatile uint32_t EIE :1; //0
volatile uint32_t IREN :1; //1
volatile uint32_t IRLP :1; //2
volatile uint32_t HDSEL :1; //3
volatile uint32_t NACK :1; //4
volatile uint32_t SCEN :1; //5
volatile uint32_t DMAR :1; //6
volatile uint32_t DMAT :1; //7
volatile uint32_t RTSE :1; //8
volatile uint32_t CTSE :1; //9
volatile uint32_t CTSIE :1; //10
volatile uint32_t ONEBIT :1; //11
volatile uint32_t Reserved :20; //12-31
} sUSART_CR3;
typedef struct{
volatile uint32_t PSC :8; //0-7
volatile uint32_t GT :8; //8-15
volatile uint32_t Reserved :16; //16-31
} sUSART_GTPR;
typedef struct
{
volatile sUSART_SR SR; /* Status register, Address offset: 0x00 */
volatile sUSART_DR DR; /* Data register, Address offset: 0x04 */
volatile sUSART_BRR BRR; /* Baud rate register, Address offset: 0x08 */
volatile sUSART_CR1 CR1; /* Control register 1, Address offset: 0x0C */
volatile sUSART_CR2 CR2; /* Control register 2, Address offset: 0x10 */
volatile sUSART_CR3 CR3; /* Control register 3, Address offset: 0x14 */
volatile sUSART_GTPR GTPR; /* Guard time and prescaler register, Address offset: 0x18 */
} sUART1_TypeDef;
#endif /* STM32_F405_UART1_REGISTER_H_ */
Создание структур с битовыми полями DMA
Точно так же поступаем и с картой регистров DMA, заполнив файл Register_DMA.h. Единственное что может напугать — это размер карты регистров DMA, она растянулась аж на 4 страницы. Но в реальности там многое повторяется, так как DMA имеет 8 потоков. Грубо говоря таблицу с регистрами DMA, можно сократить в 8 раз. В итоге мы имеем объединённую структуру регистров «sDMA_TypeDef» который можно применить к DMA1 и к DMA2.
Файл Register_DMA.h#ifndef STM32_F405_DMA_REGISTER_H_
#define STM32_F405_DMA_REGISTER_H_
#include "stdio.h"
#define rDMA1 ((sDMA_TypeDef *) DMA1_BASE)
#define rDMA2 ((sDMA_TypeDef *) DMA2_BASE)
typedef struct{
volatile uint32_t FEIF0 :1; //0
volatile uint32_t Reserved1 :1; //1
volatile uint32_t DMEIF0 :1; //2
volatile uint32_t TEIF0 :1; //3
volatile uint32_t HTIF0 :1; //4
volatile uint32_t TCIF0 :1; //5
volatile uint32_t FEIF1 :1; //6
volatile uint32_t Reserved2 :1; //7
volatile uint32_t DMEIF1 :1; //8
volatile uint32_t TEIF1 :1; //9
volatile uint32_t HTIF1 :1; //10
volatile uint32_t TCIF1 :1; //11
volatile uint32_t Reserved3 :4; //12-15
volatile uint32_t FEIF2 :1; //16
volatile uint32_t Reserved4 :1; //17
volatile uint32_t DMEIF2 :1; //18
volatile uint32_t TEIF2 :1; //19
volatile uint32_t HTIF2 :1; //20
volatile uint32_t TCIF2 :1; //21
volatile uint32_t FEIF3 :1; //22
volatile uint32_t Reserved5 :1; //23
volatile uint32_t DMEIF3 :1; //24
volatile uint32_t TEIF3 :1; //25
volatile uint32_t HTIF3 :1; //26
volatile uint32_t TCIF3 :1; //27
volatile uint32_t Reserved6 :4; //28-31
} sDMA_LISR;
typedef struct{
volatile uint32_t FEIF4 :1; //0
volatile uint32_t Reserved1 :1; //1
volatile uint32_t DMEIF04 :1; //2
volatile uint32_t TEIF4 :1; //3
volatile uint32_t HTIF4 :1; //4
volatile uint32_t TCIF4 :1; //5
volatile uint32_t FEIF5 :1; //6
volatile uint32_t Reserved2 :1; //7
volatile uint32_t DMEIF5 :1; //8
volatile uint32_t TEIF5 :1; //9
volatile uint32_t HTIF5 :1; //10
volatile uint32_t TCIF5 :1; //11
volatile uint32_t Reserved3 :4; //12-15
volatile uint32_t FEIF6 :1; //16
volatile uint32_t Reserved4 :1; //17
volatile uint32_t DMEIF6 :1; //18
volatile uint32_t TEIF6 :1; //19
volatile uint32_t HTIF6 :1; //20
volatile uint32_t TCIF6 :1; //21
volatile uint32_t FEIF7 :1; //22
volatile uint32_t Reserved5 :1; //23
volatile uint32_t DMEIF7 :1; //24
volatile uint32_t TEIF7 :1; //25
volatile uint32_t HTIF7 :1; //26
volatile uint32_t TCIF7 :1; //27
volatile uint32_t Reserved6 :4; //28-31
} sDMA_HISR;
typedef struct{
volatile uint32_t CFEIF0 :1; //0
volatile uint32_t Reserved1 :1; //1
volatile uint32_t CDMEIF0 :1; //2
volatile uint32_t CTEIF0 :1; //3
volatile uint32_t CHTIF0 :1; //4
volatile uint32_t CTCIF0 :1; //5
volatile uint32_t CFEIF1 :1; //6
volatile uint32_t Reserved2 :1; //7
volatile uint32_t CDMEIF1 :1; //8
volatile uint32_t CTEIF1 :1; //9
volatile uint32_t CHTIF1 :1; //10
volatile uint32_t CTCIF1 :1; //11
volatile uint32_t Reserved3 :4; //12-15
volatile uint32_t CFEIF2 :1; //16
volatile uint32_t Reserved4 :1; //17
volatile uint32_t CDMEIF2 :1; //18
volatile uint32_t CTEIF2 :1; //19
volatile uint32_t CHTIF2 :1; //20
volatile uint32_t CTCIF2 :1; //21
volatile uint32_t CFEIF3 :1; //22
volatile uint32_t Reserved5 :1; //23
volatile uint32_t CDMEIF3 :1; //24
volatile uint32_t CTEIF3 :1; //25
volatile uint32_t CHTIF3 :1; //26
volatile uint32_t CTCIF3 :1; //27
volatile uint32_t Reserved6 :4; //28-31
} sDMA_LIFCR;
typedef struct{
volatile uint32_t CFEIF4 :1; //0
volatile uint32_t Reserved1 :1; //1
volatile uint32_t CDMEIF4 :1; //2
volatile uint32_t CTEIF4 :1; //3
volatile uint32_t CHTIF4 :1; //4
volatile uint32_t CTCIF4 :1; //5
volatile uint32_t CFEIF5 :1; //6
volatile uint32_t Reserved2 :1; //7
volatile uint32_t CDMEIF5 :1; //8
volatile uint32_t CTEIF5 :1; //9
volatile uint32_t CHTIF5 :1; //10
volatile uint32_t CTCIF5 :1; //11
volatile uint32_t Reserved3 :4; //12-15
volatile uint32_t CFEIF6 :1; //16
volatile uint32_t Reserved4 :1; //17
volatile uint32_t CDMEIF6 :1; //18
volatile uint32_t CTEIF6 :1; //19
volatile uint32_t CHTIF6 :1; //20
volatile uint32_t CTCIF6 :1; //21
volatile uint32_t CFEIF7 :1; //22
volatile uint32_t Reserved5 :1; //23
volatile uint32_t CDMEIF7 :1; //24
volatile uint32_t CTEIF7 :1; //25
volatile uint32_t CHTIF7 :1; //26
volatile uint32_t CTCIF7 :1; //27
volatile uint32_t Reserved6 :4; //28-31
} sDMA_HIFCR;
typedef struct{
volatile uint32_t EN :1; //0
volatile uint32_t DMEIE :1; //1
volatile uint32_t TEIE :1; //2
volatile uint32_t HTIE :1; //3
volatile uint32_t TCIE :1; //4
volatile uint32_t PFCTRL :1; //5
volatile uint32_t DIR :2; //6-7
volatile uint32_t CIRC :1; //8
volatile uint32_t PINC :1; //9
volatile uint32_t MINC :1; //10
volatile uint32_t PSIZE :2; //11-12
volatile uint32_t MSIZE :2; //13-14
volatile uint32_t PINCOS :1; //15
volatile uint32_t PL :2; //16-17
volatile uint32_t DBM :1; //18
volatile uint32_t CT :1; //19
volatile uint32_t Reserved1 :1; //20
volatile uint32_t PBURST :2; //21-22
volatile uint32_t MBURST :2; //23-24
volatile uint32_t CHSEL :3; //25-27
volatile uint32_t Reserved2 :4; //28-31
} sDMA_CR;
typedef struct{
volatile uint32_t NDT :16; //0-15
volatile uint32_t Reserved1 :16; //15-31
} sDMA_NDTR;
typedef struct{
volatile uint32_t FTH :2; //0-1
volatile uint32_t DMDIS :1; //2
volatile uint32_t FS :3; //3-5
volatile uint32_t Reserved1 :1; //6
volatile uint32_t FEIE :1; //7
volatile uint32_t Reserved2 :24; //8-31
} sDMA_FCR;
typedef struct{
volatile sDMA_CR CR; /* DMA stream x configuration register, Address offset: 0x10 + 0x18 × stream number */
volatile sDMA_NDTR NDTR; /* DMA stream x number of data register, Address offset: 0x14 + 0x18 × stream number */
volatile uint32_t PAR; /* DMA stream x peripheral address register, Address offset: 0x18 + 0x18 × stream number */
volatile uint32_t M0AR; /* DMA stream x memory 0 address register, Address offset: 0x1C + 0x18 × stream number */
volatile uint32_t M1AR; /* DMA stream x memory 1 address register, Address offset: 0x20 + 0x18 × stream number */
volatile sDMA_FCR FCR; /* DMA stream x FIFO control register, Address offset: 0x24 + 0x18 × stream number */
} sDMA_Stream;
typedef struct
{
volatile sDMA_LISR LISR; /* DMA low interrupt status register, Address offset: 0x00 */
volatile sDMA_HISR HISR; /* DMA high interrupt status register, Address offset: 0x04 */
volatile sDMA_LIFCR LIFCR; /* DMA low interrupt flag clear register, Address offset: 0x08 */
volatile sDMA_HIFCR HIFCR; /* DMA high interrupt flag clear register, Address offset: 0x0C */
volatile sDMA_Stream Stream0; /* DMA stream number 1*/
volatile sDMA_Stream Stream1; /* DMA stream number 1*/
volatile sDMA_Stream Stream2; /* DMA stream number 1*/
volatile sDMA_Stream Stream3; /* DMA stream number 1*/
volatile sDMA_Stream Stream4; /* DMA stream number 1*/
volatile sDMA_Stream Stream5; /* DMA stream number 1*/
volatile sDMA_Stream Stream6; /* DMA stream number 1*/
volatile sDMA_Stream Stream7; /* DMA stream number 1*/
} sDMA_TypeDef;
#endif /* STM32_F405_DMA_REGISTER_H_ */
Далее, созданные структуры sDMA_TypeDef и sUART1_TypeDef необходимо привязать к реальным адресам регистров периферии STM32. Для этого в файлах создаём define rUART1, rDMA1 и rDMA2 и указываем им созданные структуры с ссылкой на адрес реальных регистров.
в файле Register_UART.h
#define rUART1 ((sUART1_TypeDef *) USART1_BASE)
в файле Register_DMA.h
#define rDMA1 ((sDMA_TypeDef *) DMA1_BASE)
#define rDMA2 ((sDMA_TypeDef *) DMA2_BASE)
Адреса регистров USART1_BASE, DMA1_BASE и DMA2_BASE предоставляет библиотека CMSIS, можете поискать необходимый вам адрес в файле stm32F405xx.h (смотря какой у вас МК). При желании можно указывать адреса напрямую, взяв её из документации в Reference Manual раздел 2.3 Memory map.
После проделанных манипуляций мы получаем прямой и удобный доступ к памяти микроконтроллера из наших структур и обращаться к регистрам напрямую без использования различных масок и битовых операций.
Рис. 8 Просмотр состояния регистров через структуру с битовыми полямиРис. 9 Пример работы с нашими структурами, как мне кажется, так всё выглядит куда понятней чем если бы мы использовали битовые операции.
Передача и приём данных по UART DMA
Для приёма и передачи сообщений по UART с использованием DMA необходимо сделать начальную настройку потоков DMA, сделать функцию отправки и приёма сообщений по прерыванию (окончания передачи, ошибки и т.п.), а также реализовать кольцевой буфер на приёмнике, чтоб не тормозить работу DMA.
Инициализация потоков DMA
Для инициализации нам нужно указать в потоках приёмника и передатчика адрес регистра UART DR, указать какие прерывания нам необходимы (ошибки, половина передачи, окончание передачи). В приёмном потоке указать адрес расположения буфера куда DMA положит данные при удачном приёме и длину принимаемого сообщения. Также, здесь вызовем функцию UART1_Received_DMA (), для активации прерывания по приёму байта. Данные для передающего потока здесь указывать не будем, так как это необходимо сделать непосредственно в передатчике. Функцию инициализации UART_Initialization () будем вызывать перед циклом в потоке UART_RESIVE FreeRTOS непосредственно перед началом работы кольцевого буфера.
Код: инициализация потоков DMA
static uint8_t Bluetooth;
void UART_Initialization(){
/*Received UART1 DMA Stream*/
rDMA2->Stream5.CR.DBM = 0; // 0: No buffer switching at the end of transfer
rDMA2->Stream5.CR.CIRC = 0; // 0: Circular mode disabled
rDMA2->Stream5.NDTR.NDT = 1; // Received data len
rDMA2->Stream5.PAR = &(rUART1->DR); // Uart ADRR
rDMA2->Stream5.M0AR = &Bluetooth; // ReadBuff
rDMA2->Stream5.CR.DMEIE = 1; // Direct mode error interrupts
rDMA2->Stream5.CR.TEIE = 1; // Transfer error interrupts
rDMA2->Stream5.CR.TCIE = 1; // Transfer complete interrupts
rDMA2->Stream5.CR.HTIE = 0; // Half-transfer interrupts
UART1_Received_DMA(); // Start DMA Received
/*Transmit UART1 DMA Stream*/
rDMA2->Stream7.CR.DBM = 0; // 0: No buffer switching at the end of transfer
rDMA2->Stream7.CR.CIRC = 0; // 0: Circular mode disabled
rDMA2->Stream7.PAR = &rUART1->DR; // Uart ADRR
rDMA2->Stream7.CR.DMEIE = 1; // Direct mode error interrupts
rDMA2->Stream7.CR.TEIE = 1; // Transfer error interrupts
rDMA2->Stream7.CR.TCIE = 1; // Transfer complete interrupts
rDMA2->Stream7.CR.HTIE = 0; // Half-transfer interrupts
}
Передача данных по UART DMA
Передачу будем выполнять с защитой, чтоб в функцию передачи по DMA не ложились новые данные пока не будет переданы предыдущие, а также защитим регистры DMA пока они не отработали предыдущую передачу. Для этого воспользуемся семафором, который мы создали ранее в FreeRTOS. Каждый раз, когда какая-либо функция захочет обратиться передать данные и семафор будет занят, она будет ждать указанное время, пока семафор не освободится. Семафор будем освобождать при вызове прерывания (окончания передачи, ошибке и т.п.)
Код: передача сообщений UART DMA и обработка прерываний передачи
Здесь в функцию UART1_Transmit (uint8_t *data, uint8_t dataLen) передаётся массив данных с указателем и длинной сообщения, если размер данных не более 128 байт и семафор свободен, то заполняем массив pData, далее настраиваем поток DMA, указываем откуда забирать данные, количество данных и какие прерывания включать. После отправки ждём прерывание TxCpltCallbackUart1(). После успешной отправки или ошибки, в общем при получении прерывания сбрасываем семафор и новые данные смогут отправиться в UART по DMA.
Подавать напрямую data в поток DMA можно, но при этом возможно затрёте данные и получите результат не тот который ожидали, так как DMA работает в не блокированном режиме. Поэтому лучше объявить статичную переменную, заполнять её и передавать уже данные с статичного буфера с уверенностью что с данными ничего не случиться.
void UART1_Transmit(uint8_t *data, uint8_t dataLen){
if(BinarySem_TxUART1Handle != NULL && dataLen < 128){
if(osSemaphoreWait(BinarySem_TxUART1Handle, 1000) == osOK){
static uint8_t pData[128];
for(uint8_t byte = 0; byte Stream7.NDTR.NDT = dataLen; // Configure DMA Stream data length
rDMA2->Stream7.M0AR = &(uint32_t*)pData;// Configure DMA Stream source address
rDMA2->HIFCR.CHTIF7 = 0; // Clear Half-transfer interrupt flags
rDMA2->HIFCR.CTCIF7 = 0; // Clear Transfer complete interrupt flags
rDMA2->HIFCR.CTEIF7 = 0; // Clear Transfer error interrupt flags
rDMA2->HIFCR.CDMEIF7 = 0; // Clear Direct mode error interrupt flags
rDMA2->Stream7.CR.EN = 1; // Enable the Peripheral
rUART1->SR.TC = 1; // Clear the TC flag in the SR register by writing 0 to it
rUART1->CR3.DMAT = 1; // Enable the DMA transfer for transmit request by setting the DMAT bit in the UART CR3 register
}else{
osSemaphoreRelease(BinarySem_TxUART1Handle);
}
}
}
void TxCpltCallbackUart1(){
// Transfer complete Callback
if(rDMA2->HISR.TCIF7 == 1){
rDMA2->HIFCR.CTCIF7 = 1; // Clear transfer complete interrupt flag
// Other Callback
}else{
if(rDMA2->HISR.DMEIF7 == 1) rDMA2->HIFCR.CDMEIF7 = 1; // Clear direct mode error interrupt flag
if(rDMA2->HISR.HTIF7 == 1) rDMA2->HIFCR.CHTIF7 = 1; // Clear half transfer interrupt flag
if(rDMA2->HISR.FEIF7 == 1) rDMA2->HIFCR.CFEIF7 = 1; // Clear transfer error interrupt flag
rDMA2->Stream7.CR.EN = 0; // Disable DMA operation
}
osSemaphoreRelease(BinarySem_TxUART1Handle); // Release semaphore
}
Обработчик прерываний TxCpltCallbackUart1() необходимо вызывать в файле stm32f4xx_it.c, из прерывания потока DMA, который отвечает за передачу, у меня это DMA2 поток 7, при этом желательно отключить стандартный обработчик прерывания HAL_DMA_IRQHandler (&hdma_usart1_tx), который сгенерировал визуальный редактор CubeIDE.
void DMA2_Stream7_IRQHandler(void)
{
/* USER CODE BEGIN DMA2_Stream7_IRQn 0 */
TxCpltCallbackUart1();
/* USER CODE END DMA2_Stream7_IRQn 0 */
//HAL_DMA_IRQHandler(&hdma_usart1_tx);
/* USER CODE BEGIN DMA2_Stream7_IRQn 1 */
/* USER CODE END DMA2_Stream7_IRQn 1 */
}
Приём данных по UART DMA
Приём данных будет выполняться только на прерываниях, во время инициализации потоков DMA мы вызвали функцию UART1_Received_DMA (), которая запустила DMA в работу и включила прерывания при приёме одного байта данных. Каждый раз, как получен байт, поток DMA будет перезапускаться в прерывании для получения нового байта. Каждый полученный байт будет ложиться в кольцевой буфер.
Код: приём сообщений UART DMA и обработка прерываний по приёму
Здесь по большому счёту всё понятно, очищаем флаги прерываний, включаем поток DMA, очищаем регистры RXNE и DR и разрешаем приём данных по DMA UART. Данные, куда класть и сколько, мы указали при инициализации потоков DMA т.е. нет необходимости указывать их постоянно.
При возникновении прерывания RxCpltCallbackUart1() по окончанию приёма вызывается функция Ring_buffer (Bluetooth) и в кольцевой буфер кладётся принятый байт, далее повторно вызывается UART1_Received_DMA () и запускается процедура приёма байта и так по кругу. Если возникла ошибка, будут сброшены флаги ошибок и перезапущен поток DMA на получение новых данных.
void UART1_Received_DMA(){
rDMA2->HIFCR.CHTIF5 = 0; // Clear Half-transfer interrupt flags
rDMA2->HIFCR.CTCIF5 = 0; // Clear Transfer complete interrupt flags
rDMA2->HIFCR.CTEIF5 = 0; // Clear Transfer error interrupt flags
rDMA2->HIFCR.CDMEIF5 = 0; // Clear Direct mode error interrupt flags
rDMA2->Stream5.CR.EN = 1; // Clear Enable the Peripheral interrupt flags
/* Clear the Overrun flag just before enabling the DMA Rx request: can be mandatory for the second transfer */
do{
volatile uint32_t tmpreg = 0x00U;
tmpreg = rUART1->SR.RXNE;
tmpreg = rUART1->DR.DR;
(void)tmpreg;
} while(0U);
rUART1->CR3.EIE = 1; // Enable the UART Error Interrupt: (Frame error, noise error, overrun error)
rUART1->CR3.DMAR = 1; // Enable the DMA transfer for the receiver request by setting the DMAR bit in the UART CR3 register
}
void RxCpltCallbackUart1(){
// Received complete Callback
if(rDMA2->HISR.TCIF5 == 1){
rDMA2->HIFCR.CTCIF5 = 1; // Clear transfer complete interrupt flag
Ring_buffer(Bluetooth); // Add byte to ring buffer
UART1_Received_DMA(); // Start receiving byte again
// Other Callback
}else{
if(rDMA2->HISR.DMEIF5 == 1) rDMA2->HIFCR.CDMEIF5 = 1; // Clear direct mode error interrupt flag
if(rDMA2->HISR.HTIF5 == 1) rDMA2->HIFCR.CHTIF5 = 1; // Clear half transfer interrupt flag
if(rDMA2->HISR.FEIF5 == 1) rDMA2->HIFCR.CFEIF5 = 1; // Clear transfer error interrupt flag
rDMA2->Stream7.CR.EN = 0; // Disable DMA operation
UART1_Received_DMA(); // Start receiving byte again
}
}
Также как в случае с передатчиком, обработчик прерываний RxCpltCallbackUart1() необходимо вызывать в файле stm32f4xx_it.c, из потока DMA, который отвечает за приём, у меня это DMA2 поток 5, при этом желательно отключить стандартный обработчик прерывания HAL_DMA_IRQHandler (&hdma_usart1_rx), который сгенерировал визуальный редактор CubeIDE.
void DMA2_Stream5_IRQHandler(void)
{
/* USER CODE BEGIN DMA2_Stream5_IRQn 0 */
RxCpltCallbackUart1();
/* USER CODE END DMA2_Stream5_IRQn 0 */
//HAL_DMA_IRQHandler(&hdma_usart1_rx);
/* USER CODE BEGIN DMA2_Stream5_IRQn 1 */
/* USER CODE END DMA2_Stream5_IRQn 1 */
}
Кольцевой буфер для принимаемых данных
Так как мы будем принимать сообщения неизвестной длины, похоже единственное что мы можем сделать это — принимать по одному байту и искать конец строки. Для этого, создадим кольцевой буфер, в который мы будем попадать по прерыванию об окончании приёма одного байта из DMA.
Код: побайтовое заполнение кольцевого буфера UART и поиск конца строки
У меня из Bluetooth модуля все сообщения в UART приходят вида «R N сообщение R N» если у вас сообщения приходят по другому, то этот кольцевой буфер необходимо модифицировать под себя.
В функции происходит определения начала строки, заполнение кольцевого буфера и поиск конца строки, как только обнаружен конец строки увеличивается переменная MsgCountW, а также добавляется 0 в BtBuffer буфер. По 0 обработчик кольцевого буфера будет определять, что это конец сообщения, а по MsgCountW определять количество доступных сообщений.
#define BufSize 1024
int MsgCountW = 0, //количество записанных сообщений
MsgCountR = 0, //количество прочтённых сообщений
RxPointer = 0, //указатель в массиве чтения
TxPointer = 0; //указатель в массиве записе
uint8_t RNScanBuf[3], //буфер для сканирования конца строки
BtBuffer[BufSize], //кольцевой буфер
SendState = 0,
R = 13,
N = 10,
Text = 14,
End_Mesage = 0;
void Ring_buffer(uint8_t data){
RNScanBuf[0] = RNScanBuf[1];
RNScanBuf[1] = RNScanBuf[2];
RNScanBuf[2] = data;
if (RNScanBuf[0] == R && RNScanBuf[1] == N && RNScanBuf[2] > Text){
//начало строки
if(SendState == 1){
BtBuffer[TxPointer] = End_Mesage;
UpWritePointer();
MsgCountW ++;
}
SendState = 1;
BtBuffer[TxPointer] = data;
UpWritePointer();
}else if (RNScanBuf[0] > Text && RNScanBuf[1] == R && RNScanBuf[2] == N){
//конец строки
SendState = 0;
BtBuffer[TxPointer] = End_Mesage;
UpWritePointer();
MsgCountW ++;
}else if(data > Text) {
//заполнение буфера
BtBuffer[TxPointer] = data;
UpWritePointer();
}
}
Для обработки кольцевого буфера в начале статьи мы создали поток в FreeRTOS UART_RESIVE. Здесь мы будет отслеживать MsgCountW и если есть сообщения, то собирать сообщение в новый буфер, а при нахождении конца строки вызывать функцию Received_uart1(uint8_t *data, uint8_t len) куда и придут наши драгоценные сообщения из UART.
Код: обработка кольцевого буфера, и получение сообщений в функцию
В бесконечном потоке FreeRTOS происходит сравнивание MsgCountW и MsgCountR, если они не равны, то начинаем заполнять буфер отправки и одновременно ищем конец строки »0», который нам выставил приёмный кольцевой буфер. После нахождения вызываем Received_uart1(uint8_t *data, uint8_t len) с длинной сообщения и указателем на буфер с данными. В функции Received_uart1 вы уже можете делать с этими данными что хотите, + это всё находиться в потоке и не задерживает вашу основную программу если необходимо выполнить что-то сложное.
uint8_t SendMsgBuf[255];
void Start_UART_RESIVE(void const * argument)
{
/* USER CODE BEGIN Start_UART_RESIVE */
UART_Initialization();
/* Infinite loop */
for(;;)
{
if(MsgCountW != MsgCountR){
SendMsgLen = 0;
for(uint16_t i = 0; i=BufSize) {
RxPointer = 0;
}
}
Received_uart1(&SendMsgBuf[0], SendMsgLen-1);
MsgCountR++;
}
osDelay(1);
}
}
void Received_uart1(uint8_t *data, uint8_t len){
/* Принятые данные */
}
Конечно, этот кольцевой приёмник не идеален, допустим он не будет работать если передавать байтовые данные, так как наличие нулевого байта сломает кольцевой буфер и сообщения будут сыпаться как попало, здесь уже надо придумать, как определять конец строки самостоятельно.
Вывод
В качестве примера, реализовали полностью рабочий UART+DMA, который отлично работает с использованием созданных структур на битовых полях. Если есть предложения по улучшению алгоритма готов выслушать.
Использование структур на битовых полях даёт очень удобный доступ к битам регистра без использования битовых масок и битовых операций, пользуясь ими как обычным переменным. Это особенно полезно на этапе разработки и отладки программного обеспечения, так как мы в реальном времени, без конвертаций в битовый массив, видим что происходит с регистрами.
Может показаться что есть проблемы: 1) переносимость такого кода очень низкая, возможно под каждый микроконтроллер придётся писать свою структуру и даже менять алгоритм работы программы. Но это придётся делать и в случае использования битовых операций. 2) Если необходимо изменить несколько параметров в регистре, их необходимо приравнивать по очереди, что не удобно и снижается производительность (особенное если их с десяток). Это происходит потому как мы не можем приравнять структуру к конкретному числу. Вообще при такой необходимости можно воспользоваться битовыми операциями, так как структуры привязаны к адресам периферии и имеют ключевое слово volatile, они отреагируют на любое изменение регистров микроконтроллера.
Для себя особых сложностей и проблем не пользоваться методом доступа к битам регистра через битовые поля не нашёл. Также, интересно мнение более опытных людей о таком подходе. И почему повсеместно используют битовые операции и не отдают предпочтение структурам на битовых полях?
Всем спасибо за внимание, и добра!