当前位置: 首页 > ds >正文

wifi控制舵机

一:简介

通过ESP8266模块进行通信

一个ESP8266模块作为服务器:创建TCP服务器

一个ESP8266模块作为客户端:连接TCP服务器

通过客户端将数据传递给服务器,服务器进行解析,解析是否有客户端连接,解析传递的角度数据,再调用这个角度数据,进行控制舵机。

客户端就是通过AD转换读取电位器的数据,再进行一阶滤波,转换为角度,动态步长平滑控制算法,最后调整当前角度,将得到的数据以“@150.0#*”这种数据格式发送给服务器。

二:服务器

ESP8266 工作在AP 模式 + TCP 服务器模式,实现三大核心功能:

  1. ESP8266 服务器初始化:创建 WiFi 热点、启动 TCP 服务器(端口 8080),支持多客户端连接;
  2. 数据解析与舵机控制:通过串口中断解析客户端发送的@角度值#*格式数据,转换为浮点数后控制舵机转动;
  3. 状态显示与指令下发:OLED 显示客户端连接 / 断开状态、当前舵机角度;按键触发向指定客户端发送字符1(触发客户端上传数据)。

软件流程:

ESP8266TCP通信服务器模块:

ESP8266 通过 AT 指令配置工作模式。关键步骤:

  1. 发送AT\r\n测试模块是否正常(响应OK);
  2. 配置为 AP 模式(AT+CWMODE=2),让客户端直接连接 ESP8266 的热点;
  3. 设置 AP 热点参数(名称ESP8266、密码12345678、信道 5、加密方式 WPA2-PSK);
  4. 启用多连接模式(AT+CIPMUX=1),支持最多 4 个客户端连接;
  5. 启动 TCP 服务器(AT+CIPSERVER=1,8080),端口 8080。
#define BUFFER_SIZE 100
char receiveBuffer[BUFFER_SIZE];  // 串口接收缓冲区
uint8_t receiveIndex = 0, copyreceive = 0;/*** 发送AT指令并等待指定响应*/
void ESP8266_SendAT(char *p, char *q) {USART_SendString(USART1, p);  // 发送AT指令uint32_t timeout = 0;          // 超时计数器(单位:10us)while (1) {// 条件1:收到期望响应(取缓冲区最后3个字符匹配,避免前导字符干扰)// 条件2:超时(300000 * 10us = 3s,超过则视为配置失败)if ((receiveBuffer[copyreceive - 3] == *q && receiveBuffer[copyreceive - 2] == *(q + 1)) || timeout > 300000) {break;}timeout++;Delay_us(10);  // 微秒级延时,保证响应检测精度}memset(receiveBuffer, 0, BUFFER_SIZE);  // 清空缓冲区,避免下次解析干扰
}/*** ESP8266 TCP服务器初始化(完整流程)*/
void ESP8266_Init() {ESP8266_SendAT("AT\r\n", "OK");          // 1. 测试模块通信ESP8266_SendAT("AT+CWMODE=2\r\n", "OK"); // 2. 配置为AP模式// 3. 配置AP热点(名称:ESP8266,密码:12345678,信道5,加密3=WPA2-PSK)ESP8266_SendAT("AT+CWSAP=\"ESP8266\",\"12345678\",5,3\r\n", "OK");ESP8266_SendAT("AT+CIPMUX=1\r\n", "OK"); // 4. 启用多连接模式ESP8266_SendAT("AT+CIPSERVER=1,8080\r\n", "OK"); // 5. 启动TCP服务器(端口8080)
}

串口中断与角度数据解析模块:

客户端发送的角度数据格式为@xx.x#*(如@90.5#*),需解决两个核心问题:

  1. 数据帧边界识别:区分有效数据(@开头、*结尾)与无效数据(如 ESP8266 的连接状态提示);
  2. 容错处理:防止数据溢出(如角度值过长)、格式错误(如缺少#)导致解析失败。

通过枚举定义 4 个状态,按 “起始符→数值→分隔符→结束符” 的顺序解析数据,流程如下:

  1. STATE_WAIT_AT:等待起始符@,收到后清空角度缓存,切换到接收数值状态;
  2. STATE_RECEIVE_VALUE:接收数字(0-9)或小数点(.),遇到#则切换到等待结束符状态;
  3. STATE_WAIT_STAR:等待结束符*,收到后保存角度值,触发 OLED 显示与舵机控制;
  4. 任何状态下遇到无效字符,立即重置为STATE_WAIT_AT。
// 角度解析状态枚举
typedef enum {STATE_WAIT_AT,         // 等待起始符'@'STATE_RECEIVE_VALUE,   // 接收角度值(数字/小数点)STATE_WAIT_STAR        // 等待结束符'*'
} Angle_Receive_State;Angle_Receive_State angle_recv_state = STATE_WAIT_AT;  // 初始状态
char angle_buf[10] = {0};                              // 角度缓存(最多存9个字符,避免溢出)
uint8_t angle_idx = 0;                                  // 角度缓存索引
char latest_angle[20] = {0};  // 存储解析后的完整角度字符串(如"103.3")
char show = 0;                // 显示触发标志(1=连接状态,2=角度数据)/*** USART1中断服务函数:解析角度数据+处理连接状态*/
void USART1_IRQHandler(void) {char receivedChar = 0;if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {receivedChar = USART_ReceiveData(USART1);  // 读取接收的1个字符USART_ClearITPendingBit(USART1, USART_IT_RXNE);  // 清除中断标志// 核心:角度数据解析状态机switch (angle_recv_state) {case STATE_WAIT_AT:// 收到'@',开始接收角度值if (receivedChar == '@') {angle_recv_state = STATE_RECEIVE_VALUE;memset(angle_buf, 0, sizeof(angle_buf));  // 清空缓存angle_idx = 0;}break;case STATE_RECEIVE_VALUE:// 接收有效字符(数字或小数点)if ((receivedChar >= '0' && receivedChar <= '9') || receivedChar == '.') {if (angle_idx < sizeof(angle_buf) - 1) {  // 预留1个字节存'\0',防止溢出angle_buf[angle_idx++] = receivedChar;} else {// 缓存溢出,重置状态angle_recv_state = STATE_WAIT_AT;memset(angle_buf, 0, sizeof(angle_buf));angle_idx = 0;}}// 收到'#',说明数值部分结束,等待'*'else if (receivedChar == '#') {angle_recv_state = STATE_WAIT_STAR;}// 收到无效字符,重置else {angle_recv_state = STATE_WAIT_AT;memset(angle_buf, 0, sizeof(angle_buf));angle_idx = 0;}break;case STATE_WAIT_STAR:// 收到'*',解析完成if (receivedChar == '*') {strcpy(latest_angle, angle_buf);  // 保存角度值show = 2;  // 触发OLED显示角度+舵机控制}// 无论是否收到'*',都重置为等待下一个帧angle_recv_state = STATE_WAIT_AT;break;default:angle_recv_state = STATE_WAIT_AT;break;}// 处理客户端连接/断开状态(仅在等待'@'时处理,避免与角度解析冲突)if (angle_recv_state == STATE_WAIT_AT) {// 存储非换行符的字符(ESP8266的状态提示以'\n'结尾)if (receivedChar != '\n' && receiveIndex < BUFFER_SIZE - 1) {receiveBuffer[receiveIndex++] = receivedChar;} else {receiveBuffer[receiveIndex] = '\0';  // 字符串结尾加'\0'copyreceive = receiveIndex;receiveIndex = 0;// 检测连接(CONNECT)或断开(DISCONNECT)指令if (strstr(receiveBuffer, "CONNECT") != NULL || strstr(receiveBuffer, "DISCONNECT") != NULL) {show = 1;  // 触发OLED显示连接状态}}}}
}

完整代码:

#include "stm32f10x.h"
#include "OLED.h"
#include "Delay.h"
#include "usart.h"
#include "string.h"
#include "EXTI_KEY.h"
#include "stdio.h"
#include "servo.h"
#include "stdlib.h"#define BUFFER_SIZE 100  // 缓冲区大小
char receiveBuffer[BUFFER_SIZE];  // 原始数据缓冲区
uint8_t receiveIndex = 0, copyreceive = 0;  // 接收索引
char receivedChar = 0;char server_ok = 0, show = 0;
char kehu = 0, clear = 0;
static uint8_t last_state = 0xFF;
char latest_angle[20] = {0};  // 存储完整角度数据(如"103.3")// -------------------------- 角度数据接收状态与缓存 --------------------------
typedef enum {STATE_WAIT_AT,         // 等待起始符'@'STATE_RECEIVE_VALUE,   // 接收角度值(数字和小数点)STATE_WAIT_HASH,       // 等待分隔符'#'STATE_WAIT_STAR        // 等待结束符'*'
} Angle_Receive_State;Angle_Receive_State angle_recv_state = STATE_WAIT_AT;  // 初始状态
char angle_buf[10] = {0};                              // 缓存角度数值
uint8_t angle_idx = 0;                                  // 角度缓存索引
// -----------------------------------------------------------------------------/*** 发送AT指令并等待响应(增加超时机制,防止死等)*/
void ESP8266_SendAT(char *p, char *q) {USART_SendString(USART1, p);uint32_t timeout = 0;while (1) {// 等待响应或超时(约3秒)if ((receiveBuffer[copyreceive - 3] == *q && receiveBuffer[copyreceive - 2] == *(q + 1)) || timeout > 300000) {break;}timeout++;Delay_us(10);}memset(receiveBuffer, 0, BUFFER_SIZE);  // 清空缓冲区(用0而非32,避免乱码)
}/*** 初始化ESP8266为TCP服务器*/
void ESP8266_Init() {ESP8266_SendAT("AT\r\n", "OK");          // 测试模块ESP8266_SendAT("AT+CWMODE=2\r\n", "OK"); // AP模式ESP8266_SendAT("AT+CWSAP=\"ESP8266\",\"12345678\",5,3\r\n", "OK"); // AP配置ESP8266_SendAT("AT+CIPMUX=1\r\n", "OK"); // 多连接模式ESP8266_SendAT("AT+CIPSERVER=1,8080\r\n", "OK"); // 启动服务器
}int main() {char *customer;float target_angle = 0.0f;// 硬件初始化EXTI_PE_Configuration();OLED_Init();USART1_Config();Servo_Init();ESP8266_Init();OLED_Clear();OLED_ShowString(1, 1, "Server Ready");while (1) {// 模式1:显示客户端连接状态if (show == 1) {OLED_ShowString(1, 1, "Wait Connect");customer = receiveBuffer + 2;  // 跳过前缀if (strncmp(customer, "CONNECT", 7) == 0) {uint8_t current_customer = *(customer - 2) - '0';if (last_state != current_customer) {OLED_Clear();OLED_ShowString(1, 1, "Connected");switch (current_customer) {case 0: OLED_ShowString(2, 1, "Client 0"); break;case 1: OLED_ShowString(2, 1, "Client 1"); break;case 2: OLED_ShowString(2, 1, "Client 2"); break;case 3: OLED_ShowString(2, 1, "Client 3"); break;default: OLED_ShowString(2, 1, "Unknown"); break;}OLED_ShowString(3, 1, "Status: On");}show = 0;} else if (strncmp(customer, "DISCONNECT", 10) == 0) {OLED_Clear();OLED_ShowString(1, 1, "Disconnected");OLED_ShowString(2, 1, "No Client");OLED_ShowString(3, 1, "Status: Off");last_state = 0xFF;show = 0;} else {OLED_ShowString(1, 1, "Unknown Resp");OLED_ShowString(2, 1, receiveBuffer);show = 0;}}// 模式2:显示并处理角度数据if (show == 2) {OLED_Clear();OLED_ShowString(1, 1, "Angle Data");// 显示当前角度并控制舵机OLED_ShowString(2, 1, "Current: ");OLED_ShowString(2, 9, latest_angle);target_angle = atof(latest_angle);  // 字符串转浮点数if (target_angle >= 0 && target_angle <= 180) {Servo_SetAngle(target_angle);  // 控制舵机OLED_ShowString(3, 1, "Servo OK");} else {OLED_ShowString(3, 1, "Angle Err");}show = 0;}}
}/*** 串口1中断服务函数:解析@xx.x#*格式数据*/
void USART1_IRQHandler(void) {if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {receivedChar = USART_ReceiveData(USART1);USART_ClearITPendingBit(USART1, USART_IT_RXNE);// 角度数据解析状态机(核心逻辑)switch (angle_recv_state) {case STATE_WAIT_AT:// 等待起始符'@',收到后切换状态if (receivedChar == '@') {angle_recv_state = STATE_RECEIVE_VALUE;memset(angle_buf, 0, sizeof(angle_buf));  // 清空角度缓存angle_idx = 0;}break;case STATE_RECEIVE_VALUE:// 接收角度值(数字0-9或小数点.)if ((receivedChar >= '0' && receivedChar <= '9') || receivedChar == '.') {if (angle_idx < sizeof(angle_buf) - 1) {  // 防止溢出angle_buf[angle_idx++] = receivedChar;} else {// 缓存溢出,重置状态angle_recv_state = STATE_WAIT_AT;memset(angle_buf, 0, sizeof(angle_buf));angle_idx = 0;}}// 收到'#'说明数值部分结束,切换状态else if (receivedChar == '#') {angle_recv_state = STATE_WAIT_STAR;}// 收到其他字符,视为无效数据else {angle_recv_state = STATE_WAIT_AT;memset(angle_buf, 0, sizeof(angle_buf));angle_idx = 0;}break;case STATE_WAIT_HASH:// 理论上不会进入此状态,容错处理angle_recv_state = STATE_WAIT_AT;break;case STATE_WAIT_STAR:// 收到'*'说明数据结束,处理完整角度值if (receivedChar == '*') {strcpy(latest_angle, angle_buf);  // 保存角度值show = 2;  // 触发显示和控制}// 无论是否收到'*',都重置为等待下一个'@'angle_recv_state = STATE_WAIT_AT;break;default:angle_recv_state = STATE_WAIT_AT;break;}// 处理客户端连接状态(仅在等待'@'时处理,避免冲突)if (angle_recv_state == STATE_WAIT_AT) {if (receivedChar != '\n' && receiveIndex < BUFFER_SIZE - 1) {receiveBuffer[receiveIndex++] = receivedChar;} else {receiveBuffer[receiveIndex] = '\0';copyreceive = receiveIndex;receiveIndex = 0;// 检测连接状态指令if (strstr(receiveBuffer, "CONNECT") != NULL || strstr(receiveBuffer, "DISCONNECT") != NULL) {show = 1;}}}}
}/*** 按键中断:向客户端发送数据*/
void EXTI0_IRQHandler(void) {if (EXTI_GetITStatus(EXTI_Line0) != RESET) {USART_SendString(USART1, "AT+CIPSEND=0,1\r\n");Delay_ms(100);USART_SendData(USART1, '1');Delay_ms(100);EXTI_ClearITPendingBit(EXTI_Line0);}
}

三:客户端

软件流程:

一阶滤波:

AD 模块采集的原始数据会受电路噪声、电源波动影响,导致舵机角度频繁抖动,因此需要滤波处理,同时保证数据响应速度(避免滤波后延迟过大)。

// 一阶滤波函数:平衡响应速度与滤波效果
uint16_t AD_Filter(uint16_t rawValue)
{static uint16_t filteredAD = 0;  // 静态变量:保存上一次滤波结果float a = 0.6f;  // 滤波系数(0<a<1,a越大响应越快,滤波效果越差)// 滤波公式:当前滤波值 = a*原始值 + (1-a)*上一次滤波值filteredAD = (uint16_t)(a * rawValue + (1 - a) * filteredAD);return filteredAD;
}

选用0.6,就是为了响应迅速

动态步长平滑控制算法:

// 全局变量定义
float TargetAngle;    // 目标角度(AD值转换后)
float CurrentAngle;   // 当前舵机角度
float AngleStep;      // 动态步长
float MinStep = 0.5f; // 最小步长(角度差<10°时使用,避免抖动)
float MaxStep = 10.0f;// 最大步长(角度差>90°时使用,加快响应)// 动态步长计算与角度更新
float diff = TargetAngle - CurrentAngle;
float angleDiff = fabs(diff);  // 角度差(取绝对值)// 1. 根据角度差确定步长
if (angleDiff < 10.0f) 
{AngleStep = MinStep;  // 近距离:小步慢走,避免抖动
} 
else if (angleDiff > 90.0f) 
{AngleStep = MaxStep;  // 远距离:大步快走,加快响应
} 
else 
{// 中间距离:线性插值(步长随角度差递增)AngleStep = MinStep + (MaxStep - MinStep) * (angleDiff - 10.0f) / 80.0f;
}// 2. 更新当前角度(逼近目标角度)
if (TargetAngle > CurrentAngle + AngleStep)
{CurrentAngle += AngleStep;
}
else if (TargetAngle < CurrentAngle - AngleStep)
{CurrentAngle -= AngleStep;
}
else
{CurrentAngle = TargetAngle;  // 误差小于步长时,直接对齐目标
}

当电位器缓慢转动(角度差 < 10°):舵机以 0.5°/ 次的步长转动,无明显抖动;

当电位器快速转动(角度差 > 90°):舵机以 10°/ 次的步长转动,1 秒内可从 0° 转到 180°;

当角度差在 10°-90° 之间:步长线性递增,兼顾速度与平滑度。

 ESP8266 TCP 通信模块:

ESP8266 工作在透传模式:MCU 通过 USART1 向 ESP8266 发送数据,ESP8266 直接转发到 TCP 服务器;反之,服务器发送的指令也通过 ESP8266 透传给 MCU。

数据协议:

MCU 发送角度数据:格式为@角度值#*(如@90.0#*表示当前角度 90.0°),便于服务器解析;

服务器发送指令:仅需发送字符1,MCU 收到后触发角度发送(避免频繁发送浪费带宽)。

// 1. 发送角度数据到服务器(按需发送,避免冗余)
void ESP_SendData(float current)
{char sendBuf[100];static float LastSentAngle = -1.0f;  // 上一次发送的角度(初始为无效值)// 仅当角度变化≥0.5°或首次发送时,才发送数据(减少带宽占用)if (fabs(current - LastSentAngle) >= 0.5f || LastSentAngle < 0){sprintf(sendBuf, "@%.1f#*", current);  // 格式化数据USART_SendString(USART1, sendBuf);    // 发送到ESP8266LastSentAngle = current;              // 更新上一次发送的角度}
}// 2. 接收服务器指令(需在USART1中断服务函数中实现)
void USART1_IRQHandler(void)
{static char recvBuf[10];static uint8_t recvLen = 0;if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET){char data = USART_ReceiveData(USART1);  // 读取接收数据// 假设指令为单个字符(如'1'),可根据实际需求扩展if (data == '1'){instruction = '1';  // 标记指令:触发角度发送show = 1;           // 标记OLED需要刷新指令}USART_ClearITPendingBit(USART1, USART_IT_RXNE);  // 清除中断标志}
}

完整代码:

#include "ESPClient.h"
#include "ad.h"
#include <stdio.h>
#include <math.h>uint16_t ADvalue;
uint16_t ADValueFiltered;	// 滤波后的AD值
float LastSentAngle = -1.0f; //无效的值
float TargetAngle;			// 目标角度(由AD值计算得出)
float CurrentAngle;			// 当前舵机角度(用于平滑过渡)
float AngleStep ;		// 角度变化动态步长(控制转动平滑度)
float MinStep = 0.5f;		// 最小步长(小幅调节时使用)
float MaxStep = 10.0f;		// 最大步长(大幅调节时使用)/*
ready 0 ESP8266Init没有初始化时为0,用来接收ok。
ready 1 ESP8266Init初始化完成后为1,用来接收服务器的数据。
show  0 无新指令需显示 / 已完成显示
show  1 当收到服务器的数据时,show变为1,进行刷新数据
*/
char i=0,ready=0,show=0;
uint16_t instruction=0;//接收服务器发送的指令//一阶滤波
uint16_t AD_Filter(uint16_t rawValue)
{static uint16_t filteredAD=0;float  a=0.6;//a=0.6 响应较快,同时能过滤大部分高频噪声,平衡效果好。//α×当前值 + (1-α)×上次结果filteredAD=(uint16_t)(a*rawValue+(1-a)*filteredAD);return filteredAD;
}void ESP_SendData(float current)
{char sendBuf[100];  // 数据缓冲区(足够存储各类数据)//大于0.5°,或者错误时,进行发送if (fabs(current - LastSentAngle) >= 0.5f || LastSentAngle < 0){sprintf(sendBuf, "@%.1f#*", current); LastSentAngle = current; // 新增:更新上一次发送的角度// 通过USART1发送到ESP8266,由ESP8266透传给客户端USART_SendString(USART1, sendBuf);}}int main()
{OLED_Init();USART1_Config();ESP8266_Init();ready=1;AD_Init();OLED_Clear();OLED_ShowString(1,1,"wait server");OLED_ShowString(2,1,"Angle:");OLED_ShowString(3,1,"AD:");while(1){ADvalue=AD_GetValue();ADValueFiltered=AD_Filter(ADvalue);//目标角度TargetAngle = (float)ADValueFiltered / 4095 * 180;//动态步长平滑控制算法float diff = TargetAngle - CurrentAngle;float angleDiff = diff > 0 ? diff : -diff;if (angleDiff < 10.0f) {AngleStep = MinStep;} else if (angleDiff > 90.0f) {AngleStep = MaxStep;} else {AngleStep = MinStep + (MaxStep - MinStep) * (angleDiff - 10.0f) / 80.0f;}//调整当前角度if(TargetAngle>CurrentAngle+AngleStep){CurrentAngle+=AngleStep;}else if(TargetAngle<CurrentAngle-AngleStep){CurrentAngle-=AngleStep;}else{CurrentAngle = TargetAngle;}//  实时显示AD值和角度(OLED部分优化)OLED_ShowNum(2, 7, (uint16_t)CurrentAngle, 3);  // 显示舵机角度OLED_ShowNum(3, 4, ADValueFiltered, 4);  // 显示滤波后的AD值//收到数据进行刷新if(show==1){OLED_ShowNum(4,1,instruction,2);show=0;}//客户端收到1,发送数据if(instruction=='1'){ESP_SendData(CurrentAngle);			}}}

http://www.xdnf.cn/news/19490.html

相关文章:

  • AI热点周报(8.24~8.30):Grok 2.5开源,OpenAI Realtime正式商用,Meta或与OpenAI或Google合作?
  • 从零开始的python学习——语句
  • python pyqt5开发DoIP上位机【自动化测试的逻辑是怎么实现的?】
  • lumerical_FDTD_光源_TFSF
  • 《中国棒垒球》垒球世界纪录多少米·垒球8号位
  • 第2.3节:AI大模型之Claude系列(Anthropic)
  • [特殊字符]️ STL 容器快速参考手册
  • LangChain实战(五):Document Loaders - 从多源加载数据
  • Python库2——Matplotlib2
  • JAVA EE初阶 4:文件操作和IO
  • PCIe 6.0 vs 5.0:带宽翻倍背后的技术革新与应用前景
  • 防护墙技术(一):NAT
  • 粒子群优化算法(PSO)
  • 从分子工具到技术革新:链霉亲和素 - 生物素系统与 M13 噬菌体展示的交叉应用解析
  • 项目管理方法适用场景对比
  • 每k个节点一组反转链表
  • 11 C 语言 sizeof 与指针实战指南:一维 / 二维数组计算注意事项 + 笔试真题解析 + sizeof strlen 对比
  • Python数据处理
  • MYSQL表结构优化场景
  • AI 赋能综合能源管理系统:开启智慧能源新时代
  • 深入理解 Rockchip 平台 DTS 中的 UART3 引脚配置
  • Web开发-JavaEE应用原生和FastJson反序列化URLDNS链JDBC链Gadget手搓
  • Photoshop - Ps 编辑图像
  • 深思熟虑智能体:基于 tencent youtu-agent 的五阶段投资研究系统
  • 第一个SpringBoot程序
  • 字数统计器和文本AI处理,非常好用
  • HBase高效并发锁:IdLock极简内存设计
  • 世界模型 World Models概述
  • 计算机算术8-浮点加法
  • uart学习