Пока не начал разбираться в ESP-NOW, но значительно усовершенствовал генерацию ШИМ. Теперь алгоритм не сортирует значения, а строит маски регистров, по которым будут подниматься и гаситься порты. Причем если на канале ШИМ длина импульса ноль, то бит не попадет в маску и импульса действительно не будет. Также, каналы с одинаковым значением ширины импульса теперь поднимаются/гасятся одновременно, между ними не будет вызываться функция ets_delay_us(0); (все такие вызовы, если вообще будут, то пойдут в последних итерациях цикла строки 67, уже после гашения всех портов).
Можно менять кол-во элементов в cahnnels[]
, код отработает как надо. Время выполнения практически не поменяется, но с добавлением количества каналов точность может немного падать из-за увеличения количества операций в цикле генерации импульсов (строка 67).
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
// структура для хранения пары значений "номер вывода GPIO" - "ширина импульса PWM в микросекундах"
struct tChannel { uint8_t pin; uint16_t value; };
// массив каналов PWM
tChannel cahnnels[] = { { 18, 0 }, { 48, 0 }, { 1, 0 },
{ 2, 0 }, { 3, 0 }, { 4, 0 },
{ 12, 0 }, { 13, 0 }, { 14, 0 },
{ 5, 0 }, { 6, 0 }, { 7, 0 },
{ 9, 0 }, { 10, 0 }, { 17, 0 },
{ 21, 0 }, { 38, 0 }, { 47, 0 } };
// задача для генерации импульсов PWM на все каналы каждые 20 миллисекунд
void task_pwm (void *param) {
// ИНИЦИАЛИЗАЦИЯ
const uint8_t count = sizeof(cahnnels) / sizeof(cahnnels[0]);
// ограничение максимальной длины импульса
const uint16_t pulseWidthTreshold = 3000;
// маски регистров, по которым будут подниматься каналы
uint32_t up_port_0_mask = 0, up_port_1_mask = 0;
// массив последовательности временных задержек
uint16_t delays[count];
// массивы масок регистров, по которым будут гаситься каналы
uint32_t down_port_0_masks[count];
uint32_t down_port_1_masks[count];
for (uint8_t i = 0; i < count; i++) {
// конфигурация выводов
gpio_reset_pin((gpio_num_t)cahnnels[i].pin);
gpio_set_direction((gpio_num_t)cahnnels[i].pin, GPIO_MODE_OUTPUT);
gpio_set_pull_mode((gpio_num_t)cahnnels[i].pin, GPIO_FLOATING);
// установка нулевых стартовых значений для масок и задержек
delays[i] = 0;
down_port_0_masks[i] = 0;
down_port_1_masks[i] = 0;
}
// запуск бесконечного цикла задачи
for (;;) {
// блокировка шедулера - гарантия, что формирование импульсов выполнится без остановки на другие задачи
vTaskSuspendAll();
// время начала генерации импульсов (для определения момента следующего старта)
TickType_t last_PWM_at = xTaskGetTickCount();
// ГЕНЕРАЦИЯ ИМПУЛЬСОВ
// переача в регистры масок, содержащих биты всех портов с не нулевой длиной импульса
GPIO.out_w1ts |= up_port_0_mask;
GPIO.out1_w1ts.val |= up_port_1_mask;
// гашение портов согласно очередности с соответствующими задержками
for (uint8_t i = 0; i < count; i++) {
// задержка
ets_delay_us(delays[i]);
// переача в регистры масок, содержащих биты всех портов, которые необходимо погасить на данной итерации
GPIO.out_w1tc |= down_port_0_masks[i];
GPIO.out1_w1tc.val |= down_port_1_masks[i];
}
// ВЫЗОВ ПЕРЕРАССЧЕТА КИНЕМАТИКИ
// с использованием дельты времени 20 мс в качестве инкрементов всех процессов
// ...
// ПОДГОТОВКА ДАННЫХ ДЛЯ СЛЕДУЮЩЕЙ ИТЕРАЦИИ
// маски регистров, по которым будут подниматься порты (временные переменные)
uint32_t tmp_up_port_0_mask = 0, tmp_up_port_1_mask = 0;
// массив последовательности временных задержек (временные переменные)
uint16_t tmp_delays[count];
// массивы масок регистров, по которым будут гаситься порты (временные переменные)
uint32_t tmp_down_port_0_masks[count];
uint32_t tmp_down_port_1_masks[count];
// установка нулевых значений задержек и масок
for (uint8_t i = 0; i < count; i++) {
tmp_delays[i] = 0;
tmp_down_port_0_masks[i] = 0;
tmp_down_port_1_masks[i] = 0;
}
// минимальная длина импульса, определенная на предыдущей итерации цикла наполнения массивов
uint16_t minPreviouse = 0;
// минимальная длина импульса, определенная на текущей итерации цикла наполнения массивов
uint16_t minCurrent = cahnnels[0].value;
// счетчик обработанных каналов
uint8_t current = 0;
// индекс текущего элемента во временных массивах
uint8_t to = 0;
// признак того, что наполнение массивов прошло успешно
bool succesfull = true;
// цикл наполнения массивов
while ((current < count) && succesfull) {
// поиск минимального значения длины импульса, превосходящего предыдущее значение (найденное в предыдущей итерации)
for (uint8_t i = 0; i < count; i++) if (cahnnels[i].value < minCurrent && cahnnels[i].value > minPreviouse)
minCurrent = cahnnels[i].value;
// запись значения дельты времени в массив задержек
tmp_delays[to] = minCurrent - minPreviouse;
// перезапись предыдущего минимального значения
minPreviouse = minCurrent;
// сброс признака успешного наполнения массивов
succesfull = false;
// формирование масок для всех каналов ШИМ, у которых длина импульса равна найденной
for (uint8_t from = 0; from < count; from++) if (cahnnels[from].value == minCurrent) {
// если cahnnels[] не изменялся извне и наполнение проходит успешно, то данная команда выполнится хотя бы раз на каждой итерации цикл наполнения
succesfull = true;
// если длина импульса на канале ненулевая, то в порты добавляется бит соответствующего пина
if (minCurrent > 0) {
// определение группы регистров
if (cahnnels[from].pin > 32) {
// смещение в регистрах группы 1
uint32_t add_1 = 1 << (cahnnels[from].pin - 32);
// добавление в маску регистра поднимающихся портов
tmp_up_port_1_mask |= add_1;
// добавление в маску регистра гасящихся портов
tmp_down_port_1_masks[to] |= add_1;
} else {
// смещение в регистрах группы 0
uint32_t add_0 = 1 << cahnnels[from].pin;
// добавление в маску регистра поднимающихся портов
tmp_up_port_0_mask |= add_0;
// добавление в маску регистра гасящихся портов
tmp_down_port_0_masks[to] |= add_0;
}
}
// инкриментация счетчика обработанных каналов
current++;
}
// инкриментация индекса текущего элемента в наполняемых массивах
to++;
// сброс минимума для успешного поиска на следующей итерации
minCurrent = 65535;
}
// если наполнения массивов прошло успешно, а самый длинный импульс не превышает установленный порог
if (succesfull && (minPreviouse < pulseWidthTreshold)) {
// копирование масок поднятия портов
up_port_0_mask = tmp_up_port_0_mask;
up_port_1_mask = tmp_up_port_1_mask;
// копирование значениq из временных массивов в массивы для генерации импульсов
for (uint8_t i = 0; i < count; i++) {
delays[i] = tmp_delays[i];
down_port_0_masks[i] = tmp_down_port_0_masks[i];
down_port_1_masks[i] = tmp_down_port_1_masks[i];
}
} // else при желании можно зафиксировать ошибку
// разблокировка шедулера
xTaskResumeAll();
// отдаем управление шедулеру
vTaskDelayUntil(&last_PWM_at, pdMS_TO_TICKS(20));
}
// аварийная остановка задачи (не должна сработать никогда)
vTaskDelete(NULL);
}
void setup () {
Serial.begin (9600);
// добавление задачи в шедулер (функция, имя, размер_стека, параметры_NULL, приоритет, хэндлер_NULL, текущее_ядро)
xTaskCreatePinnedToCore (task_pwm, "PWM", 4096, NULL, (2 | portPRIVILEGE_BIT), NULL, xPortGetCoreID()); //TaskHandle_t taskHandler_pwm;
}
uint16_t val[18] = { 1500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500 };
void loop() {
for (uint8_t i = 0; i < 18; i++) {
val[i] += i;
if (val[i] > 4500) val[i] = 500;
cahnnels[i].value = val[i] > 2500 ? 5000 - val[i] : val[i];
Serial.print(" " + String(cahnnels[i].value));
}
Serial.println();
delay(20);
}