首页 文章

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

提问于
浏览
1

Found a solution, see my comment below!

我正在尝试使用PROGMEM读取放置在结构中并存储在Arduino Mega(ATmega 2560)的闪存中的数据 . 使用 pointers 访问结构对象 manufacturer_1manufacturer_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 回答

  • 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

    你的行在这里: 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上:

    而他们的对立面显而易见 .

    与你刚刚拥有的ARM(宽度变体)相比:LDR and STR, Load and Store with immediate offset

    这就是为什么使用PROGMEM很麻烦 . 欢迎来到嵌入式软件开发 .

  • 0

    感谢user14042和Jeroen3,我的兄弟和我找到了一个部分有效的解决方案;最多8个 manufacturer_def 结构 . 该解决方案首先基于使用 pgm_read_ptr(&data.manufacturer[i]) 创建指向正确 manufacturer_def 结构的指针 . 其次,使用 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.但是可能导致这个值的是什么值?

  • 0

    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) .

    代码:

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

相关问题