void main() //主程序 不同单片机写法基本一致
{
}
interrupt() //不同单片机 中断程序写法不同
{
}
多个中断类似于多个中断车道的车行进,如果都来抢占红色小车的主车道,会造成红车行进缓慢。
所以中断程序写最简短的代码,尽量少的占用主车道的时间。
以上提到了四个关键名词:
中断
定时器
主程序
中断程序
书籍会给一个名词简短一两句话,作为解释或者定义,实际上大部分名词很难用一句话说明。很多同学学习过程中感觉越学越困难,越来越看不懂,实际上,关键问题就是很多名词需要一本书1000页打底的书来解释才行,我们把自己限定到仅看课本或者一两本关联度很少的书,这样自然是越学越累。因为这些关键名词都没有理解,他们又是后续章节的基础,后续的故事...
继续说这四个名词,
中断是一种机制,为了实现:
提升时间利用率
快速响应信号
尝试一心二用、一心三用
我喜欢单片机比作一个人。
以人的行为来理解中断,我们看一本小说或者看一个电影,看小说的过程中,拿起手机来接了个电话,电话接完了,接着又看小说剩下的内容,过了一会口渴了,又去喝了杯水,接着再看..
看小说是大脑主程序在运行
接电话 大脑切换到电话中断程序
喝水 大脑切换到喝水中断程序
空间和时间是永恒的主题!
从时间上理解中断:
从空间上理解中断:
程序要存储在两个主要的地方
A、电脑中,我们编写的程序源代码(主程序和中断程序都存储在main.c中)
B、编译完成形成二进制代码下载到单片机的程序存储器中(ProgramFlash)
a、首先我们要找到中断程序的位置(以stc单片机为例,其他51单片机类似)
b、看看程序存储器大小
c、写了一段定时器0的中断小程序,我们依照这个程序继续往下分析
源码下载在这里:
向导团队/step by step study singlechip
目录:step-by-step-study-singlechipstepbysteptimertimer0keil
本程序实现功能
启用定时器0
1ms定时并开启定时器中断
1ms定时到达开始计数,计数1000次 LED灯状态反转
主循环空转
主要运行函数为定时器初始化函数和中断函数
#include "reg51.h" //引用头文件
//以下两个类型转换语句只是为了用短单词替代两个单词,写程序时方便
typedef unsigned char BYTE; //BYTE 代替 unsinged char 代表一个字节长度
typedef unsigned int WORD; //WORD 代替 unsigned int 代表两个字节(一个字)长度
//-----------------------------------------------
/* define constants */
#define FOSC 11059200L //主晶振宏定义
#define T1MS (65536-FOSC/12/1000) //1ms timer calculation method in 12T mode //宏定义 1ms定时器在12T模式下的计算方法
/* define SFR */
sbit TEST_LED = P1^0; //work LED, flash once per second //工作LED 1秒闪烁1次
/* define variables */
WORD count; //1000 times counter //1000毫秒计数器
unsigned char runCode; //运行代码 主要函数和关键语句添加,通过观察此变量了解程序运行到的位置
//-----------------------------------------------
/* Timer0 interrupt routine */
void tm0_isr() interrupt 1
{
runCode=5;
TL0 = T1MS; //reload timer0 low byte //重新装入定时器0低字节
TH0 = T1MS >> 8; //reload timer0 high byte //重新装入定时器0高字节
if (count-- == 0) //1ms * 1000 -> 1s //1ms定时计数1000次 后是1秒
{
runCode=6;
count = 1000; //reset counter //复位计数器
TEST_LED = ! TEST_LED; //work LED flash //LED灯闪烁
}
}
void init_timer0()
{
runCode=3;
TMOD = 0x01; //set timer0 as mode1 (16-bit) //设置定时器0 16bit工作模式
TL0 = T1MS; //initial timer0 low byte //初始化定时器0 低字节
TH0 = T1MS >> 8; //initial timer0 high byte //初始化定时器0 高字节
TR0 = 1; //timer0 start running //启动定时器0
ET0 = 1; //enable timer0 interrupt //开启定时器0中断
EA = 1; //open global interrupt switch //打开所有中断
count = 0; //initial counter //初始化计数器
}
/* main program 主程序*/
void main()
{
runCode=1;
init_timer0(); //初始化定时器函数
while (1) //主循环
{
runCode=2;
; //分号代表空转,不做任何事情,但是;也会占用单片机运行时间
} //loop
}
中断函数很特殊是被keil内部程序调用的,它与主函数(main)是一个等级,不能被主函数直接调用,其他函数可以被主函数调用。
e、继续往下进行,我们只在keil中编写完了程序还不行,需要知道程序是怎么一步步编译成单片机能够存储的格式的。
源程序编写完成
编译链接成二级制格式文件
我们从C语言源文件很难看出中断程序最终存储在那个位置,keil有个强大的功能是可以直接变成汇编语言查看。汇编语言更接近机器语言,从里面我们就能够找到程序的具体位置。
先进行几步设置
1)keil 设置为内部仿真模式
2)点击调试按钮
3)打开汇编语言窗口 打开存储器窗口
4)我们观察下中断程序 C语言 汇编语言格式和存储代码对应的空间
5)理解一个概念 单片机程序存储空间就是用硅晶体组成的一个个电路,也就是一组组开关。然后集成在单片机里面。回头看这张图
0000H~3FFFH 把main.c 内部的程序代码都装进去了,只是变成了单片机认识的二进制的0,1,再进一步实际上是把单片机内部的一个个开关打开还是关上了,1关闭,0开启。
我们看4)中存储器是16进制数 16进制能够一一对应二进制数,例如十六进制FF=11111111
从下图我们可以看出程序就是从哪里执行,运行中从那个地址再调用函数... 所有的代码无论开始的格式是什么样的,最终都变成二进制0、1。(16进制作为中间过程)
6)我们洋洋洒洒写了很多程序代码,实际编译完成写入单片机是很短的代码
f、现在我们再看程序是怎么从C编译成二进制01的就容易理解了
g、重新编译生成hex文件,可用于proteus仿真或烧写单片机
h、最终调试观察运行结果
通过学习,我们知道中断程序(中断函数)和主程序都存储在单片机程序存储器中,通过代码的相互调用反复执行。
先在定时器寄存器中放置一个值 当这个值累加到超过寄存器能存储的最大数就触发中断,累加1实际上会消耗一定的时间,累加到特定数值就可以判断出总时间1ms,本程序64614为初始值,
#define FOSC 11059200L //主晶振宏定义
#define T1MS (65536-FOSC/12/1000)
11059200÷10÷1000=921.6 65536-921.6=64614.4 =FC66(16进制)
所以定时器寄存器要存FC66, FC66不断+1 直到等于65536(0x10000)超出了定时器的计数范围(16位定时器只能存FFFF也就是65535)1ms时间到。