# 01 前言
MCU 組成的系統在實際應用中,經常需要記錄系統 LOG 信息,可以是系統不同任務執行情況的 LOG 信息,也可以是內核寄存器等便于維護調試的信息,或者是傳感器的信息等。
上述的這些 LOG 信息,如果在能聯網的系統中,那么直接傳輸回服務器即可,但如果是離線的系統,那么就需要一個存儲設備來記錄這些 LOG 信息。一般有以下幾種方式:
- 記錄到 Nor Flash 等板載存儲器
- 記錄到可移動的存儲器,比如 U 盤等存儲設備
本文提供后一種方式,即數據記錄到 U 盤,這種方式方便進行現場維護,但實際應用中,可以結合 Nor Flash 加 U 盤的方式,避免讀寫 U 盤頻率過快導致損壞。以下內容利用 APM32F407xx 的 OTG Host 和 Fatfs 文件系統,加上 RTC 功能來實現數據記錄,包括以下功能。
- 按日期自動創建文件夾存放 `LOG.xls` 文件
- 按 1 秒的記錄頻率往 `LOG.xls` 文件寫入帶時間戳的數據
- 在 U 盤存儲空間使用超過 90%時,刪除日期最早的文件夾及 LOG 文件
# 02 外設和組件配置
下面簡單介紹一下所用到的外設和組件的配置。
## OTG Host
OTG Host 的配置比較簡單,配置為普通 FS 速度和 MSC Class 類即可。
void USB_HostInitalize(void)
{
/* USB host and class init */
USBH_Init(&gUsbHostFS, USBH_SPEED_FS, &USBH_MSC_CLASS, USB_HostUserHandler);
}
U 盤在枚舉完成后,需要獲取 `LUN` 的信息,以便后續對特定 `LUN` 進行命令和數據讀寫等操作。
有關 MSC Class 的處理在 `usbh_msc.c/h` 文件中的 `USBH_MSC_CLASS` 結構體句柄,其包含以下處理函數:
- `USBH_MSC_ClassInitHandler`:Class 初始化函數。
- `USBH_MSC_ClassDeInitHandler`:Class 解初始化函數。
- `USBH_MSC_ClassReqHandler`:Class 特定請求處理函數,用于處理 `MASS_STORAGE_RESET`、`GET_MAX_LUN` 等特定請求。
- `USBH_MSC_CoreHandler`:Class 內核處理函數,包括 BOT 命令、SCSI 命令的處理。
- `USBH_MSC_SOFHandler`:SOF 事件處理函數。
/* MSC class handler */
USBH_CLASS_T USBH_MSC_CLASS =
{
"Class MSC",
USBH_CLASS_MSC,
NULL,
USBH_MSC_ClassInitHandler,
USBH_MSC_ClassDeInitHandler,
USBH_MSC_ClassReqHandler,
USBH_MSC_CoreHandler,
USBH_MSC_SOFHandler,
};
下面的 `USBH_MSC_Handler` 狀態機由 `USBH_MSC_CoreHandler` 函數調用,包含常見的 BOT 命令、SCSI 命令的處理。
/* USB host MSC state handler function */
USBH_MscStateHandler_T USBH_MSC_Handler[] =
{
USBH_MSC_InitHandler,
USBH_MSC_IdleHandler,
USBH_MSC_InquiryHandler,
USBH_MSC_TestUnitReadyHandler,
USBH_MSC_RequestSenseHandler,
USBH_MSC_ReadCapacityHandler,
USBH_MSC_ErrorUnrecoveredHandler,
USBH_MSC_ReadHandler,
USBH_MSC_WriteHandler,
USBH_MSC_RWRequestSenseHandler,
};
BOT SCSI 命令處理。
OTG Host 部分還需要了解以下 API 函數,因為后續需要應用到 FatFs 文件系統中。
- `USBH_MSC_ReadDevInfo()`:用于獲取所枚舉成功的 `MSC` 設備信息,比如 `LUN` 邏輯單元是否準備完畢、多媒體設備是否存在、扇區數、block 數等。
- `USBH_MSC_DevStatus()`:獲取 `LUN` 是否準備就緒的狀態。
- `USBH_MSC_ReadDevWP()`:獲取 `LUN` 寫保護狀態。
- `USBH_MSC_DevRead()`:讀取 `LUN` 數據。
- `USBH_MSC_DevWrite()`:往 `LUN` 寫入數據。
USBH_STA_T USBH_MSC_ReadDevInfo(USBH_INFO_T* usbInfo, uint8_t lun, USBH_MSC_STORAGE_INFO_T* device);
uint8_t USBH_MSC_DevStatus(USBH_INFO_T* usbInfo, uint8_t lun);
uint8_t USBH_MSC_ReadDevWP(USBH_INFO_T* usbInfo, uint8_t lun);
USBH_STA_T USBH_MSC_DevRead(USBH_INFO_T* usbInfo, uint8_t lun, uint32_t address,
uint8_t* buffer, uint16_t cnt);
USBH_STA_T USBH_MSC_DevWrite(USBH_INFO_T* usbInfo, uint8_t lun, uint32_t address,
uint8_t* buffer, uint16_t cnt);
## RTC
APM32F4xx 的 RTC 是一個獨立的 BCD 定時計數器,提供完整的日歷時鐘功能。不像 F1 系列的 RTC 只是個計數器,需要自行設定元年,然后換算日期和時間。RTC 的配置比較簡單,配置時鐘源為 LSE,24 小時制即可,然后利用 RTC 的備份寄存器來判斷是否需要配置日歷和時間。
void RTC_CalendarConfig(void)
{
RTC_DateTypeDef Date_Structure;
RTC_TimeTypeDef Time_Structure;
/* Configure the Date */
Date_Structure.Year = 0x23;
Date_Structure.Month = RTC_MONTH_NOVEMBER;
Date_Structure.Date = 0x22;
Date_Structure.WeekDay = RTC_WEEKDAY_MONDAY;
if(DAL_RTC_SetDate(&hrtc,&Date_Structure,RTC_FORMAT_BCD) != DAL_OK)
{
DAL_ErrorHandler();
}
/* Configure the Time */
Time_Structure.Hours = 0x00;
Time_Structure.Minutes = 0x00;
Time_Structure.Seconds = 0x00;
Time_Structure.TimeFormat = RTC_HOURFORMAT12_AM;
Time_Structure.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
Time_Structure.StoreOperation = RTC_STOREOPERATION_RESET;
if(DAL_RTC_SetTime(&hrtc,&Time_Structure,RTC_FORMAT_BCD) != DAL_OK)
{
DAL_ErrorHandler();
}
DAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR0, RTC_BKP_VALUE);
}
## TMR
APM32F4xx 系列的 RTC 沒有秒中斷,這里我們實現一秒記錄一次數據到 U 盤,則開啟一個定時為 1 秒的定時器中斷來實現。
void DAL_TMR3_Config(void)
{
htmr3.Instance = TMR3;
htmr3.Init.Period = 10000 - 1;
htmr3.Init.Prescaler = 8400 - 1;
htmr3.Init.ClockDivision = 0;
htmr3.Init.CounterMode = TMR_COUNTERMODE_UP;
htmr3.Init.AutoReloadPreload = TMR_AUTORELOAD_PRELOAD_DISABLE;
if(DAL_TMR_Base_Init(&htmr3) != DAL_OK)
{
DAL_ErrorHandler();
}
}
## FatFs
使用 FatFs 文件系統前,要重新實現 `diskio.c` 及配置 `ffconf.h` 文件。
其中在 `diskio.c` 文件中,要利用 OTG Host 章節最后提到的 `MSC Class` 的 API ,重新實現以下函數。
`DSTATUS disk_status()`
`DSTATUS disk_initialize()`
`DRESULT disk_read()`
`DRESULT disk_write()
`DRESULT disk_ioctl()`
另外,簡單介紹下后面應用代碼中用到的幾個 API,詳細的使用方法,大家可以參考 FatFs 官方文檔。
### f_opendir
該 API 用于打開一個已存在的目錄文件夾。
FRESULT f_opendir (
DIR* dp, /* [OUT] Pointer to the directory object structure */
const TCHAR* path /* [IN] Directory name */
);
### f_readdir
該 API 用于讀取目錄項,后續用于篩選所有的有效目錄。
FRESULT f_readdir (
DIR* dp, /* [IN] Directory object */
FILINFO* fno /* [OUT] File information structure */
);
### f_mkdir
該 API 用于創建一個新的目錄文件夾。
FRESULT f_mkdir (
const TCHAR* path /* [IN] Directory name */
);
### f_open
該 API 用于打開一個文件。要注意的是參數 `mode flags` 需要使用 `FA_OPEN_ALWAYS`,避免文件內容被覆蓋。
FRESULT f_open (
FIL* fp, /* [OUT] Pointer to the file object structure */
const TCHAR* path, /* [IN] File name */
BYTE mode /* [IN] Mode flags */
);
### f_unlink
該 API 用于刪除文件或子目錄。
FRESULT f_unlink (
const TCHAR* path /* [IN] Object name */
);
### f_close
該 API 用于關閉一個文件。
FRESULT f_close (
FIL* fp /* [IN] Pointer to the file object */
);
### f_lseek
該 API 用于移動被打開的文件對象的文件讀或寫指針,后面應用時,需要用來將寫指針移動到文件末,以添加新的 LOG 信息。
FRESULT f_lseek (
FIL* fp, /* [IN] File object */
FSIZE_t ofs /* [IN] Offset of file read/write pointer to be set */
);
### f_printf
該 API 用于將格式化的字符串寫入文件。使用該 API,我們可以方便的將各種數據類型,轉換為字符串,再寫入文件中。
int f_printf (
FIL* fp, /* [IN] File object */
const TCHAR* fmt, /* [IN] Format stirng */
...
);
### f_getfree
該 API 用于獲取 volume 中的剩余空間。使用該 API,我們可以知道 U 盤剩余空間是多少,從而對老舊的 LOG 數據進行處理。
FRESULT f_getfree (
const TCHAR* path, /* [IN] Logical drive number */
DWORD* nclst, /* [OUT] Number of free clusters */
FATFS** fatfs /* [OUT] Corresponding filesystem object */
);
# 03 實現數據記錄
## 定義參數結構體
定義 `rtcInfo` 和 `diskInfo` 來記錄應用過程中的信息。
/* 記錄 RTC 時間信息 */
typedef struct
{
uint16_t year;
uint8_t month;
uint8_t day;
uint8_t hour;
uint8_t minute;
uint8_t second;
} RTC_TIME_INFO_T;
/* 記錄 U 盤和文件系統信息 */
typedef struct
{
char dirPath[512];
uint16_t dirNum;
uint32_t totSect;
uint32_t freSect;
double usedPercent;
} DISK_INFO_T;
RTC_TIME_INFO_T rtcInfo;
DISK_INFO_T diskInfo;
## 獲取時間參數
在 TMR3 回調函數中獲取 RTC 當前時間,并使能數據更新標志。
void RTC_Application(void)
{
char strBuf[50];
RTC_GetCalendar(&rtcInfo.year, &rtcInfo.month, &rtcInfo.day, &rtcInfo.hour, &rtcInfo.minute, &rtcInfo.second);
/* Display time */
sprintf(strBuf, "20%02d-%02d-%02d %02d:%02d:%02d", rtcInfo.year, rtcInfo.month, rtcInfo.day, rtcInfo.hour, rtcInfo.minute, rtcInfo.second);
DAL_LOGI(tag, "%s", strBuf);
}
void DAL_TMR_PeriodElapsedCallback(TMR_HandleTypeDef *htmr)
{
if(htmr->Instance == TMR3)
{
RTC_Application();
dataUpdate = 1;
}
}
## 記錄數據
### 提供日期和時間給 FatFs
FatFs 系統的文件和目錄的日期、時間信息由 `get_fattime` 函數提供。該函數為 `__weak` 定義,我們在應用文件中重新實現即可,這里要注意 APM32F4xx 的 RTC 日歷的起始年份和 FatFs 的不一致,做好偏移即可。
DWORD get_fattime(void)
{
return (DWORD)(rtcInfo.year + 20) << 25 |
(DWORD)rtcInfo.month << 21 |
(DWORD)rtcInfo.day << 16 |
(DWORD)rtcInfo.hour << 11 |
(DWORD)rtcInfo.minute << 5 |
(DWORD)rtcInfo.second >> 1;
}
### 創建和寫數據到 Excel 文件
下面這個函數中,首先創建一個目錄,然后創建或打開一個 `.xls` 文件,接著打開文件并用 `f_lseek` 函數移動寫指針到文件末,最后用 ` f_printf ` 函數寫入格式化后的字符串數據。
void FATFS_WriteXlsFile(FIL* file, const TCHAR *path, char *dir, char *fileName, char* logInfo, char* timeStamp)
{
FRESULT status;
char filePath[32];
char fileDir[20];
/* Write file */
sprintf(fileDir, "%s", dir);
status = f_mkdir(fileDir);
if (status == FR_OK)
{
DAL_LOGI(tag, ">>> Create direction success");
}
/* Open or create file */
sprintf(filePath, "%s%s%c%s.xls", path, fileDir, '/', fileName);
status = f_open(file, filePath, FA_OPEN_ALWAYS | FA_WRITE);
if (status == FR_OK)
{
DAL_LOGI(tag, ">>> Open or create %s %s file success", fileDir, fileName);
/* Move the file pointer to the end */
f_lseek(file, f_size(file));
f_printf(file, "%s + %s + %s ", fileName, logInfo, timeStamp);
}
else
{
DAL_LOGE(tag, ">>> Open or create file fail, status is %d", status);
}
/* Close file */
f_close(file);
}
### 獲取 U 盤剩余空間信息
Volume 的剩余空間,從之前 FatFs 章節中知道,可以用 `f_getfree` 函數來獲取 U 盤剩余空間信息,并轉換為使用率。
void MSC_Application(void)
{
...
res = f_getfree(USBHPath, &freClust, &fs);
diskInfo.totSect = (fs->n_fatent - 2) * fs->csize;
diskInfo.totSect /= 2;
diskInfo.freSect = freClust * fs->csize;
diskInfo.freSect /= 2;
diskInfo.usedPercent = (1.0 - (double)diskInfo.freSect / diskInfo.totSect) * 100.0;
...
}
### 刪除舊文件夾
以下代碼,使用 `FATFS_ViewRootDir()` 函數獲取目錄文件夾信息,并按日期進行排序輸出。最后由 `sscanf()` 來找到第一個目錄,然后刪除目錄和文件。
void MSC_Application(void)
{
...
/* Get dir information */
DAL_LOGI(tag, "------> Get dir information");
FATFS_ViewRootDir(&USBHFatFS, USBHPath, diskInfo.dirPath, &diskInfo.dirNum);
DAL_LOGI(tag, ">>> dir path :%s", diskInfo.dirPath);
DAL_LOGI(tag, ">>> dir number :%d", diskInfo.dirNum);
/* Used percent > 90% and dir number > 1 */
if((diskInfo.usedPercent - DISK_SPACE_MAX_PERCENT) > EPS && (diskInfo.dirNum > 1))
{
DAL_LOGW(tag, "<<< Disk space is not enough");
/* Delete first dir */
sscanf(diskInfo.dirPath, "/%8[^/]", delectDirName);
FATFS_DelectDir(USBHPath, delectDirName);
}
...
}
# 04 下載驗證
經過之前幾節的組件的配置,實現數據記錄的 API 函數后,最后下載到 APM32F4xx 開發板看下應用的過程。
從下面 LOG 信息可以看到,插入 U 盤后,從 UART1 輸出了 `MSC device is ready` 的 LOG 信息,這表明開發板已完成 U 盤的枚舉識別。
同時可以看到 LOG 中帶有時間戳信息,證明 RTC 已經開發工作,并按每秒打印一次的頻率輸出時間戳 LOG 信息。
...
[main] 2023-11-22 0037
[main] 2023-11-22 0038
[main] 2023-11-22 0039
[main] 2023-11-22 0040
USB Device Reset Completed
USB device speed is FS
PID: 0x1234
VID: 0x048D
Endpoint 0 max packet size if 64
USB device address: 1
Manufacturer: USB
Product: Disk 2.0k
SerialNumber: 9207156342331724518
USB device enumration ok
This is a Mass Storage device
Use 2 endpoint:
Endpoint 0x01: max packet size is 64 bytes
Endpoint 0x82: max packet size is 64 bytes
USB device has only one configuration
Set to default configuration
Inquiry Revision :2.00
Inquiry Product :ProductCode
Inquiry Vendor :VendorCo
MSC device is ready
MSC device capacity : 1006390784 bytes
MSC device block number : 1965607
MSC device block size : 512
Class is ready
緊接著,就會進行 LOG 信息的記錄,從下面 LOG 可以看到,FatFs 的操作過程。首先在 U 盤加載文件系統,然后創建或打開以 `20231122` 日期命名的文件夾并寫入數據。接著獲取 U 盤剩余空間,獲取目錄信息,最后卸載文件系統。
[main] 2023-11-22 0012
[main] ------> Update information
[main] ------> Mount U disk file system
[main] ------> Write xls file
[fatfs] >>> Open or create 20231122 LOG file success
[main] ------> Get volume information
[main] >>> (964657 / 966404) KiB Used 0.18 %
[main] ------> Get dir information
[fatfs] >>> View Directory
[fatfs] >>> 20231122, (0x10)
[fatfs] >>> 20231031, (0x10)
[fatfs] >>> 20231030, (0x10)
[fatfs] >>> 20231101, (0x10)
[fatfs] >>> Sort Directory
[main] >>> dir path :/20231030/20231031/20231101/20231122
[main] >>> dir number :4
[main] ------> Unmount U disk file system
[main] >>> U disk file system unmounted ok
我們打開 U 盤可以看到已經在設定的文件夾中創建了一個命名為 `LOG.xls` 的 Excel 文件。
以 LOG + RTC 時間戳為內容寫入到 Excel 文件中。實際應用時記錄的信息可以是系統不同任務執行情況,也可以是內核寄存器等便于維護調試的信息,或者是傳感器的信息等。
以上,就是用 U 盤記錄系統 LOG 信息的一些簡單步驟和方法,實際應用中還要考慮更多情況,附件是源碼,供大家參考。
審核編輯:彭菁
-
存儲器
+關注
關注
38文章
7492瀏覽量
163834 -
數據
+關注
關注
8文章
7030瀏覽量
89034 -
U盤
+關注
關注
7文章
489瀏覽量
63260 -
Log
+關注
關注
0文章
14瀏覽量
11330
原文標題:APM32芯得 EP.36 | APM32F4實現用U盤記錄LOG信息
文章出處:【微信號:geehysemi,微信公眾號:Geehy極海半導體】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論