2404 字
12 分钟
Re:从零开始的基于ESP32开发步进云台解码版

封面摄于2025春节在台州时

本文约2600字,撰写用时120分钟。

特别鸣谢:BI6OPR 提供的思路与原始程序

本文遵循CC BY-NC-SA 4.0协议,保留著名权。

正文开始!

前言#

笔者在筹建自己的射电天文望远镜,抛物面准备好了,但是发现旋转器真的贵得离谱。比如Yaesu的G5500,一个就要到大几千,实在破不起这费。在圈内一些大佬的指引下,发现了一款解码板被拆了的重载步进云台,了解到BI6OPR大佬已经初步将其适配了Pelco-D协议,但是还不是很完善。笔者对编程略知一二,按照大佬和网上的资料摸爬滚打,终于在BI6OPR提供的原始程序的基础上做出了比较满意的Pelco-D解码。著此文章来分享一些我的拙见,希望可以给各位朋友提供一些这方面的思路。

硬件方面#

笔者考虑到自己的技术能力,最终放弃STM32而是选择了更加适合于物联网IoT的ESP32系列芯片。基本思路是通过ESP32芯片的IO引脚对云台进行方向控制以及PWM调速,使用ESP32芯片的TTL串口于电脑建立通讯,接收转向命令,并发送转向状态给电脑端。步进电机驱动器使用了24V交流大功率驱动板。

注:本文章主要分享固件代码部分,关于硬件部分请移步至Github。

代码实现#

由于使用了ESP32系列芯片,在MicroPython和C++中选择了C++进行开发~~(当然不是因为新版Pycharm不兼容MicroPython而抛弃的它)~~。IDE采用了我们亲爱的VS Code,搭配PlatformIO,开发起来还是比较舒服的。

我们现在代码开头引入Arduino库,以便后续调用:

#include <Arduino.h>

ESP32是单片机,依靠循环来完成相应代码任务,即为循环loop()函数中的代码。基本思路是不断循环读取串口的数据,若读到相应命令,则进行转向控制后继续读取串口数据,若串口发来停止转动指令,或新的指令,则执行新的指令。

首先,让我们在setup()函数中对单片机启动时进行初始化。

配置波特率为9600串口:#

Serial.begin(9600);

初始化板载LED指示灯以及对于控制步进电机控制板的相关IO引脚:#

// 设置IO输入输出

const int AZ_DIRECTION_PIN = 26;

const int EL_DIRECTION_PIN = 27;

pinMode(LED_MSG_PIN, OUTPUT);

pinMode(AZ_DIRECTION_PIN, OUTPUT);

pinMode(EL_DIRECTION_PIN, OUTPUT);

pinMode(AZ_SPEED_PUL_PIN, OUTPUT);

pinMode(EL_SPEED_PUL_PIN, OUTPUT);

// 初始化信号灯

digitalWrite(LED_MSG_PIN, LOW);

初始化用于控制云台转速的硬件级PWM输出端口:#

const int PWM_FREQ = 10000; // PWM 频率(单位:Hz)

const int PWM_RESOLUTION = 8; // 分辨率(8 位时,占空比范围 0-255)

const int AZ_SPEED_PUL_PIN = 25;

const int EL_SPEED_PUL_PIN = 14;

ledcSetup(0 , PWM_FREQ, 8);

ledcAttachPin(AZ_SPEED_PUL_PIN, 0);

ledcSetup(1 , PWM_FREQ, 8);

ledcAttachPin(EL_SPEED_PUL_PIN, 1);


数据的读取:#

在setup()初始化完一切需要用到的东西后,我们就可以进行对串口发来的Pelco-D命令进行接收。

我们定义一个新的函数,名为readSerialData(),用于读取串口数据。

参阅Pelco-D的开发手册,注意到标准Pelco-D消息由7个字节的16进制数据组成,其中要用到的是两个数据位以及最后一个校验位。

我们先用代码读取7个字节的数据:

int rlen = Serial.readBytes(buf, 7);

然后利用校验位校验数据是否正确,若正确再做下一步拆解处理:

if (rlen == 7 && buf[0] == 0xFF && buf[1] == 0x01)

为了同一个数据被进行多次解析执行,还要添加判断数据是否于上一次读取到的不同,若不同,再执行。如果这个判读不做会出现转动一卡一卡的问题:

if (command != currentCommand) // 只有在接收到新的命令时才处理

{

handlePelcoDCommand(command);

currentCommand = command; // 更新当前命令

}

至此数据的读取与拆解完毕。


数据的执行:#

我们定义一个函数handlePelcoDCommand(int command)用于解析读取的数据并进行对步进电机驱动板的控制。

其中传入的int类型command变量是上文中分解到的最终控制命令,Pelco-D协议的基本转向命令分为停止、上、下、左、右,分别对应数据0、2、4、8、16,当然还有左上、右下之类的,这里不过多赘述。

我们先创建一个索引(其实官方名称我不确定是不是这个,叫习惯了switch (command),将所有命令放入这个索引中。

接下来我们以右转为例,首先右转对应的命令位2,我们在索引中写入:

case 2:

这代表当传入的command变量为2时执行接下来的代码块。

然后我们将控制水平的Az驱动电机的引脚设置为高电平:

digitalWrite(AZ_DIRECTION_PIN, HIGH);

并设置PWM调速命令执行的bool值为True:

is_azcontrol_stepper = true;

只有这样电机才会知道要按照什么速度转动,以上二者缺一不可。

这个bool变量在下一章的PWM调速中会提及。

于是控制右转的完整代码块为:

case 2: // 右转

digitalWrite(AZ_DIRECTION_PIN, HIGH);

is_azcontrol_stepper = true;

Serial.println("0002.");

其中 Serial.println("0002.");为向串口输出调试数据,用于程序的调试,可忽略不写。


loop()函数以及PWM调速:#

我们在上文提及,ESP32系列芯片是在循环某个代码块以达到相应目的。

我们想让上面的函数执行起来,必须将他们加入至loop()函数中**(setup()函数除外)**,否则他们不会被执行。

我们在主程序中创建loop()函数:

void loop()

{

....这里放你的代码或函数....

}

首先将我们上文写到的读取串口数据函数丢进去:

readSerialData();

然后要判断上文的PWM调速的bool变量是否为True,如果为True,则启动PWM调速(这里可以另开一个函数,我懒就直接丢loop()里了):

if (is_azcontrol_stepper)

{

ledcWrite(0, 128);

}

if (is_azcontrol_stepper == false)

{

ledcWrite(0, 0);

}

垂直方向的El电机同理。

解释一下ledcWrite(int a, int b);这条调用。其中int a代表了PWM输出通道,在前文setup()函数中我们将0通道指向了25号IO引脚,所以他就会在25号引脚输出方波PWM信号;int b代表了输出信号的频率,通过调整这个变量可以调控相应电机的转动速度,0则为停止。

最后在loop()里放入通过串口发送当前运行状态的函数:

printStatus();

这个函数下一章会详细阐述。

为了让这个函数不阻塞串口以及程序,我们对他加上一个定时器,定时执行:

if (currentMillis - previousMillis >= interval)

{

previousMillis = currentMillis;

printStatus();

}


状态回传函数:#

为了在电脑端可以顺利了解云台运动状态,我们可以通过串口回传云台的相关状态。

我们定义一个printStatus();函数,用于存放状态回传的代码块。

其中代码就是将目前状态通过串口打印,不过多赘述:

Serial.print("Current Status - AZ Direction: ");

Serial.print(az_stepper_direction ? "Forward" : "Backward");

Serial.print(", EL Direction: ");

Serial.print(el_stepper_direction ? "Forward" : "Backward");

Serial.print(", AZ Control: ");

Serial.print(is_azcontrol_stepper ? "On" : "Off");

Serial.print(", EL Control: ");

Serial.println(is_elcontrol_stepper ? "On" : "Off");

具体包含了当前云台是否转动以及转向方向。


更进一步:WIFI控制#

为了适配DTrac等安卓APP,也为了更方便的控制,我们可以将从串口读取数据改为从网络通过TCP协议读取数据。由于ESP32模块自带WiFi,故不需要加装其他硬件便可实现。

因为要调用WiFi模块,我们需要在代码开头导入WIFI库,以便后续调用:

#include <WiFi.h>

接下来要配置WiFi接入点信息,在代码头部配置,不要放入函数中:

const char* ssid = "你的WiFi名";

const char* password = "你的WiFi密码";

对于TCP协议,需要定义一个端口来通讯。我们使用80端口,在代码头部配置:

const int serverPort = 80;

接下来设置TCP模块为服务器模式,在代码头部配置:

WiFiServer server(serverPort);

WiFiClient client;

接下来要在setup()函数中初始化WiFi并进行连接操作:

WiFi.begin(ssid, password);

while (WiFi.status() != WL_CONNECTED) {

delay(1000);

Serial.println("Connecting to WiFi...");

}

Serial.println("Connected to WiFi");

Serial.print("IP address: ");

Serial.println(WiFi.localIP());

其中 Serial.println(WiFi.localIP());语句会在串口输出ESP32开发板的IP地址,也可以前往路由器后台获取。

接下来还是在setup()函数中,启动TCP服务器:

server.begin();

Serial.println("TCP Server started");

由于我们已经不用串口读取数据,而是改为WiFi读取,所以要将原来的readSerialData()函数改为readWifiData()函数,函数内代码如下,不过多赘述:

if (!client) { // 如果没有活跃的客户端

client = server.available(); // 检查新连接

if (client) {

Serial.println("New client connected");

}

} else { // 处理已连接的客户端

if (client.connected() && client.available() >= 7) {

int rlen = client.readBytes(buf, 7);

if (rlen == 7 && buf[0] == 0xFF && buf[1] == 0x01 && buf[3] != 0x53 && buf[3] != 0x51) {

int command = buf[3];

if (command != currentCommand) {

handlePelcoDCommand(command);

currentCommand = command;

}

这样就能成功从网络读取到Pelco-D命令。

后记#

感谢各位的阅读,本文在我的个人博客中发布,并搬运至各大论坛。若有不妥之处,请及时联系。笔者技术力有限,若有错误,敬请谅解。若喜欢这个项目,麻烦Star~若有疑问,请前往Github创建issue。

最后,用一张可爱的CG来收尾吧~

1

(图片版权信息:来自《冬日树下的回忆》,MagicaLuv制作组版权所有。本人已购买其CG包,合法使用于本文,用途为装饰。)

Re:从零开始的基于ESP32开发步进云台解码版
https://bg5cvt.icu/posts/hikctrlprogram/
作者
Fat Panda
发布于
2025-04-05
许可协议
CC BY-NC-SA 4.0