【STM32实战】机械臂快递分拣系统(二)——机械臂控制程序(上位机)编写
前言
近期回校上最后一门课,刚好是做机械臂有关的题目,所以写文记录一下。主要实现的是可以自动识别获取快递位置,机械臂可以抓取快递,以及根据自动识别快递上的条形码获得目的地点,机械臂可以将快递抓取并移动到目的地点,此外就是还可以通过上位机来控制机械臂,上位机可以是PC,也可以是另一部STM32,需要有图形化界面。
题目分析
自动识别获取快递位置以及识别快递上的条形码需要用到摄像头模块,需要解决STM32和摄像头模块之间的通信问题,以及快递形状。通过上位机来控制机械臂则需要使用到蓝牙或者WIFI模块,图形化界面可以使用物联网云平台来开发,这里用的是阿里云。如果使用另一部32进行控制,则还可在另一部32上做界面。 但购买的机械臂是已经组装完毕的,且上面带有STM32系统板,型号为STM32F103C8T6,只引出了串口3的接口,也就是说如果要连接摄像头模块,就无法与上位机进行通信。所以我打算将该机械臂作为一个下位机来接收命令,上位机采用另一部STM32来做,上位机来处理串口接收到的来自云平台或者摄像头的数据,然后算出各个舵机的PWM占空比重装载值,通过蓝牙模块发送到下位机机械臂,以此进行控制。 之前的博文已经完成了机械臂控制程序(下位机)的编写,并通过蓝牙以及串口调试工具能实现对机械臂的控制,本篇主要是完成上位机程序的编写,使机械臂可以接收来自上位机的指令并做出相应的动作,方法还是采用串口通信,使用蓝牙模块连接,然后就是可以让这个上位机能连接阿里云平台,这样就可以在阿里云平台上实时看到各舵机的信息,以及发出控制指令,做出控制。 之前博文的传送门如下 【STM32实战】机械臂快递分拣系统(一)——机械臂控制程序(下位机)编写 由于需要用到蓝牙模块,先介绍一下怎么将两个蓝牙模块配对。
蓝牙模块的使用
蓝牙模块就是一种透传设备,串口给它发送什么,它就接收什么,然后它将接收到的数据通过某种协议发送到跟它配对的蓝牙上,这个蓝牙将这些数据解析,最后还原成最初的未经处理的数据,这就是透明传输意思。这跟使用杜邦线将两个模块串口的RT对接是一样的。 推荐购买的蓝牙模块是这种带有按键的,按下按键然后接到电脑(通电)即可进入AT模式,设置蓝牙配对。 配对方法也很简单,接CH340模块(需要注意RT对接),在按键按下的状态,将CH340接入电脑USB口,即可让蓝牙进入AT模式,一般此模式下蓝牙模块的波特率为38400,在串口工具上发送命令AT,勾选发送新行,即可看到窗口返回OK。 然后可以使用AT命令设置蓝牙名称AT+NAME=Bluetooth-Marster,然后点击发送,这里设置名称为Bluetooth-Marster,另一个蓝牙的名称可以设置为Bluetooth-Slave。 然后设置蓝牙配对码AT+PSWD=0425,然后点击发送,这里设置为0425,另一个蓝牙的配对码也要是这个,才能成功配对。 然后设置工作模式为主机模式,命令是AT+ROLE=1,然后点击发送,另一个蓝牙需要设置为从机模式,命令就是AT+ROLE=0 然后配置蓝牙串口参数AT+UART=115200,0,0,然后点击发送即可,两个蓝牙都要设置一样。 然后设置一下模式为无需地址绑定模式,这样只要配对码一样就能配对成功了。命令AT+CMODE=1,然后发送即可。 蓝牙模块主机这就配置完毕了,接下来配置从机,也就是上面所说的另一个蓝牙。 这里因为我的两个蓝牙不是一样的,所有前面报错。这个蓝牙设置配对码需要加上冒号,因此推荐买蓝牙模块的时候最好是买一样的。 然后设置为从机模式 设置波特率115200 设置任意地址绑定模式 然后就将两个CH340模块拔掉再插入电脑,即可在串口工具上测试蓝牙能否配对成功了。 需要注意的是现在的波特率就不是38400了。 这里发送111,可以看到另一个蓝牙成功接收到了,说明配对成功了。
上位机程序的编写
这里提供一下工程模板源码、各类需要用到的库源码、以及我配置好的工程源码,一方面是方便大家将库用到自己的工程里,一方面是方便不想配置想先上手玩一玩的人的需求。资源链接如下,我设置的是0积分免费下载,如果不能下载给我留言吧! STM32机械臂控制程序(上位机) 然后就是主要介绍一下我是怎么实现的,首先在 main.c 中将这些库引入(怎么将库加入工程之前也已经说过很多次了,这里就不再解释了)
#include "SysTick.h"
#include "sys.h"
#include "string.h"
#include "timer.h"
#include "usart.h"
#include "uartconfig.h"
#include "uartprotocol.h"
#include "ui.h"
#include "lcd.h"
#include "key.h"
#include "wifi.h"
#include "mqtt.h"
#include "iot.h"
然后就是一些变量定义,这里先是定义一下任务有关的变量,task_num会在定时器中断触发的时候自增,直到值大于task_max后会清零,而task_flag会在定时器中断触发的时候被置为1,在任务完成的时候被置为0,这样就可以根据task_num的值执行不同的任务,比如0的时候执行任务1,1的时候执行任务2,task_flag则是控制每一次只执行一次任务。
uint8_t task_num = 0;
uint8_t task_flag = 0;
uint8_t task_max = 3;
然后就是串口通信相关结构体定义,这部分服务于串口通信的,之前博文(串口通信)中有介绍怎么用,想深刻理解的可以点我主页去找相应的博文看。
DataTransmit data_transmit_uart1;
DataReceive data_receive_uart1;
DataTransmit data_transmit_uart3;
DataReceive data_receive_uart3;
TargetProperty tstm32;
TargetProperty rk210;
TargetProperty rstm32;
然后是定义一个变量,控制一下是否连接阿里云。主要原因就是连接阿里云太慢,然后我要调LCD屏幕上显示的参数位置的时候,每一次都需要等连接阿里云,不等就要注释相对应的代码,很麻烦,所以我就定义一个变量来控制是否连接,这样调试LCD屏上显示的参数位置的时候就很方便了。
uint8_t ui_flag = 0;
然后是定义一下服务于按键的相关参数。keycode是按键码,这里我用的按键程序是正点原子的,按下板子上不同的按键会获得不同的按键码,我将这个按键码用keycode保存,这样就可以知道每次我按下的是什么键值。sw则是功能选择标志,sw_max是功能选择最大值,因为是6个舵机,所以该最大值是5,sw在某个按键按下的时候值会自增,直到超过5后清零,sw的值为0的时候,可以通过另外两个按键,根据key_servo的值,将舵机1的占空比重装在值进行加减key_servo的值的操作。以此类推,sw的值为1就是控制舵机2,为2就是控制舵机3……,每次加减操作舵机占空比重装载值会变化50。
uint8_t keycode = 0;
uint8_t sw = 0;
uint8_t sw_max = 5;
uint16_t key_servo = 50;
然后就是机械臂抓取参数定义,get是抓取计数器,抓取任务每一次进行的时候这个值都会自增,get_time控制的是机械臂抓取物体到移动物体再到放下物体这中间每一个动作的间隔。get_flag是抓取标志,这个值开始是0,到机械臂移动到物体的位置的时候被置为1,然后机械臂就开始抓取物体,get_down是保存抓取物体时候的二号舵机占空比值,因为放下物体的时候要将物体放到桌面再松开爪子。
uint16_t get = 0;
uint16_t get_flag = 0;
uint16_t get_down = 0;
uint16_t get_time = 3;
然后就是相关函数声明,c语言中函数写在后面的,但被前面函数调用到的,需要在最前面声明,才能正常调用。
void System_Init(void);
void Run_Task(void);
void Send_ALY(void);
void Key_Dispose(void);
void RobControl(TargetProperty *rk210);
接下来就是主函数
int main(void)
{
System_Init();
while(1)
{
Run_Task();
}
}
主函数里面初始化函数主要完成的是延时函数初始化、中断分组设置、各串口初始化、LCD初始化、定时器初始化、MQTT连接阿里云、串口通信相关结构体初始化。
void System_Init(void)
{
SysTick_Init(72);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
uart2_init(115200);
LCD_Init();
if(ui_flag != 0)
{
MQTT_Init();
}
Init_TIM3(9999,719,0,1);
Init_UART1(115200,1,1);
Init_UART3(115200,1,1);
KEY_Init();
ui();
Data_Transmit_Init(&data_transmit_uart1,0xFC,0xAA,7);
Data_Receive_Init(&data_receive_uart1,0xFC,0xAA);
Data_Transmit_Init(&data_transmit_uart3,0xFC,0xAA,12);
Data_Receive_Init(&data_receive_uart3,0xFC,0xAA);
Target_Init(&tstm32);
Target_Init(&rk210);
Target_Init(&rstm32);
tstm32.servo1 = 1500;
tstm32.servo2 = 1500;
tstm32.servo3 = 1500;
tstm32.servo4 = 1500;
tstm32.servo5 = 1500;
tstm32.servo6 = 1400;
}
运行函数则是按键扫描,以及根据不同的任务号完成不同的任务。这里我将任务1(task_num为0)中的串口1数据解析函数注释掉了,这样是为了调试方便,如果不注释掉,在Debug的时候我将不能更改结构体rk210中的值,这样RobControl就不能根据rk210中的坐标值抓取物体。K210识别物体的代码后续博文会发,后面只需要接上K210,然后将这里的注释符号删除即可。 任务1就是接收K210数据,STM32数据(来自机械臂下位机,作为回显),然后获得控制机械臂抓取物体的各舵机占空比值。 任务2就是将任务1中的占空比值发到下位机,这样机械臂才能做出相应的动作。 任务3就是LCD上的数据更新。 任务4就是将接收的K210的数据发回K210,作为回显。然后将各数据发送到阿里云。
void Run_Task(void)
{
Key_Dispose();
if(task_num == 0 && task_flag == 1)
{
Target_Parse(&data_receive_uart3,&rstm32);
RobControl(&rk210);
task_flag = 0;
}
else if(task_num == 1 && task_flag == 1)
{
data_transmit_uart3.data[0] = tstm32.servo1/256;
data_transmit_uart3.data[1] = tstm32.servo1%256;
data_transmit_uart3.data[2] = tstm32.servo2/256;
data_transmit_uart3.data[3] = tstm32.servo2%256;
data_transmit_uart3.data[4] = tstm32.servo3/256;
data_transmit_uart3.data[5] = tstm32.servo3%256;
data_transmit_uart3.data[6] = tstm32.servo4/256;
data_transmit_uart3.data[7] = tstm32.servo4%256;
data_transmit_uart3.data[8] = tstm32.servo5/256;
data_transmit_uart3.data[9] = tstm32.servo5%256;
data_transmit_uart3.data[10] = tstm32.servo6/256;
data_transmit_uart3.data[11] = tstm32.servo6%256;
Data_Pack_Transmit(&data_transmit_uart3, USART3);
task_flag = 0;
}
else if(task_num == 2 && task_flag == 1)
{
ui_updata();
task_flag = 0;
}
else if(task_num == 3 && task_flag == 1)
{
data_transmit_uart1.data[0] = rk210.x/256;
data_transmit_uart1.data[1] = rk210.x%256;
data_transmit_uart1.data[2] = rk210.y/256;
data_transmit_uart1.data[3] = rk210.y%256;
data_transmit_uart1.data[4] = rk210.color;
data_transmit_uart1.data[5] = rk210.shape;
data_transmit_uart1.data[6] = rk210.flag;
Data_Pack_Transmit(&data_transmit_uart1, USART1);
if(ui_flag!=0)
{
Send_ALY();
}
task_flag = 0;
}
}
然后就是定时器中断函数,这个之前说过了,每次触发中断,task_num的值会自增,直到超过task_max被清零,每次触发中断都会将task_flag置1。
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
{
if(task_num < task_max)
{
task_num = task_num + 1;
task_flag = 1;
}
else
{
task_num = 0;
task_flag = 1;
}
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
}
}
串口中断函数则是服务于接收串口数据。
void USART1_IRQHandler(void)
{
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
Data_Receive(&data_receive_uart1, USART_ReceiveData(USART1));
get_flag = 0;
USART_ClearFlag(USART1,USART_IT_RXNE);
}
}
void USART3_IRQHandler(void)
{
if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET)
{
Data_Receive(&data_receive_uart3, USART_ReceiveData(USART3));
USART_ClearFlag(USART3,USART_IT_RXNE);
}
}
发送参数到阿里云则是发送各舵机占空比值到阿里云。
void Send_ALY(void)
{
send_data("servo1",(int)tstm32.servo1);
send_data("servo2",(int)tstm32.servo2);
send_data("servo3",(int)tstm32.servo3);
send_data("servo4",(int)tstm32.servo4);
send_data("servo5",(int)tstm32.servo5);
send_data("servo6",(int)tstm32.servo6);
_mqtt.SendHeart();
}
然后就是按键函数,用来控制各舵机占空比,keycode是按键码,这里我用的按键程序是正点原子的,按下板子上不同的按键会获得不同的按键码,我将这个按键码用keycode保存,这样就可以知道每次我按下的是什么键值。sw则是功能选择标志,sw_max是功能选择最大值,因为是6个舵机,所以该最大值是5,sw在某个按键按下的时候值会自增,直到超过5后清零,sw的值为0的时候,可以通过另外两个按键,根据key_servo的值,将舵机1的占空比重装在值进行加减key_servo的值的操作。以此类推,sw的值为1就是控制舵机2,为2就是控制舵机3……,每次加减操作舵机占空比重装载值会变化50。
void Key_Dispose(void)
{
keycode=KEY_Scan(0);
if(keycode == 3)
{
sw++;
if(sw > sw_max)
{
sw = 0;
}
}
if(keycode == 4)
{
if(sw == 0)
{
tstm32.servo1 += key_servo;
}
else if(sw == 1)
{
tstm32.servo2 += key_servo;
}
else if(sw == 2)
{
tstm32.servo3 += key_servo;
}
else if(sw == 3)
{
tstm32.servo4 += key_servo;
}
else if(sw == 4)
{
tstm32.servo5 += key_servo;
}
else if(sw == 5)
{
tstm32.servo6 += key_servo;
}
}
if(keycode == 2)
{
if(sw == 0)
{
tstm32.servo1 -= key_servo;
}
else if(sw == 1)
{
tstm32.servo2 -= key_servo;
}
else if(sw == 2)
{
tstm32.servo3 -= key_servo;
}
else if(sw == 3)
{
tstm32.servo4 -= key_servo;
}
else if(sw == 4)
{
tstm32.servo5 -= key_servo;
}
else if(sw == 5)
{
tstm32.servo6 -= key_servo;
}
}
if(keycode == 1)
{
get_flag = 0;
}
}
机械臂控制函数则是根据接收到的物体x坐标的不同,控制爪子先靠近物体,再抓取物体,然后放到固定位置。需要注意的是这里的占空比是写死的,因为我这个机械臂的精度比较低,误差感觉上有一厘米左右,所以只能拿一个尺子,然后将物体放到1cm处,调节2、3、4号舵机占空比,使爪子靠近物体中心,记录下各舵机占空比值,然后再放到2cm处,重复上述动作,我这里只做到了第17cm。
void RobControl(TargetProperty *rk210)
{
if(rk210 -> flag != 0 && rk210 -> x != 0 && get_flag == 0)
{
if(rk210 -> x == 0)
{
tstm32.servo2 = 1460;
tstm32.servo3 = 2090;
tstm32.servo4 = 2150;
get_down = tstm32.servo2;
}
else if(rk210 -> x == 1)
{
tstm32.servo2 = 1420;
tstm32.servo3 = 2050;
tstm32.servo4 = 2150;
get_down = tstm32.servo2;
}
else if(rk210 -> x == 2)
{
tstm32.servo2 = 1380;
tstm32.servo3 = 1990;
tstm32.servo4 = 2150;
get_down = tstm32.servo2;
}
else if(rk210 -> x == 3)
{
tstm32.servo2 = 1340;
tstm32.servo3 = 1950;
tstm32.servo4 = 2150;
get_down = tstm32.servo2;
}
else if(rk210 -> x == 4)
{
tstm32.servo2 = 1300;
tstm32.servo3 = 1910;
tstm32.servo4 = 2150;
get_down = tstm32.servo2;
}
else if(rk210 -> x == 5)
{
tstm32.servo2 = 1260;
tstm32.servo3 = 1880;
tstm32.servo4 = 2150;
get_down = tstm32.servo2;
}
else if(rk210 -> x == 6)
{
tstm32.servo2 = 1300;
tstm32.servo3 = 1900;
tstm32.servo4 = 2100;
get_down = tstm32.servo2;
}
else if(rk210 -> x == 7)
{
tstm32.servo2 = 1300;
tstm32.servo3 = 1880;
tstm32.servo4 = 2100;
get_down = tstm32.servo2;
}
else if(rk210 -> x == 8)
{
tstm32.servo2 = 1280;
tstm32.servo3 = 1860;
tstm32.servo4 = 2100;
get_down = tstm32.servo2;
}
else if(rk210 -> x == 9)
{
tstm32.servo2 = 1280;
tstm32.servo3 = 1880;
tstm32.servo4 = 2020;
get_down = tstm32.servo2;
}
else if(rk210 -> x == 10)
{
tstm32.servo2 = 1280;
tstm32.servo3 = 1880;
tstm32.servo4 = 1980;
get_down = tstm32.servo2;
}
else if(rk210 -> x == 11)
{
tstm32.servo2 = 1280;
tstm32.servo3 = 1900;
tstm32.servo4 = 1940;
get_down = tstm32.servo2;
}
else if(rk210 -> x == 12)
{
tstm32.servo2 = 1280;
tstm32.servo3 = 1900;
tstm32.servo4 = 1920;
get_down = tstm32.servo2;
}
else if(rk210 -> x == 13)
{
tstm32.servo2 = 1220;
tstm32.servo3 = 1820;
tstm32.servo4 = 1960;
get_down = tstm32.servo2;
}
else if(rk210 -> x == 14)
{
tstm32.servo2 = 1240;
tstm32.servo3 = 1880;
tstm32.servo4 = 1860;
get_down = tstm32.servo2;
}
else if(rk210 -> x == 15)
{
tstm32.servo2 = 1240;
tstm32.servo3 = 1880;
tstm32.servo4 = 1840;
get_down = tstm32.servo2;
}
else if(rk210 -> x == 16)
{
tstm32.servo2 = 1240;
tstm32.servo3 = 1880;
tstm32.servo4 = 1820;
get_down = tstm32.servo2;
}
else if(rk210 -> x == 17)
{
tstm32.servo2 = 1160;
tstm32.servo3 = 1800;
tstm32.servo4 = 1820;
get_down = tstm32.servo2;
}
get_flag = 1;
}
if(get_flag == 1)
{
get++;
if(get_time < get && get < 2*get_time)
{
tstm32.servo6 = 1650;
}
else if(2*get_time < get && get < 3*get_time)
{
tstm32.servo2 = 1500;
tstm32.servo1 = 2000;
}
else if(3*get_time < get && get < 4*get_time)
{
tstm32.servo2 = get_down;
}
else if(4*get_time < get && get < 5*get_time)
{
tstm32.servo6 = 1400;
}
else if(5*get_time < get && get < 6*get_time)
{
tstm32.servo2 =1500;
}
else if(6*get_time < get)
{
tstm32.servo1 =1500;
tstm32.servo2 =1500;
tstm32.servo3 =1500;
tstm32.servo4 =1500;
tstm32.servo5 =1500;
tstm32.servo6 =1400;
get_down = 0;
get_flag = 2;
get = 0;
}
}
}
连接阿里云
这部分参数的配置在iot.h中,需要与你们自己的设备参数对应上。需要注意的是,WIFI名字只能是英文,且只支持2.4G的,5G的不行(ESP8266硬件本身不支持)。 MQTT连接参数查看位置 订阅相关参数查看位置,${deviceName}需要改成ProductKey 界面我只是简单的做了个折线图以及几个按钮,可以控制舵机运动,其他的功能还暂时想不到。 按钮的交互是这样的,每次点击,就会给ESP8266发送一个字符串,上位机通过接收到的字符串,控制舵机占空比值加减。这里的属性值可以自行设置,与上位机那边接收的代码中识别的对应上即可。 用于接收这部分命令的代码在iot.c中。
void USART2_IRQHandler(void)
{
u8 d;
static u8 rxlen = 0;
if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)
{
d = USART2 -> DR;
if(rxlen>=255) rxlen=0;
rxbuf[rxlen++] = d;
rxlen %= sizeof(rxbuf);
if(rxbuf[rxlen-3]=='Q'&& rxbuf[rxlen-2]=='W' && rxbuf[rxlen-1]=='+')
{
tstm32.servo1 = tstm32.servo1 + 50;
rxlen = 0;
}
else if(rxbuf[rxlen-3]=='Q'&& rxbuf[rxlen-2]=='W' && rxbuf[rxlen-1]=='-')
{
tstm32.servo1 = tstm32.servo1 - 50;
rxlen = 0;
}
else if(rxbuf[rxlen-3]=='E'&& rxbuf[rxlen-2]=='R' && rxbuf[rxlen-1]=='+')
{
tstm32.servo2 = tstm32.servo2 + 50;
rxlen = 0;
}
else if(rxbuf[rxlen-3]=='E'&& rxbuf[rxlen-2]=='R' && rxbuf[rxlen-1]=='-')
{
tstm32.servo2 = tstm32.servo2 - 50;
rxlen = 0;
}
else if(rxbuf[rxlen-3]=='T'&& rxbuf[rxlen-2]=='Y' && rxbuf[rxlen-1]=='+')
{
tstm32.servo3 = tstm32.servo3 - 50;
rxlen = 0;
}
else if(rxbuf[rxlen-3]=='T'&& rxbuf[rxlen-2]=='Y' && rxbuf[rxlen-1]=='-')
{
tstm32.servo3 = tstm32.servo3 + 50;
rxlen = 0;
}
else if(rxbuf[rxlen-3]=='U'&& rxbuf[rxlen-2]=='I' && rxbuf[rxlen-1]=='+')
{
tstm32.servo4 = tstm32.servo4 - 50;
rxlen = 0;
}
else if(rxbuf[rxlen-3]=='U'&& rxbuf[rxlen-2]=='I' && rxbuf[rxlen-1]=='-')
{
tstm32.servo4 = tstm32.servo4 + 50;
rxlen = 0;
}
else if(rxbuf[rxlen-3]=='O'&& rxbuf[rxlen-2]=='P' && rxbuf[rxlen-1]=='+')
{
tstm32.servo5 = tstm32.servo5 + 50;
rxlen = 0;
}
else if(rxbuf[rxlen-3]=='O'&& rxbuf[rxlen-2]=='P' && rxbuf[rxlen-1]=='-')
{
tstm32.servo5 = tstm32.servo5 - 50;
rxlen = 0;
}
else if(rxbuf[rxlen-3]=='A'&& rxbuf[rxlen-2]=='S' && rxbuf[rxlen-1]=='+')
{
tstm32.servo6 = tstm32.servo6 + 50;
rxlen = 0;
}
else if(rxbuf[rxlen-3]=='A'&& rxbuf[rxlen-2]=='S' && rxbuf[rxlen-1]=='-')
{
tstm32.servo6 = tstm32.servo6 - 50;
rxlen = 0;
}
USART_ClearFlag(USART2,USART_IT_RXNE);
}
if(USART_GetITStatus(USART2, USART_IT_IDLE))
{
d = USART2->DR;
d = USART2->SR;
_mqtt.rxlen = rxlen;
rxlen=0;
USART_ClearFlag(USART2,USART_IT_IDLE);
}
}
测试
蓝牙模块上电就会进入配对模式,如果一段时间配对无法成功,则会停止配对,需要重新上电才会重新配对,也就是说,下位机和上位机最好同时上电。 然后按下按键即可控制舵机旋转啦! 如果要控制电机抓取物体的话,在Debug中,将结构体rk210中的x设置为3(我将蓝块放到3cm处),然后将flag置1,机械臂就会开始抓物体了。 同样的,将结构体rk210中的x设置为9(我将蓝块放到9cm处),然后将flag置1,机械臂就会开始抓9cm处的物体了。 如果要用阿里云平台的web界面按钮控制舵机旋转,记得将这个标志位置1,不然没办法连接阿里云。 现在是下位机和上位机程序都写完了,后面就是差一个K210获取物体坐标信息的程序需要写了,然后将K210连接到上位机的UART1就可以了,我们下期再见咯!
|