在实际产品开发过程中,我们常常会对一些产品的内置参数进行存储,比如:
- 1、当前属于哪种语言
- 2、机器开机密码
- 3、机器出厂序列号
等等其它关键的参数,最常用的存储方法是基于xxx.ini的文件形式来进行存储,ini文件是什么在以往的文章中也有相应的介绍。
1、了解什么是INI文件?
ini 文件是Initialization File的缩写,即初始化文件,这是用来配置应用软件以实现不同用户的要求。
2、INI文件的格式
INI文件由节、键、值组成。一个简单的的INI文件例子如下:
[Setting] INIT_FLAG=0; VOLUME=1; LANGUAGE=1;
如上例子,[Setting]就是节,=号左边的值是键,=号右边的是值。
3、关于ini_parse开源C库
在github上,关于ini文件的解析已经有相应的开源软件了,网址如下:
https://github.com/ndevilla/iniparser
上面会非常详细介绍这个开源程序是如何来编译以及使用的,并且也开源了相应的源代码,具体原理本节不会多讲,因为开源的文档已经讲解得非常详细了,本节,我将基于小熊派,配置一个SD卡+Fatfs的工程,在确保文件系统在SD卡构建的情况下,来移植ini_parse库,以便于我们日常开发的使用。
4、stm32cubeMX SD卡+Fatfs文件系统工程配置
4.1 时钟配置
这里我选择是外部时钟。
4.2 串行调试接口配置
4.3 SD卡接口参数配置
以下是SD卡接口在小熊派上的电路原理图。
对应主控MCU管脚的连接
由于这里只有一条输出D0输出线,所以在CubeMX上选择SD 1bit模式,其余参数默认。
4.4 配置调试串口
4.5 配置SD卡支持Fatfs
4.6 配置一路调试灯+2个按键
我们通过两个按键来实现更改参数和读取参数,并且用LED来提示。
最后生成代码即可 。
5、下载ini_parse库到生成的代码中并进行移植以支持fatfs
将对应的库文件包含到工程源码中:
由于ini_parse基于标准库所写,所以文件操作都是基于标准文件操作进行编写的,在这里我们需要把这些接口全部转变成fatfs的接口。
iniparse.h
在iniparse.h中包含fatfs的头文件 #include "fatfs.h" 原来标准文件操作的接口 //void iniparser_dump_ini(dictionary * d, FILE * f); 替换为现在支持Fatfs文件的操作接口 void iniparser_dump_ini(dictionary * d, /*FILE *f*/FIL * f); 原来标准文件操作的接口 //void iniparser_dumpsection_ini(dictionary * d, char * s, FILE * f); 替换为现在支持Fatfs文件的操作接口 void iniparser_dumpsection_ini(dictionary * d, char * s, /*FILE * f*/ FIL *f); 原来标准文件操作的接口 //void iniparser_dump(dictionary * d,*FILE * f); 替换为现在支持Fatfs文件的操作接口 void iniparser_dump(dictionary * d, /*FILE * f*/ FIL *f);
iniparse.c
调整支持fatfs的接口
iniparser_dump函数修改
void iniparser_dump(dictionary * d, /*FILE * f*/FIL *f) { int i ; if (d == NULL || f == NULL) return ; for (i = 0 ; i < d->size ; i++) { if (d->key[i] == NULL) continue ; if (d->val[i] != NULL) { f_printf(f, "[%s]=[%s]\n", d->key[i], d->val[i]); //注释这里为标准库操作接口 //fprintf(f, "[%s]=[%s]\n", d->key[i], d->val[i]); } else { f_printf(f, "[%s]=UNDEF\n", d->key[i]); //注释这里为标准库操作接口 //fprintf(f, "[%s]=UNDEF\n", d->key[i]); } } return ; }
iniparser_dumpsection_ini修改
void iniparser_dumpsection_ini(dictionary * d, char * s, /*FILE * f*/ FIL *f) { int j ; char *keym; int secsize ; if (d == NULL || f == NULL) return ; if (! iniparser_find_entry(d, s)) return ; //注释这里为标准库操作接口 //fprintf(f, "\n[%s]\n", s); f_printf(f, "\n[%s]\n", s); secsize = (int)strlen(s) + 2; keym = (char *)malloc(secsize); snprintf(keym, secsize, "%s:", s); for (j = 0 ; j < d->size ; j++) { if (d->key[j] == NULL) continue ; if (!strncmp(d->key[j], keym, secsize - 1)) { //注释这里为标准库操作接口 /* fprintf(f, "%-30s = %s\n", d->key[j]+secsize-1, d->val[j] ? d->val[j] : ""); */ f_printf(f, "%-30s = %s\n", d->key[j] + secsize - 1, d->val[j] ? d->val[j] : ""); } } //注释这里为标准库操作接口 //fprintf(f, "\n"); f_printf(f, "\n"); free(keym); return ; }
iniparser_load函数由于太长,限于篇幅,这里我们只具体截出更改的函数:
注释原始的文件描述符 //FILE * in = NULL ; //修改fopen为f_open //if ((in=fopen(ininame, "r"))==NULL) //{ // fprintf(stderr, "iniparser: cannot open %s\n", ininame); // goto out; //} retSD = f_open(&SDFile, ininame, FA_OPEN_EXISTING | FA_READ); if(FR_OK != retSD) { fprintf(stderr, "iniparser: cannot open %s\n", ininame); goto out ; } //修改fgets为f_gets //while (fgets(line, ASCIILINESZ, in)!=NULL) { while(f_gets(line, ASCIILINESZ, &SDFile) != NULL) //修改feof为f_eof // if (line[len]!='\n' && !feof(in)) { if (line[len] != '\n' && !f_eof(&SDFile)) 修改fclose为f_close // if (in) { // fclose(in); f_close(&SDFile);
到这里移植就结束了,非常简单,只需要把对应的接口做替换就可以了。
6、编写测试代码
对应头文件包含
/* USER CODE BEGIN Includes */ #include "iniparser.h" #include "multi_button.h" /* USER CODE END Includes */
定义串口重定向
int fputc(int ch, FILE *stream) { /* 堵塞判断串口是否发送完成 */ while((USART1->ISR & 0X40) == 0); /* 串口发送完成,将该字符发送 */ USART1->TDR = (uint8_t) ch; return ch; }
定义例程对应的全局参数
multi_button相关,用于按键操作 /* USER CODE BEGIN PV */ Button button1, button2; /* USER CODE END PV */ /*文件名*/ #define SETTING_PARA "0:Para.ini" /*默认系统配置信息*/ char *System_Config_Info = "[Setting]\n" "led_flag=1;\n" /*LED标志*/ "plot_flag=0;\n" /*曲线开关*/ "WIFI_NAME=BearPi_ESP8266;\n" /*WIFI热点*/ "WIFI_PASSWORD=12345678;\n" /*WIFI密码*/ ; typedef struct { int led_flag ; int plot_flag ; char wifi_name[32] ; char wifi_password[32]; } info ; info ini_info ; dictionary *Config_ini = NULL; /*系统配置文件全局变量*/ uint8_t retUSER_SYS_CONFIG ; FATFS USERFatFS_SYS_CONFIG ; FIL USER_SYS_CONFIG_File ; /*挂载SD卡*/ int Mount_SD(void) { /*挂载SD卡*/ retSD = f_mount(&SDFatFS, SDPath, 1); if(FR_OK != retSD) return -1 ; return 0 ; } /*创建一个默认的配置文件*/ void Create_Default_InI_File(void) { retUSER_SYS_CONFIG = f_open(&USER_SYS_CONFIG_File, SETTING_PARA, FA_OPEN_ALWAYS | FA_WRITE); if(FR_OK != retUSER_SYS_CONFIG) { fprintf(stderr, "iniparser: cannot open %s\n", SETTING_PARA); return ; } f_printf(&USER_SYS_CONFIG_File, System_Config_Info); f_close(&USER_SYS_CONFIG_File); } /*加载INI文件*/ int Load_Confg_INI_Process(void) { /*加载INI文件*/ Config_ini = iniparser_load(SETTING_PARA); if(NULL == Config_ini) { printf("加载出错\n"); Create_Default_InI_File(); Config_ini = iniparser_load(SETTING_PARA); if(NULL == Config_ini) { printf("创建默认INI文件后继续加载出错\n"); return -1; } } printf("加载INI文件成功\n"); return 0 ; } /*保存参数*/ int INI_Para_Save_Process(void) { /*write config.ini parse*/ retUSER_SYS_CONFIG = f_open(&USER_SYS_CONFIG_File, SETTING_PARA, FA_OPEN_EXISTING | FA_WRITE); if(FR_OK != retUSER_SYS_CONFIG) { printf("iniparser: cannot open %s\n", SETTING_PARA); return -1; } printf("参数设置保存成功\n"); iniparser_dump_ini(Config_ini, &USER_SYS_CONFIG_File); f_close(&USER_SYS_CONFIG_File); iniparser_freedict(Config_ini); return 0 ; } //读取按键1 uint8_t read_button1() { return HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) ; } //读取按键2 uint8_t read_button2() { return HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin) ; } //按键1回调 void button1_callback(void *ptr) { static uint8_t index = 0 ; char *wifi_name_para[] = {"Yangyuanxin", "Bruce_Yang", "BearPi", "mculover666"}; if(index == 4) index = 0 ; printf("\r\n按下KEY1,改变并保存WIFI_NAME参数!\n"); Load_Confg_INI_Process(); iniparser_set(Config_ini, "Setting:WIFI_NAME", wifi_name_para[index]); INI_Para_Save_Process(); printf("设置wifi_name:%s成功\n",wifi_name_para[index]); HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET); ++index ; } //按键2回调 void button2_callback(void *ptr) { char *wifi_name = NULL ; printf("\r\n按下KEY2,读取WIFI_NAME参数!\n"); Load_Confg_INI_Process(); wifi_name = iniparser_getstring(Config_ini, "Setting:WIFI_NAME", "not found"); INI_Para_Save_Process(); memset(ini_info.wifi_name, 0, strlen(ini_info.wifi_name)); memcpy(ini_info.wifi_name, wifi_name, strlen(wifi_name)); printf("wifi_name:%s\n", ini_info.wifi_name); HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET); }
主函数实现:
/** * @brief The application entry point. * @retval int */ int main(void) { /* USER CODE BEGIN 1 */ int ret = -1 ; char *wifi_name = NULL ; char *wifi_password = NULL ; /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_SDMMC1_SD_Init(); MX_USART1_UART_Init(); MX_FATFS_Init(); /* USER CODE BEGIN 2 */ //初始化并注册按键 button_init(&button1, read_button1, 0); button_init(&button2, read_button2, 0); button_attach(&button1, SINGLE_CLICK, button1_callback); button_attach(&button2, SINGLE_CLICK, button2_callback); button_start(&button1); button_start(&button2); //挂载SD卡 ret = Mount_SD(); if(ret != 0) { printf("SD Card mount ERROR\r\n"); HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET); return -1 ; } printf("SD卡挂载成功!\n"); //加载INI文件 ret = Load_Confg_INI_Process(); if(ret != 0) { printf("读取INI文件失败!\r\n"); HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET); return -2 ; } /*加载系统参数*/ ini_info.led_flag = iniparser_getint(Config_ini, "Setting:led_flag", -1); ini_info.plot_flag = iniparser_getint(Config_ini, "Setting:plot_flag", -1); wifi_name = iniparser_getstring(Config_ini, "Setting:WIFI_NAME", "not found"); wifi_password = iniparser_getstring(Config_ini, "Setting:WIFI_PASSWORD", "not found"); memcpy(ini_info.wifi_name, wifi_name, strlen(wifi_name)); memcpy(ini_info.wifi_password, wifi_password, strlen(wifi_password)); /*改变参数led_flag*/ ret = iniparser_set(Config_ini, "Setting:led_flag", "0"); if(ret != 0) { printf("改变参数失败!\n"); return -3 ; } printf("改变参数成功\n"); /*参数保存,并释放内存*/ ret = INI_Para_Save_Process(); if(ret != 0) { printf("写入保存INI文件失败!\r\n"); HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET); return -4 ; } printf("写入保存INI文件成功!\r\n"); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ //以5ms周期调用按键tick button_ticks(); HAL_Delay(5); } /* USER CODE END 3 */ }
注意,在Keil中,把堆栈尽量设大一些,以支持fatfs和iniparse的使用,可以通过CubeMX工程设置:
也可以直接在Keil中设置,但是下次用CubeMX生成的时候参数又会复原噢,建议采用CubeMX工程设置:
7、运行效果
还记得在上上节WIFI配网的粗暴方式,就可以以这种粗暴的方式直接改SSID和PASSWORD,然后产品开机直接就加载SD卡ini文件中的SSID和PASSWORD。
例程下载
链接:https://pan.baidu.com/s/13AJ6Pds4UzNSdkk1PcCGDQ 提取码:7y54 复制这段内容后打开百度网盘手机App,操作更方便哦
往期精彩
超轻量级网红软件定时器multi_timer(51+stm32双平台实战)
基于小熊派WIFI-ESP8266实践(中)-多功能处理显示等大杂烩
基于小熊派光强传感器BH1750实践(multi_timer+状态机工程应用)
基于小熊派光强传感器BH1750状态机驱动项目升级(带LCD屏显示)