PIC12F509 - 绕过堆栈限制
12C5系列PIC只有一个两级堆栈,它将嵌套子程序调用的数量限制为两个。这可能是一个非常严重的限制。
(16C84有一个八级堆栈,它允许嵌套的子程序达到八个深度。我无法想象一个程序,这是不够的)。
在以下程序中,提供了另一个用户堆栈,它为程序员提供了替代的“调用”和“返回”功能。
请注意,使用最高数据地址实现用户堆栈。这个想法是用户堆栈从1FH开始增长,而用户变量从07H及以上分配。因此,使用这种方法允许的嵌套程度是完全变量空间(25)减去在程序中用作变量的字节数。
这种方法还有两个局限性;
1.只有程序计数器的低字节保存在用户堆栈中。因此,这限制了所有函数的调用以及函数的实现到同一页面。
但是,这并不妨碍人们将程序分成几页并在每页上实现每个被调用的函数。当然,不同页面上相同的函数实现必须具有不同的名称。也就是说,一定要小心并思考。但是,随着12C5XX的价格下降到仅仅1.00美元,我们需要思考!
这种分离程序的方法在另一个涉及I2C总线的讨论中显示。
请参考程序STACK_1.ASM,它会持续闪烁LED指示灯。请注意,主调用例程SUB1,DELAY,SUB2和DELAY然后循环返回以重复该过程。
FSR寄存器初始化为最高数据位置01FH。
每个“呼叫”包含以下说明。
MOVF PCL,W; 将PCL提取到W ADDWF OFFSET,W; 加4 MOVWF INDF; 保存到FSR DECF FSR 指向的位置,F; 用于下一个子程序GOTO SUB1; 打开LED
RET_POINT1:; …程序的继续
在上文中,请注意,获取程序计数器的低字节的当前内容。要返回的地址(RET_POINT1)通过添加4来计算,并将其保存到用户堆栈指针指向的位置。然后递减堆栈指针以容纳下一个返回地址。最后,执行跳转到该函数。
在子例程中,首先执行任务。然后使用以下代码实现“返回”。
; 任务执行INCF FSR,F; 返回MOVF INDF,W MOVWF PCL
在上文中,用户堆栈指针递增以便指向包含存储在调用例程中的程序计数器的值的位置。这被取出并放在程序计数器的低字节中。因此,执行在调用例程的返回点继续。
; STACK_1.ASM(12F509); ; 说明如何使用用户堆栈来实现“调用”和“返回”。; 这在12F509上尤为重要,因为叠加限于; 两个级别。; ; 闪烁GPIO0上的LED,250 ms开启和250 ms关闭。
列表p = 12F509
include __CONFIG 1AH
LOOP1 EQU 07H; 用于定时环路LOOP2 EQU 08H OFFSET EQU 09H
ORG 000H
MOVLW 1FH; 初始化FSR以指向“堆叠” MOVWF FSR的顶部
MOVLW .4 MOVWF OFFSET; 偏移初始化为4
MOVLW 1EH; 最小符号位是输出TRIS GPIO TOP : ; 保存堆栈上的返回地址MOVF PCL,W; 获取PCL,添加4并保存在ADDWF OFFSET,W 位置; FSR MOVWF INDF DECF FSR 指出,F; dec堆栈指针用于下一个子程序GOTO SUB1; 打开LED
MOVF PCL,W ADDWF OFFSET,W MOVWF INDF DECF FSR,F GOTO DELAY; 250毫秒延迟
MOVF PCL,W ADDWF OFFSET,W MOVWF INDF DECF FSR,F GOTO SUB2; 关闭LED
MOVF PCL,W ADDWF OFFSET,W MOVWF INDF DECF FSR,F GOTO DELAY; 250毫秒延迟
GOTO TOP
SUB1:BCF GPIO,0; 逻辑零点亮LED
; 这三行是返回INCF FSR,F的等价物; 增量FSR MOVF INDF,W; 获得返回地址MOVWF PCL; 并放入程序计数器
SUB2:BSF GPIO,0; 逻辑1关闭LED INCF FSR,F; 返回MOVF INDF,W MOVWF PCL
延迟:; 运行时将LOOP1设置为.250,将LOOP2设置为.110。; 这将导致250毫秒的延迟。MOVLW .250 MOVWF LOOP1 OUTTER:MOVLW .110; 当设置为.110 MOVWF LOOP2 INNER:NOP NOP DECFSZ LOOP2,F 时接近1.0毫秒延迟; 减量和离开结果为LOOP2 ; 如果为零则跳过下一个语句
GOTO INNER DECFSZ LOOP1,F GOTO OUTTER
INCF FSR,F; 返回MOVF INDF,W MOVWF PCL
结束
在程序STACK_2.ASM中,“调用”和“返回”是使用已标记为GOSUB和RET的宏实现的。请注意,宏使程序更容易理解。
; STACK_2.ASM。; ; 与STACK_1.ASM相同,但使用宏实现。; ; 版权所有,Peter H. Anderson,MSU,1997年6月1日
列表p = 12F509 #include __CONFIG 1AH
; 宏定义了GOSUB MACRO arg1; 使用用户定义的堆栈保存返回地址MOVF PCL,W; 并跳转到指定的例程。ADDWF OFFSET,W MOVWF INDF DECF FSR,F GOTO arg1
ENDM
RET MACRO; 从堆栈INCF FSR,F MOVF INDF,W MOVWF PCL 获取返回地址
ENDM
LOOP1 EQU 0CH; 用于定时环路LOOP2 EQU 0DH OFFSET EQU 0EH
ORG 000H
MOVLW 1FH; 初始化FSR以指向“堆叠” MOVWF FSR的顶部
MOVLW .4 MOVWF OFFSET; 将OFFSET初始化为4
MOVLW 1EH TRIS GPIO; 最小符号位定义为输出TOP:GOSUB SUB1 GOSUB DELAY GOSUB SUB2 GOSUB DELAY GOTO TOP
SUB1:BCF GPIO,0 RET SUB2:BSF GPIO,0 RET DELAY :; 运行时将LOOP1设置为.250,将LOOP2设置为.110。; 这将导致250毫秒的延迟。MOVLW .250 MOVWF LOOP1 OUTTER:MOVLW .110; 当设置为.110 MOVWF LOOP2 INNER:NOP NOP DECFSZ LOOP2,F 时接近1.0毫秒延迟; 减量和离开结果为LOOP2 ; 如果零GOTO INNER DECFSZ LOOP1,F GOTO OUTTER ,则跳过下一个语句
RET
结束