基于小熊派SD卡+Fatfs+移植开源iniparse解析库并使用

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 基于小熊派SD卡+Fatfs+移植开源iniparse解析库并使用

在实际产品开发过程中,我们常常会对一些产品的内置参数进行存储,比如:

  • 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 时钟配置

这里我选择是外部时钟。

640.png

640.png

4.2 串行调试接口配置

640.png

4.3 SD卡接口参数配置

以下是SD卡接口在小熊派上的电路原理图。

640.png

对应主控MCU管脚的连接

640.png

由于这里只有一条输出D0输出线,所以在CubeMX上选择SD 1bit模式,其余参数默认。

640.png

4.4 配置调试串口

640.png

4.5 配置SD卡支持Fatfs

640.png

4.6 配置一路调试灯+2个按键

我们通过两个按键来实现更改参数和读取参数,并且用LED来提示。

640.png

最后生成代码即可 。

5、下载ini_parse库到生成的代码中并进行移植以支持fatfs

640.png

将对应的库文件包含到工程源码中:

640.png

由于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工程设置:

640.png

也可以直接在Keil中设置,但是下次用CubeMX生成的时候参数又会复原噢,建议采用CubeMX工程设置:

640.png

7、运行效果

640.png

640.png

还记得在上上节WIFI配网的粗暴方式,就可以以这种粗暴的方式直接改SSID和PASSWORD,然后产品开机直接就加载SD卡ini文件中的SSID和PASSWORD。

640.png

基于小熊派WIFI-ESP8266实践(上)

例程下载

链接:https://pan.baidu.com/s/13AJ6Pds4UzNSdkk1PcCGDQ
提取码:7y54
复制这段内容后打开百度网盘手机App,操作更方便哦

往期精彩

网红物联网开发板小熊派使用评测


基于小熊派WIFI-ESP8266实践(上)


超轻量级网红软件定时器multi_timer(51+stm32双平台实战)


基于小熊派WIFI-ESP8266实践(中)-多功能处理显示等大杂烩


基于小熊派光强传感器BH1750实践(multi_timer+状态机工程应用)


基于小熊派光强传感器BH1750状态机驱动项目升级(带LCD屏显示)


基于小熊派光强传感器BH1750状态机驱动项目再度升级(带上位机曲线显示)

目录
相关文章
|
21天前
|
数据采集 JavaScript API
网页解析库:BeautifulSoup与Cheerio的选择
网页解析库:BeautifulSoup与Cheerio的选择
|
1月前
|
自然语言处理 并行计算 数据可视化
免费开源法律文档比对工具:技术解析与应用
这款免费开源的法律文档比对工具,利用先进的文本分析和自然语言处理技术,实现高效、精准的文档比对。核心功能包括文本差异检测、多格式支持、语义分析、批量处理及用户友好的可视化界面,广泛适用于法律行业的各类场景。
|
25天前
|
存储 Go PHP
Go语言中的加解密利器:go-crypto库全解析
在软件开发中,数据安全和隐私保护至关重要。`go-crypto` 是一个专为 Golang 设计的加密解密工具库,支持 AES 和 RSA 等加密算法,帮助开发者轻松实现数据的加密和解密,保障数据传输和存储的安全性。本文将详细介绍 `go-crypto` 的安装、特性及应用实例。
64 0
|
2月前
|
SQL Oracle 关系型数据库
SQL整库导出语录:全面解析与高效执行策略
在数据库管理和维护过程中,整库导出是一项常见的需求,无论是为了备份、迁移还是数据分析,掌握如何高效、准确地导出整个数据库至关重要
|
2月前
|
前端开发 JavaScript
pyquery:一个灵活方便的 HTML 解析库
pyquery:一个灵活方便的 HTML 解析库
28 1
|
3月前
|
XML JSON 网络协议
超级好用的C++实用库之字节流解析器
超级好用的C++实用库之字节流解析器
41 3
|
3月前
|
监控 数据可视化 搜索推荐
万界星空科技商业开源MES系统全面解析
万界星空MES系统支持对生产现场的实时监控,包括设备运行状态、生产进度、质量数据等关键信息的即时反馈。通过可视化的数据展示,管理者能够直观掌握生产全貌,及时发现问题并采取措施
88 5
|
3月前
|
数据采集 存储 JSON
从零到一构建网络爬虫帝国:HTTP协议+Python requests库深度解析
在网络数据的海洋中,网络爬虫遵循HTTP协议,穿梭于互联网各处,收集宝贵信息。本文将从零开始,使用Python的requests库,深入解析HTTP协议,助你构建自己的网络爬虫帝国。首先介绍HTTP协议基础,包括请求与响应结构;然后详细介绍requests库的安装与使用,演示如何发送GET和POST请求并处理响应;最后概述爬虫构建流程及挑战,帮助你逐步掌握核心技术,畅游数据海洋。
76 3
|
3月前
|
缓存 网络协议 分布式数据库
超级好用的C++实用库之DNS解析
超级好用的C++实用库之DNS解析
71 0
|
3月前
|
设计模式 存储 算法
PHP中的设计模式:策略模式的深入解析与应用在软件开发的浩瀚海洋中,PHP以其独特的魅力和强大的功能吸引了无数开发者。作为一门历史悠久且广泛应用的编程语言,PHP不仅拥有丰富的内置函数和扩展库,还支持面向对象编程(OOP),为开发者提供了灵活而强大的工具集。在PHP的众多特性中,设计模式的应用尤为引人注目,它们如同精雕细琢的宝石,镶嵌在代码的肌理之中,让程序更加优雅、高效且易于维护。今天,我们就来深入探讨PHP中使用频率颇高的一种设计模式——策略模式。
本文旨在深入探讨PHP中的策略模式,从定义到实现,再到应用场景,全面剖析其在PHP编程中的应用价值。策略模式作为一种行为型设计模式,允许在运行时根据不同情况选择不同的算法或行为,极大地提高了代码的灵活性和可维护性。通过实例分析,本文将展示如何在PHP项目中有效利用策略模式来解决实际问题,并提升代码质量。

推荐镜像

更多
下一篇
DataWorks