2015-05-25 13:43:41 baiyibin0530 阅读数 1256
  • 单片机控制第一个外设-LED灯-第1季第6部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第6个课程,主要讲解LED的工作原理和开发板原理图、实践编程等,通过学习目的是让大家学会给单片机编程控制LED灯,并且为进一步学习其他外设打好基础。

    3995 人正在学习 去看看 朱有鹏
对于智能家居,有个重要的设备部分红外遥控器,因为需要它来控制一些红外家电。那么我们怎么获得这些红外编码呢?常用方法是记录遥控器发出的编码,并保存,当需要控制设备时,再将其编码发射出去。那么我们怎么使用单片机来捕获这些红外编码呢?下面我就给大家讲讲我所使用的方法。
     我们一般会使用1838作为红外编码的接收头,1838的数字输出管脚是空闲为高电平(VDD),接收到红外信号时为低电平(GND)。对于很多单片机的定时器都有PWM波捕获功能,PWM捕获功能的使用,将定时器设置为从模式,意思是由其它事件启动定时器,设定好定时器的某个通道作为PWM波捕获通道,并且设置下降沿触发PWM波捕获,开启PWM捕获完成中断,如果捕获通道上出现下降沿,那么定时器外设会将定时器计数器的值存到保存PWM周期的寄存器中,此时会产生PWM捕获完成中断,然后复位计数器,再启动定时器,当捕获通道上出现上升沿,定时器外设会将计数器的值存到保存PWM波占空比的寄存器中。当处理器接收到PWM捕获中断时,就读取PWM捕获占空比寄存器和周期寄存器的值,并保存到编码缓冲器,注意第一个PWM波值需要扔掉,因为第一个中断是第一个下降沿时产生的,此时才开始第一个PWM波的捕获,需要等到第二个中断,第一个PWM波才捕获完成,此时占空比寄存器和周期寄存器里面存放的是第一个PWM波的数据,如此继续捕获。还有一个问题是,怎么判断PWM波捕获完成呢,PWM波的结束是捕获通道上出现上升沿之后不会出现下降沿了,定时器的计数器会一直计数,直到等于定时器的周期值,然后计数器才会更新为0,如果开启了定时器的更新中断,此时会产生中断,那么我们就可以利用定时器的更新中断作为PWM波捕获的结束,将周期寄存器设置为你所期望在捕获通道上出现了上升沿多久之后没有出现下降沿,就判定PWM捕获结束,可设置为几十毫秒。
       我所使用的是STM32单片机,设置的定时器计数频率为100KHz,周期寄存器为30ms,采用定时器2的CH1作为PWM波捕获通道,那么PWM的周期寄存器为TIM2->CCR2,占空比寄存器为TIM2->CCR1。当然实际情况中还会有一些红外干扰信号,红外编码太长等等。
2019-03-12 15:30:21 shizhibuyi1234 阅读数 1259
  • 单片机控制第一个外设-LED灯-第1季第6部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第6个课程,主要讲解LED的工作原理和开发板原理图、实践编程等,通过学习目的是让大家学会给单片机编程控制LED灯,并且为进一步学习其他外设打好基础。

    3995 人正在学习 去看看 朱有鹏

因为我们公司的软件和硬件是分开的,硬件人员在设计电路板的时候,为了布线方便,往往会使用一些引脚的重映射功能。

这次使用的单片机是stm32F103ZGT6,使用PB4和PB5捕获正交编码器的数据。

因为这两个引脚本身没有定时器,只有在重映射时候才能使用TIM3的ch1和ch2。

因此,我使用重映射配置,将这两个引脚配置了以下,这是一开始的代码: 

GPIO_InitTypeDef  GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef TIM_TimBaseStructure;
	TIM_ICInitTypeDef TIM_ICInitStructure;
	
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
	
	GPIO_PinRemapConfig(GPIO_FullRemap_TIM3 ,ENABLE );                //重映射相应的外设
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	TIM_TimeBaseStructInit(&TIM_TimBaseStructure);
	TIM_TimBaseStructure.TIM_Prescaler = 0x0;
	TIM_TimBaseStructure.TIM_Period = 2400;
	TIM_TimBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
	
	TIM_TimeBaseInit(TIM3,&TIM_TimBaseStructure);
	
	TIM_EncoderInterfaceConfig(TIM3,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);
	
	TIM_ICStructInit(&TIM_ICInitStructure);
	TIM_ICInitStructure.TIM_ICFilter = 0;
	TIM_ICInit(TIM3,&TIM_ICInitStructure);
	
	TIM_ClearFlag(TIM3,TIM_FLAG_Update);
	TIM_SetCounter(TIM3,0);
	TIM_Cmd(TIM3,ENABLE);

通过读取TIM3的CNT寄存器获取当前数值。

然而无论怎样旋转编码器,都无法得到计数,计数始终为0。因此开始进行排查:

猜测1:引脚是否能够捕获到高低电平

试验:将两个引脚设置为开入,然后旋转编码器,可以正确的读到两个引脚高低电平的变换,证明是配置的问题

猜测2:是否和未开启AFIO时钟有关?

试验:开启AFIO时钟。

RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);

还是不能捕获电平,证明不是由这个引起的。

猜测3:是否其他地方使用了TIM3,导致重映射功能没有正确开启?

试验:屏蔽其他程序,在重映射之前添加一行程序

TIM_DeInit(TIM3);

问题仍然没有解决。

猜测4:该引脚复用后是否具备捕获编码器脉冲的功能?

试验:百度上搜索,然后获取到了部分重映射和全部重映射的概念。在我的认知当中,部分重映射应该是只映射部分功能,我这里配置的是全部重映射,认为应该是可以实现捕获编码器功能的。

 

然而问题没有办法解决。只能寻求参考手册的帮助。

在AFIO寄存器这一章看到了这个概念:

看到这里,我瞬间明白了,stm32中的重映射,也是针对端口的,并不是说每个端口都具备映射的全部功能。在开启这个端口重映射的时候,不能够配置成FullRemap,否则是配置失败的。

因此改成了以下样子:

GPIO_InitTypeDef  GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef TIM_TimBaseStructure;
	TIM_ICInitTypeDef TIM_ICInitStructure;
	
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
	
	GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3 ,ENABLE );                //重映射相应的外设
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	TIM_TimeBaseStructInit(&TIM_TimBaseStructure);
	TIM_TimBaseStructure.TIM_Prescaler = 0x0;
	TIM_TimBaseStructure.TIM_Period = 2400;
	TIM_TimBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
	
	TIM_TimeBaseInit(TIM3,&TIM_TimBaseStructure);
	
	TIM_EncoderInterfaceConfig(TIM3,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);
	
	TIM_ICStructInit(&TIM_ICInitStructure);
	TIM_ICInitStructure.TIM_ICFilter = 0;
	TIM_ICInit(TIM3,&TIM_ICInitStructure);
	
	TIM_ClearFlag(TIM3,TIM_FLAG_Update);
	TIM_SetCounter(TIM3,0);
	TIM_Cmd(TIM3,ENABLE);

stm32由于由库函数的支持,导致我们在使用的过程中越来越忽略掉了底层的一些运作方式。知其然而不知其所以然,在技术道路上,所有忽略掉的小细节以后都会成倍的返还给你。

2019-07-24 13:36:27 weixin_44080304 阅读数 553
  • 单片机控制第一个外设-LED灯-第1季第6部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第6个课程,主要讲解LED的工作原理和开发板原理图、实践编程等,通过学习目的是让大家学会给单片机编程控制LED灯,并且为进一步学习其他外设打好基础。

    3995 人正在学习 去看看 朱有鹏
   做平衡小车目前有两种思路,第一种是使用编码器电机,这样一般是两个闭环控制,直立闭环和速度闭环,另一种是使用步进电机,一般使用步进电机很少进行闭环控制。使用Cube进行配置时,发现几点注意事项,STM32单片机自带编码器接口,可以直接进行使用,十分方便,所以根据硬石科技的资料,编码器模式在STM32HAL库中叫做Encoder 。
   首先有一点注意事项,在配置编码器模式的时候一定要对所使用定时器的初始化进行修改:
   /* TIM2 init function */

void MX_TIM2_Init(void)
{
TIM_ClockConfigTypeDef sClockSourceConfig;
TIM_MasterConfigTypeDef sMasterConfig;
TIM_IC_InitTypeDef sConfigIC;

htim2.Instance = TIM2;
htim2.Init.Prescaler = 0;
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 65535;
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
{
_Error_Handler(FILE, LINE);
}

sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
{
_Error_Handler(FILE, LINE);
}

if (HAL_TIM_IC_Init(&htim2) != HAL_OK)
{
_Error_Handler(FILE, LINE);
}

sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
{
_Error_Handler(FILE, LINE);
}
//以下是个人修改代码 ,经过试验,没有以下代码的修改,定时器初始化后不能进入编码器模式。
sEncoderConfig.EncoderMode = TIM_ENCODERMODE_TI2;

sEncoderConfig.IC1Polarity = TIM_ICPOLARITY_RISING;
sEncoderConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI;
sEncoderConfig.IC1Prescaler = TIM_ICPSC_DIV1;
sEncoderConfig.IC1Filter = 0;

sEncoderConfig.IC2Polarity = TIM_ICPOLARITY_RISING;
sEncoderConfig.IC2Selection = TIM_ICSELECTION_DIRECTTI;
sEncoderConfig.IC2Prescaler = TIM_ICPSC_DIV1;
sEncoderConfig.IC2Filter = 0;
HAL_TIM_Encoder_Init(&htim2, &sEncoderConfig);
// if (HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_1) != HAL_OK)
// {
// _Error_Handler(FILE, LINE);
// }

// if (HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_2) != HAL_OK)
// {
// _Error_Handler(FILE, LINE);
// }

}
主循环进入前,还要进行将编码器打开
打开代码如下:
HAL_TIM_Encoder_Start(&htim2,TIM_CHANNEL_ALL);
进行读取的时候还需要设置变量进行读取
uwDirection = __HAL_TIM_IS_TIM_COUNTING_DOWN(&htim2);
CaptureNumber=__HAL_TIM_GET_COUNTER(&htim2);
__IO uint16_t time_count=0;
__IO uint32_t CaptureNumber=0;
__IO uint8_t start_flag=0;
uint32_t uwDirection = 0;//读取计数方向

HAL_TIM_Encoder_Start(&htim2,TIM_CHANNEL_ALL);
PWM_Change_Duty(1800,0);
start_flag = 1;

while (1)
{

/* USER CODE END WHILE */

/* USER CODE BEGIN 3 */
OLED_Clear();
PWM_Change_Duty(3600,0);
uwDirection = __HAL_TIM_IS_TIM_COUNTING_DOWN(&htim2);
CaptureNumber=__HAL_TIM_GET_COUNTER(&htim2);//获取计数值
OLED_Clear();
sprintf(str,“1sjs:%d”,CaptureNumber>=count?CaptureNumber-count:CaptureNumber+65535-count);
OLED_ShowString(0,0,str);
sprintf(str,“sjzs:%0.2f”,(float)(CaptureNumber>=count?CaptureNumber-count:CaptureNumber+65535-count)/11/34/2);
OLED_ShowString(0,4,str);

  count=CaptureNumber;		
	
	delay_ms(1000);

}
上述的公式内容有待完善但是可以正常使用,具体参数还要继续调整
,硬件配置时一定要打开定时器的两路接口,因为这样才能对AB相同时进行捕获
这里面的预分频应该使用0,使用的时候注意一下。
在这里插入图片描述
在这里插入图片描述
以上代码是重中之重,一定要根据自己手里的电机型号编码器种类进行配置。
在这里插入图片描述
以上用于在电机反转的时候进行修正,因为此时的捕获值与实际正好相差 65535 - CaptureNumber。
经过具体试验只有以下程序能够在中断中跑起来:(切记如果太复杂了会导致程序卡死)
在这里插入图片描述
切记在中断中再次触发其它中断一定要使第一中断优先级最高,否则无法正常进入中断程序。
以下代码的中断有限最高级是外部中断EXTI9_5其次是定时器2和定时器4,经过试验发现只有这样,编码器才能正常工作。 在这里插入图片描述
最关键问题发现,中断无法进入的另一个原因是因为没有初始化完成寄存器,因此在具体使用程序的时候将中断初始化放在最后。
在这里插入图片描述
这两个函数用来读取编码器的数值。
在这里插入图片描述
这是最终的初始化流程顺序,在程序使用外部中断的时候中断初始化函数一定要放到最后,放到其他初始化前面会导致无法对未初始化的寄存器进行初始化,这条一定要在调用外部中断进行触发的时候切记,十分重要。

2016-04-03 14:50:03 weifengdq 阅读数 6183
  • 单片机控制第一个外设-LED灯-第1季第6部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第6个课程,主要讲解LED的工作原理和开发板原理图、实践编程等,通过学习目的是让大家学会给单片机编程控制LED灯,并且为进一步学习其他外设打好基础。

    3995 人正在学习 去看看 朱有鹏

本章用增量式编码器应用为例,介绍单片机的应用方法。


编码器简介

编码器(encoder)把角位移或直线位移转换成电信号,前者称为码盘,后者称为码尺。

按照工作原理编码器可分为增量式和绝对式两类:

①增量式编码器: 将位移转换成周期性的电信号,再把这个电信号转变成计数脉冲,用脉冲的个数表示位移的大小。通常为A相、B相、Z相输出,A相、B相为相互延迟1/4周期的脉冲输出,根据延迟关系可以区别正反转,而且通过取A相、B相的上升和下降沿可以进行2或4倍频;Z相为单圈脉冲,即每圈发出一个脉冲。

②绝对式编码器: 每一个位置对应一个确定的数字码,因此它的示值只与测量的起始和终止位置有关,而与测量的中间过程无关。

这里仅介绍旋转式增量编码器的使用。


两种增量式编码器

使用欧姆龙E6B2-CWZ6C 1000P/R编码器(输出A、B、Z三相)和ASLONG JGA25-371直流减速电机(带334线编码器),为使原理清晰,选用最常见的51单片机(IAP15F2K61S2)测试。

欧姆龙E6B2-CWZ6C 1000P/R编码器外形如下:
l01

参数表如下:
l02

ASLONG JGA25-371直流减速电机外形如下(左边的两根黄线是电机引线,绿色和白色线是两组脉冲输出线,下面的未经减速的直流电机每转1周输出334个脉冲,用一根线就能测量转速,双脉冲可以判断旋转方向,红色的线接3V至5V电源给测速芯片供电,黑色线接地):
l03

参数表为:
l04

以该电机为例看一下增量式编码器的特性:

接线如下(红黑鳄鱼夹为0~15V可调电源,为电机供电;排阵为5V电源为编码器测速元件供电;示波器探头连接到编码器的A、B输出相上)
l05

电压加到11V,正转(左图)和反转(右图)波形如下:
l06

可以看出,输出脉冲很稳定,正转时A相超前于B相1/4个周期,反转时B相超前于A相1/4周期。脉冲频率约为21.3kHz,又知道电机的转速比为21.3,减速前转一周输出334个脉冲,则电机的输出转速为:
l07


编码器测位置、测转速、测角加速度原理

测位置

编码器每周的脉冲数是一定的,通过记录初始位置和转过的脉冲数可以计算转过的角度。当然,为了减小单片机的运算量,没必要换算成角度。如1000线的编码器输出500个脉冲,那么我们知道转了半圈,而没必要先换算成180度,再除以360度等于半圈。

拥有固定起始位置的装置,可以用增量式编码器测角度。如倒立摆,其起始位置为自然下垂。然而,像舵机或者机器人的关节的角度测量有时并没有绝对位置,用增量式编码器就显得局限了(除非可以解决初始位置的问题),这种情况下用电位器或绝对式编码器是不错的选择。

测转速

增量式编码器有测频率(M法)和测周期(T法)以及两种结合的M/T法(高速时M法,低速时T法)。

M法测速:记取一个采样周期Tc内旋转编码器发出的脉冲个数M来算出转速n。适用于转速较高、脉冲输出比较快的情况。原理用数学上说就是:位置的微分等于速度,角度的微分得到转速。计算公式如下(这里没有考虑减速比):
l08
式中:

  • n—转速,r/min;
  • Tc—采样周期,s;
  • M1—时间Tc内的脉冲个数;
  • Z—旋转编码器每转输出的脉冲个数。

T法测速:测出编码器两个输出脉冲之间的时间间隔来计算出转速n。
由于Tc和Z为常数,所以n与M1成正比,故称M法测速。适用于转速较低、脉冲输出较慢的情况。计算公式如下:
l09
式中:

  • M2—编码器两个脉冲之间的时钟脉冲个数;
  • f0—时钟脉冲频率,Hz。

测角加速度

转速的微分为角加速度:
l10
式中

  • aω—角加速度;
  • nk—本次测量的转速;
  • nk-1—上一次的转速;
  • Tc—采样周期。

单片机编程实现原理

单片机对外唯一需要做的是捕获脉冲。

脉冲捕获的方法

①定时器的计数器模式,来一个脉冲计一个数(传统的51单片机都有Timer0、Timer1,STC89C52和IAP15F2K61S2均有Timer2,STC12系列没有Timer2。STM32的定时器有一大堆,当然只当计数器浪费了,因为其可以配置为正交编码模式)。

②外部中断(一般都有INT0、INT1,有的有INT2、INT3等),如STC89C52有INT0、INT1,支持下降沿触发和低电平触发。IAPF2K61S2有INT0~4共5路外部中断,其中INT0和INT1支持上升沿或下降沿均可触发方式和仅下降沿触发方式、INT2、INT3和INT4仅支持下降沿触发模式。

③PCA (可编程计数器阵列Programmable Counter Array)脉冲捕获(STC12C5A有2路、STC12C56有4路、STC15F2K有3路。STM32的普通定时器即可实现脉冲捕获)。

使用IAP15F2K61S2测10个编码器的速度

使用一个定时器做时基定时器测速,剩余的2个定时器计数器+5路外部中断+3路PCA=10路编码器测速,再占用10个IO口可以判断编码器旋转方向。其中INT0、INT1、3路PCA是可以编码器2倍频的,下面会有解释。

采样周期Tc

因为编码器运算一次也是定时器中断一次,所以,采样周期Tc其实就是时基定时器的中断时间,或者中断时间的倍数。

关于倍频

1倍频:仅对编码器一相输出进行上升沿或下降沿捕获。则1000线编码器转1圈仅捕获1000个脉冲。

2倍频:对编码器一相上升沿和下降沿均捕获,或者对两相的编码器进行一个边沿捕获(这个很少用),则1000线编码器转1圈可以捕获2000个脉冲。一般有的外部中断中和PCA支持上升沿、下降沿同时捕获。

4倍频:对编码器A、B相的上升沿和下降沿均捕获。这样1000线的编码器转1圈可以捕获到4000个脉冲,精度大大提高。(对于STC12或者IAP15F2K61S2,使用两路PCA进行脉冲捕获,配置为上升下降均中断。对于STM32可以直接把定时器配置为正交编码接口,上下沿均采样,一个定时器搞定一个编码器)。

由以上分析可知:

对于拥有3个定时器、2个外部中断(贴片的有4个外部中断)的直插式STC89C52来说,仅测位置的话,可以1倍频测量多达5个编码器的位置。

对于拥有6个定时器(含3路PCA)、5个外部中断的IAP15F2K61S2来说,仅测位置的话,可以1倍频测量多达11个编码器的位置。其中,3路PCA、2个外部中断支持上升沿和下降沿均捕获,可以配置成2倍频或4倍频测量。

如果需要测量速度,则需要牺牲一个定时器作为时基,定时(转速公式中的采样周期Tc)中断来测速,如若中断中10ms查询一次,则Tc=0.01s。即每10ms计算一次速度。
当然,如果需要Z相的话,可能需要再牺牲一路。


需要考虑的问题

为什么要考虑中断优先级

STC15W4K60S4系列单片机的中断优先级如下:
l11

为什么要考虑中断优先级?假设使用中断优先级为7的PCA进行脉冲捕获,频率为25kHz,即40μs进一次PCA中断;此时,若又有一个优先级为1的定时器0中断,且其内部程序执行时间大于40μs,则会出现丢脉冲现象。所以,一般所有中断中的程序加起来的时间不宜超过最大的编码器脉冲输出频率的倒数。

当然,对于进去就关总中断的流氓函数来说,其执行时间最好不要超过所有脉冲捕获的间隔。

倍频是否一定好

倍频可以获得更高的位置精度,如1000线的编码器1倍频的分辨率为360/1000=0.36度,2倍频的分辨率为360/2000=0.18度,4倍频的分辨率为360/4000=0.09度。这是很诱人的。

但上节中已经介绍,所有中断中的程序加起来的时间不宜超过最大的编码器脉冲输出频率的倒数。2倍频则意味着这个时间缩短了一倍,频繁的中断会极大的影响CPU做其它工作。所以精度满足的情况下,没必要追求更高的倍频。

51单片机的using

对于51单片机(STM32请绕过),如我们写定时器1中断函数时,常写:void tm1_isr() interrupt 3 using 1。其中3是Timer1的中断优先级,那么using后的1呢?

using是C51中的关键字,keil的help中给出了using的解释:

In all members of the 8051 family, the first 32 bytes of DATA memory (0x00-0x1F) is grouped into 4 banks of 8 registers each. Programs access these registers as R0-R7. The register bank is selected by two bits of the program status word, PSW.
Register banks are useful when processing interrupts or when using a real-time operating system because the MCU can switch to a different register bank for a task or interrupt rather than saving all 8 registers on the stack. The MCU can then restore switch back to the original register bank before returning.

所以,我们可以在中断函数分配寄存器组(using 0~3,主函数默认使用0,则其他中断可以使用寄存器组1~3)来省去压栈的时间,加快中断的切换速度。当然,using可以修饰任何函数,不过建议只用来修饰中断函数。同级中断设成同样的寄存器组。中断中调用的函数最好不要被中断意外的其他函数调用,否则会出现“重复调用”的警告。尽管可以用reentrant来修饰,但会占用大量堆栈空间。当然,也可以分成两个不同名的函数写。有时使用using会带来“莫名其妙”的错误,在中断中被调用的函数最好使用using指定与中断函数相同的寄存器组。因此,对using关键字的使用,如果没把握,宁可不用,交给编译系统自己去处理好了。


编码器测速编程举例

使用定时器的计数器模式测速度

演示程序给出了定时器0作基准定时器,计数器1捕获脉冲的测速方法, 先设置定时器1的计数器模式(timer.c,注意不要使能定时器2的中断):

void Timer_Init(unsigned int T_N100us)  //百微秒
{
    unsigned int T_100us;

//  AUXR |= 0x80;                       //定时器0为1T模式  //33.1776MHz下最多1.97ms
//  T_100us = 65536-FOSC/10000*T_N100us;    //1T模式
    AUXR &= 0x7f;                       //定时器0为12T模式 //33.1776MHz下最多23.7ms
    T_100us = 65536-FOSC/12/10000*T_N100us;     //12T模式
//    TMOD = 0x00;                      //设置定时器为模式0(16位自动重装载)
    TMOD= 0x40;                         //设置定时器为模式0(16位自动重装载),定时器1为计数器模式
    TL0 = T_100us;                      //初始化计时值
    TH0 = T_100us >> 8;
    TL1 = 0x01;
    TH1 = 0x01; 
    TR0 = 1;                            //定时器0开始计时
    TR1 = 1;                            //定时器1开始计数
    ET0 = 1;                            //使能定时器0中断
    EA = 1;     
}

然后在定时器0中断(interrupt.c)中判断速度(程序中得出1s输出的脉冲数Freq):

//使用Aslong的JGA25-371直流减速电机:334线编码器,减速比为 21.3,12V额定电压,额定转速201rpm
    //那么额定转速下10ms输出脉冲数:201*21.3*334/60/100=238.3257个脉冲
    unsigned char ch,cl;
    static unsigned int temp=0;
    static unsigned int temp_1=0;   //上次的值
    cl=TL1; //先读低位(高位变得没那么快)
    ch=TH1;
    temp_1=temp;
    temp=ch*256+cl; //用左移怎么实现? ch<<8+cl
    //if(temp>=temp_1) Freq=(temp-temp_1)/5;            // *200/1000 kHz              //20kHz 每5ms 计100个数
    //else Freq=(65536-temp_1 + temp)/5;
    if(temp>=temp_1) Freq=(temp-temp_1)*100;            //1s的脉冲数,即频率
    else Freq=(65536-temp_1 + temp)*100;

原作于 2014年10月
CSDN发表于2016年4月
weifengdq

2019-11-07 17:39:06 weixin_42098782 阅读数 25
  • 单片机控制第一个外设-LED灯-第1季第6部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第6个课程,主要讲解LED的工作原理和开发板原理图、实践编程等,通过学习目的是让大家学会给单片机编程控制LED灯,并且为进一步学习其他外设打好基础。

    3995 人正在学习 去看看 朱有鹏

在诸如F103系列、51系列的单片机中,由于没有正交解码模块,在需要测速的场合中,往往需要借助于输入捕获、甚至是计数的方法来获得编码器输出,这样会导致了主程序会经常被中断打断,并且计数也不是特别准确,而采用外接计数器的方法,则可以大大减少CPU的负载,CPU只需要定时读取计数器值即可,这在需要采集多个速度的场合下尤为必要。

CD4520是一款CMOS系列双四位递增计数器,附上数据手册:https://download.csdn.net/download/weixin_42098782/11964639

其供电电压范围达0-20V,计数频率最大为64M,是一款理想的外部计数模块。管脚分布如下:

真值表如下:

可以看出,CD4520支持两种脉冲输入方式,其CLOCK脚与ENABLE脚相配合可以实现不同的脉冲计数模式,

最常用的用法还是计数器级联的方式,将两个计数器设置为八位计数方式,接线如下,其中最低位的输出未使用,在程序中可以利用类似

		TIM2CH1_CAPTURE_VAL1 = (GPIO_ReadInputData(GPIOB)&0xfe);	//忽略最低位读数B7..1	

的语句来定时读取,当然如果要提高计数精度,同样可以将1Q0管脚连接上。

1号计数器接受上升沿脉冲并计数,在计数至0b1111时,1号计数器溢出归零,1Q3管脚由1跳变为0,该脉冲同时输入E2管脚作为下降沿脉冲,引发2号计数器计数,理想情况下,计数器位数可以无限级联,当然受限于管脚数量与功耗,一般2片CD4520就足够满足高频计数要求,在考虑节约管脚时,可以将计数输出脚与并口转串口相连即可。

欢迎联系QQ:2452683094讨论。

没有更多推荐了,返回首页