Продолжаем нашу работу с шиной USART контроллера STM32F1.
В данном уроке мы поработаем с возможностью использования периферии DMA при передаче данных по интерфейсу USART.
С передачей данных по USART с применением DMA мы уже немного знакомы из урока 15, только использовали мы при этом библиотеку HAL. Теперь нам предстоит работа с библиотекой LL, и так как в уроке 161 мы полностью познакомились с аппаратной частью шины USART, со всеми битами всех её регистров, а также шину DMA мы во всех её трёх режимах с применением библиотеки LL уже проработали в предыдущих уроках, то нам будет значительно легче, чем если бы мы изучали всё это с нуля.
Также в уроке 160 мы работали с периферией DMA с использованием библиотеки LL, применяя её к передаче данных по шине SPI. А, так как механизм работы с DMA при использовании её для передачи данных по USART очень похож на аналогичный механизм шины SPI, то некоторые моменты программирования мы можем брать и из этого урока.
Схема наша также не изменилась со времени трёх предыдущих уроков
Раз у нас всё есть, в том числе и немалый багаж теоретических знаний, то можно смело приступить к проекту, который был сделан из проекта прошлого урока с именем LL_USART_INT и новое имя ему было дано LL_USART_DMA. В новом проекте удалим файл stm32f1xx_it.c, чтобы нам не удалять потом из него прототипы несуществующих функций и чтобы он сгенерировался новый.
Откроем проект в Cube MX и отключим прерывания на шине USART1
Задействуем каналы DMA в данной шине на приём и передачу, оставив все их настройки по умолчанию
Также не забываем задействовать библиотеку LL для DMA
Сгенерируем проект, откроем его в Keil, подключим к дереву проекта файл max7219.c, настроим программатор на автоперезагрузку и отключим оптимизацию.
Попытаемся сначала собрать проект, затем перейдём в файл main.c и посмотрим сначала код тела функции инициализации шины DMA MX_DMA_Init, в котором мы увидим лишь включение тактирования шины и глобальных прерываний с настройкой их приоритетов. Значит, скорее всего вся инициализация DMA будет в функции инициализации шины USART MX_USART1_UART_Init.
Перейдём тогда в тело данной функции и посмотрим, что здесь добавилось в результате задействования периферии DMA.
Здесь и есть настройка каналов 4 и 5 периферии DMA1. Настройка практически такая как и DMA для SPI, отличается номерами каналов, а также размером одного элемента данных, в данном случае будет уже не полуслово, а байт
1 2 |
LL_DMA_SetPeriphSize(DMA1, LL_DMA_CHANNEL_5, LL_DMA_PDATAALIGN_BYTE); LL_DMA_SetMemorySize(DMA1, LL_DMA_CHANNEL_5, LL_DMA_MDATAALIGN_BYTE); |
Теперь по поводу настройки шины USART1. Соответственно, здесь уже не включаются прерывания и не устанавливаются их приоритеты. А больше никаких изменений здесь нет. Вся привязка каналов DMA к шине будет в пользовательском коде и ложится на плечи разработчика.
Прежде чем мы приступим к написанию кода, напишем новый скрипт для терминальной программы, чтобы нам затем было понятней, что нам принимать и как на это отвечать.
Код скрипта будет теперь следующий
1 2 3 4 5 6 7 8 9 10 11 12 |
program test; var i: byte; begin while (true) do begin for i:=0 to 255 do begin ComSendchr(i); end; Delay(5000); end; end. |
Мы будем передавать нарастающие числа непрерывно от 0 до 255, а затем ждём 5 секунд, тем временем наш контроллер примет данные и ответит какой-нибудь строкой также с применением DMA, а также отобразим поочерёдно принятые числа на индикаторе.
Перейдём теперь к коду в Keil.
В файле main.c удалим пока все глобальные переменные и массивы
char rx_str[30], tx_str[30], tmp_str[10];
uint8_t fl=0;
uint8_t dt1;
Вместо них добавим отдельные флаги на приём и передачу, а также инициализированный строчный массив для передачи на ПК, а также неинициализированный для приёма чисел с ПК
1 2 3 4 |
/* USER CODE BEGIN PV */ uint8_t fl_rx=0, fl_tx=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]; |
Также удалим функцию-обработчик USART1_RX_Callback вместе с телом и вместо неё добавим обработчики прерывания по полному окончанию передачи данных по каналам DMA, в которых только включим пользовательские флаги
1 2 3 4 5 6 7 8 9 10 |
/* USER CODE BEGIN 4 */ void DMA1_RecieveComplete(void) { fl_rx = 1; } //----------------------------------------- void DMA1_TransmitComplete(void) { fl_tx = 1; } |
Перейдём в файл stm32f1xx_it.c и добавим прототипы на наши пользовательские обработчики
1 2 3 |
/* USER CODE BEGIN PFP */ void DMA1_RecieveComplete(void); void DMA1_TransmitComplete(void); |
В обработчик прерывания DMA1_Channel4_IRQHandler (а это канал, который работает по передаче данных шины USART1) добавим следующий код
1 2 3 4 5 6 7 8 9 10 11 |
/* USER CODE BEGIN DMA1_Channel4_IRQn 0 */ if(LL_DMA_IsActiveFlag_TC4(DMA1)) { LL_DMA_ClearFlag_TC4(DMA1); DMA1_TransmitComplete(); } else if(LL_DMA_IsActiveFlag_TE4(DMA1)) { LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_4); LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_5); } |
В случае, если мы в данный обработчик попадаем при установленном флаге TCIF4 в регистре DMA1_ISR, то мы сбрасываем данный флаг при помощи установки флага CTCIF4 в регистре DMA1_IFCR и затем вызываем соответствующий пользовательский обработчик, а если попадаем при включенном флаге ошибки TEIF4, то отключаем оба канала.
Аналогичные действия проделываем и в обработчике DMA1_Channel5_IRQHandler канала, который настроен на приём по шине USART
1 2 3 4 5 6 7 8 9 10 11 |
/* USER CODE BEGIN DMA1_Channel5_IRQn 0 */ if(LL_DMA_IsActiveFlag_TC5(DMA1)) { LL_DMA_ClearFlag_TC5(DMA1); DMA1_RecieveComplete(); } else if(LL_DMA_IsActiveFlag_TE4(DMA1)) { LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_4); LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_5); } |
Вернёмся в файл main.c и в функции main() сначала удалим включение прерываний
LL_USART_EnableIT_RXNE(USART1);
LL_USART_EnableIT_ERROR(USART1);
Удалим также весь пользовательский код из бесконечного цикла.
Отключим каналы DMA и сбросим все флаги
1 2 3 4 5 6 7 |
Number_7219(87654321); LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_4); LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_5); LL_DMA_ClearFlag_TC4(DMA1); LL_DMA_ClearFlag_TE4(DMA1); LL_DMA_ClearFlag_TC5(DMA1); LL_DMA_ClearFlag_TE5(DMA1); |
При помощи специальных функций библиотеки LL разрешим обработку запросов от DMA по передаче и по приёму данных USART
1 2 3 |
LL_DMA_ClearFlag_TE5(DMA1); LL_USART_EnableDMAReq_RX(USART1); LL_USART_EnableDMAReq_TX(USART1); |
В результате установятся биты DMAR и DMAT регистра CR3.
Включим прерывания по окончанию передачи данных и по ошибке в каналах 4 и 5 DMA1
1 2 3 4 5 |
LL_USART_EnableDMAReq_TX(USART1); LL_DMA_EnableIT_TC(DMA1, LL_DMA_CHANNEL_4); LL_DMA_EnableIT_TE(DMA1, LL_DMA_CHANNEL_4); LL_DMA_EnableIT_TC(DMA1, LL_DMA_CHANNEL_5); LL_DMA_EnableIT_TE(DMA1, LL_DMA_CHANNEL_5); |
Сбросим все флаги на каналах
1 2 3 4 5 6 7 |
LL_DMA_EnableIT_TE(DMA1, LL_DMA_CHANNEL_5); LL_DMA_ClearFlag_GI5(DMA1); LL_DMA_ClearFlag_GI4(DMA1); LL_DMA_ClearFlag_TC4(DMA1); LL_DMA_ClearFlag_TE4(DMA1); LL_DMA_ClearFlag_TC5(DMA1); LL_DMA_ClearFlag_TE5(DMA1); |
Покажем нашему DMA адреса буферов
1 2 3 |
LL_DMA_ClearFlag_TE5(DMA1); LL_DMA_ConfigAddresses(DMA1, LL_DMA_CHANNEL_5, LL_USART_DMA_GetRegAddr(USART1), (uint32_t)&rx_str, LL_DMA_GetDataTransferDirection(DMA1, LL_DMA_CHANNEL_5)); LL_DMA_ConfigAddresses(DMA1, LL_DMA_CHANNEL_4, (uint32_t)&tx_str, LL_USART_DMA_GetRegAddr(USART1), LL_DMA_GetDataTransferDirection(DMA1, LL_DMA_CHANNEL_4)); |
Из тела функции USART_TX удалим весь код и добавим новый
1 2 3 4 5 6 7 |
void USART_TX (uint8_t* dt, uint16_t sz) { LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_4); LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_4, sz); LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_4); while (!fl_tx) {} fl_tx=0; |
В данном коде мы отключаем канал, настраиваем объём данных и затем заново включаем канал, тем самым и будет дана команда на начало передачи через по шине USART через DMA. Затем мы ждём установки флага, тем временем в данных фигурных скобках мы можем делать что угодно, данные будут отлично передаваться. При этом надо либо примерно знать, сколько длится цикл передачи, либо проверять флаг внутри цикла несколько раз.
Функцию USART_RX_TX_Str удалим вместе с телом и вместо неё добавим функцию для приёма данных, аналогичную функции для передачи, отличие будет лишь в номере канала и в имени пользовательского флага
1 2 3 4 5 6 7 8 9 10 |
//-------------------------------------------------------- void USART_RX (uint8_t* dt, uint16_t sz) { LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_5); LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_5, sz); LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_5); while (!fl_rx) {} fl_rx=0; } //-------------------------------------------------------- |
Перейдём в бесконечный цикл функции main() и примем там сначала данные из ПК
1 2 |
/* USER CODE BEGIN 3 */ USART_RX(rx_str,256); |
В ответ передадим нашу строку в ПК
1 2 |
USART_RX(rx_str,256); USART_TX((uint8_t*)rx_str,strlen(tx_str)); |
Затем поочерёдно отобразим числа, принятые из ПК
1 2 3 4 5 6 7 |
USART_TX((uint8_t*)rx_str,strlen(tx_str)); Clear_7219(); for(i=0;i<=255;i++) { NumberR_7219(rx_str[(uint8_t)i]); LL_mDelay(10); } |
Вот и весь код. После отображения данных и в начале бесконечного цикла задержек никаких не требуется, так как, пока не установятся флаги в обработчиках, мы будем висеть, ожидая их.
Соберём код, прошьём контроллер, соединимся с портом в терминальной программе и запустим наш скрипт.
Наши циферки на индикаторе побежали
В терминальную программу от контроллера строки также приходят
Посмотрим также, как идёт передача данных по USART, в программе логического анализа
Всё отлично! Данные передаются непрерывно в обе стороны.
Итак, на данном занятии мы научились использовать периферию DMA при передаче и приёме данных по шине USART, используя при этом библиотеку LL.
Всем спасибо за внимание!
Предыдущий урок Программирование МК STM32 Следующий урок
Отладочную плату STM32F103C8T6 можно приобрести здесь STM32F103C8T6
Программатор недорогой можно купить здесь ST-Link V2
Индикатор светодиодный семиразрядный с драйвером MAX7219
Логический анализатор 16 каналов можно приобрести здесь
Смотреть ВИДЕОУРОК (нажмите на картинку)
добрый день. сделал по вашему уроку, всё получилось, но вот когда решил повторить для usart2 начались проблемы, при приёме 256 символов, происходит ответ, а в ответе вместо строки из переменной:
char tx_str[] = «The universal synchronous asynchronous receiver transmitter (USART)\r\n»;
я получаю: TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT
в чем может быть проблема, я все флаги переименовал по номеру канала 6(rx) и 7(tx) вместо 4(tx) и 5(rx).
пробовал размер указывать 68 символов и всеравно тоже только короче. тоесть отправляется только 1й символ строки многократно и все.
в общем сделал так, взял ваш проект из куба, добавил в нем юсарт2, в кейле поправил каналы и флаги на 6-7 каналы, прошил и заработало, затем убрал юсарт1 и всё работает. проверил в кубе все идентично, в моем и вашем проекте не нашел различий, не понял в чем была проблема….
Обнаружил БАГ в коде урока.
В обработчике DMA1_Channel5_IRQn использована функция LL_DMA_IsActiveFlag_TE4 вместо LL_DMA_IsActiveFlag_TE5
Здравствуйте!
Уважаемые, подскажите пожалуйста. Почему в начале файла main.c при включении DMA сбрасываем все флаги, в том числе и флаг глобального прерывания
LL_DMA_ClearFlag_GI5(DMA1);
LL_DMA_ClearFlag_GI4(DMA1);
В процессе же обработки очередного прерывания DMA,глобальный флаг уже не стираем? Разве это не должно вызывать постоянное вхождение в обработчик?