Enter Your Project for a chance to win a Nano Grand Prize bundle for the most innovative use of Arduino plus a $400 shopping cart! Back to homepage | Project14 Home | |
Monthly Themes | ||
Monthly Theme Poll |
Introduction
Ireland, like much of the EU, is in lockdown and I now find myself locked out from using my Windows-10 laptop, which I need to use to complete my AVNET Guardian-100 MT3620 roadtest review. Yep, the wife and kids have commandeered the laptop for school work, Zoom video virtual meetups, "Microsoft teams" meetings and last but not least online games.
So, I'm left using my trusty old 32-bit Windows desktop in the “man den”, which is perfectly good for Arduino but not much else these days. So, I decided to tackle something I had put aside for years as I knew I could then use the project to test out a user interface on the Guardian-100 when I got the chance. This was not something I had preplanned to do but when needs must, why not as never say no to a learning opportunity.
As it was Arduino Day 2020 on March 21st, I decided to dust off an Arduino USB Host Shield from Sparkfun to see if I could get it to communicate with a FTDI USB to UART converter.
I quickly realised why I had put this aside all those years ago. There are not many helpful tutorials about and the Arduino USB Host library requires a good bit of knowledge about USB drivers before you can get anywhere. I also found that most tutorials and the library were tailored mainly to towards using game controllers and USB peripherals like a keyboard or a mouse. I could not find much on how to send data between two microcontrollers using USB for serial communication. For those wanting to understand the basics of the Universal Serial Bus, I found this article on ElectronicDesign.com quite useful.
To be honest, I've been rather surprised that this is not a common setup for MCU's like it is with SBC's (maybe it is and I'm not aware of it) as there are some interesting use cases to use USB for bi-directional data communication. I could certainly see use cases for cellular or LoRa communication more so than WiFi but the basis for application is the same, namely, when you may want to measure something but the area where you are sensing might not be suitable for wireless communication. As such you would need to place the antenna (a more common scenario) or the wireless controller in a place where it can communicate. For example, sensing something below ground like a cellar.
This time I had the time available and I had a problem to solve, i.e. I wanted to replicate a “brownfield” use-case scenario that could be used with the USB port on the Guardian 100. So, I crafted up the following top-level design:
Thankfully, the Guardian-100 uses an FTDI USB to UART controller and the Arduino USB Host library provides an example for the FTDI USB driver. A big tick then for the choice of component used on the Guardian-100 as there are many other USB chips out there that do not have ready made open source libraries, or it's more difficult to find the right information.
I happened to have two versions of the FTDI USB to UART controller as a breakout board and I even had an old FTDI Vinculo development board, which has since been discontinued. Either of the two breakout boards were ideal for my purposes as I could then attach either of these of my ESP8266 Huzzah breakout (from Adafruit), which does not include a USB to UART controller.
The “Brownfield” USB Host Application
My “brownfield” USB Host application is based off an Arduino UNO microcontroller. The application's functionality is as follows:
- a momentary push button is used to monetarily trigger relay_1. This is used to simulate a gate or a door and anything that could be actuated when the button is pushed.
- The light level, as measured from a light sensor, determines whether relay_2 is triggered. If the light level is below a threshold the relay remains on until the light level rises above the light threshold level. This could be used to turn on a light, for example.
- A proximity sensor is used to trigger the buzzer. A change in proximity state triggers the alarm. This could be used to monitor if an enclosure housing the electronics is open or closed.
- A green LED is used to inform the user of connectivity status. If the USB host shield is “running”, i.e. it detects that an FTDI USB driver is connected on the other end of the USB cable then the LED flashes, otherwise it is just stays on. If the USB peripheral (as in the ESP8266) confirms it has connected to the MQTT broker then the LED turns off. There is already a power LED that tells us the Arduino UNO is powered on.
- All event notifications are sent via the USB host to another USB controller device, which then forwards the data to the cloud via an ESP8266.
- A remote user can communicate via the cloud to enable or disable the relays and the buzzer.
{gallery:autoplay=false} Arduino UNO USB Host Application |
---|
I found that with developing USB Host application, it is quite straightforward when you know how. The big "gotcha" with using the USB Host shield is that you need to use an external power supply with the Arduino UNO and you need to connect D7 to the Arduino Reset pin for the USB Host library to function properly. I wasted a good few days struggling to get the USB Host shield to work when my Arduino UNO was just powered by the USB cable attached to my computer. The requirement to use a separate power supply is buried in the detail - I found this tip in one of the comments on the Sparkfun website product page and then subsequently on blog somewhere.
The USB Host library I used can be found on GitHub: https://github.com/felis/USB_Host_Shield_2.0
Another convenient way to install this library on your Arduino IDE is to use Library Manager:
Once the library is downloaded you are presented with quite a few examples. The examples provided can be split into a number of categories, namely:
- Game Controller (PlayStation/PS & Xbox)
- MIDI control;
- HID control;
- Bluetooth 2.0 SPP (Wii game controller examples found here too);
- CDC (Communications Device Class) Serial Terminal;
- USB Hub; and then
- Test & Utility functions.
I found that debugging / fault finding is quite tricky, especially when you have not powered your Arduino UNO with a separate power source. There is one utility example called “board_qc.ino”, which I found quite useful and it is well worth starting out with this example. This example runs through a number of “quality checks” and provides feedback on the serial monitor.
When the USB host shield is confirmed to be working, what we're then after is a CDC Terminal example that talks to a FTDI USB to UART driver and the example that is compatible is called “USBFTDILoopback.ino”. Note that there are three other CDC terminal driver examples available too, namely “acm_terminal.ino”, “XR_terminal.ino” and then some examples for “pl2303”.
{gallery:autoplay=false} USB CDC Terminal Examples Available |
---|
To make sense of this example and to help visualise the different run states I modified the code a bit to include some logic for a blinking LED. I used a fast blink LED when the USB Host fails to initialise and a slow blinking LED when the runtime “getUsbTaskState” check shows that the USB State is not shown to be running (i.e. not equal to USB_STATE_RUNNING).
Here is the modified code:
#include <cdcftdi.h> #include <usbhub.h> #include "pgmstrings.h" // Satisfy the IDE, which needs to see the include statment in the ino too. #ifdef dobogusinclude #include <spi4teensy3.h> #endif #include <SPI.h> #define LEDpin 2 class FTDIAsync : public FTDIAsyncOper { public: uint8_t OnInit(FTDI *pftdi); }; uint8_t FTDIAsync::OnInit(FTDI *pftdi) { uint8_t rcode = 0; rcode = pftdi->SetBaudRate(115200); if (rcode) { ErrorMessage<uint8_t>(PSTR("SetBaudRate"), rcode); return rcode; } rcode = pftdi->SetFlowControl(FTDI_SIO_DISABLE_FLOW_CTRL); if (rcode) ErrorMessage<uint8_t>(PSTR("SetFlowControl"), rcode); return rcode; } const uint16_t BLINK_DELAY_FAST = 250; const uint16_t BLINK_DELAY_SLOW = 750; const uint16_t INIT_RETRY = 5000; uint32_t t_retry = 0L; uint32_t t_blink = 0L; uint32_t t_now = 0L; bool LEDstate = 0; USB Usb; //USBHub Hub(&Usb); FTDIAsync FtdiAsync; FTDI Ftdi(&Usb, &FtdiAsync); void OSC_ErrorBlink() { while (1) { t_now = millis(); if (!t_retry) t_retry = t_now; else { if ((t_now - t_retry) > INIT_RETRY) { if (Usb.Init() != -1) { t_retry = 0; // reset to zero t_blink = 0; LEDstate = 0; digitalWrite(LEDpin, LOW); Serial.println("OSC Started"); break; } t_retry = t_now; } } if (!t_blink) t_blink = t_now; else { if ((t_now - t_blink) > BLINK_DELAY_FAST) { LEDstate = !LEDstate; digitalWrite(LEDpin, LEDstate); t_blink = t_now; } } } } void setup() { pinMode(LEDpin, OUTPUT); Serial.begin( 115200 ); #if !defined(__MIPSEL__) while (!Serial); // Wait for serial port to connect - used on Leonardo, Teensy and other boards with built-in USB CDC serial connection #endif Serial.println("Start"); if (Usb.Init() == -1) { Serial.println("OSC did not start."); OSC_ErrorBlink(); } delay( 200 ); } void loop() { Usb.Task(); if( Usb.getUsbTaskState() == USB_STATE_RUNNING ) { if (t_blink) { digitalWrite(LEDpin, HIGH); t_blink = 0; } uint8_t rcode; char strbuf[] = "DEADBEEF"; //char strbuf[] = "The quick brown fox jumps over the lazy dog"; //char strbuf[] = "This string contains 61 character to demonstrate FTDI buffers"; //add one symbol to it to see some garbage Serial.print("."); rcode = Ftdi.SndData(strlen(strbuf), (uint8_t*)strbuf); if (rcode) ErrorMessage<uint8_t>(PSTR("SndData"), rcode); delay(50); uint8_t buf[64]; for (uint8_t i=0; i<64; i++) buf[i] = 0; uint16_t rcvd = 64; rcode = Ftdi.RcvData(&rcvd, buf); if (rcode && rcode != hrNAK) ErrorMessage<uint8_t>(PSTR("Ret"), rcode); // The device reserves the first two bytes of data // to contain the current values of the modem and line status registers. if (rcvd > 2) Serial.print((char*)(buf+2)); delay(10); } else { t_now = millis(); if (!t_blink) t_blink = t_now; else { if ((t_now - t_blink) > BLINK_DELAY_SLOW) { LEDstate = !LEDstate; digitalWrite(LEDpin, LEDstate); t_blink = t_now; } } } }
And here is a video demonstration of the code in action:
With the logic of that example understood, I could now develop my own logic for my “brownfield” application. I started by developing a UML state sequence diagram to guide me:
Using ESP8266 to connect with CloudMQTT
As indicated above, I'm using an ESP8266 to connect to my WiFi router in order to establish a connection with and transmit data to cloudmqtt.com using MQTT as my publish and subscribe Internet transport protocol. Cloudmqtt is a hosted message broker for the Internet of Things and it has a free option for testing, although I believe the free instance option is sold out (so if you have a free account then great, if not you'll have to pay $5 per month for the “Humble Hedgehog” plan).
To get my ESP8266 to use MQTT I used an example found in the “PubSubClient” library, called “mqtt_esp8266.ino”. This library can be downloaded using the Arduino Library Manager:
I had to make a few minor tweaks to include the authorisation details and once this was added, I could then send and receive data from cloudmqtt. It was as simple as that.
/** * @filename : Element14_MT3620_ESP8266_MQTTbridge.ino * @brief : This software uses the ESP8266 PubSub library to communicate with * the CloudMQTT broker. This software is based on the examples found here * https://github.com/Protoneer/MQTT-ESP8266-CLOUDMQTT.COM * * It connects to an MQTT server then: * - publishes event data to "DG_Notify" as received via USB host * - subscribes to the topic "DG_Cmd", which sends these back to USB host * * It will reconnect to the server if the connection is lost using a blocking * reconnect function. See the 'mqtt_reconnect_nonblocking' example for how to * achieve the same result without blocking the main loop. * * @hardware : ESP8266 * * @author : Gerrikoio * * Copyright (C) Gerrikoio for Application 30 March 2020 * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documnetation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include <ESP8266WiFi.h> #include <PubSubClient.h> #include "my_connect_details.h" // Hardcoded values suitable for your network. const char* SSID_HC = WIFI_SSID; const char* PWD_HC = WIFI_PASS; const char* MQTT_SERVER = CLOUDMQTT_URL; const char* MQTT_USER = CLOUDMQTT_USER; const char* MQTT_PWD = CLOUDMQTT_PWD; const uint16_t MQTT_PORT = CLOUDMQTT_PORT; String ClientStrID = "ESP8266-DG-" + String(random(0xffff), HEX); const char* MQTT_CLIENTID = ClientStrID.c_str(); const uint16_t RECONNECT_DELAY = 5000; char *ssid, // the run-time network SSID text *pass; // the run-time network password text uint32_t t_now = 0L; uint32_t t_last = 0L; uint32_t t_reconnect = 0L; char msg[50]; int value = 0; bool MQTT_Server_Defined = true; WiFiClient espClient; PubSubClient client(espClient); void setup_wifi() { delay(10); WiFi.begin(ssid, pass); while (WiFi.status() != WL_CONNECTED) { delay(500); } randomSeed(micros()); } void callback(char* topic, byte* payload, unsigned int length) { //Serial.print("Message arrived ["); //Serial.print(topic); //Serial.print("] "); for (int i = 0; i < length; i++) { //Serial.print((char)payload[i]); } //Serial.println(); // Switch on the LED if an 1 was received as first character if ((char)payload[0] == '1') { digitalWrite(LED_BUILTIN, LOW); // Turn the LED on (Note that LOW is the voltage level // but actually the LED is on; this is because // it is active low on the ESP-01) } else { digitalWrite(LED_BUILTIN, HIGH); // Turn the LED off by making the voltage HIGH } } boolean Client_reconnect() { // Attempt to connect if (client.connect(MQTT_CLIENTID, MQTT_USER, MQTT_PWD)) { // Once connected, publish an announcement... client.publish("E14_Notify", "Now Connected"); // ... and resubscribe client.subscribe("E14_Cmd"); } return client.connected(); } void setup() { pinMode(LED_BUILTIN, OUTPUT); // Initialize the LED_BUILTIN pin as an output Serial.begin(115200); if (strlen(SSID_HC)> 2 && strlen(PWD_HC) > 2) { ssid = (char *) malloc(strlen(SSID_HC)+1); memcpy(ssid, (char *)SSID_HC, strlen(SSID_HC)+1); pass = (char *) malloc(strlen(PWD_HC)+1); memcpy(pass, (char *)PWD_HC, strlen(PWD_HC)+1); setup_wifi(); } else { digitalWrite(LED_BUILTIN, LOW); // Turn the LED on (Note that LOW is the voltage level while(1); } if (strlen(MQTT_SERVER)> 2) { cl ient.setServer(MQTT_SERVER, MQTT_PORT); client.setCallback(callback); } else { //Serial.println(F("MQTT Server is not defined...")); MQTT_Server_Defined = false; } } void loop() { if (MQTT_Server_Defined) { if (!client.connected()) { t_now = millis(); if (t_now - t_reconnect > RECONNECT_DELAY) { t_reconnect = now; // Attempt to reconnect if (Client_reconnect()) { t_reconnect = 0; } } } else { client.loop(); //t_now = millis(); //if (t_now - t_last > 15000) { // t_last = t_now; // ++value; // snprintf (msg, 50, "hello world #%ld", value); // Serial.print("Publish message: "); // Serial.println(msg); // client.publish("outTopic", msg); //} } } }
Demo Time for my “Brownfield” application
Pulling all the code together was just a systems integration exercise, making sure the receiver side could understand what the transmitter side was telling it. Took a bit of work and a fair bit of testing but there's nothing complex involved on coding side, and here is the result:
Top Comments