2019-11-29 21:33:59 qq_42011552 阅读数 77
  • 定时器和计数器-第1季第10部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第10个课程,主要内容是51单片机的定时器和计数器,本课程的学习目标是对定时器的作用和意义有深入理解,掌握通过操作寄存器来操作硬件的思路和方法。

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

51单片机定时器0中断与串口使用相冲突

做小毕设的时候想实现一秒发送一个数据,被这个问题困扰了很久,后来在一位小伙伴的帮助下解决了问题,在这里还是谢谢热心解答问题的群友。
  • 下面是最开始写的代码,冲突了
void ET0_init()//定时器0初始化             
{
   
	TMOD &= 0xf0;			//定时器0
    TMOD |= 0x01;
    TH0  = (65536-2000)/256; //2ms溢出
    TL0  = (65536-2000)%256;

}
void UartConfigurationInit()
{
    TMOD=0x21;  //设置定时器1工作方式为方式2   
    TH1=0xfd;  	  //波特率9600
    TL1=0xfd;  
    TR1=1;      //启动定时器1     
    SM0=0;SM1=1;      //串口方式1         
    REN=1;      //允许接收   
    PCON=0x00;  //关倍频   
    ES=1;       //开串口中断   
    EA=1;       //开总中断
}
void main()
{   ET0_init();    //定时器0初始化
   ET0  = 1;
   TR0  = 1;
   ET1  = 1;
   TR1  = 1;


UartConfigurationInit(); //初始化串口
   CS   = 0;
   while(1)
   
	}
}    

//从定时器0初始化课串口初始化开始就相互冲突了,下在开发板上没有任何反应,即便是加上中断函数
  
  void time() interrupt 1//定时器0事件子程序函数
{
	TH0=(65536-50000)/256;	  //每次进来都赋值
	TL0=(65536-50000)%256;	  //
	i++;
	if(i==10)
	{	i=0;
		P1=~P1;
	}
}

小灯也是不会闪的(p1口 外围电路是小灯),后来和小伙伴一起测试了很久,发现是ET1的问题,不能将定时器1中断打开,因为在串口通信中,定时器1用的是八位自动重载模式产生波特率,所以和定时器0中断相冲突,下面给出可以进中断的代码,供大家参考。


void ET0_init()//定时器0初始化             
{   
    EA=1;	 //打开总中断
	TR0=1;	 //打开定时器0中断
	ET0=1;	 //启动定时器0中断
	TH0=(65536-50000)/256;//无脑赋值 相当于50毫秒进入一次中断函数,20次就是1秒
	TL0=(65536-50000)%256; //无脑赋值
	TMOD= 0x01;//设置定时器t0的工作方式
}
void UartInit()	//串口定时器1初始化
{
     EA=1; //打开总中断
	 ES=0; 
    TMOD|=0x20;  //设置定时器1工作方式为方式2   
	 TR1=1;      //启动定时器1  
    TH1=0xfd;  	  //波特率9600
    TL1=0xfd;  
    SCON=0X50;      //允许接收   
}

这样初始化就可以了,如果有不同的意见或者错误,欢迎大佬指出。

2017-12-10 10:22:17 qq_34706280 阅读数 1448
  • 定时器和计数器-第1季第10部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第10个课程,主要内容是51单片机的定时器和计数器,本课程的学习目标是对定时器的作用和意义有深入理解,掌握通过操作寄存器来操作硬件的思路和方法。

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

1.引言:

     在串行通信中,收发双方发送或接收的数据速率要有一定的约定,我们通过软件对MCS-51串行口编程可以约定四种工作方式。其中,方式0和方式2的波特率时固定的,而方式1和方式3的波特率是可变的,由定时器T1的溢出率决定。
    串行口的四种工作方式对应这三种波特率,由于输入的移位时钟的来源不同,所以各种方式的波特率的计算公式也不同。

2.各种方式的波特率介绍

  • 工作方式0
    方式0时,移位时钟脉冲由S6(即第6个状态周期,第12个节拍)给出,即每个机器周期产生一个移位时钟,发送或接收一位数据。所以,波特率为振荡频率的十二分之一,并不受 PCON寄存器中SMOD的影响,即:
    方式0的波特率=fosc/12,fosc是系统晶振的震荡频率
  • 工作方式2
    工作方式2波特率的产生与工作方式0不同,控制接收与发送的移位时钟由振荡频率fosc的第二节拍P2(即fosc/2)给出,所以工作方式2的波特率取决于PCON中SMOD位的值。当SMOD=0时,波特率为fosc的六十四分之一,若SMOD=1,则波特率为fosc的三十二分之一,即:方式二的波特率=[(2^SMOD)/64]*fosc
  • 工作方式1和工作方式3
    方式1和方式3的移位时钟脉冲由定时器T1的溢出率决定,故波特宰由定时器T1的溢出率与SMOD值同时决定,即:方式1和方式3的波特率=[(2*SMOD)/32]*T1溢出率。其中,溢出率取决于计数速率和定时器的预置值。计数速率与TMOD寄存器中C/T的状态有关。当C/T=0时,计数速率=fosc/2;当C/T=1时,计数速率取决于外部输入时钟频率。
    当定时器T1作波特率发生器使用时,通常选用可自动装入初值模式(工作方式2),在工作方式2中,TL1作为计数用,而自动装入的初值放在TH1中,设计数初值为x,则每过“256~x”个机器周期,定时器T1就会产生一次溢出。为了避免因溢出而引起中断,此时应禁止T1中断。这时,溢出周期为:溢出周期=(12/fosc)(256-x)*,溢出率是溢出周期的倒数,所以
    工作方式1和工作方式3的波特率=[(2*SMOD)/32][fosc/(12*(256-x))]。此时定时器1的初值*x=256-[fosc(SMOD+1)/(384*波特率)]*
    系统晶振频率选为11.0592MHZ就是为了使初值为整数,从而产生精确的波特率。
    如果串行通信选用很低的波特率,可将定时器Tl置于工作方式0或工作方式1,但在这种情况下,T1溢出时,需用中断服务程序重装初值。中断响应时间和执行指令时间会使波特率产生一定的误差,可用改变初值的办法加以调整。
    常用波特率表:
    这里写图片描述
2019-01-01 21:18:59 weixin_42462552 阅读数 1420
  • 定时器和计数器-第1季第10部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第10个课程,主要内容是51单片机的定时器和计数器,本课程的学习目标是对定时器的作用和意义有深入理解,掌握通过操作寄存器来操作硬件的思路和方法。

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

项目要求:下位机使用单片机,不断发送一个随机数值给上位机,上位机收到数据,显示在显示框中。
上位机部分:https://blog.csdn.net/weixin_42462552/article/details/85937289



一、功能介绍

1.1连接串口调试助手,对其发送随机数据,串口助手发送0x00指令,单片机停止发送数据,发送0x01指令,单片机继续发送数据。

二、开发步骤

2.1 初始化定时器,中断

   TMOD=0x20;    		//定时器T1方式2
   TH1=0xf4; 	   		//设置串行口波特率为2400 bps
   TL1=0xf4;		
   TR1=1;		     	//启动波特率发生器
   SCON=0x50;   		//串行口工作方式1,允许接收
   EA=1;		     	//开总中断允许位
   ES=1;      	 		//开串行口中断

2.2 编写随机函数

unsigned int random()
{
  	unsigned int value;
	value = rand() % (500 + 1- 1) + 1;//范围:1-500
  	return value;
}

2.3 编写串行口中断函数,接收来自PC机的数据

void serial()interrupt 4	//串行口中断类型号是4
{ 
	 unsigned char rxch; 
	 if(RI)                  //中断标志 RI=1, 数据接收 
	 { 
		RI = 0;              //软件清零 
		rxch = SBUF;         //读缓冲  
    	if (rxch==0x00)        //停止指令
    	 	 frameFlag =0;       //设置接收完标志位       
		if (rxch==0x01)      //继续指令
     		 frameFlag =1;      //设置接收完标志位       
	} 
}

2.4 发送数据函数

//功能:向串口发送一个字符串
void send_string_com(unsigned char str)
{
	SBUF = str;
	while(TI == 0);            //等待发送完毕 
    TI = 0; 
}

2.5 在main函数中完成功能

unsigned char temp;
unsigned int i;
 while(1)
 {	
	temp=random();
	if(frameFlag)
	{
		send_string_com(temp);
		for(i=0;i<6000;i++);//延时,降低单片机发送数据的频率
	}
}

三、完整代码

#include <reg52.h> 
#include <stdlib.h> 
bit frameFlag=1;       //暂停标志
void send_string_com(unsigned char str);
unsigned int random();
void delay(unsigned int i);
void main()		//主函数
{	
   unsigned char temp;
   TMOD=0x20;    	//定时器T1方式2
   TH1=0xf4; 	   	//设置串行口波特率为2400 bps
   TL1=0xf4;		
   TR1=1;		     	//启动波特率发生器
   SCON=0x50;   	//串行口工作方式1,允许接收
   EA=1;		     	//开总中断允许位
   ES=1;      	 	//开串行口中断
   while(1)
		{	
		  temp=random();
			if(frameFlag)
			{
				send_string_com(temp);
				delay(60000);
			}
		}
}
void delay(unsigned int i) //延时函数
{
	unsigned int k;
	for(k=0;k<i;k++);
}
//函数名:send_string_com
//功能:向串口发送一个字符串
void send_string_com(unsigned char str)
{
	SBUF = str;
	while(TI == 0);            //等待发送完毕 
  TI = 0; 
}
//函数名:serial
//功能:串行口中断函数,接收来自PC机的数据
void serial()interrupt 4	//串行口中断类型号是4
{ 
 unsigned char rxch; 
 if(RI)                    //中断标志 RI=1, 数据接收 
	{ 
	RI = 0;                 //软件清零 
	rxch = SBUF;            //读缓冲  
  if (rxch==0x00)         //停止指令   
	{
     frameFlag =0;            
	}
	if (rxch==0x01)          //继续指令 
	{
     frameFlag =1;          
	}
	} 
}
unsigned int random()
{
  unsigned int value;
	value = rand() % (500 + 1- 1) + 1;
  return value;
}


四、实验效果

串口调试助手效果图

2017-03-24 17:17:33 singsongsing 阅读数 276
  • 定时器和计数器-第1季第10部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第10个课程,主要内容是51单片机的定时器和计数器,本课程的学习目标是对定时器的作用和意义有深入理解,掌握通过操作寄存器来操作硬件的思路和方法。

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

    在单片机里定时器也是常用了一个东西,相比的于常用的延时delay(),更精确而且用delay()有个不太好的地方就是它把主程序执行的时间占用了很多对功能上来说没有问题,但从执行效率来说,花费大量的时间才跑delay()这种循环,好像有点划不才一样。所以在main loop 这样的程序中尽量不要去写太多的delay().除非在串口中断发送数据时必要的延时。

头文件 time.h

#ifndef _timer_h_
#define _timer_h_

extern void delay(unsigned char i);
void Delay100ms();
void timer0_init(void);
void timer0_start(void);
void timer0_stop(void);

extern unsigned int timer_cnt;

extern void timer0_init(void);
extern void timer0_start(void);
extern void timer0_stop(void);
#endif


c文件 time.c

#include "../include/timer.h"
#include "../include/stc12c5a60s2.h"

unsigned int timer_cnt;

void timer0_init(void)
{    
    //20毫秒@18.432MHz
    AUXR &= 0x7F;        //定时器时钟12T模式
    TMOD &= 0xF0;        //设置定时器模式
    TL0 = 0x00;        //设置定时初值
    TH0 = 0x88;        //设置定时初值
    TF0 = 0;        //清除TF0标志
    TR0 = 0;        //定时器0开始计时
    ET0 = 1;
}

void timer0_start(void)
{
    TR0 = 1;
}

void timer0_stop(void)
{
    TR0 = 0;
    timer_cnt = 0;
}


定时器中断interrupt.c

#include "../include/stc12c5a60s2.h"
#include "../include/timer.h"


void timer_0_isr(void) interrupt 1
{
    TF0 = 0;
    timer_cnt++;  
}


一般的话我用这个timer_cnt 这个变量来做成time_clik来用,复用成多个定时器,毕竟定时器就一两个嘛。

2019-01-26 14:09:24 weixin_44612894 阅读数 222
  • 定时器和计数器-第1季第10部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第10个课程,主要内容是51单片机的定时器和计数器,本课程的学习目标是对定时器的作用和意义有深入理解,掌握通过操作寄存器来操作硬件的思路和方法。

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

一、原理
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 帧数据。

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

2、定时器计数
定时器就是按照一个特定的频率对计数值进行加一或减一操作,当数值溢出时则产生一个标志或中断。这里是用定时器计数产生一个周期性的中断。
3、实现方法
利用定时器记录两个下降沿之间的时间,通过该时间判断是否是同步头信息、数据 1 或者数据 0。当检测到同步头,开始记录 32 个数据的时间值。

二、实现
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 <<= 1;
remote_code |= 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]);
}
}
}

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