How to make a simple 1 watt audio amplifier (LM386 based)

schematic7-750x422

A tutorial on making an LM386 audio amplifier circuit from Afrotechmods:

A tutorial on how to use the popular LM386 class AB audio amplifier IC to build a simple mono 1 watt audio amplifier. It costs less than $3 in parts! There is also a brief discussion of how to use high pass filters to prevent op-amp oscillation and subsequent noise.

More info at Afrotechmods tutorial page.

Check out the video after the break.

ESP32 (6) – How to connect to a wifi network

In this post I’m going to show you how to connect to a wifi network.

The esp-idf framework includes a wifi driver that manages the wifi interface of the esp32 chip. The driver exposes API calls the programmer can use to interact with it; you’ve already used some of those APIs in my previous tutorial:

ESP_ERROR_CHECK(esp_wifi_init(&wifi_config));
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_start());

The driver is executed in parallel with the main program and communicates with it using events:

esp32-wifi02

In the main program you have to define a function that will be the event handler, i.e. a function called by the wifi driver every time it has to notify a new event:

static esp_err_t event_handler(void *ctx, system_event_t *event)
{...}

then you have to tell the framework the name of your function:

ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL));

The list of the events the framework may generate is included in the esp_event.h file. The events related to the wifi driver when working in station mode have SYSTEM_EVENT_STA as name prefix:

esp32-wifi03

The connection to a wifi network follows this flow diagram:

esp32-wifi04

  • the main program configures and starts the driver via API calls
  • after having completed its internal tasks, the driver notifies that it has successfully started triggering the event SYSTEM_EVENT_STA_START
  • the event handler, once received that event, can call the esp_wifi_connect() API to ask the driver to connect to the network specified during the configuration phase
  • when the connection is completed and after having obtained a valid IP address (if the DHCP service is used), the driver triggers the event SYSTEM_EVENT_STA_GOT_IP
  • now the event handler can inform the main program that the connection has been completed

Tasks synchronization

In the process described above, the main program waits for the connection to be completed before executing its own instructions. It’s therefore important to understand how to synchronize the different elements: the main program, the event handler and the wifi driver. FreeRTOS offers different ways to make the tasks communicate: one of the simplest is via events. Events, in FreeRTOS, are managed using event groups and event bits.

Event bits are similar to flags,  visible to the different tasks:

  • the programmer can create as many event bits as he needs
  • tasks can activate (set) o deactivate (clear) the different bits
  • a task can pause its execution waiting for one of more bits to be set

Event bits are grouped into event groups, each of them usually contains 8 event bits. Event bits in an event group are numbered depending on their “position” (BIT0, BIT1…):

esp32-wifi05

The program

The complete program is available in my Github repository. Let’s review the main sections:

static EventGroupHandle_t wifi_event_group;
const int CONNECTED_BIT = BIT0;
[...]
wifi_event_group = xEventGroupCreate();

The program defines an event group (wifi_event_group) and an event bit (CONNECTED_BIT). In the app_main() the event group is created using the FreeRTOS xEventGroupCreate() method.

#define WIFI_SSID "MYSSID"
#define WIFI_PASS "MYPASSWORD"
[...]
wifi_config_t wifi_config = {
  .sta = {
    .ssid = WIFI_SSID,
    .password = WIFI_PASS,
  },
};
ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));

The configuration of the wifi network starts with the definition of two constants: the SSID name and the password (WIFI_SSID e WIFI_PASS). The constants are then used to initialize the wifi_config struct; struct which is then passed as parameter to the esp_wifi_set_config method.

static esp_err_t event_handler(void *ctx, system_event_t *event)
{
switch(event->event_id) {
 
  case SYSTEM_EVENT_STA_START:
    esp_wifi_connect();
    break;
  case SYSTEM_EVENT_STA_GOT_IP:
    xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);
    break;  
  case SYSTEM_EVENT_STA_DISCONNECTED:
    xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
    break;

The event handler manages the different events triggered by the wifi driver as explained above. In particular, once the connection is established (STA_GOT_IP event), sets the CONNECTED_BIT event bit. On the contrary, in case of disconnection it clears the same event bit.

xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT, false, true, portMAX_DELAY);

The main task pauses its execution until the connection to the wifi network is perfomed, waiting for the CONNECTED_BIT bit to be set. The portMAX_DELAY constant will cause the task to block indefinitely (without a timeout).

tcpip_adapter_ip_info_t ip_info;
ESP_ERROR_CHECK(tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip_info));
printf("IP Address:  %s\n", ip4addr_ntoa(&ip_info.ip));
printf("Subnet mask: %s\n", ip4addr_ntoa(&ip_info.netmask));
printf("Gateway:     %s\n", ip4addr_ntoa(&ip_info.gw));

When the connection is established, the main task prints out the network parameters (IP address, netmask and gateway). The TCPIP_ADAPTER_IF_STA constant represents (see tcpip_adapter.h) the network interface of the esp32 chip when working in station mode.

Here’s a screenshot of the program in execution:

esp32-wifi06

To make its output clearer, I turned off the default logging of the wifi driver with the instruction:

esp_log_level_set("wifi", ESP_LOG_NONE);

and disabled the buffering for the standard output:

setvbuf(stdout, NULL, _IONBF, 0);

ESP32 (5) – Wifi scanner

The main feature of the esp32 chip, as it was for its predecessor esp8266, is for sure the ability to connect to wifi networks. I’ve already blogged in a previous article about the different standards (802.11 b/g/n) and security methods (WEP, WPA-PSK…) the chip supports; today I’m going to explain how to develop a program to scan for available wifi networks.

The complete program is available in my Github repository; let’s comment the source code.

As we know, every program using the esp-idf framework is executed starting from the app_main() method. This method starts initializing the tcpip stack and the wifi event handler:

tcpip_adapter_init();
ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL));
ESP_ERROR_CHECK is a handy macro, defined in esp_err.h, to check the result of the different framework methods. If a call returns the constant ESP_OK, the execution of the program can proceed; if not the program is stopped and the line in error is displayed on the serial output.

The esp-idf wifi stack requires a method (= event handler) that is called every time an event related to the wifi interface is triggered (for example the connection to a new network…). We’ll talk in a future tutorial about event handling, for now it’s enough to define an empty handler method:

static esp_err_t event_handler(void *ctx, system_event_t *event)
{
  return ESP_OK;
}

The program continues configuring, initializing and starting the wifi stack:

wifi_init_config_t wifi_config = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&wifi_config));
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_start());

You can create a default configuration with the WIFI_INIT_CONFIG_DEFAULT() macro and start the wifi interface in station mode with the constant WIFI_MODE_STA. Station mode is when the interface acts as a “client”, to connect to an access point.

Now you can configure and start the scan process:

wifi_scan_config_t scan_config = {
	.ssid = 0,
	.bssid = 0,
	.channel = 0,
        .show_hidden = true
};
ESP_ERROR_CHECK(esp_wifi_scan_start(&scan_config, true));

Using the scan_config variable you can configure some filters the scanning process will use (for example scan only a given channel) and ask to display or not the access points with hidden SSID. A zero value for each field means ALL (= no filter).

The scanning process can be executed in blocking (true) or non blocking (false) mode changing the second parameter of the function esp_wifi_scan_start. In this example I’m using the blocking mode: program execution is halted until the scan is completed.

When the scan is complete, you can obtain the list of the detected networks:

uint16_t ap_num = MAX_APs;
wifi_ap_record_t ap_records[MAX_APs];
ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&ap_num, ap_records));

The esp_wifi_scan_get_ap_records method returns an array of wifi_ap_record_t elements. You have to reserve some memory space to store that array, defining the maximum number of elements you want to retrieve. In my program, I defined therefore the MAX_APs constant:

#define MAX_APs 20
It may sound strange to pass the ap_num variable by reference to the esp_wifi_scan_get_ap_records method. The reason is that the method uses the variable in two ways (inout): as input to find the max capacity of the array passed as the second parameter and as output to return the effective number of networks found and stored in the array.

You can now print on the serial connection the details of the networks found:

printf("Found %d access points:\n", ap_num);
for(int i = 0; i < ap_num; i++)
	printf("%32s | %7d | %4d | %12s\n", 
	(char *)ap_records[i].ssid, ap_records[i].primary, ap_records[i].rssi,
	getAuthModeName(ap_records[i].authmode));

At the end, create an empty task to avoid a continuous reset of the chip:

xTaskCreate(&loop_task, "loop_task", 2048, NULL, 5, NULL);
[...]
void loop_task(void *pvParameter) {
    while(1) { 
        vTaskDelay(1000 / portTICK_RATE_MS);		
    }
}

During the program compilation, remember to activate the wifi module in the menuconfig (Component config – ESP32-specific config):

esp32-wifi01

Here’s the output of the program:

esp-scan02

ESP32 (4) – Flash, bootloader and FreeRTOS

In the previous posts I’ve described the new esp32 chip and explained how to setup the development environment, including the use of one – optional – graphical IDE, Eclipse.

Before starting to develop your programs, you need to understand three key components of the framework: flash, bootloader and FreeRTOS operating system. But don’t worry! At the end of this post you’ll write, compile and execute your first program (even if “minimal”) and starting from the next tutorial we’ll work together on more complex examples.

Flash

As I wrote in the first post of this tutorial, the esp32 chip requires an external flash memory to store programs, data, configuration parameters…

The external memory is connected to the chip via the SPI bus and the supported capacity is up to 16Mb. The official Espressif module (ESP-WROOM-32) includes a 4Mb flash memory made by GigaDevice (GD25Q32):

esp-flash01

The flash memory can store different elements: programs, data… hence it’s divided into sections (partitions). The list partitions, their size and position within the flash memory is stored in the memory itself (at address 0x8000) and it’s called partition table.

Two partition types are defined by default:

  • app (type 0) – partition that contains an application (program)
  • data (type 1) – partition that contains data

When writing a new program, the developer can decide how to organize the flash memory based on the program’s specific needs. The esp-idf framework offers two pre-configured memory layouts, you can choose from the configuration menu:

esp-flash02

You can also show the project’s partition table with the make partition_table command:

esp-flash03

In the screenshot above, you can see the partition table layout that corresponds to the simplest configuration (Single factory app, no OTA):

  • a data partition (named “nvs“) with a size of 24Kb starting from address 0x9000
  • a data partition (named “phy_init“) with a size of 4Kb starting from address 0xf000
  • an app partition (named “factory“) with a size of 1Mb starting from address 0x10000

Bootloader

To better understand why the flash memory is divided into partitions, I have to describe you how the esp32 bootloader works, that is how the application is started and how a new firmware is uploaded.

In the esp32 ROM memory there’s a small program, named first-stage bootloader. This program is executed at each reset of the chip: it configures the access to the external flash memory and, if required, stores on it new data coming from the serial/USB port (flash process). Once finished, it accesses the flash memory (at address 0x1000) and loads and executes the second-stage bootloader.

Even if the standard behavior of the esp-idf framework is to use this second-stage bootloader, you can also develop a stand-alone application that, stored at address 0x8000 of the flash memory, is directly executed by the first-stage bootloader.

The second-stage bootloader reads the partition table at address 0x8000 and searches for app partitions. It decides which application has to be executed based on the content of the otadata partition: if this partition is empty or doesn’t exist, the bootloaded executes the application stored in the factory partition. This allows to implement an over-the-air (OTA) application update process: you send the new version of your application to the esp32 chip; the version is stored in a new app partition. Once the upload is completed, the id of the partition is saved in otadata and the chip is rebooted; the bootloader will execute the new version:

esp-flash04

FreeRTOS

The esp-idf framework is based on the FreeRTOS Real-Time Operating System. It may sound strange to talk about operating systems when working on a “small” chip like the esp32… but you don’t have to think that FreeRTOS is an operating system like Windows, Linux or MacOS. The main feature an embedded operating system offers – thanks to its internal scheduler, is multitasking, that is the ability to run different tasks in parallel. We know that a microprocessor core can execute an instruction at a time: it seems that different applications run simultaneously because of the scheduler is rapidly switching between their tasks.

esp-flash05

different task statuses in FreeRTOS

A real-time operating system is design to guarantee that task scheduling is deterministic: you can predict the behavior of its scheduler. FreeRTOS allows the developer to define a priority for each task: the scheduler uses priority values to define the execution pattern of the different tasks.

Our first program

Let’s write our first program that used the esp-idf framework. This program, available in my Github repository, will be the skeleton for all the next tutorials.

First we must include some libraries:

esp-flash07

The stdio.h library (Standard I/O) is used for input/output functions (printf…) and the two freertos header files are used to define and execute the different tasks.

Every program is executed starting from the app_main() function:

esp-flash08

In the app_main() a new task is created using the xTaskCreate method. This method requires the following parameters: the pointer to the function (&loop_task) that contains task code, the name of the task (“loop_task”), the size, in words, of the stack memory to be assigned to the task (512), additional parameters for the task (NULL), the task’s priority (5) and the pointer – optional – to retrieve an handler of the task (NULL).

Once the task is created, the FreeRTOS scheduler executes it based on the different tasks and priorities.

The task has the following code:

esp-flash09

it’s a continuous loop that every second prints the sentence Hello World!”. The vTaskDelay method pauses the task for the specified number of ticks. The portTICK_RATE_MS constant defines the duration, in milliseconds, of a tick; if you therefore divide 1000 for that constant you get the number of ticks in a second.

Additional files

To be able to compile your project, you need to add a couple of additional files:

  • a Makefile in the main folder of your project that contains the name of the project and an include for the main makefile of the framework:

esp-flash10

  • component.mk file – it can be empty – in the folder where the source code is saved into to tell the compiler to process the files in that folder:

esp-flash11

Test

When the program is ready, you can compile and load it on the board as explained in a previous post:

make
make flash

If everything is ok, when you connect to the board with a serial emulator you should see:

esp-flash12

 

ESP32 (3) – Eclipse

In the previous blog post, I explained how to install the official development framework (Espressif IoT Development Framework) and how to use it to compile your first example, Hello world.

Even if you can write your programs with a simple text editor (on Windows I always suggest to use the opensource Notepad++), it’s very easier to do it with an IDE (Integrated Development Environment), that is a graphical application which you can use to write the code (it often includes syntax highlighting and auto-completion), to compile it and to upload the binary on you development board. Among the available IDEs, one of the most famous and adopted is without doubts Eclipse.

Installation

Eclipse installation is really straightforward: connect to the official site and download Eclipse IDE for C/C++ Developers for your operating system (as in the previous post, I decided to use Windows):

eclipse-01

Eclipse is shipped as a zip archive. When the download is complete, unzip the archive in a folder of your hard drive. To make it easy, I chose the same folder (the user’s home folder) where I’ve already installed the esp-idf:

eclipse-02

Run Eclipse with a double-click on eclipse.exe. At first, you are prompted to specify the path of your workspace (= the folder where your projects will be saved in). I chose to create the workspace as a subfolder of Eclipse’s installation folder:

eclipse-03

Project configuration

To be able to develop a project based on esp-idf with Eclipse, you need to do some configuration. First, import the project in the IDE. For this tutorial, I’m going to use the 01_hello_world example; in a future post I’ll show you how to start with an empty project.

Choose File – Import, then run the Existing Code as Makefile project wizard:

eclipse-04

Give the project a name, choose the folder that contains the code and make sure that the selected toolchain isCross GCC:

eclipse-05

When the import is complete, open the project Properties:

eclipse-06

First select C/C++ Build – Environment. Add (using the Add… button) two new variables:

  • name V, value 1
  • name IDF_PATH, value the path where esp-idf is installed (note: you have to use / instead of \)

On Windows, in addition, change the PATH variable with the following value (if msys32 is installed in the default path):

C:\msys32\usr\bin;C:\msys32\mingw32\bin;C:\msys32\opt\xtensa-esp32-elf\bin

eclipse-07

If you’re under Windows, now choose C/C++ Build, unflag Use default… and type the following build command:

bash ${IDF_PATH}/tools/windows/eclipse_make.sh

eclipse-08

The final step is to choose C/C++ General – Preprocessor Include Paths and open the Providers tab.

Click on CDT Cross GCC Built-in Compiler Settings and change the text ${COMMAND} with xtensa-esp32-elf-gcc:

eclipse-09

Then click on CDT GCC Build Output Parser and add xtensa-esp32-elf- at the beginning of the command:

eclipse-10

You’re now ready to compile the project:

eclipse-11

It may happens that, after the compile process, Eclipse warns about some “unresolved inclusion”:

eclipse-12

The warnings should appear only under Windows and they are caused by a known bug that doesn’t block the compile process. You may solve it manually adding the different include folders found starting from the folder esp-idf/components in the project properties (C/C++ General – Paths and Symbols – GNU C):

eclipse-13

Flash

You can also configure Eclipse to run the make flash command, to load the compiled version of your program on the development board.

Choose, on the right panel, the Build Targets tab:

eclipse-14

Right-click on the project’s folder and choose New.

Type flash as target name, then confirm with Ok:

eclipse-15

If now you right-click on the new command, you can start the flash process choosing Build Target:

eclipse-16

The Console tab, in the lower panel of the IDE, shows flash command output and result:

eclipse-17

ESP32 (2) – The development environment

After having introduced, in the previous post, the ESP32 chip and the official development board by Espressif, today I’m going to show you how to install and use the official development environment.

I’ll explain how to install the IDE under Windows because of it is the most complex procedure. The development environment its how-to-install guides are available also for Linux and MacOS.

The official development framework for the ESP32 chip is published by Expressif on Github, with an opensource license (Apache 2.0) and named Espressif IoT Development Framework (shortly idf).

To be able to use it under Windows, you need some GNU commands and toolchains (make, bash…). Espressif chose to offer a pre-configured version of MSYS2 that includes everything is required.

Download the ZIP archive from the Espressif website and unzip it in the root of your C drive; MSYS2 will be installed in the C:\msys32 subfolder:

esp32-07

Double-click on msys2_shell.cmd to run a bash shell:

esp32-08

We can now clone the idf Github repository to have a local copy of it. First we must choose a folder where it will be copied; to keep it simple let’s use the user’s home folder.

Move into that folder and run the clone command:

$ cd
$ git clone --recursive https://github.com/espressif/esp-idf.git

esp32-09

During the git clone command execution, you may get some error messages like: not a valid identifier; those errors are caused by a known bug and can be therefore ignored.

After the process is completed, the development framework is available in the esp32-idf subfolder:

esp32-10

To use if when compiling your projects, you have to export the path as an environment variable (IDF-PATH):

$ export IDF_PATH=~/esp-idf

Let’s connect the board!

You’re now ready to connect the development board to your computer.

If you already have the correct drivers, once connected it will appear as a new serial port:

esp32-12

On the contrary, the needed drivers are available on the Silicon Labs official website.

Hello World

As usual when learning a new language or framework, let’s start with a simple program that displays (using the serial-usb connection) the sentence Hello world.

The framework is distributed with some examples, among them you can find the 01_hello_world program. To compile it, move to its folder and run the make command:

$ cd
$ cd esp-idf/examples/01_hello_world/
$ make

A configuration menu will be displayed: we’ll talk more about it in a next post. Now you only need to tell the flasher which is the serial port your board is connected to.

Choose Serial flasher config, then /dev/ttyUSB0 (the default port):

esp32-14

Type the name of your serial port (as identified above), then confirm with OkSave and Exit:

esp32-16

When the compile process is complete, you can upload the program on the board with the following command:

make flash

esp32-17

Because of a bug, under Windows it may happen that the flash process is unsuccessful. The easiest solution is to keep the button boot pressed during all the flashing process.

If you now use a terminal emulator to connect to the serial port with the correct speed (115200 baud) you should see the program output:

esp32-18

(the program prints the sentence “Hello world!”, counts 10 seconds and then restart the chip…)

ESP32 (1) – Introduction

You probably already know the esp8266wifi chip, made by Espressif. It appeared on some Chinese webstores in the middle of 2014 and in the beginning it was used as a “bridge” to connect microcontrollers (Arduino…) to wifi networks thanks to its very low cost (about 5$ for a module).

esp32-01

Because of the original firmware was not well documented, it has some bugs and offered only “standard” features (via AT commands), the maker community developed some alternative firmwares  (the most famous of which is surely NodeMCU), to fully exploit the chip power and to build complete systems, without the need of external microcontrollers:

esp32-02

ESPToy, by Rayshobby

In September 2016, after a beta testing phase lasted some months, Espressif announced and made available the successor of esp8266, named ESP32.

The main features of the new chip are the following:

  • Tensilica LX6 dual core processor at 240 MHz
  • 520Kb of SRAM memory
  • Wifi 802.11 b/g/n connectivity with support for WEP, WPA/WPA2 PSK/Enterprise
  • Bluetooth connectivity (classic and LE)
  • 32 I/O pins with several builtin peripherals
  • hardware acceleration for security algorithms (AES, SHA2, RSA-4096)

With this post, I’m going to start a tutorial that will explore how to use this new chip to develop IoT systems and projects.

The ESP32 needs some additional components to work: a flash memory (to store firmware and data), a crystal (for the RTC), an antenna and some passive components. For this reason, you can find on sale ready-to-use modules:

esp32-03

On the left, the official module by Espressif (ESP-WROOM-32) while on the right the module made by Ai-Thinker (ESP-32S).

In addition, on some webstores you can start to find different development boards that include the ESP32 module, the power supply, an USB connector… everything you need to start building your project!

The esp32.net website keeps an updated list of the available boards; for my tutorials I chose the official Espressif development kit, named ESP32-DevKit3 or ESP32-CoreBoard. I brought mine from Olimex:

esp32-06 esp32-05

If you give a look to its schematics, you can find the ESP32 module, a voltage regulator (NCP1117) to lower the 5V coming from the USB bus to the 3.3V required by the chip, a CP2102 USB-to-serial adapter and two buttons. Moreover, all the most important PINs are available on the side connectors::

esp32-04

In the next post, you’ll learn how to install the development environment and how to write your first program!

MQTT – enc28j60 and Adafruit IO

Adafruit IO is the new cloud platform (at the moment still in beta) by Adafruit, designed to allow simple data connections using standard APIs and web-based dashboards.

This new platform exposes MQTT APIs, you can therefore apply what you learned in my previous posts to create a complete project that takes advantage of all its features.

The project

Goal of the project is to connect the Arduino to a dashboard hosted on Adafruit IO:

  • Arduino reads the room temperature (thanks to the DHT11 sensor already used in a previous tutorial) and sends its value every t seconds to Adafruit IO
  • Arduino is also able, using an external relay module, to turn on/off an heater
  • The dashboard on Adafruit IO displays the temperature values on a chart and has a toggle button to turn the heater connected to the Arduino on and off

mqtt-io1

Adafruit IO

Let’s see how to create the dashboard using Adafruit IO.

First, you need to subscribe to the open beta program using the button on the official website https://io.adafruit.com:

io-01

After having logged in with your Adafruit account (or having created a new account) you’ll be redirected to the default dashboard.

Click on My dashboards:

io-02

next click on Create dashboard:

io-03

give a name to the new dashboard, then click on Create dashboard:

io-04

you’ll see a completely empty dashboard. To add new elements (charts, buttons…) click on Create a new block:

io-05

choose line chart among the available elements and click on  Create:

io-06

you have to link the chart to a feed (= topic, in the MQTT language). Create a new feed named temperature:

io-07

once created, you can link it to the chart (choose), then click on next step:

io-08

you can change some chart settings (for example type °C as y-axis label), then complete the wizard with create block:

io-10

You’ll see a new block in your dashboard that represents the chart, empty for the moment. Using the drag’n’drop you can move the block in the page or resize it.

Now add the toggle button, again using Create a new block:

io-20

create a new feed named control:

io-21

and link it to the button:

io-22

give a label to the button and to the two possible values, then create the new block:

io-23

Your dashboard is now complete!

Feeds, topics and MQTT APIs

As explained above, each element of your dashboard can be linked to one or more feeds, that are data flows. For example you linked to the chart a feed named temperature, while the button is linked to another one named control.

If you use the MQTT APIs, each feed corresponds to a topic named as it follows:

<username>/f/<feedname>

where username is the name of your Adafruit account.

Hence, my two feeds correspond to the following two topics:

  • lucadentella/f/temperature
  • lucadentella/f/control

To connect to the Adafruit’s MQTT broker (address io.adafruit.com) you need to specify a username and a password.

The username is the name of your Adafruit account (lucadentella for me), while the password is your Adafruit IO key, that can be retrieved using a button in the dashboard:

io-31

io-32

You can test your dashboard using the mosquitto_sub command to subscribe to the topic linked to the toggle button. Every time you click on the button, the dashboard sends to that topic a message with the actual status of the button (ON or OFF):

mosquitto_sub.exe -h io.adafruit.com -u <username> -P <AIOkey> -t <username>/f/control

io-33

Arduino

The sketch for Arduino is available in my Github repository.

First, some constants are defined: the parameters for the Adafruit IO service (username, password, topics…) and the PINs the DHT11 sensor and the relay are connected to:

#define CLIENT_ID       "ArduinoMQTT"
#define USERNAME        "adafruit_username"
#define PASSWORD        "adafruit_io_key"
#define PUB_TOPIC       "username/f/temperature"
#define SUB_TOPIC       "username/f/control"
#define PUBLISH_DELAY   5000
#define DHTPIN          3
#define DHTTYPE         DHT11
#define RELAY_PIN       6

In the setup(),  the MQTT client is configured. Unlike the previous example, this time I also define the callback function that is called every time the client receives a new message from a topic that was subscribed:

// setup mqtt client
mqttClient.setClient(ethClient);
mqttClient.setServer("io.adafruit.com", 1883);
mqttClient.setCallback(mqttCallback);

Indeed, when the sketch connects to the broker it subscribes the topic linked to the button in the dashboard to be notified when a user clicks on it:

void mqttConnect() {
 
  while(!mqttClient.connected()) {
 
    if(mqttClient.connect(CLIENT_ID, USERNAME, PASSWORD)) {
 
      Serial.println(F("MQTT client connected"));
      mqttClient.subscribe(SUB_TOPIC);
      Serial.println(F("Topic subscribed"));
    } else {
      Serial.println(F("Unable to connect, retry in 5 seconds"));
      delay(5000);
    }
  }
}

The callback function checks if the message received is ON or OFF and changes the relay status accordingly:

void mqttCallback(char* topic, byte* payload, unsigned int length) {
 
  if(strncmp((const char*)payload, "ON", 2) == 0) {
    Serial.println("ON message received, turning relay ON");
    digitalWrite(RELAY_PIN, HIGH);
  } else {
    Serial.println("OFF message received, turning relay OFF");
    digitalWrite(RELAY_PIN, LOW);
  }
}

The following video shows the project in action: