Xbox Wireless Controller connected to the PINE64 running Ubuntu Linux using bluetooth

Although the goal of the autonomous robot project is to have a robot that can navigate without human input, it’s not a bad idea to be able to manually override and control the robot one way or another which could come in useful in cases where it gets stuck due to being trapped in between large physical obstacles, or perhaps a bug in the code. My PINE64 order came with the stock WiFi / Bluetooth 4.0 module which is still available from the PINE store, and I also got the Xbox Wireless Controller with bluetooth support ($39.99). Any bluetooth controller should be able to get the job done, but I particularly like the look of the Xbox Wireless controller. However, it was a little difficult getting the controller to actually pair due to certain quirks with the controller implementation. I finally got it to work after spending almost an entire day on the issue and accidentally wiping my primary Ubuntu install with dd (which shouldn’t have happened, but I suppose someone shouldn’t do dangerous things when they’re tired).

In this post, I will cover how to setup the PINE64’s stock Bluetooth module, how I got the XBox Wireless controller to successfully connect via bluetooth, and run some input tests using the evtest tool. I am currently running the longsleep xenial image with kernel version 3.10.105. In a future post, we’ll write C# code to handle system-wide input events for Ghost Mainframe (which is what I’ll be calling the autonomous robot project going forward until I can think of a better name).

How to setup the stock Bluetooth module on the PINE A64+

The first thing we need to do is install the firmware which will enable the bluetooth module to work properly and load the necessary drivers. This is fairly straightforward and can be done using these steps.

git clone https://github.com/lwfinger/rtl8723bs_bt
cd rtl8723bs_bt
make
sudo make install

After make install completes, copy rtk_hciattach to your preferred bin path, either /usr/bin or /usr/local/bin.

You will also need to install the bluetooth service and the bluez stack by running sudo apt install bluetooth bluez. Once this is complete, you can test to make sure it works using the following comamnds:

sudo echo 1 > /sys/class/rfkill/rfkill0/state
sudo rtk_hciattach /dev/ttyS1 rtk_h5

Run sudo rfkill list and you should get output similar to what’s shown below.

0: sunxi-bt: Bluetooth
	Soft blocked: no
	Hard blocked: no
1: phy0: Wireless LAN
	Soft blocked: no
	Hard blocked: no
2: phy1: Wireless LAN
	Soft blocked: no
	Hard blocked: no
3: hci0: Bluetooth
	Soft blocked: no
	Hard blocked: no

If Soft blocked under Bluetooth is yes, unblock it using sudo rfkill unblock 3. This only needs to be done once. You can run the list command again to make sure that it actually got unblocked.

You can bring up the Bluetooth interface using sudo hciconfig hci0 up. Note that the hciconfig command is not available in BlueZ 5.47. Since the BlueZ version available from the xenial repository is 5.37, this is not a problem. If you don’t have hciconfig in your path, you will need to turn on the bluetooth module using bluetoothctl. Simply run power on in the bluetoothctl prompt.

Finally, you can add the following lines to /etc/rc.local if you wish to automatically power on the PINE Bluetooth module on every boot.

echo 1 > /sys/class/rfkill/rfkill0/state
rtk_hciattach /dev/ttyS1 rtk_h5
sleep 2 && hciconfig hci0 up

bluetoothctl

bluetoothctl is a command line tool that you can use to power on/off the bluetooth module, scan, discover and connect to nearby bluetooth devices. Simply run sudo bluetoothctl to get the to the bluetooth control prompt. If you are at the prompt and it’s not accepting your keyboard input, that could indicate that the bluetooth service is not running. Exit (using Ctrl+C if input is not being accepted) and then run sudo service start bluetooth.

Pairing the XBox Wireless Controller with the PINE64

This is where things got hairy. You can skip to the solution if you don’t want to read about how I arrived there.

The Story
I had been following the xpad kernel driver project for a while which is how I had initially found out that there was a new version of the Xbox controller that could be connected using bluetooth. Following the instructions in this issue and this comment didn’t seem to work. Instead, I ended up with an AuthenticationCancelled error.

My initial foray into finding solutions for the problem led me to build BlueZ 5.47 from source and install, but that didn’t change anything. There were also some posts about having to update the controller’s firmware which can only be done with a Windows 10 (Why, Microsoft?!) store app called Xbox Accessories. I created a VirtualBox VM running Windows 10 Fall Creator’s edition but the app did not recognise the controller when connected over USB. This led me into a deep hole where I was trying to obtain various older Windows drivers in order to get the controller to work. None of them worked so I eventually decided to just boot Windows on my notebook, as opposed to within the VM since I guessed that the VirtualBox USB implementation was probably causing detection problems (spoiler: I was right).

I tried to boot Windows from a preinstalled SSD, which resulted in a blue screen, then I tried to burn the VM’s VHD file to a different drive connected over USB, which somehow ended up destroying all the partitions on my internal SSD (I am very sure I used /dev/sdb as the of parameter for dd, not /dev/sda, but I don’t even care anymore). Finally, I created a Windows bootable flash drive and attempted to install on the external SSD only to discover that Windows cannot be installed to a drive connected over USB or Firewire. RAGE! Since my internal SSD was wiped anyway, I finally got Windows installed with the Xbox Accessories app up and running only to discover that the controller already had the latest firmware installed. That was pointless!

I almost gave up until I decided to re-read the btmon logs which led to me noticing the error, Result: Failure - unknown options (0x0003). This finally led me in the right direction as I found this mailing list thread which points to a couple of patches I had to apply to the kernel source. After rebuilding the kernel from source and modules, the bluetoothctl connect command works, although the pair command still fails. The connect command however forces the controller to pair, and that’s what’s important.

The Solution
Apply the changes in this commit (you may need to change the USB device ID – obtained using lsusb – to match your controller; mine is 0x02ea) to the kernel source and then rebuild and deploy the kernel, headers and modules following these instructions. You can also make use of the helper scripts in that repository. Following redeployment, you can reboot the board so that the new kernel and modules are loaded. You can also add the disable_ertm line to your /etc/rc.local so that it is done at boot time. Your /etc/rc.local file should have lines like so (including changes applied above):

echo 1 > /sys/class/rfkill/rfkill0/state
rtk_hciattach /dev/ttyS1 rtk_h5
echo 1 > /sys/module/bluetooth/parameters/disable_ertm
sleep 2 && hciconfig hci0 up

Finally, you should get output like what’s showing using bluetoothctl (input commands are preceded with the bluetooth prompt). You will notice that although the pair command still fails with Failed to pair: org.bluez.Error.AuthenticationCanceled, the connect command actually works.

[NEW] Controller AA:AA:AA:AA:AA:AA pine64 [default]
[bluetooth]# power on
Changing power on succeeded
[CHG] Controller AA:AA:AA:AA:AA:AA Powered: yes
[bluetooth]# agent on
Agent registered
[bluetooth]# default-agent
Default agent request successful
[bluetooth]# scan on
Discovery started
[CHG] Controller AA:AA:AA:AA:AA:AA Discovering: yes
[NEW] Device XX:XX:XX:XX:XX:XX Xbox Wireless Controller
[bluetooth]# pair XX:XX:XX:XX:XX:XX
Attempting to pair with XX:XX:XX:XX:XX:XX
[CHG] Device XX:XX:XX:XX:XX:XX Connected: yes
[CHG] Device XX:XX:XX:XX:XX:XX Connected: no
Failed to pair: org.bluez.Error.AuthenticationCanceled
[bluetooth]# connect XX:XX:XX:XX:XX:XX
Attempting to connect to XX:XX:XX:XX:XX:XX
[CHG] Device XX:XX:XX:XX:XX:XX Connected: yes
[CHG] Device XX:XX:XX:XX:XX:XX Modalias: usb:v045Ep02E0d0903
[CHG] Device XX:XX:XX:XX:XX:XX Modalias: usb:v045Ep02FDd0903
[CHG] Device XX:XX:XX:XX:XX:XX UUIDs: 00001124-0000-1000-8000-00805f9b34fb
[CHG] Device XX:XX:XX:XX:XX:XX UUIDs: 00001200-0000-1000-8000-00805f9b34fb
[CHG] Device XX:XX:XX:XX:XX:XX Paired: yes
Connection successful
[Xbox Wireless Controller]#

evtest: Testing the Xbox Wireless Controller input

You can exit from bluetoothctl after the connection has been successfully established. Install evtest using sudo apt install evtest and then run it using sudo evtest. With the output like so, enter the number corresponding to your controller.

No device specified, trying to scan all of /dev/input/event*
Available devices:
/dev/input/event0:	sunxi-keyboard
/dev/input/event1:	axp81x-supplyer
/dev/input/event2:	sunxi-ths
/dev/input/event3:	sunxi_ir_recv
/dev/input/event4:	MCE IR Keyboard/Mouse (sunxi-rc-recv)
/dev/input/event5:	Xbox Wireless Controller
Select the device event number [0-5]:

I got the following output after I entered 5:

Input driver version is 1.0.1
Input device ID: bus 0x5 vendor 0x45e product 0x2fd version 0x903
Input device name: "Xbox Wireless Controller"
Supported events:
  Event type 0 (EV_SYN)
  Event type 1 (EV_KEY)
    Event code 158 (KEY_BACK)
    Event code 172 (KEY_HOMEPAGE)
    Event code 304 (BTN_SOUTH)
    Event code 305 (BTN_EAST)
    Event code 306 (BTN_C)
    Event code 307 (BTN_NORTH)
    Event code 308 (BTN_WEST)
    Event code 309 (BTN_Z)
    Event code 310 (BTN_TL)
    Event code 311 (BTN_TR)
    Event code 312 (BTN_TL2)
    Event code 313 (BTN_TR2)
    Event code 314 (BTN_SELECT)
    Event code 315 (BTN_START)
    Event code 316 (BTN_MODE)
    Event code 317 (BTN_THUMBL)
    Event code 318 (BTN_THUMBR)
  Event type 3 (EV_ABS)
    Event code 0 (ABS_X)
      Value  33687
      Min        0
      Max    65535
      Fuzz     255
      Flat    4095
    Event code 1 (ABS_Y)
      Value  32135
      Min        0
      Max    65535
      Fuzz     255
      Flat    4095
    Event code 2 (ABS_Z)
      Value  31698
      Min        0
      Max    65535
      Fuzz     255
      Flat    4095
    Event code 5 (ABS_RZ)
      Value  37479
      Min        0
      Max    65535
      Fuzz     255
      Flat    4095
    Event code 9 (ABS_GAS)
      Value      0
      Min        0
      Max     1023
      Fuzz       3
      Flat      63
    Event code 10 (ABS_BRAKE)
      Value      0
      Min        0
      Max     1023
      Fuzz       3
      Flat      63
    Event code 16 (ABS_HAT0X)
      Value      0
      Min       -1
      Max        1
    Event code 17 (ABS_HAT0Y)
      Value      0
      Min       -1
      Max        1
    Event code 40 (ABS_MISC)
      Value    135
      Min        0
      Max      255
      Flat      15
  Event type 4 (EV_MSC)
    Event code 4 (MSC_SCAN)
Properties:
Testing ... (interrupt to exit)

Here’s the output when I press a few buttons and move the analog sticks.

Event: time 1512754351.668219, type 3 (EV_ABS), code 5 (ABS_RZ), value 33800
Event: time 1512754351.668219, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754351.668219, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754351.668219, -------------- SYN_REPORT ------------
Event: time 1512754351.690797, type 3 (EV_ABS), code 2 (ABS_Z), value 33131
Event: time 1512754351.690797, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754351.690797, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754351.690797, -------------- SYN_REPORT ------------
Event: time 1512754351.885663, type 3 (EV_ABS), code 5 (ABS_RZ), value 33845
Event: time 1512754351.885663, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754351.885663, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754351.885663, -------------- SYN_REPORT ------------
Event: time 1512754351.900684, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754351.900684, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754351.900684, type 4 (EV_MSC), code 4 (MSC_SCAN), value 90004
Event: time 1512754351.900684, type 1 (EV_KEY), code 307 (BTN_NORTH), value 1
Event: time 1512754351.900684, -------------- SYN_REPORT ------------
Event: time 1512754351.908171, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754351.908171, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754351.908171, -------------- SYN_REPORT ------------
Event: time 1512754351.923171, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754351.923171, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754351.923171, -------------- SYN_REPORT ------------
Event: time 1512754352.005668, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754352.005668, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754352.005668, type 4 (EV_MSC), code 4 (MSC_SCAN), value 90004
Event: time 1512754352.005668, type 1 (EV_KEY), code 307 (BTN_NORTH), value 0
Event: time 1512754352.005668, -------------- SYN_REPORT ------------
Event: time 1512754352.020838, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754352.020838, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754352.020838, -------------- SYN_REPORT ------------
Event: time 1512754352.440712, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754352.440712, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754352.440712, type 4 (EV_MSC), code 4 (MSC_SCAN), value 90005
Event: time 1512754352.440712, type 1 (EV_KEY), code 308 (BTN_WEST), value 1
Event: time 1512754352.440712, -------------- SYN_REPORT ------------
Event: time 1512754352.448189, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754352.448189, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754352.448189, -------------- SYN_REPORT ------------
Event: time 1512754352.455711, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754352.455711, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754352.455711, -------------- SYN_REPORT ------------
Event: time 1512754352.553169, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754352.553169, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754352.553169, type 4 (EV_MSC), code 4 (MSC_SCAN), value 90005
Event: time 1512754352.553169, type 1 (EV_KEY), code 308 (BTN_WEST), value 0
Event: time 1512754352.553169, -------------- SYN_REPORT ------------
Event: time 1512754352.560690, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754352.560690, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754352.560690, -------------- SYN_REPORT ------------
Event: time 1512754352.568174, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754352.568174, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754352.568174, -------------- SYN_REPORT ------------
Event: time 1512754352.800698, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754352.800698, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754352.800698, type 4 (EV_MSC), code 4 (MSC_SCAN), value 90002
Event: time 1512754352.800698, type 1 (EV_KEY), code 305 (BTN_EAST), value 1
Event: time 1512754352.800698, -------------- SYN_REPORT ------------
Event: time 1512754352.808215, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754352.808215, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754352.808215, -------------- SYN_REPORT ------------
Event: time 1512754352.815704, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754352.815704, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754352.815704, -------------- SYN_REPORT ------------
Event: time 1512754352.928169, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754352.928169, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754352.928169, type 4 (EV_MSC), code 4 (MSC_SCAN), value 90002
Event: time 1512754352.928169, type 1 (EV_KEY), code 305 (BTN_EAST), value 0
Event: time 1512754352.928169, -------------- SYN_REPORT ------------
Event: time 1512754352.935711, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754352.935711, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754352.935711, -------------- SYN_REPORT ------------
Event: time 1512754352.943412, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754352.943412, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754352.943412, -------------- SYN_REPORT ------------
Event: time 1512754353.070685, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754353.070685, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754353.070685, type 4 (EV_MSC), code 4 (MSC_SCAN), value 90001
Event: time 1512754353.070685, type 1 (EV_KEY), code 304 (BTN_SOUTH), value 1
Event: time 1512754353.070685, -------------- SYN_REPORT ------------
Event: time 1512754353.078184, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754353.078184, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754353.078184, -------------- SYN_REPORT ------------
Event: time 1512754353.100726, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754353.100726, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754353.100726, -------------- SYN_REPORT ------------
Event: time 1512754353.175707, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754353.175707, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754353.175707, type 4 (EV_MSC), code 4 (MSC_SCAN), value 90001
Event: time 1512754353.175707, type 1 (EV_KEY), code 304 (BTN_SOUTH), value 0
Event: time 1512754353.175707, -------------- SYN_REPORT ------------
Event: time 1512754353.993252, type 3 (EV_ABS), code 0 (ABS_X), value 33813
Event: time 1512754353.993252, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754353.993252, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754353.993252, -------------- SYN_REPORT ------------
Event: time 1512754354.000727, type 3 (EV_ABS), code 0 (ABS_X), value 33813
Event: time 1512754354.000727, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754354.000727, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754354.000727, -------------- SYN_REPORT ------------
Event: time 1512754354.015690, type 3 (EV_ABS), code 0 (ABS_X), value 32976
Event: time 1512754354.015690, type 3 (EV_ABS), code 1 (ABS_Y), value 31899
Event: time 1512754354.015690, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754354.015690, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754354.015690, -------------- SYN_REPORT ------------
Event: time 1512754354.023188, type 3 (EV_ABS), code 0 (ABS_X), value 26432
Event: time 1512754354.023188, type 3 (EV_ABS), code 1 (ABS_Y), value 31113
Event: time 1512754354.023188, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754354.023188, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754354.023188, -------------- SYN_REPORT ------------
Event: time 1512754354.030693, type 3 (EV_ABS), code 0 (ABS_X), value 25351
Event: time 1512754354.030693, type 3 (EV_ABS), code 1 (ABS_Y), value 29971
Event: time 1512754354.030693, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754354.030693, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754354.030693, -------------- SYN_REPORT ------------
Event: time 1512754354.038192, type 3 (EV_ABS), code 0 (ABS_X), value 14885
Event: time 1512754354.038192, type 3 (EV_ABS), code 1 (ABS_Y), value 21912
Event: time 1512754354.038192, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754354.038192, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754354.038192, -------------- SYN_REPORT ------------
Event: time 1512754354.045979, type 3 (EV_ABS), code 0 (ABS_X), value 7364
Event: time 1512754354.045979, type 3 (EV_ABS), code 1 (ABS_Y), value 20983
Event: time 1512754354.045979, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754354.045979, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754354.045979, -------------- SYN_REPORT ------------
Event: time 1512754354.053239, type 3 (EV_ABS), code 0 (ABS_X), value 0
Event: time 1512754354.053239, type 3 (EV_ABS), code 1 (ABS_Y), value 12813
Event: time 1512754354.053239, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754354.053239, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754354.053239, -------------- SYN_REPORT ------------
Event: time 1512754354.135693, type 3 (EV_ABS), code 1 (ABS_Y), value 12813
Event: time 1512754354.135693, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754354.135693, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754354.135693, -------------- SYN_REPORT ------------
Event: time 1512754354.150659, type 3 (EV_ABS), code 1 (ABS_Y), value 12864
Event: time 1512754354.150659, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754354.150659, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754354.150659, -------------- SYN_REPORT ------------
Event: time 1512754354.158189, type 3 (EV_ABS), code 0 (ABS_X), value 12576
Event: time 1512754354.158189, type 3 (EV_ABS), code 1 (ABS_Y), value 19757
Event: time 1512754354.158189, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754354.158189, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754354.158189, -------------- SYN_REPORT ------------
Event: time 1512754354.165722, type 3 (EV_ABS), code 0 (ABS_X), value 42285
Event: time 1512754354.165722, type 3 (EV_ABS), code 1 (ABS_Y), value 35206
Event: time 1512754354.165722, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754354.165722, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754354.165722, -------------- SYN_REPORT ------------
Event: time 1512754354.173187, type 3 (EV_ABS), code 0 (ABS_X), value 30551
Event: time 1512754354.173187, type 3 (EV_ABS), code 1 (ABS_Y), value 34260
Event: time 1512754354.173187, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754354.173187, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754354.173187, -------------- SYN_REPORT ------------
Event: time 1512754354.180700, type 3 (EV_ABS), code 0 (ABS_X), value 31150
Event: time 1512754354.180700, type 3 (EV_ABS), code 1 (ABS_Y), value 34035
Event: time 1512754354.180700, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754354.180700, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754354.180700, -------------- SYN_REPORT ------------
Event: time 1512754354.188192, type 3 (EV_ABS), code 0 (ABS_X), value 31685
Event: time 1512754354.188192, type 3 (EV_ABS), code 1 (ABS_Y), value 33443
Event: time 1512754354.188192, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754354.188192, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754354.188192, -------------- SYN_REPORT ------------
Event: time 1512754354.195760, type 3 (EV_ABS), code 0 (ABS_X), value 31898
Event: time 1512754354.195760, type 3 (EV_ABS), code 1 (ABS_Y), value 33299
Event: time 1512754354.195760, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754354.195760, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754354.195760, -------------- SYN_REPORT ------------
Event: time 1512754354.203188, type 3 (EV_ABS), code 0 (ABS_X), value 32461
Event: time 1512754354.203188, type 3 (EV_ABS), code 1 (ABS_Y), value 33109
Event: time 1512754354.203188, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754354.203188, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754354.203188, -------------- SYN_REPORT ------------
Event: time 1512754354.210698, type 3 (EV_ABS), code 0 (ABS_X), value 32597
Event: time 1512754354.210698, type 3 (EV_ABS), code 1 (ABS_Y), value 32921
Event: time 1512754354.210698, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754354.210698, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754354.210698, -------------- SYN_REPORT ------------
Event: time 1512754354.218189, type 3 (EV_ABS), code 0 (ABS_X), value 32774
Event: time 1512754354.218189, type 3 (EV_ABS), code 1 (ABS_Y), value 32751
Event: time 1512754354.218189, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754354.218189, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754354.218189, -------------- SYN_REPORT ------------
Event: time 1512754354.225724, type 3 (EV_ABS), code 0 (ABS_X), value 32950
Event: time 1512754354.225724, type 3 (EV_ABS), code 1 (ABS_Y), value 32606
Event: time 1512754354.225724, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754354.225724, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754354.225724, -------------- SYN_REPORT ------------
Event: time 1512754354.233180, type 3 (EV_ABS), code 0 (ABS_X), value 33108
Event: time 1512754354.233180, type 3 (EV_ABS), code 1 (ABS_Y), value 32545
Event: time 1512754354.233180, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754354.233180, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754354.233180, -------------- SYN_REPORT ------------
Event: time 1512754354.240703, type 3 (EV_ABS), code 0 (ABS_X), value 33242
Event: time 1512754354.240703, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754354.240703, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754354.240703, -------------- SYN_REPORT ------------
Event: time 1512754354.248196, type 3 (EV_ABS), code 0 (ABS_X), value 33299
Event: time 1512754354.248196, type 3 (EV_ABS), code 1 (ABS_Y), value 32384
Event: time 1512754354.248196, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754354.248196, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754354.248196, -------------- SYN_REPORT ------------
Event: time 1512754354.263189, type 3 (EV_ABS), code 0 (ABS_X), value 33449
Event: time 1512754354.263189, type 3 (EV_ABS), code 1 (ABS_Y), value 32322
Event: time 1512754354.263189, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754354.263189, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754354.263189, -------------- SYN_REPORT ------------
Event: time 1512754354.285686, type 3 (EV_ABS), code 0 (ABS_X), value 33584
Event: time 1512754354.285686, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754354.285686, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754354.285686, -------------- SYN_REPORT ------------
Event: time 1512754354.293188, type 3 (EV_ABS), code 1 (ABS_Y), value 32182
Event: time 1512754354.293188, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754354.293188, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754354.293188, -------------- SYN_REPORT ------------
Event: time 1512754354.338389, type 3 (EV_ABS), code 0 (ABS_X), value 33641
Event: time 1512754354.338389, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754354.338389, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754354.338389, -------------- SYN_REPORT ------------
Event: time 1512754357.218305, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754357.218305, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754357.218305, type 4 (EV_MSC), code 4 (MSC_SCAN), value 90007
Event: time 1512754357.218305, type 1 (EV_KEY), code 310 (BTN_TL), value 1
Event: time 1512754357.218305, -------------- SYN_REPORT ------------
Event: time 1512754357.225771, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754357.225771, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754357.225771, -------------- SYN_REPORT ------------
Event: time 1512754357.233210, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754357.233210, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754357.233210, -------------- SYN_REPORT ------------
Event: time 1512754357.375728, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754357.375728, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754357.375728, type 4 (EV_MSC), code 4 (MSC_SCAN), value 90007
Event: time 1512754357.375728, type 1 (EV_KEY), code 310 (BTN_TL), value 0
Event: time 1512754357.375728, -------------- SYN_REPORT ------------
Event: time 1512754357.383257, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754357.383257, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754357.383257, -------------- SYN_REPORT ------------
Event: time 1512754357.390734, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754357.390734, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754357.390734, -------------- SYN_REPORT ------------
Event: time 1512754358.373255, type 3 (EV_ABS), code 2 (ABS_Z), value 33131
Event: time 1512754358.373255, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754358.373255, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754358.373255, -------------- SYN_REPORT ------------
Event: time 1512754358.388206, type 3 (EV_ABS), code 2 (ABS_Z), value 33131
Event: time 1512754358.388206, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754358.388206, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754358.388206, -------------- SYN_REPORT ------------
Event: time 1512754358.410732, type 3 (EV_ABS), code 2 (ABS_Z), value 33131
Event: time 1512754358.410732, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754358.410732, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754358.410732, -------------- SYN_REPORT ------------
Event: time 1512754358.463257, type 3 (EV_ABS), code 2 (ABS_Z), value 33088
Event: time 1512754358.463257, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754358.463257, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754358.463257, -------------- SYN_REPORT ------------
Event: time 1512754358.598420, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754358.598420, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754358.598420, type 4 (EV_MSC), code 4 (MSC_SCAN), value 9000c
Event: time 1512754358.598420, type 1 (EV_KEY), code 315 (BTN_START), value 1
Event: time 1512754358.598420, -------------- SYN_REPORT ------------
Event: time 1512754358.605933, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754358.605933, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754358.605933, -------------- SYN_REPORT ------------
Event: time 1512754358.613284, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754358.613284, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754358.613284, -------------- SYN_REPORT ------------
Event: time 1512754358.643259, type 3 (EV_ABS), code 5 (ABS_RZ), value 33893
Event: time 1512754358.643259, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754358.643259, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754358.643259, -------------- SYN_REPORT ------------
Event: time 1512754358.763268, type 3 (EV_ABS), code 16 (ABS_HAT0X), value 0
Event: time 1512754358.763268, type 3 (EV_ABS), code 17 (ABS_HAT0Y), value 0
Event: time 1512754358.763268, type 4 (EV_MSC), code 4 (MSC_SCAN), value 9000c
Event: time 1512754358.763268, type 1 (EV_KEY), code 315 (BTN_START), value 0
Event: time 1512754358.763268, -------------- SYN_REPORT ------------

Conclusion

While it wasn’t a pleasant experience, I’m glad I was able to get the XBox Wireless Controller to actually work with the PINE64. In my followup post, I will write out (or probably create a diagram) of the controller button mappings through evtest and then write C# code to handle the input directly from the /dev/input/event* file and generate input events accordingly.

Controlling an Arduino connected to the PINE64 over I2C with C and C#

In a previous post, we connected an Arduino Mega to the PINE64 and wrote a sketch for the Mega for data communication. The data to be sent and received will follow the simple rules which were listed in the post. Next, we’re going to write a shared library with some C code to interface with I2C natively, and then a C# class which will call the shared library and bring everything together. Just a quick recap of the data rules:

  • Maximum length: 16 bytes.
  • First byte: Number of bytes sent/received.
  • Second byte: Control command. CMD_DIGITAL_WRITE (0x01 or 1), CMD_DIGITAL_READ (0x02 or 2) and CMD_ANALOG_WRITE (0x03 or 3).
  • Third byte: Pin number.
  • Fourth byte: Value of either 1 (for high) or 0 (for low) for CMD_DIGITAL_WRITE, or
  • Fourth to seventh bytes: Integer value for CMD_ANALOG_WRITE.

Linux has native I2C support which lets us communicate directly with any device connected to the bus. Since native communication is possible, we create a shared library with methods which can be invoked from our C# code. Since we can open the connection to a device on the I2C bus as a file descriptor, we will be making use of fcntl.h for the descriptor. Let’s take a look at the C library.

#include 
#include 
#include 

#define MAX_BYTES 16

/**
 * Open an I2C connection using the specified file descriptor.
 */
int nanite_i2c_open(const char *filename, int slave_address)
{
    int fd;

    if ((fd = open(filename, O_RDWR)) < 0)
    {
        return -1;
    }

    if (ioctl(fd, I2C_SLAVE, slave_address) < 0)
    {
        return -1;
    }

    return fd;
}

The first thing we do is include required headers and define a constant specifying the maximum number of bytes, which is 16. Then we create the nanite_i2c_open function try to open the specified device file (usually /dev/i2c-1). Next we call ioctl so that we will be able to control the device using a reference to the file descriptor, and the specified slave address. The file descriptor is returned for reference in other function calls. If any of the steps in the function fails, -1 is returned to indicate an error condition. Other functions in the library will handle the failure the same way.

The nanite_i2c_close function is fairly simple. It takes the file descriptor as an argument and checks if it is open in order to close it.

/**
 * Close the I2C connection using the specified file descriptor.
 */
int nanite_i2c_close(int fd)
{
    if (fd > 0)
    {
        close(fd);
    }

    return 0;
}

Next is the nanite_i2c_send function which takes the file descriptor and the data to be sent as arguments. The bytes are written to the open file descriptor, and the number of bytes written is verified. If it does not match the length defined in the first byte, -1 is returned to indicate an error condition.

/**
 * Send up to the maximum number of bytes over I2C using the specified file descriptor.
 */
int nanite_i2c_send(int fd, const char bytes[MAX_BYTES])
{
    if (fd <= 0 || !bytes)
    {
        return -1;
    }

    int num_bytes;
    num_bytes = write(fd, bytes, bytes[0]);
    if (num_bytes != bytes[0])
    {
        return -1;
    }

    return num_bytes;
}

The nanite_i2c_read function will be used to read data over I2C. The first byte is retrieved in order to determine how many more bytes to read. Then we validate the number of bytes that were received with the expected length. 0 is returned if the operation was successful.

/**
 * Read the byte data from I2C.
 */
int nanite_i2c_read(int fd, char bytes_read[MAX_BYTES])
{
    if (fd <= 0)
    {
        return -1;
    }

    char fbyte[1];
    // Read the first byte to get the total number of bytes to read
    if (read(fd, fbyte, 1) != 1)
    {
        return -1;
    }

    if (read(fd, bytes_read, fbyte[0]) != fbyte[0])
    {
        return -1;
    }

    return 0;
}

The final function in the library is nanite_i2c_max_bytes() which returns MAX_BYTES. This gives us a complete library that we can use for I2C data communication. You can create the shared library using gcc -shared -o libnanitei2c.so -fPIC main.c. The full code listing for the library is available at https://gitlab.com/akinwale/nanitei2c/blob/master/main.c.

Using DllImport, we can call functions from the shared library in the C# code which we're going to look at next. We create our class, define the supported commands as an enumeration and specify the private members. We also define a couple of constructors with the default constructor using the default I2C device file which is /dev/i2c-1 on the PINE.

using System;
using System.Runtime.InteropServices;
using Nanite.Exceptions;

namespace Nanite.IO
{
    /// 
    /// 
    /// 
    public class I2CExtendedIO : IDisposable
    {
        public enum Commands
        {
            DigitalWrite = 1,
            DigitalRead = 2,
            AnalogWrite = 3
        }

        private const string DefaultI2CFilename = "/dev/i2c-1";

        private Object lockObject = new Object();

        private int fileDescriptor;

        private string i2cFilename;

        public I2CExtendedIO(string i2cFilename)
        {
            this.i2cFilename = i2cFilename;
        }

        public I2CExtendedIO()
            : this(DefaultI2CFilename)
        {

        }

The DllImport calls are defined within the class and are used to map the functions from the library to functions that we can call in the I2CExtendedIO class. For instance, to call the nanite_i2c_send in the class, we'll make use of I2CSendBytes.

        [DllImport("libnanitei2c.so", EntryPoint="nanite_i2c_open")]
        internal static extern int OpenI2C(string filename, int slaveAddress);

        [DllImport("libnanitei2c.so", EntryPoint = "nanite_i2c_close")]
        internal static extern int CloseI2C(int fileDescriptor);

        [DllImport("libnanitei2c.so", EntryPoint = "nanite_i2c_send")]
        internal static extern int I2CSendBytes(int fileDescriptor, byte[] bytes);

        [DllImport("libnanitei2c.so", EntryPoint = "nanite_i2c_read")]
        internal static extern int I2CReadBytes(int fileDescriptor, byte[] bytesRead);

        [DllImport("libnanitei2c.so", EntryPoint = "nanite_i2c_max_bytes")]
        internal static extern int MaxByteCount();

Here, we've defined our class dispose function which closes the file descriptor if it is open. We also have a bunch of simple helper functions which will be called within the class.

        private void ValidateDigitalReadData(int pin, byte[] bytesReceived)
        {
            if (bytesReceived[0] < 3)
            {
                throw new ExtendedIOException("Invalid response data received over the I2C connection.");
            }

            if (bytesReceived[1] != pin)
            {
                throw new ExtendedIOException(string.Format("Pin mismatch for the DigitalRead command. Expected {0} but got {1}.", pin, bytesReceived[1]));
            }

            if (!IsGPIOValueValid(bytesReceived[2]))
            {
                throw new ExtendedIOException("The returned pin value is not valid.");
            }
        }


        private static bool IsGPIOValueValid(int value)
        {
            return ( (value == (int) GPIO.Value.Low) || (value == (int) GPIO.Value.High) );
        }

        private void CheckFileDescriptor()
        {
            if (fileDescriptor <= 0)
            {
                throw new ExtendedIOException("I2C connection is not open.");
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        ~I2CExtendedIO()
        {
            // Finalizer calls Dispose(false)
            Dispose(false);
        }

        protected virtual void Dispose(bool disposing)
        {
            // Close the file descriptor if it's open
            if (fileDescriptor > 0)
            {
                CloseI2C(fileDescriptor);
                fileDescriptor = -1;
            }
        }

The Open function is simple as it delegates to the OpenI2C function with the specified device filename and the slave address, and assigns the returned file descriptor to a private member.

        public void Open(int slaveAddress)
        {
            fileDescriptor = OpenI2C(i2cFilename, slaveAddress);
            if (fileDescriptor <= 0)
            {
                throw new ExtendedIOException(string.Format("Unable to open the I2C connection to slave address: {0}", slaveAddress));
            }
        }

With DigitalWrite, we build the data to be sent based on the specified rules. Then we delegate to I2CSendBytes using the specified arguments, which calls the corresponding library function.

        public void DigitalWrite(int pin, GPIO.Value value)
        {
            CheckFileDescriptor();

            // Build the command to send
            byte[] bytes = new byte[4];
            bytes[0] = (byte) bytes.Length;
            bytes[1] = (byte) Commands.DigitalWrite;
            bytes[2] = (byte) pin;
            bytes[3] = (byte) value;

            int numBytes = I2CSendBytes(fileDescriptor, bytes);
            if (numBytes != bytes.Length)
            {
                throw new ExtendedIOException(string.Format("Could not send {0} bytes over the I2C connection.", bytes.Length));
            }
        }

AnalogWrite is similar to DigitalWrite, except that we convert the integer value to 4 bytes instead of the single byte for low (0) or high (1). Valid values are between 0 and 255 inclusive.

        public void AnalogWrite(int pin, int value)
        {
            CheckFileDescriptor();

            // Build the command to send
            byte[] bytes = new byte[7];
            bytes[0] = (byte) bytes.Length;
            bytes[1] = (byte) Commands.AnalogWrite;
            bytes[2] = (byte) pin;
            bytes[3] = (byte) (value >> 24);
            bytes[4] = (byte) (value >> 16);
            bytes[5] = (byte) (value >> 8);
            bytes[6] = (byte) value;

            int numBytes = I2CSendBytes(fileDescriptor, bytes);
            if (numBytes != bytes.Length)
            {
                throw new ExtendedIOException(string.Format("Could not send {0} bytes over the I2C connection.", bytes.Length));
            }
        }

To wrap it all up, we have the DigitalRead function which is a little different because we have to send data and then immediately receive a response. We obtain a mutual-exclusion lock so that the send and receive process completes before any subsequent operations are run. Then we validate the received data and return the value for the pin that was read.

        /// 
        /// Reads the pin value
        /// 
        /// Expected byte data always has a length of 3
        ///     bytesReceived[0] - the number of bytes
        ///     bytesReceived[1] - the pin number
        ///     bytesReceived[2] - the pin value
        /// 
        /// GPIO.Value.High if the specified pin is HIGH, or GPIO.Value.Low if the specified pin is LOW.
        /// Pin.
        public GPIO.Value DigitalRead(int pin)
        {
            CheckFileDescriptor();

            // Build the command to send
            byte[] bytes = new byte[3];
            bytes[0] = (byte) bytes.Length;
            bytes[1] = (byte) Commands.DigitalRead;
            bytes[2] = (byte) pin;

            // The array to store received byte data
            byte[] bytesRecvd = new byte[MaxByteCount()];

            // Obtain lock for send/receive
            lock (lockObject)
            {
                // Send the DigitalRead command
                int numBytes = I2CSendBytes(fileDescriptor, bytes);
                if (numBytes != bytes.Length)
                {
                    throw new ExtendedIOException(string.Format("Could not send {0} bytes over the I2C connection.", bytes.Length));
                }

                // Read the response immediately after the command
                I2CReadBytes(fileDescriptor, bytesRecvd);
            }

            ValidateDigitalReadData(pin, bytesRecvd);

            return (GPIO.Value) bytesRecvd[2];
        }

You can obtain the full code listing for the C# class at https://gitlab.com/akinwale/NaniteIo/blob/master/Nanite/IO/I2CExtendedIO.cs.

We can put this all together and test with a simple interactive console application.

public static void Main (string[] args)
{
    // Extended IO via I2C
    using (I2CExtendedIO eio = new I2CExtendedIO())
    {
        try
        {
            eio.Open(0x08);

            string input;
            do
            {
                Console.Write("Enter PIN and value: ");
                input = Console.ReadLine();

                if (input == "-1")
                {
                    break;
                }

                string[] inputParts = input.Split(new char[] { ' ' });
                if (inputParts.Length != 2 && inputParts.Length != 3)
                {
                    Console.WriteLine("Invalid PIN and value. Try again.");
                    continue;
                }

                try
                {
                    int pin = int.Parse(inputParts[0].Trim());
                    string value = inputParts[1];
                    if (value != "off" && value != "on" && inputParts.Length == 2)
                    {
                        Console.WriteLine("Invalid PIN and value. Try again.");
                        continue;
                    }

                    if (inputParts.Length == 3)
                    {
                        int analogValue = int.Parse(inputParts[2].Trim());
                        if (value != "analog")
                        {
                            Console.WriteLine("Invalid PIN and value. Try again.");
                            continue;
                        }

                        eio.AnalogWrite(pin, analogValue);
                    }
                    else
                    {
                        eio.DigitalWrite(pin, "on" == value ? GPIO.Value.High : GPIO.Value.Low);
                    }
                }
                catch (FormatException)
                {
                    Console.WriteLine("Invalid PIN and value. Try again.");
                    continue;
                }
            } while (input != "-1");
        }
        catch (ExtendedIOException ex)
        {
            Console.WriteLine(ex.ToString());
        }
    }
}

The console application accepts inputs like 13 on, 18 off or 9 analog 172 which makes it easy to test the Arduino pins. Although this is practically a complete solution for most requirements with respect to controlling an Arduino connected to the PINE (or Pi or any other SBC) over I2C using C#, you could choose to implement an additional command for analogRead. All you would have to do is follow the logic for digitalRead and add the necessary code to the sketch and the C# class.

Extending GPIO with an Arduino connected to the PINE64 using I2C

Although the PINE64 provides quite a decent number of GPIO pins, there are several reasons that you may want to have access to more pins. For example, the Arduino can provide an extra number of native PWM pins, or you may want to implement low-level control of a robot using the Arduino, with high-level operations being handled by the PINE. This post will cover how this can be achieved with the PINE64 and an Arduino Mega. We’ll also create a sketch for the Mega which for handling I2C communication. In the next post, we will write some C and C# code which will show how to send and receive data between the PINE and the Mega. Note that this can be done with any single board computer that supports I2C including any of the Raspberry Pis, the Beagleboard and others.

PINE64 connected to Arduino Mega over I2C

I2C stands for Inter-Integrated Circuit and it is a serial computer bus that enables communication between multiple devices that support the protocol. Every board that supports I2C will have 2 pins called SDA (serial data line) and SCL (serial clock line).

Pins 3 and 5 of the Pi 2 pinout on the PINE64 are the SDA and SCL pins respectively. On the Mega, they are pins 20 and 21. Connect the SDA and SCL pins from the PINE64 to the SDA and SCL pins respectively on the Arduino. I have also connected the 5V from the PINE to the Mega in order for the Mega to be powered by the PINE. If you decide to take this approach, one of the ground pins also has to be connected between both boards.

A closer look at the I2C connection between the PINE64 and the Arduino Mega

Before connecting the Mega, we’ll need to create and upload a sketch that will assign an I2C address which will be used to access the device. The sketch will make use of the Wire library which will be used for I2C communication. We will be making use of byte arrays to send and receive data over the I2C bus. You can come up with a fancy protocol for this, but I came up with the following simple rules.

  • Maximum length of 16 bytes.
  • First byte will always be the length (inclusive of the first byte) of the data sent or received.
  • Second byte is the command. We’ll support 3 simple commands, digital write (0x01 or 1), digital read (0x02 or 2) and analog write (0x03 or 3).
  • Third byte is the pin number.
  • For digital write only, fourth byte be a value of either 1 (for high) or 0 (for low).
  • For analog write only, the next four bytes after the third byte will store an integer value between 0 and 255 inclusive.

With that out of the way, let’s take a look at the sketch. First things first, define our constants and variables.

#include 

// I2C slave address
#define I2C_ADDRESS 0x08

// Custom I2C data commands
#define CMD_DIGITAL_WRITE 0x01
#define CMD_DIGITAL_READ 0x02
#define CMD_ANALOG_WRITE 0x03

// Pin states
#define IO_PIN_STATE_INPUT 0x01
#define IO_PIN_STATE_OUTPUT 0x02

// Buffer for reading and writing data from/to I2C
unsigned char bytes[16];
unsigned char sendBytes[5];

int analogValue = 0;
int lastReadCommand = 0;
int lastReadPin = 0;
int thisPin = 0;
int ioPinStates[53]; // pseudo hashmap
const int ioPinCount = 40;
const int ioPins[] = {
  2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
  33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52
};

The code is straightforward. We define 0x08 as the I2C address that we want the Mega to use. We also define our commands, pin states (for digital read / write), buffers for storing data to be sent and received and other variables that will be used. The ioPins array is a list of all the pins available on the Mega. This will need to be changed to match the board that the sketch will be uploaded to. The ioPinStates is a pseudo hashmap which will map the pin number (used as the array index) to one of the defined pin states (IO_PIN_STATE_INPUT or IO_PIN_STATE_OUTPUT). We’re keeping track of the pin states so that we can activate the pins on demand, instead of activating them all at once in the setup() function.

void setup() {
  // put your setup code here, to run once:
  Wire.begin(I2C_ADDRESS);
  Serial.begin(9600);

  Wire.onReceive(onDataReceived);
  Wire.onRequest(onDataRequested);
}

void loop() {

}

bool isPinValid(int pin) {
  for (int i = 0; i < ioPinCount; i++) {
    if (ioPins[i] == pin) {
      return true;
    }
  }

  return false;
}

The setup function simply initialises the Wire library using the specified I2C address, and enables Serial output which will be used to output debug messages. Wire.onReceive registers the onDataReceived function which will be called when data is sent from the PINE64, while Wire.onRequest registers the onDataRequested function which will be called when the PINE64 requests data from the Mega. The isPinValid function is a helper method which checks if the pin specified as the parameter is valid for the board. It checks the pin against the ioPins array that we defined earlier.

Next is the onDataReceived function which handles most of the work. It accepts an argument which represents the number of bytes that were received.

void onDataReceived(int byteCount) {
  int i = 0;
  int idx = 0;

  while (Wire.available()) {
    bytes[idx] = Wire.read();
    
    if ((idx + 1) == bytes[0]) { // Length received
      int len = (int) bytes[0];
      if (len < 3) {
        Serial.print("Invalid data received.");
        return;
      }

The while loop checks if there is data available from the Wire library. If there is, the absolute minimum number of bytes received that can be considered valid based on the rules we defined earlier is 3 (length, command, pin). If the number of bytes received is less than 3, then the function ends at that point and output is written to the Serial console. The next step is to use a switch statement to check and handle the command that was received. The second byte (index 1) contains this data.

      switch (bytes[1]) {
        case CMD_DIGITAL_WRITE:
          if (len < 4) {
            Serial.println("Incomplete digital write data.");
            return;
          }
        
          thisPin = (int) bytes[2];
          if (!isPinValid(thisPin)) {
            Serial.println("Invalid pin specified for digital write.");
            return;
          }
      
          // do digital write
          if (ioPinStates[thisPin] != IO_PIN_STATE_OUTPUT) {
            pinMode(thisPin, OUTPUT);
            ioPinStates[thisPin] = IO_PIN_STATE_OUTPUT;
          }
          
          digitalWrite(thisPin, bytes[3]);
      
          break;

For digital write, the minimum number of bytes to be considered valid is 4 (length, command, pin, value). The function is terminated if we received less than 4 bytes for the command. The isPinValid is called to check if the pin received is valid, and if it isn't, the function ends at that point. Next thing to be done is to check if the pin has been activated. We make use of the ioPinStates array to do this making use of the pin number as the index. If the pin has not yet been activated (IO_PIN_STATE_OUTPUT), then we activate the pin using pinMode. Once this check is complete, we can call digitalWrite using the pin and the value specified.

        case CMD_DIGITAL_READ:
          if (len < 3) {
            Serial.println("Incomplete digital read data.");
            return;
          }
      
          thisPin = (int) bytes[2];
          if (!isPinValid(thisPin)) {
            Serial.println("Invalid pin specified for digital read.");
            return;
          }
      
          if (ioPinStates[thisPin] != IO_PIN_STATE_OUTPUT) {
            pinMode(thisPin, OUTPUT);
            ioPinStates[thisPin] = IO_PIN_STATE_OUTPUT;
          }
      
          lastReadCommand = CMD_DIGITAL_READ;
          lastReadPin = thisPin;
      
          break;

Digital read also follows the same set of steps as digital write (validate date length, validate pin, check pin state) but we will call the actual digitalRead function in onDataRequested. What we do here is store the command and the pin in variables (lastReadCommand and lastReadPin respectively) which we can then make use of in onDataRequested.

        case CMD_ANALOG_WRITE:
          if (len < 7) {
            Serial.println("Incomplete analog write data.");
            return;
          }
      
          thisPin = (int) bytes[2];
      
          if (!isPinValid(thisPin)) {
            Serial.println("Invalid pin specified for analog write.");
            return;
          }
      
          analogValue = 0;
          analogValue = (uint32_t) bytes[3] << 24;
          analogValue |= (uint32_t) bytes[4] << 16;
          analogValue |= (uint32_t) bytes[5] << 8;
          analogValue |= (uint32_t) bytes[6];
        
          if (analogValue < 0 || analogValue > 255) {
            Serial.println("Invalid value specified for analog write.");
            return;
          }
      
          analogWrite(thisPin, analogValue);
          
          break;

Similar to digital write, analog write follows a couple of steps (validate data length and validate pin). We don't need to check or set the pin state before calling the analogWrite function. We check that the value is between 0 and 255 inclusive before calling analogWrite with the pin and the value as the arguments.

        default:
          Serial.println("Unrecognised command.");
          break;
      }

      idx = 0;
    }
    
    idx++;
  }
}

If the data sent did not match any of the defined commands, the code falls back to the default statement which outputs Unrecognised command. to the serial command, and then the onDataReceived function will be called again when new data is received.

Finally, we have the onDataRequested function which makes use of lastReadCommand and lastReadPin. The function is straightforward, as it uses the Wire library to send data back to the PINE following our simple rules.

void onDataRequested() {
  switch (lastReadCommand)
  {
    case CMD_DIGITAL_READ:
        // Send the value over I2C
        sendBytes[0] = 3; // length
        sendBytes[1] = lastReadPin; // the pin that was read
        sendBytes[2] = digitalRead(lastReadPin); // the pin value (0/1)
        Wire.write(sendBytes, sendBytes[0]);
        break;
  }
}

And that's it! Compile the sketch using the Arduino IDE and then upload it to your board. Connect your Arduino to the PINE after the sketch is successfully uploaded, and boot up the PINE. You can obtain the full code listing for the sketch at https://gitlab.com/akinwale/nanitei2c/blob/master/nanitei2c_mega.ino.

Install i2c-tools using sudo apt-get install i2c-tools. By default, only root can use the I2C commands, but you can add the user account with useradd -G i2c ubuntu (replace ubuntu with the username that you want to use to access I2C). Reboot the PINE and then scan the I2C bus with the command, i2cdetect -y 1. You should get output should be similar to the following:

     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- 08 -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --

Based on this output, we can see that the Mega was recognised over the I2C bus with the configured address in our sketch (0x08 or 8). With this, we have access to the extra pins which we will be able to control directly from the PINE. That's pretty neat. In the next post, we will write the C and C# code for the PINE for handling I2C communication.

Control GPIO pins on the PINE64 with C#

Similar to the Raspberry Pi, GPIO pins on the PINE64 can be controlled through sysfs. You can refer to my previous post which goes into the concept in detail, and the C# code remains the same for the PINE64. However, with the longsleep Ubuntu image, root access is required to control the GPIO pins. You will need to grant the necessary permissions in order to be able to control GPIO pins as a normal user.

Granting user permissions
We’ll assume that we want to be able to control the pins as the default ubuntu user. Follow these steps to grant the necessary permissions.

  1. Create a user group called gpio.
    groupadd gpio
  2. Add the ubuntu user to the gpio group.
    useradd -G gpio ubuntu
  3. Add a udev rule to run chown on the sysfs files. The chown command will set group ownership to the gpio group. Adding the udev rule will run the chown command automatically whenever you export a pin. Create a file called 99-com.rules in /etc/dev/rules.d and paste the following contents.
    SUBSYSTEM=="gpio*", PROGRAM="/bin/sh -c 'chown -R root:gpio /sys/class/gpio && chmod -R 770 /sys/class/gpio; chown -R root:gpio /sys/devices/soc.0/1c20800.pinctrl/gpio && chmod -R 770 /sys/devices/soc.0/1c20800.pinctrl/gpio; chown -R root:gpio /sys/devices/soc.0/1f02c00.pinctrl/gpio && chmod -R 770 /sys/devices/soc.0/1f02c00.pinctrl/gpio'"

    Physical pin to GPIO number mapping
    I took some time to test the physical pins on the PINE in order to determine the sysfs gpio numbers and came up with this table. As an example, physical pin 22 on the Pi 2 pinout corresponds to /sys/class/gpio/gpio79.

    Pin #GPIO #
    Pi 2 pinout
    3227
    5226
    832
    1033
    1171
    1272
    13233
    1576
    1677
    1878
    1964
    2165
    2279
    2366
    2467
    26231
    27361
    28360
    29229
    31230
    3268
    3369
    3573
    3670
    3780
    3874
    4075
    Euler pinout
    7363
    10232
    1135
    1236
    1337
    1538
    1639
    18100
    1998
    2199
    22101
    2397
    2496
    26102
    2734
    28103
    2940
    3041
    Exp pinout
    2359
    740
    841

Measuring PINE64 Idle Power Consumption

I got a DROK digital multimeter and I decided to find out just how much power the PINE64 consumes running headless.

PINE64 connected to RAVPOWER powerbank through DROK multimeter

The measurements were taken on my 1GB PINE64 running longsleep’s Ubuntu image. The measured voltage from the powerbank is 5.06V.

CPU @ 1152MHzCPU @ 480MHz
WiFi / BT module plugged in260mA (1.32W)200mA (1.01W)
WiFi / BT module removed250mA (1.27W)190mA (0.96W)

This is not by any means a proper scientific test, but it gives an idea of what to expect. Got any tips for reducing power consumption? Feel free to share them in the comments below.

Building MonoDevelop for the PINE64

MonoDevelop running on the PINE64 using SSH X11 forwarding

4 Dec 2017 Update
These instructions will not work with the latest versions of Mono and MonoDevelop from github. I tried compiling from scratch on a new image, and they will work with the following versions listed below. You can checkout the specific tags immediately after cloning the corresponding repositories before you start building.
Mono 4.8.1.0 – git checkout tags/mono-4.8.1.0
MonoDevelop 6.1.0.5441 – git checkout tags/monodevelop-6.1.0.5441

My PINE64 is here and the first thing I decided to do was build MonoDevelop which I’ll use to manage C# projects, since most of the code I’ll be writing for my autonomous project will be in C#. I’m using the longsleep Ubuntu Xenial image as a base, so these instructions assume that this is what you have installed. You can adapt as required based on your distro.

Of course, the easiest way to get MonoDevelop installed is by using the package manager. The version is also fairly recent (4.2.1.102), so you can choose skip the rest of this post if you prefer. Simply run the apt-get install command and all required dependencies will also be automatically installed.

sudo apt-get install monodevelop

Most of the steps will be similar to the MonoDevelop for Raspberry Pi build post, but we’ll be skipping fsharp altogether. I cloned the fsharp repository but the make process failed due to the following error:

* Assertion at mini-arm64.c:936, condition `arm_is_bl_disp ((code), (target))' not met

F# is only required for the fsharpbindings extension and I don’t plan on using that. There appears to be an fsharp package which you can install using apt, but this will also install the mono 4.2.1 dependencies. If you’re fine with using an older version of mono and would still like to build MonoDevelop, then you can also skip the steps up till Build MonoDevelop.

So let’s get started!

Install all prerequisites
Git is required to clone the source repositories for Mono, MonoDevelop and dependencies. The other packages are required for building MonoDevelop dependencies from source.

sudo apt-get install autoconf automake build-essential cmake gettext git libgdiplus libglade2-dev libgnome2-dev libgnomecanvas2-dev libgnomeui-dev libgtk2.0-dev libssh2-1-dev libtool libtool-bin

 

Pre-build: NuGet certificates
The MonoDevelop build process makes use of NuGet at certain points. You will need to import certificates into your certificate store using the following commands.

sudo mozroots --import --sync
sudo certmgr -ssl -m https://nuget.org

 

Build Mono
This step is fairly straightforward. Clone the mono source repository and run the build process.

git clone https://github.com/mono/mono
./autogen.sh --prefix=/usr
make get-monolite-latest
make -j 4
sudo make install

This build will take a while. If you wish to run the mono and mcs test suites, you can do a make check before make install.
 

Build MonoDevelop dependencies
MonoDevelop requires gtk-sharp and gnome-sharp to be installed on the system. To build gtk-sharp.

git clone -b gtk-sharp-2-12-branch --single-branch https://github.com/mono/gtk-sharp
cd gtk-sharp
./bootstrap-2.12 --prefix=/usr
make -j 4
sudo make install

gnome-sharp follows a similar process.

git clone https://github.com/mono/gnome-sharp
cd gnome-sharp
./bootstrap-2.24 --prefix=/usr
make -j 4
sudo make install

Some reference PCL Assemblies are required for the build to complete. You will need to build a deb package and install following the instructions below.

git clone http://github.com/directhex/xamarin-referenceassemblies-pcl
cd xamarin-referenceassemblies-pcl/debian
nano control

Remove mono-xbuild from the list of dependencies in the control file, save and close. Then proceed with the following commands.

cd ..
debuild -i -us -uc -b
cd ..
sudo dpkg -i referenceassemblies-pcl_2014.04.14-1_all.deb

 

Build MonoDevelop
First, we clone the monodevelop repository and initialise the submodules using git.

git clone https://github.com/mono/monodevelop
cd monodevelop
git submodule update --init --recursive

Next, we remove references to fsharp. The assumed working directory for these steps is the top-level monodevelop source directory.

cd main
nano Makefile.am

Remove the external/fsharpbinding/MonoDevelop.FSharpBinding/FSharpBinding.addin.xml \ line, save the file and close.

rm -r external/fsharpbinding
nano configure.in

Comment out or remove the following lines in the file and save your changes. To comment out the lines, simply prefix each line with the # character.

if test "x$FSHARPC" = "x" ; then
  AC_MSG_ERROR([Can't find "fsharpc" in your PATH])
fi

Then we can go on to build the IDE.

cd ..
./configure --prefix=/usr --profile=stable
make -j 4
sudo make install

You can run mono main/.nuget/NuGet.exe update -self if you get the following error after running make.

Unable to find version '2.8.2' of package 'NuGet.CommandLine'.
Unable to find version '2.6.4' of package 'NUnit.Runners'.

Once the build is successfully completed, you can run the application using monodevelop. If you have X11 forwarding enabled for your SSH session, you should see the MonoDevelop IDE on your screen after a couple of seconds.

MonoDevelop running on the PINE64 using SSH X11 forwarding

MonoDevelop running on the PINE64 using SSH X11 forwarding

The PINE64 is finally here

1GB PINE64 with the WiFi module and RTC battery module installed. It's huge!

I received my PINE64 yesterday and I was pretty excited after such a long wait. The following items were in the package:

  • PINE64 1GB board
  • Camera module
  • RTC battery module (which is actually bigger than I expected)
  • WiFi/Bluetooth module

They were all in good shape, and the board was not bent nor warped which I was afraid would happen after reading a few horror stories. The Pine64 is most definitely a huge board (compared to the Raspberry Pi), and the build quality also seems solid. I was able to burn the longsleep Ubuntu Xenial image to a 32GB microSD card, and since I wanted to run the board as a headless device, I mounted the microSD on my Linux box and updated the /etc/network/interfaces file to connect to my wireless network. The connection to the network was established a few seconds after providing power to the board and I was able to SSH into the device after the boot process was successfully completed.

I’ll update this post with a few pictures once I get a decent camera.

Update

1GB PINE64 with the WiFi module and RTC battery module installed. It's huge!

1GB PINE64 with the WiFi module and RTC battery module installed. It’s huge!

PINE64 side-by-side with a Raspberry Pi 3 and the Keyestudio Mega clone

PINE64 side-by-side with a Raspberry Pi 3 and the Keyestudio Mega clone

Getting started with the Raspberry Pi 3

It’s taken quite a while for my PINE64 to arrive. Apparently, the shipping was delayed because the addon camera module was not ready yet. Quite disappointing, but I guess it’s to be expected since it’s a Kickstarter project. In the mean time, I decided to grab a Raspberry Pi 3 so that I could start off with my autonomous robot project.

I started off with the Raspbian Jessie Lite image which is a 292MB download (May 2016 version). Got it set up on a Sandisk 32GB microSD card and booted it up. I was planning to connect to it using a USB to TTL serial cable as I don’t have any USB peripherals available, nor an Ethernet cable. The plan was to configure the wireless connection so that I could SSH into it (and use X forwarding for GUI applications) once it booted. This did not go smoothly, and it took quite some time to figure out since a lot of the information online only applies to the earlier Pi models.

It turns out the default Raspbian image for the Pi 3 does not support serial connections out of the box due to the in-built Bluetooth module, so I had to make some adjustments to get this to work. Hence, this is sort of a beginner’s mini guide to working with a headless Raspberry Pi 3. The following instructions will require a Linux box.

So how do you get Pi 3 serial to work?
Note that these instructions are based on the May 2016 Raspbian Jessie Lite image. I mounted the SD card on my laptop’s Ubuntu installation, and had to chroot into it (following instructions at https://hblok.net/blog/posts/2014/02/06/chroot-to-arm/) to run a few updates. Inserting the SD card will create 2 mount points: the /mnt/boot/ partition and the main partition which we’ll refer to as /mnt/main/ (note that the path to the mount points may be different depending on your Linux distribution, so verify). After mounting, run the following commands.

Before you can chroot, you need to be able to run ARM binaries using qemu.

sudo apt-get install qemu-user-static
sudo cp /usr/bin/qemu-arm-static /mnt/main/usr/bin
sudo cp /etc/resolv.conf /mnt/main/etc/resolv.conf
sudo mount -o bind /mnt/boot /mnt/main/boot
sudo mount -o bind /dev /mnt/main/dev
sudo mount -o bind /proc /mnt/main/proc
sudo mount -o bind /sys /mnt/main/sys

Next, register the ARM executable format with the QEMU static binary.

echo ':arm:M::\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28\x00:\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff:/usr/bin/qemu-arm-static:' > /proc/sys/fs/binfmt_misc/register

Now, you can chroot into /mnt/main

sudo chroot /mnt/main

If you get an error stating that ‘/bin/bash’ was not found, you may have to run

sudo chroot /mnt/main qemu-arm-static /bin/bash

Once you’ve chrooted in, update the system.

apt-get update
apt-get upgrade
apt-get dist-upgrade

If you get an error along the lines of qemu: uncaught target signal 4 (Illegal instruction) - core dumped, edit /etc/ld.so.preload and comment out the lines in the file.

Next, you’ll need to install and run rpi-update.

apt-get install rpi-update
rpi-update

Once the update is completed, edit the /boot/config.txt file. Add these lines to the end of the file and save.

enable_uart=1
dtoverlay=pi3-miniuart-bt

Unmount the microSD card and insert it into your Pi. Connect the appropriate pins for your Pi using your USB-to-TTL serial cable and plug it into your host. Instructions for this can be found at http://workshop.raspberrypiaustralia.com/usb/ttl/connecting/2014/08/31/01-connecting-to-raspberry-pi-via-usb/. Note that if you’re going to use an external power source, you do not need to connect the 5V pin from the serial cable. Connecting to the 5V pin while an external power source is connected may damage your Pi, so be careful!

You should then be able to access your Pi using screen (or your preferred serial client). Note that /dev/ttyUSB0 is the port attached after the cable was connected. To find out what port your USB cable is attached to, you can run dmesg | tail after you connect the cable.

screen /dev/ttyUSB0 115200

If you see a blank screen, your Pi has probably already finished booting up, so just type your login username and press Enter. Alternatively, you can reboot your Pi (without disconnecting the USB cable from your host) and then you should be able to see the boot messages in the serial console before the login prompt is displayed.

Configure Pi 3 WiFi from the command line
After you’re logged in, you’ll need to configure your WLAN connection. Just edit /etc/wpa_supplicant/wpa_supplicant.conf and add the following lines replacing [networkssid] and [key] with the WiFi SSID and the access key respectively:

network={
    ssid="[networkssid]"
    psk="[key]"
}

Save the file and then run the following commands

sudo ifdown wlan0
sudo ifup wlan0

Next, check if the connection was successfully established. If you see inet addr after running the ifconfig command, then you’re connected to the network and you can SSH in (after raspi-config) from a different device on the network.

ifconfig wlan0

Enable I2C and SSH with raspi-config
With raspi-config, you can make a number of configuration changes to your Pi 3. Enabling SSH is required for remote access and I plan to use I2C to connect to an Arduino Mega in order to control the pins, so I2C has to be enabled as well. To enable both, launch raspi-config.

sudo raspi-config

Then select Advanced Options, and then enable the SSH and I2C options. You can also explore the other configuration settings and modify them to suit your needs.

What now?
That’s it! I will be writing about the software I’m installing on the Pi 3 relating to my autonomous robot project over the next few posts. I will also create posts related to the PINE64 once I have the board in my hands. Hopefully, very soon!

Shopping list for parts

In anticipation of my PINE64 board arriving, I have been trying to identify items I will need for my autonomous robot project (really should come up with a name for this). Since this is pretty much my first project and I don’t have an electronics background, there are no tools nor parts lying around and I have to start from scratch. Here’s an outline of items I will be purchasing over the course of the project with pricing on some of the components to provide a basic budget estimate.

Tools

  • Basic pliers
  • Drill
  • Screwdrivers
  • Soldering kit (soldering iron, lead-free solder)
  • Third arm

Hardware

Components

  • 32GB microSD card – Amazon $10.56 / Aliexpress $12.89
  • Dagu 4 channel motor controller for the Rover 5 Chassis – Amazon $21.95
  • Flash drive, hard drive or other external USB storage
  • LEDs and resistors
  • Keyestudio MEGA 2560 R3 (an Arduino MEGA 2560 clone board) – Amazon $14.99 / Aliexpress $11
    This provides additional GPIO pins which can be used for better control of the motors using the motor controller. The plan is to connect the Arduino clone board to the PINE64 using the I2C bus.
    Male-male, male-female and female-female jumper wires
  • PINE A64+ with 1GB DDR3 RAM – $19
  • RAVPower 16750mAh portable battery pack – Amazon $31.99
  • Solderless breadboard (for prototyping)
  • USB to TTL cable – Aliexpress $1.75

PINE64 Add-on modules

  • 5MP camera module – $15.99
  • Real time clock (RTC) battery module – $2.99
  • WiFi 802.11bgn / Bluetooth 4.0 module – $12.99