这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » DIY与开源设计 » 电子DIY » w759067820的智能车DIY进程贴--!更新至小车绕8字!

共19条 2/2 1 2 跳转至
菜鸟
2015-09-25 15:58:39     打赏
11楼

有了视频之后,接下来我们一起来学习STM32的定时器吧。

 

先来了解了解STM32F103C8T6定时器,此芯片一共有四个定时器,其中一个高级定时器TIM13个通用定时器TIM2~TIM3,我主要学习的是通用定时器。高级定时器我自己现在学得还不是很明白,呵呵。通用定时器有四个独立通道。可以用来作为输入捕获、输出比较,PWM生成(边缘或中间对齐模式)、单脉冲模式输出。

这个小车四个定时器均有使用,其中使用了TIM2TIM4的编码器,TIM3用于PWM波输出功能。定时器的溢出时间公式计算如下:

                     Tout=((arr+1)×(psc+1))/Tclk

其中TclkTIM3的输入时钟频率,ToutTIM3溢出时间

注意:STM32的通用定时器是挂在APB1上的,但是时钟频率是72MHz,是APB1的两倍

 

接下来以TIM2的定时器中断为例介绍一下定时器的使用过程

第一步:TIM2时钟使能

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

 

第二步:初始化定时器参数

定时器的参数全在下面这个结构体里面

typedef struct

{

  uint16_t TIM_Prescaler;                                              

  uint16_t TIM_CounterMode;      

  uint16_t TIM_Period;          

  uint16_t TIM_ClockDivision;   

  uint8_t TIM_RepetitionCounter; 

} TIM_TimeBaseInitTypeDef;

第一个参数是用来设置分频系数的。

第二个参数是用来设置计数方式的,有向上计数、向下计数和中央对齐计数方式,比较常用的是向上计数模式。

第三个参数是设置自动重载计数周期值

第四个参数是用来设置时钟分频因子的

第五个参数只有使用高级定时器时才使用,这里就先不做介绍。

 

第三步:设置TIM2的允许更新中断,在库函数中是通过TIM_ITConfig函数来实现的

TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);

 

第四步:进行TIM2的中断优先级设置

在定时器中断使能之后因为要产生中断,必不可少地要设置NVIC相关寄存器及中断优先级。

关于中断优先级的讲解,下面这个链接讲的还是比较清楚

http://www.amobbs.com/thread-5540241-1-1.html

 

第五步:使能定时器TIM2

光配置好定时器还不能使用,不要忘了还要开启它哦

TIM_Cmd(TIM2, ENABLE);

 

第六步:编写中断服务函数

定时器溢出中断后,我们就可以在中断服务函数干自己想干的事啦,哈哈

接下来还是贴上我自己的代码吧

void TIM2_Init(u16 arr, u16 psc) //arr:自动重装值。psc:时钟预分频数
{
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
	
	TIM_TimeBaseStructure.TIM_Period = arr;
	TIM_TimeBaseStructure.TIM_Prescaler = psc;
	TIM_TimeBaseStructure.TIM_ClockDivision  = 0;
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数
	//TIM_TimeBaseStructure.TIM_RepetitionCounter = 高级定时器才用,通用定时器不用
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
	
	NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);
	
	TIM_Cmd(TIM2, ENABLE);
}


这就是定时器参数的配置代码,在附上中断函数的代码吧

void TIM2_IRQHandler(void)
{
	if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
	{
		LED1 =! LED1;
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
	}
}

 


 


菜鸟
2015-09-25 15:59:49     打赏
12楼
说得好

菜鸟
2015-10-21 17:55:38     打赏
13楼

电机驱动实验

查看原理图了解驱动芯片及接口;



第一步。先了解驱动,TB6612FNG是一款直流电机的驱动芯片。两路输入信号可以控制电机的正转反转刹车和停止。

允许输入最大的电压为15V

最重要的就是了解功能驱动表:



第二步。了解PWM定时器。两轮平衡车有两个电机,需要两路PWM波驱动,根据原理图,可以看出,小车的电机的驱动连接的是 PB0 PB 1 两个端口,通过查找STM32数据手册知道用的TIM3的通道3和通道4


附上重要的程序:

首先需要定义电机正反转控制端口

#define AIN2   PBout(15)
#define AIN1   PBout(14)
#define BIN1   PBout(13)
#define BIN2   PBout(12)

这样的宏定义方便以后直接用AN1 AN2 .。简洁明了

PWM控制

/**************************************************************************
函数功能:PWM 以及电机控制的IO初始化
入口参数:arr:自动重装值  psc:时钟预分频数 
返回  值:无
**************************************************************************/
void PWM_Init(u16 arr,u16 psc)
{
	GPIO_InitTypeDef	GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	TIM_OCInitTypeDef	TIM_OCInitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);   //使能 GPIOB 时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);    //使能 TIM3 时钟
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1;   //设置PWM输出 IO 口 PB0 PB1
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;        //复用推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;      //IO速度50MHz
	GPIO_Init(GPIOB,&GPIO_InitStructure);				   //根据设定值初始化
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15;  //设置电机驱动控制IO PB12 PB13 PB14 PB15
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;  								//推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;								//最高输出速率 50MHz	
	GPIO_Init(GPIOB, &GPIO_InitStructure);											//根据设定值初始化
	
	TIM_TimeBaseStructure.TIM_Period = arr;						 //重装值
	TIM_TimeBaseStructure.TIM_Prescaler = psc;					 //预分频值
	TIM_TimeBaseStructure.TIM_ClockDivision = 0;                 //设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
	TIM_TimeBaseInit(TIM3,&TIM_TimeBaseStructure);				 //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
	
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;			  //TIM脉冲宽度调制 PWM模式1
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
	TIM_OCInitStructure.TIM_Pulse = 0;                            //设置待装入捕获比较寄存器的脉冲值
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;     //输出极性:TIM输出比较极性高

	TIM_OC3Init(TIM3, &TIM_OCInitStructure);           //根据TIM_OCInitStruct中指定的参数初始化外设TIMx
	TIM_OC4Init(TIM3, &TIM_OCInitStructure);           //根据TIM_OCInitStruct中指定的参数初始化外设TIMx

	
	TIM_OC3PreloadConfig(TIM3, TIM_OCPreload_Enable);  //CH3预装载使能	 
	TIM_OC4PreloadConfig(TIM3, TIM_OCPreload_Enable);  //CH4预装载使能
	
	TIM_ARRPreloadConfig(TIM3, ENABLE);                //使能TIM3在ARR上的预装载寄存器
	
	TIM_Cmd(TIM3, ENABLE);                             //使能TIM3
	TIM_SetCompare4(TIM3,200);
	TIM_SetCompare3(TIM3,200);
}


		

TB6612驱动电机:

/**************************************************************************
函数功能:PWM驱动电机转动
入口参数:左轮PWM、右轮PWM
**************************************************************************/
void Set_Pwm(int moto1,int moto2)
{
			if(moto1<0) AIN2=1, AIN1=0; else AIN2=0, AIN1=1; TIM_SetCompare4(TIM3,moto1); if(moto2<0) BIN1=0, BIN2=1; else BIN1=1, BIN2=0; TIM_SetCompare3(TIM3,moto2); }




菜鸟
2015-10-23 17:15:41     打赏
14楼

编码器数据采集实验

首先了解编码器的硬件,此小车采用的是二手瑞士电机 具有512线的正交编码器 ,指的是电机转一圈,产生512正交脉冲信号。增量式正交编码器, 它产生两个方波信号 A B 它们相差+/- 90°, 其符号由转动方向决定。

STM32F10x 的所有通用定时器及高级定时器都集成了正交编码器接口。定时器的两个输入 TI1 TI2 直接与增量式正交编码器接口。

选择编码器接口模式的方法是:如果计数器只在 TI2 的边沿计数,则置 TIM1_SMCR 寄存器中的 SMS=001;如果只在 TI1 边沿计数,则置 SMS=010 如果计数器同时在 TI1 TI2 边沿计数,则置 SMS=011

32的编码器标准模式为X4. 1000线的编码器每转一周可发出4000个计数脉冲

在编写程序之前,先看原理图。以编码器1为例。接口为PA0和PA1,对应的是STM32的TIM2 CH1和CH2。

所以我们需要配置定时器2的编码器模式。由于使用的是库函数,所以必须了解的是编码器配置函数 TIM_EncoderInterfaceConfig





下面将我的程序附上


编码器初始化程序

/**************************************************************************
函数功能:编码器1初始化
入口参数:无
返回  值:无
**************************************************************************/
void Encoder1_Init(void)
{
	TIM_ICInitTypeDef  TIM2_ICInitStructure;
	GPIO_InitTypeDef	GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //使能PB 6 7时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);  //使能TIM4 时钟
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1;    //设置PWM输出 IO 口 PB6 PB7
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;   //浮空输入
	GPIO_Init(GPIOA,&GPIO_InitStructure);				    //根据设定值初始化
	

	TIM_TimeBaseStructure.TIM_Period = ENCODER_TIM_PERIOD-1;       //设置在下一个更新事件装入活动的自动重装载寄存器周期的值	 计数到5000为500ms
	TIM_TimeBaseStructure.TIM_Prescaler = 0;                       //设置用来作为TIMx时钟频率除数的预分频值 7200得到 10Khz的计数频率  
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up|TIM_CounterMode_Down;  //TIM中央对齐模式3
	TIM_TimeBaseStructure.TIM_ClockDivision = 0;        //设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);                //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位

	TIM_ITConfig(TIM2, TIM_IT_Update ,ENABLE);  //使能//TIM1//使能或者失能指定的TIM中断

	NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;  //TIM4中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;  //先占优先级1级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;  //从优先级3级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
	NVIC_Init(&NVIC_InitStructure);  //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
	
//TIM_EncoderInterfaceConfig(TIM2,TIM_EncoderMode_TI12,TIM_ICPolarity_Falling|TIM_ICPolarity_Rising,TIM_ICPolarity_Falling|TIM_ICPolarity_Rising);
	TIM_EncoderInterfaceConfig(TIM2,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);
	
	
  	TIM2_ICInitStructure.TIM_ICFilter = 0x03;           //IC1F=0011 配置输入滤波器 fSAMPLING=fCK_INT, N=8
  	TIM_ICInit(TIM2, &TIM2_ICInitStructure);
	
	TIM_SetCounter(TIM2, COUNTER_RESET);
		
	TIM_Cmd(TIM2,ENABLE);
}

 

在进行编码器读取的时候一定要考虑中断,不然读取编码器的值不会清零。


读取数据程序

/**************************************************************************
函数功能:读取编码器的数据并进行数据类型转换
入口参数:无
返回  值:无
**************************************************************************/

void ReadEncoder(void)
{
	  u16 Encoder_L,Encoder_R;       //===左右编码器的脉冲计数
		Encoder_R = TIM4 -> CNT;       //===获取正交解码1数据	
		TIM4 -> CNT=0;                 //===计数器清零  
	  Encoder_L= TIM2 -> CNT;        //===获取正交解码2数据	
	  TIM2 -> CNT=0;	               //===计数器清零
		if(Encoder_L>32768)  Encoder_Left=Encoder_L-65000; else  Encoder_Left=Encoder_L;  
	  //=这个处理的原因是:编码器到0后会跳到65000向下计数,这样处理方便我们在控制程序中使用
	  if(Encoder_R>32768)  Encoder_Right=Encoder_R-65000; else  Encoder_Right=Encoder_R;
	 	Encoder_Left=-Encoder_Left;//这里取反是因为,平衡小车的两个电机是旋转了180度安装的,为了保证前进后退时候的编码器数据符号一致
}

 


 


菜鸟
2015-10-23 18:20:30     打赏
15楼

蓝牙控制

1安装APP

将官方提供的蓝牙手机客户端安装到安卓手机上


2.打开小车开关,并平衡立于地面上

3.蓝牙配对

在手机上打开刚安装的蓝牙应用,允许打开蓝牙连接,选择菜单连接设备的SPP-CA连上,输入密码为1234.在连接蓝牙时一定要打开小车开关,否则无法搜索到小车的蓝牙

4.开始控制平衡小车

将软件界面的蓝色圆盘往前拖,小车前进。往后拖,小车后退。往左拖,小车向左转弯。(如果一直拖住不放,小车就原地旋转)。往右拖,小车向右转弯。

如此就完成了蓝牙控制的全过程

注意:在源程序中,小车的转弯速度过快,在实际控制中,很难使小车实现精确的转角,现将源程序中的MiniBalance.c做如下改动。



菜鸟
2015-10-23 19:58:45     打赏
16楼

超声波避障

PA2接trig(控制端),PA3接echo(接收端)


我使用的超声波模块是HC-SR04,如下图所示

1.基本原理

(1)采用IO口trig触发测距,给至少10us的高电平信号;

(2)模块自动发送8个40khz的方波,自动检测是否有信号返回;

(3)有信号返回,通过IO口echo输出一个高电平,高电平持续的时间就是超声波从发射到返回的时间。

测试距离=(高电平时间*声速(340M/S))/2;

超声波时序图

2.操作

初始化时将trig和echo端口都置低,首先向给trig发送至少10us的高电平脉冲(模块自动向外发送8个40K的方波),然后等待,捕捉echo端输出上升沿,捕捉到上升沿的同时,打开定时器开始计时,再次等待捕捉echo的下降沿,当捕捉到下降沿,读出计时器的时间,这就是超声波在空气中运行的时间。在这个小车中是把TIM2的CH4初始化为输入捕获的。

                                                  输入捕获的初始化配置


                                                         实现输入捕获的代码实现

最后将源程序的main.c中的Flag_Bizhang置1便可实现超声波避障功能。




菜鸟
2015-10-23 21:37:12     打赏
17楼

小车绕8字行走

小车绕8字行走的原理非常简单,先让小车在平地上顺时针做圆周运动,在定时器1的中断服务函数中使Flag_Left=0,Flag_Right=1便可实现,当小车运动到起点(8字的连接点)时改变小车的运动方向,变成逆时针圆周运动,通过Flag_Left=1,Flag_Right=0实现。

改动代码如下:


小车旋转半径的控制

注意:640、620是基于我下面那个半径参数的,一旦下面的参数有所改动,这两个值也要做相应的调整



视频地址:http://player.youku.com/player.php/sid/XMTM2Nzk4MTU4NA==/v.swf




 


菜鸟
2015-10-24 11:27:52     打赏
18楼


菜鸟
2015-11-01 15:40:55     打赏
19楼

总结

   转眼间,历时长达几个月的两轮自平衡小车活动就要结束了。其实我是第一次在论坛上参加这种DIY活动,也是第一次发帖,经验不足,所以帖子中有什么不妥的地方还望大家理解与指正。

    在学习平衡车期间由于我们要准备英语四级考试和今年的全国大学生电子设计竞赛以及手上的一些其他项目,所以我们对小车的学习、调试、更贴也是断断续续的,最后总算是磕磕绊绊的完成了小车的一部分实验。在这期间要说学到了什么东西吧,确实也有一点。我觉得最大的好处就是借助了这个平台更加深入学习了STM32这一功能非常强大的主控芯片。本来我就是学习STM32的,国赛也用的是它,刚拿到小车时得知主控也用的是它,于是我非常兴奋便饶有兴趣的开始了小车的学习之旅。到现在对STM32的定时器、串口、I2CDMA等等这些东西也有了更深入的理解。其次的收获便是学会了一些模块的使用,比如超声波、蓝牙等。最后也掌握了一些PID的调试方法。说到PID的调试,当初也是吃尽了苦头,调试初期小车总是发抖或是行动迟缓,经过长达几天的参数整定终于达到了理想的效果。调PID要的就是耐心,坚持不懈。

    经过这次活动也让自己体会到了自己技术上的不足,还急需补充一些必不可少的知识,比如模数电、高数、复变函数以及积分变换等等。论坛上的大神比比皆是,代码比自己写的优秀,写得简洁的多如牛毛。说来惭愧,有些实验我也是参考别人的才能最终完成。不过没关系,人总是不断进步的嘛,参加了这次活动之前不清楚的地方现在不也明白了吗。所以我相信只要多参加这一类的活动,自己的知识层面就会不断拓展。

    随着对小车的不断深入研究,它也没有当初那么完整,那么崭新了,我从它身上学到的东西也越来越多,所以我觉得参加这次活动非常有价值,就算活动完了,我也会继续小车的学习之旅的,把之前未完成的实验高效的完成并完善一些已完成的实验。最后祝愿活动取得圆满成功,大家都能从中得到属于自己的收获。

生命不息,奋斗不止,大家继续加油!!!


共19条 2/2 1 2 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]