Hacking the Lego EV3
Summary
You will find here all information needed to hack the Lego EV3 Linux-based Brick. On this page, you will learn how to boot your EV3 on the SD card, how to disable Lego's virtual machine, how to use a serial console, how to enable more USB WiFi dongles, how to connect your EV3 to a WPA2 network using command line instructions and, last but not least, how to double the encoders accuracy from 360 increments per turn to 720 increments per turn. At the end, you will also find some benchmarks assessing the real-time capabilities of this platform.
Note: an alternate firmware for the EV3 called "EV3.14" implementing these hacks can be downloaded here.
Building your firmware from sources
When you boot with a micro-SD card into the slot, the bootloader (uboot) looks for a kernel image on it and tries to boot it. In this section, we will download the sources, build them and flash a SD card with the same firmware than the one running on the internal flash memory.
Preparing the environment
- Download the sources from the official repository:
git clone https://github.com/mindboards/ev3sources.git
- Modify the parent directory name so that the folder "open_first" can be accessed by the command cd ~/projects/lms2012/open_first.
- Download the Sourcery G++ Lite 2009q1-203 cross-compilation toolchain from [here]. Install it by running the downloaded file.
- Install additional packages needed for the compilation:
sudo apt-get install u-boot-tools doxygen imagemagick libmagickcore-dev
Building the firmware
- In ~/projects/lms2012/open_first, enter the following commands:
make lms2012 make modules make programs make kernel make u-boot make doc
All the compilations should go without error and minimal warnings.
Flashing a SD-card
- Edit the file ~/projects/lms2012/open_first/fdisk.cmd and add an empty line before +50M.
- Run the script ~/projects/lms2012/open_first/format_sdcard.sh.
- Follow the instructions.
- If everything went fluently you should obtain the following:
*** FORMAT SDCARD ********************************** TCP120706 *** !!! ALL DATA ON DRIVE WILL BE LOST !!! FORMAT "sdb" press enter or Ctrl-C to skip ? ....formatting.sdcard [sudo] password for jacques: ....making.kernel.partition ....making.filesystem.partition ....checking.partitions SUCCESS REMOVE sdcard ******************************************************************
- Remove and reinsert the SD-card.
- Run the script ~/projects/lms2012/open_first/update_sdcard.sh
- You should obtain something like this (you can safely ignore the warnings):
------------------------------------------------------------------------------- UPDATE SDCARD WITH NEWEST KERNEL, FILESYSTEM AND APPLICATION TCP120709 ------------------------------------------------------------------------------- ....checking.sdcard ....erasing.sdcard rm: impossible de supprimer «/media/LMS2012/*»: Aucun fichier ou dossier de ce type ....copying.kernel.to.sdcard ....copying.filesystem.to.sdcard ....copying.application.to.sdcard ....copying.testprograms.to.sdcard cp: impossible d'évaluer «/home/jacques/projects/lms2012/lmssrc/Test/Test»: Aucun fichier ou dossier de ce type ....writing.to.sdcard REMOVE sdcard -------------------------------------------------------------------------------
Booting on the SD-card
- Power off the EV3.
- Insert the SD-card into the EV3 slot.
- Power on the EV3.
- The bootloader will detect the presence of a firmware on the SD-card, boot the kernel on the first MSDOS partition, mount the second partition (ext3) as root directory and start the init scripts.
- You can activate the WiFi using the EV3 standard user interface (you need a WiFi dongle with an atheros chipset).
Customizing the firmware
The last step of the boot sequence is to run lms2012, the executable of Lego's virtual machine who's responsible for all the interactions between the user and the hardware. In this section, we will deactivate lms2012 and customize the boot sequence to automatically connect to a WiFi hotspot using wpa_supplicant.
Activating the serial console
The port 1 of the EV3 is at boot time a serial port that can be used to provide a very low-level console. It is the only mean to interact with U-boot, the boot loader. You can purchase a very convenient USB/Serial converter dedicated to the EV3 on Mindsensors' website.
The advantage of using a SD-card is that every modifications you make on the filesystem is permanent. You don't have to reflash all the firmware when making a tiny modification. You can either mount your SD-card on another Linux machine and directly copy, remove or edit files on the root filesystem or you can do the same directly on the EV3 with a telnet session (of course, this suppose that the network interface has been configured).
To open a telnet session:
telnet <ip address of the EV3> Trying <ip address of the EV3>... Connected to <ip address of the EV3>. Escape character is '^]'. _____ _ _ ___ | _ |_ _ _| |___| | __| | _| | | . | . | | _| |__|__|___|___|___|_|_| Rudolf 2011.01 EV3 login:
The login is root, and there is no password.
- To activate the serial console, either mount your SD-card into a Linux machine or telnet to the EV3.
- Edit the file /etc/inittab
- At the end of the file, add the following line:
s0:2345:respawn:/sbin/getty -L 115200 ttyS0
- Save the file.
- Remove the file /etc/rc5.d/S99lms in order to deactivate lms2012. If you don't do this, lms2012 will load kernel modules that will change the configuration of port 1 and you will lose the 115200 serial connection.
- Follow Xander's tutorial to configure auto-detection of the serial adapter on an Ubuntu system.
- Connect a serial console adapter to EV3's sensor port 1.
- Connect the USB side of the adapter to a Linux machine.
- On the Linux machine, enter the command:
screen /dev/ttyEV3 115200
- Reboot the EV3.
- On the Linux machine, you should now see all the booting messages ending with a login prompt.
- If you want to exit the screen session, simple enter:
[CRTL-a] [SHIFT-k]
Automatically connect to a WPA2 hotspot
The version of wpa_supplicant provided in the initial firmware is too old and has to be upgraded. I cross-compiled the last version of wpa_supplicant and I provide a binary version below. I guarantee that I did not insert any malware or spyware into these binaries. If you don't trust me, you can of course build your own version.
- Download the following archive.
- Extract it at the root of the filesystem:
tar -zxv -f <name of the archive>.tgz
- This will add the following files to your filesystem:
sbin/iwconfig sbin/iwevent sbin/iwgetid sbin/iwlist sbin/iwpriv sbin/iwspy sbin/ifrename usr/sbin/wpa_cli usr/sbin/wpa_cli.old usr/sbin/wpa_passphrase usr/sbin/wpa_passphrase.old usr/sbin/wpa_supplicant usr/sbin/wpa_supplicant.old usr/lib/libnl.a usr/lib/libnl.so usr/lib/libnl.so.1 usr/lib/libnl.so.1.1.4
- Edit /etc/wpa_supplicant.conf and make it look like in this example:
ap_scan=1 ctrl_interface=/var/run/wpa_supplicant network={ ssid="Put here the SSID of your hotspot. Don't forget the double quotes around." psk="Put here in clear text your password. Don't forget the double quotes around." key_mgmt=WPA-PSK proto=RSN group=CCMP }
- Edit /etc/network/interfaces and add these lines:
auto wlan0 iface wlan0 inet dhcp wpa-conf /etc/wpa_supplicant.conf
- Then, issue these commands:
root@EV3:~# ifdown wlan0 cfg80211: Calling CRDA to update world regulatory domain WPA: Terminating root@EV3:~# ifup wlan0 WPA: Configuring Interface Successfully initialized wpa_supplicant rfkill: Cannot open RFKILL control device udhcpc (v1.13.2) started Sending discover... Sending discover... Sending discover... No lease, forking to background
- It may require some time for udhcpc to find a lease. After some time, entering this command:
root@EV3:~# ifconfig lo Link encap:Local Loopback inet addr:127.0.0.1 Mask:255.0.0.0 UP LOOPBACK RUNNING MTU:16436 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) wlan0 Link encap:Ethernet HWaddr 44:94:FC:EE:07:E7 inet addr:192.168.1.3 Bcast:0.0.0.0 Mask:255.255.255.0 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:1051 errors:0 dropped:0 overruns:0 frame:0 TX packets:263 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:87432 (85.3 KiB) TX bytes:98052 (95.7 KiB)
- will show you that wlan0 finally obtained an ip address: inet addr:192.168.1.3. That means that your connection is operational.
Fixing some bugs
Automount bug
By default, automount mounts every external media. So it automounts both partitions of the SD-card even if they are already mounted as root filesystem. This yields a nasty mess. To prevent it, at the end of the file /etc/udev/mount.blacklist add the following lines:
/dev/mmcblk0p1 /dev/mmcblk0p2
Shutdown script bugs
- The symptoms in serial console when shutting down are:
/etc/rc6.d/K20psplash: .: line 9: can't open /etc/default/psplash Deconfiguring network interfaces... cfg80211: Calling CRDA to update world regulatory domain WPA: Terminating cat: can't open '/var/run/udhcpc.eth0.pid': No such file or directory ifconfig: SIOCGIFFLAGS: No such device [...] mount: can't find /mnt/ram in /etc/fstab or /etc/mtab
- Fix for the first bug (psplash is not used):
root@EV3:~# update-rc.d -f psplash remove update-rc.d: /etc/init.d/psplash exists during rc.d purge (continuing) Removing any system startup links for psplash ... /etc/rc0.d/K20psplash /etc/rc1.d/K20psplash /etc/rc6.d/K20psplash
- Fix for the second bug (unless you have an USB/ethernet adapter plugged in): comment the following 3 lines in /etc/network/interfaces.
auto eth0 iface eth0 inet dhcp pre-up /bin/grep -v -e "ip=[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+" /proc/cmdline > /dev/null
- Fix for the third bug: change /mnt/ram into /mnt/ramdisk in the file /etc/init.d/umountfs.
Support for rtl8192cu chipsets
The nano-size Edimax EW-7811Un WiFi USB dongle uses a Realtek rtl8192cu chipset which is not supported by default. To use this chipset with the EV3, we need first to cross-compile the driver from sources provided by Realtek. Here are the details of the process:
- Download the sources from here.
- Unzip the archive.
- Go to the directory driver.
- Extract the tarball and go into the extracted directory.
- Edit the Makefile and add the following lines (you have to say "y" to only one platform):
CONFIG_PLATFORM_ARM_TI_AM1808 = y [...] ifeq ($(CONFIG_PLATFORM_ARM_TI_AM1808), y) EXTRA_CFLAGS += -DCONFIG_LITTLE_ENDIAN -DCONFIG_MINIMAL_MEMORY_USAGE ARCH := arm CROSS_COMPILE := ~/CodeSourcery/Sourcery_G++_Lite/bin/arm-none-linux-gnueabi- KVER := 2.6.33 KSRC := ~/projects/extra/linux-03.20.00.13 MODULE_NAME := rtl8192cu endif
- Edit the file include/autoconf.h. Comment out the two lines shown below:
//#define CONFIG_DEBUG_RTL819X //#define CONFIG_PROC_DEBUG 1
- Simply enter make to start the compilation. If everything went smoothly, you should obtain a file rtl8192cu.ko in the current directory.
- Copy this file on the EV3 SD-card filesystem into the directory /lib/modules/2.6.33-rc4/kernel/drivers/net/wireless/rtl/. You need first to create the rtl directory in wireless.
- On the EV3 shell, issue the command depmod -a.
- Insert the rtl8192cu dongle into the EV3. If everything went smoothly, you should obtain these two lines when typing dmesg:
usb 1-1: new full speed USB device using ohci and address 2 usbcore: registered new interface driver rtl8192cu
- You can also trust me blindly (I swear I did not modify the sources to insert malwares) and download a precompiled rtl8192cu.ko.
Hacking Lego's PWM module
Lego's PWM module coded in the d_pwm.c file is responsible for the actuators' management:
- Counting the encoder signals,
- Controlling the PWM signals sent to the inverters,
- Some basic low-level control loops,
- Interface with the user space through standard POSIX calls.
Main data
- This module initializes two devices:
- /dev/lms_motor: interface with the encoder.
- /dev/lms_pwm: motor control.
- Main periodic loop is handled by Device1TimerInterrupt1. Its sampling frequency is 500Hz (macro SOFT_TIMER_MS = 2ms).
- The PWM signal frequency is 10kHz (macro MAX_PWM_CNT = 10000Hz).
Doubling the encoders resolution
This section explains how to easily double the encoders resolution form the standard 1 increment per degree to 2 increments per degree or 720 increments per turn. This hack has been checked in the extreme interrupt flooding situation where 4 motors runs at full speed and no adverse effects have been observed. The principle of this hack is simply to enable edge triggered interrupts on channel B GPIOs lines. This is possible on the AM1808 platform but was not possible on the older NXT platform. This hack was implemented by Maximilien Lesellier under the supervision of Jacques Gangloff. Please send all feedbacks to Jacques Gangloff.
Just follow the explanations below to enable this hack:
- Open the file ~/projects/lms2012/d_pwm/Linuxmod_AM1808/d_pwm.c
- Look for the following declarations in this file:
static irqreturn_t IntA (int irq, void * dev); static irqreturn_t IntB (int irq, void * dev); static irqreturn_t IntC (int irq, void * dev); static irqreturn_t IntD (int irq, void * dev);
- Below, add these new prototypes:
static irqreturn_t IntA1 (int irq, void * dev); static irqreturn_t IntB1 (int irq, void * dev); static irqreturn_t IntC1 (int irq, void * dev); static irqreturn_t IntD1 (int irq, void * dev);
- Just under the defines for IRQ[A,B,C,D]_PINNO, add the same kind of declarations for the pins 1:
#define IRQA_PINN1 ((pOutputPortPin[Hw])[(0 * OUTPUT_PORT_PINS) + DIR].Pin) #define IRQB_PINN1 ((pOutputPortPin[Hw])[(1 * OUTPUT_PORT_PINS) + DIR].Pin) #define IRQC_PINN1 ((pOutputPortPin[Hw])[(2 * OUTPUT_PORT_PINS) + DIR].Pin) #define IRQD_PINN1 ((pOutputPortPin[Hw])[(3 * OUTPUT_PORT_PINS) + DIR].Pin)
- Look for the following lines :
SetGpioRisingIrq(IRQA_PINNO, IntA); SetGpioRisingIrq(IRQB_PINNO, IntB); SetGpioRisingIrq(IRQC_PINNO, IntC); SetGpioRisingIrq(IRQD_PINNO, IntD);
- Add after them the following calls that enable edge triggered interrupts for the encoders B channels.
SetGpioRisingIrq(IRQA_PINN1, IntA1); SetGpioRisingIrq(IRQB_PINN1, IntB1); SetGpioRisingIrq(IRQC_PINN1, IntC1); SetGpioRisingIrq(IRQD_PINN1, IntD1);
- After that, it is necessary to modify the interrupts handler function so that it can use this new interrupt. For this, we need the following static variables:
static unsigned char Encoder [NO_OF_OUTPUT_PORTS]; //current value of the encoder's position static unsigned char Encoder_prev [NO_OF_OUTPUT_PORTS]; //last value of the encoder's position
- The goal of the handler function is to update the value of current encoder counter. The direction is determined by comparing the current channel A and B bit pattern with the previous one. Forward or backward direction is estimated form the + or - 90 degrees phase between the two channels. The following portion of code has been tuned to minimize execution time since it runs in an interrupt context. Tests show that there is no adverse effect on the system behavior even when all motors run at full speed.
static irqreturn_t IntA1 (int irq, void * dev) // channel B { Encoder[0]=(((READDirA)?1<<6:0)|(Encoder[0]&0b10111111)); if(Encoder[0]&0b10000000) { if(Encoder[0]&0b01000000) { if(Encoder_prev[0]&0b10000000) { Motor[0].Direction = BACKWARD; (Motor[0].IrqTacho)--; } else { Motor[0].Direction = FORWARD; (Motor[0].IrqTacho)++; } } else { if(Encoder_prev[0]&0b11000000) { Motor[0].Direction = FORWARD; (Motor[0].IrqTacho)++; } else { Motor[0].Direction = BACKWARD; (Motor[0].IrqTacho)--; } } } else { if(Encoder[0]&0b01000000) { if(Encoder_prev[0]&0b11000000) { Motor[0].Direction = BACKWARD; (Motor[0].IrqTacho)--; } else { Motor[0].Direction = FORWARD; (Motor[0].IrqTacho)++; } } else { if(Encoder_prev[0]&0b10000000) { Motor[0].Direction = FORWARD; (Motor[0].IrqTacho)++; } else { Motor[0].Direction = BACKWARD; (Motor[0].IrqTacho)--; } } } Encoder_prev[0]=Encoder[0]&0b11000000; return IRQ_HANDLED; }
- Note: the use of this modified d_pwm module is similar to the original one. It increments the same variable, the access to this value from user space still remains the same with the same POSIX calls.
- The whole modified d_pwm code is available on this page.
Benchmarks
Soft Real-time capabilities
Method
The goal is to test the accuracy of a periodic task running with POSIX standard mechanisms. A timer is initialized with the function timerfd_create(CLOCK_MONOTONIC,0)
. It is made periodic with the call timerfd_settime(fd,0,&itval,NULL)
. Then a blocking read(info->timer_fd,&missed,sizeof(missed))
of the timer file descriptor is released periodically. The resolution of the timer is the nanosecond. You can download the code on this page.
Results
- With WiFi activated, small load, 10000 iterations at 100Hz, output on telnet console:
[...] Iter=9990 Jitter=-255us Mean jitter=326us Max delay=745us Max advance=1328us Iter=9991 Jitter=-280us Mean jitter=326us Max delay=745us Max advance=1328us Iter=9992 Jitter=318us Mean jitter=326us Max delay=745us Max advance=1328us Iter=9993 Jitter=-802us Mean jitter=326us Max delay=745us Max advance=1328us Iter=9994 Jitter=-248us Mean jitter=326us Max delay=745us Max advance=1328us Iter=9995 Jitter=-263us Mean jitter=326us Max delay=745us Max advance=1328us Iter=9996 Jitter=-282us Mean jitter=326us Max delay=745us Max advance=1328us Iter=9997 Jitter=331us Mean jitter=326us Max delay=745us Max advance=1328us Iter=9998 Jitter=-842us Mean jitter=326us Max delay=745us Max advance=1328us Iter=9999 Jitter=-223us Mean jitter=326us Max delay=745us Max advance=1328us
- WiFi deactivated, small load, 10000 iterations at 100Hz, output on serial console:
[...] Iter=9990 Jitter=-214us Mean jitter=227us Max delay=36us Max advance=1133us Iter=9991 Jitter=-230us Mean jitter=227us Max delay=36us Max advance=1133us Iter=9992 Jitter=-229us Mean jitter=227us Max delay=36us Max advance=1133us Iter=9993 Jitter=-219us Mean jitter=227us Max delay=36us Max advance=1133us Iter=9994 Jitter=-238us Mean jitter=227us Max delay=36us Max advance=1133us Iter=9995 Jitter=-211us Mean jitter=227us Max delay=36us Max advance=1133us Iter=9996 Jitter=-228us Mean jitter=227us Max delay=36us Max advance=1133us Iter=9997 Jitter=-227us Mean jitter=227us Max delay=36us Max advance=1133us Iter=9998 Jitter=-227us Mean jitter=227us Max delay=36us Max advance=1133us Iter=9999 Jitter=-221us Mean jitter=227us Max delay=36us Max advance=1133us
- WiFi activated, high load (cat /dev/zero > /dev/null), 10000 iterations at 100Hz, output on serial console:
[...] Iter=9990 Jitter=-296us Mean jitter=294us Max delay=868us Max advance=1456us Iter=9991 Jitter=-287us Mean jitter=294us Max delay=868us Max advance=1456us Iter=9992 Jitter=-290us Mean jitter=294us Max delay=868us Max advance=1456us Iter=9993 Jitter=-295us Mean jitter=294us Max delay=868us Max advance=1456us Iter=9994 Jitter=-292us Mean jitter=294us Max delay=868us Max advance=1456us Iter=9995 Jitter=-286us Mean jitter=294us Max delay=868us Max advance=1456us Iter=9996 Jitter=-299us Mean jitter=294us Max delay=868us Max advance=1456us Iter=9997 Jitter=-286us Mean jitter=294us Max delay=868us Max advance=1456us Iter=9998 Jitter=-270us Mean jitter=294us Max delay=868us Max advance=1456us Iter=9999 Jitter=-314us Mean jitter=294us Max delay=868us Max advance=1456us
Conclusions
- Printing the messages on a telnet console yield additional jitters due to the WiFi transmission.
- When printing the messages on a serial console, the mean jitter is 227 microseconds under small cpu load and 294 microseconds under high cpu load (100%).
- Max delays and max advances are pretty high but also very rare. They occur especially at the beginning of the test.
- Max delays and max advances tend to increase with cpu load.
Lego actuators can be controlled optimally at a sampling rate of 200Hz. A higher sampling rate is an overkill since the electrical and mechanical bandwidth of the DC motors are pretty limited. At this frequency, the mean jitter of 300 microseconds in the worst case represents 6% of the period. Thus, it can be neglected and it should not have adverse effects on digital control loops.
CPU benchmark
Tests
- Results of nbench:
BYTEmark* Native Mode Benchmark ver. 2 (10/95) Index-split by Andrew D. Balsa (11/97) Linux/Unix* port by Uwe F. Mayer (12/96,11/97) TEST : Iterations/sec. : Old Index : New Index : : Pentium 90* : AMD K6/233* --------------------:------------------:-------------:------------ NUMERIC SORT : 73.832 : 1.89 : 0.62 STRING SORT : 5.7866 : 2.59 : 0.40 BITFIELD : 2.2722e+07 : 3.90 : 0.81 FP EMULATION : 17.474 : 8.38 : 1.93 FOURIER : 85.188 : 0.10 : 0.05 IDEA : 248.19 : 3.80 : 1.13 HUFFMAN : 131.18 : 3.64 : 1.16 LU DECOMPOSITION : 4.0031 : 0.21 : 0.15 ==========================ORIGINAL BYTEMARK RESULTS========================== INTEGER INDEX : 3.004 FLOATING-POINT INDEX: 0.272 Baseline (MSDOS*) : Pentium* 90, 256 KB L2-cache, Watcom* compiler 10.0 ==============================LINUX DATA BELOW=============================== CPU : L2 Cache : OS : Linux 2.6.33-rc4 C compiler : arm-none-linux-gnueabi-gcc libc : MEMORY INDEX : 0.688 INTEGER INDEX : 1.120 FLOATING-POINT INDEX: 0.201 Baseline (LINUX) : AMD K6/233*, 512 KB L2-cache, gcc 2.7.2.3, libc-5.4.38 * Trademarks are property of their respective holder.
- Comparison with respect to a raspberry pi model B not overclocked:
BYTEmark* Native Mode Benchmark ver. 2 (10/95) Index-split by Andrew D. Balsa (11/97) Linux/Unix* port by Uwe F. Mayer (12/96,11/97) TEST : Iterations/sec. : Old Index : New Index : : Pentium 90* : AMD K6/233* --------------------:------------------:-------------:------------ NUMERIC SORT : 218.24 : 5.60 : 1.84 STRING SORT : 30.674 : 13.71 : 2.12 BITFIELD : 8.3862e+07 : 14.39 : 3.00 FP EMULATION : 41.534 : 19.93 : 4.60 FOURIER : 2249.7 : 2.56 : 1.44 IDEA : 696.66 : 10.66 : 3.16 HUFFMAN : 430.19 : 11.93 : 3.81 LU DECOMPOSITION : 80.728 : 4.18 : 3.02 ==========================ORIGINAL BYTEMARK RESULTS========================== INTEGER INDEX : 11.587 FLOATING-POINT INDEX: 3.771 Baseline (MSDOS*) : Pentium* 90, 256 KB L2-cache, Watcom* compiler 10.0 ==============================LINUX DATA BELOW=============================== CPU : L2 Cache : OS : Linux 3.6.11+ C compiler : gcc version 4.6.3 (Debian 4.6.3-14+rpi1) libc : MEMORY INDEX : 2.550 INTEGER INDEX : 3.177 FLOATING-POINT INDEX: 2.092 Baseline (LINUX) : AMD K6/233*, 512 KB L2-cache, gcc 2.7.2.3, libc-5.4.38 * Trademarks are property of their respective holder.
Conclusions
- For integer calculations, the AM1808 platform has approximately 1/3 of the power of a Raspberry pi model B running at 700MHz.
- The STRING SORT test is a good indicator of memory performance. The EV3 exhibits poor performances for this test: only 1/6 of the RPI throughput.
- The ARM926EJ-S CPU of the EV3 has no FPU. Thus, tests making extensive floating-point calculations, like FOURRIER and LU DECOMPOSITION, are respectively 26 and 20 times slower than on the ARMv6 CPU of the Raspberry pi which has an FPU.
Links
- You can send your feedback by email to Jacques Gangloff.
- My other activities are visible (in French) on my personal webpage.
- Other pages related to low-cost robotics projects are listed here.