【XR806开发板试用】儿童遥控挖掘机无线化升级改造
一、项目简介
能够在XR806开发板试用活动中成功申请到该开发板,这为自己之前一直心心念的对儿子玩具升级计划提供了最核心的部件。
如下图所示,为老母亲为孙子在路边摊花了25个铜板购置的遥控玩具车,这款车是有线遥控的,线长大约有1.5米,小朋友在玩的时候需要跟随玩具车移动,玩的很不尽兴,虽然喜欢挖掘机,也只能放起来了。如果能够将其改造为无线遥控那么毕竟棒极了。既然XR806到手了,就让我们行动起来吧。
二、硬件方案
2.1 分析玩具挖掘机的结构
如下图我对挖掘机进行了拆解,可以看出有线挖掘机的内部单元很简单,有三个直流电机,其中两个电机用于提供车行动的动力,剩下的一个电机用于提供机械臂的升降。而且内部空间可以放下XR806,这就可以将开发板内置于汽车内增加美观性。
如下图所示,该款玩具底部还预留了电池仓和开关的位置,这真是棒极了。
2.2 分析玩具挖掘机的控制逻辑
在改造之前,我们还需要知道电机的控制逻辑,以便于我们选用合适的逻辑器件与XR806配套实现对应的功能。
如下图所示为有线遥控器的控制按键的定义。该款遥控器共提供三个按钮,实现前进、后退、左拐、右拐、起重臂升降等功能,我们只需要复现这几个功能即可。
结合2.1节的内部拆解,通过万用表即可探测出整套玩具的原理图如下图所示。S1开关对应前进后退按键;S2对应左拐右拐按键;S3对应起重臂升降功能。
2.3 无线遥控硬件逻辑
2.3.1 直流电机控制逻辑
参考2.2章节的分析,需要找到一款逻辑芯片或模组实现对三个电机的控制,因为挖掘机本身还支持前进后退功能,所以还需要支持电极的反转。经过淘宝的搜索,寻得如下板卡一块。
这块板卡的使用说明如下:
我们需要驱动3个电机,因此需要使用到6个GPIO,由XR806的管脚定义可知能够满足我们的改造需求。遥控端直接利用手机实现。
2.3.2 连线
按照如下示意图将XR806、直流电机驱动板和电机连接在一起。通过XR806的PIN[2:7]共6个管脚实现对电机的控制,管脚和直流电机的对应关系按图所示。
三、软件方案
实现无线遥控的目的需要一种无线信息媒介将遥控器(手机)和XR806连接起来,实现控制命令的下发。由XR806开发板的说明可知,目前有WIFI和BT两种方案,基于自身的知识储备,本次先选WIFI来实现,利用MQTT实现命令的下发和状态的接受。
3.1 MQTT topic的定义
定义如下topic和msg用于控制命令的发送,设备端(XR806)监听这些topic接受来自于遥控器的命令。
#topic
/digger/cmd
#msg
forward
backup
turnright
turnleft
bucketlift
stop
定义如下topic用于命令执行结果的返回,遥控器(手机)监听这些topic接受来自于设备端的执行结果
#topic
/digger/cmdret
#msg
success
fail
3.2 编码实现
3.2.1 环境搭建
参考【XR806开发板试用】XR806与鸿蒙,简化构建环境流程完成开发环境的搭建。
3.2.2 设备端编码
主程序如下
#include <stdio.h>
#include <string.h>
#include "ohos_init.h"
#include "kernel/os/os.h"
#include "iot_gpio.h"
#include "wifi_device.h"
#include "common/framework/net_ctrl.h"
#include "net/mqtt/MQTTClient-C/MQTTClient.h"
static OS_Thread_t g_main_thread;
static OS_Thread_t g_mqtt_thread;
#define WIFI_DEVICE_CONNECT_AP_SSID "ssid"
#define WIFI_DEVICE_CONNECT_AP_PSK "pwm"
#define MQTT_DEMO_CLIENT_ID "6688"
#define MQTT_DEMO_HOST_NAME "broker-cn.emqx.io"
#define MQTT_DEMO_PORT "1883"
#define MQTT_DEMO_USERNAME "dev"
#define MQTT_DEMO_PASSWORD "dev"
#define MQTT_SUB_TOPIC "/digger/cmd"
#define MQTT_PUB_TOPIC "/digger/cmdret"
#define MQTT_DEMO_BUF_SIZE (2*1024)
static MQTTPacket_connectData mqtt_demo_connectData = MQTTPacket_connectData_initializer;
static Client mqtt_demo_client;
static Network mqtt_demo_network;
static int max_duty_ratio = 0;
static int mqtt_demo_publish(char *topic, char *msg) ;
static int mqtt_demo_init(void) {
char *send_buf;
char *recv_buf;
mqtt_demo_connectData.clientID.cstring = MQTT_DEMO_CLIENT_ID;
mqtt_demo_connectData.keepAliveInterval = 30; // 30s
mqtt_demo_connectData.cleansession = 0;
mqtt_demo_connectData.MQTTVersion = 4; //Version of MQTT 3.1.1
send_buf = malloc(MQTT_DEMO_BUF_SIZE);
if (send_buf == NULL) {
printf("no memory\n");
return -1;
}
recv_buf = malloc(MQTT_DEMO_BUF_SIZE);
if (recv_buf == NULL) {
free(send_buf);
printf("no memory\n");
return -1;
}
/* init network */
NewNetwork(&mqtt_demo_network);
/* init mqtt client object */
MQTTClient(&mqtt_demo_client, &mqtt_demo_network, 6000,
(unsigned char *)send_buf, MQTT_DEMO_BUF_SIZE,
(unsigned char *)recv_buf, MQTT_DEMO_BUF_SIZE);
/* set username and password */
mqtt_demo_connectData.username.cstring = MQTT_DEMO_USERNAME;
mqtt_demo_connectData.password.cstring = MQTT_DEMO_PASSWORD;
return 0;
}
static int mqtt_demo_connect(char *host_name, char *host_port) {
int ret = -1;
ret = ConnectNetwork(&mqtt_demo_network, host_name, atoi(host_port));
if (ret != 0) {
printf("mqtt connect faild, ret:%d, host:%s, port:%s\n", ret, host_name, host_port);
goto exit;
}
ret = MQTTConnect(&mqtt_demo_client, &mqtt_demo_connectData);
if (ret != 0) {
printf("mqtt connect faild, ret:%d\n", ret);
mqtt_demo_network.disconnect(&mqtt_demo_network);
goto exit;
}
printf("mqtt connected\n");
exit:
return ret;
}
static void mqtt_demo_msg_cb(MessageData *data) {
printf("get a message, topic: %.*s, msg: %.*s\n", data->topicName->lenstring.len,
data->topicName->lenstring.data, data->message->payloadlen,
(char *)data->message->payload);
if(!strncmp(data->topicName->lenstring.data, "/digger/cmd", 11) && data->message->payloadlen) {
if(!strcmp(data->message->payload, "forward") {
IoTGpioSetOutputVal(GPIO_ID_PA23, 1);
IoTGpioSetOutputVal(GPIO_ID_PA22, 0);
IoTGpioSetOutputVal(GPIO_ID_PA21, 1);
IoTGpioSetOutputVal(GPIO_ID_PA20, 0);
} else if (!strcmp(data->message->payload, "backup") {
IoTGpioSetOutputVal(GPIO_ID_PA23, 0);
IoTGpioSetOutputVal(GPIO_ID_PA22, 1);
IoTGpioSetOutputVal(GPIO_ID_PA21, 0);
IoTGpioSetOutputVal(GPIO_ID_PA20, 1);
} else if (!strcmp(data->message->payload, "turnright") {
IoTGpioSetOutputVal(GPIO_ID_PA21, 1);
IoTGpioSetOutputVal(GPIO_ID_PA20, 0);
} else if (!strcmp(data->message->payload, "turnleft") {
IoTGpioSetOutputVal(GPIO_ID_PA23, 1);
IoTGpioSetOutputVal(GPIO_ID_PA22, 0);
} else if (!strcmp(data->message->payload, "bucketlift") {
IoTGpioSetOutputVal(GPIO_ID_PA19, 1);
IoTGpioSetOutputVal(GPIO_ID_PA13, 0);
} else {
IoTGpioSetOutputVal(GPIO_ID_PA23, 0);
IoTGpioSetOutputVal(GPIO_ID_PA22, 0);
IoTGpioSetOutputVal(GPIO_ID_PA21, 0);
IoTGpioSetOutputVal(GPIO_ID_PA20, 0);
IoTGpioSetOutputVal(GPIO_ID_PA19, 0);
IoTGpioSetOutputVal(GPIO_ID_PA13, 0);
}
mqtt_demo_publish(MQTT_PUB_TOPIC, "success");
}
}
static int mqtt_demo_subscribe(char *topic) {
int ret = -1;
if (mqtt_demo_client.isconnected) {
ret = MQTTSubscribe(&mqtt_demo_client, topic, 0, mqtt_demo_msg_cb);
if (ret != 0)
printf("mqtt subscribe faild ret:%d\n", ret);
}
return ret;
}
static int mqtt_demo_unsubscribe(char *topic) {
int ret = -1;
if (mqtt_demo_client.isconnected) {
ret = MQTTUnsubscribe(&mqtt_demo_client, topic);
if (ret != 0)
printf("mqtt unsubscribe faild, ret:%d\n", ret);
}
return ret;
}
static int mqtt_demo_publish(char *topic, char *msg) {
int ret = -1;
MQTTMessage message;
memset(&message, 0, sizeof(message));
message.qos = 0;
message.retained = 0; /* disable retain the message in server */
message.payload = msg;
message.payloadlen = strlen(msg);
ret = MQTTPublish(&mqtt_demo_client, topic, &message);
if (ret != 0)
printf("mqtt publish faild, ret:%d\n", ret);
return ret;
}
static int mqtt_demo_disconnect(void) {
int ret = -1;
if (mqtt_demo_client.isconnected) {
ret = MQTTDisconnect(&mqtt_demo_client);
if (ret != 0)
printf("mqtt disconnect fail, ret:%d\n", ret);
mqtt_demo_network.disconnect(&mqtt_demo_network);
}
return ret;
}
static void mqtt_demo_deinit(void) {
if (mqtt_demo_client.buf) {
free(mqtt_demo_client.buf);
mqtt_demo_client.buf = NULL;
}
if (mqtt_demo_client.readbuf) {
free(mqtt_demo_client.readbuf);
mqtt_demo_client.readbuf = NULL;
}
}
static void mqtt_task(void *arg) {
int ret;
int reconnect_times = 0;
mqtt_demo_init();
ret = mqtt_demo_connect(MQTT_DEMO_HOST_NAME, MQTT_DEMO_PORT);
if (ret != 0)
goto exit;
ret = mqtt_demo_subscribe(MQTT_RECV_TOPIC);
if (ret != 0)
goto exit;
mqtt_demo_publish(MQTT_PUB_TOPIC, "dev ready");
while (1) {
ret = MQTTYield(&mqtt_demo_client, 300);
if (ret != 0) {
printf("mqtt yield err, ret:%d\n", ret);
reconnect:
printf("mqtt reconnect\n");
mqtt_demo_disconnect();
ret = mqtt_demo_connect(MQTT_DEMO_HOST_NAME, MQTT_DEMO_PORT);
if (ret != 0) {
reconnect_times++;
if (reconnect_times > 5)
goto exit;
OS_MSleep(5000); //5s
goto reconnect;
}
}
}
exit:
mqtt_demo_unsubscribe(MQTT_RECV_TOPIC);
mqtt_demo_disconnect();
mqtt_demo_deinit();
OS_ThreadDelete(&g_mqtt_thread);
}
static void net_cb(uint32_t event, uint32_t data, void *arg) {
uint16_t type = EVENT_SUBTYPE(event);
switch (type) {
case NET_CTRL_MSG_NETWORK_UP:
printf("NET_CTRL_MSG_NETWORK_UP\n");
if (!OS_ThreadIsValid(&g_mqtt_thread)) {
OS_ThreadCreate(&g_mqtt_thread, "connect_to_server_task",
mqtt_task, (void *)NULL, OS_THREAD_PRIO_APP, (8 * 1024));
}
break;
case NET_CTRL_MSG_NETWORK_DOWN:
break;
default:
break;
}
}
static void MainThread(void *arg) {
printf("MainThread start\r\n");
HAL_Status status = HAL_ERROR;
/*Config GPIO*/
IoTGpioInit(GPIO_ID_PA23);
IoTGpioSetDir(GPIO_ID_PA23, IOT_GPIO_DIR_OUT);
IoTGpioSetOutputVal(GPIO_ID_PA23, 0);
IoTGpioInit(GPIO_ID_PA22);
IoTGpioSetDir(GPIO_ID_PA22, IOT_GPIO_DIR_OUT);
IoTGpioSetOutputVal(GPIO_ID_PA22, 0);
IoTGpioInit(GPIO_ID_PA21);
IoTGpioSetDir(GPIO_ID_PA21, IOT_GPIO_DIR_OUT);
IoTGpioSetOutputVal(GPIO_ID_PA21, 0);
IoTGpioInit(GPIO_ID_PA20);
IoTGpioSetDir(GPIO_ID_PA20, IOT_GPIO_DIR_OUT);
IoTGpioSetOutputVal(GPIO_ID_PA20, 0);
IoTGpioInit(GPIO_ID_PA19);
IoTGpioSetDir(GPIO_ID_PA19, IOT_GPIO_DIR_OUT);
IoTGpioSetOutputVal(GPIO_ID_PA19, 0);
IoTGpioInit(GPIO_ID_PA13);
IoTGpioSetDir(GPIO_ID_PA13, IOT_GPIO_DIR_OUT);
IoTGpioSetOutputVal(GPIO_ID_PA13, 0);
if (WIFI_SUCCESS != EnableWifi()) {
printf("Error: EnableWifi fail\n");
return;
}
OS_Sleep(1);
if (WIFI_SUCCESS != Scan()) {
printf("Error: Scan fail.\n");
return;
}
OS_Sleep(3);
const char ssid_want_connect[] = WIFI_DEVICE_CONNECT_AP_SSID;
const char psk[] = WIFI_DEVICE_CONNECT_AP_PSK;
WifiScanInfo scan_results[30];
unsigned int scan_num = 30;
if (WIFI_SUCCESS != GetScanInfoList(scan_results, &scan_num)) {
printf("Error: GetScanInfoList fail.\n");
return;
}
WifiDeviceConfig config = { 0 };
int netId = 0;
int i;
for (i = 0; i < scan_num; i++) {
printf("ssid: %s ", scan_results[i].ssid);
printf("securityType: %d\n", scan_results[i].securityType);
if (0 == strcmp(scan_results[i].ssid, ssid_want_connect)) {
memcpy(config.ssid, scan_results[i].ssid,
WIFI_MAX_SSID_LEN);
memcpy(config.bssid, scan_results[i].bssid,
WIFI_MAC_LEN);
strcpy(config.preSharedKey, psk);
config.securityType = scan_results[i].securityType;
config.wapiPskType = WIFI_PSK_TYPE_ASCII;
config.freq = scan_results[i].frequency;
break;
}
}
if (i >= scan_num) {
printf("Error: No found ssid in scan_results\n");
return;
}
if (WIFI_SUCCESS != AddDeviceConfig(&config, &netId)) {
printf("Error: AddDeviceConfig Fail\n");
return;
}
printf("Config Success\n");
if (WIFI_SUCCESS != ConnectTo(netId)) {
printf("Error: ConnectTo Fail\n");
return;
}
observer_base *net_ob;
net_ob = sys_callback_observer_create(CTRL_MSG_TYPE_NETWORK, NET_CTRL_MSG_ALL, net_cb, NULL);
if (net_ob == NULL)
return;
if (sys_ctrl_attach(net_ob) != 0)
return;
while (1) {
OS_MSleep(500);
}
}
void DiggerMain(void) {
if (OS_ThreadCreate(&g_main_thread, "MainThread", MainThread, NULL, OS_THREAD_PRIO_APP, 4 * 1024) != OS_OK) {
printf("[ERR] Create MainThread Failed\n");
}
}
SYS_RUN(DiggerMain);
这部分主要是参考了【XR806开发板试用】通过MQTT实现手机远程实现PWM控灯
3.2.3 遥控器(手机端)配置
手机端安装mqtt调试软件即可,后期可以考虑自行开发APP。在调试阶段利用PC端的mqtt工具也可以,我使用的mqttfx
后记
后续还有几个优化的点,第一个是增加蓝牙的控制通路,第二个是安装电池仓。
相关推荐
【顶级EI复现】基于KKT条件与分段解析优化的双有源桥DC-DC变换器软开关协同最小应力闭环控制方法(Simulink仿真实现)
内容概要:本文针对三电平维也纳(VIENNA)整流器在中大功率应用中面临的直流侧中点电位不平衡问题,提出一种基于电容电压差值补偿的主动均衡控制策略。研究以电压电流双闭环PI控制为基础,在电压外环中引入上下电容电压差值作为反馈信号,结合空间矢量脉宽调制(SVPWM)技术,利用冗余小矢量的时间分配自由度实现对中点电位的闭环调节。通过MATLAB/Simulink搭建仿真模型,在800 V直流母线电压、20 kHz开关频率、10 kW负载功率等工况下进行验证。结果表明,该方法不仅能够有效稳定直流母线电压,还可显著抑制中点电位偏差,使其维持在极小范围内,均衡效果远优于未投入补偿的情况,同时保持较低的输入电流总谐波畸变率(THD)。该方案实现了“电压稳定—电流低谐波—中点均衡”三者的协同优化,无需增加额外硬件,具有良好的实用性与工程推广价值。; 适合人群:具备电力电子与电力传动基础知识,从事新能源发电、电动汽车充电、工业电源等相关领域研究的研发人员、高校研究生及高年级本科生。; 使用场景及目标:①掌握三电平VIENNA整流器的工作原理与中点电位波动的成因;②学习双闭环PI控制的设计与参数整定方法;③理解并应用基于SVPWM冗余矢量的中点电位主动平衡策略;④开展类似拓扑的仿真建模与控制算法研究。; 阅读建议:此文理论推导与仿真验证结合紧密,建议读者在理解控制原理的基础上,动手复现Simulink仿真模型,重点关注SVPWM模块中冗余矢量时间分配的实现细节,并尝试调整系统参数以观察控制效果的变化,从而深化对中点平衡能力边界的认识。
【AI方案集】多模态AI实时翻译与沟通智能体设计.docx
这套AI技术方案不走“先堆概念再硬套场景”的老路,而是以业务痛点为锚点,把机器视觉识别、预测性分析、大模型知识赋能等能力拆解成轻量化可落地的功能模块,直接嵌入生产运营的关键环节,既能快速落地见效,又能通过持续的数据迭代逐步放大价值,帮企业在不盲目新增硬件投入的前提下,稳步完成智能化升级,实实在在把AI变成能降本、提效、控风险的生产工具。
产业园区运营负责人如何精准识别企业技术需求?.docx
科易网基于AI模型+行业大数据,在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,推出科技创新领域的AI+工具与数智化解决方案,推动科技创新与产业创新智能化发展。
木材表面缺陷木板裂缝节疤虫眼检测数据集VOC+YOLO格式612张3类别.md
【重要提示】本页面仅为资源介绍,不含外部下载地址 亲爱的 CSDN 用户: 感谢你浏览本资源页面,在这里提前向大家说明相关情况,避免后续产生误解。本资源我设置的初衷是 0 积分免费分享文档介绍,希望可以无偿给到有需要的朋友。但 CSDN 平台存在自动调控机制,系统会根据资源下载热度、文件体积、平台规则等因素,自动修改资源所需下载积分,页面显示为 1 积分、2 积分或更高积分都属于平台自动调整结果,并非我手动修改设置,我没有权限自主锁定积分数值。 如果你打开页面后,发现下载所需积分不为 0,请务必谨慎考虑是否下载。一旦你支付积分完成下载,后续遇到资源内容和描述不符、文件损坏等问题,我无法承担对应的积分损失,也没有渠道为你办理积分退还。在此强烈建议,只有页面明确显示 0 积分的时候,再进行下载操作。 同时特别说明:这份资源介绍文案里,没有附带任何外部下载地址。资源本身是通过 CSDN 官方上传通道上传的附件,正常情况下直接点击页面自带的下载按钮即可获取文件。页面找不到外部下载链接属于正常情况,并非资源缺失。 如果因为平台积分显示异常,导致你被扣取积分,积分退款、积分异常相关问题,请直接联系 CSDN 官方客服咨询处理。平台规则由官方制定,我无法干预积分变动,望各位理解。
【微服务架构】基于Nginx与MySQL的Nacos集群部署方案:多节点高可用配置与负载均衡实施指南
内容概要:本文详细介绍了Nacos集群的部署过程,包括环境准备、文件解压与复制、数据库配置、SQL脚本导入、集群节点配置以及Nginx负载均衡设置。通过搭建三个Nacos实例(nacos01、nacos02、nacos03),实现高可用架构,并利用MySQL存储配置数据,确保数据一致性与持久化。同时,通过Nginx反向代理实现对外统一入口访问,提升系统稳定性与可维护性。文中还说明了各关键配置文件的修改方法,如application.properties和cluster.conf,并指导如何启动集群及验证服务状态。; 适合人群:具备一定Java和Linux运维基础,熟悉微服务架构,从事中间件部署或DevOps工作的技术人员;尤其适合工作1-3年、参与微服务配置中心建设的研发与运维人员。; 使用场景及目标:①搭建高可用的Nacos配置中心与注册中心集群;②掌握Nacos多节点协同工作机制及与MySQL集成方法;③通过Nginx实现负载均衡,提升服务可用性和访问效率;④为Spring Cloud等微服务体系提供稳定的配置管理支持。; 阅读建议:此文档侧重实际操作步骤,建议在测试环境中同步操作,逐项验证每一步配置的正确性,特别是数据库连接、集群通信端口和Nginx转发规则,避免因配置遗漏导致集群无法正常选举或服务不可用。


