RP2040. Асинхронный вывод в UART по прерываниям

в 13:19, , рубрики: rp2040, uart

Привет ребята. Приступая к очередному проекту решил, наконец, мигрировать с ненавистного мне STM32 на что-то более простое, компактное и понятное. Чтобы закрыть вопрос с STMками, выскажу свое мнение, что они превратились в монстров, которых без графических «костылей» и сконфигурировать то с нуля невозможно. А чтение документации на железо превращается в пытку. Взять хотя бы настройку ШИМа в таймерах…

Поскольку начинал я с AVRов, то не оставляло меня желание найти современный производительный контроллер, такой же простой и понятный. Для себя такой камень я нашел, когда дошли, наконец, руки прочитать документацию на контроллеры от Paspberry Pi. В частности, RP2040 как наиболее распространенный и раскрученный.

Сразу отвечу на возможную претензию по поводу того, что программа хранится во внешней памяти. Это не является проблемой для большинства, так как можно использовать готовые платы с контроллером и памятью и с USB разъемом для прошивки (самая доступная плата сейчас по цене 170 рублей с доставкой из Китая). А что касается производительности, так всю программу можно исполнять из внутреннего ОЗУ, благо, размер у него немаленький.

Итак, вот те моменты, из-за которых я решил инвестировать свое время в изучение этого МК и мигрировать на него с STM32:

  • сам микроконтроллер по простоте и компактности сопоставим с AVR, но тут вы получаете 2 32-разрядных ядра на частоте до 133МГц, ОЗУ 264кБ до 16МБ флэша программ, и USB 1.1 контроллер. Вся необходимая периферия, не в избытке, но присутствует;

  • отдельно радует выделенный ШИМ‑контроллер, который заточен только на эту функцию и будет очень прост в освоении даже новичками. Но все «взрослые» функции в нем присутствуют, в том числе и Phase Correct Mode;

  • очень подробная, но при этом предельно понятно написанная документация. Для новичков вместе с описанием периферийных модулей дается теоретическое описание из работы (например, ШИМ) а также примеры кода его использования с применением SDK;

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

Теперь пару слов, собственно, о цели этой статьи. Я заметил, что RP2040 оккупировали ардуинщики и микропитонщики для использования в качестве «ардуино на стероидах». Вот для этих людей, которые хотят повзрослеть и начать, наконец, embedded-разработку на настоящих языках программирования и написана эта статья. Взрослые разработчики, соответственно, дальше могут не читать.

Одна из первых вещей о которых нужно позаботиться при разработке каждого проекта - логирование в UART. Это все еще полезно несмотря на наличие внутрисхемного отладчика. И тут мы сталкиваемся с проблемой того, что запись в UART в SDK реализована только синхронно. Соответственно, если у вас простой проект без использования RTOS с многозадачностью, такое логирование будет тормозить ваш главный цикл и существенно снижать отклик вашей программы. Хорошим решением в этом случае будет использование прерываний для передачи данных в UART. Это позволит не блокировать главный цикл на время выдачи данных с последовательного порта. Минусом является необходимость выделения в ОЗУ буфера достаточной длины, чтобы вместить потенциально самое длинное сообщение. Итак, смысл в том, чтобы иметь кольцевой буфер в который можно записывать данные для передачи. При каждой записи нам нужно проверять работает ли на данный момент UART на передачу. Если нет – нужно стартовать передачу первого байта из буфера. Дальше все сделает прерывание. После завершения передачи каждого байта UART вызывает прерывание, в обработчике которого нам нужно взять очередной байт для передачи из буфера и передать его в UART. И так будет продолжаться пока буфер не опустеет (либо цикл будет повторяться, если основная программа будет подкидывать новые данные для передачи).

Теперь, собственно, к реализации. Исходники доступны в репозитории. Там же в ридми есть ссылки на документацию и руководство по установке и началу работы с МК.

Заведем файл с определениями в котором выберем модуль UART и параметры его работы.

Настройки последовательного порта

Настройки последовательного порта

Сам алгоритм реализуем в виде библиотеки в файлах log.c/log.h. В заголовочном файле определим публичный АПИ нашего логгера.

АПИ логгера
АПИ логгера

Тут видим функцию инициализации, которой нужно передать указатель на структуру используемого UARTа. Также имеем 2 функции логирования.

Среди глобальных переменных в логгере имеем указатель на UART и его прерывание, а также кольцевой буфер с указателем на его голову и размер: head_index, size. Размер буфера задается тоже тут: LOG_BUFF_SIZE.

Инициализация логгера

Инициализация логгера

UART, который передается в функцию инициализации должен быть предварительно настроен (выставлена скорость и схема передачи). Это позволяет задавать параметры последовательного порта в клиентском коде. Функция инициализации донастраивает UART: устанавливает обработчик прерывания on_tx() и активирует прерывание только на передачу uart_set_irq_enables. Также тут инициализируется состояние буфера и устанавливается флаг need_start, который указывает не необходимость стартовать передачу при следующий записи данных.

Функция Logs() является фактически прокси для основной функции логирования Log_non_blocking(), которая осуществляет запись в кольцевой буфер ( putCharactersToQueue() ) и по флагу need_start производит запуск передачи путем принудительного вызова обработчика прерывания on_tx()

Функции логирования

Функции логирования

Следует обратить внимание, что если буфер не может принять все данные, функция putCharactersToQueue() вернет реально записанное число байтов и с этим числом и нужно работать. Все, что сверху, будет потеряно.

В функции записи putCharactersToQueue() реализуется логика кольцевого буфера и производится защита целостности данных путем выключения прерываний от UART на время изменения буфера и последующим включением (строки 54, 69). В строке 61 вычисляется индекс с которого начинать дописывать новые данные. Деление по модулю длины буфера позволяет «завернуть» индекс в начало буфера. Далее в цикле записываются данные и производится инкремент индекса с его «заворачиванием».

Добавление в кольцевой буфер

Добавление в кольцевой буфер

Вся «магия» происходит в обработчике прерывания. Тут верифицируется что UART готов принять данные на передачу (строка 75) и далее, если в буфере есть еще данные, следующий байт извлекается и передается в UART на передачу (строка 81). Инкремент указателя головы и «заворачивание» при необходимости выполняются тоже тут (строки 77 – 80). Далее, если видим, что буфер опустел, выставляем флаг need_start. Если же при вызове прерывания обнаруживаем что буфер уже пустой (это происходит при завершении передачи последнего байта), нам нужно обязательно вручную сбросить флаг прерывания (строка 87) иначе сразу после выхода из обработчика прерывание будет вызвано снова. В документации на UART сказано, что флаг прерывания автоматически сбрасывается только при записи в UART нового байта на передачу.

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

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

Собственно, это и все. В мэйне делаем начальную инициализацию модуля UART и нужных GPIO и можно пользоваться. Не забыть только настроить альтернативную функцию GPIO, чтобы подключить UART к выходу (строка 14).

Использование

Использование

Автор: svperchenko

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js