Barefoot - автономная роботележка

Доброго времени суток!
Решил выложить более подробное описание своего проекта Barefoot, который в настоящий момент является прототипом учебной платформы для курса по робототехнике в нашем университете.
Начинался проект несколько лет назад как заполнитель свободного времени, потом с ним была связана моя диссертационная работа, а сейчас на основе созданного прототипа планируется разработка учебных платформ. Свою разработку я не останавливаю, хотя в настоящее время и снизил темпы.
Под катом много текста, немного кода, и чуточку фотографий)



Описание проекта разобьем на несколько частей:
1) Механическая составляющая
2) Электрическая составляющая
3) Программная составляющая
4) Возможности, текущее состояние и дальнейшее развитие

Механическая составляющая.
Самая простая часть. Так как к началу работы над проектом опыта построения роботов у меня не было, и ресурсов, где можно было почерпнуть полезную информацию, я не знал, за основу я взял китайскую игрушку радиоуправляемого танка. От оригинальной игрушки остались гусеничная платформа и мотор-редуктор. Недостающие элементы корпуса (платформа, стойка для сенсора) были изготовлены (в конечном счете, после нескольких вариантов) из оргстекла (5 мм, другого не было под рукой).

Электрическая составляющая.
Здесь мы рассмотрим электрическую и электронную составляющие платформы. Далее будут приведены схемы, прошу не ругать громко – нарисованы в выдуманной мной нотации, на ней сплошными линиями отмечены силовые каналы, а пунктирными – каналы данных.
Вначале робот был укомплектован приобретенной Freeduino Through-Hole[1] на базе чипа Atmega 168P с дополнительным Motorshiled v2[2]. Была собрана следующая схема:



Для питания использовался Li-Po аккумулятор Turnigy 1000mAh 2S 20C (7.4V).
После того, как робот начал шевелиться, захотелось, чтобы он как-то воспринимал окружающий мир. Очень хотелось оборудовать его лазерным дальномером, но их цена стала ощутимым препятствием. Поэтому в итоге был выбран инфракрасный дальномер SHARP GP2Y0A02YK0F[3]. Для поворотного механизма был использован сервопривод Power HD Sub-Micro Servo HD-1440A[4].
Всё это было соединено по следующей схеме:



После этого созрела задача, которую можно было бы опробовать на платформе – я занялся построением карты (SLAM). Мне было интересно получить в итоге платформу, которая могла бы самостоятельно (без внешних вычислительных узлов) решать подобную задачу. Естественно, что на Arduino это было нереально. Поэтому встал вопрос о «мозге». В качестве такового была использована отладочная плата SK-iMX233[5]. Для её питания потребовался регулятор напряжения. В итоге была получена следующая схема:



Для взаимодействия с Arduino была выбрана шина I2C.
На данном этапе было принято решение о переписывании уже реализованного для Arduino кода. В качестве центрального управляющего узла использовалась отладочная плата, а на плечи Arduino легло управление имеющейся периферией. Arduino работала в пассивном режиме – т.е. при старте она ожидала команды от отладочной платы (далее буду называть «мозгом»). Конфигурация платформы была зашита прямо в коде. После реализации обёрток над всеми устройствами встала проблема – полученный бинарник не влезал в имеющиеся на Arduino 14 КБ. После нескольких дней различных оптимизаций я приобрел ATmega 328P.
Далее я продолжил обвешивать платформу различными сенсорами. Были добавлены 3-х осевой акселерометр MMA7361LC[6] и 2-х осевой гироскоп LPY510AL[7]. Если быть совсем честным, то эти два устройства были успешно сожжены статическим разрядом, но на замену им были куплены точно такие же.
Для получения данных с этих устройств аналоговых каналов Arduino не хватало (1 канал был занят дальномером, на два новых устройства требовалось еще 5 каналов), да и АЦП Ardiuno оставляет желать лучшего. Поэтому была приобретена микросхема AD7998[8] — 8-ми канальный 12-ти разрядный АЦП с шиной I2C. Схема платформы пришла к виду:



На данном этапе сформировалась архитектура программной части, и она практически не измениялась до нынешнего момента.
В дополнение к этим датчикам был добавлен датчик температуры (для калибровки акселерометра и гироскопа, с учетом годового перепада температур влияние оказалось достаточно большим). Таким датчиком стал DS18B20[9], работающий по шине 1-Wire, реализация которой есть для Arduino:



Примерно в это же время я приобрел Arduino Mega 2560[10], которая заменила имеющуюся.
Так как к этому моменту робот уже достаточно резво бегал под управлением своего «мозга», который очень плохо переносил некорректное завершение работы, а внешних индикаторов заряда батарей не было, я решил сделать плату, которая бы отображала напряжение бортовой сети. Для этих целей я использовал столбчатый индикатор DC3G4Y3EWA[11] и драйвер LM3914[12].
Также для простейшей индикации состояния был сооружен цифровой дисплей (дисплей E20561[13], два драйвера дисплея 4055B[14]).
На данный момент платформа имеет следующую схему:



В настоящее время я работаю над беспроводным модулем для взаимодействия с роботом. В качестве приёмо-передающего устройство был выбран RFM23.

Программная составляющая.
Работа над программной частью тоже не окончена, сейчас я работаю над динамической загрузкой устройств, чтобы не пересобрать каждый раз весь модуль.
Однако общая архитектура от этого не зависит, поэтому её мы и рассмотрим.
Как уже было сказано, центральным управляющим узлом является «мозг». При старте приложения инициализируется объект класса Platform, который загружает конфигурацию. Конфигурация содержит в себе информацию о том, какие устройства присутствуют в системе и к каким каналам они подключены. Эта конфигурация обрабатывается платформой, которая, в свою очередь, строит конфигурацию для Arduino и отправляет её на плату. После завершения этапа конфигурирования платформа готова к приему команд.
На верхнем уровне платформа предоставляет доступ, типизированный к устройствам на основе типа (GetMotor, GetDistanceSensor,…), и не типизированный по ID.
Все устройства делятся на два типа – сенсоры и актуаторы. Для всех сенсоров доступен метод GetValue, с помощью которого можно получить текущие показания сенсора.
Кроме того, сенсоры делятся на аналоговые и цифровые, для аналоговых сенсоров определяется функция преобразования значения, полученного от АЦП.
Диаграмма полученных классов выглядит следующим образом:



Класс Device
Базовый класс для всех устройств.

class Device
{
     protected:
        short int _id;		//уникальный идентификатор устройства
        Dispatcher* _dispatcher;	//объект диспетчера соединений

        Device(short int id, Dispatcher* dispatcher);	//конструктор

     public:
        short GetID();		//получить уникальный идентификатор объекта
};


Класс DigitalDisplay
Класс для управления цифровым дисплеем. Цифровой дисплей позволяет отображать целые числа в диапазоне 0 – 99.

class DigitalDisplay: public Device
{
     public:
          DigitalDisplay(int id, Dispatcher* dispatcher); //конструктор

          void SetValue(char value);	//установить отображаемое значение
};


Класс DigitalThermometer
Класс для получения показаний цифрового термометра (подключен по шине OneWire к Arduino).

class DigitalThermometer: public Sensor
{
     public:
          DigitalThermometer(int id, Dispatcher* dispatcher); //конструктор

          float GetValue();		//получить показания сенсора
};


Класс DigitalPin
Класс для управления состоянием отдельного цифрового выхода Arduino.

class DigitalPin : public Device
{
     public:
          DigitalPin(short int id, Dispatcher* dispatcher); //конструктор

          virtual void SetState(EDigitalPinMode mode); //установить состояние выхода (Hi, Lo)
};


Класс Led
Класс для управления подключенным к Arduino светодиодом. Работает аналогично DigitalPin. Был введен для большей наглядности кода.

class Led : public Device
{
     public:
          Led(short int id, Dispatcher* dispatcher); //конструктор

          virtual void SwitchOn();	//включить светодиод
          virtual void SwitchOff();	//выключить светодиод
};


Класс Servo
Класс для управления сервоприводом. Чтобы не «свернуть» голову серву, были введены параметры, ограничивающие допустимые углы поворота.

class Servo : public Device
{
     private:
          bool _hasMinAngle;	//флаг наличия ограничений на минимальное значение угла поврота
          bool _hasMaxAngle;	//флаг наличия ограничений на максимальное значение угла поворота
          float _minAngle;		//минимальный угол поворота
          float _maxAngle;		//максимальынй угол поворота
                    
     public:
          Servo(short int id, bool hasMinAngle, float minAngle, bool hasMaxAngle, float maxAngle, Dispatcher* dispatcher); //конструктор

          virtual void SetAngle(float angle);   //установить угол поворота серво-привода                 
};


Класс Motor
Класс для управления двигателем постоянного тока.

class Motor : public Device
{                    
     public:
          Motor(short int id, Dispatcher* dispatcher); //конструктор

          virtual void SetSpeed(float speed); //установить скорость вращения двигателя (пока что в «попугаях»)
          virtual void Stop();		     //остановить двигатель
};


Класс Sensor
Базовый класс для всех сенсоров.

class Sensor: public Device
{
     private:
          int _port;		//идентификатор порта, к которому подключен сенсор 

     public:
          Sensor(short int id, Dispatcher* dispatcher); //конструктор
          virtual ~Sensor();

          ABSTRACT(float GetValue()); //получить значение сенсора 
};


Класс AnalogSensor
Базовый класс для аналоговых сенсоров, подключенных к АЦП.

class AnalogSensor: public Sensor
{
     public:
          AnalogSensor(short int id, int port, Dispatcher* dispatcher); //конструктор
          virtual ~AnalogSensor();

          float GetValue(); //получить значение сенсора

     protected:
          ABSTRACT(float Convert(int value)); //функция преобразования данных, полученных с АЦП в показания сенсора
};


Класс Gyro
Класс для работы с показаниями гироскопа (для одной оси).

class Gyro : public AnalogSensor
{
     private:
          EGyroMode::GyroMode _mode; //чувствительность гироскопа

     public:
          Gyro(EGyroMode::GyroMode mode, short int id, int port, Dispatcher* dispatcher); // конструктор

     protected:
          virtual float Convert(int value); //функция преобразования данных, полученных с АЦП в показания сенсора
};


Класс Accelerometer
Класс для работы с показаниями акселерометра (для одной оси).

class Accelerometer : public AnalogSensor
{
     private:
          EAccelerometerMode::AccelerometerMode _mode; //чувствительность акселерометра

     public:
          Accelerometer(EAccelerometerMode::AccelerometerMode mode, short int id, int port, Dispatcher* dispatcher); //конструктор

     protected:
          virtual float Convert(int value); // функция преобразования данных, полученных с АЦП в показания сенсора
};


Класс DistanceSensor
Класс для работы с дальномером.

class DistanceSensor : public AnalogSensor
{
     public:
          DistanceSensor(short int id, int port, Dispatcher* dispatcher); //конструктор

     protected:
          virtual float Convert(int value); // функция преобразования данных, полученных с АЦП в показания сенсора

};


Вся логика взаимодействия с устройствами вынесена в команды. Т.е. каждое действие для устройства (получить данные, установить параметр, отправить конфигурация и т.п.) представляет собой отдельную команду, которая выполняется диспетчером соединений.
Для работы с конфигурацией используется библиотека libconig[15].
Процесс инициализации представлен на следующей диаграмме:



При запуске программы управления происходит чтение файла конфигурации. Пример файла конфигурации приведен ниже.


platform:
{
  motors: (
		{
			id = 1; 
			pin = 1; 
			turnRate = 1;
		},
		{
			id = 2; 
			pin = 2; 
			turnRate = 1;
		}
	);
  pins: (
		{
			id = 3;
			pin = 90;
		},
		{
			id = 4;
			pin = 91;
		}
	);
  servos: (
		{
			id = 5; 
			pin = 9;
			hasMinAngle = true;
			minAngle = 0.78;
			hasMaxAngle = true;
			maxAngle = 2.35;
		}
	);
  leds: (
		{
			id = 6; 
			pin = 9;
		}
	);
  display: {
	id = 7; 
	pins: [46,50,52,48,47,51,53,49];
	};
  sensors: (
		{
			id = 8;
			type = "accelerometer";
			mode = "1.5G";
			pin = 2;
		},
		{
			id = 9;
			type = "accelerometer";
			mode = "1.5G";
			pin = 3;
		},
		{
			id = 10;
			type = "gyro";
			mode = "1X";
			pin = 4;
		},
		{
			id = 11;
			type = "digitaltermometer";
			precision = 10;
			pin = 10;
		},
		{
			id = 12;
			type = "sharp";
			pin = 9;
		}
	);  
};

connection: {
	arduinoAddress = 42;
	adcAddress = 34;
};


После чтения конфигурации для каждого соединения (всего их на данный момент два) происходит его инициализация, после чего через канал связи с Arduino отправляется конфигурация для микроконтроллера. Логика по подготовке пакета для отправки конфигурации (назовем данный процесс сериализацией) вынесен в объект SendConfigurationCommand.
На стороне микроконтроллера при его включении инициализируется процесс получения данных по каналу I2C. Каждый пакет имеет уникальный идентификатор типа команды. Первая команда, которую ожидает микроконтроллер является команда конфигурирования. Для каждого устройства в конфигурации микроконтроллер инициализирует соответствующие объекты и помещает их во внутреннюю коллекцию устройств.



При выполнении некоторого действия с устройством (сенсор или актуатор) выполняется следующая последовательность:



Платформа инициируется выполнение метода у конкретного устройства, который в свою очередь создает объект соответствующей команды, результат которой с помощью метода диспетчера ExecuteCommand отправляется через соответствующее соединение, после чего ожидает окончания отправки. В случае если действие подразумевает некоторый результат (для сенсоров), то после завершения работы команды отправки команды инициализируется команда получения данных, которая в себе содержит логику по десериализации полученных данных.
На стороне микроконтроллера выполнение команды выглядит следующим образом:



Диспетчер получает пакет от соединения и проверяет его тип. После чего отправляет полученную команду конкретному устройству, получая его результат.
Из особенностей реализации стоит отметить, что при разработке я столкнулся с проблемой размера I2C пакета – на определенном этапе пакет конфигурации превысил размер буфера на Arduino, поэтому пришлось вводить специальный вид пакета – Continuous. При получении такого пакета диспетчер на стороне микроконтроллера не переходит к парсингу команды, а просто складывает информацию во внутренний буфер.

Весь код выложен в репозиторий: https://bitbucket.org/anatoly_kryzhanovsky/barefootfull

Внешний вид
На данный момент платформа имеет следующий вид:




Ну а это она в разобранном виде:










Update
Откопал небольшое видео движения


Надеюсь те, кто осилил этот топик не пожалели потраченного на чтение времени!

Ссылки:
1 — freeduino.ru/arduino/freeduino.html
2 — freeduino.ru/arduino/mshield.html
3 — robocraft.ru/blog/electronics/783.html
4 — www.pololu.com/product/1040
5 — starterkit.ru/html/index.php?name=shop&op=view&id=41&word=SK-iMX233
6 — www.pololu.com/product/1246
7 — www.pololu.com/product/1267
8 — www.analog.com/ru/analog-to-digital-converters/ad-converters/ad7998/products/product.html
9 — robocraft.ru/files/datasheet/DS18B20.pdf
10 — robocraft.ru/shop/index.php?route=product/product&path=47&product_id=147
11 — ronggan.en.alibaba.com/product/1044855360-218983854/KINGBRIGHT_LED_DC3G4Y3EWA_AGENT.html
12 — www.ti.com.cn/general/cn/docs/lit/getliterature.tsp?genericPartNumber=lm3914&fileType=pdf
13 — vdg-el.com/catalog/product_info.php?products_id=52006
14 — www.datasheetarchive.com/4055B-datasheet.html
15 — www.hyperrealm.com/libconfig/

Комментарии (0)

RSS свернуть / развернуть

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.