一、簡介
在使用STM32等單片機驅動顯示屏時,為了顯示中文字體,常用FLASH保存字庫信息。但是字庫的更新通常只能使用SD卡更新,在一些小型單片機系統(如STM32F103C8T6、STC89C52)上,沒有增加SD卡支持的必要。為解決此問題,使用此項目,可以通過串口對FLASH的數據進行更新。
此方案包括如下部分功能:
- 讀取FLASH芯片ID。可用于檢測通信是否正常、FLASH是否正常工作。
- 擦除。可以整片擦除,可以指定地址和長度擦除。
- 生成字庫文件。指定各字體字庫文件,生成對應的數據頭,并且拼接成完整的bin文件。
- 寫入字庫到FLASH。將所需要寫入的字庫bin文件寫入到FLASH指定位置。
- 讀取FLASH信息。將FLASH指定位置和長度的數據讀出,從而用來檢測字庫是否寫入正確。
此方案包括兩個部分:
- 上位機部分。使用C#編程,軟件為Microsoft Visual Studio Professional 2019。
- 下位機部分。硬件環境使用的是自制的Air32板子,主控為為Air32F103CBT6。軟件使用C編程,硬件環境軟件為MDK5.25版本。
如下為上位機軟件界面和硬件實物圖:
硬件實物中,目前只使用了2個串口以及FLASH芯片,其他未使用。
二、通信協議
通信方式上位機作為主控端,用戶點擊界面后下發命令到下位機,下位機根據命令執行響應的動作,生成響應數據包返回給上位機。
數據包格式:
HEAD | CMD | LEN | DATA | CRC16 | END | |
---|---|---|---|---|---|---|
長度(字節) | 2 | 2 | 2 | x | 2 | 2 |
內容 | 特定,AA BB | 命令 | 數據包總長度 | 數據 | 校驗碼 | 特定,CC DD |
命令和響應數據包都遵循此格式,區別在于數據包格式中的數據有差異,根據命令進行區分。
2.1 CMD
CMD為指定的命令,執行不同的功能,主要包括如下:
命令 | 數值 | 作用 |
---|---|---|
CMD_GETINFO | 0x0000 | 獲取FLASH的信息 |
CMD_ERASE_CHIP | 0x0001 | 整片擦除 |
CMD_ERASE | 0x0002 | 指定地址、長度擦除 |
CMD_SAVE | 0x0003 | 保存數據 |
CMD_READ | 0x0004 | 讀取數據 |
2.2 LEN
LEN為數據包總長度,為整包數據的總長度,包括數據長度 + 10字節(2字節HEAD、2字節CMD、2字節LEN、2字節CRC16、2字節END)。
2.3 DATA
DATA為命令對應的具體數據,目前分為如下四類:
-
CMD_GETINFO:獲取FLASH的信息。
命令數據包:無DATA部分。
響應數據包:返回FLASH的芯片ID和容量。
芯片ID 芯片容量 長度(字節) 4 4 -
CMD_ERASE_CHIP:整片擦除。
命令數據包:無DATA部分。
響應數據包:返回當前擦除操作的起始地址(即0x0000 0000)和狀態。
地址 狀態 長度(字節) 4 2 -
CMD_ERASE:擦除指定地址、指定長度。
命令數據包:需要擦除的地址和長度。
地址 長度 長度(字節) 4 4 響應數據包:返回當前擦除操作的起始地址(即0x0000 0000)和狀態。
地址 狀態 長度(字節) 4 2 -
CMD_SAVE:將數據保存到FLASH指定地址、長度。
地址 長度 數據 長度(字節) 4 4 x 響應數據包:指定地址的數據保存操作,狀態表示當前操作是否成功,0表示成功,1表示失敗。
地址 狀態 長度(字節) 4 2 -
CMD_READ:從FLASH讀取指定地址、長度的數據。
地址 長度 長度(字節) 4 4 響應數據包:返回需要讀取的FLASH數據,不再包含地址等信息。
數據 長度(字節) x
注意:此部分只介紹了命令和響應數據包的DATA部分,其他部分(頭和尾)每個命令都應包含,且均采用統一的通信協議。
2.4 CRC16
CRC16為DATA數據的校驗碼,使用的是Modbus CRC16校驗。
上位機相關代碼邏輯為:
//CRC16校驗
public static UInt16 CalculateCRC16(byte[] data, UInt32 size)
{UInt16 wCrc = 0xFFFF;for (int i = 0; i < size; i++){wCrc ^= Convert.ToUInt16(data[i]);for (int j = 0; j < 8; j++){if ((wCrc & 0x0001) == 1){wCrc >>= 1;wCrc ^= 0xA001;//異或多項式}else{wCrc >>= 1;}}}return wCrc;
}
下位機相關代碼邏輯為:
// 計算 Modbus CRC16 校驗
static uint16_t calculateCRC16(const uint8_t *data, size_t length)
{uint16_t crc = 0xFFFF;for (size_t i = 0; i < length; ++i) {crc ^= data[i];for (int j = 0; j < 8; ++j) {if (crc & 0x0001) {crc = (crc >> 1) ^ 0xA001;} else {crc >>= 1;}}}return crc;
}
2.5 示例
-
獲取信息:
命令:BB AA 00 00 14 00 00 00 17 EF 00 00 00 00 00 01 DF EF DD CC
響應:BB AA 00 00 0C 00 00 00 FF FF DD CC
-
整片擦除:
命令:BB AA 01 00 0C 00 00 00 FF FF DD CC
響應:BB AA 01 00 14 00 00 00 00 00 C0 00 00 00 00 00 51 0B DD CC
-
指定擦除:
命令:BB AA 02 00 14 00 00 00 00 00 C0 00 29 6B 31 00 3D 1B DD CC
響應:BB AA 02 00 14 00 00 00 00 00 C0 00 00 00 00 00 51 0B DD CC
-
保存數據:
命令:保存的數據為00H - 3FH 共64(0x40)個數據。
? BB AA 03 00 54 00 00 00 00 00 C0 00 40 00 00 00
? 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
? 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
? 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
? 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
? 82 E6 DD CC響應:BB AA 03 00 14 00 00 00 00 00 C0 00 00 00 00 00 51 0B DD CC
-
讀取數據:
命令:BB AA 04 00 14 00 00 00 00 00 C0 00 40 00 00 00 44 CB DD CC
響應:BB AA 04 00 4C 00 00 00
00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
D9 08 DD CC
注:因ARM與Visual Studio的存儲模式均為小端模式,所以0xAABB在內存中的排列為BB AA。
三、字庫格式
2.1 字庫格式
字庫文件主要包括如下部分:
內容 | 長度(字節) | 描述 |
---|---|---|
字庫頭 | 1 + 字庫數量*8 | 保存字庫標志、各字庫的地址和長度 其長度根據字庫數量自定義,本例中為1+4*8=33 |
字庫1 | x | 字庫1的完整數據 |
字庫2 | x | 字庫2的完整數據 |
字庫3 | x | 字庫3的完整數據 |
字庫4 | x | 字庫4的完整數據 |
… | … |
3.2 字庫頭
長度(字節) | 描述 | |
---|---|---|
fontok | 1 | 字庫存在標志 0xAA,字庫正常;其他,字庫不存在 |
font1_addr | 4 | 字庫1的保存地址 |
font1_size | 4 | 字庫1的長度 |
font2_addr | 4 | 字庫2的保存地址 |
font2_size | 4 | 字庫2的長度 |
font3_addr | 4 | 字庫3的保存地址 |
font3_size | 4 | 字庫3的長度 |
font4_addr | 4 | 字庫4的保存地址 |
font4_size | 4 | 字庫4的長度 |
… | … |
本例中字庫數量為4個,所以字庫頭的長度為33字節。
四、上位機
4.1 界面介紹
界面主要按鈕:
-
讀取芯片信息:調用GETINFO命令,讀取FLASH芯片的ID和容量信息,并回顯到相關文本框。
-
整片擦除:調用EARSE_CHIP命令,將FLASH的所有數據進行擦除。相較于EARSE命令,執行較快。
-
擦除數據:調用EARSE命令,擦除指定地址和長度的數據,MCU執行此任務需要逐地址擦除,執行較慢。
-
生成數據:根據字庫起始地址、各字庫文件的長度生成字庫頭,并將字庫頭、各字庫文件組合成二進制save.bin文件,保存成文件。
-
發送數據:將第4步生成的文件或本地保存的文件,調用SAVE命令發送到下位機端,保存到FLASH中。
若單次發送數據的長度大于4096字節,會進行分割,每次發送4096字節,并且將當前的發送進度顯示到進度條。
發送完成后有彈窗提示,已發送的數據長度和總長度等信息。
-
讀取文件:調用READ命令,讀取指定地址、長度的數據,并且將數據保存到指定文件read.bin中。
若單次讀數據的長度大于4096字節,會進行分割,每次讀取4096字節,并且將當前的讀取進度顯示到進度條。
讀取完成后有彈窗提示,已讀取的數據長度和總長度等信息。
4.2 下位機通信流程
所有需要和下位機通信的操作均使用如下流程完成:
創建線程是由于部分操作(如讀取、保存)花費時間較長,需要一直等待通信完成,會導致界面卡頓。
在進入通信任務后,先根據通信協議生成數據包,通過串口發送出去,再讀取串口返回的響應數據包,依據通信協議判斷響應數據包是否成功,將相應的數據執行對應的操作(讀取的數據或者獲取到的信息需要顯示到文本框等)。如果當前操作已完成,則退出,如果未完成則繼續發送、接收、處理的操作。
4.3 串口通信函數
在本部分將介紹上位機和上位機通信的兩個主要通用函數——發送數據和讀取數據,基于第二章中的通信協議,將所有的命令封裝成此兩個接口,完成和串口通信的任務,方便擴展更多的命令。
4.3.1 發送數據
//通用發送數據函數發送命令,前面添加頭,后面添加尾,包含CRC16校驗功能
//參數:發送數據cmd、數據
private bool SendData(CMD cmd, byte[] data)
{Header header = new Header(); //幀頭end_t end = new end_t(); //幀尾UInt16 len = (UInt16)(Marshal.SizeOf(typeof(Header)) + //總數據包長度Marshal.SizeOf(typeof(end_t)) +data.Length);byte[] buffer = new byte[len]; //buffer,用于保存幀頭、數據、幀尾header.header = 0xAABB; //幀頭header.cmd = (UInt16)cmd; //命令header.len = len; //總長度end.crc16 = CalculateCRC16(data, (UInt32)data.Length); //CRC16校驗end.end = 0xCCDD; //幀尾byte[] headerBytes = StructToByteArray(header); //將幀頭結構體轉為數組byte[] endBytes = StructToByteArray(end); //將幀尾結構體轉為數組Array.Copy(headerBytes, 0, buffer, 0, headerBytes.Length); //拷貝幀頭到bufferArray.Copy(data, 0, buffer, headerBytes.Length, data.Length); //拷貝數據到bufferArray.Copy(endBytes, 0, buffer,headerBytes.Length + data.Length, endBytes.Length); //拷貝幀尾到bufferreturn SerialPort_Send_data(buffer); //串口發送數據
}
發送數據SendData的函數中,輸入參數為命令CMD、數據DATA,返回值為串口發送是否成功。
具體任務:根據數據的長度申請一個完整的buffer,依次對幀頭和幀尾的相關參數進行拷貝,將數據DATA進行拷貝,然后將整體buffer數據通過串口發送。
調用流程:在所有需要發送命令的操作中,生成對應的數據DATA,傳入對應的CMD即可。
比如擦除ERASE命令的調用如下:
private bool Earse_SendData(UInt32 addr, UInt32 len) //發送擦除數據的命令
{//擦除數據的命令格式:addr(4) len(4)CmdEarse cmd = new CmdEarse //定義擦除數據命令結構體{Addr = addr, //擦除數據命令地址Len = len //擦除數據命令長度};byte[] buffer = StructToByteArray(cmd); //將擦除命令結構體轉為數組return SendData(CMD.ERASE, buffer); //使用通用發送數據函數發送命令
}
4.3.2 讀取數據
//通用接收數據函數,接收命令
//參數:期望接收數據cmd、數據、長度、超時時間
private STATUS GetData(CMD cmd, byte[] data, UInt32 size, int timeout)
{if (data.Length < size){labStatus.Invoke(new Action(() => { labStatus.Text = "緩沖區大小不足"; }));return STATUS.NOVAL;}if (!checkSerialPortConnected()) //如果串口關閉,則返回失敗{labStatus.Invoke(new Action(() => { labStatus.Text = "串口未打開"; })); //跨進程設置狀態欄信息return STATUS.SERIAL;}Header header = new Header(); //幀頭end_t end = new end_t(); //幀尾UInt16 len = (UInt16)(Marshal.SizeOf(typeof(Header)) + //總數據包長度Marshal.SizeOf(typeof(end_t)) +size);byte[] buffer = new byte[len]; //接收bufferheader.header = 0xAABB; //幀頭header.cmd = (UInt16)cmd; //命令header.len = len; //總長度end.crc16 = 0x00; //CRC16校驗end.end = 0xCCDD; //幀尾byte[] headerBytes = StructToByteArray(header); //期望返回的headerbyte[] endendBytes = BitConverter.GetBytes(end.end); //期望返回的end->end,crc16不參與sp.ReadTimeout = timeout; //設置串口的超時時間int bytesRead = 0; //當前已讀的數據長度try{while (bytesRead < len) //當前已讀小于總長度len,則一直讀{bytesRead += sp.Read(buffer, bytesRead, len - bytesRead); //讀取剩余所需長度的數據if (bytesRead >= len) //已經讀滿所需長度數據{byte[] read_head = new byte[headerBytes.Length]; //讀到的數據幀頭byte[] read_endend = new byte[endendBytes.Length]; //讀到的數據幀尾Array.Copy(buffer, 0, read_head, 0, read_head.Length); //拷貝讀到的數據幀頭Array.Copy(buffer, bytesRead - read_endend.Length, //拷貝讀到的數據幀尾read_endend, 0, read_endend.Length);if (!read_head.SequenceEqual(headerBytes) || //將讀到的數據和期望的幀頭幀尾比較!read_endend.SequenceEqual(endendBytes)) //不是期望的數據,返回錯誤碼{labStatus.Invoke(new Action(() => { labStatus.Text = "讀取串口,幀頭幀尾錯誤"; }));return STATUS.NOVAL;}//讀到的數據幀頭和幀尾符合預期byte[] data_read = new byte[size]; //讀到的有效數據 Array.Copy(buffer, read_head.Length, data_read, 0, size); //拷貝有效數據UInt16 crc16 = CalculateCRC16(data_read, size); //crc16校驗UInt16 crc16_read = BitConverter.ToUInt16(buffer, //讀到的crc16bytesRead - Marshal.SizeOf(typeof(end_t)));if (crc16 != crc16_read) //crc16校驗{labStatus.Invoke(new Action(() => { labStatus.Text = "讀取串口,crc校驗失敗"; }));return STATUS.CRC16;}Array.Copy(data_read, data, size); //將有效數據拷貝到傳入的datalabStatus.Invoke(new Action(() => { labStatus.Text = "讀取串口成功"; }));return STATUS.OK;}}}catch (TimeoutException) //超時報錯{labStatus.Invoke(new Action(() => { labStatus.Text = "讀取串口超時"; }));return STATUS.TIMEOUT;}catch (Exception){labStatus.Invoke(new Action(() => { labStatus.Text = "讀取串口錯誤"; }));}labStatus.Invoke(new Action(() => { labStatus.Text = "讀取數據量不足"; })); //其他錯誤情況return STATUS.NOVAL;
}
讀取數據GetData的函數中,輸入參數為需要讀取的CMD、保存數據DATA的數組、數據長度、超時時間。返回值為自定義的ERROR值,用于指定是否成功,以及對應的錯誤碼。
此函數依次實現如下邏輯:
- 首先依據DATA計算出所有期望讀到的數據長度len。
- 然后將期望讀到的幀頭和幀尾數據定義到數組,在讀取到數據后用于判斷當前數據是否合法。
- 循環讀取數據,當數據長度夠了后,依次判斷幀頭和幀尾是否符合預期,如果不符合預期,返回錯誤碼。
- 符合預期后,再計算CRC16校驗是否正確,如果不正確,則返回錯誤碼。
- 最后將讀取到的數據DATA拷貝到傳入的參數中。
此函數中,還有一個重要邏輯超時時間timeout,可以用來設置每次讀數據的超時時間,單位為ms,如果設置為0則一直等待。如果超時后,會捕獲異常,直接返回錯誤碼。
還是以EARSE命令為例,在進入此任務后,先讀取UI界面將需要設置的起始地址、數據長度信息作為參數傳入到Earse_SendData,隨后讀取數據,依據讀取到的數據是否成功顯示彈窗。
void EarseData()
{UInt32 addr = String2UInt32(txtStart.Text); //擦除數據的起始位置UInt32 len = String2UInt32(txtLength.Text); //擦除數據的長度sp.DiscardInBuffer(); //清除串口緩沖區if (Earse_SendData(addr, len) != STATUS.OK) //發送擦除數據的命令return;RspStatus rsp = new RspStatus(); //響應數據結構體byte[] buffer = new byte[Marshal.SizeOf<RspStatus>()]; //定義一個和響應數據大小相同的bufferif (GetData(CMD.ERASE, buffer, (UInt32)buffer.Length, -1) != STATUS.OK) //擦除大概需要21秒左右,需要一直等待,或者超時時間長一點{MessageBox.Show("數據擦除失敗"); //獲取擦除數據返回值失敗,彈窗提醒return;}rsp.Addr = BitConverter.ToUInt32(buffer, 0); //buffer的前4個數據轉換成響應數據的addrrsp.Status = BitConverter.ToUInt32(buffer, 4); //buffer的后4個數據轉換成響應數據的狀態if (rsp.Status != (UInt32)RESULT.OK) //如果返回的狀態不是OK,彈窗提醒{MessageBox.Show("數據擦除失敗"); //彈窗提醒擦除失敗return;}MessageBox.Show("數據擦除成功"); //彈窗提醒,擦除成功
}
五、下位機
5.1 硬件環境
5.1.1 硬件介紹
本例使用的主控為合宙Air32,型號為Air32F103CBT6,其最高主頻可達216M。
FLASH芯片使用的是W25Q128,容量大小為16MB。
相關硬件連接圖如下:
5.1.2 硬件資源
Air32中,使用了如下硬件資源:
- USART1:用于打印當前進度及調試信息,printf重定向到此串口,使用的波特率為115200。
- USART2:用于和上位機軟件進行通信,保存和讀取FLASH的數據等任務,波特率為921600。
- TIMER4:用于和USART2配合,實現以時間間隔判斷幀的功能。
- SPI1:用于和FLASH芯片通信,引腳為PA5、PA6、PA7,CS引腳為PA4。
5.1.3 內存資源
本例中使用了4個字庫,加上字庫頭總大小為3,238,697字節,大約為3.09M。
FLASH芯片總容量為16M,將數據保存在后4M空間中,即起始地址為0x00C00000,長度為0x00316B29。
5.2 以時間間隔判斷幀
以時間間隔判斷幀是一種簡單且方便的判斷幀方法,和類似說話一樣,在一段時間(時間間隔)內沒有說話后,將之前聽到的話(接收到的數據)進行處理,以時間間隔1ms為例,以時間間隔判斷幀的主要原理如下:
- 將定時器的中斷響應時間設置為1ms,并關閉定時器。
- 在串口收到第一字節數據時,啟動定時器,并將數據保存到指定的緩沖buffer中。
- 每次收到新數據時,將定時器計數值清零。
- 停止發送數據后,定時器就會進入定時器中斷,此時buffer中收到的數據即為一幀完整的數據。
此方法在串口協議中較為常用,且實現簡單。
5.3 軟件流程
在主循環中一直監聽,如果USART2收到一幀完整數據,則調用fontupd_process進行處理,隨后調用fontupd_process對數據包進行解析,判斷HAED、END是否符合預期,做CRC16校驗,獲取CMD、LEN、DATA等關鍵信息,隨后調用具體的命令函數進行處理,并根據協議發送響應數據包。
5.4 重要函數
5.4.1 字體初始化
//初始化字體
//返回值:0,字庫完好.
// 其他,字庫丟失
u8 fontudp_init_and_check(void)
{u8 t=0;W25QXX_Init();while(t++ < 10)//連續讀取10次,都是錯誤,說明確實是有問題,得更新字庫了{W25QXX_Read((u8*)&ftinfo, fontaddr, sizeof(ftinfo));//讀出ftinfo結構體數據if(ftinfo.fontok == 0XAA)break;delay_ms(20);}if (ftinfo.fontok != 0XAA)return 1;return 0;
}
對FLASH進行初始化,并讀取字庫頭,判斷當前字庫是否存在。
5.4.2 串口數據處理
//數據包處理,傳入串口收到的數據,返回處理結果。
int fontupd_process(u8 *buffer, int size)
{struct fontupd_config_t config = {0};int ret = -1;ret = fontupd_parse(buffer, size, &config);if(ret != 0)return ret;switch (config.cmd) {case CMD_GETINFO:return flash_get_info(config.data, config.len_data);case CMD_ERASE_CHIP:return flash_erase_chip(config.data, config.len_data);case CMD_ERASE:return flash_erase(config.data, config.len_data);case CMD_SAVE:return flash_save(config.data, config.len_data);case CMD_READ:return flash_read(config.data, config.len_data);default:printf("not support cmd(%d)\r\n", config.cmd);break;}return 0;
}
在此函數中,傳入的是串口收到的數據以及數據長度,進入函數后,首先對數據包進行解析,并針對命令選擇對應的flash操作功能。
5.4.3 協議解析
static int fontupd_parse(u8 *buffer, int size, struct fontupd_config_t *config)
{//buffer為空,錯誤if (buffer == NULL)return -1;//數據長小于頭+尾,數據量不足,返回if(size < sizeof(struct header_t) + sizeof(struct end_t)){printf("%s: error size(%d) need(%d)byte at least\r\n", __func__, size, sizeof(struct header_t) + sizeof(struct end_t));return -1;}//獲取頭struct header_t *header = (struct header_t *)buffer;if(header->header.value != 0xAABB){printf("%s: error header(0x%04X)\r\n", __func__, header->header.value);return -1;}//獲取總長度int len = header->len.value;if(len != size){printf("%s: error len(%d - 0x%04X) size(%d - 0x%04X)\r\n", __func__, len, len, size, size);return -1;}//獲取數據及長度int len_data = len - sizeof(struct header_t) - sizeof(struct end_t);u8 *data = len_data ? buffer + sizeof(struct header_t) : NULL;//獲取尾struct end_t *end = (struct end_t *)(buffer + sizeof(struct header_t) + len_data);if(end->end.value != 0xCCDD){printf("%s: error end(0x%04X)\r\n", __func__, end->end.value);return -1;}uint16_t crc16 = calculateCRC16(data, len_data);printf_debug("%s: len(%d - 0x%04X) len_data(%d - 0x%04X) crc(%04X %04X)\r\n", __func__, len, len, len_data, len_data, end->crc16.value, crc16);if(end->crc16.value != crc16){printf("%s: error crc(%04X %04X)\r\n", __func__, end->crc16.value, crc16);printf_buffer_debug(buffer, size);return -1;}config->cmd = header->cmd.value;config->data = data;config->len_data = len_data;return 0;
}
在此函數中,根據第二章的通信協議進行解析,并將解析出的CMD、數據、數據長度信息返回。
5.4.4 獲取信息
static int flash_get_info(u8 *buffer, int size)
{printf("%s: enter size(%d)\r\n", __func__, size);//數據長為0if(size != 0){printf("%s: error size(%d) need(%d)\r\n", __func__, size, 0);return -1;}struct get_info_t info;info.id.value = W25QXX_TYPE;info.size.value = FLASH_SIZE;send_rsp(CMD_GETINFO, (u8 *)&info, sizeof(struct get_info_t));printf("%s: exit\r\n", __func__);return 0;
}
5.4.5 整片擦除
static int flash_erase_chip(u8 *buffer, int size)
{printf("%s: enter size(%d)\r\n", __func__, size);struct res_t res;u16 status = STATUS_ERR;//數據長為0if(size != 0){printf("%s: error size(%d) need(%d)\r\n", __func__, size, 0);goto exit;}W25QXX_Erase_Chip();status = STATUS_OK;exit:res.addr.value = fontaddr;res.status.value = status;send_rsp(CMD_ERASE_CHIP, (u8 *)&res, sizeof(struct res_t));printf("%s: exit\r\n", __func__);return 0;
}
5.4.6 指定擦除
static int flash_erase(u8 *buffer, int size)
{printf("%s: enter size(%d)\r\n", __func__, size);struct erase_data_header_t *header = NULL;struct res_t res;u16 status = STATUS_ERR;//buffer為空,錯誤if (buffer == NULL){goto exit;}//數據長小于頭,數據量不足,返回if(size < sizeof(struct erase_data_header_t)){printf("%s: error size(%d) need(%d)\r\n", __func__, size, sizeof(struct erase_data_header_t));goto exit;}//獲取頭header = (struct erase_data_header_t *)buffer;printf("%s: addr(0x%08X) len(0x%08X)\r\n", __func__, header->addr.value, header->len.value);W25QXX_Erase(header->addr.value, header->len.value);status = STATUS_OK;exit:res.addr.value = header->addr.value;res.status.value = status;send_rsp(CMD_ERASE, (u8 *)&res, sizeof(struct res_t));printf("%s: exit\r\n", __func__);return 0;
}
5.4.7 保存數據
static int flash_save(u8 *buffer, int size)
{struct res_t res;//buffer為空,錯誤if (buffer == NULL)return -1;//數據長小于頭,數據量不足,返回if(size < sizeof(struct save_data_header_t)) {printf("%s: error size(%d) need(%d)\r\n", __func__, size, sizeof(struct save_data_header_t));return -1;}//獲取頭struct save_data_header_t *header = (struct save_data_header_t *)buffer;//獲取地址和長度u32 addr = header->addr.value;u16 len = header->len.value;if (len + sizeof(struct save_data_header_t) != size) {printf("%s: error len(%d - 0x%04X) size(%d - 0x%04X)\r\n", __func__, len, len, size, size);return -1;}u8 *data = buffer + sizeof(struct save_data_header_t);printf_debug("%s: addr(0x%08X) len(%d - 0x%04X)\r\n", __func__, addr, len, len);W25QXX_Write(data, addr, len); //從0開始寫入4096個數據res.addr.value = header->addr.value;res.status.value = STATUS_OK;send_rsp(CMD_SAVE, (u8 *)&res, sizeof(struct res_t));return 0;
}
5.4.8 讀取數據
static int flash_read(u8 *buffer, int size)
{u8 data[4096];//buffer為空,錯誤if (buffer == NULL)return -1;//數據長小于頭,數據量不足,返回if(size < sizeof(struct read_data_header_t)) {printf("%s: error size(%d) need(%d)\r\n", __func__, size, sizeof(struct erase_data_header_t));return -1;}//獲取頭struct read_data_header_t *header = (struct read_data_header_t *)buffer;//獲取地址和長度u32 addr = header->addr.value;u32 len = header->len.value;printf_debug("%s: addr(0x%08X) len(0x%08X)\r\n", __func__, addr, len);if (len > 4096) {printf("%s: not support read len(%d) bytes\r\n", __func__, len);return -1;}W25QXX_Read(data, addr, len);send_rsp(CMD_READ, data, len);return 0;
}
5.4.9 發送響應數據
static void send_rsp(int cmd, u8 *data, u16 len)
{struct header_t header;struct end_t end;header.header.value = 0xAABB;header.cmd.value = cmd;header.len.value = sizeof(struct header_t) + sizeof(struct end_t) + len;end.crc16.value = calculateCRC16(data, len);;end.end.value = 0xCCDD;USART2_Send_N((char*)&header, sizeof(struct header_t));USART2_Send_N((char*)data, len);USART2_Send_N((char*)&end, sizeof(struct end_t));
}
六、其他
6.1 參考文獻
本項目參考并使用了正點原子關于字庫的部分。
6.2 開源
該項目首發于https://blog.csdn.net/chouye5700?type=blog、https://gitee.com/wuzjjj/fontupd
6.2.1 開源協議
The MIT License (MIT)Copyright (c) [2025] [wuzjjj]Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
6.2.2 資料
鏈接: https://pan.baidu.com/s/1eE0OCbkzDMeLyo-L9I4FTg?pwd=6666 提取碼: 6666
6.3 擴展
本例可非常方便的移植到STM32上,只需要將FONTUPD、USART、TIMER中重要函數及變量進行移植即可。
也可以將本項目用于更新其他FLASH信息。