关于89C52单片机11.0592M晶振产生115200波特率的方法

本文详细介绍了如何在51单片机中使用定时器T1和T2实现115200波特率的方法。重点讲解了T2作为波特率发生器的应用技巧,包括其配置、寄存器设置及特殊功能如时钟输出等。

当然下面所讨论产生115200波特率的最大难题就是因为它的时钟周期是等于12个机器周期的(12T模式)

定时器T1要想作为波特率发生器,必须是工作在8位自动重装载模式下,并且禁止使能T1的中断!
还有一个重要的寄存器就是PCON了,最高位SMOD可以使得波特率加倍!当然这里由于这里的8位自动重装载模式的限制,也并不是像我们想象的那样,既然可以产生9600的波特率那个加倍以下不就是115200了嘛?!

Too young, Too simple!

先看两个公式吧,,
当波特率不加倍使用定时器T1,产生波特率的计算方法:

TH1 = TL1 = 256 - 晶振值/12/2/16/波特率

使用波特率加倍后的计算公式为:

TH1 = TL1 = 256 - 晶振值/12/16/波特率

当我们不加倍的时候,产生的最大波特率(让TH1 = 0xFF)
28800

加倍后产生的最大波特率为57600(刚好就是28800的两倍)

所以就增强型的51单片机89C52来说,T0是可定不能作为波特率发生器的,而T1貌似又达不到要求,没辙了吗? 别忘了,它还有T2呢!!!

先来简单的认识一下T2及其有关的寄存器!

先看主要的,咱们要用的!
①作为波特率发生器

T2CON的TCLK和RCLK位为0(默认)时,串行口发送和接受的波特率由定时器1提供置位为1时,由定时器2提供。可以一个通过定时器1,一个通过定时器2,这样可以获得发送和接受时不同的波特率。

注意的地方: 定时器2作为定时器时,递增频率为晶振频率的12分频,而定时器2作为波特率发生器时,它的递增频率为晶振频率的2分频。

定时器2作为波特率发生器的时候,TH2溢出并不会置位TF2,所以此时可以不用禁止定时器2中断,若是EXEN2位被置位时,可以将T2EX作为附加的外部中断。。

定时器2作为波特率发生器的时候,不要对TH2和TL2读写,可以读陷阱寄存器,但是也不要写。当对定时器2的陷阱寄存器进行访问时,应关闭定时器(TR2清0)。

波特率 = 模式1和模式3的波特率=(振荡器频率/32) * [65535-(RCAP2H,RCAP2L)]

115200时对应的初值

#include <reg52.h>
#include <stdio.h>

void init_com( void ) 
{ 
   SCON=0x50;   
   TH2=0xFF;           
   TL2=0xFD;   
   RCAP2H=0xFF;   
   RCAP2L=0xFD; 

/*****************/
   TCLK=1;   
   RCLK=1;   
   C_T2=0;   
   EXEN2=0;

/*****************/
   TR2=1 ;
   TI = 1;
}

void main()
{
    init_com();

    while(1)
    {
         printf("Hello World\n");
    }
}

这里写图片描述

然后再次选择115200的波特率实验现象
这里写图片描述

emmmmm,本来写到这里这篇算是完结了,,但是突然的收尾似乎显得有点意犹未尽,,没错,想说的还没说完,继续造起来,一起来再来领悟一下神奇的T2以及探索一下还有哪些神奇的操作。。。

继续来看T2吧,
对了,它自身定时功能。。

16位自动重装模式中,定时器2可通过C/T2位配置为定时器/计数器,根据外部使能标志位EXEN2的置位和清0,可分为两种情况:

<1>EXEN2=0时

  定时器2为16位自动重装的普通定时器,由陷阱寄存器提供重装的值,只需要预设一下即可,可用于定时精度要求高,定时时间长(16位)的情况。

<2>EXEN2=1时,根据递减计数使能位DCEN的置位和清0可分为两种情况:

A1、T2MOD=0x00(DCEN=0;默认情况);

与上一种情况相比,此时16位自动重新装载可由外部T2EX的负跳变,和溢出任意一种触发,并都能产生中断。

A2、T2MOD=0x01(DCEN=1);

  此时允许T2EX控制计数的方向;T2EX=0时,重装的值为0FF和0FF,递减计数与陷阱寄存器预存值相等时,置位TF2产生中断。T2EX=1时;自动重装值为陷阱寄存器中的值,溢出时置位TF2产生中断。
#include "reg51.h"

typedef unsigned char BYTE;
typedef unsigned int WORD;

//-----------------------------------------------

/* define constants */
#define FOSC 11059200L

#define T1MS (65536-FOSC/12/1000)   //1ms timer calculation method in 12T mode

/* define SFR */
sbit ET2 = IE^5;

sfr T2CON = 0xc8;                   //timer2 control register
sbit TF2  = T2CON^7;
sbit TR2  = T2CON^2;

sfr T2MOD = 0xc9;                   //timer2 mode register
sfr RCAP2L = 0xca;
sfr RCAP2H = 0xcb;
sfr TL2 = 0xcc;
sfr TH2 = 0xcd;

sbit TEST_LED = P1^0;               //work LED, flash once per second

/* define variables */
WORD count;                         //1000 times counter

//-----------------------------------------------

/* Timer2 interrupt routine */
void tm2_isr() interrupt 5 using 1
{
    TF2 = 0;
    if (count-- == 0)               //1ms * 1000 -> 1s
    {
        count = 1000;               //reset counter
        TEST_LED = ! TEST_LED;      //work LED flash
    }
}

//-----------------------------------------------

/* main program */
void main()
{
    RCAP2L = TL2 = T1MS;            //initial timer2 low byte
    RCAP2H = TH2 = T1MS >> 8;       //initial timer2 high byte
    TR2 = 1;                        //timer2 start running
    ET2 = 1;                        //enable timer2 interrupt
    EA = 1;                         //open global interrupt switch
    count = 0;                      //initial counter

    while (1);                      //loop
}

②当然不外乎还有时钟输出功能
可参考这里看下其他时钟输出

52系列单片机,可设定定时器/计数器2通过T2(p1^0)引脚输出时钟。

P1^0除了可以作为普通I/O口外,还可以作为定时器2的外部计数输入和时钟信号输出。

 C/T2=0并且T2MOD的T2OE位为1时,可将定时器2选为时钟信号发生器,自动装初值。设置公式:

  时钟信号输出频率=(振荡器频率/4)* (65535-N)

在时钟输出模式下,计数器溢出不会产生中断请求。这种功能相当于定时器2可同时作为波特率发生器和时钟发生器。

因为此时外部中断并没有被暂用,若是在设置上不冲突的话可能同时还可以响应T2EX引入的外部信号,这个只是猜想,还没有用实验证明过,哈哈*

注:单片机对于外来脉冲信号具有计数功能,但是有要求: 计数脉冲的最高频率=振荡器的频率/24
并且为了确保给定电平在电平变化之前能被采样一次,则这个电平至少要维持一个机器周期。



#include "reg51.h"

typedef unsigned char BYTE;
typedef unsigned int WORD;

//-----------------------------------------------

/* define constants */
#define FOSC 11059200L

#define F38_4KHz    (65536-18432000/4/38400)

/* define SFR */

sfr T2CON = 0xc8;                   //timer2 control register
sbit TF2  = T2CON^7;
sbit TR2  = T2CON^2;

sfr T2MOD = 0xc9;                   //timer2 mode register
sfr RCAP2L = 0xca;
sfr RCAP2H = 0xcb;
sfr TL2 = 0xcc;
sfr TH2 = 0xcd;

sbit T2 = P1^0;                     //Clock Output pin

//-----------------------------------------------

/* main program */
void main()
{
    T2MOD = 0x02;                   //enable timer2 output clock
    RCAP2L = TL2 = F38_4KHz;        //initial timer2 low byte
    RCAP2H = TH2 = F38_4KHz >> 8;   //initial timer2 high byte
    TR2 = 1;                        //timer2 start running
    EA = 1;                         //open global interrupt switch

    while (1);                      //loop
}

其实,额,,虽然51很是LOW,但是有限的资源玩的花了,有了更多更大的资源后,也更能会合理利用,那才是本事对不对。。。

嗯,就是这样。。

对了,除了T2还有哪些种方法嘞?
1,把11.0592M换成22.1184M晶振。那么当在TH1=0xff时,刚好可以产生115200波特率。

2、换个IT的单片机。。。哈哈。。

<think>我们使用定时器0,模式1(16位定时器模式)来实现100微秒的定时。晶振频率为11.0592MHz,则机器周期为12/11.0592MHz≈1.085us。要定时100us,需要计数次数为:100us/1.085us≈92.16,因此我们取93次(因为定时器是向上计数,且从某个初值开始计数到溢出,所以实际计数次数为(65536-初值))。但是,计算一下:100us对应的机器周期数=100/(12/11.0592)=100*(11.0592/12)=100*0.9216=92.16,所以需要92.16个机器周期。由于定时器计数必须是整数,我们可以取92或93。但为了更准确,我们计算初值。定时器初值计算公式:计数次数=65536-初值计数次数*机器周期=定时时间所以:初值=65536-(定时时间/机器周期)机器周期=12/晶振频率=12/11.0592e6≈1.085069444us定时时间=100us计数次数=100/1.085069444≈92.16->取整为92(这样会略小于100us)或93(这样会略大于100us)?但是,由于定时器是整数计数,我们取92次,则实际定时时间=92*1.085069444≈99.826us,与100us相差0.174us,误差很小。或者取93次,实际定时时间=93*1.085069444≈100.911us,误差0.911us。考虑到100us的定时要求,我们可以接受92次(因为误差更小)。但实际应用中,通常我们按照计算值取整,然后调整。然而,我们也可以精确计算初值:初值=65536-(100/(12/11059200))=65536-(100*11059200/12/1000000)[注意:100us=0.0001s,而晶振11.0592e6Hz]=65536-(100*11.0592/12)=65536-(1105.92/12)=65536-92.16=65443.84由于初值必须是整数,我们可以四舍五入取整为65444(0xFFC4)或65443(0xFFC3)。那么实际定时时间会略有偏差。我们取初值=65536-92.16≈65444(0xFFC4),则实际计数次数=65536-65444=92,定时时间=92*1.085069444≈99.826us(误差0.174us,0.174%的误差,可以接受)。另外,也可以使用自动重装载模式(模式2,8位自动重装)来减少重装初值的误差。但是模式2最大定时时间只有256个机器周期,约256*1.085≈277.76us,大于100us,所以可以使用模式2。但是,模式2是8位定时器,最大计数256,所以我们可以用模式2来定时100us,这样在每次中断后自动重装,更精确。计算模式2的初值:计数次数=256-TH0(重装值)计数次数*机器周期=100us计数次数=100/1.085069444≈92.16->取92(则实际定时92*1.085≈99.82us)或93(93*1.085≈100.905us)我们取92,则重装值=256-92=164(0xA4)实际定时时间=92*(12/11059200)=92*1.085069444≈99.826us,误差0.174us,相对误差0.174%,可以接受。因此,我们选择定时器0,模式2(自动重装8位定时器)。初始化步骤:1.设置TMOD:定时器0模式2,即TMOD=0x02(GATE=0,C/T=0,M1=1,M0=0)2.设置TH0和TL0的初值为164(0xA4)3.开启定时器0中断(ET0=1)4.开启总中断(EA=1)5.启动定时器0(TR0=1)注意:模式2下,TL0计数,当TL0溢出时,不仅置位TF0,而且自动将TH0的值重装到TL0。下面给出代码:注意:这里我们使用中断方式,每100us(实际99.826us)进入一次中断。如果用户需要精确的100us,可能需要调整,但误差很小。但是,用户要求的是100微秒的定时,我们可以用这个作为基础,然后在中断服务程序中累计10次得到1ms(但这样会有累积误差,不过对于一般应用可以接受)。另外,如果用户需要非中断的方式,也可以使用查询方式,但这样会占用CPU。这里我们给出中断方式的代码,以及一个使用该定时器中断实现LED闪烁的例子(比如每10ms翻转一次LED,即100us中断100次为10ms)。代码框架如下:```#include<reg52.h>sbitLED=P1^0;//假设LED接在P1.0unsignedintcount=0;//中断次数计数器voidTimer0_Init(){TMOD&=0xF0;//清除定时器0的模式位TMOD|=0x02;//设置定时器0为模式2(8位自动重装)TH0=0xA4;//设置重装值,定时100us(实际99.826us)TL0=0xA4;//初始值ET0=1;//开启定时器0中断EA=1;//开启总中断TR0=1;//启动定时器0}voidmain(){Timer0_Init();LED=0;while(1);}voidTimer0_ISR()interrupt1{count++;//每次中断增加1if(count>=100){//100次中断为10ms(实际100*99.826us=9.9826ms,接近10ms)count=0;LED=~LED;//翻转LED}}```注意:由于每次中断实际是99.826us,所以100次中断的时间是9.9826ms,与10ms相差0.0174ms(即17.4us),误差为0.174%。如果需要更精确的10ms,可以调整中断次数,比如100.174次,但中断次数只能是整数,所以我们可以每100次中断补偿一下。不过,一般应用可以接受这个误差。如果用户需要更精确,可以考虑使用更高精度的晶振,或者调整重装值(但模式2下,重装值只能是整数,所以无法精确到100us无误差)。另外,如果用户不需要中断,而是需要精确的100us延时函数(用于查询方式),则可以使用以下代码:注意:使用查询方式时,需要关闭中断,并且需要考虑到函数调用和返回的时间。我们使用定时器0,模式1(16位模式)或模式2(8位模式)都可以,但模式2更精确(因为自动重装,没有重装指令的时间误差)。查询方式(模式2)的延时函数:```voidDelay100us(){TMOD&=0xF0;//清除定时器0的模式位TMOD|=0x02;//模式2TH0=0xA4;//重装值TL0=0xA4;//初始值TF0=0;//清除溢出标志TR0=1;//启动定时器while(!TF0);//等待溢出TR0=0;//停止定时器}```这个函数每次执行大约100us(实际99.826us)。注意,函数调用和返回需要几个机器周期,所以实际延时时间会略大于100us。但是,由于我们使用的是模式2,自动重装,所以启动定时器后,TL0从0xA4开始计数,到溢出(256-0xA4=92个机器周期)产生TF0。这个过程中,函数调用和返回的额外时间包括:设置TMOD、TH0、TL0、TF0、TR0的指令,以及while循环和停止定时器的指令。我们可以通过实际测试(比如用示波器观察一个引脚翻转)来调整。不过,由于额外的指令时间,这个延时函数实际时间可能大于100us。为了减少额外时间,我们可以将定时器初始化放在主程序中,而延时函数只负责启动和等待。改进的查询方式:首先,在程序初始化时设置定时器0为模式2,并设置好TH0和TL0(这样每次延时函数就不需要重新设置,减少时间)。例如:```voidTimer0_Init(){TMOD&=0xF0;//清除定时器0的模式位TMOD|=0x02;//模式2TH0=0xA4;//重装值TL0=0xA4;//初始值//不开启中断和启动定时器}voidDelay100us(){TR0=1;//启动定时器(此时TL0=0xA4,开始计数)while(!TF0);//等待溢出TR0=0;//停止定时器TF0=0;//必须软件清除溢出标志}```注意:在模式2下,当TL0溢出时,TF0被置1,同时自动重装TH0的值到TL0。所以,在每次延时结束后,我们需要清除TF0,并且停止定时器(如果不需要连续运行)。这个函数中,启动定时器(TR0=1)和停止定时器(TR0=0)以及清除TF0的指令需要时间。另外,while循环检测TF0也需要时间(当TF0置位时,循环退出,但此时可能已经超时一点点)。所以实际延时时间=92个机器周期+几条指令的时间。我们可以通过调整TH0的值来补偿。比如,我们减少TH0的值(即增加计数次数)来补偿指令时间。但是指令时间很难精确计算,一般通过示波器调整。如果不调整,那么实际延时时间约为92个机器周期+几条指令(假设每条指令1-2个机器周期),大约多出5-10个机器周期(约5.4-10.8us)。所以实际延时时间大约为99.826+5.4≈105.226us。因此,如果对时间要求非常严格,建议使用中断方式,或者用示波器调整TH0的值。考虑到100us的延时,我们可以使用_nop_()指令来精确延时,但是100us需要很多_nop_()(每个_nop_()延时1个机器周期,约1.085us,所以100us需要92个_nop_(),但这样会占用大量代码空间)。而且,在中断中插入这么多_nop_()也不现实。综上所述,我们推荐使用中断方式,或者查询方式(并调整TH0的值)来实现。下面给出一个调整后的查询方式延时函数(假设我们通过示波器调整,发现需要将TH0设置为0xA0,即计数次数为256-0xA0=96次,那么定时时间=96*1.085≈104.16us,然后减去指令时间,可能接近100us,但需要实测调整)。由于时间有限,我们无法精确计算指令时间,因此这里我们给出一个未经调整的版本,用户可以根据实际测试调整TH0的值。我们选择中断方式作为主要方案,因为更常用。最后,我们给出完整的代码(中断方式实现LED每10ms翻转,即100次100us中断):注意:由于实际中断间隔为99.826us,所以100次中断的时间为9.9826ms,我们将其视为10ms,误差很小。如果用户需要更精确的10ms,可以调整count的阈值,比如100.174次,但只能取整数,所以可以偶尔100次,偶尔101次,通过算法调整。不过,对于大多数应用,9.9826ms和10ms的差别可以忽略。代码:```#include<reg52.h>sbitLED=P1^0;unsignedintcount=0;voidTimer0_Init(){TMOD&=0xF0;//清除定时器0的模式位TMOD|=0x02;//设置定时器0为模式2(8位自动重装)TH0=0xA4;//重装值,对应92个机器周期TL0=0xA4;//初始值ET0=1;//开启定时器0中断EA=1;//开启总中断TR0=1;//启动定时器0}voidmain(){Timer0_Init();while(1);}voidTimer0_ISR()interrupt1{count++;if(count>=100){//100次中断,约10mscount=0;LED=~LED;//翻转LED}}```以上代码实现了每10ms翻转一次LED,即LED以20ms为周期闪烁(亮10ms,灭10ms)。如果用户需要的是100us的定时,可以直接在中断服务程序中执行自己的代码(但要注意中断服务程序执行时间不能超过100us)。注意:中断服务程序中的代码执行时间必须尽可能短,否则会影响下一次中断。如果用户需要精确的100us延时函数(查询方式),我们提供以下代码(未经调整,需要用户根据实际情况调整TH0的值):```#include<reg52.h>voidTimer0_Init(){TMOD&=0xF0;//清除定时器0的模式位TMOD|=0x02;//模式2TH0=0xA4;//重装值TL0=0xA4;//初始值}voidDelay100us(){TR0=1;//启动定时器while(!TF0);//等待溢出TR0=0;TF0=0;//清除溢出标志}voidmain(){Timer0_Init();while(1){//使用Delay100us函数Delay100us();//这里可以执行其他代码}}```用户可以根据需要选择合适的方法。由于引用中提供了使用定时器0模式1(16位模式)的例子,但模式1需要手动重装,在100us这样短的时间间隔下,重装指令的时间会带来误差,所以我们更推荐使用模式2(自动重装)。希望这些方法能帮助用户实现100us定时。相关问题:1.如何调整定时器的精度?2.定时器模式1和模式2有什么区别?3.如何用示波器测量单片机引脚输出的波形来验证定时时间?</think>### STC89C52单片机使用11.0592MHz晶振定时100微秒的实现方法 #### 基本原理 1. **机器周期计算** 晶振频率 $f_{osc} = 11.0592\text{MHz}$,51单片机机器周期 $T_{machine} = 12 / f_{osc}$: $$T_{machine} = \frac{12}{11.0592 \times 10^6} \approx 1.085 \mu s$$ 2. **定时器计数计算** 目标定时 $T_{target} = 100 \mu s$,所需机器周期数: $$N = \frac{T_{target}}{T_{machine}} = \frac{100}{1.085} \approx 92.16$$ 取整后 $N = 92$,实际定时时间 $T_{actual} = 92 \times 1.085 \approx 99.82 \mu s$(误差0.18%)。 3. **定时器初值计算** 使用定时器模式1(16位模式): $$\text{初值} = 65536 - N = 65536 - 92 = 65444$$ 转换为十六进制: $$TH0 = \text{0xFC}, \quad TL0 = \text{0xE4}$$ #### 代码实现 ##### 方法1:查询方式(非中断) ```c #include <reg52.h> void Delay100us() { TMOD = 0x01; // 定时器0,模式1(16位) TH0 = 0xFC; // 高8位初值 TL0 = 0xE4; // 低8位初值(65444 = 0xFCE4) TR0 = 1; // 启动定时器 while (!TF0); // 等待溢出标志 TF0 = 0; // 清除溢出标志 TR0 = 0; // 停止定时器 } void main() { while (1) { Delay100us(); // 调用100us延时 // 此处添加需要定时执行的代码 } } ``` ##### 方法2:中断方式(精确循环) ```c #include <reg52.h> sbit LED = P1^0; unsigned int count = 0; void Timer0_Init() { TMOD = 0x01; // 定时器0,模式1 TH0 = 0xFC; // 100us初值 TL0 = 0xE4; ET0 = 1; // 开启定时器0中断 EA = 1; // 开启总中断 TR0 = 1; // 启动定时器 } void main() { Timer0_Init(); while (1); // 主循环等待中断 } void Timer0_ISR() interrupt 1 { TH0 = 0xFC; // 重装初值 TL0 = 0xE4; count++; if (count == 10) { // 10×100us = 1ms count = 0; LED = ~LED; // 示例:每1ms翻转LED } } ``` #### 关键配置说明 1. **定时器模式选择** - 模式1(16位):最大定时 $65536 \times 1.085 \mu s \approx 71 ms$,适合100us定时。 - 模式2(8位自动重载):最大定时 $256 \times 1.085 \mu s \approx 277 \mu s$,但重装时间更精确[^2]。 2. **误差补偿** - 实测误差可通过示波器校准,调整初值: ```c TH0 = 0xFC; // 默认值 TL0 = 0xE4; // 减小TL0增加定时(如0xE3→99.91μs) ``` 3. **中断优先级** 若系统有多个中断,需在IP寄存器设置优先级[^3]。 #### 注意事项 1. 中断服务程序执行时间必须远小于100μs(通常<20个机器周期)。 2. 若需更高精度,建议使用模式2(自动重载): ```c TMOD = 0x02; // 模式2 TH0 = 256 - 92; // 0xA4 TL0 = 0xA4; // 自动重载值 ``` 3. 11.0592MHz晶振常用于串口通信(如波特率9600),定时器冲突时需统筹规划[^3]。 ---
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ReCclay

如果觉得不错,不妨请我喝杯咖啡

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值