一、项目简介
能够在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
后记
后续还有几个优化的点,第一个是增加蓝牙的控制通路,第二个是安装电池仓。