STM32 HAL库快速实战总集《完整项目重构》--基于黑龙江科技大学机电工业机器人实训
系列目录
点击查看
- STM32 HAL库快速实战【一】《32点灯》--基于黑龙江科技大学机电工业机器人实训 - USTHzhanglu - 博客园 (cnblogs.com)
- STM32 HAL库快速实战【三】《pwm控制舵机》--基于黑龙江科技大学机电工业机器人实训 - USTHzhanglu - 博客园 (cnblogs.com)
- STM32 HAL库快速实战【四】《串口简单使用》--基于黑龙江科技大学机电工业机器人实训 - USTHzhanglu - 博客园 (cnblogs.com)
- STM32 HAL库快速实战【五】《控制串口电机》--基于黑龙江科技大学机电工业机器人实训 - USTHzhanglu - 博客园 (cnblogs.com)
- STM32 HAL库快速实战【六】《蓝牙控制》--基于黑龙江科技大学机电工业机器人实训 - USTHzhanglu - 博客园 (cnblogs.com)
- STM32 HAL库快速实战【七】《机械臂控制》--基于黑龙江科技大学机电工业机器人实训 - USTHzhanglu - 博客园 (cnblogs.com)
- STM32 HAL库快速实战【八】《声音传感器的使用》--基于黑龙江科技大学机电工业机器人实训 - USTHzhanglu - 博客园 (cnblogs.com)
- STM32 HAL库快速实战【九】《超声波传感器的使用以及自由避障》--基于黑龙江科技大学机电工业机器人实训 - USTHzhanglu - 博客园 (cnblogs.com)
- STM32 HAL库快速实战【十】《颜色传感器的使用》--基于黑龙江科技大学机电工业机器人实训 - USTHzhanglu - 博客园 (cnblogs.com)
- STM32 HAL库快速实战【十一】《红外传感器的使用及巡线循迹》--基于黑龙江科技大学机电工业机器人实训 - USTHzhanglu - 博客园 (cnblogs.com)
前言
在前面章节,我们完成了所有子项目的构建与测试。但是随着子项目的增加,代码趋向混乱,不利于阅读与修改。这里将对整个项目进行重构,提供一个最终使用版本。
项目重构
控制功能重构
在之前的项目中,获取命令使用的是while一直等待,带来的后果就是在超声波循迹等需要while循环的地方,无法直接结束(在这里加入获取命令会一直等待,十分麻烦)。
添加串口中断
这里使用串口接收中断代码进行重写控制函数。
首先打开usart.c,定义一个命令缓冲符,用于存放接收到的命令
/* USER CODE BEGIN 0 */
uint8_t blue_cmd=NULL;
/* USER CODE END 0 */
然后加入中断回调函数
/* USER CODE BEGIN 1 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance==USART3)//如果触发USART3中断
{
HAL_UART_Receive_IT(&huart3,&blue_cmd,1);//打开中断,等待下一次中断的到来
}
}
/* USER CODE END 1 */
通过该函数,可以自动接收到串口3发送的字符,并存入blue_cmd中。
同时在main.c初始化的时候开启串口中断
/* USER CODE BEGIN 2 */
HAL_UART_Receive_IT(&huart3,&blue_cmd,1);//打开串口中断
然后在uasrt.h中加入
/* USER CODE BEGIN Private defines */
extern uint8_t blue_cmd;
/* USER CODE END Private defines */
这样就能在别的.c文件中使用这个变量。
重写get_cmd()
打开motor.c,将原来的get_cmd()注释掉,然后添加以下代码
char get_cmd(void)
{
uint8_t ch=NULL;//定义一个临时变量
if(blue_cmd!=NULL)//判断串口有没有接收到字符
{
usart_send_str(&huart3,&blue_cmd);//返回发送的字符,方便查看
ch=blue_cmd;//将blue_cmd存入ch中
blue_cmd=NULL;//清空缓冲区,这样做是避免发送两个命令之间,重复对前一次命令进行判断,会造成误识别。
return ch;//返回接收到的字符
}
return 0;//如果没有接收到字符,返回0
}
电机控制重构
将电机控制放在main.c中与后面的项目太格格不入了。
将mian.c中的电机控制部分剪切掉,打开motor.c,添加motor_task函数。
注意,为了方便电机与机械臂的控制,这里停止命令修改为Z。
点击查看代码
void motor_task(){
char input=NULL;
usart_send_str(&huart3,(unsigned char *)"Motor Control Begin!\r\n");
while(input!='Q'){
input=get_cmd();
if(input){
switch(input){
case 'B':
usart_send_str(&huart3,(unsigned char *)"car go\r\n");
motor_set(1000,1000);
break;
case 'C':
usart_send_str(&huart3,(unsigned char *)"car back\r\n");
motor_set(-1000,-1000);
break;
case 'D':
usart_send_str(&huart3,(unsigned char *)"car left\r\n");
motor_set(1000,0);
break;
case 'E':
usart_send_str(&huart3,(unsigned char *)"car right\r\n");
motor_set(0,1000);
break;
case 'Z':
usart_send_str(&huart3,(unsigned char *)"car stop\r\n");
motor_set(0,0);
break;
}
}
}usart_send_str(&huart3,(unsigned char *)"Motor Control End!\r\n");
}
然后在motor.h中添加函数声明
void motor_task(void);
然后在main.c中添加函数入口
case '1':motor_task();break;
舵机控制重构
之前的舵机控制为了方便调节角度,每发一个命令才动一次。对于手动控制,这样是非常慢的。这里复制一份get_rad重构为手动控制。
点击查看代码
/**
* @brief :手动控制舵机0到舵机5
* @param :None
* @retval None
**/
void arm_task(void)
{
char menu=NULL;
char menu_before=NULL;
int rad[6]={0,0,0,0,0,0};
while(menu!='Q'){
menu=get_cmd();
if(menu){menu_before=menu;}//如果有命令传来,备份命令到menu_before
if(!menu){menu=menu_before;}//如果没有命令传来,还原上一个命令
switch(menu)
{
case 'A':rad[0]++;break;
case 'B':rad[0]--;break;
case 'C':rad[1]++;break;
case 'D':rad[1]--;break;
case 'E':rad[2]++;break;
case 'F':rad[2]--;break;
case 'G':rad[3]++;break;
case 'H':rad[3]--;break;
case 'I':rad[4]++;break;
case 'J':rad[4]--;break;
case 'K':rad[5]++;break;
case 'L':rad[5]--;break;
case 'Z':break;//空过
}
HAL_Delay(50);//两次修改间隔,调节速度
arm_set(rad);
}
printf_rad(rad);//退出循环后打印当前角度
return;
}
在robot-arm.h中声明该函数void arm_task(void);
然后在main.c中添加函数入口
case '5':arm_task();break;
还有个问题就是舵机运动过快,且没法调整时间。
因此还需要重构arm_set()
之前的逻辑是,直接改变占空比,这样幅度很大。
如果一度一度的增加占空比,就可以减小幅度。并且通过在中间穿插延时函数,就可以调节速度。
首先声明一个机械臂状态变量。
在robot-arm.c顶部添加int arm_state[6]={0,0,0,0,0,0};
然后在robot-arm.h中声明该变量extern int arm_state[6];
重命名arm_set()为rad2compare()
rad2compare()
/**
* @brief :控制舵机0到舵机5,左上转为正,右下转为负,DJ0-5 +-135°,DJ6+-90°
* @param :rad[6],分别储存每个舵机转动角度
* @retval None
**/
void rad2compare(const int rad[6])
{
int compare[6]={
1500+rad[0]*arm_toggle[0]*1000/135,
1500+rad[1]*arm_toggle[1]*1000/135,
1500+rad[2]*arm_toggle[2]*1000/135,
1500+rad[3]*arm_toggle[3]*1000/135,
1500+rad[4]*arm_toggle[4]*1000/135,
1500+rad[5]*arm_toggle[5]*1000/90,
};
__HAL_TIM_SetCompare(&htim2,TIM_CHANNEL_2,compare[0]);
__HAL_TIM_SetCompare(&htim4,TIM_CHANNEL_3,compare[1]);
__HAL_TIM_SetCompare(&htim4,TIM_CHANNEL_4,compare[2]);
__HAL_TIM_SetCompare(&htim4,TIM_CHANNEL_1,compare[3]);
__HAL_TIM_SetCompare(&htim4,TIM_CHANNEL_2,compare[4]);
__HAL_TIM_SetCompare(&htim3,TIM_CHANNEL_1,compare[5]);
return;
}
然后重构set_rad()
set_rad()
void arm_set(const int rad[6])
{ int set_status=1;//定义标志符,判断是否需要继续循环
int diff[6]={
rad[0]-arm_state[0],
rad[1]-arm_state[1],
rad[2]-arm_state[2],
rad[3]-arm_state[3],
rad[4]-arm_state[4],
rad[5]-arm_state[5],
};//定义一个数组,计算需要转到的角度于当前角度的差值
while(set_status){
if (0 > diff[0]){diff[0]++;arm_state[0]--;}//如果差值小于0,说明当前位置需要正向运动,当前状态值--
else if (diff[0] > 0)diff[0]--,arm_state[0]++;//如果差值大于0,说明当前位置需要正向运动,当前状态值++
if (0 > diff[1])diff[1]++,arm_state[1]--;
else if(diff[1] > 0)diff[1]--,arm_state[1]++;
if(0 > diff[2])diff[2]++,arm_state[2]--;
else if(diff[2] > 0)diff[2]--,arm_state[2]++;
if(0 > diff[3])diff[3]++,arm_state[3]--;
else if(diff[3] > 0)diff[3]--,arm_state[3]++;
if(0 > diff[4])diff[4]++,arm_state[4]--;
else if(diff[4] > 0)diff[4]--,arm_state[4]++;
if(0 > diff[5])diff[5]++,arm_state[5]--;
else if(diff[5] > 0)diff[5]--,arm_state[5]++;
rad2compare(arm_state);
if((diff[0]==0)&&(diff[1]==0)&&(diff[2]==0)&&(diff[3]==0)&&(diff[4]==0)&&(diff[5]==0))set_status=0;//如果差值为0后,跳出循环
HAL_Delay(10);//舵机调节间隔,越长速度越慢
}
return;
}
入口函数重构
顺势把主函数里的switch重构下,修改下细节,使其不至于太难看。
点击查看代码
/* USER CODE BEGIN 3 */
input=get_cmd();
if(input){
switch(input){
case '1':
usart_send_str(&huart3,(unsigned char *)"Motor Control Begin!\r\n");
motor_task();
motor_set(0,0);
usart_send_str(&huart3,(unsigned char *)"Motor Control End!\r\n");break;
case '2':
usart_send_str(&huart3,(unsigned char *)"Hcsr Task Begin!\r\n");
while(get_cmd()!='Q'){hcsr_task();}
motor_set(0,0);
usart_send_str(&huart3,(unsigned char *)"Hcsr Task End!\r\n");break;
case '3':
usart_send_str(&huart3,(unsigned char *)"Color Task Begin!\r\n");
while(get_cmd()!='Q'){color_task();}
usart_send_str(&huart3,(unsigned char *)"Color Task End!\r\n");break;
case '4':
usart_send_str(&huart3,(unsigned char *)"XunJi Task Begin!\r\n");
while((xunji_task())&&(get_cmd()!='Q'));
usart_send_str(&huart3,(unsigned char *)"XunJi Task End!\r\n");break;
case '5':
usart_send_str(&huart3,(unsigned char *)"Arm Control Begin!\r\n");
arm_task();
usart_send_str(&huart3,(unsigned char *)"Arm Control End!\r\n");break;
default:usart_send_str(&huart3,(unsigned char *)"Error Cmd\r\n");break;
}
}
sound_task();
}
/* USER CODE END 3
现在每个子项目都能正确的进入和退出了。
颜色识别重构
目前识别到颜色后,没有对应的动作。按照实际思路,识别到不同颜色的物体,应该通过机械臂搬运到不同的地方。
首先在color_task()中添加相应任务程序,假设识别到红色,搬运到左边,识别到绿色,搬运到右边,识别到蓝色,搬运到后边。
color_task()
/**
* @brief :颜色传感器对应任务
* @param :无
* @retval 无
**/
void color_task(){
static uint32_t systick_ms_yanse = 0;
int millis=HAL_GetTick();//获取系统时间
uint8_t cmd_return[128];
char color_value;
if (millis - systick_ms_yanse > 20) {
systick_ms_yanse = HAL_GetTick();
color_value = get_adc_color_middle();//获取a0的ad值,计算出距离
if(color_value){
if(color_value=='R'){left_task();}
else if(color_value=='G'){right_task();}
else if(color_value=='B'){back_task();}
sprintf((char *)cmd_return, "Color = [%c]\r\n", color_value);
usart_send_str(&huart3,cmd_return);
color_value=NULL;
}else
{usart_send_str(&huart3,(uint8_t *)"no wood block \r\n");
}
}
}
void left_task(void);
void right_task(void);
void back_task(void);
别忘了在sensor.c顶部引入robot-arm.h文件。
然后再robot-arm.c中定义相应函数,加入对应动作组
动作组代码参考
首先调节好各种状态的角度,注意一定要自己调节,不同机器角度不同可能撞坏
/* 关节初始状态*/
const int arm_begin[6]={0,85,-135,-54,0,0};
/* 夹住物体*/
const int arm_clamp[6]={0,-35,-70,-59,0,0};
/* 运输物体*/
const int arm_doing[6]={0,-42,-37,0,0,0};
void left_task(){
arm_set(arm_doing);//切换到运输模式
HAL_Delay(1000);
arm_set(arm_clamp);//准备夹取
HAL_Delay(1000);
int arm_clamped[6];
memcpy(arm_clamped,arm_clamp,sizeof(arm_clamp));
arm_clamped[5]+=64;
arm_set(arm_clamped);//夹取
HAL_Delay(1000);
memcpy(arm_clamped,arm_doing,sizeof(arm_clamp));
arm_clamped[5]+=64;//切回到运输模式
arm_set(arm_clamped);
HAL_Delay(1000);
arm_clamped[0]+=90;//左转
arm_set(arm_clamped);
HAL_Delay(1000);
arm_clamped[1]=arm_clamp[1];
arm_clamped[2]=arm_clamp[2];
arm_clamped[3]=arm_clamp[3];
arm_clamped[4]=arm_clamp[4];
arm_set(arm_clamped);//准备放下
HAL_Delay(1000);
arm_clamped[5]=0;
arm_set(arm_clamped);//松开爪子
HAL_Delay(1000);
memcpy(arm_clamped,arm_doing,sizeof(arm_clamp));
arm_clamped[0]+=90;//切回到运输模式
HAL_Delay(1000);
arm_set(arm_clamped);
}
void right_task(){
arm_set(arm_doing);//切换到运输模式
HAL_Delay(1000);
arm_set(arm_clamp);//准备夹取
HAL_Delay(1000);
int arm_clamped[6];
memcpy(arm_clamped,arm_clamp,sizeof(arm_clamp));
arm_clamped[5]+=64;
arm_set(arm_clamped);//夹取
HAL_Delay(1000);
memcpy(arm_clamped,arm_doing,sizeof(arm_clamp));
arm_clamped[5]+=64;//切回到运输模式
arm_set(arm_clamped);
HAL_Delay(1000);
arm_clamped[0]-=90;//右转
arm_set(arm_clamped);
HAL_Delay(1000);
arm_clamped[1]=arm_clamp[1];
arm_clamped[2]=arm_clamp[2];
arm_clamped[3]=arm_clamp[3];
arm_clamped[4]=arm_clamp[4];
arm_set(arm_clamped);//准备放下
HAL_Delay(1000);
arm_clamped[5]=0;
arm_set(arm_clamped);//松开爪子
HAL_Delay(1000);
memcpy(arm_clamped,arm_doing,sizeof(arm_clamp));
arm_clamped[0]-=90;//切回到运输模式
HAL_Delay(1000);
arm_set(arm_clamped);
HAL_Delay(1000);
arm_set(arm_begin);//切回待机模式
}
void back_task(){
arm_set(arm_begin);//切回待机模式
arm_set(arm_doing);//切换到运输模式
HAL_Delay(1000);
arm_set(arm_clamp);//准备夹取
HAL_Delay(1000);
int arm_clamped[6];
memcpy(arm_clamped,arm_clamp,sizeof(arm_clamp));
arm_clamped[5]+=64;
arm_set(arm_clamped);//夹取
HAL_Delay(1000);
memcpy(arm_clamped,arm_doing,sizeof(arm_clamp));
arm_clamped[5]+=64;//切回到运输模式
arm_set(arm_clamped);
HAL_Delay(1000);
arm_clamped[1]=-arm_clamped[1];
arm_clamped[2]=-arm_clamped[2];
arm_clamped[3]=-arm_clamped[3];
arm_clamped[4]=-arm_clamped[4];
arm_set(arm_clamped);//镜像到后面
HAL_Delay(1000);
arm_clamped[5]=0;
arm_set(arm_clamped);//松开爪子
HAL_Delay(1000);
arm_set(arm_doing);
HAL_Delay(1000);
arm_set(arm_begin);//切回待机模式
}
工程源码
国内用户请使用gitee克隆或是使用代理访问Github
https://github.com/USTHzhanglu/stm32-hal/tree/main/robot-full-version