Keil C51实现大于64 KB程序的bank分组代码切换机制的原理
Ofweek 2021-10-22

  在8051核单片机庞大的家族中,C8051F系列作为其中的后起之秀,是目前功能最全、速度最快的8051衍生单片机之一,正得到越来越广泛的应用。它集成了嵌入式系统的许多先进技术,有丰富的模拟和数字资源.是一个完全意义上的SoC产品。

  C805IFl2X作为该系列中的高端部分,具有最快100MIPS的峰值速度,集成了最多的片上资源。其128 KB的片上Flash和8 KB的片上RAM足以满足绝大多数应用的需求。使用C8051F12X,只需外加为数不多的驱动和接口,就可构成较大型的完整系统。只是其中128 KB的Flash存储器不可避免地要处理bank分区问题。

  幸运的是Keil C51开发环境对C8051F系列有良好的支持,包括一般的跨bank分区的程序跳转和调用。作为数据存储器使用时,Flash的分区读写完全是编程者要考虑的事情,与开发环境无关。本文只针对特殊的强制转移和μC/OS—II在多bank分区中的移植问题展开讨论。

 

  1 C8051F12X在Keil C51中的多bank分区转移机制

 

  Keil C51的连接定位器支持分组连接,允许生成代码长度大于64 KB的8051目标程序_1_。一般的8051系统只提供16根地址线,需要附加地址线来实现代码分组切换,而编译器产生bank切换代码时受到配置文件L51_BANK.A51的支持,所以用户必须根据自己的硬件结构来修改这个配置文件。

  C8051F12X系列不用考虑硬件部分,也不存在地址线的扩展问题,因为128 KB的4个bank区全部都在CPU内部,所以作为常规跨bank的跳转和调用,不需要处理1.5l_BANK.A51配置文件。但在特殊情况下就必须考虑该问题,否则程序将无法工作。下面以C8051F120为例先讨论代码的透明分组切换过程。

  C805IFl20在Keil C51的项目配置中被划分为4个bank,每个32 KB。公共bank地址从0~0x7fff,其余bank从0x8000h~0xffff。在对应的配置文件L51_BANK.A51中,涉及到特殊功能寄存器PSBANK(SFR地址:0B1H)、SWITCHn宏、B_BANKn、?B_SWITCHn分组信息保存和切换代码,以及?B_CURENTBANK变量。

  PSBANK为C8051F120内的特殊功能寄存器,128KB Flash的分bank访问就是通过它来实现的。要想转移到新的bank中去,必须赋予PSBANK正确的值,然后再转向bank区内地址即可。

  SWITCHn宏共有4个,分别是SwITCH0、SWlTCH1、SWITCH2和SWITCH3,对应切换到4个bank中。其中SWITCH0对应的语句为:

  MOV PSBANK.#00h ;把00h用1Ih、22h和33h替换,

  ;就是其他三个宏

  它将插入到?B_SWITCHn代码中,用来切换新的bank和恢复到原来的bank。

  所有4组?B_BANKn和?B_SWlTCHn代码也都是用宏实现的,对应4个bank处理。它们汇集在?BANK?SWITCH代码段中,整个bank切换及恢复机制非常巧妙,可以实现任意bank之间函数的相互调用及嵌套。下面以bank3区中的main函数调用bankl区的Delay_noOS()延时函数为例说明该机制。

  void main(void){

  Init(); //初始化CPU

  Delay_n00s(10); //延时lO ms

  Lcmlnition();

  :

  :

  bank3中被调用的函数Delay_noOS(10)对应的汇编语句为:

  LCALL C:5049

  公共段(即Common段,对应bank0)中C:5049处的

  汇编语句如下:

  MOV dptr,#Delay_noOS

  AJMP B_BANKl

  这里的B_BANKl就是宏?B_BANK&N中N为1的例程。现在进入问题的核心:全部的跨bank区程序切换及恢复过程依靠公共段中?BANK?SWITCH代码段里的以下汇编代码实现,对应的N为0、1、2和3。?BANK?SWlTCH SEGMENT CODE PAGE

  ;

  ?B_BANK&N:

  PUSH ?B_CURRENTBANK (1)

  MOV A,#HIGH?BANK?SWITCH (2)

  PUSH ACC (3)

  PUSH DPL (4)

  PUSH DPH (5)

  ?B_SWITCH&N:

  MOV ?B_CURRENTBANK,#LOW? B_SWITCH&N

  (6)

  SWlTCH&N (7)

  RET (8)

  :

  Delay_noOS(10)函数的返回地址,即函数LcmIni-tion()的入口地址(也在bank3中),其高低位字节表示为ADDH和ADDL。程序进入main()后的?B_CURRENTBANK变量初值是?B_SWITCH3的低8位,其意义稍后叙述。AJMP B_BANKl后程序执行?B_BANKl和?B_SWITCHl的(1)~(8),执行到(5)时的堆栈结构如图1所示。

  

  继续执行?B_SWITCHl到(7)时,PSBANK变为指向bankl,?B_CURRENTBANK变为?B_SWITCHl的低8位。执行(8)后,从堆栈结构可以看出,堆栈弹出①作为新的PC值,程序进入Delay_noOS(10)函数,延时功能完成后,函数最后一条RET指令开始返回。这是Keil C51处理bank机制的关键,此时的返回地址为堆栈中的②,此地址即?B_SWITCH&H代码的入口,这里对应main()函数所在的bank3分组,也就是?B_SWITCH3的人口。

  因为所有?B_SWITCH&N的高8位地址,即?BANK?SWITCH代码段的高8位都一样,由语句(2)中的操作符HIGH?BANK?SWITCH确定;低8位保存在已经压栈的?B_CURRENTBANK变量中,此时堆栈中的?B_CURRENTBANK压栈值是?B_SWITCH3的低8位,这样②的地址就是?B_SWITCH3。

  程序继续执行?B_SWITCH3,在执行?B_SWITCH3的(6)语句之前,?B_CURRENTBANK还是前面执行?B_SWITCHl时的值,即?B_SWITCHl的低8位。执行语句(6)后,?B_CURRENTBANK恢复为?B_SWITCH3的低8位,为返回main函数做准备。然后PSBANK置为33h,即指向bank3,接着执行RET语句,堆栈③成为RET的返回地址,程序回到了main()中Delay_noOS(10)的下一条语句继续执行,?B_CURRENTBANK也已恢复。

  这个调用过程中,用了6个堆栈字节,3条RET指令,关键内容就是?B_CURRENTBANK变量,它保存了可以恢复调用前bank环境代码的地址低位。从被调用函数返回 到这个地址后,就能恢复调用前的bank环境,即赋予PSBANK正确的值。

  不采用直接保存PSBANK值然后再恢复,而是用压栈的方式保存了相关地址(语句(1)~(3)),是为了实现跨bank区的嵌套调用。例如,在Delay_noOS(10)函数中,如果再次跨bank去调用新函数,会再次重复上述过程,堆栈从②往上再长6个字节。Delay_noOS(10)函数之前执行?B_SWITCHI产生的?B_CURRENTBANK值(?B_SWITCHI的低8位)也会进栈,为调用完新函数后返回到bankl继续执行Delay_noOS(10)提供保证。

 

  2 无操作系统bank分区间的强制跳转

 

  通过上面的分析得知,如果要处理跨bank区的跳转、调用和返回,关键是能正确处理好PSBANK中的内容。当程序没有操作系统用于任务切换,而又需要强制退出某一函数进入到另一函数的某一地址时,比如说在中断发生后,结束原来的工作转入到另一工作去,就需要处理好PSBANK。

  如果不考虑bank,可以在转入新地址之前执行一段代码,保存该地址处的环境变量[2],包括堆栈指针sP和需要的入口地址。然后在中断返回之前,恢复此环境变量,执行中断返回指令进入该新地址。这个思路和C51库函数setjump和longjump比较相近,但比它们灵活,因为环境变量可以自己处理。

  考虑bank后的情况稍微复杂些,环境变量中需增加bank的处理信息,那么只处理PSBANK行不行呢?

  如果仅保存和恢复PSBANK,则很简单,在保存环境变量的程序中加入:

  JMPEnv[envl][3]=PSBANK;

  在恢复环境变量的程序中加入:

  PSBANK=JMPEnv[envl][3];

  这里环境变量是二维数组JMPEnv,envl代表一个环境变量,即一个返回点。第二维是变量中的参数个数。因此可以保存多个环境变量以供使用。

  初看起来这样处理是没有问题的,可实际上不行。因为进入返回点后,虽然PSBANK正确了,但是?B_CUR-RENTBANK可能已经被修改,不能和返回点程序的bank区匹配,如果再次出现跨bank调用的话将不能正确返回。

  处理方法是有点技巧的,因为C语言不支持汇编变量?B_CURRENTBANK的写法,所以在L51_bank.A51中要加上声明:

  PUBLIC BLCURRENTBANK

  和伪指令:

  B_CURRENTBANK EQU ?B_CURRENTBANK

  这样就可以在C程序中使用B_CURRENTBANK

  了,先声明B_CURRENTBANK:

  extern Uchar data B_CURRENTBANK;

  然后在保存环境变量程序中加入:

  JMPEnv[envl][3]=PSBANK;

  JMPEnv[envl][4]=B_CURRENTBANK;

  恢复环境变量程序中加入:

  PSBANK=JMPEnv[envl][3];

  B_CURRENTBANK=JMPEnv[envl][4];

  这样恢复环境变量进入到新程序后,也将恢复该程序对应的正确?B_cuRRENTBANK值,问题得到解决。

 

  3 no/0S-ll移植中的bank分区处理

 

  μC/OS-II的51版本已经很成熟,但是所有移植版本均未处理bank问题,需要增加该内容,否则不能在包括C8051F12X系列及其他多bank程序中使用。

  如前所述,Keil C51提供对跨bank调用的透明切换支持,但在使用操作系统时,这种透明切换机制还需要提供对任务切换的支持。因为任务的切换,程序可能需要到别的代码分组中去运行,而此时PSBANK和?B_CUR-RENTBANK还停留在原来代码分组中的状态,将导致程序崩溃。显然,无论由于什么情况导致的任务切换完成之前,都需要保存和恢复PSBANK和?B_CURRENT-BANK的值。解决的办法是在每次任务切换前将PS-BANK和?B_CURRENTBANK压入用户任务栈。

  按照μC/OS-II的要求,在任务创建时,任务栈必须初始化成像运行中的任务刚刚发生过中断一样嘲。?B_CURRENTBANK的初始值取决于该任务所在分组对应的切换代码段的低8位地址。所以,任务堆栈的初始化函数OSTaskStkInit需要加入一个参数INT8U bank,指明该任务位于哪个代码分组中。又由于任务堆栈的初始化函数是被任务创建函数OSTaskCreate调用的,所以该函数一样需要加入参数INT8U bank。

  在压栈,出栈宏中需要加入:

  PUSH PSBANK

  PUSH?B_CURRENTBANK

  :

  POP ?B_CURRENTBANK

  POP PSBANK

  在任务堆栈的初始化函数OSTaskStkInit中需要加入:

  *stk++=17; //堆栈长度增加2个到17

  ;

  if(bank==0x22:){ //bank2

  *stk++=bank;

  *stk++=CurrentBank2();

  else if(bank==0x33){ //bank3

  *stk++=bank;

  *stk++=CurrentBank3();

  }

  else{ //bankl和common

  *stk++=0xll; //PSBANK

  *stk++=CurrentBankl();

  )

  其中,bank0用任何的PSBANK值均没有问题,所以简化了PSBANK取值0x00的情况。

  函数INT8U CurrentBankl(void),INT8U Current-Bank2(void)和INT8U CurrentBank3(void)是用汇编语言实现的,返回值通过R7传递,目的是获得该任务所在分组对应切换代码段(?SWITCHn)的低8位地址。不用C语言编写的原因同样是?B_SWITCH&N不被C支持。

  CurrentBankl(void)代码如下,其他两个类同。

  RSEG?PR?CurrentBankl?Os_CPU_A

  CurrentBankl:

  MOV DPTR,#?B_SWITCHl

  MOV R7.DPL

  RET

 

  结 语

 

  本文介绍了Keil C51实现大于64 KB程序的bank分组代码切换机制的原理,提出了没有操作系统情况下非正常转移时bank的处理方法以及μc/os—II操作系统在多bank分区程序移植中应采取的措施,在开发实例中均得到了很好的应用。

声明: 本文转载自其它媒体或授权刊载,目的在于信息传递,并不代表本站赞同其观点和对其真实性负责,如有新闻稿件和图片作品的内容、版权以及其它问题的,请联系我们及时删除。(联系我们,邮箱:evan.li@aspencore.com )
0
评论
热门推荐
  • 相关技术文库
  • 单片机
  • 嵌入式
  • MCU
  • STM
  • ARM Cortex系列处理器知识点汇总

    最近因为要为芯片选定核,所以就在了解哪些核合适且性价比好,这是一个需要结合产品各类技术、市场分析的活,看似简单却还是需要一些储备的,今天选了一篇ARM Cortex系列的科普文章与大家分享。 众所周知,英国的ARM公司是嵌入式微处理器世界当中的佼佼者。AR

    05-11
  • 你的CPU属于哈佛结构还是冯诺依曼结构?

    现代的CPU基本上归为冯诺伊曼结构(也称普林斯顿结构)和哈佛结构。 冯洛伊曼结构就是我们所说的X86架构,而哈佛结构就是ARM架构。一个广泛用于桌面端(台式/笔记本/服务器/工作站等),一个雄踞移动领域,我们的手持设备(平板\手机用的大多就是他了)。 01

    05-10
  • 如何批量修改MCU封装管脚定义

    在做产品开发时,为了缩短研发周期,我们一般都是直接找来参考设计做参考。这些参考资料要么是来自原厂的,要么是来自方案商的。  接触过这么多的参考设计资料,发现大部分的资料都有一个通病,就是不少MCU的PIN脚定义都只是标出IO口的定义,其它复用​​​​功能

    05-08
  • MCU为什么要消抖动

    简单的说,进入了电子,不管是学纯模拟,还是学单片机,DSP、ARM等处理器,或者是我们的FPGA,一般没有不用到按键的地方。按键:人机交互控制,主要用于对系统的控制,信号的释放等。因此在这里,FPGA上应用的按键消抖动,也不得不讲! 一、为什么要消抖动 在

    05-07
  • 51单片机的ISP下载知识

    本文详细介绍了串口、51单片机的ISP下载等基础知识,已经学过单片机的也可以看看,加强一下对这方面的了解。 串口 串行接口简称串口,也称串行通信接口,是采用串行通信方式的扩展接口。 我们比较熟悉的USB接口,全名通用串行总线(Universal Serial BUS),就

    05-06
  • 硬件开发如何选择合适的MCU

    点击上方关注我们! 我在做硬件开发时,如果遇到的是一个新产品,新项目,之前没有做过的,没有任何的经验,在选MCU时,我一般是这样操作的。 首先,根据产品的需求,整理出一份硬件规格。比如,电源管理,传感器接口,人机交互接口等。 然后,整理出整个原理

    05-06
  • 单片机的功耗怎么算的?

    单片机的功耗是非常难算的,而且在高温下,单片机的功耗还是一个特别重要的参数。暂且把单片机的功耗按照下面的划分。 暂且把单片机的功耗按照下面的划分。 1.内部功耗(与频率有关) 2.数字输入输出口功耗 2.1输入口 2.2输出高 2.3输出低 3.模拟输入口功耗从

    05-07
  • 嵌入式工程师必备工具:I2C和SPI总线协议

    IIC vs SPI 现今,在低端数字通信应用领域,我们随处可见IIC (Inter-Integrated Circuit) 和 SPI (Serial Peripheral Interface)的身影。原因是这两种通信协议非常适合近距离低速芯片间通信。Philips(for IIC)和Motorola(for SPI) 出于不同背景和市场需求

    04-30
  • 嵌入式面试注意事项

    找工作也是一门技能,有的人很快就找到自己喜欢的工作,有的人找了很久也没找到合适的工作。 下面给大家分享几点找工作过程中存在的“潜规则”内容。 1、面试的本质不是考试,而是告诉面试官你会做什么 经验不够的小伙伴特别容易犯的一个错误,不清楚面试官到

    04-29
  • 为什么需要RTOS?

    很多单片机初学者都是从裸机开始的,裸机确实也能开发出好的产品,但作为一个嵌入式软件工程师,如果只能用裸机开发产品,那肯定是不够的。 要从裸机的思维转变到RTOS的思维,其实需要一个过程,而且开始的一段时间会很痛苦。但过一段时间理解了一些内容,能

    04-28
  • 使用RTOS的8个理由

    嵌入式系统中,有很多方式实现任务调度。功能有限的小系统中,无限循环足够实现系统功能。当软件设计变得庞大且复杂时,设计师应该考虑使用实时操作系统。 下面给大家分享使用RTOS的8个理由: 1.硬实时响应 基于优先级抢占的RTOS,根据任务的实时需求,执行优

    04-26
  • 单片机延时程序,Keil C编译器实现

    应用单片机的时候,经常会遇到需要短时间延时的情况。需要的延时时间很短,一般都是几十到几百微妙(us)。有时候还需要很高的精度,比如用单片机驱动 DS18B20的时候,误差容许的范围在十几us以内,不然很容易出错。这种情况下,用计时器往往有点小题大做。而

    04-26
下载排行榜
更多
广告
X
广告