【STM32实战】机械臂快递分拣系统(二)——机械臂控制程序(上位机)编写

【STM32实战】机械臂快递分拣系统(二)——机械臂控制程序(上位机)编写

  • 前言
  • 题目分析
  • 蓝牙模块的使用
  • 上位机程序的编写
  • 连接阿里云
  • 测试

前言

近期回校上最后一门课,刚好是做机械臂有关的题目,所以写文记录一下。主要实现的是可以自动识别获取快递位置,机械臂可以抓取快递,以及根据自动识别快递上的条形码获得目的地点,机械臂可以将快递抓取并移动到目的地点,此外就是还可以通过上位机来控制机械臂,上位机可以是PC,也可以是另一部STM32,需要有图形化界面。

题目分析

自动识别获取快递位置以及识别快递上的条形码需要用到摄像头模块,需要解决STM32和摄像头模块之间的通信问题,以及快递形状。通过上位机来控制机械臂则需要使用到蓝牙或者WIFI模块,图形化界面可以使用物联网云平台来开发,这里用的是阿里云。如果使用另一部32进行控制,则还可在另一部32上做界面。
但购买的机械臂是已经组装完毕的,且上面带有STM32系统板,型号为STM32F103C8T6,只引出了串口3的接口,也就是说如果要连接摄像头模块,就无法与上位机进行通信。所以我打算将该机械臂作为一个下位机来接收命令,上位机采用另一部STM32来做,上位机来处理串口接收到的来自云平台或者摄像头的数据,然后算出各个舵机的PWM占空比重装载值,通过蓝牙模块发送到下位机机械臂,以此进行控制。
之前的博文已经完成了机械臂控制程序(下位机)的编写,并通过蓝牙以及串口调试工具能实现对机械臂的控制,本篇主要是完成上位机程序的编写,使机械臂可以接收来自上位机的指令并做出相应的动作,方法还是采用串口通信,使用蓝牙模块连接,然后就是可以让这个上位机能连接阿里云平台,这样就可以在阿里云平台上实时看到各舵机的信息,以及发出控制指令,做出控制。
之前博文的传送门如下
【STM32实战】机械臂快递分拣系统(一)——机械臂控制程序(下位机)编写
由于需要用到蓝牙模块,先介绍一下怎么将两个蓝牙模块配对。

蓝牙模块的使用

蓝牙模块就是一种透传设备,串口给它发送什么,它就接收什么,然后它将接收到的数据通过某种协议发送到跟它配对的蓝牙上,这个蓝牙将这些数据解析,最后还原成最初的未经处理的数据,这就是透明传输意思。这跟使用杜邦线将两个模块串口的RT对接是一样的。
推荐购买的蓝牙模块是这种带有按键的,按下按键然后接到电脑(通电)即可进入AT模式,设置蓝牙配对。

配对方法也很简单,接CH340模块(需要注意RT对接),在按键按下的状态,将CH340接入电脑USB口,即可让蓝牙进入AT模式,一般此模式下蓝牙模块的波特率为38400,在串口工具上发送命令AT,勾选发送新行,即可看到窗口返回OK。
【STM32实战】机械臂快递分拣系统(二)——机械臂控制程序(上位机)编写_第1张图片
然后可以使用AT命令设置蓝牙名称AT+NAME=Bluetooth-Marster,然后点击发送,这里设置名称为Bluetooth-Marster,另一个蓝牙的名称可以设置为Bluetooth-Slave。
【STM32实战】机械臂快递分拣系统(二)——机械臂控制程序(上位机)编写_第2张图片
然后设置蓝牙配对码AT+PSWD=0425,然后点击发送,这里设置为0425,另一个蓝牙的配对码也要是这个,才能成功配对。
【STM32实战】机械臂快递分拣系统(二)——机械臂控制程序(上位机)编写_第3张图片
然后设置工作模式为主机模式,命令是AT+ROLE=1,然后点击发送,另一个蓝牙需要设置为从机模式,命令就是AT+ROLE=0
【STM32实战】机械臂快递分拣系统(二)——机械臂控制程序(上位机)编写_第4张图片
然后配置蓝牙串口参数AT+UART=115200,0,0,然后点击发送即可,两个蓝牙都要设置一样。
【STM32实战】机械臂快递分拣系统(二)——机械臂控制程序(上位机)编写_第5张图片
然后设置一下模式为无需地址绑定模式,这样只要配对码一样就能配对成功了。命令AT+CMODE=1,然后发送即可。
【STM32实战】机械臂快递分拣系统(二)——机械臂控制程序(上位机)编写_第6张图片
蓝牙模块主机这就配置完毕了,接下来配置从机,也就是上面所说的另一个蓝牙。
【STM32实战】机械臂快递分拣系统(二)——机械臂控制程序(上位机)编写_第7张图片
这里因为我的两个蓝牙不是一样的,所有前面报错。这个蓝牙设置配对码需要加上冒号,因此推荐买蓝牙模块的时候最好是买一样的。
【STM32实战】机械臂快递分拣系统(二)——机械臂控制程序(上位机)编写_第8张图片
然后设置为从机模式
【STM32实战】机械臂快递分拣系统(二)——机械臂控制程序(上位机)编写_第9张图片
设置波特率115200
【STM32实战】机械臂快递分拣系统(二)——机械臂控制程序(上位机)编写_第10张图片
设置任意地址绑定模式
【STM32实战】机械臂快递分拣系统(二)——机械臂控制程序(上位机)编写_第11张图片
然后就将两个CH340模块拔掉再插入电脑,即可在串口工具上测试蓝牙能否配对成功了。
【STM32实战】机械臂快递分拣系统(二)——机械臂控制程序(上位机)编写_第12张图片
需要注意的是现在的波特率就不是38400了。
【STM32实战】机械臂快递分拣系统(二)——机械臂控制程序(上位机)编写_第13张图片
这里发送111,可以看到另一个蓝牙成功接收到了,说明配对成功了。【STM32实战】机械臂快递分拣系统(二)——机械臂控制程序(上位机)编写_第14张图片

上位机程序的编写

这里提供一下工程模板源码、各类需要用到的库源码、以及我配置好的工程源码,一方面是方便大家将库用到自己的工程里,一方面是方便不想配置想先上手玩一玩的人的需求。资源链接如下,我设置的是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;															// 任务完成标志 0完成 1未完成
uint8_t task_max = 3;																// 任务序号最大值

然后就是串口通信相关结构体定义,这部分服务于串口通信的,之前博文(串口通信)中有介绍怎么用,想深刻理解的可以点我主页去找相应的博文看。

// 串口通信相关结构体定义
DataTransmit data_transmit_uart1;										// 声明全局结构体 data_transmit_uart1 
DataReceive data_receive_uart1;											// 声明全局结构体 data_receive_uart1 

DataTransmit data_transmit_uart3;										// 声明全局结构体 data_transmit_uart3 
DataReceive data_receive_uart3;											// 声明全局结构体 data_receive_uart3 

TargetProperty tstm32;											  			// 声明全局结构体 tstm32 

TargetProperty rk210;											  				// 声明全局结构体 rk210 
TargetProperty rstm32;											  			// 声明全局结构体 rstm32 

然后是定义一个变量,控制一下是否连接阿里云。主要原因就是连接阿里云太慢,然后我要调LCD屏幕上显示的参数位置的时候,每一次都需要等连接阿里云,不等就要注释相对应的代码,很麻烦,所以我就定义一个变量来控制是否连接,这样调试LCD屏上显示的参数位置的时候就很方便了。

// ui 相关参数
uint8_t ui_flag = 0;																// ui 调试模式标志 0 开启 不连接阿里云 1 关闭 连接阿里云			

然后是定义一下服务于按键的相关参数。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;															// 0 不抓取 1 开始抓取
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);	    	 																// 延时函数初始化	时钟72MHz  
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);	 				// 设置NVIC中断分组2 2位抢占优先级 2位响应优先级 即可设置 0-3 抢占优先级 0-3 子优先级 2位就是2的2次方
	
	uart2_init(115200);																			// 串口2 初始化 波特率 115200
	
	LCD_Init();																							// LCD初始化
	
	if(ui_flag != 0)														  					// 如果不为 ui 调试模式
	{
		
		MQTT_Init();  			 																	// MQTT初始化 连接阿里云
		
	}
	
	Init_TIM3(9999,719,0,1);																// 720分频 计数 10000 频率 10 Hz 抢占优先级0 子优先级1 
	
	Init_UART1(115200,1,1);																	// 串口1 初始化 波特率115200 抢占优先级1 子优先级1 
	Init_UART3(115200,1,1);																	// 串口3 初始化 波特率115200 抢占优先级1 子优先级1 
	
	KEY_Init();																							// 按键初始化
	
	ui();																										// 界面静态信息显示
	 
	Data_Transmit_Init(&data_transmit_uart1,0xFC,0xAA,7);		// 设置发送给 K210 的帧头 FC AA 有效数据长度 7
	Data_Receive_Init(&data_receive_uart1,0xFC,0xAA);				// 设置接收自 K210 的帧头 FC AA 
	
	Data_Transmit_Init(&data_transmit_uart3,0xFC,0xAA,12);	// 设置发送给 STM32 的帧头 FC AA 有效数据长度 12
	Data_Receive_Init(&data_receive_uart3,0xFC,0xAA);				// 设置接收自 STM32 的帧头 FC AA				
																		
	Target_Init(&tstm32);																		// 初始化结构体 tstm32													

	Target_Init(&rk210);															 			// 初始化结构体 rk210
	Target_Init(&rstm32);																		// 初始化结构体 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_K210(&data_receive_uart1,&rk210);				// 解析 data_receive_uart1 接收数据 给 rk210
		Target_Parse(&data_receive_uart3,&rstm32);						// 解析 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);			// 对数据进行打包 并通过 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;						// 接收数据发送回K210 回显									
		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);			// 对数据进行打包 并通过USART1 发送
		
		if(ui_flag!=0)																				// 如果之前有连接阿里云
		{
			
			Send_ALY();																				  // 发送信息到阿里云 
			
		}
				
		task_flag = 0;																				// 任务完成 等待下一个任务
		
	}
	
}

然后就是定时器中断函数,这个之前说过了,每次触发中断,task_num的值会自增,直到超过task_max被清零,每次触发中断都会将task_flag置1。

// 定时器3 中断函数
void TIM3_IRQHandler(void)   											  			// TIM3中断服务函数
{
	
	if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) 			// 检查TIM3更新中断发生与否
	{
			
		if(task_num < task_max)
		{
			
			task_num = task_num + 1;														// 任务切换
			task_flag = 1;																			// 任务完成标志 0完成 1未完成 每次只做一次任务
			
		}
		else
		{
			
			task_num = 0;																				// 重头开始执行任务
			task_flag = 1;																			// 任务完成标志 0完成 1未完成 每次只做一次任务
			
		}
		
		TIM_ClearITPendingBit(TIM3, TIM_IT_Update);  					// 清除TIM3更新中断标志 
				
	}
	
}

串口中断函数则是服务于接收串口数据。

// 串口1 中断函数
void USART1_IRQHandler(void)
{
	
	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) 													// 判断接收数据寄存器是否有数据
	{
		
		Data_Receive(&data_receive_uart1, USART_ReceiveData(USART1));									// 从 串口1 接收1个数据
		
		get_flag = 0;																																	// 开始抓取
		
		USART_ClearFlag(USART1,USART_IT_RXNE);																				// 清空中断标志 准备下一次接收
		
	}
	
}

// 串口3 中断函数
void USART3_IRQHandler(void)
{
	
	if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET) 													// 判断接收数据寄存器是否有数据
	{
		
		Data_Receive(&data_receive_uart3, USART_ReceiveData(USART3));									// 从 串口3 接收1个数据
			
		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)
{
	
	// 0 不支持连续按 1 支持连续按
	keycode=KEY_Scan(0);
	
	// KEY 2
	if(keycode == 3)
	{
		
		sw++;
		if(sw > sw_max)					
		{
			
			sw = 0;
			
		}
		
	}
	
	// KEY UP
	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;
			
		}
		
	}
	
	// KEY 1
	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;
			
		}
		
	}
	
	// KEY 0 预留
	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硬件本身不支持)。
【STM32实战】机械臂快递分拣系统(二)——机械臂控制程序(上位机)编写_第15张图片
MQTT连接参数查看位置
【STM32实战】机械臂快递分拣系统(二)——机械臂控制程序(上位机)编写_第16张图片
订阅相关参数查看位置,${deviceName}需要改成ProductKey
【STM32实战】机械臂快递分拣系统(二)——机械臂控制程序(上位机)编写_第17张图片
【STM32实战】机械臂快递分拣系统(二)——机械臂控制程序(上位机)编写_第18张图片
界面我只是简单的做了个折线图以及几个按钮,可以控制舵机运动,其他的功能还暂时想不到。
【STM32实战】机械臂快递分拣系统(二)——机械臂控制程序(上位机)编写_第19张图片
按钮的交互是这样的,每次点击,就会给ESP8266发送一个字符串,上位机通过接收到的字符串,控制舵机占空比值加减。这里的属性值可以自行设置,与上位机那边接收的代码中识别的对应上即可。
【STM32实战】机械臂快递分拣系统(二)——机械臂控制程序(上位机)编写_第20张图片
用于接收这部分命令的代码在iot.c中。

// 串口2中断 接收远程命令
void USART2_IRQHandler(void)
{
		u8 d;
    static u8 rxlen = 0;
	
    if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) 		// 判断接收数据寄存器是否有数据
    {
			
			d = USART2 -> DR;
			//rxbuf[rxlen++] = d;
			
			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,机械臂就会开始抓物体了。
【STM32实战】机械臂快递分拣系统(二)——机械臂控制程序(上位机)编写_第21张图片

同样的,将结构体rk210中的x设置为9(我将蓝块放到9cm处),然后将flag置1,机械臂就会开始抓9cm处的物体了。
【STM32实战】机械臂快递分拣系统(二)——机械臂控制程序(上位机)编写_第22张图片

如果要用阿里云平台的web界面按钮控制舵机旋转,记得将这个标志位置1,不然没办法连接阿里云。
在这里插入图片描述
现在是下位机和上位机程序都写完了,后面就是差一个K210获取物体坐标信息的程序需要写了,然后将K210连接到上位机的UART1就可以了,我们下期再见咯!

你可能感兴趣的:(stm32,单片机,arm,嵌入式硬件,mcu)