toreonify's notes

How to configure Bluetooth in Linux the hard way

Note: this post originally was written in April - June of 2018 and published on Habrahabr in both English and Russian (now it's deleted).

trik-1

While preparing for yearly town IT expo, idea came to my mind to make simple robotic arm with wireless control to demonstrate abilities of microcontrollers and single-board computers. I had TRIK controller, several servo motors, steel constructor and a month to build it.

"Everything goes according to plan", but not in this case.

Step 1. Preparation

TRIK with GNU/Linux onboard was overkill for this task, but it was familiar and easier to develop for (@ClusterM used GNU/Linux in his smart intercom too for it's convenience).

After reading technical specification, it was found that controller had Bluetooth support. If you ever worked with this controller you know that apps and projects are transfered using Wi-Fi and there's no other simple way to do this. Bluetooth never mentioned in menu either. Where's Bluetooth then?

Screenshot_20240129_133231

Equipped with SSH, screwdriver and curiosity, I started to search for him. By default there were tools for controlling Bluetooth like hcitool, hciconfig and bluetoothd daemon. No one gave a positive reply.

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

I've called all my friends to find if any had an external USB-dongle, just in case there won't be a Bluetooth chip inside.

After disassembling controller apart, suspect came in our hands – Jorjin WG7311-0A. Datasheet said that indeed it has Wi-Fi, Bluetooth and even FM-radio. Bluetooth is controlled over UART interface and chip has a BT_EN pin to enable Bluetooth module.

trik-2

I've read how to communicate with Bluetooth over UART through hcitool and tested three available UARTs, but no luck – there was no reply.

Still, we have to try BT_EN pin! Maybe Bluetooth module is disabled on hardware level and ignores all requests. I've looked through Linux kernel code for TRIK and found a mention of BT_EN! File arch/arm/mach-davinci/board-da850-trik.c revealed its GPIO number. Victory! – or so I thought.

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

Step 2. Attack!

To enable GPIO pin, first we need to find its ordinal number. In initialization routine in arch/arm/mach-davinci/board-da850-trik.c we can see that pin is set low:

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

In this routine macro GPIO_TO_PIN is used. Let's see macro definition in 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))

Now we can calculate ordinal number of this pin. 16 * 6 + 11 equals 107. Now let's turn this pin on.

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

0 or 1 in echo command is the state of the pin.

Trying to initialize Bluetooth and...

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

we have an error message. Let's try configuring through hcitool:

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

No devices found, even though hciattach said that "setup is complete". Let's try again with alternative adapter type:

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

Nothing again. But let's read the error message:

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

Our utilities cannot find firmware blob. Definetely, in /lib/firmware there's no file with that name. After a long dive in "the Internet" on TI repository we can download required file. Other versions of this firmware didn't work.

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

Rebooting controller and trying to connect again:

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

No errors here! Firmware loaded. Checking 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

Launching bluetoothd daemon and enabling discovery of our controller:

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

And controller appears in Bluetooth list on laptop:

trik-3

We can write a simple script for autostarting Bluetooth using previous commands:

#!/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)
       ;;
   *)
       ;;

After that we can add it to init scripts:

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

update-rc.d init-bluetooth enable 99

Restart and stop commands are not filled because module is behaving unpredictably after issuing them.

Step 3. Connection check

Simplest way to check connection both ways – serial port service. Using several commands we can enable terminal access through Bluetooth:

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

Having connected to serial terminal from phone and we can see login invitation:

trik-4

Keep in mind that empty password may not work to properly login.

Step 4. ./configure && make

Following instructions on "how to connect gamepad in Linux" we encounter next problems:

  • BlueZ in custom distribution of GNU/Linux doesn't understand commands from sixad daemon that needed to connect to gamepad
  • New version of BlueZ requires a lot of dependencies, so compiling from sources is out of reach/league/???
  • BlueZ from Debian requires udev and systemd, they are not available in custom distribution

One dependency that is easy to satisfy – uinput kernel module.

For that we need:

  • current kernel config from device
cp /proc/config.gz config.gz

gunzip config.gz 

After that: - copy kernel config from device to kernel source folder - modify kernel config to include uinput as a module

echo "CONFIG_INPUT_UINPUT=m" >> config
  • enable toolchain and start the build
source /opt/trik-sdk/environment-setup-arm926ejste-oe-linux-gnueabi

make
  • after build completes, copy new kernel modules to SD card
make INSTALL_MOD_PATH=/mnt/trik-sd modules_install
  • build new uBoot image and copy to /boot of SD card
make uImage

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

Now our gamepad daemon doesn't crash without the module, but still we cannot progress further without satisfying other dependencies. We will come back to configuring gamepad later on.

Step 5. Ducttaping working solution

If there's no obvious and more convienient solution to run required software out of the box, let's install something that will allow us to do that. CPU in SoC is based on ARMv5TE architechture and there' plenty of common GNU/Linux distributions to run.

First candidate was Arch Linux. At launch it said that systemd requires newer kernel. Several tries to compile newer 4.16 version of kernel didn't succeed and took a lot of time.

Let's try with Debian. It has disk images with preinstalled distribution, but it's better to start our own fresh install.

Installing in QEMU

Installer ISO can be downloaded from official archive. QEMU is available for all common platfroms (easier to get on Linux or -nix). Also, we need to supply QEMU with kernel and initrd image, that are available with netboot installer.

Next, create raw disk image with size equal to your SD card in TRIK controller (4 Gb in this case).

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

Launch the installation with this command:

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

If you want to create custom partition table, place root partition as first to eliminate editing uBoot paramaters. By default, it looks for OS in first partition.

Default partition table on SD card contains:

  1. EXT4 partition for root file system ≈ 1,3 Gb
  2. FAT32 partition for user projects ≈ 500 Mb

Output of fdisk for original SD card:

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      

After completing configuration wizards you can go for a long walk, because emulating ARM is not that fast.

For booting our fresh install we need different initrd image that can be downloaded separately from preinstalled images.

Launching system by using this command:

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"

Configuring OS

Get to superuser prompt, check Internet connection, update repositories and packages and install minimal selection of apps to help on later:

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

Terminals (TTY)

Edit /etc/inittab and remove unnecessary TTYs. Also, enable TTY on UART port for accessing controller without SSH. Enable autologin if you plan to launch your software automatically on boot later.

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 and Wi-Fi

Install bluez-utils and wpasupplicant to enable Wi-Fi and Bluetooth access.

apt-get install bluez-utils wpasupplicant

Disable eth0 network interface and configure wlan1 interface in /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

Add known Wi-Fi network to /etc/wpa_supplicant.conf right now, because it will be not easy to do that on controller itself:

wpa_passphrase ssid password >> /etc/wpa_supplicant.conf

If you don't have access to Wi-Fi, you can use UART configured earlier to access command shell. But kernel also dumps all of its messages to UART, so it looks like a mess.

Adding Bluetooth fixes. This time we will modify system service /etc/init.d/bluetooth:

Line 139:

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

Line 168:

   hciattach /dev/ttyS0 texas

   log_end_msg 0
  ;;

That way if some daemon will require to start Bluetooth it will also launch out initialization commands.

Cleanup

Remove unnecessary programs and services that can be viewed using htop, because they take up precious space in RAM:

trik-5

In this case, ConsoleKit daemon has a lot of processes that we can disable. Move its configuration file somewhere safe, like in home directory of superuser, just in case it will be required:

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

After that, RAM usage decreased from 19 Mb to 16 Mb. There's no dedicated swap, so we need every megabyte available.

Partition mounts

Even though uBoot passes root device in kernel parameters, define your root partition in /etc/fstab to be safe:

/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

Remeber to change partition number if root partition isn't first.

If you had left FAT32 user partition, remember to create mountpoint for it

mkdir /usr/share/trik

and add a line to /etc/fstab:

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

Step 6. Launching on real hardware

After configuring our system image, we need to copy modified kernel modules and kernel itself. Mount image as loop device:

# Displays partition table to lookup partition start offset (start)
fdisk -l debian.img

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

where NNNN = sector size * partition start offset. By default, one sector equals to 512 bytes.

Using same method, mount original distribution:

fdisk -l trik-distro.img

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

Remove kernel and its modules that used by QEMU, because they don't contain support for our SoC. Copy kernel and modules same way as shown earlier with uinput module.

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

We need to copy proprietary blobs for Wi-Fi from /lib/firmware in original distribution and Bluetooth blob that we found earlier.

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

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

Unmount both images:

umount /mnt/trik-clean

umount /mnt/debian

And launch copy proccess with dd:

# Lookup your SD card device name
lsblk

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

Step 7. Finish him!

Compile utilities for gamepad on new system and install sixad daemon.

Connect your gamepad using USB cable and start pairing process:

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

And daemon doesn't allow to use gamepad and prints another error:

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

But in Rapsberry Pi community this problem was already solved.

In directory 'QtSixA-1.5.1/sixad' edit the file 'bluetooth.cpp'. Change line #218 in the 'l2cap_accept' function.

OLD:
if (!legacy && req.vendor == 0x054c && req.product == 0x0268) {
NEW:
if (!legacy) {

Recompile the daemon and voila:

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]

Now gamepad is available as input device and jstest can show gamepad buttons and analog sensors status:

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

where X - device number, by default – 2. Gamepad mapping can be looked up here.

Using gamepad

Video demonstrating usage of gamepad on YouTube.

Kernel booting process:

trik-6

Terminal launched in X11:

trik-7

trik-8

And without that it wouldn't be complete:

trik-9

trik-10

App for connecting Dualshock 3 – sixpair and sixad

Lightweight C library for using input devices like gamepads – libenjoy.

Example app for controlling servomotors on TRIK – GitHub repo.

All files mentioned in this guide – GitHub repo.

Kernel source code – GitHub repo.

Some facts

  • Specifications say that RAM size is 256 Mb. If you launch htop, you'll see that only 128 Mb is available. RAM size is restricted by kernel parameter that you can find in uBoot console:
mem=128M console=ttyS1,115200n8 rw noinitrd rootwait root=/dev/mmcblk0p1 vt.global_cursor_default=0 consoleblank=0

Memory chip is marked 3PC22 D9MTD and produced by Micron. (there was a person in comments on Habrahabr that found that size is indeed 256 Mb)

  • uBoot is contained on SPI flash memory in which kernel is also contained. It's not used but you can use this space for your own tasks or place a new kernel and tell uBoot to use it.

Address table from 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"
  • Controller has a small screen, but it has a resistive film, indicating touch support. Have it been connected to SoC – don't know.
  • Dualshock 3 has LEDs to indicate player number. In video you can see only one gamepad, but LEDs show it like player 3. This is because accelerometer and gyroscope detected as separate input devices.

Problems, that happened in daily usage

  • Controller can freeze completely without safely disabling servomotors. That allows for noises to control them unpredicatably. This is also a problem on original distribution..
  • Enabling PWM differs from what documentaion says. At least, in C it doesn't work.
  • USB can stop working suddenly in Debian.
Thoughts? Leave a comment