WiFi with ESP8266

Introduction

WiFi communication is one of the most important topics when dealing with embedded electronics. Wireless communication greatly expands our possibilities.

Over the years, a few chips have emerged as direct solutions to bring wireless connectivity to any project. One of these is the ESP8266 from Espressif systems, which represents a small and economical option.

ESP-01S module available at many online electronic stores

ESP8266 boards like the famous ESP-01(S) are now commonplace and easily found at many electronic stores.

Pinout for the ESP-01(S)

Here at Dooba we love these small wifi modules. They are available on ebay for around two US dollars (~2 $USD).

We designed a driver library to make using these as easy as possible. This tutorial will demonstrate how to get started using these modules.

WARNING ! These WiFi modules are shipped with varying baud rates (communication frequency), with the most common being 115200 bps. For Dooba, this frequency MUST be 9600 bps. ALWAYS ensure that the module you are about to use is in fact configured for 9600 bps BEFORE SOLDERING ANYTHING! (The modules WE ship have all been configured accordingly so you don't have to think about it.)

Wiring

To use the module, we need to first take look at how to wire it up with an ioNode.

Power

Just like anything, we need to power the module by connecting the supply pins. Watch out - the esp8266's VCC only accepts 3.3V.

Configuration

The esp8266 can be configured by having some of its pins connected to VCC or GND upon startup.

First, the CH_PD pin will enable the module if connected to VCC. If CH_PD is connected to GND, the module will not be enabled.

The RST pin should be set high (VCC) to allow operation. We can pull the RST pin to ground (GND) to trigger a reset.

The table below presents the available modes and corresponding pin connections:

GPIO0 GPIO2 Boot Mode
HIGH HIGH Normal (boot from flash)
LOW HIGH Flash (serial programming)

 

Communication

The esp8266 communicates via UART (not SPI or I2C), and therefore must be connected to our ioNode's UART TX/RX pins. Be careful to properly "cross" the lines:

  • esp8266 TX -> ioNode RX
  • esp8266 RX -> ioNode TX

Using the library

To use the esp8266 from our code, we must first add it to our dependencies in dfe.conf:

# ...
deps:
  - esp8266

Like any socket interface in Dooba, the esp8266 driver integrates with the 'socket library'. It also makes use of the 'net' library, which defines some generic network-related details. As such, you may encounter references to things located in some of the following header files:

  • net/eth.h
  • net/ip.h
  • socket/iface.h
  • socket/socket.h

Substrate

The esp8266 driver library includes substrate generators to make our lives easier. We can define our esp8266 wifi module in our substrate in the following way:

# Use the ESP8266 with pins 8 (rst) & 29 (flash / GPIO0)
uses :esp8266, name: 'wl0', rst_pin: 8, flash_pin: 29

Operating modes

The esp8266 can operate as a client station (connecting to an access point) or as an access point. Configuring these and their parameters can be done from the substrate as well as from our C code.

Client station

Here's how to connect to an access point (act as a "Client" station) from the substrate:

# Use the ESP8266 as station connecting to 'SomeExampleWifi' with 'SuperSecretPassword'
uses :esp8266, name: 'wl0', rst_pin: 8, flash_pin: 29, sta: { net: 'SomeExampleWifi', key: 'SuperSecretPassword' }

Note: not passing a 'key' parameter will disable WPA encryption (open network).

Or if you don't want to use the substrate, you can do everything by hand from the C code:

 1 #include <esp8266/esp8266.h>
 2 
 3 // RX Buffer for esp8266
 4 uint8_t esp8266_rxbuf_data[1024];
 5 
 6 void init()
 7 {
 8     // ...
 9 
10     // Initialize ESP8266
11     esp8266_init(esp8266_rxbuf_data, 1024, 8, 29);
12     esp8266_set_mux(1);
13     
14     // Set Mode (Station) and Configure
15     esp8266_set_mode(ESP8266_MODE_STA);
16     esp8266_join_ap("SomeExampleWifi", "SuperSecretPassword");
17 
18     // ...
19 }
20 
21 void loop()
22 {
23     // ...
24 
25     // Update WiFi
26     esp8266_update();
27 
28     // ...
29 }

Note: setting the second parameter of esp8266_join_ap (WiFi key) to '0' instead of a string will disable WPA encryption (open network). Example: esp8266_join_ap("SomeOpenWifi", 0);

Listing access points

To scan the air for access points, we can call 'esp8266_list_ap'. We will need to pass it a pointer to an array of struct esp8266_ap. This table will be filled in with access points found. The last argument is a pointer to a uint8_t that will be set to the actual number of access points found and stored in the table.

 1 void init()
 2 {
 3     struct esp8266_ap ap_list[10];
 4     uint8_t ap_count;
 5     uint8_t i;
 6 
 7     // ...
 8 
 9     // List Access Points
10     if(esp8266_list_ap(ap_list, 10, &ap_count))
11     {
12         // Something went wrong :(
13     }
14 
15     // Print out Access Points
16     scli_printf("Found %i access points:\n", ap_count);
17     for(i = 0; i < ap_count; i = i + 1)
18     {
19         scli_printf(" - %t [%s] (Quality: %i)\n", ap_list[i].name, ap_list[i].name_len, ((ap_list[i].sec_type == ESP8266_SEC_OPEN) ? "OPN" : "SEC"), ap_list[i].q);
20     }
21 
22     // ...
23 }

Get local IP / MAC address

We can call 'esp8266_get_local_info' to get our MAC / IP address. Both parameters are optional - if we only need the IP address for example, we can just pass a buffer for the 'ip' argument, and pass 0 for the 'mac' argument.

 1 #include <net/eth.h>
 2 #include <net/ip.h>
 3 
 4 void init()
 5 {
 6     uint8_t hw_addr[NET_ETH_ADDR_LEN];
 7     char ip_addr[NET_IP_ADDR_LEN_TXT];
 8 
 9     // ...
10 
11     // Get Local MAC & IP Address (MAC buffer must be 6 bytes, IP Buffer must be at least 16 bytes)
12     if(esp8266_get_local_info(hw_addr, ip_addr))
13     {
14         // Something went wrong :(
15     }
16 
17     // Display through SCLI
18     scli_printf("Local MAC address: %02x:%02x:%02x:%02x:%02x:%02x\n", hw_addr[0], hw_addr[1], hw_addr[2], hw_addr[3], hw_addr[4], hw_addr[5]);
19     scli_printf("Local IP address: %s\n", ip_addr);
20 
21     // ...
22 }

The IP address buffer (second argument), if non-0, will be filled with a 0-terminated string representing the IP address of our WiFi module. It needs to be at least NET_IP_ADDR_LEN_TXT bytes in size (NET_IP_ADDR_LEN_TXT is defined as 16).

Get Access Point IP address

We can call 'esp8266_get_ap_addr' to get the IP address of the access point we are currently associated with:

 1 #include <net/ip.h>
 2 
 3 void init()
 4 {
 5     char ip_addr[NET_IP_ADDR_LEN_TXT];
 6 
 7     // ...
 8 
 9     // Get Access Point IP Address
10     if(esp8266_get_ap_addr(ip_addr))
11     {
12         // Something went wrong :(
13     }
14 
15     // Display through SCLI
16     scli_printf("Access Point IP address: %s\n", ip_addr);
17 
18     // ...
19 }

Access point

We can have our WiFi module operate as an access point by defining it in the substrate:

# Use the ESP8266 as access point named 'MyAwesomeWifi' with passphrase 'SuperSecretPassword'
uses :esp8266, name: 'wl0', rst_pin: 8, flash_pin: 29, ap: { net: 'MyExampleWifi', key: 'SuperSecretPassword', addr: '10.0.42.1', chan: 6 }

Note: parameters 'key', 'addr' and 'chan' are optional.

  • net -> Wireless network name (SSID)
  • key -> WiFi WPA Passphrase
  • addr -> IP address for esp8266 - will also define the IP range for clients
  • chan -> WiFi channel to use

Or if we don't want to use the substrate, we can still do everything by hand from our C code:

 1 #include <esp8266/esp8266.h>
 2 
 3 // RX Buffer for esp8266
 4 uint8_t esp8266_rxbuf_data[1024];
 5 
 6 void init()
 7 {
 8     // ...
 9 
10     // Initialize ESP8266
11     esp8266_init(esp8266_rxbuf_data, 1024, 8, 29);
12     esp8266_set_mux(1);
13     
14     // Set Mode (AP) and Configure
15     esp8266_set_mode(ESP8266_MODE_AP);
16     if(esp8266_create_ap("MyExampleWifi", "SuperSecretPassword", ESP8266_SEC_WPA2_PSK, 6))
17     {
18         // Something went wrong :(
19     }
20 
21     // Set our IP address (implicitly sets the range for the network)
22     esp8266_set_ap_addr("10.0.42.1");
23 
24     // ...
25 }
26 
27 void loop()
28 {
29     // ...
30 
31     // Update WiFi
32     esp8266_update();
33 
34     // ...
35 }

Listing clients

Once we have created our access point, we can call 'esp8266_list_ap_clients' to determine which devices are connected to us. We will need to pass it a pointer to an array of struct esp8266_ap_client. This table will be filled in with access point clients found. The last argument is a pointer to a uint8_t, which will be set to the number of clients actually found and stored in the table provided.

 1 void init()
 2 {
 3     struct esp8266_ap_client client_list[10];
 4     uint8_t client_count;
 5     uint8_t i;
 6 
 7     // ...
 8 
 9     // List AP clients
10     if(esp8266_list_ap_clients(client_list, 10, &client_count))
11     {
12         // Something went wrong :(
13     }
14 
15     // Print out Access Point Clients
16     scli_printf("Found %i clients:\n", client_count);
17     for(i = 0; i < client_count; i = i + 1)
18     {
19         scli_printf(" - %s\n", client_list[i].ip);
20     }
21 
22     // ...
23 }

Using sockets

The esp8266 driver integrates with the generic network stack of Dooba. To discover how to create and use sockets for client-server communication, have a look at Network sockets.

When using the substrate, a socket interface pointer (type struct socket_iface *) named after the 'name' parameter (in the "uses" line) will be made available to your application.

In the examples presented above, this means we can use 'wl0' as show here:

 1 void init()
 2 {
 3     struct socket *sock;
 4 
 5     // ...
 6 
 7     // Connect socket using 'wl0'
 8     if(socket_cnct(wl0, &sock, "example.com", 80, 0, 0, 0))
 9     {
10         // Something went wrong :(
11     }
12 
13     // ...
14 }

If we don't want to use the substrate, the driver exposes a struct socket_iface esp8266_socket_iface - creating an access pointer for the socket interface is as simple as:

 1 #include <esp8266/esp8266.h>
 2 
 3 struct socket_iface *wl0;
 4 
 5 void init()
 6 {
 7     // ...
 8 
 9     // Set Socket Interface Pointer
10     wl0 = &esp8266_socket_iface;
11 
12     // ...
13 }