nRFXX everywhere!

With Natalie, we are planning to test the nRF52 and potentially either fully switch to it and avoid having a different module (like nRF24) for communication for the case of my ornithopter. I am describing lightly the process I went through for the nRF52 because it's still ongoing and because Natalie did a much better job at recording every single detail, so look at her page if you want to know more.

Re-creating the nRF52 basic board

We had quite a few issues to reproduce the nRF52 basic board of Sam, mostly due to the not-aligned state of the BC832 pins, which led to the board having nice-looking traces, which would be valid, until we'd export the trace. The trace would have an invalid DPI that mods would interpret wrongly (or something else was messed up in that transfer). Anyway, we had to double the DPI from what Eagle gave, and it meant that some of the pins were too close, which meant fixing the trace manually with Gimp. We also encountered an issue with the SWD component we found from the Segger website. We edited it to remove the big no-go regions that prevented any trace to reach it. When tracing, don't forget to cut the holes before the outline (and so have two different files or check the order, which is likely wrong and leads to a bad board cut).


After tracing, I decided to remove the ground copper near the antenna region, this led me to remove the whole part under the chip because otherwise the board would not lay flat because of irregularities.

Eventually, we created and populated the board and went on with programming. Unfortunately for us, we did not know the provided sd-card was already all setup for openocd. We may have missed some information at some point, but there was not really anything about the sdcard having a different password or being already ready. Instead, we went straight with the instructions from the project page of Sam.

Problem 1: impossible to connect
There are many many different variants of tutorials for connecting to the Raspbian-based raspberry pi (including our raspberry pi zero) such as there, there or from Sam's tutorial. We went over everything as mentioned, but could not connect. The first problem was the usb cable we tried to do ssh over. By switching between cables, we found some that worked and some that didn't. On some computers it would connect (i.e. my mac laptop, my ubuntu desktop, but not the ubuntu from eds).

Problem 2: password not valid
For the first version of the sd-card, we understand now that the login/password pair was not the usual pi/raspberry. Unfortunately, we erased the sd-card with the original raspbian at the beginning, so we could have hoped that it'd work. But it did not. For some reason, we never managed to use a password to connect to the raspberry. Instead, the final solution was to mount the two ext4 volumes from Linux and edit the configuration of the openssh server to use public/private keys for authentication and not passwords. Then, we'd generate a key and introduce it on the partition in the repository of the pi user including in its authorized_key file, and that's it. We've effectively hacked the raspberry pi. This was way more complicated than it should be, but it eventually worked.

Installing OpenOCD from the tutorial was fine, except that you need to share your computer's connection to enable the pi to update and use apt-get to download the required dependencies for compilation. This went fine, but was quite long.

Finally, openocd was compiled and we tried to program Natalie's board first. The bootloader went there. We were happy!

We also tried with my board, but it hanged during communication. We inspected the voltages and found that the RX / TX lines were above the maximum limited of 3.7V. This was not an FTDI cable issue since it worked fine with Natalie's board repeatedly. The main lines at the bottom were slightly above 3V around 3.5V, but that would have worked. Unfortunately, the RX / TX lines were at 3.9V which seemingly disabled the board and made the bootloader programming impossible. I am assuming this is an issue with the lines being too short. Maybe the shorter, the less current can go through and thus the higher the voltage goes for any communication? The real solution is to retrace a new board with larger wires. But we went on with the real programming with Arduino.

We basically followed the instructions from this Adafruit BlueFruit tutorial that detail which libraries to install and what to do step by step. Make sure you install a recent enough arduino. Otherwise the Adafruit package cannot be loaded (I had 1.5, needed to be udpated!).

We then tested a basic hello world and that went fine. There's a trick for programming which is that you have to send the program from Arduino, and eventually trigger the reset manually. The timing is such that you must press just before (or slightly after) the beginning of the data being sent to the board. If you press too early or too late, it fails at programming.

We also tried the SPI example Jake made on the nRF52 tutorials and it went fine too!

We tried the BLE beacon example of Adafruit but that didn't work. It would load up to the BLE configuration and that would be stuck. Not sure what's causing the problem at this stage.

We also tried the UARTE example of Sam, but seemingly, it requires an udpated nRF52 framework (for the UARTE communication). This should be pretty easy to replace with a manual serial communication.

Lots of things still to be explored. However, given the time frame, I decided to go on with the nRF24 board.

For more details and images on the nRF52 board testing, go to Natalie's page.

Creating the nRF24 basic board

Eager to have the simplest board working, I decided to cut the example board (without checking for the existence of any code). Eventually, I realized that the programming part was not complete. The best I found was an initial attempt to bitbanging SPI communication with the nRF24 board from Neil. Most libraries online (notably the most cited one, RF24) tend to have specific pin assignments and none matched the serial to bluetooth board examplar board. I thus digged into the issue and realized that the main problem (beyond just switch pins in the code) was that these implementations used the hardware capacities of ATTiny44 for SPI communication, which meant the pins could not just be switched in software.

SPI can be done with software, but the class example is not finished and would require a lot of work to be integrated with RF24. Fortunately, there are Software SPI libraries, notably DigitalIO which was mainly designed for Arduino but supposedly has still pretty good speed. Fortunately again, the RF24 library already went to the extent of creating an implementation that uses it given issues 24 and 25. These gave the configuration needed to use any pin we have available and thus enable using the library with the example board. The steps are:

  1. Installing DigitalIO, including it (#include "DigitalIO.h") and setting correct compilation flags (for include and linking stages)
  2. Editing RF24_config.h to uncomment the line #define SOFTSPI, and set the corresponding pins values for SOFT_SPI_MISO_PIN, SOFT_SPI_MOSI_PIN and SOFT_SPI_SCK_PIN

However, there's a tiny problem which is program space. Since we're using the ATTiny44, we don't have much space and this program doesn't fit. It takes around 40% too much space.

avr-c++ -mmcu=attiny44 -Wall -Os -DF_CPU=8000000 -std=c++11 -DRADIO_NUM=0 -DARDUINO=150 -Wno-write-strings -I./ -I./DigitalIO/src/ -I./Arduino/hardware/arduino/avr/cores/arduino/ -I./Arduino/hardware/arduino/avr/variants/tiny14/ -I./RF24/ -o nrf24_basic.0.out nrf24_basic.cc Arduino/hardware/arduino/avr/cores/arduino/wiring.c Arduino/hardware/arduino/avr/cores/arduino/hooks.c Arduino/hardware/arduino/avr/cores/arduino/wiring_digital.c RF24/RF24.cpp 
avr-objcopy -O ihex nrf24_basic.0.out nrf24_basic.cc.hex;\
avr-size --mcu=attiny44 --format=avr nrf24_basic.0.out
AVR Memory Usage
----------------
Device: attiny44

Program:    5726 bytes (139.8% Full)
(.text + .data + .bootloader)

Data:         93 bytes (36.3% Full)
(.data + .bss + .noinit)
  

Now I look at the objdump result to see where the program takes space and try to trim that.

$ objdump -t nrf24_basic.0.out 

nrf24_basic.0.out:     file format elf32-little

SYMBOL TABLE:
00000000 l    d  .text00000000 .text
00800060 l    d  .data00000000 .data
00800092 l    d  .bss00000000 .bss
00000000 l    d  .comment00000000 .comment
00000000 l    d  .note.gnu.avr.deviceinfo00000000 .note.gnu.avr.deviceinfo
00000000 l    df *ABS*00000000 wiring_digital.c
0000003e l       *ABS*00000000 __SP_H__
0000003d l       *ABS*00000000 __SP_L__
0000003f l       *ABS*00000000 __SREG__
00000000 l       *ABS*00000000 __tmp_reg__
00000001 l       *ABS*00000000 __zero_reg__
00000000 l    df *ABS*00000000 RF24.cpp
0000003e l       *ABS*00000000 __SP_H__
0000003d l       *ABS*00000000 __SP_L__
0000003f l       *ABS*00000000 __SREG__
00000000 l       *ABS*00000000 __tmp_reg__
00000001 l       *ABS*00000000 __zero_reg__
00000071 l     O .text00000006 _ZL10child_pipe
0000006b l     O .text00000006 _ZL18child_payload_size
00000077 l     O .text00000006 _ZL17child_pipe_enable
0000005e l     O .text0000000d _ZZN4RF2412printDetailsEvE3__c
00000055 l     O .text00000009 _ZZN4RF2412printDetailsEvE3__c_1
00000000 l    df *ABS*00000000 nrf24_basic.cc
0000003e l       *ABS*00000000 __SP_H__
0000003d l       *ABS*00000000 __SP_L__
0000003f l       *ABS*00000000 __SREG__
00000000 l       *ABS*00000000 __tmp_reg__
00000001 l       *ABS*00000000 __zero_reg__
008000ac l     O .bss00000002 _ZZ12serial_writePVhhPcE5index
00800060 l     O .data00000011 _ZZ12serial_digithE6digits
00800071 l     O .data0000000c _ZL9addresses
000015cc l     F .text0000000e _GLOBAL__sub_I__Z12serial_flushv
00000000 l    df *ABS*00000000 _clear_bss.o
000000ac l       .text00000000 .do_clear_bss_start
000000aa l       .text00000000 .do_clear_bss_loop
00000000 l    df *ABS*00000000 wiring.c
0000003e l       *ABS*00000000 __SP_H__
0000003d l       *ABS*00000000 __SP_L__
0000003f l       *ABS*00000000 __SREG__
00000000 l       *ABS*00000000 __tmp_reg__
00000001 l       *ABS*00000000 __zero_reg__
008000b6 l     O .bss00000001 _ZL12timer0_fract
00000000 l    df *ABS*00000000 hooks.c
0000003e l       *ABS*00000000 __SP_H__
0000003d l       *ABS*00000000 __SP_L__
0000003f l       *ABS*00000000 __SREG__
00000000 l       *ABS*00000000 __tmp_reg__
00000001 l       *ABS*00000000 __zero_reg__
0000053a l     F .text00000002 __empty
00000000 l    df *ABS*00000000 _udivmodhi4.o
000015f8 l       .text00000000 __udivmodhi4_ep
000015ea l       .text00000000 __udivmodhi4_loop
00000000 l    df *ABS*00000000 _exit.o
0000162a l       .text00000000 __stop_program
000010ec g     F .text00000004 _ZN4RF2410get_statusEv
000006dc g     F .text00000066 _ZN4RF2413read_registerEhPhh
00000e1e g     F .text00000034 _ZN4RF2412setCRCLengthE16rf24_crclength_e
00000d82 g     F .text0000002c _ZN4RF2410setPALevelEh
008000b2 g     O .bss00000004 timer0_overflow_count
000008fa g     F .text00000040 _ZN4RF2414write_registerEhh
00001028 g     F .text00000056 _ZN4RF2414startListeningEv
0000053a  w    F .text00000002 yield
00000b98 g     F .text00000032 _ZN4RF2415setAddressWidthEh
0000004f g     O .text00000006 port_to_mode_PGM
000000ca  w      .text00000000 __vector_1
000012c0 g     F .text00000006 _ZN4RF249availableEv
0000114c g     F .text00000004 _ZN4RF245writeEPKvh
008000ae g     O .bss00000004 timer0_millis
000010f0 g     F .text00000024 _ZN4RF2412printDetailsEv
00000c9e g     F .text0000002e _ZN4RF2421enableDynamicPayloadsEv
0000081a g     F .text0000000a _ZN4RF2421isAckPayloadAvailableEv
000015e2 g       .text00000028 .hidden __udivmodhi4
00000022 g       .text00000000 __trampolines_start
000011b2 g     F .text00000004 _ZN4RF249writeFastEPKvh
00000834 g     F .text0000000a _ZN4RF2410getPALevelEv
0000162c g       .text00000000 _etext
000015de  w    F .text00000004 _ZN4RF2414endTransactionEv
000005f4 g     F .text00000038 digitalRead
00000d16 g     F .text00000018 _ZN4RF2416enableDynamicAckEv
000000ca  w      .text00000000 __vector_12
000011f2 g     F .text00000096 _ZN4RF249txStandByEmb
000000ca g       .text00000000 __bad_interrupt
000014ee g     F .text00000002 _ZN8SPIClass3endEv
00001616 g     F .text00000012 memcpy
008000b7  w    O .bss00000004 _ZZ11serial_putdImEvT_E6digits
0000165e g       *ABS*00000000 __data_load_end
00000376 g     F .text00000008 _Z14serial_writelnPc
00000e76 g     F .text0000008c _ZN4RF2413write_payloadEPKvhh
00001024 g     F .text00000004 _ZN4RF248flush_txEv
000000ca  w      .text00000000 __vector_6
0000069a g     F .text0000000c _ZN4RF2414setPayloadSizeEh
00000356 g     F .text0000000a _Z11serial_putch
00000022 g       .text00000000 __trampolines_end
00000654 g     F .text00000016 _ZN4RF242ceEb
000000ca  w      .text00000000 __vector_3
00000742 g     F .text00000080 _ZN4RF2422print_address_registerEPKchh
000005b4 g     F .text00000040 digitalWrite
00000332 g     F .text0000001a _Z11serial_readv
0000053c g     F .text00000078 pinMode
000014f2 g     F .text00000002 _ZN8SPIClass11setBitOrderEh
00000038 g     O .text0000000b digital_pin_to_port_PGM
0000162c g       *ABS*00000000 __data_load_start
00000080 g       .text00000000 __dtors_end
000012e8 g     F .text0000008c _ZN4RF2413writeBlockingEPKvhm
008000bd g       .bss00000000 __bss_end
00000a26 g     F .text00000070 _ZN4RF2415openWritingPipeEy
00000964 g     F .text0000002c _ZN4RF247powerUpEv
0000083e g     F .text0000001e _ZN4RF2411getDataRateEv
0000037e g     F .text00000094 __vector_11
00001374 g     F .text00000038 _ZN4RF2421getDynamicPayloadSizeEv
000002ca g     F .text00000058 _Z12serial_writePVhhPc
00000080  w      .text00000000 __init
000015da  w    F .text00000004 _ZN4RF2416beginTransactionEv
000000ca  w      .text00000000 __vector_13
00000946 g     F .text0000001e _ZN4RF249powerDownEv
00000670 g     F .text0000002a _ZN4RF24C2Ejj
0000148a g     F .text00000064 _ZN4RF2415writeAckPayloadEhPKvh
000000ca  w      .text00000000 __vector_7
00000a96 g     F .text0000002e _ZN4RF2415openWritingPipeEPKh
00000fee g     F .text00000032 _ZN4RF248spiTransEh
00000f02 g     F .text0000002e _ZN4RF2414startFastWriteEPKvhbb
00800093 g     O .bss00000019 radio
00001114 g     F .text00000038 _ZN4RF245writeEPKvhb
00000fda g     F .text00000014 _ZN4RF244readEPvh
000006ac g     F .text00000006 _ZN4RF2410isPVariantEv
000000a2 g       .text00000010 .hidden __do_clear_bss
000011b6 g     F .text0000003c _ZN4RF249txStandByEv
00810000 g       .comment00000000 __eeprom_end
0000002d g     O .text0000000b digital_pin_to_bit_mask_PGM
00000bca g     F .text0000009e _ZN4RF2415openReadingPipeEhPKh
0000085c g     F .text00000038 _ZN4RF2412getCRCLengthEv
00000ce8 g     F .text0000002e _ZN4RF2416enableAckPayloadEv
00000000 g       .text00000000 __vectors
000007fe g     F .text00000012 _ZN4RF2415isChipConnectedEv
00800092 g       .data00000000 __data_end
008000bb  w    O .bss00000002 _ZZ11serial_putdIiEvT_E6digits
00000000  w      .text00000000 __vector_default
000000ca  w      .text00000000 __vector_5
00001288 g     F .text00000038 _ZN4RF249availableEPh
00000670 g     F .text0000002a _ZN4RF24C1Ejj
00000ccc g     F .text0000001c _ZN4RF2422disableDynamicPayloadsEv
0000160a g       .text0000000c .hidden __tablejump2__
000004fc g     F .text0000003e init
00001020 g     F .text00000004 _ZN4RF248flush_rxEv
0000007e g       .text00000000 __ctors_start
0000008c g       .text00000016 .hidden __do_copy_data
000006b2 g     F .text00000018 _ZN8SPIClass5beginEv
0000062c g     F .text00000028 _ZN4RF243csnEb
00800092 g       .bss00000000 __bss_start
000014f6 g     F .text000000d6 main
00000e52 g     F .text00000018 _ZN4RF2410disableCRCEv
000014f4 g     F .text00000002 _ZN8SPIClass15setClockDividerEh
000000ca  w      .text00000000 __vector_4
0000066c g     F .text00000002 _ZN4RF2416print_observe_txEh
0000042a g     F .text0000004e micros
00000360 g     F .text0000000c _Z12serial_digith
0000034c g     F .text0000000a _Z10serial_putc
000000da g     F .text000000e6 _Z11serial_readPVhhPc
00800092 g     O .bss00000001 role
00000000  w      *ABS*00000000 __heap_end
000000ca  w      .text00000000 __vector_9
00000478 g     F .text00000072 delay
000000ca  w      .text00000000 __vector_2
000013ac g     F .text00000018 _ZN4RF2415toggle_featuresEv
000001c0 g     F .text0000010a _Z10serial_putPVhhc
00000990 g     F .text00000054 _ZN4RF247maskIRQEbbb
000014f0 g     F .text00000002 _ZN8SPIClass11setDataModeEh
00001150 g     F .text00000062 _ZN4RF249writeFastEPKvhb
000000ca  w      .text00000000 __vector_15
000006ca g     F .text00000012 _ZN8SPIClass8transferEh
00000328 g     F .text0000000a _Z11serial_readPc
00000049 g     O .text00000006 port_to_output_PGM
0000066e g     F .text00000002 _ZN4RF2419print_byte_registerEPKchh
0000082c g     F .text00000008 _ZN4RF247testRPDEv
000007c2 g     F .text00000038 _ZN4RF2413read_registerEh
00000322 g     F .text00000006 _Z12serial_beginv
00000080 g       .text00000000 __dtors_start
00000f54 g     F .text00000086 _ZN4RF2412read_payloadEPvh
00000080 g       .text00000000 __ctors_end
0000107e g     F .text0000006e _ZN4RF2413stopListeningEv
0000015f  w      *ABS*00000000 __stack
0000036c g     F .text0000000a _Z12serial_writePc
00000043 g     O .text00000006 port_to_input_PGM
00800092 g       .data00000000 _edata
00000824 g     F .text00000008 _ZN4RF2411testCarrierEv
008000bd g       .comment00000000 _end
000000ca  w      .text00000000 __vector_8
00000022 g     O .text0000000b digital_pin_to_timer_PGM
00000e6a g     F .text0000000c _ZN4RF2410setRetriesEhh
00000894 g     F .text00000066 _ZN4RF2414write_registerEhPKhh
00001628  w      .text00000000 .hidden exit
00000dae g     F .text00000070 _ZN4RF2411setDataRateE15rf24_datarate_e
00000810 g     F .text0000000a _ZN4RF2410rxFifoFullEv
00001628 g       .text00000000 .hidden _exit
000012c6 g     F .text00000022 _ZN4RF247reUseTXEv
000006a6 g     F .text00000006 _ZN4RF2414getPayloadSizeEv
000000ca  w      .text00000000 __vector_14
00000c68 g     F .text00000036 _ZN4RF2416closeReadingPipeEh
000000ca  w      .text00000000 __vector_10
000000cc g     F .text0000000e _Z12serial_flushv
0000066a g     F .text00000002 _ZN4RF2412print_statusEh
000000ca  w      .text00000000 __vector_16
00800060 g       .data00000000 __data_start
000013c4 g     F .text000000c6 _ZN4RF245beginEv
000007fa g     F .text00000004 _ZN4RF2410getChannelEv
00000d3c g     F .text00000046 _ZN4RF2410setAutoAckEhb
00000f30 g     F .text00000024 _ZN4RF2410startWriteEPKvhb
000009e4 g     F .text00000042 _ZN4RF2412whatHappenedERbS0_S0_
0000093a g     F .text0000000c _ZN4RF2410setChannelEh
00000412 g     F .text00000018 millis
00000d2e g     F .text0000000e _ZN4RF2410setAutoAckEb
00000ac4 g     F .text000000d4 _ZN4RF2415openReadingPipeEhy
000004ea g     F .text00000012 delayMicroseconds
000000b2 g       .text00000014 .hidden __do_global_ctors
  

And following this post, I added a few triggers to ask the linker to remove unused functions and data from the program object resulting in much less data being used! These corresponds to -fdata-sections -ffunction-sections -Wl,-gc-sections in the avr-c++ command below.

$ make -f nrf24_basic.cc.make RADIO_NUM=0
avr-c++ -mmcu=attiny44 -Wall -Os -DF_CPU=8000000 -std=c++11 -DRADIO_NUM=0 -DARDUINO=150 -Wno-write-strings -fdata-sections -ffunction-sections -Wl,-gc-sections -I./ -I./DigitalIO/src/ -I./Arduino/hardware/arduino/avr/cores/arduino/ -I./Arduino/hardware/arduino/avr/variants/tiny14/ -I./RF24/ -o nrf24_basic.0.out nrf24_basic.cc Arduino/hardware/arduino/avr/cores/arduino/wiring.c Arduino/hardware/arduino/avr/cores/arduino/hooks.c Arduino/hardware/arduino/avr/cores/arduino/wiring_digital.c RF24/RF24.cpp 
avr-objcopy -O ihex nrf24_basic.0.out nrf24_basic.cc.hex;\
avr-size --mcu=attiny44 --format=avr nrf24_basic.0.out
AVR Memory Usage
----------------
Device: attiny44

Program:    3270 bytes (79.8% Full)
(.text + .data + .bootloader)

Data:         92 bytes (35.9% Full)
(.data + .bss + .noinit)
    

The sending radio part is slightly more hungry since it requires more code, but it still fit (fortunately!).

$ make -f nrf24_basic.cc.make RADIO_NUM=1
avr-c++ -mmcu=attiny44 -Wall -Os -DF_CPU=8000000 -std=c++11 -DRADIO_NUM=1 -DARDUINO=150 -Wno-write-strings -fdata-sections -ffunction-sections -Wl,-gc-sections -I./ -I./DigitalIO/src/ -I./Arduino/hardware/arduino/avr/cores/arduino/ -I./Arduino/hardware/arduino/avr/variants/tiny14/ -I./RF24/ -o nrf24_basic.1.out nrf24_basic.cc Arduino/hardware/arduino/avr/cores/arduino/wiring.c Arduino/hardware/arduino/avr/cores/arduino/hooks.c Arduino/hardware/arduino/avr/cores/arduino/wiring_digital.c RF24/RF24.cpp 
avr-objcopy -O ihex nrf24_basic.1.out nrf24_basic.cc.hex;\
avr-size --mcu=attiny44 --format=avr nrf24_basic.1.out
AVR Memory Usage
----------------
Device: attiny44

Program:    3454 bytes (84.3% Full)
(.text + .data + .bootloader)

Data:        108 bytes (42.2% Full)
(.data + .bss + .noinit)

Trying to program, and nothing is output. I figure out which pins should be mapped where and change the GpioPinMap of DigitalIO to fit my board:

static const GpioPinMap_t GpioPinMap[] = {
  GPIO_PIN(A, 0),  // D0  = p0
  GPIO_PIN(A, 1),  // D1  = p1
  GPIO_PIN(A, 2),  // D2  = p2
  GPIO_PIN(A, 3),  // D3  = p3
  GPIO_PIN(A, 4),  // D4  = p4
  GPIO_PIN(A, 5),  // D5  = p5
  GPIO_PIN(A, 6),  // D6  = p6
  GPIO_PIN(A, 7),  // D7  = p7
  GPIO_PIN(B, 2),  // D8  = p8
  GPIO_PIN(B, 1),  // D9  = p9
  GPIO_PIN(B, 0)  //  D10 = p10
};

And I update the RF24 configuration to use the correct pins for software SPI MISO (0 = PA0), MOSI (9 = PB1) and SCK (10 = PB0). Still nothing. I thus start debugging with serial_write and figure out a few bugs in the serial code which I fixed, but more importantly realize that millis() and micros() always output 0, which makes all the Arduino-based code go crazy.

Looking at how millis() works, we find interesting things:

Thus I add init() before doing any time operation or delay stuff.

At this stage, the system would go in a loop but all radio operations would fail. Upon inspection of the signals between the ATTiny44 and the nRF24 board, the CE/CS pin are working as expected and sending what seems a reasonable stream of data. However, SPI is nowhere to be found. I digged further and realized that the macros of RF24 are such that using an ATTiny board excludes the case of software spi. Thus, I added a few cases to ensure that the ATTiny SPI work is not overwriting software spi usage. But now, compilation does load all the extra software SPI code, which results in too much space being used again!

$ make -f nrf24_basic.cc.make RADIO_NUM=1
avr-c++ -mmcu=attiny44 -DF_CPU=8000000 -DRADIO_NUM=1 -DARDUINO=150 -DSOFTSPI=1 -DSOFT_SPI_MISO_PIN=0 -DSOFT_SPI_MOSI_PIN=9 -DSOFT_SPI_SCK_PIN=10 -D_SPI=spi -DSPI_MODE=0 -Wall -Os -std=c++11 -Wno-write-strings -fdata-sections -ffunction-sections -Wl,-gc-sections -I./ -I./DigitalIO/src/ -I./Arduino/hardware/arduino/avr/cores/arduino/ -I./Arduino/hardware/arduino/avr/variants/tiny14/ -I./RF24/ -o nrf24_basic.1.out nrf24_basic.cc Arduino/hardware/arduino/avr/cores/arduino/wiring.c Arduino/hardware/arduino/avr/cores/arduino/hooks.c Arduino/hardware/arduino/avr/cores/arduino/wiring_digital.c RF24/RF24.cpp 
avr-objcopy -O ihex nrf24_basic.1.out nrf24_basic.cc.1.hex;\
avr-size --mcu=attiny44 --format=avr nrf24_basic.1.out
AVR Memory Usage
----------------
Device: attiny44

Program:    5260 bytes (128.4% Full)
(.text + .data + .bootloader)

Data:        182 bytes (71.1% Full)
(.data + .bss + .noinit)
    

I need to go down to 4096 bytes maximum. As a first attempt, I looked at inlining, but it does exactly the opposite. It makes things faster, but duplicates portions of uses more program space. For example, forcing the inlining of

inline void serial_writeln(char *str) __attribute__((always_inline));
inline void serial_writeln(char *str) {
  serial_write(str);
  serial_write("\r\n");
}

This ends up taking 16 extra bytes of program space. It's faster inlined, but we are not about speed, we are about space here (or maybe just give up and get a better IC with more space). By de-unrolling some code, I can get less space usage. For example the serial code can be made with a for loop to remove 180 bytes of space.

The catch with the case of the serial unrolled loop of Neil is that the timings were designed specifically for it. When reconstructing the simpler loop, we gain 180 bytes of data space, but the timing becomes wrong and communication stops working (i.e. I got garbage in the terminal console). I could have updated the timing, but these 180 bytes were not sufficient, so I kept the unrolled loop and looked for a better optimization.

Following the size tips from Atmel, I tried using PROGMEM to avoid having static strings in memory, but this didn't seem to help enough and I had sparse strings spread a bit everywhere.

Eventually, I looked at the SoftSPI implementation and realized that all functions were forced inline. Although for good accurate SPI communication, I am sure that inlining is important, I checked the change in memory and it is pretty big, because these are the main calls that are repeatedly used in the RF24 code. By removing the inlining, I go from 4830 bytes (117.9% Full) to 3496 bytes (85.4% Full). Hooray, we traded speed for space and got the program to fit the tight memory of the chip.

And we program both boards (the receiver takes less space, so no issue), and we start them and ... it works! It works! It works! It works! IT WORKS!

The first part is about testing SoftSPI (similarly working for MOSI, MISO and SCK):

The second part is about doing the actual communication between the two board. This is magical!

Important details

The key findings for this week were: