Регистры 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 можно настроить как вам необходимо, на работу с регистрами это не влияетРис. 1 UART1 можно настроить как вам необходимо, на работу с регистрами это не влияетРис 2. Потоки DMA выберите те, которые доступны, остальные настройки для TX и RX потоков можно выставить как показано на изображенииРис 2. Потоки DMA выберите те, которые доступны, остальные настройки для TX и RX потоков можно выставить как показано на изображенииРис. 3 Включить все глобальные прерыванияРис. 3 Включить все глобальные прерывания

Далее, желательно включить FreeRTOS и добавить один поток для обработки кольцевого буфера принимаемых данных и добавить семафор для защиты передатчика UART DMA.

Рис. 4 Добавить поток. Можно сделать как указано на изображении, необходимый поток выделен синим и подчёркнут красной линией, на остальные потоки не обращайте внимание.Рис. 4 Добавить поток. Можно сделать как указано на изображении, необходимый поток выделен синим и подчёркнут красной линией, на остальные потоки не обращайте внимание.Рис. 5 Добавить семафор как указано на изображении, выделен синим цветом и подчеркнут красной линией, на остальные потоки не обращайте внимание.Рис. 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, с остальными 3 мя регистрами поступаем точно так же (сюда просто не влезли)

Как видно из Рис. 6, структуры регистров с битовыми полями в точности повторяют карту регистров UART. Очень важно, расположение битов регистра должно быть строго как указано в карте и если в регистре присутствуют зарезервированные участки бит, их тоже необходимо указывать, также все эти битовые поля должны быть volatile так как они могут меняться без участия нашей программы и для защиты от оптимизации. Если всё сделать правильно, одна такая структура будет занимать ровно 32 бита.

После создания структур с битовыми полями из карты регистров, объединим всё в общую структуру «sUART1_TypeDef», можете назвать её как угодно.

Рис. 7 Объединение структур регистров с битовыми полями в общую структуру 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 Просмотр состояния регистров через структуру с битовыми полямиРис. 8 Просмотр состояния регистров через структуру с битовыми полямиРис. 9 Пример работы с нашими структурами, как мне кажется, так всё выглядит куда понятней чем если бы мы использовали битовые операции.Рис. 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, они отреагируют на любое изменение регистров микроконтроллера.

Для себя особых сложностей и проблем не пользоваться методом доступа к битам регистра через битовые поля не нашёл. Также, интересно мнение более опытных людей о таком подходе. И почему повсеместно используют битовые операции и не отдают предпочтение структурам на битовых полях?

Всем спасибо за внимание, и добра!

© Habrahabr.ru