In this post we are going to set up the Arduino Uno to measure light intensity. Furthermore, we configure and recompile the Pandaboard’s kernel to communicate with the Arduino Uno through USB-serial.
For measuring light, we will require some kind of photoresistor. If you do not have any spare ones around, you can easily get them on ebay. A photoresistor is a resistor that changes its resistance depending on the current light intensity. In the end, we will simply measure an analogue voltage that changes depending on the attached photoresistor.
For this tutorial you will need:
- Pandaboard with Linaro’s Android build from Part I
- Arduino Uno
- 1 Photoresistor
- 1 Resistor with a value around 3 kilo-ohm (we are using 3.9kOhm)
- 3 jumper wire cables (male-male)
Lets get started with setting up the Arduino Uno
The following diagram shows the components and connections for interacting with the Arduino Uno. As can be seen, we will measure the voltage in between the photoresistor and the common resistor, as this terminal is connected to the Arduino’s analog in pin A0. At normal daylight, our photoresistor has a resistance of around 3kOhm (measured with a multimeter). So we choose the other resistor at around the same value of 3.9kOhm (this was just the closest resistor value we had lying around). This way, when there is normal light, the voltage at the A0 pin will be about half of 5V, so 2.5V, and yielding an ADC value of 128. If there is more light, the photoresistor’s resistance drops and the voltage at the terminal rises. And vice versa if there is less light.
So take out your breadboard and said components and try to rebuild the above diagram. If you do not have a photoresistor, you can also just attach a jumper cable to the A0 pin and measure different voltages such as the on-board 5V, 3.3V and GND pins.
We will of course also need some firmware running on the Arduino. The following code will measure the voltage at the A0 pin 10 times per second and send back the values over the serial communication interface. So build and upload the following source file:
Communication between the Arduino and your host computer
Once this is done, we can already try if everything works so far by connecting the Arduino to our host computer via USB. Right after connecting, run dmesg in a terminal. You should see something like new USB device, and the corresponding name, ttyACM0 in our case:
user@host:~$ dmesg ... [185930.828750] usb 1-2.4.2: new full-speed USB device number 21 using ehci_hcd [185930.924549] cdc_acm 1-2.4.2:1.0: ttyACM0: USB ACM device
Let’s have a look at the newly created device node on the host computer:
user@host:~$ ls -l /dev/ttyACM* crw-rw---- 1 root dialout 166, 0 Nov 28 17:38 /dev/ttyACM0
The Arduino shows itself as a device node with major number 166, which is used for USB ACM devices. You can double check that in the kernel documentation under /opt/android/source/kernel/Documentation/devices.txt. The actual ACM device is the small ATmega16u2 controller right next to the USB connector. We will use this device node for serial communication, so we need to adjust the permissions:
user@host:~$ sudo chmod 666 /dev/ttyACM0
Now there are many different ways to read from the serial console. I personally like to use hterm (you will need the ia32-libs package if your host is 64 Bit.. which should already be installed if you followed the guide in part 1). You can also use gtkterm, or even minicom. Our firmware on the Arduino simply writes the current ADC value (0…255) to the serial console. So if this output is interpreted as ASCII symbols, this will pretty much look like bogus. Some terminal programs such as hterm allow you to change the output type to hex or dec. Minicom does not have this functionality however. If you want to use a terminal program, just remember to use the following settings:
Port: /dev/ttyACM0 Speed: 9600baud Data-Parity-Stopbits: 8N1
In this tutorial we will use a small program which reads directly from the created serial device node.
Download the above source file, compile it with gcc and run it.
user@host:~$ gcc termios.c -o termios user@host:~$ ./termios read 136: 166 read 1: 157 read 1: 159 read 1: 160 read 1: 160 read 1: 158 ...
If you do not see any output, make sure the device node still has the appropriate permissions as set above (sudo chmod 666 /dev/ttyACM0), also the device node name /dev/ttyACM0 is hard-coded in this example. So if your device node is called ttyACM1 for example, make sure to adjust it in termios.c as well. If everything works, try to shade your photoresistor or brighten it up with a flash light. The values should update accordingly.
Configuring the Pandaboard to communicate with the Arduino
One can communicate between the Arduino and the Pandaboard pretty much in the same way. However, the current Pandaboard kernel does not have USB ACM support yet. So we need to reconfigure and recompile the kernel with this configuration enabled. With the new kernel running, a device node /dev/ttyACM0 will be created on the Pandaboard as soon as the Arduino is connected, just like on our host computer.
The current kernel configuration is called android_omap4_defconfig. We will use this config and add USB ACM support.
user@host:/opt/android/source/kernel$ cp arch/arm/configs/android_omap4_defconfig .config user@host:/opt/android/source/kernel$ make menuconfig ARCH=arm
Now navigate to Device drivers, USB support, and enable USB Modem (CDC ACM) support by pressing “Y”. Navigate back with Escape and save your changes. We will also save the new config for later:
user@host:/opt/android/source/kernel$ mv .config arch/arm/configs/android_omap4_cdc_defconfig user@host:/opt/android/source/kernel$ make clean user@host:/opt/android/source/kernel$ make mrproper
Our kernel was automatically built through the Android build system and we need to make adjustments so that from now on our modified kernel configuration is used.
user@host:/opt/android/source/kernel$ cd .. user@host:/opt/android/source$ gedit device/linaro/pandaboard/BoardConfig.mk
In the above BoardConfig.mk file, we replace all occurences of android_omap4_defconfig with our new config android_omap4_cdc_defconfig. Now we are ready to rebuild the kernel:
user@host:/opt/android/source$ . build/envsetup.sh user@host:/opt/android/source$ choosecombo 1 pandaboard 3 user@host:/opt/android/source$ make boottarball TARGET_TOOLS_PREFIX=../android-toolchain-eabi/bin/arm-linux-androideabi-
The new kernel will be found in the out folder and needs to be copied onto the SD card’s boot partition:
user@host:/opt/android/source$ cp out/target/product/pandaboard/boot/uImage /media/boot user@host:/opt/android/source$ sync user@host:/opt/android/source$ umount /media/*
Congratulations! You have just recompiled and deployed the kernel to support USB ACM devices. On the Pandaboard’s Android terminal, run ‘dmesg’ right after connecting the Arduino to your Pandaboard. You should see that a new device node /dev/ttyACM0 was created.
So what now? We have ACM support enabled on our Pandaboard’s kernel, now we need a user space program to actually read from the created device node. The cool thing is that we already have an existing command line program, the exact same as above – termios.c – which we used on the host computer. Earlier we used gcc to compile this program for the host computer, this time however we need to cross-compile the exact same program for our target, the Pandaboard, and thus compile it for an ARM architecture.
Again, there are multiple ways to do this. Here we will present a method that is very similar to the way that Android’s external projects are built. First of all download the Android Native Development Kit (Android NDK). Make sure that you choose the right version (Linux 64-bit). Extract the Android NDK anywhere you like, it’s also a good idea to add the installation directory to your PATH variable. Inside the extracted Android NDK folder, there are many good sample applications for using JNI and also a well-written documentation in html form. Be sure to check that out some time. For now, we are only going to use the ‘ndk-build’ executable inside the Android NDK top folder. When calling the ‘ndk-build’ executable from the command line, it will look for a sub-folder called ‘jni’ (relative to the current working directory), and within this folder it will parse a file named Android.mk. The reason for that is that ndk-build is normally used in conjunction with a Java/Android project (just like the ones created with Eclipse ADT Bundle), and all native C code inside an Android project source folder is placed in a sub-folder called jni. Inside this jni folder there should be at least said Android.mk file and also of course the native source file, termios.c in our case.
user@host:~$ mkdir termios user@host:~$ cd termios user@host:~/termios$ mkdir jni user@host:~/termios$ cd jni user@host:~/termios/jni$ gedit termios.c user@host:~/termios/jni$ gedit Android.mk
The contents for termios.c can again be downloaded here. The contents for the Android.mk file should be :
LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES:= termios.c LOCAL_MODULE := termios include $(BUILD_EXECUTABLE)
(compare this to /opt/android/source/external/ping/Android.mk)
Within the created termios folder call ‘ndk-build’
This will cross-compile the program termios.c as an executable for our ARM target architecture and save it under termios/libs/armeabi/termios. Use the Android Debug Bridge (adb) to push this file to the target, and then execute it on the Pandaboard. You should see a similar output as on the host computer.
user@host:~/termios$ adb push libs/armeabi/termios /termios user@host:~/termios$ adb shell root@android:/# chmod 777 termios root@android:/# ./termios
In this tutorial’s next part we will show you how to create a JNI wrapper function and thus how to use JNI to communicate with external hardware from within an Android Java application.