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 }