я же правильно понял, что вы напрямую выводами мк управляли, согласно протоколу i2c, в режиме “только передача данных”? Я хз пока, как i2c работает, предполагаю, что scl ловит импульсы, по которым инкрементит регистры, а sda забирает значения битов, но разобрать чужой код, генерирующий сигналы по такому принципу пока не в силах) особенно как там сессия открывается и адрес устройства передается. Но это ладно, текущая реализация пока что ок
Там же весь код в открытом виде и с примерами …
Ну, да. Логично же. Пульт он и есть пульт…управления. Коды команд, да и сами команды универсальны на всех роботов
Он командует, зачем ему узнавать может-не может.
привожу пример. Запустить анимационное движение можно только в “ходовой” стойке, и если не совершается никаких шагов.
Допустим, робот сейчас калибрует сервопривод №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 пикселя рисуются. Но если кому понадобится сходу разобраться с этим экраном без библиотек на подключение/шрифты то думаю код понятен и поддается правкам
дополнительно картинка шрифта и говнокодина (прям лютая) для конвертации картинки в текст массива для 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>
Да, я понимаю, у вас собственный концепт, весьма витиеватый, как по мне
Калибровка у меня - разовая акция - нахождение значений углов серв некой “нулевой позе”, от которой идёт отсчёт всего остального. Она состоит из двух этапов - механической и программной.
Механическая : сервы ставятся в угол 90 гр., ставятся качалки и собирается механика робота в “нулевой позе”.
Программная : В реальности, после выставления на все сервы 90 гр. робот чуть, чуть не станет в желаемою позу… Подбором значений углов в окрестностях 85-100гр. ставим позу и запоминаем значение углов реальных для данного экземпляра робота. Всё.
“Сложен для транспортировки” и “Ходовая стойка” - не более чем позы-кадры. Анимация - межкадровый переход, не более. У других роботов будут свои кадры.
…в случае с двуногим я писал пульт-приложение на ПК, как раз для получения интересных поз и той самой - “калибровочной”
Вот это кстати рубль в рубль… у меня так когда в меню калибровки заходишь, надо только как-то в печатных моделях заложить нулевые метки, но это чутка по-позже)) А вот в меню у меня можно каждую серву выбрать, напрямую энкодером крутить, и какое-либо текущее положение 1 из 2х кнопок задать как минимум, либо максимум. Нажатие на энкодер приведет к записи калибровки в ПЗУ. Т.е. можно и не менять значения, и в то же время, можно одну серву поменять, и только ее откалибровать
Ну, у меня проще, на бумажку, потом в массив
Я прошу прощения, но я, похоже, уже что-то запамятовал (последняя версия моей библиотеки для подобного дисплея вышла в 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 и без буфера…надо по русски…добавить…обязательно.
Эхх… С вашими талантами надо было сразу цветной экранчик брать.

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

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

ибо поза ставится на глазок
для этого есть гира