Storing WiFi configuration in EEPROM on Arduino

The Arduino streaming using AJAX example provides a simple way to access analog and digital ports on Arduino over WiFi, but one of its deficiencies is the challenge of configuring WiFi. It establishes its own network, but is not able to connect to any of the existing network, which may be the configuration you prefer. It is easy to modify the code to make it connect to an access point, but the configuration would be hardcoded and any change would require a new firmware upload.

Since the example already provides a simple web interface, wouldn't it be convenient to have a way to update board's WiFi configuration from a browser and make the board to store and use that configuration? This is exactly what I am about to discuss.

Storing to and retrieving configuration from EEPROM

First, we need a way to store and retrieve data to and from EEPROM. EEPROM is the memory that keeps its values even when you remove any external power from the Arduino board. Ardino Uno (powered by ATmega328 processor) has 1KB of EEPROM. Arduino documentation has an example with the code to manage EEPROM. This is what I ended up with based on that code:

#define CONFIG_VERSION "ar1"
#define CONFIG_START 0

struct WiFiStorageStruct {
  char version[4];
  char ssid[24];
  char pwd[16];
  byte addr[4];
  unsigned int id;
} WiFiConfig = {
  CONFIG_VERSION,
  "NetworkConnectTo",
  "",
  {0, 0, 0, 0},
  0
};

void loadConfig() {
  if (EEPROM.read(CONFIG_START + 0) == CONFIG_VERSION[0] &&
      EEPROM.read(CONFIG_START + 1) == CONFIG_VERSION[1] &&
      EEPROM.read(CONFIG_START + 2) == CONFIG_VERSION[2])
    for (unsigned int t=0; t<sizeof(WiFiConfig); t++)
      *((char*)&WiFiConfig + t) = EEPROM.read(CONFIG_START + t);
}

void saveConfig() {
  for (unsigned int t=0; t<sizeof(WiFiConfig); t++)
    EEPROM.write(CONFIG_START + t, *((char*)&WiFiConfig + t));
}

version field is used to identify the format version, ssid, pwd, and ip store your WiFi configuration, and id keeps unique id of the Arduino you are working with, which is useful when you need to have several instances running at the same time (as I do when I run several robots with the same firmware). You will see this id used in the network name, for example, TestNetwork0, TestNetwork1, and so on.

Note that both loadConfig and saveConfig simply read or write a specific number of char values starting at configured CONFIG_START position.

Using stored configuration

The logic is simple: the WiFi card attempts to connect to a configured network (if any) and if the connection fails, it will establish its own ad-hoc network. This way if you bring the card to a location where the configured network is not available, you can always access the card using its own network and configure it to use whatever access point you prefer. This is the code that implements this:

   loadConfig();
    RedFly.scan();
    adhoc = ret = RedFly.join(WiFiConfig.ssid, WiFiConfig.pwd, INFRASTRUCTURE);
    if (ret) {
      char network[16];
      sprintf(network, "TestNetwork%d", WiFiConfig.id);
      ret = RedFly.join(network, IBSS_CREATOR, 10);
    }
    if (!ret) {
      currentIP = adhoc ? serverIP : WiFiConfig.addr;
      ret = RedFly.begin(currentIP, gateway, netmask);
    }

The join command returns a non-zero result when it fails. In this case it will try to establish its own network with a unique name (using the identifier stored with the configuration). There is no code that modifies this identifier as it is stored only once; you can do it with the following fragment: loadConfig(); WiFiConfig.id = 1; saveConfig();.

Updating WiFi configuration

Now we only need to implement a web interface to show the current configuration and process an updated on. We can update the main page with a link to the WiFi configuration form; the main page now looks like this:

The configuration page is straightforward: its main component is the <form> that includes SSID, password and IP address fields that are stored as WiFi config. This page is returned as a response to GET /wifi request. Note that the strings are stored in PROGMEM, to reduce the amount of SRAM required for the application. These strings are still loaded in SRAM when they are used, but only temporarily and don't consume any SRAM if this branch of the code is not used.

The form doesn't specify any URL, only the method (POST) to be used to send the data with. The same URL (..../wifi) will be used in this case, which is exactly what we want.

     const char *OK = PSTR("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n");
     const char *STYLE = PSTR("\r\n<html style='font-family:Verdana,Geneva,Georgia,Chicago,Arial,Sans-serif;color:#002d80'>");
...
       else if (strncmp_P(out, PSTR("GET /wifi HTTP"), 14) == 0) {
        RedFly.socketSendPGM(sock, OK);
        RedFly.socketSendPGM(sock, STYLE);
        RedFly.socketSendPGM(sock, PSTR("<form method='post'>Wifi configuration<p>SSID: <input style='margin-left:48px' value='"));
        RedFly.socketSend(sock, (uint8_t*)WiFiConfig.ssid, strlen(WiFiConfig.ssid));
        RedFly.socketSendPGM(sock, PSTR("' name='s'/><br/>Password: <input type='password' name='p' style='margin-left:10px'/><br/>IP address: <input name='a' value='"));
        sprintf(out, "%d.%d.%d.%d", WiFiConfig.addr[0], WiFiConfig.addr[1], WiFiConfig.addr[2], WiFiConfig.addr[3]);
        RedFly.socketSend(sock, (uint8_t*)out, strlen(out));
        RedFly.socketSendPGM(sock, PSTR("'/> (xxx.xxx.xxx.xxx)<br/><br/>Clicking <input type='submit' value='Update'/> will update the configuration and restart the board.</p></form></html>"));
        RedFly.socketClose(sock);
      }

This is a snapshot of what you would see in the browser when you access an arduino board that runs this code:

The next part is the processing of the HTTP POST request that includes the new configuration. The data will come in the body of HTTP request in the format: s=SSID&p=password&a=1.2.3.4. First we parse the request by starting from the last parameter and using sscanf to split the IP address into 4 numbers we are interested in. Then the password and the SSID get processed. The configuration is updated only when all three parameters are found in the request.

After the configuration is saved, the board is soft-restarted using a jump to zero address: void(* reset) (void) = 0; reset();. Note that while this may look like a restart, the controller itself is not reset and no hardware initialization is done. The board is restarted with the new configuration and if the configuration is correct, the board connects to the network.

     else if (strncmp_P(out, PSTR("POST /wifi HTTP"), 15) == 0) {
        char *st, *fi;
        char *a = NULL, *p = NULL, *s = NULL;
        out[buf_len] = '\0';
        if (st = strstr(out, "a="))
          if (sscanf(st+2, "%d.%d.%d.%d", WiFiConfig.addr, WiFiConfig.addr+1, WiFiConfig.addr+2, WiFiConfig.addr+3) == 4) a = st+2;
        if (st = strstr(out, "p="))
          if (fi = strstr(st, "&")) { fi[0] = '\0'; p = st+2; }
        if (st = strstr(out, "s="))
          if (fi = strstr(st, "&")) { fi[0] = '\0'; s = st+2; }

        RedFly.socketSendPGM(sock, OK);
        RedFly.socketSendPGM(sock, STYLE);
        if (a && p && s) {
          strcpy(WiFiConfig.pwd, p);
          strcpy(WiFiConfig.ssid, s);
          saveConfig();
          RedFly.socketSendPGM(sock, PSTR("Updated. The board has been restarted with the new configuration.</html>"));
          RedFly.socketClose(sock);

          // restart Arduino by calling a (pseudo)function at address 0
          void(* reset) (void) = 0; // declare reset function @ address 0
          reset();
        }
        else {
          RedFly.socketSendPGM(sock, PSTR("Error. Please return back and update the configuration.</html>"));
          RedFly.socketClose(sock);
        }
      }

Last touch is that depending on whether there is any error and whether adhoc or AP connection is established this code will use an LED connected to pin 13 to signal its state:

  // 10 blinks on error, 5 blinks on AP connection and 3 blinks on adhoc connection
  blink(LEDPIN, ret ? 10 : (adhoc ? 3 : 5));

All the code is available in the updated DHCPLite library. Please leave a comment or send me an email if you notice something is not working as expected.

2 Comments

Paul -

Thanks for your code example. I was able to run your DHCPLite sketch on my Arduino Uno with a RedFly shield.

I get this output from the Serial monitor but can not connect to the adhoc network. I have tried the IP (192.168.1.177) but it just spins. Is there something I am missing?

Thanks.

Un AT+RSIFWVERSION? AT+RSIBAND=0 AT+RSIINIT AT+RSINUMSCAN=0 AT+RSISCAN=0 AT+RSINETWORK=INFRASTRUCTURE AT+RSIPSK= AT+RSIJOIN=NetworkConnectTo,0,2 AT+RSINETWORK=IBSS,1,10 AT+RSIJOIN=TestNetwork0,0,2 AT+RSIDNSSERVER=0.0.0.0 AT+RSIIPCONF=0,192.168.1.177,255.255.255.0,255.255.255.0 AT+RSILUDP=67 AT+RSILTCP=53 AT+RSILUDP=53 AT+RSILUDP=80 AT+RSI_LTCP=80

Hi okay? 'm Eduardo from Brazil / Sao Paulo.

I am an inventor who has many ideas but little knowledge, I manufacture and use part of the profits to help the poor people of Brazil, and fiqei wondering how you get time to do so many things, one day I will be like you, congratulations. hug

Leave a comment

what will you say?
(required)
(required)

About

I am Paul Kulchenko.
I live in Kirkland, WA with my wife and three kids.
I do consulting as a software developer.
I study robotics and artificial intelligence.
I write books and open-source software.
I teach introductory computer science.
I develop a slick Lua IDE and debugger.

Recommended

Close