WIP: snapshot before repo migration

This commit is contained in:
2026-05-27 02:14:33 +08:00
parent aa7ed7cf51
commit ec41ed8a48
51 changed files with 3119 additions and 2015 deletions

View File

@@ -15,6 +15,7 @@ CompileFlags:
- -isystem/home/fallensigh/.platformio/packages/toolchain-xtensa-esp32s3/lib/gcc/xtensa-esp32s3-elf/8.4.0/include-fixed
- -isystem/home/fallensigh/.platformio/packages/toolchain-xtensa-esp32s3/xtensa-esp32s3-elf/sys-include
- -isystem/home/fallensigh/.platformio/packages/toolchain-xtensa-esp32s3/xtensa-esp32s3-elf/include
- -Ilib/FTServo/src
Remove:
- -mlongcalls
- -fstrict-volatile-bitfields

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 ftservo
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation 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
furnished 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 FOR 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.

View File

@@ -0,0 +1,44 @@
# FEETECH BUS Servo
> FEETECH BUS Servo library for Arduino and ESP32
## Table of Contents
<!-- TOC -->
* [FEETECH BUS Servo](#ft-series-servo)
* [Table of Contents](#table-of-contents)
* [Requirements](#requirements)
* [Usage](#usage)
* [Notes](#notes)
* [Release](#release)
<!-- TOC -->
## Requirements
* arduino-1.6.10 or newer(https://www.arduino.cc/).
## Usage
For usage examples, see the [examples](./examples) directory.
## Notes
The code for the `SCServo` library is divided into the following
parts:
* communication layer: [src/SCS.cpp](src/SCS.cpp)
* hardware interface layer: [src/SCSerial.cpp](src/SCSerial.cpp)
* application layer:
* corresponds to the three series of FEETECH BUS Servo
* `SCSCL` application layer program: [src/SCSCL.h](src/SCSCL.h) and [src/SCSCL.cpp](src/SCSCL.cpp)
* `SMS` and `STS` application layer program: [src/SMS_STS.h](src/SMS_STS.h) and [src/SMS_STS.cpp](src/SMS_STS.cpp)
* `HLS` application layer program: [src/HLSCL.h](src/HLSCL.h) and [src/HLSCL.cpp](src/HLSCL.cpp)
* instruction definition header file: [src/INST.h](src/INST.h)
* communication layer program: [src/SCS.h](src/SCS.h) and [src/SCS.cpp](src/SCS.cpp)
* hardware interface program: [src/SCSerial.h](src/SCSerial.h) and [src/SCSerial.cpp](src/SCSerial.cpp)
Note that there are differences in the memory table definitions of different series of FEETECH BUS servos.
## Release
New (GitHub) releases of this library are automatically ingressed by the Arduino Library Manager.

View File

@@ -0,0 +1,22 @@
#include <SCServo.h>
HLSCL hlscl;
void setup()
{
//Serial1.begin(1000000, SERIAL_8N1, 18, 17);//esp32-s3
Serial1.begin(1000000);//mega2560
hlscl.pSerial = &Serial1;
delay(1000);
}
void loop()
{
//舵机(广播)以最高速度V=60*0.732=43.92rpm加速度A=50*8.7deg/s^2最大扭矩电流T=500*6.5=3250mA运行至P1=4095位置
hlscl.WritePosEx(0xfe, 4095, 60, 50, 500);
delay((4095-0)*1000/(60*50) + (60*50)*10/(50) + 50);//[(P1-P0)/(V*50)]*1000+[(V*50)/(A*100)]*1000 + 50(误差)
//舵机(广播)以最高速度V=60*0.732=43.92rpm加速度A=50*8.7deg/s^2最大扭矩电流T=500*6.5=3250mA运行至P0=0位置
hlscl.WritePosEx(0xfe, 0, 60, 50, 500);
delay((4095-0)*1000/(60*50) + (60*50)*10/(50) + 50);//[(P1-P0)/(V*50)]*1000+[(V*50)/(A*100)]*1000 + 50(误差)
}

View File

@@ -0,0 +1,28 @@
/*
中位校准例子
*/
#include <SCServo.h>
HLSCL hlscl;
void setup()
{
Serial.begin(115200);
//Serial1.begin(1000000, SERIAL_8N1, 18, 17);//esp32-s3
Serial1.begin(1000000);//mega2560
hlscl.pSerial = &Serial1;
delay(1000);
hlscl.CalibrationOfs(1);
delay(10);
}
void loop()
{
int pos = hlscl.ReadPos(1);
if(!hlscl.getLastError()){
Serial.print("mid pos:");
Serial.println(pos);
}
delay(1000);
}

View File

@@ -0,0 +1,148 @@
/*
回读所有舵机反馈参数:位置、速度、负载、电压、温度、移动状态、电流;
FeedBack函数回读舵机参数于缓冲区Readxxx(-1)函数返回缓冲区中相应的舵机状态;
函数Readxxx(ID)ID=-1返回FeedBack缓冲区参数ID>=0通过读指令直接返回指定ID舵机状态,
无需调用FeedBack函数。
*/
#include <SCServo.h>
HLSCL hlscl;
int LEDpin = 13;
void setup()
{
pinMode(LEDpin,OUTPUT);
digitalWrite(LEDpin, HIGH);
Serial1.begin(1000000);//mega2560
//Serial1.begin(1000000, SERIAL_8N1, 18, 17);//esp32-s32
Serial.begin(115200);
hlscl.pSerial = &Serial1;
delay(1000);
}
void loop()
{
int Pos;
int Speed;
int Load;
int Voltage;
int Temper;
int Move;
int Current;
hlscl.FeedBack(1);
if(!hlscl.getLastError()){
digitalWrite(LEDpin, LOW);
Pos = hlscl.ReadPos(-1);
Speed = hlscl.ReadSpeed(-1);
Load = hlscl.ReadLoad(-1);
Voltage = hlscl.ReadVoltage(-1);
Temper = hlscl.ReadTemper(-1);
Move = hlscl.ReadMove(-1);
Current = hlscl.ReadCurrent(-1);
Serial.print("Position:");
Serial.println(Pos);
Serial.print("Speed:");
Serial.println(Speed);
Serial.print("Load:");
Serial.println(Load);
Serial.print("Voltage:");
Serial.println(Voltage);
Serial.print("Temper:");
Serial.println(Temper);
Serial.print("Move:");
Serial.println(Move);
Serial.print("Current:");
Serial.println(Current);
delay(10);
}else{
digitalWrite(LEDpin, HIGH);
Serial.println("FeedBack err");
delay(500);
}
Pos = hlscl.ReadPos(1);
if(!hlscl.getLastError()){
digitalWrite(LEDpin, LOW);
Serial.print("Servo position:");
Serial.println(Pos, DEC);
delay(10);
}else{
Serial.println("read position err");
digitalWrite(LEDpin, HIGH);
delay(500);
}
Voltage = hlscl.ReadVoltage(1);
if(!hlscl.getLastError()){
digitalWrite(LEDpin, LOW);
Serial.print("Servo Voltage:");
Serial.println(Voltage, DEC);
delay(10);
}else{
Serial.println("read Voltage err");
digitalWrite(LEDpin, HIGH);
delay(500);
}
Temper = hlscl.ReadTemper(1);
if(!hlscl.getLastError()){
digitalWrite(LEDpin, LOW);
Serial.print("Servo temperature:");
Serial.println(Temper, DEC);
delay(10);
}else{
Serial.println("read temperature err");
digitalWrite(LEDpin, HIGH);
delay(500);
}
Speed = hlscl.ReadSpeed(1);
if(!hlscl.getLastError()){
digitalWrite(LEDpin, LOW);
Serial.print("Servo Speed:");
Serial.println(Speed, DEC);
delay(10);
}else{
Serial.println("read Speed err");
digitalWrite(LEDpin, HIGH);
delay(500);
}
Load = hlscl.ReadLoad(1);
if(!hlscl.getLastError()){
digitalWrite(LEDpin, LOW);
Serial.print("Servo Load:");
Serial.println(Load, DEC);
delay(10);
}else{
Serial.println("read Load err");
digitalWrite(LEDpin, HIGH);
delay(500);
}
Current = hlscl.ReadCurrent(1);
if(!hlscl.getLastError()){
digitalWrite(LEDpin, LOW);
Serial.print("Servo Current:");
Serial.println(Current, DEC);
delay(10);
}else{
Serial.println("read Current err");
digitalWrite(LEDpin, HIGH);
delay(500);
}
Move = hlscl.ReadMove(1);
if(!hlscl.getLastError()){
digitalWrite(LEDpin, LOW);
Serial.print("Servo Move:");
Serial.println(Move, DEC);
delay(10);
}else{
Serial.println("read Move err");
digitalWrite(LEDpin, HIGH);
delay(500);
}
Serial.println();
}

View File

@@ -0,0 +1,34 @@
/*
Ping指令测试,测试总线上相应ID舵机是否就绪,广播指令只适用于总线只有一个舵机情况
*/
#include <SCServo.h>
HLSCL hlscl;
int LEDpin = 13;
void setup()
{
pinMode(LEDpin,OUTPUT);
digitalWrite(LEDpin, HIGH);
Serial.begin(115200);
//Serial1.begin(1000000, SERIAL_8N1, 18, 17);//esp32-s3
Serial1.begin(1000000);//mega2560
hlscl.pSerial = &Serial1;
delay(1000);
}
void loop()
{
int ID = hlscl.Ping(1);
if(!hlscl.getLastError()){
digitalWrite(LEDpin, LOW);
Serial.print("Servo ID:");
Serial.println(ID, DEC);
delay(100);
}else{
Serial.println("Ping servo ID error!");
digitalWrite(LEDpin, HIGH);
delay(2000);
}
}

View File

@@ -0,0 +1,27 @@
/*
舵机参数编程
*/
#include <SCServo.h>
int LEDpin = 13;
HLSCL hlscl;
void setup()
{
pinMode(LEDpin, OUTPUT);
//Serial1.begin(1000000, SERIAL_8N1, 18, 17);//esp32-s3
Serial1.begin(1000000);//mega2560
hlscl.pSerial = &Serial1;
delay(1000);
digitalWrite(LEDpin, LOW);
hlscl.unLockEprom(1);//打开EPROM保存功能
hlscl.writeByte(1, HLSCL_ID, 2);//ID
hlscl.LockEprom(2);//关闭EPROM保存功能
digitalWrite(LEDpin, HIGH);
}
void loop()
{
}

View File

@@ -0,0 +1,26 @@
#include <SCServo.h>
HLSCL hlscl;
void setup()
{
//Serial1.begin(1000000, SERIAL_8N1, 18, 17);//esp32-s3
Serial1.begin(1000000);//mega2560
hlscl.pSerial = &Serial1;
delay(1000);
}
void loop()
{
//舵机(ID1/ID2)以最高速度V=60*0.732=43.92rpm加速度A=50*8.7deg/s^2最大扭矩电流T=3500*6.5=3250mA运行至P1=4095位置
hlscl.RegWritePosEx(1, 4095, 60, 50, 500);
hlscl.RegWritePosEx(2, 4095, 60, 50, 500);
hlscl.RegWriteAction();
delay((4095-0)*1000/(60*50) + (60*50)*10/(50) + 50);//[(P1-P0)/(V*50)]*1000+[(V*50)/(A*100)]*1000 + 50(误差)
//舵机(ID1/ID2)以最高速度V=60*0.732=43.92rpm加速度A=50*8.7deg/s^2最大扭矩电流T=500*6.5=3250mA运行至P1=4095位置
hlscl.RegWritePosEx(1, 0, 60, 50, 500);
hlscl.RegWritePosEx(2, 0, 60, 50, 500);
hlscl.RegWriteAction();
delay((4095-0)*1000/(60*50) + (60*50)*10/(50) + 50);//[(P1-P0)/(V*50)]*1000+[(V*50)/(A*100)]*1000 + 50(误差)
}

View File

@@ -0,0 +1,45 @@
/*
同步读指令回读ID1与ID2两个舵机的位置与速度信息
*/
#include <SCServo.h>
HLSCL hlscl;
uint8_t ID[] = {1, 2};
uint8_t rxPacket[4];
int16_t Position;
int16_t Speed;
void setup()
{
Serial.begin(115200);
//Serial1.begin(1000000, SERIAL_8N1, 18, 17);//esp32-s3
Serial1.begin(1000000);//mega2560
hlscl.pSerial = &Serial1;
hlscl.syncReadBegin(sizeof(ID), sizeof(rxPacket), 5);//10*10*2=200us<5ms
delay(1000);
}
void loop()
{
hlscl.syncReadPacketTx(ID, sizeof(ID), HLSCL_PRESENT_POSITION_L, sizeof(rxPacket));//同步读指令包发送
for(uint8_t i=0; i<sizeof(ID); i++){
//接收ID[i]同步读返回包
if(!hlscl.syncReadPacketRx(ID[i], rxPacket)){
Serial.print("ID:");
Serial.println(ID[i]);
Serial.println("sync read error!");
continue;//接收解码失败
}
Position = hlscl.syncReadRxPacketToWrod(15);//解码两个字节 bit15为方向位,参数=0表示无方向位
Speed = hlscl.syncReadRxPacketToWrod(15);//解码两个字节 bit15为方向位,参数=0表示无方向位
Serial.print("ID:");
Serial.println(ID[i]);
Serial.print("Position:");
Serial.println(Position);
Serial.print("Speed:");
Serial.println(Speed);
}
delay(10);
}

View File

@@ -0,0 +1,39 @@
#include <SCServo.h>
HLSCL hlscl;
byte ID[2];
s16 Position[2];
u16 Speed[2];
byte ACC[2];
u16 Torque[2];
void setup()
{
//Serial1.begin(1000000, SERIAL_8N1, 18, 17);//esp32-s3
Serial1.begin(1000000);//mega2560
hlscl.pSerial = &Serial1;
delay(1000);
ID[0] = 1;//舵机ID1
ID[1] = 2;//舵机ID2
Speed[0] = 60;//最高速度V=60*0.732=43.92rpm
Speed[1] = 60;//最高速度V=60*0.732=43.92rpm
ACC[0] = 50;//加速度A=50*8.7deg/s^2
ACC[1] = 50;//加速度A=50*8.7deg/s^2
Torque[0] = 300;//最大扭矩电流T=500*6.5=3250mA
Torque[1] = 300;//最大扭矩电流T=500*6.5=3250mA
}
void loop()
{
//舵机(ID1/ID2)以最高速度V=60*0.732=43.92rpm加速度A=50*8.7deg/s^2最大扭矩电流T=500*6.5=3250mA运行至P1=4095位置
Position[0] = 4095;
Position[1] = 4095;
hlscl.SyncWritePosEx(ID, 2, Position, Speed, ACC, Torque);
delay((4095-0)*1000/(60*50) + (60*50)*10/(50) + 50);//[(P1-P0)/(V*50)]*1000+[(V*50)/(A*100)]*1000 + 50(误差)
//舵机(ID1/ID2)以最高速度V=60*0.732=43.92rpm加速度A=50*8.7deg/s^2最大扭矩电流T=500*6.5=3250mA运行至P0=0位置
Position[0] = 0;
Position[1] = 0;
hlscl.SyncWritePosEx(ID, 2, Position, Speed, ACC, Torque);
delay((4095-0)*1000/(60*50) + (60*50)*10/(50) + 50);//[(P1-P0)/(V*50)]*1000+[(V*50)/(A*100)]*1000 + 50(误差)
}

View File

@@ -0,0 +1,50 @@
#include <SCServo.h>
HLSCL hlscl;
byte ID[2];
s16 Speed[2];
byte ACC[2];
u16 Torque[2];
void setup()
{
//Serial1.begin(1000000, SERIAL_8N1, 18, 17);//esp32-s3
Serial1.begin(1000000);//mega2560
hlscl.pSerial = &Serial1;
delay(1000);
hlscl.WheelMode(1);//舵机ID1切换至恒速模式
hlscl.WheelMode(2);//舵机ID2切换至恒速模式
ID[0] = 1;//舵机ID1
ID[1] = 2;//舵机ID2
ACC[0] = 50;//加速度A=50*8.7deg/s^2
ACC[1] = 50;//加速度A=50*8.7deg/s^2
Torque[0] = 500;//最大扭矩电流T=500*6.5=3250mA
Torque[1] = 500;//最大扭矩电流T=500*6.5=3250mA
}
void loop()
{
//舵机(ID1/ID2)以加速度A=50*8.7deg/s^2加速至最高速度V=60*0.732=43.92rpm并保持恒速运行最大扭矩电流T=500*6.5=3250mA
Speed[0] = 60;
Speed[1] = 60;
hlscl.SyncWriteSpe(ID, 2, Speed, ACC, Torque);
delay(5000);
//舵机(ID1/ID2)以加速度A=50*8.7deg/s^2减速至速度0停止运行
Speed[0] = 0;
Speed[1] = 0;
hlscl.SyncWriteSpe(ID, 2, Speed, ACC, Torque);
delay(2000);
//舵机(ID1/ID2)以加速度A=50*8.7deg/s^2加速至最高速度V=-60*0.732=-43.92rpm并保持恒速运行最大扭矩电流T=500*6.5=3250mA
Speed[0] = -60;
Speed[1] = -60;
hlscl.SyncWriteSpe(ID, 2, Speed, ACC, Torque);
delay(5000);
//舵机(ID1/ID2)以加速度A=50*8.7deg/s^2减速至速度0停止运行
Speed[0] = 0;
Speed[1] = 0;
hlscl.SyncWriteSpe(ID, 2, Speed, ACC, Torque);
delay(2000);
}

View File

@@ -0,0 +1,31 @@
#include <SCServo.h>
HLSCL hlscl;
void setup()
{
//Serial1.begin(1000000, SERIAL_8N1, 18, 17);//esp32-s3
Serial1.begin(1000000);//mega2560
hlscl.pSerial = &Serial1;
delay(1000);
hlscl.EleMode(1);//舵机ID1切换至电机恒力模式
}
void loop()
{
//舵机(ID1)以最大扭矩电流T=300*6.5=1950mA正向旋转
hlscl.WriteEle(1, 300);
delay(5000);
//舵机(ID1/ID2)以扭矩0停止旋转
hlscl.WriteEle(1, 0);
delay(2000);
//舵机(ID1)以最大扭矩电流T=300*6.5=1950mA反向旋转
hlscl.WriteEle(1, -300);
delay(5000);
//舵机(ID1)以扭矩0停止旋转
hlscl.WriteEle(1, 0);
delay(2000);
}

View File

@@ -0,0 +1,22 @@
#include <SCServo.h>
HLSCL hlscl;
void setup()
{
//Serial1.begin(1000000, SERIAL_8N1, 18, 17);//esp32-s3
Serial1.begin(1000000);//mega2560
hlscl.pSerial = &Serial1;
delay(1000);
}
void loop()
{
//舵机(ID1)以最高速度V=60*0.732=43.92rpm加速度A=50*8.7deg/s^2最大扭矩电流T=500*6.5=3250mA运行至P1=4095位置
hlscl.WritePosEx(1, 4095, 60, 50, 500);
delay((4095-0)*1000/(60*50) + (60*50)*10/(50) + 50);//[(P1-P0)/(V*50)]*1000+[(V*50)/(A*100)]*1000 + 50(误差)
//舵机(ID1)以最高速度V=60*0.732=43.92rpm加速度A=50*8.7deg/s^2最大扭矩电流T=500*6.5=3250mA运行至P0=0位置
hlscl.WritePosEx(1, 0, 60, 50, 500);
delay((4095-0)*1000/(60*50) + (60*50)*10/(50) + 50);//[(P1-P0)/(V*50)]*1000+[(V*50)/(A*100)]*1000 + 50(误差)
}

View File

@@ -0,0 +1,31 @@
#include <SCServo.h>
HLSCL hlscl;
void setup()
{
//Serial1.begin(1000000, SERIAL_8N1, 18, 17);//esp32-s3
Serial1.begin(1000000);//mega2560
hlscl.pSerial = &Serial1;
delay(1000);
hlscl.WheelMode(1);//舵机ID1切换至电机恒速模式
}
void loop()
{
//舵机(ID1/ID2)以加速度A=50*8.7deg/s^2加速至最高速度V=60*0.732=43.92rpm并保持恒速正向旋转最大扭矩电流T=500*6.5=3250mA
hlscl.WriteSpe(1, 60, 50, 500);
delay(5000);
//舵机(ID1/ID2)以加速度A=50*8.7deg/s^2减速至速度0停止旋转
hlscl.WriteSpe(1, 0, 50, 500);
delay(2000);
//舵机(ID1/ID2)以加速度A=50*8.7deg/s^2加速至最高速度V=-60*0.732=-43.92rpm并保持恒速反向旋转最大扭矩电流T=500*6.5=3250mA
hlscl.WriteSpe(1, -60, 50, 500);
delay(5000);
//舵机(ID1/ID2)以加速度A=50*8.7deg/s^2减速至速度0停止旋转
hlscl.WriteSpe(1, 0, 50, 500);
delay(2000);
}

View File

@@ -0,0 +1,25 @@
/*
广播写例子在SCS15中测试通过如果测试其它型号SCS系列舵机请更改合适的位置、速度与延时参数。
*/
#include <SCServo.h>
SCSCL sc;
void setup()
{
Serial1.begin(1000000);
sc.pSerial = &Serial1;
delay(1000);
}
void loop()
{
//舵机(广播)以最高速度V=1500*0.059=88.5rpm运行至P1=1000位置
sc.WritePos(0xfe, 1000, 0, 1500);
delay((1000-20)*1000/(1500) + 100);//[(P1-P0)/(V)]*1000 + 100(误差)
//舵机(广播)以最高速度V=1500*0.059=88.5rpm运行至P0=20位置
sc.WritePos(0xfe, 20, 0, 1500);
delay((1000-20)*1000/(1500) + 100);//[(P1-P0)/(V)]*1000 + 100(误差)
}

View File

@@ -0,0 +1,147 @@
/*
回读所有舵机反馈参数:位置、速度、负载、电压、温度、移动状态、电流;
FeedBack函数回读舵机参数于缓冲区Readxxx(-1)函数返回缓冲区中相应的舵机状态;
函数Readxxx(ID)ID=-1返回FeedBack缓冲区参数ID>=0通过读指令直接返回指定ID舵机状态,
无需调用FeedBack函数。
*/
#include <SCServo.h>
SCSCL sc;
int LEDpin = 13;
void setup()
{
pinMode(LEDpin,OUTPUT);
digitalWrite(LEDpin, HIGH);
Serial1.begin(1000000);
Serial.begin(115200);
sc.pSerial = &Serial1;
delay(1000);
}
void loop()
{
int Pos;
int Speed;
int Load;
int Voltage;
int Temper;
int Move;
int Current;
sc.FeedBack(1);
if(!sc.getLastError()){
digitalWrite(LEDpin, LOW);
Pos = sc.ReadPos(-1);
Speed = sc.ReadSpeed(-1);
Load = sc.ReadLoad(-1);
Voltage = sc.ReadVoltage(-1);
Temper = sc.ReadTemper(-1);
Move = sc.ReadMove(-1);
Current = sc.ReadCurrent(-1);
Serial.print("Position:");
Serial.println(Pos);
Serial.print("Speed:");
Serial.println(Speed);
Serial.print("Load:");
Serial.println(Load);
Serial.print("Voltage:");
Serial.println(Voltage);
Serial.print("Temper:");
Serial.println(Temper);
Serial.print("Move:");
Serial.println(Move);
Serial.print("Current:");
Serial.println(Current);
delay(10);
}else{
digitalWrite(LEDpin, HIGH);
Serial.println("FeedBack err");
delay(500);
}
Pos = sc.ReadPos(1);
if(!sc.getLastError()){
digitalWrite(LEDpin, LOW);
Serial.print("Servo position:");
Serial.println(Pos, DEC);
delay(10);
}else{
Serial.println("read position err");
digitalWrite(LEDpin, HIGH);
delay(500);
}
Voltage = sc.ReadVoltage(1);
if(!sc.getLastError()){
digitalWrite(LEDpin, LOW);
Serial.print("Servo Voltage:");
Serial.println(Voltage, DEC);
delay(10);
}else{
Serial.println("read Voltage err");
digitalWrite(LEDpin, HIGH);
delay(500);
}
Temper = sc.ReadTemper(1);
if(!sc.getLastError()){
digitalWrite(LEDpin, LOW);
Serial.print("Servo temperature:");
Serial.println(Temper, DEC);
delay(10);
}else{
Serial.println("read temperature err");
digitalWrite(LEDpin, HIGH);
delay(500);
}
Speed = sc.ReadSpeed(1);
if(!sc.getLastError()){
digitalWrite(LEDpin, LOW);
Serial.print("Servo Speed:");
Serial.println(Speed, DEC);
delay(10);
}else{
Serial.println("read Speed err");
digitalWrite(LEDpin, HIGH);
delay(500);
}
Load = sc.ReadLoad(1);
if(!sc.getLastError()){
digitalWrite(LEDpin, LOW);
Serial.print("Servo Load:");
Serial.println(Load, DEC);
delay(10);
}else{
Serial.println("read Load err");
digitalWrite(LEDpin, HIGH);
delay(500);
}
Current = sc.ReadCurrent(1);
if(!sc.getLastError()){
digitalWrite(LEDpin, LOW);
Serial.print("Servo Current:");
Serial.println(Current, DEC);
delay(10);
}else{
Serial.println("read Current err");
digitalWrite(LEDpin, HIGH);
delay(500);
}
Move = sc.ReadMove(1);
if(!sc.getLastError()){
digitalWrite(LEDpin, LOW);
Serial.print("Servo Move:");
Serial.println(Move, DEC);
delay(10);
}else{
Serial.println("read Move err");
digitalWrite(LEDpin, HIGH);
delay(500);
}
Serial.println();
}

View File

@@ -0,0 +1,33 @@
/*
Ping指令测试,测试总线上相应ID舵机是否就绪,广播指令只适用于总线只有一个舵机情况
*/
#include <SCServo.h>
SCSCL sc;
int LEDpin = 13;
void setup()
{
pinMode(LEDpin,OUTPUT);
digitalWrite(LEDpin, HIGH);
Serial.begin(115200);
Serial1.begin(1000000);
sc.pSerial = &Serial1;
delay(1000);
}
void loop()
{
int ID = sc.Ping(1);
if(!sc.getLastError()){
digitalWrite(LEDpin, LOW);
Serial.print("Servo ID:");
Serial.println(ID, DEC);
delay(100);
}else{
Serial.println("Ping servo ID error!");
digitalWrite(LEDpin, HIGH);
delay(2000);
}
}

View File

@@ -0,0 +1,28 @@
/*
舵机参数编程
*/
#include <SCServo.h>
int LEDpin = 13;
SCSCL sc;
void setup()
{
pinMode(LEDpin, OUTPUT);
Serial1.begin(1000000);
sc.pSerial = &Serial1;
delay(1000);
digitalWrite(LEDpin, LOW);
sc.unLockEprom(1);//打开EPROM保存功能
sc.writeByte(1, SCSCL_ID, 2);//ID
sc.writeWord(2, SCSCL_MIN_ANGLE_LIMIT_L, 20);
sc.writeWord(2, SCSCL_MAX_ANGLE_LIMIT_L, 1000);
sc.LockEprom(2);////关闭EPROM保存功能
digitalWrite(LEDpin, HIGH);
}
void loop()
{
}

View File

@@ -0,0 +1,29 @@
/*
异步写例子在SCS15中测试通过如果测试其它型号SCS系列舵机请更改合适的位置、速度与延时参数。
*/
#include <SCServo.h>
SCSCL sc;
void setup()
{
Serial1.begin(1000000);
sc.pSerial = &Serial1;
delay(1000);
}
void loop()
{
//舵机(ID1/ID2)以最高速度V=1500*0.059=88.5rpm运行至P1=1000位置
sc.RegWritePos(1, 1000, 0, 1500);
sc.RegWritePos(2, 1000, 0, 1500);
sc.RegWriteAction();
delay((1000-20)*1000/(1500) + 100);//[(P1-P0)/(V)]*1000 + 100(误差)
//舵机(ID1/ID2)以最高速度V=1500*0.059=88.5rpm运行至P0=20位置
sc.RegWritePos(1, 20, 0, 1500);
sc.RegWritePos(2, 20, 0, 1500);
sc.RegWriteAction();
delay((1000-20)*1000/(1500) + 100);//[(P1-P0)/(V)]*1000 + 100(误差)
}

View File

@@ -0,0 +1,37 @@
/*
同步写例子在SCS15中测试通过如果测试其它型号SCS系列舵机请更改合适的位置、速度与延时参数。
*/
#include <SCServo.h>
SCSCL sc;
byte ID[2];
u16 Position[2];
u16 Speed[2];
void setup()
{
Serial1.begin(1000000);
sc.pSerial = &Serial1;
delay(1000);
ID[0] = 1;//舵机ID1
ID[1] = 2;//舵机ID2
Speed[0] = 1500;//最高速度V=1500*0.059=88.5rpm
Speed[1] = 1500;//最高速度V=1500*0.059=88.5rpm
}
void loop()
{
//舵机(ID1/ID2)以最高速度V=1500*0.059=88.5rpm运行至P1=1000位置
Position[0] = 1000;
Position[1] = 1000;
sc.SyncWritePos(ID, 2, Position, 0, Speed);
delay((1000-20)*1000/(1500) + 100);//[(P1-P0)/(V)]*1000 + 100(误差)
//舵机(ID1/ID2)以最高速度V=1500*0.059=88.5rpm运行至P0=20位置
Position[0] = 20;
Position[1] = 20;
sc.SyncWritePos(ID, 2, Position, 0, Speed);
delay((1000-20)*1000/(1500) + 100);//[(P1-P0)/(V)]*1000 + 100(误差)
}

View File

@@ -0,0 +1,34 @@
/*
电机模式例子
*/
#include <SCServo.h>
SCSCL sc;
void setup()
{
Serial1.begin(1000000);
sc.pSerial = &Serial1;
delay(1000);
sc.PWMMode(1);//舵机切换到PWM开环调速度模式
}
void loop()
{
//舵机(ID1)以最大50%扭矩正向旋转
sc.WritePWM(1, 500);
delay(2000);
//舵机(ID1)停止旋转
sc.WritePWM(1, 0);
delay(2000);
//舵机(ID1)以最大50%扭矩反向旋转
sc.WritePWM(1, -500);
delay(2000);
//舵机(ID1)停止旋转
sc.WritePWM(1,0);
delay(2000);
}

View File

@@ -0,0 +1,25 @@
/*
普通写例子在SCS15中测试通过如果测试其它型号SCS系列舵机请更改合适的位置、速度与延时参数。
*/
#include <SCServo.h>
SCSCL sc;
void setup()
{
Serial1.begin(1000000);
sc.pSerial = &Serial1;
delay(1000);
}
void loop()
{
//舵机(ID1)以最高速度V=1500*0.059=88.5rpm运行至P1=1000位置
sc.WritePos(1, 1000, 0, 1500);
delay((1000-20)*1000/(1500) + 100);//[(P1-P0)/(V)]*1000 + 100(误差)
//舵机(ID1)以最高速度V=1500*0.059=88.5rpm运行至P0=20位置
sc.WritePos(1, 20, 0, 1500);
delay((1000-20)*1000/(1500) + 100);//[(P1-P0)/(V)]*1000 + 100(误差)
}

View File

@@ -0,0 +1,22 @@
#include <SCServo.h>
SMS_STS sms_sts;
void setup()
{
//Serial1.begin(115200);//sms舵机波特率115200
Serial1.begin(1000000);//sts舵机波特率1000000
sms_sts.pSerial = &Serial1;
delay(1000);
}
void loop()
{
//舵机(广播)以最高速度V=60*0.732=43.92rpm加速度A=50*8.7deg/s^2运行至P1=4095位置
sms_sts.WritePosEx(0xfe, 4095, 60, 50);
delay((4095-0)*1000/(60*50) + (60*50)*10/(50) + 50);//[(P1-P0)/(V*50)]*1000+[(V*50)/(A*100)]*1000 + 50(误差)
//舵机(广播)以最高速度V=60*0.732=43.92rpm加速度A=50*8.7deg/s^2运行至P0=0位置
sms_sts.WritePosEx(0xfe, 0, 60, 50);
delay((4095-0)*1000/(60*50) + (60*50)*10/(50) + 50);//[(P1-P0)/(V*50)]*1000+[(V*50)/(A*100)]*1000 + 50(误差)
}

View File

@@ -0,0 +1,25 @@
/*
中位校准例子
*/
#include <SCServo.h>
int LEDpin = 13;
SMS_STS sm_st;
void setup()
{
pinMode(LEDpin, OUTPUT);
//Serial1.begin(115200);//sms舵机波特率115200
Serial1.begin(1000000);//sts舵机波特率1000000
sm_st.pSerial = &Serial1;
delay(1000);
}
void loop()
{
digitalWrite(LEDpin, LOW);
sm_st.CalibrationOfs(1);
digitalWrite(LEDpin, HIGH);
while(1);
}

View File

@@ -0,0 +1,148 @@
/*
回读所有舵机反馈参数:位置、速度、负载、电压、温度、移动状态、电流;
FeedBack函数回读舵机参数于缓冲区Readxxx(-1)函数返回缓冲区中相应的舵机状态;
函数Readxxx(ID)ID=-1返回FeedBack缓冲区参数ID>=0通过读指令直接返回指定ID舵机状态,
无需调用FeedBack函数。
*/
#include <SCServo.h>
SMS_STS sms_sts;
int LEDpin = 13;
void setup()
{
pinMode(LEDpin,OUTPUT);
digitalWrite(LEDpin, HIGH);
//Serial1.begin(115200);//sms舵机波特率115200
Serial1.begin(1000000);//sts舵机波特率1000000
Serial.begin(115200);
sms_sts.pSerial = &Serial1;
delay(1000);
}
void loop()
{
int Pos;
int Speed;
int Load;
int Voltage;
int Temper;
int Move;
int Current;
sms_sts.FeedBack(1);
if(!sms_sts.getLastError()){
digitalWrite(LEDpin, LOW);
Pos = sms_sts.ReadPos(-1);
Speed = sms_sts.ReadSpeed(-1);
Load = sms_sts.ReadLoad(-1);
Voltage = sms_sts.ReadVoltage(-1);
Temper = sms_sts.ReadTemper(-1);
Move = sms_sts.ReadMove(-1);
Current = sms_sts.ReadCurrent(-1);
Serial.print("Position:");
Serial.println(Pos);
Serial.print("Speed:");
Serial.println(Speed);
Serial.print("Load:");
Serial.println(Load);
Serial.print("Voltage:");
Serial.println(Voltage);
Serial.print("Temper:");
Serial.println(Temper);
Serial.print("Move:");
Serial.println(Move);
Serial.print("Current:");
Serial.println(Current);
delay(10);
}else{
digitalWrite(LEDpin, HIGH);
Serial.println("FeedBack err");
delay(500);
}
Pos = sms_sts.ReadPos(1);
if(!sms_sts.getLastError()){
digitalWrite(LEDpin, LOW);
Serial.print("Servo position:");
Serial.println(Pos, DEC);
delay(10);
}else{
Serial.println("read position err");
digitalWrite(LEDpin, HIGH);
delay(500);
}
Voltage = sms_sts.ReadVoltage(1);
if(!sms_sts.getLastError()){
digitalWrite(LEDpin, LOW);
Serial.print("Servo Voltage:");
Serial.println(Voltage, DEC);
delay(10);
}else{
Serial.println("read Voltage err");
digitalWrite(LEDpin, HIGH);
delay(500);
}
Temper = sms_sts.ReadTemper(1);
if(!sms_sts.getLastError()){
digitalWrite(LEDpin, LOW);
Serial.print("Servo temperature:");
Serial.println(Temper, DEC);
delay(10);
}else{
Serial.println("read temperature err");
digitalWrite(LEDpin, HIGH);
delay(500);
}
Speed = sms_sts.ReadSpeed(1);
if(!sms_sts.getLastError()){
digitalWrite(LEDpin, LOW);
Serial.print("Servo Speed:");
Serial.println(Speed, DEC);
delay(10);
}else{
Serial.println("read Speed err");
digitalWrite(LEDpin, HIGH);
delay(500);
}
Load = sms_sts.ReadLoad(1);
if(!sms_sts.getLastError()){
digitalWrite(LEDpin, LOW);
Serial.print("Servo Load:");
Serial.println(Load, DEC);
delay(10);
}else{
Serial.println("read Load err");
digitalWrite(LEDpin, HIGH);
delay(500);
}
Current = sms_sts.ReadCurrent(1);
if(!sms_sts.getLastError()){
digitalWrite(LEDpin, LOW);
Serial.print("Servo Current:");
Serial.println(Current, DEC);
delay(10);
}else{
Serial.println("read Current err");
digitalWrite(LEDpin, HIGH);
delay(500);
}
Move = sms_sts.ReadMove(1);
if(!sms_sts.getLastError()){
digitalWrite(LEDpin, LOW);
Serial.print("Servo Move:");
Serial.println(Move, DEC);
delay(10);
}else{
Serial.println("read Move err");
digitalWrite(LEDpin, HIGH);
delay(500);
}
Serial.println();
}

View File

@@ -0,0 +1,33 @@
/*
Ping指令测试,测试总线上相应ID舵机是否就绪,广播指令只适用于总线只有一个舵机情况
*/
#include <SCServo.h>
SMS_STS sms_sts;
int LEDpin = 13;
void setup()
{
pinMode(LEDpin,OUTPUT);
digitalWrite(LEDpin, HIGH);
Serial.begin(115200);
Serial1.begin(1000000);
sms_sts.pSerial = &Serial1;
delay(1000);
}
void loop()
{
int ID = sms_sts.Ping(1);
if(!sms_sts.getLastError()){
digitalWrite(LEDpin, LOW);
Serial.print("Servo ID:");
Serial.println(ID, DEC);
delay(100);
}else{
Serial.println("Ping servo ID error!");
digitalWrite(LEDpin, HIGH);
delay(2000);
}
}

View File

@@ -0,0 +1,27 @@
/*
舵机参数编程
*/
#include <SCServo.h>
int LEDpin = 13;
SMS_STS sms_sts;
void setup()
{
pinMode(LEDpin, OUTPUT);
//Serial1.begin(115200);//sms舵机波特率115200
Serial1.begin(1000000);//sts舵机波特率1000000
sms_sts.pSerial = &Serial1;
delay(1000);
digitalWrite(LEDpin, LOW);
sms_sts.unLockEprom(1);//打开EPROM保存功能
sms_sts.writeByte(1, SMS_STS_ID, 2);//ID
sms_sts.LockEprom(2);//关闭EPROM保存功能
digitalWrite(LEDpin, HIGH);
}
void loop()
{
}

View File

@@ -0,0 +1,26 @@
#include <SCServo.h>
SMS_STS sms_sts;
void setup()
{
//Serial1.begin(115200);//sms舵机波特率115200
Serial1.begin(1000000);//sts舵机波特率1000000
sms_sts.pSerial = &Serial1;
delay(1000);
}
void loop()
{
//舵机(ID1/ID2)以最高速度V=60*0.732=43.92rpm加速度A=50*8.7deg/s^2运行至P1=4095位置
sms_sts.RegWritePosEx(1, 4095, 60, 50);
sms_sts.RegWritePosEx(2, 4095, 60, 50);
sms_sts.RegWriteAction();
delay((4095-0)*1000/(60*50) + (60*50)*10/(50) + 50);//[(P1-P0)/(V*50)]*1000+[(V*50)/(A*100)]*1000 + 50(误差)
//舵机(ID1/ID2)以最高速度V=60*0.732=43.92rpm加速度A=50*8.7deg/s^2运行至P0=0位置
sms_sts.RegWritePosEx(1, 0, 60, 50);
sms_sts.RegWritePosEx(2, 0, 60, 50);
sms_sts.RegWriteAction();
delay((4095-0)*1000/(60*50) + (60*50)*10/(50) + 50);//[(P1-P0)/(V*50)]*1000+[(V*50)/(A*100)]*1000 + 50(误差)
}

View File

@@ -0,0 +1,45 @@
/*
同步读指令回读ID1与ID2两个舵机的位置与速度信息
*/
#include <SCServo.h>
SMS_STS sms_sts;
uint8_t ID[] = {1, 2};
uint8_t rxPacket[4];
int16_t Position;
int16_t Speed;
void setup()
{
Serial.begin(115200);
//Serial1.begin(115200);//sms舵机波特率115200
Serial1.begin(1000000);//sts舵机波特率1000000
sms_sts.pSerial = &Serial1;
sms_sts.syncReadBegin(sizeof(ID), sizeof(rxPacket), 5);//10*10*2=200us<5ms
delay(1000);
}
void loop()
{
sms_sts.syncReadPacketTx(ID, sizeof(ID), SMS_STS_PRESENT_POSITION_L, sizeof(rxPacket));//同步读指令包发送
for(uint8_t i=0; i<sizeof(ID); i++){
//接收ID[i]同步读返回包
if(!sms_sts.syncReadPacketRx(ID[i], rxPacket)){
Serial.print("ID:");
Serial.println(ID[i]);
Serial.println("sync read error!");
continue;//接收解码失败
}
Position = sms_sts.syncReadRxPacketToWrod(15);//解码两个字节 bit15为方向位,参数=0表示无方向位
Speed = sms_sts.syncReadRxPacketToWrod(15);//解码两个字节 bit15为方向位,参数=0表示无方向位
Serial.print("ID:");
Serial.println(ID[i]);
Serial.print("Position:");
Serial.println(Position);
Serial.print("Speed:");
Serial.println(Speed);
}
delay(10);
}

View File

@@ -0,0 +1,36 @@
#include <SCServo.h>
SMS_STS sms_sts;
byte ID[2];
s16 Position[2];
u16 Speed[2];
byte ACC[2];
void setup()
{
//Serial1.begin(115200);//sms_stss舵机波特率115200
Serial1.begin(1000000);//sts舵机波特率1000000
sms_sts.pSerial = &Serial1;
delay(1000);
ID[0] = 1;//舵机ID1
ID[1] = 2;//舵机ID2
Speed[0] = 60;//最高速度V=60*0.732=43.92rpm
Speed[1] = 60;//最高速度V=60*0.732=43.92rpm
ACC[0] = 50;//加速度A=50*8.7deg/s^2
ACC[1] = 50;//加速度A=50*8.7deg/s^2
}
void loop()
{
//舵机(ID1/ID2)以最高速度V=60*0.732=43.92rpm加速度A=50*8.7deg/s^2运行至P1=4095位置
Position[0] = 4095;
Position[1] = 4095;
sms_sts.SyncWritePosEx(ID, 2, Position, Speed, ACC);
delay((4095-0)*1000/(60*50) + (60*50)*10/(50) + 50);//[(P1-P0)/(V*50)]*1000+[(V*50)/(A*100)]*1000 + 50(误差)
//舵机(ID1/ID2)以最高速度V=60*0.732=43.92rpm加速度A=50*8.7deg/s^2运行至P0=0位置
Position[0] = 0;
Position[1] = 0;
sms_sts.SyncWritePosEx(ID, 2, Position, Speed, ACC);
delay((4095-0)*1000/(60*50) + (60*50)*10/(50) + 50);//[(P1-P0)/(V*50)]*1000+[(V*50)/(A*100)]*1000 + 50(误差)
}

View File

@@ -0,0 +1,47 @@
#include <SCServo.h>
SMS_STS sms_sts;
byte ID[2];
s16 Speed[2];
byte ACC[2];
void setup()
{
//Serial1.begin(115200);//sms_stss舵机波特率115200
Serial1.begin(1000000);//sts舵机波特率1000000
sms_sts.pSerial = &Serial1;
delay(1000);
sms_sts.WheelMode(1);//舵机ID1切换至恒速模式
sms_sts.WheelMode(2);//舵机ID2切换至恒速模式
ID[0] = 1;//舵机ID1
ID[1] = 2;//舵机ID2
ACC[0] = 50;//加速度A=50*8.7deg/s^2
ACC[1] = 50;//加速度A=50*8.7deg/s^2
}
void loop()
{
//舵机(ID1/ID2)以加速度A=50*8.7deg/s^2加速至最高速度V=60*0.732=43.92rpm,并保持恒速正向旋转
Speed[0] = 60;
Speed[1] = 60;
sms_sts.SyncWriteSpe(ID, 2, Speed, ACC);
delay(5000);
//舵机(ID1/ID2)以加速度A=50*8.7deg/s^2减速至速度0停止旋转
Speed[0] = 0;
Speed[1] = 0;
sms_sts.SyncWriteSpe(ID, 2, Speed, ACC);
delay(2000);
//舵机(ID1/ID2)以加速度A=50*8.7deg/s^2加速至最高速度V=-60*0.732=-43.92rpm,并保持恒速反向旋转
Speed[0] = -60;
Speed[1] = -60;
sms_sts.SyncWriteSpe(ID, 2, Speed, ACC);
delay(5000);
//舵机(ID1/ID2)以加速度A=50*8.7deg/s^2减速至速度0停止运行
Speed[0] = 0;
Speed[1] = 0;
sms_sts.SyncWriteSpe(ID, 2, Speed, ACC);
delay(2000);
}

View File

@@ -0,0 +1,22 @@
#include <SCServo.h>
SMS_STS sms_sts;
void setup()
{
//Serial1.begin(115200);//sms舵机波特率115200
Serial1.begin(1000000);//sts舵机波特率1000000
sms_sts.pSerial = &Serial1;
delay(1000);
}
void loop()
{
//舵机(ID1)以最高速度V=60*0.732=43.92rpm加速度A=50*8.7deg/s^2运行至P1=4095位置
sms_sts.WritePosEx(1, 4095, 2400, 50);
delay((4095-0)*1000/(60*50) + (60*50)*10/(50) + 50);//[(P1-P0)/(V*50)]*1000+[(V*50)/(A*100)]*1000 + 50(误差)
//舵机(ID1)以最高速度V=60*0.732=43.92rpm加速度A=50*8.7deg/s^2运行至P0=0位置
sms_sts.WritePosEx(1, 0, 2400, 50);
delay((4095-0)*1000/(60*50) + (60*50)*10/(50) + 50);//[(P1-P0)/(V*50)]*1000+[(V*50)/(A*100)]*1000 + 50(误差)
}

View File

@@ -0,0 +1,31 @@
#include <SCServo.h>
SMS_STS sms_sts;
void setup()
{
//Serial1.begin(115200);//sms舵机波特率115200
Serial1.begin(1000000);//sts舵机波特率1000000
sms_sts.pSerial = &Serial1;
delay(1000);
sms_sts.WheelMode(1);//舵机ID1切换至电机恒速模式
}
void loop()
{
//舵机(ID1)以加速度A=50*8.7deg/s^2加速至最高速度V=60*0.732=43.92rpm,并保持恒速正向旋转
sms_sts.WriteSpe(1, 60, 50);
delay(5000);
//舵机(ID1)以加速度A=50*8.7deg/s^2减速至速度0停止旋转
sms_sts.WriteSpe(1, 0, 50);
delay(2000);
//舵机(ID1/ID2)以加速度A=50*8.7deg/s^2加速至最高速度V=-60*0.732=-43.92rpm,并保持恒速反向旋转
sms_sts.WriteSpe(1, -60, 50);
delay(5000);
//舵机(ID1/ID2)以加速度A=50*8.7deg/s^2减速至速度0停止旋转
sms_sts.WriteSpe(1, 0, 50);
delay(2000);
}

View File

@@ -0,0 +1,11 @@
# see https://arduino.github.io/arduino-cli/1.0/library-specification/#library-metadata
name=FTServo
version=2.0.0
author=ftservo
maintainer=FEETECH <github.com/ftservo>
sentence=FEETECH BUS Servo library for Arduino and ESP32
paragraph=This library is compatible with all series of FEETECH BUS Servo
category=Other
url=https://github.com/ftservo/FTServo_Arduino
architectures=*

View File

@@ -0,0 +1,260 @@
/*
* HLSCL.cpp
* 飞特HTS/HLS系列串行舵机应用层程序
* 日期: 2024.11.21
* 作者: txl
*/
#include "HLSCL.h"
HLSCL::HLSCL()
{
End = 0;
}
HLSCL::HLSCL(u8 End):SCSerial(End)
{
}
HLSCL::HLSCL(u8 End, u8 Level):SCSerial(End, Level)
{
}
int HLSCL::WritePosEx(u8 ID, s16 Position, u16 Speed, u8 ACC, u16 Torque)
{
if(Position<0){
Position = -Position;
Position |= (1<<15);
}
u8 bBuf[7];
bBuf[0] = ACC;
Host2SCS(bBuf+1, bBuf+2, Position);
Host2SCS(bBuf+3, bBuf+4, Torque);
Host2SCS(bBuf+5, bBuf+6, Speed);
return genWrite(ID, HLSCL_ACC, bBuf, 7);
}
int HLSCL::RegWritePosEx(u8 ID, s16 Position, u16 Speed, u8 ACC, u16 Torque)
{
if(Position<0){
Position = -Position;
Position |= (1<<15);
}
u8 bBuf[7];
bBuf[0] = ACC;
Host2SCS(bBuf+1, bBuf+2, Position);
Host2SCS(bBuf+3, bBuf+4, Torque);
Host2SCS(bBuf+5, bBuf+6, Speed);
return regWrite(ID, HLSCL_ACC, bBuf, 7);
}
void HLSCL::SyncWritePosEx(u8 ID[], u8 IDN, s16 Position[], u16 Speed[], u8 ACC[], u16 Torque[])
{
u8 offbuf[7*IDN];
for(u8 i = 0; i<IDN; i++){
if(Position[i]<0){
Position[i] = -Position[i];
Position[i] |= (1<<15);
}
if(ACC){
offbuf[i*7] = ACC[i];
}else{
offbuf[i*7] = 0;
}
Host2SCS(offbuf+i*7+1, offbuf+i*7+2, Position[i]);
Host2SCS(offbuf+i*7+3, offbuf+i*7+4, Torque[i]);
Host2SCS(offbuf+i*7+5, offbuf+i*7+6, Speed[i]);
}
syncWrite(ID, IDN, HLSCL_ACC, offbuf, 7);
}
void HLSCL::SyncWriteSpe(u8 ID[], u8 IDN, s16 Speed[], u8 ACC[], u16 Torque[])
{
u8 offbuf[7*IDN];
for(u8 i = 0; i<IDN; i++){
if(Speed[i]<0){
Speed[i] = -Speed[i];
Speed[i] |= (1<<15);
}
if(ACC){
offbuf[i*7] = ACC[i];
}else{
offbuf[i*7] = 0;
}
Host2SCS(offbuf+i*7+1, offbuf+i*7+2, 0);
Host2SCS(offbuf+i*7+3, offbuf+i*7+4, Torque[i]);
Host2SCS(offbuf+i*7+5, offbuf+i*7+6, Speed[i]);
}
syncWrite(ID, IDN, HLSCL_ACC, offbuf, 7);
}
int HLSCL::WheelMode(u8 ID)
{
return writeByte(ID, HLSCL_MODE, 1);
}
int HLSCL::EleMode(u8 ID)
{
return writeByte(ID, HLSCL_MODE, 2);
}
int HLSCL::WriteSpe(u8 ID, s16 Speed, u8 ACC, u16 Torque)
{
if(Speed<0){
Speed = -Speed;
Speed |= (1<<15);
}
u8 bBuf[7];
bBuf[0] = ACC;
Host2SCS(bBuf+1, bBuf+2, 0);
Host2SCS(bBuf+3, bBuf+4, Torque);
Host2SCS(bBuf+5, bBuf+6, Speed);
return genWrite(ID, HLSCL_ACC, bBuf, 7);
}
int HLSCL::WriteEle(u8 ID, s16 Torque)
{
if(Torque<0){
Torque = -Torque;
Torque |= (1<<15);
}
return writeWord(ID, HLSCL_GOAL_TORQUE_L, Torque);
}
int HLSCL::EnableTorque(u8 ID, u8 Enable)
{
return writeByte(ID, HLSCL_TORQUE_ENABLE, Enable);
}
int HLSCL::unLockEprom(u8 ID)
{
EnableTorque(ID, 0);
return writeByte(ID, HLSCL_LOCK, 0);
}
int HLSCL::LockEprom(u8 ID)
{
return writeByte(ID, HLSCL_LOCK, 1);
}
int HLSCL::CalibrationOfs(u8 ID)
{
EnableTorque(ID, 0);
unLockEprom(ID);
return Recal(ID);
}
int HLSCL::FeedBack(int ID)
{
int nLen = Read(ID, HLSCL_PRESENT_POSITION_L, Mem, sizeof(Mem));
if(nLen!=sizeof(Mem)){
return -1;
}
return nLen;
}
int HLSCL::ReadPos(int ID)
{
int Pos = -1;
if(ID==-1){
Pos = Mem[HLSCL_PRESENT_POSITION_H-HLSCL_PRESENT_POSITION_L];
Pos <<= 8;
Pos |= Mem[HLSCL_PRESENT_POSITION_L-HLSCL_PRESENT_POSITION_L];
}else{
Pos = readWord(ID, HLSCL_PRESENT_POSITION_L);
}
if(Pos&(1<<15)){
Pos = -(Pos&~(1<<15));
}
return Pos;
}
int HLSCL::ReadSpeed(int ID)
{
int Speed = -1;
if(ID==-1){
Speed = Mem[HLSCL_PRESENT_SPEED_H-HLSCL_PRESENT_POSITION_L];
Speed <<= 8;
Speed |= Mem[HLSCL_PRESENT_SPEED_L-HLSCL_PRESENT_POSITION_L];
}else{
Speed = readWord(ID, HLSCL_PRESENT_SPEED_L);
}
if(Speed&(1<<15)){
Speed = -(Speed&~(1<<15));
}
return Speed;
}
int HLSCL::ReadLoad(int ID)
{
int Load = -1;
if(ID==-1){
Load = Mem[HLSCL_PRESENT_LOAD_H-HLSCL_PRESENT_POSITION_L];
Load <<= 8;
Load |= Mem[HLSCL_PRESENT_LOAD_L-HLSCL_PRESENT_POSITION_L];
}else{
Load = readWord(ID, HLSCL_PRESENT_LOAD_L);
}
if(Load&(1<<10)){
Load = -(Load&~(1<<10));
}
return Load;
}
int HLSCL::ReadVoltage(int ID)
{
int Voltage = -1;
if(ID==-1){
Voltage = Mem[HLSCL_PRESENT_VOLTAGE-HLSCL_PRESENT_POSITION_L];
}else{
Voltage = readByte(ID, HLSCL_PRESENT_VOLTAGE);
}
return Voltage;
}
int HLSCL::ReadTemper(int ID)
{
int Temper = -1;
if(ID==-1){
Temper = Mem[HLSCL_PRESENT_TEMPERATURE-HLSCL_PRESENT_POSITION_L];
}else{
Temper = readByte(ID, HLSCL_PRESENT_TEMPERATURE);
}
return Temper;
}
int HLSCL::ReadMove(int ID)
{
int Move = -1;
if(ID==-1){
Move = Mem[HLSCL_MOVING-HLSCL_PRESENT_POSITION_L];
}else{
Move = readByte(ID, HLSCL_MOVING);
}
return Move;
}
int HLSCL::ReadCurrent(int ID)
{
int Current = -1;
if(ID==-1){
Current = Mem[HLSCL_PRESENT_CURRENT_H-HLSCL_PRESENT_POSITION_L];
Current <<= 8;
Current |= Mem[HLSCL_PRESENT_CURRENT_L-HLSCL_PRESENT_POSITION_L];
}else{
Current = readWord(ID, HLSCL_PRESENT_CURRENT_L);
}
if(Current&(1<<15)){
Current = -(Current&~(1<<15));
}
return Current;
}
int HLSCL::ServoMode(u8 ID)
{
return writeByte(ID, HLSCL_MODE, 0);
}

View File

@@ -0,0 +1,89 @@
/*
* HLSCL.h
* 飞特HLS系列串行舵机应用层程序
* 日期: 2024.11.21
* 作者: txl
*/
#ifndef _HLSCL_H
#define _HLSCL_H
//内存表定义
//-------EPROM(只读)--------
#define HLSCL_MODEL_L 3
#define HLSCL_MODEL_H 4
//-------EPROM(读写)--------
#define HLSCL_ID 5
#define HLSCL_BAUD_RATE 6
#define HLSCL_SECOND_ID 7
#define HLSCL_MIN_ANGLE_LIMIT_L 9
#define HLSCL_MIN_ANGLE_LIMIT_H 10
#define HLSCL_MAX_ANGLE_LIMIT_L 11
#define HLSCL_MAX_ANGLE_LIMIT_H 12
#define HLSCL_CW_DEAD 26
#define HLSCL_CCW_DEAD 27
#define HLSCL_OFS_L 31
#define HLSCL_OFS_H 32
#define HLSCL_MODE 33
//-------SRAM(读写)--------
#define HLSCL_TORQUE_ENABLE 40
#define HLSCL_ACC 41
#define HLSCL_GOAL_POSITION_L 42
#define HLSCL_GOAL_POSITION_H 43
#define HLSCL_GOAL_TORQUE_L 44
#define HLSCL_GOAL_TORQUE_H 45
#define HLSCL_GOAL_SPEED_L 46
#define HLSCL_GOAL_SPEED_H 47
#define HLSCL_TORQUE_LIMIT_L 48
#define HLSCL_TORQUE_LIMIT_H 49
#define HLSCL_LOCK 55
//-------SRAM(只读)--------
#define HLSCL_PRESENT_POSITION_L 56
#define HLSCL_PRESENT_POSITION_H 57
#define HLSCL_PRESENT_SPEED_L 58
#define HLSCL_PRESENT_SPEED_H 59
#define HLSCL_PRESENT_LOAD_L 60
#define HLSCL_PRESENT_LOAD_H 61
#define HLSCL_PRESENT_VOLTAGE 62
#define HLSCL_PRESENT_TEMPERATURE 63
#define HLSCL_MOVING 66
#define HLSCL_PRESENT_CURRENT_L 69
#define HLSCL_PRESENT_CURRENT_H 70
#include "SCSerial.h"
class HLSCL : public SCSerial
{
public:
HLSCL();
HLSCL(u8 End);
HLSCL(u8 End, u8 Level);
int WritePosEx(u8 ID, s16 Position, u16 Speed, u8 ACC = 0, u16 Torque = 0);//普通写单个舵机位置指令
int RegWritePosEx(u8 ID, s16 Position, u16 Speed, u8 ACC = 0, u16 Torque = 0);//异步写单个舵机位置指令(RegWriteAction生效)
void SyncWritePosEx(u8 ID[], u8 IDN, s16 Position[], u16 Speed[], u8 ACC[], u16 Torque[]);//同步写多个舵机位置指令
void SyncWriteSpe(u8 ID[], u8 IDN, s16 Speed[], u8 ACC[], u16 Torque[]);//同步写多个舵机速度指令
int ServoMode(u8 ID);//Servo模式
int WheelMode(u8 ID);//恒速模式
int EleMode(u8 ID);//恒力模式
int WriteSpe(u8 ID, s16 Speed, u8 ACC = 0, u16 Torque = 0);//恒速模式控制指令
int WriteEle(u8 ID, s16 Torque);//恒力模式控制指令
int EnableTorque(u8 ID, u8 Enable);//扭力控制指令
int unLockEprom(u8 ID);//eprom解锁
int LockEprom(u8 ID);//eprom加锁
int CalibrationOfs(u8 ID);//中位校准
int FeedBack(int ID);//反馈舵机信息
int ReadPos(int ID);//读位置
int ReadSpeed(int ID);//读速度
int ReadLoad(int ID);//读输出至电机的电压百分比(0~1000)
int ReadVoltage(int ID);//读电压
int ReadTemper(int ID);//读温度
int ReadMove(int ID);//读移动状态
int ReadCurrent(int ID);//读电流
private:
u8 Mem[HLSCL_PRESENT_CURRENT_H-HLSCL_PRESENT_POSITION_L+1];
};
#endif

View File

@@ -0,0 +1,51 @@
/*
* INST.h
* 飞特串行舵机协议指令定义
* 日期: 2024.11.24
* 作者: txl
*/
#ifndef _INST_H
#define _INST_H
typedef char s8;
typedef unsigned char u8;
typedef unsigned short u16;
typedef short s16;
typedef unsigned long u32;
typedef long s32;
enum SCS_ERR_LIST
{
ERR_NO_REPLY = 1,
ERR_CRC_CMP = 2,
ERR_SLAVE_ID = 3,
ERR_BUFF_LEN = 4,
};
#define INST_PING 0x01
#define INST_READ 0x02
#define INST_WRITE 0x03
#define INST_REG_WRITE 0x04
#define INST_REG_ACTION 0x05
#define INST_SYNC_READ 0x82
#define INST_SYNC_WRITE 0x83
#define INST_RECOVERY 0x06
#define INST_RESET 0x0A
#define INST_CAL 0x0B
//波特率定义
#define _1M 0
#define _0_5M 1
#define _250K 2
#define _128K 3
#define _115200 4
#define _76800 5
#define _57600 6
#define _38400 7
#define _19200 8
#define _14400 9
#define _9600 10
#define _4800 11
#endif

View File

@@ -0,0 +1,450 @@
/*
* SCS.cpp
* 飞特串行舵机通信层协议程序
* 日期: 2024.12.21
* 作者: txl
*/
#include <stddef.h>
#include "SCS.h"
SCS::SCS()
{
Level = 1;//除广播指令所有指令返回应答
u8Status = 0;
}
SCS::SCS(u8 End)
{
Level = 1;
this->End = End;
u8Status = 0;
}
SCS::SCS(u8 End, u8 Level)
{
this->Level = Level;
this->End = End;
u8Status = 0;
}
//1个16位数拆分为2个8位数
//DataL为低位DataH为高位
void SCS::Host2SCS(u8 *DataL, u8* DataH, u16 Data)
{
if(End){
*DataL = (Data>>8);
*DataH = (Data&0xff);
}else{
*DataH = (Data>>8);
*DataL = (Data&0xff);
}
}
//2个8位数组合为1个16位数
//DataL为低位DataH为高位
u16 SCS::SCS2Host(u8 DataL, u8 DataH)
{
u16 Data;
if(End){
Data = DataL;
Data<<=8;
Data |= DataH;
}else{
Data = DataH;
Data<<=8;
Data |= DataL;
}
return Data;
}
void SCS::writeBuf(u8 ID, u8 MemAddr, u8 *nDat, u8 nLen, u8 Fun)
{
u8 msgLen = 2;
u8 bBuf[6];
u8 CheckSum = 0;
bBuf[0] = 0xff;
bBuf[1] = 0xff;
bBuf[2] = ID;
bBuf[4] = Fun;
if(nDat){
msgLen += nLen + 1;
bBuf[3] = msgLen;
bBuf[5] = MemAddr;
writeSCS(bBuf, 6);
}else{
bBuf[3] = msgLen;
writeSCS(bBuf, 5);
}
CheckSum = ID + msgLen + Fun + MemAddr;
u8 i = 0;
if(nDat){
for(i=0; i<nLen; i++){
CheckSum += nDat[i];
}
writeSCS(nDat, nLen);
}
writeSCS(~CheckSum);
}
//普通写指令
//舵机IDMemAddr内存表地址写入数据写入长度
int SCS::genWrite(u8 ID, u8 MemAddr, u8 *nDat, u8 nLen)
{
rFlushSCS();
writeBuf(ID, MemAddr, nDat, nLen, INST_WRITE);
wFlushSCS();
return Ack(ID);
}
//异步写指令
//舵机IDMemAddr内存表地址写入数据写入长度
int SCS::regWrite(u8 ID, u8 MemAddr, u8 *nDat, u8 nLen)
{
rFlushSCS();
writeBuf(ID, MemAddr, nDat, nLen, INST_REG_WRITE);
wFlushSCS();
return Ack(ID);
}
//异步写执行指令
//舵机ID
int SCS::RegWriteAction(u8 ID)
{
rFlushSCS();
writeBuf(ID, 0, NULL, 0, INST_REG_ACTION);
wFlushSCS();
return Ack(ID);
}
//同步写指令
//舵机ID[]数组IDN数组长度MemAddr内存表地址写入数据写入长度
void SCS::syncWrite(u8 ID[], u8 IDN, u8 MemAddr, u8 *nDat, u8 nLen)
{
rFlushSCS();
u8 mesLen = ((nLen+1)*IDN+4);
u8 Sum = 0;
u8 bBuf[7];
bBuf[0] = 0xff;
bBuf[1] = 0xff;
bBuf[2] = 0xfe;
bBuf[3] = mesLen;
bBuf[4] = INST_SYNC_WRITE;
bBuf[5] = MemAddr;
bBuf[6] = nLen;
writeSCS(bBuf, 7);
Sum = 0xfe + mesLen + INST_SYNC_WRITE + MemAddr + nLen;
u8 i, j;
for(i=0; i<IDN; i++){
writeSCS(ID[i]);
writeSCS(nDat+i*nLen, nLen);
Sum += ID[i];
for(j=0; j<nLen; j++){
Sum += nDat[i*nLen+j];
}
}
writeSCS(~Sum);
wFlushSCS();
}
int SCS::writeByte(u8 ID, u8 MemAddr, u8 bDat)
{
rFlushSCS();
writeBuf(ID, MemAddr, &bDat, 1, INST_WRITE);
wFlushSCS();
return Ack(ID);
}
int SCS::writeWord(u8 ID, u8 MemAddr, u16 wDat)
{
u8 bBuf[2];
Host2SCS(bBuf+0, bBuf+1, wDat);
rFlushSCS();
writeBuf(ID, MemAddr, bBuf, 2, INST_WRITE);
wFlushSCS();
return Ack(ID);
}
//读指令
//舵机IDMemAddr内存表地址返回数据nData数据长度nLen
int SCS::Read(u8 ID, u8 MemAddr, u8 *nData, u8 nLen)
{
rFlushSCS();
writeBuf(ID, MemAddr, &nLen, 1, INST_READ);
wFlushSCS();
u8Error = 0;
if(!checkHead()){
u8Error = ERR_NO_REPLY;
return 0;
}
u8 bBuf[4];
u8Status = 0;
if(readSCS(bBuf, 3)!=3){
u8Error = ERR_NO_REPLY;
return 0;
}
if(bBuf[0]!=ID && ID!=0xfe){
u8Error = ERR_SLAVE_ID;
return 0;
}
if(bBuf[1]!=(nLen+2)){
u8Error = ERR_BUFF_LEN;
return 0;
}
int Size = readSCS(nData, nLen);
if(Size!=nLen){
u8Error = ERR_NO_REPLY;
return 0;
}
if(readSCS(bBuf+3, 1)!=1){
u8Error = ERR_NO_REPLY;
return 0;
}
u8 calSum = bBuf[0]+bBuf[1]+bBuf[2];
u8 i;
for(i=0; i<Size; i++){
calSum += nData[i];
}
calSum = ~calSum;
if(calSum!=bBuf[3]){
u8Error = ERR_CRC_CMP;
return 0;
}
u8Status = bBuf[2];
return Size;
}
//读1字节超时返回-1
int SCS::readByte(u8 ID, u8 MemAddr)
{
u8 bDat;
int Size = Read(ID, MemAddr, &bDat, 1);
if(Size!=1){
return -1;
}else{
return bDat;
}
}
//读2字节超时返回-1
int SCS::readWord(u8 ID, u8 MemAddr)
{
u8 nDat[2];
int Size;
u16 wDat;
Size = Read(ID, MemAddr, nDat, 2);
if(Size!=2)
return -1;
wDat = SCS2Host(nDat[0], nDat[1]);
return wDat;
}
//Ping指令返回舵机ID超时返回-1
int SCS::Ping(u8 ID)
{
rFlushSCS();
writeBuf(ID, 0, NULL, 0, INST_PING);
wFlushSCS();
u8Status = 0;
if(!checkHead()){
u8Error = ERR_NO_REPLY;
return -1;
}
u8 bBuf[4];
u8Error = 0;
if(readSCS(bBuf, 4)!=4){
u8Error = ERR_NO_REPLY;
return -1;
}
if(bBuf[0]!=ID && ID!=0xfe){
u8Error = ERR_SLAVE_ID;
return -1;
}
if(bBuf[1]!=2){
u8Error = ERR_BUFF_LEN;
return -1;
}
u8 calSum = ~(bBuf[0]+bBuf[1]+bBuf[2]);
if(calSum!=bBuf[3]){
u8Error = ERR_CRC_CMP;
return -1;
}
u8Status = bBuf[2];
return bBuf[0];
}
int SCS::checkHead()
{
u8 bDat;
u8 bBuf[] = {0, 0};
u8 Cnt = 0;
while(1){
if(!readSCS(&bDat, 1)){
return 0;
}
bBuf[1] = bBuf[0];
bBuf[0] = bDat;
if(bBuf[0]==0xff && bBuf[1]==0xff){
break;
}
Cnt++;
if(Cnt>10){
return 0;
}
}
return 1;
}
int SCS::Ack(u8 ID)
{
u8Error = 0;
if(ID!=0xfe && Level){
if(!checkHead()){
u8Error = ERR_NO_REPLY;
return 0;
}
u8Status = 0;
u8 bBuf[4];
if(readSCS(bBuf, 4)!=4){
u8Error = ERR_NO_REPLY;
return 0;
}
if(bBuf[0]!=ID){
u8Error = ERR_SLAVE_ID;
return 0;
}
if(bBuf[1]!=2){
u8Error = ERR_BUFF_LEN;
return 0;
}
u8 calSum = ~(bBuf[0]+bBuf[1]+bBuf[2]);
if(calSum!=bBuf[3]){
u8Error = ERR_CRC_CMP;
return 0;
}
u8Status = bBuf[2];
}
return 1;
}
int SCS::syncReadPacketTx(u8 ID[], u8 IDN, u8 MemAddr, u8 nLen)
{
rFlushSCS();
syncReadRxPacketLen = nLen;
u8 checkSum = (4+0xfe) + IDN + MemAddr + nLen + INST_SYNC_READ;
u8 i;
writeSCS(0xff);
writeSCS(0xff);
writeSCS(0xfe);
writeSCS(IDN+4);
writeSCS(INST_SYNC_READ);
writeSCS(MemAddr);
writeSCS(nLen);
for(i=0; i<IDN; i++){
writeSCS(ID[i]);
checkSum += ID[i];
}
checkSum = ~checkSum;
writeSCS(checkSum);
wFlushSCS();
syncReadRxBuffLen = readSCS(syncReadRxBuff, syncReadRxBuffMax, syncTimeOut);
return syncReadRxBuffLen;
}
void SCS::syncReadBegin(u8 IDN, u8 rxLen, u32 TimeOut)
{
syncReadRxBuffMax = IDN*(rxLen+6);
syncReadRxBuff = new u8[syncReadRxBuffMax];
syncTimeOut = TimeOut;
}
void SCS::syncReadEnd()
{
if(syncReadRxBuff){
delete syncReadRxBuff;
syncReadRxBuff = NULL;
}
}
int SCS::syncReadPacketRx(u8 ID, u8 *nDat)
{
u16 syncReadRxBuffIndex = 0;
syncReadRxPacket = nDat;
syncReadRxPacketIndex = 0;
u8Error = 0;
while((syncReadRxBuffIndex+6+syncReadRxPacketLen)<=syncReadRxBuffLen){
u8 bBuf[] = {0, 0, 0};
u8 calSum = 0;
while(syncReadRxBuffIndex<syncReadRxBuffLen){
bBuf[0] = bBuf[1];
bBuf[1] = bBuf[2];
bBuf[2] = syncReadRxBuff[syncReadRxBuffIndex++];
if(bBuf[0]==0xff && bBuf[1]==0xff && bBuf[2]!=0xff){
break;
}
}
if(bBuf[2]!=ID){
continue;
}
if(syncReadRxBuff[syncReadRxBuffIndex++]!=(syncReadRxPacketLen+2)){
continue;
}
u8Status = syncReadRxBuff[syncReadRxBuffIndex++];
calSum = ID + (syncReadRxPacketLen+2) + u8Status;
for(u8 i=0; i<syncReadRxPacketLen; i++){
syncReadRxPacket[i] = syncReadRxBuff[syncReadRxBuffIndex++];
calSum += syncReadRxPacket[i];
}
calSum = ~calSum;
if(calSum!=syncReadRxBuff[syncReadRxBuffIndex++]){
u8Error = ERR_CRC_CMP;
return 0;
}
return syncReadRxPacketLen;
}
return 0;
}
int SCS::syncReadRxPacketToByte()
{
if(syncReadRxPacketIndex>=syncReadRxPacketLen){
u8Error = ERR_BUFF_LEN;
return -1;
}
return syncReadRxPacket[syncReadRxPacketIndex++];
}
int SCS::syncReadRxPacketToWrod(u8 negBit)
{
if((syncReadRxPacketIndex+1)>=syncReadRxPacketLen){
u8Error = ERR_BUFF_LEN;
return -1;
}
int Word = SCS2Host(syncReadRxPacket[syncReadRxPacketIndex], syncReadRxPacket[syncReadRxPacketIndex+1]);
syncReadRxPacketIndex += 2;
if(negBit){
if(Word&(1<<negBit)){
Word = -(Word & ~(1<<negBit));
}
}
return Word;
}
int SCS::Reset(u8 ID)
{
rFlushSCS();
writeBuf(ID, 0, NULL, 0, INST_RESET);
wFlushSCS();
return Ack(ID);
}
int SCS::Recal(u8 ID)
{
rFlushSCS();
writeBuf(ID, 0, NULL, 0, INST_CAL);
wFlushSCS();
return Ack(ID);
}

View File

@@ -0,0 +1,64 @@
/*
* SCS.h
* 飞特串行舵机通信层协议程序
* 日期: 2024.11.21
* 作者: txl
*/
#ifndef _SCS_H
#define _SCS_H
#include "INST.h"
class SCS{
public:
SCS();
SCS(u8 End);
SCS(u8 End, u8 Level);
int genWrite(u8 ID, u8 MemAddr, u8 *nDat, u8 nLen);//普通写指令
int regWrite(u8 ID, u8 MemAddr, u8 *nDat, u8 nLen);//异步写指令
int RegWriteAction(u8 ID = 0xfe);//异步写执行指令
void syncWrite(u8 ID[], u8 IDN, u8 MemAddr, u8 *nDat, u8 nLen);//同步写指令
int writeByte(u8 ID, u8 MemAddr, u8 bDat);//写1个字节
int writeWord(u8 ID, u8 MemAddr, u16 wDat);//写2个字节
int Read(u8 ID, u8 MemAddr, u8 *nData, u8 nLen);//读指令
int readByte(u8 ID, u8 MemAddr);//读1个字节
int readWord(u8 ID, u8 MemAddr);//读2个字节
int Ping(u8 ID);//Ping指令
int syncReadPacketTx(u8 ID[], u8 IDN, u8 MemAddr, u8 nLen);//同步读指令包发送
int syncReadPacketRx(u8 ID, u8 *nDat);//同步读返回包解码成功返回内存字节数失败返回0
int syncReadRxPacketToByte();//解码一个字节
int syncReadRxPacketToWrod(u8 negBit=0);//解码两个字节negBit为方向为negBit=0表示无方向
void syncReadBegin(u8 IDN, u8 rxLen, u32 TimeOut);//同步读开始
void syncReadEnd();//同步读结束
int Reset(u8 ID);//重置舵机状态
int Recal(u8 ID);//重置舵机中位
u8 getState() { return u8Status; }
u8 getLastError() { return u8Error; }
public:
u8 Level;//舵机返回等级
u8 End;//处理器大小端结构
u8 u8Status;//舵机状态
u8 u8Error;//通信状态
u8 syncReadRxPacketIndex;
u8 syncReadRxPacketLen;
u8 *syncReadRxPacket;
u8 *syncReadRxBuff;
u16 syncReadRxBuffLen;
u16 syncReadRxBuffMax;
u32 syncTimeOut;
protected:
virtual int writeSCS(unsigned char *nDat, int nLen) = 0;
virtual int readSCS(unsigned char *nDat, int nLen) = 0;
virtual int readSCS(unsigned char *nDat, int nLen, unsigned long TimeOut) = 0;
virtual int writeSCS(unsigned char bDat) = 0;
virtual void rFlushSCS() = 0;
virtual void wFlushSCS() = 0;
protected:
void writeBuf(u8 ID, u8 MemAddr, u8 *nDat, u8 nLen, u8 Fun);
void Host2SCS(u8 *DataL, u8* DataH, u16 Data);//1个16位数拆分为2个8位数
u16 SCS2Host(u8 DataL, u8 DataH);//2个8位数组合为1个16位数
int Ack(u8 ID);//返回应答
int checkHead();//帧头检测
};
#endif

View File

@@ -0,0 +1,203 @@
/*
* SCSCL.cpp
* 飞特SCSCL系列串行舵机应用层程序
* 日期: 2024.4.2
* 作者:
*/
#include "SCSCL.h"
SCSCL::SCSCL()
{
End = 1;
}
SCSCL::SCSCL(u8 End):SCSerial(End)
{
}
SCSCL::SCSCL(u8 End, u8 Level):SCSerial(End, Level)
{
}
int SCSCL::WritePos(u8 ID, u16 Position, u16 Time, u16 Speed)
{
u8 bBuf[6];
Host2SCS(bBuf+0, bBuf+1, Position);
Host2SCS(bBuf+2, bBuf+3, Time);
Host2SCS(bBuf+4, bBuf+5, Speed);
return genWrite(ID, SCSCL_GOAL_POSITION_L, bBuf, 6);
}
int SCSCL::RegWritePos(u8 ID, u16 Position, u16 Time, u16 Speed)
{
u8 bBuf[6];
Host2SCS(bBuf+0, bBuf+1, Position);
Host2SCS(bBuf+2, bBuf+3, Time);
Host2SCS(bBuf+4, bBuf+5, Speed);
return regWrite(ID, SCSCL_GOAL_POSITION_L, bBuf, 6);
}
void SCSCL::SyncWritePos(u8 ID[], u8 IDN, u16 Position[], u16 Time[], u16 Speed[])
{
u8 offbuf[6*IDN];
for(u8 i = 0; i<IDN; i++){
u16 T, V;
if(Time){
T = Time[i];
}else{
T = 0;
}
if(Speed){
V = Speed[i];
}else{
V = 0;
}
Host2SCS(offbuf+i*6+0, offbuf+i*6+1, Position[i]);
Host2SCS(offbuf+i*6+2, offbuf+i*6+3, T);
Host2SCS(offbuf+i*6+4, offbuf+i*6+5, V);
}
syncWrite(ID, IDN, SCSCL_GOAL_POSITION_L, offbuf, 6);
}
int SCSCL::EnableTorque(u8 ID, u8 Enable)
{
return writeByte(ID, SCSCL_TORQUE_ENABLE, Enable);
}
int SCSCL::unLockEprom(u8 ID)
{
return writeByte(ID, SCSCL_LOCK, 0);
}
int SCSCL::LockEprom(u8 ID)
{
return writeByte(ID, SCSCL_LOCK, 1);
}
int SCSCL::FeedBack(int ID)
{
int nLen = Read(ID, SCSCL_PRESENT_POSITION_L, Mem, sizeof(Mem));
if(nLen!=sizeof(Mem)){
return -1;
}
return nLen;
}
int SCSCL::ReadPos(int ID)
{
int Pos = -1;
if(ID==-1){
Pos = Mem[SCSCL_PRESENT_POSITION_L-SCSCL_PRESENT_POSITION_L];
Pos <<= 8;
Pos |= Mem[SCSCL_PRESENT_POSITION_H-SCSCL_PRESENT_POSITION_L];
}else{
Pos = readWord(ID, SCSCL_PRESENT_POSITION_L);
}
return Pos;
}
int SCSCL::ReadSpeed(int ID)
{
int Speed = -1;
if(ID==-1){
Speed = Mem[SCSCL_PRESENT_SPEED_L-SCSCL_PRESENT_POSITION_L];
Speed <<= 8;
Speed |= Mem[SCSCL_PRESENT_SPEED_H-SCSCL_PRESENT_POSITION_L];
}else{
Speed = readWord(ID, SCSCL_PRESENT_SPEED_L);
}
if(Speed&(1<<15)){
Speed = -(Speed&~(1<<15));
}
return Speed;
}
int SCSCL::ReadLoad(int ID)
{
int Load = -1;
if(ID==-1){
Load = Mem[SCSCL_PRESENT_LOAD_L-SCSCL_PRESENT_POSITION_L];
Load <<= 8;
Load |= Mem[SCSCL_PRESENT_LOAD_H-SCSCL_PRESENT_POSITION_L];
}else{
Load = readWord(ID, SCSCL_PRESENT_LOAD_L);
}
if(Load&(1<<10)){
Load = -(Load&~(1<<10));
}
return Load;
}
int SCSCL::ReadVoltage(int ID)
{
int Voltage = -1;
if(ID==-1){
Voltage = Mem[SCSCL_PRESENT_VOLTAGE-SCSCL_PRESENT_POSITION_L];
}else{
Voltage = readByte(ID, SCSCL_PRESENT_VOLTAGE);
}
return Voltage;
}
int SCSCL::ReadTemper(int ID)
{
int Temper = -1;
if(ID==-1){
Temper = Mem[SCSCL_PRESENT_TEMPERATURE-SCSCL_PRESENT_POSITION_L];
}else{
Temper = readByte(ID, SCSCL_PRESENT_TEMPERATURE);
}
return Temper;
}
int SCSCL::ReadMove(int ID)
{
int Move = -1;
if(ID==-1){
Move = Mem[SCSCL_MOVING-SCSCL_PRESENT_POSITION_L];
}else{
Move = readByte(ID, SCSCL_MOVING);
}
return Move;
}
int SCSCL::ReadCurrent(int ID)
{
int Current = -1;
if(ID==-1){
Current = Mem[SCSCL_PRESENT_CURRENT_L-SCSCL_PRESENT_POSITION_L];
Current <<= 8;
Current |= Mem[SCSCL_PRESENT_CURRENT_H-SCSCL_PRESENT_POSITION_L];
}else{
Current = readWord(ID, SCSCL_PRESENT_CURRENT_L);
}
if(Current&(1<<15)){
Current = -(Current&~(1<<15));
}
return Current;
}
int SCSCL::PWMMode(u8 ID)
{
u8 bBuf[4];
bBuf[0] = 0;
bBuf[1] = 0;
bBuf[2] = 0;
bBuf[3] = 0;
return genWrite(ID, SCSCL_MIN_ANGLE_LIMIT_L, bBuf, 4);
}
int SCSCL::WritePWM(u8 ID, s16 pwmOut)
{
if(pwmOut<0){
pwmOut = -pwmOut;
pwmOut |= (1<<10);
}
u8 bBuf[2];
Host2SCS(bBuf+0, bBuf+1, pwmOut);
return genWrite(ID, SCSCL_GOAL_TIME_L, bBuf, 2);
}

View File

@@ -0,0 +1,77 @@
/*
* SCSCL.h
* 飞特SCSCL系列串行舵机应用层程序
* 日期: 2024.4.2
* 作者:
*/
#ifndef _SCSCL_H
#define _SCSCL_H
//内存表定义
//-------EPROM(只读)--------
#define SCSCL_VERSION_L 3
#define SCSCL_VERSION_H 4
//-------EPROM(读写)--------
#define SCSCL_ID 5
#define SCSCL_BAUD_RATE 6
#define SCSCL_MIN_ANGLE_LIMIT_L 9
#define SCSCL_MIN_ANGLE_LIMIT_H 10
#define SCSCL_MAX_ANGLE_LIMIT_L 11
#define SCSCL_MAX_ANGLE_LIMIT_H 12
#define SCSCL_CW_DEAD 26
#define SCSCL_CCW_DEAD 27
//-------SRAM(读写)--------
#define SCSCL_TORQUE_ENABLE 40
#define SCSCL_GOAL_POSITION_L 42
#define SCSCL_GOAL_POSITION_H 43
#define SCSCL_GOAL_TIME_L 44
#define SCSCL_GOAL_TIME_H 45
#define SCSCL_GOAL_SPEED_L 46
#define SCSCL_GOAL_SPEED_H 47
#define SCSCL_LOCK 48
//-------SRAM(只读)--------
#define SCSCL_PRESENT_POSITION_L 56
#define SCSCL_PRESENT_POSITION_H 57
#define SCSCL_PRESENT_SPEED_L 58
#define SCSCL_PRESENT_SPEED_H 59
#define SCSCL_PRESENT_LOAD_L 60
#define SCSCL_PRESENT_LOAD_H 61
#define SCSCL_PRESENT_VOLTAGE 62
#define SCSCL_PRESENT_TEMPERATURE 63
#define SCSCL_MOVING 66
#define SCSCL_PRESENT_CURRENT_L 69
#define SCSCL_PRESENT_CURRENT_H 70
#include "SCSerial.h"
class SCSCL : public SCSerial
{
public:
SCSCL();
SCSCL(u8 End);
SCSCL(u8 End, u8 Level);
int WritePos(u8 ID, u16 Position, u16 Time, u16 Speed = 0);//普通写单个舵机位置指令
int RegWritePos(u8 ID, u16 Position, u16 Time, u16 Speed = 0);//异步写单个舵机位置指令(RegWriteAction生效)
void SyncWritePos(u8 ID[], u8 IDN, u16 Position[], u16 Time[], u16 Speed[]);//同步写多个舵机位置指令
int PWMMode(u8 ID);//PWM模式
int WritePWM(u8 ID, s16 pwmOut);//PWM输出模式指令
int EnableTorque(u8 ID, u8 Enable);//扭矩控制指令
int unLockEprom(u8 ID);//eprom解锁
int LockEprom(u8 ID);//eprom加锁
int FeedBack(int ID);//反馈舵机信息
int ReadPos(int ID);//读位置
int ReadSpeed(int ID);//读速度
int ReadLoad(int ID);//读输出至电机的电压百分比(0~1000)
int ReadVoltage(int ID);//读电压
int ReadTemper(int ID);//读温度
int ReadMove(int ID);//读移动状态
int ReadCurrent(int ID);//读电流
private:
u8 Mem[SCSCL_PRESENT_CURRENT_H-SCSCL_PRESENT_POSITION_L+1];
};
#endif

View File

@@ -0,0 +1,99 @@
/*
* SCSerial.h
* 飞特串行舵机硬件接口层程序
* 日期: 2024.4.2
* 作者:
*/
#include "SCSerial.h"
SCSerial::SCSerial()
{
IOTimeOut = 10;
pSerial = NULL;
}
SCSerial::SCSerial(u8 End):SCS(End)
{
IOTimeOut = 10;
pSerial = NULL;
}
SCSerial::SCSerial(u8 End, u8 Level):SCS(End, Level)
{
IOTimeOut = 10;
pSerial = NULL;
}
int SCSerial::readSCS(unsigned char *nDat, int nLen, unsigned long TimeOut)
{
int Size = 0;
int ComData;
unsigned long t_begin = millis();
unsigned long t_user;
while(1){
ComData = pSerial->read();
if(ComData!=-1){
if(nDat){
nDat[Size] = ComData;
}
Size++;
}
if(Size>=nLen){
break;
}
t_user = millis() - t_begin;
if(t_user>TimeOut){
break;
}
}
return Size;
}
int SCSerial::readSCS(unsigned char *nDat, int nLen)
{
int Size = 0;
int ComData;
unsigned long t_begin = millis();
unsigned long t_user;
while(1){
ComData = pSerial->read();
if(ComData!=-1){
if(nDat){
nDat[Size] = ComData;
}
Size++;
t_begin = millis();
}
if(Size>=nLen){
break;
}
t_user = millis() - t_begin;
if(t_user>IOTimeOut){
break;
}
}
return Size;
}
int SCSerial::writeSCS(unsigned char *nDat, int nLen)
{
if(nDat==NULL){
return 0;
}
return pSerial->write(nDat, nLen);
}
int SCSerial::writeSCS(unsigned char bDat)
{
return pSerial->write(&bDat, 1);
}
void SCSerial::rFlushSCS()
{
while(pSerial->read()!=-1);
}
void SCSerial::wFlushSCS()
{
}

View File

@@ -0,0 +1,38 @@
/*
* SCSerial.h
* 飞特串行舵机硬件接口层程序
* 日期: 2024.11.22
* 作者: txl
*/
#ifndef _SCSERIAL_H
#define _SCSERIAL_H
#if defined(ARDUINO) && ARDUINO >= 100
#include "Arduino.h"
#else
#include "WProgram.h"
#endif
#include "SCS.h"
class SCSerial : public SCS
{
public:
SCSerial();
SCSerial(u8 End);
SCSerial(u8 End, u8 Level);
protected:
int writeSCS(unsigned char *nDat, int nLen);//输出nLen字节
int readSCS(unsigned char *nDat, int nLen);//输入nLen字节
int readSCS(unsigned char *nDat, int nLen, unsigned long TimeOut);
int writeSCS(unsigned char bDat);//输出1字节
void rFlushSCS();//
void wFlushSCS();//
public:
unsigned long IOTimeOut;//输入输出超时
HardwareSerial *pSerial;//串口指针
};
#endif

View File

@@ -0,0 +1,15 @@
/*
* SCServo.h
* 飞特串行舵机接口
* 日期: 2024.11.24
* 作者: txl
*/
#ifndef _SCSERVO_H
#define _SCSERVO_H
#include "SCSCL.h"
#include "SMS_STS.h"
#include "HLSCL.h"
#endif

View File

@@ -0,0 +1,249 @@
/*
* SMS_STS.cpp
* 飞特SMS_STS系列串行舵机应用层程序
* 日期: 2024.11.21
* 作者: txl
*/
#include "SMS_STS.h"
SMS_STS::SMS_STS()
{
End = 0;
}
SMS_STS::SMS_STS(u8 End):SCSerial(End)
{
}
SMS_STS::SMS_STS(u8 End, u8 Level):SCSerial(End, Level)
{
}
int SMS_STS::WritePosEx(u8 ID, s16 Position, u16 Speed, u8 ACC)
{
if(Position<0){
Position = -Position;
Position |= (1<<15);
}
u8 bBuf[7];
bBuf[0] = ACC;
Host2SCS(bBuf+1, bBuf+2, Position);
Host2SCS(bBuf+3, bBuf+4, 0);
Host2SCS(bBuf+5, bBuf+6, Speed);
return genWrite(ID, SMS_STS_ACC, bBuf, 7);
}
int SMS_STS::RegWritePosEx(u8 ID, s16 Position, u16 Speed, u8 ACC)
{
if(Position<0){
Position = -Position;
Position |= (1<<15);
}
u8 bBuf[7];
bBuf[0] = ACC;
Host2SCS(bBuf+1, bBuf+2, Position);
Host2SCS(bBuf+3, bBuf+4, 0);
Host2SCS(bBuf+5, bBuf+6, Speed);
return regWrite(ID, SMS_STS_ACC, bBuf, 7);
}
void SMS_STS::SyncWritePosEx(u8 ID[], u8 IDN, s16 Position[], u16 Speed[], u8 ACC[])
{
u8 offbuf[7*IDN];
for(u8 i = 0; i<IDN; i++){
if(Position[i]<0){
Position[i] = -Position[i];
Position[i] |= (1<<15);
}
u16 V;
if(Speed){
V = Speed[i];
}else{
V = 0;
}
if(ACC){
offbuf[i*7] = ACC[i];
}else{
offbuf[i*7] = 0;
}
Host2SCS(offbuf+i*7+1, offbuf+i*7+2, Position[i]);
Host2SCS(offbuf+i*7+3, offbuf+i*7+4, 0);
Host2SCS(offbuf+i*7+5, offbuf+i*7+6, V);
}
syncWrite(ID, IDN, SMS_STS_ACC, offbuf, 7);
}
void SMS_STS::SyncWriteSpe(u8 ID[], u8 IDN, s16 Speed[], u8 ACC[])
{
u8 offbuf[7*IDN];
for(u8 i = 0; i<IDN; i++){
if(Speed[i]<0){
Speed[i] = -Speed[i];
Speed[i] |= (1<<15);
}
if(ACC){
offbuf[i*7] = ACC[i];
}else{
offbuf[i*7] = 0;
}
Host2SCS(offbuf+i*7+1, offbuf+i*7+2, 0);
Host2SCS(offbuf+i*7+3, offbuf+i*7+4, 0);
Host2SCS(offbuf+i*7+5, offbuf+i*7+6, Speed[i]);
}
syncWrite(ID, IDN, SMS_STS_ACC, offbuf, 7);
}
int SMS_STS::WheelMode(u8 ID)
{
return writeByte(ID, SMS_STS_MODE, 1);
}
int SMS_STS::WriteSpe(u8 ID, s16 Speed, u8 ACC)
{
if(Speed<0){
Speed = -Speed;
Speed |= (1<<15);
}
u8 bBuf[7];
bBuf[0] = ACC;
Host2SCS(bBuf+1, bBuf+2, 0);
Host2SCS(bBuf+3, bBuf+4, 0);
Host2SCS(bBuf+5, bBuf+6, Speed);
return genWrite(ID, SMS_STS_ACC, bBuf, 7);
}
int SMS_STS::EnableTorque(u8 ID, u8 Enable)
{
return writeByte(ID, SMS_STS_TORQUE_ENABLE, Enable);
}
int SMS_STS::unLockEprom(u8 ID)
{
return writeByte(ID, SMS_STS_LOCK, 0);
}
int SMS_STS::LockEprom(u8 ID)
{
return writeByte(ID, SMS_STS_LOCK, 1);
}
int SMS_STS::CalibrationOfs(u8 ID)
{
return writeByte(ID, SMS_STS_TORQUE_ENABLE, 128);
}
int SMS_STS::FeedBack(int ID)
{
int nLen = Read(ID, SMS_STS_PRESENT_POSITION_L, Mem, sizeof(Mem));
if(nLen!=sizeof(Mem)){
return -1;
}
return nLen;
}
int SMS_STS::ReadPos(int ID)
{
int Pos = -1;
if(ID==-1){
Pos = Mem[SMS_STS_PRESENT_POSITION_H-SMS_STS_PRESENT_POSITION_L];
Pos <<= 8;
Pos |= Mem[SMS_STS_PRESENT_POSITION_L-SMS_STS_PRESENT_POSITION_L];
}else{
Pos = readWord(ID, SMS_STS_PRESENT_POSITION_L);
}
if(Pos&(1<<15)){
Pos = -(Pos&~(1<<15));
}
return Pos;
}
int SMS_STS::ReadSpeed(int ID)
{
int Speed = -1;
if(ID==-1){
Speed = Mem[SMS_STS_PRESENT_SPEED_H-SMS_STS_PRESENT_POSITION_L];
Speed <<= 8;
Speed |= Mem[SMS_STS_PRESENT_SPEED_L-SMS_STS_PRESENT_POSITION_L];
}else{
Speed = readWord(ID, SMS_STS_PRESENT_SPEED_L);
}
if(Speed&(1<<15)){
Speed = -(Speed&~(1<<15));
}
return Speed;
}
int SMS_STS::ReadLoad(int ID)
{
int Load = -1;
if(ID==-1){
Load = Mem[SMS_STS_PRESENT_LOAD_H-SMS_STS_PRESENT_POSITION_L];
Load <<= 8;
Load |= Mem[SMS_STS_PRESENT_LOAD_L-SMS_STS_PRESENT_POSITION_L];
}else{
Load = readWord(ID, SMS_STS_PRESENT_LOAD_L);
}
if(Load&(1<<10)){
Load = -(Load&~(1<<10));
}
return Load;
}
int SMS_STS::ReadVoltage(int ID)
{
int Voltage = -1;
if(ID==-1){
Voltage = Mem[SMS_STS_PRESENT_VOLTAGE-SMS_STS_PRESENT_POSITION_L];
}else{
Voltage = readByte(ID, SMS_STS_PRESENT_VOLTAGE);
}
return Voltage;
}
int SMS_STS::ReadTemper(int ID)
{
int Temper = -1;
if(ID==-1){
Temper = Mem[SMS_STS_PRESENT_TEMPERATURE-SMS_STS_PRESENT_POSITION_L];
}else{
Temper = readByte(ID, SMS_STS_PRESENT_TEMPERATURE);
}
return Temper;
}
int SMS_STS::ReadMove(int ID)
{
int Move = -1;
if(ID==-1){
Move = Mem[SMS_STS_MOVING-SMS_STS_PRESENT_POSITION_L];
}else{
Move = readByte(ID, SMS_STS_MOVING);
}
return Move;
}
int SMS_STS::ReadCurrent(int ID)
{
int Current = -1;
if(ID==-1){
Current = Mem[SMS_STS_PRESENT_CURRENT_H-SMS_STS_PRESENT_POSITION_L];
Current <<= 8;
Current |= Mem[SMS_STS_PRESENT_CURRENT_L-SMS_STS_PRESENT_POSITION_L];
}else{
Current = readWord(ID, SMS_STS_PRESENT_CURRENT_L);
}
if(Current&(1<<15)){
Current = -(Current&~(1<<15));
}
return Current;
}
int SMS_STS::ServoMode(u8 ID)
{
return writeByte(ID, SMS_STS_MODE, 0);
}

View File

@@ -0,0 +1,86 @@
/*
* SMS_STS.h
* 飞特SMS/STS系列串行舵机应用层程序
* 日期: 2024.11.21
* 作者: txl
*/
#ifndef _SMS_STS_H
#define _SMS_STS_H
//内存表定义
//-------EPROM(只读)--------
#define SMS_STS_MODEL_L 3
#define SMS_STS_MODEL_H 4
//-------EPROM(读写)--------
#define SMS_STS_ID 5
#define SMS_STS_BAUD_RATE 6
#define SMS_STS_MIN_ANGLE_LIMIT_L 9
#define SMS_STS_MIN_ANGLE_LIMIT_H 10
#define SMS_STS_MAX_ANGLE_LIMIT_L 11
#define SMS_STS_MAX_ANGLE_LIMIT_H 12
#define SMS_STS_CW_DEAD 26
#define SMS_STS_CCW_DEAD 27
#define SMS_STS_OFS_L 31
#define SMS_STS_OFS_H 32
#define SMS_STS_MODE 33
//-------SRAM(读写)--------
#define SMS_STS_TORQUE_ENABLE 40
#define SMS_STS_ACC 41
#define SMS_STS_GOAL_POSITION_L 42
#define SMS_STS_GOAL_POSITION_H 43
#define SMS_STS_GOAL_TIME_L 44
#define SMS_STS_GOAL_TIME_H 45
#define SMS_STS_GOAL_SPEED_L 46
#define SMS_STS_GOAL_SPEED_H 47
#define SMS_STS_TORQUE_LIMIT_L 48
#define SMS_STS_TORQUE_LIMIT_H 49
#define SMS_STS_LOCK 55
//-------SRAM(只读)--------
#define SMS_STS_PRESENT_POSITION_L 56
#define SMS_STS_PRESENT_POSITION_H 57
#define SMS_STS_PRESENT_SPEED_L 58
#define SMS_STS_PRESENT_SPEED_H 59
#define SMS_STS_PRESENT_LOAD_L 60
#define SMS_STS_PRESENT_LOAD_H 61
#define SMS_STS_PRESENT_VOLTAGE 62
#define SMS_STS_PRESENT_TEMPERATURE 63
#define SMS_STS_MOVING 66
#define SMS_STS_PRESENT_CURRENT_L 69
#define SMS_STS_PRESENT_CURRENT_H 70
#include "SCSerial.h"
class SMS_STS : public SCSerial
{
public:
SMS_STS();
SMS_STS(u8 End);
SMS_STS(u8 End, u8 Level);
int WritePosEx(u8 ID, s16 Position, u16 Speed, u8 ACC = 0);//普通写单个舵机位置指令
int RegWritePosEx(u8 ID, s16 Position, u16 Speed, u8 ACC = 0);//异步写单个舵机位置指令(RegWriteAction生效)
void SyncWritePosEx(u8 ID[], u8 IDN, s16 Position[], u16 Speed[], u8 ACC[]);//同步写多个舵机位置指令
void SyncWriteSpe(u8 ID[], u8 IDN, s16 Speed[], u8 ACC[]);//同步写多个舵机速度指令
int ServoMode(u8 ID);//Servo模式
int WheelMode(u8 ID);//恒速模式
int WriteSpe(u8 ID, s16 Speed, u8 ACC = 0);//恒速模式控制指令
int EnableTorque(u8 ID, u8 Enable);//扭力控制指令
int unLockEprom(u8 ID);//eprom解锁
int LockEprom(u8 ID);//eprom加锁
int CalibrationOfs(u8 ID);//中位校准
int FeedBack(int ID);//反馈舵机信息
int ReadPos(int ID);//读位置
int ReadSpeed(int ID);//读速度
int ReadLoad(int ID);//读输出至电机的电压百分比(0~1000)
int ReadVoltage(int ID);//读电压
int ReadTemper(int ID);//读温度
int ReadMove(int ID);//读移动状态
int ReadCurrent(int ID);//读电流
private:
u8 Mem[SMS_STS_PRESENT_CURRENT_H-SMS_STS_PRESENT_POSITION_L+1];
};
#endif

View File

@@ -1,410 +0,0 @@
#define ESP_NOW_HOST
// ===== 配置宏 =====
#ifndef ESP_NOW_DEFAULT_CHANNEL
#define ESP_NOW_DEFAULT_CHANNEL 6
#endif
#ifndef ESP_NOW_MAX_SLAVES
#define ESP_NOW_MAX_SLAVES 20
#endif
#ifndef ESP_NOW_CFG_INTERVAL
#define ESP_NOW_CFG_INTERVAL 500
#endif
// Flash Key
#define HOST_CFG_NS "host_cfg"
#define HOST_CHANNEL_KEY "channel"
#define SLAVES_NS "slaves"
#define SLAVE_CFG_NS "slave_cfg"
#define SLAVE_ID_KEY "id"
#define SLAVE_HOST_KEY "host"
#define SLAVE_CHANNEL_KEY "channel"
class EspNowPeer {
private:
bool _initialized = false;
bool _ready = false;
uint8_t _myId;
uint8_t _hostMac[6];
uint8_t _currentChannel;
#ifdef ESP_NOW_HOST
uint8_t _slaveMacs[ESP_NOW_MAX_SLAVES + 1][6];
bool _slaveActive[ESP_NOW_MAX_SLAVES + 1];
Preferences _prefs;
#endif
char _recvBuf[256];
bool _hasMsg = false;
String _parsedValue;
volatile bool _waitingReg = false;
volatile uint8_t _waitingId = 0;
volatile bool _regReceived = false;
static bool _str2mac(const char* s, uint8_t* m) {
return sscanf(s, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
&m[0],&m[1],&m[2],&m[3],&m[4],&m[5]) == 6;
}
static void _mac2str(const uint8_t* m, char* s) {
sprintf(s, "%02X:%02X:%02X:%02X:%02X:%02X", m[0],m[1],m[2],m[3],m[4],m[5]);
}
static void _onRecv(const uint8_t* mac, const uint8_t* data, int len) {
if(_instance) _instance->_handleRecv(mac, data, len);
}
void _handleRecv(const uint8_t* mac, const uint8_t* data, int len) {
if(len >= 256) return;
memcpy(_recvBuf, data, len);
_recvBuf[len] = '\0';
_hasMsg = true;
#ifdef ESP_NOW_HOST
if(_waitingReg && getMessageValue("climac")) {
String val = lastValue();
int dashPos = val.indexOf('-');
if(dashPos > 0) {
uint8_t recv_id = val.substring(0, dashPos).toInt();
if(recv_id == _waitingId && recv_id >= 1 && recv_id <= ESP_NOW_MAX_SLAVES) {
String macStr = val.substring(dashPos + 1);
uint8_t recv_mac[6];
if(_str2mac(macStr.c_str(), recv_mac)) {
_regReceived = true;
_registerSlave(recv_id, recv_mac);
}
}
}
}
#endif
}
#ifdef ESP_NOW_HOST
bool _saveChannel(uint8_t ch) {
Preferences prefs;
if(prefs.begin(HOST_CFG_NS, false)) {
prefs.putUChar(HOST_CHANNEL_KEY, ch);
prefs.end();
return true;
}
return false;
}
uint8_t _loadChannel() {
Preferences prefs;
if(prefs.begin(HOST_CFG_NS, true)) {
uint8_t ch = prefs.getUChar(HOST_CHANNEL_KEY, 0);
prefs.end();
if(ch >= 1 && ch <= 13) return ch;
}
return ESP_NOW_DEFAULT_CHANNEL;
}
bool _changeChannel(uint8_t newChannel) {
if(newChannel < 1 || newChannel > 13) return false;
if(newChannel == _currentChannel) return true;
_saveChannel(newChannel);
esp_wifi_set_channel(newChannel, WIFI_SECOND_CHAN_NONE);
_currentChannel = newChannel;
esp_now_deinit();
delay(100);
if(esp_now_init() != ESP_OK) return false;
esp_now_register_recv_cb(_onRecv);
return true;
}
bool _registerSlave(uint8_t id, uint8_t* mac) {
if(id < 1 || id > ESP_NOW_MAX_SLAVES) return false;
if(_slaveActive[id] && memcmp(_slaveMacs[id], mac, 6) == 0) return true;
memcpy(_slaveMacs[id], mac, 6);
_slaveActive[id] = true;
_prefs.begin(SLAVES_NS, false);
_prefs.putBytes((String("s")+String(id)).c_str(), mac, 6);
_prefs.end();
esp_now_peer_info_t p = {};
memcpy(p.peer_addr, mac, 6);
p.channel = _currentChannel;
p.encrypt = false;
esp_now_add_peer(&p);
return true;
}
uint8_t* _findMac(uint8_t id) {
if(id < 1 || id > ESP_NOW_MAX_SLAVES) return nullptr;
return _slaveActive[id] ? _slaveMacs[id] : nullptr;
}
void _loadSlaves() {
for(int i=0; i<=ESP_NOW_MAX_SLAVES; i++) _slaveActive[i] = false;
_prefs.begin(SLAVES_NS, true);
for(uint8_t id=1; id<=ESP_NOW_MAX_SLAVES; id++) {
uint8_t mac[6];
if(_prefs.getBytes((String("s")+String(id)).c_str(), mac, 6) == 6) {
memcpy(_slaveMacs[id], mac, 6);
_slaveActive[id] = true;
esp_now_peer_info_t p = {};
memcpy(p.peer_addr, mac, 6);
p.channel = _currentChannel;
p.encrypt = false;
esp_now_add_peer(&p);
}
}
_prefs.end();
}
#endif
bool _send(const uint8_t* mac, const char* key, const char* value) {
if(!_ready || !mac || !key || !value) return false;
char json[250];
snprintf(json, sizeof(json), "{\"%s\":\"%s\"}", key, value);
int len = strlen(json);
if(len > 250) return false;
return esp_now_send(mac, (uint8_t*)json, len) == ESP_OK;
}
static EspNowPeer* _instance;
public:
EspNowPeer() { _instance = this; }
bool begin(uint8_t myId = 0, const uint8_t* hostMac = nullptr) {
if(_initialized) return true;
_myId = myId;
if(hostMac) memcpy(_hostMac, hostMac, 6);
#ifdef ESP_NOW_HOST
_currentChannel = _loadChannel();
#else
_currentChannel = _loadChannelSlave();
#endif
WiFi.mode(WIFI_STA);
WiFi.disconnect();
esp_wifi_set_channel(_currentChannel, WIFI_SECOND_CHAN_NONE);
if(esp_now_init() != ESP_OK) return false;
esp_now_register_recv_cb(_onRecv);
#ifdef ESP_NOW_HOST
_prefs.begin(SLAVES_NS, false);
_loadSlaves();
_prefs.end();
#endif
_initialized = true;
_ready = true;
return true;
}
// ===== 主机公开接口 =====
#ifdef ESP_NOW_HOST
void setDevice(uint8_t slaveId, uint8_t channel = 0) {
if(!_ready) return;
if(channel == 0) channel = _currentChannel;
if(channel != _currentChannel) _changeChannel(channel);
uint8_t myMac[6];
esp_read_mac(myMac, ESP_MAC_WIFI_STA);
char hostMacStr[18];
_mac2str(myMac, hostMacStr);
_waitingReg = true;
_waitingId = slaveId;
_regReceived = false;
uint32_t lastSend = 0;
while(!_regReceived) {
if(millis() - lastSend >= ESP_NOW_CFG_INTERVAL) {
Serial1.printf("CFG:%d:%s:%dWZHY\n", slaveId, hostMacStr, channel);
Serial.printf("CFG:%d:%s:%dWZHY\n", slaveId, hostMacStr, channel);
lastSend = millis();
}
delay(10);
}
_waitingReg = false;
}
bool sendMessage(uint8_t slaveId, const String& key, const String& value) {
if(!_ready) return false;
uint8_t* mac = _findMac(slaveId);
if(!mac) return false;
return _send(mac, key.c_str(), value.c_str());
}
uint8_t getChannel() { return _currentChannel; }
#endif
// ===== 从机公开接口 =====
#ifdef ESP_NOW_SLAVE
uint8_t _loadChannelSlave() {
Preferences prefs;
if(prefs.begin(SLAVE_CFG_NS, true)) {
uint8_t ch = prefs.getUChar(SLAVE_CHANNEL_KEY, 0);
prefs.end();
if(ch >= 1 && ch <= 13) return ch;
}
return ESP_NOW_DEFAULT_CHANNEL;
}
bool _saveChannelSlave(uint8_t ch) {
Preferences prefs;
if(prefs.begin(SLAVE_CFG_NS, false)) {
prefs.putUChar(SLAVE_CHANNEL_KEY, ch);
prefs.end();
return true;
}
return false;
}
// ✅ 核心接口:解析并应用配置
bool applyConfig(uint8_t id, const uint8_t* hostMac, uint8_t channel) {
bool changed = false;
// 信道变更
if(channel != _currentChannel && channel >= 1 && channel <= 13) {
_saveChannelSlave(channel);
esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE);
_currentChannel = channel;
esp_now_deinit();
delay(100);
esp_now_init();
esp_now_register_recv_cb(_onRecv);
changed = true;
}
// ID 变更
Preferences prefs;
prefs.begin(SLAVE_CFG_NS, false);
if(prefs.getUChar(SLAVE_ID_KEY, 0) != id) {
prefs.putUChar(SLAVE_ID_KEY, id);
_myId = id;
changed = true;
}
// 主机 MAC 变更
uint8_t storedHost[6];
if(prefs.getBytes(SLAVE_HOST_KEY, storedHost, 6) != 6 || memcmp(storedHost, hostMac, 6) != 0) {
prefs.putBytes(SLAVE_HOST_KEY, hostMac, 6);
memcpy(_hostMac, hostMac, 6);
changed = true;
}
prefs.end();
// 发送 climac 确认
_sendRegistration();
return changed;
}
bool loadConfig() {
Preferences prefs;
prefs.begin(SLAVE_CFG_NS, true);
_myId = prefs.getUChar(SLAVE_ID_KEY, 0);
if(_myId == 0) { prefs.end(); return false; }
if(prefs.getBytes(SLAVE_HOST_KEY, _hostMac, 6) != 6) { prefs.end(); return false; }
prefs.end();
return true;
}
bool _sendRegistration() {
uint8_t myMac[6];
esp_read_mac(myMac, ESP_MAC_WIFI_STA);
char macStr[18];
_mac2str(myMac, macStr);
char content[40];
snprintf(content, sizeof(content), "%d-%s", _myId, macStr);
return _send(_hostMac, "climac", content);
}
bool sendMessage(const String& key, const String& value) {
if(!_ready) return false;
char content[220];
snprintf(content, sizeof(content), "cli-%d-%s", _myId, value.c_str());
return _send(_hostMac, key.c_str(), content);
}
uint8_t getChannel() { return _currentChannel; }
uint8_t getMyId() { return _myId; }
#endif
// ===== 通用接口 =====
bool hasMessage() {
bool has = _hasMsg;
_hasMsg = false;
return has;
}
bool getMessageValue(const String& key) {
_parsedValue = "";
if(strlen(_recvBuf) == 0) return false;
String pattern = "\"" + key + "\":\"";
int start = String(_recvBuf).indexOf(pattern);
if(start == -1) return false;
start += pattern.length();
int end = String(_recvBuf).indexOf("\"", start);
if(end == -1) return false;
_parsedValue = String(_recvBuf).substring(start, end);
return true;
}
String lastValue() { return _parsedValue; }
void clearMessage() { _recvBuf[0] = '\0'; _parsedValue = ""; _hasMsg = false; }
bool isReady() { return _ready; }
};
EspNowPeer* EspNowPeer::_instance = nullptr;
EspNowPeer peer;
void setup() {
Serial.begin(115200);
delay(1000);
Serial1.begin(115200, SERIAL_8N1, 1, 2);//K10自身管脚不够利用串口接外部芯片扩展
// ✅ 初始化主机ID=255
peer.begin(255);
Serial.printf("📡 主机就绪 信道=%d\n", peer.getChannel());
// ===== 关键函数调用示例 =====
// 示例 1配置从机#5使用当前信道
peer.setDevice(5);
// 示例 2配置从机#5 并切换到信道 6
// peer.setDevice(5, 6);
// 示例 3发送消息到从机#5
// peer.sendMessage(5, "c", "GO");
// 示例 4发送消息到从机#5带参数
// peer.sendMessage(5, "set", "speed:100");
}
void loop() {
// ✅ 接收从机消息
if(peer.hasMessage()) {
if(peer.getMessageValue("c")) {
String val = peer.lastValue();
// 解析 cli-<id>-<content> 格式
if(val.startsWith("cli-")) {
int d1 = val.indexOf('-', 4);
if(d1 > 0) {
String idStr = val.substring(4, d1);
String content = val.substring(d1 + 1);
Serial.printf("📥 从机#%s: %s\n", idStr.c_str(), content.c_str());
}
}
}
}
// ✅ 实际使用时,根据业务逻辑调用:
// peer.setDevice(5, 6); // 配置从机
peer.sendMessage(5, "c", "GO"); // 发送命令
delay(100);
}

View File

@@ -1,911 +0,0 @@
#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>
#include "esp_camera.h"
#include "esp_heap_caps.h"
#include <freertos/queue.h>
#include <Preferences.h>
#include <WiFiUdp.h>
#include <stdarg.h>
#include <esp_now.h> // ✅【改动 1】添加 ESP-NOW 头文件
#if 1
// ===== UDP通信类最小改动=====
class UDPPeer {
private:
WiFiUDP udp;
bool ready = false;
uint16_t local_port = 8888;
// 双缓存
String receivedRaw;
String parsedValue;
// 连接参数
const char* ssid = nullptr;
const char* password = nullptr;
IPAddress local_ip;
IPAddress gateway;
IPAddress subnet;
// ✅ 监视器IP最后一位
uint8_t monitor_ip_last = 101;
// ✅ 改回普通成员变量(非静态)
bool is_initialized = false;
uint32_t last_maintenance = 0;
const uint32_t MAINTENANCE_INTERVAL = 1000;
// ✅【改动 2】新增ESP-NOW消息标志
bool espnowready=false;
bool espnowHasMsg=false;
void autoMaintain() {
if (!is_initialized) return;
if (millis() - last_maintenance < MAINTENANCE_INTERVAL) return;
last_maintenance = millis();
if (WiFi.status() != WL_CONNECTED) {
WiFi.config(local_ip, gateway, subnet);
if (!ready)
{
//WiFi.disconnect(true);
//delay(500);
Serial.printf("正在启动网络连接!!!\n");
WiFi.begin(ssid, password);
delay(500);
WiFi.begin(ssid, password);
delay(500);
WiFi.begin(ssid, password);
delay(500);
WiFi.begin(ssid, password);
delay(500);
}
else
{
Serial.printf("网络中断,恢复中!!!\n");
WiFi.reconnect();
//esp_now_deinit();
}
return;
}
if (!ready) {
ready = (udp.begin(local_port) == 1);
if (ready) {
//initEspNowRecv();
Serial.printf("✅ 网络就绪: IP=%s 端口=%d\n",
WiFi.localIP().toString().c_str(), local_port);
} else {
return;
}
}
}
public:
// ✅ 新增:访问 is_initialized 的成员方法
bool isInitialized() {
return is_initialized;
}
bool wifiisready() {
return ready;
}
bool espnowisready(){
return espnowready;
}
void setespnowreadyflg()
{
espnowready=true;
}
void resetespnowreadyflg()
{
espnowready=false;
}
// ✅ 设置监视器IP
void setMonitorIPLast(uint8_t last_octet) {
monitor_ip_last = last_octet;
}
// ✅ 获取完整监视器IP
String getMonitorIP() {
return String(gateway[0]) + "." + String(gateway[1]) + "." +
String(gateway[2]) + "." + String(monitor_ip_last);
}
bool connect(const char* _ssid, const char* _pwd,
uint8_t gw1, uint8_t gw2, uint8_t gw3, uint8_t gw4,
uint8_t local_ip_last, uint16_t port = 8888) {
WiFi.disconnect(true);
last_maintenance = 0;
ready = false;
espnowHasMsg = false; // ✅ 重置ESP-NOW标志
ssid = _ssid;
password = _pwd;
local_ip = IPAddress(gw1, gw2, gw3, local_ip_last);
gateway = IPAddress(gw1, gw2, gw3, gw4);
subnet = IPAddress(255, 255, 255, 0);
local_port = port;
is_initialized = true;
Serial.printf("📶 wifi%s pwd%s配置网络: IP=%d.%d.%d.%d 网关=%d.%d.%d.%d 端口=%d\n",
ssid, password, gw1, gw2, gw3, local_ip_last, gw1, gw2, gw3, gw4, port);
Serial.println("💡 网络将在后台自动连接...");
autoMaintain();
return true;
}
// ✅【改动 3】修改hasMessage()优先检查ESP-NOW
bool hasMessage() {
autoMaintain();
if (!is_initialized || !ready) return false;
// 优先检查ESP-NOW消息
if (espnowHasMsg) {
return true; // 数据已在receivedRaw中
}
// 再检查UDP消息原有逻辑
receivedRaw = "";
parsedValue = "";
int len = udp.parsePacket();
if (len <= 0 || len > 512) return false;
char buf[513];
int read = udp.read((uint8_t*)buf, min(len, 512));
if (read > 0) {
buf[read] = '\0';
receivedRaw = String(buf);
return true;
}
return false;
}
bool getMessageValue(const String& key) {
parsedValue = "";
if (receivedRaw.length() == 0) return false;
String keyPattern = "\"" + key + "\"";
int startIndex = receivedRaw.indexOf(keyPattern);
if (startIndex == -1) return false;
int colonIndex = receivedRaw.indexOf(":", startIndex);
if (colonIndex == -1) return false;
int valueStart = colonIndex + 1;
while (valueStart < receivedRaw.length() &&
receivedRaw.charAt(valueStart) == ' ') valueStart++;
if (valueStart < receivedRaw.length() && receivedRaw.charAt(valueStart) == '"') {
int valueEnd = receivedRaw.indexOf("\"", valueStart + 1);
if (valueEnd != -1) {
parsedValue = receivedRaw.substring(valueStart + 1, valueEnd);
return true;
}
} else {
int valueEnd = valueStart;
while (valueEnd < receivedRaw.length() &&
receivedRaw.charAt(valueEnd) != ',' &&
receivedRaw.charAt(valueEnd) != '}' &&
receivedRaw.charAt(valueEnd) != ' ') valueEnd++;
while (valueEnd > valueStart && receivedRaw.charAt(valueEnd - 1) == ' ') valueEnd--;
if (valueEnd > valueStart) {
parsedValue = receivedRaw.substring(valueStart, valueEnd);
return true;
}
}
return false;
}
bool sendTo(const String& target_ip_str, const String& key, const char* format, ...) {
autoMaintain();
if (!is_initialized || !ready)
{
Serial.println("连接还未完成!!!");
return false;
}
IPAddress target_ip;
if (!target_ip.fromString(target_ip_str)) return false;
char content[256];
va_list args;
va_start(args, format);
vsnprintf(content, sizeof(content) - 1, format, args);
va_end(args);
char json[300];
snprintf(json, sizeof(json), "{\"%s\":\"%s\"}\r\n", key.c_str(), content);
return udp.beginPacket(target_ip, local_port) &&
udp.write((const uint8_t*)json, strlen(json)) &&
udp.endPacket();
}
bool sendTo(const String& target_ip_str, const String& key, const String& value) {
return sendTo(target_ip_str, key, "%s", value.c_str());
}
String lastValue() { return parsedValue; }
String lastMessage() { return receivedRaw; }
String getLocalIP() {
autoMaintain();
return is_initialized ? WiFi.localIP().toString() : "未配置";
}
bool isConnected() {
autoMaintain();
return is_initialized && ready;
}
// ✅【改动 4】新增获取本机MAC地址
String getMacAddress() {
return WiFi.macAddress();
}
// ✅【改动 5】新增设置ESP-NOW消息回调调用
void setEspNowMessage(const char* data, int len) {
if (len <= 0 || len > 250) return; // ESP-NOW最大250字节
char buf[251];
memcpy(buf, data, len);
buf[len] = '\0';
receivedRaw = String(buf);
espnowHasMsg = true;
}
// ✅【改动 6】新增清除ESP-NOW标志解析后调用
void clearEspNowFlag() {
espnowHasMsg = false;
}
};
extern UDPPeer peer;
UDPPeer peer;
// ✅【改动 7】ESP-NOW接收回调直接调用peer方法
static void OnEspNowRecv(const uint8_t *mac, const uint8_t *data, int len) {
peer.setEspNowMessage((const char*)data, len);
}
// ✅【改动 8】初始化ESP-NOW接收
bool initEspNowRecv() {
if (esp_now_init() != ESP_OK) {
Serial.println("❌ ESP-NOW初始化失败");
return 0;
}
esp_now_register_recv_cb(OnEspNowRecv);
Serial.println("✅ ESP-NOW接收已启用");
return 1;
}
// ===== 默认配置 =====
#define DEFAULT_SSID "ESP32-Camera"
#define DEFAULT_PWD "12345678"
#define DEFAULT_GW1 192
#define DEFAULT_GW2 168
#define DEFAULT_GW3 4
#define DEFAULT_GW4 1
#define DEFAULT_LOCAL_LAST 100
#define DEFAULT_MONITOR_LAST 101
// ===== Flash配置管理 =====
void loadConfigFromFlash() {
Preferences prefs;
if (!prefs.begin("cam_cfg", true)) {
Serial.println("⚠️ Flash配置不存在使用默认值");
peer.connect(DEFAULT_SSID, DEFAULT_PWD,
DEFAULT_GW1, DEFAULT_GW2, DEFAULT_GW3, DEFAULT_GW4,
DEFAULT_LOCAL_LAST);
peer.setMonitorIPLast(DEFAULT_MONITOR_LAST);
prefs.end();
return;
}
String ssid = prefs.getString("ssid", DEFAULT_SSID);
String pwd = prefs.getString("pwd", DEFAULT_PWD);
uint8_t gw1 = prefs.getUChar("gw1", DEFAULT_GW1);
uint8_t gw2 = prefs.getUChar("gw2", DEFAULT_GW2);
uint8_t gw3 = prefs.getUChar("gw3", DEFAULT_GW3);
uint8_t gw4 = prefs.getUChar("gw4", DEFAULT_GW4);
uint8_t local_last = prefs.getUChar("local", DEFAULT_LOCAL_LAST);
uint8_t monitor_last = prefs.getUChar("monitor", DEFAULT_MONITOR_LAST);
prefs.end();
peer.connect(ssid.c_str(), pwd.c_str(), gw1, gw2, gw3, gw4, local_last);
peer.setMonitorIPLast(monitor_last);
Serial.println("✅ 从Flash加载配置成功");
}
void saveConfigToFlash(const char* ssid, const char* pwd,
uint8_t gw1, uint8_t gw2, uint8_t gw3, uint8_t gw4,
uint8_t local_last, uint8_t monitor_last) {
Preferences prefs;
if (!prefs.begin("cam_cfg", false)) {
Serial.println("❌ Flash配置保存失败");
return;
}
prefs.putString("ssid", ssid);
prefs.putString("pwd", pwd);
prefs.putUChar("gw1", gw1);
prefs.putUChar("gw2", gw2);
prefs.putUChar("gw3", gw3);
prefs.putUChar("gw4", gw4);
prefs.putUChar("local", local_last);
prefs.putUChar("monitor", monitor_last);
prefs.end();
Serial.println("✅ 配置已保存到Flash");
}
// ===== 串口指令解析 =====
void handleSerialCommand() {
if (!Serial.available()) return;
String cmd = Serial.readStringUntil('\n');
cmd.trim();
if (!cmd.startsWith("wificar:"))
{
if(cmd.endsWith("LZU")||cmd.endsWith("LZU\r"))
{
peer.sendTo(peer.getMonitorIP(), "f", cmd);
Serial.printf("📤 已发送到 %s: {\"f\":%s}\n", peer.getMonitorIP().c_str(), cmd);
}
// ✅【改动 9】ESPNOW分支回复MAC+IP给主机
else if(cmd.endsWith("ESPNOW")||cmd.endsWith("ESPNOW\r"))
{
String mac = peer.getMacAddress();
String ip = peer.getLocalIP();
char json[150];
snprintf(json, sizeof(json), "{\"mac\":\"%s\",\"ip\":\"%s\"}\r\n",
mac.c_str(), ip.c_str());
bool ok = peer.sendTo(peer.getMonitorIP(), "ESPNOW_REG", json);
if(ok) {
Serial.printf("✅ ESPNOW注册: MAC=%s IP=%s\n", mac.c_str(), ip.c_str());
} else {
Serial.printf("❌ 发送ESPNOW注册信息失败\n");
}
}
return;
}
int pos[7];
int count = 0;
for (int i = 8; i < cmd.length() && count < 7; i++) {
if (cmd.charAt(i) == ':') {
pos[count++] = i;
}
}
if (count < 7) {
Serial.println("❌ 指令格式错误");
return;
}
String ssid = cmd.substring(8, pos[0]);
String pwd = cmd.substring(pos[0] + 1, pos[1]);
uint8_t gw1 = cmd.substring(pos[1] + 1, pos[2]).toInt();
uint8_t gw2 = cmd.substring(pos[2] + 1, pos[3]).toInt();
uint8_t gw3 = cmd.substring(pos[3] + 1, pos[4]).toInt();
uint8_t gw4 = cmd.substring(pos[4] + 1, pos[5]).toInt();
uint8_t local_last = cmd.substring(pos[5] + 1, pos[6]).toInt();
uint8_t monitor_last = cmd.substring(pos[6] + 1).toInt();
peer.connect(ssid.c_str(), pwd.c_str(), gw1, gw2, gw3, gw4, local_last);
peer.setMonitorIPLast(monitor_last);
saveConfigToFlash(ssid.c_str(), pwd.c_str(), gw1, gw2, gw3, gw4, local_last, monitor_last);
Serial.println("✅ WiFi配置已更新并保存");
}
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("\n╔════════════════════════════════════════════════════════════════╗");
Serial.println("║ ESP32-S3 摄像头 UDP+ESP-NOW 通信系统 v1.3 ║");
Serial.println("║ 支持Flash配置 | 串口配置 | UDP+ESP-NOW双通道统一解析 ║");
Serial.println("╚════════════════════════════════════════════════════════════════╝");
loadConfigFromFlash();
//esp_now_deinit();
//initEspNowRecv(); // ✅【改动 10】初始化ESP-NOW接收
}
void loop() {
handleSerialCommand();
if (peer.isInitialized()) {
// if (peer.wifiisready())
// {
// if(!peer.espnowisready())
// {
// if(initEspNowRecv())
// {
// peer.setespnowreadyflg();
// String mac = peer.getMacAddress();
// String ip = peer.getLocalIP();
// char json[150];
// snprintf(json, sizeof(json), "mac:%s;ip:%s",
// mac.c_str(), ip.c_str());
// bool ok = peer.sendTo(peer.getMonitorIP(), "r", json);//ESPNOW_REG
// if(ok) {
// Serial.printf("✅ ESPNOW注册: MAC=%s IP=%s\n", mac.c_str(), ip.c_str());
// } else {
// Serial.printf("❌ 发送ESPNOW注册信息失败\n");
// }
// }
// }
// }
// else if(peer.espnowisready())
// {
// peer.resetespnowreadyflg();
// esp_now_deinit();
// } //k10的总线扩展机制导espnow优势不能有效发挥每次查阅按钮时会直接打断espnow通讯再启动会延迟将近200ms感觉是硬件通道重启
//若是常规udp通讯不一定被按钮查阅打断但是会被此间隔时间冲断再连接延迟好使120ms左右感觉是软件握手重启
//也就是采用k10板子espnow效果反而更差于udp通讯
if (peer.hasMessage()) {
if (peer.getMessageValue("c")) {
Serial.printf("%sZHY\n", peer.lastValue().c_str());
// Serial.printf("📶 Wi-Fi 模式:%s\n",
// WiFi.getMode() == WIFI_AP ? "AP" :
// WiFi.getMode() == WIFI_STA ? "STA" : "AP+STA");
// Serial.printf("📶 信道:%d\n", WiFi.channel());
// Serial.printf("📶 ESP-NOW%s\n", peer.espnowisready()? "已启用" : "未启用");
// Serial.printf("📶 状态:%d\n", WiFi.status());
}
// ✅【改动 11】解析后清除ESP-NOW标志避免重复处理
peer.clearEspNowFlag();
}
}
delay(1);
}
主机端espnow版本
#include <WiFi.h>
#include <WiFiUdp.h>
#include <stdarg.h>
#include <esp_now.h> // ✅ 新增ESP-NOW 头文件
// ✅ 新增ESP-NOW 对等节点结构(最多 10 个从机)
#define MAX_SLAVES 10
typedef struct {
uint8_t mac[6];
IPAddress ip;
bool active;
} SlaveNode;
class UDPPeer {
private:
WiFiUDP udp;
bool ready = false;
uint16_t local_port = 8888;
// 双缓存
String receivedRaw;
String parsedValue;
// 连接参数
const char* ssid = nullptr;
const char* password = nullptr;
IPAddress local_ip;
IPAddress gateway;
IPAddress subnet;
// ✅ 新增AP模式标志
bool is_ap_mode = false;
// ✅ 新增:网络配置缓存
String current_ssid = "";
String current_password = "";
uint8_t cached_gw1 = 0, cached_gw2 = 0, cached_gw3 = 0, cached_gw4 = 0;
// 状态标志
bool is_initialized = false;
uint32_t last_maintenance = 0;
const uint32_t MAINTENANCE_INTERVAL = 1000;
// ✅ 新增ESP-NOW 相关
bool espnowInited = false;
SlaveNode slaves[MAX_SLAVES]; // 从机注册表
uint8_t slaveCount = 0;
// ✅ 新增:解析 MAC 字符串 "AA:BB:CC:DD:EE:FF" → 字节数组
bool parseMacString(const String& macStr, uint8_t* macBuf) {
int vals[6];
if (sscanf(macStr.c_str(), "%x:%x:%x:%x:%x:%x",
&vals[0], &vals[1], &vals[2], &vals[3], &vals[4], &vals[5]) != 6) {
return false;
}
for (int i = 0; i < 6; i++) macBuf[i] = (uint8_t)vals[i];
return true;
}
// ✅ 新增:初始化 ESP-NOW网络就绪后调用
void initEspNow() {
if (espnowInited) return;
if (esp_now_init() != ESP_OK) {
Serial.println("❌ ESP-NOW 初始化失败");
return;
}
// 注册发送回调(可选,用于确认发送状态)
esp_now_register_send_cb([](const uint8_t* mac, esp_now_send_status_t status) {
// Serial.printf("ESP-NOW Send to %02x... %s\n", mac[5],
// status == ESP_NOW_SEND_SUCCESS ? "OK" : "FAIL");
});
espnowInited = true;
Serial.println("✅ ESP-NOW 已启用");
}
// ✅ 新增:注册/更新从机MAC+IP 映射)
bool registerSlave(const String& macStr, const String& ipStr) {
if (!espnowInited) initEspNow();
uint8_t mac[6];
if (!parseMacString(macStr, mac)) return false;
IPAddress ip;
if (!ip.fromString(ipStr)) return false;
// 检查 MAC 是否已存在 → 更新 IP
for (int i = 0; i < slaveCount; i++) {
if (slaves[i].active && memcmp(slaves[i].mac, mac, 6) == 0) {
slaves[i].ip = ip;
Serial.printf("🔄 更新从机: MAC=%s IP=%s\n", macStr.c_str(), ipStr.c_str());
return true;
}
}
// 新从机 → 添加到列表
if (slaveCount >= MAX_SLAVES) {
Serial.println("⚠️ 从机列表已满,无法添加");
return false;
}
SlaveNode* node = &slaves[slaveCount++];
memcpy(node->mac, mac, 6);
node->ip = ip;
node->active = true;
// 注册到 ESP-NOW 对等列表
esp_now_peer_info_t peerInfo = {};
memcpy(peerInfo.peer_addr, mac, 6);
peerInfo.channel = 0; // 自动使用 Wi-Fi 当前信道
peerInfo.encrypt = false;
if (esp_now_add_peer(&peerInfo) == ESP_OK) {
Serial.printf("✅ 注册从机: MAC=%s IP=%s ( #%d )\n",
macStr.c_str(), ipStr.c_str(), slaveCount);
return true;
} else {
Serial.println("❌ ESP-NOW 添加对等节点失败");
slaveCount--; // 回滚
return false;
}
}
// ✅ 新增:根据 IP 查找从机 MAC
uint8_t* findSlaveByIP(const String& ipStr) {
IPAddress target;
if (!target.fromString(ipStr)) return nullptr;
for (int i = 0; i < slaveCount; i++) {
if (slaves[i].active && slaves[i].ip == target) {
return slaves[i].mac;
}
}
return nullptr;
}
// ✅ 新增ESP-NOW 发送(内部使用)
bool sendEspNow(const uint8_t* mac, const char* data, int len) {
if (!espnowInited) initEspNow();
if (!mac || !data || len <= 0 || len > 250) return false; // ESP-NOW 最大 250 字节
Serial.printf("📶 信道:%d\n", WiFi.channel());
/* int sendreslut=esp_now_send(mac, (uint8_t*)data, len);
Serial.printf("📶 Wi-Fi 模式:%s\n",
WiFi.getMode() == WIFI_AP ? "AP" :
WiFi.getMode() == WIFI_STA ? "STA" : "AP+STA");
Serial.printf("📶 信道:%d\n", WiFi.channel());
Serial.printf("📶 状态:%d\n", WiFi.status());
Serial.printf("📶 ESP-NOW%s\n", espnowInited ? "已启用" : "未启用");
Serial.printf("ESP-NOW 发送, 信道:%d, mac:%s:%s, 状态:0x%X\n",WiFi.channel(), mac, data, sendreslut); */
return esp_now_send(mac, (uint8_t*)data, len)== ESP_OK;
}
// ✅ 修正后的轻量维护
void autoMaintain() {
if (!is_initialized) return;
if (millis() - last_maintenance < MAINTENANCE_INTERVAL) return;
last_maintenance = millis();
if (is_ap_mode) {
if (!ready) {
ready = (udp.begin(local_port) == 1);
if (ready) Serial.printf("✅ AP模式UDP就绪: 端口=%d\n", local_port);
}
return;
}
if (WiFi.status() != WL_CONNECTED) {
WiFi.config(local_ip, gateway, subnet);
if (!ready) {
Serial.printf("start local net 正在启动网络连接!!!\n");
WiFi.begin(ssid, password);
delay(1000);
} else {
Serial.printf("reconnect 网络中断,恢复中!!!\n");
WiFi.reconnect();
}
return;
}
if (!ready) {
ready = (udp.begin(local_port) == 1);
if (ready) {
Serial.printf("✅ 网络就绪: IP=%s 端口=%d\n",
WiFi.localIP().toString().c_str(), local_port);
// ✅ 网络就绪后初始化 ESP-NOW
//initEspNow();
} else {
return;
}
}
}
public:
// ✅ STA模式连接
bool connect(const char* _ssid, const char* _pwd,
uint8_t gw1, uint8_t gw2, uint8_t gw3, uint8_t gw4,
uint8_t local_ip_last, uint16_t port = 8888) {
WiFi.mode(WIFI_STA);
WiFi.disconnect(true);
last_maintenance = 0;
ready = false;
ssid = _ssid; password = _pwd;
local_ip = IPAddress(gw1, gw2, gw3, local_ip_last);
gateway = IPAddress(gw1, gw2, gw3, gw4);
subnet = IPAddress(255, 255, 255, 0);
local_port = port;
is_initialized = true;
is_ap_mode = false;
current_ssid = String(_ssid);
current_password = String(_pwd);
cached_gw1 = gw1; cached_gw2 = gw2; cached_gw3 = gw3; cached_gw4 = gw4;
Serial.printf("📶 配置STA网络: IP=%d.%d.%d.%d 网关=%d.%d.%d.%d 端口=%d\n",
gw1, gw2, gw3, local_ip_last, gw1, gw2, gw3, gw4, port);
Serial.println("💡 网络将在后台自动连接...");
autoMaintain();
return true;
}
// ✅ AP模式连接
bool connectAP(const char* _ssid, const char* _pwd, uint16_t port = 8888) {
WiFi.disconnect(true);
WiFi.mode(WIFI_AP);
last_maintenance = 0;
ready = false;
WiFi.softAPConfig(IPAddress(192, 168, 4, 1),
IPAddress(192, 168, 4, 1),
IPAddress(255, 255, 255, 0));
if (!WiFi.softAP(_ssid, _pwd, 1, false, 10)) {
Serial.println("❌ AP启动失败");
return false;
}
local_port = port;
ready = (udp.begin(local_port) == 1);
is_initialized = true;
is_ap_mode = true;
current_ssid = String(_ssid);
current_password = String(_pwd);
cached_gw1 = 192; cached_gw2 = 168; cached_gw3 = 4; cached_gw4 = 1;
Serial.printf("✅ AP模式启动: 热点=\"%s\" 密码=\"%s\"\n", _ssid, _pwd);
Serial.printf("🌐 本机IP: %s | 端口: %d | 客户端: %d/%d\n",
WiFi.softAPIP().toString().c_str(), port,
WiFi.softAPgetStationNum(), 10);
// ✅ AP模式也初始化 ESP-NOW
initEspNow();
return true;
}
// ✅ 向小车发送配置
void sendConfigToDevice(uint8_t car_last_octet, uint8_t monitor_last_octet) {
if (!is_initialized) {
Serial.println("❌ 手柄未配置网络,无法发送配置");
return;
}
if (car_last_octet == cached_gw4 || car_last_octet == 0 || car_last_octet == 255) {
Serial.println("❌ 无效的小车编号需1~254且不能与网关相同");
return;
}
uint8_t local_last = is_ap_mode ? 1 : local_ip[3];
if(monitor_last_octet != 255) local_last = monitor_last_octet;
Serial.printf("wificar:%s:%s:%d:%d:%d:%d:%d:%d\nWZHY",
current_ssid.c_str(), current_password.c_str(),
cached_gw1, cached_gw2, cached_gw3, cached_gw4,
car_last_octet, local_last);
Serial1.printf("wificar:%s:%s:%d:%d:%d:%d:%d:%d\nWZHY",
current_ssid.c_str(), current_password.c_str(),
cached_gw1, cached_gw2, cached_gw3, cached_gw4,
car_last_octet, local_last);
Serial.printf("✅ 已配置小车 #%d → IP=%d.%d.%d.%d | 监视器=%d.%d.%d.%d\n",
car_last_octet,
cached_gw1, cached_gw2, cached_gw3, car_last_octet,
cached_gw1, cached_gw2, cached_gw3, local_last);
}
// ✅ 接收:解析 ESPNOW_REG 并自动注册
bool hasMessage() {
autoMaintain();
if (!is_initialized || !ready) return false;
receivedRaw = "";
parsedValue = "";
int len = udp.parsePacket();
if (len <= 0 || len > 512) return false;
char buf[513];
int read = udp.read((uint8_t*)buf, min(len, 512));
if (read > 0) {
buf[read] = '\0';
receivedRaw = String(buf);
// ✅ 解析 ESPNOW_REG 格式: "mac:AA:BB:CC:DD:EE:FF;ip:192.168.4.6"
if (getMessageValue("r")) {
String raw = lastValue(); // "mac:AA:BB:CC:DD:EE:FF;ip:192.168.4.6"
// 提取 MAC
int macStart = raw.indexOf("mac:");
int macEnd = raw.indexOf(";", macStart);
// 提取 IP
int ipStart = raw.indexOf("ip:", macEnd);
if (macStart != -1 && macEnd != -1 && ipStart != -1) {
String macStr = raw.substring(macStart + 4, macEnd); // +4 跳过 "mac:"
String ipStr = raw.substring(ipStart + 3); // +3 跳过 "ip:"
// ✅ 注册/更新从机
registerSlave(macStr, ipStr);
}
}
return true;
}
return false;
}
// ✅ 解析字段(不变)
bool getMessageValue(const String& key) {
parsedValue = "";
if (receivedRaw.length() == 0) return false;
String keyPattern = "\"" + key + "\"";
int startIndex = receivedRaw.indexOf(keyPattern);
if (startIndex == -1) return false;
int colonIndex = receivedRaw.indexOf(":", startIndex);
if (colonIndex == -1) return false;
int valueStart = colonIndex + 1;
while (valueStart < receivedRaw.length() &&
receivedRaw.charAt(valueStart) == ' ') valueStart++;
if (valueStart < receivedRaw.length() && receivedRaw.charAt(valueStart) == '"') {
int valueEnd = receivedRaw.indexOf("\"", valueStart + 1);
if (valueEnd != -1) {
parsedValue = receivedRaw.substring(valueStart + 1, valueEnd);
return true;
}
} else {
int valueEnd = valueStart;
while (valueEnd < receivedRaw.length() &&
receivedRaw.charAt(valueEnd) != ',' &&
receivedRaw.charAt(valueEnd) != '}' &&
receivedRaw.charAt(valueEnd) != ' ') valueEnd++;
while (valueEnd > valueStart && receivedRaw.charAt(valueEnd - 1) == ' ') valueEnd--;
if (valueEnd > valueStart) {
parsedValue = receivedRaw.substring(valueStart, valueEnd);
return true;
}
}
return false;
}
// ✅ 发送智能路由UDP/ESP-NOW 自动选择)
bool sendTo(const String& target_ip_str, const String& key, const char* format, ...) {
autoMaintain();
if (!is_initialized || !ready) return false;
IPAddress target_ip;
if (!target_ip.fromString(target_ip_str)) return false;
// ✅ 智能路由:如果目标 IP 是已注册从机,优先使用 ESP-NOW
uint8_t* slaveMac = findSlaveByIP(target_ip_str);
if (slaveMac && espnowInited) {
// 构造 ESP-NOW 消息(保持 JSON 格式,与 UDP 一致)
char content[256];
va_list args;
va_start(args, format);
vsnprintf(content, sizeof(content) - 1, format, args);
va_end(args);
char json[300];
snprintf(json, sizeof(json), "{\"%s\":\"%s\"}", key.c_str(), content);
// ✅ 使用 ESP-NOW 发送(更快、更低延迟)
if (sendEspNow(slaveMac, json, strlen(json))) {
Serial.println("⚠️ ESP-NOW 发送");
delay(10);
return true;
}
// ESP-NOW 失败时,降级使用 UDP可选
//Serial.println("⚠️ ESP-NOW 发送失败,降级使用 UDP");
}
// ✅ 默认:使用 UDP 发送
char content[256];
va_list args;
va_start(args, format);
vsnprintf(content, sizeof(content) - 1, format, args);
va_end(args);
char json[300];
snprintf(json, sizeof(json), "{\"%s\":\"%s\"}\r\n", key.c_str(), content);
return udp.beginPacket(target_ip, local_port) &&
udp.write((const uint8_t*)json, strlen(json)) &&
udp.endPacket();
}
// ✅ 积木友好重载
bool sendTo(const String& target_ip_str, const String& key, const String& value) {
return sendTo(target_ip_str, key, "%s", value.c_str());
}
// ✅ 辅助方法
String lastValue() { return parsedValue; }
String lastMessage() { return receivedRaw; }
String getLocalIP() {
autoMaintain();
return is_initialized ?
(is_ap_mode ? WiFi.softAPIP().toString() : WiFi.localIP().toString()) :
"未配置";
}
bool isConnected() {
autoMaintain();
return is_initialized && ready;
}
bool isAPMode() { return is_ap_mode; }
// ✅ 新增:获取已注册从机数量(调试用)
int getRegisteredSlaveCount() { return slaveCount; }
};
extern UDPPeer peer;

View File

@@ -2,11 +2,11 @@
#include <WiFi.h>
#include <WebServer.h>
#include <AsyncUDP.h> // ✅【改动 1】替换头文件
#include "HardwareSerial.h"
#include "esp_camera.h"
#include "esp_heap_caps.h"
#include <freertos/queue.h>
#include <Preferences.h>
#include <stdarg.h>
#include <esp_now.h>
#include <esp_wifi.h>
@@ -41,8 +41,7 @@ volatile bool has_active_client = false;
// ===== 系统配置 =====
String ssid = "ESP32-S3-Camera";
String password = "12345678";
const uint8_t channel = 4;
const uint8_t max_clients = 10;
const unsigned long WIFI_TIMEOUT_MS = 15000;
const uint16_t WEB_SERVER_PORT = 80;
// ✅【改动 2】修改 UDP 对象定义,移除缓冲区变量 (AsyncUDP 不需要)
@@ -144,7 +143,7 @@ void sendMjpegFrame(WiFiClient &client, camera_fb_t *fb) {
// ✅ 流处理(自适应传输帧率)- 保持阻塞模式
void handleStream() {
has_active_client = true;
Serial.println("✅ 客户端连接,开始推流 (AsyncUDP 后台运行)");
Serial.println("✅ 客户端连接,开始推流");
WiFiClient client = server.client();
if (!client || !client.connected()) {
@@ -313,19 +312,24 @@ bool initCamera() {
void setup() {
Serial.begin(1000000);
delay(1000);
// 锁定 CPU 最高主频,确保流媒体性能
setCpuFrequencyMhz(240);
Serial.printf("⚡ CPU 主频: %d MHz\n", getCpuFrequencyMhz());
Preferences prefs;
prefs.begin("wifi_cfg", false);
String saved_ssid = prefs.getString("ssid", "ESP32-S3-Camera");
String saved_pwd = prefs.getString("pwd", "12345678");
// String saved_ssid = prefs.getString("ssid", "FS");
// String saved_pwd = prefs.getString("pwd", "zcw666666");
String saved_ssid = "FS";
String saved_pwd = "zcw666666";
if (saved_ssid.length() > 0 && saved_ssid.length() <= 32) ssid = saved_ssid;
if (saved_pwd.length() >= 8 && saved_pwd.length() <= 64) password = saved_pwd;
prefs.end();
Serial.println("\n");
Serial.println("╔══════════════════════════════════════════════════════════════════════╗");
Serial.println("║ ESP32-S3 + OV2640 双核并行流系统 v4.0 (AsyncUDP 中断版)");
Serial.println("║ ESP32-S3 + OV2640 双核并行流系统 v4.0 (STA 模式) ");
Serial.println("╚══════════════════════════════════════════════════════════════════════╝");
Serial.println("💡 串口指令: WIFI:名称:密码 (例如: WIFI:LabCam:Secure1234)");
@@ -344,14 +348,26 @@ void setup() {
while (1) delay(1000);
}
Serial.println("\n[2] WiFi 初始化...");
WiFi.softAPConfig(IPAddress(192, 168, 4, 1), IPAddress(192, 168, 4, 1), IPAddress(255, 255, 255, 0));
WiFi.softAP(ssid.c_str(), password.c_str(), channel, false, max_clients);
Serial.print("✅ 热点:"); Serial.println(ssid);
Serial.print("🔒 密码:"); Serial.println(password);
Serial.print("🌐 IP: "); Serial.println(WiFi.softAPIP());
Serial.printf("🌐 访问http://%s\n", WiFi.softAPIP().toString().c_str());
Serial.println("\n[2] WiFi 初始化 (STA 模式)...");
WiFi.mode(WIFI_STA);
WiFi.begin(ssid.c_str(), password.c_str());
Serial.printf("📡 连接中: %s", ssid.c_str());
unsigned long startAttempt = millis();
while (WiFi.status() != WL_CONNECTED && millis() - startAttempt < WIFI_TIMEOUT_MS) {
delay(500);
Serial.print(".");
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\n✅ WiFi 已连接");
WiFi.setSleep(false);
Serial.print("📶 SSID: "); Serial.println(ssid);
Serial.print("🌐 IP: "); Serial.println(WiFi.localIP());
Serial.printf("🌐 访问http://%s\n", WiFi.localIP().toString().c_str());
} else {
Serial.println("\n❌ WiFi 连接失败,继续运行 (无网络)");
}
Serial.println("\n[3] 摄像头初始化 (SVGA)...");
if (!initCamera()) {
@@ -387,7 +403,7 @@ void setup() {
"<body>"
"<header>"
"<h1>📸 局域物联网-" + String(ssid) + "</h1>"
"<div class=\"subtitle\">ESP32-S3 双核并行摄像头 (AsyncUDP)</div>"
"<div class=\"subtitle\">ESP32-S3 双核并行摄像头 (STA 模式)</div>"
"</header>"
"<div class=\"camera-container\">"
@@ -395,7 +411,7 @@ void setup() {
"</div>"
"<div class=\"footer\">"
"<p>ESP32-S3 双核流系统 | 推流阻塞不影响 UDP 中断</p>"
"<p>ESP32-S3 双核流系统 | STA 模式</p>"
"</div>"
"<script>"
@@ -418,7 +434,7 @@ void setup() {
char json_buffer[180];
snprintf(json_buffer, sizeof(json_buffer),
"{\"clients\":%d,\"capture_fps\":%.1f,\"transmit_fps\":%.1f,\"failed\":%lu,\"free_psram\":%lu,\"has_client\":%s}",
WiFi.softAPgetStationNum(),
(WiFi.status() == WL_CONNECTED) ? 1 : 0,
capture_fps,
transmit_fps,
failed_frames,
@@ -444,13 +460,13 @@ void setup() {
Serial.println("❌ AsyncUDP 监听失败");
}
Serial.printf("✅ 系统就绪!访问 http://%s\n\n", WiFi.softAPIP().toString().c_str());
Serial.println("💡 提示UDP 数据现在由中断处理,推流不会卡顿通讯。");
Serial.printf("✅ 系统就绪!访问 http://%s\n\n", WiFi.localIP().toString().c_str());
Serial.println("💡 提示UDP 数据由中断处理,推流不阻塞通讯。");
}
void loop() {
server.handleClient();
// ✅【改动 5】删除了所有 udpServer.parsePacket() 相关代码
// 现在 UDP 处理完全在 onUdpPacket 回调中自动完成loop 中无需任何操作

View File

@@ -1,672 +0,0 @@
从机端
#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>
#include "esp_camera.h"
#include "esp_heap_caps.h"
#include <freertos/queue.h>
#include <Preferences.h>
#include <WiFiUdp.h>
#include <stdarg.h>
#if 1
// ===== UDP通信类is_initialized 改回普通成员 + 新增访问方法)=====
class UDPPeer {
private:
WiFiUDP udp;
bool ready = false;
uint16_t local_port = 8888;
// 双缓存
String receivedRaw;
String parsedValue;
// 连接参数
const char* ssid = nullptr;
const char* password = nullptr;
IPAddress local_ip;
IPAddress gateway;
IPAddress subnet;
// ✅ 监视器IP最后一位
uint8_t monitor_ip_last = 101;
// ✅ 改回普通成员变量(非静态)
bool is_initialized = false;
uint32_t last_maintenance = 0;
const uint32_t MAINTENANCE_INTERVAL = 1000;
void autoMaintain() {
if (!is_initialized) return;
if (millis() - last_maintenance < MAINTENANCE_INTERVAL) return;
last_maintenance = millis();
//Serial.printf("testA");
if (WiFi.status() != WL_CONNECTED) {
WiFi.config(local_ip, gateway, subnet);
if (!ready)
{
Serial.printf("正在启动网络连接!!!\n");
WiFi.begin(ssid, password);
delay(1000);
}
else
{
Serial.printf("网络中断,恢复中!!!\n");
WiFi.reconnect();
}
//last_maintenance = 0;
return;
}
//Serial.printf("testB");
if (!ready) {
ready = (udp.begin(local_port) == 1);
if (ready) {
Serial.printf("✅ 网络就绪: IP=%s 端口=%d\n",
WiFi.localIP().toString().c_str(), local_port);
} else {
//last_maintenance = 0;
return;
}
}
//last_maintenance = millis();
}
public:
// ✅ 新增:访问 is_initialized 的成员方法
bool isInitialized() {
return is_initialized;
}
// ✅ 设置监视器IP
void setMonitorIPLast(uint8_t last_octet) {
monitor_ip_last = last_octet;
}
// ✅ 获取完整监视器IP
String getMonitorIP() {
return String(gateway[0]) + "." + String(gateway[1]) + "." +
String(gateway[2]) + "." + String(monitor_ip_last);
}
bool connect(const char* _ssid, const char* _pwd,
uint8_t gw1, uint8_t gw2, uint8_t gw3, uint8_t gw4,
uint8_t local_ip_last, uint16_t port = 8888) {
WiFi.disconnect(true);
last_maintenance = 0;
ready = false;
ssid = _ssid;
password = _pwd;
local_ip = IPAddress(gw1, gw2, gw3, local_ip_last);
gateway = IPAddress(gw1, gw2, gw3, gw4);
subnet = IPAddress(255, 255, 255, 0);
local_port = port;
is_initialized = true; // ✅ 普通成员赋值
Serial.printf("📶 wifi%s pwd%s配置网络: IP=%d.%d.%d.%d 网关=%d.%d.%d.%d 端口=%d\n",
ssid, password, gw1, gw2, gw3, local_ip_last, gw1, gw2, gw3, gw4, port);
Serial.println("💡 网络将在后台自动连接...");
autoMaintain();
return true;
}
bool hasMessage() {
autoMaintain();
if (!is_initialized || !ready) return false;
receivedRaw = "";
parsedValue = "";
int len = udp.parsePacket();
if (len <= 0 || len > 512) return false;
char buf[513];
int read = udp.read((uint8_t*)buf, min(len, 512));
if (read > 0) {
buf[read] = '\0';
receivedRaw = String(buf);
return true;
}
return false;
}
bool getMessageValue(const String& key) {
parsedValue = "";
if (receivedRaw.length() == 0) return false;
String keyPattern = "\"" + key + "\"";
int startIndex = receivedRaw.indexOf(keyPattern);
if (startIndex == -1) return false;
int colonIndex = receivedRaw.indexOf(":", startIndex);
if (colonIndex == -1) return false;
int valueStart = colonIndex + 1;
while (valueStart < receivedRaw.length() &&
receivedRaw.charAt(valueStart) == ' ') valueStart++;
if (valueStart < receivedRaw.length() && receivedRaw.charAt(valueStart) == '"') {
int valueEnd = receivedRaw.indexOf("\"", valueStart + 1);
if (valueEnd != -1) {
parsedValue = receivedRaw.substring(valueStart + 1, valueEnd);
return true;
}
} else {
int valueEnd = valueStart;
while (valueEnd < receivedRaw.length() &&
receivedRaw.charAt(valueEnd) != ',' &&
receivedRaw.charAt(valueEnd) != '}' &&
receivedRaw.charAt(valueEnd) != ' ') valueEnd++;
while (valueEnd > valueStart && receivedRaw.charAt(valueEnd - 1) == ' ') valueEnd--;
if (valueEnd > valueStart) {
parsedValue = receivedRaw.substring(valueStart, valueEnd);
return true;
}
}
return false;
}
bool sendTo(const String& target_ip_str, const String& key, const char* format, ...) {
autoMaintain();
if (!is_initialized || !ready) return false;
IPAddress target_ip;
if (!target_ip.fromString(target_ip_str)) return false;
char content[256];
va_list args;
va_start(args, format);
vsnprintf(content, sizeof(content) - 1, format, args);
va_end(args);
char json[300];
snprintf(json, sizeof(json), "{\"%s\":\"%s\"}\r\n", key.c_str(), content);
return udp.beginPacket(target_ip, local_port) &&
udp.write((const uint8_t*)json, strlen(json)) &&
udp.endPacket();
}
bool sendTo(const String& target_ip_str, const String& key, const String& value) {
return sendTo(target_ip_str, key, "%s", value.c_str());
}
String lastValue() { return parsedValue; }
String lastMessage() { return receivedRaw; }
String getLocalIP() {
autoMaintain();
return is_initialized ? WiFi.localIP().toString() : "未配置";
}
bool isConnected() {
autoMaintain();
return is_initialized && ready;
}
};
extern UDPPeer peer;
UDPPeer peer;
// ===== 默认配置 =====
#define DEFAULT_SSID "ESP32-Camera"
#define DEFAULT_PWD "12345678"
#define DEFAULT_GW1 192
#define DEFAULT_GW2 168
#define DEFAULT_GW3 4
#define DEFAULT_GW4 1
#define DEFAULT_LOCAL_LAST 100
#define DEFAULT_MONITOR_LAST 101
// ===== Flash配置管理 =====
void loadConfigFromFlash() {
Preferences prefs;
if (!prefs.begin("cam_cfg", true)) { // 只读模式
Serial.println("⚠️ Flash配置不存在使用默认值");
peer.connect(DEFAULT_SSID, DEFAULT_PWD,
DEFAULT_GW1, DEFAULT_GW2, DEFAULT_GW3, DEFAULT_GW4,
DEFAULT_LOCAL_LAST);
peer.setMonitorIPLast(DEFAULT_MONITOR_LAST);
prefs.end();
return;
}
// 读取配置
String ssid = prefs.getString("ssid", DEFAULT_SSID);
String pwd = prefs.getString("pwd", DEFAULT_PWD);
uint8_t gw1 = prefs.getUChar("gw1", DEFAULT_GW1);
uint8_t gw2 = prefs.getUChar("gw2", DEFAULT_GW2);
uint8_t gw3 = prefs.getUChar("gw3", DEFAULT_GW3);
uint8_t gw4 = prefs.getUChar("gw4", DEFAULT_GW4);
uint8_t local_last = prefs.getUChar("local", DEFAULT_LOCAL_LAST);
uint8_t monitor_last = prefs.getUChar("monitor", DEFAULT_MONITOR_LAST);
prefs.end();
// 应用配置
peer.connect(ssid.c_str(), pwd.c_str(), gw1, gw2, gw3, gw4, local_last);
peer.setMonitorIPLast(monitor_last);
Serial.println("✅ 从Flash加载配置成功");
}
void saveConfigToFlash(const char* ssid, const char* pwd,
uint8_t gw1, uint8_t gw2, uint8_t gw3, uint8_t gw4,
uint8_t local_last, uint8_t monitor_last) {
Preferences prefs;
if (!prefs.begin("cam_cfg", false)) { // 读写模式
Serial.println("❌ Flash配置保存失败");
return;
}
prefs.putString("ssid", ssid);
prefs.putString("pwd", pwd);
prefs.putUChar("gw1", gw1);
prefs.putUChar("gw2", gw2);
prefs.putUChar("gw3", gw3);
prefs.putUChar("gw4", gw4);
prefs.putUChar("local", local_last);
prefs.putUChar("monitor", monitor_last);
prefs.end();
Serial.println("✅ 配置已保存到Flash");
}
// ===== 串口指令解析 =====
void handleSerialCommand() {
if (!Serial.available()) return;
String cmd = Serial.readStringUntil('\n');
cmd.trim();
if (!cmd.startsWith("wificar:"))
{
if(cmd.endsWith("LZU")||cmd.endsWith("LZU\r"))
{
peer.sendTo(peer.getMonitorIP(), "f", cmd);
Serial.printf("📤 已发送到 %s: {\"f\":%s}\n", peer.getMonitorIP().c_str(), cmd);
}
return;
}
// 解析格式: wificar:ssid:pwd:gw1:gw2:gw3:gw4:local_last:monitor_last
// 示例: wificar:MyCam:secure123:192:168:43:1:100:101
int pos[7]; // 存储6个冒号位置
int count = 0;
for (int i = 8; i < cmd.length() && count < 7; i++) { // 从"wificar:"后开始
if (cmd.charAt(i) == ':') {
pos[count++] = i;
}
}
if (count < 7) {
Serial.println("❌ 指令格式错误,应为: wificar:ssid:pwd:gw1:gw2:gw3:gw4:local_last:monitor_last");
return;
}
String ssid = cmd.substring(8, pos[0]);
String pwd = cmd.substring(pos[0] + 1, pos[1]);
uint8_t gw1 = cmd.substring(pos[1] + 1, pos[2]).toInt();
uint8_t gw2 = cmd.substring(pos[2] + 1, pos[3]).toInt();
uint8_t gw3 = cmd.substring(pos[3] + 1, pos[4]).toInt();
uint8_t gw4 = cmd.substring(pos[4] + 1, pos[5]).toInt();
uint8_t local_last = cmd.substring(pos[5] + 1, pos[6]).toInt();
uint8_t monitor_last = cmd.substring(pos[6] + 1).toInt();
// 应用新配置
peer.connect(ssid.c_str(), pwd.c_str(), gw1, gw2, gw3, gw4, local_last);
peer.setMonitorIPLast(monitor_last);
// 保存到Flash
saveConfigToFlash(ssid.c_str(), pwd.c_str(), gw1, gw2, gw3, gw4, local_last, monitor_last);
Serial.println("✅ WiFi配置已更新并保存");
}
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("\n╔════════════════════════════════════════════════════════════════╗");
Serial.println("║ ESP32-S3 摄像头 UDP 通信系统 v1.0 ║");
Serial.println("║ 支持Flash配置存储 | 串口动态配置 | 自动重连 ║");
Serial.println("╚════════════════════════════════════════════════════════════════╝");
Serial.println("💡 串口指令:wificar:ssid:pwd:gw1:gw2:gw3:gw4:local_last:monitor_last");
Serial.println(" 示例: wificar:MyCam:secure123:192:168:43:1:100:101\n");
// 从Flash加载配置自动回退到默认值
loadConfigFromFlash();
}
void loop() {
// 处理串口配置指令
handleSerialCommand();
// ✅ 使用成员方法读取 is_initialized 状态
if (peer.isInitialized()) {
// 每2秒发送一次消息到监视器
// static uint32_t last_send = 0;
// if (millis() - last_send >= 2000) {
// last_send = millis();
// peer.sendTo(peer.getMonitorIP(), "f", "device");
// Serial.printf("📤 已发送到 %s: {\"f\":\"start\"}\n", peer.getMonitorIP().c_str());
// }
// 每5ms检查一次消息
static uint32_t last_check = 0;
if (millis() - last_check >= 6) {
last_check = millis();
if (peer.hasMessage()) {
if (peer.getMessageValue("c")) {
Serial.printf("📥 收到消息: %sZHY\n", peer.lastValue().c_str());
}
}
}
}
delay(1); // 避免忙等待
}
#else
主机端
#include <WiFi.h>
#include <WiFiUdp.h>
#include <stdarg.h>
class UDPPeer {
private:
WiFiUDP udp;
bool ready = false;
uint16_t local_port = 8888;
// 双缓存
String receivedRaw;
String parsedValue;
// 连接参数
const char* ssid = nullptr;
const char* password = nullptr;
IPAddress local_ip;
IPAddress gateway;
IPAddress subnet;
// ✅ 新增AP模式标志默认false=STA模式
bool is_ap_mode = false;
// ✅ ✅ ✅ 新增网络配置缓存6行 ✅ ✅ ✅
String current_ssid = "";
String current_password = "";
uint8_t cached_gw1 = 0, cached_gw2 = 0, cached_gw3 = 0, cached_gw4 = 0;
// 状态标志
bool is_initialized = false;
uint32_t last_maintenance = 0;
const uint32_t MAINTENANCE_INTERVAL = 1000;
// ✅ 修正后的轻量维护AP模式特殊处理
void autoMaintain() {
if (!is_initialized) return;
if (millis() - last_maintenance < MAINTENANCE_INTERVAL) return;
last_maintenance = millis();
// ✅ AP模式无需维护WiFi始终在线仅确保UDP就绪
if (is_ap_mode) {
if (!ready) {
ready = (udp.begin(local_port) == 1);
if (ready) {
Serial.printf("✅ AP模式UDP就绪: 端口=%d\n", local_port);
}
}
//last_maintenance = millis();
return;
}
if (WiFi.status() != WL_CONNECTED) {
WiFi.config(local_ip, gateway, subnet);
if (!ready)
{
Serial.printf("start local net正在启动网络连接\n");
WiFi.begin(ssid, password);
delay(1000);
}
else
{
Serial.printf("reconnect网络中断恢复中\n");
WiFi.reconnect();
}
//last_maintenance = 0;
return;
}
if (!ready) {
ready = (udp.begin(local_port) == 1);
if (ready) {
Serial.printf("✅ 网络就绪: IP=%s 端口=%d\n",
WiFi.localIP().toString().c_str(), local_port);
} else {
//last_maintenance = 0;
return;
}
}
//last_maintenance = millis();
}
public:
// ✅ STA模式连接末尾添加配置缓存 - 6行
bool connect(const char* _ssid, const char* _pwd,
uint8_t gw1, uint8_t gw2, uint8_t gw3, uint8_t gw4,
uint8_t local_ip_last, uint16_t port = 8888) {
// 切换到STA模式
WiFi.mode(WIFI_STA);
WiFi.disconnect(true);
last_maintenance = 0;
ready = false;
ssid = _ssid;
password = _pwd;
local_ip = IPAddress(gw1, gw2, gw3, local_ip_last);
gateway = IPAddress(gw1, gw2, gw3, gw4);
subnet = IPAddress(255, 255, 255, 0);
local_port = port;
is_initialized = true;
is_ap_mode = false;
// ✅ ✅ ✅ 新增:缓存网络配置 ✅ ✅ ✅
current_ssid = String(_ssid);
current_password = String(_pwd);
cached_gw1 = gw1;
cached_gw2 = gw2;
cached_gw3 = gw3;
cached_gw4 = gw4;
Serial.printf("📶 配置STA网络: IP=%d.%d.%d.%d 网关=%d.%d.%d.%d 端口=%d\n",
gw1, gw2, gw3, local_ip_last, gw1, gw2, gw3, gw4, port);
Serial.println("💡 网络将在后台自动连接...");
autoMaintain();
return true;
}
// ✅ ✅ ✅ AP模式连接末尾添加配置缓存 - 6行✅ ✅ ✅
bool connectAP(const char* _ssid, const char* _pwd, uint16_t port = 8888) {
WiFi.disconnect(true);
WiFi.mode(WIFI_AP);
last_maintenance = 0;
ready = false;
WiFi.softAPConfig(IPAddress(192, 168, 4, 1),
IPAddress(192, 168, 4, 1),
IPAddress(255, 255, 255, 0));
if (!WiFi.softAP(_ssid, _pwd, 4, false, 4)) {
Serial.println("❌ AP启动失败");
return false;
}
local_port = port;
ready = (udp.begin(local_port) == 1);
is_initialized = true;
is_ap_mode = true;
// ✅ ✅ ✅ 新增缓存AP模式固定配置 ✅ ✅ ✅
current_ssid = String(_ssid);
current_password = String(_pwd);
cached_gw1 = 192;
cached_gw2 = 168;
cached_gw3 = 4;
cached_gw4 = 1;
Serial.printf("✅ AP模式启动: 热点=\"%s\" 密码=\"%s\"\n", _ssid, _pwd);
Serial.printf("🌐 本机IP: %s | 端口: %d | 客户端: %d/%d\n",
WiFi.softAPIP().toString().c_str(), port,
WiFi.softAPgetStationNum(), 4);
return true;
}
// ✅ ✅ ✅ 核心向小车发送配置仅需IP最后1段 - 20行✅ ✅ ✅
void sendConfigToDevice(uint8_t car_last_octet, uint8_t monitor_last_octet) {
if (!is_initialized) {
Serial.println("❌ 手柄未配置网络,无法发送配置");
return;
}
if (car_last_octet == cached_gw4 || car_last_octet == 0 || car_last_octet == 255) {
Serial.println("❌ 无效的小车编号需1~254且不能与网关相同");
return;
}
// 获取本机IP最后1段AP模式固定为1STA模式取local_ip[3]
uint8_t local_last = is_ap_mode ? 1 : local_ip[3];
if(monitor_last_octet!=255) local_last =monitor_last_octet;
// 发送标准配置指令与终端handleSerialCommand完全兼容
Serial.printf("wificar:%s:%s:%d:%d:%d:%d:%d:%d\nWZHY",
current_ssid.c_str(),
current_password.c_str(),
cached_gw1, cached_gw2, cached_gw3, cached_gw4,
car_last_octet, // 小车本机IP最后1段
local_last); // 监视器IP最后1段指向本机
Serial1.printf("wificar:%s:%s:%d:%d:%d:%d:%d:%d\nWZHY",
current_ssid.c_str(),
current_password.c_str(),
cached_gw1, cached_gw2, cached_gw3, cached_gw4,
car_last_octet, // 小车本机IP最后1段
local_last); // 监视器IP最后1段指向本机
Serial.printf("✅ 已配置小车 #%d → IP=%d.%d.%d.%d | 监视器=%d.%d.%d.%d\n",
car_last_octet,
cached_gw1, cached_gw2, cached_gw3, car_last_octet,
cached_gw1, cached_gw2, cached_gw3, local_last);
}
// ✅ 接收:轻量维护 + 纯检查自动适配AP/STA
bool hasMessage() {
autoMaintain();
if (!is_initialized || !ready) return false;
receivedRaw = "";
parsedValue = "";
int len = udp.parsePacket();
if (len <= 0 || len > 512) return false;
char buf[513];
int read = udp.read((uint8_t*)buf, min(len, 512));
if (read > 0) {
buf[read] = '\0';
receivedRaw = String(buf);
// ✅【修复 1】补全右括号 + 简化 this->
// ✅【修复 2】类内调用直接写 lastValue()
if (getMessageValue("ESPNOW_REG")) {
Serial.println(lastValue());
}
return true;
}
return false;
}
// ✅ 解析字段(不变)
bool getMessageValue(const String& key) {
parsedValue = "";
if (receivedRaw.length() == 0) return false;
String keyPattern = "\"" + key + "\"";
int startIndex = receivedRaw.indexOf(keyPattern);
if (startIndex == -1) return false;
int colonIndex = receivedRaw.indexOf(":", startIndex);
if (colonIndex == -1) return false;
int valueStart = colonIndex + 1;
while (valueStart < receivedRaw.length() &&
receivedRaw.charAt(valueStart) == ' ') valueStart++;
if (valueStart < receivedRaw.length() && receivedRaw.charAt(valueStart) == '"') {
int valueEnd = receivedRaw.indexOf("\"", valueStart + 1);
if (valueEnd != -1) {
parsedValue = receivedRaw.substring(valueStart + 1, valueEnd);
return true;
}
} else {
int valueEnd = valueStart;
while (valueEnd < receivedRaw.length() &&
receivedRaw.charAt(valueEnd) != ',' &&
receivedRaw.charAt(valueEnd) != '}' &&
receivedRaw.charAt(valueEnd) != ' ') valueEnd++;
while (valueEnd > valueStart && receivedRaw.charAt(valueEnd - 1) == ' ') valueEnd--;
if (valueEnd > valueStart) {
parsedValue = receivedRaw.substring(valueStart, valueEnd);
return true;
}
}
return false;
}
// ✅ 发送:轻量维护 + 直接发送自动适配AP/STA
bool sendTo(const String& target_ip_str, const String& key, const char* format, ...) {
autoMaintain();
if (!is_initialized || !ready) return false;
IPAddress target_ip;
if (!target_ip.fromString(target_ip_str)) return false;
char content[256];
va_list args;
va_start(args, format);
vsnprintf(content, sizeof(content) - 1, format, args);
va_end(args);
char json[300];
snprintf(json, sizeof(json), "{\"%s\":\"%s\"}\r\n", key.c_str(), content);
return udp.beginPacket(target_ip, local_port) &&
udp.write((const uint8_t*)json, strlen(json)) &&
udp.endPacket();
}
// ✅ 积木友好重载
bool sendTo(const String& target_ip_str, const String& key, const String& value) {
return sendTo(target_ip_str, key, "%s", value.c_str());
}
// ✅ 辅助方法自动返回正确IPAP/STA自适应
String lastValue() { return parsedValue; }
String lastMessage() { return receivedRaw; }
String getLocalIP() {
autoMaintain();
return is_initialized ?
(is_ap_mode ? WiFi.softAPIP().toString() : WiFi.localIP().toString()) :
"未配置";
}
bool isConnected() {
autoMaintain();
return is_initialized && ready;
}
bool isAPMode() { return is_ap_mode; }
};
extern UDPPeer peer;