First, create your working directory :
$ mkdir $HOME/bbb_nunchuk $ cd $HOME/bbb_nunchuk
Then, clone the mainline Linux tree and fetch last stable version and build the kernel :
$ git clone https://github.com/RobertCNelson/bb-kernel $ cd bb-kernel $ git checkout origin/am33x-v4.1 -b tmp $ ./build_kernel.sh
First, connect the Beagle to your USB-TTL Serial adapter. Connect the ground wire to the pin closest to the power supply connector (let’s call it pin 1), TX to the pin 4 and RX to the pin 5.
Then, install picocom and add your user to dialout group as root and start serial communication :
$ sudo adduser $USER dialout $ picocom -b 115200 /dev/ttyUSB0
Now, reset your board. Press a key quickly in the picocom terminal to stop the U-boot countdown and You should then see the U-Boot prompt
The next step is to configure U-boot and your workstation to let your board download files, such as the kernel image and Device Tree Binary (DTB), using the TFTP protocol through an Ethernet cable. First, install TFTP server :
$ sudo apt-get install tftpd-hpa
The, configure your Ethernet iface via Network manager or via command-line :
$ nano /etc/network/interfaces
Choose your interface and write your configuration file, for this example, our interface is eth0 :
iface eth0 inet static
address 192.168.0.1
netmask 255.255.255.0
Now, it’s time to configure networking on U-Boot’s side. Back to the U-Boot command line, set the below environment variables:
setenv ipaddr 192.168.0.100 setenv serverip 192.168.0.1
And Save these settings to the eMMC storage on the board :
saveenv
It's time to test your TFTP server. Create a small text file in /var/lib/tftpboot :
$ touch /var/lib/tftpboot/textfile.txt $ echo "Test" > /var/lib/tftpboot/textfile.txt
Then, from U-Boot, do:
tftp 0x81000000 textfile.txt
The tftp command should have downloaded the textfile.txt file from your development workstation into the board’s memory at location 0x81000000 (this location is part of the board DRAM). You can verify that the download was successful by dumping the contents of the memory:
md 0x81000000
We are now ready to load and boot a Linux Kernel.
First, you have to download a root file system :
$ wget -c https://rcn-ee.com/rootfs/eewiki/barefs/debian-8.2-bare-armhf-2015-09-07.tar.xz
Then, extract FS from archive and rename it to nfs :
$ tar -xvf debian-8.2-minimal-armhf-2015-09-07.tar.xz $ tar -xvf debian-8.2-minimal-armhf-2015-09-07/armhf-rootfs-debian-jessie.tar $ mkdir nfs $ cp -fr debian-8.2-minimal-armhf-2015-09-07/armhf-rootfs-debian-jessie nfs
Your Root File System is ready !
By default, second I2C interface is disabled into Kernel from Robert Nelson but we want use it for our Nunchuk. Moreover, we have to write a little driver for our Nunchuk ! First, you have to edit arch/arm/boot/dts/am335x-boneblack.dts file and add few lines :
/* Do not add those lines. */ / { model = "TI AM335x BeagleBone Black"; compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx"; }; /* Copy &i2c1.... after this block */ &i2c1 { pinctrl-names = "default"; pinctrl-0 = <&i2c1_pins>; status = "okay"; clock-frequency = <0x186a0>; nunchuk: nunchuk@52 { model = "nunchuk"; compatible = "nintendo,nunchuk"; reg = <0x52>; }; i2c1_pins: pinmux_i2c1_pins { pinctrl-single,pins = < 0x158 (PIN_INPUT_PULLUP | MUX_MODE2) 0x15c (PIN_INPUT_PULLUP | MUX_MODE2) >; }; };
In this part, we tell to kernel that we want activate the second I2C and that there is a device named “nunchuk” who is registered as 0x52 on the I2C bus. It seems everything is okay, now you can compile your kernel !
In this part, we will see how to compile a linux kernel for an embedded system like BeagleBone Black. Go back to $HOME/bbb_nunchuk/bb-kernel/KERNEL and install a cross-compiling toolchain from Linaro, a very popular source for ARM toolchains :
$ sudo apt-get install gcc-arm-linux-gnueabi
Now, we will edit .config file. This file describes how your kernel will be compiled. You can set several options according to your needs. For our main mission, we have to specify some options. Edit the .config file, search keys and edit their values :
CONFIG_ROOT_NFS=y CONFIG_INPUT_POLLDEV=y CONFIG_INPUT_EVDEV=y
Now, we will compile your kernel :
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- -j4
Once compilation is finished, copy zImage and am335x-boneblack.dtb to /var/lib/tftpboot :
$ cp arch/arm/boot/zImage /var/lib/tftpboot/ $ cp arch/arm/boot/dts/am335x-boneblack.dtb /var/lib/tftpboot/
Now, you will setup the NFS server, first, install the nfs-kernel-server package. Once installed, edit the /etc/exports as root to add the folowing line :
/home/<user>/bbb_nunchuk/nfs/modules/nfsroot *(rw,no_root_squash,no_subtree_check)
Make sure that the path and the options are on the same line. Also make sure that there is no space between the * and the NFS options, otherwise default options will be used for this IP address, causing your root filesystem to be read-only. Then, restart the NFS server :
$ sudo /etc/init.d/nfs-kernel-server restart
First, boot the board to the U-Boot prompt. Before booting the kernel, we need to tell it which console to use and that the root filesystem should be mounted over NFS, by setting some kernel parameters.
Do this by setting U-boot’s bootargs environment variable (all in just one line, pay attention to the O character, like ”OMAP”, in ttyO0):
setenv bootargs root=/dev/nfs rw ip=192.168.0.100 console=ttyO0,115200n8 nfsroot=192.168.0.1:/home/<user>/bbb_nunchuk/nfs/modules/nfsroot saveenv
Now, download the kernel image and the device tree blob through tftp :
tftp 0x81000000 zImage tftp 0x82000000 am335x-boneblack.dtb
And now, boot your kernel:
bootz 0x81000000 - 0x82000000
If everything goes right, you should reach a login prompt !
Let's write Nunchuk driver as module ! First, you have to go to $HOME/bb-kernel/nfs/home/debian/ and create a directory named nunchuk then go to nunchuk. First, create a Makefile and a file named nunchuk.c. Fill your Makefile with folowings :
ifneq ($(KERNELRELEASE),) obj-m := nunchuk.o else KDIR := $(HOME)/bbb_nunchuk/bb-kernel/KERNEL all: $(MAKE) -C $(KDIR) M=$$PWD endif
Let's write our driver ! Edit your nunchuk.c file :
#include <linux/init.h> #include <linux/module.h> #include <linux/i2c.h> #include <linux/slab.h> #include <linux/delay.h> #include <linux/input.h> #include <linux/input-polldev.h> #include <linux/platform_device.h> struct nunchuk_dev { struct input_polled_dev *polled_input; struct i2c_client *i2c_client; }; static int handle_error(int ret) { printk("Nunchuk Error handled : %d", ret); return (0); } static char *nunchuk_read_registers(struct i2c_client *client, int size) { char buff[6] = {0x00}; int ret = 0; char *buffi; buffi = kmalloc(size, GFP_KERNEL); mdelay(10); if ((ret = i2c_master_send(client, buff, 1)) < 0) { handle_error(ret); return (NULL); } mdelay(10); if ((ret = i2c_master_recv(client, buffi, 6)) < 0) { handle_error(ret); return (NULL); } return (buffi); } void nunchuk_poll(struct input_polled_dev *dev) { int cPressed = 0, zPressed = 0; char *buff_ret; struct nunchuk_dev *nunchuk; char coordX, coordY; char ax, ay, az; nunchuk = dev->private; /* Read data provided by nunchuk */ buff_ret = nunchuk_read_registers(nunchuk->i2c_client, 6); zPressed = !((buff_ret[5] & 0x1)); cPressed = !((buff_ret[5] & 0x2) >> 1); coordX = buff_ret[0]; coordY = buff_ret[1]; ax = (buff_ret[2] << 2) & ((buff_ret[5] >> 2) & 0x3); ay = (buff_ret[3] << 2) & ((buff_ret[5] >> 4) & 0x3); az = (buff_ret[4] << 2) & ((buff_ret[5] >> 6) & 0x3); /* Notify new event */ input_event(nunchuk->polled_input->input, EV_KEY, BTN_Z, zPressed); input_event(nunchuk->polled_input->input, EV_KEY, BTN_C, cPressed); input_event(nunchuk->polled_input->input, EV_ABS, ABS_X, coordX); input_event(nunchuk->polled_input->input, EV_ABS, ABS_Y, coordY); input_event(nunchuk->polled_input->input, EV_ABS, ABS_RX, ax); input_event(nunchuk->polled_input->input, EV_ABS, ABS_RY, ax); input_event(nunchuk->polled_input->input, EV_ABS, ABS_RZ, ay); input_sync(nunchuk->polled_input->input); kfree(buff_ret); } static int nunchuk_probe(struct i2c_client *client, const struct i2c_device_id *id) { int ret = 0; char buff[] = {0xF0, 0x55}; struct input_polled_dev *polled_input; struct input_dev *input; struct nunchuk_dev *nunchuk; polled_input = input_allocate_polled_device(); if (!(nunchuk = devm_kzalloc(&client->dev, sizeof(struct nunchuk_dev), GFP_KERNEL))) { dev_err(&client->dev, "Failed to allocate memory\n"); return (-ENOMEM); } if ((ret = i2c_master_send(client, buff, 2)) < 0) //Send initialization frame to device return (handle_error(ret)); udelay(1000); /* Second initialization */ buff[0] = 0xFB; buff[1] = 0x00; if ((ret = i2c_master_send(client, buff, 2)) < 0) return (handle_error(ret)); nunchuk->i2c_client = client; nunchuk->polled_input = polled_input; polled_input->private = nunchuk; i2c_set_clientdata(client, nunchuk); input = polled_input->input; input->dev.parent = &(client->dev); input->name = "Wii Nunchuk"; input->id.bustype = BUS_I2C; /* Set input events for buttons */ set_bit(EV_KEY, input->evbit); set_bit(BTN_C, input->keybit); set_bit(BTN_Z, input->keybit); /* Set input events for joystick */ set_bit(EV_ABS, input->evbit); set_bit(ABS_X, input->keybit); set_bit(ABS_Y, input->keybit); set_bit(ABS_RX, input->keybit); set_bit(ABS_RY, input->keybit); set_bit(ABS_RZ, input->keybit); input_set_abs_params(input, ABS_X, 0, 255, 2, 4); input_set_abs_params(input, ABS_Y, 0, 255, 2, 4); input_set_abs_params(input, ABS_RX, 0, 0x3ff, 4, 8); input_set_abs_params(input, ABS_RY, 0, 0x3ff, 4, 8); input_set_abs_params(input, ABS_RZ, 0, 0x3ff, 4, 8); polled_input->poll = nunchuk_poll; polled_input->poll_interval = 50; if ((ret = input_register_polled_device(polled_input))) { pr_info("Error while registering polled_input.../n"); input_free_polled_device(polled_input); return (0); } return (0); } static int nunchuk_remove(struct i2c_client *client) { struct nunchuk_dev *nunchuk; nunchuk = i2c_get_clientdata(client); input_unregister_polled_device(nunchuk->polled_input); input_free_polled_device(nunchuk->polled_input); pr_info("Nunchuk Module removed\n"); return (0); } static const struct i2c_device_id nunchuk_id[] = { { "nunchuk", 0 }, { } }; MODULE_DEVICE_TABLE(i2c, nunchuk_id); static const struct of_device_id nunchuk_dt_ids[] = { { .compatible = "nitendo,nunchuk", }, //Allow to identify our nunchuk by it compatible string { } }; MODULE_DEVICE_TABLE(of, nunchuk_dt_ids); static struct i2c_driver nunchuk_driver = { .probe = nunchuk_probe, //Handler for device detection .remove = nunchuk_remove, //Handler for module deletion .id_table = nunchuk_id, .driver = { .name = "nunchuk", .owner = THIS_MODULE, .of_match_table = of_match_ptr(nunchuk_dt_ids), }, }; module_i2c_driver(nunchuk_driver); //Register our driver Kernel side MODULE_LICENSE("GPL");
Then, compile your module :
$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
Go to BBB side and go to /home/debian/nunchuk/ and insert your module :
# insmod nunchuk.ko
The module tells you how the nunchuk is identified (e.g /etc/input/event0).
Now, we will write a little program to read our input provided by our driver.
To communicate with our missile launcher, we have to use libusb. First, download latest libusb, on your workstation, go to $HOME/bbb_nunchuk/bb-kernel/nfs/home/debian/nunchuk :
$ wget -c http://netix.dl.sourceforge.net/project/libusb/libusb-1.0/libusb-1.0.9/libusb-1.0.9.tar.bz2 $ tar -jxvf libusb-1.0.9.tar.bz2
Once archive is extracted go to libusb-1.0.9/libusb and make the object files :
$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
Now, we can write the program :
#include <inttypes.h> #include <math.h> #include <stdio.h> #include <sys/time.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <termios.h> #include <linux/input.h> #include <libusb.h> #define _POSIX_C_SOURCE 200809L #define ML_VENDOR_ID 0x0416 #define ML_DEVICE_ID 0x9391 #define ML_ACTION_FIRE 0x10 #define ML_ACTION_MOVE_DOWN 0x1 #define ML_ACTION_MOVE_LEFT 0x8 #define ML_ACTION_MOVE_RIGHT 0x4 #define ML_ACTION_MOVE_UP 0x2 #define ML_ACTION_STOP 0x0 #define ML_TIME_FIRING 4300 #define ML_X_DEADZONE 15 #define ML_Y_DEADZONE 15 static struct libusb_device_handle *devh; int mlbin_init_usb(void) { libusb_device **list; libusb_device *device = NULL; int count, ret, i; ret = libusb_init(NULL); if (ret < 0) { perror(""); printf("Couldn't initialize libusb => %d.\n", ret); goto error; } count = libusb_get_device_list(NULL, &list); if (count < 0) { printf("Couldn't get device list\n"); goto list_error; } for (i = 0; i < count; i++) { //Browse USB Devices list to find Missile Launcher struct libusb_device_descriptor desc; device = list[i]; libusb_get_device_descriptor(device, &desc); printf("Found a new device : %x:%x\n", desc.idVendor, desc.idProduct); if (desc.idVendor == ML_VENDOR_ID && desc.idProduct == ML_DEVICE_ID) //Check Missile Launcher identity break; device = NULL; } if (!device) { printf("Couldn't find the device\n"); goto not_found_error; } ret = libusb_open(device, &devh); if (ret) { printf("Couldn't open device: %d\n", ret); goto open_dev_error; } libusb_detach_kernel_driver(devh, 0); ret = libusb_claim_interface(devh, 0); if (ret < 0) { printf("Couldn't claim the interface : %d.\n", ret); goto if_error; } libusb_free_device_list(list, count); printf("Interface setup.\n"); return 0; if_error: libusb_close(devh); detach_error: open_dev_error: not_found_error: libusb_free_device_list(list, count); list_error: libusb_exit(NULL); error: exit(1); } int mlbin_free_usb(void) { libusb_release_interface(devh, 0); libusb_close(devh); libusb_exit(NULL); return 0; } void send_data(unsigned char action) { unsigned char data[] = {0x5f, action, 0xe0, 0xff, 0xfe}; libusb_control_transfer(devh, 0x21, 0x09, 0, 0, data, 5, 300); } int startup(int ac, char **argv, int *fd, int *calibration, int *verbose) { int i = 1; if (ac < 2) { printf("Usage : %s <Input iface> [--calibration] [-v]\n", argv[0]); return (0); } if ((*fd = open(argv[1], O_RDWR | O_NOCTTY | O_NDELAY)) == -1) { printf("Unable to open %s\n", argv[1]); return (0); } while (i < ac) { if (!strcmp(argv[i], "--calibration")) *calibration = 1; else if (!strcmp(argv[i], "-v")) *verbose = 1; i++; } if (*calibration) { printf("Mode : Calibration\n"); printf("Press Nunchuk C button for start and exactly repress C when you hear second click\n"); } else { printf("Mode : Game\n"); printf("Change ML orientation with joystick\nLaunch missile one by one by clicking C button\nLaunch every missiles by pressing Z button\n"); } return (1); } void calibration_mode(struct input_event *evnt, int *fire, int *calibration) { //Calibration allows to synchronise missile launcher mechanism if (evnt->code == 306 && evnt->value && !(*fire)) { send_data(ML_ACTION_FIRE); *fire = 1; printf("Starting calibration ...\n"); } else if (evnt->code == 306 && evnt->value && *fire) { send_data(ML_ACTION_STOP); *fire = 0; printf("Calibration finished ...\n"); *calibration = 0; } } void game_mode(struct input_event evnt, int *fire, struct timeval *tv, int *fire_time, int *plus_time) { //Check every actions provided by input iface if (evnt.code == 306 && evnt.value) { //Z Button pressed if (*fire) { send_data(ML_ACTION_STOP); *fire = 0; } else { gettimeofday(tv, NULL); *fire_time = (tv->tv_sec) * 1000 + (tv->tv_usec) / 1000; //Get current time *plus_time = ML_TIME_FIRING; //Firing while 4,2s send_data(ML_ACTION_FIRE); *fire = 1; } } else if (evnt.code == 309 && evnt.value) { //C Button pressed if (*fire) { send_data(ML_ACTION_STOP); *fire = 0; } else { gettimeofday(tv, NULL); *fire_time = (tv->tv_sec) * 1000 + (tv->tv_usec) / 1000; send_data(ML_ACTION_FIRE); *plus_time = ML_TIME_FIRING * 3; //Same as one firing but while 12,6s for 3 missiles *fire = 1; } } else if (evnt.type == 3 && !evnt.code) { //X-axis Joystick moved if (evnt.value < (127 - ML_X_DEADZONE)) send_data(ML_ACTION_MOVE_LEFT); else if (evnt.value > (127 + ML_X_DEADZONE)) send_data(ML_ACTION_MOVE_RIGHT); else send_data(ML_ACTION_STOP); } else if (evnt.type == 3 && evnt.code) { //Y-axis Joystick moved if (evnt.value < (127 - ML_Y_DEADZONE)) send_data(ML_ACTION_MOVE_DOWN); else if (evnt.value > (127 + ML_Y_DEADZONE)) send_data(ML_ACTION_MOVE_UP); else send_data(ML_ACTION_STOP); } } void launch_app(int fd, int calibration, int verbose) { struct input_event evnt; int fire = 0, n; int fire_time = 0; struct timeval tv; int plus_time = 0; while (1) { gettimeofday(&tv, NULL); //Stop fire when ML fired one missile, based on time, 4,2 sec for one missile if (fire && ((tv.tv_sec) * 1000 + (tv.tv_usec) / 1000) >= fire_time + plus_time) { send_data(ML_ACTION_STOP); fire = 0; } n = read(fd, &evnt, sizeof(evnt)); //Read data received by ML if (n > 0) { if (calibration) calibration_mode(&evnt, &fire, &calibration); else { game_mode(evnt, &fire, &tv, &fire_time, &plus_time); if (verbose) //Debug Verbose printf("Readed : Type : %d - Code : %d - Value : %d\n", evnt.type, evnt.code, evnt.value); } } } } int main(int ac, char *argv[]) { int fd; int calibration = 0, verbose = 0; //Check usage, set calibration and verbose flags and open input device if (!startup(ac, argv, &fd, &calibration, &verbose)) return (0); mlbin_init_usb(); //LibUSB initialisation and search device send_data(ML_ACTION_STOP); launch_app(fd, calibration, verbose); //Launch main loop mlbin_free_usb(); close(fd); return (0); }
Then, compile your program and link it with libusb object files :
$ arm-linux-gnueabi-gcc --static nunchukbin.c ./libusb-1.0.9/libusb/*.o -I ./libusb-1.0.9/libusb -lpthread -lrt -o nunchuk
Now, you can go to BBB side and test your new program :
./nunchuk /dev/input/event1