<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[KinoWheels M5Stack porting WIP...]]></title><description><![CDATA[<p dir="auto">Hi All, this is my porting of KinoWheels to m5stack using espnow to play wireless with R0nin</p>
<p dir="auto">tested with:<br />
R0nin 1</p>
<p dir="auto"><a href="https://www.kinowheels.com/" target="_blank" rel="noopener noreferrer nofollow ugc">KinoWheels</a><br />
<a href="https://forum.dji.com/thread-167232-1-1.html" target="_blank" rel="noopener noreferrer nofollow ugc">R0nin Forum</a></p>
<p dir="auto">TX: <a href="https://m5stack.com/collections/m5-core/products/basic-core-iot-development-kit" target="_blank" rel="noopener noreferrer nofollow ugc">M5Stack</a> + <a href="https://m5stack.com/collections/m5-module/products/proto-module" target="_blank" rel="noopener noreferrer nofollow ugc">PROTO MODULE</a><br />
RX: <a href="https://m5stack.com/products/atom-lite-esp32-development-kit" target="_blank" rel="noopener noreferrer nofollow ugc">M5Atom Lite</a>  + <a href="https://www.amazon.fr/Grove-Sensor-Cable-20cm-Quality/dp/B01EZ37LUO/ref=sr_1_1?__mk_fr_FR=%C3%85M%C3%85%C5%BD%C3%95%C3%91&amp;dchild=1&amp;keywords=GROVE+jumpers+female&amp;qid=1590368181&amp;sr=8-1" target="_blank" rel="noopener noreferrer nofollow ugc">GROVE cable</a></p>
<p dir="auto">SOFTWARE:</p>
<p dir="auto">first we upload rx code to know the mac address on serial monitor</p>
<p dir="auto">in the arduino IDE select M5StickC board for ATOM</p>
<pre><code>#include "M5Atom.h"
#include &lt;esp_now.h&gt;
#include &lt;WiFi.h&gt;

#define WIFI_CHANNEL 1
uint8_t localCustomMac[] = {0x36, 0x33, 0x33, 0x33, 0x33, 0x33};
const byte maxDataFrameSize = 200;

// must match the controller struct
struct __attribute__((packed)) DataStruct {
  int X;
  int Y;
  int Z;
};
DataStruct myData;

#define BAUDRATE 100000  // oder  100000 115200
#define SERIALPORT Serial2  // - uncomment this line if using an arduino based board with more than one HW serial port


class BMC_SBUS
{
  public:
    uint8_t sbusData[25];
    int16_t servos[18];
    void begin(void);
    void Servo(uint8_t ch, int16_t position);
    void Send(void);
    void Update(void);

  private:
    uint8_t byte_in_sbus;
    uint8_t bit_in_sbus;
    uint8_t ch;
    uint8_t bit_in_servo;
};


void BMC_SBUS::begin()
{
  //intialise private data arrays
  //sbus_data is formatted for correct serial output
  //note that the actual 11bit sbus data for each channel is embedded across multiple data bytes in a very stange order
  //byte 1 and bytes 24 and 25 should be left as is
  //the first is a start byte, the last is a stop byte and the second last holds various flags
  //servos is the internal per channel position and is more straightforward - one int_16 per channel

  uint8_t loc_sbusData[25] = {0x0f, 0x01, 0x04, 0x20, 0x00, 0xff, 0x07, 0x40, 0x00, 0x02, 0x10, 0x80, 0x2c, 0x64, 0x21, 0x0b, 0x59, 0x08, 0x40, 0x00, 0x02, 0x10, 0x80, 0x00, 0x00};
  int16_t loc_servos[18]   = {1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 0, 0};

  //setup serial port to transmit at 100k baud and use 1 parity and 2 stop bits
  //               (baud-rate/protocol/RXpin/TXpin/INVERT)
    SERIALPORT.begin(BAUDRATE, SERIAL_8E2, 32, 26, true); // ATOM GROVE PORT 

  //setup public data arrays

  memcpy(sbusData, loc_sbusData, 25);
  memcpy(servos, loc_servos, 18);
}

void BMC_SBUS::Servo(uint8_t ch, int16_t position)
{
  //set servo position on single channel

  if ((ch &gt; 0) &amp;&amp; (ch &lt;= 16))
  {
    constrain (position, 0, 2048); //keep within min/max values
    servos[ch - 1] = position; //expects a non zero starting index to the channel
  }
}

void BMC_SBUS::Send(void)
{
  //send data over serial port
  SERIALPORT.write(sbusData, 25); //according to docs for Serial we can send the array along as is without a loop
}

void BMC_SBUS::Update(void)
{
  //update positions for all servo channels within the SBUS data frame
  //ignores digital servos and any failsafe mode stuff that was originally written

  //clear out existing sbus data for all channel data bytes
  //ignores first and last bytes in the array (start and stop bytes)
  //mapping loop relies on initial 0 values - do not omit this step!

  uint8_t i;
  for (i = 1; i &lt; 24; i++)
  {
    sbusData[i] = 0;
  }

  //reset counters

  ch = 0;
  bit_in_servo = 0;
  byte_in_sbus = 1;
  bit_in_sbus = 0;

  //format sbus data - maps sevo data array to sbus data array 1bit at a time
  //correctly deals with the little endian byte order in the process

  for (i = 0; i &lt; 176; i++) //16channels*11bits = 176bits
  {
    if (servos[ch] &amp; (1 &lt;&lt; bit_in_servo)) //bitwise AND to check if the correct servo databit is set to 1
    {
      sbusData[byte_in_sbus] |= (1 &lt;&lt; bit_in_sbus); //bitwise OR sets the correct sbus databit if true
    }

    //increment bit counters

    bit_in_sbus++;
    bit_in_servo++;

    //if end of sbus byte reset sbus bit counter and increment sbus byte counter

    if (bit_in_sbus == 8)
    {
      bit_in_sbus = 0;
      byte_in_sbus++;
    }

    // if we have reached bit 11 in the servo data increment channel index and reset servo bit counter

    if (bit_in_servo == 11)
    {
      bit_in_servo = 0;
      ch++;
    }
  }
}

//Declare BMC_SBUS Object
BMC_SBUS mySBUS;

// Sbus delay value
const int sbusWAIT = 7;      //frame timing delay in msecs

// Declare sbus control channels
int panChannel = 1;
int tiltChannel = 2;
int rollChannel = 4;


int sentX;
int sentY;
int sentZ;


/* ********************************************************
  /*                      Void setup                           *
  /* ****************************************************** */
void setup() {

  //  M5.begin();
  M5.begin(true, false, true);
  Serial.begin(115200);
  delay(200);
  Serial.print("\r\n\r\n");
  WiFi.mode(WIFI_AP);
  Serial.println( WiFi.softAPmacAddress() );
  WiFi.disconnect();
  if (esp_now_init() == ESP_OK)
  {
    Serial.println("ESPNow Init Success!");
  }
  else
  {
    Serial.println("ESPNow Init Failed....");
  }
  esp_now_register_recv_cb(OnDataRecv);

  sentX = 1023;
  sentY = 1023;
  sentZ = 1023;

  // Start BMC_SBUS object
  mySBUS.begin();

}

/* ********************************************************
  /*                      Void Loop                           *
  /* ****************************************************** */
void loop() { 

  sentX = (myData.X);
  sentY = (myData.Y);
  sentZ = (myData.Z);

//  Serial.print("sentX ");
//  Serial.println(sentX);
//  Serial.print("sentY ");
//  Serial.println(sentY);
//  Serial.print("sentZ ");
//  Serial.println(sentZ);

  for (int i = 0; i &lt; 1; i++) {   //SBUS needs data every 7 Milliseconds. I repeat it three times for some time to pass for calculating speeds.

    mySBUS.Servo(panChannel, sentX);
    mySBUS.Servo(tiltChannel, sentY);
    mySBUS.Servo(rollChannel, sentZ);

    // Update SBUS object and send data
    mySBUS.Update();
    mySBUS.Send();

    delay(sbusWAIT);

  }

  if (M5.Btn.wasPressed())
  {}

  M5.update();
}


void OnDataRecv(const uint8_t *mac_addr, const uint8_t *incomingData, int data_len)
{
  memcpy(&amp;myData, incomingData, sizeof(myData));
  Serial.print ("MAC ADDRES = ");
  for (byte n = 0; n &lt; 6; n++) {
    Serial.print (mac_addr[n], HEX);
  }
  stato ++;
  Serial.println ();
}
</code></pre>
<p dir="auto">open arduino serial monitor and pick the receiver MAC ADDRESS.</p>
<p dir="auto">put the receiver MAC ADDRESS, in the TX code instead of "XX"</p>
<pre><code>
#include &lt;M5Stack.h&gt;

#include &lt;ESP32Encoder.h&gt;

ESP32Encoder encoderX;
ESP32Encoder encoderY;

////////////////////ESPNOW/////////////////////////
#include &lt;esp_now.h&gt;
#include &lt;WiFi.h&gt;

#define WIFI_CHANNEL 1

esp_now_peer_info_t slave;

// RX MAC ADDRESS HERE
uint8_t remoteMac[] = {0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX};

const uint8_t maxDataFrameSize = 200;

// must match the controller struct
struct __attribute__((packed)) DataStruct {

  int X;
  int Y;
  int Z;

};
DataStruct myData;
const esp_now_peer_info_t *peer = &amp;slave;
uint8_t dataToSend[maxDataFrameSize];

unsigned long lastSentMillis;
unsigned long sendIntervalMillis = 10;
unsigned long sentMicros;
unsigned long ackMicros;

///////////////////////////FINE ESPNOW///////////////

int xStampEnd = 0, yStampEnd = 0, timeStampEnd = 0;
int xPassed, yPassed, timePassed;
int X = 1023, Y = 1023, Z = 1023;
int pulsesX, pulsesY;
int  potiX = 100,  potiY = 100;

byte gearX = 2;
byte gearY = 2;

bool toggleZ;
bool toggleUSD;

//bool StatoRX;


void setup() {
  M5.begin();
  Serial.begin(115200);

  M5.Lcd.clear(BLACK);

  M5.Lcd.fillRoundRect(5, 10, 310, 150, 10, TFT_BLACK);
  M5.Lcd.fillRoundRect(15, 20, 290, 130, 5, TFT_YELLOW);
  M5.Lcd.fillRoundRect(25, 30, 270, 110, 55, TFT_BLACK);

  M5.Lcd.fillRoundRect(5, 150, 310, 90, 10, TFT_BLACK);
  M5.Lcd.fillRoundRect(15, 160, 290, 70, 5, TFT_YELLOW);
  M5.Lcd.fillRoundRect(25, 170, 270, 50, 25, TFT_BLACK);

  M5.Lcd.setTextColor(RED);
  M5.Lcd.setTextSize(5);
  M5.Lcd.setCursor(80, 60);
  M5.Lcd.println("RONIN");
  M5.Lcd.setTextColor(YELLOW);
  M5.Lcd.setCursor(55, 180);
  M5.Lcd.println("P");
  M5.Lcd.setCursor(245, 180);
  M5.Lcd.println("T");

  M5.update();

  Serial.print("\r\n\r\n");
  WiFi.mode(WIFI_STA);
  Serial.println( WiFi.softAPmacAddress() );
  WiFi.disconnect();
  if (esp_now_init() == ESP_OK)
  {
    Serial.println("ESP NOW INIT!");
  }
  else
  {
    Serial.println("ESP NOW INIT FAILED....");
  }
  memcpy( &amp;slave.peer_addr, &amp;remoteMac, 6 );
  slave.channel = WIFI_CHANNEL;
  slave.encrypt = 0;
  if ( esp_now_add_peer(peer) == ESP_OK)
  {
    Serial.println("Added Peer!");
    Serial.println();
  }
  esp_now_register_send_cb(OnDataSent);

  ///////////////////////////////////////////////////////////////////////////////

  // clear the encoder's raw count and set the tracked count to zero
  encoderX.clearCount();
  encoderY.clearCount();

  // Attache pins for use as encoder pins
  encoderX.attachHalfQuad(2, 3);
  encoderY.attachHalfQuad(34, 35);

  //////////////////////////////////////////////////////////
  Serial.println("&lt;ready&gt;");
}

void loop() {
  M5.update();
  ariloop();
  M5.update();
  sendData();
  Serial.print("x");
  Serial.print(pulsesX);
  Serial.print("y");
  Serial.print(pulsesY);
  Serial.println("end");
  delay(20); // PROVARE A TOGLIERLA
}

void ariloop() {
  pulsesX = encoderX.getCount();
  pulsesY = encoderY.getCount();

  timePassed = millis() - timeStampEnd;
  xPassed = xStampEnd - pulsesX;
  yPassed = pulsesY - yStampEnd;

  if (M5.BtnC.wasReleased()) {
    gearY ++;
    if (gearY == 4) {
      gearY = 1;
    }
    M5.Lcd.fillRoundRect(25, 170, 270, 50, 25, TFT_BLACK);
    if (toggleUSD) {
      M5.Lcd.setCursor(140, 180);
      M5.Lcd.println("UP");
    }
    M5.Lcd.setCursor(55, 180);
    M5.Lcd.println("P");
    M5.Lcd.setCursor(105, 180);
    M5.Lcd.println(gearX);
    M5.Lcd.setCursor(205, 180);
    M5.Lcd.println("T");
    M5.Lcd.setCursor(255, 180);
    M5.Lcd.println(gearY);
    delay(200);
  } else if (M5.BtnB.wasReleased()) {
    toggleZ = !toggleZ;
    if (toggleZ) {
      M5.Lcd.fillRoundRect(25, 170, 270, 50, 25, TFT_BLACK);
      M5.Lcd.setCursor(55, 180);
      M5.Lcd.println("(");
      M5.Lcd.setCursor(120, 180);
      M5.Lcd.println("ROLL");
      M5.Lcd.setCursor(255, 180);
      M5.Lcd.println(")");
    } else {
      M5.Lcd.fillRoundRect(25, 170, 270, 50, 25, TFT_BLACK);
      if (toggleUSD) {
        M5.Lcd.setCursor(140, 180);
        M5.Lcd.println("UP");
      }
      M5.Lcd.setCursor(55, 180);
      M5.Lcd.println("P");
      M5.Lcd.setCursor(105, 180);
      M5.Lcd.println(gearX);
      M5.Lcd.setCursor(205, 180);
      M5.Lcd.println("T");
      M5.Lcd.setCursor(255, 180);
      M5.Lcd.println(gearY);
      delay(200);
    }
    delay(200);

  } else if (M5.BtnA.wasReleased()) {
    gearX ++;
    if (gearX == 4) {
      gearX = 1;
    }
    M5.Lcd.fillRoundRect(25, 170, 270, 50, 25, TFT_BLACK);
    if (toggleUSD) {
      M5.Lcd.setCursor(140, 180);
      M5.Lcd.println("UP");
    }
    M5.Lcd.setCursor(55, 180);
    M5.Lcd.println("P");
    M5.Lcd.setCursor(105, 180);
    M5.Lcd.println(gearX);
    M5.Lcd.setCursor(205, 180);
    M5.Lcd.println("T");
    M5.Lcd.setCursor(255, 180);
    M5.Lcd.println(gearY);
    delay(200);

  } else if (M5.BtnB.wasReleasefor(700)) {
    toggleUSD = !toggleUSD;
    if (toggleUSD) {
      M5.Lcd.fillRoundRect(25, 170, 270, 50, 25, TFT_BLACK);
      M5.Lcd.setCursor(140, 180);
      M5.Lcd.println("UP");
      M5.Lcd.setCursor(55, 180);
      M5.Lcd.println("P");
      M5.Lcd.setCursor(105, 180);
      M5.Lcd.println(gearX);
      M5.Lcd.setCursor(205, 180);
      M5.Lcd.println("T");
      M5.Lcd.setCursor(255, 180);
      M5.Lcd.println(gearY);
      delay(200);
    } else {
      M5.Lcd.fillRoundRect(25, 170, 270, 50, 25, TFT_BLACK);
      M5.Lcd.setCursor(55, 180);
      M5.Lcd.println("P");
      M5.Lcd.setCursor(105, 180);
      M5.Lcd.println(gearX);
      M5.Lcd.setCursor(205, 180);
      M5.Lcd.println("T");
      M5.Lcd.setCursor(255, 180);
      M5.Lcd.println(gearY);
      delay(200);
    }

  }

  if (M5.BtnA.isPressed() &amp;&amp; toggleZ) {
    Z = 723;   //823, 1023, 1223
    delay(100);

  }   else if (M5.BtnC.isPressed() &amp;&amp; toggleZ) {
    Z = 1323;//823, 1023, 1223
    delay(100);

  } else {
    Z = 1023;  //823, 1023, 1223
  }

  if (gearX == 1) {
    potiX = 100;
  }
  if (gearX == 2) {
    potiX = 200;
  }
  if (gearX == 3) {
    potiX = 300;
  }

  if (gearY == 1) {
    potiY = 100;
  }
  if (gearY == 2) {
    potiY = 200;
  }
  if (gearY == 3) {
    potiY = 300;
  }

  if (toggleUSD) {
    // GRAFICA UPSIDEDOWN
    potiX = potiX * - 1;
    potiY = potiY * - 1;
  } else {
    // GRAFICA DRITTA
  }

  X = 1023 + potiX * xPassed / timePassed;
  Y = 1023 + potiY * yPassed / timePassed;

  pulsesX = encoderX.getCount();
  pulsesY = encoderY.getCount();

  xStampEnd = pulsesX;
  yStampEnd = pulsesY;
  timeStampEnd = millis();

}

void sendData() {
  if (millis() - lastSentMillis &gt;= sendIntervalMillis) {
    lastSentMillis += sendIntervalMillis;

    myData.X = X;
    myData.Y = Y;
    myData.Z = Z;

    uint8_t bs[sizeof(myData)];
    memcpy(bs, &amp;myData, sizeof(myData));
    sentMicros = micros();
    esp_now_send(NULL, bs, sizeof(myData)); // NULL means send to all peers
    Serial.println("  sent data");
    Serial.println();
  }
}

void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status)
{
  if (status == ESP_NOW_SEND_SUCCESS) {
    Serial.println("RX ACCESO");
    M5.Lcd.fillCircle(60, 60, 10, TFT_GREEN);
    //    StatoRX = 1;
  } else {
    Serial.println("RX SPENTO");
    M5.Lcd.fillCircle(60, 60, 10, TFT_RED);
    //    StatoRX = 0;
  }
  Serial.println();
}
</code></pre>
<p dir="auto">HARDWARE<br />
encoders wiring on M5Stack is a bit different of original kinowheels (arduino mega) one.</p>
<p dir="auto"><img src="/assets/uploads/files/1590398693218-screen-shot-2020-05-25-at-11.21.09-resized.png" alt="0_1590398679133_Screen Shot 2020-05-25 at 11.21.09.png" class=" img-fluid img-markdown" /></p>
<p dir="auto">with esp32 based board you can invert ttl signal in serial.begin declaration... so no need to add a serial inverter IC for D-Bus, so ATOM GROVE cable go straight to R0nin D-Bus</p>
<p dir="auto">GROVE ------ D-BUS<br />
GRD ---------- Black<br />
5V ------------ Red<br />
TX ------------ White<br />
RX ------------- Yellow (not connected on R0nin1) to test on R0nin2</p>
<p dir="auto">this is the code to use M5Stack kinowheels in the simulator:</p>
<pre><code>#include &lt;M5Stack.h&gt;
#include &lt;ESP32Encoder.h&gt;

ESP32Encoder encoderX;
ESP32Encoder encoderY;

float pulsesX, pulsesY;

void setup() {
  M5.begin();

  M5.Lcd.clear(BLACK);

  M5.Lcd.fillRoundRect(5, 10, 310, 150, 10, TFT_BLACK);
  M5.Lcd.fillRoundRect(15, 20, 290, 130, 5, TFT_YELLOW);
  M5.Lcd.fillRoundRect(25, 30, 270, 110, 55, TFT_BLACK);

  M5.Lcd.fillRoundRect(5, 150, 310, 90, 10, TFT_BLACK);
  M5.Lcd.fillRoundRect(15, 160, 290, 70, 5, TFT_YELLOW);
  M5.Lcd.fillRoundRect(25, 170, 270, 50, 25, TFT_BLACK);

  M5.Lcd.setTextColor(RED);
  M5.Lcd.setTextSize(5);
  M5.Lcd.setCursor(130, 40);
  M5.Lcd.println("PC");
  M5.Lcd.setTextColor(YELLOW);
  M5.Lcd.setCursor(55, 180);
  M5.Lcd.println("T");
  M5.Lcd.setCursor(245, 180);
  M5.Lcd.println("P");

  M5.update();

  Serial.begin(115200);

  // clear the encoder's raw count and set the tracked count to zero
  encoderX.clearCount();
  encoderY.clearCount();

  // Attache pins for use as encoder pins
  encoderX.attachHalfQuad(2, 3);
  encoderY.attachHalfQuad(34, 35);

}

void loop() {

  pulsesX = encoderX.getCount();
  pulsesY = encoderY.getCount();

  Serial.print("x");
  Serial.print(pulsesX);
  Serial.print("y");
  Serial.print(pulsesY);
  Serial.println("end");
  delay(20);
}
</code></pre>
<p dir="auto">I want to use this case in the future...<br />
<a href="https://github.com/BoaSimon/NakinoWheels" target="_blank" rel="noopener noreferrer nofollow ugc">NaKino</a></p>
]]></description><link>https://community.m5stack.com/topic/1988/kinowheels-m5stack-porting-wip</link><generator>RSS for Node</generator><lastBuildDate>Mon, 16 Mar 2026 00:04:34 GMT</lastBuildDate><atom:link href="https://community.m5stack.com/topic/1988.rss" rel="self" type="application/rss+xml"/><pubDate>Mon, 25 May 2020 00:30:27 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[Reply to KinoWheels M5Stack porting WIP... on Sat, 26 Aug 2023 18:30:18 GMT]]></title><description><![CDATA[<p dir="auto">Has anyone gotten this setup to work. Having issues with the Mac address setup, need help.<br />
Thanks</p>
]]></description><link>https://community.m5stack.com/post/22080</link><guid isPermaLink="true">https://community.m5stack.com/post/22080</guid><dc:creator><![CDATA[StarLog]]></dc:creator><pubDate>Sat, 26 Aug 2023 18:30:18 GMT</pubDate></item><item><title><![CDATA[Reply to KinoWheels M5Stack porting WIP... on Thu, 13 Aug 2020 16:37:35 GMT]]></title><description><![CDATA[<p dir="auto"><a class="mention plugin-mentions-user plugin-mentions-a" href="https://community.m5stack.com/uid/935">@cepics</a></p>
<p dir="auto">Do you have a photo of the wiring? I don't understand the wiring diagram well. Thank you.</p>
]]></description><link>https://community.m5stack.com/post/9641</link><guid isPermaLink="true">https://community.m5stack.com/post/9641</guid><dc:creator><![CDATA[fonzie]]></dc:creator><pubDate>Thu, 13 Aug 2020 16:37:35 GMT</pubDate></item><item><title><![CDATA[Reply to KinoWheels M5Stack porting WIP... on Mon, 25 May 2020 00:30:57 GMT]]></title><description><![CDATA[<p dir="auto">RESERVED</p>
]]></description><link>https://community.m5stack.com/post/8818</link><guid isPermaLink="true">https://community.m5stack.com/post/8818</guid><dc:creator><![CDATA[cepics]]></dc:creator><pubDate>Mon, 25 May 2020 00:30:57 GMT</pubDate></item></channel></rss>