11. Networking and Communications¶

-
Group assignment:
-
Send a message between two projects
-
Document your work to the group work page and reflect on your individual page what you learned
-
Individual assignment:
-
design, build and connect wired or wireless node(s) with network or bus addresses and a local input and/or output device(s)
Group Assignment:¶
-
Group assignment:
-
Send a message between two projects
-
Document your work to the group work page and reflect on your individual page what you learned
Individual Assignment¶
Since I am planning to design my Final Project to utilize Motor as the Torque Sensor to control other motor to follow it as Bi-Lateral Control, my instructor Kae introduced me Serial Bus Servo Motor which can be used as a torque sensor.
Firstly, I tried to communicate with two Serial Bus Servo Motor using TX communication.
Serial Bus Servo Motor¶
Serial Bus Servo Motor has micro computer inside and by setting different ID for several motors you can use them at the same time with one PC board.
Serial Bus Servo Motor SCS0009 with my Development Board with Potentiometer
Serial Bus Servo Motor SCS0009 Data Sheet
FeeTech Servo Inner Memory table shows many outputs information that can be measured by the Serial Bus Servo Motor.
I might need to use following address information for my coding;
| Address | SCS command | R/W | initial value |
|---|---|---|---|
| 0x05 | ID | R/W | 00 |
| 0x28 | Torque ON/OFF | R/W | 00 |
| 0x2A | Target Pos(H) | R/W | - |
| 0x2B | Target Pos(L) | R/W | - |
| 0x38 | Current Pos(H) | R | - |
| 0x39 | Current Pos(L) | R | - |
| 0x3C | Current Load(H) | R | - |
| 0x3D | Current Load(L) | R | - |
Similar type of Servo Motor English information shared by {Miriam}() in Asian Review
Serial Bus Servo Motor SC09
Install “SC Servo Library” for Arduino IDE.
Set different ID into 2 Serial Bus Servo Motors to control by TX¶
Each Serial Bus Servo Motor’s ID is set ID=1 as default, so I need to set one motor’s ID=2.
Select “SC Servo” –> “SCSCL” –> “ProgramEprom”
To set Motor ID as “2”, modify the code at following coding part as follows.
int ID_Changeto = 2;

Then connect the target “Serial Bus Motor” to the PC Board.
Compile the “ProgramEprom” program and transfer to XIAO-ESP32C3.
Now, one Serial Bus Servo Motor’s ID was changed as “2”.
2 Serial Bus Servo Motors connected by wiring
Connection part of two Serial Bus Motors

Arduino IDE code for 2 motors control one by one with different rotational angles¶
As the first trial, my instuctor Kae shared me a reference program for Arduino IDE using UART communication for Serial Bus Servo Motors.
Following Program (made by Kae) coded to move Motor1 to move with small rotational angle, then move Motor2 with bigger angle.
Pulldown menu from “File” –> “Example” –> “SC Servo”
Select “SC Servo” –> “SCSCL” –> “WritePos”
Modify following part for XIAO-ESP32C3 Pin Set.
#define S_RXD 20
#define S_TXD 21
Following is the First Trial Code for 2 Serial Bus Servo Motors motion test.
/*
The normal write example passed the test in SCS15,
and if testing other models of SCS series servos
please change the appropriate position, speed and delay parameters.
*/
// the UART used to control servos.
// GPIO 18 - S_RXD, GPIO 19 - S_TXD, as default.
//#define S_RXD 18
//#define S_TXD 19
#define S_RXD 20
#define S_TXD 21
#include <SCServo.h>
SCSCL sc;
int pos;
void setup()
{
Serial.begin(115200);
Serial1.begin(1000000, SERIAL_8N1, S_RXD, S_TXD);
sc.pSerial = &Serial1;
delay(1000);
}
void loop()
{
sc.WritePos(1, 500, 0, 1500); // Servo(ID1) moves at max speed=1500, moves to position=1000.
delay(754); // [(P1-P0)/V]1000+100
pos = sc.ReadPos(1); // ID=1
Serial.print("Position: ");
Serial.println(pos);
delay(500);
sc.WritePos(2, 1000, 0, 1500); // Servo(ID1) moves at max speed=1500, moves to position=1000.
delay(754); // [(P1-P0)/V]1000+100
pos = sc.ReadPos(2); // ID=2
Serial.print("Position: ");
Serial.println(pos);
delay(500);
sc.WritePos(1, 50, 0, 1500); // Servo(ID1) moves at max speed=1500, moves to position=20.
delay(754); // [(P1-P0)/V]1000+100
pos = sc.ReadPos(1); // ID=1
Serial.print("Position: ");
Serial.println(pos);
delay(500);
sc.WritePos(2, 20, 0, 1500); // Servo(ID1) moves at max speed=1500, moves to position=20.
delay(754); // [(P1-P0)/V]1000+100
pos = sc.ReadPos(2); // ID=2
Serial.print("Position: ");
Serial.println(pos);
delay(500);
/*
int pos = sc.ReadPos(1); // ID=1
Serial.print("Position: ");
Serial.println(pos);
delay(500);
*/
}
Serial Bus Servo Motors 1st trial (Video)
Serial Bus Servo Motor1 and Motor2 are rotating one after another.
Modify code for potentiometer input and serial bus servo motors output¶
/*
The normal write example passed the test in SCS15,
and if testing other models of SCS series servos
please change the appropriate position, speed and delay parameters.
*/
// the UART used to control servos.
// GPIO 18 - S_RXD, GPIO 19 - S_TXD, as default.
//#define S_RXD 18
//#define S_TXD 19
#define S_RXD 20
#define S_TXD 21
// Set input signal for A2 Pin
const int POT_PIN = A2;
// Use SCServo Library
#include <SCServo.h>
SCSCL sc;
int pos1;
int pos2;
// Set Potentiometer value
int anavalue = 0;
void setup()
{
Serial.begin(115200);
// optional for ADC
pinMode(POT_PIN, INPUT);
Serial1.begin(1000000, SERIAL_8N1, S_RXD, S_TXD);
sc.pSerial = &Serial1;
delay(1000);
}
void loop()
{
anavalue = analogRead(POT_PIN); // typically 0..4095 on ESP32 ADC
anavalue = constrain(anavalue, 0, 4095);
// Map Potentiometer value -> angles
int angle1 = map(anavalue, 0, 2048, 0, 300); // normal phase
int angle2 = 300 - angle1; // opposite phase
// Safety clamp for Potentiometer
angle1 = constrain(angle1, 0, 300);
angle2 = constrain(angle2, 0, 300);
// sc.WritePos(1, 500, 0, 1500); // Servo(ID1) moves at max speed=1500, moves to position=500.
sc.WritePos(1, angle1, 0, 1500); // Servo(ID1) moves at max speed=1500, moves to position=angle1.
delay(754); // [(P1-P0)/V]1000+100
pos1 = sc.ReadPos(1); // ID=1
Serial.print("Position1: ");
Serial.println(pos1);
delay(500);
sc.WritePos(2, angle2, 0, 1500); // Servo(ID1) moves at max speed=1500, moves to position=angle2.
delay(754); // [(P1-P0)/V]1000+100
pos2 = sc.ReadPos(2); // ID=2
Serial.print("Position2: ");
Serial.println(pos2);
delay(500);
//sc.WritePos(1, 50, 0, 1500); // Servo(ID1) moves at max speed=1500, moves to position=50.
//delay(754); // [(P1-P0)/V]1000+100
//pos = sc.ReadPos(1); // ID=1
//Serial.print("Position: ");
//Serial.println(pos);
//delay(500);
//sc.WritePos(2, 20, 0, 1500); // Servo(ID1) moves at max speed=1500, moves to position=20.
//delay(754); // [(P1-P0)/V]1000+100
//pos = sc.ReadPos(2); // ID=2
//Serial.print("Position:");
//Serial.println(pos);
//delay(500);
// Debug
Serial.print("Potentio=");
Serial.print(anavalue);
Serial.print(" angle1=");
Serial.print(angle1);
Serial.print(" angle2=");
Serial.println(angle2);
// delay(20); // ~50Hz update
/*
int pos = sc.ReadPos(1); // ID=1
Serial.print("Position: ");
Serial.println(pos);
delay(500);
*/
}
Resulted as following video;
I found that it worked only 90 degree maximum each.
Then, changed coding as follows which also aim that 2 motors would work in smaller delay;
/*
The normal write example passed the test in SCS15,
and if testing other models of SCS series servos
please change the appropriate position, speed and delay parameters.
*/
// the UART used to control servos.
// GPIO 18 - S_RXD, GPIO 19 - S_TXD, as default.
//#define S_RXD 18
//#define S_TXD 19
#define S_RXD 20
#define S_TXD 21
// Set input signal for A2 Pin
const int POT_PIN = A2;
// Use SCServo Library
#include <SCServo.h>
SCSCL sc;
int pos1;
int pos2;
int maxpos=1000;
// Set Potentiometer value
int anavalue = 0;
void setup()
{
Serial.begin(115200);
// optional for ADC
pinMode(POT_PIN, INPUT);
Serial1.begin(1000000, SERIAL_8N1, S_RXD, S_TXD);
sc.pSerial = &Serial1;
delay(1000);
}
void loop()
{
anavalue = analogRead(POT_PIN); // typically 0..4095 on ESP32 ADC
anavalue = constrain(anavalue, 0, 4095);
// Map Potentiometer value -> angles
int angle1 = map(anavalue, 0, 4095, 0, maxpos); // normal phase
int angle2 = maxpos - angle1; // opposite phase
// Safety clamp for Potentiometer
angle1 = constrain(angle1, 0, maxpos);
angle2 = constrain(angle2, 0, maxpos);
// sc.WritePos(1, 500, 0, 1500); // Servo(ID1) moves at max speed=1500, moves to position=500.
sc.WritePos(1, angle1, 0, 1500); // Servo(ID1) moves at max speed=1500, moves to position=angle1.
sc.WritePos(2, angle2, 0, 1500); // Servo(ID1) moves at max speed=1500, moves to position=angle2.
//delay(754); // [(P1-P0)/V]1000+100
delay(100); // as a trial
pos1 = sc.ReadPos(1); // ID=1
pos2 = sc.ReadPos(2); // ID=2
Serial.print("Position1: ");
Serial.print("Position2: ");
Serial.println(pos1);
Serial.println(pos2);
//delay(500);
delay(20); // as a trial
//sc.WritePos(1, 50, 0, 1500); // Servo(ID1) moves at max speed=1500, moves to position=50.
//delay(754); // [(P1-P0)/V]1000+100
//pos = sc.ReadPos(1); // ID=1
//Serial.print("Position: ");
//Serial.println(pos);
//delay(500);
//sc.WritePos(2, 20, 0, 1500); // Servo(ID1) moves at max speed=1500, moves to position=20.
//delay(754); // [(P1-P0)/V]1000+100
//pos = sc.ReadPos(2); // ID=2
//Serial.print("Position:");
//Serial.println(pos);
//delay(500);
// Debug
Serial.print("Potentio=");
Serial.print(anavalue);
Serial.print(" angle1=");
Serial.print(angle1);
Serial.print(" angle2=");
Serial.println(angle2);
// delay(20); // ~50Hz update
/*
int pos = sc.ReadPos(1); // ID=1
Serial.print("Position: ");
Serial.println(pos);
delay(500);
*/
}
Result of angle increase and less delay coding as following video;
Close to what I want, but still Servo1 and Servo2 delay obvious delay exists.
Simultaneously motion of two serial bus servo motor possibility¶
Then, I decided to ask AI (Copilot) the way to improve the simultaneously motion of two motors with following Question.
You are the expert instructor of Fab Academy.
I would like you to teach me how I can control two Serial Bus Servo Motors by one potentiometer.
Necessary condition is as potentiometer input signal goes, One Serial Bus Motor follows the signal up to 300 degree, and the other serial Bus Motor follows the signal with opposite phase (angle) from the other motor but simultaneously.
My servo model is SCS0009.
The bus is single-wire (one data line).
I am using XIAO-ESP32C3 as a micro computer in the development board.
Based on AI (Copilot)’s information, I could have following information;¶
-
Most serial bus servos do not accept degrees directly. They accept a position count (an integer).¶
My variable maxpos = 1000; implies I am assuming the usable range is 0–1000 counts. Servo model uses something like: 0…1000 counts ≈ 0…240° (common in some series), or 0…1023 counts ≈ 0…300° (common in others), or 0…4095 counts ≈ 0…360° (higher resolution types) Since, my Servo works 0–300° angle, I should use “1023” as the max counts.
-
For Opposite phase (“mirrored angle”) mapping;¶
If Servo #1 follows:
potentio min -> 0°
potentio max -> 300°
Then Servo #2 should do:
potentio min -> 300°
potentio max -> 0°
I used following code for this
pos2 = maxpos -pos1;
However, AI (Copilot) suggested slightly different formula as follows;
pos2 = posMin + posMax - pos1;
-
Make both servos move simultaneously¶
With bus servos, you can make motion start at the same time by:
RegWritePos() to each servo (stores target but does not move yet)
then RegWriteAction() once (all stored targets begin at the same moment) - RegWritePos(…) to store the target
- RegWriteAction() to launch the move simultaneously
Example:
// Queue both positions, then start together
sc.RegWritePos(1, pos1, acc, speed);
sc.RegWritePos(2, pos2, acc, speed);
sc.RegWriteAction(); // both servos begin motion at the same time
With this coding, you create 50 small timing offsets to start both at the same time.
Many bus-servo protocols support this, and many SCServo variants expose it.
If your SCServo library supports these functions, use them.
If not, two WritePos() calls are usually “close enough,” but they are technically sequential.
-
Alternative: “SYNC_WRITE” (one packet for both servos)¶
Some libraries offer something like SyncWritePos(…) or a generic sync write function. The protocol defines INST_SYNC_WRITE (0x83) for multi-servo updates in one broadcast packet.
If your SCServo library supports SyncWrite, it is usually the cleanest approach: one bus message updates both. -
Other possibilities to improve motion delay¶
(A) Stop reading back too often (it can worsen timing) ReadPos() requires servo replies on the same half‑duplex bus; frequent reads can: * delay the next command * increase visible phase lag Because it’s half‑duplex, reads add traffic and timing sensitivity. So: read feedback at 5–10 Hz, not 50 Hz.
(B) Use the same “move duration” (if supported) Some SCS0009 code examples use a time (ms) parameter in WritePos. If your specific SCServo variant supports time-based moves, giving both servos the same move time reduces perceived mismatch.
-
ADC noise: smooth your potentiometer input¶
ESP32 ADC readings can be noisy. Without smoothing, your servos may “buzz” or jitter. Two easy fixes:
Exponential moving average (EMA) filter (software)
0.1 µF capacitor from pot wiper → GND (hardware) -
Fix the Serial prints (you currently print labels, not values)¶
My current code:
Serial.print("Position1: ");
Serial.print("Position2: ");
Serial.println(pos1);
Serial.println(pos2);
This prints “Position1: Position2:” then prints pos1 and pos2 on separate lines without labels.
This can be fixed as well.
-
Half‑duplex: use the SAME GPIO for TX+RX if possible?¶
Some platforms can do “single pin half duplex UART.” If your Arduino core + ESP32 variant supports it, you can map both RX and TX to the same pin. If not, keep separate pins but use the resistor combiner.
Rtx = 1 kΩ from MCU TX → DATA bus
Rrx = 1 kΩ from DATA bus → MCU RX (optional, but helps protect RX)
Optional pull‑up: 4.7–10 kΩ from DATA → 3.3V (sometimes helps with long cables)
ESP32 TX (S_TXD) ---[1k]---+---- DATA (servo white wire)
|
ESP32 RX (S_RXD) ---[1k]---+
|
(optional 4.7k~10k)
|
3.3V
-
Don’t spam ReadPos every 20 ms¶
Reading back from two servos at high frequency can: - create bus collisions
- slow down the loop
- amplify half‑duplex timing issues
Read less often (e.g., every 200 ms) or only for debugging.
Then got following code for 2 Serial Bus Servo Motors Simultaneous motion improvement¶
/*
Two Serial Bus Servos mirrored by one potentiometer.
Servo #1: follows pot 0 -> maxpos
Servo #2: follows pot maxpos -> 0 (opposite phase)
*/
#include <SCServo.h>
#define S_RXD 20
#define S_TXD 21
const int POT_PIN = A2;
const uint8_t ID1 = 1;
const uint8_t ID2 = 2;
// SCS0009: 0..1023 corresponds to 0..300° [2](https://www.feetechrc.com/6v-23kg-serial-bus-steering-gear_65522.html)[4](https://raspberrypi.mongonta.com/stack-chan-scs0009-arduino/)
const int POS_MIN = 0;
const int POS_MAX = 1023; // <-- set 1023 as the max position of 0-300° type Serial Bus Servo Motor
SCSCL sc;
// ADC filter (simple EMA to reduce jitter)
float filt = 0;
const float alpha = 0.15f; // 0.05~0.2 typical (higher = faster response)
// Motion settings
const int SPEED = 1500;
const int ACC = 0;
// Timing
uint32_t lastCmd = 0;
uint32_t lastRead = 0;
void setup() {
Serial.begin(115200);
pinMode(POT_PIN, INPUT);
// SCS0009 supports up to 1 Mbps [2](https://www.feetechrc.com/6v-23kg-serial-bus-steering-gear_65522.html)
Serial1.begin(1000000, SERIAL_8N1, S_RXD, S_TXD);
sc.pSerial = &Serial1;
delay(500);
Serial.println("SCS0009 mirror + ACTION start");
}
void loop() {
// --- Update rate ~50Hz (20ms) for smooth motion ---
if (millis() - lastCmd >= 20) {
lastCmd = millis();
int raw = analogRead(POT_PIN);
raw = constrain(raw, 0, 4095);
filt = (1.0f - alpha) * filt + alpha * raw;
int p1 = map((int)filt, 0, 4095, POS_MIN, POS_MAX);
p1 = constrain(p1, POS_MIN, POS_MAX);
int p2 = POS_MIN + POS_MAX - p1; // mirrored / opposite phase
// Queue both targets then start simultaneously (REG_WRITE + ACTION) [1](https://deepwiki.com/adityakamath/SCServo_Linux/10.5-protocol-specifications)
sc.RegWritePos(ID1, p1, ACC, SPEED);
sc.RegWritePos(ID2, p2, ACC, SPEED);
sc.RegWriteAction();
}
// Optional: feedback read at 5 Hz (keep low on half-duplex bus)
if (millis() - lastRead >= 200) {
lastRead = millis();
int fb1 = sc.ReadPos(ID1);
int fb2 = sc.ReadPos(ID2);
Serial.print("fb1="); Serial.print(fb1);
Serial.print(" fb2="); Serial.println(fb2);
}
}
The result of actual motion is as follows;
The simultaneous motion of 2 motors is improved, however the delay from the potentiometer got extremely worse.
Here under should be considered for the next step;
Why “step interpolation” (moveServos) is not the right fix here¶
Your function improves smoothness for PWM servos, but on a single serial bus it can actually make timing worse: For each of 50 steps, you still send two packets sequentially (ID1 then ID2) So you create 50 small offsets instead of one And if you also read back frequently, you add extra bus traffic
For bus servos, protocol-level synchronization (ACTION or SYNC_WRITE) is the right tool.¶
Quick diagnostic checklist (so I can verify the improvement)¶
After adding the 1 kΩ resistor(s) and switching to RegWrite + Action, check:
* Do both servos “kick off” together when you quickly twist the pot?
* If still slightly off: temporarily disable ReadPos() and see if synchronization improves. (Reads are bus traffic.)
When I explained my concern about Motors’s delay in the Asian Session, Rico shared me following information;
Servo Motor Control Simultaneously shared by Rico¶
Use one serial bus servo motor as a Torque Sensor and Move other motor by following the torque¶
Since I am planning to design my Final Project to utilize Motor as the Torque Sensor to control other motor to follow it as Bi-Lateral Control, my instructor Kae introduced me Serial Bus Servo Motor which can be used as a torque sensor.
Bus Servo Driver Board for Seeed Studio XIAO

Information of Bus Servo Driver Board for Seeed Studio XIAO
PCB Board Schematic information

System for Torque Measurement by Serial Servo Motor
Here under is the Code of Torque Measurement by Serial Bus Servo Motor for Arduino IDE.
#define S_RXD 20
#define S_TXD 21
#include <SCServo.h>
SCSCL sc;
int pos;
int load;
void setup()
{
Serial.begin(115200);
Serial1.begin(1000000, SERIAL_8N1, S_RXD, S_TXD);
sc.pSerial = &Serial1;
delay(1000);
}
void loop()
{
sc.WritePos(1, 1000, 0, 1500); // Servo(ID1) moves at max speed=1500, moves to position=1000.
delay(754); // [(P1-P0)/V]1000+100
pos = sc.ReadPos(1); // ID=1
//Serial.print("Position: ");
//Serial.println(pos);
Serial.print(pos);
delay(500);
load = sc.ReadLoad(1);
//0〜1023 : Forward Torque 1024〜2047: Reverse Torque
int direction = (load & 0x400) ? -1 : 1; // bit10
int torque = load & 0x3FF; // lower layer 10bit
int signedLoad = direction * torque;
//Serial.print("Load: ");
Serial.print(",");
Serial.println(signedLoad);
delay(500);
}