[牛人杂谈] 不同单片机架构的数据常量定义的时候有哪些不同,哪些关键字使用了会导致不可移植

[复制链接]
 楼主| huangcunxiake 发表于 2025-7-17 08:52 | 显示全部楼层 |阅读模式
在嵌入式开发中,不同单片机架构(如8051、PIC、AVR、MSP430)及其编译器对常量数据(特别是需要存储在只读存储器/Flash中的常量)的处理方式存在显著差异,这直接导致了代码移植性问题。以下是主要区别和需要特别注意的不可移植关键字/特性:

 楼主| huangcunxiake 发表于 2025-7-17 08:53 | 显示全部楼层
各平台具体实现与不可移植关键字
8051 (通常使用Keil C51 / SDCC)

标准const: 在Keil C51中,默认情况下 const 对象会被放在RAM中(具体是哪个RAM区由内存模型决定)。启动代码会将其初始化。这不是通常想要的常量存储位置(浪费RAM)。

Flash存储关键字: code

这是Keil C51的核心不可移植关键字。

用法:code 数据类型 变量名 = 初值;

示例:code unsigned char LookupTable[] = {0x00, 0x01, 0x02};

访问:编译器自动生成 MOVC A, @A+DPTR 或 MOVC A, @A+PC 指令来访问。

其他内存修饰符: xdata, idata, pdata 等用于指定RAM区域,与const或code结合时语义更复杂,也是不可移植的。

SDCC: 更接近标准C。const 默认尝试放入Flash (CODE区),也可以用 __code (等效于Keil的 code) 显式指定。访问方式类似。

PIC (通常使用Microchip XC8 / XC16 / XC32)

标准const: 在XC编译器中,const 的行为取决于具体型号和编译器优化设置。对于PIC18及更新的PIC,优化级别较高时,编译器可能会将 const 放入Flash。但不保证。较低优化级别或特定型号下,const 可能仍放在RAM中。

Flash存储关键字/修饰符:

__prog__ (XC8 for PIC10/12/16): 这是早期PIC的显式Flash修饰符。不可移植。

示例:__prog__ const unsigned char table[] = {...};

__flash / __memx (XC8 for PIC18, 部分支持): 较新的方式。

const __flash / __flash const (XC16, XC32): 这是Microchip 16位和32位PIC编译器(XC16, XC32)推荐的、显式指定Flash存储的标准方式。虽然用了 const,但 __flash 是关键修饰符。不可移植。

示例:const __flash uint16_t CalibrationValue = 0x1234;

rom (某些历史编译器/版本): 已过时,但可能在旧代码中看到。

访问: 编译器根据 __flash 修饰符自动生成访问程序存储空间的代码(可能需要库函数调用或特定指令)。

AVR (通常使用GCC AVR, 如AVR-GCC, Atmel Studio, MPLAB X)

标准const: 在AVR-GCC中,仅使用 const 默认会将常量放在RAM中。启动代码从Flash复制初始化值到RAM。这不是最优的(浪费RAM)。

Flash存储关键字/修饰符: PROGMEM

这是AVR-Libc提供的核心不可移植宏/属性。

需要包含 <avr/pgmspace.h>。

用法:数据类型 变量名[] PROGMEM = { ... };

示例:const uint8_t FontData[] PROGMEM = { ... }; (注意 const 和 PROGMEM 常一起用,但 PROGMEM 是关键)

底层是GCC的 __attribute__((progmem))。

访问: 不能直接用 变量名[i] 访问! 必须使用 pgm_read_byte(&变量名[i]), pgm_read_word(...) 等专门的宏/函数来读取。这是访问方式上最大的移植性障碍。

F() 宏 (用于字符串): Serial.print(F("Hello World")); 将字符串字面量放入Flash,避免消耗RAM。这也是AVR特有的。

MSP430 (通常使用TI CCS/IAR/MSP430-GCC)

标准const: 在MSP430-GCC和现代TI编译器(如CCS使用的Clang/LLVM后端)中,const 通常默认被放入Flash(.text段)中,因为MSP430是冯·诺依曼架构,访问方式统一。这是最接近标准C期望行为的。

显式Flash存储 (通常不需要但存在):

MSP430-GCC: 类似于标准GCC,const 通常足够。也可以用 __attribute__((section(".rodata"))) 显式控制段,但这不是必须的。

TI CCS (Clang/LLVM): 行为类似GCC,const 默认放Flash。

IAR for MSP430: 传统上可能需要 __flash 或 const __flash 来确保放入Flash。虽然IAR也趋向标准,但旧代码或特定需求可能用到。使用 __flash 在IAR中是不可移植的。

示例(IAR):__flash const uint16_t SecretKey = 0xDEAD;

访问: 由于统一地址空间,在代码中直接使用 变量名 访问即可,编译器生成正常的加载指令(如 MOV.W &SecretKey, R15),无需特殊函数。这是移植性最好的地方。

 楼主| huangcunxiake 发表于 2025-7-17 08:54 | 显示全部楼层
提高移植性的建议
抽象存储位置:
  1. #if defined(__C51__) || defined(SDCC) // 8051
  2.     #define FLASH_CONST code const
  3. #elif defined(__XC8__) || defined(__XC16__) || defined(__XC32__) // PIC
  4.     #define FLASH_CONST const __flash // 根据具体XC编译器调整
  5. #elif defined(__AVR__) // AVR
  6.     #include <avr/pgmspace.h>
  7.     #define FLASH_CONST const PROGMEM
  8. #elif defined(__MSP430__) // MSP430-GCC, TI CCS (通常不需要特殊修饰符)
  9.     #define FLASH_CONST const // 大多数情况下 const 就够了
  10.     // 对于IAR可能需要单独处理
  11. #else // 标准C环境 (如x86, ARM Cortex未指定嵌入式)
  12.     #define FLASH_CONST const
  13. #endif
  14. // 使用
  15. FLASH_CONST uint8_t MyArray[] = {1, 2, 3};
抽象访问方式:
  1. #if defined(__AVR__)
  2.     #include <avr/pgmspace.h>
  3.     #define READ_FLASH_BYTE(addr) pgm_read_byte(addr)
  4.     #define READ_FLASH_WORD(addr) pgm_read_word(addr)
  5. #elif defined(__C51__) || defined(SDCC) || defined(__XC8__) || defined(__XC16__) || defined(__XC32__) || defined(__IAR_SYSTEMS_ICC__)
  6.     // 8051, PIC, IAR MSP430:编译器通常能直接生成正确访问代码或提供透明访问
  7.     // 如果编译器能正确处理 FLASH_CONST 定义的类型,直接访问即可
  8.     #define READ_FLASH_BYTE(addr) (*(const uint8_t *)(addr))
  9.     #define READ_FLASH_WORD(addr) (*(const uint16_t *)(addr))
  10. #else // MSP430-GCC, TI CCS, 标准环境
  11.     // 统一编址或标准环境,直接访问
  12.     #define READ_FLASH_BYTE(addr) (*(const uint8_t *)(addr))
  13.     #define READ_FLASH_WORD(addr) (*(const uint16_t *)(addr))
  14. #endif
  15. // 使用
  16. uint8_t val = READ_FLASH_BYTE(&MyArray[index]);
字符串常量:

在AVR上坚持使用 F()。

在其他平台,可以定义一个空宏 #define F(str) (str) 或者更智能的宏来兼容。

或者直接使用 const char * 并依赖编译器的优化(在非AVR平台上可能没问题)。

优先使用标准 const: 在明确知道目标平台编译器将 const 放入Flash(如MSP430-GCC, CCS, 现代XC编译器高优化等级)且性能可接受时,优先只用 const 以简化代码。

文档和注释: 清晰注释常量预期的存储位置和访问方式。

编译器文档: 始终查阅目标平台和编译器的具体文档,了解 const 的确切行为和推荐的常量定义方式。

 楼主| huangcunxiake 发表于 2025-7-17 08:54 | 显示全部楼层
总而言之,code (8051 Keil), PROGMEM 及其访问宏 (AVR), __flash (PIC XC, IAR MSP430) 是最主要的、会导致代码不可移植的关键字/特性。 通过仔细的抽象(宏定义、访问函数)和条件编译,可以在一定程度上管理这种差异性,但底层访问机制的差异始终是嵌入式跨平台开发中需要面对的一个挑战。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

222

主题

3700

帖子

11

粉丝
快速回复 在线客服 返回列表 返回顶部