Начало
Летом появилась мысль сделать радиоуправляемую машинку, но не просто нечто похожее на творение китайской инженерной мысли, которое продается на каждом шагу, а машинку, управлять которой можно было бы с компьютера или телефона. Понято, что машинка, которой можно управлять через Wi-Fi в чистом виде, совершенно не интересна. А вот если бы у нее была бы камера? А если еще и управление через 3G/EDGE/GPRS? Вот это – другое дело! Значит, управляющее устройство должно иметь USB и Wi-Fi (ну или только USB, можно купить USB Wi-Fi адаптер). Теперь нужно придумать, как управлять двигателями. Изначально я хотел сделать это с помощью COM-порта и регистра сдвига (74HC595), но спалив 5 таких микросхем, от такого способа отказался. Позже мой взгляд пал на Arduino, а именно на Arduino Duemilanove. Эта плата имеет 14 цифровых I/O портов, из них шесть – ШИМ (можно будет управлять напряжением на двигателе и повесить сервы для камеры), два можно использовать как Tx и Rx последовательного порта.
Роутер
Обнаружив в своем городе роутер D-Link DIR-320, у которого есть USB порт, сразу же его купил. Придя домой, узнал, что у этого роутера есть невыведенный UART-порт. Таким образом, у нас появляется канал связи между роутером и ардуиной.
Для роутера я выбрал прошивку OpenWrt. Можно скачать готовую прошивку с OpenWrt для DIR-320 это здесь. Уже не помню почему, но я решил собрать прошивку сам (подробно описано тут). Для этого понадобится Linux (я собирал на Ubuntu 11.10). Для начала, скачаем исходники прошивки и соберем все, что нужно:
svn co svn://svn.openwrt.org/openwrt/branches/backfire dir320
cd dir320
./scripts/feeds update -a && ./scripts/feeds install –a
make prereq && make tools/install && make toolchain/install
Конфигурирование прошивки
make menuconfig
Выбираем следующие пакеты:
- Target System ---> <*> Broadcom BCM947xx/953xx – ядро 2.6
- Image configuration ---> <*> LAN IP Address ---> <IP-адрес> – [не обязательно] Можно выбрать IP-адрес, который будет у роутера после загрузки ядра и всех модулей
- Kernel modules ---> <*> Filesystems ---> <*> kmod-fs-ext3 — Об этом позже
- Utilities ---> Filesystem ---> <*> e2fsprogs – И об этом
- Utilities ---> disc ---> <*> block-extroot – И об этом тоже
- Kernel modules ---> <*> USB Support ---> <*> kmod-usb-core – поддержка USB
- Kernel modules ---> <*> USB Support ---> <*> kmod-usb-ohci – для USB-хаба. Зачем он? Об этом тоже позже
- Kernel modules ---> <*> USB Support ---> <*> kmod-usb-storage – поддержка USB-флешек
- Kernel modules ---> <*> USB Support ---> <*> kmod-usb2 – USB 2.0
- Administration --> webif ---> <*> webif-applications – админка
- Kernel modules ---> <*> Video Support ---> <*> kmod-usb-video-core – поддержка USB-video
- Kernel modules ---> <*> Video Support ---> <*> kmod-usb-video-uvc – поддержка UVC-веб камер
Последний пункт выбирайте сами, у меня была UVC веб камера.
Так зачем же мы выбрали те пакеты, назначение которых я не объяснил? Проблема в том, что объём флэш-памяти установленной в роутере — 4МБ, что может помешать дальнейшей нашей работе. Мы же перенесем rootfs на флешку, и роутер будет грузиться с нее.
Кстати, про флэш-память: нужно не забыть следующее:
make kernel_menuconfig
- Device Drivers ---> <*> Memory Technology Device (MTD) support ---> RAM/ROM/Flash chip drivers ---> [*] Flash chip driver advanced configuration options --> [*] Specific CFI Flash geometry selection --> [*] Support 8-bit buswidth
- Device Drivers ---> <*> Memory Technology Device (MTD) support ---> RAM/ROM/Flash chip drivers ---> [*] Flash chip driver advanced configuration options --> [*] Specific CFI Flash geometry selection --> [*] Support 16-bit buswidth
И еще в Kernel Hacking’е нужно исправить
console=/dev/ttyS0
на console=/dev/null
, чтоб роутер не использовал этот порт как отладочный.Компилируем и прошиваем
Компиляция прошивки:
make V=99 -j2
Теперь нужно ее прошить:
Для bash’а:
#!/bin/bash
echo "=================================================================="
echo "This script will upload dd-wrt firmware (firmware.bin)"
echo "in the current directory to 192.168.0.1 "
echo "during the router's bootup. "
echo ""
echo "* Set your ethernet card's settings to: "
echo " IP: 192.168.0.10 "
echo " Mask: 255.255.255.0 "
echo " Gateway: 192.168.0.1 "
echo "* Unplug the router's power cable. "
echo ""
echo "Press Ctrl+C to abort or any other key to continue... "
read
echo ""
echo "* Re-plug the router's power cable. "
echo ""
echo "=================================================================="
echo "Waiting for the router... Press Ctrl+C to abort. "
echo ""
try(){
ping -c 1 -w 1 192.168.0.1
}
try
while [ "$?" != "0" ] ;
do
try
done
echo "*** Start Flashing **** "
atftp --no-source-port-checking -p -l firmware.bin 192.168.0.1
echo "Firmware successfully loaded!"
Для винды:
@echo off
echo ==================================================================
echo This batch file will upload dd-wrt firmware in the current directory to
echo 192.168.0.1 during the router's bootup.
echo.
echo * Set your ethernet card's settings to:
echo IP: 192.168.0.2
echo Mask: 255.255.255.0
echo Gateway: 192.168.0.1
echo * Unplug the router's power cable.
echo.
echo Press Ctrl+C to abort or any other key to continue...
pause > nul
echo.
echo * Re-plug the router's power cable.
echo.
echo ==================================================================
echo Waiting for the router... Press Ctrl+C to abort.
echo.
set FIND=%WINDIR%\command\find.exe
if exist %FIND% goto LPING
set FIND=%WINDIR%\system32\find.exe
if exist %FIND% goto LPING
set FIND=find
:LPING
ping -n 1 -w 50 192.168.0.1 | %FIND% "TTL="
if errorlevel 1 goto LPING
echo *** Start Flashing ****
tftp -i 192.168.0.1 put firmware.bin
if errorlevel 1 goto LPING
set FIND=
echo.
echo ==================================================================
echo * WAIT for about 2 minutes while the firmware is being flashed.
echo * Reset your ethernet card's settings back to DHCP.
echo * The default router address will be at 192.168.1.1
echo.
Pause
Настройка загрузки с флешки
После первого включения заходим на веб-интерфейс роутера и изменяем пароль. Теперь подключаемся к нему через SSH. Нужно настроить загрузку с флешки, для этого сначала нужно ее разметить. У меня было два раздела: первый – ext3-раздел для rootfs, второй – swap. Открываем /etc/config/fstab в vim’е и пишем то, что соответствует нашей фелшке. У меня так:
config global automount
option from_fstab 1
option anon_mount 1
config global autoswap
option from_fstab 1
option anon_swap 0
config mount
option target /
option device /dev/sda1
option fstype ext3
option options rw,sync
option enabled 1
option enabled_fsck 1
option is_rootfs 1
config swap
option device /dev/sda2
option enabled 1
Сохраняем, перезагружаемся (
reboot
).Демон
Управлять двигателями будет ардуина, поэтому напишем демон, который будет перенаправлять всё, что пришло на TCP порт 5554 в /dev/ttyS0.
Мой скомпилированный вариант демона искать в архиве (card)
Компилируем с помощью gcc, который был собран в процессе подготовки к сборке прошивки:
<…>/dir320/staging_dir/toolchain-mipsel_gcc-4.3.3+cs_uClibc-0.9.30.1/usr/bin/mipsel-openwrt-linux-uclibc-gcc-4.3.3 <имя файла с исходниками> -o <имя выходного файла>
Небольшое отступление об удобстве организации работы с роутером
- После каждого включения мне приходилось писать
opkg update
, поэтому я его добавил в /etc/rc.local - Довольно удобно использовать FTP-сервер. Я поставил pure-ftpd. Для этого пишем:
opkg install pure-ftpd
Добавим его в /etc/rc.local:
pure-ftpd -4 –B –M –l unix –U 000:000
- Удобно будет сменить веб-интерфейс на luci, для этого пишем:
opkg remove webif* opkg install luci
Демон [продолжение]
Заливаем на роутер наш демон, добавляем его в автозагрузку.
Теперь ставим mjpg-streamer:
opkg install mjpg-streamer
Пишем в /etc/config/mjpg-stramer следующие:
config mjpg-streamer
option device “/dev/video0”
option resolution “640x480”
option fps “24”
option port “8080”
option enabled “true”
Пробуем подключить камеру. Если все нормально, то можно будет увидеть изображение тут:
http://<IP-адрес_роутера>/?action=stream.
Arduino и соединение
Для начала определимся со схемой подключения двигателей. Так как я брал корпус от уже готовой машинки, то мне с двигателями повезло – они уже там были. Передний отвечал за повороты (влево, вправо, прямо), а задний за движение (мне пришлось его поменять на двигатель кнопки блокировки дверей какого-то ВАЗа). Управлять нагрузками можно ардуиной с помощью полевых транзисторов (95N2LH5, но я использовал IRF 630, потому что и эти ели нашел в своём городе). Подключение такое: земля транзистора – к управляющему пину ардуины, source – к земле ардуины и минусу питания нагрузки, drain – к минусу нагрузки, плюс питания к плюсу нагрузки. Но таким образом мы сможем ездить только вперед и поворачивать только в одну сторону. Для того чтобы справиться с проблемой, к нам на помощь спешит реле с двумя группами контактов. У меня один двигатель (передний) питался 6 вольтами, а другой 12. При этом использовалось два 6 вольтовых аккумулятора (один из них — свинцово-кислотный от бесперебойника), учитывая, что минус роутера позже придется соединить с землей ардуины, то получить 6 вольт для роутера не получается (проверяйте сколько вольт подаёте на роутер — мне пришлось покупать еще один после того, как я подал на него 12 вольт). Поэтому пришлось использовать еще одну релюшку для подачи/неподачи питания на передний двигатель.
Схему рисовал давно. Теперь там все транзисторы полевые и нет резисторов.
Теперь о самом коде. У меня все довольно просто – есть 4 команды, у которых есть свой параметр размером 1 байт:
- m – Отвечает за напряжение, а, следовательно, и за скорость, на двигателе значение от 0 до 255
- r – Отвечает за повороты. “1” – поворачивать, “0” – не поворачивать
- n – “1” – ехать назад, “0” – ехать вперед
- e – “1” – поворачивать в другую сторону
Вот мой код программы для ардуины:
int inByte, val;
void setup() {
Serial.begin(9600);
pinMode(2, OUTPUT);
pinMode(4, OUTPUT);
pinMode(7, OUTPUT);
}
void loop() {
if (Serial.available() > 0)
{
inByte = Serial.read();
if ((inByte=='n')||(inByte=='e')){
while (Serial.available()==0) {}
val=Serial.read();
if (inByte=='n'){
if (val=='1'){
digitalWrite(2, HIGH);
Serial.print("Writing to 2 pin\n\r");
}
if (val=='0'){
digitalWrite(2, 0);
Serial.print("Writing to 2 pin\n\r");
}
}
if (inByte=='e'){
if (val=='1'){
digitalWrite(4, HIGH);
Serial.print("Writing to 4 pin\n\r");
}
if (val=='0'){
digitalWrite(4, LOW);
Serial.print("Writing to 4 pin\n\r");
}
}
}
if ((inByte=='m')||(inByte=='r')){
while (Serial.available()==0) {}
val=Serial.read();
if (inByte=='m'){
if (val!='0')
analogWrite(3, val);
else
analogWrite(3, 0);
Serial.print("Writing to 3 pin\n\r");
}
if (inByte=='r'){
if (val=='1'){
digitalWrite(7, HIGH);
Serial.print("Writing to 7 pin\n\r");
}
if (val=='0'){
digitalWrite(7, LOW);
Serial.print("Writing to 7 pin\n\r");
}
}
}
}
}
Как видно, задний двигатель у меня подключен к 3 пину, передний – к 7, реле заднего – к 2 пину, переднего – к 4. Так как 3 – это ШИМ-пин, то используя analogWrite(3, val);, где val от 0 до 255, мы можем управлять напряжением на двигателе.
Разбираем наш маршрутизатор. Видим UART порт. Соединяем его с ардуиной.
Теперь смотрим, как это все работает. Подключаемся телнетом к нашему порту и проверяем:
- n1 – щелкает реле
- m<пробел> — колёса начинают немного вращаться
- n0 – колеса вращаются в другую сторону
- m0 – колеса перестают вращаться
- r1 – поворачивают передние колеса
- e1 – колеса поворачивают в другую сторону
- r0 – колеса становятся прямо
- e0 – щелкает реле
Для отладки работы с ком портом на роутере можно использовать minicom (
opkg install minicom
).Программная часть
В архиве моя программка для управления машинкой (rotate и power из архива нужно скопировать в /bin/ на роутере, card – мой демон). Работает только с джойстиком. На вкладке планирование вы можете написать bash скрипт (не забудьте
opkg install bash
на роутере) для его выполнения с помощью демона cron. Так как этот демон нужно после изменения его настроек перезапускать, моя программа запускает скрипт по адресу http://<IP-адрес>/cron-restart. Поэтому нужно его создать (/www/cgi-bin/cron-restart) и не забыть сделать исполняемым. Код:#!/bin/bash
/etc/init.d/cron stop; /etc/init.d/cron start
Заключение
Вроде бэ ничего не забыл. А, ну, конечно же! Вот мой шедевр:
К такому девайсу можно присоединить Bluetooth (не пробовал, но драйвера есть), 3G-модем (интернет получить у меня получилось, но похоже провайдер не выдаёт каждому клиенту собственный внешний ip-адрес, поэтому придется использовать что-то типа back-connect’а или vpn), gps приёмник (проблем возникнуть не должно – ведь он должен определиться как последовательный порт).
Примечания
Если вдруг роутер перезагружается, то стоит убрать от него подальше все провода или все их экранировать. Экспериментальным путем я понял, что роутер может перезагрузиться от наводок, поэтому пришлось обмотать хаб несколькими слоями изоляции и алюминиевой фольги.
И вот еще. Вместо роутера можно использовать Raspberry Pi, а вместо транзисторов и реле — Arduino Motor Shield.
Архив, о котором не раз я упоминал (зеркало).