Научимся управлять интерфейсом UART через HAL-функции с использованием прерываний. Разработаем несколько учебных проектов.
Предыдущий урок Список уроков Следующий урок
В уроке 20 при работе с UART мы использовали аппаратные прерывания. Контроллер в бесконечном цикле выполнял основную программу и “отвлекался” на обслуживание UART только при приходе или окончании передачи данного.
Очевидно, что такой способ работы с UART позволяет значительно разгрузить контроллер, уменьшить время реакции программы на поступление данных и т.п. Есть задачи, которые практически невозможно решить, используя только блокирующий режим.
Конечно, библиотека HAL поддерживает работу с UART не только в блокирующем режиме, но и с использованием прерываний. Опять рекомендую просмотреть в справочнике библиотеки HAL функции работы с UART, обращая внимание на функции использующие прерывания.
Инициализация, установка конфигурации UART для работы с использованием прерываний.
В уроке 21 мы инициализировали UART для работы в блокирующем режиме. В режиме с использованием прерываний конфигурация UART задается аналогично. Единственное отличие – надо разрешить прерывание UART.
Давайте сделаем это с помощью STM32CubeMX.
Создадим проект Lesson23_1.
Настроим систему тактирования на частоту 72 мГц. Конфигурируем вывод светодиода общего назначения (PC13) как выход.
Разрешим работу UART1 в асинхронном режиме (Connectivity -> USART1 -> Mode -> Asynchronous).
В окне Configuration выбираем закладку Parameter Settings и задаем параметры, как в уроке 21.
Для работы с использованием прерываний выбираем закладку NVIC Settings и разрешаем прерывания UART1.
Завершаем создание проекта.
Откроем проект в Atollic TrueStudio. STM32CubeMX выполнил для конфигурации UART абсолютно такие же действия, как в уроке 21. Только в файле stm32f1xx_hal_msp.c добавил разрешение прерываний UART1.
/* USART1 interrupt Init */
HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
И в stm32f1xx_it.h появился прототип обработчика прерываний
void USART1_IRQHandler(void);
Теперь можно работать с UART через HAL-функции с использованием прерываний.
Передача данных.
Для этого существует функция HAL_UART_Transmit_IT.
Формат функции:
HAL_StatusTypeDef HAL_UART_Transmit_IT (UART_HandleTypeDef * huart, uint8_t * pData, uint16_t Size)
У нее 3 параметра:
- huart – указатель на структуру конфигурации типа UART_HandleTypeDef. Мы будем использовать уже заданный экземпляр huart1.
- pData – указатель на буфер передаваемых данных.
- Size – количество данных, которые надо передать.
Функция возвращает значение состояния типа HAL_StatusTypeDef, описанное в уроке 21.
Данные передаются пакетом:
- через UART, заданным в структуре huart;
- из массива с указателем pData;
- длина пакета задана в Size.
По сравнению с аналогичной функцией для работы в блокирующем режиме отсутствует параметр “время тайм-аута операции”. Система будет бесконечно ожидать окончания передачи, но, не мешая работать основной программе.
Вот программа, передающая в цикле 24 байта. Аналог примера из урока 21.
/* USER CODE BEGIN WHILE */
uint8_t str[] = "Проверка передачи UART\r\n\0";
while (1) {
HAL_UART_Transmit_IT(&huart1, str, 24);
HAL_Delay(500);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
От соответствующего кода урока 21 программа отличается только именем функции передачи - HAL_UART_Transmit_IT вместо HAL_UART_Transmit. Но теперь программа не останавливается на время передачи данных.
- В блокирующем режиме программа “зависала” в функции HAL_UART_Transmit примерно на 24 мс, затем отрабатывала задержку 500 мс в HAL_Delay. Таким образом, цикл while выполнялся примерно за 524 мс.
- Программа этого урока инициирует передачу данных функцией HAL_UART_Transmit_IT и, не ожидая окончания передачи пакета, переходит к отработке задержки HAL_Delay. Передача данных происходит во время выполнения функции HAL_Delay. В этом случае период выполнения цикла while составляет примерно 500 мс.
В предыдущем примере при вызове функции HAL_UART_Transmit_IT мы не проверяем состояние процесса передачи. Мы уверены, что за паузу 500 мс все данные будут переданы. Но в случае, когда задержка отсутствует или пакет данных непредсказуемой длины вероятна ситуация вызова функции HAL_UART_Transmit_IT в тот момент, когда передача предыдущего пакета еще не закончилась.
Если вообще убрать задержку из цикла, то мы увидим, что передается тот же самый текст без искажений. Запросы передачи, пришедшие раньше времени просто игнорируются.
При вызове функции HAL_UART_Transmit_IT в случае, когда передача предыдущего пакета еще не завершилась, функция:
- игнорирует запуск передачи;
- возвращает значение HAL_BUSY.
При непредсказуемом вызове передачи необходимо проверять состояние UART. Вот строка, в которой происходит ожидание передачи предыдущего пакета и запуска передачи следующего.
while( HAL_UART_Transmit_IT(&huart1, str, 24) == HAL_BUSY );
Состояние процесса передачи UART также можно проверить с помощью функции HAL_UART_GetState. При занятом передачей UART функция возвращает значение HAL_UART_STATE_BUSY_TX.
Аналог предыдущего кода будет выглядеть так.
while( HAL_UART_GetState (&huart1) == HAL_UART_STATE_BUSY_TX ) ;
HAL_UART_Transmit_IT(&huart1, str, 24);
Следующий код осуществляет передачу данных в блокирующем режиме.
HAL_UART_Transmit_IT(&huart1, str, 24);
while( HAL_UART_GetState (&huart1) == HAL_UART_STATE_BUSY_TX ) ;
Программа зависает во второй строке до тех пор, пока не закончится передача. Естественно, это только пример для объяснения работы HAL_UART_GetState. В реальных программах проще использовать функцию для блокирующего режима.
Прервать передачу данных можно вызвав функции HAL_UART_AbortTransmit или HAL_UART_AbortTransmit_IT.
- В первом случае при выходе из функции HAL_UART_AbortTransmit передача будет прервана и UART готов для следующих операций.
- Во втором случае будет только инициирован процесс остановки передачи пакета. Выход из функции HAL_UART_AbortTransmit_IT не будет означать, что UART готов для следующей передачи. На это может потребоваться какое-то время. О завершении передачи сообщит вызов соответствующей Callback-функции. Но об этом позже.
Т.е. функции остановки процессов передачи и приема UART (HAL_UART_Abort ) тоже могут работать в блокирующем режиме и в режиме с использованием прерываний.
Следующая программа прерывает передачу пакета из 24 символов через 15 мс после начала передачи.
/* USER CODE BEGIN WHILE */
uint8_t str[] = "Проверка передачи UART\r\n\0";
while (1) {
HAL_UART_Transmit_IT(&huart1, str, 24);
HAL_Delay(15);
HAL_UART_AbortTransmit(&huart1);
HAL_Delay(500);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
В результате передается только часть данных.
Прием данных с использованием прерываний.
Для этой операции предназначена функция HAL_UART_Receive_IT.
Формат функции:
HAL_StatusTypeDef HAL_UART_Receive_IT (UART_HandleTypeDef * huart, uint8_t * pData, uint16_t Size)
У нее 3 параметра:
- huart – указатель на структуру конфигурации типа UART_HandleTypeDef. Мы будем использовать уже заданный экземпляр huart1.
- pData – указатель на буфер для принятых данных.
- Size – количество данных, которые необходимо принять.
Функция возвращает значение состояния типа HAL_StatusTypeDef, описанное в уроке 21.
Принимается пакет данных:
- через UART, заданным в структуре huart;
- сохраняются в массиве с указателем pData;
- длина пакета задана в Size.
В отличие от аналогичной функции приема данных в блокирующем режиме отсутствует параметр “время тайм-аута операции”. Система будет бесконечно ожидать прием данных, но, не мешая работать основной программе.
Функция HAL_UART_Receive_IT только инициирует прием данных. Если в случае передачи данных с использованием прерываний мы могли предсказать, когда закончится передача, то при приеме данных принципиально другая ситуация. Окончание приема зависит от поступления данных на вход UART от другого устройства. Т.е. в большинстве случаев это совершенно непредсказуемое событие, зависящее от внешних факторов.
Как следствие, проверка окончания приема данных практически всегда необходима. Выполняется она такими же способами, как и при передаче:
- проверкой ответа функции HAL_UART_Receive_IT;
- с использованием функции HAL_UART_GetState.
Вот простейшая программа, реализующая функции эхо-терминала.
uint8_t str[3];
while (1) {
if( HAL_UART_Receive_IT (&huart1, str, 1) != HAL_BUSY ) {
while( HAL_UART_Transmit_IT(&huart1, str, 1) == HAL_BUSY );
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
В цикле проверяется состояние приема. Если UART освобождается, значит данное принято. Тогда оно передается назад через UART.
При длинных пакетах данные начинают пропускаться.
В уроке 22 я показывал, что невозможно с использованием HAL-функций создать эхо-терминал, который будет возвращать бесконечное число подряд поступающих байтов.
При приеме очередного байта, передача ответа начнется с некоторой задержкой. После окончания передачи следующий байт уже будет принят и его передача еще сдвинется относительно приема. Таким образом, задержка между приемом и передачей байтов будет все время увеличиваться, пока байт не будет пропущен.
Проблему частично может решить кольцевой буфер, но он только отсрочит время пропуска байта.
Проверять состояние приема можно и с помощью функции HAL_UART_GetState.
uint8_t str[3];
while (1) {
uint8_t state = HAL_UART_GetState(&huart1);
if( (state != HAL_UART_STATE_BUSY_RX) && (state != HAL_UART_STATE_BUSY_TX_RX) ) {
while( HAL_UART_Transmit_IT(&huart1, str, 1) == HAL_BUSY );
HAL_UART_Receive_IT (&huart1, str, 1);
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
Обратите внимание, что для проверки окончания передачи используется конструкция
if( (state != HAL_UART_STATE_BUSY_RX) && (state != HAL_UART_STATE_BUSY_TX_RX) )
Если UART занят одновременно приемом и передачей, то функция HAL_UART_GetState вернет значение HAL_UART_STATE_BUSY_TX_RX. Это надо учитывать, когда UART может одновременно принимать и передавать данные. В примерах об UART в режиме передачи мы проверяли только ответ HAL_UART_STATE_BUSY_TX.
Искусственно прервать прием можно функциями HAL_UART_AbortReceive и HAL_UART_AbortReceive_IT . Относительно них справедливо все выше сказанное про аналогичные функции для передачи.
Все примеры, описанные выше, собраны в проекте Lesson23_1.
Очень важное замечание. При вызове функция HAL_UART_Receive_IT не сбрасывает содержимое регистра данных UART. Это приводит к тому, что если в UART было данное, принятое до вызова HAL_UART_Receive_IT, то оно будет считано этой функцией. Это позволяет делать короткие перерывы между вызовами HAL_UART_Receive_IT без потери данных.
Например, нам необходимо получать пакеты из 10 данных. Сами пакеты могут приходить в любое время, а данные пакета передаются подряд, без значительных задержек. Алгоритм приема может быть таким.
- Инициируем функцией HAL_UART_Receive_IT прием одного байта (Size=1);
- Бесконечное время ждем первый байт.
- После его приема инициируем прием 9 байтов уже с контролем времени.
- Если через заданное время оставшиеся 9 байтов не приходят, то завершаем прием функцией HAL_UART_AbortReceive.
Ниже я приведу пример разработки подобного алгоритма обмена.
Callback-функции (функции обратного вызова).
В предыдущих примерах урока как-то не чувствовалось особого преимущества применения функций HAL с использованием прерываний. Может быть, эффективно выглядела только передача пакета данных. Инициировали ее и продолжили выполнение основной программы. А в это время происходит передача. В остальных примерах мы постоянно что-то проверяли, чего-то ожидали в основном цикле.
По-настоящему "красиво" выглядела программа из урока 20. В ней все операции выполнялись в обработчиках прерываний. Основной цикл while был пустым.
Подобные программы, работающие в фоновом режиме, могут быть реализованы с использованием callback-функций.
Проблема предыдущих программ урока была в том, что мы постоянно контролировали состояние работы UART с помощью циклических опросов. Конечно, по сравнению с блокирующим режимом программа у нас не “подвисала”. Мы могли чередовать опросы с короткими действиями. Но все равно на опросы уходила масса времени.
Проблему можно решить с помощью функций обратного вызова или callback-функций. Переходы на них происходят по определенным событиям. Например, закончилась передача данных или произошла ошибка и т.д. Инициирует вызовы callback-функций HAL-библиотека. Это своеобразные программные прерывания.
Вызовы callback-функций происходят в обработчиках аппаратных прерываний UART. Более того, callback-функция это часть обработчика прерывания, ее выполнение – удлинение обработчика. Только при выходе из callback-функции управление вернется в обработчик прерывания, и оно будет завершено.
- Callback-функции удлиняют время обработки прерываний.
- Они должны выполняться за минимально короткое время. Обычно это установка флага или инициация процесса.
- Ожидание какого-либо флага циклическим опросом, так как мы делали в предыдущих примерах урока, совершенно недопустимо. Конструкции типа
while( HAL_UART_GetState (&huart1) == HAL_UART_STATE_BUSY_TX ) ;
работать не будут.
В HAL-библиотеке callback-функции объявлены с атрибутом weak. Например
__weak void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
Атрибут weak объявляет функцию как ”слабую” ссылку. Проще говоря, если встретится функция с таким же именем, но без атрибута weak, то она заменит функцию, объявленную с атрибутом weak.
Для нас это означает, что для определения callback-функции достаточно создать свое определение без атрибута weak. И наша функция заменит функцию HAL-библиотеки.
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) {
// тело функции
}
Больше никаких действий производить не надо.
Информация по callback-функциям UART есть в справочнике HAL-библиотеки. Сейчас нас интересуют функции:
HAL_UART_TxCpltCallback - вызывается по завершению передачи данных и
HAL_UART_RxCpltCallback – вызывается по окончанию приема пакета данных.
Эхо-терминал с использованием callback-функций.
Создадим эхо-терминал без кольцевого буфера и посмотрим, какой длины пакеты он сможет возвращать без искажений.
Начнем с того, что очистим основной цикл while. Наша цель – оставить его пустым.
Массив str объявим глобальной переменной. Теперь к нему придется обращаться из разных функций.
Добавим 2 переменные-признаки. Первый признак активен при получении нового данного. Второй – при завершении передачи данного, т.е. при свободном для передачи UART.
/* USER CODE BEGIN PV */
uint8_t str[3];
uint8_t dataReceived=0; // признак данное получено
uint8_t dataTransmitted=1; // признак данное передано
/* USER CODE END PV */
Перед циклом запустим прием данных. Иначе откуда нам ждать обратный вызов.
/* USER CODE BEGIN WHILE */
HAL_UART_Receive_IT (&huart1, str, 1);
while (1) {
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
Определим callback-функции приема и передачи данных.
/* USER CODE BEGIN 4 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if(huart == &huart1) {
dataReceived=1;
if( dataTransmitted != 0 ) {
HAL_UART_Transmit_IT(&huart1, str, 1);
dataReceived=0;
dataTransmitted=0;
}
HAL_UART_Receive_IT (&huart1, str, 1);
}
}
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) {
if(huart == &huart1) {
dataTransmitted=1;
if( dataReceived != 0 ) {
HAL_UART_Transmit_IT(&huart1, str, 1);
dataReceived=0;
dataTransmitted=0;
}
}
}
/* USER CODE END 4 */
При приеме данного мы:
- Устанавливаем признак dataReceived.
- Если UART свободен для передачи, то передаем данное.
При завершении передачи данного:
- Устанавливаем признак dataTransmitted.
- Если есть принятое данное, то передаем его.
Т.е. передача данного происходит:
- если данное принято и передатчик свободен;
- если передача завершена и данное принято.
Полностью проект можно загрузить здесь Lesson23_2.
Проверяем.
Я передал несколько одинаковых строк. Результат примерно одинаковый. Пропускается каждый 13-14й символ.
Для пакетов большей длины надо использовать кольцевой буфер.
Пример реализация простого протокола обмена данными с компьютером.
Повторим задачу из предыдущего урока. А именно, разработаем программу, которая обменивается данными с компьютером по протоколу из урока 48 Ардуино.
Только теперь основной цикл while должен оставаться пустым. Обмен с компьютером будет происходить в фоновом режиме.
Создадим переменные: массив приема данных, признак ожидания первого байта команды и структуру с данными для ответа компьютеру.
uint8_t buf[5];
uint8_t firstByteWait=1; // признак ожидание первого байта
struct {
float t; // температура
float u; // напряжение
uint8_t b; // состояние кнопки
uint8_t r; // резерв
uint16_t s; // контрольная сумма
} par = {36.6, 12.5, 1, 0};
Перед основным циклом запустим прием первого байта команды.
/* USER CODE BEGIN WHILE */
buf[0]=0; buf[1]=0; buf[2]=0;
firstByteWait=1; // признак ожидание первого байта
HAL_UART_Receive_IT (&huart1, buf, 1); // запуск приема
while (1) {
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
Цикл while остается пустым.
Создадим callback-функцию приема данных.
/* USER CODE BEGIN 4 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if(huart == &huart1) {
if( firstByteWait != 0 ) {
// пришел первый байт
firstByteWait=0;
HAL_UART_Receive_IT (&huart1, buf+1, 2); // запуск приема остальных байтов команды
}
else {
// принят весь пакет (3 байта)
// проверка команды
if ( (buf[0] == 0x10) && ((buf[0] ^ buf[1] ^ 0xe5) == buf[2]) ) {
// команда принята правильно
// подсчет контрольного кода ответа
uint16_t sum= 0;
for (uint16_t i=0; i<10; i++) sum += * ((uint8_t *)(& par) + i);
par.s = sum ^ 0xa1e3;
// ответ на компьютер
HAL_UART_Transmit_IT(&huart1, (uint8_t *)(& par), 12);
// запуск приема
buf[0]=0; buf[1]=0; buf[2]=0;
firstByteWait=1;
HAL_UART_Receive_IT (&huart1, buf, 1);
}
else {
// ошибка
buf[0]=0; buf[1]=0; buf[2]=0;
firstByteWait=1;
HAL_UART_Receive_IT (&huart1, buf, 1); // запуск приема
}
}
}
}
/* USER CODE END 4 */
Эта функция вызывается как при приеме первого данного, так и при завершении приема всего пакета (еще 2х байтов). Признак firstByteWait позволяет разделить эти события.
- Если принят первый байт, то мы запускаем прием остальных 2 байтов команды.
- Если получена вся команда, то проверяем контрольный код команды, инициируем ответ и запускаем прием первого байта.
Запускаем программу верхнего уровня из урока 48 Ардуино, проверяем работу контроллера.
Все работает. Но, если перед запуском программы верхнего уровня послать на контроллер какой-нибудь байт, например, с помощью CoolTerm, то обмен перестает работать. Ни одно правильного пакета. Сплошные ошибки.
Мы упростили себе задачу и не стали проверять целостность байтов команды во времени.
Происходит следующее. Мы послали один “случайный” байт. Контроллер принял его и стал ожидать оставшиеся 2 байта команды. Причем ожидать бесконечно. Потом запустили программу верхнего уровня и компьютер начал циклически передавать команды. Но каждый первый байт от компьютера воспринимался как второй. Произошел сдвиг ожидаемых данных, который будет существовать бесконечно. Вывести из этого состояния систему можно только сбросом или подачей недостающих 2 байтов.
Для исключения такой ситуации необходимо ограничивать время получения команды. Если пришел первый байт, то оставшиеся 2 должны быть получены в течение ограниченного времени. Иначе необходимо фиксировать ошибку.
Такой алгоритм можно реализовать различными способами. Я думаю, самый простой – это контролировать состояние признака firstByteWait. Когда контроллер ожидает прихода 2х оставшихся байтов команды, этот признак находится в состоянии 0. Если это состояние длится более определенного времени (времени тайм-аута), то необходимо прервать прием данных.
Расточительно тратить на отсчет времени тайм-аута аппаратный таймер. Давайте использовать программный.
Не вдаваясь в подробности, скажу, что в файле stm32f1xx_it.c есть обработчик прерывания тиков системного времени void SysTick_Handler(void). Он вызывается с периодом 1 мс. Давайте в нем и создадим программный таймер контроля времени приема команды.
void SysTick_Handler(void) {
/* USER CODE BEGIN SysTick_IRQn 0 */
if( firstByteWait != 0 ) timeOut=0;
else {
timeOut++;
if( timeOut >= 10 ) {
// ошибка таймаута
HAL_UART_AbortReceive_IT(&huart1); // остановка приема
firstByteWait=1; // признак ожидание первого байта
timeOut=0;
HAL_UART_Receive_IT (&huart1, (uint8_t *)buf, 1); // запуск приема
}
}
/* USER CODE END SysTick_IRQn 0 */
Алгоритм простой.
- Если признак firstByteWait не равен 0 (ожидается первый байт), то мы сбрасываем счетчик timeout.
- Если система находится в ожидании оставшихся байтов команды, то мы увеличиваем счетчик на 1 и проверяем, не достиг ли он 10.
- Если значение счетчика превышает или равно 10, это означает, что признак firstByteWait был в ненулевом состоянии не менее 10 мс.
- Тогда мы прерываем прием команды.
Полностью проект можно загрузить здесь Lesson23_3.
Теперь “лишний” принятый бай не “подвешивает” обмен. Я проверил.
По поводу библиотеки LL.
Функции библиотеки LL для управления UART принципиально отличаются от HAL/UART-функций. Они обеспечивают только доступ к регистрам и отдельным битам UART. Работа с ними ничем не отличается от управления UART с использованием регистров CMSIS. Об этом написано в уроке 20.
Поэтому я не буду создавать урок по LL-библиотеке для UART. А только опишу функции LL в справочнике.
О работе UART с использованием прямого доступа к памяти я расскажу позже после изучения контроллера DMA.
А этот вызов приёма с поддержкой прерывания разве не будет блокировать основную программу?
while( HAL_UART_Transmit_IT(&huart1, str, 24) == HAL_BUSY );
Здесь же ожидание в цикле.
Здравствуйте!
Да, конечно. Но это учебный пример.
изивините, но не могу понять: для чего выставляете тактировку на 72МГц ?
Здравствуйте!
Это учебные проекты. Для них я использую максимальную частоту.
Почему-то у меня принимает только нули, какие бы я не посылал данные — хоть цифры, хоть латиницу http://joxi.ru/ZrJb3LEHbQw1kr
Как начать принимать пакет с начала, а не с середины, если потом данных уже передается, но между посылками есть промежуток времени?
Здравствуйте!
Обычно протоколы требуют в начале пакета большую паузу. Необходимо дождаться, пока окончится передача пакета, т.е. данные не передаются заданное время. И дальше ожидать начало пакета.
А как лучше с DMA USARt передачу или приём?
Здравствуйте!
Как я за вас могу решить. Смотрите по задаче.
При выполнении этого кода:
uint8_t str[] = «Проверка передачи UART\r\n\0»;
while (1)
{
HAL_UART_Transmit_IT(&huart1, str, 24);
HAL_Delay(500);
}
В терминале получаю следующее:
?Проверка пер
В чём может быть дело?
Здравствуйте!
А мой проект из урока работает?
Поменял русские буквы на англиские и всё заработало
Видимо русские буквы кодируются 2 байтами (не знаю какая кодировка у Вас), поэтому длина сообщения не 24 а 40 байт.
Для кодирования русских символов надо два байта, соотвественно один русский символ при передаче занимает 2 байта. У вас передаётся 11 русских символов (11*2=22 байта) + 2 обычных символа.
Подскажите, в файле stm32f1xx_it.c как скроссировать переменные из main.c?
Говорит, что firstByteWait и timeout — undeclared.
А. Всё. Разобрался.
В разделе /* External variables*/
надо написать:
extern uint8_t firstByteWait;
Здравствуйте. Объясните еще раз, пожалуйста, как работает hal для передачи с прер-ем. Он начинает передачу по прерыванию (одного из флагов txe, rnxe и еще один флаг для сдвигателя) или после передачи генерирует прерывание? И почему у нас обработчик прерывания пустой? Такое ощущение, что эта передача все равно что просто писать в выходной буфер с задержкой после записи.
Здравствуйте!
Если не вдаваться во внутренние алгоритмы HAL-библиотеки, то вы вызываете функцию HAL_UART_Transmit_IT, указываете буфер данных и количество байтов для передачи. Все остальное делает HAL-библиотека. Передача происходит в фоновом режиме. Окончание передачи можно проверить функцией HAL_UART_GetState или выдержать время, необходимое для передачи.
HAL-библиотека загружает байт в передатчик UART и возвращается к основной программе. По освобождению буферного регистра данных UART (флаг txe), возникает прерывание, по которому HAL загружает в регистр данных UART следующий байт и опять возвращается к основной программе. По завершению передачи данных вызывается функция HAL_UART_TxCpltCallback, если вы ее определили.
понял принял, спасибо
хотя нет, есть еще вопрос. При первом вызове transmit_it он возвращает ок? Не могу понять вот эту строку
while( HAL_UART_Transmit_IT(&huart1, str, 24) == HAL_BUSY );
вызываем функцию, а она занята
т.е. ждет передачи опустошения передающего буфера, передалось содержимое, передает след,, и тд пока не передаст весь пакет. Так вот когда он передастся, вызов функции вернет ок? Тогда ок!=Busy и мы выходим из цикла? Итого два одинаковых пакета передалось?(Еще раз извиняюсь за вопросы)
Здравствуйте!
В цикле вызываем функцию HAL_UART_Transmit_IT.
Если она возвращает значение HAL_BUSY, то бесконечный цикл продолжается. При этом передачи не происходит.
В случае возврата другого значения, происходит запуск процесса передачи пакета, и программа выходит из цикла.
Функция abortReceive предназначена для прерывания передачи в блокирующем режиме.
И еще вопрос. Почему в обработчике systick мы используем abortREceiv_IT, а не просто abortReceive?
// ответ на компьютер
HAL_UART_Transmit_IT(&huart1, (uint8_t *)(& par), 12);
запускаем по флагу в основном цикле while, а не в обработчике прерывания, также можно сделать и на прием, должно избавить от разсинхрона
Здравствуйте!
Я скопировал Вашу функцию Callback’ а, но у меня не происходит приëм остальных байт. Подскажите, пожалуйста, в чëм может быть проблема?
Здравствуйте!
Попробуйте весь проект копировать и запустить.
Здравствуйте!
В уроке рассказывается про проблему с потерей информации эхо-терминала, но не указывается природа этой проблему (кроме как того факта что МК должен выполнять несколько больше команд при приеме-отправке). Не могли бы, пожалуйста, рассказать подробнее почему так происходит?
Здравствуйте!
Сразу и не вспомню, что я писал по этому поводу.
В общих чертах. Потеря информации с эхо-терминала может произойти по причине не получения ответа от ведомого устройства. Само устройство не ответило, оборвалась линия связи и т.п. В этом случае ведущее устройство будет бесконечно ждать ответа и висеть в этом режиме.
Для исключения такой ситуации надо отрабатывать тайм-аут приема. Т.е. если на ведущее устройство в течение определенного времени не придет ответ необходимо считать, что обмен был неудачным и переходить к последующим действиям. Например, запросить еще раз.
Не, не про то вопрос)
В уроке приводится пример где в обмене МК «не успевает» за передатчиком:
«При длинных пакетах данные начинают пропускаться.»
«Я передал несколько одинаковых строк. Результат примерно одинаковый. Пропускается каждый 13-14й символ.»
Не очень понятно причины этой проблемы(
И второй вопрос, возможно связанный с первым — на практике оказалось что после передачи побайтно с помощью HAL_UART_Transmit_IT и callback-функции время посылки увеличивается на время передачи одного символа (т.е. на 9600 это 150мкс, на 1200 — это 830 мкс и т.д.). На осциллограмме это выглядит как будто стоповый бит увеличивается на 1 символ.
Соответственно если так передавать десятки байт то расхождение становится критическим.
Если передавать массив целиком (например посредством HAL_UART_Transmit_IT(&huart1, str, 24);) — то всё как должно быть
Поправка, на 9600 — 105 мкс
105 мкс это время передачи одного бита, а не символа.
UART — это асинхронный интерфейс. Ошибка в нем не накапливается. Синхронизация идет относительно первого бита с нулевым значением — стартового бита.
У вас ошибка в чем-то другом. Возможно тайм-аут неправильно задаете.
Да, согласен — не символа, а бита.
Асинхронный — это понятно. Но здесь вопрос только про передачу.
Есть предположение что такое поведение связано с тактированием передатчика — если не успел хоть на 1 нс, то твоя посылка всё равно уйдет на следующем отчете тактов baudrate.
Спасибо за оперативные ответы, Эдуард. Буду копать дальше)
В UART есть аппаратная буферизация на один байт.
Т.е. есть после чтения байта есть почти 2 мс на реакцию на следующий.
спасибо.
давно не программировал на stm32
подзабыл
решил восполнить.
хорошие странички.