【关键词】:架构、循环、中断
读懂指令固然重要,但远远不是最重要的事情。我们还有更加重要的事情要做,这就是:如何搭建一个程序的框架,有了这个框架怎样来添砖加瓦,然后是如何去构建我们的应用程序。
一、【架构】
修建一栋房子,我们首先得明白房子的最基本结构:屋基、墙体、顶盖、门窗。。。那么,写MCU程序也是一样,无论多么简单的程序,它都必须有一个完整的架构才能运行。
1)寄存器定义(这就是程序的“屋基”):
对于一个汇编程序,首先我们要给它建立工作环境:为MCU的各种特殊功能寄存器定义存/取的房间(它们的房间号码是固定的,但是我们还得告诉程序);为我们要用到的变量寄存器定义存/取的房间(它们的房间号码可以灵活分配)。
2)变量初始化赋值(类似于我家里需要几个窗户,几个门,客厅要多大面积。。。):
这些“变量”,就是我们为程序里面要处理的内容所分配的代号。
3)常量定义:用来给程序中经常用到的数值定义一个常量名,以方便使用。例如我们把端口A的第5腿定义为按键K1,就设:
K1 EQU .5
以后检测PORTA,K1就行了,不必在程序中老是去回想K1是端口A的那条腿。
3)程序的主体:
在程序运行中,我们需要不断地做事情。这就是主题。主体可以有不同的形式:
1)大循环模式:这是在MCU程序中最为基本的模式:
把需要MCU做的事情分成A、B、C、D。。。各个程序段,做完A段接着做B。。C。。。D,然后又回到(循环)到A,周而复始。
2)循环+中断模式:这是在循环的基础上,有突发中断就转入中断处理,处理完了再回到循环。
这让我想起派出所警察值班:一旦有人报案他就要去处理,处理完了再回到值班室做他日常的事情。这种模式是目前最为常用的模式,下面我们会详细讨论这种模式。
3)在嵌入式操作系统下的进程调用:这是类似于PC的DOS操作系统的做法,对MCU的资源有很高的要求,往往在较复杂的应用里才会用到,这不在我们讨论的范围了。
二、循环+中断的架构:
1)定义常量
2)定义变量
3)定义端口
4)初始化变量
5)中断设置
关于中断设置,因为是和硬件相关的,通常有如下步骤:
a)设置相应的特殊功能寄存器
b)设置中断的入口地址
c)引导中断进入服务程序;
d)打开中断允许位,等待中断的产生;
e)一旦有中断产生,就调用中断服务程序;
f)保存好原有公共寄存器的变量,执行中断服务程序;
g)执行完毕,恢复好原有的公共决寄存器变量,返回中断的断点,继续执行原来的程序。
三、循环+定时中断的例程:
TITLE TIMER_INT PROGRAM
(C) copyright by Little Dot CO,.LTD.
DESIGN DATE BEGIN: 2007/11/14
FINISH: 2007/11/14 (V1.0)
Programan: My
=================================================
本程序是一个演示MCU的定时中断示范程序,
当程序运行时,会在MCU定时器的控制下分别
循环显示出:
1、2、3、4 四个字符
其中四个按键分别给定时器赋上不同的时间值。
list p=16f873A
#include
***********************************************
* Variable definitions *
***********************************************
以下为常量赋值:
K1 EQU .2 PORTA
K2 EQU .3
K3 EQU .4
K4 EQU .5 以上定义了端口A的A2~A4作为按键输入端口
W1 EQU .0 PORTB
W2 EQU .1
W3 EQU .2
W4 EQU .3 以上定义了端口B的B0~B3作为点亮4个位LED的输出端口
TMR_COUNT EQU .217 Timer Reload value: 39X256=10 mS
基本定时时间为:10 mS 中断一次。
================================================
以下为变量定义:
W_TEMP EQU 0X20
STATUS_TEMP EQU 0X21
TIMER EQU 0X22
TIMER_DATA EQU 0X23
LOOP_REG EQU 0X24
================================================
ORG 0X00 程序的复位入口
NOP
GOTO INIT_START
ORG 0X04 程序的定时中断入口
GOTO TIMER_INT
ORG 0X07
INIT_START
CLRF PORTA
BSF STATUS,RP0
MOVLW B00111100 RA2-4 INPUT
MOVWF TRISA
MOVLW B10000110 Select all digital
MOVWF ADCON1
BCF STATUS,RP0
CLRF PORTB
BSF STATUS,RP0
MOVLW B11110000 RB0-3 OUTPUT
MOVWF TRISB
BCF STATUS,RP0
CLRF PORTC
BSF STATUS,RP0
MOVLW B00000000 RC ALL OUTPUT
MOVWF TRISC
BCF STATUS,RP0
MOVLW 0X3C
MOVWF PORTA 把A2~A4拉高
MOVLW 0X0F 把B0~B3拉高,相当于从正端先关闭4个数码管。
MOVWF PORTB
MOVLW 0XFF
MOVWF PORTC 拉高端口C,相当于从负端关闭4个数码管的7个笔画段。
MOVLW .100
MOVWF TIMER_DATA
MOVLW .1
MOVWF LOOP_REG
TIMER_SET
MOVLW TMR_COUNT TMR_COUNT 是一个常量,用来给定时器0赋值
MOVWF TMR0 BASIC TIMER = 10 mS
BSF STATUS,RP0
MOVLW B00000111 T0 X 256 设定预分频系数为:每当时钟计数256次,计数器TMR0就加一
MOVWF OPTION_REG 因为常量TMR_COUNT=217,当增加了39次以后,定时器0就从256(FFH)
BCF STATUS,RP0 溢出变为0,这时候就产生一次中断,中断的时间:
1 uS (硬件对4MHZ石英晶体的4分频) X 256 X 39 = 9984 uS 约等于10 mS
BSF INTCON,T0IE 允许定时器0中断
BSF INTCON,GIE 开总中断位(这是所有中断的总控制位)
========================================
Main_Loop: 主循环开始
NOP
TEST_1:
BTFSC PORTA,K1 测试按键1是否按下?
GOTO TEST_2
MOVLW .100 如果按下,则把1秒的数据送给定时器数据变量
MOVWF TIMER_DATA 10 mS X 100 = 1000 mS = 1 S
TEST_2:
BTFSC PORTA,K2 测试按键2是否按下?
GOTO TEST_3
MOVLW .50 如果按下,则把0.5秒的数据送给定时器数据变量
MOVWF TIMER_DATA 10 mS X 50 = 500 mS
TEST_3:
BTFSC PORTA,K3 测试按键3是否按下?
GOTO TEST_4
MOVLW .25 如果按下,则把0.25秒的数据送给定时器数据变量
MOVWF TIMER_DATA 10 mS X 25 = 250 mS
TEST_4:
BTFSC PORTA,K4 测试按键4是否按下?
GOTO TEST_END
MOVLW .10 如果按下,则把0.1秒的数据送给定时器数据变量
MOVWF TIMER_DATA 10 mS X 10 = 100 mS
TEST_END:
goto Main_Loop 返回主循环开始处
========================================
定时中断服务子程序
========================================
TIMER_INT:
MOVWF W_TEMP 把W寄存器保存起来,以备中断完成后恢复现场
SWAPF STATUS,W
BCF STATUS,RP0 把状态寄存器保存起来,以备中断完成后恢复现场
MOVWF STATUS_TEMP PUSH
BCF INTCON,T0IE 关闭定时中断允许控制位
MOVLW TMR_COUNT 重新载入基本定时的数据(设定计数的次数)
MOVWF TMR0 BASIC TIMER = 10 mS
DECFSZ TIMER,F 每中断一次定时计数器减去1
GOTO TI_EX 如果不等于0,则退出中断服务子程序
MOVF TIMER_DATA,W 如果=0,则重新给定时计数器赋值
MOVWF TIMER
BTFSS LOOP_REG,0 测试:循环寄存器第一位为高吗?
GOTO TI_1 否,去测下一位
BSF PORTB,W2 是,关闭第2个显示字
BSF PORTB,W3 关闭第3个显示字
BSF PORTB,W4 关闭第4个显示字
BCF PORTB,W1 打开第1个显示字
MOVLW 0XF9 再把要显示的笔画段码送到端口C,点亮“1”
MOVWF PORTC 所需要的各段(这对应于LED数码管的下端)
RLF LOOP_REG,F 循环寄存器往左移动一位(为点亮第2个字做准备)
GOTO TI_EX
TI_1
BTFSS LOOP_REG,1 第2个字的处理,原理和第1个字相同
GOTO TI_2
BSF PORTB,W1
BSF PORTB,W3
BSF PORTB,W4
BCF PORTB,W2
MOVLW 0XA4
MOVWF PORTC
RLF LOOP_REG,F
GOTO TI_EX
TI_2
BTFSS LOOP_REG,2 第3个字的处理,原理和第1个字相同
GOTO TI_3
BSF PORTB,W1
BSF PORTB,W2
BSF PORTB,W4
BCF PORTB,W3
MOVLW 0XB0
MOVWF PORTC
RLF LOOP_REG,F
GOTO TI_EX
TI_3
BTFSS LOOP_REG,3 第4个字的处理,原理和第1个字相同
GOTO TI_EX
BSF PORTB,W1
BSF PORTB,W2
BSF PORTB,W3
BCF PORTB,W4
MOVLW 0X99
MOVWF PORTC
MOVLW .1 完成了最后一个字的移位扫描,重新把循环寄存器赋值为1
MOVWF LOOP_REG 为回到第1个字段扫描做准备
TI_EX
SWAPF STATUS_TEMP,W
MOVWF STATUS 中断服务程序执行完毕,恢复状态寄存器原来的值
SWAPF W_TEMP,F 中断服务程序执行完毕,恢复W寄存器原来的值
SWAPF W_TEMP,W POP
BCF INTCON,T0IF 清除上次中断的标志位
BSF INTCON,T0IE 重设定时中断允许控制位
RETFIE 从中断服务子程序返回主循环程序
================================================
END
读懂指令固然重要,但远远不是最重要的事情。我们还有更加重要的事情要做,这就是:如何搭建一个程序的框架,有了这个框架怎样来添砖加瓦,然后是如何去构建我们的应用程序。
一、【架构】
修建一栋房子,我们首先得明白房子的最基本结构:屋基、墙体、顶盖、门窗。。。那么,写MCU程序也是一样,无论多么简单的程序,它都必须有一个完整的架构才能运行。
1)寄存器定义(这就是程序的“屋基”):
对于一个汇编程序,首先我们要给它建立工作环境:为MCU的各种特殊功能寄存器定义存/取的房间(它们的房间号码是固定的,但是我们还得告诉程序);为我们要用到的变量寄存器定义存/取的房间(它们的房间号码可以灵活分配)。
2)变量初始化赋值(类似于我家里需要几个窗户,几个门,客厅要多大面积。。。):
这些“变量”,就是我们为程序里面要处理的内容所分配的代号。
3)常量定义:用来给程序中经常用到的数值定义一个常量名,以方便使用。例如我们把端口A的第5腿定义为按键K1,就设:
K1 EQU .5
以后检测PORTA,K1就行了,不必在程序中老是去回想K1是端口A的那条腿。
3)程序的主体:
在程序运行中,我们需要不断地做事情。这就是主题。主体可以有不同的形式:
1)大循环模式:这是在MCU程序中最为基本的模式:
把需要MCU做的事情分成A、B、C、D。。。各个程序段,做完A段接着做B。。C。。。D,然后又回到(循环)到A,周而复始。
2)循环+中断模式:这是在循环的基础上,有突发中断就转入中断处理,处理完了再回到循环。
这让我想起派出所警察值班:一旦有人报案他就要去处理,处理完了再回到值班室做他日常的事情。这种模式是目前最为常用的模式,下面我们会详细讨论这种模式。
3)在嵌入式操作系统下的进程调用:这是类似于PC的DOS操作系统的做法,对MCU的资源有很高的要求,往往在较复杂的应用里才会用到,这不在我们讨论的范围了。
二、循环+中断的架构:
1)定义常量
2)定义变量
3)定义端口
4)初始化变量
5)中断设置
关于中断设置,因为是和硬件相关的,通常有如下步骤:
a)设置相应的特殊功能寄存器
b)设置中断的入口地址
c)引导中断进入服务程序;
d)打开中断允许位,等待中断的产生;
e)一旦有中断产生,就调用中断服务程序;
f)保存好原有公共寄存器的变量,执行中断服务程序;
g)执行完毕,恢复好原有的公共决寄存器变量,返回中断的断点,继续执行原来的程序。
三、循环+定时中断的例程:
TITLE TIMER_INT PROGRAM
(C) copyright by Little Dot CO,.LTD.
DESIGN DATE BEGIN: 2007/11/14
FINISH: 2007/11/14 (V1.0)
Programan: My
=================================================
本程序是一个演示MCU的定时中断示范程序,
当程序运行时,会在MCU定时器的控制下分别
循环显示出:
1、2、3、4 四个字符
其中四个按键分别给定时器赋上不同的时间值。
list p=16f873A
#include
***********************************************
* Variable definitions *
***********************************************
以下为常量赋值:
K1 EQU .2 PORTA
K2 EQU .3
K3 EQU .4
K4 EQU .5 以上定义了端口A的A2~A4作为按键输入端口
W1 EQU .0 PORTB
W2 EQU .1
W3 EQU .2
W4 EQU .3 以上定义了端口B的B0~B3作为点亮4个位LED的输出端口
TMR_COUNT EQU .217 Timer Reload value: 39X256=10 mS
基本定时时间为:10 mS 中断一次。
================================================
以下为变量定义:
W_TEMP EQU 0X20
STATUS_TEMP EQU 0X21
TIMER EQU 0X22
TIMER_DATA EQU 0X23
LOOP_REG EQU 0X24
================================================
ORG 0X00 程序的复位入口
NOP
GOTO INIT_START
ORG 0X04 程序的定时中断入口
GOTO TIMER_INT
ORG 0X07
INIT_START
CLRF PORTA
BSF STATUS,RP0
MOVLW B00111100 RA2-4 INPUT
MOVWF TRISA
MOVLW B10000110 Select all digital
MOVWF ADCON1
BCF STATUS,RP0
CLRF PORTB
BSF STATUS,RP0
MOVLW B11110000 RB0-3 OUTPUT
MOVWF TRISB
BCF STATUS,RP0
CLRF PORTC
BSF STATUS,RP0
MOVLW B00000000 RC ALL OUTPUT
MOVWF TRISC
BCF STATUS,RP0
MOVLW 0X3C
MOVWF PORTA 把A2~A4拉高
MOVLW 0X0F 把B0~B3拉高,相当于从正端先关闭4个数码管。
MOVWF PORTB
MOVLW 0XFF
MOVWF PORTC 拉高端口C,相当于从负端关闭4个数码管的7个笔画段。
MOVLW .100
MOVWF TIMER_DATA
MOVLW .1
MOVWF LOOP_REG
TIMER_SET
MOVLW TMR_COUNT TMR_COUNT 是一个常量,用来给定时器0赋值
MOVWF TMR0 BASIC TIMER = 10 mS
BSF STATUS,RP0
MOVLW B00000111 T0 X 256 设定预分频系数为:每当时钟计数256次,计数器TMR0就加一
MOVWF OPTION_REG 因为常量TMR_COUNT=217,当增加了39次以后,定时器0就从256(FFH)
BCF STATUS,RP0 溢出变为0,这时候就产生一次中断,中断的时间:
1 uS (硬件对4MHZ石英晶体的4分频) X 256 X 39 = 9984 uS 约等于10 mS
BSF INTCON,T0IE 允许定时器0中断
BSF INTCON,GIE 开总中断位(这是所有中断的总控制位)
========================================
Main_Loop: 主循环开始
NOP
TEST_1:
BTFSC PORTA,K1 测试按键1是否按下?
GOTO TEST_2
MOVLW .100 如果按下,则把1秒的数据送给定时器数据变量
MOVWF TIMER_DATA 10 mS X 100 = 1000 mS = 1 S
TEST_2:
BTFSC PORTA,K2 测试按键2是否按下?
GOTO TEST_3
MOVLW .50 如果按下,则把0.5秒的数据送给定时器数据变量
MOVWF TIMER_DATA 10 mS X 50 = 500 mS
TEST_3:
BTFSC PORTA,K3 测试按键3是否按下?
GOTO TEST_4
MOVLW .25 如果按下,则把0.25秒的数据送给定时器数据变量
MOVWF TIMER_DATA 10 mS X 25 = 250 mS
TEST_4:
BTFSC PORTA,K4 测试按键4是否按下?
GOTO TEST_END
MOVLW .10 如果按下,则把0.1秒的数据送给定时器数据变量
MOVWF TIMER_DATA 10 mS X 10 = 100 mS
TEST_END:
goto Main_Loop 返回主循环开始处
========================================
定时中断服务子程序
========================================
TIMER_INT:
MOVWF W_TEMP 把W寄存器保存起来,以备中断完成后恢复现场
SWAPF STATUS,W
BCF STATUS,RP0 把状态寄存器保存起来,以备中断完成后恢复现场
MOVWF STATUS_TEMP PUSH
BCF INTCON,T0IE 关闭定时中断允许控制位
MOVLW TMR_COUNT 重新载入基本定时的数据(设定计数的次数)
MOVWF TMR0 BASIC TIMER = 10 mS
DECFSZ TIMER,F 每中断一次定时计数器减去1
GOTO TI_EX 如果不等于0,则退出中断服务子程序
MOVF TIMER_DATA,W 如果=0,则重新给定时计数器赋值
MOVWF TIMER
BTFSS LOOP_REG,0 测试:循环寄存器第一位为高吗?
GOTO TI_1 否,去测下一位
BSF PORTB,W2 是,关闭第2个显示字
BSF PORTB,W3 关闭第3个显示字
BSF PORTB,W4 关闭第4个显示字
BCF PORTB,W1 打开第1个显示字
MOVLW 0XF9 再把要显示的笔画段码送到端口C,点亮“1”
MOVWF PORTC 所需要的各段(这对应于LED数码管的下端)
RLF LOOP_REG,F 循环寄存器往左移动一位(为点亮第2个字做准备)
GOTO TI_EX
TI_1
BTFSS LOOP_REG,1 第2个字的处理,原理和第1个字相同
GOTO TI_2
BSF PORTB,W1
BSF PORTB,W3
BSF PORTB,W4
BCF PORTB,W2
MOVLW 0XA4
MOVWF PORTC
RLF LOOP_REG,F
GOTO TI_EX
TI_2
BTFSS LOOP_REG,2 第3个字的处理,原理和第1个字相同
GOTO TI_3
BSF PORTB,W1
BSF PORTB,W2
BSF PORTB,W4
BCF PORTB,W3
MOVLW 0XB0
MOVWF PORTC
RLF LOOP_REG,F
GOTO TI_EX
TI_3
BTFSS LOOP_REG,3 第4个字的处理,原理和第1个字相同
GOTO TI_EX
BSF PORTB,W1
BSF PORTB,W2
BSF PORTB,W3
BCF PORTB,W4
MOVLW 0X99
MOVWF PORTC
MOVLW .1 完成了最后一个字的移位扫描,重新把循环寄存器赋值为1
MOVWF LOOP_REG 为回到第1个字段扫描做准备
TI_EX
SWAPF STATUS_TEMP,W
MOVWF STATUS 中断服务程序执行完毕,恢复状态寄存器原来的值
SWAPF W_TEMP,F 中断服务程序执行完毕,恢复W寄存器原来的值
SWAPF W_TEMP,W POP
BCF INTCON,T0IF 清除上次中断的标志位
BSF INTCON,T0IE 重设定时中断允许控制位
RETFIE 从中断服务子程序返回主循环程序
================================================
END