Выйдя на рынок недорогих 8-разрядных микроконтроллеров, компания STMicroelectronics сделала «предложение, от которого нельзя отказаться». Судя по количеству проектов и публикаций в сети, радиолюбителям и профессиональным разработчикам особо «понравились» микросхемы бюджетной серии STM8S в компактных корпусах TSSOP20. Не последнюю роль в этом, возможно, сыграло наличие недорогих плат с предустановленными микросхемами STM8S103F3P6 (Рисунок 1), которые можно установить в беспаечные отладочные платы, так обожаемые поклонниками платформы Arduino. И хоть сейчас из-за общемировой нехватки радиоэлементов розничные цены на микросхемы этой серии возросли более чем в пять раз, на сайте STMicroelectronics при заказе партии не менее чем из 10 тысяч приборов микроконтроллеры STM8S003F3P6 все еще доступны по цене 0.28 USD за шутку. А это, в совокупности с постепенным снижением розничных цен на эти микросхемы, происходившим в течение 2021 года, вселяет надежду, что они снова станут такими же доступными, как и раньше.
Рисунок 1. | Отладочная плата с микроконтроллером STM8S103F3P6. |
Я тоже не устоял перед искушением использовать эти микроконтроллеры в своих разработках и создал на основе STM8S003F3P6 небольшую систему домашней автоматизации, первая версия которой через несколько месяцев будет проходить испытания в установке для выращивания рассады. Фактически я создал конструктор, состоящий из унифицированных модулей с общим аппаратным и программным интерфейсом (Рисунок 2), комбинирование которых позволяет решать самые разные задачи. Например, в первом варианте система будет по расписанию управлять фитолампами, включая их только днем и при условии, что уровень внешнего освещения будет ниже запрограммированного порога.
Рисунок 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.
Рисунок 3. | Принципиальная схема модуля реле. |
В нормальном режиме работы микроконтроллер и приемопередатчик RS-485 питаются напряжением 5 В, формируемым стабилизатором DA1. Однако из-за того, что дешевые версии программатора ST-LINK могут работать только с сигналами, уровень которых равен 3.3 В, во время программирования и отладки программного обеспечения питать микроконтроллер и основные микросхемы приходится напряжением 3.3 В, получаемым от программатора через разъем X2. Однако это даже оказалось удобным, поскольку при отладке всю систему можно запитать от одного USB-порта и не возиться с громоздкими лабораторными источниками питания. Можно было бы, конечно, и перейти на основное питание 3.3 В, однако микросхемы, рассчитанные на работу при таком напряжении, например, MAX3485 или LM1117-3.3, почему-то стоят в несколько раз дороже своих 5-вольтовых аналогов.
Остальные элементы являются «стандартной обвязкой», установленной в соответствии с технической документацией на выбранные микросхемы или на основе общих принципов проектирования радиоэлектронных устройств.
Протокол обмена данными
Обмен данными в системе происходит в форме диалога. Ведущее устройство (модуль контроллера) периодически посылает каждому модулю команду в определенном формате (Рисунок 4).
Рисунок 4. | Формат команды от ведущего к ведомому. |
Во время работы все ведомые модули «слушают» информационную шину и при обнаружении пакета, адресованного им (при совпадении поля «Номер устройства» с настройкой, записанной в собственной энергонезависимой памяти), передают ведущему ответ, также в фиксированном формате (Рисунок 5).
Рисунок 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), перенаправляющих основной поток программы на один из двух обработчиков, вызываемых, соответственно, при передаче и приеме информации. Однако не все прерывания нужны для нашей задачи.
Рисунок 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 | ||||||||||||||||||||||||||||||||||||
|
Листинг 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) | |||||||||||||||||||||||||||||||||||||||||||||
|
Таблица 3. | Область применения констант UART1_IT_TypeDef | ||||||||||||||||||||||||||||||||||||
|
Например, флаг SR.TXE (регистр передачи пуст) можно сбросить только путем записи информации в регистр DR (Data Register — регистр, через который проходит вся принимаемая и передаваемая информация) (Таблица 2). Но если все байты уже отправлены, то записывать в этот регистр уже нельзя, иначе передатчик только будет бесконечно что-то передавать. Но если в регистр DR ничего не записать, то флаг SR.TXE не будет опущен, и ядро микроконтроллера будет бесконечно выполнять инструкции из обработчика прерывания. Единственным выходом в этой ситуации является отключение этого прерывания, в данном случае — путем сброса бита CR2.TIEN.
В этом и состоит главное отличие работы с прерываниями, генерируемыми модулем UART, от работы с прерываниями, генерируемыми, например, таймерами, где большинство флагов приходится сбрасывать программно. Прерывания модуля UART нужно включать только тогда, когда они необходимы, и выключать, когда они не нужны. Это «жонглирование» прерываниями является обязательным. В противном случае программа будет «висеть» в одном из обработчиков, ожидая сброса флага, который, возможно, уже не будет сброшен никогда. Следует отметить, что подобное «поведение» модуля UART характерно для многих платформ, в том числе PIC и AVR. Однако STM8, на мой взгляд, является, абсолютным лидером по сложности сброса флагов этого узла.
Дополнительная информация
- STM8S Series and STM8AF Series 8-bit microcontrollers. Reference Manual
- STM8S Value line
- STM8S/A Standard Peripherals Library
- Настройка UART на микроконтроллере STM8
Материалы по теме
- Datasheet STMicroelectronics STM8S003F3