Simple ESP8266 blink example with NONOS SDK and custom Makefile

02 Dec 2019 - tsp
Last update 11 Dec 2019
Reading time 6 mins

This is a short introduction / example on how one can use the ESP8266 NONOS SDK to build applications using a custom Makefile (i.e. not using the nested Makefiles of espressifs SDK) to build one of the most simple microcontroller projects. Note that this already requires the presence of the SDK (one can - for example - use the setup procedure I’ve described earlier). The Makefile showed in this tutorial still uses the Python gen_appbin.py from espressif’s SDK. If one’s interested in how their build process really works I’ve started to collect some information about that.

If one wants to do fast and simple experiments with ESP8266 using a finished prototyping board like the NodeMCU Amica that’s based on the ESP-12E component board is a nice and fast solution (note: Link is an Amazon affilate link, this pages author profits from purchases)

What this code will do

It will blink an LED attached to GPIO PIN 4 every 500 milliseconds. It allows one to easily verify the build environment and upload mechanisms are working and provides a nice entry point for beginners to start experimenting with one of the most basic ESP8266 programs.

Project structure

The project consists of a single directory situated directly in the root of the SDK (that’s the way espressifs SDK tools are designed - using the custom makefile one can of course supply different paths - the only path hardcoded in this example is the call to gen_appbin.py).

The following files are required to compile this example:

First we’ll take a look at partitions.h. This header file contains the partition table that we’ll load into the firmware during the boot sequence. Note that this has to match the layout of your linker script and also the information that’s written by esptool during flashing (note that for example the SPI_FLASH_SIZE_MAP parameter gets adjusted by esptool during the flash process using an heuristic that determines flash size and layout).

#if ((SPI_FLASH_SIZE_MAP == 0) || (SPI_FLASH_SIZE_MAP == 1))
        #error "The flash map is not supported"
#elif (SPI_FLASH_SIZE_MAP == 2)
        #define SYSTEM_PARTITION_OTA_SIZE                       0x6A000
        #define SYSTEM_PARTITION_OTA_2_ADDR                     0x81000
        #define SYSTEM_PARTITION_RF_CAL_ADDR                    0xfb000
        #define SYSTEM_PARTITION_PHY_DATA_ADDR                  0xfc000
        #define SYSTEM_PARTITION_SYSTEM_PARAMETER_ADDR          0xfd000
#elif (SPI_FLASH_SIZE_MAP == 3)
        #define SYSTEM_PARTITION_OTA_SIZE                       0x6A000
        #define SYSTEM_PARTITION_OTA_2_ADDR                     0x81000
        #define SYSTEM_PARTITION_RF_CAL_ADDR                    0x1fb000
        #define SYSTEM_PARTITION_PHY_DATA_ADDR                  0x1fc000
        #define SYSTEM_PARTITION_SYSTEM_PARAMETER_ADDR          0x1fd000
#elif (SPI_FLASH_SIZE_MAP == 4)
        #define SYSTEM_PARTITION_OTA_SIZE                       0x6A000
        #define SYSTEM_PARTITION_OTA_2_ADDR                     0x81000
        #define SYSTEM_PARTITION_RF_CAL_ADDR                    0x3fb000
        #define SYSTEM_PARTITION_PHY_DATA_ADDR                  0x3fc000
        #define SYSTEM_PARTITION_SYSTEM_PARAMETER_ADDR          0x3fd000
#elif (SPI_FLASH_SIZE_MAP == 5)
        #define SYSTEM_PARTITION_OTA_SIZE                       0x6A000
        #define SYSTEM_PARTITION_OTA_2_ADDR                     0x101000
        #define SYSTEM_PARTITION_RF_CAL_ADDR                    0x1fb000
        #define SYSTEM_PARTITION_PHY_DATA_ADDR                  0x1fc000
        #define SYSTEM_PARTITION_SYSTEM_PARAMETER_ADDR          0x1fd000
#elif (SPI_FLASH_SIZE_MAP == 6)
        #define SYSTEM_PARTITION_OTA_SIZE                       0x06A000
        #define SYSTEM_PARTITION_OTA_2_ADDR                     0x101000
        #define SYSTEM_PARTITION_RF_CAL_ADDR                    0x3fb000
        #define SYSTEM_PARTITION_PHY_DATA_ADDR                  0x3fc000
        #define SYSTEM_PARTITION_SYSTEM_PARAMETER_ADDR          0x3fd000
#else
        #error "The flash map is not supported"
#endif

/*
    Non FOTA partition table
*/

#define EAGLE_FLASH_BIN_ADDR				(SYSTEM_PARTITION_CUSTOMER_BEGIN + 1)
#define EAGLE_IROM0TEXT_BIN_ADDR			(SYSTEM_PARTITION_CUSTOMER_BEGIN + 2)

static const partition_item_t partition_table[] = {
    { EAGLE_FLASH_BIN_ADDR, 	0x00000, 0x10000},
    { EAGLE_IROM0TEXT_BIN_ADDR, 0x10000, 0x60000},
    { SYSTEM_PARTITION_RF_CAL, SYSTEM_PARTITION_RF_CAL_ADDR, 0x1000},
    { SYSTEM_PARTITION_PHY_DATA, SYSTEM_PARTITION_PHY_DATA_ADDR, 0x1000},
    { SYSTEM_PARTITION_SYSTEM_PARAMETER,SYSTEM_PARTITION_SYSTEM_PARAMETER_ADDR, 0x3000},
};

The main code is situated in user_main.c:

#include "osapi.h"
#include "gpio.h"
#include "user_interface.h"

#include "partitions.h"

#define LED_PIN 4

static os_timer_t blinkTimer;
static int blinkState;

LOCAL void ICACHE_FLASH_ATTR blinkTimerCallback(void *arg) {
	blinkState = (blinkState == 0) ? 1 : 0;

	/* Set GPIO */
	gpio_output_set(
		(blinkState == 0) ? 0 : (1 << LED_PIN),
		(blinkState == 0) ? (1 << LED_PIN) : 0,
		(1 << LED_PIN),
		0
	);
}

void ICACHE_FLASH_ATTR
init_done(void)
{
	os_timer_disarm(&blinkTimer);
	os_timer_setfn(&blinkTimer, &blinkTimerCallback, (void*)0);
	os_timer_arm(&blinkTimer, 500, 1);
}

void ICACHE_FLASH_ATTR user_pre_init(void) {
	if(!system_partition_table_regist(partition_table, sizeof(partition_table)/sizeof(partition_table[0]),SPI_FLASH_SIZE_MAP)) {
		os_printf("system_partition_table_regist fail\r\n");
		for(;;) { }
	} else {
		os_printf("system_partition_table_regist success\r\n");
	}
}

void ICACHE_FLASH_ATTR
user_init(void)
{
	blinkState = 0;

	gpio_init();
	PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO4_U, FUNC_GPIO4); // Use GPIO4 as GPIO4.
	gpio_output_set((1 << LED_PIN), 0, (1 << LED_PIN), 0);

	system_init_done_cb(init_done);
}

As one can see the code simply sets up the GPIO matrix to map GPIO pin 4 to GPIO function 4 and registers an init-done callback. After initialization has finished the application initializes a timer to call blinkTimerCallback every 500 milliseconds and arms the timer for repeated execution. The timer callback just inverts blinkState and sets the GPIO pin.

In case the partition table is invalid the code will hang during startup and output debug information.

To build the code the following (expressive but way longer than required and less flexible than possible) Makefile is used:

OBJ_USER=user/user_main.o
OBJ_DRIVER=

CC=xtensa-lx106-elf-gcc
CC_COMPILE_OPTS=-c -Os -g -Wpointer-arith -Wundef -Wl,-EL -fno-inline-functions -nostdlib -mlongcalls -mtext-section-literals -ffunction-sections -fdata-sections -fno-builtin-printf -fno-guess-branch-probability -freorder-blocks-and-partition -fno-cse-follow-jumps -DICACHE_FLASH -DSPI_FLASH_SIZE_MAP=4 -I include -I ./ -I ../../include/ets -I ../include -I ../../include -I ../../include/eagle -I ../../driver_lib/include
CC_LINK_OPTS=  -L./ -L../lib -nostdlib -T../ld/eagle.app.v6.ld -Wl,--no-check-sections -Wl,--gc-sections -u call_user_start -Wl,-static

USER_OBJS=user/user_main.c

AR=xtensa-lx106-elf-ar
OBJDUMP=xtensa-lx106-elf-objdump
OBJCOPY=xtensa-lx106-elf-objcopy

ESPLIBS=-lc -lgcc -lhal -lphy -lpp -lnet80211 -llwip -lwpa -lcrypto -lmain -ljson -lupgrade -lssl -lpwm -lsmartconfig

all: nonfota

nonfota: eagle.app.flash.bin eagle.app.irom0text.bin

    @echo "eagle.flash.bin-------->0x00000"
    @echo "eagle.irom0text.bin---->0x10000"

user/%.o : user/%.c

    $(CC) $(CC_COMPILE_OPTS) -o $@ $<

driver/%.o : driver/*.c

    $(CC) $(CC_COMPILE_OPTS) -o $@ $<

libuser.a: $(OBJ_USER)

    $(AR) ru ./libuser.a $(OBJ_USER)

libdriver.a: $(OBJ_DRIVER)

    $(AR) ru ./libdriver.a $(OBJ_DRIVER)

eagle.app.v6.out: libuser.a libdriver.a

    $(CC) $(CC_LINK_OPTS) -Wl,--start-group $(ESPLIBS) libuser.a libdriver.a -Wl,--end-group -o eagle.app.v6.out

eagle.dump: eagle.app.v6.out

    $(OBJDUMP) -x -s eagle.app.v6.out > eagle.dump

eagle.S: eagle.app.v6.out

    $(OBJDUMP) -S eagle.app.v6.out > eagle.S

eagle.app.v6.text.bin: eagle.app.v6.out

    $(OBJCOPY) --only-section .text -O binary eagle.app.v6.out eagle.app.v6.text.bin

eagle.app.v6.data.bin: eagle.app.v6.out

    $(OBJCOPY) --only-section .data -O binary eagle.app.v6.out eagle.app.v6.data.bin

eagle.app.v6.rodata.bin: eagle.app.v6.out

    $(OBJCOPY) --only-section .rodata -O binary eagle.app.v6.out eagle.app.v6.rodata.bin

eagle.app.v6.irom0text: eagle.app.v6.out

    $(OBJCOPY) --only-section .irom0.text -O binary eagle.app.v6.out eagle.app.v6.irom0text.bin

eagle.app.flash.bin: eagle.app.v6.out eagle.app.v6.text.bin eagle.app.v6.data.bin eagle.app.v6.rodata.bin eagle.app.v6.irom0text

    env COMPILE=gcc python ../tools/gen_appbin.py eagle.app.v6.out 0 0 0 4 0

eagle.app.irom0text.bin: eagle.app.v6.irom0text.bin

    cp eagle.app.v6.irom0text.bin eagle.app.irom0text.bin

clean:

    @-rm -f eagle.app.v6.irom0text.bin
    @-rm -f eagle.app.v6.rodata.bin
    @-rm -f eagle.app.v6.data.bin
    @-rm -f eagle.app.v6.text.bin
    @-rm -f eagle.S
    @-rm -f eagle.dump
    @-rm -f eagle.app.v6.out
    @-rm -f libuser.a
    @-rm -f libdriver.a
    @-rm -f user/*.o
    @-rm -f driver/*.o

cleanall: clean

    @-rm -f eagle.app.flash.bin
    @-rm -f eagle.app.irom0text.bin

flash: nonfota

    esptool.py --port /dev/ttyU0 write_flash 0x00000 eagle.app.flash.bin 0x10000 eagle.app.irom0text.bin

.PHONY: all clean nonfota flash

Note that ESP8266 does always output debug information during boot (and if BAUD rate is not changed later on) with a BAUD rate of 74880. This is not supported by some tools (like for example screen). A safe way is to use miniterm.py that’s installed by py-serial when one deploys esptool.

miniterm.py --raw /dev/cuaU0 74880

This article is tagged: Electronics, ESP8285, ESP8266, ESP32, Tutorial


Data protection policy

Dipl.-Ing. Thomas Spielauer, Wien (webcomplains389t48957@tspi.at)

This webpage is also available via TOR at http://rh6v563nt2dnxd5h2vhhqkudmyvjaevgiv77c62xflas52d5omtkxuid.onion/

Valid HTML 4.01 Strict Powered by FreeBSD IPv6 support