基于stm32的智能小车设计(一)

这篇具有很好参考价值的文章主要介绍了基于stm32的智能小车设计(一)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

一、原理简述

二、系统硬件设计

1.电机驱动选型

1.1  L298N

1.2  L9110

1.3  DRV8833

1.4 TB6612

2.系统整体硬件设计

3.stm32主控制模块

4.舵机云台

5.超声波模块

三、系统软件设计

1.系统整体软件设计

2.电机驱动及速度的控制

3.舵机云台的控制

4.超声波测距

四、实物展示

五、完整原理图

六、完整代码


        单片机智能小车一直是大家很喜欢的小设计,智能小车的制作虽然难度不高,但是对于初学者来说,由于陌生,因此也总是觉得门槛高。事实上,很多东西都禁不起深挖,当你做完之后才会发现,原来也不过如此。

        我计划在接下来的一段时间里,断断续续地给大家带来智能小车的相关设计,由易到难。涉及蓝牙控、WIFI控、NRF24L01控制,红外遥控控制,红外、超声波避障、寻迹等。每一篇都是一个完整的设计,有着完整的软硬件分析过程,希望可以给你帮助。

        本节将制作一个超声波避障的小车。

一、原理简述

        作为智能小车,最基本的一点肯定是解决电机驱动的问题。

        现在市场上有许多适合小车电机驱动的模块,常见的有L298N、L9110S、DRV8833、TB6612等。这些芯片/模块各有优劣,但是控制原理和方法基本相似。这些芯片/模块的详细介绍可以参照第二章系统硬件设计,将详细地介绍上述几个芯片/模块的重要参数以及使用方法。

        电机驱动解决后,接下来就是超声波模块。超声波配合舵机,可以轻松实现避障,使得小车看上去更加智能。

        本设计主将以stm32为核心,配合电机驱动、舵机和超声波模块,实现小车的自动避障功能。

二、系统硬件设计

1.电机驱动选型

1.1  L298N

工作电压:2.5~46V,

单通道最大输出电流:2A,

逻辑电源(Vss):4.5~7V,

低电平输入范围:-0.3~1.5V,

高电平输入范围:2.3~Vss,

可驱动两路电机。

应用电路图:

                基于stm32智能小车毕业设计,单片机设计,单片机,stm32,智能小车,电机驱动,电机

         上述应用电路为参考电路,VS和VSS都可以使用5V(不建议使用3.3V),IN输入以及PWM可以使用5V或者3.3V单片机,注意PCB板布线适当加粗,且供电电池保持充足电量。

1.2  L9110

工作电压:2.5V~12V,

连续电流输出能力:DIP8  1.0A(8V)

                                SOP8  0.8A(8V),

输入高电平:2.5V~10V

输入低电平:<0.7V,

单路输出。

真值表:

基于stm32智能小车毕业设计,单片机设计,单片机,stm32,智能小车,电机驱动,电机

应用电路图:

                                                  基于stm32智能小车毕业设计,单片机设计,单片机,stm32,智能小车,电机驱动,电机

1.3  DRV8833

输入电压:2.7V~10.8V,

输出电流(VM = 5V,25°C 时),

– 采用 PWP/RTY 封装:每条 H 桥的 RMS 电流为 1.5A,峰值电流为 2A;

– 采用 PW 封装:每条 H 桥的 RMS 电流为500mA,峰值电流为 2A;

• 可以将输出并联,以实现

– 3A RMS 电流、4A 峰值电流(PWP 和 RTY 封装);

– 1A RMS 电流、4A 峰值电流(PW 封装);

可驱动两路电机。

应用电路图:

             基于stm32智能小车毕业设计,单片机设计,单片机,stm32,智能小车,电机驱动,电机

       上述应用电路为参考电路,IN输入以及PWM可以使用5V或者3.3V单片机,注意PCB板布线适当加粗,且供电电池保持充足电量。

1.4  TB6612

输入电压:

         VCC:2.7~5.5V       芯片小信号电源

         VM:2.5~13.5V    电机供电

电流:MAX 1.0A   VM>=4.5V

           MAX 0.4A   2.5<=VM<4.5V

PWM:最大支持100KHz。

可驱动两路电机。

真值表:

基于stm32智能小车毕业设计,单片机设计,单片机,stm32,智能小车,电机驱动,电机

 应用电路图:

基于stm32智能小车毕业设计,单片机设计,单片机,stm32,智能小车,电机驱动,电机

        这些芯片各有优劣,可根据实际情况选择合适的芯片。在本设计中,将采用TB6612作为电机驱动。值得说明的是,目前TB6612已经停产,且价格相对高昂,请谨慎选择。(那为什么我会选择这个呢,因为我还有库存,顺便用了,哈哈哈哈)

2.系统整体硬件设计

        在本设计中,硬件分为stm32主控制模块、TB6612电机驱动模块、超声波模块、舵机模块四个部分。其整体逻辑如下图所示:

基于stm32智能小车毕业设计,单片机设计,单片机,stm32,智能小车,电机驱动,电机

        这里需要注意电池的选型,建议选择三洋、松下、索尼等品牌,这些品牌的电池一般输出电流稳定,动力充足。不能贪便宜,否则到时候效果不佳,坑害的是自己。

3.stm32主控制模块

        stm32最小系统板原理图如下所示(看不清请放大):

基于stm32智能小车毕业设计,单片机设计,单片机,stm32,智能小车,电机驱动,电机

         stm32f103c8t6为意法半导体生产的一款高性能32位处理器,采用ARM cortex-M3为内核,在稳定运行的情况下,主频可高达72M,是传统51单片机的性能的几十倍,能完成许多复杂的功能。其最小系统主要包括:stm32芯片、复位电路、时钟电路、电源电路、代码烧录电路和boot选择电路。

        stm32f103c8t6用着丰富的外设,例如GPIO、USART、ADC、PWM、TIMER、硬件SPI、硬件IIC、USB等。在本设计中,将会使用到的它的外设有:GPIO、TIMER和PWM。其中,GPIO和TIMER用于控制电机正反转、控制超声波测距以及舵机转向引脚的信号;PWM用于调节电机转速。
 

4.舵机云台

        舵机云台采用常用的SG90,其实物图如下:

                    基于stm32智能小车毕业设计,单片机设计,单片机,stm32,智能小车,电机驱动,电机

        SG90只有三根线:VCC、GND和信号线(4-6V供电,信号引脚可用5V,也可用3.3V)。其中信号线就是控制舵机旋转的。控制原理图很简单,只需要控制单片机产生周期为20ms,高电平在0.5ms~2.5ms之间的一个方波,事实上,经过实际测试,周期在3-20ms都是可以的。舵机最大只能180°旋转,这里,我们以90°的位置为中心线,具体旋转情况如下(注意左右偏的参考点,这里只表示两种偏向):

       高电平为1.5ms时,舵机位于中心位置。

        高电平为0.5ms时,舵机左偏90°。

        高电平为2.5ms时,舵机右偏90°。

        具体软件控制方法,将在软件部分详细介绍。

5.超声波模块

        超声波测距模块的实物图如下:

                                            基于stm32智能小车毕业设计,单片机设计,单片机,stm32,智能小车,电机驱动,电机

                    

            超声波模块时利用声波在空气中传输的为原理而设计的。模块通过trig引脚启动声波,此时开始计时,当发出的声波遇到障碍物就会反弹,反弹回来的声波会被超声波模块的探头接收,此时echo引脚状态将发生改变,计时停止,根据声波在控制中传播速度,就可以很方便计算出超声波距离障碍物的距离。

        此模块的供电为5V,trig和echo可接5V单片机,也可以接3.3V单片机。

三、系统软件设计

1.系统整体软件设计

        软件的整体流程图如下:

基于stm32智能小车毕业设计,单片机设计,单片机,stm32,智能小车,电机驱动,电机

        stm32首先需要将所有需要用到外设进行初始化,然后进入主循环,不断地进行超声波测距并显示,当前方遇到障碍物时,将启动舵机左右旋转进行分别测距,并将两个距离值进行比较,然后车身往距离更大的一方旋转。如此往复,就完成了整个超声波避障的过程。

2.电机驱动及速度的控制

        在本设计中,电机速度控制由两路PWM控制,分别接在了stm32的PB6和PB9上,为TIM4的通道一和通道四,但由于本设计使用的stm32具体型号为stm32f103c6t6,其外设并没有TIM4,因此,这里采用定时器周期变化产生PWM。事实上,可将TB6612的PWMA和PWMB直接接高电平,这样,电机将在该电压下最高功率运行,但是这样不利于控制。

        在使用定时器周期变化产生PWM前,首先需要初始化TB6612连接stm32的所有GPIO,其初始化代码如下所示:


void TB6612_GPIO_Init(void)
{
	GPIO_InitTypeDef  GPIO_InitStructure;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);	 //使能PB端口时钟
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_9;				
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		 //IO口速度为50MHz
	GPIO_Init(GPIOB, &GPIO_InitStructure);					 //根据设定参数初始化
	GPIO_ResetBits(GPIOB,GPIO_Pin_6|GPIO_Pin_9);	

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15;				
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		 //IO口速度为50MHz
	GPIO_Init(GPIOB, &GPIO_InitStructure);					 //根据设定参数初始化
	GPIO_ResetBits(GPIOB,GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15);	
}

       为了使用这些GPIO方便,不妨使用宏定义来给这些GPIO重新起个名字,如下:

#define PWMA PBout(6)
#define PWMB PBout(9)

#define AIN1 PBout(14)
#define AIN2 PBout(13)

#define BIN1 PBout(15)
#define BIN2 PBout(12)

       使用stm32定时器,首先需要使能所在总线时钟,然后设置定时器定时时间及定时方式,打开定时器,最后编写定时器中断服务函数即可。其具体代码如下:


void TIM1_Int_Init(u16 arr,u16 psc)
{
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	NVIC_InitTypeDef NVIC_InitStructure;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); //时钟使能

	TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值	
	TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值  
	TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
	TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
 
	TIM_ITConfig(TIM1,TIM_IT_Update,ENABLE ); //使能指定的TIM3中断,允许更新中断

	NVIC_InitStructure.NVIC_IRQChannel = TIM1_UP_IRQn;  //TIM3中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  //先占优先级0级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;  //从优先级3级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
	NVIC_Init(&NVIC_InitStructure);  //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器

	TIM_Cmd(TIM1, ENABLE);  //使能TIMx外设
							 
}
//定时器3中断服务程序
void TIM1_UP_IRQHandler(void)   //TIM3中断
{
	if (TIM_GetITStatus(TIM1, TIM_IT_Update) != RESET) //检查指定的TIM中断发生与否:TIM 中断源 
	{
		TIM_ClearITPendingBit(TIM1, TIM_IT_Update  );  //清除TIMx的中断待处理位:TIM 中断源 
		PWMA = !PWMA;
		PWMB = !PWMB;
	}
}

        然后在主函数中调用这两个函数,这里不妨控制定时器的频率为100KHz,产生的PWM频率为50KHz,占空比为50%,如下:

TB6612_GPIO_Init();
TIM1_Int_Init(719, 0);//72000000/720 =  100 000

        如果想让电机控制的速度更快,直接加大定时器3的占空比即可。

        最后就是小车前进、后退,左转、右转和停止的控制了,这个相对简单,具体代码如下:

void go_straight(void)
{
	AIN1 = 1;
	AIN2 = 0;
	BIN1 = 1;
	BIN2 = 0;
}

void go_back(void)
{
	AIN1 = 0;
	AIN2 = 1;
	BIN1 = 0;
	BIN2 = 1;
}

void ture_right(void)
{
	AIN1 = 1;
	AIN2 = 0;
	BIN1 = 0;
	BIN2 = 1;
}

void ture_left(void)
{
	AIN1 = 0;
	AIN2 = 1;
	BIN1 = 1;
	BIN2 = 0;
}

void stop(void)
{
	AIN1 = 0;
	AIN2 = 0;
	BIN1 = 0;
	BIN2 = 0;
}

        至此,有关电机的控制结束,事实上,换用其他的电机驱动芯片,其驱动方式和上面驱动方式大同小异,甚至直接照搬照抄就可以。

3.舵机云台的控制

        舵机云台的控制原理在前文中已经简单介绍过,软件部分,将采用PWM来控制舵机的转动。在原理图的设计中,舵机信号的控制引脚连接到了STM32的PB5,为定时器3的通道2,因此,这里需要开启stm32定时3通道2的PWM初始化。如下:

void TIM3_PWM_SG90_Init(u16 arr,u16 psc)
{  
	GPIO_InitTypeDef GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	TIM_OCInitTypeDef  TIM_OCInitStructure;
	

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);	//使能定时器3时钟
 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB  | RCC_APB2Periph_AFIO, ENABLE);  //使能GPIO外设和AFIO复用功能模块时钟
	
	GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); //Timer3部分重映射  TIM3_CH2->PB5    
 
   //设置该引脚为复用输出功能,输出TIM3 CH2的PWM脉冲波形	GPIOB.5
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //TIM_CH2
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //复用推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIO
 
   //初始化TIM3
	TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
	TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值 
	TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
	
	//初始化TIM3 Channel2 PWM模式	 
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //选择定时器模式:TIM脉冲宽度调制模式2
 	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //输出极性:TIM输出比较极性高
	TIM_OC2Init(TIM3, &TIM_OCInitStructure);  //根据T指定的参数初始化外设TIM3 OC2

	TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);  //使能TIM3在CCR2上的预装载寄存器
 
	TIM_Cmd(TIM3, ENABLE);  //使能TIM3
	

}

        同样,为了使用方便,这里将一些GPIO,频率,旋转角度等(这里需要联系舵机的控制原理来理解,这里就不再赘述)相关信息,用宏来定义:

#define TIM3_PWM_SG90_PERIOD 	(1200-1)
#define TIM3_PWM_SG90_PRESCALER (1200-1)

/* 频率50HZ 20ms */
#define SG90_PWM_FREQ  72000000/((TIM3_PWM_SG90_PERIOD)*(TIM3_PWM_SG90_PRESCALER))

#define SG90_CENTRE  	((int)(1.5/20*TIM3_PWM_SG90_PERIOD))
#define SG90_RIGHT	    ((int)(0.5/20*TIM3_PWM_SG90_PERIOD))
#define SG90_LEFT		((int)(2.5/20*TIM3_PWM_SG90_PERIOD))

        PWM的频率为50Hz,及20ms,在主函数调用:

TIM3_PWM_SG90_Init(TIM3_PWM_SG90_PERIOD,TIM3_PWM_SG90_PRESCALER);//PWM频率=72000/((1200)*(1200))=50hz 

        后续只需要在需要控制舵机旋转时,调用函数即可,例如,我想控制舵机云台左转,这里这么操作:

TIM_SetCompare2(TIM3, SG90_LEFT);

4.超声波测距

        超声波的控制原理同样在硬件介绍章节中做了介绍。具体的可以查看超声波模块的数据手册。

        首先,需要初始化一个定时器,用于后面对于声波发出到返回的计时,这里使用stm32的定时器2来计时,其初始化函数如下:

void TIM2_Ultrasonic_Init(void)
{
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructer;
	NVIC_InitTypeDef NVIC_InitStructer;


	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
	
	/*定时器TIM2初始化*/
	TIM_DeInit(TIM2);
	TIM_TimeBaseInitStructer.TIM_Period = 999;//定时周期为1000
	TIM_TimeBaseInitStructer.TIM_Prescaler = 71; //分频系数72
	TIM_TimeBaseInitStructer.TIM_ClockDivision = TIM_CKD_DIV1;//不分频
	TIM_TimeBaseInitStructer.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructer);
	
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);//开启更新中断
	
	NVIC_InitStructer.NVIC_IRQChannelPreemptionPriority = 2;
	NVIC_InitStructer.NVIC_IRQChannelSubPriority = 2;
	NVIC_InitStructer.NVIC_IRQChannel = TIM2_IRQn;
	NVIC_InitStructer.NVIC_IRQChannelCmd = ENABLE;
	
	NVIC_Init(&NVIC_InitStructer);
	TIM_Cmd(TIM2, DISABLE);//关闭定时器使能

}

        然后就可以开始测距了。

        需要控制超声波发出声波,如下:

GPIO_SetBits(TRIG_PORT, TRIG_PIN);  //拉高信号,作为触发信号
delay_us(20);  						//高电平信号超过10us
GPIO_ResetBits(TRIG_PORT, TRIG_PIN);

        然后,需要等待超声波遇到障碍物回弹,同时开启定时器计时:

/*等待回响信号*/
while(GPIO_ReadInputDataBit(ECHO_PORT, ECHO_PIN) == RESET);
TIM_Cmd(TIM2,ENABLE);//回响信号到来,开启定时器计数
		
while(GPIO_ReadInputDataBit(ECHO_PORT, ECHO_PIN) == SET);//回响信号消失
TIM_Cmd(TIM2, DISABLE);//关闭定时器

        得到从定时器开启到结束的时间:

tim = TIM_GetCounter(TIM2);//获取计TIM2数寄存器中的计数值,一边计算回响信号时间

        得到这个时间后,根据声波在空气中传播的速度为340m/s(25℃),就可以得到超声波模块距离障碍物的大致距离(单位:cm):

distance = (tim + overcount * 1000) / 58.0;//通过回响信号计算距离

        为了使得测量的距离更加准确和稳定,这里采用多次测量求平均值的方法:去掉一个最大值,去掉一个最小值,然后求平均值。如下:

float get_ultrasonic_distance(void)
{
	float distance = 0;
	u16 tim;
	GPIO_SetBits(TRIG_PORT, TRIG_PIN);  //拉高信号,作为触发信号
	delay_us(20);  						//高电平信号超过10us
	GPIO_ResetBits(TRIG_PORT, TRIG_PIN);

	/*等待回响信号*/
	while(GPIO_ReadInputDataBit(ECHO_PORT, ECHO_PIN) == RESET);
	TIM_Cmd(TIM2,ENABLE);//回响信号到来,开启定时器计数
		
	while(GPIO_ReadInputDataBit(ECHO_PORT, ECHO_PIN) == SET);//回响信号消失
	TIM_Cmd(TIM2, DISABLE);//关闭定时器
		
	tim = TIM_GetCounter(TIM2);//获取计TIM2数寄存器中的计数值,一边计算回响信号时间
		
	distance = (tim + overcount * 1000) / 58.0;//通过回响信号计算距离
		
	TIM2->CNT = 0;  //将TIM2计数寄存器的计数值清零
	overcount = 0;  //中断溢出次数清零
	delay_ms(1);
	return distance;		//距离作为函数返回值
}


void bubble(unsigned long *a, int n)

{
  int i, j, temp;
  for (i = 0; i < n - 1; i++)
  {
    for (j = i + 1; j < n; j++)
    {
      if (a[i] > a[j])
      {
        temp = a[i];
        a[i] = a[j];
        a[j] = temp;
      }
    }
  }
}

float get_average_distance(void)
{
	float Distance;
	unsigned long ultrasonic[5] = {0};
	int a,num = 0;
	int lastDistance;
	while (num < 5)
	{
		Distance = get_ultrasonic_distance();
		while(((int)Distance >= 500 || (int)Distance == 0))
		{
			Distance = get_ultrasonic_distance();
		}

		if(Distance >0 || (int)Distance <500)
		{
			ultrasonic[num] = Distance;
			//lastDistance=Distance;
			num++;
			delay_ms(10);
		}

	}
	num = 0;
	bubble(ultrasonic, 5);
	Distance = (ultrasonic[1] + ultrasonic[2] + ultrasonic[3]) / 3;
	return Distance;
	//printf("Distance=%d\n",Distance);
}

          至此,超声波测距介绍结束。

四、实物展示

基于stm32智能小车毕业设计,单片机设计,单片机,stm32,智能小车,电机驱动,电机

 基于stm32智能小车毕业设计,单片机设计,单片机,stm32,智能小车,电机驱动,电机

 基于stm32智能小车毕业设计,单片机设计,单片机,stm32,智能小车,电机驱动,电机

五、完整原理图

基于stm32智能小车毕业设计,单片机设计,单片机,stm32,智能小车,电机驱动,电机

六、完整代码

pwm.c

#include "pwm.h"

//TIM3 PWM部分初始化 
//PWM输出初始化
//arr:自动重装值
//psc:时钟预分频数
void TIM3_PWM_SG90_Init(u16 arr,u16 psc)
{  
	GPIO_InitTypeDef GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	TIM_OCInitTypeDef  TIM_OCInitStructure;
	

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);	//使能定时器3时钟
 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB  | RCC_APB2Periph_AFIO, ENABLE);  //使能GPIO外设和AFIO复用功能模块时钟
	
	GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); //Timer3部分重映射  TIM3_CH2->PB5    
 
   //设置该引脚为复用输出功能,输出TIM3 CH2的PWM脉冲波形	GPIOB.5
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //TIM_CH2
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //复用推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIO
 
   //初始化TIM3
	TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
	TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值 
	TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
	
	//初始化TIM3 Channel2 PWM模式	 
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //选择定时器模式:TIM脉冲宽度调制模式2
 	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //输出极性:TIM输出比较极性高
	TIM_OC2Init(TIM3, &TIM_OCInitStructure);  //根据T指定的参数初始化外设TIM3 OC2

	TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);  //使能TIM3在CCR2上的预装载寄存器
 
	TIM_Cmd(TIM3, ENABLE);  //使能TIM3
	

}

pwm.h

#ifndef __PWM_H
#define __PWM_H
#include "sys.h"

#define TIM3_PWM_SG90_PERIOD 	(1200-1)
#define TIM3_PWM_SG90_PRESCALER (1200-1)

/* 频率50HZ 20ms */
#define SG90_PWM_FREQ  72000000/((TIM3_PWM_SG90_PERIOD)*(TIM3_PWM_SG90_PRESCALER))

#define SG90_CENTRE  	((int)(1.5/20*TIM3_PWM_SG90_PERIOD))
#define SG90_RIGHT	((int)(0.5/20*TIM3_PWM_SG90_PERIOD))
#define SG90_LEFT		((int)(2.5/20*TIM3_PWM_SG90_PERIOD))
	

void TIM3_Int_Init(u16 arr,u16 psc);
void TIM3_PWM_SG90_Init(u16 arr,u16 psc);

#endif

car.c

#include "car.h"


void TIM1_Int_Init(u16 arr,u16 psc)
{
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	NVIC_InitTypeDef NVIC_InitStructure;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); //时钟使能

	TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值	
	TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值  
	TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
	TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
 
	TIM_ITConfig(TIM1,TIM_IT_Update,ENABLE ); //使能指定的TIM1中断,允许更新中断

	NVIC_InitStructure.NVIC_IRQChannel = TIM3_UP_IRQn;  //TIM3中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  //先占优先级0级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;  //从优先级3级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
	NVIC_Init(&NVIC_InitStructure);  //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器

	TIM_Cmd(TIM1, ENABLE);  //使能TIMx外设
							 
}
//定时器1中断服务程序
void TIM1_UP_IRQHandler(void)   //TIM1中断
{
	if (TIM_GetITStatus(TIM1, TIM_IT_Update) != RESET) //检查指定的TIM中断发生与否:TIM 中断源 
	{
		TIM_ClearITPendingBit(TIM1, TIM_IT_Update  );  //清除TIMx的中断待处理位:TIM 中断源 
		PWMA = !PWMA;
		PWMB = !PWMB;
	}
}


void TB6612_GPIO_Init(void)
{
	GPIO_InitTypeDef  GPIO_InitStructure;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);	 //使能PB端口时钟
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_9;				
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		 //IO口速度为50MHz
	GPIO_Init(GPIOB, &GPIO_InitStructure);					 //根据设定参数初始化
	GPIO_ResetBits(GPIOB,GPIO_Pin_6|GPIO_Pin_9);	

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15;				
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		 //IO口速度为50MHz
	GPIO_Init(GPIOB, &GPIO_InitStructure);					 //根据设定参数初始化
	GPIO_ResetBits(GPIOB,GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15);	
}


void go_straight(void)
{
	AIN1 = 1;
	AIN2 = 0;
	BIN1 = 1;
	BIN2 = 0;
}

void go_back(void)
{
	AIN1 = 0;
	AIN2 = 1;
	BIN1 = 0;
	BIN2 = 1;
}

void ture_right(void)
{
	AIN1 = 1;
	AIN2 = 0;
	BIN1 = 0;
	BIN2 = 1;
}

void ture_left(void)
{
	AIN1 = 0;
	AIN2 = 1;
	BIN1 = 1;
	BIN2 = 0;
}

void stop(void)
{
	AIN1 = 0;
	AIN2 = 0;
	BIN1 = 0;
	BIN2 = 0;
}

car.h

#ifndef __CAR_H__
#define __CAR_H__

#include "sys.h"
#include "delay.h"

#define PWMA PBout(6)
#define PWMB PBout(9)

#define AIN1 PBout(14)
#define AIN2 PBout(13)

#define BIN1 PBout(15)
#define BIN2 PBout(12)


void TIM3_Int_Init(u16 arr,u16 psc);
void TB6612_GPIO_Init(void);
void go_straight(void);
void ture_left(void);
void go_back(void);
void ture_right(void);
void stop(void);

#endif

ultrasonic.c

#include "ultrasonic.h"

/*记录定时器溢出次数*/
unsigned int overcount = 0;

void ultrasonic_gpio_init(void)
{
    GPIO_InitTypeDef  GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(TRIG_RCC, ENABLE);
    RCC_APB2PeriphClockCmd(ECHO_RCC, ENABLE);
	GPIO_InitStructure.GPIO_Pin = TRIG_PIN;	
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 
	GPIO_Init(TRIG_PORT, &GPIO_InitStructure);
    
    GPIO_InitStructure.GPIO_Pin = ECHO_PIN;	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; 
	GPIO_Init(ECHO_PORT, &GPIO_InitStructure);
}

void TIM2_Ultrasonic_Init(void)
{
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructer;
	NVIC_InitTypeDef NVIC_InitStructer;


	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
	
	/*定时器TIM2初始化*/
	TIM_DeInit(TIM2);
	TIM_TimeBaseInitStructer.TIM_Period = 999;//定时周期为1000
	TIM_TimeBaseInitStructer.TIM_Prescaler = 71; //分频系数72
	TIM_TimeBaseInitStructer.TIM_ClockDivision = TIM_CKD_DIV1;//不分频
	TIM_TimeBaseInitStructer.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructer);
	
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);//开启更新中断

	/*定时器中断初始化*/
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	NVIC_InitStructer.NVIC_IRQChannelPreemptionPriority = 2;
	NVIC_InitStructer.NVIC_IRQChannelSubPriority = 2;
	NVIC_InitStructer.NVIC_IRQChannel = TIM2_IRQn;
	NVIC_InitStructer.NVIC_IRQChannelCmd = ENABLE;
	
	NVIC_Init(&NVIC_InitStructer);
	TIM_Cmd(TIM2, DISABLE);//关闭定时器使能

}

void TIM2_IRQHandler(void) //中断,当回响信号很长是,计数值溢出后重复计数,用中断来保存溢出次数
{
	if(TIM_GetITStatus(TIM2,TIM_IT_Update) != RESET)
	{
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);//清除中断标志
		overcount++;	
	}
}
float get_ultrasonic_distance(void)
{
	float distance = 0;
	u16 tim;
	GPIO_SetBits(TRIG_PORT, TRIG_PIN);  //拉高信号,作为触发信号
	delay_us(20);  						//高电平信号超过10us
	GPIO_ResetBits(TRIG_PORT, TRIG_PIN);

	/*等待回响信号*/
	while(GPIO_ReadInputDataBit(ECHO_PORT, ECHO_PIN) == RESET);
	TIM_Cmd(TIM2,ENABLE);//回响信号到来,开启定时器计数
		
	while(GPIO_ReadInputDataBit(ECHO_PORT, ECHO_PIN) == SET);//回响信号消失
	TIM_Cmd(TIM2, DISABLE);//关闭定时器
		
	tim = TIM_GetCounter(TIM2);//获取计TIM2数寄存器中的计数值,一边计算回响信号时间
		
	distance = (tim + overcount * 1000) / 58.0;//通过回响信号计算距离
		
	TIM2->CNT = 0;  //将TIM2计数寄存器的计数值清零
	overcount = 0;  //中断溢出次数清零
	delay_ms(1);
	return distance;		//距离作为函数返回值
}


void bubble(unsigned long *a, int n)

{
  int i, j, temp;
  for (i = 0; i < n - 1; i++)
  {
    for (j = i + 1; j < n; j++)
    {
      if (a[i] > a[j])
      {
        temp = a[i];
        a[i] = a[j];
        a[j] = temp;
      }
    }
  }
}

float get_average_distance(void)
{
	float Distance;
	unsigned long ultrasonic[5] = {0};
	int a,num = 0;
	int lastDistance;
	while (num < 5)
	{
		Distance = get_ultrasonic_distance();
		while(((int)Distance >= 500 || (int)Distance == 0))
		{
			Distance = get_ultrasonic_distance();
		}

		if(Distance >0 || (int)Distance <500)
		{
			ultrasonic[num] = Distance;
			//lastDistance=Distance;
			num++;
			delay_ms(10);
		}

	}
	num = 0;
	bubble(ultrasonic, 5);
	Distance = (ultrasonic[1] + ultrasonic[2] + ultrasonic[3]) / 3;
	return Distance;
	//printf("Distance=%d\n",Distance);
}

ultrasonic.h

#ifndef __ULTRASONIC_H__
#define __ULTRASONIC_H__

#include "delay.h"
#include "sys.h"

#define TRIG_RCC		RCC_APB2Periph_GPIOB
#define ECHO_RCC		RCC_APB2Periph_GPIOB

#define TRIG_PIN		GPIO_Pin_8
#define ECHO_PIN		GPIO_Pin_7

#define TRIG_PORT		GPIOB
#define ECHO_PORT		GPIOB

void ultrasonic_gpio_init(void);
void TIM2_Ultrasonic_Init(void);
float get_ultrasonic_distance(void);
void bubble(unsigned long *a, int n);
float get_average_distance(void);

#endif

main.c

#include "led.h"
#include "delay.h"
#include "sys.h"
#include "pwm.h"
#include "lcd1602.h"
#include "ultrasonic.h"
#include <stdio.h>
#include <string.h>
#include "car.h"

u8 display_buf[16] = {0};

int main(void)
{	
	float left_distance = 0.0;
	float right_distance = 0.0;
	float current_distance = 0.0;
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	delay_init();	    	  
	LED_Init();		  
	LCD1602_Init();   
	TIM3_PWM_SG90_Init(TIM3_PWM_SG90_PERIOD,TIM3_PWM_SG90_PRESCALER);//PWM频率=72000/((1200)*(1200))=50hz 
   
    ultrasonic_gpio_init();
	TIM2_Ultrasonic_Init();
    
	TB6612_GPIO_Init();
	TIM1_Int_Init(719, 0);//72000000/720 =  100KHz 
	TIM_SetCompare2(TIM3, SG90_CENTRE);
    delay_ms(1000);
	while(1)
	{
		current_distance = get_average_distance();
        sprintf((char *)display_buf, "dis:%3.0fcm   ", current_distance);
		LCD1602_Show_Str(0,0,display_buf, strlen((char *)display_buf));
		if(current_distance <= 30.0)  //距离小于30cm
		{
			stop();  //停止
			delay_ms(100);
			TIM_SetCompare2(TIM3, SG90_LEFT); //舵机左转
			delay_ms(1000);
			left_distance = get_average_distance(); //得到左边的距离
			TIM_SetCompare2(TIM3, SG90_RIGHT); //右转
			delay_ms(1000);
			right_distance = get_average_distance(); //得到右边的距离
           
            TIM_SetCompare2(TIM3, SG90_CENTRE);
             delay_ms(1000);
            if(left_distance > right_distance)
            {
                go_back();
                delay_ms(600);
                ture_left();
                delay_ms(300);
            }
            else 
            {
                go_back();
                delay_ms(600);
                ture_right();
                delay_ms(300);
            }
		}
        else
        {
            go_straight();
        }

	} 
}

注:工程源码的模板参考正点原子stm32f103系列标准库版。文章来源地址https://www.toymoban.com/news/detail-784042.html

到了这里,关于基于stm32的智能小车设计(一)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【毕业设计】基于STM32的智能路灯设计与实现 - 物联网 嵌入式 单片机

    Hi,大家好,这里是丹成学长,今天向大家介绍一个 单片机项目 基于STM32的智能路灯设计与实现 大家可用于 课程设计 或 毕业设计 单片机-嵌入式毕设选题大全及项目分享: https://blog.csdn.net/m0_71572576/article/details/125409052 每当夜幕降临,城市中各种各样、色彩缤纷的路灯亮起,

    2024年01月16日
    浏览(63)
  • 毕业设计 - 基于STM32的智能路灯设计与实现 - 物联网 嵌入式 单片机

    Hi,大家好,今天向大家介绍一个 单片机项目 基于STM32的智能路灯设计与实现 大家可用于 课程设计 或 毕业设计 🔥 项目分享与指导: https://gitee.com/sinonfin/sharing 每当夜幕降临,城市中各种各样、色彩缤纷的路灯亮起,为城市披上了一层绚丽的外衣。但在这绚丽的外表下则隐

    2024年02月05日
    浏览(53)
  • 【毕业设计】基于单片机的智能鱼缸系统设计与实现 - 嵌入式 物联网 stm32 51单片机 智能鱼缸

    Hi,大家好,今天向大家介绍一个 单片机项目, 大家可用于 课程设计 或 毕业设计 基于单片机的智能鱼缸系统设计与实现 🔥 项目分享与指导: https://gitee.com/sinonfin/sharing 近年以来,随着我国综合实力飞速飙升,人们对物质和精神生活质量的要求也不断提升,各式各样的智能

    2024年02月04日
    浏览(85)
  • 【毕业设计】基于单片机的智能感应垃圾桶设计与实现 - 物联网 stm32 嵌入式

    Hi,大家好,这里是丹成学长,今天向大家介绍一个 单片机项目 基于单片机的智能感应垃圾桶设计与实现 大家可用于 课程设计 或 毕业设计 单片机-嵌入式毕设选题大全及项目分享: https://blog.csdn.net/m0_71572576/article/details/125409052 学长设计的系统主要使用 STC89C52 单片机为基础设

    2024年01月17日
    浏览(121)
  • 【单片机毕业设计1-基于stm32c8t6的智能加湿系统】

    🔥这里是小殷学长,单片机毕业设计篇1 基于stm32的智能加湿系统 🧿创作不易,拒绝白嫖 可私 ------------------------------------------智能加湿系统----------------------------------------- 1.按键进行界面模式切换和参数阈值调节(定时时间、温湿度值) 2.蓝牙进行界面模式切换和参数阈值

    2024年02月11日
    浏览(56)
  • 毕业分享 stm32智能平衡小车设计与实现

    文章目录 0 前言 1 项目背景 2 设计思路 3 硬件设计 4 软件设计 4.2 直立控制程序设计 4.3 速度控制程序设计 4.4 方向控制程序设计 4.5 关键代码 5 最后 🔥 这两年开始毕业设计和毕业答辩的要求和难度不断提升,传统的毕设题目缺少创新和亮点,往往达不到毕业答辩的要求,这

    2024年02月02日
    浏览(49)
  • 毕业设计 基于STM32的自动跟随小车

    🔥 这两年开始毕业设计和毕业答辩的要求和难度不断提升,传统的毕设题目缺少创新和亮点,往往达不到毕业答辩的要求,这两年不断有学弟学妹告诉学长自己做的项目系统达不到老师的要求。 为了大家能够顺利以及最少的精力通过毕设,学长分享优质毕业设计项目,今天

    2024年02月05日
    浏览(45)
  • 毕业设计——基于STM32单片机的绿植养护系统(物联网、智能家居、手机APP控制、自动监测土壤湿度)

    本工程包括一下功能:1、环境温湿度监测                                     2、土壤湿度监测                                     3、环境可燃气体浓度监测                                     4、RTC万年历功能                                     5、数据

    2024年02月15日
    浏览(85)
  • 单片机毕业设计 stm32智能扫地机器人设计与实现

    Hi,大家好,学长今天向大家介绍一个 单片机项目,大家可用于 课程设计 或 毕业设计 基于stm32的智能扫地机器人设计与实现 随着人口老龄化的到来和人民对提升生活品质的需要, 人们对在现实生活场景中取代人力的服务机器人有着迫切的需要。 同时, 机电、 自动控制、

    2024年02月04日
    浏览(53)
  • 【毕业设计】39-基于单片机的智能小车寻迹系统的设计与实现(原理图工程+仿真工程+源代码+答辩论文+答辩PPT)

    附件包含:均为毕业设计全配套资料 原理图工程文件 原理图截图 仿真工程文件 源代码 仿真截图 实物图 答辩论文低重复率 答辩PPT 主要内容: 理解并掌握单片机的基本知识;设计一款能够让小车的寻迹功能,使小车智能化实现自动寻迹。 基本要求: 1、掌握单片机的发展情

    2024年01月20日
    浏览(45)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包