串口數據記錄儀DIY,體積小,全開源

作用

產品到客戶現場出現異常情況,這個時候就需要一個日志記錄儀、黑匣子,可以記錄產品的工作情況,當出現異常時,可以搜集到上下文的數據,從而判斷問題原因。

之前從網上買過,但是出現過丟數據的情況耽誤了問題分析,自此以后就一直心有懷疑不敢全信它了,后面都是掛兩個來交叉對比,生怕又被坑。于是索性乘自己有空來做一個。
在這里插入圖片描述

軟硬件開源地址:https://gitee.com/qlexcel/serial-port-data-logger

硬件

在這里插入圖片描述

在這里插入圖片描述
尺寸長38mm,寬21mm
在這里插入圖片描述

使用

找一張TF卡,格式化為FAT32格式。沒有的話可以網上買,8G的8塊錢包郵。比如: TF卡

記錄儀開機的時候會讀取SD中的配置文件config.txt,獲取用戶配置。如果沒有讀取成功或者參數異常,就會恢復默認配置。
恢復默認配置后,config.txt文件內容就會被覆蓋為以下內容:

baud:115200,stopbit:0,parity:0,filemb:30,time:0,timeout:4,filename:log,

來解釋下配置的作用:

baud:115200,  //波特率  9600  115200  921600
stopbit:0,    //停止位    =0,1停止位  =1,0.5停止位  =2,2停止位  =3,1.5停止位
parity:0,     //奇偶校驗位  =0,無校驗  =2,偶校驗  =3,奇校驗
filemb:30,    //日志文件最大大小,單位MB
time:0,       //是否添加時間戳  =1,添加  =0,不添加
timeout:4,    //超時時間  超過此時間沒有收到新數據,就會關閉日志文件,此時可以安全拔出SD卡
filename:log, //日志文件名 

LED燈的作用:
綠燈閃爍表示接收到數據。
藍燈亮起表示正在保存數據到SD卡。
藍燈快閃表示出現故障。

軟件

如下是main函數代碼,功能實現都在這里

#include "bsp.h"/*
最好每次抓數據時,把SD中的log文件清空
*//******************************************* 宏定義區域 ****************************************/
#define QL_SUCCESS          0       //函數執行成功
#define QL_FAIL             1       //函數執行失敗
#define QL_ERROR_SD_CARD    2       //SD卡故障
#define QL_ERROR_FILE_SYS   3       //文件系統故障#define CFG_FILE_NAME "config.txt"  //配置文件名
#define FILE_NAME     "log"         //默認的日志文件名
#define DEBUG_STA 1                 //=1,打開調試模式   =0,關閉調試模式
#define WR_BUF_LEN 14000            //寫數據buf大小/******************************************* 變量定義區域 ****************************************/
FATFS fs;
FIL fsrc; 
uint8_t wrbuffer[WR_BUF_LEN];        //寫數據buf 
//                      1   2       4   5       7   8      10  11  
uint8_t  Date[14]={'[','0','0','_','0','1',':','0','1',':','0','1',']',0};
uint8_t  TimeStamp;                  //是否添加時間戳  =1,添加  =0,不添加
uint8_t  Day=0;                      //天數
uint8_t  PastSecond;                 //多長時間沒有接收到數據,單位秒
uint8_t  TimeOut=4;                  //超時時間
uint8_t  FileOpenSta=0;              //日志文件打開狀態
uint32_t BaudRate=115200;            //波特率
uint8_t  StopBits=0;                 //停止位    =0,1停止位  =1,0.5停止位  =2,2停止位  =3,1.5停止位
uint8_t  Parity=0;                   //奇偶校驗位  =0,無校驗  =2,偶校驗  =3,奇校驗
uint16_t Head,Tail,len;
uint32_t TotalLen=0,FileMaxLen;
uint32_t len_bk,DateWr,DateWrOld;
char  FileName[11];                  //日志文件名字
uint8_t FileNameIndex=0;             //日志文件名字編號
uint32_t LedG_Blink_Cnt;             //接收到數據綠燈閃爍計數
uint8_t  LedG_Blink_Flg;             //接收到數據綠燈閃爍標志/******************************************* 函數聲明區域 ****************************************/
uint8_t Filesystem_Handle(void);
uint8_t GetstrstrValue(uint8_t* str, char* substr, uint8_t* out, uint16_t MaxLen);
uint32_t MyStr2Int(uint8_t* str, uint8_t dot);int main(void)   //監視下文件系統每次寫扇區個數
{  	uint8_t res,tmp;mGPIO_Init();	RTC_Init();res=Filesystem_Handle();UART_Init();printf("/** Uart Data Saver V1.0**/\r\n");if(res==QL_ERROR_SD_CARD)printf("SD Card Error!\r\n");else if(res==QL_ERROR_FILE_SYS)printf("File System Error!\r\n");else if(res)printf("Other Error!\r\n");elseprintf("Init ok!\r\n");while(res)          //初始化不成功,就死循環閃燈提示{		gd_eval_led_toggle(LED_BLUE);delay_1ms(200);}		LED_G_OFF();LED_B_OFF();while(1){		Head=RX_BUF_LEN-DMA_CHCNT(X1_UART_DMA, X1_UART_DMA_CH_RX);  //DMA已經接收到的數據個數 		if(Head!=Tail)  //如果接收到數據{wrbuffer[len++]=rxbuffer[Tail++];if(Tail==RX_BUF_LEN) Tail=0;if(TimeStamp && DateWrOld!=DateWr)                      //時間戳打開 且 寫入的時間發生了改變{if(wrbuffer[len-1]=='\n' || wrbuffer[len-1]=='\r')  //遇到換行,寫入一次時間{tmp=0;while(tmp<13){wrbuffer[len++]=Date[tmp++];DateWrOld=DateWr;}
#if DEBUG_STA					printf("%s\r\n",Date);
#endif					}}PastSecond=0;LedG_Blink_Flg=1;   //接收到數據,LED閃爍提示}if(len>(WR_BUF_LEN-5))  //當接收數據比較多時,就寫入一次SD卡。數據寫完后,日志文件不關閉{LED_B_ON();if(FileOpenSta==0)  //如果日志文件沒有打開{res = f_open(&fsrc,FileName,FA_WRITE);f_lseek(&fsrc, f_size(&fsrc));FileOpenSta=1;}res = f_write(&fsrc,wrbuffer,len,&len_bk);if(res) break;printf("wr%d\r\n",len);	TotalLen+=len;if(TotalLen>FileMaxLen)   //如果數據大小超過了文件最大{tmp=strlen(FileName);  //文件編號+1FileNameIndex++;FileName[tmp-7]=FileNameIndex/100+'0';FileName[tmp-6]=FileNameIndex%100/10+'0';FileName[tmp-5]=FileNameIndex%10+'0';f_close(&fsrc);res = f_open(&fsrc,FileName,FA_WRITE|FA_OPEN_ALWAYS);  //打開新文件f_lseek(&fsrc, f_size(&fsrc));TotalLen=0;}len=0;	LED_B_OFF();						}else if(PastSecond>TimeOut && (len>0 || FileOpenSta==1))  //沒有新數據一段時間后,如果還有數據沒寫入,就立即寫入{                                                          //或者日志文件處于打開狀態,就關閉LED_B_ON();if(len>0)  //有數據沒寫入,就立即寫入{if(FileOpenSta==0)  //沒有打開文件{	res = f_open(&fsrc,FileName,FA_WRITE);f_lseek(&fsrc, f_size(&fsrc));FileOpenSta=1;				}				res = f_write(&fsrc,wrbuffer,len,&len_bk);if(res) break;}if(FileOpenSta==1){	f_close(&fsrc);FileOpenSta=0;}			printf("wr%d\r\n",len);	TotalLen+=len;len=0;	LED_B_OFF();			}if(LedG_Blink_Flg)  //接收到數據,LED閃爍提示{LedG_Blink_Cnt++;if(LedG_Blink_Cnt>30000){LedG_Blink_Cnt=0;if(gpio_input_bit_get(LED_G_PORT, LED_G_PIN))  //如果IO是高,LED滅的狀態{LED_G_ON();}else  //如果IO是低,LED亮的狀態{LED_G_OFF();LedG_Blink_Flg=0;}}}if(RTC_CTL & RTC_FLAG_SECOND)          //秒中斷標志{RTC_CTL &= ~RTC_FLAG_SECOND;       //清除中斷標志DateWr=(RTC_CNTH << 16)|RTC_CNTL;  //獲取當前時間if (DateWr == 86399)               //當時間到達23:59:59時清零,天數加1{RTC_CTL |= RTC_CTL_CMF;RTC_CNTH=0;RTC_CNTL=0;RTC_CTL &= ~RTC_CTL_CMF;Day++;rtc_lwoff_wait();}	PastSecond++;Date[1]=Day%100/10+'0';Date[2]=Day%10+'0';			tmp=DateWr/3600;     //hoursDate[4]=tmp/10+'0';Date[5]=tmp%10+'0';tmp=DateWr%3600/60;  //minutesDate[7]=tmp/10+'0';Date[8]=tmp%10+'0';			tmp=DateWr%60;       //seconds	Date[10]=tmp/10+'0';Date[11]=tmp%10+'0';	
//			printf("%s\r\n",Date);}			}while(1)  //當文件系統有問題,就會跳出上面循環到達這里,閃燈提示{		gd_eval_led_toggle(LED_BLUE);delay_1ms(50);}	
}#define CONFIG_DEFAULT_LEN 71
static const char CONFIG_DEFAULT[CONFIG_DEFAULT_LEN]="baud:115200,stopbit:0,parity:0,filemb:30,time:0,timeout:4,filename:log,";
uint8_t Filesystem_Handle(void)
{uint8_t res,i;res=SD_Init();if(res) return QL_ERROR_SD_CARD;          //SD卡有問題res=f_mount(&fs,"0:",1);if(res!=FR_OK) return QL_ERROR_FILE_SYS;  //文件系統有問題res = f_open(&fsrc,CFG_FILE_NAME,FA_READ); //以只讀方式打開配置文件if(res==FR_OK)                             //打開成功,說明文件存在{res = f_read(&fsrc,rxbuffer,200,&len_bk);f_close(&fsrc);if(res==FR_OK && len_bk>50)            //數據讀取成功且大小正常{res |= GetstrstrValue(rxbuffer,"baud:",wrbuffer,10);      //波特率BaudRate=MyStr2Int(wrbuffer,0);res |= GetstrstrValue(rxbuffer,"stopbit:",wrbuffer,10);   //停止位StopBits=MyStr2Int(wrbuffer,0);res |= GetstrstrValue(rxbuffer,"parity:",wrbuffer,10);    //校驗位Parity=MyStr2Int(wrbuffer,0);res |= GetstrstrValue(rxbuffer,"filemb:",wrbuffer,10);    //文件大小 FileMaxLen=MyStr2Int(wrbuffer,0);res |= GetstrstrValue(rxbuffer,"time:",wrbuffer,10);      //時間戳   TimeStamp=MyStr2Int(wrbuffer,0);res |= GetstrstrValue(rxbuffer,"timeout:",wrbuffer,10);   //超時事件TimeOut=MyStr2Int(wrbuffer,0);res |= GetstrstrValue(rxbuffer,"filename:",wrbuffer,10);  //日志文件名strcpy(FileName,(char*)wrbuffer);}elseres=1;}if(res!=FR_OK)  //如果配置文件讀取失敗,使用默認配置參數{BaudRate=115200;StopBits=0;Parity=0;FileMaxLen=30;TimeStamp=0;TimeOut=4;strcpy(FileName,FILE_NAME);res = f_open(&fsrc,CFG_FILE_NAME,FA_WRITE|FA_CREATE_ALWAYS);      //把默認參數寫入SD卡res |= f_write(&fsrc,CONFIG_DEFAULT,CONFIG_DEFAULT_LEN,&len_bk);		f_close(&fsrc);if(res!=FR_OK) return QL_FAIL;	for(i=0;i<100;i++)  //配置參數被恢復默認了,要閃燈提示,避免用戶不知道導致數據丟{		gd_eval_led_toggle(LED_BLUE);delay_1ms(50);gd_eval_led_toggle(LED_GREEN);}			}FileMaxLen<<=20;  //總大小單位由MB變成字節i=strlen(FileName);FileName[i]  =FileNameIndex/100+'0';     //日志文件名編號,第1個文件名是log000.txt,當超過文件大小,保存到第2個文件log001.txtFileName[i+1]=FileNameIndex%100/10+'0';FileName[i+2]=FileNameIndex%10+'0';FileName[i+3]='.';FileName[i+4]='t';FileName[i+5]='x';FileName[i+6]='t';FileName[i+7]=0;res = f_open(&fsrc,FileName,FA_OPEN_ALWAYS);  //如果日志文件存在,則打開該文件。 如果沒有,將創建一個新文件。f_close(&fsrc); 	if(res!=FR_OK) return QL_FAIL;elsereturn QL_SUCCESS;
}/*********************************************************************************************************
* 功能說明: 在字符串str中匹配子字符串,并獲取子字符串后面的字符串
*********************************************************************************************************/
uint8_t GetstrstrValue(uint8_t* str, char* substr, uint8_t* out, uint16_t MaxLen)
{uint16_t len = 0;char *p1,*p2;while(*str != 0){p1 = (char*)str;p2 = substr;while(*p1 && *p2 && *p1 == *p2){p1++;p2++;}if (*p2 == 0){p2 = (char*)out;while (*p1 != ','){*p2 = *p1;p1++;p2++;len++;if (len >= MaxLen)return QL_FAIL;}*p2 = 0;return QL_SUCCESS;}str++;}return QL_FAIL;
}/*********************************************************************************************************
* 功能說明: 字符串轉整型數
* 形    參: str:字符串
*           dot:小數點  =0表示不放大  =2表示放大100倍
* 返 回 值: 整型數
*********************************************************************************************************/
uint32_t MyStr2Int(uint8_t* str, uint8_t dot)
{int out = 0;while (*str != 0){out *= 10;out += *str - '0';str++;if (*str == '.'){if (dot == 2){str++;out *= 10;if (*str >= '0' && *str <= '9')out += *str - '0';str++;out *= 10;if (*str >= '0' && *str <= '9')out += *str - '0';return out;}elsereturn out;}}if (dot == 2)out *= 100;return out;
}

可以看到main函數中,先初始化IO、RTC后,就執行Filesystem_Handle函數。

	mGPIO_Init();	RTC_Init();res=Filesystem_Handle();UART_Init();printf("/** Uart Data Saver V1.0**/\r\n");if(res==QL_ERROR_SD_CARD)printf("SD Card Error!\r\n");else if(res==QL_ERROR_FILE_SYS)printf("File System Error!\r\n");else if(res)printf("Other Error!\r\n");elseprintf("Init ok!\r\n");while(res)          //初始化不成功,就死循環閃燈提示{		gd_eval_led_toggle(LED_BLUE);delay_1ms(200);}		

在Filesystem_Handle函數中初始化SD卡,然后讀取配置文件config.txt中的內容來獲取用戶配置,如果沒有讀取成功就使用默認配置。
一切正常就進入while循環。在while循環中不停讀取串口buf中的數據到wrbuffer中,當wrbuffer大小夠大時就寫入SD卡。
在這里插入圖片描述

測試

方法1:不同波特率發送1M大小txt文件給串口數據記錄儀,對比原始文件和日志文件內容。

方法2:單片機發送隨機數據給兩個串口數據記錄儀,長時間測試后對比兩個日志文件內容。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/73218.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/73218.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/73218.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

JVM中是如何定位一個對象的

在 Java 中&#xff0c;對象定位指的是如何通過引用&#xff08;Reference&#xff09;在堆內存中找到對象實例及其元數據&#xff08;如類型信息&#xff09;。JVM 主要通過 直接指針訪問 和 句柄訪問 兩種方式實現&#xff0c;各有其優缺點和應用場景&#xff1a; 一、直接指…

Mac 如何在idea集成SVN

在windows系統上面有我們最為熟悉的小烏龜TortoiseSVN&#xff0c;在mac系統上面則沒有什么好用的svn的工具&#xff0c;而且大部分都付費&#xff0c;需要各種渠道找PJ版&#xff0c;費事費力&#xff0c;作為程序員&#xff0c;大部分人應該都會安裝開發工具&#xff0c;本文…

批量測試IP和域名聯通性

最近需要測試IP和域名的聯通性&#xff0c;因數量很多&#xff0c;單個ping占用時間較長。考慮使用Python和Bat解決。考慮到依托的環境&#xff0c;Bat可以在Windows直接運行。所以直接Bat處理。 方法1 echo off for /f %%i in (E:\封禁IP\ipall.txt) do (ping %%i -n 1 &…

LabVIEW變頻器諧波分析系統

隨著工業自動化的發展&#xff0c;變頻器在電力、機械等領域的應用日益廣泛&#xff0c;但諧波問題直接影響系統效率與穩定性。傳統諧波檢測設備&#xff08;如Norma5000&#xff09;精度雖高&#xff0c;但價格昂貴且操作復雜&#xff0c;難以適應現場快速檢測需求。本項目基于…

Unity Shader學習總結

1.幀緩沖區和顏色緩沖區區別 用于存儲每幀每個像素顏色信息的緩沖區 幀緩沖區包括&#xff1a;顏色緩沖區 深度緩沖區 模板緩沖區 自定義緩沖區 2.ImageEffectShader是什么 后處理用的shader模版 3.computerShader 獨立于渲染管線之外&#xff0c;在顯卡上運行&#xff0c;大量…

OpenPLC WebServer啟動

簡述 OpenPLC OpenPLC 可運行在嵌入式系統和普通計算機上&#xff0c;其基本原理是在硬件上安裝類似 Linux 的操作系統&#xff0c;并在該環境下運行 OpenPLC 應用程序&#xff0c;從而讓用戶開發、調試和運行工業自動化控制邏輯。它目前只支持部分 ARM 架構的嵌入式系統&…

【基礎知識】回頭看Maven基礎

版本日期修訂人描述V1.02025/3/7nick huang創建文檔 背景 項目過程中&#xff0c;對于Maven的pom.xml文件&#xff0c;很多時候&#xff0c;我通過各種參考、仿寫&#xff0c;最終做出想要的效果。 但實際心里有些迷糊&#xff0c;不清楚具體哪個基礎的配置所實現的效果。 今…

ROS實踐(四)機器人SLAM建圖(gmapping)

目錄 一、SLAM技術 二、常用工具和傳感器 三、相關功能包 1. gmapping建圖功能包 2. map_server 四、SLAM 建圖實驗 1. 配置gmapping(launch文件) 2. 啟動機器人仿真(含機器人以及傳感器) 3. 運行gmapping節點 4. 啟動rviz可視化工具 5. 保存地圖文件 一、SLAM技…

二進制安裝指定版本的MariaDBv10.11.6

一、官網下載mariadb安裝包 Download MariaDB Server - MariaDB.org 找到對應的版本 下載安裝包后上傳到服務器這里不再贅述。 二、安裝二進制包 1、解壓安裝包 2、查看安裝包內的安裝提示文檔根據提示文檔進行安裝 # 解壓安裝包 tar xf mariadb-10.11.6-linux-systemd-x8…

【抽獎項目】|第二篇

前言&#xff1a; 高并發的活動預熱肯定不可以在數據庫操作&#xff0c;需要redis&#xff0c;特別是這種秒殺活動更是需要注意&#xff0c;所以可以在高并發的前夕先進行活動預熱。 思路&#xff1a; 1、 通過定時任務調度每分鐘查詢數據庫也沒有需要預熱的活動 2、采用分布式…

異或和之和 第十四屆藍橋杯大賽軟件賽省賽C/C++ 大學 A 組

異或和之和 題目來源 第十四屆藍橋杯大賽軟件賽省賽C/C++ 大學 A 組 原題鏈接 藍橋杯 異或和之和 https://www.lanqiao.cn/problems/3507/learning/ 問題描述 問題分析 要點1:異或運算 概念 異或(Exclusive OR,簡稱 XOR)是一種數學運算符,常用于邏輯運算與計算機…

從零到一:如何系統化封裝并發布 React 組件庫到 npm

1. 項目初始化 1.1 創建項目 首先&#xff0c;創建一個新的項目目錄并初始化 package.json 文件。 mkdir my-component-library cd my-component-library npm init -y1.2 安裝依賴 安裝開發所需的依賴項&#xff0c;如構建工具、測試框架等。 npm install --save-dev webp…

現代互聯網網絡安全與操作系統安全防御概要

現階段國與國之間不用對方路由器&#xff0c;其實是有道理的&#xff0c;路由器破了&#xff0c;內網非常好攻擊&#xff0c;內網共享開放端口也非常多&#xff0c;更容易攻擊。還有些內存系統與pe系統自帶瀏覽器都沒有javascript腳本功能&#xff0c;也是有道理的&#xff0c;…

2025-03-12 學習記錄--C/C++-PTA 習題8-4 報數

合抱之木&#xff0c;生于毫末&#xff1b;九層之臺&#xff0c;起于累土&#xff1b;千里之行&#xff0c;始于足下。&#x1f4aa;&#x1f3fb; 一、題目描述 ?? 習題8-4 報數 報數游戲是這樣的&#xff1a;有n個人圍成一圈&#xff0c;按順序從1到n編好號。從第一個人開…

【js逆向】某預約票網站 (webpack技術)

1、查看數據包 calendar是需要的數據包&#xff0c;看下它的請求參數。 accTimes參數加密&#xff0c;_times是時間戳。 2、全局搜索 accTimes 關鍵字 3、n的定義 4、把整個js代碼復制下來&#xff0c;應用到了webpack技術&#xff0c;圖中的Q是n

前端模塊管理新思路:如何使用 Import Maps

前言 前端開發中&#xff0c;我們常常需要使用各種庫和模塊來構建功能豐富的應用。在傳統方式中&#xff0c;管理這些庫和模塊的引用可能會有些繁瑣。 幸運的是&#xff0c;Import Maps 的出現為我們提供了一種更簡潔和高效的解決方案。今天我們就來聊聊如何使用 Import Maps。…

AI模型的構建過程是怎樣的(下)

你好,我是舒旻。 上節課,我們講了一個模型構建的前 2 個環節,模型設計和特征工程。今天,我們繼續來講模型構建的其他 3 個環節,說說模型訓練、模型驗證和模型融合中,算法工程師的具體工作內容,以及 AI 產品經理需要掌握的重點。 模型訓練 模型訓練是通過不斷訓練、驗證…

人工智能混合編程實踐:Python ONNX進行圖像超分重建

人工智能混合編程實踐:Python ONNX進行圖像超分重建 前言相關介紹Python簡介ONNX簡介圖像超分辨率重建簡介應用場景前提條件實驗環境項目結構使用Python ONNX進行圖像超分重建sr_py_infer.py參考文獻前言 由于本人水平有限,難免出現錯漏,敬請批評改正。更多精彩內容,可點擊…

視頻理解之Actionclip(論文宏觀解讀)

配合解讀代碼解讀 1.研究背景 1. 視頻行為識別的重要性 視頻行為識別是視頻理解領域的核心任務之一&#xff0c;旨在通過分析視頻內容來識別和分類其中的人物行為或活動。這一任務在多個領域具有重要的應用價值&#xff0c;例如智能監控、人機交互、自動駕駛、醫療健康等。隨…

【mysql】centOS7安裝mysql詳細操作步驟!

【mysql】centOS7安裝mysql詳細操作步驟&#xff01;—通過tar包方式 需要 root 權限&#xff0c;使用 root 用戶進行命令操作。 1. 查看 CentOS 版本 cat /etc/redhat-release2. 安裝rpm包&#xff0c;以8為例 打開 MySQL 官方 yum 倉庫網站&#xff0c;獲取與當前 CentOS …