2020年电赛:基于PID算法的STM32控制坡道行驶电动小车详解

文章目录

  • 前言
  • 一、题目设计
  • 二、硬件选择
  • 三、软件设计
  • 四、程序设计

  • 前言

    本题源于2020年TI杯大学生电子设计竞赛C题—–坡道行驶电动小车

    由于手上没有MSP430/MSP432 板子,所以本篇采用stm32实现

    一、题目设计

    任务
    利用 TI 的 MSP430/MSP432 平台,设计制作一个四轮电动小车。要求小车能沿着指定路线在坡道上自动循迹骑线行驶。小车必须独立运行,车外不能使用任何设备(包括电源)。小车(含电池)重量小于 1.5kg,外形尺寸在地面投影不大于 25cm×25cm。坡道用长、宽约 1m 的细木工板制作,允许板上有木质本色及自然木纹。木工板表面铺设画有 1cm×1cm 黑白间隔的纸条(以下简称为标记线)作为路线指示;标记线起始段为直线,平行于木板两边;标记线在坡顶转向 90°,转弯半径 20cm;标记线平行坡顶距≥30cm,距坡顶距离≤20cm;标记线总长度为 1m。停车标记为宽 1cm 长 5cm 的黑色线条,垂直于坡顶标记线。小车坡度

    角示意及行驶线路顶视图如图 1 所示。

    要求

    (1) 坡度角 θ=0°,电动小车能够沿标记线自动骑线行驶,在停车点停车; 小车上标记点到停车标记中心线的垂直距离误差≤2cm。停车时立即发出声音提示。小车行驶过程中,其地面投影不得脱离标记线。(15 分)

    (2) 在完成(1)的基础上,电动小车能够设定行驶时间,自动控制小车匀速通过 1 米长的线路,在停车点停车。行驶时间可在 10s~20s 间设定。误差绝对值≤1s。行驶过程中不得碾压、脱离标记线。时间误差每超过1s 扣 1 分。 (20 分)

    (3) 坡度角 θ=10°,完成要求(2)的动作。 (20 分)

    (4) 可任意指定坡度角 θ 在 11°~30°,完成要求(2)的动作。 (20 分)

    (5) 在完成(4)后,尽量增加坡度角 θ,完成要求(2)动作。 (20 分)

    (6) 其他。 (5 分)

    (7) 设计报告: (20 分)

    二、硬件选择

    1. 主控芯片
    采用STM32F103c8t6作为主控芯片

    2. 电机
    采用霍尔编码器电机(4个)

    3. 电机驱动
    需要采用2个TB6612电机驱动,分别控制左前轮左后轮、右前轮右后轮

    4. 轮胎
    选用黑色轮胎 海绵内胎,软橡胶外胎,金属轮毂(4个)

    5. 红外传感器
    采用三路循迹模块

    6. 小车底板
    采用铝合金底板

    7. 电池
    采用7.4v 3600mah锂电池

    8. 面包板
    插接导线,搭建电路

    9. 下载器
    采用ST-LINK V2仿真器

    10. 其余工具零件
    若干杜邦线、螺母、铜柱、螺丝刀、剥线钳等

    实物图:

    三、软件设计

    1. stm32最小系统板
    插在面包板上,方便引脚连接。由于stm32最小系统板没有基本定时器,所以最多可采用4个定时器( 1个高级定时器TIM1和3个通用定时器TIM2、TIM3、TIM4 ),在这里需要全部用上。

    2. 定时器
    TIM1:配置四个电机的PWM输出
    左前:PA8 左后:PA9 右前:PA10 右后:PA11

    TIM2:配置编码器模式,通道1和通道2分别作为小车左后轮编码器A、B相
    PA0、PA1

    TIM3:配置编码器模式,通道1和通道2分别作为小车右后轮编码器A、B相
    PA6、PA7

    TIM4:在中断读取TIM2、TIM3计数器(cnt)的值,设置TIM4每隔5ms溢出一次,每向上计数四次cnt的值加1,并且在中断函数中读取完cnt的值后就清零,那么每次进入TIM4溢出中断时,cnt的值都不会太大,可直接将cnt的值作为小车的当前速度。

    3. 三路红外传感器
    Vcc和GND连接TB6612模块上的Vcc和GND
    红外输出: 左 中 右 分别接线PA2、PA3、PA4
    (1) 当左右均未踩到黑线时,小车匀速直行
    (2) 当左没踩到,右踩到黑线时,小车右转
    (3) 当左踩到,右没踩到黑线时,小车左转
    (4) 当左中右都踩到黑线时,小车停止

    4. TB6612驱动
    逻辑输入:
    左前轮 配置为通道1 连接PB12,PB13
    左后轮 配置为通道2 连接PB10,PB11
    右前轮 配置为通道3 连接PB14,PB15
    右后轮 配置为通道4 连接PB0,PB1

    用锂电池给TB6612模块供电,即锂电池的正负极连接TB6612的Vin+和-;然后用该TB6612模块的Vout+和-连接另一块TB6612的Vin+和-,相继再用Vcc和GND与面包饭上的Vcc和GND相连,实现供电。

    四、程序设计

    1. 红外传感设计

    void GPIOB_Init(void)
    {
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    	
    	GPIO_InitTypeDef GPIO_InitStructure;
    	/*红外out引脚:PA2,PA3,PA4*/
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_15;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA,&GPIO_InitStructure);
    	
    	
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_10 | GPIO_Pin_11 | GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOB,&GPIO_InitStructure);
    	/*初始为低电平*/
    	GPIO_ResetBits(GPIOB,GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_10 | GPIO_Pin_11 | GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15);
    
    }
    
    void Change_Target(int t_l,int t_r)
    {
    	target_left = t_l;
    	target_right = t_r;
    }
    
    void forward(int t)
    {
    	Change_Target(t,t);
    	GPIO_SetBits(GPIOB,GPIO_Pin_1| GPIO_Pin_11 | GPIO_Pin_13 | GPIO_Pin_15);
    	GPIO_ResetBits(GPIOB,GPIO_Pin_0| GPIO_Pin_10 | GPIO_Pin_12 | GPIO_Pin_14);
    }
    
    void back(int t)
    {
    	Change_Target(t,t);
    	GPIO_SetBits(GPIOB,GPIO_Pin_0| GPIO_Pin_10 | GPIO_Pin_12 | GPIO_Pin_14);
    	GPIO_ResetBits(GPIOB,GPIO_Pin_1| GPIO_Pin_11 | GPIO_Pin_13 | GPIO_Pin_15);
    }
    
    void turn_left(int t_l,int t_r)
    {
    	Change_Target(t_l,t_r);
    	GPIO_SetBits(GPIOB,GPIO_Pin_1| GPIO_Pin_11 | GPIO_Pin_12 | GPIO_Pin_15);
    	GPIO_ResetBits(GPIOB,GPIO_Pin_0| GPIO_Pin_10 | GPIO_Pin_13 | GPIO_Pin_14);
    }
    
    void turn_right(int t_l,int t_r)
    {
    	Change_Target(t_l,t_r);
    	GPIO_SetBits(GPIOB,GPIO_Pin_1| GPIO_Pin_11 | GPIO_Pin_13 | GPIO_Pin_14);
    	GPIO_ResetBits(GPIOB,GPIO_Pin_0| GPIO_Pin_10 | GPIO_Pin_12 | GPIO_Pin_15);
    }
    
    void stop(void)
    {
    	GPIO_ResetBits(GPIOB,GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_10 | GPIO_Pin_11 | GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15);
    }
    
    

    2. 定时器

    void TIM1_PWM_Init(u16 per,u16 psc)
    {
    	/*使能TIM4时钟*/
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1,ENABLE);
    	
    	/*使能GPIO*/
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    	
    	/*使能AFIO*/
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
    	
    	/*配置GPIO*/
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 |GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA,&GPIO_InitStructure);
    	
    	/*设置重映射*/
    	//GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3,ENABLE);//部分重映射	
    	
    	/*初始化定时器参数*/
    	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//选择时钟分频为1分频
    	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//选择计数模式为向上计数
    	TIM_TimeBaseInitStructure.TIM_Period = per;//配置周期(ARR自动重装器的值)
    	TIM_TimeBaseInitStructure.TIM_Prescaler = psc;//配置PSC预分频器的值
    	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器的值,高级计数器才需配置
    	TIM_TimeBaseInit(TIM1,&TIM_TimeBaseInitStructure);
    	//TIM_ClearFlag(TIM4,TIM_FLAG_Update);//先清除标志位,避免刚初始化就进入中断
    	
    	/*初始化PWM参数*/
    	TIM_OCInitTypeDef TIM_OCInitStructure;
    	TIM_OCInitStructure.TIM_Pulse = 0;
    	TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset;   //选择空闲状态下的非工作状态 低电平
    	TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Set;  //选择互补空闲状态下的非工作状态 低电平
    	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;//选择PWM1模式
    	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;//输出极性:高电平有效
    	TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_Low;
    	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;  //输出比较使能
    	TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable;  //互补输出比较使能
    	TIM_OC1Init(TIM1,&TIM_OCInitStructure);
    	TIM_OC2Init(TIM1,&TIM_OCInitStructure);
    	TIM_OC3Init(TIM1,&TIM_OCInitStructure);
    	TIM_OC4Init(TIM1,&TIM_OCInitStructure);
    	
    	/*使能TIMX在CCRX上的预装载寄存器*/
    	TIM_OC1PreloadConfig(TIM1,TIM_OCPreload_Enable);
    	TIM_OC2PreloadConfig(TIM1,TIM_OCPreload_Enable);
    	TIM_OC3PreloadConfig(TIM1,TIM_OCPreload_Enable);
    	TIM_OC4PreloadConfig(TIM1,TIM_OCPreload_Enable);
    	
    	TIM_CtrlPWMOutputs(TIM1,ENABLE);
    	
    	/*使能TIMX在ARR上的预装载寄存器允许位*/
    	//TIM_ARRPreloadConfig(TIM4,ENABLE);
    	
    	/*开启定时器*/
    	TIM_Cmd(TIM1,ENABLE);
    }
    
    void TIM2_Init(u16 per,u16 psc)
    {
    	/*开启通用定时器TIM2和GPIO时钟*/
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    	
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA,&GPIO_InitStructure);
    	
    	/*配置时基单元*/
    	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//选择时钟分频为1分频
    	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//选择计数模式为向上计数
    	/*
    	定时频率 = 72M / (PSC+1) / (ARR+1)
    	*/
    	TIM_TimeBaseInitStructure.TIM_Period = per;//已知编码器线数:线数*4-1
    	TIM_TimeBaseInitStructure.TIM_Prescaler = psc;
    	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器的值,高级计数器才需配置
    	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
    	
    	/*选择时钟源,配置正交解码*/
    	TIM_ICInitTypeDef TIM_ICInitStructure;  
        TIM_EncoderInterfaceConfig(TIM2, TIM_EncoderMode_TI12,  TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);//TI12:A、B项都计数
        TIM_ICStructInit(&TIM_ICInitStructure);//缺省输入
        TIM_ICInitStructure.TIM_ICFilter = 10;  //滤波器
        TIM_ICInit(TIM2, &TIM_ICInitStructure);
    	
    	TIM_SetCounter(TIM2,0X7FFF); //TIM4->CNT=0X7FFF(65536的一半)
    	/*启动定时器*/
    	TIM_Cmd(TIM2,ENABLE);
    }
    
    void TIM3_Init(u16 per,u16 psc)
    {
    	/*开启通用定时器TIM2和GPIO时钟*/
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    	
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA,&GPIO_InitStructure);
    	
    	/*配置时基单元*/
    	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//选择时钟分频为1分频
    	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//选择计数模式为向上计数
    	/*
    	定时频率 = 72M / (PSC+1) / (ARR+1)
    	*/
    	TIM_TimeBaseInitStructure.TIM_Period = per;//已知编码器线数:线数*4-1
    	TIM_TimeBaseInitStructure.TIM_Prescaler = psc;
    
    	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器的值,高级计数器才需配置
    	TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);
    	
    	/*选择时钟源,配置正交解码*/
    	TIM_ICInitTypeDef TIM_ICInitStructure;  
        TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_BothEdge ,TIM_ICPolarity_BothEdge);//TI12:A、B项都计数
        TIM_ICStructInit(&TIM_ICInitStructure);//缺省输入
        TIM_ICInitStructure.TIM_ICFilter = 0;  //滤波器
        TIM_ICInit(TIM3, &TIM_ICInitStructure);
    	
    	TIM_SetCounter(TIM3,0X7FFF); //TIM4->CNT=0X7FFF(65536的一半)
    	/*启动定时器*/
    	TIM_Cmd(TIM3,ENABLE);
    }
    
    void TIM4_Init(u16 per,u16 psc)
    {
    	/*1、开启通用定时器TIM4时钟*/
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);
    	
    	/*2、选择时钟源为内部时钟*/
    	//TIM_InternalClockConfig(TIM4);//默认为内部时钟
    	
    	/*3、配置时基单元*/
    	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//选择时钟分频为1分频
    	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//选择计数模式为向上计数
    	/*
    	定时频率 = 72M / (PSC+1) / (ARR+1)
    	*/
    	TIM_TimeBaseInitStructure.TIM_Period = per;//配置周期(ARR自动重装器的值)
    	TIM_TimeBaseInitStructure.TIM_Prescaler = psc;//配置PSC预分频器的值
    	TIM_TimeBaseInit(TIM4,&TIM_TimeBaseInitStructure);
    	
    	/*4、使能更新中断*/
    	TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE);
    	
    	/*5、配置NVIC*/
    	
    	NVIC_InitTypeDef NVIC_InitStructure;
    	NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;//选择中断通道
    	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//开启中断通道
    	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;//抢占优先级
    	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;//响应优先级
    	NVIC_Init(&NVIC_InitStructure);	
    	
    	/*6、启动定时器*/
    	TIM_Cmd(TIM4,ENABLE);
    }
    
    

    3. PID设计

    int target_left,target_right;
    int err_now1,err_now2;
    int err_last1,err_last2;
    int err_last_last1,err_last_last2;
    int spd_now1,spd_now2;
    int jisuan1,jisuan2;
    int out_left,out_right;
    int cnt_left,cnt_right;
    
    float Kp=0.5;
    float Ki=0.4;
    float Kd= 0.1;
    		
    		cnt_left = abs((TIM2->CNT)-0X7FFF);
    		cnt_right = abs((TIM3->CNT)-0X7FFF);
    		
    		TIM2->CNT = 0X7FFF;
    		TIM3->CNT = 0X7FFF;
    
    		
    		//左电机	
    		spd_now1 = cnt_left;
    		err_now1 = target_left - spd_now1;
    		jisuan1 = Kp*(err_now1-err_last1) + Ki*err_now1 + Kd*(err_now1+err_last_last1-2*err_last1);
    		out_left += jisuan1;
    		if(out_left<0)  out_left = 0;
    		if(out_left>100)  out_left = 100; 
    		TIM_SetCompare1(TIM1,out_left);	   //左前
    		TIM_SetCompare2(TIM1,out_left);	  
    		err_last_last1 = err_last1;
    		err_last1 = err_now1;
    		
    		//右电机
    		spd_now2 = cnt_right;
    		err_now2 = target_right - spd_now2;
    		jisuan2 = Kp*(err_now2-err_last2) + Ki*err_now2 + Kd*(err_now2+err_last_last2-2*err_last2);
    		out_right += jisuan2;
    		if(out_right<0)  out_right = 0;
    		if(out_right>100)  out_right = 100; 	
    		TIM_SetCompare3(TIM1,out_right);	 //右前轮
    		TIM_SetCompare4(TIM1,out_right);
    		err_last_last2 = err_last2;
    		err_last2 = err_now2; 
    		
    		TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
    
    

    4. main

    int main(void)
    {
    	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组
    	GPIOB_Init();
    	
    	TIM1_PWM_Init(100-1,72-1);
    	TIM2_Init(65535,9);
    	TIM3_Init(65535,9);
    	TIM4_Init(5000,72-1); 
    	int i;
    	
    	while(1)
    	{
    		if(L==0 && R==0)  i=0;
    		
    		if(L==1 && R==0)  i=1;
    		
    		if(L==0 && R==1)  i=3;
    		
    		if(L==1 && M==1 && R==1)  i=5;
    		
    		switch(i)
    		{
    			case 0:
    				forward(4);
    			  break;
    			
    			case 1:
    				turn_left(4,10);	
    			  break;
    
    			case 3:
    				turn_right(10,4);
    			  break;
    
    			case 5:
    				stop();
    				delay_ms(1000);
    			  break;
    		}		
    	}
    }
    
    物联沃分享整理
    物联沃-IOTWORD物联网 » 2020年电赛:基于PID算法的STM32控制坡道行驶电动小车详解

    发表评论