РадиоЛоцман - Все об электронике

Жонглируем прерываниями: особенности работы с модулем UART микроконтроллеров STM8. Часть 2 - программная

STMicroelectronics STM8S003F3

- Одесса

Часть 1

Подготовка к процессу передачи/приема информации

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

Вебинар «Необычное в обычном. Сравнительный анализ современных решений Recom» (27.01.2022)

В своих проектах я компоновал все переменные, связанные с модулем UART, в виде отдельной структуры TCommunicator (Листинг 3), содержащей, в том числе, и два буфера для передачи (Tx) и приема (Rx) данных размером 255 байт. Количество принятых/переданных байт хранится в переменной DataCount. Поскольку обмен информацией возможен только в полудуплексном режиме, то одной переменной вполне достаточно для обеспечения этого процесса. Также мне понадобились два собственных флага (IsReceiveComplete и IsTransferComplete), сигнализирующие высокоуровневым функциям об окончании, соответственно, приема и передачи пакета.

Листинг 3. Описание структуры TCommunicator.

typedef struct
{

  // управление приемопередатчиком RS-485
  GPIO_TypeDef     *RS485_Port;
  GPIO_Pin_TypeDef RS485_Pin;

  uint8_t   DataCount;               // количество байт
  uint8_t   Rx[255];                 // буфер приема
  uint8_t   Tx[255];                 // буфер передачи

  bool      IsReceiveComplete;       // флаг приема команды
  bool      IsTransferComplete;      // флаг передачи команды
} TCommunicator;

Управление приемопередатчиком RS-485 может осуществляться любым портом ввода-вывода, поддерживающим режим Push-Pull. На практике вывод микросхемы, подключаемый к выводам 2, 3 микросхемы SP485 (Рисунок 3), может выбираться из достаточно большого количества соображений, в том числе и из соображений удобства разводки печатной платы. Поэтому в разных модулях системы (Рисунок 2) управление приемопередатчиком RS-485 может осуществляться разными выводами микроконтроллера. Чтобы потом «не блуждать в дебрях» уже написанного кода, в структуру TCommunicator были добавлены две переменные RS485_Port и RS485_Pin, которым на этапе инициализации микроконтроллера присваиваются нужные значения.

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

Перед началом передачи или приема информации следует выполнить ряд стандартных операций, в том числе и активизировать нужные прерывания. Перед началом передачи пакета данных необходимо вначале переключить приемопередатчик RS-485 в режим передачи (Листинг 4). После этого нужно записать в регистр DR первый байт буфера Tx, что приведет к сбросу флагов SR.TXE и SR.TС. И только после этого можно включать прерывания, причем вначале TC, а затем TXE. Дело в том, что после начала передачи первого байта регистр DR практически сразу станет пустым и первое прерывание, связанное с передачей, будет сгенерировано еще до окончания выполнения подпрограммы StartTransfer.

Листинг 4. Подпрограмма начала передачи пакета данных.

void Communicator_StartTransfer(TCommunicator* P)
{

  // включение передатчика RS-485
  if (P->RS485_Port != NULL)
    GPIO_WriteHigh(P->RS485_Port, P->RS485_Pin);

  // копирование данных
  UART1->DR = P->Tx[0];
  P->DataCount = 1;

  // настройка прерываний
  UART1_ITConfig(UART1_IT_TC, ENABLE);
  UART1_ITConfig(UART1_IT_TXE, ENABLE);
}

При подготовке к приему данных последовательность действий практически аналогична (Листинг 5), за исключением одной особенности – перед включением прерываний нужно очистить регистр DR – вдруг там находится какой-то «старый» необработанный байт, который будет удерживать флаги в регистре статуса SR. Делать это нужно строго в соответствии с технической документацией: вначале читаем регистр SR, а затем – регистр DR, иначе флаги в регистре SR так и останутся поднятыми и после включения прерываний мы сразу же «влетим» в обработчик приема данных, которых пока еще не никто не отправлял.

Листинг 5. Подпрограмма начала передачи пакета данных.

void Communicator_ResetReceiver(TCommunicator* P)
{

  // выключение передатчика RS-485
  if (P->RS485_Port != NULL)
    GPIO_WriteLow(P->RS485_Port, P->RS485_Pin);

  // подготовка к приему данных
  P->DataCount = 0;

  // очистка флагов приема
  UART_Flags = UART1->SR;
  UART_Data = UART1->DR;

  // настройка прерываний
  UART1_ITConfig(UART1_IT_RXNE_OR, ENABLE);
  UART1_ITConfig(UART1_IT_IDLE, DISABLE);
}

Небольшая проблема заключается в том, что данные, полученные из регистров DR и SR, некуда девать. Из-за этого компилятор может «ругаться» на это место в коде, показывая на возможную ошибку. В данном случае я присвоил эти значения двум глобальным переменным – UART_Flags и UART_Data. Локальные переменные в такой ситуации лучше не использовать, а если и использовать, то с выключенной оптимизацией этого участка кода, потому что при формировании итоговой прошивки компилятор может исключить эти операции как бессмысленные, что приведет к тому, что при правильно написанном коде (на С/С++) регистры SR и DR останутся без изменений.

Обратите внимание, что в самом начале приема данных прерывание от простоя линии IDLE отключено – пока не принят первый байт пакета, нас это состояние совершенно не интересует.

Обработка прерывания при передаче данных

В начале передачи данных мы активизировали прерывания от всех событий, связанных с процессом передачи данных, поэтому обработчик этого прерывания будет вызываться и при опустошении буфера, и по окончании передачи байта. Согласно технической документации, флаг SR.TC (завершение передачи) может быть поднят только при поднятом флаге SR.TXE (регистр DR пуст). Это значит, что событие SR.TC должно произойти всего один раз – в самом конце передачи пакета, когда будет сформирован стоп-бит последнего байта (Рисунок 7). Поэтому в этом обработчике проверять флаги прерывания с помощью функции UART1_GetITStatus есть смысл только в случае, когда прерывания от модуля UART могут отключаться на длительное время. В рассматриваемой системе такое маловероятно, поэтому подобная проверка не реализовывалась.

Процессы, происходящие при передаче пакета, содержащего четыре байта.
Рисунок 7. Процессы, происходящие при передаче пакета, содержащего четыре байта.

В этом случае алгоритм обработки прерывания при передаче данных (Листинг 6) определяется исключительно количеством уже переданных байт, содержащемся в переменной DataCount. Если значение этой переменной меньше размера пакета, хранимого в нулевом элементе буфера Tx, то мы копируем в регистр DR следующий байт, после чего увеличиваем на единицу переменную DataCount. После этого нам нужно снова сравнить ее значение с размером пакета. Если оказалось, что мы отправили на передачу последний байт, то прерывание по флагу SR.TXE нужно отключить, ведь чтобы его сбросить в следующий раз, нужно будет что-то записать в регистр DR, а мы уже записали в него все, что было нужно.

После отключения прерывания TXE обработчик будет вызван еще раз, но уже в самом конце передачи пакета, когда будет сформирован стоп-бит последнего байта (Рисунок 7). В этом случае прерывание будет гарантированно вызвано флагом SR.TC, а переменная DataCount будет равна размеру пакета, поэтому нам не остается ничего другого, как выключить передатчик RS-485, отключить последнее активное прерывание и, подняв флаг IsTransferComplete, указать высокоуровневым функциям, что передача пакета завершена.

Листинг 6. Обработчик прерывания передачи данных.

void Communicator_TransferByte(TCommunicator* P)
{
  if (P->DataCount < P->Tx[0])
  {
    UART1->DR = P->Tx[P->DataCount++];

    // отправили последний байт – отключаем прерывание
    if (P->DataCount == P->Tx[0])
      UART1_ITConfig(UART1_IT_TXE, DISABLE);
  }
  else
  {

    // все байты полностью переданы
    UART1_ITConfig(UART1_IT_TC, DISABLE);

    // выключаем передатчик RS485
    if (P->RS485_Port != NULL)
      GPIO_WriteLow(P->RS485_Port, P->RS485_Pin);

    P->IsTransferComplete = TRUE;
  }
}

Обработка прерывания при приеме данных

Обработка прерывания, возникающего при приеме данных, начинается со сброса флагов (Листинг 7). Здесь снова нужно строго соблюсти определенную последовательность действий: вначале читается регистр SR, а затем DR. Так же, как и в подпрограмме ResetReceiver, я сохранил значения этих регистров в переменные UART_Flags и UART_Data, хотя, в этом случае, тип переменных – локальные или глобальные – не принципиален.

Обработчик прерывания при приеме данных может быть вызван тремя событиями: приемом байта, переполнением входного буфера и прекращением поступления данных. При первых вызовах обработчика – когда значение переменной DataCount равно нулю – прерывание при обнаружении простоя линии отключено, поэтому, попав в этот обработчик, мы, в любом случае, что-то приняли.

В первую очередь нужно проверить аппаратные флаги SR.NF (зашумленная передача) и SR.FE (ошибка кадра). Установка хотя бы одного из них свидетельствует о наличии проблем на аппаратном уровне, например, плохой пайке или несоответствии скоростей обмена приемника и передатчика. Наличие подобных ошибок является весомым основанием для игнорирования всех принятых данных, поскольку они, с большой вероятностью, являются некорректными.

Не самым хорошим событием является также и установка флага SR.OR (переполнение входного буфера). В технической документации указано, что сразу после установки этого флага данные еще можно получить в полном объеме, однако у модуля UART нет никаких инструментов для определения количества уже потерянных байт. Поэтому при установке флага SR.OR лучше всего проигнорировать все сообщение. А если этот флаг будет часто подниматься, то придется пересмотреть все программное обеспечение – похоже, микроконтроллер «не успевает» обрабатывать поток данных с нужной скоростью.

И аппаратные, и программные проблемы приведут к частичной потере данных, из-за чего дальнейшая обработка полученной информации будет невозможна. Поэтому если хотя бы один из флагов SR.NF, SR.FE или SR.OR поднят, приемник переводится в исходное состояние путем вызова подпрограммы ResetReceiver.

Листинг 7. Обработчик прерывания приема данных.

void Communicator_ReceiveByte(TCommunicator* P)
{

  // сброс флагов
  UART_Flags = UART1->SR;
  UART_Data = UART1->DR;

  // проверка ошибок
  if (UART_Flags & (UART1_FLAG_NF | UART1_FLAG_FE | UART1_FLAG_OR))
  {
    Communicator_ResetReceiver(P);
    return;
  }

  if (P->DataCount == 0)
  {

    // длина команды не может быть меньше 4 байт
    if (Data > 3)
    {

      // включаем другие прерывания
      UART1_ITConfig(UART1_IT_IDLE, ENABLE);

      // сохраняем данные
      P->Rx[P->DataCount++] = UART_Data;
    }
  }
  else
  {
    if (UART_Flags & UART1_FLAG_IDLE)
    {
      Communicator_ResetReceiver(P);
    }
    else
    {

      // сохраняем данные
      P->Rx[P->DataCount++] = UART_Data;

      // проверка все ли байты приняты
      if (P->DataCount == P->Rx[0])
      {

        // выключаем прерывания
        UART1_ITConfig(UART1_IT_RXNE_OR, DISABLE);
        UART1_ITConfig(UART1_IT_IDLE, DISABLE);

        P->IsReceiveComplete = TRUE;
      }
    }
  }
}

Прием первого байта никак не гарантирует того, что мы приняли начало сообщения, ведь приемник может «включиться» в произвольный момент, например, на середине пакета, адресованного другому модулю. Достоверно определить, что принятый байт является первым и содержит информацию о длине информационной посылки невозможно. Даже если ввести специальный маркер начала передачи, например, 0xFF, и следить за тем, чтобы коды команд или параметров не принимали этого значения, все равно существует высокая вероятность наличия подобных байтов в секциях данных или контрольной суммы. А использование более сложных маркеров приведет к значительному увеличению «накладных» расходов на обмен данными, ведь при минимальной длине команды 4 байта добавление даже однобайтного маркера приведет к увеличению длины сообщения на 25%.

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

Включение прерывания по флагу SR.IDLE является очень важным. Оно позволит приемнику «не зависнуть» при нарушении общей синхронизации. Например, пусть приемник начал «слушать» шину в самом конце сообщения и принял байт со значением, равным 255 (Рисунок 8). Пусть, сразу же после передачи этого байта на информационной шине наступила тишина, что может произойти, например, если пакет предназначался этому устройству, и теперь ведущий ждет от него ответа. Однако приемник, для того чтобы приступить к обработке к пакета, должен принять еще 254 байта, которых по информационной шине никто не передает. В этом случае через некоторое время, обычно равное длительности одного байта, в модуле UART поднимется флаг SR.IDLE, и произойдет прерывание, в данном случае – из-за прекращения передачи информации.

Процессы, происходящие при приеме пакета, содержащего четыре байта.
Рисунок 8. Процессы, происходящие при приеме пакета, содержащего четыре байта.

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

Таким образом, синхронизация передатчика и приемников на уровне информационных пакетов рано или поздно произойдет в любом случае, просто активизация прерывания при обнаружении простоя в линии позволит ускорить наступление этого события. Если же приемник принимает информацию синхронно с передатчиком, то выключение всех прерываний и установка флага IsReceiveComplete произойдут сразу после приема последнего байта – задолго до установки флага SR.IDLE и перевода приемника в исходное состояние.

Заключение

Модули UART микроконтроллеров STM8, как и любые другие, имеют свои особенности, достоинства и недостатки. Проводить сравнительную оценку их с аналогичными решениями других производителей лишено всякого смысла, ведь все они обеспечивают главную функцию – обмен информацией по нужным интерфейсам. А такое понятие как «удобство пользования», в данном случае – «удобство программирования», – является чисто субъективным. Вот и получается, что страдания программистов в процессе работы с этими периферийными модулями всегда можно объяснить недостатками и невежеством самих программистов, не уделивших должного времени и внимания изучению технической документации.

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


Дополнительная информация

  1. STM8S Series and STM8AF Series 8-bit microcontrollers. Reference Manual
  2. STM8S Value line
  3. STM8S/A Standard Peripherals Library
  4. Настройка UART на микроконтроллере STM8

Материалы по теме

  1. Datasheet STMicroelectronics STM8S003F3

Изготовление 1-4 слойных печатных плат за $2

15 предложений от 12 поставщиков
Рабочее напряжение 3.3-5VHC-12 - радио модуль связи на базе чипа SI4463, работающий со стандартным Serial UART интерфейсом. Модуль поддерживает AT команды,...
STM8S003F3P6
STMicroelectronics
14 ₽
T-electron
Россия и страны СНГ
STM8S003F3P6
STMicroelectronics
40 ₽
Элитан
Россия
STM8S003F3P6
STMicroelectronics
74 ₽
PL-1
Россия
Радиомодуль 433 MHz HC-12 SI4463 (STM8S003F3Pb)
от 401 ₽
Запись вебинара «Микросхемы для защиты цепей питания: ограничители всплесков напряжения и тока, контроллеры горячей замены, идеальные диоды»
Для комментирования материалов с сайта и получения полного доступа к нашему форуму Вам необходимо зарегистрироваться.
Имя