14.串口更新FLASH字庫

一、簡介

在使用STM32等單片機驅動顯示屏時,為了顯示中文字體,常用FLASH保存字庫信息。但是字庫的更新通常只能使用SD卡更新,在一些小型單片機系統(如STM32F103C8T6、STC89C52)上,沒有增加SD卡支持的必要。為解決此問題,使用此項目,可以通過串口對FLASH的數據進行更新。

此方案包括如下部分功能:

  1. 讀取FLASH芯片ID。可用于檢測通信是否正常、FLASH是否正常工作。
  2. 擦除。可以整片擦除,可以指定地址和長度擦除。
  3. 生成字庫文件。指定各字體字庫文件,生成對應的數據頭,并且拼接成完整的bin文件。
  4. 寫入字庫到FLASH。將所需要寫入的字庫bin文件寫入到FLASH指定位置。
  5. 讀取FLASH信息。將FLASH指定位置和長度的數據讀出,從而用來檢測字庫是否寫入正確。

此方案包括兩個部分:

  1. 上位機部分。使用C#編程,軟件為Microsoft Visual Studio Professional 2019。
  2. 下位機部分。硬件環境使用的是自制的Air32板子,主控為為Air32F103CBT6。軟件使用C編程,硬件環境軟件為MDK5.25版本。

如下為上位機軟件界面和硬件實物圖:
在這里插入圖片描述
在這里插入圖片描述
硬件實物中,目前只使用了2個串口以及FLASH芯片,其他未使用。

二、通信協議

通信方式上位機作為主控端,用戶點擊界面后下發命令到下位機,下位機根據命令執行響應的動作,生成響應數據包返回給上位機。

數據包格式:

HEADCMDLENDATACRC16END
長度(字節)222x22
內容特定,AA BB命令數據包總長度數據校驗碼特定,CC DD

命令和響應數據包都遵循此格式,區別在于數據包格式中的數據有差異,根據命令進行區分。

2.1 CMD

CMD為指定的命令,執行不同的功能,主要包括如下:

命令數值作用
CMD_GETINFO0x0000獲取FLASH的信息
CMD_ERASE_CHIP0x0001整片擦除
CMD_ERASE0x0002指定地址、長度擦除
CMD_SAVE0x0003保存數據
CMD_READ0x0004讀取數據

2.2 LEN

LEN為數據包總長度,為整包數據的總長度,包括數據長度 + 10字節(2字節HEAD、2字節CMD、2字節LEN、2字節CRC16、2字節END)。

2.3 DATA

DATA為命令對應的具體數據,目前分為如下四類:

  1. CMD_GETINFO:獲取FLASH的信息。

    命令數據包:無DATA部分。

    響應數據包:返回FLASH的芯片ID和容量。

    芯片ID芯片容量
    長度(字節)44
  2. CMD_ERASE_CHIP:整片擦除。

    命令數據包:無DATA部分。

    響應數據包:返回當前擦除操作的起始地址(即0x0000 0000)和狀態。

    地址狀態
    長度(字節)42
  3. CMD_ERASE:擦除指定地址、指定長度。

    命令數據包:需要擦除的地址和長度。

    地址長度
    長度(字節)44

    響應數據包:返回當前擦除操作的起始地址(即0x0000 0000)和狀態。

    地址狀態
    長度(字節)42
  4. CMD_SAVE:將數據保存到FLASH指定地址、長度。

    地址長度數據
    長度(字節)44x

    響應數據包:指定地址的數據保存操作,狀態表示當前操作是否成功,0表示成功,1表示失敗。

    地址狀態
    長度(字節)42
  5. CMD_READ:從FLASH讀取指定地址、長度的數據。

    地址長度
    長度(字節)44

    響應數據包:返回需要讀取的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 示例

  1. 獲取信息:

    命令: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

  2. 整片擦除:

    命令: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

  3. 指定擦除:

    命令: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

  4. 保存數據:

    命令:保存的數據為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

  5. 讀取數據:

    命令: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
字庫1x字庫1的完整數據
字庫2x字庫2的完整數據
字庫3x字庫3的完整數據
字庫4x字庫4的完整數據

3.2 字庫頭

長度(字節)描述
fontok1字庫存在標志
0xAA,字庫正常;其他,字庫不存在
font1_addr4字庫1的保存地址
font1_size4字庫1的長度
font2_addr4字庫2的保存地址
font2_size4字庫2的長度
font3_addr4字庫3的保存地址
font3_size4字庫3的長度
font4_addr4字庫4的保存地址
font4_size4字庫4的長度

本例中字庫數量為4個,所以字庫頭的長度為33字節。

四、上位機

4.1 界面介紹

在這里插入圖片描述

界面主要按鈕:

  1. 讀取芯片信息:調用GETINFO命令,讀取FLASH芯片的ID和容量信息,并回顯到相關文本框。

  2. 整片擦除:調用EARSE_CHIP命令,將FLASH的所有數據進行擦除。相較于EARSE命令,執行較快。

  3. 擦除數據:調用EARSE命令,擦除指定地址和長度的數據,MCU執行此任務需要逐地址擦除,執行較慢。

  4. 生成數據:根據字庫起始地址、各字庫文件的長度生成字庫頭,并將字庫頭、各字庫文件組合成二進制save.bin文件,保存成文件。

  5. 發送數據:將第4步生成的文件或本地保存的文件,調用SAVE命令發送到下位機端,保存到FLASH中。

    若單次發送數據的長度大于4096字節,會進行分割,每次發送4096字節,并且將當前的發送進度顯示到進度條。

    發送完成后有彈窗提示,已發送的數據長度和總長度等信息。

  6. 讀取文件:調用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值,用于指定是否成功,以及對應的錯誤碼。

此函數依次實現如下邏輯:

  1. 首先依據DATA計算出所有期望讀到的數據長度len。
  2. 然后將期望讀到的幀頭和幀尾數據定義到數組,在讀取到數據后用于判斷當前數據是否合法。
  3. 循環讀取數據,當數據長度夠了后,依次判斷幀頭和幀尾是否符合預期,如果不符合預期,返回錯誤碼。
  4. 符合預期后,再計算CRC16校驗是否正確,如果不正確,則返回錯誤碼。
  5. 最后將讀取到的數據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中,使用了如下硬件資源:

  1. USART1:用于打印當前進度及調試信息,printf重定向到此串口,使用的波特率為115200。
  2. USART2:用于和上位機軟件進行通信,保存和讀取FLASH的數據等任務,波特率為921600。
  3. TIMER4:用于和USART2配合,實現以時間間隔判斷幀的功能。
  4. SPI1:用于和FLASH芯片通信,引腳為PA5、PA6、PA7,CS引腳為PA4。

5.1.3 內存資源

本例中使用了4個字庫,加上字庫頭總大小為3,238,697字節,大約為3.09M。

FLASH芯片總容量為16M,將數據保存在后4M空間中,即起始地址為0x00C00000,長度為0x00316B29。

5.2 以時間間隔判斷幀

以時間間隔判斷幀是一種簡單且方便的判斷幀方法,和類似說話一樣,在一段時間(時間間隔)內沒有說話后,將之前聽到的話(接收到的數據)進行處理,以時間間隔1ms為例,以時間間隔判斷幀的主要原理如下:

  1. 將定時器的中斷響應時間設置為1ms,并關閉定時器。
  2. 在串口收到第一字節數據時,啟動定時器,并將數據保存到指定的緩沖buffer中。
  3. 每次收到新數據時,將定時器計數值清零。
  4. 停止發送數據后,定時器就會進入定時器中斷,此時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信息。

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

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

相關文章

Lombok常用注解及功能詳解

Lombok常用注解及功能詳解一、Lombok簡介與環境配置1.1 什么是Lombok&#xff1f;1.2 環境配置1.2.1 Maven項目1.2.2 Gradle項目1.2.3 IDE配置&#xff08;關鍵&#xff09;二、Lombok常用注解詳解2.1 Data&#xff1a;一站式生成核心方法2.2 Getter/Setter&#xff1a;單獨生成…

應用分層

應用分層是?種軟件開發設計思想&#xff0c;它將應用程序分成N個層次&#xff0c;這N個層次分別負責各自的職責&#xff0c; 多個層次之間協同提供完整的功能。根據項目的復雜度&#xff0c;把項目分成三層&#xff0c;四層或者更多層。常見的MVC設計模式&#xff0c;就是應用…

[特殊字符] 【JAVA進階】StringBuilder全方位解析:從使用到源碼,一文搞定!

&#x1f525; 掌握StringBuilder&#xff0c;讓你的Java字符串操作性能飆升&#xff01;&#x1f9e9; StringBuilder是什么&#xff1f; StringBuilder是Java中用于動態構建字符串的可變字符序列類&#xff0c;位于java.lang包中。與不可變的String類不同&#xff0c;StringB…

Redis 數據結構全景解析

Redis 不是簡單的 key-value 緩存&#xff0c;它更像一把“瑞士軍刀”。 只要掌握數據結構&#xff0c;就能把同一份內存用出 10 倍效率。0. 開場白&#xff1a;為什么聊數據結構&#xff1f; 面試常問“Redis 有幾種數據類型&#xff1f;”——很多人答 5 種&#xff08;Strin…

ansible.cfg 配置文件的常見配置項及其說明

配置項說明默認值defaults默認配置部分inventory指定清單文件的位置&#xff0c;可以是文件路徑、目錄或動態清單腳本。/etc/ansible/hostsremote_user默認的遠程用戶roothost_key_checking是否啟用主機密鑰檢查。設置為 False 跳過 SSH 主機密鑰驗證。Trueask_pass是否在執行時…

Effective C++ 條款15:在資源管理類中提供對原始資源的訪問

Effective C 條款15&#xff1a;在資源管理類中提供對原始資源的訪問核心思想&#xff1a;RAII類需要提供訪問其封裝原始資源的顯式或隱式接口&#xff0c;以兼容需要直接操作資源的API&#xff0c;同時維持資源的安全管理。 ?? 1. 原始資源訪問的必要性 使用場景示例&#x…

Linux 進程管理與計劃任務設置

Linux 進程管理與計劃任務設置一、進程管理進程管理用于監控、控制系統中運行的程序&#xff08;進程&#xff09;&#xff0c;包括查看進程狀態、調整優先級、終止異常進程等。以下是核心命令及操作說明&#xff1a;1. 常用進程查看命令&#xff08;1&#xff09;ps&#xff1…

MYSQL數據庫之索引

1、引入索引的問題在圖書館查找一本書的過程&#xff0c;可類比數據庫查詢場景。在一般軟件系統中&#xff0c;對數據庫操作以查詢為主&#xff0c;數據量較大時&#xff0c;優化查詢是關鍵&#xff0c;索引便是優化查詢的重要手段 。2、索引是什么索引是一種特殊文件&#xff…

ArcGIS以及ArcGIS Pro如何去除在線地圖制作者名單

問題&#xff1a;ArcGIS和ArcGIS Pro提供了許多在線地圖服務&#xff0c;但是這些地圖會自動生成制作者名單&#xff0c;如下圖所示&#xff1a; 在線地圖加載方式可參考&#xff1a;如何在ArcGIS和ArcGIS Pro中添加在線底圖 這在出圖時有時會造成圖的部分信息遮擋或出圖不美觀…

InfluxDB 與 Golang 框架集成:Gin 實戰指南(二)

四、實際應用案例4.1 案例背景某智能工廠部署了大量的物聯網設備&#xff0c;如傳感器、智能儀表等&#xff0c;用于實時監測生產線上設備的運行狀態、環境參數&#xff08;如溫度、濕度&#xff09;以及生產過程中的各項指標&#xff08;如產量、次品率&#xff09;。這些設備…

Linux系統磁盤未分配的空間釋放并分配給 / 根目錄的詳細操作【openEuler系統】

選擇 Fix 修正 GPT 表 輸入 Fix 并按回車&#xff0c;parted 會自動&#xff1a; 擴展 GPT 表的 結束位置 到磁盤末尾。釋放未被使用的空間&#xff08;1048576000 個 512B 塊&#xff0c;約 500GB&#xff09;。 驗證修正結果 修正后&#xff0c;再次運行&#xff1a; parted …

王道考研-數據結構-01

數據結構-01視頻鏈接&#xff1a;https://www.bilibili.com/video/BV1b7411N798?spm_id_from333.788.videopod.sections&vd_source940d88d085dc79e5d2d1c6c13ec7caf7&p2 數據結構到底在學什么? 數據結構這門課他要學習的就是怎么用程序代碼把現實世界的問題給信息化&…

k8s云原生rook-ceph pvc快照與恢復(上)

#作者&#xff1a;Unstopabler 文章目錄前言部署rook-ceph on kubernets條件Ceph快照概述什么是PVC安裝快照控制器和CRD1.安裝crds資源2.安裝控制器3.安裝快照類前言 Rook 是一個開源的云原生存儲編排器&#xff0c;為各種存儲解決方案提供平臺、框架和支持&#xff0c;以便與…

springcloud04——網關gateway、熔斷器 sentinel

目錄 注冊中心 nacos | eurekaServer |zookeeper(dubbo) 配置中心 nacos | config Server 遠程服務調用 httpClient | RestTemplate | OpenFeign 負載均衡服務 ribbon | loadbalancer 網關 zuul | gateway 熔斷器 hystrix | sentinel 網關 sentinel 流控 壓測工具 1…

XSS跨站腳本攻擊詳解

一、XSS攻擊簡介跨站腳本攻擊的英文全稱是Cross-Site Scripting&#xff0c;為了與CSS有所區別&#xff0c;因此縮寫為“XSS”由于同源策略的存在&#xff0c;攻擊者或者惡意網站的JavaScript代碼沒有辦法直接獲取用戶在其它網站的信息&#xff0c;但是如果攻擊者有辦法把惡意的…

Linux /proc/目錄詳解

文章目錄前言文件說明注意事項前言 在 Linux 系統中&#xff0c;/proc 目錄是一個特殊的虛擬文件系統&#xff0c;它提供了對系統內核和進程的訪問。/proc 目錄中的文件和目錄不是真實存在的&#xff0c;它們是在運行時由內核動態生成的&#xff0c;用于提供系統和進程的相關信…

北斗變形監測在地質災害監測中的應用

內容概要 北斗形變監測系統在地質災害監測領域發揮著核心作用&#xff0c;該系統基于北斗衛星導航技術&#xff0c;實現對地表變形的精確追蹤。通過毫米級精度定位能力&#xff0c;北斗形變監測技術為滑坡等災害提供關鍵數據支撐&#xff0c;尤其在偏遠地區應用中&#xff0c;單…

2025新征程杯全國54校園足球錦標賽在北京世園公園隆重開幕

2025年8月1日&#xff0c;備受矚目的2025新征程杯全國54校園足球錦標賽&#xff08;北京&#xff09;在北京世園公園盛大拉開帷幕。開幕式上&#xff0c;中國關心下一代健康體育基金會副秘書長、中國青少年研究會理事、全國 54 校園足球人才培養計劃創始人何占強主任表示&#…

分類預測 | Matlab實現CPO-PNN冠豪豬算法優化概率神經網絡多特征分類預測

分類預測 | Matlab實現CPO-PNN冠豪豬算法優化概率神經網絡多特征分類預測 目錄分類預測 | Matlab實現CPO-PNN冠豪豬算法優化概率神經網絡多特征分類預測分類效果基本介紹程序設計分類效果 基本介紹 1.Matlab實現CPO-PNN冠豪豬算法優化概率神經網絡多特征分類預測&#xff0c;運…

機器學習——邏輯回歸(LogisticRegression)的核心參數:以約會數據集為例

理解 LogisticRegression 的核心參數&#xff1a;以約會數據集為例 邏輯回歸&#xff08;Logistic Regression&#xff09;是機器學習中一種基礎且重要的分類算法&#xff0c;特別適用于解決二分類和多分類問題。本文將基于 sklearn.linear_model.LogisticRegression 的用法&a…