Introduction: How to Write ESP8266 Firmware From Scratch (using ESP Bare Metal SDK and C Language)
Espressif’s ESP8266 had quite an evolution. Some may even call it controversial. It all started with ESP8266 being a WiFi module with a basic UART interface. But later it became clear that it’s powerful enough for embedded system. It’s essentially a module that can be used for running full-fledged applications.
Espressif realised this as well and released an SDK. As first versions go, it was full of bugs but since has become significantly better. Another SDK was released which offered FreeRTOS ported to ESP. Here, I want to talk about the non-OS version. Of course, there are third-party firmwares which offer support for script language to simplify development (just google for these), but ESP8266 is still a microchip (emphasis on MICRO) and using script language might be overkill. So what we are going to come back to is the ESP SDK and bare C. You’ll be surprised, it’s easier than it looks!
Step 1: First Steps
To develop firmware you’ll need:
1. ESP8266 connected to your computer via USB.
There are a lot of articles how to connect an ESP to a computer. You will need several dupont cables and a UART-to-USB adapter. If you have a Arduino board you can use it as UART-to-USB. Google “connect esp8266 to computer” - there are a lot of articles about this.
2. SDK.
I suggest using this one: https://github.com/pfalcon/esp-open-sdk Download it and follow its readme to build. There is nothing extraordinary in this process, all you need is to install prerequisites and invoke “make”. In general, this SDK is intended for *nix systems, but there is port for Windows as well.
In short, to start development you should have an ESP device available as /dev/ttyUSB0 (/dev/ttyACM0 is you use Arduino or COMn in Windows) and the SDK installed in a certain path.
Step 2: Main()
In C `int main()` is an entry point to a program. But, in the case of ESP the entry point is `void user_init()`. This function must be used only for initialisation, not for long-running logic.
Here an example:
void sdk_init_done_cb(void) {
/* You app Initialization here */
}
void user_init() {
system_init_done_cb(sdk_init_done_cb);
}
Note, that all we do in user_init is calling the system_init_done_cb API function. This function accepts one parameter, which is a pointer to function which will be called once all system modules will be properly initialised. You can put your initialisation code in user_init too, but you can face problems with some system function (like WiFi), just because appropriate modules aren’t initialised yet. Thus, it is better to use system_init_done_cb and perform initialisation in the callback function.
Beware of the dog
ESP8266 has a watchdog functionality. And there is NO documented API to rule it (there is some undocumented stuff, but out of scope for this tutorial). Its timeout is 1 second.
What does that mean? It means, that you have to return the control flow to system every second, otherwise the device will be rebooted. This code leads to the device reboot:
void sdk_init_done_cb(void)
{ while(1); /* or any long operation */
}
In general, watchdog is not evil, it helps if the program hangs. And, 1 second is not so small as it sounds. Just keep this fact in mind.
Step 3: Doing Something
Taking what we learnt about the watchdog into account, we face an obvious question: where can I run my tasks?
The simplest answer is in timers. The timer API is very simple in ESP.
os_timer_t start_timer; /* declare a variable which will be used to control the timer */
void start_timer_cb(void *arg) { /* callback function for timer */
/* Our code here, remember about watchdog */
}
void sdk_init_done_cb(void) {
os_timer_disarm(&start_timer);
os_timer_setfn(&start_timer, start_timer_cb, NULL); /* Set callback for timer */
os_timer_arm(&start_timer, 5000 /* call every 5 second */, 1 /* repeat */);
}
If the last parameter of the os_time_arm function is 0, the timer callback will be invoked only once. If it’s 1, it will be called repeatedly until the os_timer_disarm is called.
And, finally, we have a place to put our code: The start_timer_cb function.
Our task here is to make an LED blink. Some ESP boards have an LED attached to GPIO16, if your board doesn’t have it, you can attach an LED to any free GPIO.
void start_timer_cb(void *arg) {
static int on = 0;
if (!on) { /* if LED is OFF - turn it on */
gpio_output_set(BIT16, 0, BIT16, 0);
on = 1;
} else { /* if LED is ON - turn is off */
gpio_output_set(0, BIT12, BIT12, 0):
on = 0;
}
}
As you remember start_timer_cb is a timer callback function, and it is called every 5 seconds. On first call on variable is 0 and we set GPIO16 to high - as result LED will be turned on. On second call we set GPIO16 to low - and LED is turned off. And so on and so on.
Step 4: Building the Project
Now it is time to build our project. Let’s say, we have only one source file - main.c. I cannot recommend using makefiles which are used for building examples. They are too complicated and a bit weird. So, I’d suggest to write your own (simple!) makefile.
Here are steps:
1. Compile main.c to main.o.
Use xtensa-lx106-elf-gcc compiler which is a part of esp-open-sdk.
2. Link project.
Linker to use - the same xtensa-lx106-elf-gcc. Libraries to link with are: c gcc hal m pp phy net80211 wpa main
Also, you need to supply the linker script (.ld file). Choose one from esp-open-sdk that matches the flash size of your device. After this step you’ll have .elf file.
3. Convert .elf file to .bin
For this, use esptool.py script from esp-open-sdk. Run it like this: esptool elf2image -o <outputdir>/<path to your elf file>
If everything is ok, you should have 3 files in with names like 0x00000.bin, 0x11000.bin 0x66000.bin.
Step 5: Flashing
The final step is to put our firmware onto the device. For this we will use the esptool again, but now we should use write_flash option. Like this:
esptool --port <port name here> --baud 115200 write_flash 0x00000 0x000000.bin 0x11000 0x11000.bin 0x66000 0x66000.bin
You should use real filenames from previous step.
And, if everything is still ok, the LED attached to the device will start to blink every 5 seconds.
Step 6: Next Steps
Writing the firmware for any device is a huge topic. Working with ESP8266 is not an exception. So, the purpose of this article is only to highlight the direction. There are a lot of different APIs in the ESP8266 SDK: WiFi, GPIO, TCP/UDP and more. Make sure to check out the documentation fully here. It’s also good to check out the examples by firmware providers and esp-open-sdk. If you want to start with an example, check out this one which goes through running Mongoose Embedded Web Server on ESP8266.