Found a solution, see my comment below!
我正在尝试使用PROGMEM读取放置在结构中并存储在Arduino Mega(ATmega 2560)的闪存中的数据 . 使用 pointers
访问结构对象 manufacturer_1
和 manufacturer_2
.
由于草图的大小;我决定创建一个(相对)小例子来说明问题 . 以下代码显示了我如何定义结构和数据 .
typedef struct
{
char info[20];
} manufacturer_def;
typedef struct
{
unsigned int totalManufacturers;
const manufacturer_def* manufacturer[2];
} data_def;
const manufacturer_def manufacturer_1 PROGMEM =
{
"Manufacturer 1"
};
const manufacturer_def manufacturer_2 PROGMEM =
{
"Manufacturer 2"
};
const data_def data PROGMEM =
{
2,
{
&manufacturer_1,
&manufacturer_2
}
};
void setup()
{
// Serial monitor setup
Serial.begin(115200); // Begin serial monitor
}
void loop()
{
mainMenu();
}
The problem!
我想用循环填充一个带字符串的数组 . 以下代码无法正常工作:
void mainMenu()
{
unsigned int i = 0;
unsigned int totalMenuItems = pgm_read_word(&data.totalManufacturers);
String menuItems[totalMenuItems];
char str_buf[20];
// Create array with items for menu
for (i = 0; i < totalMenuItems; i++)
{
strcpy_P(str_buf, data.manufacturer[i]->info);
menuItems[i] = str_buf;
Serial.println(menuItems[i]);
}
}
输出(部分):
p�
p�
奇怪的是,当我将 strcpy_P
命令放在循环外并手动指定迭代变量时,它可以工作:
void mainMenu()
{
unsigned int i = 0;
unsigned int totalMenuItems = pgm_read_word(&data.totalManufacturers);
String menuItems[totalMenuItems];
char str_buf[20];
strcpy_P(str_buf, data.manufacturer[0]->info);
menuItems[0] = str_buf;
strcpy_P(str_buf, data.manufacturer[1]->info);
menuItems[1] = str_buf;
// Create array with items for menu
for (i = 0; i < totalMenuItems; i++)
{
Serial.println(menuItems[i]);
}
}
输出:
Manufacturer 1
Manufacturer 2
为什么会这样?
4 回答
我认为它与PROGMEM有关,就是将变量存储在FLASH中,而不是RAM中 . 读this documentation on PROGMEM,所以当你不使用pgm_read_word_near()并动态访问FLASH存储变量时,就会出现问题 . 但是当你使用常量(字面值)时:
访问变量没关系 .
由于strcpy_P()的实现,问题可能会出现 .
所以在那个文档中他们这样做了:
你的行在这里:
strcpy_P(str_buf, data.manufacturer[i]->info);
问题是
data
不在ram中,毕竟你默认使用ram加载指令来读取数据作为strcpy_P
的参数 .由于芯片的harvard architecture,它需要使用特定指令从闪存中读取数据 .
首先,您指示编译器将您的字符串放在PROGMEM中,即闪存 . 如果不这样做,启动代码将在启动时将数据从闪存复制到DATA,以便您使用常规数据指针和指令进行访问 .
然后,当您想要从PROGMEM地址读取数据时,您必须再次使用
pgm_read...
告诉编译器您的给定地址在PROGMEM中 .您无法看到指针是程序,数据存储器或外设寄存器的值,而ARM架构则只有一个4GB的地址空间,其中闪存,RAM和外围位置可以通过它们在地址空间中的位置来区分 .
在AVR上:
MOV - Copy Register进行注册
LDS - Load Direct from data space进行注册
LPM - Load Program Memory进行注册
IN - Load an I/O Location to Register
而他们的对立面显而易见 .
与你刚刚拥有的ARM(宽度变体)相比:LDR and STR, Load and Store with immediate offset
这就是为什么使用PROGMEM很麻烦 . 欢迎来到嵌入式软件开发 .
感谢user14042和Jeroen3,我的兄弟和我找到了一个部分有效的解决方案;最多8个
manufacturer_def
结构 . 该解决方案首先基于使用pgm_read_ptr(&data.manufacturer[i])
创建指向正确manufacturer_def
结构的指针 . 其次,使用strcpy_P(str_buf, manufacturer_ptr->info)
从闪存中检索字符串 .代码:
当我使用8个
manufacturer_def
结构时,草图上传没有任何警告或错误:但是,当我使用超过8个
manufacturer_def
结构时,启动麻烦 . 使用9个manufacturer_def
结构,草图上传时没有警告或错误,但Arduino无法正确启动 . 使用10个manufacturer_def
结构,我收到以下错误:使用11
manufacturering_def
结构,我收到以下错误:我知道2个字节可以容纳最大值65535.但是可能导致这个值的是什么值?
Solution!
由于
data
结构的大小,某些地址超过了标准2字节指针可以容纳的最大值 . 因此,有必要使用_far
命令来寻址PROGMEM
的那一部分,从而解决4字节指针的问题 . 对于用户定义的PROGMEM
数据,这很容易解决 . 但是,通过在PROGMEM
中使用这么多用户定义的数据,一些内部Arduino功能也会被推到64K标记之外,并且那些不使用4字节指针 . 此问题源于默认链接器映射,它将这些函数放在用户定义的PROGMEM数据之后 . 这里的解决方案是将该数据移动到PROGMEM的另一部分 . 有关修复的更多信息和请求,请参阅以下主题:https://github.com/arduino/Arduino/issues/2226
https://github.com/arduino/Arduino/pull/6317
当应用上述修复时,我的代码正在使用大于64k字节的
data
结构 . 在PROGMEM
中以> 64k字节存储structs
的一个警告是,必须使用偏移量手动计算地址(即,要获得PROGMEM
中数组的ith
元素,可以使用mystruct_ptr + offsetoff(mystruct, myarray) + i * sizeof(myarrayitem)
.代码: