定时器

一、原理

1、红外发射协议

红外通信的协议有很多种。这个实验使用的是NEC协议。这个协议采用PWM的方法进行调制,利用脉冲宽度来表示 0 和 1 。

NEC 遥控指令的数据格式为:同步码头、地址码、地址反码、控制码、控制反码。同步码由一个 9ms 的低电平和一个 4.5ms 的高电平组成,地址码、地址反码、控制码、控制反码均是 8 位数据格式。按照低位在前,高位在后的顺序发送。采用反码是为了增加传输的可靠性。因此,每帧的数据为 32 位,包括地址码,地址反码,控制码,控制反码。反码可用于解码时进行校验比对。

NEC码的位定义:一个脉冲对应 560us 的连续载波,一个逻辑 1 传输需要 2.25ms(560us 脉冲+1680us 低电平),一个逻辑 0 的传输需要 1.125ms(560us 脉冲+560us 低电平)。而遥控接收头在收到脉冲的时候为低电平,在没有脉冲的时候为高电平,这样,在接收头端收到的信号为:逻辑 1 应该是 560us 低+1680us 高,逻辑 0 应该是 560us 低+560us 高。

红外数据的波形如下图:包括一个同步头和 32 帧数据。

STM32 定时器实现红外遥控数据接收

下图可看出,同步头为 9ms 低电平加上 4.5ms 高电平,控制码为 8 个 0,控制反码为 8 个 1。

STM32 定时器实现红外遥控数据接收

2、定时器计数

定时器就是按照一个特定的频率对计数值进行加一或减一操作,当数值溢出时则产生一个标志或中断。这里是用定时器计数产生一个周期性的中断。

3、实现方法

利用定时器记录两个下降沿之间的时间,通过该时间判断是否是同步头信息、数据 1 或者数据 0。当检测到同步头,开始记录 32 个数据的时间值。

STM32 定时器实现红外遥控数据接收

二、实现

1、配置 GPIO 口下降沿触发中断

示例代码中使用 PA7 管脚,配置为上拉输入模式。

选择下降沿触发,是因为红外接收管默认情况下保持高电平,接收到数据时从高电平转变为低电平。

中断源选择为 EXTI_Line7 ,在库函数中对该中断源定义的服务函数为 EXTI9_5_IRQHandler(),也就是说外部中断 5 到 9 是 共用一个中断服务函数的。

配置代码如下:

void IR_Pin_init()
{
    GPIO_InitTypeDef GPIO_InitStructure;
    EXTI_InitTypeDef EXTI_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO,ENABLE);
    GPIO_InitStructure.GPIO_Pin=GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
    GPIO_Init(GPIOA,&GPIO_InitStructure);
    EXTI_ClearITPendingBit(EXTI_Line7);
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource7); 
    EXTI_InitStructure.EXTI_Line=EXTI_Line7;
    EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling;
    EXTI_InitStructure.EXTI_LineCmd=ENABLE;
    EXTI_Init(&EXTI_InitStructure); 
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); 
    NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn; 
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; 
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; 	 
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;  
    NVIC_Init(&NVIC_InitStructure);
}

2、配置定时器计数值

定时器使用的是 TIM2 通用定时器,模式为向上计数。在该模式中,计数器从 0 计数到自动加载值 (TIMx_ARR计数器的内容) ,然后重新从 0 开始计数并且产生一个计数器溢出事件。

示例函数接收两个参数,分别为预分频器的值和自动加载值。通过调整这两个参数,可以灵活地改变定时器的计数周期。例如在 TIM2 的默认时钟源 PCLK1 为96MHz时,使用语句 Tim2_UPCount_Init(SystemCoreClock/1000000-1,100-1); //0.1ms 进行初始化,可以每 0.1ms 产生一次中断。

示例代码如下:

void Tim2_UPCount_Init(u16 Prescaler,u16 Period)
{
    TIM_TimeBaseInitTypeDef TIM_StructInit;
    NVIC_InitTypeDef NVIC_StructInit;
	
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
		
    TIM_StructInit.TIM_Period=Period;
    TIM_StructInit.TIM_Prescaler=Prescaler;
    TIM_StructInit.TIM_ClockDivision=TIM_CKD_DIV1;
    TIM_StructInit.TIM_CounterMode=TIM_CounterMode_Up;
    TIM_StructInit.TIM_RepetitionCounter=0;
    TIM_TimeBaseInit(TIM2, &TIM_StructInit);
	
    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
    TIM_Cmd(TIM2, ENABLE);	
    TIM_ClearFlag(TIM2, TIM_FLAG_Update);
	
    NVIC_StructInit.NVIC_IRQChannel=TIM2_IRQn;
    NVIC_StructInit.NVIC_IRQChannelCmd=ENABLE;
    NVIC_StructInit.NVIC_IRQChannelPreemptionPriority=0;
    NVIC_StructInit.NVIC_IRQChannelSubPriority=1;
    NVIC_Init(&NVIC_StructInit);
}

3、定时器中断函数统计时间

如上所说,定时器每 0.1ms 计数完成,产生中断,在中断函数中对标志位 ucTim2Flag 加 1,意味着时间过去了 0.1ms。

时间标志位原型为 uint16_t ucTim2Flag; 。

示例代码如下:

void TIM2_IRQHandler(void)
{
    TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    ucTim2Flag++;
}

4、GPIO 中断函数中接收 32 位数据

在下降沿触发的 IO 口中断函数中,需要实现统计两个下降沿之间的时间,并将其记录在数组中。

下降沿第一次触发时,清除当前定时器中的计数值,以便统计时间。之后每一次下降沿触发就记录下当前计数值,然后再对其清零。如果该时间在同步头的时间区间内,对索引进行清零,表示重新开始接收数据。

完整接收同步头和 32 个数据之后,表示接收完成。

示例代码如下:

uint8_t irdata[33];   //用于记录两个下降沿之间的时间
bool receiveComplete; //接收完成标志位
uint8_t idx;          //用于索引接收到的数值
bool startflag;       //表示开始接收
void EXTI9_5_IRQHandler(void)
{
    uint16_t ir_time;
    if(startflag)
    {
        ir_time = ucTim2Flag;	
        if(ucTim2Flag < 150 && ucTim2Flag >= 50 ) // 接收到同步头
        {
            idx=0;  // 数组下标清零
        }
		
        irdata[idx] = ucTim2Flag;  // 获取计数时间
        ucTim2Flag = 0;            // 清零计数时间,以便下次统计
        idx++;                     // 接收到一个数据,索引加1
		
        if(idx==33)       // 如果接收到33个数据,包括32位数和以一个同步头
        {
            idx=0;
            ucTim2Flag = 0;
            receiveComplete = TRUE;
        }
    }
    else   // 下降沿第一次触发
    {
        idx = 0;
        ucTim2Flag = 0;
        startflag = TRUE;
    }
	
    EXTI_ClearITPendingBit(EXTI_Line7);  // 清除中断标志
}

5、判断控制码值

由于中断函数中接收并记录下的数据是两个下降沿之间的时间,并不是红外所发送的数据。因此需要根据红外协议,对 32 个时间进行判断,从而获得红外真正发送的数据。

下面这个函数需要在红外完整接收数据后执行,可通过判断接收完成标志位 receiveComplete 来实现。

示例代码如下:

uint8_t Ir_Server()
{
    uint8_t i,j,idx=1; //idx 从1 开始表示对同步头的时间不处理
    uint8_t temp;
    for(i=0; i < 4; i++)
    {
        for(j=0; j < 8; j++)
        {
            if(irdata[idx] > =8 && irdata[idx] < 15)   //表示 0
            {
                temp = 0;
            }
            else if(irdata[idx] > =18 && irdata[idx] < 25) //表示 1
            {
                temp = 1;
            }
            remote_code[i] << = 1;
            remote_code[i] |= temp;
            idx++;
        }
    }
	
    return remote_code[2];  // 该数组中记录的是控制码,每个按键不一样
    //for(idx=0; idx < 4; idx++)
    //{
    //	printf("remote_code[%d] = %#x\n",idx,remote_code[idx]);
    //}
}

6、主函数

在 main 函数中,对 IO 口和 定时器进行初始化。

主循环中,通过判断接收完成标志位,对接收完成的按键控制码进行打印。

示例代码如下:

void main()
{
    ...
	
    IR_Pin_init();
    Tim2_UPCount_Init(SystemCoreClock/1000000-1,100-1);
 	
    while(1)
    {
        if(repeatEnable)
        {
            repeatEnable = FALSE;
            Ir_Server();
            printf("key_code = %#x\n",remote_code[2]);
        }
    }
  
}

三、演示

如下图为串口打印出接收的红外按键值信息:

STM32 定时器实现红外遥控数据接收

说明1:这只是实现红外接收的其中一种方法,网上还有一种比较常见的方法是利用下降沿触发,在中断中进行延迟,判断高电平持续时间以此来判断信号类别。个人感觉这不是一种很好的方法,因为在中断中进行延时会导致主函数得不到及时的处理。

说明2:在调试时,不要在中断处理中加入过多无关语句,例如打印语句,这会导致结果出错。

本文档基于 STM32 F1 系列 MCU,固件库版本 3.5。其他 MCU 及固件库仅需要对库函数略作修改。

作者: cyang
转自:
cyang's blog

围观 547

1.引言

在电子仪器、仪表的制造及使用行业,有大量的印刷电路板需要调试、测量与维修,需要对电阻电容的数值进行测试。

本文介绍了一种基于AT89C51单片机和555定时器的数显式电阻和电容测量系统设计方案,然后制作出电路实物,实现系统的功能。系统利用555定时器和待测电阻(或电容)组成多谐振荡器,通过单片机定时器测量555输出信号的周期,根据周期和待测电阻(或电容)的数学关系再计算出电阻(或电容)值,再通过1602液晶显示器将其显示出来。经仿真结果表明该测量系统具有结构简单,方便实用等优点。

2.设计方案与原理

2.1 设计总方案

整个测量系统由单片机最小系统,按键,电阻、电容和555组成的多谐振荡器和液晶显示等几个电路模块组成。如图1所示。

采用555定时器和单片机的RC测量系统设计方案

2.2 多谐振荡器原理
采用555定时器和单片机的RC测量系统设计方案

如图2所示,测量电容时,利用555和待测电容CX和电阻R1和R2(R1和R2为已知电阻)等组成多谐振荡器,这样从555的输出端Q将输出周期性方波,接到示波器,如图2(b)所示。该信号不是一个占空比为50%的方波,根据参考文献2,一个周期T中高电平时间持续时间为:
采用555定时器和单片机的RC测量系统设计方案

测量电阻时,另用一个555组成一个多谐振荡器电路,将待测电阻RX接在R1的位置(或者将RX和一个已知电阻串联),CX替换成一个已知的电容C.这样一个周期时间为:
采用555定时器和单片机的RC测量系统设计方案

2.3 单片机计时原理

555输出的周期性方波信号送给单片机进行计时,测量出信号的一个周期时间T,再利用上面的数学关系进行计算处理,得到待测的电容或者电阻值。单片机计时的原理是:利用单片机的外部中断0和定时器0.555的输出信号接到单片机的外部中断0引脚P3.2,将其设置成下降沿触发。当555的输出信号为下降沿时,触发外部中断,开启单片机的定时器0开始计时,直到下一次下降沿到达时,即一个周期到达了,停止计时,这时定时器记下的就是一个周期的时间长度。

3.硬件模块设计

3.1 单片机最小系统

系统核心的控制器采用的是AT89C51单片机,图3所示为单片机最小系统,包括单片机和单片机正常工作需要的晶振电路和复位电路。Proteus中默认单片机电源和地已接好,所以图中省去了。

采用555定时器和单片机的RC测量系统设计方案

3.2 按键电路

按键电路用于确定是测量电容还是电阻,如图4所示,采用了一个单刀双掷按键。当按键打到上方接通单片机P3.6引脚时,用于测量电容;打到下方P3.7引脚时,用于测量电阻。

采用555定时器和单片机的RC测量系统设计方案

3.3 555多谐振荡器
采用555定时器和单片机的RC测量系统设计方案

如图5所示,利用555和待测电容或者电阻组成多谐振荡器,555产生的周期性方波从Q引脚输出,然后接至单片机的外部中断INT0引脚,即P3.2引脚。测量时,两电路只有一个接至单片机,分别用于测量电容和电阻。

3.4 液晶显示电路

采用555定时器和单片机的RC测量系统设计方案

测量的结果要显示出来,本系统采用LCD1602作为显示器,图6为LCD1602和单片机的连接电路,P0口接了上拉电阻,作为数据口;P2口的前3位作为读写和使能的控制引脚。

4.软件设计

系统软件流程图如7所示。接通电源,首先是初始化工作,包括定时器T0、外部中断0和LCD1602的初始化。然后启动555芯片,通过单片机判断是否有中断请求,若无的话,继续等待中断请求;若有的话,启动定时器开始计时直到有中断请求时停止计时。得到计时值,即555输出信号的一个周期后,判断是测量电阻还是测量电容。判断后将电阻或者电容值由LCD1602显示出来。

采用555定时器和单片机的RC测量系统设计方案

5.仿真结果

将上述各电路模块整合到一起,组成一个测量系统。采用Keil编写好程序无误后,在Proteus中进行电路仿真。分别测量一个50kΩ电阻和一个150μF电容的仿真结果如图8所示。从中可以看出,测量有一定的误差,这主要是因为采用前面公式计算时取了近似值。仿真通过后,按照仿真电路,购买需要的元器件,制作出实物电路。

采用555定时器和单片机的RC测量系统设计方案

6.结束语

本文介绍了一种基于555定时器和单片机的电阻和电容测量系统设计方案。在系统的设计和仿真中,是以Keil和Proteus两种软件为平台。在Keil中使用C语言编写了程序,再利用Proteus仿真了系统电路的功能。该测量电路简单可靠,较易实现,能够测量一定范围内的电阻和电容值从而证实了本设计方案的实用性。

来源: eepw

围观 425

页面

订阅 RSS - 定时器