Ещё раз.
Попробую оформить в виде небольших лаб. работ.
Генерируем сигнал при помощи конечного автомата (State Machine).
По умолчанию наши ядра тактируются частотой 125 МГц. От этой же частоты тактируются модули PIO.
К сожалению, делитель 16-разрядный (+8 разрядов дробных), поэтому не получится сделать такты ниже чем 125 МГц / 65 535 = 1907 Гц,
поэтому для начала просто сгенерируем меандр частотой 1 кГц.
В конечный автомат можно вписать небольшую программу, которая будет исполняться независимо от работы ядер центрального процессора.
Пусть наша программа:
- устанавливает вывод пина в 1,
- отсчитывает 50 тактов,
- устанавливает вывод пина в 0,
- отсчитывает ещё 50 тактов,
- повторяет всё снова.
Таким образом, мы получаем меандр.
Посчитаем такты: установить пин - 1 такт, подождать - 49 тактов,
установить пин в 0 - 1 такт, подождать - 48 тактов,
перейти на начало - 1 такт.
Общий период 1+49+1+48+1 = 100 тактов.
Если я хочу получить 1000 Гц за 100 тактов при частоте ядра 125 МГц, то
делитель частоты ядра будет равен 125 000 000 / 100*1000 = 1250.
Проверим,
Fядра = 125 000 000
делитель = 1250
Fpio = 125 000 000 / 1250 = 100 000 Гц.
100 000 Гц / 100 тактов = 1000 Гц.
Всё верно.
Но как установить задержки? В структуре команд PIO есть 5 бит отвечающих за счётчик команд задержки.
Таким образом, дополнительно к одному такту самой команды можно добавить до 31 такта задержки.
А нам нужно 49 и 48.
Придётся или добавлять команду NOP, или просто повторить команду установки/сброса пина.
Программа (адрес:команда)
0: установить вывод пина в 1, пропустить 31 такт,
1: установить вывод пина в 1, пропустить 17 тактов,
2: установить вывод пина в 0, пропустить 31 такт,
3: установить вывод пина в 0, пропустить 16 тактов,
4: перейти на адрес 0
Проверяем такты: 1+31+1+17 = 50, 1+31+1+16+1 = 50.
Должен получиться меандр.
Код:
// генератор 1 кГц на PIO
#include "hardware/pio.h"
#include "hardware/clocks.h"
static const uint16_t blink_instructions[] = {
0xff01, // 0: set pins, 1 [31]
0xf101, // 1: set pins, 1 [17]
0xff00, // 2: set pins, 0 [31]
0xf000, // 3: set pins, 0 [16]
0x0000 // 4: jmp 0
};
static const struct pio_program blink_prog = {
.instructions = blink_instructions,
.length = 5,
.origin = -1,
};
void setup() {
float div = (float)clock_get_hz(clk_sys) / (1000.0f * 100.0f);
const uint led_pin = 15;
PIO pio = pio0;
pio_clear_instruction_memory(pio);
uint offset = pio_add_program(pio, &blink_prog);
uint sm = pio_claim_unused_sm(pio, true);
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_set_pins(&c, led_pin, 1);
pio_gpio_init(pio, led_pin);
pio_sm_set_consecutive_pindirs(pio, sm, led_pin, 1, true);
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
void loop() {
}
Но одних только команд - мало.
инструкцией
Код:
static const uint16_t blink_instructions[] = {0xff01, 0xf101, 0xff00, 0xf000, 0x0000};
мы просто создали массив blink_instructions[] с 16-битными данными.
Надо его загрузить в память PIO.
Создаём дескриптор программы blink_prog для PIO SDK, который сообщает системе, как загрузить код в память инструкций PIO.
Код:
static const struct pio_program blink_prog = {
.instructions = blink_instructions,
.length = 5,
.origin = -1,
};
Что мы сообщаем:
- программа называется blink_prog,
- инструкции хранятся в массиве blink_instructions,
- длина программы = 5 инструкций (инструкции у нас 16-битные, нулевая инструкция тоже считается),
- программа релоцируемая (.origin = -1). Она может быть загружена по любому адресу в памяти PIO (от 0 до 31). Если бы здесь стояло число, например 5, программа могла бы работать только при загрузке строго в пятую ячейку памяти.
Далее, в блоке setup надо запустить наш конечный автомат.
Код:
float div = (float)clock_get_hz(clk_sys) / (1000.0f * 100.0f);
Считаем делитель
Код:
const uint led_pin = 15;
Назначаем пин, с которым будет работать конечный автомат
Код:
PIO pio = pio0;
Указываем аппаратный блок, из которого вызовем конечный автомат
В RP2040 доступны два независимых блока (pio0 и pio1), каждый блок имеет свою память инструкций по 32 инструкции на блок.
В каждом блоке можно запустить до 4-х самостоятельных конечных автомата.
Сейчас мы используем pio0.
Код:
pio_clear_instruction_memory(pio);
Очистить все инструкции в аппаратном блоке pio = pio0
Код:
uint offset = pio_add_program(pio, &blink_prog);
pio_add_program добавляет программу blink_prog в аппаратный блок pio = pio0
поскольку программа релоцируемая, то функция возвращает значение offset - фактический адрес начала размещения программы
Код:
uint sm = pio_claim_unused_sm(pio, true);
Запрашиваем свободный конечный автомат из аппаратного блока pio (который у нас pio0)
true: Этот флаг (panic) означает: «Если свободных машин нет - немедленно останови работу всей программы (вызови panic)».
функция резервирует и возвращает sm - номер свободной State Machine (от 0 до 3).
Код:
pio_sm_config c = pio_get_default_sm_config();
Создаём объект конфигурации С со значениями «по умолчанию» для нашего конечного автомата.
В PIO огромное количество настроек, лучше сначала загрузить дефолтные их значения и внести правки.
Код:
sm_config_set_set_pins(&c, led_pin, 1);
В структуре конфигурации С присвоить базовый индекс пина (led_pin=15) инструкции set pins. Работать с последовательностью пинов длиной 1 (т.е. только с одним 15 пином). Если бы было 2, то пины 15 и 16 управлялись командой set pins одновременно.
Код:
pio_gpio_init(pio, led_pin);
Выполняем аппаратную коммутацию пина (led_pin=15) от стандартного программного управления (функций вроде digitalWrite) на аппаратный блок (pio = pio0)
Код:
pio_sm_set_consecutive_pindirs(pio, sm, led_pin, 1, true);
Для конечного автомата sm в аппаратном блоке pio устанавливаем направление работы (input/output) для группы пинов начиная с (led_pin=15), длина группы - 1, направление true означает выход (Output), false означало бы вход.
Код:
sm_config_set_clkdiv(&c, div);
В структуре конфигурации С записываем рассчитанный коэффициент деления div.
Код:
pio_sm_init(pio, sm, offset, &c);
Записываем все настройки конфигурации С в конечный автомат sm аппаратного блока pio. Счётчик команд конечного автомата устанавливается на ячейку инструкций offset. Внутренние регистры конечного автомата очищаются.
Код:
pio_sm_set_enabled(pio, sm, true);
Запускается работа (true) конечного автомата sm аппаратного блока pio (команда со значением false мгновенно остановит конечный автомат).
С этого момента процессор больше не участвует в генерации меандра.
Результат - ровно 1000 Гц
Вложение:
1KHZ.jpg [ 138.95 КБ | 88 просмотров ]