Arduino 中如何正确读取 PROGMEM 字符串数组?
在经典 AVR Arduino 上,存放在 PROGMEM 中的指针表需要用 pgm_read_ptr() 读取,不能直接通过 myCharArray[x] 取出指针。原来的声明只将指针数组放进了 Flash,指针指向的字符串并没有一起放进去。
要把字符串也存入 Flash,需要分别用 PROGMEM 声明每个字符串。输出时先读取指针,再用 strcpy_P() 将字符串复制到 RAM 缓冲区。
为什么固定下标能用,循环读取却失败?
原声明中的 myCharArray 是字符串指针数组:
const char *const myCharArray[] PROGMEM = {
"C/4/", "Y/1000/H/", "X/1000/H/", "D/-1000/-1000/L/", "Z/100/H/"
};
这里有两层数据:数组元素保存的指针,以及指针指向的字符。PROGMEM 只作用于声明的数组,不会把指针指向的字符串自动放进 Flash。AVR-LibC 的字符串数组说明中有对应示例。
在程序空间与数据空间分离的 AVR 上,直接读取 myCharArray[x] 会使用普通数据空间的访问方式,无法可靠地读出 Flash 中的指针。
固定下标看起来正常,可能是编译器在编译时直接替换了已知的指针值,绕过了实际读取指针表的过程。这只是对现象的推测;没有开发板型号、编译选项和反汇编结果,就不能确认具体的优化过程,也不能据此认为固定下标访问是正确写法。
只修正原声明的读取方式
如果暂时保留原来的数组声明,字符串仍按普通数据空间字符串处理,可以先这样读取指针:
for (uint8_t x = 1; x <= 3; ++x) {
const char *p =
static_cast<const char *>(pgm_read_ptr(&myCharArray[x]));
Serial.println(p);
}
&myCharArray[x] 计算数组元素的地址,pgm_read_ptr() 从该 Flash 地址读取指针值。原声明中的字符串没有单独标记为 PROGMEM,因此不要对 p 使用 strcpy_P()。
将指针表和字符串都放进 PROGMEM
将 myCommand.h 改为:
#ifndef COMMAND_H
#define COMMAND_H
#include <avr/pgmspace.h>
const char command0[] PROGMEM = "C/4/";
const char command1[] PROGMEM = "Y/1000/H/";
const char command2[] PROGMEM = "X/1000/H/";
const char command3[] PROGMEM = "D/-1000/-1000/L/";
const char command4[] PROGMEM = "Z/100/H/";
const char *const myCharArray[] PROGMEM = {
command0,
command1,
command2,
command3,
command4
};
#endif
主 sketch 改为:
#include "myCommand.h"
void setup() {
Serial.begin(9600);
// command3 is the longest string in the current table.
char buffer[sizeof(command3)];
for (uint8_t x = 1; x <= 3; ++x) {
const char *p =
static_cast<const char *>(pgm_read_ptr(&myCharArray[x]));
strcpy_P(buffer, p);
Serial.println(buffer);
}
}
void loop() {
}
这段代码先用 pgm_read_ptr() 从 Flash 中取出字符串地址,再用 strcpy_P() 把该地址处的字符串复制到 RAM。随后,Serial.println(buffer) 输出 RAM 中的字符串。这与 AVR-LibC 的 PROGMEM 字符串读取方法一致。
按当前数据和循环范围,预期依次输出:
Y/1000/H/
X/1000/H/
D/-1000/-1000/L/
需要注意的细节
- 原示例的
#include myCommand.h缺少引号,应写成#include "myCommand.h"。如果实际代码也是如此,会先出现预处理错误。 - 数组下标从
0开始,当前有效范围为0到4。循环1到3会跳过首尾两项。 sizeof(command3)包含末尾的\0,足以容纳当前最长字符串。以后新增更长的命令时,需要同步调整缓冲区;strcpy_P()不检查目标容量。- 上述解释以经典 AVR 的独立程序空间访问方式为前提。部分 AVR 和其他 Arduino 平台的内存映射不同,不能把这一读取限制直接推广到所有开发板。
备注:内容仅供参考。
