容易注意到,796 条 NOP 指令呈现出典型的“重复性工作”特点,于是可以想到通过使用循环结构来以较少的 ROM 指令数实现较多条数的指令实际执行数量。
那么我们尝试把上面的例子改成循环结构:
1 2 3 4 5 6 7 8 9
; ================ ; An Example function ; a demo function, just for example ; ARGS: NONE ; BREAKS: NONE EXAM_FUNC: NOP ; Do something JMP EXAM_FUNC ; Jump to label EXAM_FUNC RET
在原子程序的 RET 前添加了一条 JMP EXAM_FUNC 指令,这样 PIOC 执行完子程序中的指令后,就会跳转到 EXAM_FUNC 继续执行,形成循环。
有没有觉得这有什么不对?由指令集手册可知,JMP 指令是无条件跳转,也就是说无论怎样,只要执行这条指令,程序就会跳转到 EXAM_FUNC 标签处继续执行,永远不会执行到 RET 指令。也就是说,这个子程序一旦被调用,就会陷入死循环,永远不会返回,消耗 ∞ 个指令周期。这显然不是我们期望的结果。
因此,需要给循环确定一个退出条件,比如循环次数。熟悉 C 语言的用户可能很快想到了这就是以下形式的循环:
1 2 3
for (int i = N; i > 0; i--) { ; // do something }
不过,PIOC 的汇编语言并没有直接提供类似 C 语言的 for 循环语法,因此我们需要使用 PIOC 的指令来实现循环。PIOC 提供了 DECSZ 指令,它可以对一个 SFR 寄存器进行减 1 操作,并在减到 0 时跳过下一条指令。也就是如下伪代码:
1 2 3 4 5 6
cnt--; if (cnt == 0) { goto LABEL_1; } ; // Next instruction here LABEL_1: ; // Next+1 instruction here
; ================ ; An Example function ; a demo function, just for example ; ARGS: NONE ; BREAKS: Z,SFR_INDIR_ADDR EXAM_FUNC: MOVIP 3 ; take 3 loop cycles ; Do something DECSZ SFR_INDIR_ADDR,F ; cnt--, skip next if cnt==0 JMP EXAM_FUNC ; Jump to label EXAM_FUNC RET
; ================ ; An Example function ; a demo function, just for example ; ARGS: NONE ; BREAKS: Z,SFR_INDIR_ADDR EXAM_FUNC: MOVIP 3 ; take 3 loop cycles EXAM_FUNC_1: ; Do something DECSZ SFR_INDIR_ADDR,F ; cnt--, skip next if cnt==0 JMP EXAM_FUNC_1 ; Jump to label EXAM_FUNC_1 RET
; ================ ; Delay function of 5us*A ; call DELAY_5US with A as loop counter. additional cycles not counted but negligible. ; ARGS: A as loop counter ; BREAKS: A,Z,C DELAY_XUS: CALL DELAY_5US ; Delay 5us ADDL 0XFF ; cnt--, Z=1 if cnt==0 JNZ DELAY_XUS ; else continue loop RET ; ; To call this function, set A to the number of 5us units. ; for example, to delay for 480us(96*5us): MOVL 96 CALL DELAY_XUS ; At this point, 480us has passed and A is 0.
在调用子程序前,只需将所需的循环次数装入 A 寄存器即可。
在继续编写 DS18B20 的基本通信子程序之前,我们先来分析一下 DELAY_XUS 的实现。
DELAY_XUS 是一个典型的“循环执行 n 次”的逻辑。它的循环体是 CALL DELAY_5US,每次循环调用 DELAY_5US 延时 5μs。循环计数器的初值由调用方在调用前通过 MOVL 装入 A 寄存器,循环体内通过 ADDL 0XFF 自减,JNZ 判断循环计数器非零则继续循环。由于循环控制用的指令消耗的指令周期远小于 DELAY_5US,因此可以忽略不计。
; ================ ; DS18B20 Write Byte ; write a byte to bus ; ARGS: SFR_DATA_EXCH ; BREAKS: Z,SFR_DATA_EXCH DS18B20_WB: CALL DS18B20_WSLOT ; Write a bit to bus RCR SFR_DATA_EXCH,F ; Shift right to put next bit on LSB CALL DS18B20_WSLOT ; Write a bit to bus RCR SFR_DATA_EXCH,F ; Shift right to put next bit on LSB CALL DS18B20_WSLOT ; Write a bit to bus RCR SFR_DATA_EXCH,F ; Shift right to put next bit on LSB CALL DS18B20_WSLOT ; Write a bit to bus RCR SFR_DATA_EXCH,F ; Shift right to put next bit on LSB CALL DS18B20_WSLOT ; Write a bit to bus RCR SFR_DATA_EXCH,F ; Shift right to put next bit on LSB CALL DS18B20_WSLOT ; Write a bit to bus RCR SFR_DATA_EXCH,F ; Shift right to put next bit on LSB CALL DS18B20_WSLOT ; Write a bit to bus RCR SFR_DATA_EXCH,F ; Shift right to put next bit on LSB CALL DS18B20_WSLOT ; Write a bit to bus RET
; ================ ; DS18B20 Read Byte ; read a byte from bus ; ARGS: NONE ; BREAKS: Z,SFR_DATA_EXCH DS18B20_RB: CALL DS18B20_RSLOT ; Read a bit from bus RCR SFR_DATA_EXCH,F ; Shift right CALL DS18B20_RSLOT ; Read a bit from bus RCR SFR_DATA_EXCH,F ; Shift right CALL DS18B20_RSLOT ; Read a bit from bus RCR SFR_DATA_EXCH,F ; Shift right CALL DS18B20_RSLOT ; Read a bit from bus RCR SFR_DATA_EXCH,F ; Shift right CALL DS18B20_RSLOT ; Read a bit from bus RCR SFR_DATA_EXCH,F ; Shift right CALL DS18B20_RSLOT ; Read a bit from bus RCR SFR_DATA_EXCH,F ; Shift right CALL DS18B20_RSLOT ; Read a bit from bus RCR SFR_DATA_EXCH,F ; Shift right CALL DS18B20_RSLOT ; Read a bit from bus RET
07 完成整个 PIOC 固件开发
一次完整的 1-Wire 命令流程为:复位 → ROM 命令 → 功能命令。单器件应用时 ROM 命令用 SKIP ROM (0xCC) 跳过寻址;功能命令则是本次要执行的操作,本文用到 CONVERT T (0x44) 启动温度转换、READ SCRATCHPAD (0xBE) 读取温度数据。
07.1 DS18B20 的操作流程
由于 DS18B20 设计上支持单总线多器件运行,因此在开始向一个器件发送或接收数据之前,必然存在一个“寻址”过程,对于 DS18B20 来说,这个过程通过 ROM 命令来实现。
因此,在通信时,主机必须先发出复位脉冲,然后等待器件响应的存在脉冲,确认有器件在总线上后再发送 ROM 命令进行“寻址”。由于许多应用中,每条总线上只有一个 DS18B20 器件,对于这种单器件应用的情况,可以在 ROM 命令阶段直接使用 SKIP ROM(指令码 0xCC)跳过寻址。为简单起见,本文不讨论单总线多器件应用的情况。
; ================ ; DS18B20 Start Temperature Conversion ; send SKIP ROM and CONVERT T command to DS18B20 ; ARGS: NONE ; BREAKS: A,Z,SFR_DATA_EXCH ; RETURN: A=0 OK, else pass error code from DS18B20_BUSRST DS18B20_CONV: CALL DS18B20_BUSRST ; Reset bus and check for presence pulse ANDL 0XFF ; Test if A==0 JNZ DS18B20_CONV_1 ; If A!=0, return error code MOVL 0XCC ; Load SKIP ROM command into A MOVA SFR_DATA_EXCH ; Load command to SFR_DATA_EXCH CALL DS18B20_WB ; Write a byte to bus MOVL 0X44 ; Load CONVERT T command into A MOVA SFR_DATA_EXCH ; Load command to SFR_DATA_EXCH CALL DS18B20_WB ; Write a byte to bus DS18B20_CONV_1: RET
; ================ ; DS18B20 Read Data ; send SKIP ROM and READ command to DS18B20 ; ARGS: NONE ; BREAKS: A,Z,SFR_DATA_EXCH,SFR_INDIR_ADDR2,SFR_DATA_REG0-8,SFR_DATA_REG31 ; RETURN: A=0 OK, else pass error code from DS18B20_BUSRST DS18B20_READ: CALL DS18B20_BUSRST ; Reset bus and check for presence pulse ANDL 0XFF ; Test if A==0 JNZ DS18B20_READ_2 ; If A!=0, return error code MOVL 0XCC ; Load SKIP ROM command into A MOVA SFR_DATA_EXCH ; Load command to SFR_DATA_EXCH CALL DS18B20_WB ; Write a byte to bus MOVL 0XBE ; Load READ command into A MOVA SFR_DATA_EXCH ; Load command to SFR_DATA_EXCH CALL DS18B20_WB ; Write a byte to bus MOVL 9 ; take 9 bytes to read MOVA SFR_DATA_REG31 ; loopCnt is in SFR_DATA_REG31 MOVIA 0x20 ; Set ptr to SFR_DATA_REG0 using INDIR2 DS18B20_READ_1: CALL DS18B20_RB ; Read a byte from bus MOV SFR_DATA_EXCH,A ; Load read byte into A MOVA SFR_INDIR_PORT2 ; Store A into SFR_DATA_x, auto increment DECSZ SFR_DATA_REG31,F ; cnt--, skip next if cnt==0 JNZ DS18B20_READ_1 ; else continue loop CLRA ; return A=0, OK DS18B20_READ_2: RET
在正常读取数据的执行路径中,有一条 CLRA 指令将 A 清零。细心的读者如果对比上一节中发起温度转换的子程序可能会发现,这条指令在发起温度转换的子程序中并没有出现。原因是在发起温度转换的子程序中,返回 A=0 的执行路径中没有任何指令会破坏 A 寄存器的值,因此可以直接返回 A=0;而在读取温度数据的子程序中,返回 A=0 的执行路径里,为了将 SFR_DATA_EXCH 中读到的数据送入间接寻址指向的寄存器,需要经由 A 中转,因此有一条 MOV SFR_DATA_EXCH,A 指令,而这会破坏 A 寄存器的值。因此需要在返回前将 A 设置为正确的返回值。由于此执行路径上,A 在破坏前的值必然是 0,因此可以直接使用 CLRA 指令将 A 清零。
; PIOC FW for DS18B20 ; ASSUME DQ @ IO0, fClk=160M ; ; Include SFR definition INCLUDE PIOC_INC.ASM ; ; RESET VECTOR at 0x0000 ORG 0X0000 ; Begin at 0 DW 0X0000 ; RSVD word JMP MAIN_FUNC ; ; ================ ; Delay function ; caller CALL used 2cyc, NOP+RET used 3cyc, every loop used 5cyc. ; ARGS: NONE ; BREAKS: Z,SFR_INDIR_ADDR DELAY_5US: MOVIP 159 ; 5us takes 159 loop cycles DELAY_5US_1: NOP NOP DECSZ SFR_INDIR_ADDR,F ; cnt--, skip next if cnt==0 JMP DELAY_5US_1 ; else continue loop NOP RET ; ; ================ ; Delay function of 5us*A ; call DELAY_5US with A as loop counter. additional cycles not counted but negligible. ; ARGS: A as loop counter ; BREAKS: A,Z,C DELAY_XUS: CALL DELAY_5US ; Delay 5us ADDL 0XFF ; cnt--, Z=1 if cnt==0 JNZ DELAY_XUS ; else continue loop RET ; ; ================ ; DS18B20 Bus Reset & Presence Detect ; reset bus and check for presence pulse ; ARGS: NONE ; BREAKS: A,SFR_DATA_EXCH ; RETURN: A=0 OK ; A=1 Bus is busy ; A=2 No presence pulse detected DS18B20_BUSRST: MOVA1F 0B00000100 ; IO0 Input, Pull-up BG2F BI_PORT_IN0,0 ; IO0 -> DATA_EXCH bit0 BTSS SFR_DATA_EXCH,0 ; Check if bus is high RETL 1 ; else, return A=1, bus is busy BC SFR_PORT_IO,SB_PORT_OUT0 ; LOW -> IO0 MOVA1F 0B00000001 ; IO0 Output MOVL 96 ; Load arg=480us(5us*96) CALL DELAY_XUS ; Delay 480us MOVA1F 0B00000100 ; IO0 Input, Pull-up MOVL 14 ; Load arg=70us(5us*14) CALL DELAY_XUS ; Delay 70us, wait for presence pulse BG2F BI_PORT_IN0,0 ; IO0 -> DATA_EXCH bit0 MOVL 82 ; Load arg=410us(5us*82) CALL DELAY_XUS ; Delay 410us, wait to end BTSC SFR_DATA_EXCH,0 ; Check if presence pulse detected RETL 2 ; else, return A=2, no presence pulse RETL 0 ; return A=0, OK ; ; ================ ; DS18B20 Write slot ; write a bit to bus, from LSB ; ARGS: SFR_DATA_EXCH Bit0 ; BREAKS: A DS18B20_WSLOT: BC SFR_PORT_IO,SB_PORT_OUT0 ; LOW -> IO0 MOVA1F 0B00000001 ; IO0 Output CALL DELAY_5US ; Call twice, delay 10us CALL DELAY_5US BTSC SFR_DATA_EXCH,0 ; Check if bit to write is 1 MOVA1F 0B00000100 ; IO0 Input, Pull-up MOVL 12 ; Load arg=60us(5us*12) CALL DELAY_XUS ; Delay 60us MOVA1F 0B00000100 ; IO0 Input, Pull-up CALL DELAY_5US ; Call twice, delay 10us CALL DELAY_5US RET ; ; ================ ; DS18B20 Read slot ; read a bit from bus, to MSB ; ARGS: NONE ; BREAKS: A,SFR_DATA_EXCH DS18B20_RSLOT: BC SFR_PORT_IO,SB_PORT_OUT0 ; LOW -> IO0 MOVA1F 0B00000001 ; IO0 Output CALL DELAY_5US ; Delay 5us MOVA1F 0B00000100 ; IO0 Input, Pull-up CALL DELAY_5US ; Call twice, delay 10us CALL DELAY_5US BG2F BI_PORT_IN0,7 ; IO0 -> DATA_EXCH bit7 MOVL 13 ; Load arg=65us(5us*13) CALL DELAY_XUS ; Delay 65us RET ; ; ; ================ ; DS18B20 Write Byte ; write a byte to bus ; ARGS: SFR_DATA_EXCH ; BREAKS: Z,SFR_DATA_EXCH DS18B20_WB: CALL DS18B20_WSLOT ; Write a bit to bus RCR SFR_DATA_EXCH,F ; Shift right to put next bit on LSB CALL DS18B20_WSLOT ; Write a bit to bus RCR SFR_DATA_EXCH,F ; Shift right to put next bit on LSB CALL DS18B20_WSLOT ; Write a bit to bus RCR SFR_DATA_EXCH,F ; Shift right to put next bit on LSB CALL DS18B20_WSLOT ; Write a bit to bus RCR SFR_DATA_EXCH,F ; Shift right to put next bit on LSB CALL DS18B20_WSLOT ; Write a bit to bus RCR SFR_DATA_EXCH,F ; Shift right to put next bit on LSB CALL DS18B20_WSLOT ; Write a bit to bus RCR SFR_DATA_EXCH,F ; Shift right to put next bit on LSB CALL DS18B20_WSLOT ; Write a bit to bus RCR SFR_DATA_EXCH,F ; Shift right to put next bit on LSB CALL DS18B20_WSLOT ; Write a bit to bus RET ; ; ================ ; DS18B20 Read Byte ; read a byte from bus ; ARGS: NONE ; BREAKS: Z,SFR_DATA_EXCH DS18B20_RB: CALL DS18B20_RSLOT ; Read a bit from bus RCR SFR_DATA_EXCH,F ; Shift right CALL DS18B20_RSLOT ; Read a bit from bus RCR SFR_DATA_EXCH,F ; Shift right CALL DS18B20_RSLOT ; Read a bit from bus RCR SFR_DATA_EXCH,F ; Shift right CALL DS18B20_RSLOT ; Read a bit from bus RCR SFR_DATA_EXCH,F ; Shift right CALL DS18B20_RSLOT ; Read a bit from bus RCR SFR_DATA_EXCH,F ; Shift right CALL DS18B20_RSLOT ; Read a bit from bus RCR SFR_DATA_EXCH,F ; Shift right CALL DS18B20_RSLOT ; Read a bit from bus RCR SFR_DATA_EXCH,F ; Shift right CALL DS18B20_RSLOT ; Read a bit from bus RET ; ; ================ ; DS18B20 Start Temperature Conversion ; send SKIP ROM and CONVERT T command to DS18B20 ; ARGS: NONE ; BREAKS: A,Z,SFR_DATA_EXCH ; RETURN: A=0 OK, else pass error code from DS18B20_BUSRST DS18B20_CONV: CALL DS18B20_BUSRST ; Reset bus and check for presence pulse ANDL 0XFF ; Test if A==0 JNZ DS18B20_CONV_1 ; If A!=0, return error code MOVL 0XCC ; Load SKIP ROM command into A MOVA SFR_DATA_EXCH ; Load command to SFR_DATA_EXCH CALL DS18B20_WB ; Write a byte to bus MOVL 0X44 ; Load CONVERT T command into A MOVA SFR_DATA_EXCH ; Load command to SFR_DATA_EXCH CALL DS18B20_WB ; Write a byte to bus DS18B20_CONV_1: RET ; ; ================ ; DS18B20 Read Data ; send SKIP ROM and READ command to DS18B20 ; ARGS: NONE ; BREAKS: A,Z,SFR_DATA_EXCH,SFR_INDIR_ADDR2,SFR_DATA_REG0-8,SFR_DATA_REG31 ; RETURN: A=0 OK, else pass error code from DS18B20_BUSRST DS18B20_READ: CALL DS18B20_BUSRST ; Reset bus and check for presence pulse ANDL 0XFF ; Test if A==0 JNZ DS18B20_READ_2 ; If A!=0, return error code MOVL 0XCC ; Load SKIP ROM command into A MOVA SFR_DATA_EXCH ; Load command to SFR_DATA_EXCH CALL DS18B20_WB ; Write a byte to bus MOVL 0XBE ; Load READ command into A MOVA SFR_DATA_EXCH ; Load command to SFR_DATA_EXCH CALL DS18B20_WB ; Write a byte to bus MOVL 9 ; take 9 bytes to read MOVA SFR_DATA_REG31 ; loopCnt is in SFR_DATA_REG31 MOVIA 0x20 ; Set ptr to SFR_DATA_REG0 using INDIR2 DS18B20_READ_1: CALL DS18B20_RB ; Read a byte from bus MOV SFR_DATA_EXCH,A ; Load read byte into A MOVA SFR_INDIR_PORT2 ; Store A into SFR_DATA_x, auto increment DECSZ SFR_DATA_REG31,F ; cnt--, skip next if cnt==0 JNZ DS18B20_READ_1 ; else continue loop CLRA ; return A=0, OK DS18B20_READ_2: RET ; ; ================ ; Main entry ; Dispatch commands from SFR_CTRL_WR ; MAIN_FUNC: BC SFR_SYS_CFG,SB_INT_REQ ; Cancel IRQ WAITB WB_DATA_MW_SR_1 ; Wait for CmdCode from MCU MOV SFR_CTRL_WR,A ; Load CmdCode into A CMPL 0X01 ; If: CmdCode==0x01? JZ CMD_CONV ; Then: Jump to CALL_CONVERT_T CMPL 0X02 ; ElseIf: CmdCode==0x02? JZ CMD_READ ; Then: Jump to CALL_READ_SCR CMPL 0X03 ; ElseIf: CmdCode==0x03? JZ CMD_POLL ; Then: Jump to CALL_POLL_CONV JMP MAIN_FUNC ; Else: No CmdCode matched. Wait for next CmdCode. ; CMD_CONV: CALL DS18B20_CONV MOVA SFR_CTRL_RD ; Load A into SFR_CTRL_RD BS SFR_SYS_CFG,SB_INT_REQ ; Set IRQ WAITB WB_DATA_SW_MR_0 ; Wait for MCU reading result JMP MAIN_FUNC ; CMD_READ: CALL DS18B20_READ MOVA SFR_CTRL_RD ; Load A into SFR_CTRL_RD BS SFR_SYS_CFG,SB_INT_REQ ; Set IRQ WAITB WB_DATA_SW_MR_0 ; Wait for MCU reading result JMP MAIN_FUNC ; CMD_POLL: CALL DS18B20_RSLOT MOV SFR_DATA_EXCH,A ; Load ReadSlot result into A MOVA SFR_CTRL_RD ; Then load A into SFR_CTRL_RD BS SFR_SYS_CFG,SB_INT_REQ ; Set IRQ WAITB WB_DATA_SW_MR_0 ; Wait for MCU reading result JMP MAIN_FUNC ; END ;