понял, спасибо) А в целом, я же правильно тайминги рассчитал для частоты камня?
в целом, есть впечатление что ты добьешь эту тему, когда еще чуток почитаешь, и не про ардуино прсти Г-споди, а про ESP-IDF, в данном случае про ардуино забудь, оно не упрощает, а наоборот запутывает, имхо.
extern unsigned xthal_get_ccount(); // Сколько тиков. Переполняется за несколько минут.
extern uint32_t getCpuFrequencyMhz(); // Частота процессора в мегагерцах
Если в качестве хобби, когда больше удовлетворение от процесса, а не от конечного результата - почему нет? Пусть занимается…
Тут еще есть побочный результат - изучение
Ну и пофик на него, не облучение же )))
так…
timer_set_divider (group, num, divider); // uint16_t divider 2..65535
timer_set_auto_reload (group, num, autoreload); // enum autoreload 0 disable | 1 enable
timer_set_counter_mode (group, num, countUp); // enum countUp: 0 -- | 1 ++
timer_set_alarm_value (group, num, alarm_value); // uint64_t alarm_value: ?..?
timer_set_alarm (group, num, alarm_en); // alarm_en: 0 disable | 1 enable
timer_start(group, num); // Go!
timer_get_counter_value (group, num, &value); // uint64_t value
это понятно… а вот привязка обработчика прерывания… мой мозг поломался на этом участке кода из esp32-hal-timer.c
bool IRAM_ATTR timerFnWrapper(void *arg){
void (*fn)(void) = arg;
fn();
return false; // some additional logic or handling may be required here to approriately yield or not
}
timer_isr_callback_add (group, num, timerFnWrapper, fn, intr_alloc_flags); // intr_alloc_flags = 0 ???
Как будто я могу привязать какой-то обработчик, который определит, нужно ли делать yeld после обработчика прерывания, сам обработчик прерывания и какие-то фиг победи параметры intr_alloc_flags, о которых на оффе написано “Flags used to allocate the interrupt. One or multiple (ORred) ESP_INTR_FLAG_* values. See esp_intr_alloc.h for more info”. И это всё может прерываться критическими секциями, если ESP-IDF решит, что им нужнее…
Но есть еще timer_isr_register(), о различиях инфа расплывчатая:
timer_isr_callback_add и timer_isr_register — функции, связанные с управлением таймерами в контексте платформы ESP32.
timer_isr_callback_add добавляет callback-обработчик прерывания для соответствующего таймера. Этот callback должен вернуть значение bool, которое определяет, нужно ли уступить контекст (YIELD) по окончанию ISR. Обработчик будет вызван из тела ISR, но в нём не надо обрабатывать статус прерывания, и его код должен быть настолько коротким, насколько это возможно.
timer_isr_register регистрирует обработчик прерывания таймера (ISR). Этот обработчик будет подключён к тому же ядру CPU, на котором был вызов timer_isr_register. Если использовать эту функцию, необходимо полностью реализовать весь код обработчика ISR таймера, то есть нужно вызвать timer_spinlock_take() перед кодом обработчика и вызвать timer_spinlock_give() после.
Таким образом, timer_isr_callback_add подходит для регистрации короткого обработчика прерывания, а timer_isr_register — для реализации более сложного подхода, когда необходимо полностью написать код обработчика ISR.
Если правильно понял, мне более подходит timer_isr_register, где я просто в IRAM_ATTR зарегаю isr функцию, принимающую void и возвращающую void. Причем мне нужно в функции сначала написать блокировку других прерываний timer_spinlock_take(group), а в конце вернуть их timer_spinlock_give(group), и как будто я могу их вызвать для обеих групп таймеров, полностью залочив все прерывания на время выполнения моего (3мс + тригонометрия). А можно ли timer_spinlock_give(group) вызвать не в конце функции, а после самого критичного участка - в котором “генерируются” импульсы, а тригонометрия уже пусть в порядке живой очереди рассчитывается за оставшиеся 17мс?
P.s. говоря “тригонометрия” я имею ввиду расчет шести параметрических кривых 4й степени в трех координатах каждая, и плюсом 12х hypot, 6х asin, 12x acos и 6x atan2. Я думаю для такого проца, у которого наверняка инструкций поболя чем у атмеги, эти вычисления вообще труда не составят
Эта функция регистрирует низкоуровневый обработчик. Тебе самому придется сбрасывать флаг прерывания (пример с прерыванием PCNT)
static void IRAM_ATTR pcnt_unit_interrupt(void *arg) {
pcnt_unit_t unit = (pcnt_unit_t )arg;
units[unit].overflow++;
PCNT.int_clr.val = BIT(unit); // <--- вот тут
}
В случае же с callback, низкоуровневое прерывание реализуется внутри ESP-IDF, и оно же отвечает за вызов твоей функции.
Там, где у тебя мозг поломался - сотри “Как будто”, а “я” напиши с большой. Флаги тебе не интересны, пусть будет 0.
Да, произойдет прерывание от таймера, будет вызван обработчик в кишках ESP-IDF, который посмотрит в свои списки и увидит, что у него callback тут недавно зарегистрировали. Ну и вызовет его.
Возвращай false.
Нет у сервы столько. Если на пару сотен отреагирует - хорошо.
не понял, при использовании timer_isr_register() в случае со счетчиком, мне самому надо будет обнулять его, и перерегистрировать чтобы в следующий раз запустился?
Ладно, попробую timer_isr_callback_add для начала, может там всё хорошо будет и импульс не будет “плавать”.
оригинал цифровые towerpro mg90d, вполне соответствуют описанию. Мертвая зона 1 мкс
Хороший момент. А сколько реальное разрешение сервопривода, например sg90? Гугл не знает, или я не нашёл.
Если в описании указан параметр dead band width то он либо в долях гардуса, либо в микросекундах. В обоих случаях означает, на какие отклонения сигнала серва не реагирует, чтобы избегать jitterring - дрожания при удержании позиции. Собсно отношение всего диапазона в соответствующей единице измерения (градусы либо разница между максимальной и минимальной длиной импульса) к dead band width даст разрешение сервы, с некоторыми условностями.
У аналоговых всё бывает хуже, чем у цифровых - они могут при малых отклонениях давать на моторы малые токи, которых не хватает для изменения позиции, но хватает чтобы серва начала безбожно греться…
dead band width 10 мкс, делит область определения управляющего импульса 1000…2000 мкс на 101 дискретное значение, повторюсь, это прикидка с некоторыми допущениями. Скорее всего 10 мкс имеется ввиду +/- 5 мкс от последнего занятого положения вала - а это вдвое большая точность, 201 значение
Причем есть SG90 Digital – Tower Pro цифровая sg90, по заявлениям производителя в 10 раз точнее
Ну так производителю виднее. А шестерёнки всёравно пластиковые, под нагрузкой не очень. Делал кошкогонялку. Две сервы в двух плоскостях и лазерная указка. Сервы случайным образом гоняют точку по комнате. Особенно хорошо видна зона нечувствительности сервы когда хочется на расстоянии пары метров подвинуть пятно лазера на пару сантиметров - а из за не чувствительности облом. И как оказалось на долго гонялки не хватает. Минут через 5 кошка ложиться в центр(!) бегания лазера и только глазами следит.
Не. Когда случается прерывание (от какого-то внутреннего устройства, например, от счетчика импульсов, или от пина, или от таймера), то в соответствующем регистре взводится бит “в прерывании”. Пока этот бит не погаснет, новое прерывание не произойдет.
Соответственно, в обработчике, ты делаешь то, что хотел, а потом, в конце, сбрасываешь этот бит, сообщая системе, что прерывание обработано, несите новое.
Пример такого прерывания (в данном случае - прерывание от PCNT):
static void IRAM_ATTR pcnt_unit_interrupt(void *arg) {
...
...
PCNT.int_clr.val = BIT(unit); // <-- сбросили тот самый бит
}
Альтернативный способ: попросить ESP-IDF предоставить уже готовый обработчик, который сам взводит и гасит нужные битики и вызывает твою функцию
Пример, с пинами:
gpio_set_intr_type(pin, GPIO_INTR_ANYEDGE);
// Регистрируем встроенный в ESP-IDF обработчик прерывания от пинов
gpio_install_isr_service((int)ARDUINO_ISR_FLAG); //<-- можно вызывать сколько хочешь раз.
// указываем этому встроенному обработчику, чтобы вызывал нашу функцию (count_pin_anyedge_interrupt())
//каждый раз, когда случится прерывание на **нашем пине**
gpio_isr_handler_add(pin, count_pin_anyedge_interrupt, &t);
Можно и глобальный обработчик было зарегистрировать в примере выше. Но, тогда бы пришлось самому разбираться от какого пина и что за прерывание пришло. В глобальный обработчик сваливается все разом. А в случае с isr_service - только то, что ты попросил, и только для того пина, который ты указал.
Какой способ использовать? Проще всего - ISR Service (как во втором примере). Но в некоторых редких случаях глобальное прерывание может дать приемущество: в основном жто так или иначе будет выражаться в скорости. Если ты там за наносекунды бьешся, например.
Не помню, говорил я или нет - помимо штуки под названием LEDC (LED Controller), который на ESP32 все используют для генерации ШИМ, есть еще RMT - модуль, позволяющий произвольные последовательности импульсов посылать. Из плюсов - оно аппаратное, и там неплохая точность: 0.0125мкс.
[spoiler]
Можно, например, “Войну и Мир” перевести в азбуку морзе и выдать на какой-нибудь пин. Это неблокирующая операция - пин будет в фоне передавать, что ему сказали, а ты будешь своими делами заниматься.
Соответствующие функции, упрощающие жизнь, находятся в Arduino Core, по-моему в esp32-hal-rmt.c
это интересно, но сколько же у нее каналов?
Что здесь есть unit? группа таймеров?
как я понял наследовательность: ОБЩИЙ_ОБЪЕКТ_ПРЕРЫВАНИЙ.Прерывания_по_счетчику.значение = BIT(группа_таймеров)
Кстати, timer_spinlock_give(group) не то же самое ли делает?
Да штож такое… есть значит функции timer_ и есть pcnt_ До чего же “разнообразный” контроллер
да, логично. если сервы были с бэндом 10 мкс, при изменяющейся на 1000 ширине импульса, то как выше написал, там в лучшем случае 200 позиций. Т.е. точность чуть выше 1 градуса, а плечо 2 метра…
Насчет пластикового редуктора, хочу сказать что если он качественный, то ничем не уступает металлическому по точности, а если устройство на этих же сервах себя держит то может еще и выигрывать - суммарная масса пластиковых серв сильно меньше чем металлических. Потреблямый ток тоже ниже будет, сл-но можно и АКБ взять меньшей емкости и массы
Ну, если сложить с LEDC, то тебе должно хватить :). Я не помню сколько.
The RMT (Remote Control Transceiver) peripheral was designed to act as an infrared transceiver. However, due to the flexibility of its data format, RMT can be extended to a versatile and general-purpose transceiver, transmitting or receiving many other types of signals.
…
ESP32-S3 contains multiple channels in the RMT peripheral (Remote Control Transceiver (RMT) - ESP32-S3 - — ESP-IDF Programming Guide v5.4.1 documentation). Each channel can be independently configured as either transmitter or receiver.
…
Typically, the RMT peripheral can be used in the following scenarios:
- Transmit or receive infrared signals, with any IR protocols, e.g., NEC
- General-purpose sequence generator
- Transmit signals in a hardware-controlled loop, with a finite or infinite number of times
- Multi-channel simultaneous transmission
- Modulate the carrier to the output signal or demodulate the carrier from the input signal
…
…
In some real-time control applications (e.g., to make two robotic arms move simultaneously), you do not want any time drift between different channels. The RMT driver can help to manage this by creating a so-called Sync Manager .
…
кажется нашел в статье Учим железки разговаривать, или ESP32 DAC и немного таймера / Хабр автор приводит код функции прерывания:
static void IRAM_ATTR timer0_ISR(void *ptr) {
// Очистить флаги прерываний
timer_group_clr_intr_status_in_isr(TIMER_GROUP_0, TIMER_0);
// Перезапустить прерывание Alarm
timer_group_enable_alarm_in_isr(TIMER_GROUP_0, TIMER_0);
// тут был остальной код прерывания
}
То есть он чистил бит timer_group_clr_intr_status_in_isr, а зачем timer_group_enable_alarm_in_isr я не до конца понял, видимо timer_set_auto_reload не работает при использовании timer_isr_register, или я не понял смысл timer_group_enable_alarm_in_isr
П.с. про инфу об RTM спасибо большое, я пока попробую одним подходом общим сгенерить сигналы. Если не взлетит, то уже альтернативы буду смотреть…
Наваял пока что такое
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);
// тут сравниваем с нужной длиной шим и дергаем порты
}
// тут считаем тригонометрию, которую применим в следующем прерывании (дельта времени всегда 20мс)
// timer_spinlock_give(group); // надо ли?
}
void setup () {
timer_config_t config = {
.divider = 2, // предделитель uint16_t: 2..65535
.counter_dir = TIMER_COUNT_UP, // направление счетчика enum: 0 TIMER_COUNT_DOWN | 1 TIMER_COUNT_UP
.counter_en = TIMER_PAUSE, // cостояние после инициализации enum: 0 TIMER_PAUSE | 1 TIMER_START
.alarm_en = TIMER_ALARM_EN, // включение аларма таймера enum: 0 TIMER_ALARM_DIS | 1 TIMER_ALARM_EN
.intr_type = TIMER_INTR_LEVEL, // прерывание по уровню enum: 0 TIMER_INTR_LEVEL
.auto_reload = TIMER_AUTORELOAD_EN, // настройка автоматического рестарта прерывания enum: 0 TIMER_AUTORELOAD_DIS | 1 TIMER_AUTORELOAD_EN
};
// инициализация таймера
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);
}
буду тестить на нем… самое волнительное сейчас - будет ли timer_get_counter_value (TIMER_GROUP_0, TIMER_0, &value);
в строке 13 давать значения от каких-то нескольких единиц до 360000 (или чуть чуть больше). То есть будет ли при запуске прерывания значение таймера уже обнуленным, и будет ли оно увеличиваться на итерациях while?