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)
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.
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:
Makefile
runs the build process. Note that the Makefile could
be written way simpler but is written that way to stay clear and expressive.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.include/user_config.h
- that’s also an artifact
of the SDK template.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
Dipl.-Ing. Thomas Spielauer, Wien (webcomplains389t48957@tspi.at)
This webpage is also available via TOR at http://rh6v563nt2dnxd5h2vhhqkudmyvjaevgiv77c62xflas52d5omtkxuid.onion/