INI文件以良好的可阅读与修改特性,在程序运行初期,能够提供一些初始变量。使用C++进行程序编写过程中,微软官方提供了良好的文件操作API,便于直接使用。但是在嵌入式操作系统中,缺乏一套良好的方法。现参考https://github.com/wernsey/rengine/blob/master/src/ini.c提供的方式,结合STM32进行实现。另附工程源码。
本文基于RTX嵌入式操作系统和FatFs文件操作系统,实现对INI文件的读写操作。
1. 采用STM32CubeMX生成相关的底层库,需要配置U盘、SD卡以及FatFs。
2. 打开配置好的工程文件,并添加RTX操作系统。
3. 添加Thread.c、ini.c以及utils.c文件
4. 修改ini.c以及utils.c文件,以适应FatFs操作系统
首先需要添加#include "fatfs.h"头文件,另外由于ini.c中运用了大量的断言语句,所以需要结合Keil自身的断言编写__aeabi_assert这个函数。
void __aeabi_assert(const char *s1, const char *s2, int len)
{
printf(">> error: (%s). function: %s, line %d\r\n", s1, s2, len);
}
其次,将源文件中关于文件API接口统一修改为FatFs操作系统的API接口,主要有以下几类函数
a. 变量 FILE *f -> FIL *f = &SDFile
b. 文件打开操作 f = fopen(fname, "w"); -> f_open(f, fname, FA_WRITE)
c. 文件关闭操作 fclose(f) -> f_close(f)
d. 字符串写入操作 fputs("]\n", f) -> f_puts("]\n", f)
e. 读文件函数
char *my_readfile(const char *fname)
{
FILE *f;
long len, r;
char *str;
if (!(f = fopen(fname, "rb")))
return NULL;
fseek(f, 0, SEEK_END);
len = ftell(f);
rewind(f);
if (!(str = malloc(len + 2)))
return NULL;
r = fread(str, 1, len, f);
if (r != len)
{
free(str);
return NULL;
}
fclose(f);
str[len] = '\0';
return str;
}
修改为
char *my_readfile(const char *fname)
{
FIL *f = &SDFile;
unsigned int len, r;
char *str;
if (f_open(f, fname, FA_READ) != FR_OK)
{
return NULL;
}
len = f_size(f);
str = malloc(len + 2);
if (str == NULL)
{
return NULL;
}
f_read(f, str, len, &r);
if(r!=len)
{
free(str);
return NULL;
}
f_close(f);
str[len] = '\0';
return str;
}
这样就完成了对ini.c以及utils.c文件的移植
5. 修改usbd_storage_if.c文件,以适配电脑读取操作
6. 在Thread.c文件中,编写相关读写函数入下
#include "cmsis_os2.h"
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include "usart.h"
#include "ini.h"
#include "fatfs.h"
/*----------------------------------------------------------------------------
* Thread 1 'Thread_Name': Sample thread
*---------------------------------------------------------------------------*/
int fputc(int ch, FILE *file)
{
HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, 1000);
return ch;
}
typedef enum
{
TSK_RECORD,
TSK_MAX,
} TSK_ID_e;
#define T(x) \
case x: \
return #x
char *Fil_RetInfo(FRESULT ret)
{
switch (ret)
{
T(FR_OK);
T(FR_DISK_ERR);
T(FR_INT_ERR);
T(FR_NOT_READY);
T(FR_NO_FILE);
T(FR_NO_PATH);
T(FR_INVALID_NAME);
T(FR_DENIED);
T(FR_EXIST);
T(FR_INVALID_OBJECT);
T(FR_WRITE_PROTECTED);
T(FR_INVALID_DRIVE);
T(FR_NOT_ENABLED);
T(FR_NO_FILESYSTEM);
T(FR_MKFS_ABORTED);
T(FR_TIMEOUT);
T(FR_LOCKED);
T(FR_NOT_ENOUGH_CORE);
T(FR_TOO_MANY_OPEN_FILES);
T(FR_INVALID_PARAMETER);
default:
return "Erro Status";
}
}
#undef T
#define FF_INFO(x) printf(">> Fatfs Status(%s).\r\n", Fil_RetInfo(sta));
#define IF_RET_BOOL(x, sta) \
retSD = x; \
if ((retSD != sta)) \
{ \
printf(">> Fatfs Error(%s). Fun %s, Line %d.\r\n", Fil_RetInfo((FRESULT)retSD), __FUNCTION__, __LINE__); \
return false; \
}
osThreadId_t tskId[TSK_MAX];
unsigned char USB_IsConected(void);
DIR dataDir;
const char dirData[] = "Data";
const char dirTask[] = "Task";
const char dirInit[] = "Init";
const char filData[] = "Data.txt";
const char filTask[] = "Task.auv";
const char filInit[] = "Init.ini";
const char *dirContent[] = {dirData, dirTask, dirInit};
const char *filName[] = {filData, filTask, filInit};
char *filContent[3];
bool isSDLoadOK = false;
bool isDirExist[3] = {false};
/* 加载 SD 卡
*
*/
bool SD_Load(void)
{
IF_RET_BOOL(f_mount(&SDFatFS, SDPath, FM_FAT32), FR_OK);
return true;
}
/* 创建子目录
*
*/
bool Dir_CreateOpen(const char *name)
{
if (!isSDLoadOK)
{
return false;
}
retSD = f_stat(name, NULL);
if (retSD == FR_OK)
{
IF_RET_BOOL(f_opendir(&dataDir, name), FR_OK);
IF_RET_BOOL(f_closedir(&dataDir), FR_OK);
}
else
{
IF_RET_BOOL(f_mkdir(name), FR_OK);
IF_RET_BOOL(f_opendir(&dataDir, name), FR_OK);
IF_RET_BOOL(f_closedir(&dataDir), FR_OK);
}
return true;
}
/* 创建文件目录名称
*
*/
bool Fil_CreateName(const char *s1, const char *s2, char **ret)
{
if (ret[0] != NULL)
return false;
const char spliter[] = "/";
ret[0] = calloc(strlen(s1) + strlen(s2) + 2 + 1, sizeof(char));
if (ret[0] == NULL)
return false;
strcpy(ret[0], spliter);
strcat(ret[0], s1);
strcat(ret[0], spliter);
strcat(ret[0], s2);
return true;
}
bool Fil_Create(bool isDirOK, char *path)
{
if (isDirOK)
{
if (f_stat(path, NULL) != FR_OK)
{
IF_RET_BOOL(f_open(&SDFile, path, FA_CREATE_NEW), FR_OK);
IF_RET_BOOL(f_close(&SDFile), FR_OK);
}
return true;
}
else
{
return false;
}
}
void Thread_Record(void *arg)
{
uint32_t ulTim = 0;
while (1)
{
ulTim = osKernelGetTickCount();
osDelayUntil(ulTim + 100);
}
}
bool test(void);
int Thread_Init(void)
{
printf("=== %s %s === Init Test ===\r\n", __TIME__, __DATE__);
isSDLoadOK = SD_Load();
for (unsigned char i = 0; i < 3; i++)
{
isDirExist[i] = Dir_CreateOpen(dirContent[i]);
Fil_CreateName(dirContent[i], filName[i], &filContent[i]);
Fil_Create(isDirExist[i], filContent[i]);
}
test();
tskId[TSK_RECORD] = osThreadNew(Thread_Record, NULL, NULL);
if (tskId[TSK_RECORD] == 0)
return -1;
return 0;
}
static struct ini_file *iniTst = NULL;
double p1;
char numToStrBuf[15];
bool test()
{
int err, line;
p1 = 1234.56789111;
sprintf(numToStrBuf,"%lf", p1);
iniTst = ini_read(filContent[2], &err, &line);
ini_put(iniTst, "Tst1", "a", "0");
ini_put(iniTst, "Tst2", "b", "0.0");
ini_put(iniTst, "Tst2", "c", "0.0");
ini_put(iniTst, "Tst2", "d", "0.0");
ini_put(iniTst, "Tst3", "b", "0.0");
ini_put(iniTst, "Tst3", "c", "0.0");
ini_put(iniTst, "Tst3", "d", numToStrBuf);
int result = ini_write(iniTst, filContent[2]);
if (result != SUCCESS)
{
printf(">> function %s, line %d\r\n", __FUNCTION__, __LINE__);
}
else
{
printf(">> ok\r\n");
}
ini_free(iniTst);
iniTst = ini_read(filContent[2], &err, &line);
p1 = atof(ini_get(iniTst,"Tst3", "d", NULL));
printf(">> p1 = %lf, numToStrBuf = %s\r\n", p1, numToStrBuf);
ini_free(iniTst);
iniTst = NULL;
return true;
}
7. 程序运行结果如下
a. 串口打印图示
b. U盘插入电脑中图示
至此,基于C语言加载ini文件方式介绍完毕。