51, RAM中运行程序@120MHz, STC32G144K246 | 从QSPI直接运行程序开发中 本例程使用内部24MHz高速时钟,经过HPLL1倍频到120MHz后,给系统时钟使用 另有实际测试:同样的一段计算函数(1000次整数乘法和加法),计算时关闭总中断 主程序120MHz消耗了200us, 而在RAM内运行消耗了124us(此时WTST设置失效,零等待执行),速度提高了38% 程序拥有两种方式来载入RAM程序,一种是同一个工程中,通过对函数取地址来获取程序内容。这种方法在调用其他函数的时候,本质还是会跳转回到FLASH部分运行 第二种是使用独立的工程来编译RAM中需要运行的程序,这部分通过设置程序整体偏移地址为0x800000实现,同时通过hex2bin.exe实现自动转为bin文件,这个编译后的bin文件通过“打开EEPROM文件”载入即可使用 使用同一工程的方式,效果是P32和P35闪烁,长按P32按键后,P32、P35停止闪烁。这部分程序是主程序,非RAM内运行 如果按下P33按键,则跳转到RAM内运行程序,效果是P6上持续自减数值,也就是二进制不断刷新显示。


使用不同工程的方式,主程序运行效果和上面所述一致
但是如果按下P33按键,程序跳转到RAM内运行程序,效果是P60持续闪烁


需要注意的是,使用不同工程的情况下,需要手动设定ECALL跳转地址
具体操作为打开独立的RAM_EXE工程:
选择模拟仿真后点击仿真按钮

然后查看main函数内第一行的语句所在地址:这里是0x80:000f,将这个地址回填到主程序内的绝对地址跳转调用上即可


注意事项:
在CKCON的最高位bit7中RAMEXE中

如果RAMEXE设置为0时,可以通过03:0000H~03:0FFFH的far地址读写,此时可以认为是拓展的xdata地址
这个时候不可以读800000H~800FFFH,否则会造成错误,进而导致卡死(因为800000H~800FFFH属于ecode,本来也不让写)
如果RAMEXE设置为1时,可以通过80:0000H~80:0FFFH地址来读内容
这个时候写03:0000H~03:0FFFH,写入内容不会生效,读取内容也和实际内容不符(作为保护程序的一个措施,如果需要更改程序,需要将RAMEXE重新设置为0)

简单科普:
far类型是用来定义变量/常量的,
不同于xdata这种自带偏移的地址(char xdata a _at_ 0;的实际地址是01:0000H)
far类型类似一种绝对地址,是从00:0000H开始的,覆盖edata和xdata部分
以下为keil 帮助文档的解释:
far
远存类型可用于变量和常量。此内存使用24位地址访问,可以是片上或外部的。
对于变量,远存内存限制为16M。对象限制为64K,并且不能跨越64K边界(链接器确保这一点)。声明为far的变量位于HDATA内存类别中。
对于常量(ROM变量),远区内存被限制为16M。对象被限制为64K,且不能跨越64K的边界(链接器会确保这一点)。声明为远区的常量变量位于HCONST组中。
以下是程序内容:
- #include "STC32G.H"
- #include "stdio.h"
- #include "stdarg.h"
- #include "string.h"
- //本例程使用CHIPID内预置参数,设置HIRC为24MHz
- //使用HPLL1,提供60Mhz,80Mhz,120Mhz的设置例程
- #define Fosc_60Mhz 0 //系统时钟为60Mhz
- #define Fosc_80Mhz 1 //系统时钟为80Mhz
- #define Fosc_120Mhz 2//系统时钟为120Mhz
- #define Main_Fosc Fosc_120Mhz //设置系统时钟为120Mhz
- void CLK_Init(void); //设置系统时钟,由Main_Fosc定义设置
- void Timer0_Init(void); //定时器0初始化函数
- void Io_Init(void); //I/O口初始化函数,设置P32为开漏+打开内部上拉电阻模式
- void Uart1_Init(void); //串口初始化函数,115200bps
- void uart_send(int num);
- bit P32_OUT = 1; //用于确定输出电平
- char uart_buff[64] = {0};
- void Delay100ms(void) //@120MHz
- {
- unsigned long edata i;
- _nop_();
- _nop_();
- i = 2999998UL;
- while (i) i--;
- }
- char far ROM_EXE[4096] _at_ 0x800000; //指定ROM_EXE地址
- char ecode CODE_RAM_FLASH[4096] _at_ 0xfc2800; //指定CODE_RAM_FLASH地址
- char volatile far RAM_EXE[4096] _at_ 0x030000; //指定RAM_EXE地址
- //用于运行RAM区域的函数
- void exeram_code()
- {
- while (1)
- {
- P6--;
- Delay100ms();
- }
- }
- void main(void)
- {
- int i;
- EAXFR = 1; //使能访问扩展RAM区特殊功能寄存器(XFR)
- CKCON &= ~0x07; //清空[2:0],设置外部数据总线等待时钟为0(最快),默认为7
- CLK_Init(); //设置HPLL时钟为指定频率
- Timer0_Init(); //初始化定时器0,50毫秒@120MHz
- Uart1_Init(); //串口初始化函数,115200bps
- Io_Init(); //初始化I/O口,设置P32等效为原准双向口模式(开漏模式+打开内部上拉电阻)
- EA = 1; //打开总中断
- CKCON &= ~0x80;
- for (i = 0; i < 4096; i++)RAM_EXE[i] = ((char far *)&exeram_code)[i]; //同一程序下的RAM程序运行
- //for (i = 0; i < 4096; i++)RAM_EXE[i] = ((char far *)&CODE_RAM_FLASH)[i]; //独立程序下的RAM程序运行
- while(1)
- {
- //用户程序
- for (i = 0; i < 20; i++)uart_send(sprintf(uart_buff, "%02bx ", RAM_EXE[i]));//串口打印RAM内容的前20位
- uart_send(sprintf(uart_buff, "\n"));
- if(P33==0){
- CKCON |= 0x80; //切换到ROM运行方式
- _nop_(); //add 2 nops here at least
- _nop_();
- _nop_();
- _nop_();
- //((void (far *)())&ROM_EXE)();//通过数组跳转
- ((void (far *)())0x800000)();//通过绝对地址跳转,等效位为SP->0x800000
- //((void (far *)())0x80000f)();//独立程序通过绝对地址跳转,需要通过仿真查看独立程序的起始地址
- }//跳转到RAM地址
- Delay100ms();
- }
- }
- bit uart_flag = 0;
- void send_dat(char c)
- {
- uart_flag = 1;
- SBUF = c;
- while(uart_flag);
- }
- int data dat_len = 0;
- void uart_send(int num)
- {
- for(dat_len = 0; dat_len<num; dat_len++)
- {
- send_dat(uart_buff[dat_len]);
- }
- }
- void Uart1_Isr(void) interrupt 4
- {
- if (TI) //检测串口1发送中断
- {
- TI = 0; //清除串口1发送中断请求位
- uart_flag = 0;
- }
- if (RI) //检测串口1接收中断
- {
- RI = 0; //清除串口1接收中断请求位
- }
- }
- void Uart1_Init(void) //115200bps@120MHz
- {
- SCON = 0x50; //8位数据,可变波特率
- AUXR |= 0x40; //定时器时钟1T模式
- AUXR &= 0xFE; //串口1选择定时器1为波特率发生器
- TMOD &= 0x0F; //设置定时器模式
- TL1 = 0xFC; //设置定时初始值
- TH1 = 0xFE; //设置定时初始值
- ET1 = 0; //禁止定时器中断
- TR1 = 1; //定时器1开始计时
- ES = 1; //使能串口1中断
- }
- char data off_t0_cnt = 0;
- void Timer0_Isr(void) interrupt 1
- {
- if(P32_OUT == 1&&P32 == 0&&off_t0_cnt<100)off_t0_cnt++; //判断外部P32按键按下一定时间时,关闭定时器0
- if(off_t0_cnt>5){TR0 = 0;}//注:仅在P32输出为1的时候,外部的按键按下才能被读到
- P32_OUT = ~P32_OUT;//每隔10ms亮/灭切换一次
- P32 = P32_OUT; //将输出电平给P32管脚
- }
- void Timer0_Init(void) //50毫秒@120MHz
- {
- TM0PS = 0x5B; //设置定时器时钟预分频 ( 注意:并非所有系列都有此寄存器,详情请查看数据手册 )
- AUXR |= 0x80; //定时器时钟1T模式
- TMOD &= 0xF0; //设置定时器模式
- TL0 = 0x3F; //设置定时初始值
- TH0 = 0x01; //设置定时初始值
- TF0 = 0; //清除TF0标志
- TR0 = 1; //定时器0开始计时
- ET0 = 1; //使能定时器0中断
- T0CLKO = 1; //使能P35输出定时器溢出时钟
- }
- void Io_Init(void)
- {
- P3M0 = 0x26; P3M1 = 0xdd;
- P3PU = 0x0d;
- P3SR = 0xdc;
- P3DR = 0xdc;
- P6M0 = 0xff; P6M1 = 0x00; //推挽输出,P6,PWM
- P6SR = 0x00; //转换速度和驱动电流最大,P6
- P6DR = 0x00;
- }
- void Delay10ms(void) //@120MHz
- {
- unsigned long edata i;
- _nop_();
- _nop_();
- i = 299998UL;
- while (i) i--;
- }
- void CLK_Init(void)
- {
- #if Main_Fosc == Fosc_120Mhz
- WTST = 4;CLKDIV = 2; //设置系统时钟=480MHz/2/2=120MHz,(因为CLKSEL选择时,已经将HPLL/2了)
- #elif Main_Fosc == Fosc_80Mhz
- WTST = 3;CLKDIV = 3; //设置系统时钟=480MHz/2/3=80MHz
- #elif Main_Fosc == Fosc_60Mhz
- WTST = 2;CLKDIV = 4; //设置系统时钟=480MHz/2/4=60MHz
- #endif
- //以下为超过60MHz时,系统时钟使用HPLL方式提供
- VRTRIM = CHIPID22; //载入27MHz频段的VRTRIM值
- IRTRIM = CHIPID12; //指定当前HIRC为24MHz,此时会覆盖掉ISP设置的时钟频率
- IRCBAND &= ~0x03; //清空频段选择
- IRCBAND |= 0x01; //选择27Mhz频段
- HPLLCR &= ~0x10; //选择HPLL输入时钟源为HIRC
- HPLLPDIV = 4; //24MHz/4=6MHz,需要保证输入HPLL的时钟在6MHz附近
- HPLLCR |= 0x0e; //HPLL=6MHz*80=480MHz
- HPLLCR |= 0x80; //使能HPLL
- Delay10ms();
- CLKSEL &= ~0x03; //BASE_CLK选择为HIRC,用以提供给HPLL
- CLKSEL &= ~0x0c; //清空主时钟源选择
- CLKSEL |= 1<<2; //设置主时钟源为内部 HPLL1 输出/2
- }

被折叠的 条评论
为什么被折叠?



