After implementing the MLX90614 driver for NuttX I noticed that although the write command to change the device I2C address in the EEPROM was reporting success, after the power cycle the new address was not working. Worst: even the previous address was not working.
Basically the device appears damaged and didn’t respond to any I2C command.
Then doing my research I discovered a Melexis document about SMBus communication:
In this document I noticed they are using the address 0x00 instead of the default 0x5a address. After modifying the driver to use the 0x00 address the device accepted the new command.
There are many discussing about this subject on Arduino and similar forum:
Note that someone discovered that using a brute force CRC approach eventually will work and everybody decided to copy it. Too bad guys!
The MLX90614 uses CRC8-CCITT standard to check if data are correct, so any CRC8-CCITT implementation will work, for example this one: https://3dbrew.org/wiki/CRC-8-CCITT
Now on NuttX you can call the mlx90614 example application to change the device’s I2C address this way: (changing from default 0x5a to 0x3a)
nsh> mlx90614 0x3a
Address 0x3a stored on your device!
Please do a power cycle and update the I2C board address!
nsh>
I am creating a NuttX device driver for Melexis MLX90614 and it is a nice adventure.
First I downloaded the adafruit lib and tested it on Arduino Uno just to confirm the sensor was working, but I didn’t look the source code because I always prefer to read the datasheet and implement the driver from scratch.
All things fine, the mlxtest example worked correctly and I got the ambient temperature and the object temperature printed correctly.
Then I created the driver and decided to test it on STM32F103, but discovered that I2C_TRANSFER() for STM32F103 is broken. No problem, let to test it on STM32F4Discovery board:
“I haven’t checked your CRC implementation but there is a mistake in the MLX datasheet or at least it’s badly written. You have to include all the I2C frame’s data for the PEC’s calculation not just the replied data. For a read word command you have to include [SA_W, Command, SA_R, LSB, MSB] and for a write word command [SA_W, Command, LSB, MSB]. So, for their first example the calculation must be made on [ 0xB4, 0x07, 0xB5, 0xD2, 0x3A ] and not just on [ 0xD2, 0x3A ] and this way you get the expected 0x30.”
What this guy is saying is: we need to include all the bits of the Slave Address, the address needs to be seen as 8-bit including the the less significant bit Write or Read. So I2C Write at address 0x5A is seen as 0xB4 and I2C Read is seen as 0xB5 (pay attention at bits over the “5A” in the logic analyzer image above).
Today I tested NuttX running the LVGL demo on LPCXpresso54628.
Here you can find the steps needed to get it working.
First compile the firmware to create the nuttx.bin:
$ ./tools/configure.sh lpcxpresso-lpc54628/lvgl
$ make menuconfig
$ make
Now you can flash the firmware using JLinkExe on Linux.
You can use the LPC54608J512 even to flash the LPC54628:
$ sudo JLinkExe -if SWD -device LPC54608J512
SEGGER J-Link Commander V6.32h (Compiled Jul 5 2018 18:15:02)
DLL version V6.32h, compiled Jul 5 2018 18:14:58
Connecting to J-Link via USB...O.K.
Firmware: J-Link ARM V8 compiled Nov 28 2014 13:44:46
Hardware version: V8.00
S/N: 268006167
License(s): FlashBP, GDB
OEM: SEGGER-EDU
VTref=3.293V
Type "connect" to establish a target connection, '?' for help
Run “connect” command:
J-Link> connect
Specify target interface speed [kHz]. : 4000 kHz
Speed>
Device "LPC54608J512" selected.
Connecting to target via SWD
Found SW-DP with ID 0x2BA01477
Found SW-DP with ID 0x2BA01477
Scanning AP map to find all available APs
AP[1]: Stopped AP scan as end of AP map has been reached
AP[0]: AHB-AP (IDR: 0x24770011)
Iterating through AP map to find AHB-AP to use
AP[0]: Core found
AP[0]: AHB-AP ROM base: 0xE00FF000
CPUID register: 0x410FC241. Implementer code: 0x41 (ARM)
Found Cortex-M4 r0p1, Little endian.
FPUnit: 6 code (BP) slots and 2 literal slots
CoreSight components:
ROMTbl[0] @ E00FF000
ROMTbl[0][0]: E000E000, CID: B105E00D, PID: 000BB00C SCS-M7
ROMTbl[0][1]: E0001000, CID: B105E00D, PID: 003BB002 DWT
ROMTbl[0][2]: E0002000, CID: B105E00D, PID: 002BB003 FPB
ROMTbl[0][3]: E0000000, CID: B105E00D, PID: 003BB001 ITM
ROMTbl[0][4]: E0040000, CID: B105900D, PID: 000BB9A1 TPIU
ROMTbl[0][5]: E0041000, CID: B105900D, PID: 000BB925 ETM
Cortex-M4 identified.
Finally flash nuttx.bin using the loadbin command:
Now on minicom type: Ctrl + A – Y to select a file to paste.
It worked fine, see:
nsh> ls -l /mnt
/mnt:
-rw-rw-rw- 48 test.txt
nsh>
nsh> cat /mnt/test.txt
This is a small test!
Just to prove the idea!!!
Now I decided to send a bigger file (4KiB) :
I used the COPYING file of NuttX, just cutting it to 4KiB:
$ sudo dd if=COPYING of=/COPYING bs=1 count=4096
Then on minicom:
nsh> dd if=/dev/console of=/mnt/COPYING bs=64 count=64
Ctrl + A - Y
After selecting the file I noticed that the TXD LED on USB/Serial adapter was blinking at each 1 second. So after about 64 seconds it transfered the 4KiB file.
nsh> ls -l /mnt
/mnt:
-rw-rw-rw- 48 test.txt
-rw-rw-rw- 4096 COPYING
nsh> cat /mnt/COPYING
COPYING -- Describes the terms under which Nuttx is distributed. A
copy of the BSD-style licensing is included in this file. In my
words -- I believe that you should free to use NuttX in any
environment, private, private, commercial, open, closed, etc.
provided only that you repect the modest copyright notices as
...
uIP
^^^
Many lower-level networking components of NuttX derive from uIP which
has a similar BSD style
That is it! It is not the faster transfer solution but it proved to work.
I think this same approach should have worked on Linux at that time.
In this post I will explain how I did the HC-SR04 driver for NuttX.
I think this is the first time I try to explain how to create a driver for NuttX, oh wait! It is not! The first time was this post: NuttX driver to control a single LED
But this time it will be different, instead of just posting the code here, I will try to explain how I came with the driver. So, let to get started.
First I searched for the HC-SR04 datasheet, it was easy to find:
After sending the Trigger pulse we will receive a pulse in the Echo pin with the width encoding the measured distance.
Because we need to measure width of a pulse the first idea that came to my mind was to use the Input Capture of STM32 Timer (I’m using the STM32F103-Minimum board), but I want this driver to be generic, then I decided to use an ordinary GPIO interrupt pin of STM32.
I can setup STM32 to detect level changing (rising edge and falling edge), but some microcontrollers don’t support it, you need to select rising edge or falling edge, not both at same time. We need to work around it because I need this driver to be generic enough to work on these “poor man” MCUs.
Then to get it working I need to implement a ping-pong approach: first setup the GPIO pin to detect rising edge of signal and inside the ISR (Interrupt Service Routine or just interrupt handler) it needs to change the pin configuration to detect interruption in the falling edge.
So at hcsr04_int_handler I did it:
if (priv->rising)
{
/* Get the clock ticks from the free running timer */
priv->time_start_pulse = priv->config->get_clock(priv->config);
/* Now we need to wait for the falling edge interruption */
priv->rising = false;
priv->config->irq_setmode(priv->config, priv->rising);
priv->config->irq_enable(priv->config, true);
}
else
{
/* Get the clock ticks from the free running timer */
priv->time_finish_pulse = priv->config->get_clock(priv->config);
/* Disable interruptions */
priv->config->irq_enable(priv->config, false);
/* Convertion is done */
sem_post(&priv->conv_donesem);
}
So now you got the idea how it works, we just need to understand the magic under the hood. These functions irq_enable(), irq_setmode(), get_clock(), etc, are in fact “function pointers” to board specific functions:
The real functions are at “boards/arm/stm32/common/src/stm32_hcsr04.c”
This way we can create a generic abstraction and let it work with any board/microcontroller.
As you probably already figured-out, it is the irq_setmode() function that defines if the GPIO pin (connected to Echo pin of HC-SR04 module) will detect rising edge signal or falling edge signal:
Ok, we just need the “rising” variable to store the current edge mode, because the “falling” variable always will be the inverse. But let us to use both for didactic reason. Now irq_enable() just call stm32_gpiosetevent() passing these edge parameters and enabling it (passing the driver’s interrupt handler) or disabling it (passing a NULL).
Finally the get_clock() just returns the current clock tick of a free running timer configured to run at 1 microsecond resolution. I just need to convert the value of returned “timespec” variable “ts” to microseconds, multiplying tv_sec by 1 million (1 second has 1000000 us) and dividing tv_nsec by 1000 (1us = 1000 ns), see:
/* Return the current Free Running clock tick */
static int64_t hcsr04_get_clock(FAR const struct hcsr04_config_s *state)
{
/* Get the time from free running timer */
stm32_freerun_counter(&g_freerun, &ts);
/* Return time in microseconds */
return ((ts.tv_sec * 1000000) + (ts.tv_nsec / 1000));
}
So, I think you got the idea how this driver works. It is was easy to implement. You can read the complete code here: “nuttx/drivers/sensors/hc_sr04.c”
You can see this driver working on this video tutorial:
“I’m really not sure how Intel has convinced the Linux Foundation to promote Zephyr. If someone is looking for an operating system with Linux heritage, then it would something like NuttX or eCos in my view. On the other hand: more free and open source choices are good :-).”
$ arm-none-eabi-size nuttx
text data bss dec hex filename
15068 84 1676 16828 41bc nuttx
So, the NuttX’s helloworld is about 15KB.
Time to compile and compare Zephyr:
$ git clone https://gerrit.zephyrproject.org/r/zephyr zephyr-project
$ cd zephyr-project/
$ git checkout tags/v1.6.0
$ export ZEPHYR_BASE=/home/alan/zephyr-project
$ export ZEPHYR_GCC_VARIANT=gccarmemb
$ export GCCARMEMB_TOOLCHAIN_PATH=/usr
$ cd samples/hello_world/
$ make BOARD=frdm_k64f
make[1]: Entering directory '/home/alan/zephyr/zephyr-project'
make[2]: Entering directory '/home/alan/zephyr/zephyr-project/samples/hello_world/outdir/frdm_k64f'
GEN ./Makefile
scripts/kconfig/conf --silentoldconfig Kconfig
Using /home/alan/zephyr/zephyr-project as source for kernel
GEN ./Makefile
CHK include/generated/version.h
UPD include/generated/version.h
CHK misc/generated/configs.c
UPD misc/generated/configs.c
CHK include/generated/offsets.h
UPD include/generated/offsets.h
CHK misc/generated/sysgen/prj.mdef
UPD misc/generated/sysgen/prj.mdef
LD lib/iot/built-in.o
LD lib/libc/minimal/source/stdlib/built-in.o
CC lib/libc/minimal/source/stdout/fprintf.o
CC lib/libc/minimal/source/stdout/prf.o
CC lib/libc/minimal/source/stdout/sprintf.o
CC lib/libc/minimal/source/stdout/stdout_console.o
LD lib/libc/minimal/source/stdout/built-in.o
CC lib/libc/minimal/source/string/string.o
LD lib/libc/minimal/source/string/built-in.o
LD lib/libc/minimal/source/built-in.o
LD lib/libc/minimal/built-in.o
LD lib/libc/built-in.o
LD lib/built-in.o
CC misc/printk.o
LD misc/debug/built-in.o
CC misc/generated/configs.o
CC misc/generated/sysgen/kernel_main.o
LD misc/generated/sysgen/built-in.o
LD misc/generated/built-in.o
LD misc/built-in.o
LD net/built-in.o
LD boards/arm/frdm_k64f/built-in.o
LD boards/built-in.o
LD ext/fs/built-in.o
CC ext/hal/ksdk/devices/MK64F12/fsl_clock.o
LD ext/hal/ksdk/components/built-in.o
LD ext/hal/ksdk/drivers/built-in.o
LD ext/hal/ksdk/built-in.o
LD ext/hal/built-in.o
LD ext/lib/crypto/built-in.o
LD ext/lib/built-in.o
LD ext/built-in.o
LD subsys/built-in.o
LD tests/built-in.o
AS arch/arm/core/exc_exit.o
CC arch/arm/core/irq_init.o
AS arch/arm/core/swap.o
CC arch/arm/core/fault.o
CC arch/arm/core/irq_manage.o
CC arch/arm/core/thread.o
AS arch/arm/core/cpu_idle.o
AS arch/arm/core/fault_s.o
AS arch/arm/core/isr_wrapper.o
CC arch/arm/core/fatal.o
CC arch/arm/core/sys_fatal_error_handler.o
CC arch/arm/core/thread_abort.o
AS arch/arm/core/cortex_m/vector_table.o
AS arch/arm/core/cortex_m/reset.o
AS arch/arm/core/cortex_m/nmi_on_reset.o
CC arch/arm/core/cortex_m/prep_c.o
CC arch/arm/core/cortex_m/scs.o
CC arch/arm/core/cortex_m/scb.o
CC arch/arm/core/cortex_m/nmi.o
CC arch/arm/core/cortex_m/exc_manage.o
CC arch/arm/core/cortex_m/irq_vector_table.o
AS arch/arm/core/cortex_m/sw_isr_table.o
LD arch/arm/core/cortex_m/built-in.o
LD arch/arm/core/built-in.o
CC arch/arm/soc/nxp_kinetis/k6x/soc_config.o
CC arch/arm/soc/nxp_kinetis/k6x/soc.o
AS arch/arm/soc/nxp_kinetis/k6x/wdog.o
LD arch/arm/soc/nxp_kinetis/k6x/built-in.o
LD arch/arm/soc/nxp_kinetis/built-in.o
LD arch/arm/built-in.o
LD arch/built-in.o
CC drivers/console/uart_console.o
LD drivers/console/built-in.o
CC drivers/gpio/gpio_k64.o
LD drivers/gpio/built-in.o
LD drivers/interrupt_controller/built-in.o
CC drivers/pinmux/k64/pinmux.o
CC drivers/pinmux/k64/pinmux_board_frdm_k64f.o
LD drivers/pinmux/built-in.o
LD drivers/random/built-in.o
CC drivers/serial/uart_k20.o
LD drivers/serial/built-in.o
CC drivers/timer/cortex_m_systick.o
CC drivers/timer/sys_clock_init.o
LD drivers/timer/built-in.o
LD drivers/built-in.o
CC kernel/unified/version.o
LD kernel/unified/built-in.o
CC kernel/unified/alert.o
CC kernel/unified/device.o
CC kernel/unified/errno.o
CC kernel/unified/fifo.o
CC kernel/unified/idle.o
CC kernel/unified/init.o
CC kernel/unified/legacy_offload.o
CC kernel/unified/legacy_timer.o
CC kernel/unified/lifo.o
CC kernel/unified/mailbox.o
CC kernel/unified/mem_pool.o
CC kernel/unified/mem_slab.o
CC kernel/unified/msg_q.o
CC kernel/unified/mutex.o
CC kernel/unified/pipes.o
CC kernel/unified/sched.o
CC kernel/unified/sem.o
CC kernel/unified/stack.o
CC kernel/unified/sys_clock.o
CC kernel/unified/system_work_q.o
CC kernel/unified/thread.o
CC kernel/unified/thread_abort.o
CC kernel/unified/timer.o
CC kernel/unified/work_q.o
AR kernel/unified/lib.a
CC src/main.o
LD src/built-in.o
AR libzephyr.a
LINK zephyr.lnk
BIN zephyr.bin
make[2]: Leaving directory '/home/alan/zephyr/zephyr-project/samples/hello_world/outdir/frdm_k64f'
make[1]: Leaving directory '/home/alan/zephyr/zephyr-project'
And the size is:
$ arm-none-eabi-size outdir/frdm_k64f/zephyr.elf
text data bss dec hex filename
10226 400 7808 18434 4802 outdir/frdm_k64f/zephyr.elf
Then Zephyr’s size is about 10KB!
Maybe you could think: “Oh, NuttX is 50% bigger!”, but you are wrong here.
First: NuttX is a real POSIX RTOS, the helloworld example use the POSIX printf() function. Zephyr is not POSIX and it uses a limited/trimmed-down printk() function.
Second: as you saw NuttX compiled many more files and this simple helloworld includes File System support and the structure to support Device Drivers.
Third: this 15KB is only the initial overhead, probably NuttX increases more slowly than Zephyr because NuttX is very modular and its features are separated in many files.