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

2023-11-14

【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;															// 任务完成标志 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硬件本身不支持)。
在这里插入图片描述
MQTT连接参数查看位置
在这里插入图片描述
订阅相关参数查看位置,${deviceName}需要改成ProductKey
在这里插入图片描述
在这里插入图片描述
界面我只是简单的做了个折线图以及几个按钮,可以控制舵机运动,其他的功能还暂时想不到。
在这里插入图片描述
按钮的交互是这样的,每次点击,就会给ESP8266发送一个字符串,上位机通过接收到的字符串,控制舵机占空比值加减。这里的属性值可以自行设置,与上位机那边接收的代码中识别的对应上即可。
在这里插入图片描述
用于接收这部分命令的代码在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,机械臂就会开始抓物体了。
在这里插入图片描述
在这里插入图片描述
同样的,将结构体rk210中的x设置为9(我将蓝块放到9cm处),然后将flag置1,机械臂就会开始抓9cm处的物体了。
在这里插入图片描述
在这里插入图片描述
如果要用阿里云平台的web界面按钮控制舵机旋转,记得将这个标志位置1,不然没办法连接阿里云。
在这里插入图片描述
现在是下位机和上位机程序都写完了,后面就是差一个K210获取物体坐标信息的程序需要写了,然后将K210连接到上位机的UART1就可以了,我们下期再见咯!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • armv8 NEON if 条件

    我想了解armv8 NEON内联汇编代码中的if条件 在armv7中 这可以通过检查溢出位来实现 如下所示 VMRS r4 FPSCR BIC r4 r4 1 lt lt 27 VMSR FPSCR r4 vtst 16 d30 d30 d
  • 我们可以优化代码来降低功耗吗?

    有没有什么技术可以优化代码以确保更低的功耗 架构是ARM 语言是C 来自 ARM 技术参考网站 ARM11 MPCore 的特性 提高能源效率的处理器 效率包括 准确的分支和子程序返回预测 减少数量 错误的指令获取和 解码操作 使用物理寻址
  • 为什么无符号类型在arm cpu中效率更高?

    我正在阅读手臂手册并提出这个建议 但没有提到原因 为什么无符号类型更快 在 ARMv4 之前 ARM 没有对加载半字和有符号字节的本机支持 要加载有符号的字节 你必须LDRB然后对值进行符号扩展 LSL那就起来吧ASR它回落 这很痛苦所以c
  • 多核ARM cpu上的中断如何工作

    这个问题已经针对 x86 得到了解答 但是 我找不到太多关于 ARM MP cpu 如 Cortex A9 Cortex A15 等 的信息 更重要的是我想知道是否可以在非主CPU上引发中断而无需任何配置等 我正在开发一款仅处理主 cpu
  • 在 Contiki 程序中使用 malloc

    考虑以下 Contiki 程序 include
  • 将 ZeroMQ 交叉编译为 ARM,以便在 MonoTouch iPhone 应用程序配置设置中使用

    我正在尝试在使用 MonoTouch 用 C 开发的 iPhone 应用程序中使用 ZeroMQ 库 我几乎解决了所有的问题 却在最后一道坎倒下了 我正在使用 ZeroMQ 2 1 10 和 C CLR 绑定 包装器 并在 Mac OS X
  • 嵌入式 C++ (ARM9) 单元测试

    我来自 Java 和 JUnit 的世界 我演示了 Hudson 以及我使用 JUnit 取得的所有成果 我想在嵌入式设备上对 C 代码执行相同的操作 但找不到从哪里开始 该项目使用 iccarm exe IAR 编译器 进行编译 现在使用
  • 使用 STM32F0 ADC 单独读取不同的输入

    STM32F072CBU 微控制器 我有多个 ADC 输入 并且希望单独读取它们 STMcubeMX 生成样板代码 假设我希望按顺序读取所有输入 但我无法弄清楚如何纠正这个问题 这篇博文 http blog koepi info 2015
  • 小型 ARM 微控制器的 RTOS 内核之间的可量化差异 [关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 有许多不同的 RTOS 可用于微控制器 我专门寻找支持 ARM Cortex M 处理器的 RTOS 另外 我对闭源解决方案不感兴趣 试图从网站
  • 用于 RHEL 的 gdb-multiarch

    我正在尝试寻找方法来运行gdb 多架构RHEL 中的命令 我已经安装了用于 ARM 处理的 QEMU 模拟器 我想安装GDB进行调试 我能够安装GDB 多体系结构在 Ubuntu 中运行命令成功 sudo apt get GDB multi
  • 哪些变量类型/大小在 STM32 微控制器上是原子的?

    以下是 STM32 微控制器上的数据类型 http www keil com support man docs armcc armcc chr1359125009502 htm http www keil com support man d
  • ARM架构中不同处理器模式下如何使用内核堆栈?

    据我了解 每个进程都有一个用户堆栈和内核堆栈 除此之外 ARM 架构中的每种模式都有一个堆栈 所以我想知道不同的堆栈和堆栈指针在 ARM 模式下如何工作 另外 何时会使用与进程关联的内核堆栈 何时会使用与进程关联的内核堆栈 当您进行系统调用
  • DSP 库 - RFFT - 奇怪的结果

    最近我一直在尝试在我的STM32F4 Discovery评估板上进行FFT计算 然后将其发送到PC 我已经调查了我的问题 我认为我对制造商提供的 FFT 函数做错了 我正在使用 CMSIS DSP 库 现在我一直在用代码生成样本 如果工作正
  • GCC ARM 汇编预处理器宏

    我正在尝试使用汇编 ARM 宏进行定点乘法 define MULT a b asm volatile SMULL r2 r3 0 1 n t ADD r2 r2 0x8000 n t ADC r3 r3 0 n t MOV 0 r2 ASR
  • 交叉编译 Qt 4.7 时出现“非法指令”

    我已经在这个问题上苦苦挣扎了一个多星期了 但仍然找不到解决方案 我正在尝试为 ARM 设备交叉编译 Qt 4 7 嵌入式开源版本 构建过程本身可以顺利完成 但生成的二进制文件似乎包含处理器无法理解的指令 构建主机是 i386 上的 Debi
  • 分析 Cortex-M7 (stm32f7) 上的 memcpy 性能

    简洁版本 从 GNU ARM 工具链中提取的 memcpy 的性能指标在 ARM Cortex M7 上对于不同的副本大小似乎差异很大 即使复制数据的代码始终保持不变 这可能是什么原因造成的 长版 我是使用 GNU Arm 工具链 11 2
  • STM32 传输结束时,循环 DMA 外设到存储器的行为如何?

    我想问一下 在以下情况下 STM32 中的 DMA SPI rx 会如何表现 我有一个指定的 例如 96 字节数组 名为 A 用于存储从 SPI 接收到的数据 我打开循环 SPI DMA 它对每个字节进行操作 配置为 96 字节 是否有可能
  • 是否可以将 SpaCy 安装到 Raspberry Pi 4 Raspbian Buster

    我一整天都在安装 SpaCy sudo pip install U spacy Looking in indexes https pypi org simple https www piwheels org simple Collectin
  • saber sd 如何在没有 SPL 的情况下直接从 uboot 启动

    sabre sd 基于 imx 6 最大内部 RAM 约为 150Kb 然而 uboot 足够大 可以容纳在这个空间中 在这个场景中事情是如何进行的 https community freescale com docs DOC 95015
  • 使用 NEON 内在函数除以浮点数

    我当时正在处理四个像素的图像 这是在armv7对于 Android 应用程序 我想分一个float32x4 t向量由另一个向量组成 但其中的数字与大约不同0 7 to 3 85 在我看来 除法的唯一方法是使用右移 但这是针对一个数字2 n

随机推荐

  • TypeScript类型注解和类型推断

    类型注解和类型推断 类型注解 let count number count 123 这段代码就是类型注解 意思是显示的告诉代码 我们的count变量就是一个数字类型 这就叫做类型注解 当你明白了类型注解的概念之后 再学类型推断就更简单了 先
  • 类的定义方法

    一 类的定义方法 注意 1 类定义最后的分号不能省略 2 类中的数据成员不能在类中进行初始化 需要在构造函数中初始化 3 类名最好能体现其功能 首字母建议大写 二 类的实现方法 1 在类定义时同时实现 若成员函数中在类中定义 则被计算机自动
  • 陀螺产业区块链第四季

    2020年4月 国家发改委在例行新闻发布会上宣布区块链被正式列为新型基础设施中的信息基础设施 自此区块链正式搭上新基建的 风口 与传统基础设施建设相比 新型基础设施建设更加侧重于突出产业转型升级的新方向 无论是5G还是区块链 都体现出加快推
  • SIFT特征提取-应用篇

    SIFT特征具有缩放 旋转特征不变性 下载了大牛的matlab版SIFT特征提取代码 解释如下 1 调用方法 将文件加入matlab目录后 在主程序中有两种操作 op1 寻找图像中的Sift特征 image descrips locs si
  • 最全iOS 应用上架流程(提交到AppStore)

    一 上架基本需求资料 1 苹果开发者账号 公司已有可以不用申请 需要开通开发者功能 每年 99 美元 2 开发好的APP 借助辅助工具appuploader创建证书跟描述文件 二 证书 描述文件 借助辅助工具appuploader 上架版本
  • django runserver开启服务(开启外网访问与ipv6访问)

    首先setting要设置 ALLOWED HOSTS 然后在启动时 使用 python manage py runserver 8000 或是 python manage py runserver 8000 这样 开启外网访问 ipv4与i
  • numpy中argsort()排序函数的使用

    argsort 函数返回的是数组从小到大的索引值 直接上例子 一 对于一维数组 1 升序排列 默认是升序 import numpy as np 一维数组升序排列 data np array 3 5 1 np argsort data 输出结
  • 数据库原理-关系模式的规范化

    关系数据库的规范化理论是数据库逻辑设计的工具 一个关系只要其分量都是不可分的数据项 它就是规范化的关系 但这只是最基本的规范化 规范化程度可以有6个不同的级别 即6个范式 规范化程度过低的关系不一定能够很好地描述现实世界 可能会存在插入异
  • 通过WIndows命令行访问MySQL数据库

    在cmd命令行里输入mysql h127 0 0 1 uroot p用户密码 这样就可以连接了 使用 show databases 这个命令可以显示出所有的database表以方便我们查看 输入 quit 退出
  • @vant/weapp

    文章目录 一 介绍 二 安装 1 cd 到项目文件目录 2 使用 npm 安装 3 修改项目配置 4 构建 5 其他文件 三 使用 四 参考 微信小程序使用 vant weapp组件 一 介绍 Vant 是一个开源的移动端组件库 在微信小程
  • 基本的tcp套接口编程

    基本的tcp套接口编程 1 socket函数 为了执行网络I O 一个进程必须做的第一件事情是调用socket函数 指定期望的通信协议类型 include
  • 基于dlib的目标追踪

    之前都在做目标检测 训练自己的检测器做检测 缺点就是电脑性能稍微差点的话 预测一次的时间就会稍久 我做的又正好是视频预测 所以连续播放都卡成PPT了 无奈之下选择抽帧检测 于是那闪烁效果堪比蹦迪现场的灯光 最后拍板定案的方法就是目标追踪了
  • 软件开发项目流程 - 立项

    引言 在互联网行业从事开发工作三年余 虽然时间不长 但对于开发流程也有一定的认识 写一篇关于这方面的内容 以记录自己的成长历程 引言 目的 立项流程 标准项目管理 总结 目的 当我们发现市场上有一个项目有利可图 且我们有能力做的时候 发起的
  • 实用的Qt窗口标志(Qt::WindowFlags)

    窗口标志枚举类型用于指定小部件的各种窗口系统属性 其中一些标志取决于底层窗口管理器是否支持它们 以下是一些比较实用的窗口标志 1 Qt Widget 这是QWidget的默认类型 如果有父窗口则为子窗口小部件 如果没有父窗口则为独立窗口小部
  • 微积分基础知识

    微积分基础知识 前言 在信息学算法竞赛中 常有些数学类问题需用到微积分 或用到微积分的算法 如母函数 HNOI2012排队 这里本人总结了一下竞赛中常用的微积分基础知识 供大家参考 有写得不好的地方请多多包涵 感谢学长帮助修改审校 若有错误
  • 技术篇之蓝牙Mesh设备是如何加入网络中

    概述 配网 Provisioning 流程属于蓝牙Mesh网络中的重要一环 正是通过配网流程 才使得蓝牙Mesh设备 Device 变成网络中的一个节点 Node 因此 本文将着重讲解配网流程及其相关概念 希望能够给读者以清晰的理解 名词解
  • SOA和伪技术

    中国的造假之风 已经成为整个国家整个社会的一个老大难问题 从最开始的假烟假酒 到后来的假名牌假新闻 从最个人化的假球迷假学历 到严重社会化的假文凭假鸡蛋 可以说是 长江后假推前假 一代新假换旧假 沉舟侧畔有千假 病树前头有十假 科学领域的造
  • 跨站脚本攻击(XSS) 漏洞原理及防御方法

    注 转载请注明出自 https blog csdn net qq 36711453 article details 83745195 XSS跨站脚本攻击 两种情况 一种通过外部输入然后直接在浏览器端触发 即反射型XSS 还有一种则是先把利用
  • react组件在页面切换时,如果页面组件已经被销毁,终止已销毁页面的请求的解决方案

    就比如说下面这个请求时间太长了 在它还没有请求成功就切换了页面 销毁了页面组件 useEffect就会给它设置成true 抛弃这个请求 const ignore useRef false useEffect gt return gt ign
  • 【STM32实战】机械臂快递分拣系统(二)——机械臂控制程序(上位机)编写

    STM32实战 机械臂快递分拣系统 二 机械臂控制程序 上位机 编写 前言 题目分析 蓝牙模块的使用 上位机程序的编写 连接阿里云 测试 前言 近期回校上最后一门课 刚好是做机械臂有关的题目 所以写文记录一下 主要实现的是可以自动识别获取快