Привет ребята. Приступая к очередному проекту решил, наконец, мигрировать с ненавистного мне 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).
Комментарии (15)
artalex
01.01.2025 14:55Очень странно что не используется FIFO.
Раз уж вы решили чему-то полезному научить "ардуинщиков", то на полпути не стоило останавливаться :)
lolikandr
01.01.2025 14:55Тогда уж и stdout отправить в uart, что бы весь набор puts, printf и т.д. заработал.
svperchenko Автор
01.01.2025 14:55ИМХО, конечно. Но FIFO имеет смысл использовать когда есть непрерывный стабильный поток данных на передачу. Например, оцифрованный сигнал какой-нибудь.
CrashLogger
01.01.2025 14:55Спасибо. Пишите еще. В сети действительно очень мало материалов по нативному программировани под R2040. Одни ардуинщики и питонщики (
Yuri0128
01.01.2025 14:55В смысле, - на ассемблере? Так он здеся не обписан....
Я вот на микропитоне на rp2040 тоже писал, как раз з применением инлайна на асме - вот это было нативное пограммирование, - но так прилично ускорить удалось некоторые векторные операции.
kuzzdra
01.01.2025 14:55Перфекционисты спрашивают, что это за прикол - программный код в виде скриншотов, все разной ширины, у одного
лапка соскальзываеткрай обрезан?
Gordon01
01.01.2025 14:55Вот никогда не понимал этих переизобретений колёс
Все уже написано за нас https://docs.embassy.dev/embassy-rp/git/rp2040/uart/struct.BufferedUart.html
Просто делаешь
uart.write(&data).await
и ВСЕ
juramehanik
01.01.2025 14:55где printf? Прежде чем ругать ардуинщиков, откройте там учебник по программированию где начинают с hello world и какие стандартные иснструменты используют в отрасли, когда стараются получать результат которым могут пользоваться другие.
Stdin stdout тут не обязателен, достаточно релизаций _read _write
bazuchan
01.01.2025 14:55Про stm32 - для слабых вариантов libopencm3 попробуйте, все отлично без графических конфигураторов. Для мощных - какую-нить из опенсорсных rtos.
Rp4020 - нет вообще никакого readout protection, без него мало на что годится...
slog2
Самый главный недостаток STM32 - слишком популярные, из-за этого и цены стали не адекватные и не поддельный чип купить проблема большая. Особенно когда "санкции только на пользу" а из поставщиков остались только китайцы.
MikeSmith
Второй главный недостаток - это наличие "графических" средств конфигурации и генерации шаблонных проектов. Что существенно снижает порог входа, но не делает микроконтроллер проще, и не отменяет необходимость чтения даташитов. После изучения экосистемы STM32 работа с любым контроллером этого семейства не вызывает особой сложности. А познание RP2040 пригодится только для работы с RP2040
Moog_Prodigy
ST еще в 2010 годах (примерно) вкинуло в рекламу больше денег, чем было потрачено на разработку этих МК (они сами об этом писали). И медленно но верно реклама дала плоды. Примерно чуть позже начало штормить уютненький мир pic и atmel...и мы все знаем, что произошло дальше.
Но тем не менее, мк жирные, и даже за оверпрайс они хорошие. Китайцы бы их не копировали, будь это не так.