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

Выйдя на рынок недорогих 8-разрядных микроконтроллеров, компания STMicroelectronics сделала «предложение, от которого нельзя отказаться». Судя по количеству проектов и публикаций в сети, радиолюбителям и профессиональным разработчикам особо «понравились» микросхемы бюджетной серии STM8S в компактных корпусах TSSOP20. Не последнюю роль в этом, возможно, сыграло наличие недорогих плат с предустановленными микросхемами STM8S103F3P6 (Рисунок 1), которые можно установить в беспаечные отладочные платы, так обожаемые поклонниками платформы Arduino. И хоть сейчас из-за общемировой нехватки радиоэлементов розничные цены на микросхемы этой серии возросли более чем в пять раз, на сайте STMicroelectronics при заказе партии не менее чем из 10 тысяч приборов микроконтроллеры STM8S003F3P6 все еще доступны по цене 0.28 USD за шутку. А это, в совокупности с постепенным снижением розничных цен на эти микросхемы, происходившим в течение 2021 года, вселяет надежду, что они снова станут такими же доступными, как и раньше.

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

Рисунок 1. Отладочная плата с микроконтроллером STM8S103F3P6.

Я тоже не устоял перед искушением использовать эти микроконтроллеры в своих разработках и создал на основе STM8S003F3P6 небольшую систему домашней автоматизации, первая версия которой через несколько месяцев будет проходить испытания в установке для выращивания рассады. Фактически я создал конструктор, состоящий из унифицированных модулей с общим аппаратным и программным интерфейсом (Рисунок 2), комбинирование которых позволяет решать самые разные задачи. Например, в первом варианте система будет по расписанию управлять фитолампами, включая их только днем и при условии, что уровень внешнего освещения будет ниже запрограммированного порога.

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

Рисунок 2. Структурная схема автоматизированной системы управления фитолампами.

В этой статье будут рассмотрены особенности работы с модулями UART микроконтроллеров STM8 при использовании прерываний. Статья будет полезна, в первую очередь, специалистам, находящимся на начальных этапах знакомства с этими микросхемами. Я надеюсь, что она действительно ускорит написание программного кода, потому что у меня, несмотря на наличие практического опыта работы с аппаратным UART на платформах AVR и PIC, на эту часть проекта ушло в несколько раз больше времени, чем я планировал.

Материал статьи рассчитан на читателя, уже имеющего определенный уровень подготовки и знающего что такое UART, RS-485, прерывания, флаги, регистры, переменные и прочие элементарные вещи. Энциклопедических данных и переводов технической документации в этой статье будет совсем немного — ровно столько, сколько нужно для пояснения того или иного момента. Поэтому хорошим дополнением к этой статье станут оригинальная техническая документация, а также примеры работы с UART из категории «что-то приняли и радуемся», которые можно найти не только в сети, но и в «фирменной» библиотеке STM8S/A Standard Peripherals Library (StdPeriph_Lib) [3].

Аппаратная часть системы

Электрические схемы всех модулей системы аналогичны (Рисунок 3), по крайней мере, в части, связанной с обработкой информации. Приемопередатчиком интерфейса RS-485 является микросхема DA2 (SP485). Она связана с микроконтроллером DD1 (STM8S003F3P6) с помощью трех линий, две из которых соединены с интегрированным модулем UART (выводы 2 и 3), а третья — соединенная с выводом 1, настроенным в режиме обычного порта ввода-вывода, — используется для управления приемопередающей частью SP485.

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

Рисунок 3. Принципиальная схема модуля реле.

В нормальном режиме работы микроконтроллер и приемопередатчик RS-485 питаются напряжением 5 В, формируемым стабилизатором DA1. Однако из-за того, что дешевые версии программатора ST-LINK могут работать только с сигналами, уровень которых равен 3.3 В, во время программирования и отладки программного обеспечения питать микроконтроллер и основные микросхемы приходится напряжением 3.3 В, получаемым от программатора через разъем X2. Однако это даже оказалось удобным, поскольку при отладке всю систему можно запитать от одного USB-порта и не возиться с громоздкими лабораторными источниками питания. Можно было бы, конечно, и перейти на основное питание 3.3 В, однако микросхемы, рассчитанные на работу при таком напряжении, например, MAX3485 или LM1117-3.3, почему-то стоят в несколько раз дороже своих 5-вольтовых аналогов.

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

Протокол обмена данными

Обмен данными в системе происходит в форме диалога. Ведущее устройство (модуль контроллера) периодически посылает каждому модулю команду в определенном формате (Рисунок 4).

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

Рисунок 4. Формат команды от ведущего к ведомому.

Во время работы все ведомые модули «слушают» информационную шину и при обнаружении пакета, адресованного им (при совпадении поля «Номер устройства» с настройкой, записанной в собственной энергонезависимой памяти), передают ведущему ответ, также в фиксированном формате (Рисунок 5).

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

Рисунок 5. Формат ответа ведомого устройства.

Как видно из рисунка, структура ответа ведомого отличается от структуры запроса ведущего лишь одним полем — кодом ошибки. Код ошибки является критерием правильной работы ведомого устройства и корректности переданных данных. Например, если в модуле часов произошел сбой в работе микросхемы RTC (Real Time Clock), то при запросе времени технически проще передать последние данные, полученные от этого узла, с кодом «Аппаратная ошибка», чем как-то иначе сигнализировать о наличии неисправности. Ведущему этого будет вполне достаточно для того, чтобы «понять», что полученным данным о времени верить нельзя, и принять меры по восстановлению работы системы (например, перезагрузить аварийный модуль и, если ошибка не исчезла, «замигать» аварийным светодиодом).

Инициализация модуля UART

Особенности аппаратной части модуля UART микроконтроллеров STM8 и его работы во всех возможных режимах достаточно хорошо описаны как в технической документации, так и в примерах, поэтому я не вижу особого смысла останавливаться на этом вопросе. При использовании библиотеки StdPeriph_Lib для настройки модуля проще всего использовать функцию UART1_Init (Листинг 1), передав в нее значения в виде заранее предопределенных констант. В данном примере модуль UART переводится в режим, используемый во многих радиолюбительских проектах: асинхронный режим работы (используются только выводы RX и TX), скорость обмена данными — 9600 бод, длина пакета — 8 бит, один стоповый бит, без бита четности. При инициализации модуля также сразу происходит включение и приемника, и передатчика — за это «отвечает» последний входной параметр, которому присваивается значение UART1_MODE_TXRX_ENABLE.

Листинг 1. Исходный код инициализации аппаратной части.  

   // восстановление исходных настроек UART
   UART1_DeInit();

   // настройка UART
   UART1_Init((uint32_t)9600,
       UART1_WORDLENGTH_8D,
       UART1_STOPBITS_1,
       UART1_PARITY_NO,
       UART1_SYNCMODE_CLOCK_DISABLE,
       UART1_MODE_TXRX_ENABLE);

    // настройка порта ввода-вывода, связанного с приемопередатчиком RS485
    GPIO_Init(GPIOD, GPIO_PIN_4, GPIO_MODE_OUT_PP_LOW_FAST);

Перед настройкой модуля UART рекомендуется вызвать вначале подпрограмму UART1_DeInit (в библиотеке StdPeriph_Lib подобные подпрограммы есть у каждого периферийного модуля). Она гарантированно вернет модуль UART в исходное состояние, поскольку подпрограмма UART1_Init изменяет лишь часть настроек, и если в момент вызова UART1_Init другие настройки UART были изменены, то модуль может работать некорректно.

В этой части кода также происходит настройка линии 4 порта D (вывод 1 микросхемы) для работы в качестве выхода в режиме Push-Pull (Рисунок 3). После инициализации на этой линии порта сразу устанавливается низкий уровень, переводящий микросхему SP485 в режим приема. (За все эти настройки отвечает комбинированная константа GPIO_MODE_OUT_PP_LOW_FAST).

Особенности прерываний модуля UART

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

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

Рисунок 6. Организация прерываний модуля UART микроконтроллеров STM8.

Например, бит четности при передаче/приема пакета не используется, поэтому событие, возникающее при обнаружении его несоответствия, нас не интересует. Точно также нас не интересуют два специфических прерывания, возникающих при работе модуля по стандарту LIN (Local Interconnect Network) (CR4.LBDF и CR6.LHDF). Таким образом, остаются только пять событий, которые можно практически использовать в нашей задаче: освобождение передающего буфера (Transmit Data Register Empty, ТXE), завершение передачи байта (Transmission Complete, ТС), прием байта (Receive Data Register Not Empty, RXNE), переполнение приемного буфера (Overrun Error, OR) и остановка передачи данных в канале приема (Idle Line Detected, IDLE).

В микроконтроллерах STM8 система контроля и управления прерываниями приемопередатчика UART «размазана» по всему модулю. Например, биты хIEN (Interrupt ENabled), включающие прерывания, расположены в четырех разных конфигурационных регистрах (CR1, CR2, CR4 и CR6). Точно также флаги, показывающие, какое событие стало причиной вызова обработчика, расположены в трех регистрах, один из которых (Status Register, SR), как и положено, относится к категории регистров состояния, а вот остальные (CR4 и CR6) — к конфигурационным регистрам (Control Register, CR).

Но эта рассредоточенность не особо влияет на скорость написания программного кода. Например, если воспользоваться специализированными функциями из библиотеки StdPeriph_Lib: UART1_GetITStatus, UART1_ClearITPendingBit и UART1_ITConfig, то можно вообще не думать о том, в каком из регистров физически находится тот или иной управляющий бит или флаг. Дело в том, что одним из параметров, передаваемых в эти функции, является константа типа UART1_IT_TypeDef, в которой уже закодированы и номер нужного регистра, и номер нужного бита (Таблица 1). Таким образом, включить и выключить любое прерывание в любой момент можно с помощью функции UART1_ITConfig (Листинг 2), не особо думая о технических подробностях этой операции (хоть и немного в ущерб быстродействию).

Таблица 1. Описание констант UART1_IT_TypeDef
Константа Бит управления
прерыванием
Флаг
прерывания
Событие
UART1_IT_TXE CR2.TIEN SR.TXE Регистр передачи пустой
(начало передачи байта)
UART1_IT_TC CR2.TCIEN SR.TC Завершение передачи байта
UART1_IT_IDLE CR2.ILIEN SR.IDLE Прекращение приема данных
(простой линии)
UART1_IT_PE CR1.PIEN SR.PE Ошибка бита четности
UART1_IT_OR CR2.RIEN SR.OR Переполнение приемного
буфера
UART1_IT_RXNE CR2.RIEN SR.RXNE Прием байта (регистр приема
не пустой)
UART1_IT_RXNE_OR CR2.RIEN SR.PE (!!!) Прием байта и переполнение
буфера
UART1_IT_LBDF CR4.LBDIEN CR4.LBDF Остановка обмена при работе
по стандарту LIN

 

Листинг 2. Пример включения и выключения прерывания TXE с помощью подпрограммы UART1_ITConfig.

    // включение прерывания TXE
    UART1_ITConfig(UART1_IT_TXE, ENABLE);

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

Однако при использовании констант UART1_IT_TypeDef следует быть очень внимательным, поскольку разработчики StdPeriph_Lib немного «перемудрили» с этим инструментом. Например, при использовании подпрограммы UART1_ITConfig для того, чтобы включить прерывание при приеме байта и переполнении входного буфера, контролируемое общим битом CR2.RIEN (Рисунок 6), следует использовать константу UART1_IT_RXNE_OR. При включенной проверке значений входных параметров попытка передачи в эту подпрограмму констант UART1_IT_OR или UART1_IT_RXNE, ссылающихся на тот же управляющий бит, приведет к срабатыванию конструкции assert_param и к безусловному переходу в цикл обработки ошибок. Но если эту проверку отключить, что обычно делается в первую очередь, когда прошивка уже «не влезает» в память программ, то результат будет аналогичен — бит CR2.RIEN будет установлен или сброшен при передаче в эту функцию любой из трех констант UART1_IT_OR, UART1_IT_RXNE или UART1_IT_RXNE_OR.

А вот при проверке флагов с помощью функции UART1_GetITStatus константу UART1_IT_RXNE_OR использовать категорически нельзя, потому что она проверяет флаг… ошибки бита четности. Дело в том, что принцип кодирования, использованный при формировании значений констант UART1_IT_TypeDef, не позволяет сослаться одновременно на два бита (SR.OR и SR.RXNE). Поэтому авторы StdPeriph_Lib, видимо, не смогли придумать ничего лучшего, как вместо флага SR.OR, который, согласно технической документации и здравому смыслу, не может быть установлен при опущенном флаге SR.RXNE, решили проверить бит SR.PE, не имеющий никакого отношения к этим процессам (зато у него нулевая позиция). Поэтому при проверке флагов прерываний с помощью функций UART1_GetITStatus для проверки наличия принятого байта и переполнения входного буфера нужно использовать только соответствующие константы UART1_IT_RXNE и UART1_IT_OR.

Но особое недоумение вызывает функция UART1_ClearITPendingBit, предназначенная для сброса флага, вызвавшего прерывание. Дело в том, что в регистре статуса SR программно можно сбросить всего один флаг — флаг завершения передачи байта SR.TC (Таблица 2), но константы UART1_IT_TC в списке разрешенных параметров функции UART1_ClearITPendingBit почему-то нет. Зато есть константа UART1_IT_RXNE, хотя, согласно технической документации, бит SR.RXNE программно можно сбросить только в модулях UART2 и UART3.

В общем, кроме функции UART1_ITConfig, инструменты, реализованные в StdPeriph_Lib для работы с прерываниями модуля UART1, как-то не особо вдохновили на их использование, однако это не значит, что их нельзя дорабатывать по своему усмотрению. В любом случае, разработчики StdPeriph_Lib все эти моменты прописали в исходном коде соответствующих модулей, честно написав в лицензионном соглашении, что они не дают никакой гарантии, что этот код будет правильно работать (только, кто же все это читает?). Поэтому при использовании констант UART1_IT_TypeDef следует, по возможности, не отключать конструкции assert_param и понять, что, где и когда можно использовать (Таблица 3).

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

Таблица 2. Описание регистра SR (Status Register)
Бит Обозн. Функция Бит, разрешающий
прерывания
Алгоритм сброса бита
0 PE Ошибка бита четности CR1.PIEN Чтение регистра SR с последующим чтением
регистра DR, перед этим нужно дождаться
установки флага RXNE
1 FE Ошибка кадра Чтение регистра SR с последующим
чтением регистра DR
2 NF Зашумленный прием Чтение регистра SR с последующим
чтением регистра DR
3 OR Переполнение приемного
буфера
CR2.RIEN Чтение регистра SR с последующим
чтением регистра DR
4 IDLE Прекращение передачи
данных
CR2.ILIEN Чтение регистра SR с последующим
чтением регистра DR
5 RXNE Прием байта CR2.RIEN Чтение регистра DR. В модулях UART2 и UART3
возможна программная установка в «0»
6 TC Завершение передачи
байта
CR2.TCIEN Чтение регистра SR с последующей записью в
регистр DR. Программная установка в «0»
7 TXE Регистр передачи пуст CR2.TIEN Запись в регистр DR

 

Таблица 3. Область применения констант UART1_IT_TypeDef
  UART1_GetITStatus UART1_ClearITPendingBit UART1_ITConfig
UART1_IT_TXE + +
UART1_IT_TC + +
UART1_IT_IDLE + +
UART1_IT_PE + +
UART1_IT_OR +
UART1_IT_RXNE + +
UART1_IT_RXNE_OR +
UART1_IT_LBDF + + +

Например, флаг SR.TXE (регистр передачи пуст) можно сбросить только путем записи информации в регистр DR (Data Register — регистр, через который проходит вся принимаемая и передаваемая информация) (Таблица 2). Но если все байты уже отправлены, то записывать в этот регистр уже нельзя, иначе передатчик только будет бесконечно что-то передавать. Но если в регистр DR ничего не записать, то флаг SR.TXE не будет опущен, и ядро микроконтроллера будет бесконечно выполнять инструкции из обработчика прерывания. Единственным выходом в этой ситуации является отключение этого прерывания, в данном случае — путем сброса бита CR2.TIEN.

В этом и состоит главное отличие работы с прерываниями, генерируемыми модулем UART, от работы с прерываниями, генерируемыми, например, таймерами, где большинство флагов приходится сбрасывать программно. Прерывания модуля UART нужно включать только тогда, когда они необходимы, и выключать, когда они не нужны. Это «жонглирование» прерываниями является обязательным. В противном случае программа будет «висеть» в одном из обработчиков, ожидая сброса флага, который, возможно, уже не будет сброшен никогда. Следует отметить, что подобное «поведение» модуля UART характерно для многих платформ, в том числе PIC и AVR. Однако 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
Оцените статью
Добавить комментарий