STM32 HAL库快速实战总集《完整项目重构》--基于黑龙江科技大学机电工业机器人实训

系列目录

点击查看

前言

在前面章节,我们完成了所有子项目的构建与测试。但是随着子项目的增加,代码趋向混乱,不利于阅读与修改。这里将对整个项目进行重构,提供一个最终使用版本。

项目重构

控制功能重构

在之前的项目中,获取命令使用的是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");
		}
	}
}
然后在robot-arm.h中声明对应的任务
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

posted @ 2021-12-13 18:00  USTHzhanglu  阅读(660)  评论(0编辑  收藏  举报