Starting With Arduino
In this chapter we will start programming our microcontrollers using the Arduino framework.
What you will learn
- Programming with Arduino
- A simple, event-driven programming model
Terminology
- event-driven
- A programming model where the flow of the program is determined by events such as user actions, sensor outputs, or messages from other programs
- state diagram
- A diagram used to describe the behavior of systems.
- sequence diagram
- An interaction diagram that shows how processes operate with one another and in what order.
Lecture
Planning out our Arduino WebSocket code
Programming our robot using Arduino
Before coding, remember our high-level pseudocode:
global state for module 1
global state for module 2
…
set update period for module 1
set update period for module 2
…
setup
initialize non-trivial global states
loop
short update/poll for module 1
short update/poll for module 2
…
Here’s the code for just connecting to WiFi:
#include <WiFi.h>
const char* SSID = "Pomona";
void setup() {
// Start serial connection for debugging
.begin(115200);
Serial
// Connect to WiFi network
.begin(SSID);
WiFi.printf("\n[SETUP] Connecting to '%s'...", SSID);
Serialwhile (WiFi.status() != WL_CONNECTED) {
(500);
delay.print(".");
Serial}
.print("done\n");
Serial
= WiFi.localIP();
IPAddress ip .printf("[SETUP] IP Address: %d.%d.%d.%d\n", ip[0], ip[1], ip[2], ip[3]);
Serial}
void loop() { /* nothing to do... */ }
Creating a WebSocket server on Arduino
Here’s the updated version including a WebSocket server:
#include <WiFi.h>
#include <WebSocketsServer.h>
typedef enum {
,
WS_ENABLED
WS_DISABLED} WsState;
const char* SSID = "Pomona";
const int PORT = 8181;
= WebSocketsServer(PORT);
WebSocketsServer webSocket
= WS_DISABLED;
WsState wsState
unsigned long heartbeatInterval = 1000;
unsigned long heartbeatLastTime = 0;
void webSocketEvent(uint8_t num, WStype_t type, uint8_t* payload, size_t length) {
;
IPAddress ip
switch (type) {
case WStype_DISCONNECTED:
= WS_DISABLED;
wsState .printf("[%u] Disconnected!\n", num);
Serialbreak;
case WStype_CONNECTED:
= webSocket.remoteIP(num);
ip .printf("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload);
Serial.sendTXT(num, "Connected");
webSocketbreak;
case WStype_TEXT:
if (strcmp((char*)payload, "heartbeat") == 0) {
= WS_ENABLED;
wsState = millis();
heartbeatLastTime } else {
.printf("[%u] get Text: %s\n", num, payload);
Serial}
break;
case WStype_BIN:
.printf("[%u] get binary length: %u\n", num, length);
Serialbreak;
case WStype_ERROR:
case WStype_FRAGMENT_TEXT_START:
case WStype_FRAGMENT_BIN_START:
case WStype_FRAGMENT:
case WStype_FRAGMENT_FIN:
.printf("[%u] received unhandled WS event type: %d\n", num, type);
Serialbreak;
}
}
void setup() {
// Start serial connection for debugging
.begin(115200);
Serial
// Connect to WiFi network
.begin(SSID);
WiFi.printf("\n[SETUP] Connecting to '%s'...", SSID);
Serialwhile (WiFi.status() != WL_CONNECTED) {
(500);
delay.print(".");
Serial}
.print("done\n");
Serial
= WiFi.localIP();
IPAddress ip .printf("[SETUP] IP Address: %d.%d.%d.%d\n", ip[0], ip[1], ip[2], ip[3]);
Serial
// Start WebSockets server
.begin();
webSocket.onEvent(webSocketEvent);
webSocket.printf("[SETUP] WebSocketsServer started at ws://%d.%d.%d.%d:%d\n", ip[0], ip[1], ip[2], ip[3], PORT);
Serial}
void loop() {
.loop();
webSocket
// Update for handling heartbeat
unsigned long now = millis();
if (now - heartbeatLastTime > heartbeatInterval) {
if (wsState == WS_ENABLED) {
= WS_DISABLED;
wsState .println("[HEARTBEAT] Heartbeat timeout");
Serial}
}
}
Blinking an LED when heartbeats are received
And here’s the final version with an LED blinking when heartbeats are received:
#include <WiFi.h>
#include <WebSocketsServer.h>
typedef enum {
,
WS_ENABLED
WS_DISABLED} WsState;
typedef enum {
= LOW,
LED_ON = HIGH
LED_OFF } LedState;
// ▄▄▄ ▀▀█ █ ▀▀█ ▄▄▄▄ ▄ ▄
// ▄▀ ▀ █ ▄▄▄ █▄▄▄ ▄▄▄ █ █▀ ▀ ▄▄█▄▄ ▄▄▄ ▄▄█▄▄ ▄▄▄
// █ ▄▄ █ █▀ ▀█ █▀ ▀█ ▀ █ █ ▀█▄▄▄ █ ▀ █ █ █▀ █
// █ █ █ █ █ █ █ ▄▀▀▀█ █ ▀█ █ ▄▀▀▀█ █ █▀▀▀▀
// ▀▄▄▄▀ ▀▄▄ ▀█▄█▀ ██▄█▀ ▀▄▄▀█ ▀▄▄ ▀▄▄▄█▀ ▀▄▄ ▀▄▄▀█ ▀▄▄ ▀█▄▄▀
// This is an open network that our microcontrollers can connect to
const char* SSID = "Pomona";
const int PORT = 8181;
= WebSocketsServer(PORT);
WebSocketsServer webSocket
// Heartbeat state
= WS_DISABLED;
WsState wsState
// LED state
= LED_OFF;
LedState ledState
// ▄ ▄ █ ▄ ▄▄▄▄▄ ▀ █
// █ █ ▄▄▄▄ ▄▄▄█ ▄▄▄ ▄▄█▄▄ ▄▄▄ █ ▀█ ▄▄▄ ▄ ▄▄ ▄▄▄ ▄▄▄ ▄▄▄█
// █ █ █▀ ▀█ █▀ ▀█ ▀ █ █ █▀ █ █▄▄▄█▀ █▀ █ █▀ ▀ █ █▀ ▀█ █▀ ▀█
// █ █ █ █ █ █ ▄▀▀▀█ █ █▀▀▀▀ █ █▀▀▀▀ █ █ █ █ █ █
// ▀▄▄▄▄▀ ██▄█▀ ▀█▄██ ▀▄▄▀█ ▀▄▄ ▀█▄▄▀ █ ▀█▄▄▀ █ ▄▄█▄▄ ▀█▄█▀ ▀█▄██
// █
// ▀
unsigned long heartbeatInterval = 1000;
unsigned long heartbeatLastTime = 0;
unsigned long blinkInterval = 250;
unsigned long blinkLastTime = 0;
// ▄ ▄ ▄▄▄▄ ▄ ▄ █ ▀▀█
// █ █ █ █▀ ▀ █ █ ▄▄▄ ▄ ▄▄ ▄▄▄█ █ ▄▄▄ ▄ ▄▄
// ▀ █▀█ █ ▀█▄▄▄ █▄▄▄▄█ ▀ █ █▀ █ █▀ ▀█ █ █▀ █ █▀ ▀
// ██ ██▀ ▀█ █ █ ▄▀▀▀█ █ █ █ █ █ █▀▀▀▀ █
// █ █ ▀▄▄▄█▀ █ █ ▀▄▄▀█ █ █ ▀█▄██ ▀▄▄ ▀█▄▄▀ █
void webSocketEvent(uint8_t num, WStype_t type, uint8_t* payload, size_t length) {
;
IPAddress ip
switch (type) {
case WStype_DISCONNECTED:
= WS_DISABLED;
wsState .printf("[%u] Disconnected!\n", num);
Serialbreak;
case WStype_CONNECTED:
= webSocket.remoteIP(num);
ip .printf("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload);
Serial.sendTXT(num, "Connected");
webSocketbreak;
case WStype_TEXT:
if (strcmp((char*)payload, "heartbeat") == 0) {
= WS_ENABLED;
wsState = millis();
heartbeatLastTime } else {
.printf("[%u] get Text: %s\n", num, payload);
Serial}
break;
case WStype_BIN:
.printf("[%u] get binary length: %u\n", num, length);
Serial// hexdump(payload, length);
// webSocket.sendBIN(num, payload, length);
break;
case WStype_ERROR:
case WStype_FRAGMENT_TEXT_START:
case WStype_FRAGMENT_BIN_START:
case WStype_FRAGMENT:
case WStype_FRAGMENT_FIN:
.printf("[%u] received unhandled WS event type: %d\n", num, type);
Serialbreak;
}
}
// ▄▄▄▄ ▄
// █▀ ▀ ▄▄▄ ▄▄█▄▄ ▄ ▄ ▄▄▄▄
// ▀█▄▄▄ █▀ █ █ █ █ █▀ ▀█
// ▀█ █▀▀▀▀ █ █ █ █ █
// ▀▄▄▄█▀ ▀█▄▄▀ ▀▄▄ ▀▄▄▀█ ██▄█▀
// █
// ▀
void setup() {
// Start serial connection for debugging
.begin(115200);
Serial
// Connect to WiFi network
.begin(SSID);
WiFi.printf("\n[SETUP] Connecting to '%s'...", SSID);
Serialwhile (WiFi.status() != WL_CONNECTED) {
(500);
delay.print(".");
Serial}
.print("done\n");
Serial
= WiFi.localIP();
IPAddress ip .printf("[SETUP] IP Address: %d.%d.%d.%d\n", ip[0], ip[1], ip[2], ip[3]);
Serial
// Start WebSockets server
.begin();
webSocket.onEvent(webSocketEvent);
webSocket.printf("[SETUP] WebSocketsServer started at ws://%d.%d.%d.%d:%d\n", ip[0], ip[1], ip[2], ip[3], PORT);
Serial
// Set the builtin LED pin as output
(LED_BUILTIN, OUTPUT);
pinMode(LED_BUILTIN, ledState);
digitalWrite
}
// ▄
// █ ▄▄▄ ▄▄▄ ▄▄▄▄
// █ █▀ ▀█ █▀ ▀█ █▀ ▀█
// █ █ █ █ █ █ █
// █▄▄▄▄▄ ▀█▄█▀ ▀█▄█▀ ██▄█▀
// █
// ▀
void loop() {
.loop();
webSocket
// Update for handling heartbeat
unsigned long now = millis();
if (now - heartbeatLastTime > heartbeatInterval) {
if (wsState == WS_ENABLED) {
= WS_DISABLED;
wsState .println("[HEARTBEAT] Heartbeat timeout");
Serial}
}
// Update for blinking the builtin LED
if (wsState == WS_ENABLED) {
= millis();
now if (now - blinkLastTime > blinkInterval) {
= now;
blinkLastTime
= (ledState == LED_ON) ? LED_OFF : LED_ON;
ledState (LED_BUILTIN, ledState);
digitalWrite}
} else {
(LED_BUILTIN, LED_OFF);
digitalWrite}
}
Exercise
Today, you will will write and run your first Arduino programs. Specifically, the goal is to write a program that blinks an LED when a heartbeat is received over a WebSocket connection.
You will submit your responses on gradescope. Only one partner should submit. The submitter will add the other partner through the gradescope interface.
Additional details for using gradescope can be found here:
You should open the gradescope assignment now so that you know what to work complete.
Grading
I will grade all exercises using a scale of “Nailed It” / “Not Yet”. See the course grading policy for more information, and check gradescope for deadlines.
Overview
First, make certain that your Arduino IDE is setup properly. Specifically, you need to add the ESP32 board manager from Espressif Systems and the WebSocket Server and Client for Arduino library.
Once you’re IDE is setup, you can grab the code from the lecture and run it on your microcontroller.
Wrap-Up
As it stands now, our Arduino code is a bit of a mess. We have a lot of global variables and the code is not very modular. In the next chapter, we’ll look at how to structure our code using a simple, event-driven programming model.