first commit
This commit is contained in:
commit
f596912057
|
|
@ -0,0 +1,5 @@
|
|||
.pio
|
||||
.vscode/.browse.c_cpp.db*
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/ipch
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||
// for the documentation about the extensions.json format
|
||||
"recommendations": [
|
||||
"platformio.platformio-ide"
|
||||
],
|
||||
"unwantedRecommendations": [
|
||||
"ms-vscode.cpptools-extension-pack",
|
||||
"pioarduino.pioarduino-ide"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
|
||||
This directory is intended for project header files.
|
||||
|
||||
A header file is a file containing C declarations and macro definitions
|
||||
to be shared between several project source files. You request the use of a
|
||||
header file in your project source file (C, C++, etc) located in `src` folder
|
||||
by including it, with the C preprocessing directive `#include'.
|
||||
|
||||
```src/main.c
|
||||
|
||||
#include "header.h"
|
||||
|
||||
int main (void)
|
||||
{
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Including a header file produces the same results as copying the header file
|
||||
into each source file that needs it. Such copying would be time-consuming
|
||||
and error-prone. With a header file, the related declarations appear
|
||||
in only one place. If they need to be changed, they can be changed in one
|
||||
place, and programs that include the header file will automatically use the
|
||||
new version when next recompiled. The header file eliminates the labor of
|
||||
finding and changing all the copies as well as the risk that a failure to
|
||||
find one copy will result in inconsistencies within a program.
|
||||
|
||||
In C, the convention is to give header files names that end with `.h'.
|
||||
|
||||
Read more about using header files in official GCC documentation:
|
||||
|
||||
* Include Syntax
|
||||
* Include Operation
|
||||
* Once-Only Headers
|
||||
* Computed Includes
|
||||
|
||||
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
|
||||
This directory is intended for project specific (private) libraries.
|
||||
PlatformIO will compile them to static libraries and link into the executable file.
|
||||
|
||||
The source code of each library should be placed in a separate directory
|
||||
("lib/your_library_name/[Code]").
|
||||
|
||||
For example, see the structure of the following example libraries `Foo` and `Bar`:
|
||||
|
||||
|--lib
|
||||
| |
|
||||
| |--Bar
|
||||
| | |--docs
|
||||
| | |--examples
|
||||
| | |--src
|
||||
| | |- Bar.c
|
||||
| | |- Bar.h
|
||||
| | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
|
||||
| |
|
||||
| |--Foo
|
||||
| | |- Foo.c
|
||||
| | |- Foo.h
|
||||
| |
|
||||
| |- README --> THIS FILE
|
||||
|
|
||||
|- platformio.ini
|
||||
|--src
|
||||
|- main.c
|
||||
|
||||
Example contents of `src/main.c` using Foo and Bar:
|
||||
```
|
||||
#include <Foo.h>
|
||||
#include <Bar.h>
|
||||
|
||||
int main (void)
|
||||
{
|
||||
...
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
The PlatformIO Library Dependency Finder will find automatically dependent
|
||||
libraries by scanning project source files.
|
||||
|
||||
More information about PlatformIO Library Dependency Finder
|
||||
- https://docs.platformio.org/page/librarymanager/ldf.html
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
; PlatformIO Project Configuration File
|
||||
;
|
||||
; Build options: build flags, source filter
|
||||
; Upload options: custom upload port, speed and extra flags
|
||||
; Library options: dependencies, extra library storages
|
||||
; Advanced options: extra scripting
|
||||
;
|
||||
; Please visit documentation for the other options and examples
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[env:esp32dev]
|
||||
platform = espressif32
|
||||
board = esp32dev
|
||||
framework = arduino
|
||||
monitor_speed = 115200
|
||||
lib_compat_mode = soft
|
||||
lib_deps =
|
||||
jrowberg/I2Cdevlib-Core
|
||||
jrowberg/I2Cdevlib-MPU6050
|
||||
Adafruit_MPU6050
|
||||
Adafruit_Sensor
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "../../../Arduino/sketch_oct8a"
|
||||
},
|
||||
{
|
||||
"name": "TwoFootBot-arduion",
|
||||
"path": "."
|
||||
}
|
||||
],
|
||||
"settings": {}
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
#include "I2Cdev.h"
|
||||
#include "MPU6050_6Axis_MotionApps20.h"
|
||||
#include "Wire.h"
|
||||
#include <MPU6050.h>
|
||||
#include "imu.h"
|
||||
|
||||
MPU6050 mpu;
|
||||
bool dmpReady = false;
|
||||
uint8_t fifoBuffer[64];
|
||||
|
||||
Quaternion q;
|
||||
VectorFloat gravity;
|
||||
|
||||
// ===== 初始化 =====
|
||||
void imu_setup() {
|
||||
Serial.begin(115200);
|
||||
Wire.begin(21, 22);
|
||||
|
||||
mpu.initialize();
|
||||
if (!mpu.testConnection()) {
|
||||
Serial.println("MPU6050 连接失败");
|
||||
while (1);
|
||||
}
|
||||
|
||||
uint8_t devStatus = mpu.dmpInitialize();
|
||||
if (devStatus == 0) {
|
||||
mpu.CalibrateAccel(6);
|
||||
mpu.CalibrateGyro(6);
|
||||
mpu.setDMPEnabled(true);
|
||||
dmpReady = true;
|
||||
Serial.println("DMP 就绪");
|
||||
} else {
|
||||
Serial.print("DMP 初始化失败,错误码: ");
|
||||
Serial.println(devStatus);
|
||||
}
|
||||
}
|
||||
|
||||
// ===== DMP 读取,返回 YPR(角度制)=====
|
||||
YPR dmp() {
|
||||
YPR out = {0, 0, 0};
|
||||
if (!dmpReady) return out;
|
||||
|
||||
if (mpu.dmpGetCurrentFIFOPacket(fifoBuffer)) {
|
||||
mpu.dmpGetQuaternion(&q, fifoBuffer);
|
||||
mpu.dmpGetGravity(&gravity, &q);
|
||||
float ypr[3];
|
||||
mpu.dmpGetYawPitchRoll(ypr, &q, &gravity);
|
||||
|
||||
out.yaw = ypr[0] * 180.0f / M_PI;
|
||||
out.pitch = ypr[1] * 180.0f / M_PI;
|
||||
out.roll = ypr[2] * 180.0f / M_PI;
|
||||
}
|
||||
// Serial.print("Yaw: "); Serial.print(a.yaw);
|
||||
// Serial.print(" Pitch: "); Serial.print(a.pitch);
|
||||
// Serial.print(" Roll: "); Serial.println(a.roll);
|
||||
return out;
|
||||
}
|
||||
|
||||
// ===== 原始数据读取,返回 RawData =====
|
||||
RawData raw() {
|
||||
RawData r;
|
||||
mpu.getMotion6(&r.ax, &r.ay, &r.az, &r.gx, &r.gy, &r.gz);
|
||||
|
||||
// ±2g 量程下 1g = 16384 LSB
|
||||
// r.ax_g = r.ax / 16384.0f;
|
||||
// r.ay_g = r.ay / 16384.0f;
|
||||
// r.az_g = r.az / 16384.0f;
|
||||
|
||||
// // ±250°/s 量程下 1°/s = 131 LSB
|
||||
// r.gx_dps = r.gx / 131.0f;
|
||||
// r.gy_dps = r.gy / 131.0f;
|
||||
// r.gz_dps = r.gz / 131.0f;
|
||||
// Serial.print("Accel(g): X="); Serial.print(r.ax_g, 3);
|
||||
// Serial.print(" Y="); Serial.print(r.ay_g, 3);
|
||||
// Serial.print(" Z="); Serial.print(r.az_g, 3);
|
||||
// Serial.print(" | Gyro(°/s): X="); Serial.print(r.gx_dps, 2);
|
||||
// Serial.print(" Y="); Serial.print(r.gy_dps, 2);
|
||||
// Serial.print(" Z="); Serial.println(r.gz_dps, 2);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
#ifndef IMU_DATA_H
|
||||
#define IMU_DATA_H
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
// 返回姿态角(单位:度)
|
||||
struct YPR {
|
||||
float yaw;
|
||||
float pitch;
|
||||
float roll;
|
||||
};
|
||||
|
||||
// 返回原始数据及换算值
|
||||
struct RawData {
|
||||
int16_t ax, ay, az;
|
||||
int16_t gx, gy, gz;
|
||||
// float ax_g, ay_g, az_g;
|
||||
// float gx_dps, gy_dps, gz_dps;
|
||||
//
|
||||
};
|
||||
|
||||
// 函数声明
|
||||
YPR dmp();
|
||||
RawData raw();
|
||||
void imu_setup();
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
|
||||
#include "imu/imu.h"
|
||||
#include "wifi/wifi-sg90.h"
|
||||
#include <esp32-hal.h>
|
||||
|
||||
void setup() {
|
||||
// 串口初始化等公共配置
|
||||
Serial.begin(115200);
|
||||
|
||||
// 调用子模块的初始化函数
|
||||
imu_setup();
|
||||
wifi_setup();
|
||||
}
|
||||
void loop() {
|
||||
static unsigned long lastTelemetry = 0;
|
||||
unsigned long now = millis();
|
||||
//unsigned long now = micros(); // 改用微秒级计时
|
||||
|
||||
if (now - lastTelemetry >= 300) {
|
||||
lastTelemetry = now;
|
||||
YPR a = dmp();
|
||||
RawData r = raw();
|
||||
sendLineData(a, r);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
#pragma once
|
||||
#ifndef WIFI_SG90_H
|
||||
#define WIFI_SG90_H
|
||||
|
||||
#include "../imu/imu.h"
|
||||
|
||||
void sg90();
|
||||
void wifi_setup();
|
||||
|
||||
// TCP IMU telemetry: 22-byte frame: A5 5A, then 9 signed big-endian int16
|
||||
// values (yaw/pitch/roll in centidegrees, then ax/ay/az/gx/gy/gz), then
|
||||
// CRC-16/CCITT-FALSE over the first 20 bytes. Text command replies remain lines.
|
||||
void sendLineData(YPR a, RawData r);
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,381 @@
|
|||
#include <WiFi.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/queue.h>
|
||||
#include <math.h>
|
||||
#include "wifi/wifi-sg90.h"
|
||||
|
||||
// ==================== WiFi / TCP 配置 ====================
|
||||
const char* AP_SSID = "ESP32_AP";
|
||||
const char* AP_PASS = "12345678";
|
||||
const int TCP_PORT = 3333;
|
||||
|
||||
// ==================== 舵机配置 ====================
|
||||
const uint8_t NUM_SERVOS = 8;
|
||||
// 注意:避开 GPIO 6~11(Flash)、34~39(仅输入)、2/15(启动脚)
|
||||
const uint8_t SERVO_PINS[NUM_SERVOS] = {13, 14, 25, 26, 27, 32, 33, 4};
|
||||
|
||||
const uint16_t PULSE_MIN_US = 500; // 0 rad
|
||||
const uint16_t PULSE_MAX_US = 2400; // π rad
|
||||
const uint16_t PERIOD_US = 20000; // 50Hz = 20ms
|
||||
const float RAD_MIN = 0.0f;
|
||||
const float RAD_MAX = PI;
|
||||
|
||||
// ==================== 状态 ====================
|
||||
struct ServoState {
|
||||
uint8_t pin;
|
||||
float targetRad;
|
||||
unsigned long lastCycle;
|
||||
};
|
||||
|
||||
ServoState g_servos[NUM_SERVOS];
|
||||
|
||||
WiFiServer tcpServer(TCP_PORT);
|
||||
WiFiClient client;
|
||||
|
||||
struct TelemetryData {
|
||||
YPR attitude;
|
||||
RawData raw;
|
||||
};
|
||||
|
||||
QueueHandle_t telemetryQueue = nullptr;
|
||||
char commandBuffer[128];
|
||||
size_t commandLength = 0;
|
||||
bool commandOverflow = false;
|
||||
|
||||
// ==================== 前向声明 ====================
|
||||
bool isNumeric(const String& s);
|
||||
uint8_t parseFloatList(const String& s, float* out, uint8_t maxCount);
|
||||
void sendLine(const String& msg);
|
||||
void sg90Task(void* parameter);
|
||||
void sendQueuedTelemetry();
|
||||
int16_t encodeAngle(float degrees);
|
||||
void writeInt16BE(uint8_t* buffer, size_t offset, int16_t value);
|
||||
uint16_t calculateCrc16(const uint8_t* data, size_t length);
|
||||
|
||||
// ==================== 舵机初始化 / 更新 ====================
|
||||
|
||||
void servoInit() {
|
||||
for (uint8_t i = 0; i < NUM_SERVOS; i++) {
|
||||
g_servos[i].pin = SERVO_PINS[i];
|
||||
g_servos[i].targetRad = PI / 2.0f; // 默认 90°
|
||||
// 相位错开:第 i 个舵机在 20ms 周期内的 (i * 2.5ms) 处触发
|
||||
g_servos[i].lastCycle = micros() -
|
||||
(unsigned long)i * (PERIOD_US / NUM_SERVOS);
|
||||
|
||||
pinMode(SERVO_PINS[i], OUTPUT);
|
||||
digitalWrite(SERVO_PINS[i], LOW);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 更新 8 个舵机的 PWM,由 SG90 通讯任务周期调用
|
||||
*/
|
||||
void servoUpdateAll() {
|
||||
unsigned long now = micros();
|
||||
for (uint8_t i = 0; i < NUM_SERVOS; i++) {
|
||||
ServoState& s = g_servos[i];
|
||||
|
||||
if ((unsigned long)(now - s.lastCycle) >= PERIOD_US) {
|
||||
s.lastCycle += PERIOD_US;
|
||||
|
||||
// 若长时间滞后(例如遇到耗时长的指令),重新对齐到当前时刻
|
||||
if ((unsigned long)(now - s.lastCycle) >= PERIOD_US) {
|
||||
s.lastCycle = now;
|
||||
}
|
||||
|
||||
// 弧度 -> 脉宽(µs)
|
||||
float rad = s.targetRad;
|
||||
if (rad < RAD_MIN) rad = RAD_MIN;
|
||||
if (rad > RAD_MAX) rad = RAD_MAX;
|
||||
|
||||
uint16_t pulse = PULSE_MIN_US +
|
||||
(uint16_t)((PULSE_MAX_US - PULSE_MIN_US) *
|
||||
(rad / PI) + 0.5f);
|
||||
|
||||
digitalWrite(s.pin, HIGH);
|
||||
delayMicroseconds(pulse);
|
||||
digitalWrite(s.pin, LOW);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== WiFi / TCP ====================
|
||||
|
||||
void initWiFiAP() {
|
||||
WiFi.mode(WIFI_AP);
|
||||
WiFi.softAP(AP_SSID, AP_PASS);
|
||||
Serial.println("==== WiFi AP 已启动 ====");
|
||||
Serial.print("SSID : "); Serial.println(AP_SSID);
|
||||
Serial.print("Password: "); Serial.println(AP_PASS);
|
||||
Serial.print("AP IP : "); Serial.println(WiFi.softAPIP());
|
||||
}
|
||||
|
||||
void initTcpServer() {
|
||||
tcpServer.begin();
|
||||
tcpServer.setNoDelay(true);
|
||||
Serial.printf("TCP 服务器已启动,端口: %d\n", TCP_PORT);
|
||||
}
|
||||
|
||||
void sendLine(const String& msg) {
|
||||
if (client && client.connected()) {
|
||||
client.println(msg);
|
||||
}
|
||||
}
|
||||
|
||||
void acceptClientIfNeeded() {
|
||||
if (!client || !client.connected()) {
|
||||
client = tcpServer.available();
|
||||
if (client) {
|
||||
commandLength = 0;
|
||||
commandOverflow = false;
|
||||
Serial.print("上位机已连接: ");
|
||||
Serial.println(client.remoteIP());
|
||||
sendLine("ESP32: 已连接。请发送 8 个角度,例如 "
|
||||
"RAD:1.57,1.57,1.57,1.57,1.57,1.57,1.57,1.57");
|
||||
} else {
|
||||
commandLength = 0;
|
||||
commandOverflow = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 解析工具 ====================
|
||||
|
||||
bool isNumeric(const String& s) {
|
||||
if (s.length() == 0) return false;
|
||||
bool dotSeen = false;
|
||||
for (size_t i = 0; i < s.length(); i++) {
|
||||
char c = s.charAt(i);
|
||||
if (c == '-' || c == '+') {
|
||||
if (i != 0) return false;
|
||||
} else if (c == '.') {
|
||||
if (dotSeen) return false;
|
||||
dotSeen = true;
|
||||
} else if (!isDigit(c)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 解析逗号分隔的浮点列表
|
||||
* @return 成功解析出的元素个数(遇到非法 token 立即停止)
|
||||
*/
|
||||
uint8_t parseFloatList(const String& s, float* out, uint8_t maxCount) {
|
||||
uint8_t count = 0;
|
||||
int start = 0;
|
||||
int len = s.length();
|
||||
|
||||
while (start < len && count < maxCount) {
|
||||
int comma = s.indexOf(',', start);
|
||||
String token;
|
||||
if (comma < 0) {
|
||||
token = s.substring(start);
|
||||
start = len;
|
||||
} else {
|
||||
token = s.substring(start, comma);
|
||||
start = comma + 1;
|
||||
}
|
||||
token.trim();
|
||||
if (token.length() == 0) continue;
|
||||
|
||||
if (!isNumeric(token)) break; // 非法 token -> 放弃后续
|
||||
out[count++] = token.toFloat();
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// ==================== 指令处理 ====================
|
||||
|
||||
void sendCurrentState() {
|
||||
String msg = "ESP32:";
|
||||
for (uint8_t i = 0; i < NUM_SERVOS; i++) {
|
||||
msg += " S";
|
||||
msg += String(i);
|
||||
msg += "=";
|
||||
msg += String(g_servos[i].targetRad, 4);
|
||||
}
|
||||
sendLine(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 支持指令:
|
||||
* RAD:r0,r1,...,r7 弧度值列表
|
||||
* DEG:d0,d1,...,d7 角度值列表(内部转弧度)
|
||||
* r0,r1,...,r7 纯数字按弧度处理
|
||||
* GET 查询当前 8 路角度
|
||||
* PING 心跳
|
||||
*/
|
||||
void processCommand(const String& raw) {
|
||||
String cmd = raw;
|
||||
cmd.trim();
|
||||
if (cmd.length() == 0) return;
|
||||
|
||||
if (cmd == "PING") { sendLine("ESP32: PONG"); return; }
|
||||
if (cmd == "GET") { sendCurrentState(); return; }
|
||||
|
||||
float values[NUM_SERVOS];
|
||||
uint8_t n = 0;
|
||||
bool inDeg = false;
|
||||
|
||||
if (cmd.startsWith("RAD:")) {
|
||||
n = parseFloatList(cmd.substring(4), values, NUM_SERVOS);
|
||||
} else if (cmd.startsWith("DEG:")) {
|
||||
n = parseFloatList(cmd.substring(4), values, NUM_SERVOS);
|
||||
inDeg = true;
|
||||
} else if (cmd.startsWith("SET:")) {
|
||||
n = parseFloatList(cmd.substring(4), values, NUM_SERVOS);
|
||||
} else {
|
||||
n = parseFloatList(cmd, values, NUM_SERVOS);
|
||||
}
|
||||
|
||||
if (n == 0) {
|
||||
sendLine("ESP32: 无法解析 -> " + cmd);
|
||||
return;
|
||||
}
|
||||
|
||||
// 写入目标角度
|
||||
for (uint8_t i = 0; i < n && i < NUM_SERVOS; i++) {
|
||||
float rad = inDeg ? (values[i] * PI / 180.0f) : values[i];
|
||||
if (rad < RAD_MIN) rad = RAD_MIN;
|
||||
if (rad > RAD_MAX) rad = RAD_MAX;
|
||||
g_servos[i].targetRad = rad;
|
||||
}
|
||||
|
||||
// 简单日志(不要频繁打印,避免影响时序)
|
||||
Serial.printf("SET %u servo(s)\n", n);
|
||||
|
||||
sendLine("ESP32: OK 设置 " + String(n) + " 个舵机");
|
||||
}
|
||||
|
||||
void handleClientCommand() {
|
||||
if (!client || !client.connected()) return;
|
||||
|
||||
uint16_t bytesRead = 0;
|
||||
while (client.available() && bytesRead < sizeof(commandBuffer)) {
|
||||
int incoming = client.read();
|
||||
if (incoming < 0) break;
|
||||
bytesRead++;
|
||||
|
||||
if (incoming == '\n') {
|
||||
if (commandOverflow) {
|
||||
sendLine("ESP32: 指令过长");
|
||||
} else {
|
||||
commandBuffer[commandLength] = '\0';
|
||||
processCommand(String(commandBuffer));
|
||||
}
|
||||
commandLength = 0;
|
||||
commandOverflow = false;
|
||||
} else if (!commandOverflow) {
|
||||
if (commandLength < sizeof(commandBuffer) - 1) {
|
||||
commandBuffer[commandLength++] = static_cast<char>(incoming);
|
||||
} else {
|
||||
commandOverflow = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void sendQueuedTelemetry() {
|
||||
if (!client || !client.connected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
TelemetryData data;
|
||||
if (telemetryQueue == nullptr ||
|
||||
xQueueReceive(telemetryQueue, &data, 0) != pdTRUE) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Frame: A5 5A, 3 angles (centidegrees), 6 raw int16 values, CRC16-CCITT.
|
||||
uint8_t packet[22] = {0xA5, 0x5A};
|
||||
writeInt16BE(packet, 2, encodeAngle(data.attitude.yaw));
|
||||
writeInt16BE(packet, 4, encodeAngle(data.attitude.pitch));
|
||||
writeInt16BE(packet, 6, encodeAngle(data.attitude.roll));
|
||||
writeInt16BE(packet, 8, data.raw.ax);
|
||||
writeInt16BE(packet, 10, data.raw.ay);
|
||||
writeInt16BE(packet, 12, data.raw.az);
|
||||
writeInt16BE(packet, 14, data.raw.gx);
|
||||
writeInt16BE(packet, 16, data.raw.gy);
|
||||
writeInt16BE(packet, 18, data.raw.gz);
|
||||
uint16_t crc = calculateCrc16(packet, 20);
|
||||
packet[20] = static_cast<uint8_t>(crc >> 8);
|
||||
packet[21] = static_cast<uint8_t>(crc);
|
||||
|
||||
if (client.write(packet, sizeof(packet)) != sizeof(packet)) {
|
||||
Serial.println("警告:IMU 二进制数据发送不完整");
|
||||
}
|
||||
}
|
||||
|
||||
int16_t encodeAngle(float degrees) {
|
||||
long scaled = lroundf(degrees * 100.0f);
|
||||
if (scaled > 32767L) scaled = 32767L;
|
||||
if (scaled < -32768L) scaled = -32768L;
|
||||
return static_cast<int16_t>(scaled);
|
||||
}
|
||||
|
||||
void writeInt16BE(uint8_t* buffer, size_t offset, int16_t value) {
|
||||
uint16_t encoded = static_cast<uint16_t>(value);
|
||||
buffer[offset] = static_cast<uint8_t>(encoded >> 8);
|
||||
buffer[offset + 1] = static_cast<uint8_t>(encoded);
|
||||
}
|
||||
|
||||
uint16_t calculateCrc16(const uint8_t* data, size_t length) {
|
||||
uint16_t crc = 0xFFFF;
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
crc ^= static_cast<uint16_t>(data[i]) << 8;
|
||||
for (uint8_t bit = 0; bit < 8; bit++) {
|
||||
crc = (crc & 0x8000)
|
||||
? static_cast<uint16_t>((crc << 1) ^ 0x1021)
|
||||
: static_cast<uint16_t>(crc << 1);
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
void sg90Task(void* parameter) {
|
||||
(void)parameter;
|
||||
for (;;) {
|
||||
sg90();
|
||||
sendQueuedTelemetry();
|
||||
vTaskDelay(1);
|
||||
}
|
||||
}
|
||||
void sendLineData(YPR a, RawData r) {
|
||||
if (telemetryQueue == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
TelemetryData data = {a, r};
|
||||
xQueueOverwrite(telemetryQueue, &data);
|
||||
}
|
||||
|
||||
// ==================== Arduino 主入口 ====================
|
||||
|
||||
void wifi_setup() {
|
||||
Serial.begin(115200);
|
||||
servoInit();
|
||||
initWiFiAP();
|
||||
initTcpServer();
|
||||
|
||||
telemetryQueue = xQueueCreate(1, sizeof(TelemetryData));
|
||||
if (telemetryQueue == nullptr) {
|
||||
Serial.println("错误:无法创建 IMU 遥测队列");
|
||||
return;
|
||||
}
|
||||
|
||||
BaseType_t taskCreated = xTaskCreatePinnedToCore(
|
||||
sg90Task, "sg90", 4096, nullptr, 2, nullptr, 1);
|
||||
if (taskCreated != pdPASS) {
|
||||
Serial.println("错误:无法创建 SG90 通讯任务");
|
||||
vQueueDelete(telemetryQueue);
|
||||
telemetryQueue = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void sg90() {
|
||||
acceptClientIfNeeded(); // 1) 接入客户端
|
||||
handleClientCommand(); // 2) 解析上位机指令
|
||||
servoUpdateAll(); // 3) 非阻塞更新 8 路 PWM
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
This directory is intended for PlatformIO Test Runner and project tests.
|
||||
|
||||
Unit Testing is a software testing method by which individual units of
|
||||
source code, sets of one or more MCU program modules together with associated
|
||||
control data, usage procedures, and operating procedures, are tested to
|
||||
determine whether they are fit for use. Unit testing finds problems early
|
||||
in the development cycle.
|
||||
|
||||
More information about PlatformIO Unit Testing:
|
||||
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html
|
||||
Loading…
Reference in New Issue