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

Выручайте, я поплыл…

Сейчас трогаю weact esp32s3 mini с пакетом “esp32 by Espressif Systems” версии 3.1.3 в arduino ide (но потом хочу работать с ESP32-S3-Nano и пакетом “Arduino ESP32 Boards 2.0.18-arduino.5”)

Код следующий:

#include "driver/timer.h"

uint32_t last = 0;

static void IRAM_ATTR timer0_ISR (void *arg) {
  
//  timer_spinlock_take(group); // надо ли?

  // очистка флагов прерываний
	timer_group_clr_intr_status_in_isr(TIMER_GROUP_0, TIMER_0);
	// перезапуск прерывания Alarm
	timer_group_enable_alarm_in_isr(TIMER_GROUP_0, TIMER_0);
  
  uint64_t value = 0;
  while (value < 360000) {
    // получение текущего значения таймера uint64_t value
    timer_get_counter_value (TIMER_GROUP_0, TIMER_0, &value);
    
    // тут сравниваем с нужной длиной шим и дергаем порты

    
  }

  last = value;
  
  // тут считаем тригонометрию, которую применим в следующем прерывании (дельта времени всегда 20мс)
  
//  timer_spinlock_give(group); // надо ли?

}

void setup () {

  /*rtc_wdt_protect_off();
  rtc_wdt_disable();*/
  
  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 = 2400000;
  // начальное значение счетчика 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);
  printf("Hi!\r\n");

}



void loop() {

  //
  
  String s = "loop! " + String(last) + "\r\n";
  printf(s.c_str());
  //delay(2000);

}

Компилится и даже ощущение, что делает желаемое, но компилятор предупреждает #warning “legacy timer group driver is deprecated, please migrate to driver/gptimer.h” что как бы намекает на использование устаревшей библиотеки для использования таймеров.

Я в их референсах ESP-IDF Programming Guide уже несколько разных реализаций прерываний нашел, не пойму, что именно мне нужно изучать

Interrupt Allocation - ESP32-S3 - — ESP-IDF Programming Guide v5.4 documentation это?

А тут смотрел?

да. Либо я слепой, либо там ничего, кроме пары доков с схемотехникой и не очень полезных ссылок для s3 нет.

Походу нужно просто дождаться платы Nano и на всякий сдделать чистую установку IDE, а эти WeAct кому-нибудь подарить за спасибо

забыл важное уточнение, я выбираю плату ESP32S3 Dev Module, так как более подходящей не нашел в списке соотв. пакета. По крайней мере старые таймеры работают, также получилось анимировать “радугой” встроенный в плату ws2812b на 48 пине. Serial.println не работает))

Начинаются приключения)) Итак…

Пакет Arduino ESP32 Boards 2.0.18-arduino.5

Пата esp32s3 nano

наконец-таки получил. Шим генерируется, но почему-то все мною ранее вычисленные значения оказались втрое больше нужных. То ли я неправильно задал настройки таймера, то ли плата работает не на 240мгц (getCpuFrequencyMhz() возвращает 240), то ли я криво посчитал изначально. Код, на котором ШИМ формируется по крайней мере с нужной частотой 50Гц, прошу чекнуть таймер:

#include "driver/timer.h"

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

uint64_t servos[18] = { 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500 };

static void IRAM_ATTR timer0_ISR (void *arg) {
  
//  timer_spinlock_take(TIMER_GROUP_0); // надо ли?

  // очистка флагов прерываний
	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();

  uint64_t value = 0;
  while (value < 120000) {
    // получение текущего значения таймера uint64_t value
    timer_get_counter_value (TIMER_GROUP_0, TIMER_0, &value);
    
    // тут сравниваем с нужной длиной шим и дергаем порты
    count++;

    digitalWrite(A7, servos[0] > value);
    digitalWrite(A6, servos[1] > value);
    digitalWrite(A5, servos[2] > value);
    digitalWrite(A4, servos[3] > value);
    digitalWrite(A3, servos[4] > value);
    digitalWrite(A2, servos[5] > value);
    digitalWrite(A1, servos[6] > value);
    digitalWrite(A0, servos[7] > value);
    digitalWrite(B0, servos[8] > value);

    /*digitalWrite(D2, servos[9] > value);
    digitalWrite(D3, servos[10] > value);
    digitalWrite(D4, servos[11] > value);
    digitalWrite(D5, servos[12] > value);
    digitalWrite(D6, servos[13] > value);
    digitalWrite(D7, servos[14] > value);
    digitalWrite(D8, servos[15] > value);
    digitalWrite(D9, servos[16] > value);
    digitalWrite(D10, servos[17] > value);*/
    
  }

  microscount = micros() - microscount;
  
  // тут считаем тригонометрию, которую применим в следующем прерывании (дельта времени всегда 20мс)
  
//  timer_spinlock_give(TIMER_GROUP_0); // надо ли?

}

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(B0, INPUT_PULLDOWN);
  pinMode(B0, 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);*/

  
  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!");

}



void loop() {

  //

  for (uint8_t i = 1; i < 18; i++) {
    servos[i] +=  i;
    if (servos[i] > 70000) servos[i] = 500;
  }
  servos[0] = 40000;
  
  Serial.println ("pheriod: " + String(pheriod) + ", long: " + String(microscount) + ", iterations: " + String(count) + ", on " + String(getCpuFrequencyMhz()) + "mHz"); 

}

Напомню. мне нужна сработка прерывания каждые 20мс, и чтобы цикл while работал в течение 3мс, при этом значение счетчика value менялось от 0 до достаточно большого значения, которое обеспечит необходимое разрешение ШИМ - нынешних 120000 более чем хватит, в принципе.

На говнокод пока не смотреть, я еще не нашел как через порты напрямую с выходами работать…

И еще важный для меня вопрос: вывод B0 он же GPIO46 относится к strapping pins. Я хочу его тоже использовать для подключения сервопривода, но на нем как будто бы постоянная подтяжка к +3V3. Это в МК подтяжка, или на плате? На схемотехнике https://files.waveshare.com/wiki/ESP32-S3-Nano/ESP32-S3-Nano-Schematic.pdf он вообще отмечен как AREFF… Можно ли переназначить, или отключить ADC вообще? На этой плате мне АЦП не нужен

поправочка, он там обозначен как AREFF вероятно потому, что плата в формате Arduino Nano выполнена, и у последней там как раз вывод REF.

The timer uses the APB_CLK clock source (typically 80 MHz) так, с этим понятно… Вопрос с B0 остается пока открыт, gpio_set_pull_mode(GPIO_NUM_46, GPIO_FLOATING); не помогает, на пине постоянно +3,3В

Сдался, распаял перемычку до gpio48 (D13). Вопрос можно считать неактуальным

Осталось разобраться, как рулить выходами через порты, вместо digitalWrite();

 // Энкодер
  //--------- create tasks on core0 --------------------------------
  xTaskCreatePinnedToCore(task0, "Task0", 4096, NULL, 1, NULL, 0);

н-да… буду признателен за ссылки на инфу.

у официалов ничего ниже gpio_set_level не отыскал, при этом существует gpio_output_set, про которую в референсах ни слова, но arduinoIDE о ней видите ли знает, но как физические пины сопоставляются с 32 битными масками я боюсь не догадаться… И почему маски 32 битные, но у контроллера существует gpio48???

еще есть REG_WRITE(GPIO_OUT_W1TS_REG, BIT5); и REG_WRITE(GPIO_OUT_W1TC_REG, BIT5); которые ошибок компиляции не вызывают. Рискну предположить, что есть два регистра: на поднятие группы портов и на опускание группы портов. Для обоих бит маски передаются. Опять же, что такое BIT5, как пользоваться - хз…

UPD: Кажется нащупал Dedicated GPIO - ESP32-S3 - — ESP-IDF Programming Guide latest documentation (если есть “за” или “против”, расскажите, плиз)

  1. gpio_ll_output_enable(&GPIO, pin); // pin - GPIO number
  2. Используя W1TS. Маска задает ноги, на которых нужно выставить 1. Можно сразу это сделать на нескольких ногах
  3. Можно использовать dedicated IO: похоже на п.2, но еще быстрее, чем пункт 2. Одновременно можно рулить 1…8 ногами., но это есть только на S3.
1 лайк

BIT(5), это просто макрос. Пятый бит.
По скорости выполнения:

  1. Быстрый (5нс, чтобы изменить состояние до 8 ног за раз): Dedicated IO
  2. Помедленнее - использование W1TS (set) и W1TC (clear), но зато хоть всеми ногами одновременно
  3. Еще поиедленнее gpio_ll_set_level
  4. Остальное еще помедленнее

Через регистры: если битов дюже много :slight_smile:

//SET
     if(pin < 32) 
     {
        GPIO.out_w1ts = ((uint32_t)1 << pin);
     } 
     else if(pin < 34) {
        GPIO.out1_w1ts.val = ((uint32_t)1 << (pin - 32));
     }

//CLEAR
     if(pin < 32) 
     {
        GPIO.out_w1tc = ((uint32_t)1 << pin);
     } 
     else if(pin < 34) 
     {
        GPIO.out1_w1tc.val = ((uint32_t)1 << (pin - 32));
     }

Коды выше может работать одновременно хоть со всеми пинами (w1 - это маска)

PS: что-то код со сравнением пина <34 вызывает сомнения. :-/
Но я помню, писал через w1ts/c, все работало
PPS:

GPIO.out_w1tc = ((uint32_t)1 << pin);
GPIO.out1_w1tc.val = ((uint32_t)1 << (pin - 32));
1 лайк

Приветствую, о учитель!

как раз она и есть

в инетах говорят, что GPIO.out_w1ts и GPIO.out1_w1ts.val задекларированы в gpio_periph.h. Блин, я весь репозиторий гита перерыл, но файла этого не нашел, и ощущение, что это старая библиотека, на смену которой пришло gpio_ll

А я могу несколько таких битбэндингов сделать на одном ядре, чтобы дергать 18 ног за 3 раза (подряд)? Если нет, то очень жаль.

Это меня ппц смущает: если не ошибаюсь, маски там 32 битные, а выходов 45… Вот как?

я думаю на каких-то платах это могло быть максимальное кол-во пинов. Защита от дурака.

Да, для понимания. С 18-ю gpio_set_level() сейчас цикл, в котором дергаются ноги выполняется чего-то там кажется 480 раз (за 3 миллисекунды). Мне нужно увеличить до +/-2к или больше. Т.е. в 5 раз требуется ускориться примерно, ну надеюсь это вполне реально

Кстати, перечень выводов, которыми надо дергать:

GPIO1
GPIO2
GPIO3
GPIO4
GPIO5
GPIO6
GPIO7
GPIO8
GPIO9
GPIO10
GPIO11
GPIO12
GPIO13
GPIO14
GPIO17
GPIO18
GPIO21
GPIO46

домой приеду - попробую. Было бы круто, если сработает. Я же вообще могу 4 маски заранее сформировать, по две на каждый порт: на поднятие портов и на гашение портов. А потом побитовым “или” в соотв. регистры записать четыре маски.

Кстати, есть у меня идея альтернативной реализации, на самом деле… При генерации импульса ШИМ состояние выхода меняется дважды, а я зачем-то каждую итерацию дергаю пины, не от большого ума походу))

Если одновременно несколько ног - то w1ts/w1tc.
PS:
gpio_ll_set_level работает побыстрее, чем gpio_set_…

1 лайк

Тут:
#include <soc/gpio_struct.h>

1 лайк

их там две. итого 64 бита. см. пример выше

Вот, например, установка всех твоих ног за раз:

GPIO.out_w1ts = 0b100111111111111111110; //пины 1..18, 21
GPIO.out_w1ts.val = 1 << (46 - 32); //пин 46
1 лайк

К сожалению, в группу (bundle) можно напихать максимум 8 пинов.
Это (dedic IO) было сделано, насколько я понимаю, для того, чтобы делать произвольные 8-ми битовые шины данных.

Будет ли оптимальным пересоздавать группу три раза - я не знаю, надо проверять

думаю, врядли))

Большое спасибо - так и попробую. Для каждого канала введу свои булевки, запишу результаты сравнения на итерации цикла, потом в uint32_t сведу их (в четыре разных) со сдвигами, и в завершении побитовым “или” сложу с текущим значением соотв. регистра (для “гасящего” регистра возьму инвертированные значения булевских перед тем как складывать в uint_32)

#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();

  /*
               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.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 = ((value < PWM01) << 14)
	                   | ((value < PWM02) << 13)
	                   | ((value < PWM03) << 12)
                     | ((value < PWM04) << 11)
	                   | ((value < PWM05) << 4 )
	                   | ((value < PWM06) << 3 )
	                   | ((value < PWM07) << 2 )
	                   | ((value < PWM08) << 1 )
	                   | ((value < PWM10) << 5 )
                     | ((value < PWM11) << 6 )
                     | ((value < PWM12) << 7 )
                     | ((value < PWM13) << 8 )
                     | ((value < PWM14) << 9 )
                     | ((value < PWM15) << 10)
                     | ((value < PWM16) << 17)
                     | ((value < PWM17) << 18)
                     | ((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;

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

}

или я где-то обосрался, или метод не рабочий (( просто ничего не происходит, на портах по 0,47 вольт

а на второй регистр вообще жалуется “Compilation error: request for member ‘val’ in ‘GPIO.gpio_dev_s::out_w1ts’, which is of non-class type ‘volatile uint32_t’ {aka ‘volatile unsigned int’}”