当前位置: 首页 > news >正文

Arduino框架下ESP32/8266使用PROGMEM功能将数据存储到flash中的使用规范

Arduino框架下ESP32/8266使用PROGMEM功能将数据存储到flash中的使用规范


  • 📌参考文档:https://arduino-esp8266.readthedocs.io/en/latest/PROGMEM.html

本文主要内容介绍:PROGMEM功能以及FPSTR( )F( ) 宏的使用对比。

📖PROGMEM功能简介

PROGMEM 是一项原 Arduino AVR 单片机的功能,后来移植到 ESP8266 中,以确保与现有 Arduino 库的兼容性,并节省 RAM。在esp8266上声明一个字符串,如const char * xyz = "this is a string"将把这个字符串放在RAM中,而不是flash。可以将String放入flash中,然后在需要时将其加载到RAM中。在8位AVR上,这个过程非常简单。在32位ESP8266上,必须满足一些条件才能从flash中回读。

  • 📌在 ESP8266 PROGMEM 上有一个宏定义:
#define PROGMEM   ICACHE_RODATA_ATTR
  • ICACHE_RODATA_ATTR定义如下:#define ICACHE_RODATA_ATTR __attribute__((section(".irom.text")))

📍它将变量放在闪存的 .irom.text中。将字符串放入闪存中需要使用上述方法。

🌻声明要存储在闪存中的全局字符串

static const char xyz[] PROGMEM = "This is a string stored in flash";

📝在代码块中声明一个闪存字符串

🎈可以使用 PSTR 宏。这些都定义在 pgmspace.h 中:

#define PGM_P       const char *
#define PGM_VOID_P  const void *
#define PSTR(s) (__extension__({static const char __c[] PROGMEM = (s); &__c[0];}))
  • 在程序设计中:
void myfunction(void) {
PGM_P xyz = PSTR("Store this string in flash");
const char * abc = PSTR("Also Store this string in flash");
}

🌿上面的两个示例将这些字符串存储在闪存中。要检索和操作闪存字符串,必须以 4 字节的字数从闪存中读取它们。在 arduino IDE 中,esp8266 有几个函数可以帮助从闪存中检索使用 PROGMEM 存储的字符串。上面的两个示例都返回 。但是,使用这些指针时,如果没有正确的 32 位对齐,则会导致分段错误,ESP8266 将崩溃。您必须从闪存读取32位对齐。const char *

🎯从闪存中回读的函数

回读的函数都定义在 pgmspace.h 中:

int memcmp_P(const void* buf1, PGM_VOID_P buf2P, size_t size);
void* memccpy_P(void* dest, PGM_VOID_P src, int c, size_t count);
void* memmem_P(const void* buf, size_t bufSize, PGM_VOID_P findP, size_t findPSize);
void* memcpy_P(void* dest, PGM_VOID_P src, size_t count);
char* strncpy_P(char* dest, PGM_P src, size_t size);
char* strcpy_P(dest, src)
char* strncat_P(char* dest, PGM_P src, size_t size);
char* strcat_P(dest, src)
int strncmp_P(const char* str1, PGM_P str2P, size_t size);
int strcmp_P(str1, str2P)
int strncasecmp_P(const char* str1, PGM_P str2P, size_t size);
int strcasecmp_P(str1, str2P)
size_t strnlen_P(PGM_P s, size_t size);
size_t strlen_P(strP)
char* strstr_P(const char* haystack, PGM_P needle);
int printf_P(PGM_P formatP, ...);
int sprintf_P(char *str, PGM_P formatP, ...);
int snprintf_P(char *str, size_t strSize, PGM_P formatP, ...);
int vsnprintf_P(char *str, size_t strSize, PGM_P formatP, va_list ap);

🧾里面有很多功能,但实际上它们是标准c函数的版本,适用于从esp8266 32位对齐闪存读取。他们都采取一个本质上是一个hood下,这些函数都使用一个进程来确保读取4个字节,并返回请求字节。

static const char xyz[] PROGMEM = "This is a string stored in flash";
Serial.println(FPSTR(xyz));

将这两种方法组合在一起,以创建一种简单快捷的方法,用于在闪存中存储内联字符串,并返回类型 。例如:F() __FlashStringHelper

Serial.println(F("This is a string stored in flash"));

✨虽然这两个函数提供了类似的功能,但它们具有不同的角色。 允许您定义一个全局闪存字符串,然后在采用 的任何函数中使用它。 允许您就地定义这些闪存字符串,但不能在其他任何地方使用它们。这样做的结果是共享公共字符串是可能的,但不能使用 。 是 String 类用于重载其构造函数的内容:FPSTR() __FlashStringHelper F() FPSTR() F()__FlashStringHelper

String(const char *cstr = nullptr); // constructor from const char *
String(const String &str); // copy constructor
String(const __FlashStringHelper *str); // constructor for flash strings
  • 也可以这样写:
String mystring(F("This string is stored in flash"));

📑如何编写函数以使用__FlashStringHelper简单:将指针移回PGM_P并使用上面显示的函数。这是字符串的一个示例实现,用于 concat 函数。

unsigned char String::concat(const __FlashStringHelper * str) {
    if (!str) return 0; // return if the pointer is void
    int length = strlen_P((PGM_P)str); // cast it to PGM_P, which is basically const char *, and measure it using the _P version of strlen.
    if (length == 0) return 1;
    unsigned int newlen = len + length;
    if (!reserve(newlen)) return 0; // create a buffer of the correct length
    strcpy_P(buffer + len, (PGM_P)str); //copy the string in using strcpy_P
    len = newlen;
    return 1;
}

🌳如何声明全局闪存字符串并使用它?

static const char xyz[] PROGMEM = "This is a string stored in flash. Len = %u";

void setup() {
    Serial.begin(115200); Serial.println();
    Serial.println( FPSTR(xyz) ); // just prints the string, must convert it to FlashStringHelper first using FPSTR().
    Serial.printf_P( xyz, strlen_P(xyz)); // use printf with PROGMEM string
}

📝如何使用内联闪存字符串?

void setup() {
    Serial.begin(115200); Serial.println();
    Serial.println( F("This is an inline string")); //
    Serial.printf_P( PSTR("This is an inline string using printf %s"), "hello");
}

📑如何在 PROGMEM 中声明和使用数据?

const size_t len_xyz = 30;
const uint8_t xyz[] PROGMEM = {
  0x53, 0x61, 0x79, 0x20, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20,
  0x74, 0x6f, 0x20, 0x4d, 0x79, 0x20, 0x4c, 0x69, 0x74, 0x74,
  0x6c, 0x65, 0x20, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x00};

 void setup() {
     Serial.begin(115200); Serial.println();
     uint8_t * buf = new uint8_t[len_xyz];
     if (buf) {
      memcpy_P(buf, xyz, len_xyz);
      Serial.write(buf, len_xyz); // output the buffer.
     }
 }

🍭如何在 PROGMEM 中声明一些数据,并从中检索一个字节

const size_t len_xyz = 30;
const uint8_t xyz[] PROGMEM = {
  0x53, 0x61, 0x79, 0x20, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20,
  0x74, 0x6f, 0x20, 0x4d, 0x79, 0x20, 0x4c, 0x69, 0x74, 0x74,
  0x6c, 0x65, 0x20, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x00
};

void setup() {
  Serial.begin(115200); Serial.println();
  for (int i = 0; i < len_xyz; i++) {
    uint8_t byteval = pgm_read_byte(xyz + i);
    Serial.write(byteval); // output the buffer.
  }
}

🎯如何在PROGMEM中声明字符串数组并从中检索元素。

🎉在处理大量文本(如带有 LCD 显示器的项目)时,设置字符串数组通常很方便。因为字符串本身是数组,所以这实际上是二维数组的一个示例。

📍这些往往是大型结构,因此将它们放入程序内存通常是可取的。下面的代码说明了这个想法。

/ Define Strings
const char string_0[] PROGMEM = "String 0";
const char string_1[] PROGMEM = "String 1";
const char string_2[] PROGMEM = "String 2";
const char string_3[] PROGMEM = "String 3";
const char string_4[] PROGMEM = "String 4";
const char string_5[] PROGMEM = "String 5";

// Initialize Table of Strings
const char* const string_table[] PROGMEM = { string_0, string_1, string_2, string_3, string_4, string_5 };

char buffer[30]; // buffer for reading the string to (needs to be large enough to take the longest string

void setup() {
  Serial.begin(9600);
  Serial.println("OK");
}

void loop() {
  for (int i = 0; i < 6; i++) {
    strcpy_P(buffer, (char*)pgm_read_dword(&(string_table[i])));
    Serial.println(buffer);
    delay(500);
  }
}

PROGMEM总结

使用PROGMEMPSTR很容易在flash中存储字符串,但你必须创建专门使用指针的函数,因为它们基本上是const char *。另一方面,FPSTRF()提供了一个可以进行隐式转换的类,在重载函数和进行隐式类型转换时非常有用。值得补充的是,如果你希望存储一个int, float或指针,这些可以存储和直接读回来,因为他们的大小是4字节,因此将始终对齐,如果是很少的字符串:1、2或3个字符,最好在RAM中设置它们,因为它们在Flash中都占用4字节

FPSTR( )F( ) 宏使用,通过编译对比

  • 引用:https://github.com/esp8266/Arduino/issues/1143
//很少的字符串1 2或3个字符,最好在RAM中设置它们,因为它们在Flash中都占用4字节
/*
 * //项目使用了 263293 字节,占用了 (25%) 程序存储空间。最大为 1044464 字节。
//全局变量使用了27988字节,(34%)的动态内存,余留53932字节局部变量。最大为81920字节。

#define STR "0123456789" 
const char FP_TEST[] PROGMEM = STR;
void setup() {}
void loop() {
  Serial.print(STR);  Serial.print(STR);  Serial.print(STR);  Serial.print(STR);  Serial.print(STR);
  Serial.print(STR);  Serial.print(STR);  Serial.print(STR);  Serial.print(STR);  Serial.print(STR);
}

*/

/*
 * 项目使用了 263373 字节,占用了 (25%) 程序存储空间。最大为 1044464 字节。
全局变量使用了27976字节,(34%)的动态内存,余留53944字节局部变量。最大为81920字节。

#define STR "0123456789" 
const char FP_TEST[] PROGMEM = STR;
void setup() {}
void loop() {
  Serial.print(F(STR));  Serial.print(F(STR));  Serial.print(F(STR));  Serial.print(F(STR));  Serial.print(F(STR));
  Serial.print(F(STR));  Serial.print(F(STR));  Serial.print(F(STR));  Serial.print(F(STR));  Serial.print(F(STR));
  }
*/

/*
 * 项目使用了 263329 字节,占用了 (25%) 程序存储空间。最大为 1044464 字节。
全局变量使用了27976字节,(34%)的动态内存,余留53944字节局部变量。最大为81920字节。

#define STR "0123456789" 
const char FP_TEST[] PROGMEM = STR;
void setup() {}
void loop() {
  Serial.print(FPSTR(FP_TEST));  Serial.print(FPSTR(FP_TEST));  Serial.print(FPSTR(FP_TEST));  Serial.print(FPSTR(FP_TEST));  Serial.print(FPSTR(FP_TEST));  
  Serial.print(FPSTR(FP_TEST));  Serial.print(FPSTR(FP_TEST));  Serial.print(FPSTR(FP_TEST));  Serial.print(FPSTR(FP_TEST));  Serial.print(FPSTR(FP_TEST));  
  }
*/
//项目使用了 263297 字节,占用了 (25%) 程序存储空间。最大为 1044464 字节。
//全局变量使用了27976字节,(34%)的动态内存,余留53944字节局部变量。最大为81920字节。
#define STR "0123456789" 
const char FP_TEST[] PROGMEM = STR;
void setup() {}
void loop() {
  Serial.print(FP_TEST);  Serial.print(FP_TEST);  Serial.print(FP_TEST);  Serial.print(FP_TEST);  Serial.print(FP_TEST);
  Serial.print(FP_TEST);  Serial.print(FP_TEST);  Serial.print(FP_TEST);  Serial.print(FP_TEST);  Serial.print(FP_TEST);
}

相关文章:

  • 并查集(路径压缩)
  • 防火墙实验二——实现域间、域内双向NAT、双机热备实验
  • 【Django】REST_Framework框架——视图集ViewSet和ModelViewSet源码解析
  • 如何对SAP数据库表进行增删改查操作(3)
  • Spring-06 Xml和注解方式配置Aop
  • 同步请求和异步请求(利用axios)
  • 猿创征文|瑞吉外卖——移动端_笔记
  • SpringBoot异常处理——异常显示的页面
  • 高等数学二从零开始学习的总结笔记(持续更新)
  • 无服务器学习01:基本概念+优点+面临的挑战
  • C#实验二
  • 熟悉c语言结构体
  • uboot源码分析(基于S5PV210)之启动第二阶段
  • 【分布式】分布式系统、Redis中间件 、Cache穿透、击穿、雪崩
  • Rust基础语法
  • 【干货分享】SpringCloud微服务架构分布式组件如何共享session对象
  • 【面试系列】之二:关于js原型
  • Android路由框架AnnoRouter:使用Java接口来定义路由跳转
  • CSS实用技巧干货
  • Django 博客开发教程 16 - 统计文章阅读量
  • golang中接口赋值与方法集
  • isset在php5.6-和php7.0+的一些差异
  • Java 最常见的 200+ 面试题:面试必备
  • Java-详解HashMap
  • JS基础篇--通过JS生成由字母与数字组合的随机字符串
  • LeetCode29.两数相除 JavaScript
  • LeetCode算法系列_0891_子序列宽度之和
  • Logstash 参考指南(目录)
  • PAT A1092
  • Promise面试题,控制异步流程
  • spring + angular 实现导出excel
  • WePY 在小程序性能调优上做出的探究
  • 阿里云前端周刊 - 第 26 期
  • 从@property说起(二)当我们写下@property (nonatomic, weak) id obj时,我们究竟写了什么...
  • 关于 Linux 进程的 UID、EUID、GID 和 EGID
  • 事件委托的小应用
  • 试着探索高并发下的系统架构面貌
  • 数据可视化之 Sankey 桑基图的实现
  • 携程小程序初体验
  • 一、python与pycharm的安装
  • 译有关态射的一切
  • 用jquery写贪吃蛇
  • 长三角G60科创走廊智能驾驶产业联盟揭牌成立,近80家企业助力智能驾驶行业发展 ...
  • ​LeetCode解法汇总2304. 网格中的最小路径代价
  • ​软考-高级-系统架构设计师教程(清华第2版)【第12章 信息系统架构设计理论与实践(P420~465)-思维导图】​
  • ​软考-高级-系统架构设计师教程(清华第2版)【第1章-绪论-思维导图】​
  • (16)Reactor的测试——响应式Spring的道法术器
  • (9)STL算法之逆转旋转
  • (C语言版)链表(三)——实现双向链表创建、删除、插入、释放内存等简单操作...
  • (JS基础)String 类型
  • (Mirage系列之二)VMware Horizon Mirage的经典用户用例及真实案例分析
  • (附源码)ssm高校运动会管理系统 毕业设计 020419
  • (附源码)小程序 交通违法举报系统 毕业设计 242045
  • (原創) 如何將struct塞進vector? (C/C++) (STL)
  • (转载)PyTorch代码规范最佳实践和样式指南