STM32循迹PWM调速及阿克曼舵机转向

1,准备

红外循迹模块,L298N电机驱动模块,STM32C8T6最小系统,SG90舵机,KEIL5

2,涉及的理论知识和硬件参考

理论

阿克曼转向是一种现代汽车的转向方式,在汽车转弯的时候,内外轮转过的角度不一样, 内侧轮胎转弯半径小于外侧轮胎。下图就是理想的阿克曼转向。

根据阿克曼转向几何设计转向机构,在车辆沿着弯道转弯时,利用四连杆的相等曲柄, 可以使内侧轮的转向角比外侧轮大大约 2~4 度,使四个轮子路径的圆心大致上交会于后轴的 延长线上瞬时转向中心,从而让车辆可以顺畅的转弯。 

如果把每个轮子的运动半径画出来的话,就会得到图上所示画面。倒库比正着往里开更容易,因为后轮的运动半径小于前轮的 运动半径,所以相对于两前轮而言,后轮的运动范围更小,这也就意味着更容易将车“塞” 进停车位。

在车辆转弯的时候,内侧车轮的转弯半径明显会短于外侧车轮。这时,如果我们希望两 个转向轮可以正常转弯,就需要让内侧车轮的转角大于外侧车轮。

小车硬件


(1)红外循迹模块

一般就是数字红外输出和模拟红外输出,两种都可以,最终都是检测红外光返回是否符合要求。比如数字红外,当检测到黑线时,大部分红外光被吸收,红外模块引脚输出高电平1,当无黑线时,红外模块引脚输出低电平0。

(2)L298N驱动模块

这WE里采用六线接口L298N,即ENA,ENB,IN1,IN2,IN3,IN4.根据资料说明,由于本代码采用PWM调速,所以ENA,ENB作为PWM接收端。

(3)SG90舵机

 SG90舵机的控制需要一个20ms时基脉冲,该脉冲的高电平部分一般为0.5ms~2.5ms范围

PWM周期为20ms = (7200*200)/72000000=0.02所以TIM_Period = 199,TIM_Prescaler = 7199

(4)STM32C8T6最小系统

通用定时器2和4,GPIO,时钟配置等。这里有个细节就是定时器与管脚选择有关,需看最小系统芯片原理图为基础配置定时器。

3,代码实践(以下代码已完整,可自行调试)


(1)配置各个控制连接引脚及功能



void OUT_Init(void)//控制电机引脚配置
{
	     GPIO_InitTypeDef GPIO_InitStructure;
	     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);

		 PIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
	     GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_12|GPIO_Pin_13;
	     GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	     GPIO_Init(GPIOB,&GPIO_InitStructure);

		 GPIO_ResetBits(GPIOB,GPIO_Pin_13);
		 GPIO_ResetBits(GPIOB,GPIO_Pin_12);
		 GPIO_ResetBits(GPIOB,GPIO_Pin_0);
		 GPIO_ResetBits(GPIOB,GPIO_Pin_1);

}
    
void TIM2_PWM_Init(u16 arr,u16 psc)//PWM控制调速电机输出配置
{  
	GPIO_InitTypeDef GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	TIM_OCInitTypeDef  TIM_OCInitStructure;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);//开启定时器2时钟
 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO,ENABLE);  
	GPIO_PinRemapConfig(GPIO_FullRemap_TIM2,ENABLE);
                                                       	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; 
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; 
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);

	
	TIM_TimeBaseStructure.TIM_Period = arr;
	TIM_TimeBaseStructure.TIM_Prescaler =psc;
	TIM_TimeBaseStructure.TIM_ClockDivision = 0; 
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); 

	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; 
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
	TIM_OCInitStructure.TIM_Pulse =0; 
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; 
	TIM_OC3Init(TIM2, &TIM_OCInitStructure);  
    TIM_OCInitStructure.TIM_Pulse =0; 
	TIM_OC4Init(TIM2, &TIM_OCInitStructure); 

    TIM_OC3PreloadConfig(TIM2, TIM_OCPreload_Enable);   	
	TIM_OC4PreloadConfig(TIM2, TIM_OCPreload_Enable);   
	TIM_ARRPreloadConfig(TIM2, ENABLE); 
    TIM_Cmd(TIM2, ENABLE);  

}
   
void TIM4_PWM_Init(u16 arr1,u16 psc1)//PWM输出控制舵机角度引脚及功能配置
{ 
	 GPIO_InitTypeDef GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	TIM_OCInitTypeDef  TIM_OCInitStructure;
	

 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);  
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);                                        	

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //B_8引脚
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);

	TIM_TimeBaseStructure.TIM_Period = arr1; 
	TIM_TimeBaseStructure.TIM_Prescaler =psc1;  
	TIM_TimeBaseStructure.TIM_ClockDivision = 0; 
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  
	TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); 

	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; 
	TIM_OCInitStructure.TIM_Pulse = 0;
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; 
	TIM_OC3Init(TIM4, &TIM_OCInitStructure); 

	TIM_OC3PreloadConfig(TIM4, TIM_OCPreload_Enable);   
	TIM_ARRPreloadConfig(TIM4, ENABLE); 
	TIM_Cmd(TIM4, ENABLE);  
 
	}
               
void INFR_Init(void)//红外引脚接收引脚配置
{
	GPIO_InitTypeDef GPIO_InitStructure;	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//GPIOB时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);//GPIOC时钟//此处由于引脚数量限制开启两个GPIO始终端口
	
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;//此处配置为浮空输入模式,便于检测红外模块高低电平状态,以此判断循迹状态
 
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
  
 
GPIO_ResetBits(GPIOA,GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7);
	
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_15;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOC,&GPIO_InitStructure);
	GPIO_ResetBits(GPIOC,GPIO_Pin_15);

	
		   
}
          

(2)电机运动模式配置

void forward(void)
{	
	TIM_SetCompare3(TIM4,187);//-------PB_8----定时器4通道3控制舵机转向
	TIM_SetCompare3(TIM2,1000);//-------ENA--PB_10--定时器2通道3电机调速
	GPIO_SetBits(GPIOB,GPIO_Pin_12);//-----IN1--PB_12
	GPIO_ResetBits(GPIOB,GPIO_Pin_13);//----IN2--PB_13
	
	TIM_SetCompare4(TIM2,1000);//-------ENB---PB_11-定时器2通道4电机调速
	GPIO_SetBits(GPIOB,GPIO_Pin_0);//----IN3---PB_0	
	GPIO_ResetBits(GPIOB,GPIO_Pin_1);//-----IN4---PB_1
}

void turnleft(void)//
{		
	TIM_SetCompare3(TIM4,150);
	TIM_SetCompare3(TIM2,50);//-------ENA--PB10--
	GPIO_SetBits(GPIOB,GPIO_Pin_12);//-----IN1
	GPIO_ResetBits(GPIOB,GPIO_Pin_13);//----IN2
	
	TIM_SetCompare4(TIM2,1000);//-------ENB---PB11-
	GPIO_SetBits(GPIOB,GPIO_Pin_0);//----IN3
	GPIO_ResetBits(GPIOB,GPIO_Pin_1);//-----IN4
}
void turnright(void)//
{
  TIM_SetCompare3(TIM4,100);
	TIM_SetCompare3(TIM2,1000);//-------ENA--PB10
	GPIO_SetBits(GPIOB,GPIO_Pin_12);//-----IN1
	GPIO_ResetBits(GPIOB,GPIO_Pin_13);//----IN2	
	
	TIM_SetCompare4(TIM2,50);//-------ENB---PB11
	GPIO_SetBits(GPIOB,GPIO_Pin_0);//----IN3
	GPIO_ResetBits(GPIOB,GPIO_Pin_1);//-----IN4
}
void stop(void)
{
	  TIM_SetCompare3(TIM2,1500);//pwm
	  TIM_SetCompare4(TIM2,1500);
	  GPIO_ResetBits(GPIOB,GPIO_Pin_12);//·
	  GPIO_ResetBits(GPIOB,GPIO_Pin_13);
	
	  GPIO_ResetBits(GPIOB,GPIO_Pin_0);//
	  GPIO_ResetBits(GPIOB,GPIO_Pin_1);
}



void turnleft2(void)//
{
  TIM_SetCompare3(TIM4,150);
	TIM_SetCompare3(TIM2,50);//-------ENA--PB10--
	GPIO_SetBits(GPIOB,GPIO_Pin_12);//-----IN1
	GPIO_ResetBits(GPIOB,GPIO_Pin_13);//----IN2
	
	TIM_SetCompare4(TIM2,1000);//-------ENB---PB11--
	GPIO_SetBits(GPIOB,GPIO_Pin_0);//----IN3
	GPIO_ResetBits(GPIOB,GPIO_Pin_1);//-----IN4
}
void turnright2(void)//
{
	TIM_SetCompare3(TIM4,100);
	TIM_SetCompare3(TIM2,1000);//-------ENA--PB10
	GPIO_SetBits(GPIOB,GPIO_Pin_12);//-----IN1
	GPIO_ResetBits(GPIOB,GPIO_Pin_13);//----IN2	
	
	TIM_SetCompare4(TIM2,50);//-------ENB---PB11
	GPIO_SetBits(GPIOB,GPIO_Pin_0);//----IN3
	GPIO_ResetBits(GPIOB,GPIO_Pin_1);//-----IN4
}

(3)实现主函数搭配功能融合

int main(void)
{
	int LXJ0,LXJ1,LXJ2,LXJ3,XJ4,RXJ5,RXJ6,RXJ7,RXJ15=0;
	
                                 INFR_Init();
                                 OUT_Init();
                                 delay_init();
                                 TIM4_PWM_Init(2000,719);
                                 TIM2_PWM_Init(2000,719);
                                 delay_ms(500);
                                 elay_ms(500);
                                 delay_ms(500);
                                 delay_ms(500);¯
	


while(1) 
{ 
	        LXJ0=GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0);//读取红外引脚高低电平,
			LXJ1=GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_1);//以此判断是否在黑线上
		    LXJ2=GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_2);
			LXJ3=GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_3);	
		    XJ4=GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_4);
		    RXJ5=GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_5);
		    RXJ6=GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6);
			RXJ7=GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_7);
	        RXJ15=GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_15);
      		if((XJ4==1&&LXJ1==0&&LXJ2==0&&LXJ3==0&&RXJ5==0&&RXJ6==0&&RXJ7==0&&RXJ15==0&&LXJ0==0))
			 {		
				 forward();	 			 
			 }
			else if(LXJ3==1|XJ4==1&&LXJ0==0&&LXJ2==0&&LXJ3==0&&RXJ5==0&&RXJ6==0&&RXJ7==0&&RXJ15==0&&XJ4==0)
			 {		 
		    turnright();		 
			 }
			 else if(XJ4==1|RXJ5==1&&LXJ1==0&&LXJ0==0&&LXJ3==0&&RXJ5==0&&RXJ6==0&&RXJ7==0&&RXJ15==0&&XJ4==0)
			 {		
				  turnleft();						 	 
			 }
		   else if(LXJ0==1|LXJ1==1|LXJ2==1&&LXJ1==0&&LXJ2==0&&LXJ0==0&RXJ5==0&&RXJ6==0&&RXJ7==0&RXJ15==0&&XJ4==0)
			 {		
				  turnright2();	 
			 }
			
	  	 else if(RXJ6==1|RXJ7==1|RXJ15==1 &&LXJ1==0&&LXJ2==0&&LXJ3==0&&RXJ5==0&&RXJ6==0&&RXJ7==0&&RXJ15==0&&XJ4==0)
			 {		
				  turnleft2(); 	 
			 }
			

### 构建任务失败解决方案 当遇到 `Execution failed for task ':app:shrinkReleaseRes'` 错误时,这通常意味着资源压缩过程中出现了问题。此错误可能由多种原因引起,包括但不限于配置不正确、依赖冲突或特定于项目的其他因素。 #### 可能的原因分析 1. **ProGuard 或 R8 配置不当** ProGuard 和 R8 是用于优化和混淆代码以及减少 APK 大小的工具。如果这些工具的配置存在问题,可能会导致资源无法正常处理[^1]。 2. **重复资源** 如果项目中有多个模块定义了相同的资源名称,可能导致冲突并引发该错误。检查是否存在重名的 drawable、string 等资源文件[^2]。 3. **第三方库兼容性** 某些第三方库可能当前使用的 Gradle 插件版本或其他库存在兼容性问题,从而影响到资源打包过程中的行为[^3]。 4. **Gradle 缓存问题** 有时旧缓存数据会干扰新编译的结果,尝试清理本地仓库和重新同步项目可以帮助排除此类潜在障碍[^4]。 #### 推荐的操作方法 为了有效解决问题,建议按照以下步骤逐一排查: ```bash # 清理项目构建目录 ./gradlew clean # 删除 .gradle 文件夹下的所有内容以清除缓存 rm -rf ~/.gradle/caches/ ``` 调整 `build.gradle` 中的相关设置也是一个重要环节: ```groovy android { ... buildTypes { release { minifyEnabled true // 是否启用代码缩减 shrinkResources true // 是否开启资源压缩 proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' // 尝试禁用 shrinkResources 来测试是否为资源压缩引起的错误 // shrinkResources false } } } ``` 此外,在 `proguard-rules.pro` 文件内添加必要的保留规则,防止关键类被意外移除: ```text -keep class com.example.yourpackage.** { *; } # 替换为你自己的包路径 -dontwarn androidx.**,com.google.** # 忽略警告信息 ``` 最后,确保所使用的 Android Studio 版本是最新的稳定版,并且已经应用了所有的补丁更新。
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值