ESP32. Step на RMT

Попробовал сделать генерацию step сигнала для шаговых моторах используя встроенный контроллер RMT.

после нескольких попыток заставить его работать хотел было уже бросить. Проблема была, что вызов установки значений частоты приводил к блокировке функции.

после многократных попыток понять причину блокировки - бросил эту затею. На некоторых ресурсах читал, что нужно обновлять фреймфорк ардуино до версии 5.* . Но у меня это не получилось.

Но нашел техническую документацию на esp32 и там есть описание регистров rmt контроллера.

Спустя еще сутки получилось генерировать импульсы.

bool MotorStepRMT::init() {
    // --- DIR ---
    gpio_set_direction((gpio_num_t)_dirPin, GPIO_MODE_OUTPUT);
    GPIO.out_w1tc = (1 << _dirPin);                     // DIR = LOW

    // --- EN ---
    gpio_set_direction((gpio_num_t)_enablePin, GPIO_MODE_OUTPUT);
    GPIO.out_w1tc = (1 << _enablePin);                  // EN = LOW (мотор включён)

    // --- RMT TX канал ---
    rmt_config_t rmt_conf = RMT_DEFAULT_CONFIG_TX((gpio_num_t)_stepPin, _rmtChannel);
    rmt_conf.clk_div = RMT_CLK_DIV;
    rmt_conf.mem_block_num = 1;
    rmt_config(&rmt_conf);

    esp_err_t err = rmt_driver_install(_rmtChannel, 0, 256);
    if (err != ESP_OK) {
        xPrintf("RMT driver install FAILED ch=%d err=%d\n", _rmtChannel, err);
        return false;
    }

    // Выключаем передачу
    RMT.conf_ch[_rmtChannel].conf1.tx_conti_mode = 0;
    RMT.conf_ch[_rmtChannel].conf1.tx_start = 0;
    RMT.conf_ch[_rmtChannel].conf1.mem_rd_rst = 1;
    RMT.conf_ch[_rmtChannel].conf1.mem_rd_rst = 0;
    RMT.conf_ch[_rmtChannel].conf1.mem_owner = 0;
    RMT.conf_ch[_rmtChannel].conf0.mem_size = 2;

    _items[0].duration0 = 100; _items[0].level0 = 1;
    _items[0].duration1 = 100; _items[0].level1 = 0;
    _items[1].duration0 = 0;   _items[1].level0 = 0;
    _items[1].duration1 = 0;   _items[1].level1 = 0;

    RMTMEM.chan[_rmtChannel].data32[0].val = _items[0].val;
    RMTMEM.chan[_rmtChannel].data32[1].val = _items[1].val;

    RMT.conf_ch[_rmtChannel].conf0.clk_en = 1;
    RMT.conf_ch[_rmtChannel].conf0.div_cnt = RMT_CLK_DIV;  // делитель 256

    xPrintf("RMT driver installed ch=%d OK\n", _rmtChannel);
    xPrintf("RMT ch=%d step=%d\n", _rmtChannel, _stepPin);
    return true;
}


// =============================================================================
//  setSpeed() — частота в Гц, знак = направление
// =============================================================================
void MotorStepRMT::setSpeed(int32_t freq) {
    if (freq == 0) {
        stop();
        return;
    }

    // Применяем реверс
    int32_t target = freq * _conf.revers;
    uint32_t freq_hz = (uint32_t)abs(target);
    if (freq_hz < RMT_FREQ_MIN) freq_hz = RMT_FREQ_MIN;

    // Полупериод в тиках: f_clk = 80 МГц / 256 = 312500 Гц
    // half_ticks = f_clk / (2 * freq_hz)
    uint32_t half_ticks = 312500 / (2 * freq_hz);
    if (half_ticks < RMT_HALF_TICKS_MIN) half_ticks = RMT_HALF_TICKS_MIN;
    if (half_ticks > RMT_HALF_TICKS_MAX) half_ticks = RMT_HALF_TICKS_MAX;

    // Обновляем буфер
    _items[0].duration0 = (uint16_t)half_ticks;
    _items[0].level0 = 1;
    _items[0].duration1 = (uint16_t)half_ticks;
    _items[0].level1 = 0;

    // DIR через регистры GPIO
    int dir = (target > 0) ? 1 : 0;
    if (dir != _lastDir) {
        if (dir) {
            GPIO.out_w1ts = (1 << _dirPin);   // HIGH
        } else {
            GPIO.out_w1tc = (1 << _dirPin);   // LOW
        }
        _lastDir = dir;
    }



    // Запись в память RMT на лету
    RMTMEM.chan[_rmtChannel].data32[0].val = _items[0].val;

    // Отладка
    // uint32_t mem_val = RMTMEM.chan[_rmtChannel].data32[0].val;
    // xPrintf("ch=%d written half_ticks=%u mem.val=0x%08X\n", _rmtChannel, half_ticks, mem_val);

    // Запуск передатчика (если не был запущен)
    RMT.conf_ch[_rmtChannel].conf1.tx_conti_mode = 1;
    RMT.conf_ch[_rmtChannel].conf1.tx_start = 1;

    _lastFreq = target;
    _lastUpdate = micros();
}

// =============================================================================
//  stop()
// =============================================================================
void MotorStepRMT::stop() {
    if (_lastFreq != 0) {
        fixPosition();
        RMT.conf_ch[_rmtChannel].conf1.tx_start = 0;   // остановка передатчика
        _lastFreq = 0;
        _lastUpdate = micros();
    }
}


Глючные библиотеки получается

И еще обнаружил странную проблему.

из описания RMT имеет 8 независимых каналов 0..7

Однако на канале 0 сделать генерацию импульсов не вышло. Давал задание на генерацию 100 Гц. Но реально на пине получал более 200 кГц.

Сместил каналы на 1 для левого мотора и 2 для правого. Все заработало как положено.

И есть еще ограничение на 5Гц. Если установить 4 то генерация работает не верно и опять на выходе получаем десятки кГц. Это скорее с делителем связано.Но не стал вникать просто если меньше - установил 0. Но если нужна точность = то такой вариант конечно не подойдет. В моем же случае это не существенно. Так как 5 гц это примерно 0,5 мм/сек, что ни как не повлияет на его работу.

Ого, по bare metal решил пройтись? Успехов :). Но проще будет открыть

esp-idf-v5.x.x/examples/peripherals/rmt/stepper_motor

Ну или другие примеры в esp-idf-v5.x.x/examples/peripherals/rmt/

Спойлер

У меня не было таких странностей с RMT. Были проблемы с приемом, но не с передачей. Затем. Что значит давали “значение на генерацию 100Гц”? В RMT нет понятия фиксированной несущей. В RMT вы задаете форму сигнала вручную.

Вот в таком виде: “657 тиков держать 1, 345 тиков держать 0, 1000 тиков держать 1”. Тик - это скорость работы RMT контроллера. Нупример, 1 мкс\тик. Минимально - около 12нан на тик (или 120? надо посчитать для 80Мгц частоты APB шины), максимально - не помню.

Все задается в этих самых тиках.

% Sequence #0:
% Resolution : 1.0000uS per tick  (Frequency: 1000000 Hz)
% Levels are
% 1/100, 0/200, 1/200, 0/400,
% Total: 4 levels, duration: 900 ticks, (~900 uS)

Так же RMT позволяет молулировать\демодулировать сигнал (нолики можно промодулировать или единички).

Да но espdif стоит 4 версии. 5 не желает вставать. Куча огибок сыпется.

А в 4 библиотека сломана.

Да и завтра новая либа выйдет и опять сломают. Регистро то в железе на месте останутся

Конечно частоту рмт задаю длительностью 0 и длительностью 1. В тиках.

У меня они одинаковы. Скважность 50%.

Про частоту - в контексте функции setSpeed

Зы. Трудно со смартфона на форуме отвечать.

Тоже верно. Писанины только много, а так - можно , конечно. По bare-metal есть специалисты на esp32.com . Надо зайти в English Forum->ESP-IDF-> и спроисть там.

Да step уже генерит стабильно и плавно. Дальше буду разбираться.

вот тут еще мысль в голову закралась.

Для генерации сигнала я использовал только 2 значения в таблице рмт. длительность 0 и длительность 1.

Но можно же этот массив увеличить скажем до 4х или более элементов. (плюс один терминатор)

И в массиве указывать

ticks state

100 0

100 0

100 1

100 1

0 0

И таким образом можно добиться еще меньшей частоты.

Да, можно еще делителем поиграть но боюсь это может привести к необходимости перезапуска самого рмт, а это может привести в “рывкам” на моторах.