【STM32实战】机械臂快递分拣系统(一)——机械臂控制程序编写
前言
近期回校上最后一门课,刚好是做机械臂有关的题目,所以写文记录一下。主要实现的是可以自动识别获取快递位置,机械臂可以抓取快递,以及根据自动识别快递上的条形码获得目的地点,机械臂可以将快递抓取并移动到目的地点,此外就是还可以通过上位机来控制机械臂,上位机可以是PC,也可以是另一部STM32,需要有图形化界面。
题目分析
自动识别获取快递位置以及识别快递上的条形码需要用到摄像头模块,需要解决STM32和摄像头模块之间的通信问题,以及快递形状。通过上位机来控制机械臂则需要使用到蓝牙或者WIFI模块,图形化界面可以使用物联网云平台来开发,这里用的是阿里云。如果使用另一部32进行控制,则还可在另一部32上做界面。 但购买的机械臂是已经组装完毕的,且上面带有STM32系统板,型号为STM32F103C8T6,只引出了串口3的接口,也就是说如果要连接摄像头模块,就无法与上位机进行通信。所以我打算将该机械臂作为一个下位机来接收命令,上位机采用另一部STM32来做,上位机来处理串口接收到的来自云平台或者摄像头的数据,然后算出各个舵机的PWM占空比重装载值,通过蓝牙模块发送到下位机机械臂,以此进行控制。
工程模板生成
这部分因人而异,如果是自己组装的机械臂,则PWM输出引脚设置上比较自由,但我这一款机械臂是已经组装完毕的,因此需要根据这一款机械臂的原理图来设置对应引脚,方便起见,这里采用STM32CubeMX来生成工程模板,机械臂上的STM32系统板引脚说明如下。 点击 ACCESS TO MCU SELECTOR 搜索并选择F103C8T6,如果你的主控是其他型号,就选择其他型号。 点击选择高速时钟为外部晶振。 如图所示设置时钟频率为72M。 设置串口1为 Asynchronous,这个串口在我这款机械臂的STM32系统板上接的是CH340,转成USB。 同样的方法设置串口3。 如果有Debug需求的话可以设置一下SW的调试方式,我刚创建这个工程模板的时候是有设置的,后来发现如果只是用来接收命令的话也没什么必要。 然后就是根据上面的引脚说明图设置PWM了,PB3在TIM2的CH2上。 PB4在TIM3的CH1上。 其他的引脚都在TIM4上,因为TIM4默认PWM通道就是这几个引脚,就不需要重映射。 然后设置一下TIM2,TIM3,TIM4的预分频系数71,重装载值19999,使能自动重装载即可。 然后设置一下定时器1,这个主要用来控制各任务运行周期。 然后就是在NVIC中使能一下中断。 然后设置好你要保存到路径和工程名字,IDE。 这里我习惯只复制用得到的库,以及生成额外的c和h文件。 然后生成代码即可。
蓝牙模块的使用
蓝牙模块就是一种透传设备,串口给它发送什么,它就接收什么,然后它将接收到的数据通过某种协议发送到跟它配对的蓝牙上,这个蓝牙将这些数据解析,最后还原成最初的未经处理的数据,这就是透明传输意思。这跟使用杜邦线将两个模块串口的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,可以看到另一个蓝牙成功接收到了,说明配对成功了。
蓝牙接收数据解析与机械臂控制
这里需要用到如下代码文件,将这些代码文件添加进工程即可。 uartconfig.c
#include "uartconfig.h"
void Data_Transmit(DataTransmit *data, UART_HandleTypeDef *huart)
{
HAL_UART_Transmit(huart, data -> transmit_data, data -> cnt , 0xFFFF);
}
void Data_Pack_Transmit(DataTransmit *data, UART_HandleTypeDef *huart)
{
Data_Pack(data);
Data_Transmit(data,huart);
}
uint8_t Buffer_Receive(DataReceive *data, UART_HandleTypeDef *huart)
{
HAL_UART_Receive_IT(huart, &data -> data, 1);
return data -> data;
}
uartconfig.h
#ifndef __UARTCONFIG_H
#define __UARTCONFIG_H
#include "stm32f1xx_hal.h"
#include "uartprotocol.h"
typedef signed char int8_t;
typedef short int int16_t;
typedef int int32_t;
typedef unsigned char uint8_t;
typedef unsigned short int uint16_t;
typedef unsigned int uint32_t;
void Data_Transmit(DataTransmit *data, UART_HandleTypeDef *huart);
void Data_Pack_Transmit(DataTransmit *data, UART_HandleTypeDef *huart);
uint8_t Buffer_Receive(DataReceive *data, UART_HandleTypeDef *huart);
#endif
uartprotocol.c
#include "uartprotocol.h"
void Data_Transmit_Init(DataTransmit *data, uint8_t head1, uint8_t head2, uint8_t length)
{
data -> head1 = head1;
data -> head2 = head2;
data -> length = length;
data -> cnt = length + 4;
for(uint8_t i = 0; i < length; i++)
{
data -> data[i] = 0;
}
for(uint8_t j = 0; j < length + 4; j++)
{
data -> transmit_data[j] = 0;
}
}
void Data_Pack(DataTransmit *data)
{
data -> transmit_data[0] = data -> head1;
data -> transmit_data[1] = data -> head2;
data -> transmit_data[2] = data -> length;
for(uint8_t i = 0; i < data -> length; i++)
{
data -> transmit_data[3+i] = data -> data[i];
}
uint8_t sum = 0;
for(uint8_t j = 0; j < data -> length + 3; j++)
{
sum = sum + data -> transmit_data[j];
}
data -> transmit_data[data -> length + 3] = sum;
}
void Data_Receive_Init(DataReceive *data, uint8_t head1, uint8_t head2)
{
data -> head1 = head1;
data -> head2 = head2;
data -> length = 0;
data -> cnt = 0;
data -> state = 0;
data -> i = 0;
data -> data = 0;
for(uint8_t j = 0; j < 50; j++)
{
data -> receive_data[j] = 0;
}
}
void Data_Receive(DataReceive *data, uint8_t buf)
{
if(data -> state == 0 && buf == data -> head1)
{
data -> state = 1;
data -> receive_data[0] = buf;
}
else if(data -> state == 1 && buf == data -> head2)
{
data -> state = 2;
data -> receive_data[1] = buf;
}
else if(data -> state == 2 && buf < 40)
{
data -> state = 3;
data -> length = buf;
data -> cnt = buf+4;
data -> receive_data[2] = buf;
}
else if(data -> state == 3 && data -> length > 0)
{
data -> length = data -> length - 1;
data -> receive_data[3 + data -> i] = buf;
data -> i = data -> i + 1;
if(data -> length == 0)
{
data -> state = 4;
}
}
else if(data -> state == 4)
{
data -> receive_data[3 + data -> i] = buf;
data -> state = 0;
data -> i = 0;
}
else
{
data -> state = 0;
data -> i = 0;
}
}
void Target_Init(TargetProperty *target)
{
target -> servo1 = 0;
target -> servo2 = 0;
target -> servo3 = 0;
target -> servo4 = 0;
target -> servo5 = 0;
target -> servo6 = 0;
}
void Target_Parse(DataReceive *data, TargetProperty *target)
{
uint8_t sum = 0;
uint8_t i = 0;
while(i < data -> cnt - 1)
{
sum = sum + data -> receive_data[i];
i = i + 1;
}
if(sum == data -> receive_data[data -> cnt - 1])
{
target -> servo1 = data -> receive_data[3]*256 + data -> receive_data[4];
target -> servo2 = data -> receive_data[5]*256 + data -> receive_data[6];
target -> servo3 = data -> receive_data[7]*256 + data -> receive_data[8];
target -> servo4 = data -> receive_data[9]*256 + data -> receive_data[10];
target -> servo5 = data -> receive_data[11]*256 + data -> receive_data[12];
target -> servo6 = data -> receive_data[13]*256 + data -> receive_data[14];
}
}
uartprotocol.h
#ifndef __UARTPROTOCOL_H
#define __UARTPROTOCOL_H
typedef signed char int8_t;
typedef short int int16_t;
typedef int int32_t;
typedef unsigned char uint8_t;
typedef unsigned short int uint16_t;
typedef unsigned int uint32_t;
typedef struct
{
uint8_t head1;
uint8_t head2;
uint8_t length;
uint8_t cnt;
uint8_t data[40];
uint8_t transmit_data[50];
}DataTransmit;
void Data_Transmit_Init(DataTransmit *data, uint8_t head1, uint8_t head2, uint8_t length);
void Data_Pack(DataTransmit *data);
typedef struct
{
uint8_t head1;
uint8_t head2;
uint8_t length;
uint8_t cnt;
uint8_t state;
uint8_t i;
uint8_t data;
uint8_t receive_data[50];
}DataReceive;
void Data_Receive_Init(DataReceive *data, uint8_t head1, uint8_t head2);
void Data_Receive(DataReceive *data, uint8_t buf);
typedef struct
{
uint16_t servo1;
uint16_t servo2;
uint16_t servo3;
uint16_t servo4;
uint16_t servo5;
uint16_t servo6;
}TargetProperty;
void Target_Init(TargetProperty *target);
void Target_Parse(DataReceive *data, TargetProperty *target);
#endif
然后在main.c中的这些位置添加如下代码即可,这部分逻辑简单,我也将注释都写在后面了,就不再解释了。 /* USER CODE BEGIN Includes */
#include "uartconfig.h"
#include "uartprotocol.h"
/* USER CODE BEGIN 0 */
uint16_t task_num = 0;
uint16_t task_flag = 0;
uint16_t task_max = 40;
DataTransmit data_transmit_uart3;
DataReceive data_receive_uart3;
TargetProperty tmaster;
TargetProperty tslave;
void System_Init(void);
void Task_Run(void);
/* USER CODE BEGIN 2 */
System_Init();
/* USER CODE BEGIN 3 */
Task_Run();
/* USER CODE BEGIN 4 */
void System_Init(void)
{
Data_Transmit_Init(&data_transmit_uart3,0xFC,0xAA,12);
Data_Receive_Init(&data_receive_uart3,0xFC,0xAA);
Target_Init(&tmaster);
Target_Init(&tslave);
tslave.servo1 = 1500;
tslave.servo2 = 1500;
tslave.servo3 = 2100;
tslave.servo4 = 1500;
tslave.servo5 = 1500;
tslave.servo6 = 1500;
HAL_TIM_Base_Start_IT(&htim1);
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_2);
HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_2);
HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_3);
HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_4);
Buffer_Receive(&data_receive_uart3,&huart3);
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if( htim == (&htim1) )
{
if(task_num < task_max)
{
task_num = task_num + 1;
task_flag = 1;
}
else
{
task_num = 0;
task_flag = 1;
}
}
}
void Task_Run(void)
{
if(task_num == 0 && task_flag == 1)
{
data_transmit_uart3.data[0] = tmaster.servo1/256;
data_transmit_uart3.data[1] = tmaster.servo1%256;
data_transmit_uart3.data[2] = tmaster.servo2/256;
data_transmit_uart3.data[3] = tmaster.servo2%256;
data_transmit_uart3.data[4] = tmaster.servo3/256;
data_transmit_uart3.data[5] = tmaster.servo3%256;
data_transmit_uart3.data[6] = tmaster.servo4/256;
data_transmit_uart3.data[7] = tmaster.servo4%256;
data_transmit_uart3.data[8] = tmaster.servo5/256;
data_transmit_uart3.data[9] = tmaster.servo5%256;
data_transmit_uart3.data[10] = tmaster.servo6/256;
data_transmit_uart3.data[11] = tmaster.servo6%256;
Data_Pack_Transmit(&data_transmit_uart3, &huart3);
task_flag = 0;
}
else if(task_num == 10 && task_flag == 1)
{
Target_Parse(&data_receive_uart3,&tslave);
task_flag = 0;
}
else if(task_num == 20 && task_flag == 1)
{
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, tslave.servo1);
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_3, tslave.servo2);
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_4, tslave.servo3);
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_1, tslave.servo4);
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_2, tslave.servo5);
__HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_1, tslave.servo6);
task_flag = 0;
}
else if(task_num == 30 && task_flag == 1)
{
tmaster.servo1 = tslave.servo1;
tmaster.servo2 = tslave.servo2;
tmaster.servo3 = tslave.servo3;
tmaster.servo4 = tslave.servo4;
tmaster.servo5 = tslave.servo5;
tmaster.servo6 = tslave.servo6;
task_flag = 0;
}
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if( huart == &huart3 )
{
Data_Receive(&data_receive_uart3,Buffer_Receive(&data_receive_uart3,&huart3));
}
}
然后就可以编译烧入运行了,还是讲下运行函数。这里的task_num在定时器中断触发的时候会被加1,直到加到40然后清零,每一次触发定时器中断都会将task_flag置为1,在执行完任务后,task_flag将会置0,也就是说一段时间内不管执行几次Task_Run,里面的任务都只会执行一次。 task_num为0的时候,将接收到的数据发回给上位机,这一步可以没有,因为我是用这一步来判断发送是否出错,是一个回显的作用。 task_num为10的时候,将解析上位机发送过来的数据, task_num为20的时候,将解析后获得的各舵机占空比重装载值装载,调节占空比。 task_num为30的时候,将解析后的数据赋值给发送到上位机的参数,等待下一个task_num为0的时候,将数据发送给上位机,就是完成回显。
void Task_Run(void)
{
if(task_num == 0 && task_flag == 1)
{
data_transmit_uart3.data[0] = tmaster.servo1/256;
data_transmit_uart3.data[1] = tmaster.servo1%256;
data_transmit_uart3.data[2] = tmaster.servo2/256;
data_transmit_uart3.data[3] = tmaster.servo2%256;
data_transmit_uart3.data[4] = tmaster.servo3/256;
data_transmit_uart3.data[5] = tmaster.servo3%256;
data_transmit_uart3.data[6] = tmaster.servo4/256;
data_transmit_uart3.data[7] = tmaster.servo4%256;
data_transmit_uart3.data[8] = tmaster.servo5/256;
data_transmit_uart3.data[9] = tmaster.servo5%256;
data_transmit_uart3.data[10] = tmaster.servo6/256;
data_transmit_uart3.data[11] = tmaster.servo6%256;
Data_Pack_Transmit(&data_transmit_uart3, &huart3);
task_flag = 0;
}
else if(task_num == 10 && task_flag == 1)
{
Target_Parse(&data_receive_uart3,&tslave);
task_flag = 0;
}
else if(task_num == 20 && task_flag == 1)
{
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, tslave.servo1);
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_3, tslave.servo2);
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_4, tslave.servo3);
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_1, tslave.servo4);
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_2, tslave.servo5);
__HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_1, tslave.servo6);
task_flag = 0;
}
else if(task_num == 30 && task_flag == 1)
{
tmaster.servo1 = tslave.servo1;
tmaster.servo2 = tslave.servo2;
tmaster.servo3 = tslave.servo3;
tmaster.servo4 = tslave.servo4;
tmaster.servo5 = tslave.servo5;
tmaster.servo6 = tslave.servo6;
task_flag = 0;
}
}
测试
给舵机模块上电,这里可以用串口助手进行测试,帧头是FC,AA,有效数据长度是12,对应十六进制是0C,1500对应的十六进制是05 DC,校验码是F8,如果蓝牙成功连接上的话,舵机将会直立,因为1500对应的度数是90度(这个跟舵机安装方式有关)
如果要调节1号舵机,也就是底盘,对应的序号是3和4,这里将3改成04,因此校验码就要减1,变成F7
可以看到舵机转了,再测试一下二号舵机,将5改成04,校验码改成F6。 到这里不难发现用串口调试助手的缺点是,每一次调节舵机占空比都需要计算对应十六进制的值,比如1500的十六进制是05 DC,04 DC对应的是1244,虽然差是256,但是05 DC 和 04 DC相加的结果只差1,所以校验码是F8改成F7,计算会比较繁琐,这时上位机的作用就来了,但上位机部分的代码还比较杂乱,我还没注释,因此我们下期再见啦!
|