Суть работы с микроконтроллером - грамотно использовать имеющуюся периферию. Мы можем реализовать любой протокол через GPIO - но это будет неэффективно - на CPU помимо работы с памятью ложится обязанность управлять напряжением на ножках. Так он тратит драгоценное время на пустые циклы ожидания, чтобы выдержать частоту протокола.
Чтобы последний успевал делать хоть что-то, разработчики МК поместили на кристалл отдельную схемотехнику для самых распространенных протоколов: SPI I2C UART.
Каждая из этих аппаратных реализаций имеет свой входной FIFO буфер. Размеры отличаются в зависимости от протокола и камня - от двух до нескольких десятков байт.
В этом случае CPU берет данные из RAM, кладет их в специальный регистр и запускает тот же SPI, записав пару флагов в его спец регистры. Как только SPI закончил, может произойти два варианта - он поставит спец флаг означающий "буфер пустой" или дернет прерывание которое CPU обработает.
Представим теперь, что нам нужно затолкать буфер в OLED 128х64 дисплей - 1024 байта, на каждый из которых процессор должен мотаться перекладывая данные внутри RAM.
Чтобы этого избежать, инженеры ввели специальный аппаратный модуль - DMA.
Direct Memory Access нужен как раз для такой тупой работы. CPU скажет (а говорит от всегда через запись каких-то битов в какие-то регистры) "клади 1024 байта начиная с 0xXXXXXXXX в регистр данных SPI (у него есть адрес в памяти)".
Чтобы он понимал с какой скоростью заталкивать туда биты (а он делает это вообще говоря быстрее чем периферия отрабатывает), вся периферия подпаяна "проводочком" DREQ с модулем DMA. Считайте что это некоторая упрощенная версия прерывания. У каждого такого проводочка есть свой числовой ID (лежат в hardware/regs/dreq.h).
#define DREQ_SPI0_TX 16
#define DREQ_SPI0_RX 17
//...
#define DREQ_UART0_TX 20
#define DREQ_UART0_RX 21
Когда мы конфигурируем DMA - мы складываем эту чиселку в его управляющий регистр. Во время работы контроллер будет ожидать сигналы от DREQ данной указанной периферии.
DMA на rp2040 имеет 12 каналов, которые гоняются арбитром по Round-Robin. Канал это ресурс - при написании кода с ними нужно быть осторожными. Например библиотека "oled_lib_by_vasek1337.h" могла захардкодить себе несколько каналов. Pico SDK предоставляет следующие методы:
int my_dma_channel = dma_claim_unused_channel(true); // (bool panic)
dma_channel_unclaim(my_dma_channel);
Специфика i2c на rp2ххх
У аппаратного I2C в RP2040 используется 16-битный регистр IC_DATA_CMD. Чтобы отправить байт по I2C, вы обязаны записать туда 16-битное слово - 8 бит на данные и 8 бит на настройки. Причем, последний байт передаваемых данных обязательно должен содержать stop бит.
Это усложняет работу с DMA. На выбор у вас есть три решения:
- Хранить framebuffer дисплея в памяти не как массив
uint8_t(1024 байта), а как массивuint16_t(2048 байт). Соответствующим образом подготовив биты. - Один канал DMA кидает данные, а второй (контрольный) канал по завершении первого просыпается и дозаписывает команду STOP в регистр управления I2C.
- официальном репозитории
pico-examplesесть пример I2C реализованного через PIO.
Лабораторная 2
Собрать физический интерфейс управления GPIO для rp2040. Используем OLED и ротационный энкодер
Условия:
Использование сторонних библиотек запрещено.
Задачи:
- Подключить дисплей из набора через аппаратный i2c, используя модуль DMA. Понять и объяснить проблему и обосновать решение.
- Подключить и обосновать способ обработки ротационного энкодера.
- Придумать интерфейс для отображения GPIO пинов:
- Отображение текущего состояния
- Режим и его смена (input/output/pwm)
- Так как дисплей миниатюрный, продуманность и качество интерфейса оценивается выше, чем количество управляемых пинов. Уместите сколько сможете не отходя от вашей задумки, но хочется видеть хотя бы 8.
Допзадачи:
- Применить pio для обработки энкодера.
- Наивный подход к обновлению экрана - обновить framebuffer - затолкать framebuffer. sd13xx в стандартном режиме принимает данные "полосками", но ширина полоски может быть сконфигурирована - так мы будем обновлять только тот квадрат, что изменился (Dirty Rectangles). Нужно предложить удачную реализацию такой оптимизации.