Machine Design
This week, I focused on designing, fabricating, and assembling a custom PCB. I used KiCad for schematic and PCB design, then fabricated the board using milling and laser etching, and finally assembled and tested it.
Learning Objectives
Assignments
Group Assignments
Individual Assignments
Group Assignment
Group AssignmentLearning Section
Communication Protocols
UART/I2C/SPI
WiFi/Bluetooth/ZigBee
NFC/RFID
MQTT/HTTP/Websockets

ESP-NOW WS2812B LED Control
ESP-NOW is a wireless communication protocol defined by Espressif, which enables the direct, quick and low-power control of smart devices, without the need of a router. ESP-NOW can work with Wi-Fi and Bluetooth LE, and supports the ESP8266, ESP32, ESP32-S and ESP32-C series of SoCs. It's widely used in smart-home appliances, remote controlling, sensors, etc.
Getting Started with ESP-NOWTo communicate via ESP-NOW, you need to know the MAC Address of the ESP32 receiver. That's how you know to which device you'll send the data to. Each ESP32 has a unique MAC Address and that's how we identify each board to send data to it using ESP-NOW
/*
Rui Santos & Sara Santos - Random Nerd Tutorials
Complete project details at https://RandomNerdTutorials.com/get-change-esp32-esp8266-mac-address-arduino/
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
#include
#include
void readMacAddress(){
uint8_t baseMac[6];
esp_err_t ret = esp_wifi_get_mac(WIFI_IF_STA, baseMac);
if (ret == ESP_OK) {
Serial.printf("%02x:%02x:%02x:%02x:%02x:%02x\n",
baseMac[0], baseMac[1], baseMac[2],
baseMac[3], baseMac[4], baseMac[5]);
} else {
Serial.println("Failed to read MAC address");
}
}
void setup(){
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.STA.begin();
Serial.print("[DEFAULT] ESP32 Board MAC Address: ");
readMacAddress();
}
void loop(){
}
For better understanding, we'll call “sender” to ESP32 #1 and “receiver” to ESP32 #2.
Here's what we should include in the sender sketch:
- Initialize ESP-NOW;
- Register a callback function upon sending data - the OnDataSent function will be executed when a message is sent. This can tell us if the message was successfully delivered or not;
- Add a peer device (the receiver). For this, you need to know the receiver MAC address;
- Send a message to the peer device.
On the receiver side, the sketch should include:
- Initialize ESP-NOW;
- Register for a receive callback function (OnDataRecv). This is a function that will be executed when a message is received.
- Inside that callback function, save the message into a variable to execute any task with that information.
typedef struct structName {
type member1;
type member2;
// ...
} typedefName;
Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");
This is a ternery operator,replacing if-else statement. If the status is successful, then print 'Delivery Success' else print 'Delivery Fail'
ESP32 Sender Code
/*
Rui Santos & Sara Santos - Random Nerd Tutorials
Complete project details at https://RandomNerdTutorials.com/esp-now-esp32-arduino-ide/
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
#include
#include
// REPLACE WITH YOUR RECEIVER MAC Address
uint8_t broadcastAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
// Structure example to send data
// Must match the receiver structure
typedef struct struct_message {
char a[32];
int b;
float c;
bool d;
} struct_message;
// Create a struct_message called myData
struct_message myData;
esp_now_peer_info_t peerInfo;
// callback when data is sent
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
Serial.print("\r\nLast Packet Send Status:\t");
Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");
}
void setup() {
// Init Serial Monitor
Serial.begin(115200);
// Set device as a Wi-Fi Station
WiFi.mode(WIFI_STA);
// Init ESP-NOW
if (esp_now_init() != ESP_OK) {
Serial.println("Error initializing ESP-NOW");
return;
}
// Once ESPNow is successfully Init, we will register for Send CB to
// get the status of Trasnmitted packet
esp_now_register_send_cb(OnDataSent);
// Register peer
memcpy(peerInfo.peer_addr, broadcastAddress, 6);
peerInfo.channel = 0;
peerInfo.encrypt = false;
// Add peer
if (esp_now_add_peer(&peerInfo) != ESP_OK){
Serial.println("Failed to add peer");
return;
}
}
void loop() {
// Set values to send
strcpy(myData.a, "THIS IS A CHAR");
myData.b = random(1,20);
myData.c = 1.2;
myData.d = false;
// Send message via ESP-NOW
esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &myData, sizeof(myData));
if (result == ESP_OK) {
Serial.println("Sent with success");
}
else {
Serial.println("Error sending the data");
}
delay(2000);
}
ESP32 receiver Code
/*
Rui Santos & Sara Santos - Random Nerd Tutorials
Complete project details at https://RandomNerdTutorials.com/esp-now-esp32-arduino-ide/
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
#include
#include
// Structure example to receive data
// Must match the sender structure
typedef struct struct_message {
char a[32];
int b;
float c;
bool d;
} struct_message;
// Create a struct_message called myData
struct_message myData;
// callback function that will be executed when data is received
void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) {
memcpy(&myData, incomingData, sizeof(myData));
Serial.print("Bytes received: ");
Serial.println(len);
Serial.print("Char: ");
Serial.println(myData.a);
Serial.print("Int: ");
Serial.println(myData.b);
Serial.print("Float: ");
Serial.println(myData.c);
Serial.print("Bool: ");
Serial.println(myData.d);
Serial.println();
}
void setup() {
// Initialize Serial Monitor
Serial.begin(115200);
// Set device as a Wi-Fi Station
WiFi.mode(WIFI_STA);
// Init ESP-NOW
if (esp_now_init() != ESP_OK) {
Serial.println("Error initializing ESP-NOW");
return;
}
// Once ESPNow is successfully Init, we will register for recv CB to
// get recv packer info
esp_now_register_recv_cb(esp_now_recv_cb_t(OnDataRecv));
}
void loop() {
}
ESP32 MCODE(Color) Sender
This code sends what brush size to select - 1,2,3 with 3 colors to select - RED, GREEN, BLUE.
- M051 - Brush 1
- M052 - Brush 2
- M053 - Brush 3
- M054 - LED OFF
By default, the LED will be RED. You can add color parameters with the M Code to select color(RED, GREEN, BLUE). For. eg. M051 GREEN
Firmware
Firmware Urumbu
Firmware to ESP32
/*
Rui Santos & Sara Santos - Random Nerd Tutorials
Modified by Ashish & ChatGPT for improved serial handling and user interaction.
Original: https://RandomNerdTutorials.com/esp-now-esp32-arduino-ide/
*/
#include < esp_now.h >
#include < WiFi.h >
// Replace with your receiver's MAC address
uint8_t broadcastAddress[] = {0x7C, 0x2C, 0x67, 0x64, 0xBA, 0xF8}; //7C:2C:67:64:BA:F8
int led;
String receivedData = "";
esp_now_peer_info_t peerInfo;
// Optional: onboard LED pin for visual feedback (usually GPIO 2)
const int onboardLED = 2;
// Callback when data is sent
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
Serial.print("\r\nLast Packet Send Status:\t");
Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");
// Blink onboard LED to indicate a send action
digitalWrite(onboardLED, HIGH);
delay(100);
digitalWrite(onboardLED, LOW);
}
void setup() {
// Init Serial Monitor
Serial.begin(115200);
WiFi.mode(WIFI_STA); // Set device as a Wi-Fi Station
// Set onboard LED pin as output
pinMode(onboardLED, OUTPUT);
digitalWrite(onboardLED, LOW);
// Info for user
Serial.println("ESP-NOW Command Sender Initialized");
Serial.println("Available Commands:");
Serial.println(" - M051 (LED 1)");
Serial.println(" - M052 (LED 2)");
Serial.println(" - M053 (LED 3)");
Serial.println(" - M054 (LED 4)");
Serial.println("Type your command and press Enter...\n");
// Init ESP-NOW
if (esp_now_init() != ESP_OK) {
Serial.println("Error initializing ESP-NOW");
return;
}
// Register callback for sent message
esp_now_register_send_cb(OnDataSent);
// Register peer
memcpy(peerInfo.peer_addr, broadcastAddress, 6);
peerInfo.channel = 0;
peerInfo.encrypt = false;
if (esp_now_add_peer(&peerInfo) != ESP_OK) {
Serial.println("Failed to add peer");
return;
}
}
void loop() {
// Check if data is available in Serial
if (Serial.available() > 0) {
char incomingChar = Serial.read();
if (incomingChar != '\n') {
receivedData += incomingChar;
} else {
receivedData.trim();
receivedData.toUpperCase();
Serial.print("Full command received: ");
Serial.println(receivedData);
esp_err_t result;
// Command handling
if(receivedData == "M051"){
led = 1;
result = esp_now_send(broadcastAddress, (uint8_t *)&led, sizeof(led));
}
else if (receivedData == "M051 GREEN") {
led = 11;
result = esp_now_send(broadcastAddress, (uint8_t *)&led, sizeof(led));
}
else if (receivedData == "M051 RED") {
led = 12;
result = esp_now_send(broadcastAddress, (uint8_t *)&led, sizeof(led));
}
else if (receivedData == "M051 BLUE") {
led = 13;
result = esp_now_send(broadcastAddress, (uint8_t *)&led, sizeof(led));
}
else if (receivedData == "M052") {
led = 2;
result = esp_now_send(broadcastAddress, (uint8_t *)&led, sizeof(led));
}
else if (receivedData == "M052 GREEN") {
led = 21;
result = esp_now_send(broadcastAddress, (uint8_t *)&led, sizeof(led));
}
else if (receivedData == "M052 RED") {
led = 22;
result = esp_now_send(broadcastAddress, (uint8_t *)&led, sizeof(led));
}
else if (receivedData == "M052 BLUE") {
led = 23;
result = esp_now_send(broadcastAddress, (uint8_t *)&led, sizeof(led));
}
else if (receivedData == "M053 BLUE") {
led = 3;
result = esp_now_send(broadcastAddress, (uint8_t *)&led, sizeof(led));
}
else if (receivedData == "M053 GREEN") {
led = 31;
result = esp_now_send(broadcastAddress, (uint8_t *)&led, sizeof(led));
}
else if (receivedData == "M053 RED") {
led = 32;
result = esp_now_send(broadcastAddress, (uint8_t *)&led, sizeof(led));
}
else if (receivedData == "M053 BLUE") {
led = 33;
result = esp_now_send(broadcastAddress, (uint8_t *)&led, sizeof(led));
}
else if (receivedData == "M054") {
led = 0;
result = esp_now_send(broadcastAddress, (uint8_t *)&led, sizeof(led));
}
else if (receivedData == "HELP") {
Serial.println("Available Commands:");
Serial.println(" - M051 GREEN/RED/BLUE(LED 1)");
Serial.println(" - M052 (LED 2)");
Serial.println(" - M053 (LED 3)");
Serial.println(" - M054 (LED 4)");
receivedData = "";
return;
} else {
Serial.println("Unknown command. Type HELP to see available commands.");
receivedData = "";
return;
}
// Check send result
if (result == ESP_OK) {
Serial.println("Command sent successfully");
} else {
Serial.println("Failed to send command");
}
receivedData = ""; // Clear input buffer
delay(500); // Optional delay
}
}
}
ESPNOW Reciever LED Control
#include
#include < WiFi.h >
#include < Adafruit_NeoPixel.h >
#define PIN 0 // Change 'D0' to 0 if you're using GPIO0
#define NUMPIXELS 7
int led = -1;
int lastLed = -1;
int brush = 0;
uint32_t color;
uint32_t green, red, blue;
Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
// ✅ ESP-NOW receive callback
void OnDataRecv(const esp_now_recv_info_t *recvInfo, const uint8_t *incomingData, int len) {
memcpy(&led, incomingData, sizeof(led));
// Serial.print("Bytes received: ");
// Serial.println(len);
Serial.print("LED value received: ");
Serial.println(led);
}
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
// Print MAC address (for pairing with sender)
Serial.print("Receiver MAC Address: ");
Serial.println(WiFi.macAddress());
if (esp_now_init() != ESP_OK) {
Serial.println("Error initializing ESP-NOW");
return;
}
esp_now_register_recv_cb(OnDataRecv); // Register receive callback
// Initialize NeoPixels
pixels.begin();
pixels.clear();
pixels.show();
green=pixels.Color(0,150,0);
red=pixels.Color(150,0,0);
blue=pixels.Color(0,0,150);
}
void loop() {
if (led != lastLed) {
lastLed = led;
pixels.clear();
// Determine number of pixels to light up
switch (led) {
case 1: brush = 1; color=red; break;
case 11: brush = 1; color=green; break;
case 12: brush = 1; color=red; break;
case 13: brush = 1; color=blue; break;
case 2: brush = 4; color=red;break;
case 21: brush = 4; color=green;break;
case 22: brush = 4; color=red;break;
case 23: brush = 4; color=blue;break;
case 3: brush = 7; color=red;break;
case 31: brush = 7; color=green;break;
case 32: brush = 7; color=red;break;
case 33: brush = 7; color=blue;break;
case 0: brush = 0; break;
default: brush = 0; break;
}
Serial.print("Brush level: ");
Serial.println(brush);
// Light up pixels
for (int i = 0; i < brush; i++) {
pixels.setPixelColor(i, color); // Green
}
pixels.show();
}
}
Sending Brush Size and Color using HEX
Sender Code
/*
Rui Santos & Sara Santos - Random Nerd Tutorials
Modified by Ashish & ChatGPT for improved serial handling and user interaction.
Original: https://RandomNerdTutorials.com/esp-now-esp32-arduino-ide/
*/
#include <esp_now.h>
#include <WiFi.h>
// Replace with your receiver's MAC address
uint8_t broadcastAddress[] = {0x7C, 0x2C, 0x67, 0x64, 0xBA, 0xF8}; //7C:2C:67:64:BA:F8
typedef struct struct_message {
int brushSize;
char hexColor[8]; // "#RRGGBB" + null terminator
} struct_message;
struct_message led;
String receivedData = "";
esp_now_peer_info_t peerInfo;
// Optional: onboard LED pin for visual feedback (usually GPIO 2)
const int onboardLED = 2;
// Callback when data is sent
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
Serial.print("\r\nLast Packet Send Status:\t");
Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");
// Blink onboard LED to indicate a send action
digitalWrite(onboardLED, HIGH);
delay(100);
digitalWrite(onboardLED, LOW);
}
void setup() {
// Init Serial Monitor
Serial.begin(115200);
WiFi.mode(WIFI_STA); // Set device as a Wi-Fi Station
// Set onboard LED pin as output
pinMode(onboardLED, OUTPUT);
digitalWrite(onboardLED, LOW);
// Info for user
Serial.println("ESP-NOW Command Sender Initialized");
Serial.println("Available Commands:");
Serial.println(" - M051 (LED 1)");
Serial.println(" - M052 (LED 2)");
Serial.println(" - M053 (LED 3)");
Serial.println(" - M054 (LED 4)");
Serial.println("Type your command and press Enter...\n");
// Init ESP-NOW
if (esp_now_init() != ESP_OK) {
Serial.println("Error initializing ESP-NOW");
return;
}
// Register callback for sent message
esp_now_register_send_cb(OnDataSent);
// Register peer
memcpy(peerInfo.peer_addr, broadcastAddress, 6);
peerInfo.channel = 0;
peerInfo.encrypt = false;
if (esp_now_add_peer(&peerInfo) != ESP_OK) {
Serial.println("Failed to add peer");
return;
}
}
void loop() {
if (Serial.available()) {
receivedData = Serial.readStringUntil('\n'); // Read until newline
receivedData.trim(); // Remove trailing \r or whitespace
if (receivedData.length() == 0) return;
// Extract command and color
int spaceIndex = receivedData.indexOf(' ');
String command, hexColor;
if (spaceIndex != -1) {
command = receivedData.substring(0, spaceIndex);
hexColor = receivedData.substring(spaceIndex + 1);
hexColor.trim();
hexColor.toUpperCase(); // Make sure color is uppercase
hexColor.toCharArray(led.hexColor, sizeof(led.hexColor));
} else {
command = receivedData;
strcpy(led.hexColor, "#FFFFFF");
}
command.toUpperCase(); // Convert command to uppercase for matching
if (command == "HELP") {
Serial.println("Available Commands:");
Serial.println(" - M051 <#HEXCOLOR> (LED 1)");
Serial.println(" - M052 (LED 2)");
Serial.println(" - M053 (LED 3)");
Serial.println(" - M054 (LED 4)");
return;
}
if (command == "M051") {
led.brushSize = 1;
} else if (command == "M052") {
led.brushSize = 2;
} else if (command == "M053") {
led.brushSize = 3;
} else if (command == "M054") {
led.brushSize = 0;
} else {
Serial.println("Unknown command. Type HELP to see available commands.");
return;
}
esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *)&led, sizeof(led));
Serial.print("Sending -> Brush Size: ");
Serial.print(led.brushSize);
Serial.print(" | Color: ");
Serial.println(led.hexColor);
Serial.println(result == ESP_OK ? "Command sent successfully" : "Failed to send command");
delay(500);
}
}
Recieving Brush Size and Hex Color
#include <esp_now.h>
#include <WiFi.h>
#include <Adafruit_NeoPixel.h>
#define PIN 0
#define NUMPIXELS 7
typedef struct struct_message {
int brushSize;
char hexColor[8]; // "#RRGGBB" + null terminator
} struct_message;
struct_message ledData;
int lastBrushSize = -1;
int brush = 0;
uint32_t color = 0;
Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
// Convert HEX string to RGB color
uint32_t hexToColor(const String &hex) {
String hexClean = hex;
if (hexClean.startsWith("#")) hexClean.remove(0, 1);
long number = strtol(hexClean.c_str(), NULL, 16);
int r = (number >> 16) & 0xFF;
int g = (number >> 8) & 0xFF;
int b = number & 0xFF;
return pixels.Color(r, g, b);
}
// Callback when data is received
void OnDataRecv(const esp_now_recv_info_t *recvInfo, const uint8_t *incomingData, int len) {
memcpy(&ledData, incomingData, sizeof(ledData));
Serial.print("Brush Size: ");
Serial.println(ledData.brushSize);
Serial.print("Hex Color: ");
Serial.println(ledData.hexColor);
color = hexToColor(String(ledData.hexColor));
Serial.print("RGB Color Code: ");
Serial.println(color);
}
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
Serial.print("Receiver MAC Address: ");
Serial.println(WiFi.macAddress());
if (esp_now_init() != ESP_OK) {
Serial.println("Error initializing ESP-NOW");
return;
}
esp_now_register_recv_cb(OnDataRecv);
pixels.begin();
pixels.clear();
pixels.show();
}
void loop() {
if (ledData.brushSize != lastBrushSize || brush > 0) {
lastBrushSize = ledData.brushSize;
switch (ledData.brushSize) {
case 1: brush = 1; break;
case 2: brush = 4; break;
case 3: brush = 7; break;
default: brush = 0; break;
}
pixels.clear();
for (int i = 0; i < brush; i++) {
pixels.setPixelColor(i, color);
}
pixels.show();
Serial.print("Lighting up ");
Serial.print(brush);
Serial.println(" pixels.");
}
}
Neoπ Wireless CNC Controller (3-axis)
The NeoPi Wireless CNC Controller was designed by Saheen Palayi, our instructor. It offers a 3 Axis CNC Contoller which runs using the ESP32 as the Main Control Unit of the controller. We used the DRV8825 as motor drivers for the two motors.
Neoπ CNC Controller - DocumentationThe CNC Machine works on Core X Y Configuration.
Firmware - GRBL ESP32
GRBL ESP32 Github
Design Files
You can download my design files from below
NFC+Motor XIAO ESP32C6 Board
Milling Files
- KiCad Files
Laser Files