Programming for ESP8266 (Part 2)
Before you read further make sure that you have finished reading part 1 and prepared your virtual machine and hardware.
In this article you will learn how to connect and access your ESP8266 from your virtual machine. Followed by creating, compiling and uploading on the device a sample Hello Real World application.
Connect
We will explain now how to connect your ESP8266 to your virtual machine.
Let’s connect the Olimex’s ESP8266 EVB to our computer. Do not power the ESP8266 yet! Connect the USB-SERIAL-CABLE-F to ESP8266-EVB UEXT connector. The blue wire goes to pin 2, the green wire goes to pin 3 and the red wire goes to pin 4. Double check if you connected the wires as described above.
If everything looks good then plug the USB cable to your computer. In Ubuntu, or any other modern Linux, you can check if the new USB device is discovered by issuing the following command:
sudo lsusb |
You should see in the output lines containing “Bus 004 Device 005: ID 067b:2303 Prolific Technology, Inc. PL2303 Serial P”.
You can type also
dmesg |
To see if your OS discovered the device and attach it to a local device path. In most of the cases this should be /dev/ttyUSB0.
So far we have access to our host system but we want to be able to access this device from inside our virtual machine. For that we need to start it using the following command:
vagrant up |
And then go inside the virtual machine
vagrant ssh |
…and check if we have access to the USB device.
ls -lha /dev/ttyUSB0 |
The last command should be executed inside the virtual machine. The result should be something like this:
crw-rw---- 1 root dialout 188, 0 Mar 26 10:48 /dev/ttyUSB0
Serial Interface Program
In order to be able to communicate with the device we will install on the virtual machine a serial interface program called “screen”. The installation on the virtual machine is done using the following command:
sudo apt-get -y install screen |
This program allows us relatively easy to communicate with the device.
Now we will try to see what the device is writing to us when it boots. Make sure that the USB cable is connected to your computer. Start screen with the following options
sudo screen /dev/ttyUSB0 74880 |
Now plug the 5V power adapter into ESP8266. After a short moment on the terminal of your virtual machine you should see a text like that one:
...
ip:192.168.4.1,mask:255.255.255.0,gw:192.168.4.1)
add if1
pm close 0 0 0/474860
bcn 100
...
Sometimes I had to power reset the device twice to see the text above. If everything so far worked for you then you are ready to program the device.
Program
Here we will explain what a typical “Hello World” program to ESP8266 looks like.
A typical “Hello World” program for C looks like this:
1 2 3 4 5 6 7 | #include <stdio.h> main() { printf("Hello World"); } |
When running this program there are few assumptions. The first one is that the main function will be called when we run the program. The second one is that there is already configured output where the data can be visualized to the user. And the third assumption is that when this program ends the OS will continue to work.
In the world of microcontrollers, or the ESP8266 world, these three assumptions are no longer valid. First the function that is called when the program is run is called “user_init”. Second we might need to prepare the output so that it is goes through the serial interface and is shown on our screen. And third if this program ends there will be no more programs that will be run. That is because we are dealing here with a bare-metal device.
Bare-Metal Device
In our ESP8266 in the general case we do not have an operating system, in the sense that we use to describe modern operating systems like Ubuntu GNU/Linux for example. The device has a logic that boots the initial set of instructions and after that looks for a function called user_init and gives the control to it. In that function we need to take care to initialize the needed subsystems and once all the initialization is done we can continue with our main logic.
Source Code Examples
Let’s take a look at an example source code that will get us started. Clone the following repository in your virtual machine. This can be done with the following commands:
1 2 3 | cd ~/dev/ git clone https://github.com/slaff/esp-hello-world.git cd esp-hello-world |
What we get is the final version of our Hello World application. But let’s get back in time and see how our application evolved.
Initialization
From the root directory where the esp-hello-world code is we have to execute the following command to go “back in time”.
git checkout init |
You will notice that we have a Makefile, one C file called user/hello.c and one header file called user/user_config.h. The Makefile is here to make our life easier and with it’s help we can compile our application and flash it to the device. The commands that we will be using are
make # just compile the application code |
and
sudo make flash # this will upload the compiled code to the device. |
The user/user_config.h file at the moment is more or less empty but is required from the SDK in order to compile our application. Without this file the compilation won’t work.
Hello.c is the file where we will be doing most of the coding for our initial example. Open this one and take a look at the code.
1 2 3 4 5 6 7 8 9 10 11 12 13 | #include "ets_sys.h" #include "osapi.h" #include "os_type.h" #include "user_config.h" void user_init() { // This is used to setup the serial communication uart_div_modify(0, UART_CLK_FREQ / 115200); // And this is used to print some information os_printf("Hello, world!\n"); } |
As it was already mentioned the user_init function is the one that will be called first. In it we do two things. The first one is to set up the baud rate. In our case it is 115200. And the second one is to print “Hello, world!”.
If you want to compile this code and see what is happening then run the following command.
make |
This output should be similar to:
CC user/hello.c
AR build/app_app.a
LD build/app.out
FW firmware/0x00000.bin
FW firmware/0x40000.bin
We have compiled the code and the next step is to upload it on the device. To do this first power off your ESP8266. Then prepare it for flashing. On the Olimex ESP8266-EVB you have to push the button and hold it. While holding it you have to power on the device and release the button. This will instruct the device to enter in flash mode.
If you have your device waiting for command in flash mode then you can the command that will upload our code to the device.
sudo make flash |
The output should be something like
esptool.py --port /dev/ttyUSB0 write_flash 0x00000 firmware/0x00000.bin 0x40000 firmware/0x40000.bin
Connecting...
Erasing flash...
Writing at 0x00007700... (100 %)
Erasing flash...
Writing at 0x00065700... (100 %)
If that worked for you then let’s try to connect to our device. Open a new terminal window and log in the virtual machine. From the second terminal start the following command:
sudo screen /dev/ttyUSB0 115200 |
This should connect us to the device. What you should see is empty screen. Now power off and power on your device. You should see garbled text and our Hello, World message. This message should appear before another message saying the wifi connection details.
Start
It is a good idea to start our code once the system initialization is done. And this is exactly what is implemented in the second example. To see it rewind the history using the following command:
git checkout start |
The main change is in the hello.c file and the code starts to look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | #include "ets_sys.h" #include "osapi.h" #include "os_type.h" #include "user_config.h" void start(void) { // And this is used to print some information once the device is initialized os_printf("Hello, world!\n"); } /* * user_init should be used mainly for initialization */ void user_init() { // This is used to setup the serial communication uart_div_modify(0, UART_CLK_FREQ / 115200); // once the initialization is done we can start with our code system_init_done_cb(start); } |
If you compile and flash this code you will see that the Hello, World message will appear after the wifi initialization.
Looping
We were able to print some characters but if you do not connect in the beginning to your device you will not be able to see any messages. The next code that can be fetched using.
git checkout timer |
It demonstrates how to register a timer and execute a task every Xms. Take a look at the source code below and read the comments in it for more information.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | #include "ets_sys.h" #include "osapi.h" #include "os_type.h" #include "user_config.h" //Main Timer static volatile os_timer_t main_timer; void loop(void *arg) { os_printf("."); } void start(void) { // And this is used to print some information once the device is initialized os_printf("Hello, world!\n"); //Disarm timer os_timer_disarm(&main_timer); //Setup timer os_timer_setfn(&main_timer, (os_timer_func_t *) loop, NULL); //Arm the timer //&main_timer is the pointer //1000 is the fire time in ms //0 for once and 1 for repeating os_timer_arm(&main_timer, 1000, 1); } /* * user_init should be used mainly for initialization */ void user_init() { // This is used to setup the serial communication uart_div_modify(0, UART_CLK_FREQ / 115200); // once the initialization is done we can start with our code system_init_done_cb(start); } |
If you compile and flash this code you will see in the beginning a Hello World message and dots that are printed on the screen every 1000 milliseconds.
Using Arduino-style library
Outputting information on the screen is relatively easy but reading input from the user can by quite challenging for a beginner. It requires multiple steps. First all UART interrupts have to be disabled. Then the RX UART have to be initialized. And an interrupt for the RX have to be initialized that will be called every time someone types a character. The buffer needs to be handled carefully because it can overflow and .. a lot of other small things that make it very difficult for beginner to start. But fear not!
Olimex already recognized this hindrance and created an arduino style library that allows you to easily initialize the serial interface, print and read information as well as communicate with the other available peripheral interfaces. If you rewind the history again using the command:
git checkout arduino |
You will see how easy this can be implemented also in ESP8266. Those of you that have already experience with Arduino will be able to quickly catch up and continue extending the “Hello, World” application.
The important changes are in the beginning of user_init where we use the following to initialize our serial interface:
38 39 40 41 42 | void user_init() { // This is used to setup the serial communication Serial.begin(115200); // ... } |
And the loop function now reads the input from the user and outputs it to the screen.
10 11 12 13 14 15 16 | void loop(void *arg) { char c; while (Serial.available() > 0) { c = (char) Serial.read(); Serial.write(c); } } |
If you want compile the code and flash it to your device to see how it behaves.
This is the end of part 2. In part 3 we will see how we can turn on and off LEDs, relays and read the state of the push button.
Ivajlo Kamazov
Part 3 – whe it will be ready???
admin
Hi Ivajlo, part 3 has just being published here: https://blog.attachix.com/sming-for-esp8266-programming-for-esp8266-part-3/ . Have a pleasant reading 🙂
Nicola Lunghi
Hello I have a problem with the Makefile (sorry if is a silly question) with timer example
the linker cannot find the SDK include directory
I have the sdk in /opt/Espressif/esp-open-sdk/ESP8266_NONOS_SDK_V1.5.4_16_05_20
(I’m using esp-open-sdk)
I go with:
>> export SDK_BASE=/opt/Espressif/esp-open-sdk/sdk
>> export XTENSA_TOOLS_ROOT=/opt/Espressif/esp-open-sdk/xtensa-lx106-elf/bin
(i’ve created a export-xtensa.sh file that I source with source export-xtensa.sh
>> make
LD build/app.out
build/app_app.a(hello.o):(.text+0x4): undefined reference to `os_prinf’
build/app_app.a(hello.o): In function `loop’:
/home/damnick/src/esp8266/demo/esp-hello-world/user/hello.c:10: undefined reference to `os_prinf’
collect2: error: ld returned 1 exit status
Makefile:117: set di istruzioni per l’obiettivo “build/app.out” non riuscito
make: *** [build/app.out] Errore 1
I have osapi.h in
/opt/Espressif/esp-open-sdk/ESP8266_NONOS_SDK_V1.5.4_16_05_20/include/osapi.h
/opt/Espressif/esp-open-sdk/xtensa-lx106-elf/xtensa-lx106-elf/sysroot/usr/include/osapi.h
admin
Make sure that “/opt/Espressif/esp-open-sdk/sdk” is a symbolic link pointing to the existing “/opt/Espressif/esp-open-sdk/ESP8266_NONOS_SDK_V1.5.4_16_05_20” directory.
Chris
I can build and successfully flash “blinky” from /opt/Espressif/examples. However, the examples here seem to fail.
If I connect screen to the board before resetting it, it fills with repeated fatal exceptions:
Fatal exception (0):
epc1=0x40210cdb, epc2=0x00000000, epc3=0x00000000, excvaddr=0x00000000, depc=0x00000000
If I reset the board (or power cycle it) and connect screen, I simply get repeated garbage.
This is a Huzzah Feather board. Any suggestions as to what may be wrong?
Alfredo Sebastián
In line 10 of looping example there is a error, it should be like this os_printf instead of os_prinf. Its missing a t in printf. Regards!