I2c + SH1106 с использованием только wire.h

я же правильно понял, что вы напрямую выводами мк управляли, согласно протоколу i2c, в режиме “только передача данных”? Я хз пока, как i2c работает, предполагаю, что scl ловит импульсы, по которым инкрементит регистры, а sda забирает значения битов, но разобрать чужой код, генерирующий сигналы по такому принципу пока не в силах) особенно как там сессия открывается и адрес устройства передается. Но это ладно, текущая реализация пока что ок

Там же весь код в открытом виде и с примерами …

Ну, да. Логично же. Пульт он и есть пульт…управления. Коды команд, да и сами команды универсальны на всех роботов :slight_smile:

Он командует, зачем ему узнавать может-не может.

привожу пример. Запустить анимационное движение можно только в “ходовой” стойке, и если не совершается никаких шагов.

Допустим, робот сейчас калибрует сервопривод №2 в лапе №4, пока не будет нажато “сохранить” он не перейдет в меню выбора сервоприводов, из которого можно вернуться в состояние “сложен для транспортировки”, уже из которого можно разложиться в “ходовую” стойку. Причем процесс раскладывания тоже анимирован - лапы плавно раздвигаются вбок, потом только землю толкают вниз. Я не вижу варианта реализации без обратной связи, чтобы это было не топорно, чтобы нельзя было даже выбрать анимацию, пока в роботе не совершатся необходимые последовательности действий

У другого робота может вообще не быть состояния “сложен для транспортировки”, например у колесного или гусиничного. Думаю, продолжать не стоит…

Спойлер
#include <Wire.h>

const uint8_t FONT[91][5] PROGMEM = {
  { B00000000, B00000000, B00000000, B00000000, B00000000 },
  { B00000000, B00000000, B01011111, B00000000, B00000000 },
  { B00000000, B00000110, B00000000, B00000110, B00000000 },
  { B00010100, B00111110, B00010100, B00111110, B00010100 },
  { B01001000, B01010100, B11010110, B01010100, B00100100 },
  { B00100011, B00010011, B00001000, B01100100, B01100010 },
  { B00101000, B01010100, B01010100, B00100000, B01010000 },
  { B00000000, B00000000, B00000110, B00000000, B00000000 },
  { B00000000, B00011100, B00100010, B01000001, B00000000 },
  { B00000000, B01000001, B00100010, B00011100, B00000000 },
  { B00010100, B00001000, B00111110, B00001000, B00010100 },
  { B00001000, B00001000, B00111110, B00001000, B00001000 },
  { B00000000, B10000000, B01100000, B00000000, B00000000 },
  { B00001000, B00001000, B00001000, B00001000, B00001000 },
  { B00000000, B00000000, B01000000, B00000000, B00000000 },
  { B00100000, B00010000, B00001000, B00000100, B00000010 },
  { B00111110, B01010001, B01001001, B01000101, B00111110 },
  { B00000000, B01000010, B01111111, B01000000, B00000000 },
  { B01110010, B01001001, B01001001, B01001001, B01000110 },
  { B00100001, B01000001, B01001001, B01001101, B00110011 },
  { B00011000, B00010100, B00010010, B01111111, B00010000 },
  { B00100111, B01000101, B01000101, B01000101, B00111001 },
  { B00111100, B01001010, B01001001, B01001001, B00110001 },
  { B01000001, B00100001, B00010001, B00001001, B00000111 },
  { B00110110, B01001001, B01001001, B01001001, B00110110 },
  { B01000110, B01001001, B01001001, B00101001, B00011110 },
  { B00000000, B00000000, B00100100, B00000000, B00000000 },
  { B00000000, B10000000, B01100100, B00000000, B00000000 },
  { B00001000, B00010100, B00100010, B01000001, B00000000 },
  { B00010100, B00010100, B00010100, B00010100, B00010100 },
  { B00000000, B01000001, B00100010, B00010100, B00001000 },
  { B00000010, B00000001, B01011001, B00001001, B00000110 },
  { B00111110, B01000001, B01011101, B01010001, B01001110 },
  { B01111100, B00010010, B00010001, B00010010, B01111100 },
  { B01111111, B01001001, B01001001, B01001001, B00110110 },
  { B00111110, B01000001, B01000001, B01000001, B00100010 },
  { B01111111, B01000001, B01000001, B01000001, B00111110 },
  { B01111111, B01001001, B01001001, B01001001, B01000001 },
  { B01111111, B00001001, B00001001, B00001001, B00000001 },
  { B00111110, B01000001, B01000001, B01010001, B01110010 },
  { B01111111, B00001000, B00001000, B00001000, B01111111 },
  { B00000000, B01000001, B01111111, B01000001, B00000000 },
  { B00100000, B01000000, B01000001, B00111111, B00000001 },
  { B01111111, B00001000, B00010100, B00100010, B01000001 },
  { B01111111, B01000000, B01000000, B01000000, B01000000 },
  { B01111111, B00000010, B00011100, B00000010, B01111111 },
  { B01111111, B00000100, B00001000, B00010000, B01111111 },
  { B00111110, B01000001, B01000001, B01000001, B00111110 },
  { B01111111, B00001001, B00001001, B00001001, B00000110 },
  { B00111110, B01000001, B01010001, B00100001, B01011110 },
  { B01111111, B00001001, B00011001, B00101001, B01000110 },
  { B00100110, B01001001, B01001001, B01001001, B00110010 },
  { B00000001, B00000001, B01111111, B00000001, B00000001 },
  { B00111111, B01000000, B01000000, B01000000, B00111111 },
  { B00011111, B00100000, B01000000, B00100000, B00011111 },
  { B00111111, B01000000, B00111000, B01000000, B00111111 },
  { B01100011, B00010100, B00001000, B00010100, B01100011 },
  { B00000011, B00000100, B01111000, B00000100, B00000011 },
  { B01100001, B01010001, B01001001, B01000101, B01000011 },
  { B00000000, B01111111, B01000001, B01000001, B00000000 },
  { B00000010, B00000100, B00001000, B00010000, B00100000 },
  { B00000000, B01000001, B01000001, B01111111, B00000000 },
  { B00000100, B00000010, B00000001, B00000010, B00000100 },
  { B01000000, B01000000, B01000000, B01000000, B01000000 },
  { B00000000, B00000010, B00000100, B00000000, B00000000 },
  { B00100000, B01010100, B01010100, B01111000, B01000000 },
  { B01111111, B00101000, B01000100, B01000100, B00111000 },
  { B00111000, B01000100, B01000100, B01000100, B00101000 },
  { B00111000, B01000100, B01000100, B00101000, B01111111 },
  { B00111000, B01010100, B01010100, B01010100, B00011000 },
  { B00000000, B00001000, B01111110, B00001001, B00000010 },
  { B00011000, B10100100, B10100100, B10100100, B01111000 },
  { B01111111, B00010000, B00001000, B00001000, B01110000 },
  { B00000000, B01001000, B01111010, B01000000, B00000000 },
  { B00100000, B01000000, B01000000, B00111010, B00000000 },
  { B01111111, B00010000, B00101000, B01000100, B00000000 },
  { B00000000, B01000001, B01111111, B01000000, B00000000 },
  { B01111100, B00000100, B01111000, B00000100, B01111000 },
  { B01111100, B00001000, B00000100, B00000100, B01111000 },
  { B00111000, B01000100, B01000100, B01000100, B00111000 },
  { B11111100, B00101000, B01000100, B01000100, B00111000 },
  { B00111000, B01000100, B01000100, B00101000, B11111100 },
  { B01111100, B00001000, B00000100, B00000100, B00001000 },
  { B01001000, B01010100, B01010100, B01010100, B00100100 },
  { B00000100, B00000100, B00111110, B01000100, B00100100 },
  { B00111100, B01000000, B01000000, B00100000, B01111100 },
  { B00011100, B00100000, B01000000, B00100000, B00011100 },
  { B00111100, B01000000, B00111000, B01000000, B00111100 },
  { B01000100, B00101000, B00010000, B00101000, B01000100 },
  { B00001100, B01010000, B01010000, B01010000, B00111100 },
  { B01000100, B01100100, B01010100, B01001100, B01000100 }
};
  


class cScreen {

  public:
  
    String L0, L1, L2, L3, L4, L5;

    void define () {

      Wire.begin();
      Wire.setClock(400000);
      Wire.beginTransmission(_address);
      Wire.write(0x00); // OLED_COMMAND_MODE режим отправки команд нон-стопом
      Wire.write(0xAE); // OLED_DISPLAY_OFF выключить дисплей
      Wire.write(0xD5); // OLED_CLOCKDIV по всей видимости, делитель частоты внутренних часов, хз зачем
      Wire.write(0x80); //
      Wire.write(0x8D); // OLED_CHARGEPUMP ???
      Wire.write(0x14); //
      Wire.write(0x20); // OLED_ADDRESSING_MODE способ адресации какой-то
      Wire.write(0x01); // OLED_VERTICAL вертикальная ориентация
      Wire.write(0xA1); // OLED_NORMAL_H без отражения слева направо
      Wire.write(0xC8); // OLED_NORMAL_V без отражения сверху вниз
      Wire.write(0x81); // OLED_CONTRAST контраст
      Wire.write(0x7F); // по всей видимости величина контраста
      Wire.write(0xDB); // OLED_SETVCOMDETECT ???
      Wire.write(0x40); // 
      Wire.write(0xA6); // OLED_NORMALDISPLAY не инвертировать состояние пикселей
      Wire.write(0xAF); // OLED_DISPLAY_ON включить дисплей
      Wire.endTransmission();
      Wire.beginTransmission(_address);
      Wire.write(0x00); // OLED_COMMAND_MODE режим отправки команд нон-стопом 
      Wire.write(0xDA); // OLED_SETCOMPINS    не представляю, что именно, связано с разрешением.
      Wire.write(0x12); // OLED_HEIGHT_64     Возможно, какой-то режим адресации/сопоставления памяти
      Wire.write(0xA8); // OLED_SETMULTIPLEX  (чип может не знать, какая именно матрица к нему подключена)
      Wire.write(0x3F); // OLED_64
      Wire.endTransmission();

      L0 = "";
      L1 = "";
      L2 = "";
      L3 = "";
      L4 = "";
      L5 = "";

    }

    void draw () {
    
      // вычисление индексов
      uint8_t addresses [6][21];

      if (L0.length() > 20) for (uint8_t i = 0; i < 21; i++) addresses[0][i] = L0[i] - 32;
      else {
        for (uint8_t i = 0; i < L0.length(); i++) addresses[0][i] = L0[i] - 32;
        for (uint8_t i = L0.length(); i < 21; i++) addresses[0][i] = 0;
      }
      if (L1.length() > 20) for (uint8_t i = 0; i < 21; i++) addresses[1][i] = L1[i] - 32;
      else {
        for (uint8_t i = 0; i < L1.length(); i++) addresses[1][i] = L1[i] - 32;
        for (uint8_t i = L1.length(); i < 21; i++) addresses[1][i] = 0;
      }
      if (L2.length() > 20) for (uint8_t i = 0; i < 21; i++) addresses[2][i] = L2[i] - 32;
      else {
        for (uint8_t i = 0; i < L2.length(); i++) addresses[2][i] = L2[i] - 32;
        for (uint8_t i = L2.length(); i < 21; i++) addresses[2][i] = 0;
      }
      if (L3.length() > 20) for (uint8_t i = 0; i < 21; i++) addresses[3][i] = L3[i] - 32;
      else {
        for (uint8_t i = 0; i < L3.length(); i++) addresses[3][i] = L3[i] - 32;
        for (uint8_t i = L3.length(); i < 21; i++) addresses[3][i] = 0;
      }
      if (L4.length() > 20) for (uint8_t i = 0; i < 21; i++) addresses[4][i] = L4[i] - 32;
      else {
        for (uint8_t i = 0; i < L4.length(); i++) addresses[4][i] = L4[i] - 32;
        for (uint8_t i = L4.length(); i < 21; i++) addresses[4][i] = 0;
      }
      if (L5.length() > 20) for (uint8_t i = 0; i < 21; i++) addresses[5][i] = L5[i] - 32;
      else {
        for (uint8_t i = 0; i < L5.length(); i++) addresses[5][i] = L5[i] - 32;
        for (uint8_t i = L5.length(); i < 21; i++) addresses[5][i] = 0;
      }
        
      // вывод 6 строк текста на 8 страниц памяти экрана

      // страница 0
      Wire.beginTransmission(_address);
      Wire.write(0x00); // OLED_COMMAND_MODE режим отправки команд нон-стопом
      Wire.write(0xB0 + 0); // выбор адреса страницы 0..7
      Wire.write(2); // младший адрес столбца (не менять)
      Wire.write(16); // старший адрес столбца (не менять)
      Wire.endTransmission();
      uint8_t i = 0;
      uint8_t ch = 0;
      for (uint8_t p = 0; p < 8; p++) { // отправляем 8 посылок по 16 байт из-за ограничений Wire.h
        Wire.beginTransmission(_address);
        Wire.write(0x40); // OLED_DATA_MODE
        for (uint8_t c = 0; c < 16; c++) { // 16 колонок в блоке
          if (i == 6) {
            // инкремент символа, сброс счетчика столбцов
            i = 0;
            ch++;
          }
          if (ch == 21 || i == 0) Wire.write(B00000000);
          else Wire.write( pgm_read_byte(&FONT[ addresses[0][ch] ][i - 1]) );
          i++;
        }
        Wire.endTransmission();
      }
      // страница 1 (подчеркивание 1й строки)
      Wire.beginTransmission(_address);
      Wire.write(0x00); // OLED_COMMAND_MODE режим отправки команд нон-стопом
      Wire.write(0xB0 + 1); // выбор адреса страницы 0..7
      Wire.write(2); // младший адрес столбца (не менять)
      Wire.write(16); // старший адрес столбца (не менять)
      Wire.endTransmission();
      i = 0;
      ch = 0;
      for (uint8_t p = 0; p < 8; p++) { // отправляем 8 посылок по 16 байт из-за ограничений Wire.h
        Wire.beginTransmission(_address);
        Wire.write(0x40); // OLED_DATA_MODE
        for (uint8_t c = 0; c < 16; c++) { // 16 колонок в блоке
          if (i == 6) {
            // инкремент символа, сброс счетчика столбцов
            i = 0;
            ch++;
          }
          if (ch == 21) Wire.write(B00000000);
          else if (i == 0) Wire.write(B00000010);
          else Wire.write( pgm_read_byte(&FONT[ addresses[1][ch] ][i - 1]) << 4 | B00000010 );
          i++;
        }
        Wire.endTransmission();
      }
      // страница 2
      Wire.beginTransmission(_address);
      Wire.write(0x00); // OLED_COMMAND_MODE режим отправки команд нон-стопом
      Wire.write(0xB0 + 2); // выбор адреса страницы 0..7
      Wire.write(2); // младший адрес столбца (не менять)
      Wire.write(16); // старший адрес столбца (не менять)
      Wire.endTransmission();
      i = 0;
      ch = 0;
      for (uint8_t p = 0; p < 8; p++) { // отправляем 8 посылок по 16 байт из-за ограничений Wire.h
        Wire.beginTransmission(_address);
        Wire.write(0x40); // OLED_DATA_MODE
        for (uint8_t c = 0; c < 16; c++) { // 16 колонок в блоке
          if (i == 6) {
            // инкремент символа, сброс счетчика столбцов
            i = 0;
            ch++;
          }
          if (ch == 21 || i == 0) Wire.write(B00000000);
          else Wire.write( pgm_read_byte(&FONT[ addresses[1][ch] ][i - 1]) >> 4 | pgm_read_byte(&FONT[ addresses[2][ch] ][i - 1]) << 7 );
          i++;
        }
        Wire.endTransmission();
      }
      // страница 3
      Wire.beginTransmission(_address);
      Wire.write(0x00); // OLED_COMMAND_MODE режим отправки команд нон-стопом
      Wire.write(0xB0 + 3); // выбор адреса страницы 0..7
      Wire.write(2); // младший адрес столбца (не менять)
      Wire.write(16); // старший адрес столбца (не менять)
      Wire.endTransmission();
      i = 0;
      ch = 0;
      for (uint8_t p = 0; p < 8; p++) { // отправляем 8 посылок по 16 байт из-за ограничений Wire.h
        Wire.beginTransmission(_address);
        Wire.write(0x40); // OLED_DATA_MODE
        for (uint8_t c = 0; c < 16; c++) { // 16 колонок в блоке
          if (i == 6) {
            // инкремент символа, сброс счетчика столбцов
            i = 0;
            ch++;
          }
          if (ch == 21 || i == 0) Wire.write(B00000000);
          else Wire.write( pgm_read_byte(&FONT[ addresses[2][ch] ][i - 1]) >> 1 );
          i++;
        }
        Wire.endTransmission();
      }
      // страница 4
      Wire.beginTransmission(_address);
      Wire.write(0x00); // OLED_COMMAND_MODE режим отправки команд нон-стопом
      Wire.write(0xB0 + 4); // выбор адреса страницы 0..7
      Wire.write(2); // младший адрес столбца (не менять)
      Wire.write(16); // старший адрес столбца (не менять)
      Wire.endTransmission();
      i = 0;
      ch = 0;
      for (uint8_t p = 0; p < 8; p++) { // отправляем 8 посылок по 16 байт из-за ограничений Wire.h
        Wire.beginTransmission(_address);
        Wire.write(0x40); // OLED_DATA_MODE
        for (uint8_t c = 0; c < 16; c++) { // 16 колонок в блоке
          if (i == 6) {
            // инкремент символа, сброс счетчика столбцов
            i = 0;
            ch++;
          }
          if (ch == 21 || i == 0) Wire.write(B00000000);
          else Wire.write( pgm_read_byte(&FONT[ addresses[3][ch] ][i - 1]) << 2 );
          i++;
        }
        Wire.endTransmission();
      }
      // страница 5
      Wire.beginTransmission(_address);
      Wire.write(0x00); // OLED_COMMAND_MODE режим отправки команд нон-стопом
      Wire.write(0xB0 + 5); // выбор адреса страницы 0..7
      Wire.write(2); // младший адрес столбца (не менять)
      Wire.write(16); // старший адрес столбца (не менять)
      Wire.endTransmission();
      i = 0;
      ch = 0;
      for (uint8_t p = 0; p < 8; p++) { // отправляем 8 посылок по 16 байт из-за ограничений Wire.h
        Wire.beginTransmission(_address);
        Wire.write(0x40); // OLED_DATA_MODE
        for (uint8_t c = 0; c < 16; c++) { // 16 колонок в блоке
          if (i == 6) {
            // инкремент символа, сброс счетчика столбцов
            i = 0;
            ch++;
          }
          if (ch == 21 || i == 0) Wire.write(B00000000);
          else Wire.write( pgm_read_byte(&FONT[ addresses[3][ch] ][i - 1]) >> 6 | pgm_read_byte(&FONT[ addresses[4][ch] ][i - 1]) << 5 );
          i++;
        }
        Wire.endTransmission();
      }
      // страница 6
      Wire.beginTransmission(_address);
      Wire.write(0x00); // OLED_COMMAND_MODE режим отправки команд нон-стопом
      Wire.write(0xB0 + 6); // выбор адреса страницы 0..7
      Wire.write(2); // младший адрес столбца (не менять)
      Wire.write(16); // старший адрес столбца (не менять)
      Wire.endTransmission();
      i = 0;
      ch = 0;
      for (uint8_t p = 0; p < 8; p++) { // отправляем 8 посылок по 16 байт из-за ограничений Wire.h
        Wire.beginTransmission(_address);
        Wire.write(0x40); // OLED_DATA_MODE
        for (uint8_t c = 0; c < 16; c++) { // 16 колонок в блоке
          if (i == 6) {
            // инкремент символа, сброс счетчика столбцов
            i = 0;
            ch++;
          }
          if (ch == 21 || i == 0) Wire.write(B00000000);
          else Wire.write( pgm_read_byte(&FONT[ addresses[4][ch] ][i - 1]) >> 3 );
          i++;
        }
        Wire.endTransmission();
      }
      // страница 7
      Wire.beginTransmission(_address);
      Wire.write(0x00); // OLED_COMMAND_MODE режим отправки команд нон-стопом
      Wire.write(0xB0 + 7); // выбор адреса страницы 0..7
      Wire.write(2); // младший адрес столбца (не менять)
      Wire.write(16); // старший адрес столбца (не менять)
      Wire.endTransmission();
      i = 0;
      ch = 0;
      for (uint8_t p = 0; p < 8; p++) { // отправляем 8 посылок по 16 байт из-за ограничений Wire.h
        Wire.beginTransmission(_address);
        Wire.write(0x40); // OLED_DATA_MODE
        for (uint8_t c = 0; c < 16; c++) { // 16 колонок в блоке
          if (i == 6) {
            // инкремент символа, сброс счетчика столбцов
            i = 0;
            ch++;
          }
          if (ch == 21 || i == 0) Wire.write(B00000000);
          else Wire.write( pgm_read_byte(&FONT[ addresses[5][ch] ][i - 1]) );
          i++;
        }
        Wire.endTransmission();
      }

      //return millis() - begin;
    
    }

    /* // вкл/выкл экран
    void power (bool value) {
      Wire.beginTransmission(_address);
      Wire.write(0x80); // OLED_ONE_COMMAND_MODE
      Wire.write(value ? 0xAF : 0xAE); // OLED_DISPLAY_ON / OLED_DISPLAY_OFF
      Wire.endTransmission();
      // delayMicroseconds(2);
    }
    
    // яркость 0-255
    void contrast (uint8_t value) {
      Wire.beginTransmission(_address);
      Wire.write(0x00); // OLED_COMMAND_MODE
      Wire.write(0x81); // OLED_CONTRAST
      Wire.write(value);
      Wire.endTransmission();
      //delayMicroseconds(2);
    } */

  private:
  
    const uint8_t _address = 0x3C;

};


// инициализация

screen.define();

// использование
screen.L0 = "CONNECTION LOST...";
screen.L1 = "";
screen.L2 = "";
screen.L3 = "    Check device!";
screen.L4 = "";
screen.L5 = "";
screen.draw();

В общем, работает… Получилась внушительная простыня, т.к. включил в выкладку кода массив шрифта, а также в методе вывода на экран не стал мудрить с вычислением сдвигов - просто 8 страниц вывел последовательными циклами.

Получилось, конечно, специфичненько… первая строка всегда подчеркнута (заголовок), остальные с отступом в 3 пикселя рисуются. Но если кому понадобится сходу разобраться с этим экраном без библиотек на подключение/шрифты то думаю код понятен и поддается правкам

font

дополнительно картинка шрифта и говнокодина (прям лютая) для конвертации картинки в текст массива для progmem (html + javascript, сохранить в html файл, открыть в браузере)

Спойлер
<HTML>
<HEAD>
<TITLE>Font To Byte Array</TITLE>
<meta charset="utf-8">
</HEAD>
<BODY>

<input type="file" onchange="load(this)" />
<canvas style="display: block" id="test1"></canvas>
<textarea style="display: block" id="r" rows="50" cols="63"></textarea>

<script>

function load(fl) {

  var canvas = document.getElementById('test1');
  ctx = canvas.getContext('2d');
  tex = document.getElementById("r");
  
  let reader = new FileReader();
  var img1 = new Image();
  
  img1.onload = function () {
  
    canvas.width = img1.width;
    canvas.height = img1.height;
    ctx.drawImage(img1, 0, 0);
    
    var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    var data = imageData.data;

    const chars = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz";
  
    tex.value = "const uint8_t FONT[91][5] PROGMEM = {\r\n";
    var c = 0;
    for (y = 0; y < canvas.height / 9; y++)
      for (x = 0; x < canvas.width / 6; x++) {
      
        tex.value += "  { B";
        for (i = 7; i > -1; i--) tex.value += data[(y*9 + i) * (canvas.width * 4) + (x*6 + 0) * 4] < 128 ? "1" : "0" ;
        tex.value += ", B";
        for (i = 7; i > -1; i--) tex.value += data[(y*9 + i) * (canvas.width * 4) + (x*6 + 1) * 4] < 128 ? "1" : "0" ;
        tex.value += ", B";
        for (i = 7; i > -1; i--) tex.value += data[(y*9 + i) * (canvas.width * 4) + (x*6 + 2) * 4] < 128 ? "1" : "0" ;
        tex.value += ", B";
        for (i = 7; i > -1; i--) tex.value += data[(y*9 + i) * (canvas.width * 4) + (x*6 + 3) * 4] < 128 ? "1" : "0" ;
        tex.value += ", B";
        for (i = 7; i > -1; i--) tex.value += data[(y*9 + i) * (canvas.width * 4) + (x*6 + 4) * 4] < 128 ? "1" : "0" ;
        tex.value += c == 90 ? " }\r\n" : " },\r\n";
        c++;
        
      }
    tex.value += "};";
  
  };
  
  reader.onload = e => img1.src = e.target.result;
  reader.readAsDataURL(fl.files[0]);
  
}
</script>


</BODY>
</HTML>

Да, я понимаю, у вас собственный концепт, весьма витиеватый, как по мне :slight_smile:
Калибровка у меня - разовая акция - нахождение значений углов серв некой “нулевой позе”, от которой идёт отсчёт всего остального. Она состоит из двух этапов - механической и программной.
Механическая : сервы ставятся в угол 90 гр., ставятся качалки и собирается механика робота в “нулевой позе”.
Программная : В реальности, после выставления на все сервы 90 гр. робот чуть, чуть не станет в желаемою позу… Подбором значений углов в окрестностях 85-100гр. ставим позу и запоминаем значение углов реальных для данного экземпляра робота. Всё.
“Сложен для транспортировки” и “Ходовая стойка” - не более чем позы-кадры. Анимация - межкадровый переход, не более. У других роботов будут свои кадры.
…в случае с двуногим я писал пульт-приложение на ПК, как раз для получения интересных поз и той самой - “калибровочной”

1 лайк

Вот это кстати рубль в рубль… у меня так когда в меню калибровки заходишь, надо только как-то в печатных моделях заложить нулевые метки, но это чутка по-позже)) А вот в меню у меня можно каждую серву выбрать, напрямую энкодером крутить, и какое-либо текущее положение 1 из 2х кнопок задать как минимум, либо максимум. Нажатие на энкодер приведет к записи калибровки в ПЗУ. Т.е. можно и не менять значения, и в то же время, можно одну серву поменять, и только ее откалибровать

Ну, у меня проще, на бумажку, потом в массив :slight_smile:

Я прошу прощения, но я, похоже, уже что-то запамятовал (последняя версия моей библиотеки для подобного дисплея вышла в 2017 году), страница - это горизонтальный ряд на дисплее 128*8 пикселей? Но все равно непонятно, ну не попадает и что? Может, нужно позаботиться, чтобы попадали?

А этой фразы я вообще не понял.

В таком случае я бы порекомендовал отказаться от идеи делать высоту строк не кратной 8 пикселям.

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

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

Я не знаю ни что такое “!анимационное движение”, ни что такое “ходовая стойка”. Похоже, мне надо “выпиливаться” из этой темы.
Хочу лишь заметить, что

Обратная связь существует, минимум, на двух-трех уровнях.

  • любой сервопривод сам по себе работает с обратной связью внутри себя,
  • обратная связь на уровне отдельной конечности,
  • обратная связь всей механической конструкции.

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

Не, ну там напряжение аккумулятора при ходьбе, ток потребления можно в обратку пускать.

Видимо речь о содержательной части скетчей для мк пульта и робота.

“Можно” и “нужно” - разные вещи.
И потом, я ведь не предлагаю ТС полностью отказаться от обратной связи. Я лишь предлагаю попытаться спроектировать систему без оной. Хотя бы для того, чтобы самому лучше понять задачу и лучше продумать подходы к ее решению.

Видимо у ТС пульт по замыслу не совсем пульт. Скорее информационный экран с возможностью взаимодействия с оператором. Каждый робот даёт на него допустимый шаблон управления собой и инфу о себе по мере движения-общения с оператором.

Совершенно верно.

есть немного. Вообще иногда проще показать, чем объяснить…

Давайте я примерную логику распишу, условно, некоторые пункты могу забыть.

У робота есть набор состояний, в каком-то 1 из них он находится в определенный момент времени, например:

сложен (лапы прижаты к туловищу для хранения)
1 раскладывается (лапы оттягиваются до положения “робот стоит”) *
1.1 стоит (разложен - ходовая стойка, ходьба и анимации с нее стартуют) #
1.1.1 идет *
1.1.2 отображает список фиксированных анимаций
1.1.2.1 выполняет запущенную фиксированную анимацию *
1.1.3 отображает настройки шага
1.1.3.1 меняет настройки шага *
1.1.4 складывается (переходит обратно в состояние “сложен”) *
1.2 запущена калибровка (все серво выставляются в 90, отображается список)
1.2.1 калибруется выбранный из списка сервопривод

Отмеченные * имеют продолжительность - робот должен начать и закончить движение. Отображается блокирующая надпись. При завершении обязательно переход (в частности возврат) в одно из состояний, в котором отображается меню.
Отмеченное # чуть отличается от остальных - робот начнет идти, если не нулевые показания аналоговых стиков. Шаг по сути тоже дискретная анимация с продолжительностью, но траектории динамически рассчитываются на основе команд с пульта (векторы поступательного и вращательного движения)

Вот представьте я добавляю в 1.1.2 новый пункт меню (новую анимацию, чтоб робот по паттернам на столе потанцевал 10 секунд), мне что прошивать из-за этого и робота и пульт? Вот я кумекал много, думал даже при включении целиком меню передавать по запросу пульта… ну тоесть пульт включился, и спамит роботу запрос на получение всей менюшки, пока не получит ее в ответ.

А потом меня осенило, зачем все меню кешировать, если экран всего на несколько строчек, и робот точно “знает” какие следует отображать в определенный момент времени. Да, вот вам еще пара “плюшек”. На время прошивки робота я могу пульт не выключать вовсе. Если вдруг выключился пульт, то после включения отображается меню робота на том же месте, где остановились.

Высота строк, именно символов ровно 8 пикселей, но между строк есть отступы по 3 пикселя - межстрочный интервал. Получается более разряжено и удобно читать

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

не попадает именно по высоте, т.е. чё там заботиться - пихай себе без отступа строки каждую в свою страницу и всё попадет…

из картинки нашей, смотрите, 3-я страница памяти дисплея (зеленая) - в нее попадают части сразу 2х строк текста, поэтому:

Wire.write( pgm_read_byte(&FONT[ addresses[1][ch] ][i - 1]) >> 4 | pgm_read_byte(&FONT[ addresses[2][ch] ][i - 1]) << 7 );

применяю “или” к результатам сдвигов соответствующих битмапок символов из набора шрифта. Сдвиги по вертикали на дисплее…

Сдаёцца мне, чота ты мудришь…

но работает же))

Ясно. Тут фишка 6 строк вместо 8 и без буфера…надо по русски…добавить…обязательно.
:slight_smile:
Эхх… С вашими талантами надо было сразу цветной экранчик брать.

ждём’c автокалибровку )
PS ну не остановится жеж ТС на полпути

Это одноразовая акция, нуу до замены первой сервы точно… И сугубо необъективная, ибо поза ставится на глазок.

для этого есть гира