使用循环从PROGMEM中的结构读取字符串

时间:2017-05-16 14:56:46

标签: c arduino

找到解决方案,请参阅下面的评论!

我正在尝试使用PROGMEM读取放置在结构中并存储在Arduino Mega(ATmega 2560)闪存中的数据。使用manufacturer_1访问结构对象manufacturer_2pointers

由于草图的大小;我决定创建一个(相对)小例子来说明问题。以下代码显示了我如何定义结构和数据。

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

为什么会这样?

4 个答案:

答案 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;
  }
 }