当您的程序在一个简单的PIC微控制器上增长到足够长的时候,您最终将达到一个点,即您的代码不再适合单个页面。
我写这篇文章是为了解释这个问题,希望能帮助您避免我最近第一次遇到这个问题时遇到的一些陷阱。
那么什么是页面呢?嗯,像GOTO或CALL这样的指令只能包含这么多位地址。我们使用page来引用单个goto或调用可以到达的所有指令地址,而不需要额外的帮助器命令。
例如,像PIC16F88或PIC16F1765的GOTO和CALL这样的14位设备在它们的操作码中只包含11位的地址,这意味着页面大小是2^11(2048)条指令。16F88和16F1765总共有4096(2^12)和8192(2^13)个地址,这意味着它们分别有2页和4页。像PIC10F系列这样的12位设备在其GOTO/CALL操作码(512个地址)中只有9位地址,但最多可以有1024条指令。
我在这里引用的数字的具体内容可能会因设备而异,但本文的一般原则应该适用。值得注意的是,PIC18及更高版本的设备不使用分页,因此本文对这些设备可以忽略不计。
分页是一个问题,因为当我们尝试跳转到与我们不在同一代码页上的标签时,我们会跳转到我们自己页面中的等效位置,这显然不是我们想要的位置。
您可以使用下表将问题形象化。试想一下,该表有2048行。只需查找您想要跳转到的地址,然后跟随它并查看您当前所在页面的列。此表适用于14位设备。
例如,如果我们在14位设备上,在第0页(即我们当前在0x0000到0x07FF之间的指令上),那么如果我们尝试转到地址0x0FFD(第1页)的标签,我们将改为跳转到0x7FD处的任何指令。
由于显而易见的原因,在您不打算执行的代码指令处登陆(并随后执行)可能是灾难性的。
那么,我们如何使用那些与我们不同的指令呢?我们如何确保我们跳到了我们想要的指令?
在我们解释这一点之前,让我们先来看看像goto和call这样的跳转指令到底在做什么。
实际上,goto和call只是设置程序计数器的快捷方便的方法(如果我们暂时忽略call的返回部分)。在PIC中,程序计数器分为两个字节;最低有效位在PCL中,而最高有效位在PCLATH中。GOTO和CALL指令将一个完整字节写入PCL,然后将几位写入PCLATH。问题是它们不会写入标签的所有位。最高有效位。
作为示例,我将仅使用movlw和movwf以等效的方式重写一条goto指令。不过,我并不是建议我们考虑使用第二块代码!
movlw高标签;相当于movlw 0x08 movwf PCLATH movlw低标签;这相当于movlw 0x00 movwf PCL组织0x0800;标签在1label:页上;这里有一些代码。
从技术上讲,第二段代码不是100%等价的。为什么?嗯,它使用了更多的指令(因此内存更大,执行时间也更长),但它也设置了程序计数器的所有位,而不仅仅是goto或call可以设置的位。
我们可以使用第二个代码块转到正确的页面,但在实践中使用它会很烦人,因为它会破坏W寄存器。
PAGSEL是一条汇编指令,它自动生成命令来调整GOTO和CALL不设置的PCLATH的高位。
如果标签在14位微控制器的第二页上(即GOTO只有11位),则页面可能被转换为:
Bcf PCLATH,4 BSF PCLATH,3转到标签组织0x0800;标签在第1label:页;此处有一些代码。
显然,我们可以自己编写代码,但通常更清晰(当您想要将代码转移到新设备时,也更便于移植!)。仅使用PageSel。
我们进行分页的原因是因为GOTO和Call不包含它们试图返回的标签的完整地址。那么返程、复飞还是复飞呢?这些命令是否包含完整地址?
简而言之,是的。在这些命令之前不需要PAGSEL,因为在调用或中断发生时,完整地址存储在堆栈上。我们将始终返回到正确的页面和说明。
但是,return/retfie/retlw不会将PCLATH更正为指向我们要返回的页面。这可能是有用的,但也可能是一种障碍。
在下面的示例中,我们希望对第1页上的例程进行多次调用。这里没有返回更改PCLATH很有用,因为我们不必在每次调用之前重复分页。
页面集label1调用label1;因为PCLATH没有被返回更改,;我们这里不需要页面集调用label2;因为PCLATH没有被返回更改,;我们不需要页面集这里调用label3 org 0x0800;label1、label2和label3都在第1页label1:;这里的一些代码returlabel2:;这里的一些代码returlabel2:;
在下一个示例中,在想要转到第0页上的例程之前,我们只调用了第1页上的例程一次。由于PCLATH未通过返回进行更改,因此GoTo Foo将失败,而转到第1页中的某个位置。
页面标签呼叫标签;危险!PCLATH不会因返回而改变,因此;这里需要一个分页程序来停止`goto`;将我们放在页面1中的某个地方转到foofoo:;这里的一些代码org 0x0800;标签在页面1label:;这里的一些代码返回。
如果我们想要安全,我们总是可以在调用之后包含一个PagSel$指令。PAGSEL$只是表示“选择我们当前正在执行的页面”的一种简单方式。
页面集label1调用label1页面集$;这是可行的,但是不必要的页面集label2调用label2页集$;这是可行的,但是不必要的页集label3调用label3页集$org 0x0800;label1、label2和label3都在页面1label1:;这里的一些代码返回label2:;这里的一些代码返回label3:;这里的一些代码返回。
PAGSEL标签调用LABEL PAGSEL$;我们的‘Goto foo’现在到了正确的位置!goto foofoo:;此处的一些代码组织0x0800;标签在页面1label:;此处的一些代码返回。
就像组装中的许多事情一样,这通常归结为理解规则。如果你不清楚,有一种安全但不太理想的方法,但如果你知道自己在做什么,你可以更有效率。
前面的部分已经涵盖了不使用中断的PIC微控制器项目需要了解的所有内容。
正如您可能知道的那样,当进入中断服务例程时,您的程序会跳出正常的程序流,跳转到特殊的中断服务例程。此跳转可以发生在应用程序中任何两个不同代码行之间的任何点。遗憾的是,中断服务例程中的指令可能会因更改公共寄存器的值而无意中中断非ISR代码。
这里需要注意的是,我们可能无意中更改的寄存器之一是PCLATH寄存器-程序计数器的高位字节。当跳转到中断向量时,中断不会改变这一点,但是您可能会在不知不觉中在代码中更改它。如果您这样做了,那么从中断返回并执行调用或GOTO将导致您的代码跳转到错误的页面,即使以前正确使用了PageSel!
值得庆幸的是,如果你使用的是较新的设备,MicroChip已经考虑到了这一点,并实现了硬件“上下文保存”功能,所以你不必担心。在您的数据表中搜索“Automatic Context Saving”以检查您是否拥有它。
在较旧的设备上,您必须自己实现此功能。我将以下代码块用于我的中断服务例程。我不记得这是我自己写的,所以我不可避免地从什么地方偷来了它,但我认为它是一段非常标准的样板代码,所以我不介意分享它。
中断Enter:movwf W_TEMP;保存W寄存器交换状态,w;要保存到W中的交换状态(movf更改状态)movwf status_temp;保存状态寄存器movfw PCLATH movwf PCLATH_TEMP;保存PCLATH寄存器;您的中断例程代码位于此处movfw PCLATH_TEMP;恢复PCLATH寄存器(正确的代码页)movwf PCLATH swapf状态。
当然,如果您确定不会更改ISR中的W、状态或PCLATH,则不需要执行上述部分/全部操作。
中断不会更改PCLATH。这意味着如果您从不同的页面跳到ISR,任何调用或GOTO都会指向错误的页面!
我建议在前面的代码片断中包含一个页面集$;您的中断例程代码放在这里。
自动上下文保存不能解决此问题,因此在具有上下文保存的较新设备上,您的中断可能如下所示:
我想写这篇文章的主要原因是为了突出以下问题。我花了一段时间才到这里,我知道!
您将经常在在线汇编程序的顶部看到类似这样的内容:
Org 0x0000;Reset Vector Goto Main Organization 0x0004;Interrupt Vector Goto ISR org 0x2100;EEPROM Presets de 0x00,0x01,.。
这是一组简单的向量,告诉您的应用程序要去哪里。这是标准的东西。
实际上,汇编器/链接器/库管理员用户指南的第99/100页中也使用了类似的内容。
使用的特定向量并不重要;很多都不会有EEPROM,有些会有不同的向量,不管怎样。
这里重要的是GOTO ISR线路。如果你到目前为止一直在注意,那应该是对你大喊大叫!
如果我们的ISR在与ISR标签不同的页面上被触发,会发生什么情况?
在过去的几天里,我对此有一个真正的问题。这是一行您甚至没有想过的无害的代码,但是它可能会给您带来麻烦!
相反,我建议将整个ISR代码移到org0x0004指令的正下方。您可以对其他矢量重新排序,这无关紧要。在我检查过的每个芯片中,程序内存都是从0x0005开始的,所以我不认为这样做有任何问题。不过,我会检查一下您数据表中的“程序内存映射”,以确保这一点。
组织0x0000;将矢量重置为主组织0x2100;EEPROM预设为0x00、0x01、.。org 0x0004;中断向量movwf W_TEMP;保存W寄存器交换状态,w;要保存到W中的交换状态(movf更改状态)movwf status_temp;保存状态寄存器movfw PCLATH movwf PCLATH_TEMP;保存PCLATH寄存器页面$;您的中断例程代码位于此处;.。你明白我的意思了吗?
另外,我参加聚会迟到了,不过我最近有一个推特账号,你可以在这里关注。