Saturday, December 8, 2018

ESP8266 library for EFM8 microcontrollers

ESP8266 is a low-cost serial to Wi-Fi module that is very popular among electronics hobbyists. It can be used to connect a microcontroller to internet over Wi-Fi. The things you can do with it are numerous. You could monitor and log various sensor data like atmospheric pressure, temperature and humidity together with geo locations on websites like thingspeak.com. Or you could retrieve information from internet such as weather, number of subscribers on YouTube, send tweets with your microcontroller, send email notification if your plants get dry, etc.


The module consists mainly of a 32-bit microcontroller with full TCP/UDP stack support and an external flash memory chip from 0.5MB to 8MB depending on the module type. The cheapest one is ESP8266-01.
The module can be used in two ways:
1. With an external microcontroller that sends commands to ESP using so called AT commands. This is the simplest way. Communication between ESP and microcontroller is done via UART. A serial terminal such as Termite terminal can be used to manually test the module.
2. The ESP can also be programmed and used without an external MCU since already contains a powerful MCU. The easiest way to program it is by using Arduino IDE with appropriate libraries. Once the chip is reprogrammed it cannot take AT commands anymore. The disadvantage is that the cheap modules have only one or two general purpose pins so you can't do much without an external microcontroller.

ESP8266 pinout and power

Power

The voltage to the ESP8266 must not exceed 3.3V on supply and communication lines. So make sure that the USB to UART or the microcontroller used for interface also uses 3.3V or use a voltage level translator. Also the power supply must be able to source at least 170mA - the peak current of the ESP.

Some modules are not breadboard compatible. You can use an adapter or simply temporarily solder some wire jumpers to the pins and connect them to breadboard for testing or use DuPont connectors (recommended).

To communicate with the ESP8266 via a serial terminal you can make a USB to serial adapter like in this post.

Pinout

On the back side of the ESP there is the marking for the pins as follow:

Pin Number Pin Name Alternate Name Normally used for Alternate purpose
1 GND
Power ground
2 TX GPIO–1 UART Transmit.
Connected to Rx pin of programmer/u
Can act as a General purpose
input/output pin when not used as TX
3 IO2
General purpose input/output pin 2
4 EN
Chip Enable – active high
5 IO0 Flash General purpose input/output pin 0 Takes module into serial programming
when held low during start up
6 RST
Resets the module
7 RX GPIO-3 UART Receive
Connected to Tx pin of programmer/uC
Can act as a General purpose
input/output pin when not used as RX
8 3V3
VCC. Connect to +3.3V only

Schematic

ESP8266 schematic

R16 is a 10k resistor used to pull-up the Reset pin. VCC and EN (enable) pins are connected to 3.3V. The Reset pin is connected also to a momentary switch and when pressed it resets the ESP. GPIO2 can be connected to a header but is optional. Same for GPIO0 except this pin is pulled high by R1 a 10k resistor. SW32 is another momentary switch that pulls GPIO0 to ground when pressed and is used only for programming (flashing) the chip. If you are using only AT commands, the two switches can be omitted.

ESP8266 library using AT commands

This library depends on the following UART library that can be downloaded from here or from the end of this page.
For now this library is only made for EFM8BB1 Busy Bee and EFM8BB3 microcontrollers but in the future it will be adapted for AVR also.

In the ESP8266.h file there are a few optional settings

#define DEBUGGING               1 // default 0

If set to 1 some error messages will be displayed over UART in case of setup errors.
Debugging tip: when testing the interface between the MCU and ESP you can use the Rx (receive0 pin of an USB to serial adapter to see the communication messages between the two. If you put the Rx pin on Tx of the microcontroller then you can see what MCU is transmitting. If Rx is connected to Rx of the microcontroller then you can see what ESP8266 is transmitting.

#define USE_SERVER_MODE  1

Set this to 0 if not using the ESP in server mode, to save some space.

For setting the ESP8266 in server mode read this article ESP8266 library for EFM8 microcontrollers - Server mode

Library setup

In your main c file add the following defines. These defines must be added before the includes

main.c
//-----------------------------------------------------------------------------
// Defines
//-----------------------------------------------------------------------------
#define F_CPU                     24500000 // CPU FREQUENCY IN Hz
#define BAUD_RATE                 115200
#define RECEIVED_DATA_ARRAY_SIZE  41 // size of array where to store the incoming data

BAUD_RATE is the speed that the microcontroller will communicate with the ESP. Usually 9600 or 115200 is used. If you see gibberish in the serial terminal, try another baud rate.
RECEIVED_DATA_ARRAY_SIZE is the size of the array declared later bellow, in which the received data from internet will be stored. Be sure to leave 1 byte for the null character that indicates the end of string.

Then include the following header files. The links with the necessary files can be found at the end of the page.

main.c
//-----------------------------------------------------------------------------
// Includes
//-----------------------------------------------------------------------------
#include <SI_EFM8BB1_Register_Enums.h> // SFR declarations
#include "globals.h"
#include "delay.h"
#include "uart.h" #include "ESP8266.h"

Library Functions


ESP_Setup(tcp_ip_mode, ssid, key)

Function:
sets up the UART at declared baud rate, checks if ESP module is responding and then sets up the Wi-Fi mode, TCP/IP mode and connects the ESP to the router with offered credentials
Arguments:
tcp_ip_mode: one of the available macros CLIENT or SERVER
ssid:
key: (char *) SSID and KEY of the router. The key must be between 8 and 64 bytes ASCII. Can be left empty like this "" useful in some scenarios
Returns:
(bool) true if successful or false if not

ESP_Setup_AP(wifi_mode, tcp_ip_mode, ap_ssid, ap_key)

Function:
Sets up an access point with an SSID and key given by the user and with WPA_WPA2_PSK encryption on channel 1
Arguments:
wifi_mode: one of the available macros ACCESS_POINT_MODE

ACCESS_POINT_MODE - ESP is used to connect other devices to the Internet through router
tcp_ip_mode: one of the available macros CLIENT or SERVER
ap_ssid:
ap_key: (char *) desired SSID and KEY of the access point. The key must be between 8 and 64 bytes ASCII
Returns:
(bool) true if successful or false if not

ESP_Connect(linkID, type, remoteIP, remotePort, UDPLocalPort, UDPMode)

Function:
Connects to a domain or IP to send or retrieve data
Arguments:
linkID: (char *) ID of network connection (0~4), used for multiple connections
type: connection type. Available macros: TCP or UDP 
remoteIP: (char *) string that represents a domain or IP to connect to. Do not append http://
remotePort: (char *) the remote port number
UDPLocalPort: (char *) optional. UDP port of the ESP8266. Pass an empty string if not used, like this ""
UDPMode: (char *) In the UDP transparent transmission, the value of this parameter has to be 0.
       0: the destination peer entity of UDP will not change; this is the default setting.
       1: the destination peer entity of UDP can change once.
       2: the destination peer entity of UDP is allowed to change.

Returns:
true if successful or false if not

ESP_SendData(linkID, payload, dataLength)

Function:
Sends data after ESP_Connect() is executed. Must run immediately after that.
Arguments:
linkID: (char *) ID of network connection (0~4), used for multiple connections
payload: (char *) string or array that represents the payload. Can also be a GET request like this: "GET http://192.168.100.2 HTTP/1.0\r\n\r\n"
dataLength: (uint8_t) number of bytes/length of the payload
Returns:
true if successful or false if not

ESP_ParseAndCheck(keywords[],  match_all, uppercase_all, timeout_ms, data_ret[], array_size)

Function:
used by the above two functions to check the response from the ESP8266 if it contains certain keywords like CONNECT OK or ERROR.
After ESP_Connect() exits, the ESP will start dumping data in the UART Rx buffer so this function must be executed as soon as possible in order to process the incoming data.
This function is very useful to extract some data between two keywords. Also the user can provide up to 4 keywords and it will return true or false if one or all keywords are found in the incoming data from the internet
Arguments:
keywords: (char *) a pointer to an array of up to 4 keywords to be searched. See the examples bellow to see how to use this
match_all: (bool) 1 or 0. If 1, the function will return true only if all the keywords are found in the incoming string of data. If you want to know if only one keyword was found then use 0 
uppercase_all: (bool) if true then the keywords and the incoming data will be converted to uppercase characters. Is useful when say for example you want to find OK and the data contains Ok. Without uppercase the function will return false - not found 
timeout_ms: (uint16_t) timeout in milliseconds. Depending of how much data you expect to receive set this to a few seconds. If the keywords are found the function will exit before timeout. If the keywords are not found the function will wait until timeout 
data_ret: (unsigned char) an array that user provides in which the data between two keywords will be stored 
array_size: (uint8_t) the size of the data_ret array. The array must be sized to fit expected data + 1 for null character. If set to 0 the data will not be returned in the data_ret array. This way data_ret can be initiated with only 1 byte in size and the function used to just return true or false for keywords match
Returns:
true if a keyword or all keywords are found - depending of match_all argument, false if not

ESP_UpdateQueryString(URL[], query, new_value, nrOfDigits)

Function:
Modifies the values of  a URL query string. The values bust be of the type integer. Used for say if you have some readings like temperature, voltage values... and you want to send them in a URL quey. See examples bellow
Arguments:
URL: (char) the URL 
query: (char *) the URL query string including = for which the value needs to be modified 
value: (int16_t) the new value after the = sign 
nrOfDigits: (uint8_t) how many digits is the value formed of
Returns:
void

linkID is not fully implemented and should be always 0. The ESP8266 can support up to 5 connections anyway. Also CIPMUX must be 1 for multiple connections by using SERVER_MODE macro as ESP_Setup() argument.

Extra Functions


ESP_DisconnectAP(void)

Function:
Disconnects the ESP from the access point (router). To reconnect it again, run ESP_Setup() 
Arguments:
none
Returns:
(bool) true if successful or false if not
 
ESP_FactoryReset(void)

Function:
Reset the ESP device to factory settings
Arguments:
none
Returns:
(bool) true if successful or false if not
 

Practical Examples

Log temperature and humidity on thingspeak.com using ESP8266

In the following code are presented two examples of how to use this ESP8266 library.
URL_1 is used to log temperature and humidity on thingspeak.com. You can use the api keys that I provided and see the results by visiting this public channel that I've made for testing purposes https://thingspeak.com/channels/429357.

URL_2 is another example that uses an app from thingspeak to receive a command string. This string is extracted from the received data and the HTTP headers and other unnecessary data is discarded. This way you can parse megabytes of received data and keep only a small part that matters, in a small array.

A small delay of a few milliseconds should be added between connections because after the connection is established, the ESP has to receive at least some HTTP headers in response. After that you can initiate a new connection.
Also depending of what sites you use for IoT, there needs to be a waiting time between connections like 5 seconds to prevent the site being flooded with too many connections in a short time.

main.c
//-----------------------------------------------------------------------------
// Includes
//-----------------------------------------------------------------------------
#include <SI_EFM8BB1_Register_Enums.h> // SFR declarations
#include "globals.h"
#include "delay.h"
#include "ESP8266.h"
#include "uart.h"

//-----------------------------------------------------------------------------
// Defines
//-----------------------------------------------------------------------------
#define F_CPU          24500000 // CPU FREQUENCY IN Hz
#define BAUD_RATE        115200
#define RECEIVED_DATA_ARRAY_SIZE   41 // size of array where to store the incoming data

//-----------------------------------------------------------------------------
// Global VARIABLES
//-----------------------------------------------------------------------------
int8_t field1_temperature = 27;
int8_t field2_humidity = 65;

//-----------------------------------------------------------------------------
// Global CONSTANTS
//-----------------------------------------------------------------------------
// URLs (IMPORTANT: don't forget to add the HTTP headers after the URL HTTP/1.0\r\n\r\n)
char xdata URL_1[] = "GET /update?api_key=2CY50F6OFB6RWP06&field1=0000&field2=0000 HTTP/1.0\r\n\r\n";
char xdata URL_2[] = "GET /talkbacks/23510/commands/14569888.json?api_key=0JAXMR1PR130D6XM HTTP/1.0\r\n\r\n";
const char *URL_1_DOMAIN = "api.thingspeak.com";
const char *ESP_PORT = "80";

//-----------------------------------------------------------------------------
// Function PROTOTYPES
//-----------------------------------------------------------------------------
void Port_Init(void);

//-----------------------------------------------------------------------------
// SiLabs_Startup() Routine
// ----------------------------------------------------------------------------
// This function is called immediately after reset, before the initialization
// code is run in SILABS_STARTUP.A51 (which runs before main() ). This is a
// useful place to disable the watchdog timer, which is enable by default
// and may trigger before main() in some instances.
//-----------------------------------------------------------------------------
void SiLabs_Startup(void){
  // Disable Watchdog with key sequence
  WDTCN = 0xDE; // First key
  WDTCN = 0xAD; // Second key
}


//-----------------------------------------------------------------------------
// main() Routine
// ----------------------------------------------------------------------------
// Note: the software watchdog timer is not disabled by default in this
// example, so a long-running program will reset periodically unless
// the timer is disabled or your program periodically writes to it.
//-----------------------------------------------------------------------------
int main(void){
  //-------------------------
  // Local variables
  // ------------------------
  uint8_t xdata receivedData[RECEIVED_DATA_ARRAY_SIZE] = {'\0'};
  // Number of keywords in this case is 2 but the array must be of size 3 so one byte is for null terminator
  char *keywords[3] = {"command_string\":\"", "\",\"position"};
  bool esp_setup_ok = false;
  bool esp_url_sent_ok = false;
 
  //-------------------------
  // Initialization
  // ------------------------
  Port_Init();
 
  _delay_ms(2000);
 
  esp_setup_ok = ESP_Setup(STATION_MODE, CLIENT);
 
  // Update and send measured temperature and humidity
  if(esp_setup_ok){
  // Update the URL's query string with a new value 
  ESP_UpdateQueryString(URL_1, "field1=", field1_temperature, URL_VALUE_NR_OF_DIGITS);
  
  // Update the URL's query string with a new value
  ESP_UpdateQueryString(URL_1, "field2=", field2_humidity, URL_VALUE_NR_OF_DIGITS);
  
  // Send measured temperature and humidity
  ESP_Connect(URL_1_DOMAIN, ESP_PORT, URL_1);
  
  _delay_ms(1000);
  
  // Receive and parse data from an URL
  if(ESP_Connect(URL_1_DOMAIN, ESP_PORT, URL_2)){
   // Incoming data will be
   // {"id":14569888,"command_string":"Important string that needs to be parsed","position":1,"executed_at":null,"created_at":"2018-11-29T03:46:03Z"}
   // and we want to extract this: Important string that needs to be parsed
   // So the first keyword will be <command_string":"> and the second keyword will be <","position> The " must be escaped by putting \ in front of them
   if(ESP_ParseAndCheck(keywords, true, false, 10000, receivedData, RECEIVED_DATA_ARRAY_SIZE)){
    // Display received and parsed data
    UART_Send("\n");
    UART_Send(receivedData);
    UART_Send("\nKeyword found\n");
   }else{
    UART_Send("\nKeyword not found\n");
   }
  }
 }
 
 
 // Some servers don't allow many queries in a short time so add the necessary delay
 _delay_ms(2000);
 
 // Change the keywords if necessary
 keywords[0] = "id\":";
 keywords[1] = ",\"";
 
 // Receive and parse data from another URL
 if(esp_setup_ok){
  if(ESP_Connect(URL_1_DOMAIN, ESP_PORT, URL_2)){
   // Incoming data will be
   // {"id":14569888,"command_string":"Important string that needs to be parsed","position":1,"executed_at":null,"created_at":"2018-11-29T03:46:03Z"}
   // and we want to extract id: 14569888
   // So the first keyword will be <id":> and the second keyword will be <,"> The " must be escaped by putting \ in front of them
   if(ESP_ParseAndCheck(keywords, false, false, 10000, receivedData, RECEIVED_DATA_ARRAY_SIZE)){ 
    // Display received and parsed data
    UART_Send("\n");
    UART_Send(receivedData);
    UART_Send("\nKeyword found\n");
   }else{
    UART_Send("\nKeyword not found\n");
   }
  }
 }
 
 // Update and send measured temperature and humidity
 if(esp_setup_ok){
  field1_temperature = -5;
  field2_humidity = 55;
  
  // Update the URL's query string with a new value
  ESP_UpdateQueryString(URL_1, "field1=", field1_temperature, URL_VALUE_NR_OF_DIGITS);
  
  // Update the URL's query string with a new value
  ESP_UpdateQueryString(URL_1, "field2=", field2_humidity, URL_VALUE_NR_OF_DIGITS);
  
  // Send measured temperature and humidity
  esp_url_sent_ok = ESP_Connect(URL_1_DOMAIN, ESP_PORT, URL_1);
 }
 
  //-------------------------
  // while loop
  // ------------------------
  while(1){
 
 }
}


//-----------------------------------------------------------------------------
// Functions
//-----------------------------------------------------------------------------
void Port_Init(void){
 // -------------------------------------------------------------
 // 1. Select the input mode (analog or digital) for all port pins
 // using the Port Input Mode register (PnMDIN)
 // -------------------------------------------------------------
 
 // --- Configure a pin as a digital input
 // -------------------------------------------------------------
 // 1.1 Set the bit associated with the pin in the PnMDIN register to 1.
 // This selects digital mode for the pin.
 //P0MDIN |= (1<<0);
 
 // 2.1 Clear the bit associated with the pin in the PnMDOUT register to 0.
 // This configures the pin as open-drain.
 //P0MDOUT &= ~(1<<0);
 
 // 3.1 Set the bit associated with the pin in the Pn register to 1.
 // This tells the output driver to “drive” logic high. Because the pin is
 // configured as open-drain, the high-side driver is disabled,
 // and the pin may be used as an input.
 //P0 |= (1<<0);
 
 
 // --- To configure a pin as a digital, push-pull output:
 // -------------------------------------------------------------
 // 1.1 Set the bit associated with the pin in the PnMDIN register to 1.
 // This selects digital mode for the pin.
 P0MDIN |= (1<<0);
 
 
 // 2.1 Set the bit associated with the pin in the PnMDOUT register to 1.
 // This configures the pin as push-pull.
 P0MDOUT |= (1<<0);
 
 
 // --- To configure a pin as analog, the following steps should be taken:
 // -------------------------------------------------------------
 // 1.1 Clear the bit associated with the pin in the PnMDIN register to 0.
 // This selects analog mode for the pin.
 //P0MDIN &= ~(1<<0);
 
 // 2.1 Set the bit associated with the pin in the Pn register to 1.
 //P0 |= (1<<0);
 
 // 3.1 Skip the bit associated with the pin in the PnSKIP register
 // to ensure the crossbar does not attempt to assign a function to the pin.
 //P0SKIP |= (1<<0);
 
 // -------------------------------------------------------------
 // 2. Select any pins to be skipped by the I/O crossbar
 // using the Port Skip registers (PnSKIP)
 // -------------------------------------------------------------
 
 // -------------------------------------------------------------
 // 3. Assign port pins to desired peripherals
 // -------------------------------------------------------------
 
 // -------------------------------------------------------------
 // 4. Enable crossbar and weak pull-ups
 // -------------------------------------------------------------
 XBR2 |= 0x40;
 
 // SYSCLK - CPU speed
 CLKSEL = CLKSEL_CLKSL__HFOSC | CLKSEL_CLKDIV__SYSCLK_DIV_1;
}


//-----------------------------------------------------------------------------
// Interrupts
//-----------------------------------------------------------------------------


Simple example

main.c

int main(void){

    bool esp_setup_ok = false;
    
    // Wait for ESP to connect to wifi
    _delay_ms(10000);
    
    // Set ESP in client mode
    esp_setup_ok = ESP_Setup(STATION_MODE, CLIENT, "YOUR_SSID", "YOUR_KEY");
    
    while(1){
    
    }

}

You can leave questions or report bugs in the comment section bellow.

Download

v2.1
+ support for server mode
+ UDP mode
- fixed some errors in the search functions


Other resources

delay.h

Setting the ESP8266 in server mode
ESP8266 library for EFM8BB1 Busy Bee microcontrollers - Server mode

AT instruction set
https://www.espressif.com/sites/default/files/documentation/4a-esp8266_at_instruction_set_en.pdf


No comments:

Post a Comment