Библиотека для LCD на базе контроллера HD44780


Писал я как-то статейку про подключение этих дисплеев к Arduino. Но сейчас моё кремниевое сердце принадлежит STM32, а с имеющимися дисплеями работать хочется. Как-то раз мне понадобилось визуально отладить алгоритм для этих МК — я собрался духом и сел писать библиотеку.

Писать с нуля, постоянно копошась в даташите, мне не хотелось, поэтому я решил портировать ардуиновскую библиотеку LiquidCrystal на STM32F10x, для начала. После того, как основная работа была сделана (это заняло примерно три дня с отладкой, матюками и другой работой) и дисплей подружился с STM32VLDISCOVERY, я подумал, что было бы неплохо заиметь поддержку таких дисплеев и для других подсемейств STM32.

В результате более чем недельного программерского задротства

получилась кросс-платформенная библиотека, которую я, не мудрствуя лукаво, назвал банально — HD44780. Её исходники открыты под лицензией MIT и выложены на GitHub (архив). Рассмотрим библиотеку поподробнее.

Структура

Так как я хотел добиться максимальной портируемости кода и лёгкой его подстройки под разные семейства МК, я отделил логику работы библиотеки от непосредственной работы с выводами конкретных МК, и реализовал это известными методами абстракции — интерфейсами и драйверами. Что это такое и как работает? Библиотека работает с GPIO через интерфейс — набор функций. Каждая функция интерфейса принимает в качестве аргументов указатель на интерфейс, идентификатор вывода дисплея (HD44780_PIN_RS, HD44780_PIN_DP5 и т.п.) и остальные аргументы, необходимые для выполнения операции над GPIO:

/* Пара объявлений  для красоты */
struct HD44780_GPIO_Interface_Struct;
typedef struct HD44780_GPIO_Interface_Struct HD44780_GPIO_Interface;

/* Интерфейс GPIO: configure(), write() и read() */
struct HD44780_GPIO_Interface_Struct
{
  HD44780_Result (*configure)(HD44780_GPIO_Interface *interface,
    HD44780_Pin pin, HD44780_PinMode mode);
  HD44780_Result (*write)(HD44780_GPIO_Interface *interface,
    HD44780_Pin pin, HD44780_PinState value);
  HD44780_Result (*read)(HD44780_GPIO_Interface *interface,
    HD44780_Pin pin, HD44780_PinState *value);
};

Скорее всего, у вас возник закономерный вопрос: на кой нужно функции, члену интерфейса, передавать указатель на этот интерфейс? А сделано это потому, что в библиотеке любой драйвер GPIO представляет собой структуру, первым членом которой является как раз такой интерфейс, поэтому можно спокойно приводить указатель на драйвер к указателю на интерфейс — таким образом, драйвер может «прикинуться» интерфейсом. Гляньте на объявление драйвера GPIO для STM32F10x:

/* Структура, описывающая одну ножку МК для драйвера */
typedef struct
{
  GPIO_TypeDef *gpio;
  uint16_t pinmask;
} HD44780_STM32F10x_Pin;

/* Массив пинов обёрнут в структуру для более строгой проверки
   типов данных компилятором. Сам массив будет заполнять пользователь
   библиотеки, указывая GPIO, которые он отдаст драйверу. */
typedef struct
{
  HD44780_STM32F10x_Pin pins[HD44780_PINS_AMOUNT];
} HD44780_STM32F10x_Pinout;

/* А вот и драйвер, содержащий интерфейс, распиновку
   и обработчик ошибок. */
typedef struct
{
  HD44780_GPIO_Interface interface;
  HD44780_STM32F10x_Pinout pinout;
  HD44780_AssertFn assert_failure_handler;
} HD44780_STM32F10x_GPIO_Driver;

Теперь, если заставить библиотеку работать с GPIO только через указатель на HD44780_GPIO_Interface, можно подсунуть ей указатель на экземпляр драйвера, просто приведя последний к HD44780_GPIO_Interface* . А библиотека вообще не знает, с чем на самом деле она работает — может, там не GPIO, а толпа китайцев, замыкающих контакты по вызову функции интерфейса. Налицо реализация типичной парадигмы ООП — полиморфизма. Кто там говорил, что C — не объектно-ориентированный язык?

Драйвер, в свою очередь, реализует функции интерфейса и в них приводит указатель на интерфейс обратно к указателю на драйвер. Получается, что библиотеке видна только «верхушка» драйвера, а оставшаяся часть видна только драйверу. Вот и инкапсуляция нарисовалась. Дополню своё объяснение схемой:

а также кодом одной из функций драйвера:

static HD44780_Result stm32f10x_default_pin_write(
  HD44780_GPIO_Interface *interface, HD44780_Pin pin, HD44780_PinState value)
{
  HD44780_STM32F10X_RETURN_ASSERT(interface != NULL, HD44780_RESULT_ERROR);

  HD44780_STM32F10x_GPIO_Driver *driver = (HD44780_STM32F10x_GPIO_Driver*)interface;
  HD44780_STM32F10x_Pin *hw_pin = &driver->pinout.pins[pin];

  HD44780_STM32F10X_RETURN_ASSERT(hw_pin != NULL, HD44780_RESULT_ERROR);
  HD44780_STM32F10X_RETURN_ASSERT(hw_pin->gpio != NULL, HD44780_RESULT_ERROR);

  GPIO_WriteBit(hw_pin->gpio, hw_pin->pinmask,
    (value == HD44780_PINSTATE_LOW ? Bit_RESET : Bit_SET));

  return HD44780_RESULT_OK;
}

С такой организацией доступа к GPIO драйвера полностью отделены от библиотеки, так что если у вас есть кучка китайцев, можете написать драйвер и для них, не меняя код библиотеки (:

Делу — время, а потехе — час

Окей, пора браться за дело, но перед этим проясню один момент: библиотека использует пользовательскую функцию для создания задержек, которую вам нужно написать. Я предпочитаю реализовывать её при помощи таймера SysTick. Функция должна принимать число микросекунд и делать соответствующую задержку (или дольше). Конечно, delay()-ориентированное программирование не есть гуд, но потом ниже я объясню, как с этим жить.

И да, в настройках проекта поставьте стандарт языка C99 (флаг -std=c99).

#include <stm32f10x.h>
#include <stm32f10x_gpio.h>
#include <stm32f10x_rcc.h>

#include <stdio.h>
#include <stdlib.h>

/* Подключаем библиотеку и драйвер GPIO */
#include "hd44780.h"
#include "hd44780_stm32f10x.h"

void init_lcd(void);
void delay_microseconds(uint16_t us);
uint32_t uint32_time_diff(uint32_t now, uint32_t before);
void hd44780_assert_failure_handler(const char *filename, unsigned long line);

HD44780 lcd;
HD44780_STM32F10x_GPIO_Driver lcd_pindriver;
volatile uint32_t systick_ms = 0;

int main(void)
{
  SysTick_Config(SystemCoreClock / 1000);
  init_lcd();

  while (1)
  {
    /* Каждую секунду выводим счётчик на экран и увеличиваем его */

    static uint32_t lcd_update_ms = 0;

    if (uint32_time_diff(systick_ms, lcd_update_ms) >= 1000)
    {
      lcd_update_ms = systick_ms;

      static unsigned counter = 0;

      const size_t buf_size = lcd.columns_amount + 1;
      char buf[buf_size];
      snprintf(buf, buf_size, "%d", counter);

      ++counter;

      hd44780_clear(&lcd);
      hd44780_write_string(&lcd, buf);
    }
  }
}

void SysTick_Handler(void)
{
  ++systick_ms;
}

void init_lcd(void)
{
  /* Распиновка дисплея */
  const HD44780_STM32F10x_Pinout lcd_pinout =
  {
    {
      /* RS        */  { GPIOA, GPIO_Pin_6 },
      /* ENABLE    */  { GPIOA, GPIO_Pin_5 },
      /* RW        */  { GPIOA, GPIO_Pin_4 },
      /* Backlight */  { NULL, 0 },
      /* DP0       */  { NULL, 0 },
      /* DP1       */  { NULL, 0 },
      /* DP2       */  { NULL, 0 },
      /* DP3       */  { NULL, 0 },
      /* DP4       */  { GPIOA, GPIO_Pin_3 },
      /* DP5       */  { GPIOA, GPIO_Pin_2 },
      /* DP6       */  { GPIOA, GPIO_Pin_1 },
      /* DP7       */  { GPIOA, GPIO_Pin_0 },
    }
  };

  /* Настраиваем драйвер: указываем интерфейс драйвера (стандартный),
     указанную выше распиновку и обработчик ошибок GPIO (необязателен). */
  lcd_pindriver.interface = HD44780_STM32F10X_PINDRIVER_INTERFACE;
  /* Если вдруг захотите сами вручную настраивать GPIO для дисплея
     (зачем бы вдруг), напишите здесь ещё (библиотека учтёт это):
  lcd_pindriver.interface.configure = NULL; */
  lcd_pindriver.pinout = lcd_pinout;
  lcd_pindriver.assert_failure_handler = hd44780_assert_failure_handler;

  /* И, наконец, создаём конфигурацию дисплея: указываем наш драйвер,
     функцию задержки, обработчик ошибок дисплея (необязателен) и опции.
     На данный момент доступны две опции - использовать или нет
     вывод RW дисплея (в последнем случае его нужно прижать к GND),
     и то же для управления подсветкой. */
  const HD44780_Config lcd_config =
  {
    (HD44780_GPIO_Interface*)&lcd_pindriver,
    delay_microseconds,
    hd44780_assert_failure_handler,
    HD44780_OPT_USE_RW
  };

  /* Ну, а теперь всё стандартно: подаём тактирование на GPIO,
     инициализируем дисплей: 16x2, 4-битный интерфейс, символы 5x8 точек. */
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
  hd44780_init(&lcd, HD44780_MODE_4BIT, &lcd_config, 16, 2, HD44780_CHARSIZE_5x8);
}

void delay_microseconds(uint16_t us)
{
  SysTick->VAL = SysTick->LOAD;
  const uint32_t systick_ms_start = systick_ms;

  while (1)
  {
    uint32_t diff = uint32_time_diff(systick_ms, systick_ms_start);

    if (diff >= ((uint32_t)us / 1000) + (us % 1000 ? 1 : 0))
      break;
  }
}

uint32_t uint32_time_diff(uint32_t now, uint32_t before)
{
  return (now >= before) ? (now - before) : (UINT32_MAX - before + now);
}

void hd44780_assert_failure_handler(const char *filename, unsigned long line)
{
  (void)filename; (void)line;
  do {} while (1);
}

Максимальная суммарная задержка при вызове функции библиотеки при такой реализации delay_microseconds() может составлять до 20 мс, что может быть критичным. С другой стороны, реализация полностью асинхронной библиотеки — это задача нетривиальная, а в данном случае ещё и сложная. Да и использовать такую библиотеку будет не очень-то просто.

Но выход есть — использовать операционную систему реального времени (RTOS). Есть подробная статья о том, как подружить STM32 и FreeRTOS — свободную и открытую RTOS для микроконтроллеров. Она очень популярна, функциональна и поддерживает тонны различных МК, включая STM32. Так вот, в ней есть функция vTaskDelay(), которая замораживает не всю программу, как это было бы без RTOS, а только ту задачу (поток), в которой была вызвана функция. В этом случае реализация delay_microseconds() будет заключаться в вызове vTaskDelay(), и задержки будут влиять только на ту задачу, которая работает с LCD.

Интерфейс библиотеки

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

HD44780_Result hd44780_init(HD44780 *display, HD44780_Mode mode,
  const HD44780_Config *config, uint8_t columns, uint8_t rows, HD44780_CharSize charsize);
HD44780_Result hd44780_write_char(HD44780 *display, char c);
HD44780_Result hd44780_write_string(HD44780 *display, const char *s);
HD44780_Result hd44780_clear(HD44780 *display);
HD44780_Result hd44780_home(HD44780 *display);
HD44780_Result hd44780_scroll_left(HD44780 *display);
HD44780_Result hd44780_scroll_right(HD44780 *display);
HD44780_Result hd44780_left_to_right(HD44780 *display);
HD44780_Result hd44780_right_to_left(HD44780 *display);
HD44780_Result hd44780_create_char(HD44780 *display, uint8_t code, const uint8_t *charmap);
HD44780_Result hd44780_move_cursor(HD44780 *display, uint8_t column, uint8_t row);
HD44780_Result hd44780_display_on(HD44780 *display);
HD44780_Result hd44780_display_off(HD44780 *display);
HD44780_Result hd44780_blink_on(HD44780 *display);
HD44780_Result hd44780_blink_off(HD44780 *display);
HD44780_Result hd44780_cursor_on(HD44780 *display);
HD44780_Result hd44780_cursor_off(HD44780 *display);
HD44780_Result hd44780_autoscroll_on(HD44780 *display);
HD44780_Result hd44780_autoscroll_off(HD44780 *display);
HD44780_Result hd44780_backlight_on(HD44780 *display);
HD44780_Result hd44780_backlight_off(HD44780 *display);

Функция hd44780_create_char() создаёт символ с заданным ASCII-кодом от 0 до 7, используя битовое описание символа в виде массива из 8 байтов, каждый из которых кодирует строку из 5 точек. Пользоваться ей нужно так:

uint8_t arnie[8] =
{
  HD44780_MAKE_5BITS(1,0,1,0,1),
  HD44780_MAKE_5BITS(1,1,1,1,1),
  HD44780_MAKE_5BITS(1,0,1,1,1),
  HD44780_MAKE_5BITS(1,1,1,1,1),
  HD44780_MAKE_5BITS(1,1,1,1,1),
  HD44780_MAKE_5BITS(0,0,1,1,1),
  HD44780_MAKE_5BITS(1,1,1,1,0),
  HD44780_MAKE_5BITS(0,0,0,0,0),
};

hd44780_create_char(&lcd, 1);
hd44780_write_string(&lcd, "Here is Arnie: ");
hd44780_write_char(&lcd, '\1');

Разумеется, символы можно встраивать в строки через escape-последовательности, но тут нужно иметь ввиду, что код 0 является завершением строки, так что такой символ придётся печатать отдельно, как в примере. Кстати, для просмотра созданных символов не обязательно запускать код — достаточно в редакторе сделать поиск текста «1» и воспользоваться функцией подсветки результатов поиска (:

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

Функция hd44780_clear() не только очищает экран, но и перемещает курсор в начало, как это делает hd44780_home().

Заключение

На данный момент я написал и протестировал драйвера GPIO для STM32F10x, STM32F2xx, STM32F4xx и STM32L1xx. Так что все STM32 охвачены, кроме ещё не поступивших в продажу STM32F0 на базе архитектуры Cortex-M0. Ну, тут я немножко лукавлю: драйвер для STM32F2xx я тестировал на STM32F4DISCOVERY, т.к. МК этой серии у меня нет (голый кристалл не в счёт). Но результатам можно верить, т.к. STM32F2xx совместимы по периферии с STM32F4xx, и более того — код для STM32F2xx таки заработал на вышеуказанной плате, так что должен работать и с настоящим STM32F2xx.

Был у меня лёгкий порыв написать ещё дровишку для AVR, но угас, когда я вспомнил об Arduino, с которого портировал библиотеку. Впрочем, если кому сильно нужно, можете попросить меня или написать сами по образцу.

Сами драйвера лежат в директории drivers, а в директорию examples я положил все 4 примера для STM32, на которых тестировал работу дисплея, в виде своих проектов для Eclipse. Впрочем, не обязательно импортировать проект в Eclipse — достаточно в консоли зайти в <проект>/Debug и выполнить команду make (GNU ARM Eclipse plugin создаёт makefile’ы для конфигураций Debug, Release и т.п.)

Надеюсь, библиотека вам пригодится, несмотря на сравнительную сложность её применения (:


23 комментария на «“Библиотека для LCD на базе контроллера HD44780”»

  1. спасибо, приятно почитать!
    Я сегодня как раз с почты забрал два дисплея с таким контроллером, которые с месяц назад заказал «напоиграть». Попробую заюзать вместе с stm32vl-discovery.

  2. Спасибо за статью! Скачал и посмотрел исходники и появился вопрос: каким образом в драйвере hd44780_stm32f4xx.c функции интерфейса stm32f4xx_default_pin_write и stm32f4xx_default_pin_read переключают направление GPIO?

    • Они этого не делают. Библиотека при инициализации дисплея косвенно вызывает stm32f4xx_default_pin_configure() через указатель на функцию configure в интерфейсе HD44780_GPIO_Interface, коим прикидывается драйвер GPIO.

  3. Скомпилил в кокосе, залил в STM32F103C8, что на Pinboard II… подключил WH1602B — не работает!((((

    Распиновка PB12,PB13,PB14 — RS,E,RW; PA9,10,11,12 — DATA т.е все пины FT…

    • Боюсь показаться К.О., но вам определённо стоит выложить куда-нибудь фото подключения и архив с проектом и привести на них ссылки, чтобы получить какую-то помощь — при использовании телепатии результат обычно оставляет желать лучшего (:

    • так собсно все «из коробки», поэтому и не привел ничего. Код прямо из этой статьи… сначала подключение, как в статье — все к порту А. После того как не запустилось, озадачился различием уровней МК и дисплея… переключил на толерантные к 5V пины, результат тот же… горит верхняя строка символов и все.

      Сейчас фотика нет под рукой, вот фотка PINboard II с сайта DI Halt’a easyelectronics.ru/files/PinBoard/frontview.jpg
      а вот распиновка модуля STM32 — easyelectronics.ru/files/PinBoard/STM32_base.pdf
      соединены согласно lcd_pinout

      вот картинка проекта — ftp://ftp.asinex.com/project.jpg

    • Так обратитесь к Di Halt’у с его армией битодролюбов: PinBoard II — это ж его плата (:
      К тому же:

      • У меня такой платы нет — проверить не на чем.
      • Вместо самих исходников вы дали их фотку.

      Всё указывает на то, что обращаться нужно в первую очередь к тем, кто имеет PinBoard II, и кто с ней знаком — к автору платы, Di Halt’у и посетителям его сайта. Они 99% помогут, там даже по статистике телепатов должно быть больше, чем здесь.

    • вот зип с проектом — ftp://ftp.asinex.com/lcd.zip
      Контроллер проводками подключен к дисплею… какая разница на какой отладочной плате? Ок, проверю вечером на STM32VL_Discovery

  4. Не могли бы вы прокомментировать этот кусочек кода, как он работает. Что за преобразования происходят в буфером? У меня почему-то ничего не выводится на дисплей, а когда пишу например hd44780_write_string(&lcd, «Hello»), — то всё прекрасно работает.

    lcd_update_ms = systick_ms;
    static unsigned counter = 0;
    const size_t buf_size = lcd.columns_amount + 1;
    char buf[buf_size], *end = buf;
    snprintf(buf, buf_size, "%d", counter);
    *end = 0;
    ++counter;

    И почему используется hd44780_write_string(), хотя при этом мы выводим на дисплей число? Разьве не логичней использовать hd44780_write_char()?

    • snprintf(buf, buf_size, "%d", counter);

      Здесь происходит «печать» счётчика в буфер. А результат выводится на дисплей вызовом hd44780_write_string().
      hd44780_write_char() записывает ровно один символ, так что он никакого отношения к числам не имеет.

    • Никак не могу понять почему snprintf не работает.
      Вот состояния переменных. Почему у buf значение [0]?
      counter unsigned int 4
      buf_size const size_t 17
      buf char [] [0]
      end char * 0x20001fb8 ""
      lcd_update_ms uint32_t 4000

    • О, прошу прощения, в код в статье закралась ошибка. Видимо, изначально я хотел написать:

      end += snprintf(buf, buf_size, "%d", counter);

      но забыл. Потом я прочитал документацию и узнал, что snprintf() сама добавляет завершающий нуль в буфер. Поправил код в статье, посмотрите.

    • Вот теперь всё в порядке… Поправите так же на GitHub. Хотел спросить, будут-ли продолжен цикл статей по АРМ?

    • Спасибо за напоминание, поправлю (:
      Насчёт курса — ох не знаю… Нет ни денег, ни времени, и сейчас я вынужден искать работу не в RoboCraft, а на вольных хлебах статьи особо не попишешь.

  5. Буржууууй! Я призываю тебя поговорить со мноооой!!! :DDD Зайди в аську, или я не знаю там, зайду куданить, хочу слов тебе хороших написать ;D

    • Работа отнимает много энергии, после неё не очень-то хочется кодить что-то ещё, даже для себя. Да и за время моего отлучения от написания статей по STM32 появилось огромное количество информации, поэтому если я и буду про них что-то писать, то, скорее, не в образовательном плане, а в более прикладном: не художественный пересказ документации, а какое-нибудь простенькое, но законченное устройство в каждой статье — желательно, полезное 🙂 Статьи такого плана не только будет интереснее писать, но и получаться это будет ещё более неплохо (спасибо, кстати), т.к. не придётся делать самую нудятину — объяснять в деталях периферию. Думаю, инфы, необходимой для создания конкретного устройства, будет достаточно. К тому же, так можно быстрее добраться до крутых штук типа DMA.

      А вообще, на работе я занимаюсь разработкой под Android, и возникали мысли сделать что-нибудь с использованием Android и STM32. Если есть интересные идеи на этот счёт — делитесь, с фантазией у меня туго. Беспроводная связь тут прямо-таки напрашивается, тема интересная.

    • Ну тогда стоит подождать выхода Black Swift . Думаю она действительно будет востребована как среди любителей, так и профессионалов.
      А так, что касается Android, у меня была идея использовать смартфон в качестве измерителя частоты технологических операций которые сопровождаются звуком, используя микрофон. К примеру на ткацком станке, проброс уточной нити, зная частоту можно вычислить среднюю производительность, что очень удобно для технологов. Поэтому думаю что приложение может быть платным) Вот дело до реализации так не дошло, т.к. не занимался разработкой под Android.

  6. Срасибо за библиотеку. На F0 завелась. Возникло несколько вопросов:
    1- Только начинаю осваивать STM32 (слез с AVR :)), скажите, а 40кБ кода только для экрана 16х2 это нормально?
    2- Порядок пинов в примере драйвера не такой как у индикатора(хотя может у меня такой индикатор).
    В драйвере: RS-Е-RW
    У индикатора: RS-RW-E
    Я пару часов потерял пока не понял что не так пины забил ))
    3- К FreeRTOS не удалось пока прикрутить. Вываливается еще на стадии инициализации индикатора в Infinite_Loop после:

    /* We start in 8bit mode, try to set 4 bit mode */
        HD44780_RETURN_IF_ERROR(hd44780_write_bits(display, 0x03));
        delay_microseconds(4500); // wait min 4.1ms
    

    т.е. на задержке.

    код задержки такой:

    void delay_microseconds(uint16_t us)
    {
    	if (us < 1000)
    		vTaskDelay(1000 / portTICK_RATE_MS);
    	else
    	vTaskDelay((us / 1000) / portTICK_RATE_MS);
    }
    • При вызове с аргументом 4500 ваша функция сделает задержку в 4 мс, т.к. происходит целочисленное деление на 1000. Можно написать так:

      vTaskDelay((us / 1000) / portTICK_RATE_MS + (us % 1000 == 0 ? 0 : 1));
  7. Спасибо за библиотеку. Подключил к VL Discovery все заработало. Не могу разобраться как правильно вывести 2 строки.

    HD44780_Result hd44780_clear(&lcd);
    HD44780_Result hd44780_write_string(&lcd,buf);
    HD44780_Result hd44780_move_cursor(&lcd,0,1);
    HD44780_Result hd44780_write_string(&lcd,buf);
    

    Если так, то вторая строчка моргает сильно

    • А как включить поддержку float в sprintf. CooCox Coide. Не отображаются дробные числа.

Добавить комментарий

Arduino

Что такое Arduino?
Зачем мне Arduino?
Начало работы с Arduino
Для начинающих ардуинщиков
Радиодетали (точка входа для начинающих ардуинщиков)
Первые шаги с Arduino

Разделы

  1. Преимуществ нет, за исключением читабельности: тип bool обычно имеет размер 1 байт, как и uint8_t. Думаю, компилятор в обоих случаях…

  2. Добрый день! Я недавно начал изучать программирование под STM32 и ваши уроки просто бесценны! Хотел узнать зачем использовать переменную типа…

3D-печать AI Arduino Bluetooth CraftDuino DIY Google IDE iRobot Kinect LEGO OpenCV Open Source Python Raspberry Pi RoboCraft ROS swarm ИК автоматизация андроид балансировать бионика версия видео военный датчик дрон интерфейс камера кибервесна манипулятор машинное обучение наше нейронная сеть подводный пылесос работа распознавание робот робототехника светодиод сервомашинка собака управление ходить шаг за шагом шаговый двигатель шилд юмор

OpenCV
Робототехника
Будущее за бионическими роботами?
Нейронная сеть - введение