Готовясь на работе к ежегодному форуму посвященному IT, возникла идея создать простой манипулятор управляемый беспроводным геймпадом для демонстрации возможностей микроконтроллеров и одноплатных компьютеров. Под рукой был контроллер ТРИК, несколько сервомоторов, железный конструктор и месяц до начала форума.


«Все идет по плану», но не в этом случае.


Этап 1. Подготовка


ТРИК на борту с Linux был перебором для такого манипулятора, но «дело в банальном удобстве использования и обслуживания» (цитата ClusterM про Linux в умном домофоне).


Прочитав спецификацию, было обнаружено, что в нем есть Bluetooth. Если вы работали с этим контроллером, то знаете, что передача программ осуществляется по Wi-Fi и других удобных способов общения с ним нет. В меню нет упоминания о наличии Bluetooth. Но как так?


Вооружившись SSH, отверткой и любопытством я начал искать Bluetooth. В системе присутствовали утилиты hcitool, hciconfig и демон bluetoothd. Все они говорили о том, что его нет.


root@trik-7dda93:~# hcitool dev
Devices:

root@trik-7dda93:~# hciconfig hci0
Can't get device info: No such device

root@trik-7dda93:~# bluetoothd -n &
[1] 5449
root@trik-7dda93:~# bluetoothd[5449]: Bluetooth daemon 4.101
bluetoothd[5449]: Starting SDP server
bluetoothd[5449]: Bluetooth Management interface initialized

Обзвонив друзей в поисках внешнего USB модуля, я продолжил искать.


Разобрав контроллер, был найден модуль Jorjin WG7311-0A. В спецификации указано, что, действительно, есть Wi-Fi, Bluetooth и даже FM-радио. Интерфейс для общения с Bluetooth – UART, а включается он через контакт BT_EN.



Прочитав, как Bluetooth модуль подключается по UART через hcitool я испытал удачу и – ничего. Два из трех свободных UART портов молчали.


Но у нас есть контакт BT_EN! Возможно, что модуль просто выключен и не отвечает на запросы. Изучив устройство ядра Linux для ARM устройств, был найден файл, где прописываются все контакты, используемые SoC. Открыв arch/arm/mach-davinci/board-da850-trik.c в исходном коде ядра, и вправду был найден GPIO контакт для Bluetooth. Победа! – подумал я.


static const short da850_trik_bluetooth_pins[] __initconst = {
    DA850_GPIO6_11, /*BT_EN_33 */
    DA850_GPIO6_10,  /*BT_WU_33*/
    -1
};

Этап 2. Наступление


Для включения контакта через GPIO, нужно найти его сквозной порядковый номер. Находим следующую строчку в коде ядра с запросом на инициализацию контакта BT_EN_33 в arch/arm/mach-davinci/board-da850-trik.c:


ret = gpio_request_one(GPIO_TO_PIN(6, 11), GPIOF_OUT_INIT_LOW, "BT_EN_33");

В ней используется макрос GPIO_TO_PIN. Смотрим описание макроса в arch/arm/mach-davinci/include/mach/gpio-davinci.h:


/* Convert GPIO signal to GPIO pin number */
#define GPIO_TO_PIN(bank, gpio) (16 * (bank) + (gpio))

При помощи его и можно узнать сквозной номер контакта. Получаем, что 16 * 6 + 11 = 107. Теперь перейдем к включению контакта.


echo 1 >> /sys/devices/virtual/gpio/gpio107/value

0 или 1 в команде echo является состоянием контакта.


Запускаем команду на подключение и...


root@trik-7dda93:~# hciattach /dev/ttyS0 texas
Found a Texas Instruments' chip!
Firmware file : /lib/firmware/TIInit_7.6.15.bts
can't open firmware file: No such file or directory
Warning: cannot find BTS file: /lib/firmware/TIInit_7.6.15.bts
Device setup complete

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


root@trik-7dda93:~# hcitool dev
Devices:

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


root@trik-7dda93:~# hciattach /dev/ttyS0 texasalt
Texas module LMP version : 0x06
Texas module LMP sub-version : 0x1f0f
    internal version freeze: 15
    software version: 6
    chip: wl1271 (7)
Opening firmware file: /etc/firmware/wl1271.bin
Could not open firmware file /etc/firmware/wl1271.bin: No such file or directory (2).
Device setup complete

И вновь ничего. Давайте вернемся к первой ошибке и применим знания английского языка:


Warning: cannot find BTS file: /lib/firmware/TIInit_7.6.15.bts

Открываем папку /lib/firmware с прошивками и не находим нужного файла. После долгих поисков в интернете, находим на репозиторие TI нужный файл и скачиваем его. Другие версии этого же файла работать отказывались.


curl -k https://git.ti.com/wilink8-bt/ti-bt-firmware/blobs/raw/45897a170bc30afb841b1491642e774f0c89b584/TIInit_7.6.15.bts > TIInit_7.6.15.bts

cp TIInit_7.6.15.bts /lib/firmware/TIInit_7.6.15.bts

Перезагружаем контроллер и подключаемся вновь:


root@trik-7dda93:~# echo 1 >> /sys/devices/virtual/gpio/gpio107/value
root@trik-7dda93:~# hciattach /dev/ttyS0 texas
Found a Texas Instruments' chip!
Firmware file : /lib/firmware/TIInit_7.6.15.bts
Loaded BTS script version 1
Device setup complete

Ура! Прошивка загрузилась. Проверяем hciconfig:


root@trik-7dda93:~# hciconfig 
hci0:   Type: BR/EDR  Bus: UART
    BD Address: 78:**:**:**:**:B3  ACL MTU: 1021:4  SCO MTU: 180:4
    DOWN 
    RX bytes:509 acl:0 sco:0 events:21 errors:0
    TX bytes:388 acl:0 sco:0 commands:21 errors:0

Запускаем службу bluetoothd, сканирование устройств и обнаружение нашего модуля:


root@trik-7dda93:~# bluetoothd -n &
[1] 4689
bluetoothd[4689]: Bluetooth daemon 4.101
bluetoothd[4689]: Starting SDP server
bluetoothd[4689]: Bluetooth Management interface initialized
bluetoothd[4689]: Parsing /etc/bluetooth/serial.conf failed: No such file or directory
bluetoothd[4689]: Could not get the contents of DMI chassis type
bluetoothd[4689]: Adapter /org/bluez/4689/hci0 has been enabled

root@trik-7dda93:~# hciconfig hci0 piscan

Поиск на компьютере обнаруживает устройство:



Для включения Bluetooth можно сделать скрипт:


#!/bin/bash

case "$1" in
    start)
        echo 1 >> /sys/devices/virtual/gpio/gpio107/value
        bluetoothd -n &
        hciattach /dev/ttyS0 texas
        hciconfig hci0 piscan
        ;;
    stop)
        ;;
    restart)
        ;;
    status)
        ;;
    *)
        ;;

И добавить его в автозапуск:


cp init-bluetooth /etc/init.d/init-bluetooth

update-rc.d init-bluetooth enable 99

Перезапуск и отключение модуля ведут себя непредсказуемо, поэтому варианты stop и restart не имеют никаких команд.


Этап 3. Проверка связи


Самый простой способ проверки связи в обе стороны – служба COM-порта. При помощи нескольких команд включаем её:


root@trik-7dda93:~# sdptool add --channel=3 SP
Serial Port service registered
root@trik-7dda93:~# mknod -m 666 /dev/rfcomm0 c 216 0
root@trik-7dda93:~# rfcomm watch /dev/rfcomm0 3 /sbin/getty rfcomm0 115200 linux
Waiting for connection on channel 3

Подключаемся с телефона и видим приглашение на вход в систему:



Ни один из проверенных терминалов не дал ввести пустой пароль пользователя, поэтому пришлось отправить данные для входа при помощи перенаправления потоков в SSH-сессии.


Этап 4. ./configure && make


Следуя инструкциям по подключению геймпада в Linux мы сталкиваемся со следующими проблемами:


  • BlueZ в дистрибутиве устарел и не понимает команд от демона sixad, который устанавливает связь с геймпадом
  • Новая версия BlueZ из исходных кодов отказывается компилироваться из-за множества зависимостей
  • BlueZ из свежего Debian требует udev и systemd, которые отсутствуют в текущем дистрибутиве

Единственную зависимость, которую получилось удовлетворить – это модуль ядра uinput.


Для этого:


  • получаем конфигурацию текущего ядра на устройстве

cp /proc/config.gz config.gz

gunzip config.gz 

  • скачиваем код ядра
  • скачиваем и устанавливаем toolchain
  • копируем конфигурацию ядра в папку с кодом ядра
  • добавляем модуль uinput в конфигурацию

echo "CONFIG_INPUT_UINPUT=m" >> config

  • запускаем сборку, предварительно включив toolchain

source /opt/trik-sdk/environment-setup-arm926ejste-oe-linux-gnueabi

make

  • копируем модули ядра на карту памяти

make INSTALL_MOD_PATH=/mnt/trik-sd modules_install

  • собираем образ uBoot и копируем в /boot

make uImage

cp arch/arm/boot/uImage /mnt/trik-sd/boot/uImage-3.6.7

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


Этап 5. Варим кашу из топора


Приступаем к плану "тяп-ляп". Раз нет удобного способа поставить нужные программы на оригинальный дистрибутив, то поставим что-нибудь популярное. Процессор имеет архитектуру ARMv5TE, значит и дистрибутивы есть под неё.


Пробуем распаковать и запустить универсальный Arch Linux для ARM и при загрузке в консоли видим, что systemd требует ядро более новой версии, чего у нас нет. Попытки переноса ядра 4.16 не увенчались огромным успехом и на это было потрачено слишком много времени.


Переходим к другому варианту – Debian. Образ диска с установленной системой для ARM существует, но лучше поставить чистую систему с нужными для нас пакетами и настройками.


Установка в QEMU


Скачиваем установочный образ (ссылка на .iso) и устанавливаем QEMU.
Также нам нужны ядро и образ initrd для загрузки установки, которые можно скачать отсюда.


Создаем образ карты памяти с объемом настоящей карты памяти (в данном случае 4 Гб):


qemu-img create -f raw debian.img 4G

Запускаем установку:


qemu-system-arm -M versatilepb -kernel vmlinuz-3.2.0-4-versatile -initrd initrd.gz -hda debian.img -cdrom debian-7.11.0-armel-CD-1.iso

Если вы собираетесь сделать разметку диска нестандартной относительного оригинального дистрибутива, то оставьте корневой раздел первым, иначе придется менять параметры загрузки ядра в uBoot. Там прописан номер раздела на котором находится корневая файловая система.


Стандартная разметка содержит:


  1. Раздел EXT4 для корневой файловой системы размером ? 1,3 Гб
  2. Раздел FAT32 для хранения данных пользователя размером ? 500 Мб

Вывод fdisk для образа оригинального дистрибутива:


Disk: trik-distro.img   geometry: 893/64/63 [3604478 sectors]
Signature: 0xAA55
         Starting       Ending
 #: id  cyl  hd sec -  cyl  hd sec [     start -       size]
------------------------------------------------------------------------
 1: 83 1023   3  32 - 1023   3  32 [   1040382 -    2564096] Linux files*
 2: 0C   64   0   1 - 1023   3  32 [      8192 -    1032190] Win95 FAT32L
 3: 00    0   0   0 -    0   0   0 [         0 -          0] unused      
 4: 00    0   0   0 -    0   0   0 [         0 -          0] unused      

После настройки параметров уходим пить несколько чашек чая, т.к. эмулятор ненамного быстрее настоящего ARM процессора.


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


Запускаем систему:


qemu-system-arm -M versatilepb -kernel vmlinuz-3.2.0-4-versatile -initrd initrd.img-3.2.0-4-versatile -hda debian.img -append "root=/dev/sda1"

Настройка системы


После запуска входим в суперпользователя, проверяем связь с интернетом, обновляем репозитории и систему, ставим минимальный набор программ:


apt-get update
apt-get upgrade
apt-get install curl git mc htop joystick

Терминалы


Редактируем /etc/inittab, убираем лишние терминалы, включаем нужный для нас UART и добавляем автовход для нужного пользователя (используйте root только при отладке). Автовход пригодится, если вы планируете запускать оболочку для управления на контроллере.


1:2345:respawn:/sbin/getty 38400 tty1 --autologin root
#2:23:respawn:/sbin/getty 38400 tty2
#3:23:respawn:/sbin/getty 38400 tty3
#4:23:respawn:/sbin/getty 38400 tty4
#5:23:respawn:/sbin/getty 38400 tty5
#6:23:respawn:/sbin/getty 38400 tty6

uart:12345:respawn:/sbin/getty -L 115200 ttyS1

Bluetooth и Wi-Fi


Устанавливаем bluez-utils и wpasupplicant для доступа к Wi-Fi и Bluetooth.


apt-get install bluez-utils wpasupplicant

Отключаем интерфейс eth0 и настраиваем интерфейс wlan1 в /etc/network/interfaces:


# /etc/network/interfaces -- configuration file for ifup(8), ifdown(8)

# The loopback interface
auto lo
iface lo inet loopback

# Wireless interfaces
auto wlan1
iface wlan1 inet dhcp
    wireless_mode managed
    wireless_essid any
    wpa-driver wext
    wpa-conf /etc/wpa_supplicant.conf

Добавляем заранее сеть в /etc/wpa_supplicant.conf, т.к. делать это на самом контроллере не так удобно:


wpa_passphrase ssid password >> /etc/wpa_supplicant.conf

Если у вас нет доступа к Wi-Fi, вы можете использовать UART для дальнейшей настройки, но учтите, что по умолчанию ядро выводит в данный терминал все ошибки. Поэтому во время работы вас может прервать внезапное сообщение от ядра или службы.


Добавляем скрипт на включение Bluetooth. В этот раз, модифицируем /etc/init.d/bluetooth:


Строка 139:

case $1 in
  start)
    echo 1 >> /sys/devices/virtual/gpio/gpio107/value

Строка 168:

    hciattach /dev/ttyS0 texas

    log_end_msg 0
  ;;

Таким образом, все службы, которые требуют службу Bluetooth, будут запускать необходимые команды для инициализации.


Взмах влево, взмах вправо


Убираем ненужные программы и службы которые можно посмотреть при помощи htop, ведь они занимают драгоценное место в ОЗУ:



В данном случае, служба ConsoleKit имеет очень много процессов. Переместим файл этой службы в домашнюю папку суперпользователя на случай восстановления:


mv /usr/share/dbus-1/system-services/org.freedesktop.ConsoleKit.service /root/

До отключения службы потребление ОЗУ было 19 Мб, а после – 16 Мб.


Разделы системы


Хоть uBoot и передает ядру устройство, на котором расположен корневой раздел, стоит прописать его в /etc/fstab для надежности. Изменяем первую строчку, отвечающую за корневой раздел:


/dev/mmcblk0p1            /                    auto       defaults              1  1
proc                 /proc                proc       defaults              0  0
devpts               /dev/pts             devpts     mode=0620,gid=5       0  0
usbdevfs             /proc/bus/usb        usbdevfs   noauto                0  0
tmpfs                /run                 tmpfs      mode=0755,nodev,nosuid,strictatime 0  0
tmpfs                /var/volatile        tmpfs      defaults              0  0

Если вы сделали корневой раздел не первым, не забудьте указать нужный номер раздела.


Если вы оставили второй раздел FAT для пользовательских данных, то вам необходимо создать папку для монтирования раздела в неё


mkdir /usr/share/trik

и прописать раздел в /etc/fstab:


/dev/mmcblk0p2 /usr/share/trik vfat defaults 0  0

Этап 6. Пробуем нашу кашу


Настроив образ системы, необходимо примонтировать его для установки модулей ядра и самого ядра:


# Смотрим, откуда начинается раздел системы (start)
fdisk -l debian.img

mount -o loop,offset=NNNN debian.img /mnt/debian

где, NNNN = размер сектора * начало раздела. Размер сектора по умолчанию равен 512 байтам.


Монтируем также и оригинальный дистрибутив:


fdisk -l trik-distro.img

mount -o loop,offset=NNNN trik-distro.img /mnt/trik-clean

Удаляем ядро для QEMU и его модули, т.к. они не предназначены для нашей платформы. Копируем новое ядро и модули, так же, как и на оригинальном дистрибутиве.


rm -rf /mnt/debian/boot/
rm -rf /mnt/debian/lib/modules/3.2.0-4-versatile
rm -rf /mnt/debian/lib/modules/3.2.0-5-versatile

mkdir /mnt/debian/boot/

cp arch/arm/boot/uImage /mnt/debian/boot/
make INSTALL_MOD_PATH=/mnt/debian modules_install

Нам понадобятся прошивки для Wi-Fi модуля, которые есть в оригинальном дистрибутиве в папке /lib/firmware и прошивка Bluetooth, которую мы нашли ранее.


cp /mnt/trik-clean/lib/firmware/* /mnt/debian/lib/firmware/

cp TIInit_7.6.15.bts /mnt/debian/lib/firmware/

Отсоединяем образы дисков:


umount /mnt/trik-clean

umount /mnt/debian

И запускаем копирование образа на карту памяти с помощью dd:


# Смотрим номер устройства (карты памяти)
lsblk

dd if=debian.img of=/dev/sdX bs=4M

Этап 7. Финишная прямая


Компилируем программы для подключения геймпада на новой системе и устанавливаем демон sixad.


Подключаем геймпад через USB к контроллеру и запускаем программу для создания пары:


root@trik:~/bt# ./sixpair
Current Bluetooth master: 78:**:**:**:**:b9
Setting master bd_addr to 78:**:**:**:**:b9

При подключении геймпада ничего не происходит и служба sixad молчит:


sixad-bin[2675]: started
sixad-bin[2675]: sixad started, press the PS button now
sixad-bin[2675]: unable to connect to sdp session

Но в сообществе Raspberry Pi уже изготовили "костыль" для исправления подключения.


Пересобираем программу и радуемся.


sixad-bin[2833]: started
sixad-bin[2833]: sixad started, press the PS button now
sixad-bin[2833]: unable to connect to sdp session
sixad-sixaxis[2836]: started
sixad-sixaxis[2836]: Connected 'PLAYSTATION(R)3 Controller (00:**:**:**:**:09)' [Battery 02]

Теперь геймпад доступен системе как устройство ввода и программа jstest покажет состояние всех кнопок и аналоговых датчиков:


root@trik:~# ls /dev/input/
by-path  event0  event1  event2  event3  js0  js1  js2  mice

root@trik:~# jstest --normal /dev/input/jsX
Driver version is 2.1.0.
Joystick (PLAYSTATION(R)3 Controller (00:**:**:**:**:09)) has 29 axes (X, Y, Z, Rx, Ry, Rz, Throttle, Rudder, Wheel, Gas, Brake, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, (null), (null), (null), (null), (null), (null), (null), (null))
and 17 buttons (Trigger, ThumbBtn, ThumbBtn2, TopBtn, TopBtn2, PinkieBtn, BaseBtn, BaseBtn2, BaseBtn3, BaseBtn4, BaseBtn5, BaseBtn6, BtnDead, BtnA, BtnB, BtnC, BtnX).
Testing ... (interrupt to exit)
Axes:  0:     0  1:     0  2:     0  3:     0  4: -7150  5: -7746  6:-32767
7:     0  8:     0  9:     0 10:     0 11:     0 12:     0 
13:     0 14:     0 15:     0 16:     0 17:     0 18:     0 
19:     0 20:     0 21:     0 22:     0 23:     0 24:     0 
25:     0 26:     0 27:     0 28:     0
Buttons:  0:off  1:off  2:off  3:off  4:off  5:off  6:off  
7:off  8:off  9:off 10:off 11:off 12:off 13:off 14:off 15:off 16:off

где X – номер устройства в системе, по умолчанию – 2. Номера кнопок и осей можно посмотреть здесь.


Используем на практике


Видео с демонстрацией работы геймпада на YouTube.


Фото работы дистрибутива

Загрузка ядра:

Терминал, запущенный в X11:



И по традиции:



Полезные ссылки


Программы для подключения геймпада Dualshock 3 – sixpair и sixad.


Для геймпадов и других устройств ввода есть легкая библиотека на C – libenjoy.


Исходный код программы для управления сервомоторами и моторами – репозиторий GitHub.


Все файлы конфигурации из статьи для самодельного дистрибутива – репозиторий GitHub.


Исходный код ядра – репозиторий GitHub.


Интересные факты о контроллере


  • В спецификации заявлено, что объем ОЗУ составляет 256 Мб. Но если вы запустите htop, то увидите, что доступно только 128 Мб. Это ограничено параметрами ядра, которые можно посмотреть в консоли uBoot:

mem=128M console=ttyS1,115200n8 rw noinitrd rootwait root=/dev/mmcblk0p1 vt.global_cursor_default=0 consoleblank=0

Чип памяти имеет маркировку 3PC22 D9MTD производства Micron. Найти информацию о его настоящем объеме не удалось.


  • uBoot хранится на SPI флэш-памяти в которой также зашито ядро, и оно не используется. Вы можете попробовать использовать это место для своих задач или скопировать новое ядро и перенастроить uBoot, чтобы он его использовал.

Адреса образов из dmesg:


[   11.598170] 0x000000000000-0x000000040000 : "uboot"
[   11.642985] 0x000000040000-0x000000080000 : "uboot-env1"
[   11.706256] 0x000000080000-0x0000000c0000 : "uboot-env2"
[   11.761827] 0x0000000c0000-0x000000100000 : "config-periph"
[   11.805129] 0x000000100000-0x000000400000 : "kernel"
[   11.861864] 0x000000400000-0x000001000000 : "RootFS"

  • Экран у контроллера хоть и небольшой, но на самом деле имеет резистивный сенсор. Подключен ли сам сенсор – неизвестно.
  • Dualshock 3 имеет светодиоды у разъема USB, которые показывают номер геймпада/джойстика. В видео присутствует один геймпад, но номер у него 3. Это не ошибка, т.к. в системе присутствуют ещё два "джойстика": акселерометр и гироскоп.

Проблемы, возникшие при использовании


  • Робот иногда зависает намертво, не отключая сервомоторы, что позволяет им изменять свое положение от шума на линии данных. Это было замечено даже на стандартном дистрибутиве.
  • Включение PWM-контроллеров отличается от того, что написано в документации. По крайней мере, в чистом C так не получилось.
  • USB иногда перестает работает на Debian.

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


  1. iig
    25.06.2018 07:54

    'Хоть uBoot и передает ядру устройство, на котором расположен корневой раздел, стоит прописать его в /etc/fstab для надежности.'


    Не понял в чем проблема надёжности.


    'uBoot хранится на SPI флэш-памяти в которой также зашито ядро, и оно не используется.'


    Если видно в dmesg, то скорее всего используется.


    Статья поучительная. Спасибо.


    1. toreonify Автор
      25.06.2018 20:55

      По умолчанию в Debian для ARM прописано монтирование /dev/root на /
      /dev/root берет устройство из параметров ядра, которые ядру передает U-Boot. И у меня не получилось загрузиться с данной конфигурацией. В QEMU работает, как ни странно.

      Если вы посмотрите fstab на x86 дистрибутиве, то там, скорее всего, будет использоваться UUID раздела, но не /dev/root. Раньше можно было встретить как раз название устройства, например /dev/sda1. На компьютере это не так удобно, ведь жесткий диск поменяет своё обозначение, если его переподключить в другой разъем и вдобавок добавить второй. А для такого контроллера, где есть только одна карта памяти, можно и именем устройства ограничиться.


  1. sol77
    25.06.2018 10:59

    Спасибо, интересно и познавательно хотя костыльно до нельзя


    1. toreonify Автор
      25.06.2018 20:40

      Расскажите, пожалуйста, что вы хотели бы сделать иначе и почему. Есть некоторые вещи, которые я хотел сделать «более правильно», но оставил так, как есть для наглядности поиска способа включения Bluetooth


  1. dmitryrf
    25.06.2018 15:27

    У микрона есть декодер для кодов BGA:
    www.micron.com/support/tools-and-utilities/fbga?fbga=D9MTD
    Выдает MT47H128M16RT-25E:C, это 256 Мбайт.