ESP32 & stepper motor RS485/CAN

Пока искал сервомоторы с последовательной шиной, нашел интересные шаговые моторы, на которые установлены драйверы с обратной связью (с 14 бит энкодэрами). Драйверы кроме step/dir имеют интерфейсы на выбор: RS485/CAN. На борту самого драйвера процессор на Cortex-M4. Из описания можно до 3000RPM раскрутить. Поддерживается режимы: положения, скорости, крутящего момента.

И я подумал, что использование таких моторов сильно упростит задачу постройки балансирующего робота. Первую попытку я делал на шаговых моторах NEMA17. Но отсутствие энкодэров не давало информации о реальном положении в следствии пропуска шагов. Да и генерировать step с высокой частотой на два мотора тоже не легкая задача для esp32.

В связи с этим возник вопрос: Если подключить 2 мотора по CAN, реально добиться частоты обмена не менее 100 Гц? Если правильно прочитал, то CAN может работать с частотой до 1 МГц. С учетом того, что моторы имеют замкнутый контур, мне не потребуется считывать данные энкодэров. Достаточно отправлять задание скорости (4 байта данных) на два мотора.

Прикольный девайс. Однако ж шаговики не могут стартовать мгновенно с максимальными оборотами, в отличии от … Может и не стоит изобретать колесо?

Это понятно. ни какой мотор не может мгновенно стартовать. Всегда есть момент инерции. Если подать сразу большую скорость то шаговик начнет пропускать шаги. А если есть обратная связь, то он дошагает хоть и с небольшим запозданием. Там же энкодер имеется.

Да как раз и не хочется изобретать колесо. Проще использовать мотор с обратной связью. А CAN позволит исключить генерацию step на esp32.

Удавалось кому либо поднять CAN на 1 Мгц на esp32

При такой скорости шины я думаю легко уложиться в цикл 100 Гц.

SERVO42D RS485/CAN

Закал моторы с драйверами с обратной связью. Интерфейс CAN. Заказал CAN трансивер. Обещают через 2 недели привезти.

Посоветуйте проверенную библиотеку CAN для esp32.

Приехали Nema17 с драйверами с CAN интерфейсом.

Имеет интерактивное меню и 3 кнопки через которое можно сделать настройки.

Приехал модуль трансивера TJA1051

Какую библиотеку посоветуете для работы с CAN?

И чем же она хороша? Практически все функции- виртуальные. …главное отметится
PS реально здесь только Макс владеет ситуацией, может появится

что-то не взлетает.

В примерах видел используют библиотеку <can.h>

Но ide говорит не существует такая. Отдельно в менеджере библиотек не находится просто CAN. Adafruit CAN находится поставил с гитхаба, но при компиляции ошибка, что не предназначена для esp32

/*
 * Adafruit Feather M4 CAN Receiver Callback Example
 */

#include <CANSAME5x.h>

CANSAME5x CAN;

void setup() {
  Serial.begin(115200);
  while (!Serial) delay(10);

  Serial.println("CAN Receiver Callback");

  // start the CAN bus at 250 kbps
  if (!CAN.begin(250000)) {
    Serial.println("Starting CAN failed!");
    while (1) delay(10);
  }
  Serial.println("Starting CAN!");

  // register the receive callback
  CAN.onReceive(onReceive);
}

void loop() {
  // do nothing
}

void onReceive(int packetSize) {
  // received a packet
  Serial.print("Received ");

  if (CAN.packetExtended()) {
    Serial.print("extended ");
  }

  if (CAN.packetRtr()) {
    // Remote transmission request, packet contains no data
    Serial.print("RTR ");
  }

  Serial.print("packet with id 0x");
  Serial.print(CAN.packetId(), HEX);

  if (CAN.packetRtr()) {
    Serial.print(" and requested length ");
    Serial.println(CAN.packetDlc());
  } else {
    Serial.print(" and length ");
    Serial.println(packetSize);

    // only print packet data for non-RTR packets
    while (CAN.available()) {
      Serial.print((char)CAN.read());
    }
    Serial.println();
  }

  Serial.println();
}



"C:\\Users\\Barsu\\AppData\\Local\\Arduino15\\packages\\esp32\\tools\\esp-x32\\2601/bin/xtensa-esp32-elf-g++" "-Wl,--Map=C:\\Users\\Barsu\\AppData\\Local\\arduino\\sketches\\6A5D1CB03B336044FD380A8E675A4CF8/feather_m4can_onreceive.ino.map" "-LC:\\Users\\Barsu\\AppData\\Local\\Arduino15\\packages\\esp32\\tools\\esp32-libs\\3.3.8/lib" "-LC:\\Users\\Barsu\\AppData\\Local\\Arduino15\\packages\\esp32\\tools\\esp32-libs\\3.3.8/ld" "-LC:\\Users\\Barsu\\AppData\\Local\\Arduino15\\packages\\esp32\\tools\\esp32-libs\\3.3.8/qio_qspi" -Wl,--wrap=esp_panic_handler "@C:\\Users\\Barsu\\AppData\\Local\\Arduino15\\packages\\esp32\\tools\\esp32-libs\\3.3.8/flags/ld_flags" "@C:\\Users\\Barsu\\AppData\\Local\\Arduino15\\packages\\esp32\\tools\\esp32-libs\\3.3.8/flags/ld_scripts" -Wl,--start-group "C:\\Users\\Barsu\\AppData\\Local\\arduino\\sketches\\6A5D1CB03B336044FD380A8E675A4CF8\\sketch\\feather_m4can_onreceive.ino.cpp.o" "C:\\Users\\Barsu\\AppData\\Local\\arduino\\sketches\\6A5D1CB03B336044FD380A8E675A4CF8\\libraries\\Adafruit_CAN\\CANController.cpp.o" "C:\\Users\\Barsu\\AppData\\Local\\arduino\\sketches\\6A5D1CB03B336044FD380A8E675A4CF8\\libraries\\Adafruit_CAN\\CANSAME5x.cpp.o" "C:\\Users\\Barsu\\AppData\\Local\\arduino\\cores\\b691c88827570bb7b8d279989e847971\\core.a" "@C:\\Users\\Barsu\\AppData\\Local\\Arduino15\\packages\\esp32\\tools\\esp32-libs\\3.3.8/flags/ld_libs" -Wl,--end-group -Wl,-EL -o "C:\\Users\\Barsu\\AppData\\Local\\arduino\\sketches\\6A5D1CB03B336044FD380A8E675A4CF8/feather_m4can_onreceive.ino.elf"
C:/Users/Barsu/AppData/Local/Arduino15/packages/esp32/tools/esp-x32/2601/bin/../lib/gcc/xtensa-esp-elf/14.2.0/../../../../xtensa-esp-elf/bin/ld.exe: C:\Users\Barsu\AppData\Local\arduino\sketches\6A5D1CB03B336044FD380A8E675A4CF8\sketch\feather_m4can_onreceive.ino.cpp.o:(.literal._Z41__static_initialization_and_destruction_0v+0x8): undefined reference to `CANSAME5x::~CANSAME5x()'
C:/Users/Barsu/AppData/Local/Arduino15/packages/esp32/tools/esp-x32/2601/bin/../lib/gcc/xtensa-esp-elf/14.2.0/../../../../xtensa-esp-elf/bin/ld.exe: C:\Users\Barsu\AppData\Local\arduino\sketches\6A5D1CB03B336044FD380A8E675A4CF8\sketch\feather_m4can_onreceive.ino.cpp.o:(.literal._Z41__static_initialization_and_destruction_0v+0xc): undefined reference to `CANSAME5x::CANSAME5x()'
C:/Users/Barsu/AppData/Local/Arduino15/packages/esp32/tools/esp-x32/2601/bin/../lib/gcc/xtensa-esp-elf/14.2.0/../../../../xtensa-esp-elf/bin/ld.exe: C:\Users\Barsu\AppData\Local\arduino\sketches\6A5D1CB03B336044FD380A8E675A4CF8\sketch\feather_m4can_onreceive.ino.cpp.o:(.literal._Z5setupv+0x24): undefined reference to `CANSAME5x::begin(long)'
C:/Users/Barsu/AppData/Local/Arduino15/packages/esp32/tools/esp-x32/2601/bin/../lib/gcc/xtensa-esp-elf/14.2.0/../../../../xtensa-esp-elf/bin/ld.exe: C:\Users\Barsu\AppData\Local\arduino\sketches\6A5D1CB03B336044FD380A8E675A4CF8\sketch\feather_m4can_onreceive.ino.cpp.o:(.literal._Z5setupv+0x28): undefined reference to `CANSAME5x::onReceive(void (*)(int))'
C:/Users/Barsu/AppData/Local/Arduino15/packages/esp32/tools/esp-x32/2601/bin/../lib/gcc/xtensa-esp-elf/14.2.0/../../../../xtensa-esp-elf/bin/ld.exe: C:\Users\Barsu\AppData\Local\arduino\sketches\6A5D1CB03B336044FD380A8E675A4CF8\sketch\feather_m4can_onreceive.ino.cpp.o: in function `__static_initialization_and_destruction_0()':
C:\Users\Barsu\AppData\Local\Temp\.arduinoIDE-unsaved2026514-7688-1nmjvei.du01k\feather_m4can_onreceive/feather_m4can_onreceive.ino:7:(.text._Z41__static_initialization_and_destruction_0v+0x6): undefined reference to `CANSAME5x::CANSAME5x()'
C:/Users/Barsu/AppData/Local/Arduino15/packages/esp32/tools/esp-x32/2601/bin/../lib/gcc/xtensa-esp-elf/14.2.0/../../../../xtensa-esp-elf/bin/ld.exe: C:\Users\Barsu\AppData\Local\arduino\sketches\6A5D1CB03B336044FD380A8E675A4CF8\sketch\feather_m4can_onreceive.ino.cpp.o: in function `setup()':
C:\Users\Barsu\AppData\Local\Temp\.arduinoIDE-unsaved2026514-7688-1nmjvei.du01k\feather_m4can_onreceive/feather_m4can_onreceive.ino:13:(.text._Z5setupv+0x42): undefined reference to `CANSAME5x::begin(long)'
C:/Users/Barsu/AppData/Local/Arduino15/packages/esp32/tools/esp-x32/2601/bin/../lib/gcc/xtensa-esp-elf/14.2.0/../../../../xtensa-esp-elf/bin/ld.exe: C:\Users\Barsu\AppData\Local\Temp\.arduinoIDE-unsaved2026514-7688-1nmjvei.du01k\feather_m4can_onreceive/feather_m4can_onreceive.ino:18:(.text._Z5setupv+0x6c): undefined reference to `CANSAME5x::onReceive(void (*)(int))'
collect2.exe: error: ld returned 1 exit status

А где вы это прочли? Покажите, пожалуйста.

Это мне так алиса подсказывает

зы. CAN для esp32 нужно отдельно как-то загружать?

В папках с библиотеками и платами нашел только 2 файла can.h

Один в папке esp32s3 - это явно не моя плата.

Второй

C:\Users\Barsu\AppData\Local\Arduino15\packages\arduino\hardware\sam\1.6.12\system\libsam\include\can.h 

попробуй эту

PS и таки да, вдруг ты совсем не в теме ESP

/*
This code example shows normal operation of the SimpleCANio library.

This code is specific to Teensy boards using FlexCAN_T4.
*/
#include <Arduino.h>
#include "SimpleCANio.h"   // <- this is the only include required, it should be smart enough to find the correct subclass

#define CAN_ID 0x321

// | Board        | CAN bus|  CAN RX Pin | CAN TX Pin |
// |--------------|-------------|------------|------------|
// | Teensy 3.2   |CAN0         | 4          | 3          |
// | Teensy 3.5   |CAN0         | 4          | 3          |
// | Teensy 3.6   |CAN0         | 4          | 3          |
// | Teensy 3.6   |CAN1         | 34         | 33         |
// | Teensy 4.0   |CAN1         | 23         | 22         |
// | Teensy 4.1   |CAN2         | 0          | 1          |
// | Teensy 4.1   |CAN3         | 30         | 31         |    
#define CAN_BUS CAN0 // TODO set the bus you want to use , Teensy
#define CAN_SHDN NC
#define CAN_ENABLE NC

// auto can_bus = TEENSY_FLEXCAN(CAN_BUS);
#define RX_PIN 4
#define TX_PIN 5
//CANio can(can_bus, CAN_SHDN, CAN_ENABLE); 
CANio can(RX_PIN, TX_PIN, CAN_SHDN, CAN_ENABLE); 

void setup()
{

    Serial.begin(230400);

    can.logTo(&Serial);
    delay(2000);
    Serial.println("Starting CAN");
    // can.enableInternalLoopback();
    
    // choose one of the receive filters to apply
    CanFilter filter = CanFilter(MASK_EXTENDED, CAN_ID, CAN_ID, FILTER_ANY_FRAME);
    // CanFilter filter = CanFilter(MASK_STANDARD, CAN_ID, CAN_ID, FILTER_ANY_FRAME);
    // CanFilter filter = CanFilter(MASK_ACCEPT_ALL);
    can.filter(filter);

    // 1 mbps
    can.begin(1000000);
    delay(10);
}

uint8_t data[8] = {0};
uint8_t num = 0;

void loop()
{

    data[0] = num++;

    bool isExtendedFrame = false;
    bool isRtr = false;
    delay(50);
    CanMsg txMsg = CanMsg(
        isExtendedFrame ? CanExtendedId(CAN_ID, isRtr) : CanStandardId(CAN_ID, isRtr),
        4,
        data);
    delay(50);

    can.write(txMsg);
    delay(1);

    if (can.available() > 0)
    {
        CanMsg const rxMsg = can.read();

        Serial.print("polling read: ");
        if (rxMsg.isExtendedId())
        {
            Serial.print(rxMsg.getExtendedId(), HEX);
            Serial.println(" Extended ✅");
        }
        else
        {
            Serial.print(rxMsg.getStandardId(), HEX);
            Serial.println(" Standard ✅");
        }
    }

    delay(2000);
}

вот что нашел…

twai.h есть такой.

Пример скомпилился и трансмит ок пишет.

Но на указанных пинах

tx - 15

rx 17

тишина.

/* ESP32 TWAI transmit example.
  This transmits a message every second.

  Connect a CAN bus transceiver to the RX/TX pins.
  For example: SN65HVD230

  The API gives other possible speeds and alerts:
  https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/twai.html

  created 27-06-2023 by Stephan Martin (designer2k2)
*/

#include <Arduino.h>
#include "driver/twai.h"

// Pins used to connect to CAN bus transceiver:
#define RX_PIN 15
#define TX_PIN 17

// Interval:
#define TRANSMIT_RATE_MS 3000

#define POLLING_RATE_MS 3000

static bool driver_installed = false;

unsigned long previousMillis = 0;  // will store last time a message was send

void setup() {
  // Start Serial:
  Serial.begin(115200);

  // Initialize configuration structures using macro initializers
  twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT((gpio_num_t)TX_PIN, (gpio_num_t)RX_PIN, TWAI_MODE_NORMAL);
  twai_timing_config_t t_config = TWAI_TIMING_CONFIG_500KBITS();  //Look in the api-reference for other speed sets.
  twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL();

  // Install TWAI driver
  if (twai_driver_install(&g_config, &t_config, &f_config) == ESP_OK) {
    Serial.println("Driver installed");
  } else {
    Serial.println("Failed to install driver");
    return;
  }

  // Start TWAI driver
  if (twai_start() == ESP_OK) {
    Serial.println("Driver started");
  } else {
    Serial.println("Failed to start driver");
    return;
  }

  // Reconfigure alerts to detect TX alerts and Bus-Off errors
  uint32_t alerts_to_enable = TWAI_ALERT_TX_IDLE | TWAI_ALERT_TX_SUCCESS | TWAI_ALERT_TX_FAILED | TWAI_ALERT_ERR_PASS | TWAI_ALERT_BUS_ERROR;
  if (twai_reconfigure_alerts(alerts_to_enable, NULL) == ESP_OK) {
    Serial.println("CAN Alerts reconfigured");
  } else {
    Serial.println("Failed to reconfigure alerts");
    return;
  }

  // TWAI driver is now successfully installed and started
  driver_installed = true;
}

static void send_message() {
  // Send message

  // Configure message to transmit
  twai_message_t message;
  message.identifier = 0x0F6;
  message.data_length_code = 4;
  for (int i = 0; i < 4; i++) {
    message.data[i] = 0;
    Serial.print(message.data[i]);
  }

  // Queue message for transmission
  if (twai_transmit(&message, pdMS_TO_TICKS(1000)) == ESP_OK) {
    printf("Message queued for transmission\n");
  } else {
    printf("Failed to queue message for transmission\n");
  }
}

void loop() {
  if (!driver_installed) {
    // Driver not installed
    delay(1000);
    return;
  }
  // Check if alert happened
  uint32_t alerts_triggered;
  twai_read_alerts(&alerts_triggered, pdMS_TO_TICKS(POLLING_RATE_MS));
  twai_status_info_t twaistatus;
  twai_get_status_info(&twaistatus);

  // Handle alerts
  if (alerts_triggered & TWAI_ALERT_ERR_PASS) {
    Serial.println("Alert: TWAI controller has become error passive.");
  }
  if (alerts_triggered & TWAI_ALERT_BUS_ERROR) {
    Serial.println("Alert: A (Bit, Stuff, CRC, Form, ACK) error has occurred on the bus.");
    Serial.printf("Bus error count: %" PRIu32 "\n", twaistatus.bus_error_count);
  }
  if (alerts_triggered & TWAI_ALERT_TX_FAILED) {
    Serial.println("Alert: The Transmission failed.");
    Serial.printf("TX buffered: %" PRIu32 "\t", twaistatus.msgs_to_tx);
    Serial.printf("TX error: %" PRIu32 "\t", twaistatus.tx_error_counter);
    Serial.printf("TX failed: %" PRIu32 "\n", twaistatus.tx_failed_count);
  }
  if (alerts_triggered & TWAI_ALERT_TX_SUCCESS) {
    Serial.println("Alert: The Transmission was successful.");
    Serial.printf("TX buffered: %" PRIu32 "\t", twaistatus.msgs_to_tx);
  }

  // Send message
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= TRANSMIT_RATE_MS) {
    previousMillis = currentMillis;
    send_message();
  }
}

load:0x3fff0030,len:4876
ho 0 tail 12 room 4
load:0x40078000,len:16560
load:0x40080400,len:3500
entry 0x400805b4
Driver installed
Driver started
CAN Alerts reconfigured
0000Message queued for transmission
Alert: The Transmission was successful.
TX buffered: 0	0000Message queued for transmission
Alert: The Transmission was successful.
TX buffered: 0	0000Message queued for transmission
Alert: The Transmission was successful.

питание надеюсь 5 вольт на передатчике

Да, питание 5В.

Хидер нашел.

Искал реализацию twai_driver_install(…) но нигде не нашел. Есть только совпадение в *.a

#include "driver/twai.h"

Разобрались с передачей команд на моторы по CAN интерфейсу.

За основу взяли пример twai и выделили его в модуль.

Вот что получилось:

can.h

#ifndef CAN_H
#define CAN_H

#include "driver/twai.h"

// Pins used to connect to CAN bus transceiver:
#define RX_PIN 26
#define TX_PIN 25

// Interval min, max:
#define TRANSMIT_RATE_MS 1000

#define POLLING_RATE_MS 1000

uint8_t CalcCRC(twai_message_t msg);

bool canInit(void);

bool sendMessage(twai_message_t msg);

#endif

can.cpp

#include <Arduino.h>
#include "can.h"

uint8_t CalcCRC(twai_message_t msg) {
  uint16_t sum = msg.identifier;
  for(uint8_t i = 0; i < msg.data_length_code - 1; i++)
    {
      sum += msg.data[i];  //Calculate accumulated value
    }
  return(sum & 0xFF);     //return checksum
}

bool canInit(void) {
  // Initialize configuration structures using macro initializers
  twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT((gpio_num_t)TX_PIN, (gpio_num_t)RX_PIN, TWAI_MODE_NORMAL);
  twai_timing_config_t t_config = TWAI_TIMING_CONFIG_500KBITS();  //Look in the api-reference for other speed sets.
  twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL();

  // Install TWAI driver
  if (twai_driver_install(&g_config, &t_config, &f_config) == ESP_OK) {
    Serial.println("Driver installed");
  } else {
    Serial.println("Failed to install driver");
    return false;
  }

  // Start TWAI driver
  if (twai_start() == ESP_OK) {
    Serial.println("Driver started");
  } else {
    Serial.println("Failed to start driver");
    return false;
  }

  // Reconfigure alerts to detect TX alerts and Bus-Off errors
  uint32_t alerts_to_enable = TWAI_ALERT_TX_IDLE | TWAI_ALERT_TX_SUCCESS | TWAI_ALERT_TX_FAILED | TWAI_ALERT_ERR_PASS | TWAI_ALERT_BUS_ERROR;
  if (twai_reconfigure_alerts(alerts_to_enable, NULL) == ESP_OK) {
    Serial.println("CAN Alerts reconfigured");
  } else {
    Serial.println("Failed to reconfigure alerts");
    return false;
  }


  return true;
}

bool sendMessage(twai_message_t msg) {
   // Queue message for transmission
  if (twai_transmit(&msg, pdMS_TO_TICKS(1000)) != ESP_OK) {
    printf("Failed to queue message for transmission\n");
    return false;
  }
  printf("Message queued for transmission\n");
  return true;
}

.ino

/* ESP32 TWAI transmit example.
  This transmits a message every second.

  Connect a CAN bus transceiver to the RX/TX pins.
  For example: SN65HVD230

  The API gives other possible speeds and alerts:
  https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/twai.html

  created 27-06-2023 by Stephan Martin (designer2k2)
*/

#include <Arduino.h>
#include "driver/twai.h"
#include "can.h"

int16_t speed = 100;
unsigned long previousMillis = 0;  // will store last time a message was send

void setup() {
  // Start Serial:
  Serial.begin(115200);

  canInit();
}

static void send_message() {
  // Send message
  // Configure message to transmit
  twai_message_t message;
  message.identifier = 0x01; //slave address
  message.data_length_code = 5; //DLC
  message.data[0] = 0xF6;    //function code
  message.data[1] = ( (speed >> 8) & 0x0F); //High 4 bits for direction and speed
  message.data[2] = speed & 0x00FF; //8 bits lower
  message.data[3] = 0; //0xF5; // acc
  message.data[4] = CalcCRC(message);
  sendMessage(message);
}

void loop() {
  // Check if alert happened
  uint32_t alerts_triggered;
  twai_read_alerts(&alerts_triggered, pdMS_TO_TICKS(POLLING_RATE_MS));
  twai_status_info_t twaistatus;
  twai_get_status_info(&twaistatus);

  // Handle alerts
  if (alerts_triggered & TWAI_ALERT_ERR_PASS) {
    Serial.println("Alert: TWAI controller has become error passive.");
  }
  if (alerts_triggered & TWAI_ALERT_BUS_ERROR) {
    Serial.println("Alert: A (Bit, Stuff, CRC, Form, ACK) error has occurred on the bus.");
    Serial.printf("Bus error count: %" PRIu32 "\n", twaistatus.bus_error_count);
  }
  if (alerts_triggered & TWAI_ALERT_TX_FAILED) {
    Serial.println("Alert: The Transmission failed.");
    Serial.printf("TX buffered: %" PRIu32 "\t", twaistatus.msgs_to_tx);
    Serial.printf("TX error: %" PRIu32 "\t", twaistatus.tx_error_counter);
    Serial.printf("TX failed: %" PRIu32 "\n", twaistatus.tx_failed_count);
  }
  if (alerts_triggered & TWAI_ALERT_TX_SUCCESS) {
    //Serial.println("Alert: The Transmission was successful.");
    //Serial.printf("TX buffered: %" PRIu32 "\t", twaistatus.msgs_to_tx);
  }

  // Send message
  // плавно наращиваем скорость мотора и обнуляем. пока 0 - 20 сек задежки
  // для возможности ручной кооректировки параметров мотора через GUI мотора
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= TRANSMIT_RATE_MS) {
    previousMillis = currentMillis;
    //speed = 0;
    Serial.println(speed);
    //speed = 0;
    send_message();
    if (speed == 0) {
      delay(20000);
    }
    speed +=50;
    if (speed > 1800) {
      speed = 0;
    }
  }
}

Теперь мотор крутится с заданной скоростью.

Есть еще конечно с чем разбираться. На моторе 3 режима OPEN, CLOSE, FOC

На низких оборотах в режиме OPEN/CLOSE мотор очень сильно греется. Так он батарею сожжет очень быстро. А моторов 2.

В режиме FOC (векторное управление) ситуация с потреблением заметно упала.

Если OPEN/CLOSE поднималось до 450 мА, то в режиме FOR не более 300 мА.

А вот реальная частота вращения вала мотора зависит еще от установленного микрошага.

Я даю команду на мотор speed = 1000 и при микрошаге 16 он крутится довольно шустро (реально rpm не измеряд)

Если установить микрошаг 256 и задать speed = 1000 то вал мотороа крутится визуально очень медленно.

Т.е. это нужно учитывать при настройке.

Есть отдельная настройка тока удержания в %% от номинального тока. Минимум 10%. Но для робота желательно снимать напряжение совсем, что бы экономить энергию.

Можно попытаться en использовать но как будет успевать вкл/выкл мотор пока не ясно

Утро посвятил приему данных с CAN шины.

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

Решил, что принимать лучше по прерыванию. Т.е. по прерыванию выставлять некий флаг, а в отдельной функции/в отдельной задаче проверять флаг, принимать сообщение и запускать парсинг сообщения.

Первые попытки не удачные. И судя по исследованиям разработчик библиотеки twai считает такой подход не стандартным.Так же в библиотеке twai.h нет ни каких методов зарегистрировать callback функцию.

Из описания понял, что нужно создать отдельную задачу и включить в ее цикл блокирующую функцию, которая будет ждать алерта от CAN драйвера.

продолжаю исследование…

Утро вечера мудренее.

Создал задачу, поместил в нее блокирующую функцию чтения алертов.

Все заработало.

Скорость шины подняли до 1 мбит. При инициализации настроек моторов новые значения почему-то не всегда записывались. Пока не понятна причина. Возможно помехи на высокой скорости. Диагностику нужно добавлять, что бы анализировать.

Но в целом можно считать успешно. Получаю скорость по запросу, положение энкодера по запросу.

Так же есть команда, которая задает периодическую отсылку сообщений с моторов. Задать номер параметра и частоту отправки. Не проверял пока.

Код прилагаю. Мало ли кто похожим вопросом занимается.

#include <Arduino.h>
#include "can.h"
#include "mksservocmd.h"
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
#include "driver/twai.h"

TaskHandle_t h_Can;

void parsing(twai_message_t msg);

void setup() {
  Serial.begin(115200);
  while (!canInit()) {
    Serial.println("Error init CAN BUS");
    delay(5000);
  };
  xTaskCreatePinnedToCore(task_Can, "Can", 4096, NULL, 10, &h_Can, 1);
  mksInit();
};

int sp = 50;
int dir = 1;
void loop() {
  //Serial.printf("SPEED: %d | ", sp);
  //mksRequestSpeed(1);
  mksRequestPosition(1);
  //mksSetSpeed(1, sp);
  if (sp == 0) {
    delay(20000);
  }
  if (sp > 1500) {
    dir = -1;
  }
  if (sp < -1500) {
    dir = 1;
  }
  sp += 50 * dir;
  delay(200);
};

void task_Can(void *pvParameters) {  // This is a task.
  (void)pvParameters;

  uint32_t alerts;
  twai_message_t rx_msg;
  
  for (;;) {
    if (twai_read_alerts(&alerts, portMAX_DELAY) == ESP_OK) {
      if (alerts & TWAI_ALERT_RX_DATA) {
        if (twai_receive(&rx_msg, 0) == ESP_OK){
          // Serial.print("Received CMD: 0x");
          // Serial.println(rx_msg.data[0], HEX);
          parsing(rx_msg);
        }
      } 
      else if (alerts & TWAI_ALERT_BUS_OFF) {
        Serial.println("BUS OFF - restarting...");
        twai_stop();
        vTaskDelay(pdMS_TO_TICKS(100));
        twai_start();
      }
    }
  }
};

void parsing(twai_message_t msg){
  int16_t speed;
  int32_t carry;
  uint16_t value;
  switch (msg.data[0]) {
    case 0x32: // speed
      Serial.printf("code: %X ", msg.data[0]);
      speed = (msg.data[1] << 8) | msg.data[2];
      Serial.printf("speed: %d\n", speed);
      break;
    case 0x30: // position
      Serial.printf("code: %X ", msg.data[0]);
      carry = (msg.data[1] << 24) | (msg.data[2] << 16) | (msg.data[3] << 8) | msg.data[4];
      Serial.printf("carry: %d ", carry);
      value = (msg.data[5] << 8) | msg.data[6];
      Serial.printf("value: %d ", value);
      Serial.printf("pos: %d\n", carry * 0x4000 + value);
      break;
    default:
      break;
  }
}

can.h

#ifndef CAN_H
#define CAN_H

#include "driver/twai.h"

// Pins used to connect to CAN bus transceiver:
#define RX_PIN 26
#define TX_PIN 25

// Interval:
#define POLLING_RATE_MS 1000

// инициализация can шины
bool canInit(void);

// расчет контрольной суммы пакета
uint8_t CalcCRC(twai_message_t msg);

//отправка сообщения
bool sendMessage(twai_message_t msg);

// прием сообщения
esp_err_t reciveMessage(void);

//Команды для установки начальных значений в мотор MKS
void mksservoinit(void);

#endif


can.cpp

#include <Arduino.h>
#include "can.h"
#include "mksservocmd.h"

/*************************************************************
Инициализация CAN шины
Включаем минимально необходимые алерты для приема и отправки 
сообщений, ошибка шины
При приеме сообщения генерируется прерывание и выставляет флаг.
В отдельной функции проверить флаг и принять сообщение
**************************************************************/
//uint8_t masiv[10] = {1,2,3,4,5,6,7,8,9,10};
//int arr[3][2] = {{23, 34}, {12, 5}, {7, 9}};
  // // Send message
  // // Configure message to transmit
  // twai_message_t message;
  // message.identifier = 0x01; //slave address
  // message.data_length_code = 5; //DLC
  // message.data[0] = 0xF6;    //function code
  // message.data[1] = ( (speed >> 8) & 0x0F); //High 4 bits for direction and speed
  // message.data[2] = speed & 0x00FF; //8 bits lower
  // message.data[3] = 0; //0xF5; // acc
  // message.data[4] = CalcCRC(message);
  // sendMessage(message);


bool canInit(void) {
    // Initialize configuration structures using macro initializers
  twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT((gpio_num_t)TX_PIN, (gpio_num_t)RX_PIN, TWAI_MODE_NORMAL);
  //twai_timing_config_t t_config = TWAI_TIMING_CONFIG_500KBITS();  //Look in the api-reference for other speed sets.
  twai_timing_config_t t_config = TWAI_TIMING_CONFIG_1MBITS();  //Look in the api-reference for other speed sets.
  twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL();

  // Install TWAI driver
  esp_err_t err = twai_driver_install(&g_config, &t_config, &f_config);
  if ( err != ESP_OK) {
    Serial.printf("Failed to install driver. err: 0x%x\n", err);
    return false;
  }
  Serial.printf("Driver installed\n");

  // Start TWAI driver
  err = twai_start();
  if ( err != ESP_OK) {
    Serial.printf("Failed to start driver err: 0x%x\n", err);
    return false;
    Serial.printf("Driver started\n");
  }

  // Настройка минимально необходимых алертов
  uint32_t alerts_to_enable = TWAI_ALERT_TX_SUCCESS |
                            TWAI_ALERT_RX_DATA |
                            TWAI_ALERT_BUS_ERROR;
  
  err =  twai_reconfigure_alerts(alerts_to_enable, NULL);
  if ( err != ESP_OK) {
    Serial.printf("Failed to reconfigure alerts. err: 0x%x\n", err);
    return false;
  }   
  Serial.printf("CAN Alerts reconfigured\n");
  return true;
}

/*********************************************************
прием сообщений
**********************************************************/
// esp_err_t reciveMessage(void) {
//   return twai_receive(&rxMsg, 0);
// };

bool sendMessage(twai_message_t msg) {
  // Queue message for transmission
  esp_err_t err = twai_transmit(&msg, pdMS_TO_TICKS(10));
  if (err != ESP_OK) {
    Serial.printf("Failed message transmission. err: 0x%x\n", err);
    return false;
  }
  //printf("Message transmission ok\n");
  return true;
}

/*****************************************************
вычисляем контрольную сумму
typedef struct {
    uint32_t identifier;               включаем в расчет
    uint8_t data_length_code;          не включаем
    uint8_t data[TWAI_FRAME_MAX_DLC];  включаем кроме последнего байта
} twai_message_t;
В последнем байте будет рассчитанный CRC
******************************************************/
uint8_t CalcCRC(twai_message_t msg) {
  uint16_t sum = msg.identifier;
  for(uint8_t i = 0; i < msg.data_length_code - 1; i++)
    {
      sum += msg.data[i];  //Calculate accumulated value
    }
  return(sum & 0xFF);     //return checksum
}



mksservocmd.h

/*****************************************************
Команды для установки начальных значений в мотор MKS

0x82 установка режима FOC
0x83 установка максимального тока 1600 ма (0x640)
0x9B установка тока удержания 10%
0x84 установка микро шага 16
0x8C установка режима ответа slave

******************************************************/
#ifndef MKSSERVOCMD_H
#define MKSSERVOCMD_H

#include "driver/twai.h"

void mksSetModeFoc(uint8_t id);
void mksSetMaxCurrent(uint8_t id);
void mksSetCurrentHold(uint8_t id);
void mksSetMicroStep(uint8_t id);
void mksSetModeRequestSlave(uint8_t id);
void mksInit(void);
void mksSetSpeed(uint8_t id, int16_t speed);
void mksRequestSpeed(uint8_t id);
void mksRequestPosition(uint8_t id);

#endif

mksservocmd.cpp

#include "mksservocmd.h"
#include "can.h"

void mksSetModeFoc(uint8_t id) {
  twai_message_t msg; 
  msg.identifier = id;
  msg.data_length_code = 0x03;
  msg.data[0] = 0x82;     // установка режима V_FOC
  msg.data[1] = 0x05;     // foc
  msg.data[2] = CalcCRC(msg);
  sendMessage(msg);
};

void mksSetMaxCurrent(uint8_t id) {
  twai_message_t msg; 
  msg.identifier = id;
  msg.data_length_code = 0x04;
  msg.data[0] = 0x83;     // Установка максимального тока 1600 ма (0x640)
  msg.data[1] = 0x06;
  msg.data[2] = 0x40;
  msg.data[3] = CalcCRC(msg);
  sendMessage(msg);
};

void mksSetCurrentHold(uint8_t id) {
  twai_message_t msg; 
  msg.identifier = id;
  msg.data_length_code = 0x03;
  msg.data[0] = 0x9B;     // Установка тока удержания на 10%
  msg.data[1] = 0x00;
  msg.data[2] = CalcCRC(msg);
  sendMessage(msg);
};

void mksSetMicroStep(uint8_t id) {
  twai_message_t msg; 
  msg.identifier = id;
  msg.data_length_code = 0x03;
  msg.data[0] = 0x84;     // установка микрошага 16
  msg.data[1] = 0x10;
  msg.data[2] = CalcCRC(msg);
  sendMessage(msg);  
};

void mksSetModeRequestSlave(uint8_t id){
  twai_message_t msg; 
  msg.identifier = id;
  msg.data_length_code = 0x04;
  msg.data[0] = 0x8C;     // установка режима ответа slave
  msg.data[1] = 0x01;
  msg.data[2] = 0x01;
  msg.data[3] = CalcCRC(msg);
  sendMessage(msg);  
};

void mksInit(void) {
  for (uint8_t i = 1; i <= 2; i++){
    mksSetModeFoc(i);
    mksSetMaxCurrent(i);
    mksSetCurrentHold(i);
    mksSetMicroStep(i);
    mksSetModeRequestSlave(i);
  }
}

void mksSetSpeed(uint8_t id, int16_t speed) {
  // Send message
  // Configure message to transmit
  uint8_t dir = (speed < 0 ? 1 : 0);
  speed = abs(speed);
  
  twai_message_t msg;
  msg.identifier = id; //slave address
  msg.data_length_code = 0x5; //DLC
  msg.data[0] = 0xF6;    //function code
  msg.data[1] = (dir<<7) | ( (speed >> 8) & 0x0F); //High 4 bits for direction and speed  
  msg.data[2] = speed & 0x00FF; //8 bits lower
  msg.data[3] = 0; //0xF5; // acc
  msg.data[4] = CalcCRC(msg);
  sendMessage(msg);
}

//запрос скорости в реальном времени
void mksRequestSpeed(uint8_t id){
  twai_message_t msg;
  msg.identifier = id; //slave address
  msg.data_length_code = 0x02; //DLC
  msg.data[0] = 0x32;    //function code
  msg.data[1] = CalcCRC(msg);
  sendMessage(msg);
}

//запрос позиции в реальном времени
void mksRequestPosition(uint8_t id){
  twai_message_t msg;
  msg.identifier = id; //slave address
  msg.data_length_code = 0x02; //DLC
  msg.data[0] = 0x30;    //function code
  msg.data[1] = CalcCRC(msg);
  sendMessage(msg);
}

Моторы на месте.

CAN трансивер подключен.

По отдельности все элементы проверены.

Начнем с собирать все модули воедино.

Оффтоп:
А он как в рабочем положении оказывается? Нужно рукой придерживать и включать или как-то иначе?

Пока так. Удерживаем вертикально и включаем.

Крутящего момента сервоприводов в принципе хватит, что бы поднять его. Но эта задача пока на дальней полке.

Собрать все модули воедино нужно. Заставить удерживать позицию.

Радует то, что дети включились. Один сын 3д модели делает, второй вникает в С++