找到解决方案,请参阅下面的评论!
我正在尝试使用PROGMEM读取放置在结构中并存储在Arduino Mega(ATmega 2560)闪存中的数据。使用manufacturer_1
访问结构对象manufacturer_2
和pointers
。
由于草图的大小;我决定创建一个(相对)小例子来说明问题。以下代码显示了我如何定义结构和数据。
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();
}
问题!
我想使用循环用字符串填充数组。以下代码无法正常运行:
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
为什么会这样?
答案 0 :(得分:1)
我认为与PROGMEM有关的是将变量存储在FLASH中,而不是RAM中。阅读this documentation on PROGMEM,因此当您不使用 pgm_read_word_near()并动态访问FLASH存储变量时,就会出现问题。但是当你使用常量(字面值)时:
strcpy_P(str_buf, data.manufacturer[0]->info);
menuItems[0] = str_buf;
访问变量没问题。
由于 strcpy_P()的实现,问题可能会出现。
所以在那份文件中他们这样做了:
const char* const string_table[] PROGMEM = {string_0, string_1, string_2, string_3, string_4, string_5};
char buffer[30]; // make sure this is large enough for the largest string it must hold
void loop()
{
/* Using the string table in program memory requires the use of special functions to retrieve the data.
The strcpy_P function copies a string from program space to a string in RAM ("buffer").
Make sure your receiving string in RAM is large enough to hold whatever
you are retrieving from program space. */
for (int i = 0; i < 6; i++)
{
strcpy_P(buffer, (char*)pgm_read_word(&(string_table[i]))); // Necessary casts and dereferencing, just copy.
Serial.println(buffer);
delay( 500 );
}
}
答案 1 :(得分:1)
你的行在这里:
strcpy_P(str_buf, data.manufacturer[i]->info);
在您指定了PROGMEM之后,问题是data
不在ram中,但是您默认使用ram加载指令将数据作为strcpy_P
的参数读取。< / p>
由于芯片的harvard architecture,需要使用特定指令从闪存中读取数据 首先,您指示编译器将您的字符串放在PROGMEM中,即闪存。如果不这样做,启动代码将在启动时将数据从闪存复制到DATA,以便您使用常规数据指针和指令进行访问。
然后,当您想要从PROGMEM地址读取数据时,您必须再次使用pgm_read...
告诉编译器您的给定地址在PROGMEM中。
您无法看到指针是程序,数据存储器或外设寄存器的值,而ARM架构则只有一个4GB的地址空间,闪存,RAM和外围位置可以通过它们在地址空间中的位置来区分
在AVR上:
显然他们的对立面。
与刚刚拥有的ARM(宽度变体)形成鲜明对比:LDR and STR, Load and Store with immediate offset
这就是为什么使用PROGMEM很麻烦的原因。欢迎使用嵌入式软件开发。
答案 2 :(得分:0)
感谢user14042和Jeroen3,我的兄弟和我找到了一个部分有效的解决方案;最多8个manufacturer_def
个结构。该解决方案首先基于使用manufacturer_def
创建指向正确pgm_read_ptr(&data.manufacturer[i])
结构的指针。其次,使用strcpy_P(str_buf, manufacturer_ptr->info)
从闪存中检索字符串。
代码:
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++)
{
manufacturer_def* manufacturer_ptr = pgm_read_ptr(&data.manufacturer[i]);
strcpy_P(str_buf, manufacturer_ptr->info);
menuItems[i] = str_buf;
}
}
当我使用8个manufacturer_def
结构时,草图上传没有任何警告或错误:
const data_def data PROGMEM =
{
8,
{
&manufacturer_1,
&manufacturer_2,
&manufacturer_3,
&manufacturer_4,
&manufacturer_5,
&manufacturer_6,
&manufacturer_7,
&manufacturer_8
}
};
然而,当我使用超过8个manufacturer_def
结构时,启动麻烦。使用9个manufacturer_def
结构,草图上传时没有警告或错误,但Arduino无法正确启动。使用10个manufacturer_def
结构,我收到以下错误:
/tmp/ccovyDEX.s: Assembler messages:
/tmp/ccovyDEX.s:5799: Error: value of 70776 too large for field of 2 bytes at 78808
使用11个manufacturering_def
结构,我收到以下错误:
/tmp/ccCa42WT.s: Assembler messages:
/tmp/ccCa42WT.s:6513: Error: value of 78640 too large for field of 2 bytes at 86672
/tmp/ccCa42WT.s:6514: Error: value of 70776 too large for field of 2 bytes at 86674
我知道2个字节可以容纳最大值65535.但是可能导致这个值的是什么值?
答案 3 :(得分:0)
<强>解决方案<!/强>
由于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
结构。在structs
中以{64}字节存储PROGMEM
的警告是,必须使用偏移手动计算地址(即在ith
中获取数组的PROGMEM
元素,人们会做mystruct_ptr + offsetoff(mystruct, myarray) + i * sizeof(myarrayitem)
。
代码:
void mainMenu()
{
unsigned int i = 0;
unsigned int totalMenuItems = data.totalManufacturers;
String menuItems[totalMenuItems];
char str_buf[20];
// Create array with items for menu
for (i = 0; i < totalMenuItems; i++)
{
uint_farptr_t manufacturer_ptr = data.manufacturer[i];
strcpy_PF(str_buf, manufacturer_ptr + offsetof(manufacturer_def, info));
menuItems[i] = str_buf;
}
}