一, C 语言相关
Q1:sbit 与 sfr 代表是什么? 有什么作用?
Q2:#define OSC_FREQ 22118400L 这句宏命令里的 "L" 是什么意思?
Q3: 我粘贴了别人的代码, 怎么发现没有 unit 这个类型?
Q4: 为什么好多变量都是 char 类型? 它不是字符类型吗? 怎么可以用来计数?
Q4.1:51 单片机中的 char,int,long,float,double 各占多少个字节, 取值范围多大?
Q5:unsigned char data 是什么数据类型?
Q6:void timer() interrupt 1 using 2 是什么意思?
Q7: 如何写一个 1ms 延迟的函数?
Q8: 经常看到 TH0 与 TL0, 例如 TH0 = 0xD8;TL0 = 0xEF; 这起什么作用?
二, 51 单片机相关
Q1: 单片机的引脚电压是多少? 它的电压是由谁控制的?
Q2:P3 口的 8 个引脚有哪些复用功能(第二功能), 默认开启吗?
问题解决 1: 程序通过 USB 口无法烧入单片机
Q3: 我的程序编译后生成的 HEX 文件超过了 8k, 烧进单片机不会有问题吗?
问题解决 2:Keil 生成超过 8K 的 HEX 文件会报错, 提示 Target not created
Q4: 为了写中断程序, 我需要详细了解一下中断系统.
程序示例 1: 蜂鸣器滴两次, 进入中断服务 -- 数码管显示 8, 延迟 1 秒后熄灭.
Q5: 为了理解某段程序的作用, 我需要详细了解一下计时 / 计数系统.
Q6: 如何用定时 / 计数器进行 1ms 的延迟?
程序示例 2: 定时器的配置
程序示例 3: 蜂鸣器持续滴滴, 利用定时器完成 Nms 的延时, 进入中断服务 -- 数码管显示 8, 延迟 1 秒后熄灭.
Q7: 单片机的有好多特殊寄存器, 我需要总结一下他们的名称及用途
最近做比赛, 需要写程序做一个智能小车. C 语言的基础和编程的能力我是有的, 但是我对单片机等硬件不是很了解, 特意进行了一番学习. 估计以后也用不了多少, 特此写一篇笔记, 方便后人参考学习.
我不喜欢翻着教材或视频一节一节地学习, 我的学习方式是问题启发式学习: 直接切入正题, 遇到不会的问题就找度娘, 学会之后再次进入正题, 遇到问题再查阅资料, 循环往复, 直到走通为止~ 整个学习下来, 虽然可能会有些漏洞, 但是已经基本进入状态了.
由于我是业余的, 所以难免会有错解或不妥之处, 还请读者能以挑剔的眼光为我指出.
一, C 语言相关
Q1:sbit 与 sfr 代表是什么? 有什么作用?
A1:sfr 用来声明特殊功能的寄存器, sbit 用来声明特殊功能位.
sfr 占用一个内存单元 (8 位, 取值范围为 0 ~ 255 = 2^8-1. 对于 I/O 端口来说, 刚好每一位对应一个引脚), 例如 sfr P0 = 0x80; 这一句定义了 P0 端口与地址 0x90 对应. 特殊功能的寄存器一般在开发工具(Keil) 中自带的头文件, 例如 reg52.H 中声明好了, 只需要在程序中引入该头文件就好了;
w 用法: sfr 变量名 = 地址值;
w 需要注意的一点是, 例如 P0 对应的是一个 "8" 字型的数码管, 若要显示 3, 则可对 P0 口赋值: P0 = 0x0D, 若要将其熄灭, 只需对其赋值: P0=0xFF. 这里的值并不代表地址, 而是一个 16 进制的数(值的前两位 0x 代表 16 进制, 后两位. 刚好 FF 代表十进制的 255).
w sfr16 也是用来声明特殊功能寄存器, 所不同的是它用于操作占两个字节 (取值范围为 0~65535) 的寄存器, 比如定时器 T0 和 T1.
sbit 只占用一个位, 也就是说用它定义的变量只能取 0 和 1 两个值. 一般用来给引脚取别名, 例如 sbit P1_0 = P0^1; 就是定义用符号 P1_0 来表示 P1.0 引脚. 需要注意的是, 一单用了 sbit 定义某个变量, 这个变量的地址就是确定的了(不能修改了);
w 对于引脚来说, 这个 0 和 1 是有物理意义的: 0 代表低电平, 1 代表高电平. 而机器是不懂代码只能识别高低电平.(脑补: 这样我们就打通了从代码 / 软件通往硬件的大路~) 高电平就是 5 伏正电压, 低电平就是 0 伏, 这个是理想值, 实际上它也有一个范围......(参考自 https://zhidao.baidu.com/question/266456228.html )
w 用法 1:sbit 位变量名 = 地址值;
w 用法 2:sbit 位变量名 = SFR 名称 ^ 地址值;
w 用法 3:sbit 位变量名 = SFR 地址值 ^ 变量位地址值;
w 3 种用法参考自: https://blog.csdn.net/guzicheng/article/details/7242981
关于 8051 单片机特殊功能寄存器的说明,可以查阅: https://wenku.baidu.com/view/6f7b242c0975f46526d3e1aa.html?from=search 为防止链接失效,这里给出文件名:《 8051,STC89C52 单片机特殊功能寄存器》 |
Q2:#define OSC_FREQ 22118400L 这句宏命令里的 "L" 是什么意思?
A2: 长整型数字在数字的后面加字母 L, 如 104L,034L 等. 总结如下:
w 十进制: 直接用一般数字来表示, 例如 123,111,-999 等;
w 十六进制: 以 0x 开头, 如 0xFF,0x01 等;
w 长整型: 在数字后面加字母 L, 如 104L,034L 等
w 浮点型: 分为十进制形式和指数形式两种, 统一格式为 [±](数)(. 数){e[±]数}, 其中 [ ] 为可选项,( )表示二者必有其一,{ }十进制不填, 指数必填. 例如: 3.14,-.1,+2;.3e-3,12e+3,6.66e13
w 字符型: 用单引号括住括住, 例如'a','c'等. 对于特殊字符, 例如换行符, 反斜杠等请参考 C 语言等教材.
w 字符串: 略
Q3: 我粘贴了别人的代码, 怎么发现没有 unit 这个类型?
A3: 别人的代码只给了函数部分, 没有给头文件中的预处理命令. 可以在自己的头文件中加入:#typedef unsigned int uint; (后面要加分号), 这样就可以用 uint 类型来代表 unsigned int 类型了.
Q4: 为什么好多变量都是 char 类型? 它不是字符类型吗? 怎么可以用来计数?
A4:int 在 8 位的 51 单片机是占用 2 个字节, char 在占用 1 个字节, 所以说 char 类型占用空间更小. 单片机的存储器很小, 尽量不要浪费空间, 能用小的就用小的, 且一般都用无符号的.
参考: https://zhidao.baidu.com/question/342140311.html
至于它为什么可以计数, 因为字符本来就是用二进制表示的, 所以当你对 char 类型的变量赋值时 (例如 char a = 'A'), 它(a) 底层仍然是二进制, 将二进制转化为十进制, 当然可以用来计数.
Q4.1:51 单片机中的 char,int,long,float,double 各占多少个字节, 取值范围多大?
表: Keil uVision4 面向 51 单片机的基本数据类型各种属性一览表
参考: http://blog.sina.com.cn/s/blog_6ac7328f0102uzd2.html
Q5:unsigned char data 是什么数据类型?
A5: 定义一个变量的格式为:[存储种类] 数据类型 [存储器类型] 变量名表
在定义格式中除了触及类型和变量名表是必要的, 其他都是可选项. 存储种类有四种:
w auto(自动),extern(外部),static(静态)和 register(寄存器), 默认类型为自动.
存储器类型的说明是指定该变量在 C51 硬件系统中所使用的存储区域, 并在编译是准确定位. 如果省略存储器类型, 系统则会按编译模式 SMALL,COMPACT 或 LARGE 所规定的默认存储器类型去指定变量的存储区域. 89C51 中的存储器类型有:
w data : 可直接寻址的内部数据存储区(128B), 访问速度最快;
w idata: 间接寻址的内部数据存储区(256B), 允许访问全部内存地址;
w bdata: 可位寻址内部数据存储区(16B), 允许位与字节混合访问;
w pdata: 分页的外部数据存储区(256 字节), 用 MOVX @Ri 指令访问;
w xdata: 外部数据存储区(64KB), 用 MOVX @A+DPTR 指令访问;
w code : 程序存储区(64KB), 用 MOVC @A+DPTR 指令访问;
--51 单片机 C 语言入门教程, 磁动力工作室, 第六课 变量
一般需要严格控制变量读取速度的时候用 data. 例如变量更新速度很快, 或者需要很短时间内读取或者修改的变量. 一般容量要求大的, 但速度并没有太大要求的, 放在 xdata 里面.
如果所有变量都不加这些关键字的话, 编译器会自动分配, 但编译器的分配方案并不一定是最好的. 而且一般都不会非常合理.
参考: https://bbs.csdn.net/topics/360163178
Q6:void timer() interrupt 1 using 2 是什么意思?
注: 关于 "中断" 的详细学习放在 第二节: 51 单片机相关
A5: 关键字 interrupt 表示这是一个中断函数, 具体的书写格式为:
void 函数名() interrupt n [using m]
{ do something; }
首先需要注意的是中断函数没有参数传递其无返回值. n 表示中断源, m 为单片机工作寄存器编号.[using m]为非必须内容. 在设计中断时, 尽量让中断函数做少量的工作, 这样中断服务时间短, 系统可以及时的响应其他中断. 有些系统如果丢失中断或对中断反应太慢将产生十分严重的后果, 这时有充足的时间等待中断是十分重要的.
89C51 单片机的中断系统有 5 个中断源, 2 个优先级, 可以实现二级中断嵌套.(中断服务进行中再进行一次优先级更高的中断)
所以 n 的取值为 0,1,2,3,4 共 5 个, 对应了 5 种中断源, 这 5 种中断源可以分为三种类型: 外部中断, 定时器中断, 串口中断.
w 0: 外部中断 0(INT0)
w 1: 定时器 0(T0)
w 2: 外部中断 1(INT1)
w 3: 定时器 1 中断(T1)
w 4: 串行口中断(RX/TX)
m 的取值有 0,1,2,3 共 4 个, 它涉及到中断的优先权, 如果用不到二级中断, using m 可以不加, 系统会为你自动分配. 如果加可能会导致不必要的冲突.
Q7: 如何写一个 1ms 延迟的函数?
A6: 在写函数之前首先要认识到, 假如采用 for 循环, 则循环一次所花费的时间是多少? 这就涉及到单片机深层的概念: 机器周期. 而单片机的机器周期并不是最小的周期, 在计算它之前还要了解一下其他几个周期的定义:
w 晶振频率 OSC: 单片机的最小系统中有一个晶振, 它能够使得 CPU 跑起来, 这个晶振为单片机的 CPU 提供主频. 这个晶振的频率就称为晶振频率(外加频率).
w 时钟周期 Tc: 又称为 "震荡周期", 它等于晶振频率的导数. 这是最基础的周期.
w 机器周期 Tm:1 机器周期 = 12 个震荡周期; 单片机复位至少需要两个机器周期的高电平.
w 指令周期 Ti: 执行一条指令所需的机器周期数. 1 指令周期 = 1,2,4 个机器周期;
Ÿ 一条赋值语句(j = 0)2 个机器周期, j 是 unsign char 类型;
Ÿ 一条判断语句(j <1)4 个机器周期, j 是 unsign char 类型;
Ÿ 一条自增 / 减语句(j++)1 个机器周期, j 是 unsign char 类型;
Ÿ 一条空语句(循环体内)1 个机器周期.
Ÿ https://zhidao.baidu.com/question/89303563.html
晶振频率 OSC | 11.0592 MHz | 12.0000 MHz |
时钟周期 | 9.04225e-5 ms | 8.33333e-5 ms |
机器周期 | 1.08507e-3 ms | 1.00000e-3 ms |
假如采用 for 循环, 例如 for(j=X;j>0;j--){ }; 这行代码有 X 个循环, 每次循环有一条判断语句(j>0,4Tm), 一条空语句({ },1Tm), 一条自减语句(j--,1Tm), 略去第一个循环的赋值语句(j=X,2Tm), 共 6X 个机器周期. 略去最后一次的判断语句(j=0 时, 4Tm), 若要延迟一秒, 只需令 6X*Tm = 1, 当采用 12MHz 的晶振时, X 167. 被略去的语句达 6Tm, 刚好等于一次循环所耗费的时间, 所以对 X 进行 X=X-1 的修正, 最终可得: X 166.
这里讲的是 j 为 char 类型的变量, 它最大只能取到 255, 所以要获得更大的延时, 需要用到 int 类型. 前面也学到 int 类型是 16 位的, 而单片机是 8 位的, 所以这会更加复杂.
参考: https://zhidao.baidu.com/question/89303563.html
下面给的两个延时函数. 这里多说一句: 我查阅网络资料发现延时 1ms 的程序不尽相同, 甚至相差很大, 如果你需要非常准确的延时, 推荐你参考正规的教材或采用其他方法比如计时系统.
两种晶振的单片机,延时 1ms 的函数 | |
11.0592MHz 晶振 | 12MHz 晶振 |
void delay_ms(unsigned int i){ unsigned int j; for(;i>0;i--) for(j=114;j>0;j--); }
| void delay_ms(unsigned int i){ unsigned int j; for(;i>0;i--) for(j=123;j>0;j--); }
|
参考: https://blog.csdn.net/feike24/article/details/52357772
这种方法也有很大的缺点: 延迟过程中, CPU 被占用, 无法进行其他任务, 导致系统效率降低. 延迟时间越长, 该缺点便越明显, 因此软件延时只适用于短暂延时, 或简单项目.
参考: https://www.bilibili.com/video/av15466938/?p=20
Q8: 经常看到 TH0 与 TL0, 例如 TH0 = 0xD8;TL0 = 0xEF; 这起什么作用?
A6: 从上面的学习可知 TH0 与 TL0 是与定时器 / 计数器有关的 SFR 寄存器. 这两句的含义是给定时 / 计数器赋初值, 寄存器会按固定的时间间隔累加, 当寄存器的值达到最大时会触发中断, 这时可以利用中断函数进行一系列操作. 而计时 T 就等于时间间隔 *(最大值 - 初值). 大概就是这个意思.
二, 51 单片机相关
Q1: 单片机的引脚电压是多少? 它的电压是由谁控制的?
A1: 单片机的引脚有两种电平: 高电平与低电平. 高电平的电压与单片机的工作电压有关, 一般有 5V 和 3.3V 两种. 低电平一般为 0V.
P0,P1,P2,P3 又称为并行 I/O 端口, 它的输出 / 入是双向的: 当其作为输出时, 单片机可以通过程序指令控制其为高电平 1 还是低电平 0; 当其作为输入时, 单片机可以检测其是高或低电平, 例如扩展了红外寻迹模块, 对应的引脚的高低电平由红外模块控制, 低电平 0 代表红外光被反射并被接收管接收, 高电平 1 代表红外光被外界 (黑线等) 吸收.
P1 称为端口, P1.1 称为 P1 端口的引脚, 这两个概念之间的关系就是 "整体" 与 "个体" 的关系.
参考: https://wenku.baidu.com/view/39a0bda0700abb68a982fbfa.html
Q2:P3 口的 8 个引脚有哪些复用功能(第二功能), 默认开启吗?
A2: 当复用功能没有开启时, P3 可以做为普通 I/O 口使用. 一般情况下, 复位后第二功能都是关闭的, 需要设置对应寄存器才能打开.
Ÿ P3.0 RXD 串行输入口
Ÿ P3.1 TXD 串行输出口
Ÿ P3.2 INT0 外部中断 0 输入口
Ÿ P3.3 INT1 外部中断 1 输入口
Ÿ P3.4 T0 定时器 / 计数器 0 外部时间脉冲输入端
Ÿ P3.5 T1 定时器 / 计数器 1 外部时间脉冲输入端
Ÿ P3.6 WR 外部数据存储器写脉冲
Ÿ P3.7 RD 外部数据存储器读脉冲
问题解决 1: 程序通过 USB 口无法烧入单片机
我的单片机插座要用一个 USB 转 TTL 设备才能从电脑上给单片机烧程序, 当时我就在 P3.0 和 P3.1 上连了其他模块, 结果每次下载都失败. 后来我才明白 TTL 插口是和单片机上的 RXD,TXD 连着的, 下载时是开启了它们的复用功能的.
Q3: 我的程序编译后生成的 HEX 文件超过了 8k, 烧进单片机不会有问题吗?
A3:HEX 文件不只包含了实际的操作指令, 还包含了地址代码, 这个文件是为了易于下载器的理解. 真正下载到单片机上的并不是 HEX 文件, 参考下面的链接, 给单片机烧入 160+K 的 HEX 文件仍然没有问题.
参考: http://tieba.baidu.com/p/3408654325?red_tag=b2302518919
问题解决 2:Keil 生成超过 8K 的 HEX 文件会报错, 提示 Target not created
这是因为你所使用的 Keil 没有经过注册, 需要注册一下就可以生成超过 8K 的文件了. 至于如何注册这里就不多说了.
注意: 如果真的是代码量超过所选单片机的容量(STC89C52RC 的容量为 8K), 那么编译器在生产 HEX 文件时会提示 xxx code limit 之类的.
参考: https://bbs.csdn.net/topics/390011912
Q4: 为了写中断程序, 我需要详细了解一下中断系统.
A3:CPU 在处理某一事件 A 时, 事件 B 请求 CPU 迅速去处理, CPU 暂时中断当前工作 A, 转去处理事件 B, 待 CPU 将事件 B 处理完后再返回继续处理 A 事件, 这一过程称为中断. 在这之中有几个专业名词需要解释一下:
w 中断发生: 事件 B 请求 CPU 迅速去处理;
w 中断响应: CPU 暂时中断当前工作 A;
w 中断服务: CPU 转去处理事件 B;
w 中断返回: CPU 再返回继续处理 A 事件;
w 断点: 程序 A 被中断的地方;
w 中断源: 引起 CPU 中断的根源. 断源能够向 CPU 提出中断请求;
中断系统的结构如下图所示.
w 第一列解释: INT0: 外部中断 0,INT1: 外部中断 1,T0 定时器中断 0,T1 定时器中断 0,RX,TX: 串口中断(包装在一起的). 中断引起原因如下:
Ÿ INT0:P3.2 引脚低电平或下降沿信号;
Ÿ T0: 定时 / 计数器 0 计数回 0 溢出;
Ÿ INT1:P3.3 引脚低电平或下降沿信号;
Ÿ T1: 定时 / 计数器 1 计数 0 溢出;
Ÿ 串行通信完成一帧数据发送或接受引起中断.
w 第二列 TCON 解释: 该列的第二列代表了五种中断源的中断标志, 所谓中断标志就是 "中断请求的标志",CPU 要进行中断服务, 首先要判断中断请求标志, 再判断中断使能标志是否 Enable, 最后才会响应这个中断.-- https://zhidao.baidu.com/question/81735469.html
Ÿ 对于外部中断, 当中断到来时(引脚的电平发生变化), 硬件会自动将中断标志置为 1; 而对于计时器中断, 中断标志的值是可以认为修改, 所以可以利用这一点进行人为中断(通过软件 / 程序), 可以达到计数, 时钟累加, 自检, 扫描等目的.
ü 外部中断需要外部条件触发, 计时器中断不用.
Ÿ 需要注意的是, 无论是机器中断还是人为中断, 在中断服务完成后机器并不一定会清除该中断标志位(不同的 MCU 情况不同), 所以为安全起见, 我们一般利用程序清除.
Ÿ 外部中断的中断标志前 (第一列) 各有两个开关, 对应了外部中断的两种触发方式: 当 IT0/IT1=0 时, 选择为低电平 0 触发; 当 IT0/IT1=1 时, 选择为下降沿触发(从高电平 1 过渡到低电平 0 的过程). 这两种触发方式有不同的效果, 低电平可以持续一段时间, 而电平下降却是一瞬间的事, 所以两种触发方法在延时效果上不同.
w 第三列 IE 解释: IE 代表中断允许 / 使能寄存器, 它控制了所有中断的开放和屏蔽. 共有两列开关, EA 是总开关(EA=1 时, 第二列的 5 个开关全部闭合), 第一列的 5 个开关: EX0,ET0,EX1,ET1,ES 分别对应了第一列的 5 个中断源.
Ÿ 如需开启 INT0 中断, 需要将 EX0 与 EA 都合上, 即 EX0=1;EA=1;
w 第四列 IP 解释: 中断优先级控制寄存器. 其中 IP.7,IP.6 与 IP.5 为保留位, 其他位 (PS=IP.4, PT1=IP.3, PX1=IP.2, PT0=IP.1, PX0=IP.0) 值为 1 时表示对应中断源具有高优先级, 值为 0 表示其具有低优先级.
Ÿ 若这 5 个中断源被设置为同等优先级, 则按自然优先级排序依次执行中断服务. 如下表所示:
89C51 单片机的中断优先级有三条原则:
1,CPU 同时受到几个中断时, 首先响应优先级别最高的中断请求.
2, 正在进行的中断服务不能被新的同级或低级的中断请求所打断.
3, 正在进行的低级中断服务能被高级的中断请求所打断.
CPU 响应中断的条件: 1, 中断源有中断请求; 2, 此中断的中断允许 / 使能标志为 1;CPU 开总中断(EA=1).
参考: https://www.bilibili.com/video/av5833218
程序示例 1: 蜂鸣器滴两次, 进入中断服务 -- 数码管显示 8, 延迟 1 秒后熄灭.
#include <AT89X51.h> // 预处理命令
#define Led P0 // 定义数码管显示端口 #define Buzz P2_3 // 定义蜂鸣器的端口 // 11.0592M 的晶振延迟 1ms 。这个函数要放在 Buzz_didi 的上面,否则会报错。 void delay_ms(unsigned int i){ unsigned int j; for(;i>0;i--)
for(j=114;j>0;j--); } // 蜂鸣器发出滴滴声 void Buzz_didi(){ Buzz=0; delay_ms(100); Buzz=1; delay_ms(300); } void main(){ EA = 1; // 打开总中断开关 ET0 = 1; // 开定时器中断 0 while(1){
Buzz_didi(); // 蜂鸣器滴一次
Buzz_didi(); // 蜂鸣器滴两次
TF0 = 1; // 定时器中断 0 的中断标志置 1 } } // 中断函数一般放在 main 函数的下面 void LED_Show8() interrupt 1{ Led = 0x01; // 数码管显示为 8 delay_ms(1000); // 延迟 1 秒钟 Led = 0xFF; // 数码管不显示 // TF0 = 0; // 定时器中断 0 的中断标志置 0 }
|
Q5: 为了理解某段程序的作用, 我需要详细了解一下计时 / 计数系统.
A5: 单片机中有多个小闹钟(T0,T1;52 单片机还有一个 T2 小闹钟), 可以用来计数, 定时等. 它们的结构图如下
w 定时 / 计数器 0,T0, 它的触发引脚为 P3.4, 计数器为 8 位寄存器 TL0 和 TH0, 用于存放数值, TL0 是低八位, TH0 是高八位. 当低八位计数满了之后会向高八位进一位. 对于 T1 同理.
w 配置寄存器 TCON: 控制寄存器, 控制 T0,T1 的启动和停止及设置溢出标志, 与之相关的 sbit 由 TF1,TR1,TF0,TR0;T2CON 是 T2 时钟的控制寄存器, 52 单片机才有.
Ÿ TF0,TF1 是溢出中断请求标志为, 详细参考 "本节 Q4: 为了写中断程序, 我需要详细了解一下中断系统."
Ÿ TR0,TR1 是运行控制位, TRX=1 时, TX 开始工作; TRX=0 时, TX 停止工作. TRX 由软件置 1 或清 0, 所以可以用软件控制定时 / 计数器的启动与停止.
w 配置寄存器 TMOD: 定时 / 计数器的工作方式寄存器, 用来确定工作方式 (M0 和 M1) 和功能(GATE 和 C/T)
Ÿ C/T: 定时器或计数器功能的选择位. C/T=1 时为计数器, 通过外部引脚 P3.4 或 P3.5 输入计数脉冲, 这样可以设置外部时钟源, 不过比较复杂一般不用; C/T=0 时为定时器, 由内部系统时钟提供计时工作脉冲., 加 1 计数器的计时间隔为 1 个机器周期(计数频率为晶振频率的 1/12). 所以定时时间 T = 计数值 N * 机器周期 Tm.
Ÿ GATE: 门控位. 当 GATE=0 时, 只要用软件使 TCON 中的 TR0 或 TR1 为 1, 就可以启动定时 / 计数器工作; 当 GATE=1 时, 要用软件是 TR0 或 TR1 为 1, 同时外部中断引脚为高电平时, 才能启动定时 / 计数器工作. 我们一般让 GATE=0.
T0,T1 定时 / 计数器可以在四种方式下工作, 由 M0 和 M1 的取值来确定.
方式 1, 以 T0 为例: 定时 / 计数器 0 的实质是的由计数脉冲触发的按递增规律 (即累加方式) 工作的循环累加计数器, 这个寄存器是 16 位的, 由高八位的 TH0 与第八位的 TL0 组成. 从预先设定的初始值开始, 每来一个计数脉冲 (时间间隔固定) 就加计数器 1, 当 TL0 溢出后, 对 TH0 进位, 当 TH0 溢出后, TF0 会被硬件置 1, 从而发出中断请求.
Ÿ 溢出: 当计数器的每一位都是 1 时, 对计数器再加 1 就会溢出, 结果就是计数器的每一位都回 0.
Ÿ 计数值 N = 溢出时计数器的值(65536=2^16) - 计数初值 X
Ÿ 当 TF0=1 时, cpu 可以不做响应. 学习了后面的中断系统后就会知道, cpu 对中断做出响应需要两个判断条件, 另外一个就是开启中断使能标志: EA=1;ET0=1;
参考: https://www.bilibili.com/video/av15466938/?p=20
Q6: 如何用定时 / 计数器进行 1ms 的延迟?
A5: 定时器的操作步骤(下面的 X 代表 0 或 1):
w 选择工作方式(设置 M0,M1), 这里选用方式 1, 即 M0=1;M1=0;;
w 选择控制方式(设置 GATE), 一般设 GATE=0;;
w 选择定时器还是计数器模式(设置 C/T), 一般采用定时器模式即 C/T=0;;
w 给定时 / 计数器赋初值(设置 THX 和 TLX), 注意结合一下两个公式:
Ÿ 计数值 N = 溢出时计数器的值(65536=2^16) - 计数初值 X
Ÿ 定时时间 T = 计数值 N * 机器周期 Tm = N*12 / 晶振频率
n 如果频率的单位是 MHz(兆赫兹), 则时间的单位为 us(微秒)
Ÿ 计算出初值 X 后将其转化为 16 进制, THX 就等于前 2 个数, TLX 就等于后 2 个数. 或者将其转化为 10 进制, 它除以 256 的商为 THX, 余数为 TLX.
w 开启定时器中断(ETX=1;);
w 开总中断(EA=1;);
w 打开计数器(TRX=1).
程序示例 2: 定时器的配置
void TimerConfiguration(){ TMOD = 0x01; // 定时器 0 选择工作方式 1 TH0 = 0x3C; TL0 = 0xB0; // 设置初始值 EA = 1; // 打开总中断 ET0 = 1; // 打开定时器 0 中断 TR0 = 1; // 启动定时器 0 }
|
程序示例 3: 蜂鸣器持续滴滴, 利用定时器完成 Nms 的延时, 进入中断服务 -- 数码管显示 8, 延迟 1 秒后熄灭.
#include <AT89X51.h> // 预处理命令 #define Led P0 // 定义数码管显示端口 #define Buzz P2_3 // 定义蜂鸣器的端口
// 11.0592M 的晶振延迟 1ms 。 void delay_ms(unsigned int i){
unsigned int j;
for(;i>0;i--)
for(j=114;j>0;j--); } // 初始化定时器 0 void TimerConfiguration(){ //
延迟 1ms 的计数 N 的精确值为 921.6 ,这里舍入为 921 //
以计数的方式,一次性最多可以延迟 70ms TMOD = 0x01; // 定时器 0 选择工作方式 1 TH0 = (65536-921)/256; // 设置初始值 TL0 = (65536-921)%6; // 设置初始值 EA = 1; // 打开总中断 ET0 = 1; // 打开定时器 0 中断 TR0 = 1; // 启动定时器 0 } unsigned int count = 1; // 用于计数 void main(){ TimerConfiguration(); while(1){
Buzz=0; delay_ms(100);
Buzz=1; delay_ms(200); } } // 中断函数一般放在 main 函数的下面 void inter_t0() interrupt 1{ count++; // 中断一次( 1ms )计数加一 TH0 = (65536-921)/256; // 设置初始值 TL0 = (65536-921)%6; // 设置初始值
//TF0 = 0; // 定时器 0 的中断标志置 0 if(count>1000){ // 延迟 1000ms ,这里可以修改为 Nms count = 1; // 重置计数
Led = 0x01; // 数码管显示为 8
delay_ms(1000); // 延迟 200ms Led = 0xFF; // 数码管不显示 } }
|
从程序示例 1 与程序示例 3 的对比可以得出以下结论:
l 利用定时器中断有两种方法, 一种方法只需将定时器 0 的中断标志置 1(TF0 = 1; 即程序示例 1), 另一种方法需要开启定时器 0(TR0 = 1; 即程序示例 3)
l 利用定时器进行延时, 这个计时进程与 main 进程是并行的.
l 无论采用定时器延时中断还是手动中断, 中断服务与 mian 进程总是串行的.
l 改程序的测试结果表明: 有时候, 当数码管显示 8 时, 蜂鸣器是静音的; 而有时候, 当数码管显示 8 时, 蜂鸣器在一直鸣响. 这说明两点:
n 定时器中断可以中断函数类型的延迟.
n 单片机在服务中断的时候, 既定的事实不会发生变化. 也就是说若中断发生在蜂鸣器响的中间时刻, 则蜂鸣器会一直响下去, 直到中断服务返回.
Q7: 单片机的有好多特殊寄存器, 我需要总结一下他们的名称及用途
A6: 关于 8051 单片机特殊功能寄存器的说明, 可以查阅:
https://wenku.baidu.com/view/6f7b242c0975f46526d3e1aa.html?from=search
为防止链接失效, 这里给出文件名:8051,STC89C52 单片机特殊功能寄存器
下面给出第一页的预览图:
来源: https://www.cnblogs.com/stxs/p/8878088.html