проблема:
Сбой приема через последовательный порт возникает, когда часто отправляются и принимаются большие объемы данных.
анализ:
Библиотека HAL инкапсулирует прерывание последовательного порта, оставляя пользовательский интерфейс только с одной функцией обратного вызова.
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) // Получение функции обратного вызова прерывания
То, что обычно записывается в функции прерывания, теперь записывается в этой функции обратного вызова.
При использовании библиотечных функций или регистров нам обычно приходится самостоятельно очищать бит флага прерывания, а включение и отключение принимающего прерывания может контролироваться пользователем, непосредственно управляющим регистром, и они инкапсулируются библиотекой HAL, и единственное, что предоставляется пользователю — это Две функции:
HAL_UART_Receive(&huart2,(uint8_t*)Data_Buf, Data_Length,Timeout)// Блокировка получения, ожидание поступления данных в течение указанного времени
HAL_UART_Receive_IT(&huart2,(uint8_t*)Data_Buf,Data_Length);// Прерванный прием
Первая функция используется реже. В нормальных условиях мы хотим воспроизвести однобайтовый метод прерывания приема библиотечных функций или программирование регистров для обработки данных, мы можем использовать вторую функцию и установить Data_Length на 1. Таким образом, при получении байта данных он войдет в функцию обратного вызова. следующим образом:
HAL_UART_Receive_IT(&huart2,(uint8_t*)&UART2_RX_TEMP,1);// Открываем последовательный порт 2 для приема прерывания
Причина, по которой библиотека HAL инкапсулирует их, заключается в пропускной способности большого количества данных (поскольку длина данных может быть указана, а такие операции, как очистка бита флага, выполняются самой библиотекой HAL).
Теперь очень неэффективно использовать его в качестве однобайтового прерывания приема, потому что каждый байт должен выполнять множество оценок перед входом в функцию обратного вызова.
А функция HAL_UART_Receive_IT разрешает прерывание последовательного порта внутри функции каждый раз, когда она используется.
Поскольку библиотека HAL добавляет такой механизм к прерыванию последовательного порта: после получения указанного байта прерывание отключается.
Чтобы гарантировать непрерывный прием, нам обычно нужно вызвать эту функцию снова, чтобы прерывание приема могло получить следующий байт в конце функции обратного вызова, как показано ниже:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance==USART2)
{
UART1_Rx_Buf[UART1_Rx_cnt] = UART2_temp;
UART1_Rx_cnt++;
HAL_UART_Receive_IT(&huart2,(uint8_t*)&UART2_RX_TEMP,1);// Повторно разрешаем прерывание приема
}
}
Введем HAL_UART_Receive_IT () для наблюдения за прототипом функции
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
/* Check that a Rx process is not already ongoing */
if (huart->RxState == HAL_UART_STATE_READY)
{
if ((pData == NULL) || (Size == 0U))
{
return HAL_ERROR;
}
/* Process Locked */
__HAL_LOCK(huart);
huart->pRxBuffPtr = pData;
huart->RxXferSize = Size;
huart->RxXferCount = Size;
huart->ErrorCode = HAL_UART_ERROR_NONE;
huart->RxState = HAL_UART_STATE_BUSY_RX;
/* Process Unlocked */
__HAL_UNLOCK(huart);
/* Enable the UART Parity Error Interrupt */
__HAL_UART_ENABLE_IT(huart, UART_IT_PE);
/* Enable the UART Error Interrupt: (Frame error, noise error, overrun error) */
__HAL_UART_ENABLE_IT(huart, UART_IT_ERR);
/* Enable the UART Data Register not empty Interrupt */
__HAL_UART_ENABLE_IT(huart, UART_IT_RXNE);
return HAL_OK;
}
else
{
return HAL_BUSY;
}
}
Видно, что функция может выйти из строя, когда прерывание разрешено. Если включить не удается, возвращается значение HAL_BUSY.
Я изучал здесь конкретные причины сбоя в течение некоторого времени, но уровня все еще недостаточно. Некоторые большие ребята в Интернете дали ответ (ссылка в конце статьи).
Примерный вопрос:
Существует конфликт между отправкой и получением последовательного порта. HAL_UART_Transmit — это блокирующий вызов, и при возникновении прерывания во время блокировки возникнут проблемы.
Некоторое время я моделировал отправку и получение больших объемов данных.
if(HAL_UART_Receive_IT(&huart2,(uint8_t*)&UART2_RX_TEMP,1) == HAL_BUSY);// Открываем последовательный порт 2 для приема прерывания
{
printf("Uart2 is BUSY!rn");
}
Если не удается включить последовательный порт, отобразится сообщение Uart2 is BUSY !, результат интерфейса последовательного интерфейса будет следующим:
Когда объем данных слишком велик, происходит сбой в разрешении прерывания последовательного порта, затем происходит сбой приема последовательного порта, другие части программы работают нормально, и отправка через последовательный порт также является нормальной.
Решение
Обнаружив, что HAL_UART_Receive_IT () возвращает HAL_BUSY, повторно включите его. Будьте осторожны, не включайте его быстро. Не пишите в прерывании подобное:
while(HAL_UART_Receive_IT(&huart2,(uint8_t*)&UART2_RX_TEMP,1) == HAL_BUSY);
Если приведенный выше код застрянет в прерывании, это приведет к сбою программы. Лучше всего сделать это:
if(HAL_UART_Receive_IT(&huart2,(uint8_t*)&UART2_RX_TEMP,1) == HAL_BUSY)// Открываем последовательный порт 2 для приема прерывания
{
UART_ERR = 1;
}
Затем проверьте UART_ERR в цикле (или другой задаче) и снова включите его:
if(UART_ERR)
{
UART_ERR = 0;
printf("rnUart2 is BUSY!-------------------->rn");
while(HAL_UART_Receive_IT(&huart2,(uint8_t*)&UART2_RX_TEMP,1) == HAL_BUSY);// Открываем последовательный порт 2 для приема прерывания
}
Этот метод может действовать только как сторожевой таймер и не может принципиально решить проблему получения последовательного порта библиотеки HAL.
Если такая проблема возникает только изредка в обычных приложениях и есть механизм для повторной передачи данных, этот метод можно использовать для ее решения.
Я не думаю, что это ОШИБКА библиотеки HAL, потому что здесь можно прочитать ошибку включения, а не неизвестную ошибку, можно только сказать, что этот механизм не принимает во внимание обычную Удобство дизайна небольших приложений.
В следующем сообщении подробно объясняется эта проблема библиотеки HAL, которую можно решить, изменив соответствующую часть кода библиотеки HAL:
https://blog.csdn.net/suxingtian/article/details/86526746
USART (Universal Synchronous-Asynchronous Receiver/Transmitter) — универсальный синхронно-асинхронный приёмопередатчик, интерфейс для для передачи данных между цифровыми устройствами. Он на столько универсальный, что даже провода по которым к вам домой приходит интернет, это тоже USART (RS485).
USART микроконтроллера stm32 может работать в различных режимах — асинхронный, синхронный, полудуплекс и т.д.
Asynchronous
Это самый распространённый режим, устройства соединяются так…
RX
<>
TX
TX
<>
RX
… а согласуются между собой с помощью заранее установленной скорости передачи данных.
Тут следует немного пояснить работу 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 (шесть единичек — чётное количество), то бит чётности будет 0. Пакет будет выглядеть так — 101111010.
Если отправляется 01110011 (пять единичек — нечётное количество), то бит чётности будет 1. Пакет будет выглядеть так — 011100111.
При приёме такого пакета производится проверка — соответствует ли количество единичек значению в девятом бите, и если не соответствует, то пакет считается повреждённым — ошибка контроля чётности (HAL_UART_ERROR_PE).
Вычисления производятся с помощью битовой операции XOR (исключающее «или»).
Проверка конечно так себе. Теоретически может одна единичка превратится в ноль, а другой ноль в единичку, и тогда проверка ничего не выявит.
С помощью опций —
Even
и
Odd
, можно выбрать что будет считаться чётным.
Hardware Flow Control
— это контроль передачи данных (с помощью дополнительных линий) используемый, например, для соединения через COM-порт. Оставьте Disable, нам это не понадобится.
RTS/CTS
Если устройство хочет прекратить передачу данных, оно устанавливает состояние сигнала 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» можно так:
HAL_UART_Transmit(&huart1, (uint8_t*)"Hello Worldn", 12, 1000);
Первый аргумент — указатель на структуру USART’а, второй — строка (приведённая к указателю), третий кол-во символов в отправляемой строке, четвёртый — таймаут в миллисекундах (если по каким-то причинам не удастся выполнить отправку в течении секунды, то вернётся ошибка).
Эта функция блокирующая, то есть пока она не выполнится программа будет приостановлена.
Чтоб не блокировать программу на время отправки, нужно воспользоваться функцией с прерыванием по завершению передачи.
Включите глобальное прерывание…
Отправлять данные так:
HAL_UART_Transmit_IT(&huart1, (uint8_t*)"Hello Worldn", 12);
Здесь нет таймаута так как нам ничего не нужно ждать — отправили и забыли. Остальные аргументы те же, что и у блокирующей функции.
По окончании отправки сработает прерывание и вызовет колбек:
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart == &huart1)
{
// можно установить какой-то флаг, сообщающий об окончании отправки
}
}
Отправить массив:
/* USER CODE BEGIN Includes */
#include "string.h"
/* USER CODE END Includes */
...
char buff[16] = {0,};
snprintf(buff, 15, "Hello Worldn");
HAL_UART_Transmit_IT(&huart1, (uint8_t*)buff, strlen(buff));
Второй аргумент — указатель на массив, третий — длина строки в массиве.
Через DMA
Включаем…
Отправляем:
HAL_UART_Transmit_DMA(&huart1, (uint8_t*)buff, strlen(buff));
Аргументы те же, что и у предыдущей функции.
Колбек тот же что и
HAL_UART_Transmit_IT(…)
, но тут есть нюанс. DMA вызывает два прерывания, первое после отправки половины буфера, а второе при завершении. Чтоб отключить половинку, нужно в файле
stm32f1xx_hal_uart.c
найти функцию
HAL_UART_Transmit_DMA(…)
и закомментировать строчку…
/* Set the UART DMA Half transfer complete callback */
//huart->hdmatx->XferHalfCpltCallback = UART_DMATxHalfCplt;
Больше про отправку сказать нечего.
ПРИЁМ
Для приёма нужно сделать так:
uint8_t buff[16] = {0,};
HAL_UART_Receive(&huart1, (uint8_t*)buff, 15, 1000);
Второй аргумент — указатель на массив в который будут записываться полученные данные, третий — кол-во байт, которые нужно принять, последний — таймаут.
Эта функция тоже блокирующая, она будет ожидать до тех пор пока не получит все запрошенные байты, или пока не истечёт таймаут.
Если для отправки данных блокирующая функция вполне подходит, то для приёма совсем не годится (мы ведь не знаем когда придут данные, если конечно они не идут сплошным потоком). Поэтому нужно использовать функцию вызывающую прерывание…
uint8_t buff[16] = {0,};
HAL_UART_Receive_IT(&huart1, (uint8_t*)buff, 15);
Второй аргумент — указатель на массив в который будут записываться полученные данные, третий — кол-во байт, которые нужно принять.
Эта функция не блокирует программу. Прерывание произойдёт только после того, как всё запрошенные байты будут приняты.
Колбек:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart == &huart1)
{
// что-то делаем
}
}
Помните о том, что если посылаете данные из какого-то терминала, то этот терминал может посылать символы «новой строки» и «воврат каретки». То есть, если вы пошлёте 15 символов, а терминал добавит ещё свои, то программа получит первые 15, вызовет колбек, примет ещё (то что добавил терминал) и будет ожидать. Короче говоря, программа провернётся через колбек и будет ждать.
Через DMA
Включаем канал для приёма…
Запускаем…
HAL_UART_Receive_DMA(&huart1, (uint8_t*)buff, 15);
Второй аргумент — указатель на массив в который будут записываться полученные данные, третий — кол-во байт, которые нужно принять.
Колбек тот же что и при использовании
HAL_UART_Receive_IT(…)
, и так же как и с отправкой, будут вызываться два прерывания — половина буфера и полный. Как отключить половинку вы уже знаете.
Поскольку при приёме/отправке могут возникать ошибки, то чтобы их отловить нужно создать специальный колбек…
error
Сюда приходят все ошибки USART’а.
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
if (huart == &huart1)
{
uint32_t er = HAL_UART_GetError(&huart1);
if (er & HAL_UART_ERROR_PE)
{
HAL_UART_Transmit(&huart1, (uint8_t*) "ERR_Callbck - Parity errorn", 27, 1000);
__HAL_UART_CLEAR_PEFLAG(&huart1);
}
if (er & HAL_UART_ERROR_NE)
{
HAL_UART_Transmit(&huart1, (uint8_t*) "ERR_Callbck - Noise errorn", 26, 1000);
__HAL_UART_CLEAR_NEFLAG(&huart1);
}
if (er & HAL_UART_ERROR_FE)
{
HAL_UART_Transmit(&huart1, (uint8_t*) "ERR_Callbck - Frame errorn", 26, 1000);
__HAL_UART_CLEAR_FEFLAG(&huart1);
}
if (er & HAL_UART_ERROR_ORE)
{
HAL_UART_Transmit(&huart1, (uint8_t*) "ERR_Callbck - Overrun errorn", 28, 1000);
__HAL_UART_CLEAR_OREFLAG(huart);
}
if (er & HAL_UART_ERROR_DMA)
{
HAL_UART_Transmit(&huart1, (uint8_t*) "ERR_Callbck - DMA transfer errorn", 33, 1000);
__HAL_UART_CLEAR_NEFLAG(&huart1);
}
huart->ErrorCode = HAL_UART_ERROR_NONE;
}
}
Файл stm32f1xx_hal_uart.h
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)
…
С приёмом есть заморочка, мы можем не знать сколько данных должно прилететь, следовательно непонятно какое кол-во байт указывать. Например, если мы укажем пять байт, а прилетят только три, то прерывание не сработает пока не придут оставшиеся два. На этот случай есть несколько способов приёма.
Если нужно время от времени получать один-два байта, то можно просто в начале программы (перед бесконечным циклом) вызвать функцию приёма…
/* USER CODE BEGIN PV */
volatile char sim;
/* USER CODE END PV */
...
HAL_UART_Receive_IT(&huart1, (uint8_t*)&sim, 1);
… а в колбеке что-то поделывать, и вызывать снова…
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart == &huart1)
{
HAL_UART_Transmit(&huart1, (uint8_t*)&sim, 1, 1000);
HAL_UART_Receive_IT(&huart1, (uint8_t*)&sim, 1);
}
}
Можно организовать кольцевой буфер.
Идея заключается в следующем: создаётся массив
(размер указывается исходя из потребностей)
, полученный байт записывается в этот массив, указатель сдвигается на следующую ячейку, следующий байт сохраняется в эту ячейку, указатель опять сдвигается, и так до тех пор пока буфер не заполнится. После того как буфер заполнился можно поступить двумя способами: первый — указатель перескакивает на нулевую ячейку и буфер заполняется заново постепенно затирая ранее полученные данные. То есть запись идёт по кругу без остановки. Второй — при заполнении буфер останавливается, а новые данные записываются в него по мере вычитывания замещая прочитанные.
Наглядный пример кольцевого буфера. Пользоваться им не нужно, я его сделал просто для красоты когда мне было скучно.
Вот тут библиотека кольцевого буфера, которой я пользуюсь постоянно.
И ещё один способ — это воспользоваться флагом IDLE. Этот флаг устанавливается аппаратно при обнаружении незанятой линии. То есть, если в приёмник поступает несколько байт подряд, а потом возникает пауза (линия находиться в состоянии HIGH некоторое время), то взводится флаг IDLE генерирующий прерывание, а мы по этому прерыванию определяем что данные перестали поступать.
Допустим мы точно не знаем сколько байт должны прийти, но знаем что их не больше 12, тогда делаем так…
HAL_UART_Receive_IT(&huart1, (uint8_t*)buff, 15);
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
Другое устройство пульнёт нам две строчки…
HAL_UART_Transmit(&huart2, (uint8_t*)"Hello World", 11, 1000);
HAL_UART_Transmit(&huart2, (uint8_t*)"Hello World", 11, 1000);
В результате сначала произойдёт прерывание от IDLE (в буфер запишется строка «Hello World»), а потом прерывание при заполнении буфера. В итоге в буфере будет лежать «Hello WorldHell» — 15 байт.
Если это делать через DMA…
HAL_UART_Receive_DMA(&huart1, (uint8_t*)buff, 15);
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
… то вся работа, кроме обработчиков двух прерываний, будет выполняться аппаратно. Не забудьте отключить половинку DMA.
Примеры — раз и два. Эти примеры рабочие, но код я так и не привёл в порядок, ибо как писал выше, пользуюсь «кольцевиком».
Замечание: прерывание по IDLE нужно отключать вручную в колбеке, а так же сбрасывать флаг. Это есть в примере.
Single Wire (Half-Duplex)
Полудуплексный режим — данные передаются и принимаются по одной линии (TX). В этом режиме устройства обмениваются данными по очереди — один слушает, другой отправляет. Линию нужно обязательно подтянуть к «плюсу» резистором ~10кОм…
Команды те же, что и в обычном (асинхронном) режиме, только надо предварительно настроить линию на приём или передачу…
HAL_HalfDuplex_EnableReceiver(&huart1); // устройство в режиме приёма
HAL_UART_Receive(&huart1, (uint8_t*)buff, 12, 1000);
HAL_HalfDuplex_EnableTransmitter(&huart1); // устройство в режиме отправки
HAL_UART_Transmit(&huart1, (uint8_t*)buff, 12, 1000);
Можно использовать прерывания и DMA.
… то есть, если одно устройство отправляет данные, то другое должно находиться в режиме приёма.
Линия RX соединена с TX внутри микроконтроллера.
MultiProcessor Communication
В этом режиме можно соединить несколько устройств. Одно устройство выступает в роли Мастера, а другие являются Слейвами. Мастер обращается к слейвам отправляя им их адреса — слейв видя что это его адрес принимает полезные данные.
Подключение обычное, как в асинхронном режиме…
Настройка в CubeMX…
F303
World Lenght
— девятибитный режим. Можно и восьмибитный (см. в конце главы).
В момент старта слейв находится в так называемом «тихом» режиме, то есть он не будет принимать полезные данные (принимаются только адреса) пока не поймёт, что они адресованы именно ему. Как только слейв видит что данные адресованы ему, он выходит (Wake-Up) из «тихого» режима и начинает приём полезных данных.
Wake-Up Method ⇨ Address Mark
— слейвы будут выходить из «тихого» режима при получении своего адреса.
Wake-Up Address
— адрес устройства. Для мастера укажите — 0, а для слейвов — 1, 2 и т.д.
Если у вас только два устройства, один мастер и один слейв, тогда подтяните к «плюсу» резистором 10кОм TX мастера. Без этого у меня не работало. Если слейвов несколько, то всё окей.
Как работает:
• Мастер может отравлять данные любому слейву (предварительно отправляя адрес), и получать данные от любого слейва.
• Слейвы слушают линию, и если прилетает правильный адрес, то принимают данные следующие за этим адресом.
• Слейвы могут отправлять данные только мастеру.
• Мастер запускается в обычном режиме, то есть ни каких специальных команд не требуется.
• Слейв запускается в «тихом» режиме.
Для запуска в «тихом» режиме нужно перед бесконечным циклом сделать так…
Для F103:
HAL_MultiProcessor_EnterMuteMode(&huart1);
Для F303:
HAL_MultiProcessor_EnableMuteMode(&huart1);
HAL_MultiProcessor_EnterMuteMode(&huart1);
Пока ничего делать не нужно — команды приведены в ознакомительных целях.
Слейвы в «тихом» режиме ожидают адрес. Мастер, чтобы отправить кому-то данные, сначала отправляет в линию адрес, а затем данные.
Адреса и полезные данные передаются в девятибитном формате (9-ти битный байт). Если отправляется байт с адресом, то в первых восьми битах хранится сам адрес, а в девятый бит записывается единица. Если отправляется байт данных, то в девятый бит записывается ноль. То есть, приёмное устройство отличает байт с данными от байта с адресом по тому, что записано в девятом бите.
Адресный байт мастер отправляет так:
uint16_t adr_data = 0;
adr_data = 1 | 0x0100;
HAL_UART_Transmit(&huart1, (uint8_t*)&adr_data, 1, 1000);
Поскольку байт девятибитный, то переменная 16-ти битная. Адрес первого слейва — 1, записываем его в переменную, а девятый бит превращаем в единицу.
Для слейва с адресом 2 команда будет такая:
uint16_t adr_data = 0;
adr_data = 2 | 0x0100;
HAL_UART_Transmit(&huart1, (uint8_t*)&adr_data, 1, 1000);
После того как адрес отправлен, слейвы проверяют его (это происходит аппаратно, без нашего участия). Тот слейв, чей адрес совпал, переходит из «тихого» режима в обычный и начинает приём данных, которые мы шлём вслед за адресом…
uint16_t adr_data = 0;
adr_data = 1 | 0x0100;
HAL_UART_Transmit(&huart1, (uint8_t*)&adr_data, 1, 1000);
HAL_UART_Transmit(&huart1, (uint8_t*)"A", 1, 1000);
Не обязательно слать один байт, можно отправлять массив, и сколько угодно раз. То есть дальше идёт передача как в обычном режиме, только в 9-ый бит каждого байта будет автоматически (без нашего участия) записываться ноль, чтоб принимающая сторона понимала, что это данные, а не адрес.
Остальные слейвы в это время по прежнему находятся в «тихом» режиме и только проверяют последний бит каждого байта — если там ноль, то пакет выбрасывается, если единичка, то смотрят что за адрес. Это происходит на аппаратном уровне.
Теперь, если мастер пошлёт адрес другого слейва…
uint16_t adr_data = 0;
adr_data = 2 | 0x0100;
HAL_UART_Transmit(&huart1, (uint8_t*)&adr_data, 1, 1000);
… то принимать данные будет слейв с адресом 2, а слейв с адресом 1 перейдёт в «тихий» режим.
Вот наглядная схема…
Здесь показано как ведёт себя слейв с адресом 1. Входит в «тихий» режим (RWU written), получает адрес 0 (Addr=0) и игнорирует его вместе с последующими байтами данных, затем получает адрес 1 (Addr=1) и переходит в режим приёма (получает два байта данных — Data 3 и Data 4), а когда получает адрес 2 (Addr=2), то автоматически уходит в «тихий» режим.
Помимо автоматических переходов в «тихий» режим и обратно, это можно делать программно.
Для F103:
HAL_MultiProcessor_EnterMuteMode(&huart1); // затихариться
HAL_MultiProcessor_ExitMuteMode(&huart1); // очнутся
Для F303:
HAL_MultiProcessor_EnableMuteMode(&huart1);
HAL_MultiProcessor_EnterMuteMode(&huart1); // затихариться
HAL_MultiProcessor_DisableMuteMode(&huart1); // очнутся
Слейвы могут посылать данные мастеру в любой момент без каких-либо адресов. Мастеру конечно можно назначить адрес, но в этом нет смысла так как он один.
программа для тестов
СЛЕЙВ
/* USER CODE BEGIN PV */
volatile uint16_t sim;
char trans_str[16] = {0,};
/* USER CODE END PV */
В колбеке ловим данные от мастера и выводим их в USART_2 (основной USART_1).
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart == &huart1)
{
HAL_UART_Transmit(&huart2, (uint8_t*)&sim, 1, 1000);
HAL_UART_Transmit(&huart2, (uint8_t*)"rn", 2, 1000);
//HAL_MultiProcessor_EnterMuteMode(&huart1);
HAL_UART_Receive_IT(&huart1, (uint8_t*)&sim, 1);
}
}
В бесконечном цикле раз в секунду отправляем мастеру букву U.
/* USER CODE BEGIN 2 */
HAL_MultiProcessor_EnterMuteMode(&huart1);
HAL_UART_Receive_IT(&huart1, (uint8_t*)&sim, 1);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
HAL_Delay(990);
HAL_UART_Transmit(&huart1, (uint8_t*)"U", 1, 1000);
...
МАСТЕР
/* USER CODE BEGIN PV */
volatile uint8_t sim;
/* USER CODE END PV */
В колбеке ловим букву U и моргаем лампочкой.
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart == &huart1)
{
if(sim == 'U')
{
HAL_GPIO_TogglePin(LD10_GPIO_Port, LD10_Pin);
}
HAL_UART_Receive_IT(&huart1, (uint8_t*)&sim, 1);
}
}
В цикле отправляем данные на разные адреса.
/* USER CODE BEGIN 2 */
HAL_UART_Receive_IT(&huart1, (uint8_t*)&sim, 1);
uint16_t adr_data = 0;
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
adr_data = 1 | 0x0100;
HAL_UART_Transmit(&huart1, (uint8_t*)&adr_data, 1, 1000);
HAL_UART_Transmit(&huart1, (uint8_t*)"A", 1, 1000);
HAL_Delay(1000);
HAL_UART_Transmit(&huart1, (uint8_t*)"B", 1, 1000);
HAL_Delay(1000);
adr_data = 2 | 0x0100;
HAL_UART_Transmit(&huart1, (uint8_t*)&adr_data, 1, 1000);
HAL_UART_Transmit(&huart1, (uint8_t*)"C", 1, 1000);
HAL_Delay(1000);
HAL_UART_Transmit(&huart1, (uint8_t*)"z", 1, 1000);
HAL_Delay(1000);
HAL_UART_Transmit(&huart1, (uint8_t*)"x", 1, 1000);
HAL_Delay(1000);
HAL_UART_Transmit(&huart1, (uint8_t*)"n", 1, 1000);
HAL_Delay(1000);
adr_data = 1 | 0x0100;
HAL_UART_Transmit(&huart1, (uint8_t*)&adr_data, 1, 1000);
HAL_UART_Transmit(&huart1, (uint8_t*)"D", 1, 1000);
HAL_Delay(1000);
...
Слейв 1 полчает это…
Восьмибитный режим тоже можно использовать. Всё происходит так же как и в девятибитном, а передаваемые данные будут семибитными — символы с 0x00 по 0x7F. Восьмой бит используется для определения адрес это или данные.
Если не передаёте какие-то специфичные символы, то можно использовать восьмибитный режим.
Адрес подготавливается так:
uint8_t adr_data = 0;
adr_data = 1 | 0x80;
IrDA
Это USART через инфракрасный передатчик. Такие штуки были в старинных сотовых телефонах, можно было что-то передавать с телефона на телефон или на компьютер. К компьютеру подключался инфракрасный приёмопередатчик и определялся как COM-порт. В общем можно связать два МК через USART «по воздуху».Функции для работы находятся в файле —
stm32f1xx_hal_irda.c
.
С остальными режимами я подробно не разбирался, поэтому просто коротенькая справка. Synchronous
— синхронный режим USART’а отличается от асинхронного тем, что частота синхронизации подается от одного устройства к другому по линии CLOCK. На сколько я понимаю, можно работать с SPI…
LIN
— посмотрите здесь.
SmartCard
— подключение к смарт-картам.
На этом всё.
Всем спасибо
Телеграм-чат istarik
Телеграм-чат STM32
There is a race condition present in the stm32 port that relates to the way the UART overrun (ORE bit in USART status register) is handled in the IRQ service routine.
If this condition is met, the pyboard completly locks up and cannot be recovered (you have to hard-reset or power cycle).
The reason is because the UART RX ISR function does not clear the ORE flag if the receive buffer is empty and hence the irq is starting again as soon as the handler exits (ORE is also triggering the IRQ). It results in a 100% CPU utilisation.
This situation is explained in the STM32 reference manual page chapter «Overrun error» in the USART description.
Steps to reproduce:
- Wire up a pyboard UART Rx pin (e.g. X10) to an USB->serial adapter’s TX pin
- on the pyboard enter:
from pyb import UART
u=UART(1,230400)
while True: u.read(10)
- on the PC enter:
import time, serial, os
u=serial.serial_for_url('COMxx',230400)
while True: u.write(os.urandom(100)); time.sleep(0.01)
you should now see a lot of random data being transferred to pyboard. Since the window time of the critical section (https://github.com/micropython/micropython/blob/master/ports/stm32/uart.c#L486-L494) is somewhere smaller than 1usec it’s very hard to hit the bug and normally you won’t see any troubles.
However, the raise condition can be forced if another IRQ can be triggered on the pyboard, hence delaying the calling of the UART service routine.
One way that worked for me was to load a file, e.g. boot.py
, in a text editor and hit several times «save» (normally 10-20 times should be ok) -> BOOM. The pyboard freezes and continously fires the UART RX IRQ
The way to resolve is to put the following code
if (__HAL_UART_GET_FLAG(&self->uart, UART_FLAG_ORE) != RESET) {
if (__HAL_UART_GET_FLAG(&self->uart, UART_FLAG_RXNE) == RESET) {
// overrun and empty data, just do a dummy read to clear ORE
// and prevent a raise condition where a continous interrupt stream (due to ORE set) occurs
// (see chapter "Overrun error" ) in STM32 reference manual
self->uart.Instance->DR;
}
}
just before https://github.com/micropython/micropython/blob/master/ports/stm32/uart.c#L486
On the L4, the overrun can be disabled in the UART’s control register, so it doesn’t need that check.