Про меня

Приветствую! Меня зовут Александр, 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 <dataLen; byte++){
				pData[byte] = data[byte];
			}

			rDMA2->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; i++){
				
			  if(SendMsgLen<255){				
				  SendMsgBuf[SendMsgLen] = BtBuffer[RxPointer];				
			  }
				
			  SendMsgLen++;
		
			  if(BtBuffer[RxPointer]==End_Mesage){				
				  i = BufSize;				
			  }				
			  RxPointer++;				
			  if(RxPointer>=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, они отреагируют на любое изменение регистров микроконтроллера.

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

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

Комментарии (19)


  1. r1000ru
    21.04.2022 23:02
    +4

    Я очень извиняюсь, но вы изобрели CMSIS. Там все уже есть


    1. Alekssandr Автор
      22.04.2022 01:10

      В CMSIS всё это есть, но не в том виде как это представлено в статье.


  1. rus084
    21.04.2022 23:14
    +2

    В периферийных библиотеках для PIC16/PIC24 структуры с регистрами описаны именно битовыми полями. Там это имеет явную пользу, так как ядро умеет атомарно манипулировать битами.

    Ядра cortex таких возможностей не имеют (кроме m3, там отдельные биты озу и периферии мапятся на адреса памяти), наверное поэтому производители и не заморачиваются в описании регистров периферии в виде битовых полей.

    Было бы круто если регистры периферии были представлены в виде структур с методами из с++ хотя-бы для инициализации, но так почему-то никто не делает.

    У этого способа еще есть недостаток: когда нужно поменять значения нескольких полей в одном регистре, компилятор сгенерирует отдельные операции чтения/модификации/записи для каждого поля, вместо одной когда оперируем целым регистром


    1. MinimumLaw
      22.04.2022 07:42
      +1

      Битовые поля - удобный и мощный инструмент. К сожалению, не лишенный недостатков.

      И если уж на то пошло, то неатомарые операции с битами в памяти... Ну вы поняли...

      А вот избавление от потенциальных граблей и ошибок класса "опечатка" или "copy-paste" это безусловное благо. Не просто же так в том же Linux настоятельно рекомендуют использовать подсистему regmap для работы с регистрами. Проще контролировать это в одном месте, чем раскиданным по всему коду. Особенно когда это часть компилятора. Вопрос же одновременного изменения нескольких полей (как правильно пишут немного дальше) решается union'ом.

      А вот если говорить о недостатках которые серьезнее - то это в первую очередь никакая переносимость битовых полей между BE и LE системами. Приходится городить множество #ifdef'ов. Потому для кода, специфичного для процессора оно хорошо и допустимо, а вот для переносимого... Стоит подумать.

      Ну, и уж совсем в качестве оффтопика - мне безумно не хватает типов в стиле le16_t и be16_t. Да с поддержкой битовых полей в них. Как и реально переносимых атрибутов big_endian и little_endian для структур и объединений. В лучшем случае эти моменты есть у конкретно взятого компилятора, но это очень не хорошее решение. В первую очередь в плане переносимости.


  1. j_aleks
    22.04.2022 01:24
    +1

    Рабочий тестовый проект наверно можно было-бы выложить на файлообменник какой нибудь... или git пока живой еще...

    Если использовать как отправную точку по обмену UART, то очень полезно для начинающих, или заблудившихся именно рабочий тест...


    1. Alekssandr Автор
      22.04.2022 01:32

      Согласен, я дополню статью с исходниками.


  1. Zuy
    22.04.2022 04:07
    +3

    Вы чуть чуть недоизобрели этот велосипед. Следующий шаг это внутри структуры каждого регистра сделать union, где объединить доступ по байтам, словам или по двойным словам. Тогда будет уже точно, как в либах от TI или NXP.


  1. Sergey78
    22.04.2022 07:26

    Зачем вы используете DMA на приеме, если принимаете по 1 байту?

    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

    Флаги сбрасываются записью 1, а не 0.

    Во что эта вот конструкция превращается компилятором не смотрели? В классическом варианте сброса флагов, вроде такого:

    DMA2->HIFCR = (DMA_HIFCR_CDMEIF5|DMA_HIFCR_CFEIF5|DMA_HIFCR_CHTIF5|DMA_HIFCR_CTCIF5|DMA_HIFCR_CTEIF5);

    Все битовые операции происходят еще на стадии сборки и в коде только три инструкции будет, LDR адреса, MOV значения в регистр и затем STR в регистр.


    1. Alekssandr Автор
      22.04.2022 11:00

      Спасибо, действительно ошибся со сбросом флага.

      Ваш вариант конечно выполниться гораздо быстрее, всего 3 инструкции.

       74       	DMA2->HIFCR = (DMA_HIFCR_CDMEIF5|DMA_HIFCR_CHTIF5|DMA_HIFCR_CTCIF5|DMA_HIFCR_CTEIF5);
      08026682:   ldr     r3, [pc, #84]   ; (0x80266d8 <UART1_Received_DMA+140>)
      08026684:   mov.w   r2, #3840       ; 0xf00
      08026688:   str     r2, [r3, #12]

      С битовыми полями на одну операцию 4 инструкции + если операции производить по очереди, в данном случае 4 то 4*4 = 16 инструкций. Что гораздо медленнее.

       67       	rDMA2->sHIFCR.CHTIF5  = 1; // Clear Half-transfer interrupt flags
      08026652:   ldr     r2, [pc, #132]  ; (0x80266d8 <UART1_Received_DMA+140>)
      08026654:   ldr     r3, [r2, #12]
      08026656:   orr.w   r3, r3, #1024   ; 0x400
      0802665a:   str     r3, [r2, #12]


  1. Kelbon
    22.04.2022 09:01
    +1

    Почему у вас каждое поле volatile вместо обычной структуры и

    typedef volatile MyStructImpl MyStruct; ?


    1. Kelbon
      22.04.2022 12:03

      Более того, можно так

      typedef volatile struct {
      	int x;
      } hehe;


      1. Alekssandr Автор
        22.04.2022 12:22

        Да, можно и так, как бы если использовать volatile для структуры, действие спецификатора будет распространяться на все содержимое структуры (нам это и нужно). Если этого не нужно, то можно применить спецификатор volatile к отдельным элементам структуры.


  1. Indemsys
    22.04.2022 10:19
    +1

    Давно уже не использую битовые структуры для работы с регистрами.
    В целом себя не оправдывает.
    Когда регистры были 8-и битными, простыми и редкими, в 8-и битниках типа Z86 - это ещё что-то упрощало.
    Но теперь главный источник ошибок не в том, что бит не туда попал, а в том, что этот бит делает не то что надо.
    У производителей теперь видна гибридизация. BSP пишут команды, где хаотично могут сочетаться и битовые структуры и макросы с масками, смещениями и позициями битов.
    Это проистекает, думается, из-за предельной запутанности регистров периферии и методами обращения с ними.

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

    Много времени уходит на то чтобы удостовериться в правильности позиции бита. Для это всегда открыт мануал на чип. Готовым хидерам от производителя даже доверять нельзя. Они хидеры пишут оптом для всех ревизий и типов сразу заворачивая в каскады макросов. Особо тестированием не заморачиваются.

    Размещая в битовых структурах всегда есть риск ошибиться с позицией. Т.е. применение битовых структур требует последующего тестирования. Объявление же битов явно по их позиции значительно упрощает написание кода. Поскольку можно проигнорить и не объявлять ненужные биты, можно легко проверить по мануалу правильность позиции бита, и можно легко поменять позицию бита.
    А то как у автора позиция бита объявляется в комментарии - это самый коварный способ выстрелить себе в ногу. Нет ничего опаснее забытых и неверных комментариев.

    Т.е. по моему опыту некоторые накладные вызванные явным объявлением бит по номеру с лихвой окупаются упрощением рефакторинга, упрощением чекинга, и возможность игнорирования ненужных объявлений.
    Поскольку регистров могут быть сотни, а в них по 32-а бита и писать тщательно всю структуры под них, а потом ещё в ручную подсчитывать правильно ли бит стоит - это обременительно.

    Да и STM32 не скоро ещё вернётся в продажу. С начала кризиса нам пришлось поменять три семейства микроконтроллеров разных производителей по причине прекращения поставок. Метод объявления и использования битов явно и по номерам себя вполне оправдывает, он устойчив к вариабельности платформ, компиляторов, версия языков, отладчиков, библиотек и проч.


  1. DungeonLords
    22.04.2022 17:30

    "STM32, в котором все регистры имеют 32 бита. "

    Это совсем не так.


    1. Alekssandr Автор
      22.04.2022 23:01

      А какие регистры в STM32 не имеют 32 бита?


  1. Gryphon88
    22.04.2022 19:02

    Можете пояснить за работу DMA в данном случае? Я никак не пойму. насколько такой способ переносим, а сколько тут нарушение стандарта и MISRA C, как тут. Насколько я понимаю, DMA просто берет память по адресу и копирует её 1к1 по другому адресу, никакого переупорядочивания не происходит. То есть для того, чтобы всё было как задумано и по стандарту (пусть и мимо misra), надо, чтобы соблюдались следующие условия:

    1. структура данных порта

    utypedef union portStruct {uint32_t val;

    struct {uint32_t pin1:1;

    uint32_t pin2:1;

    ...

    uint32_t pin32:1;

    };

    } portStruct_t;

    union для "обмана" компилятора типобезопасностью.

    1. sizeof(uint32_t) == sizeof(portStruct_t)

    2. \_AlignOf(uint32_t) == \_AlignOf(portStruct_t)И всё

    И все равно мы можем получить неправильное поведение из-за endianess, которую на этапе компиляции не получишь, если для платформы не определен <endianness.h>. Правильно ли я понимаю, что если мы используем структуру выше как источник копирования (16 битный вариант, с нулями в последних 16 битах), а GPIO->ODR в качестве цели, она ляжет "задом наперед", поскольку m3 le? Является ли использование меппинга структуры UB по стандарту? Что говорит про такой код MISRA C?


  1. Alekssandr Автор
    22.04.2022 23:22

    Случайно отклонил комментарий по поводу:

    	do{
      ...
    	} while(0U);

    Здесь происходить сброс флага чтением регистров DR и RXNE перед включением запроса DMA. Этот кусок кода я взял из библиотеки HAL, когда изучал как она устроена.


  1. ColdHam
    23.04.2022 09:28

    У stm есть прерывание юарт по символу (любому, в ТЧ окончания строки). Благодаря этому прерыванию можно не проверять каждый байт


  1. besitzeruf
    23.04.2022 14:21

    Действительно, только лет существует CMSIS и вы думаете что никто не догадальься использовать битовые поля в структурах? А знаете почему?

    1) они запрещены во многих стандартах таких как misra и т.п.

    2) теперь у вас не uin32_t для накпример регистра SR а структура... Как мне из него или в него записать сразу несколько бит (реторический вопрос, хочу на ответ посмотреть)? Учитывая что вы используете FreeRTOS да и в принципе еще есть прерывания... обычно нужно сначало сформировать конкретную маску (например для установки битов) и одной инструкцией биты регистра установить. В вашем методе это не возможно.

    П.С. CMSIS давно внедрили макросы для ститывания бита/битов из регистра, на читабельности никак не сказывается.