Arduino - Use PROGMEM para gravar dados na memória flash (espaço de armazenamento do programa)

Introdução ao armazenamento AVR:

Existem três tipos de memórias endereçáveis ​​independentemente dentro dos microcontroladores da série AVR:
1. Memória de programa Flash (isto é: espaço de armazenamento de programa, memória flash)
2. Memória de dados SRAM (isto é: memória dinâmica)
3. Memória de dados EEPROM

O microcomputador de chip único adota a estrutura Haval para separar a memória do programa e a memória de dados. A memória RAM de dados geralmente é relativamente pequena, e a memória Flash do programa tem um espaço relativamente grande. Portanto, é necessário colocar os dados que não precisam ser alterados com um espaço maior no Flash.

Por exemplo, se você precisa de um microcomputador de chip único para oferecer suporte ao texto do display LCD, você precisa de uma biblioteca de fontes enorme, que pode atingir vários kb. Essa grande quantidade de dados não é adequada para armazenamento em RAM e só pode ser armazenada em Flash.

pgmspace.h fornece operações de leitura e gravação relacionadas a ele.

Guia de perguntas:

Ao compilar o programa Arduino, ele solicitará:

项目使用了 656 字节,占用了 (0%) 程序存储空间。最大为 253952 字节。
全局变量使用了9字节,(0%)的动态内存,余留8183字节局部变量。最大为8192字节。

Os problemas que costumamos encontrar ao compilar são:

项目使用了 4756 字节,占用了 (15%) 程序存储空间。最大为 30720 字节。
全局变量使用了2246字节,(109%)的动态内存,余留-198字节局部变量。最大为2048字节。
没有足够的内存; 编译时出错。

[Espaço de armazenamento do programa] tem muito espaço restante, mas [Memória dinâmica] não é suficiente, o que torna impossível escrever com sucesso. Esse problema geralmente ocorre quando constantes com dados "maiores" são declaradas, especialmente matrizes.

solução:

Para resolver este problema, podemos escrever constantes que deveriam ter sido escritas em [Memória Dinâmica] em [Espaço de Armazenamento do Programa] para economizar espaço de [Memória Dinâmica].

Ao definir constantes globais, use a palavra-chave PROGMEM ou use o tipo de dados PROGMEM para dizer ao compilador para "armazenar essas informações no espaço de armazenamento do programa" em vez de armazená-las na "memória dinâmica".

A biblioteca usada pela palavra-chave PROGMEM (ou tipo de dados): pgmspace.h

#include <avr/pgmspace.h>

Definição de dados:

const dataType variableName [] PROGMEM = {data0,data1,data3 ...};
// dataType- 任何变量类型
// variableName- 数据数组的名称
O espaço de armazenamento do programa FLASH não pode ser alterado, portanto, é um bom hábito adicionar a palavra-chave const ao defini-la.

1. Quando usado como uma constante [global], você pode usar diretamente a palavra-chave PROGMEM.A posição da palavra-chave PROGMEM é relativamente arbitrária, mas para a compatibilidade da versão anterior do Arduino, é recomendado colocá-la para trás. Tal como:

const char str1[] PROGMEM = "Hi, I would like to tell you a bit about myself.\n"
const PROGMEM char str2[] = "Hi, I would like to tell you a bit about myself.\n"
PROGMEM const char str3[] = "Hi, I would like to tell you a bit about myself.\n"

2. Quando usado como uma constante [local], precisa ser usado com a palavra-chave estática, como:

const static char flash_str[] PROGMEM = "Hi, I would like to tell you a bit about myself.\n"

3. Outro formulário de definição não usa a palavra-chave PROGMEM, mas usa diretamente o tipo de dados PROGMEM, como:

const prog_char flash_str[] = "Hi, I would like to tell you a bit about myself.\n"

4. A definição de constantes de string

// 全局定义形式:
const char flash_str[] PROGMEM = “Hello, world!”;

// 函数内定义形式:
		/* pgmspace.h提供了一个宏 PSTR 用来申明Flash中的字符串:
		# define PSTR(s) ((const PROGMEM char *)(s))
		所以,函数内可以采用下面的定义形式:*/
const char *flash_str = PSTR(“Hello, world!”);

// 以下为应用示例:
const char flash_str1[] PROGMEM = “全局定义字符串”;
int main(void)
{
	char *flash_str2=PSTR(“函数内定义字符串”);
	printf_P(flash_str1);
	printf_P(flash_str2);
}

Leitura de dados:

Até este ponto, nosso programa não está funcionando corretamente.
Pois quando você passa um ponteiro do Flash para uma função, ele vai pensar que é um ponteiro para a RAM, por isso procura os dados na RAM, causando o erro do programa.
Portanto, uma função especial é necessária para lidar com ponteiros para o Flash.
Depois que os dados são salvos no espaço de armazenamento do programa, um método especial (função) é necessário para lê-los:

1. Como ler constantes não matriciais
char ram_val; //存到 ram 内的变量
const PROGMEM flash_val = 1; // 存到 flash 内的常量
// 读取
ram_val = pgm_read_byte( &flash_val ); // 读 flash 常量值到 RAM 变量,参数使用【地址&】传递。
2. Como ler constantes de array
char displayInt;
const char charSet [] RROGMEM = {0,1,2,3,4,5,6,7,8,9};
// 读取
for (int i = 0; i < 10; i++)
  {
    displayInt = pgm_read_byte(charSet + i);   // 第一种方法
    displayInt = pgm_read_byte(&charSet[i]);   // 第二种方法
  }
3. Método de cópia de string

A função strcpy_P é responsável por copiar uma string de caracteres de [espaço de armazenamento do programa] para o buffer "buffer" de [memória dinâmica].
Nota: Certifique-se de que o buffer seja grande o suficiente ao copiar.

char buffer[30]; 
// 方式一
strcpy_P(buffer,PSTR("dGltQuY29t\r\n"));  
// 方式二
const char string_0[] PROGMEM = "This is a String"; 
strcpy_P(buffer, (char *)pgm_read_word(&string_0)); 

Funções de processamento relacionadas:

// 数据读取函数
pgm_read_byte(addr)
pgm_read_word(addr)
pgm_read_dword(addr)
pgm_read_float(addr)
pgm_read_ptr(addr)

// 字符串处理函数
void *memcpy_P(void *, const void *, size_t);
char *strcat_P(char *, const char *);
int strcmp_P(const char *, const char *);
char *strcpy_P(char *, const char *);

可以看到,字符串处理函数与标准处理函数一样,只是以_P结尾,它们的功能也是一样的。 
看这一句代码:

strcmp_P("ram item", PSTR("flash item"));

这句代码用来比较两个字符串,第一个字符串”ram item”是申明在RAM中的,第二个字符串”flash item”通过宏PSTR申明在Flash中。

Sobre o uso da macro F ():

Normalmente usamos as seguintes instruções para saída de porta serial:

Serial.print("Write something on  the Serial Monitor");

Mas, desta forma, os dados serão armazenados na [Memória dinâmica] cada vez que forem chamados.
Quando queremos uma string longa, ela vai ocupar muito espaço de [memória dinâmica].
Usar F () pode resolver este problema, e F () pode salvar facilmente a string em FLASH.

Serial.print(F("Write something on the Serial Monitor that is stored in FLASH"));

Sobre leitura de dados

1. Para o processamento de matrizes em Flash, pgmspace.h também fornece várias macros:
pgm_read_byte(addr)
pgm_read_word(addr)
pgm_read_dword(addr)
pgm_read_float(addr)
pgm_read_ptr(add)

Eles são usados ​​para ler 1, 2, 4 bytes no endereço addr e ler números de ponto flutuante.
Diante desse problema, várias strings são armazenadas no Flash, e o endereço de cada string é armazenado no Flash na forma de um array, a saber:

const char *s1 PROGMEM = "s1";const char *s2 PROGMEM = "s2";const char* strPointer[] PROGMEM = {s1, s2};

Como comparar s1 e s2?
Primeiro, você precisa ler os endereços das duas strings e, em seguida, comparar as strings por meio da função strcmp_P.
Deve-se observar que ambos os ponteiros são de 16 bits, não de 8 bits!
Portanto, o código deve ser:

strcmp_P(pgm_read_word(&strPointer[0]),pgm_read_word(&strPointer[1]));

O compilador dará um aviso para tal código, porque pgm_read_byte () obtém um inteiro de 16 bits, e a partir do protótipo da função, você pode ver que a função precisa de um ponteiro. Existem duas maneiras de eliminar este aviso:

(1) Conversão de tipo forçada

strcmp_P(pgm_read_word((char*)&strPointer[0]), (char*)pgm_read_word(&strPointer[1]));

(2) Use outra macro

strcmp_P(pgm_read_ptr(&strPointer[0]), pgm_read_ptr(&strPointer[1]));
2. O problema de leitura de dados

Conforme mencionado anteriormente, o ponteiro tem 16 bits e pode endereçar um espaço de endereço de 64kB.
Em alguns chips AVR, como o mega2560, o espaço do Flash é de 256 kB. Como o espaço que excede 64 kB será tratado?
pgmspace.h fornece dois tipos de macros de endereçamento Flash:

Um é o endereçamento de endereço curto usando endereços de 16 bits, até 64kB:

pgm_read_byte_near(address_short)
pgm_read_word_near(address_short)
pgm_read_dword_near(address_short)
pgm_read_float_near(address_short)
pgm_read_ptr_near(address_short)

O outro é o endereçamento de endereço longo com endereços de 32 bits, até 4 GB de espaço:

pgm_read_byte_far(address_short)
pgm_read_word_far(address_short)
pgm_read_dword_far(address_short)
pgm_read_float_far(address_short)
pgm_read_ptr_faraddress_short)

Existem diferenças de desempenho entre os dois tipos de endereçamento. A velocidade de endereçamento de endereço curto é muito mais rápida e 64 kB também é suficiente, portanto, o endereçamento de endereço curto é usado por padrão:

#define pgm_read_byte(address_short) pgm_read_byte_near(address_short)
#define pgm_read_word(address_short) pgm_read_word_near(address_short)
#define pgm_read_dword(address_short) pgm_read_dword_near(address_short)
#define pgm_read_float(address_short) pgm_read_float_near(address_short)
#define pgm_read_ptr(address_short) pgm_read_ptr_near(address_short)

Quando você precisa endereçar mais de 64kB de espaço, pode usar manualmente o endereçamento de endereço longo.

Acho que você gosta

Origin blog.csdn.net/sdlgq/article/details/88720706
Recomendado
Clasificación