S32K1xx 一部分代码从外部输入例程

关键字 :NXPRAMS32K1

1. 目的

         本文主要探究如果一个工程中有一部分代码功能可知,但实现方式不知道 ,需要通过通信接口 (如 CAN 、UART )接收代码才能运行这种情况在 S32K1xx 中该如何实现 。这里以闪灯为例 ,使用的开发环境是 S32DS for ARM V2.2 版本。

 

2. 需要的功能

         时钟 、GPIO 、通信接口(本文直接将一个数组当做外部输入的数据 ,所以在此不用通信接口 ,感兴趣的可以直接尝试添加) ,Flash 模块 。

 

3. Flash 详解

         S32K116 共有 PFlash 128KB ,为一个分区 ,有 FlexNVM 共 32KB ,为另一个分区 ,都可以存放代码 ,扇区大小都为 2KB ,擦除最小单位为一个扇区即 2KB ,每次要写入需要 64位 (8 字节) 对齐 ,S32K1xx 其余系列情况可具体看参考手册 。注意 :同一个分区的 Flash不能边读边写 ,不同分区可以在一个分区读取 Flash 数据 ,另一个分区写入数据 。

         另 :PFlahs 首地址 0x00000000 , FlexNVM 首地址 0x10000000 。

 

4. 将外部代码放在 RAM 中执行

首先初始化时钟 ,GPIO 。注意 :这里使用数组作为外部输入数据缓存区 ,我这里就不实现外部输入了 ,直接在数组里面写入需要的内容 。

4.1 查看相关文件

         打开 Project_Settings->Linker_Files ,选择工程选中的 .ld 文件 (可以右击工程 ,选中 Properties->C/C++ Build->Settings->Standard S32DS C Linker->General 中查看当前使用的哪个 .ld 文件) ,可以知道在这里默认已经有了 .code_ram 段 (在 201 行) 。

         再打开 Project_Settings->Startup_Code->startup.c ,可以看到这里开始初始化bss 、data 等段 ,包括 .code_ram 段 ,以及将 ROM 代码复制到 RAM 中 ,所以在使用过程中可以把某一函数定义在 .code_ram 段中 ,如 void __attribute__((section (".code_ram"))) led_on(void),然后在 init_data_bss 中会将 led_on 代码复制到 RAM ,每次调用总会解释成 RAM 中的代码 。

         除此之外 ,还需要查看 .map 文件 ,这个文件在 Debug_FLASH 中 ,这是因为我们需要查看 RAM 中代码的位置 。这是需要编译之后才有的文件 。

4.2 代码解析

使用了 GPIO 口 PTE8 、PTD15 、PTD16 ,对应三个灯 。

#include "S32K116.h"
#include "startup.h"

#define RAM_LED_OFF_CODE_NUMBER 36 // 关灯代码字节数
#define RAM_LED_ON_CODE_NUMBER 36 // 开灯代码字节数
#define RAM_LED_OFF_CODE_OFFSET (0x00) // 关灯函数首地址相对 .code_ram 偏移量
#define RAM_LED_ON_CODE_OFFSET (0x50) // 开灯函数首地址相对 .code_ram 偏移量
// 灯暗的代码 ,存在数组中
uint8_t ram_led_off_code[RAM_LED_OFF_CODE_NUMBER]={
0x80,0xB5,0x00,0xAF, // 压栈LR并保存 SP

0x05,0x4B,0xC0,0x22,
0x52,0x02,0x9A,0x60,
0x04,0x4B,0x80,0x22,
0x52,0x00,0x9A,0x60,

0xC0,0x46, // NOP
0xBD,0x46,0x80,0xBD, // 出栈并还原SP
0xC0,0x46, // NOP

0xC0,0xF0,0x0F,0x40, // 本函数使用寄存器地址
0x00,0xF1,0x0F,0x40
};
// 灯亮的代码 ,存在数组中
uint8_t ram_led_on_code[RAM_LED_ON_CODE_NUMBER]={
0x80,0xB5,0x00,0xAF, // 压栈LR并保存 SP

0x05,0x4B,0xC0,0x22,
0x52,0x02,0x5A,0x60,
0x04,0x4B,0x80,0x22,
0x52,0x00,0x5A,0x60,

0xC0,0x46, // NOP
0xBD,0x46,0x80,0xBD, // 出栈并还原SP
0xC0,0x46, // NOP
0xC0,0xF0,0x0F,0x40, // 本函数使用寄存器地址
0x00,0xF1,0x0F,0x40
};
// 灯初始化
void led_init(void){
PCC-> PCCn[PCC_PORTD_INDEX] = PCC_PCCn_CGC_MASK; // 打开 PTD 时钟
PCC-> PCCn[PCC_PORTE_INDEX] = PCC_PCCn_CGC_MASK; // 打开 PTE 时钟
PTD->PDDR |= 1<<15; // PTD15 设置成输出 ,绿灯
PTD->PDDR |= 1<<16; // PTD16 设置成输出 ,红灯
PTE->PDDR |= 1<<8; // PTE8 设置成输出 ,蓝灯

PORTD->PCR[15] = 0x00000100; // 作为 GPIO
PORTD->PCR[16] = 0x00000100; // 作为 GPIO
PORTE->PCR[8] = 0x00000100; // 作为 GPIO
PTD->PSOR = (1<<15) | (1<<16); // 灯亮
PTE->PSOR = (1<<8) ;
}
// 灯暗的函数 ,将其复制在 RAM 中 ,并在 RAM 中运行 ,使用 NOP() 是因为我们等会需要将缓存数组里的代码放在此函数在 RAM 中的地址 ,NOP()起到占位作用 , 此时调用这个的函数就相当于在 RAM 中运行灯暗的代码 ,所以 NOP() 占的空间大小要大于等于代码容量 。
void __attribute__((section (".code_ram"))) led_off(void){
//PTD->PCOR = (1<<15) | (1<<16);
//PTE->PCOR = (1<<8) ;
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
}
// 灯亮的函数 ,将其复制在 RAM 中 ,并在 RAM 中运行 ,使用 NOP() 是因为我们等会需要将缓存数组里的代码放在此函数在 RAM 中的地址 ,NOP()起到占位作用 , 此时调用这个的函数就相当于在 RAM 中运行灯亮的代码 ,所以 NOP() 占的空间大小要大于等于代码容量 。
void __attribute__((section (".code_ram"))) led_on(void){
//PTD->PSOR = (1<<15) | (1<<16);
//PTE->PSOR = (1<<8) ;
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
}

int main(void) {
// 获取 .code_ram 段首地址
extern uint32_t __CODE_RAM[];
uint8_t * code_ram=(uint8_t *)__CODE_RAM;
// 由于 .code_ram 段存放了两个函数代码 ,我们需要通过 .map 文件分别知道其代码首地址相对于 .code_ram 段首地址的偏移量 ,然后才可以知道代码对应的首地址 。
uint8_t * code_led_off_ram=(uint8_t *)(code_ram+RAM_LED_OFF_CODE_NUMBER);
uint8_t * code_led_on_ram=(uint8_t *)(code_ram+ RAM_LED_ON_CODE_NUMBER);
uint32_t i;
// 初始化灯
led_init();
// 用外部传进来的代码代替 RAM 中的函数的代码
for(i=0;i<RAM_LED_OFF_CODE_NUMBER;i++){
*code_led_off_ram=ram_led_off_code[i];
code_led_off_ram++;
}
for(i=0;i<RAM_LED_ON_CODE_NUMBER;i++){
*code_led_on_ram=ram_led_on_code[i];
code_led_on_ram++;
}
// 灯闪烁
for(;;){
led_off();
for(i=0;i<999999;i++);
led_on();
for(i=0;i<999999;i++);
}
return 0;
}

 

 4.3 总结如下 :

注意 :

  • 这里面可以看到数组最后存放的是寄存器地址 ,所以需要注意取此地址的语句要寻址到正确的地址 。经过观察发现 ,存放寄存器地址的数据首地址最好与 4 字节对齐 。
  • NOP() 占位的空间大小要大于等于要写入的代码大小 。
  • 如果需要改为在 ROM 运行 ,需要用整数倍个扇区来存放代码 ,以便擦除和写入 。
  • 若有更好的方法 ,期望留言告知 。

3. 参考资料

(1) NXP :《SW12218SW》

         下载链接 :

https://www.nxp.com/docs/en/application-note-software/AN12218SW.zip

 

★博文内容均由个人提供,与平台无关,如有违法或侵权,请与网站管理员联系。

★文明上网,请理性发言。内容一周内被举报5次,发文人进小黑屋喔~

评论