前面的7节课把开发板上基本的外设都测试过一边,接下来马上就要进入wifi和蓝牙应用的测试了
在此之前,还需要把掉电数据保存的功能给实现,在STM32中,可以使用内部的flash或者有些自带的EEPROM
在 ESP32-C3 上,使用非易失性存储 (NVS) 库的方式,进行简单数据的掉电保存
contenu
avant-propos
Les tests fonctionnels ESP32-C3 suivants sont basés sur la carte de développement conçue par nous-mêmes :
L'environnement de développement est l'ESP-IDF officiel d'Espressif, construit sur la base du plugin VScode :
Parce que c'est le fonctionnement du Flash interne de l'ESP32-C3, nous n'utilisons pas d'autres périphériques ici.
1, introduction de base NVS
En termes simples, NVS est un morceau d'espace mémoire alloué sur le flash, qui est fourni à l'utilisateur pour enregistrer les données qui ne sont pas perdues lorsque l'alimentation est coupée.
L'objectif principal de cet article est d'apprendre à utiliser NVS basé sur le SDK officiel, et de mentionner brièvement les connaissances connexes, telles que la table de partition, etc.
1.1 Introduction de base
Pour l'introduction d'ESP32-C3 NVS, le lien de description du site officiel d'Espressif est le suivant :
Description officielle de la pièce Espressif ESP32-C3 NVS
Simplement à travers l'introduction officielle pour comprendre, ou utilisez quelques images officielles pour montrer :
1.2 Tableau des partitions
On dit que NVS est une zone en mémoire, alors quelle est l'adresse ? Quelle est la taille?
Celui-ci doit mentionner le contenu lié à la table de partition. L'adresse de description officielle est la suivante :
Table de partition flash ESP32-C3
Selon l'introduction officielle, nous énumérons ce qui doit être utilisé ici :
Chaque entrée de la table de partition comprend les parties suivantes : nom (étiquette), type (application, données, etc.), sous-type et décalage dans la mémoire flash (adresse de chargement de la partition).
- Le champ Type peut être spécifié en tant qu'application (0x00) ou données (0x01), ou utiliser directement les nombres 0-254 (ou hexadécimal 0x00-0xFE). Notez que 0x00-0x3F ne doit pas être utilisé (réservé aux fonctionnalités de base d'esp-idf).
- La longueur du champ SubType est de 8 bits et le contenu est lié au type de partition spécifique. À l'heure actuelle, esp-idf ne spécifie que la signification du sous-type des deux types de partition "app" et "data".
Par conséquent, selon l'introduction de la table de partition ci-dessus, pour l'ESP32-C3 que nous utilisons, la puce imprimera automatiquement les informations système au démarrage.La description NVS correspondante est la suivante :
Pour savoir comment générer une partition NVS, nous n'en discuterons pas ici, vous pouvez vous référer à la section officielle : Description officielle du programme de génération de partition NVS
1.3 Étapes d'utilisation de NVS
Le test NVS de cet article est basé sur la table de partition par défaut, nous n'avons donc pas besoin d'utiliser la table de partition pendant le processus d'utilisation.
L'API que NVS doit utiliser, dans le nvs_flash.h
fichier, le chemin est :esp-idf/components/nvs_flash/include/nvs_flash.h
-
Pour initialiser NVS, utilisez la fonction
nvs_flash_init
:
Dans l'exemple :
-
Ouvrez NVS et utilisez la
nvs_open
fonction :
Dans l'exemple, le deuxième paramètre doit indiquer que la zone ouverte peut être lue ou écrite, et la lecture seule estNVS_READONLY
:
-
Opérations de lecture et d'écriture, utilisez
nvs_get_*
(* pour indiquer différents types de données, tels quenvs_get_i32
,nvs_get_u16
) Opérations de lecture, utiliseznvs_set_*
pour les opérations d'écriture :
Dans l'exemple, les applications de lecture et d'écriture sont les suivantes :
-
Après avoir écrit la valeur, une
nvs_commit
fonction est requise pour s'assurer que la valeur est écrite avec succès.
-
Fermez NVS et lorsque vous avez fini d'écrire, utilisez
nvs_close
close.
2. Exemple d'essai
2.1 Exemple de test de base
Dans la routine officielle, nous nous référons à deux exemples de programmes, comme indiqué ci-dessous :
Exemple nvs_rw_value :
L'exemple dans les étapes d'utilisation de NVS que nous avons présentées ci-dessus est le nvs_rw_value
projet utilisé. Il n'y a rien pour modifier le projet de base. Les résultats des tests sont les suivants, et restart_counter
la augmentera de 1, comme illustré dans la figure suivante :
Exemple nvs_rw_blob :
L'effet de test du deuxième projet est le suivant :
regardez d'abord l'effet de test, expliquons brièvement le code source, la première fonction save_restart_counter
fonction est fondamentalement la même que l'exemple nvs_rw_blob
, pas grand chose à dire.
Regardons la deuxième fonction save_run_time
Dans cette fonction, nous utilisons une fonction de sum nvs_get_blob
et nvs_set_blob
remarquons qu'ils ont tous les deux un paramètre de void*
type , ce qui signifie que les opérations de ces deux nvs peuvent être appliquées à n'importe quel type de données.
Pour la save_run_time
fonction ci-dessus, nous avons directement ajouté le code source commenté :
/* Save new run time value in NVS
by first reading a table of previously saved values
and then adding the new value at the end of the table.
Return an error if anything goes wrong
during this process.
*/
esp_err_t save_run_time(void)
{
nvs_handle_t my_handle;
esp_err_t err;
// Open 正常的操作步骤,打开nvs,第一个命名空间,读写,句柄名称
err = nvs_open(STORAGE_NAMESPACE, NVS_READWRITE, &my_handle);
if (err != ESP_OK) return err;
/* Read the size of memory space required for blob
unsigned int required_size
读nvs,读取键值对为 "run_time" 处的内容放入 变量required_size
*/
size_t required_size = 0; // value will default to 0, if not set yet in NVS
/*先使用一次nvs_get_blob函数,但是第三个参数输出地址使用的是NULL,
表示读出的数据不保存,因为这里使用只是为了看一下 "run_time" 处是否
有数据,只是先读一下数据,看一下读完以后 required_size 还是不是0
*/
err = nvs_get_blob(my_handle, "run_time", NULL, &required_size);
if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) return err;
/* Read previously saved blob if available
这里申请一块地址,定义为 run_time ,地址上存放的示uint32_t数据,大小为
required_size大小 + sizeof(uint32_t)
*/
uint32_t* run_time = malloc(required_size + sizeof(uint32_t));
/*
如果上面读到的 required_size 大于0 ,说明"run_time" 处以前是保存过数据的
那么,就先读出来,保存在刚才申请的 地址 run_time 处(第3个参数)。
*/
if (required_size > 0) {
err = nvs_get_blob(my_handle, "run_time", run_time, &required_size);
if (err != ESP_OK) {
free(run_time);
return err;
}
}
/*
Write value including previously saved blob if available
不管 required_size 有过还是没有过,进行这步操作,都会使得 required_size 增加
增加 sizeof(uint32_t) 大小,因为示例本意示重启一次,计数必须加一次
*/
required_size += sizeof(uint32_t);
/*
每一次保存的是使用数组形式保存数据:类似于 uint32_t run_time[] 数组
给数组赋值
*/
run_time[required_size / sizeof(uint32_t) - 1] = xTaskGetTickCount() * portTICK_PERIOD_MS;
/*
最后把需要保存的数组处理完成以后
调用 nvs_set_blob 函数进行保存
*/
err = nvs_set_blob(my_handle, "run_time", run_time, required_size);
free(run_time);
if (err != ESP_OK) return err;
// Commit
err = nvs_commit(my_handle);
if (err != ESP_OK) return err;
// Close
nvs_close(my_handle);
return ESP_OK;
}
Après avoir compris la fonction ci-dessus, l' nvs_rw_blob
exemple ne pose pratiquement aucun problème. Dans la print_what_saved(void)
fonction suivante, différentes fonctions get sont utilisées pour extraire différentes données de différentes valeurs de clé dans le même espace de noms (nous en discuterons dans la section suivante). ), les autres n'ont rien de spécial :
2.2 Suppression des données
Comme mentionné précédemment, NVS ouvre en fait une zone dans l'espace Flash, donc cette zone doit avoir une adresse, mais l'ESP32-C3 utilise la méthode NVS, et l'adresse mémoire est opaque pour l'utilisateur, uniquement via l'espace de noms et la clé- les paires de valeurs sont automatiquement affectées (expliqué dans la section suivante). Puisqu'il est enregistré, vous devez également savoir le supprimer, car s'il n'est pas supprimé, les données peuvent toujours exister dans cet espace d'adressage.
Les exemples et les instructions officielles expliquent uniquement comment utiliser NVS pour enregistrer des données sans perte de puissance, mais n'expliquent pas spécifiquement comment effacer les données.
Nous avons nvs.h
trouvé deux fonctions par :
esp_err_t nvs_erase_key(nvs_handle_t handle, const char* key);
esp_err_t nvs_erase_all(nvs_handle_t handle);
À l'aide de ces deux fonctions, testons-la. La fonction de test est basée sur Exemplenvs_rw_blob
: , puis ajoutez le pilote de bouton :
Test d'apprentissage ESP32-C3 (deux, interruption GPIO, test de lecteur de bouton)
Nous supprimons les données enregistrées dans l'exemple par une opération de clé. Dans le pilote de clé, nous modifions le code :
static void button_single_click_cb(void *arg){
uint8_t *num = (uint8_t *)arg;
uint8_t gpio_num = *num;
ESP_LOGI(TAG, "BTN%d: BUTTON_SINGLE_CLICK\n", gpio_num);
printf("nvs_erase_key test!\r\n");
// nvs_erase_key(my_handle,"restart_conter");
nvs_handle_t my_handle;
// Open
nvs_open(STORAGE_NAMESPACE, NVS_READWRITE, &my_handle);
nvs_erase_key(my_handle,"restart_conter");
// err = nvs_commit(my_handle);
// nvs_close(my_handle);
}
L' image est la suivante : L'
effet de test est le suivant :
Ensuite, changez une clé et essayez-la :
Enfin, testez-la nvs_erase_all
, comme indiqué dans l'image ci-dessous :
2.3 Espaces de noms, paires clé-valeur
Les espaces de noms sont utilisés dans les routines ci-dessus, qui peuvent distinguer différentes zones de stockage, mais il existe également un paramètre clé, qui doit indiquer les paires clé-valeur. Il ressemble aux espaces de noms et est également utilisé pour distinguer différentes données, comme indiqué dans la figure:
Dans les multiples exemples de tests précédents, nous avons également une certaine compréhension des espaces de noms (généralement représentés par des paramètres dans functions const char* name
) et des paires clé-valeur (généralement représentées par des paramètres dans functions const char* key
).Tout d'abord, dans l'exemple, le même espace de noms est utilisé , obtenez différentes données via différentes paires clé-valeur :
Effectuez quelques tests simples pour les espaces de noms et les paires clé-valeur. Appuyez sur le bouton pour enregistrer la valeur du nouvel espace de noms, et enregistrez les nouvelles données dans l'opération clé :
ces données sont également lues à la mise sous tension : les
résultats du test sont les suivants :
Pour résumer, sur la base de l'exemple de test précédent, une image est dessinée, comme suit :
2.4 Stockage du type de données chaîne
Les données utilisées dans notre exemple ci-dessus sont essentiellement des entiers. Bien que nous les utilisions dans notre exemple nvs_set_blob
, il s'agit toujours des données entières entrantes. Testons et enregistrons les données de type chaîne et numérique.
Comme dans la routine ci-dessus, enregistrez une chaîne en appuyant sur une touche :
static void button_long_press_start_cb(void *arg){
uint8_t *num = (uint8_t *)arg;
uint8_t gpio_num = *num;
ESP_LOGI(TAG, "BTN%d: BUTTON_LONG_PRESS_START\n", gpio_num);
char test_str[]="this is my test str,boom!";
printf("nvs_new_name test!\r\n");
nvs_handle_t my_handle;
nvs_open(TEST_NAMESPACE, NVS_READWRITE, &my_handle);
nvs_set_str(my_handle,"str_test",test_str);
nvs_commit(my_handle);
nvs_close(my_handle);
}
Dans la fonction principale, créez une nouvelle fonction de lecture :
esp_err_t my_test_str(void)
{
nvs_handle_t my_handle;
esp_err_t err;
char get_char[30] = {
0};
// Open
err = nvs_open(TEST_NAMESPACE, NVS_READWRITE, &my_handle);
if (err != ESP_OK) return err;
// Read
size_t required_size = 0;
err = nvs_get_str(my_handle, "str_test",NULL,&required_size);
// err = nvs_get_i32(my_handle, "str_test", &restart_counter);
// if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) return err;
// err = nvs_get_blob(my_handle, "run_time", NULL, &required_size);
if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) return err;
if (required_size > 0) {
err = nvs_get_str(my_handle, "str_test", get_char, &required_size);
printf("test str is: %s \nsize is %d \n",get_char,required_size);
if (err != ESP_OK) {
return err;
}
}
else{
printf("no str data now!!!\n");
}
// Close
nvs_close(my_handle);
return ESP_OK;
}
Bien sûr, il doit toujours app_main
être appelé dans my_test_str
, et l'effet de test est le suivant :
dans l'apprentissage et l'utilisation ultérieurs du wifi ESP32-C3, le SSID et le mot de passe enregistrés seront fréquemment utilisés par les opérations de chaîne NVS.