Simple ESP8266 blink example with NONOS SDK and custom Makefile
02 Dec 2019 - tsp
Last update 11 Dec 2019
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:
- The
Makefile
runs the build process. Note that the Makefile could
be written way simpler but is written that way to stay clear and expressive.
- The
driver
directory exists only for historical reason. It might
include additional code to provide some logical separation but doesnāt really
serve any important purpose or different to the traditional user
directory.
include/partitions.h
includes all descriptors to describe the partitions
used by this application.
- One might encounter
include/user_config.h
- thatās also an artifact
of the SDK template.
- Then there is
user/user_main.c
(thatās only at that path also for
historical reasons - the SDK template puts it there) that contains
all of the magical code.
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