8位单片机::pic汇编::实例学习::无死角学习0::串口与lcd显示
2019-04-15 14:10发布
生成海报
由于我本人刚刚接触pic汇编,但是目前工作时间不太多,也由于pic汇编指令较少,工作中习惯于c语言,所以汇编学习的目的是为了能读懂他人的汇编程序。此次采用实例学习,通过protues提供的例子实例图,汇编代码,无死角理解汇编执行过程。
汇编完整代码:
; Simple PIC 16F877X Program to drive Serial LCDs.
; Assumes 1MHZ CPU clock, 2400 baud rate for the LCD.
LIST p=16F874
#include "P16F874.INC"
; Counter variables for delay
cblock 0x20
char,cmd,lc1,lc2;
endc
; Vector for normal start up.
org 0
goto start
org 4
goto inthlr
; Main program starts here:
start clrw ; Clear W.
movwf PORTA ; Ensure PORTA is zero before we enable it.
movwf PORTB ; Ensure PORTB is zero before we enable it.
; Set up ports:
bcf STATUS,RP0 ; Select Bank 0
bsf RCSTA,SPEN ; Enable USART.
bsf RCSTA,CREN ; Enable Receeive
bsf STATUS,RP0 ; Select Bank 1
clrw ; Set W to mask for all outputs.
movwf TRISA ; Set TRISA register as outputs.
movwf TRISB ; Set TRISB register as outputs.
movlw 0x19 ; BRG value for 2400 baud @ 1MHz
movwf SPBRG ; Write it to register.
movlw 0xA4 ; CSRC/TXEN (Internal clock, 8 bit mode, Async operation, High Speed)
movwf TXSTA ; Write to TX control register.
movlw 80
call delay
movlw 'M' ; Value to transmit.
call putc
movlw 'i' ; Value to transmit.
call putc
movlw 'l' ; Value to transmit.
call putc
movlw 'f' ; Value to transmit.
call putc
movlw 'o' ; Value to transmit.
call putc
movlw 'r' ; Value to transmit.
call putc
movlw 'd' ; Value to transmit.
call putc
movlw ' ' ; Value to transmit.
call putc
movlw 'L' ; Value to transmit.
call putc
movlw 'C' ; Value to transmit.
call putc
movlw 'D' ; Value to transmit.
call putc
movlw ' ' ; Value to transmit.
call putc
movlw 'D' ; Value to transmit.
call putc
movlw 'e' ; Value to transmit.
call putc
movlw 'm' ; Value to transmit.
call putc
movlw 'o' ; Value to transmit.
call putc
movlw 0xC0 ; Move cursor to row two
call wrcmd
movlw 0x0D ; Show the cursor
call wrcmd
loop call getc
movwf char
sublw 0d
btfsc STATUS,Z
goto cls
movf char,w
sublw 08
btfsc STATUS,Z
goto bspace
movf char,W
call putc ; Send the character straight thru
goto loop
cls movlw 0x01 ; Send command prefix
call wrcmd
goto loop
bspace movlw 0x10 ; Send command prefix
call wrcmd
goto loop
hang clrwdt ; Clear WDT in case it is enabled.
goto hang
;Subroutine to sent a command
wrcmd movwf cmd ; Store the command
movlw 0xFE ; Write the command prefix
call putc
movf cmd,W ; Write the command code
goto putc
;Subroutine to wait and receive a byte
;Returns character in W
;
getc bcf STATUS,RP0 ; Select Bank 0.
getc1 btfss PIR1,RCIF ; Skip if RC int flag set
goto getc1 ; Try again
movf RCREG,W ; Read the character
bcf PIR1,RCIF ; Clear the interrupt flag
return
;Subroutine to transmit a byte and wait
;W = Character
;
putc bcf STATUS,RP0 ; Select Bank 0.
movwf TXREG ; Write it!
bsf STATUS,RP0 ; Select Bank 1
movf TXSTA,W ; Peek transmit status
putc1 btfss TXSTA,1 ; Skip if TXbuffer empty
goto putc1 ; Try again
bcf STATUS,RP0 ; Select Bank 0.
return
;Delay Routine
;W = delay time
;
delay movwf lc2
_sw2 movlw 0xFF
movwf lc1
_sw3 nop
decfsz lc1,f
goto _sw3
decfsz lc2,f
goto _sw2
return
inthlr retfie
END
上面的原理图是protues sample 里提供,只是用于学习,未有版权。
现在开始学习这个例子
该例子总体功能是实现串口与单片机通信,并将数据显示到lcd屏上。
LIST p=16F874
;list 伪指令可以设定程序编译时的一些信息,例如所选单片机的型号,编译时选择的缺省数制等。
#include "P16F874.INC"
;#include 伪指令的作用是把另外一个文件的内容全部包含复制到本伪指令所在的位置。被包含复制的
文件可以是任何形式的文本文件,当然文件中的内容和语法结构必须是MPASM 能够识别的。最经常被
“include”的是针对 PIC 单片机内部特殊功能寄存器定义的包含头文件,在 MPLAB 安装后它们全部放
在路径“ C:Program FilesMPLAB IDEMCHIP_Tools”下,每一个型号的 PIC 单片机都有一个对应的预
定义包含头文件,扩展名是“.inc”。
cblock 0x20
char,cmd,lc1,lc2;
endc
用 equ 伪指令可以给一个符号变量分配一个地址。但在一个程序设计过程中往往需要定义很多变量,你
当然可以给每一个变量逐个用 equ 的方法分配一个地址空间。但如果变量很多,这样做就显得非常麻烦
,你必须自己安排每个变量的地址,小心不能出现地址重叠;若要在已定义分配好的变量间插入新的变量
,那就必须重新逐个安排随后变量的地址等等。cblock/endc 伪指令可以轻松解决有很多变量定义的场合
出现的这些问题,我们把它叫作变量块连续定义。具体用法如下:
cblock 伪指令声明变量块的起始地址,endc 伪指令声明变量块定义结束,cblock/endc中间可以插入任
意多的变量声明。其地址编排由编译器自动计算:第一个变量地址分配从起始地址开始,然后按所声明变
量保留的字节数自动分配后面变量的地址,变量所需保留的字节数用“:”加后面的数字表示,如果只有
一个字节“:1”可以省略不写。
cblock 0x20 ;变量定义起始地址为0x20 ,
char;char地址是0x20,占一个字节。
cmd;cmd的地址是0x21,占一个字节
若这样写buffer:8代表预留8个字节,后面的就必须在该地址后加8存储该变量的地址。
org 0
org 用以定义程序代码的起始地址,通过此伪指令你可以把程序定位到任何可用的程序空间,它实现的
是程序代码绝对定位
goto start
状态转换模块之间大多用GOTO指令,即由此状态进入另一种状态不需返回,由于PIC单片机的堆栈有限,在
程序中,不能无止境地使用GOTO语句,这样会使堆栈溢出,程序无法正常运行。各个小程序内部循环占用
堆栈的级数不多,使用GOTO指令是可行的,但在大的程序中用GOTO则无法返回到调用前程序的下一条指令
。start是程序的入口,
org 4
;中断复位向量入口,
goto inthlr
start clrw ; Clear W.
清零 工作寄存器 (累加器),大部分单操作指令的一个操作数取自累加器,很多双操作数指令中的一个
操作数也取自累加器。加、减、乘、除法运算的指令,运算结果都存放于累加器A或AB累加器对中。大部
分的数据操作都会通过累加器A进行,它形象于一个交通要道,在程序比较复杂的运算中,累加器成了制
约软件效率的“瓶颈”,它的功能较多,地位也十分重要。以至于后来发展的单片机,有的集成了多累加
器结构,或者使用寄存器阵列来代替累加器,即赋予更多寄存器以累加器的功能,目的是解决累加器的“
交通堵塞”问题。提高单片机的软件效率。
movwf PORTA ; Ensure PORTA is zero before we enable it.
movwf PORTB ; Ensure PORTB is zero before we enable it.
movwf指令指将w内容传到f中。将w寄存器中的值存入f寄存器中,开始w寄存器中的值为0, 也就是对文件
寄存器地址清零(0x00 到0x7F)在 #include "P16F874.INC" 定义PORTA地址05H,PORTB地址06H
; Set up ports:
bcf STATUS,RP0 ; Select Bank 0
bcf指令指将f寄存器中的某位清零,STATUS寄存器,包含1:ALU的算术运算状态2: 复位状态:3: 数据
存储器的存储区选择位 (SRAM)。和任何其他寄存器一样,STATUS寄存器也可以作为任何指令的目标寄
存器。如果STATUS寄存器是影响Z、DC或C位的指令的目标寄存器,那么禁止写这3位。根据器件逻辑,这
些位会被置1或清零。而且TO和PD位不可写。因此,当执行一条把STATUS寄存器作为目标寄存器的指令后
,STATUS寄存器的结果可能和预想的不一样。例如,CLRF STATUS会清零高3位,将Z位置1。STATUS寄存器
变为“000u u1uu”(其中u=不变)。因此,建议仅使用BCF、BSF、SWAPF和MOVWF指令来改变STATUS寄存
器,因为这些指令不影响任何状态位。
每个存储区可以最大扩展到7Fh(128字节)。将每个存储区的低地址单元保留用于特殊功能寄存器
。特殊功能寄存器的上方是通用寄存器,实现为静态RAM。所 有实现的存储区都包含特殊功能寄存器。一
些常用的特殊
功能寄存器可从一个存储区镜像到另一个存储区,以缩减代码,加快访问速度。
RP1:RP0
00=存储区0(00h-7Fh)
01=存储区1(80h-FFh)
10=存储区2(100h-17Fh)
11=存储区3(180h-1FFh)
bsf RCSTA,SPEN ; Enable USART.
bsf RCSTA,CREN ; Enable Receeive
bsf指令是指将f中的某位置1,RCSTA寄存器,
bit 7 SPEN:串行端口使能位
1=使能串行端口 (将RX/DT和TX/CK引脚配置为串行端口引脚)
0=禁止串行端口 (保持在复位状态)
bit 6 RX9:9位接收使能位
1=选择9位接收
0=选择8位接收
bit 5 SREN:单字节接收使能位
异步模式:
无关位
同步主模式:
1=使能单字节接收
0=禁止单字节接收
此位在接收完成后清零。
同步从模式:
无关位
bit 4 CREN:连续接收使能位
异步模式:
1=使能接收器
0=禁止接收器
同步模式:
1=使能连续接收,直到使能位CREN清零 (CREN的优先级高于SREN)
0=禁止连续接收
bit 3 ADDEN:地址检测使能位
9位异步模式 (RX9 = 1):
1=当RSR<8>置1时,使能地址检测、允许中断和装入接收缓冲区
0=禁止地址检测、接收所有字节并且第9位可作为奇偶校验位
8位异步模式 (RX9 = 0):
无关位
同步模式:
必须设置为0
bit 2 FERR:帧错误位
1=帧错误 (可以通过读RCREG寄存器更新该位并接收下一个有效字节)
0=无帧错误
bit 1 OERR:溢出错误位
1=溢出错误 (可以通过清零CREN位来清零该位)
0=无溢出错误
bit 0 RX9D:接收数据的第9位
该位可以是地址/数据位或奇偶校验位,并且必须由用户固件计算得到。
bsf STATUS,RP0 ; Select Bank 1
选择存储区1,选择存储区1原因是需要定义该存储区地址值,前面bcf指令解释为何汇编要定义几个分区
来存放寄存器。
clrw ; Set W to mask for all outputs.
将W寄存器清零,
movwf TRISA ; Set TRISA register as outputs.
movwf TRISB ; Set TRISB register as outputs.
是否记得movwf指令,这里也是一样的,将w中的值装入f中,w目前值为0,将0放入TRISA,TRISB中,pic
规定选择输入方向时将TRIS*设为1,选择输出方向时将TRIS*设为0,
movlw 0x19 ; BRG value for 2400 baud @ 1MHz
movwf SPBRG ; Write it to register.
movlw指令是指将立即数传送到W,movwf指令是指将0x19传入SPBRG(波特率发生器)中,
波特率公式
SYNC BRGH AUSART模式 波特率公
0 0 异步 FOSC/[64 (n+1)]
0 1 异步 FOSC/[16 (n+1)]
1 x 同步 FOSC/[4 (n+1)]
x= 任意值,n = SPBRG寄存器的值
64改为16;
movlw 0xA4 ; CSRC/TXEN (Internal clock, 8 bit mode, Async operation, High Speed)
movwf TXSTA ; Write to TX control register.
TXSTA接收发送寄存器配置位如下:
bit 7 CSRC:时钟源选择位
异步模式:
无关位
同步模式:
1=主模式 (时钟来自内部BRG)
0=从模式 (时钟来自外部时钟源)
bit 6 TX9:9位发送使能位
1=选择9位发送
0=选择8位发送
bit 5 TXEN:发送使能位(1)
1=使能发送
0=禁止发送
bit 4 SYNC:AUSART模式选择位
1=同步模式
0=异步模式
bit 3 未实现:读为0
bit 2 BRGH:高波特率选择位
异步模式:
1=高速
0=低速
同步模式:
在此模式下未使用
bit 1 TRMT:发送移位寄存器状态位
1=TSR为空
0=TSR已满
bit 0 TX9D:发送数据的第9位
可以是地址/数据位或奇偶校验位。
A4 = 1010 0100
movlw 80
call delay
call指令指调用子程序,首先,将返回地址(PC+1)压入堆栈。将11位立即数地址装入PC位<10:0>。将
PCLATH(PC<12:8>的保持寄存器,可将PC<12:8>的内容传送到程序计数器的高字节)的内容装入PC的高位
。CALL是双周期指令。
delay movwf lc2
_sw2 movlw 0xFF
movwf lc1
decfsz lc1,f
goto _sw3
decfsz lc2,f
goto _sw2
return
这是一个延时程序,将立即数80放入定义的变量lc2中,sw2前加下划线意思是一个函数,该函数作用就是
将立即数放入w中。然后放入到变量lc1中,也就是给变量赋值,同理sw3就是空操作,decfsz指令作用:f
递减1,为0则跳过,goto跳转指令跳到sw3反复循环,直到lc1为0,然后跳出执行sw2函数,也是一样,
sw2执行完就退出循环,返回,就是O(n*n)循环。
movlw 'M' ; Value to transmit.
call putc
movlw 'i' ; Value to transmit.
call putc
movlw 'l' ; Value to transmit.
call putc
movlw 'f' ; Value to transmit.
call putc
movlw 'o' ; Value to transmit.
call putc
movlw 'r' ; Value to transmit.
call putc
movlw 'd' ; Value to transmit.
call putc
movlw ' ' ; Value to transmit.
call putc
movlw 'L' ; Value to transmit.
call putc
movlw 'C' ; Value to transmit.
call putc
movlw 'D' ; Value to transmit.
call putc
movlw ' ' ; Value to transmit.
call putc
movlw 'D' ; Value to transmit.
call putc
movlw 'e' ; Value to transmit.
call putc
movlw 'm' ; Value to transmit.
call putc
movlw 'o' ; Value to transmit.
call putc
movlw 0xC0
; Move cursor to row two
call wrcmd
movlw 0x0D
; Show the cursor
call wrcmd
;Subroutine to transmit a byte and wait
;W = Character
;
putc bcf STATUS,RP0 ; Select Bank 0.
movwf TXREG ; Write it!
bsf STATUS,RP0 ; Select Bank 1
movf TXSTA,W ; Peek transmit status
putc1 btfss TXSTA,1 ; Skip if TXbuffer empty
goto putc1 ; Try again
bcf STATUS,RP0 ; Select Bank 0.
return
该段代码,发觉很多重复,就晓得是在传递写好的字符,现在看看一直调用的putc,
第一句是选择工作区,将w中的值传入TXREG寄存器(AUSART发送数据寄存器)
第二句中有个movf指令作用是根据d的状态,将寄存器f的内容传送到目标寄存器。如果d = 0,目标寄存
器为W寄存器。如果d = 1,则目标寄存器为文件寄存器f本身。默认值为d = 1。
putc1中
第一句btfss指令的作用检测f中的某位,为1则跳过,
bit 1 TRMT:发送移位位寄存器状态
1=TSR为空
0=TSR已满,就退出返回,表示数据传输完成。
如果你感觉到有点不能理解,应该是movf这条指令了,在这里就是将w理解成一个字符也行,他本身是等
于0,就比如我是一个寄存器,没有任何值,我放了值那也并不是我的,是别人的,你加我还是不会给你
什么。你要加我的值,这条指令执行还是将自身保持。
;Subroutine to sent a command
wrcmd movwf cmd ; Store the command
movlw 0xFE ; Write the command prefix
call putc
movf cmd,W
; Write the command code
goto putc
这段函数功能实现光标功能。
loop call getc
movwf char
sublw 0d
btfsc STATUS,Z
goto cls
movf char,w
sublw 08
btfsc STATUS,Z
goto bspace
movf char,W
call putc ; Send the character straight thru
goto loop
该函数实现输入字符,第一条语句调用getc函数:
;Subroutine to wait and receive a byte
;Returns character in W
;
getc bcf STATUS,RP0 ; Select Bank 0.
getc1 btfss PIR1,RCIF ; Skip if RC int flag set
goto getc1 ; Try again
movf RCREG,W ; Read the character
bcf PIR1,RCIF ; Clear the interrupt flag
return
PIR1是外设中断寄存器器,这句话是检测RCIF是否被置位,也就是检测是否有外部中断,有外部中断就读
RECEG接收数据寄存器的值。现在跳回loop第二条指令处。将w寄存器的值放到变量char中,sublw指令是
指从立即数中减去W的内容。btfsc指令作用是检测f中的某位,为0则跳过,不为0就执行下一条:
cls movlw 0x01 ; Send command prefix
call wrcmd
goto loop
这条函数作用就是实现如果没有输入,就让光标不停闪烁。
bspace movlw 0x10 ; Send command prefix
call wrcmd
goto loop
这条函数式实现光标移动。
最后就
hang clrwdt ; Clear WDT in case it is enabled.
goto hang
关闭看门狗
打开微信“扫一扫”,打开网页后点击屏幕右上角分享按钮