Выбор контроллера (18+ ШИМ, беспроводная связь, 3.3 логика, встроенный BEC)

#include "driver/timer.h"
#include <soc/gpio_struct.h>

uint64_t prev = 0;
uint64_t pheriod = 0;
uint64_t count = 0;
uint64_t microscount = 0;


uint32_t PWM01 = 0, PWM02 = 0, PWM03 = 0, PWM04 = 0, PWM05 = 0, PWM06 = 0,
         PWM07 = 0, PWM08 = 0, PWM09 = 0, PWM10 = 0, PWM11 = 0, PWM12 = 0,
         PWM13 = 0, PWM14 = 0, PWM15 = 0, PWM16 = 0, PWM17 = 0, PWM18 = 0;

static void IRAM_ATTR timer0_ISR (void *arg) {
  
  // очистка флагов прерываний
	timer_group_clr_intr_status_in_isr (TIMER_GROUP_0, TIMER_0);
	// перезапуск прерывания Alarm
	timer_group_enable_alarm_in_isr (TIMER_GROUP_0, TIMER_0);
  

  pheriod = millis() - prev;
  prev = millis();
  count = 0;
  microscount = micros();

  /*
               NANO
  GPIO48 PWM09      PWM18 GPIO21
   GPIO1 PWM08      PWM17 GPIO18
   GPIO2 PWM07      PWM16 GPIO17
   GPIO3 PWM06      PWM15 GPIO10
   GPIO4 PWM05      PWM14 GPIO9
  GPIO11 PWM04      PWM13 GPIO8
  GPIO12 PWM03      PWM12 GPIO7
  GPIO13 PWM02      PWM11 GPIO6
  GPIO14 PWM01      PWM10 GPIO5
  
  */

  // поднимаем порты - начало импульса ШИМ
  gpio_set_level(GPIO_NUM_14, HIGH);
  gpio_set_level(GPIO_NUM_13, HIGH);
  gpio_set_level(GPIO_NUM_12, HIGH);
  gpio_set_level(GPIO_NUM_11, HIGH);
  gpio_set_level(GPIO_NUM_4 , HIGH);
  gpio_set_level(GPIO_NUM_3 , HIGH);
  gpio_set_level(GPIO_NUM_2 , HIGH);
  gpio_set_level(GPIO_NUM_1 , HIGH);
  gpio_set_level(GPIO_NUM_48, HIGH);
  gpio_set_level(GPIO_NUM_5 , HIGH);
  gpio_set_level(GPIO_NUM_6 , HIGH);
  gpio_set_level(GPIO_NUM_7 , HIGH);
  gpio_set_level(GPIO_NUM_8 , HIGH);
  gpio_set_level(GPIO_NUM_9 , HIGH);
  gpio_set_level(GPIO_NUM_10, HIGH);
  gpio_set_level(GPIO_NUM_17, HIGH);
  gpio_set_level(GPIO_NUM_18, HIGH);
  gpio_set_level(GPIO_NUM_21, HIGH);

  bool wait01 = true, wait02 = true, wait03 = true, wait04 = true, wait05 = true, wait06 = true,
       wait07 = true, wait08 = true, wait09 = true, wait10 = true, wait11 = true, wait12 = true,
       wait13 = true, wait14 = true, wait15 = true, wait16 = true, wait17 = true, wait18 = true;
  
  uint64_t value = 0;
  while (value < 120000) {

     count++;
  
    // получение текущего значения таймера uint64_t value
    timer_get_counter_value (TIMER_GROUP_0, TIMER_0, &value);
    
    if (wait01) if (value > PWM01) { gpio_set_level(GPIO_NUM_14, LOW); wait01 = false; }
    if (wait02) if (value > PWM02) { gpio_set_level(GPIO_NUM_13, LOW); wait02 = false; }
    if (wait03) if (value > PWM03) { gpio_set_level(GPIO_NUM_12, LOW); wait03 = false; }
    if (wait04) if (value > PWM04) { gpio_set_level(GPIO_NUM_11, LOW); wait04 = false; }
    if (wait05) if (value > PWM05) { gpio_set_level(GPIO_NUM_4 , LOW); wait05 = false; }
    if (wait06) if (value > PWM06) { gpio_set_level(GPIO_NUM_3 , LOW); wait06 = false; }
    if (wait07) if (value > PWM07) { gpio_set_level(GPIO_NUM_2 , LOW); wait07 = false; }
    if (wait08) if (value > PWM08) { gpio_set_level(GPIO_NUM_1 , LOW); wait08 = false; }
    if (wait09) if (value > PWM09) { gpio_set_level(GPIO_NUM_48, LOW); wait09 = false; }
    if (wait10) if (value > PWM10) { gpio_set_level(GPIO_NUM_5 , LOW); wait10 = false; }
    if (wait11) if (value > PWM11) { gpio_set_level(GPIO_NUM_6 , LOW); wait11 = false; }
    if (wait12) if (value > PWM12) { gpio_set_level(GPIO_NUM_7 , LOW); wait12 = false; }
    if (wait13) if (value > PWM13) { gpio_set_level(GPIO_NUM_8 , LOW); wait13 = false; }
    if (wait14) if (value > PWM14) { gpio_set_level(GPIO_NUM_9 , LOW); wait14 = false; }
    if (wait15) if (value > PWM15) { gpio_set_level(GPIO_NUM_10, LOW); wait15 = false; }
    if (wait16) if (value > PWM16) { gpio_set_level(GPIO_NUM_17, LOW); wait16 = false; }
    if (wait17) if (value > PWM17) { gpio_set_level(GPIO_NUM_18, LOW); wait17 = false; }
    if (wait18) if (value > PWM18) { gpio_set_level(GPIO_NUM_21, LOW); wait18 = false; }

  }
  
  // на всякий случай опускаем порты - гарантировано конец импульса ШИМ (если передадим импульс длиннее 120000)
  gpio_set_level(GPIO_NUM_14, LOW);
  gpio_set_level(GPIO_NUM_13, LOW);
  gpio_set_level(GPIO_NUM_12, LOW);
  gpio_set_level(GPIO_NUM_11, LOW);
  gpio_set_level(GPIO_NUM_4 , LOW);
  gpio_set_level(GPIO_NUM_3 , LOW);
  gpio_set_level(GPIO_NUM_2 , LOW);
  gpio_set_level(GPIO_NUM_1 , LOW);
  gpio_set_level(GPIO_NUM_48, LOW);
  gpio_set_level(GPIO_NUM_5 , LOW);
  gpio_set_level(GPIO_NUM_6 , LOW);
  gpio_set_level(GPIO_NUM_7 , LOW);
  gpio_set_level(GPIO_NUM_8 , LOW);
  gpio_set_level(GPIO_NUM_9 , LOW);
  gpio_set_level(GPIO_NUM_10, LOW);
  gpio_set_level(GPIO_NUM_17, LOW);
  gpio_set_level(GPIO_NUM_18, LOW);
  gpio_set_level(GPIO_NUM_21, LOW);

  microscount = micros() - microscount;

}



void setup () {

  pinMode(A7, OUTPUT);
  pinMode(A6, OUTPUT);
  pinMode(A5, OUTPUT);
  pinMode(A4, OUTPUT);
  pinMode(A3, OUTPUT);
  pinMode(A2, OUTPUT);
  pinMode(A1, OUTPUT);
  pinMode(A0, OUTPUT);
  pinMode(D13, OUTPUT);

  pinMode(D2, OUTPUT);
  pinMode(D3, OUTPUT);
  pinMode(D4, OUTPUT);
  pinMode(D5, OUTPUT);
  pinMode(D6, OUTPUT);
  pinMode(D7, OUTPUT);
  pinMode(D8, OUTPUT);
  pinMode(D9, OUTPUT);
  pinMode(D10, OUTPUT);

  // таймер опирается на 80mHz осцилятор!
  timer_config_t config = {
    .alarm_en = TIMER_ALARM_EN, // включение аларма таймера enum: 0 TIMER_ALARM_DIS | 1 TIMER_ALARM_EN
    .counter_en = TIMER_PAUSE, // cостояние после инициализации enum: 0 TIMER_PAUSE | 1 TIMER_START
    .intr_type = TIMER_INTR_LEVEL, // прерывание по уровню enum: 0 TIMER_INTR_LEVEL
    .counter_dir = TIMER_COUNT_UP, // направление счетчика enum: 0 TIMER_COUNT_DOWN | 1 TIMER_COUNT_UP
    .auto_reload = TIMER_AUTORELOAD_EN, // настройка автоматического рестарта прерывания enum: 0 TIMER_AUTORELOAD_DIS | 1 TIMER_AUTORELOAD_EN
    .divider = 2 // предделитель uint16_t: 2..65535
	};
  // инициализация таймера
  timer_init(TIMER_GROUP_0, TIMER_0, &config);
  uint64_t value = 1;
  uint64_t alarm_value = 800000;
  // начальное значение счетчика uint64_t value: 0..4294967295
  timer_set_counter_value (TIMER_GROUP_0, TIMER_0, value);
  // значение, при достижении которого сработает прерывание uint64_t alarm_value: 0..4294967295
  timer_set_alarm_value (TIMER_GROUP_0, TIMER_0, alarm_value);
  // разрешение прерывания
  timer_enable_intr (TIMER_GROUP_0, TIMER_0);
  // привязка функции обработки перрывания
  timer_isr_register (TIMER_GROUP_0, TIMER_0, timer0_ISR, NULL, 0, NULL);
  // запуск таймера
  timer_start (TIMER_GROUP_0, TIMER_0);

  Serial.begin (9600);
  Serial.println ("Hi!");

}



uint32_t val = 20000;

void loop() {

  val++;
  if (val > 180000) val = 20000;
  uint32_t pwm = val > 100000 ? 200000 - val : val;

  PWM01 = 60000;
  PWM02 = pwm;
  PWM03 = pwm;
  PWM04 = pwm;
  PWM05 = pwm;
  PWM06 = pwm;
  PWM07 = pwm;
  PWM08 = pwm;
  PWM09 = pwm;
  PWM10 = pwm;
  PWM11 = pwm;
  PWM12 = pwm;
  PWM13 = pwm;
  PWM14 = pwm;
  PWM15 = pwm;
  PWM16 = pwm;
  PWM17 = pwm;
  PWM18 = pwm;
  
  Serial.println ("pheriod: " + String(pheriod) + ", long: " + String(microscount) + ", iterations: " + String(count) + ", pwm: " + String(pwm) + ", on " + String(getCpuFrequencyMhz()) + "mHz"); 

}

Пока что только этот код выдает стабильный ШИМ. При цикле 3мс обеспечивает 1100-1300 дискретных состояний. Разумеется, т.к. у сервы импульсы короче 500 мкс ничего не решают, как и импульсы длиннее 2500 мкс, этот диапазон сокращается на треть. Итого метод гарантирует +/- 800 состояний. Такое себе разрешение, конечно. Но в целом, цифровая mg90d норм отрабатывает. Даже на скорости около 10 градусов в секунду рывки мало заметны, основном слышны. А если на ее канал давать фиксированное значение, меняя все остальные - держит стабильно, не джиттерит (не дергается), чего я больше всего боялся.

Но все равно осадок… Ощущение наиговеннейшей реализации

А чему равны все эти A0…A10 и D0…D10?

Мож я чо проглядел, но где у тебя инициализируются переменные PWMxx? Ну, начальный ноль - вижу. А потом - не очень

Это так или иначе в самом низу вызовет запись в tw1s.

Так что ковыряй регистр, должно работать.

PS:
Вместо GPIO_NUM_14 можно просто написать 14

в loop(), мне их там было лень сортировать, но они все там

мне теперь надо придумать, как ускорить сравнения и timer_get_counter_value в этом цикле - это повысит разрешение ШИМ

Там, где у тебя написано ((value < PWM) << …), преобразуй явно, то , что в скобках к int. Дело в том, что у тебя там bool, а он на esp32 - однобайтный, и если его сдвинуть на 14 бит, то скорее всего там будет 0. Может, конечно, ошибаюсь.

Тут тоже что-то странное.
В tw1c надо записывать то-же самое значение, что и в s.
c = clear, s = set.
Во вторых у тебя совсем нет паузы между записью в tw1s и tw1с, т.е. импульс там наносекундный генерируется.

	GPIO.out_w1ts     |= port1_H; // TO_HIGH
         ets_delay_us(666); // если с названием не напутал...
	GPIO.out_w1tc     |= port_H;

я думал что set это поднять порт, а clear - опустить. Т.е. как будто до следующего тика периферии сохраняются в разные регистры маски портов, которые надо поднять и которые надо опустить. А как на самом деле?

Еще момент - мы это сейчас выясняем уже чисто из спортивного интереса. Вариант, где пины дергаются по условию работает быстрее. Его бы оптимизировать, именно сравнение pwmXX с value и быть может саму процедуру получения значения счетчика заменить на чтение из регистра

хотя нет, вру. gpio_set_level даже если 18 раз вызываются, отжирают нехило (около 400 итераций цикла). Наверное есть смысл гасить порты через регистры

Если тебе надо записать 1 в GPIO0, то ты записываешь в tw1s 1<<0
Если тебе надо записать 0 в GPIO0, то ты записываешь в tw1с 1<<0

GPIO.out_w1ts |= port1_H;
GPIO.out_w1tc |= !port1_H & 0b00000000000100111111111111111110;

Если port1_H , допустим, содержит битовую маску с пинами 0,1,2,3,
то в в w1tc запишется 1111 1111 1111 1111 1111 0000 & 0000 0000 0001 0011 1111 1111 1111 1110 = 0000 000 0000 10011 1111 1111 1111 0000, что, вообще говоря, неправильно: обнулятся какие попало пины, кроме 0, 1, 2, 3

я в GPIO.out_w1tc пишу ! port1_H т.е. инвертирую (верно?)

& 0b00000000000100111111111111111110 я использую, чтобы затереть инвертированные в единицы нули, соответствующие пинам, которые я трогать не должен

я понял. Не ! а ~

Если ты хочешь обнулить пины, указанные в port1_H, то нет, неправильно. Для установки и обнуления одно и то же число записывается в s или c

Кстати.

! у тебя ж всегда там ноль возвращал, и ты всегда 0 писал в регистр, у которого активация единицей :slight_smile:

ага, это я так круто инвертировал port1_H. Безапелляционно прям))) просто импульс 3мс получался, а для сервы это уже не сигнал. Отсюда и 0,44 вольта тестер показывал (3/20 заполнение ШИМ)

#include "driver/timer.h"

uint64_t prev = 0;
uint64_t pheriod = 0;
uint64_t count = 0;
uint64_t microscount = 0;


uint32_t PWM01 = 0, PWM02 = 0, PWM03 = 0, PWM04 = 0, PWM05 = 0, PWM06 = 0, PWM07 = 0, PWM08 = 0, PWM09 = 0,
         PWM10 = 0, PWM11 = 0, PWM12 = 0, PWM13 = 0, PWM14 = 0, PWM15 = 0, PWM16 = 0, PWM17 = 0, PWM18 = 0;

static void IRAM_ATTR timer0_ISR (void *arg) {
  
  // очистка флагов прерываний
	timer_group_clr_intr_status_in_isr (TIMER_GROUP_0, TIMER_0);
	// перезапуск прерывания Alarm
	timer_group_enable_alarm_in_isr (TIMER_GROUP_0, TIMER_0);
  

  pheriod = millis() - prev;
  prev = millis();
  count = 0;
  microscount = micros();

  // поднимаем порты - начало импульса ШИМ
  GPIO.out_w1ts     |= 0b00000000000100111111111111111110; // пины 1..18, 21 TO_HIGH
  //GPIO.out_w1ts.val |= 0b00000000000000001000000000000000; // пин 48 TO_HIGH (48 - 32 = 16)
  
  uint64_t value = 0;
  while (value < 120000) {

     count++;
  
    // получение текущего значения таймера uint64_t value
    timer_get_counter_value (TIMER_GROUP_0, TIMER_0, &value);
    
	  // подготовка значений обновления регистров
    uint32_t port1_H = ((uint32_t)(value < PWM01) << 14)
	                   | ((uint32_t)(value < PWM02) << 13)
	                   | ((uint32_t)(value < PWM03) << 12)
                     | ((uint32_t)(value < PWM04) << 11)
	                   | ((uint32_t)(value < PWM05) << 4 )
	                   | ((uint32_t)(value < PWM06) << 3 )
	                   | ((uint32_t)(value < PWM07) << 2 )
	                   | ((uint32_t)(value < PWM08) << 1 )
	                   | ((uint32_t)(value < PWM10) << 5 )
                     | ((uint32_t)(value < PWM11) << 6 )
                     | ((uint32_t)(value < PWM12) << 7 )
                     | ((uint32_t)(value < PWM13) << 8 )
                     | ((uint32_t)(value < PWM14) << 9 )
                     | ((uint32_t)(value < PWM15) << 10)
                     | ((uint32_t)(value < PWM16) << 17)
                     | ((uint32_t)(value < PWM17) << 18)
                     | ((uint32_t)(value < PWM18) << 21);
    uint32_t port2_H = (value < PWM09) << 16;
	
	GPIO.out_w1ts     |= port1_H; // TO_HIGH
   // GPIO.out_w1ts.val |= port2_H; // TO_HIGH
	GPIO.out_w1tc     |= (~ port1_H) & 0b00000000000100111111111111111110; // TO_LOW
   // GPIO.out_w1tc.val |= (~ port2_H) & 0b00000000000000001000000000000000; // TO_LOW

  }
  
  // на всякий случай опускаем порты - гарантировано конец импульса ШИМ (если передадим импульс длиннее 120000)
  GPIO.out_w1tc     |= 0b00000000000100111111111111111110; // пины 1..18, 21 TO_LOW
  //GPIO.out_w1tc.val |= 0b00000000000000001000000000000000; // пин 48 TO_LOW

  microscount = micros() - microscount;

}



void setup () {

  pinMode(A7, OUTPUT);
  pinMode(A6, OUTPUT);
  pinMode(A5, OUTPUT);
  pinMode(A4, OUTPUT);
  pinMode(A3, OUTPUT);
  pinMode(A2, OUTPUT);
  pinMode(A1, OUTPUT);
  pinMode(A0, OUTPUT);
  pinMode(D13, OUTPUT);

  pinMode(D2, OUTPUT);
  pinMode(D3, OUTPUT);
  pinMode(D4, OUTPUT);
  pinMode(D5, OUTPUT);
  pinMode(D6, OUTPUT);
  pinMode(D7, OUTPUT);
  pinMode(D8, OUTPUT);
  pinMode(D9, OUTPUT);
  pinMode(D10, OUTPUT);

  // таймер опирается на 80mHz осцилятор!
  timer_config_t config = {
    .alarm_en = TIMER_ALARM_EN, // включение аларма таймера enum: 0 TIMER_ALARM_DIS | 1 TIMER_ALARM_EN
    .counter_en = TIMER_PAUSE, // cостояние после инициализации enum: 0 TIMER_PAUSE | 1 TIMER_START
    .intr_type = TIMER_INTR_LEVEL, // прерывание по уровню enum: 0 TIMER_INTR_LEVEL
    .counter_dir = TIMER_COUNT_UP, // направление счетчика enum: 0 TIMER_COUNT_DOWN | 1 TIMER_COUNT_UP
    .auto_reload = TIMER_AUTORELOAD_EN, // настройка автоматического рестарта прерывания enum: 0 TIMER_AUTORELOAD_DIS | 1 TIMER_AUTORELOAD_EN
    .divider = 2 // предделитель uint16_t: 2..65535
	};
  // инициализация таймера
  timer_init(TIMER_GROUP_0, TIMER_0, &config);
  uint64_t value = 1;
  uint64_t alarm_value = 800000;
  // начальное значение счетчика uint64_t value: 0..4294967295
  timer_set_counter_value (TIMER_GROUP_0, TIMER_0, value);
  // значение, при достижении которого сработает прерывание uint64_t alarm_value: 0..4294967295
  timer_set_alarm_value (TIMER_GROUP_0, TIMER_0, alarm_value);
  // разрешение прерывания
  timer_enable_intr (TIMER_GROUP_0, TIMER_0);
  // привязка функции обработки перрывания
  timer_isr_register (TIMER_GROUP_0, TIMER_0, timer0_ISR, NULL, 0, NULL);
  // запуск таймера
  timer_start (TIMER_GROUP_0, TIMER_0);

  Serial.begin (9600);
  Serial.println ("Hi!");

}



uint32_t val = 20000;

void loop() {

  val++;
  if (val > 180000) val = 20000;
  uint32_t pwm = val > 100000 ? 200000 - val : val;

  PWM01 = pwm;
  PWM02 = pwm;
  PWM03 = pwm;
  PWM04 = pwm;
  PWM05 = pwm;
  PWM06 = pwm;
  PWM07 = pwm;
  PWM08 = pwm;
  PWM09 = pwm;
  PWM10 = pwm;
  PWM11 = pwm;
  PWM12 = pwm;
  PWM13 = pwm;
  PWM14 = pwm;
  PWM15 = pwm;
  PWM16 = pwm;
  PWM17 = pwm;
  PWM18 = pwm;
  
  Serial.println ("pheriod: " + String(pheriod) + ", long: " + String(microscount) + ", iterations: " + String(count) + ", pwm: " + String(pwm) + ", on " + String(getCpuFrequencyMhz()) + "mHz"); 

}

Вот так работает как часики, только не очень быстрые)) сейчас попробую прикрутить установку LOW к реализации с гашением порта индивидуально, по условию.

Остается вопрос, почему на 32-45 порты у меня регистра нету…

Нашел в gpio_struct.h: GPIO.out1_w1ts.val и GPIO.out1_w1tc.val (именно out1)

в итоге через регистры:

#include "driver/timer.h"

uint64_t prev = 0;
uint64_t pheriod = 0;
uint64_t count = 0;
uint64_t microscount = 0;


uint32_t PWM01 = 0, PWM02 = 0, PWM03 = 0, PWM04 = 0, PWM05 = 0, PWM06 = 0, PWM07 = 0, PWM08 = 0, PWM09 = 0,
         PWM10 = 0, PWM11 = 0, PWM12 = 0, PWM13 = 0, PWM14 = 0, PWM15 = 0, PWM16 = 0, PWM17 = 0, PWM18 = 0;

static void IRAM_ATTR timer0_ISR (void *arg) {
  
  // очистка флагов прерываний
	timer_group_clr_intr_status_in_isr (TIMER_GROUP_0, TIMER_0);
	// перезапуск прерывания Alarm
	timer_group_enable_alarm_in_isr (TIMER_GROUP_0, TIMER_0);
  

  pheriod = millis() - prev;
  prev = millis();
  count = 0;
  microscount = micros();

  // поднимаем порты - начало импульса ШИМ
  GPIO.out_w1ts     |= 0b00000000000100111111111111111110; // пины 1..18, 21 TO_HIGH
  //GPIO.out_w1ts.val |= 0b00000000000000001000000000000000; // пин 48 TO_HIGH (48 - 32 = 16)
  
  uint64_t value = 0;
  while (value < 120000) {

     count++;
  
    // получение текущего значения таймера uint64_t value
    timer_get_counter_value (TIMER_GROUP_0, TIMER_0, &value);
    
	  // подготовка значений обновления регистров
    uint32_t port1_H = ((uint32_t)(value < PWM01) << 14)
	                   | ((uint32_t)(value < PWM02) << 13)
	                   | ((uint32_t)(value < PWM03) << 12)
                     | ((uint32_t)(value < PWM04) << 11)
	                   | ((uint32_t)(value < PWM05) << 4 )
	                   | ((uint32_t)(value < PWM06) << 3 )
	                   | ((uint32_t)(value < PWM07) << 2 )
	                   | ((uint32_t)(value < PWM08) << 1 )
	                   | ((uint32_t)(value < PWM10) << 5 )
                     | ((uint32_t)(value < PWM11) << 6 )
                     | ((uint32_t)(value < PWM12) << 7 )
                     | ((uint32_t)(value < PWM13) << 8 )
                     | ((uint32_t)(value < PWM14) << 9 )
                     | ((uint32_t)(value < PWM15) << 10)
                     | ((uint32_t)(value < PWM16) << 17)
                     | ((uint32_t)(value < PWM17) << 18)
                     | ((uint32_t)(value < PWM18) << 21);
    uint32_t port2_H = (value < PWM09) << 16;
	
	GPIO.out_w1ts     |= port1_H; // TO_HIGH
  GPIO.out1_w1ts.val |= port2_H; // TO_HIGH
	GPIO.out_w1tc     |= (~ port1_H) & 0b00000000000100111111111111111110; // TO_LOW
  GPIO.out1_w1tc.val |= (~ port2_H) & 0b00000000000000010000000000000000; // TO_LOW

  }
  
  // на всякий случай опускаем порты - гарантировано конец импульса ШИМ (если передадим импульс длиннее 120000)
  GPIO.out_w1tc     |= 0b00000000000100111111111111111110; // пины 1..18, 21 TO_LOW
  GPIO.out1_w1tc.val |= 0b00000000000000010000000000000000; // пин 48 TO_LOW

  microscount = micros() - microscount;

}



void setup () {

  pinMode(A7, OUTPUT);
  pinMode(A6, OUTPUT);
  pinMode(A5, OUTPUT);
  pinMode(A4, OUTPUT);
  pinMode(A3, OUTPUT);
  pinMode(A2, OUTPUT);
  pinMode(A1, OUTPUT);
  pinMode(A0, OUTPUT);
  pinMode(D13, OUTPUT);

  pinMode(D2, OUTPUT);
  pinMode(D3, OUTPUT);
  pinMode(D4, OUTPUT);
  pinMode(D5, OUTPUT);
  pinMode(D6, OUTPUT);
  pinMode(D7, OUTPUT);
  pinMode(D8, OUTPUT);
  pinMode(D9, OUTPUT);
  pinMode(D10, OUTPUT);

  // таймер опирается на 80mHz осцилятор!
  timer_config_t config = {
    .alarm_en = TIMER_ALARM_EN, // включение аларма таймера enum: 0 TIMER_ALARM_DIS | 1 TIMER_ALARM_EN
    .counter_en = TIMER_PAUSE, // cостояние после инициализации enum: 0 TIMER_PAUSE | 1 TIMER_START
    .intr_type = TIMER_INTR_LEVEL, // прерывание по уровню enum: 0 TIMER_INTR_LEVEL
    .counter_dir = TIMER_COUNT_UP, // направление счетчика enum: 0 TIMER_COUNT_DOWN | 1 TIMER_COUNT_UP
    .auto_reload = TIMER_AUTORELOAD_EN, // настройка автоматического рестарта прерывания enum: 0 TIMER_AUTORELOAD_DIS | 1 TIMER_AUTORELOAD_EN
    .divider = 2 // предделитель uint16_t: 2..65535
	};
  // инициализация таймера
  timer_init(TIMER_GROUP_0, TIMER_0, &config);
  uint64_t value = 1;
  uint64_t alarm_value = 800000;
  // начальное значение счетчика uint64_t value: 0..4294967295
  timer_set_counter_value (TIMER_GROUP_0, TIMER_0, value);
  // значение, при достижении которого сработает прерывание uint64_t alarm_value: 0..4294967295
  timer_set_alarm_value (TIMER_GROUP_0, TIMER_0, alarm_value);
  // разрешение прерывания
  timer_enable_intr (TIMER_GROUP_0, TIMER_0);
  // привязка функции обработки перрывания
  timer_isr_register (TIMER_GROUP_0, TIMER_0, timer0_ISR, NULL, 0, NULL);
  // запуск таймера
  timer_start (TIMER_GROUP_0, TIMER_0);

  Serial.begin (9600);
  Serial.println ("Hi!");

}



uint32_t val = 20000;

void loop() {

  val++;
  if (val > 180000) val = 20000;
  uint32_t pwm = val > 100000 ? 200000 - val : val;

  PWM01 = pwm;
  PWM02 = pwm;
  PWM03 = pwm;
  PWM04 = pwm;
  PWM05 = pwm;
  PWM06 = pwm;
  PWM07 = pwm;
  PWM08 = pwm;
  PWM09 = pwm;
  PWM10 = pwm;
  PWM11 = pwm;
  PWM12 = pwm;
  PWM13 = pwm;
  PWM14 = pwm;
  PWM15 = pwm;
  PWM16 = pwm;
  PWM17 = pwm;
  PWM18 = pwm;
  
  Serial.println ("pheriod: " + String(pheriod) + ", long: " + String(microscount) + ", iterations: " + String(count) + ", pwm: " + String(pwm) + ", on " + String(getCpuFrequencyMhz()) + "mHz"); 

}

Сейчас попробую дергать только по условию

Я, возможно, не понимаю логики работы кода. Ты, когда пишешь в w1tc, ты какие пины обнулить пытаешся? Те же, что мгновением до этого установил в 1? Или те, которые наоборот, не установил?

да. То есть для каждого пина посчитал, должен ли он быть поднят или нет, построил битмапку поднятых пинов, а в clear регистр записал инвертированную битмапку, затерев логическим “и” биты портов, которые к моему рукоблудию отношения не имеют. Но это пока башкой не подумал.

Сейчас код такой:

#include "driver/timer.h"



//               NANO
//  GPIO48 PWM09      PWM18 GPIO21
//   GPIO1 PWM08      PWM17 GPIO18
//   GPIO2 PWM07      PWM16 GPIO17
//   GPIO3 PWM06      PWM15 GPIO10
//   GPIO4 PWM05      PWM14 GPIO9
//  GPIO11 PWM04      PWM13 GPIO8
//  GPIO12 PWM03      PWM12 GPIO7
//  GPIO13 PWM02      PWM11 GPIO6
//  GPIO14 PWM01      PWM10 GPIO5



uint32_t PWM01 = 0, PWM02 = 0, PWM03 = 0, PWM04 = 0, PWM05 = 0, PWM06 = 0,
         PWM07 = 0, PWM08 = 0, PWM09 = 0, PWM10 = 0, PWM11 = 0, PWM12 = 0,
         PWM13 = 0, PWM14 = 0, PWM15 = 0, PWM16 = 0, PWM17 = 0, PWM18 = 0;



uint64_t count = 0;

static void IRAM_ATTR timer0_ISR (void *arg) {
  
  // очистка флагов прерываний
	timer_group_clr_intr_status_in_isr (TIMER_GROUP_0, TIMER_0);
	// перезапуск прерывания Alarm
	timer_group_enable_alarm_in_isr (TIMER_GROUP_0, TIMER_0);
  
  count = 0;

  bool wait01 = true, wait02 = true, wait03 = true, wait04 = true, wait05 = true, wait06 = true,
       wait07 = true, wait08 = true, wait09 = true, wait10 = true, wait11 = true, wait12 = true,
       wait13 = true, wait14 = true, wait15 = true, wait16 = true, wait17 = true, wait18 = true;
  
  uint64_t value = 0;

    // поднимаем порты - начало импульса ШИМ
  GPIO.out_w1ts      |= 0b00000000000100111111111111111110; // пины 1..18, 21 TO_HIGH
  GPIO.out1_w1ts.val |= 0b00000000000000010000000000000000; // пин 48 TO_HIGH (48 - 32 = 16)

  while (value < 120000) {

     count++;
  
    // получение текущего значения таймера uint64_t value
    timer_get_counter_value (TIMER_GROUP_0, TIMER_0, &value);

    // если значение таймера превзошло указаную длину импульса ШИМ - соответствующий порт гасится
    if (wait01) if (value > PWM01) { GPIO.out_w1tc      |= 0b00000000000000000100000000000000; wait01 = false; }
    if (wait02) if (value > PWM02) { GPIO.out_w1tc      |= 0b00000000000000000010000000000000; wait02 = false; }
    if (wait03) if (value > PWM03) { GPIO.out_w1tc      |= 0b00000000000000000001000000000000; wait03 = false; }
    if (wait04) if (value > PWM04) { GPIO.out_w1tc      |= 0b00000000000000000000100000000000; wait04 = false; }
    if (wait05) if (value > PWM05) { GPIO.out_w1tc      |= 0b00000000000000000000000000010000; wait05 = false; }
    if (wait06) if (value > PWM06) { GPIO.out_w1tc      |= 0b00000000000000000000000000001000; wait06 = false; }
    if (wait07) if (value > PWM07) { GPIO.out_w1tc      |= 0b00000000000000000000000000000100; wait07 = false; }
    if (wait08) if (value > PWM08) { GPIO.out_w1tc      |= 0b00000000000000000000000000000010; wait08 = false; }
    if (wait09) if (value > PWM09) { GPIO.out1_w1tc.val |= 0b00000000000000010000000000000000; wait09 = false; }
    if (wait10) if (value > PWM10) { GPIO.out_w1tc      |= 0b00000000000000000000000000100000; wait10 = false; }
    if (wait11) if (value > PWM11) { GPIO.out_w1tc      |= 0b00000000000000000000000001000000; wait11 = false; }
    if (wait12) if (value > PWM12) { GPIO.out_w1tc      |= 0b00000000000000000000000010000000; wait12 = false; }
    if (wait13) if (value > PWM13) { GPIO.out_w1tc      |= 0b00000000000000000000000100000000; wait13 = false; }
    if (wait14) if (value > PWM14) { GPIO.out_w1tc      |= 0b00000000000000000000001000000000; wait14 = false; }
    if (wait15) if (value > PWM15) { GPIO.out_w1tc      |= 0b00000000000000000000010000000000; wait15 = false; }
    if (wait16) if (value > PWM16) { GPIO.out_w1tc      |= 0b00000000000000100000000000000000; wait16 = false; }
    if (wait17) if (value > PWM17) { GPIO.out_w1tc      |= 0b00000000000001000000000000000000; wait17 = false; }
    if (wait18) if (value > PWM18) { GPIO.out_w1tc      |= 0b00000000001000000000000000000000; wait18 = false; }

  }
  
  // на всякий случай опускаем порты - гарантировано конец импульса ШИМ (если передадим импульс длиннее 120000)
  GPIO.out_w1tc      |= 0b00000000000100111111111111111110; // пины 1..18, 21 TO_LOW
  GPIO.out1_w1tc.val |= 0b00000000000000010000000000000000; // пин 48 TO_LOW

}



void setup () {

  Serial.begin (9600);
  Serial.println ("Hi!");

  pinMode(A7, OUTPUT);
  pinMode(A6, OUTPUT);
  pinMode(A5, OUTPUT);
  pinMode(A4, OUTPUT);
  pinMode(A3, OUTPUT);
  pinMode(A2, OUTPUT);
  pinMode(A1, OUTPUT);
  pinMode(A0, OUTPUT);
  pinMode(D13, OUTPUT);
  pinMode(D2, OUTPUT);
  pinMode(D3, OUTPUT);
  pinMode(D4, OUTPUT);
  pinMode(D5, OUTPUT);
  pinMode(D6, OUTPUT);
  pinMode(D7, OUTPUT);
  pinMode(D8, OUTPUT);
  pinMode(D9, OUTPUT);
  pinMode(D10, OUTPUT);

  // таймер опирается на 80mHz осцилятор!
  timer_config_t config = {
    .alarm_en = TIMER_ALARM_EN, // включение аларма таймера enum: 0 TIMER_ALARM_DIS | 1 TIMER_ALARM_EN
    .counter_en = TIMER_PAUSE, // cостояние после инициализации enum: 0 TIMER_PAUSE | 1 TIMER_START
    .intr_type = TIMER_INTR_LEVEL, // прерывание по уровню enum: 0 TIMER_INTR_LEVEL
    .counter_dir = TIMER_COUNT_UP, // направление счетчика enum: 0 TIMER_COUNT_DOWN | 1 TIMER_COUNT_UP
    .auto_reload = TIMER_AUTORELOAD_EN, // настройка автоматического рестарта прерывания enum: 0 TIMER_AUTORELOAD_DIS | 1 TIMER_AUTORELOAD_EN
    .divider = 2 // предделитель uint16_t: 2..65535
	};
  // инициализация таймера
  timer_init(TIMER_GROUP_0, TIMER_0, &config);
  uint64_t value = 1;
  uint64_t alarm_value = 800000;
  // начальное значение счетчика uint64_t value: 0..4294967295
  timer_set_counter_value (TIMER_GROUP_0, TIMER_0, value);
  // значение, при достижении которого сработает прерывание uint64_t alarm_value: 0..4294967295
  timer_set_alarm_value (TIMER_GROUP_0, TIMER_0, alarm_value);
  // разрешение прерывания
  timer_enable_intr (TIMER_GROUP_0, TIMER_0);
  // привязка функции обработки перрывания
  timer_isr_register (TIMER_GROUP_0, TIMER_0, timer0_ISR, NULL, 0, NULL);
  // запуск таймера
  timer_start (TIMER_GROUP_0, TIMER_0);

}



uint32_t val = 20000;

void loop() {

  val++;
  if (val > 180000) val = 20000;
  uint32_t pwm = val > 100000 ? 200000 - val : val;

  PWM01 = pwm;
  PWM02 = pwm;
  PWM03 = pwm;
  PWM04 = pwm;
  PWM05 = pwm;
  PWM06 = pwm;
  PWM07 = pwm;
  PWM08 = pwm;
  PWM09 = pwm;
  PWM10 = pwm;
  PWM11 = pwm;
  PWM12 = pwm;
  PWM13 = pwm;
  PWM14 = pwm;
  PWM15 = pwm;
  PWM16 = pwm;
  PWM17 = pwm;
  PWM18 = pwm;
  
  Serial.println ("count: " + String(count) + ", pwm: " + String(pwm));

}

Суть прерывание вызывается по таймеру раз в 20мс - период ШИМ для серво (таймер 40мГц, аларм 800000, что соответствует 20 мс)

В прерывании сразу же все каналы устанавливаем HIGH, т.е. на всех выходах серво начался импульс.

Далее запускаем цикл, выход из которого произойдет через примерно 3мс, а точнее когда timer_get_counter_value прочтет значение, превышающее 120000, что на 40мГц счетчике как раз соответствует 3мс - это прям максимум-перемаксимум длина импульса какой-нибудь больной с завода сервы.

А внутри цикла для каждого канала, его значение сравнивается с timer_get_counter_value на текущей итерации - если таймер обогнал значение, то гасим порт, его импульс закончился. Чем резвее будет выполняться этот while - тем выше будет разрешение ШИМ (отражается в значении count)

Вот первая идея была просто в порты результат сравнения отправлять при каждой итерации, но это оказалось совсем неоптимально… Сейчас что-то похожее на правду, хотелось бы timer_get_counter_value заменить на значение регистра напрямую. И быть может оптимизировать условия сравнения, но я пробовал, например, временную переменную uint32_t для значения счетчика, в надежде что будет быстрее сравнивать с uint32_t PWMXX - ему плевать (соврал, аж на 10 итераций примерно больше стало)

1 лайк

А как ты, кстати, быстродействие кода измеряешь?

Я - вот так:

static inline __attribute__((always_inline)) uint32_t cpu_ticks() {
  uint32_t register ccount;
  asm ( "rsr.ccount %0;" : "=a"(ccount) /*Out*/ : /*In*/ : /* Clobber */);
  return ccount;
}

На C2,3,5,6,61 это, скорее всего, работать не будет. а на обыкновенном esp32, esp32s2, esp32s3 - будет. Счетчик тиков процессора.

Возьму на заметку, спасибо.

В целом, спасибо за терпение и дельные советы!

меня смущает сейчас, что

while (value64 < 120000) {

    count++;
    timer_get_counter_value (TIMER_GROUP_0, TIMER_0, &value64);

}

дает в count всего лишь 1630. Тогда как со всеми сравнениями и записями в регистры 1200-1300. Это timer_get_counter_value() медленный, или сам по себе цикл уже быстрее быть не может?