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

关闭看门狗