STM Урок 184. CMSIS. STM32F1. USART. DMA
Продолжаем нашу работу с шиной USART контроллера STM32F1 с применением библиотеки CMSIS.
В данном уроке мы поработаем с возможностью использования периферии DMA при передаче данных по интерфейсу USART.
С применением DMA в работе с модулем USART мы уже знакомы из урока 15 и урока 164. Правда, тогда мы использовали при этом библиотеки HAL и LL, но тем не менее со спецификой применения DMA мы познакомились полностью. Также, как работать с DMA и как его настраивать при использовании библиотеки CMSIS, мы также знаем уже не понаслышке. Мы работали с данной периферией в уроке 176 и уроке 178.
Схема наша также со времён трёх прошлых уроков никаких изменений не претерпела.
Поэтому мы можем смело приступать к проекту, который был сделан из проекта прошлого урока с именем CMSIS_USART_INT и имя ему новое было дано CMSIS_USART_DMA.
Откроем наш проект в Keil и в файле main.c удалим вот эти две глобальные переменные
uint8_t fl=0;
uint8_t dt1;
Вместо них добавим два флага, один — для отслеживания окончания передачи пакета, другой — приёма
|
1 2 |
char rx_str[30], tx_str[30], tmp_str[10]; uint8_t fl_rx=0, fl_tx=0; |
Удалим также объявления символьных массивов
char rx_str[30], tx_str[30], tmp_str[10];
А добавим один инициализированный, другой — неинициализированный
|
1 2 3 |
__IO uint32_t SysTick_CNT = 0; char tx_str[] = "The universal synchronous asynchronous receiver transmitter (USART)\r\noffers a flexible means of full-duplex data exchange\r\nwith external equipment requiring an industry\nstandard NRZ asynchronous serial data format.\r\n"; uint8_t rx_str[256]; |
Добавим также функцию инициализации DMA над функцией USART1_Init
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//-------------------------------------------------------- static void DMA1_Init(void) { //DMA controller clock enable SET_BIT(RCC->AHBENR, RCC_AHBENR_DMA1EN); tmpreg = READ_BIT(RCC->AHBENR, RCC_AHBENR_DMA1EN); (void)tmpreg; //DMA1_Channel4_IRQn interrupt init NVIC_EnableIRQ(DMA1_Channel4_IRQn); //DMA1_Channel5_IRQn interrupt init NVIC_EnableIRQ(DMA1_Channel5_IRQn); } //-------------------------------------------------------- |
Так как в уроке 164 мы полностью проанализировали автоматически сгенерированную подобную функцию, то ничего нового тут для нас не будет.
Основная инициализация DMA будет уже в функции инициализации модуля USART1 — USART1_Init.
Для начала в данной функции мы удалим включение глобальных прерываний от данного модуля, так как прерывания мы уже будем использовать от DMA
//USART1 interrupt Init
NVIC_EnableIRQ(USART1_IRQn);
Вместо этих строк добавим код инициализации нашего DMA
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
GPIO_CRH_CNF10_0 | GPIO_CRH_MODE9 | GPIO_CRH_CNF9_1); //USART1 DMA Init //USART1_RX Init //Set transfer direction (Peripheral to Memory) CLEAR_BIT(DMA1_Channel5->CCR, DMA_CCR5_DIR | DMA_CCR5_MEM2MEM); //Set priority level CLEAR_BIT(DMA1_Channel5->CCR, DMA_CCR5_PL); //Transfer mode NORMAL CLEAR_BIT(DMA1_Channel5->CCR, DMA_CCR5_CIRC); //Set peripheral no increment mode CLEAR_BIT(DMA1_Channel5->CCR, DMA_CCR5_PINC); //Set memory increment mode SET_BIT(DMA1_Channel5->CCR, DMA_CCR5_MINC); //Set peripheral data width CLEAR_BIT(DMA1_Channel5->CCR, DMA_CCR5_PSIZE_1 | DMA_CCR5_PSIZE_0); //Set memory data width CLEAR_BIT(DMA1_Channel5->CCR, DMA_CCR5_MSIZE_1 | DMA_CCR5_MSIZE_0); //SPI1_TX Init //Set transfer direction (Memory to Peripheral) MODIFY_REG(DMA1_Channel4->CCR, DMA_CCR4_MEM2MEM, DMA_CCR4_DIR); //Set priority level CLEAR_BIT(DMA1_Channel4->CCR, DMA_CCR4_PL); //Transfer mode NORMAL CLEAR_BIT(DMA1_Channel4->CCR, DMA_CCR4_CIRC); //Set peripheral no increment mode CLEAR_BIT(DMA1_Channel4->CCR, DMA_CCR4_PINC); //Set memory increment mode SET_BIT(DMA1_Channel4->CCR, DMA_CCR4_MINC); //Set peripheral data width CLEAR_BIT(DMA1_Channel4->CCR, DMA_CCR4_PSIZE_1 | DMA_CCR4_PSIZE_0); //Set memory data width CLEAR_BIT(DMA1_Channel4->CCR, DMA_CCR4_MSIZE_1 | DMA_CCR4_MSIZE_0); |
Данную инициализацию мы тажке видели в уроке 164, здесь она просто переписана с использованием макросов библиотеки CMSIS. Практически такая же инициализация DMA была у нас в уроке 178, когда мы работали с модулем SPI.
Универсальную функцию USART_RX_TX_Str удалим вместе с телом.
В функции main вызовем функцию инициализации DMA
|
1 2 |
GPIO_Init(); DMA1_Init(); |
Удалим разрешение локальных прерываний от USART
SET_BIT(USART1->CR1, USART_CR1_RXNEIE);
SET_BIT(USART1->CR3, USART_CR3_EIE);
Вместо этого сначала отключим каналы DMA
|
1 2 3 4 |
Number_7219(87654321); //Disable DMA channels CLEAR_BIT(DMA1_Channel4->CCR, DMA_CCR4_EN); CLEAR_BIT(DMA1_Channel5->CCR, DMA_CCR5_EN); |
Очистим флаги прерываний каналов
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
CLEAR_BIT(DMA1_Channel5->CCR, DMA_CCR5_EN); //Clear Channel 4 global interrupt flag WRITE_REG(DMA1->IFCR, DMA_IFCR_CGIF4); //Clear Channel 5 global interrupt flag WRITE_REG(DMA1->IFCR, DMA_IFCR_CGIF5); //Clear Channel 4 transfer complete flag WRITE_REG(DMA1->IFCR, DMA_IFCR_CTCIF4); //Clear Channel 4 transfer error flag WRITE_REG(DMA1->IFCR, DMA_IFCR_CTEIF4); //Clear Channel 5 transfer complete flag WRITE_REG(DMA1->IFCR, DMA_IFCR_CTCIF5); //Clear Channel 5 transfer error flag WRITE_REG(DMA1->IFCR, DMA_IFCR_CTEIF5); |
Разрешим запросы DMA для USART1
|
1 2 3 4 5 |
WRITE_REG(DMA1->IFCR, DMA_IFCR_CTEIF5); //Enable DMA Mode for reception SET_BIT(USART1->CR3, USART_CR3_DMAR); //Enable DMA Mode for transmission SET_BIT(USART1->CR3, USART_CR3_DMAT); |
Разрешим прерывания по событиям ошибки и окончания передачи для каналов DMA
|
1 2 3 4 5 6 7 8 9 |
SET_BIT(USART1->CR3, USART_CR3_DMAT); //Enable Channel 4 Transfer complete interrupt SET_BIT(DMA1_Channel4->CCR, DMA_CCR4_TCIE); //Enable Channel 4 Transfer error interrupt SET_BIT(DMA1_Channel4->CCR, DMA_CCR4_TEIE); //Enable Channel 5 Transfer complete interrupt SET_BIT(DMA1_Channel5->CCR, DMA_CCR5_TCIE); //Enable Channel 5 Transfer error interrupt SET_BIT(DMA1_Channel5->CCR, DMA_CCR5_TEIE); |
Сконфигурируем в DMA адреса буферов источника и приёмника для передачи и приёма
|
1 2 3 4 5 |
//Configure the Source and Destination addresses WRITE_REG(DMA1_Channel5->CPAR, (uint32_t)&(USART1->DR)); WRITE_REG(DMA1_Channel5->CMAR, (uint32_t)&rx_str); WRITE_REG(DMA1_Channel4->CPAR, (uint32_t)&(USART1->DR)); WRITE_REG(DMA1_Channel4->CMAR, (uint32_t)&tx_str); |
Из бесконечного цикла пока всё удалим.
Обработчик прерываний USART1_IRQHandler удалим вместе с телом, а вместо него добавим два обработчика прерываний от каналов DMA
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
//---------------------------------------------------------- void DMA1_Channel4_IRQHandler(void) { if(READ_BIT(DMA1->ISR, DMA_ISR_TCIF4) == (DMA_ISR_TCIF4)) { WRITE_REG(DMA1->IFCR, DMA_IFCR_CTCIF4); fl_tx = 1; } else if(READ_BIT(DMA1->ISR, DMA_ISR_TEIF4) == (DMA_ISR_TEIF4)) { //Disable DMA channels CLEAR_BIT(DMA1_Channel4->CCR, DMA_CCR4_EN); CLEAR_BIT(DMA1_Channel5->CCR, DMA_CCR5_EN); } } //---------------------------------------------------------- void DMA1_Channel5_IRQHandler(void) { if(READ_BIT(DMA1->ISR, DMA_ISR_TCIF5) == (DMA_ISR_TCIF5)) { WRITE_REG(DMA1->IFCR, DMA_IFCR_CTCIF5); fl_rx = 1; } else if(READ_BIT(DMA1->ISR, DMA_ISR_TEIF5) == (DMA_ISR_TEIF5)) { //Disable DMA channels CLEAR_BIT(DMA1_Channel4->CCR, DMA_CCR4_EN); CLEAR_BIT(DMA1_Channel5->CCR, DMA_CCR5_EN); } } //---------------------------------------------------------- |
Здесь всё так же, как и в уроке 164.
Если мы попали в обработчик канала 4, значит это окончание передачи данных через DMA приёмного пакета. В данном случае, если это именно окончание, то мы сбрасываем флаг прерывания, а устанавливаем пользовательский по приёму, а если ошибка, то отключаем оба канала. В случае с каналом 5 всё то же самое, только флаг устанавливаем передачи.
В функции передачи пакета по шине USART в ПК удалим весь код из тела, а вместо него добавим вот такой
|
1 2 3 4 5 6 7 8 9 10 |
void USART_TX (uint8_t* dt, uint16_t sz) { //Disable DMA channel 4 CLEAR_BIT(DMA1_Channel4->CCR, DMA_CCR4_EN); //Set Number of data to transfer MODIFY_REG(DMA1_Channel4->CNDTR, DMA_CNDTR4_NDT, sz); //Enable DMA channel 4 SET_BIT(DMA1_Channel4->CCR, DMA_CCR4_EN); while (!fl_tx) {} fl_tx=0; |
Здесь мы сначала отключаем канал 4 DMA, затем устанавливаем размер пакета в соответствующем регистре DMA, затем включаем канал. Потом, дождавшись окончания передачи с помощью отслеживания установки пользовательского флага, сбрасываем его.
Ниже добавим функцию приёма пакета от ПК по шине USART
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//---------------------------------------------------------- void USART_RX (uint8_t* dt, uint16_t sz) { //Disable DMA channel 5 CLEAR_BIT(DMA1_Channel5->CCR, DMA_CCR5_EN); //Set Number of data to transfer MODIFY_REG(DMA1_Channel5->CNDTR, DMA_CNDTR5_NDT, sz); //Enable DMA channel 5 SET_BIT(DMA1_Channel5->CCR, DMA_CCR5_EN); while (!fl_rx) {} fl_rx=0; } //-------------------------------------------------------- |
Здесь всё происходит подобным образом, только используем мы уже канал 5 DMA и дожидаемся установки пользовательского флага приёма.
В бесконечный цикл функции main() добавим следующий код
|
1 2 3 4 5 6 7 8 9 10 |
while(1) { USART_RX(rx_str,256); USART_TX((uint8_t*)rx_str,strlen(tx_str)); Clear_7219(); for(i=0;i<=255;i++) { NumberR_7219(rx_str[(uint8_t)i]); delay_ms(10); } |
Здесь мы сначала принимаем пакет данных с массивом чисел из ПК, передаём нашу текстовую строку в ПК, далее мы гасим все разряды индикатора и в цикле с перерывом в 10 милисекунд выводи туда числа из принятого массива данных.
Соберём проект, прошьём контроллер, запустим терминальную программу, соединимся с портом и запустим тот же скрипт, который запускали в уроке 164.
Индикатор начнёт показ принятых чисел

А в терминальной программе начнут в ответ появляться наши строки, принятые из МК

Посмотрим также, как идёт передача данных по USART, в программе логического анализа
Всё передаётся без пропусков и непрерывно.
Итак, на данном занятии мы научились использовать периферию DMA для обеспечения передачи и приёма данных по USART, используя при этом возможности библиотеки CMSIS.
Всем спасибо за внимание!
Отладочную плату STM32F103C8T6 можно приобрести здесь STM32F103C8T6
Программатор недорогой можно купить здесь ST-Link V2
Индикатор светодиодный семиразрядный с драйвером MAX7219
Логический анализатор 16 каналов можно приобрести здесь
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)






why didn't you use dma circular mode?
Здравствуйте, непонятно зачем в функциях USART_RX , USART_TX ссылки на буфер,если они там не используются?