找回密码
 立即注册
查看: 298|回复: 4

RAM中运行程序@120MHz, STC32G144K246 | 从QSPI直接运行程序开发中

[复制链接]
  • 打卡等级:以坛为家III
  • 打卡总天数:604
  • 最近打卡:2025-10-30 10:25:05
已绑定手机
已实名认证

121

主题

2941

回帖

7621

积分

版主

积分
7621
发表于 3 天前 | 显示全部楼层 |阅读模式
STC32G144K246 用户手册
https://www.stcaimcu.com/data/do ... t/STC32G144K246.pdf

51, RAM运行程序@120MHz, STC32G144K246 | 从QSPI直接运行程序开发中
本例程使用内部24MHz高速时钟,经过HPLL1倍频到120MHz后,给系统时钟使用

另有实际测试:同样的一段计算函数(1000次整数乘法和加法),计算时关闭总中断

主程序120MHz消耗了200us,
而在RAM内运行消耗了124us(此时变量被设置到了edata区域,相比主程序的xdata区域存取快),速度提高了38%
补充:这个差距经查,并非是由RAM和FLASH运行速度差距导致的,而是由于内存模式的设置导致的(WTST在FLASH运行和RAM都可设置)
这里得到一个结论:如果需要使用循环大量计算时,且对时间较为敏感的实时性需求,建议定义变量到edata区域

程序拥有两种方式来载入RAM程序,一种是同一个工程中,通过对函数取地址来获取程序内容。这种方法在调用其他函数的时候,本质还是会跳转回到FLASH部分运行
第二种是使用独立的工程来编译RAM中需要运行的程序,这部分通过设置程序整体偏移地址为0x800000实现,同时通过hex2bin.exe实现自动转为bin文件,这个编译后的bin文件通过“打开EEPROM文件”载入即可使用


使用同一工程的方式,效果是P32和P35闪烁,长按P32按键后,P32、P35停止闪烁。这部分程序是主程序,非RAM内运行
如果按下P33按键,则跳转到RAM内运行程序,效果是P6上持续自减数值,也就是二进制不断刷新显示。
截图202510281745281150.jpg



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

需要注意的是,使用不同工程的情况下,需要手动设定ECALL跳转地址
具体操作为打开独立的RAM_EXE工程:
选择模拟仿真后点击仿真按钮
截图202510281742147303.jpg
然后查看main函数内第一行的语句所在地址:这里是0x80:000f,将这个地址回填到主程序内的绝对地址跳转调用上即可
截图202510281743404286.jpg
截图202510281744457883.jpg

注意事项:
在CKCON的最高位bit7中RAMEXE中
截图202510281749283613.jpg
如果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)

截图202510281748483231.jpg
简单科普:
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组中。

以下是程序下载链接:
STC32G144K246-RAM运行程序测试.zip (182.38 KB, 下载次数: 8)
以下是程序内容:
  1. #include "STC32G.H"
  2. #include "stdio.h"
  3. #include "stdarg.h"
  4. #include "string.h"
  5. //本例程使用CHIPID内预置参数,设置HIRC为24MHz
  6. //使用HPLL1,提供60Mhz,80Mhz,120Mhz的设置例程
  7. #define Fosc_60Mhz 0 //系统时钟为60Mhz
  8. #define Fosc_80Mhz 1 //系统时钟为80Mhz
  9. #define Fosc_120Mhz 2//系统时钟为120Mhz
  10. #define Main_Fosc Fosc_120Mhz //设置系统时钟为120Mhz
  11. void CLK_Init(void);                                //设置系统时钟,由Main_Fosc定义设置
  12. void Timer0_Init(void);                        //定时器0初始化函数
  13. void Io_Init(void);                                        //I/O口初始化函数,设置P32为开漏+打开内部上拉电阻模式
  14. void Uart1_Init(void);                        //串口初始化函数,115200bps
  15. void uart_send(int num);
  16. bit P32_OUT = 1;                                                //用于确定输出电平
  17. char uart_buff[64] = {0};
  18. void Delay100ms(void)        //@120MHz
  19. {
  20.         unsigned long edata i;
  21.         _nop_();
  22.         _nop_();
  23.         i = 2999998UL;
  24.         while (i) i--;
  25. }
  26. char far ROM_EXE[4096] _at_ 0x800000;                                                                                //指定ROM_EXE地址
  27. char ecode CODE_RAM_FLASH[4096] _at_ 0xfc2800;                                        //指定CODE_RAM_FLASH地址
  28. char volatile far RAM_EXE[4096] _at_ 0x030000;                                        //指定RAM_EXE地址
  29. //用于运行RAM区域的函数
  30. void exeram_code()
  31. {
  32.     while (1)
  33.     {
  34.         P6--;
  35.         Delay100ms();
  36.     }
  37. }
  38. void main(void)
  39. {
  40.         int i;
  41.         EAXFR = 1;                                        //使能访问扩展RAM区特殊功能寄存器(XFR)
  42.         CKCON &= ~0x07;                        //清空[2:0],设置外部数据总线等待时钟为0(最快),默认为7
  43.         CLK_Init();                                        //设置HPLL时钟为指定频率
  44.         Timer0_Init();                        //初始化定时器0,50毫秒@120MHz
  45.         Uart1_Init();                                //串口初始化函数,115200bps
  46.         Io_Init();                                        //初始化I/O口,设置P32等效为原准双向口模式(开漏模式+打开内部上拉电阻)
  47.         EA = 1;                                                        //打开总中断
  48.         CKCON &= ~0x80;
  49.         for (i = 0; i < 4096; i++)RAM_EXE[i] = ((char far *)&exeram_code)[i]; //同一程序下的RAM程序运行
  50.         //for (i = 0; i < 4096; i++)RAM_EXE[i] = ((char far *)&CODE_RAM_FLASH)[i]; //独立程序下的RAM程序运行
  51.         while(1)
  52.         {
  53.                 //用户程序
  54.                 for (i = 0; i < 20; i++)uart_send(sprintf(uart_buff, "%02bx ", RAM_EXE[i]));//串口打印RAM内容的前20位
  55.                 uart_send(sprintf(uart_buff, "\n"));
  56.                 if(P33==0){
  57.                         CKCON |= 0x80;                                        //切换到ROM运行方式
  58.                         _nop_();                //add 2 nops here at least
  59.                         _nop_();
  60.                         _nop_();
  61.                         _nop_();
  62.                         //((void (far *)())&ROM_EXE)();//通过数组跳转
  63.                         ((void (far *)())0x800000)();//通过绝对地址跳转,等效位为SP->0x800000
  64.                         //((void (far *)())0x80000f)();//独立程序通过绝对地址跳转,需要通过仿真查看独立程序的起始地址
  65.                 }//跳转到RAM地址
  66.                 Delay100ms();
  67.         }
  68. }
  69. bit uart_flag = 0;
  70. void send_dat(char c)
  71. {
  72.         uart_flag = 1;
  73.         SBUF = c;
  74.         while(uart_flag);
  75. }
  76. int data dat_len = 0;
  77. void uart_send(int num)
  78. {
  79.         for(dat_len = 0; dat_len<num; dat_len++)
  80.         {
  81.                 send_dat(uart_buff[dat_len]);
  82.         }
  83. }
  84. void Uart1_Isr(void) interrupt 4
  85. {
  86.         if (TI)                                //检测串口1发送中断
  87.         {
  88.                 TI = 0;                        //清除串口1发送中断请求位
  89.                 uart_flag = 0;
  90.         }
  91.         if (RI)                                //检测串口1接收中断
  92.         {
  93.                 RI = 0;                        //清除串口1接收中断请求位
  94.         }
  95. }
  96. void Uart1_Init(void)        //115200bps@120MHz
  97. {
  98.         SCON = 0x50;                //8位数据,可变波特率
  99.         AUXR |= 0x40;                //定时器时钟1T模式
  100.         AUXR &= 0xFE;                //串口1选择定时器1为波特率发生器
  101.         TMOD &= 0x0F;                //设置定时器模式
  102.         TL1 = 0xFC;                        //设置定时初始值
  103.         TH1 = 0xFE;                        //设置定时初始值
  104.         ET1 = 0;                        //禁止定时器中断
  105.         TR1 = 1;                        //定时器1开始计时
  106.         ES = 1;                                //使能串口1中断
  107. }
  108. char data off_t0_cnt = 0;
  109. void Timer0_Isr(void) interrupt 1
  110. {
  111.         if(P32_OUT == 1&&P32 == 0&&off_t0_cnt<100)off_t0_cnt++;        //判断外部P32按键按下一定时间时,关闭定时器0
  112.         if(off_t0_cnt>5){TR0 = 0;}//注:仅在P32输出为1的时候,外部的按键按下才能被读到
  113.         P32_OUT = ~P32_OUT;//每隔10ms亮/灭切换一次
  114.         P32 = P32_OUT;                //将输出电平给P32管脚
  115. }
  116. void Timer0_Init(void)                //50毫秒@120MHz
  117. {
  118.         TM0PS = 0x5B;                        //设置定时器时钟预分频 ( 注意:并非所有系列都有此寄存器,详情请查看数据手册 )
  119.         AUXR |= 0x80;                        //定时器时钟1T模式
  120.         TMOD &= 0xF0;                        //设置定时器模式
  121.         TL0 = 0x3F;                                //设置定时初始值
  122.         TH0 = 0x01;                                //设置定时初始值
  123.         TF0 = 0;                                //清除TF0标志
  124.         TR0 = 1;                                //定时器0开始计时
  125.         ET0 = 1;                                //使能定时器0中断
  126.         T0CLKO = 1;                        //使能P35输出定时器溢出时钟
  127. }
  128. void Io_Init(void)
  129. {
  130.         P3M0 = 0x26; P3M1 = 0xdd;
  131.         P3PU = 0x0d;
  132.         P3SR = 0xdc;
  133.         P3DR = 0xdc;
  134.         P6M0 = 0xff; P6M1 = 0x00; //推挽输出,P6,PWM
  135.         P6SR = 0x00; //转换速度和驱动电流最大,P6
  136.         P6DR = 0x00;
  137. }
  138. void Delay10ms(void)        //@120MHz
  139. {
  140.         unsigned long edata i;
  141.         _nop_();
  142.         _nop_();
  143.         i = 299998UL;
  144.         while (i) i--;
  145. }
  146. void CLK_Init(void)
  147. {
  148.         #if Main_Fosc == Fosc_120Mhz
  149.         WTST = 4;CLKDIV = 2;                 //设置系统时钟=480MHz/2/2=120MHz,(因为CLKSEL选择时,已经将HPLL/2了)
  150.         #elif Main_Fosc == Fosc_80Mhz
  151.         WTST = 3;CLKDIV = 3;         //设置系统时钟=480MHz/2/3=80MHz
  152.         #elif Main_Fosc == Fosc_60Mhz
  153.         WTST = 2;CLKDIV = 4;         //设置系统时钟=480MHz/2/4=60MHz
  154.         #endif
  155.         //以下为超过60MHz时,系统时钟使用HPLL方式提供
  156.         VRTRIM = CHIPID22;                //载入27MHz频段的VRTRIM值
  157.         IRTRIM = CHIPID12;                //指定当前HIRC为24MHz,此时会覆盖掉ISP设置的时钟频率
  158.         IRCBAND &= ~0x03;                        //清空频段选择
  159.         IRCBAND |= 0x01;                        //选择27Mhz频段
  160.         HPLLCR &= ~0x10;            //选择HPLL输入时钟源为HIRC
  161.         HPLLPDIV = 4;                                        //24MHz/4=6MHz,需要保证输入HPLL的时钟在6MHz附近
  162.         HPLLCR |= 0x0e;             //HPLL=6MHz*80=480MHz
  163.         HPLLCR |= 0x80;             //使能HPLL
  164.         Delay10ms();
  165.         CLKSEL &= ~0x03;                        //BASE_CLK选择为HIRC,用以提供给HPLL
  166.         CLKSEL &= ~0x0c;                        //清空主时钟源选择
  167.         CLKSEL |= 1<<2;                                //设置主时钟源为内部 HPLL1 输出/2
  168. }
复制代码







回复

使用道具 举报 送花

  • 打卡等级:以坛为家I
  • 打卡总天数:223
  • 最近打卡:2025-10-30 18:32:37

792

主题

1万

回帖

2万

积分

管理员

积分
20149
发表于 3 天前 | 显示全部楼层
截图202510292110373115.jpg
https://www.stcaimcu.com/data/do ... t/STC32G144K246.pdf

运行程序的
i-Cache 512字节

内部 程序存储器 128位宽度;
特别设计的能运行程序的 SRAM 是 4K 字节;

4K 字节能运行程序SRAM 的地址
code, 80:0000H,从这个地址可以运行程序,但不能改写;

但要写入这段地址的 RAM,
xdata, 03:0000H
,从这个地址不可以运行程序,但可以改写;
这个特殊设计:应该是为了防止改写 80:0000H 这个地址开始的程序

截图202510292112081217.jpg








截图202510281920456986.jpg
回复

使用道具 举报 送花

  • 打卡等级:偶尔看看I
  • 打卡总天数:18
  • 最近打卡:2025-10-30 11:24:26

2

主题

210

回帖

1240

积分

金牌会员

积分
1240
发表于 前天 10:57 | 显示全部楼层
1000次整数乘法和加法的测试程序也发上来吧

点评

好的,以上是计算速度测试程序,可以通过示波器测量P32引脚上的高电平宽度看到 默认是主程序,按下P33时切换到RAM内运行,1000次计算过程中关闭EA总中断,防止中断影响时间统计 我这的数据是主程序内运行200us,RA  详情 回复 发表于 前天 11:54
回复

使用道具 举报 送花

  • 打卡等级:以坛为家III
  • 打卡总天数:604
  • 最近打卡:2025-10-30 10:25:05
已绑定手机
已实名认证

121

主题

2941

回帖

7621

积分

版主

积分
7621
发表于 前天 11:54 | 显示全部楼层
zxcv*** 发表于 2025-10-29 10:57
1000次整数乘法和加法的测试程序也发上来吧

STC32G144K246 计算1000次,运行速度差距.zip (184.59 KB, 下载次数: 3)

好的,以上是计算速度测试程序,可以通过示波器测量P32引脚上的高电平宽度看到
默认是主程序,按下P33时切换到RAM内运行,1000次计算过程中关闭EA总中断,防止中断影响时间统计
我这的数据是主程序内运行200us,RAM内运行124us

补充:这个差距经查,并非是由RAM和FLASH运行速度差距导致的,而是由于内存模式的设置导致的(WTST在FLASH运行和RAM都可设置)
这里得到一个结论:如果需要使用循环大量计算时,且对时间较为敏感的实时性需求,建议定义变量到edata区域
回复

使用道具 举报 送花

  • 打卡等级:偶尔看看I
  • 打卡总天数:18
  • 最近打卡:2025-10-30 11:24:26

2

主题

210

回帖

1240

积分

金牌会员

积分
1240
发表于 昨天 11:24 | 显示全部楼层
王*** 发表于 2025-10-29 11:54
好的,以上是计算速度测试程序,可以通过示波器测量P32引脚上的高电平宽度看到
默认是主程序,按下P33时 ...

STC32G144K246指令缓存效率还可以,1000次计算循环在120MHz闪存运行下能做到零等待,200us循环1000次相当于1个循环要24个时钟,正好等于零等待下的指令时钟数
回复

使用道具 举报 送花

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|深圳国芯人工智能有限公司 ( 粤ICP备2022108929号-2 )

GMT+8, 2025-10-31 03:23 , Processed in 0.133162 second(s), 76 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表