Меню Закрыть

Hal uart receive dma

Содержание

Урок 15

Сегодня мы продолжим занятия по подключению микроконтроллера STM32 к ПК посредством интерфейса USART.

Только в отличие от прошлых уроков мы применим для этого технологию DMA.

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

DMA (direct memory access) — прямой доступ к памяти. То есть мы из одной области памяти копируем данные в другую область памяти, либо из периферии в область памяти, либо из области памяти в периферию, но копируем не по одному байту, применяя при этом обязательно регистры АЛУ, а напрямую, без использования АЛУ. Этим самым достигается большее быстролействие, разгружается процессорное время. АЛУ лишь получает информацию о том, сколько определенных информационных единиц нужно переслать, а также адрес памяти источника и приемника, всё остальное уже происходит без его участия. Мы либо знаем приблизительно, сколько для этого требуется времени и исходя из этого уже строим свой алгоритм, либо пользуемся прерываниями и обрабатываем там событие окончания передачи через DMA.

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

Но у нас сегодня именно USART.

Проект был создан из USART_TRANSMIT, назван USART_TRANSMIT_DMA, схема также не изменилась.

Запускаем куб, в USART2 отключаем CTS и RTS, если они у вас были включены.

В настройках в Configuration включаем сначала общие прерывания. Затем заходим в DMA. В закладке DMA Settings добавляем DMA и настраиваем его следующим образом

Выбираем там USART2_TX, так как мы будем заниматься передачей и технологию DMA мы применим именно в передаче данных. В данном случае все потоки и каналы, а также направление работы DMA, Cube MX выберет за нас.

В DMA и в первом и во втором существует насколько каналов и несколько потоков, предназначенных для того, чтобы распределить всё шинам и интерфейсам. С каналами и так всё ясно. С потоками немного по-другому. Чтобы предотвратить различные коллизии в буфере FIFO в DMA, было создано таких целых 8 буферов и для каждого буфера был назначен свой поток. Посмотрим организацию DMA в STM32 4 серии в Reference Manual

Также в этой же технической документации отлично расписано, какой канал и поток для какого именно интерфейса работает

Таблица данная продолжается и на данной странице, также за ней следует подобная таблица и для второго DMA, но нам это уже не так интересно, так как наша периферия именно на данной странице и поток с каналом для передачи данных по нашей периферии выделен синим квадратом. То есть Cube MX не ошибся, выставив нам именно 6 поток.

Внизу в настройках DMA мы ничего не трогаем.

Оставляем режим Normal. Также существует ещё режим Circular, который заставляет заниматься DMA передачей данных циклично без остановки.

Также оставляем инкрементирование адреса только памяти. Адрес периферии мы в каждом цикле не прибавляем, мы работаем только с одной периферией и у неё только один адрес памяти.

Буфер FIFO мы не используем, поэтому мы не вклчаем его в нашем случае. Он нужен в случае, чтобы за один цикл передачи мы могли передавать и принимать потоками за один цикл, ещё и используя при этом специальный буфер. Это ещё больше ускоряет скорость передачи данных. Единицу передачи данных оставляем байт, нам так будет удобнее, хотя мы вполне можем передавать и полусловами, и даже словами (по 4 байта).

Также включаем в закладке NVIC на всякий случай прерывания

В главной функции main() для красоты исправляем переменную

/* USER CODE BEGIN 1 */

uint8_t str[]="USART Transmit DMA
";

/* USER CODE END 1 */

Находим функцию для передачи с использованием DMA следующим образом:

И в бесконечном цикле меняем также

/* USER CODE END WHILE */

Как, я думаю, все заметили, применена уже другая функция, предназначенная именно для передачи с использованием DMA.

Таймаут уже не нужен. В этом нам и помогают прерывания по окончанию передачи.

Собираем, открываем терминальную программу, жмём там Connect, прошиваем, смотрим

Все у нас передается.

Дисплей я не отключал, так как он потребуется нам для отображения уже принятых данных.

Для приема данных был создан другой проект из проекта USART_RECEIVE.

Имя нового проекта USART_RECEIVE_DMA.

Запускаем вновь созданный проект в CUBE, аналогичным образом заходим в Configuration, убедимся что у нас включены прерывания в USART2, затем заходим в DMA и настраиваем всё вот так:

У нас здесь уже выбрался поток 5, если мы заглянем в таблицу, которая дана была выше, то так оно и есть, По приёму у нас именно такой поток и используется. Также у нас, соответственно, изменилось и направление передачи — от периферии к памяти.

Применяем настройки, г енерируем проект. Открываем его.

Добавляем файл lcd.c

Изменим немного размер переменной в main()

/* USER CODE BEGIN 1 */

/* USER CODE END 1 */

У нас же дисплей на 20 символов в строке, вот и будем заполнять символами всю строку.

/* USER CODE END 2 */

В бесконечном цикле также меняем функцию и размер принятых данных, а также нолик ставим по индексу 20

Давайте попробуем собрать дисплей и проверить работоспособность нашего кода

Как мы видим, передавать с ПК мы можем уже не обязательно полностью по 20 символов сразу, то есть поток передачи уже, не взирая на то, что у нас стоит именно 20 байт в функции, напрямик передаёт в память дисплея байты в любом количестве, просто когда в совокупности будет передано именно 20, то тогда шина USART перейдёт в состояние, при котором она разрешит опять к ней обращаться снаружи, и ПК сможет дальше передавать данные. Передадим ещё такие же 10 байтов

Как мы видим, строка у нас заполнилась до конца.

Попробуем передать какие-нибудь другие символы, всё передается.

Только теперь строка if(huart2.RxXferCount==0) теряет свой смысл, т.к. проверяемый параметр в случае использования DMA всегда будет в нуле. Что же нам тогда проверять, чтобы выводить строку, когда она будет полностью заполнена?

На помощь нам придет дебагер.

Соберем проект, запускаем дебаггер, ставим точку останова тут в файле stm32f4xx_it.c

В терминале передаем строку например «123456789» — в прерывание не попадаем, а как только мы передадим 10й символ, то попадем в прерывание. Шагаем дальше внутрь функции HAL_DMA_IRQHandler(&hdma_usart2_rx)

В окно переменных для отслеживания добавляем строку hdma_usart2_rx – это параметр нашей функции-обработчика, также можно добавить hdma, что в принципе одно и то же. И ждем когда у данной структуры изменится параметр State. Перед изменением запомним текущий статус – он будет

0x02 HAL_DMA_STATE_BUSY. Когда он изменится, он примет вид 0x31 HAL_DMA_STATE_READY_HALF_MEM0.

Это означает, что у нас прерывание вызвало заполнение половины памяти DMA. Запускаем дальше код.

В терминале опять сначала отправляем «123456789», затем вводим один символ – он получается будет уже для строки и для памяти DMA 20й – попадаем опять в обработчик прерывания от DMA.

Шагаем опять до изменения статуса. Теперь он у нас будет 0x11 HAL_DMA_STATE_READY_MEM0.

Вот это как раз то что нам и нужно. Его мы и будем отслеживать В нашем условии в бесконечном цикле меняем на

Читайте также:  Asus p5ld2 x 1333 характеристики

Также в конце обработки условия добавим сброс этого флага в вид по умолчанию HAL_DMA_STATE_BUSY

/* USER CODE END WHILE */

Собираем, прошиваем, смотрим. Проверяем терминалом

Исходный код

Смотреть ВИДЕОУРОК

11 комментариев на “ STM Урок 15. HAL. USART. DMA ”

Меня зовут Саболч из Венгрии.
Я недавно начал используеться STM32. Но я всегда смотрю твои видео, мне очень нравятся, полезные.

Я хотел бы спросить твой помощь, у меня есть проект в университете;

Мне надо используеться ADC c DMA (это хорошо работает), и SPI или I2C TX c DMA. не могу новые данные отправлено из памяти. Я пробавал режим Circular,но не могу обновление данные в этом адресе.

надеюсь ты можешь помочь.
Я жду твоего ответа

Здравтсвуйте!
Спасибо за интерес к моим ресурсам!
А как объявлена переменная data?

Спасибо за ответ,

data глобальная (uint32_t)

Я хочу независимая работа от CPU как ADC_DMA работает.
CPU работает только, если отправлено значение.
По твоему, это возможно?
Мне нужно очень быстрая работа.

(Извиный не так хорошо говорю по русски)

Здравствуйте, изучая данный урок, я столкнулся с тем, что если по какой-то причине данных пришло больше чем объявлено в функции HAL_UART_Receive_DMA(), то запись в массив str[] сбивается даже в том случае если следующий пакет данных правильного размера. Подскажите, пожалуйста, какие есть механизмы отлова и исправления такого рода ошибок?

Евгений, начните с логического анализа приходящего потока.

Владимир, спасибо за ответ. Всю неделю я пытался разобраться с тем, что же происходит при такой работе UART + DMA. Вот как я понимаю, пожалуйста, поправьте меня если я где то не прав: Вызывая функцию HAL_UART_Receive_DMA() мы передаем ей ожидаемое кол-во байт для приема. Этот параметр устанавливает внутренний счетчик DMA, (регистр NDT). Для каждого байта, который он получает, счетчик уменьшается на единицу, пока не достигнет нуля. Когда счетчик становится равен нулю происходит передача данных. По всей видимости в этот момент мы попадаем в условие (hdma_usart2_rx.State==HAL_DMA_STATE_READY_MEM0), где мы опять передаем в функцию HAL_UART_Receive_DMA() ожидаемое кол-во байт для следующего приема. Пока все хорошо, однако если мы намеренно посылаем пакет большего размера чем у нас заявлено, например, вместо четырех байт отправим пять, то массив принятых данных сбивается, «лишние» эл-ты становятся на первые места в массиве, я отследил в отладчике что в этом случае происходит следующее: счетчик NDT достигает 0, вызывается прерывание, сразу же счетчик становится равен заданному значению и дочитываются оставшиеся байты, их значения заносятся в массив str[] на первые места (так как будто DMA работает в циклическом режиме!). А этого быть не должно т.к. у нас режим работы DMA – нормальный. Ситуацию спасает задержка + время на вывод строки, при их наличии все работает корректно.

If (hdma_usart2_rx.State==HAL_DMA_STATE_READY_MEM0)
<
HAL_Delay(100);
LCD_String(str);
HAL_UART_Receive_DMA(&huart2, (uint8_t*) str, 20);
hdma_usart2_rx.State=HAL_DMA_STATE_BUSY;
>

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

Здравствуйте, при компиляции примера Keil выдает одну ошибку (../Src/main.c(114): error: #20: identifier «HAL_DMA_STATE_READY_MEM0» is undefined) подскажите пожалуйста в чём может быть проблема.

Здравствуйте!
Скорей всего со времён урока у текло много воды и в библиотеках HAL что-то изменилось.

я нашел такой ответ в интернете (и попробовал):

volatile bool full_received = false;

while(!full_data); // wait all 20 bytes to be received
full_data = false;

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
<
full_data = true;
return;
>

Работает как надо. Последняя функция вызывается когда все байты уже заполнены, то есть, код не работает, пока все байты не получены.

Да, да, спасибо!
Мы уже так и сделали на F103 в одном из последних уроков.

Здравствуйте! У меня работает при HAL_DMA_STATE_READY вместо HAL_DMA_READY_MEM0. В новой версии так.

USART (Universal Synchronous-Asynchronous Receiver/Transmitter) — универсальный синхронно-асинхронный приёмопередатчик, интерфейс для для передачи данных между цифровыми устройствами. Он на столько универсальный, что даже провода по которым к вам домой приходит интернет, это тоже USART (RS485).

USART микроконтроллера stm32 может работать в различных режимах — асинхронный, синхронный, полудуплекс и т.д.

Asynchronous

Это самый распространённый режим, устройства соединяются так…

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

Тут следует немного пояснить работу USART’а для упрощения понимания некоторых настроек.

Мы будем передавать 8-ми битные байты (в отдельных случаях байты могут 9-ти битные), на скорости 115200 бит в секунду…


Parity — использование бита проверки чётности (см. ниже).
Stop Bits — количество стоповых битов (1 или 2).
Data Direction — включается приём и передача, или что-то одно.
Over Sampling — см. ниже.

Получаемый пакет из восьми бит (один байт) выглядит так…


Отправляемые/получаемые байты отделяются друг от друга стартовыми и стоповыми битами.

Если ничего не передаётся, то линия находится в состоянии HIGH. Передатчик, начиная отправку, прижимает линию к «земле», а приёмник расценивает это как начало пакета — START (стартовый бит). Далее идёт приём (отсчитывая биты) и после последнего (D7) бита проверяется что линия установлена в HIGH — STOP (стоповый бит). Иногда для большей достоверности окончания приёма используются два стоповых бита.

Количество принятых бит подсчитывается следующим образом: как только появляется стартовый бит (переход с HIGH на LOW) сразу же начинается отсчёт — 16 тиков на каждый бит…


Over Sampling — 16 Samples.

Эти 16 семплов «прощупывают» уровень линии, и если в момент предполагаемой середины бита (MIDBIT) линия находится в состоянии HIGH, то в соответствующий разряд приёмного регистра записывается единичка, если LOW, то нолик. За это отвечают три центральных семпла…

Другие семплы сигнализирует об ошибках. Например, если первые два семпла покажут что линия находится в состоянии HIGH, а следующие два скажут что она LOW, и снова HIGH, то микроконтроллеру станет совершенно очевидно, что при передаче в линию вклинились помехи. В результате вместо байта мы получим сообщение об ошибке — HAL_UART_ERROR_NE — шум в линии. Если же нужные нам семплы «съезжают» в сторону, в право или влево, то это будет означать, что у передающего устройства «плавает» частота, а мы получим ошибку кадра — HAL_UART_ERROR_FE.

Вот таким вот нехитрым образом происходит принятие одного байта.

Я не могу говорить за все микроконтроллеры stm32, но например на F303 можно указать частоту семплирования либо 16 либо 8. За счёт уменьшения семплирования можно повышать скорость USART’а. Каким образом — да очень просто: USART_1 тактируется от шины APB2, на каждый бит нам нужно по 16 тиков, значит при частоте шины 72МГц и семплирований 16, мы можем получить максимальную скорость USART’а равную 4.5 Mбит/с (72000000 / 16 = 4500000). Если же указать семплирование 8, то теоретически мы получим скорость 9 Mбит/с.

Parity — бит чётности. С помощью этого бита можно проверять целостность полученного пакета.

Пакет с добавленным битом чётности выглядит так…


Word Length нужно указать 9 Bits. Если оставить 8 Bits, то полезных битов будет 7.

При отправке в этот бит записывается единица или ноль в зависимости от количества единичек в предыдущих восьми битах. Если единичек чётное кол-во, то записывается 0, если нечётное, то 1.

Если отправляется 10111101 (шесть единичек — чётное количество), то бит чётности будет . Пакет будет выглядеть так — 101111010.

Читайте также:  Древние свитки 4 обливион

Если отправляется 01110011 (пять единичек — нечётное количество), то бит чётности будет 1. Пакет будет выглядеть так — 011100111.

При приёме такого пакета производится проверка — соответствует ли количество единичек значению в девятом бите, и если не соответствует, то пакет считается повреждённым — ошибка контроля чётности (HAL_UART_ERROR_PE).

Вычисления производятся с помощью битовой операции XOR (исключающее «или»).

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

С помощью опций — Even и Odd , можно выбрать что будет считаться чётным.

Hardware Flow Control — это контроль передачи данных (с помощью дополнительных линий) используемый, например, для соединения через COM-порт. Оставьте Disable , нам это не понадобится.

Если устройство хочет прекратить передачу данных, оно устанавливает состояние сигнала RTS (Request To Send) в «ноль» (-12 вольт). Это означает — «не посылать запросы ко мне» (прекратить передачу). Когда устройство готово для принятия очередной партии данных оно устанавливает сигнал RTS в «единицу» (+12 вольт) и обмен данными возобновляется.

Сигналы контроля передачи данных всегда посылаются в противоположном направлении от потока данных. DCE устройства (модемы) работают по тому же принципу, только посылают сигнал на контакте CTS (Clear To Send). Поэтому тип контроля передачи данных RTS/CTS использует 2 линии.

Advanced Features — здесь можно включить автоопределение скорости (Auto Baudrate) и отключить сообщения об ошибках (Overrun, DMA on RX Error), остальные пункты, это различные специфические настройки, которые не стоит менять. Оставьте всё по умолчанию.

Почитать про USART подробно здесь и здесь.

Теперь можно попрограммировать.

Чтобы принять или отправить данные достаточно описанных выше настроек.

Отправить строку «Hello World» можно так:

Первый аргумент — указатель на структуру USART’а, второй — строка (приведённая к указателю), третий кол-во символов в отправляемой строке, четвёртый — таймаут в миллисекундах (если по каким-то причинам не удастся выполнить отправку в течении секунды, то вернётся ошибка).

Эта функция блокирующая, то есть пока она не выполнится программа будет приостановлена.

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

Включите глобальное прерывание…

Отправлять данные так:

Здесь нет таймаута так как нам ничего не нужно ждать — отправили и забыли. Остальные аргументы те же, что и у блокирующей функции.

По окончании отправки сработает прерывание и вызовет колбек:

Второй аргумент — указатель на массив, третий — длина строки в массиве.

Аргументы те же, что и у предыдущей функции.

Колбек тот же что и HAL_UART_Transmit_IT(. ) , но тут есть нюанс. DMA вызывает два прерывания, первое после отправки половины буфера, а второе при завершении. Чтоб отключить половинку, нужно в файле stm32f1xx_hal_uart.c найти функцию HAL_UART_Transmit_DMA(. ) и закомментировать строчку…

Больше про отправку сказать нечего.

Для приёма нужно сделать так:

Второй аргумент — указатель на массив в который будут записываться полученные данные, третий — кол-во байт, которые нужно принять, последний — таймаут.

Эта функция тоже блокирующая, она будет ожидать до тех пор пока не получит все запрошенные байты, или пока не истечёт таймаут.

Если для отправки данных блокирующая функция вполне подходит, то для приёма совсем не годится (мы ведь не знаем когда придут данные, если конечно они не идут сплошным потоком). Поэтому нужно использовать функцию вызывающую прерывание…

Второй аргумент — указатель на массив в который будут записываться полученные данные, третий — кол-во байт, которые нужно принять.

Эта функция не блокирует программу. Прерывание произойдёт только после того, как всё запрошенные байты будут приняты.

Помните о том, что если посылаете данные из какого-то терминала, то этот терминал может посылать символы «новой строки» и «воврат каретки». То есть, если вы пошлёте 15 символов, а терминал добавит ещё свои, то программа получит первые 15, вызовет колбек, примет ещё (то что добавил терминал) и будет ожидать. Короче говоря, программа провернётся через колбек и будет ждать.

Включаем канал для приёма…

Второй аргумент — указатель на массив в который будут записываться полученные данные, третий — кол-во байт, которые нужно принять.

Колбек тот же что и при использовании HAL_UART_Receive_IT(. ) , и так же как и с отправкой, будут вызываться два прерывания — половина буфера и полный. Как отключить половинку вы уже знаете.

Поскольку при приёме/отправке могут возникать ошибки, то чтобы их отловить нужно создать специальный колбек…

Сюда приходят все ошибки USART’а.

HAL_UART_ERROR_PE — ошибка контроля чётности.

HAL_UART_ERROR_NE — шум в линии.

HAL_UART_ERROR_FE — ошибка кадра. У передающего устройства «плавает» частота.

HAL_UART_ERROR_ORE — ошибка переполнения. Возникает когда в USART приходит новый байт, а предыдущий ещё не был считан из приёмного регистра — Data Register (DR). Это происходит из-за того, что прерывание обрабатывается слишком медленно и программа не успевает очистить приёмный регистр.

HAL устроен так, что в случае ошибки приём будет перезапущен. Это можно отключить в файле stm32f1xx_hal_uart.c в функции void HAL_UART_IRQHandler(UART_HandleTypeDef *huart) …

С приёмом есть заморочка, мы можем не знать сколько данных должно прилететь, следовательно непонятно какое кол-во байт указывать. Например, если мы укажем пять байт, а прилетят только три, то прерывание не сработает пока не придут оставшиеся два. На этот случай есть несколько способов приёма.

Если нужно время от времени получать один-два байта, то можно просто в начале программы (перед бесконечным циклом) вызвать функцию приёма…

… а в колбеке что-то поделывать, и вызывать снова…

Можно организовать кольцевой буфер.

Идея заключается в следующем: создаётся массив (размер указывается исходя из потребностей) , полученный байт записывается в этот массив, указатель сдвигается на следующую ячейку, следующий байт сохраняется в эту ячейку, указатель опять сдвигается, и так до тех пор пока буфер не заполнится. После того как буфер заполнился можно поступить двумя способами: первый — указатель перескакивает на нулевую ячейку и буфер заполняется заново постепенно затирая ранее полученные данные. То есть запись идёт по кругу без остановки. Второй — при заполнении буфер останавливается, а новые данные записываются в него по мере вычитывания замещая прочитанные.

И ещё один способ — воспользоваться флагом IDLE. Этот флаг устанавливается аппаратно при обнаружении незанятой линии. То есть если в приёмник поступает несколько байт подряд, а потом возникает пауза (линия находиться в состоянии HIGH некоторое время), то взводится флаг IDLE, который может генерировать прерывание.

Допустим мы точно не знаем сколько байт должны прийти, но знаем что их не больше 12, тогда делаем так…

Другое устройство пульнёт нам две строчки…

В результате сначала произойдёт прерывание от IDLE (в буфер запишется строка «Hello World»), а потом прерывание при заполнении буфера. В итоге в буфере будет лежать «Hello WorldHell» — 15 байт.

Если это делать через DMA…

… то вся работа, кроме обработчиков двух прерываний, будет выполняться аппаратно. Не забудьте отключить половинку DMA.

Single Wire (Half-Duplex)

Команды те же, что и в обычном (асинхронном) режиме, только надо предварительно настроить линию на приём или передачу…

Можно использовать прерывания и DMA.

… то есть, если одно устройство отправляет данные, то другое должно находиться в режиме приёма.

Линия RX соединена с TX внутри микроконтроллера.

Читайте также:  Товары для лада гранта на алиэкспресс
MultiProcessor Communication

В этом режиме можно соединить несколько устройств. Одно устройство выступает в роли Мастера, а другие являются Слейвами. Мастер обращается к слейвам отправляя им их адреса — слейв видя что это его адрес принимает полезные данные.

Подключение обычное, как в асинхронном режиме…

Настройка в CubeMX…

World Lenght — девятибитный режим. Можно и восьмибитный (см. в конце главы).

В момент старта слейв находится в так называемом «тихом» режиме, то есть он не будет принимать полезные данные (принимаются только адреса) пока не поймёт, что они адресованы именно ему. Как только слейв видит что данные адресованы ему, он выходит (Wake-Up) из «тихого» режима и начинает приём полезных данных.

Wake-Up Method ⇨ Address Mark — слейвы будут выходить из «тихого» режима при получении своего адреса.

Wake-Up Address — адрес устройства. Для мастера укажите — , а для слейвов1, 2 и т.д.

Если у вас только два устройства, один мастер и один слейв, тогда подтяните к «плюсу» резистором 10кОм TX мастера. Без этого у меня не работало. Если слейвов несколько, то всё окей.

• Мастер может отравлять данные любому слейву (предварительно отправляя адрес), и получать данные от любого слейва.
• Слейвы слушают линию, и если прилетает правильный адрес, то принимают данные следующие за этим адресом.
• Слейвы могут отправлять данные только мастеру.
• Мастер запускается в обычном режиме, то есть ни каких специальных команд не требуется.
• Слейв запускается в «тихом» режиме.

Для запуска в «тихом» режиме нужно перед бесконечным циклом сделать так…

Пока ничего делать не нужно — команды приведены в ознакомительных целях.

Слейвы в «тихом» режиме ожидают адрес. Мастер, чтобы отправить кому-то данные, сначала отправляет в линию адрес, а затем данные.

Адреса и полезные данные передаются в девятибитном формате (9-ти битный байт). Если отправляется байт с адресом, то в первых восьми битах хранится сам адрес, а в девятый бит записывается единица. Если отправляется байт данных, то в девятый бит записывается ноль. То есть, приёмное устройство отличает байт с данными от байта с адресом по тому, что записано в девятом бите.

Адресный байт мастер отправляет так:

Поскольку байт девятибитный, то переменная 16-ти битная. Адрес первого слейва — 1, записываем его в переменную, а девятый бит превращаем в единицу.

Для слейва с адресом 2 команда будет такая:

После того как адрес отправлен, слейвы проверяют его (это происходит аппаратно, без нашего участия). Тот слейв, чей адрес совпал, переходит из «тихого» режима в обычный и начинает приём данных, которые мы шлём вслед за адресом…

Не обязательно слать один байт, можно отправлять массив, и сколько угодно раз. То есть дальше идёт передача как в обычном режиме, только в 9-ый бит каждого байта будет автоматически (без нашего участия) записываться ноль, чтоб принимающая сторона понимала, что это данные, а не адрес.

Остальные слейвы в это время по прежнему находятся в «тихом» режиме и только проверяют последний бит каждого байта — если там ноль, то пакет выбрасывается, если единичка, то смотрят что за адрес. Это происходит на аппаратном уровне.

Теперь, если мастер пошлёт адрес другого слейва…

… то принимать данные будет слейв с адресом 2, а слейв с адресом 1 перейдёт в «тихий» режим.

Вот наглядная схема…

Здесь показано как ведёт себя слейв с адресом 1. Входит в «тихий» режим (RWU written), получает адрес (Addr=0) и игнорирует его вместе с последующими байтами данных, затем получает адрес 1 (Addr=1) и переходит в режим приёма (получает два байта данных — Data 3 и Data 4), а когда получает адрес 2 (Addr=2), то автоматически уходит в «тихий» режим.

Помимо автоматических переходов в «тихий» режим и обратно, это можно делать программно.

Слейвы могут посылать данные мастеру в любой момент без каких-либо адресов. Мастеру конечно можно назначить адрес, но в этом нет смысла так как он один.

В колбеке ловим данные от мастера и выводим их в USART_2 (основной USART_1).

В бесконечном цикле раз в секунду отправляем мастеру букву U.

В колбеке ловим букву U и моргаем лампочкой.

В цикле отправляем данные на разные адреса.

Слейв 1 полчает это…

Восьмибитный режим тоже можно использовать. Всё происходит так же как и в девятибитном, а передаваемые данные будут семибитными — символы с 0x00 по 0x7F. Восьмой бит используется для определения адрес это или данные.

Если не передаёте какие-то специфичные символы, то можно использовать восьмибитный режим.

Адрес подготавливается так:

Это USART через инфракрасный передатчик. Такие штуки были в старинных сотовых телефонах, можно было что-то передавать с телефона на телефон или на компьютер. К компьютеру подключался инфракрасный приёмопередатчик и определялся как COM-порт. В общем можно связать два МК через USART «по воздуху».Функции для работы находятся в файле — stm32f1xx_hal_irda.c .

С остальными режимами я подробно не разбирался, поэтому просто коротенькая справка.

Synchronous — синхронный режим USART’а отличается от асинхронного тем, что частота синхронизации подается от одного устройства к другому по линии CLOCK. На сколько я понимаю, можно работать с SPI…

SmartCard — подключение к смарт-картам.

На этом всё.

Всем спасибо

В кубе включаем UART2 в асинхронном режиме, подтягиваем прерывание.

Заходим в настройки DMA1 и выбираем USART2_TX:

По поводу Stream 6. Заглядываем в референс мануал и находим раздел 10.3.3 Channel Selection. Таблица для DMA1 показывает, что USART2_TX обслуживается Channel 4 — Stream 6. Куб же настраивает это все автоматически.

Попробуем передать данные через функцию HAL_UART_Transmit_DMA. Параметры те же, что и в HAL_UART_Transmit, только без указания таймаута.

/* USER CODE BEGIN 1 */

uint8_t helloString[] = "USART Transmit DMA
";

/* USER CODE END 1 */

/* USER CODE BEGIN 2 */

/* USER CODE END 2 */

Контроллер должен отдавать при включении сообщение USART Transmit DMA .

Теперь в DMA подключаем и отправку.

При этом в настройках UART2 автоматически появляют включенные прерывания по DMA_TX и DMA_RX:

Для визуализации приема данных МК подключаем библиотеки LCD.

Добавляем приемный буфер:

/* USER CODE BEGIN 1 */

uint8_t helloString[] = "USART Transmit DMA
";

/* USER CODE END 1 */

17 значений в массиве: 16 по количеству символов, отображаемых дисплеем + 1 ячейка для символа конфа строки.

Инициализируем дисплей, отдаем приветствие и принимаем 16 байт данных.

/* USER CODE BEGIN 2 */

/* USER CODE END 2 */

Как только пакет из 16 байт будет получен, программа уйдет в прерывание и поменяет состояние своего флага hdma_usart2_rx.State на HAL_DMA_STATE_READY.

Отслеживая данный флаг, можно обрабатывать пакеты 16-байтных данных. В данном случае мы выводим содержимое буфера на дисплей:

/* USER CODE BEGIN WHILE */

/* USER CODE END WHILE */

/* USER CODE BEGIN 3 */

for(uint8_t i = 0; i

/* USER CODE END 3 */

В блок if программа не попадет до тех пор, пока флаг не примет значение HAL_DMA_STATE_READY, т.е. не будет получено 16 байт.

После этого необходимо снова запустить DMA ( HAL_UART_Receive_DMA ) и обязательно вручную выставить флаг HAL_DMA_STATE_BUSY.

Суть в том, что DMA работает независимо от процессора. И мы можем принимать и записывать данный в память без прерывания выполнения основной программы. Процессорное время понадобится только после того, как пакет данных будет записан полностью и потребуется его как-то обработать.

Рекомендуем к прочтению

Добавить комментарий

Ваш адрес email не будет опубликован.