TFT屏幕:STM32硬件SPI+DMA+隊列自動傳輸

看了網上的很多的SPI+DMA的代碼,感覺都有一些缺陷,就是基本都是需要有手動等待DMA完成的這個操作,我感覺這種等待操作在很大程度上浪費了時間,那么我加入的“隊列”就是一種將等待時間利用起來的方法。

原本的SPI+DMA的操作邏輯如下圖,是比較簡單的

下面是我加入隊列邏輯的過程圖,會變得比較龐大,占用的內容資源也多會比較多,需要斟酌使用

加入隊列前的基本流程是控制"DC電平->寫入數據->等待DMA傳輸完成->DC電平->寫入數據->等待DMA傳輸完成"這種操作是很浪費時間資源的,那么加入隊列之后的操作是"寫入數據->寫入數據->寫入數據"大部分時間都在寫入隊列與中斷中,其它部分都是DMA自己在傳輸數據,不需要一直等待

我會在最后放一個完整的代碼(我使用的是ST7789,其中的執行代碼是適配LVGL的)包括我自己使用芯片的驅動,有需要的可以直接拿走,基本上只需要修改一點點就可以用了。

首先是隊列的創建,我這里只是簡單寫了一下,這個隊列創建很占內存,內存分配也不是很靈活,其中忙標志也沒有鎖,小概率會出事,的如果有大佬的話可以修改一下,使其更完善一點

#define SPI_BUFFER_SIZE 4096
#define SPI_QUEUE_SIZE 8//數據結構體
typedef struct {uint8_t data[SPI_BUFFER_SIZE];uint16_t data_size;bool is_data; // 0:命令, 1:數據 用于控制DC
} spi_transaction_t;// 全局傳輸隊列
spi_transaction_t tx_queue[SPI_QUEUE_SIZE];//隊列索引
volatile uint16_t tx_read_index = 0;
volatile uint16_t tx_write_index = 0;
volatile uint16_t tx_count = 0;
//DMA忙標志
volatile uint8_t dma_busy = 0;

之后是隊列數據的輸入,需要輸入數據地址,數據大小還有DC電平翻轉方向(0命令/1數據),如果是ESP32的話好像可以實現DC的自動翻轉,會方便很多,這里實現不了,就只能在DMA之前先把DC翻轉給提前完成? ??if (tx->is_data)。還要注意隊列的索引變化

/******************************************************************************函數說明:添加SPI傳輸任務到隊列
******************************************************************************/
static void LCD_Add_To_Queue(uint8_t *data, uint16_t size, uint8_t is_data)
{// 等待隊列有空位(非阻塞式可添加超時機制)while (tx_count >= SPI_QUEUE_SIZE) {// 可在此處添加少量延遲或任務調度}// 復制數據到隊列uint8_t index = tx_write_index;memcpy(tx_queue[index].data, data, size);tx_queue[index].data_size = size;tx_queue[index].is_data = is_data;// 更新隊列索引tx_write_index = (tx_write_index + 1) % SPI_QUEUE_SIZE;tx_count++;// 如果DMA空閑,立即啟動傳輸if (!dma_busy) {LCD_Start_Next_Transaction();}
}/******************************************************************************函數說明:啟動下一個DMA傳輸任務
******************************************************************************/
static void LCD_Start_Next_Transaction(void)
{if (tx_count == 0){       //lv_disp_t *disp = lv_disp_get_default();//if (disp != NULL) {//    lv_disp_flush_ready(disp->driver); //}這個是為了適配LVGL加的return;}else if(dma_busy){return;}uint8_t index = tx_read_index;spi_transaction_t *tx = &tx_queue[index];// 設置DC引腳if (tx->is_data) {OLED_DC_Set(); // 數據模式} else {OLED_DC_Clr(); // 命令模式}OLED_CS_Clr();dma_busy = 1;HAL_SPI_Transmit_DMA(&hspi1, tx->data, tx->data_size);
}

然后最后一個是DMA傳輸完成的回調,進入回調后需要尋找隊列中是否還有數據,如果還有數據,就把數據傳給DMA

/******************************************************************************函數說明:DMA傳輸完成回調
******************************************************************************/
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{if (hspi == &hspi1) {OLED_CS_Set(); // 傳輸完成,拉高CSdma_busy = 0;// 更新隊列tx_read_index = (tx_read_index + 1) % SPI_QUEUE_SIZE;tx_count--;// 立即啟動下一個傳輸if (tx_count == 0){       //lv_disp_t *disp = lv_disp_get_default();//if (disp != NULL) {//    lv_disp_flush_ready(disp->driver);//}這個是為了適配LVGL}else if (tx_count > 0) {LCD_Start_Next_Transaction();}}
}

最后是完整版本的代碼

#include "oled.h"
#include <stdbool.h>
#include <stdint.h>
//#include "lvgl.h"
#include "stm32f4xx_hal.h"
// 在oled.h中定義#define SPI_BUFFER_SIZE 4096#define SPI_QUEUE_SIZE 8
typedef struct {uint8_t data[SPI_BUFFER_SIZE];uint16_t data_size;bool is_data; // 0:命令, 1:數據
} spi_transaction_t;// 全局傳輸隊列
spi_transaction_t tx_queue[SPI_QUEUE_SIZE];//隊列索引
volatile uint16_t tx_read_index = 0;
volatile uint16_t tx_write_index = 0;
volatile uint16_t tx_count = 0;
//DMA忙標志
volatile uint8_t dma_busy = 0;// 外部 SPI 句柄
extern SPI_HandleTypeDef hspi1;/******************************************************************************函數說明:啟動下一個DMA傳輸任務
******************************************************************************/
static void LCD_Start_Next_Transaction(void)
{if (tx_count == 0){       //lv_disp_t *disp = lv_disp_get_default();//if (disp != NULL) {//    lv_disp_flush_ready(disp->driver);  // 正確!//}return;}else if(dma_busy){return;}uint8_t index = tx_read_index;spi_transaction_t *tx = &tx_queue[index];// 設置DC引腳if (tx->is_data) {OLED_DC_Set(); // 數據模式} else {OLED_DC_Clr(); // 命令模式}OLED_CS_Clr();dma_busy = 1;HAL_SPI_Transmit_DMA(&hspi1, tx->data, tx->data_size);
}/******************************************************************************函數說明:添加SPI傳輸任務到隊列
******************************************************************************/
static void LCD_Add_To_Queue(uint8_t *data, uint16_t size, uint8_t is_data)
{// 等待隊列有空位(非阻塞式可添加超時機制)while (tx_count >= SPI_QUEUE_SIZE) {// 可在此處添加少量延遲或任務調度}// 復制數據到隊列uint8_t index = tx_write_index;memcpy(tx_queue[index].data, data, size);tx_queue[index].data_size = size;tx_queue[index].is_data = is_data;// 更新隊列索引tx_write_index = (tx_write_index + 1) % SPI_QUEUE_SIZE;tx_count++;// 如果DMA空閑,立即啟動傳輸if (!dma_busy) {LCD_Start_Next_Transaction();}
}/******************************************************************************函數說明:DMA傳輸完成回調
******************************************************************************/
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{if (hspi == &hspi1) {OLED_CS_Set(); // 傳輸完成,拉高CSdma_busy = 0;// 更新隊列tx_read_index = (tx_read_index + 1) % SPI_QUEUE_SIZE;tx_count--;// 立即啟動下一個傳輸if (tx_count == 0){       //lv_disp_t *disp = lv_disp_get_default();//if (disp != NULL) {//    lv_disp_flush_ready(disp->driver);//}}else if (tx_count > 0) {LCD_Start_Next_Transaction();}}
}/******************************************************************************函數說明:DMA傳輸錯誤回調
******************************************************************************/
void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi)
{if (hspi == &hspi1) {//OLED_CS_Set(); // 錯誤時也要拉高CSdma_busy = 0;// 可添加錯誤處理邏輯}
}/******************************************************************************函數說明:LCD寫命令
******************************************************************************/
void LCD_WR_REG(u8 dat)
{LCD_Add_To_Queue(&dat, 1, 0); // 0表示命令
}/******************************************************************************函數說明:LCD寫8位數據
******************************************************************************/
void LCD_WR_DATA8(u8 dat)
{LCD_Add_To_Queue(&dat, 1, 1); // 1表示數據
}/******************************************************************************函數說明:LCD寫16位數據
******************************************************************************/
void LCD_WR_DATA(u16 dat)
{uint8_t temp[2] = {dat >> 8, dat & 0xFF};LCD_Add_To_Queue(temp, 2, 1); // 1表示數據
}/******************************************************************************函數說明:批量寫入數據(優化性能)
******************************************************************************/
void LCD_Write_Bulk(uint8_t *data, uint16_t size)
{LCD_Add_To_Queue(data, size, 1);
}/******************************************************************************函數說明:設置顯示區域起始坐標和結束坐標參數說明:x1,x2 起始和結束的列地址y1,y2 起始和結束的行地址返回值:  無
******************************************************************************/
void LCD_Address_Set(u16 x1, u16 y1, u16 x2, u16 y2)
{if (USE_HORIZONTAL == 0){LCD_WR_REG(0x2A); // Column Address SetLCD_WR_DATA(x1);LCD_WR_DATA(x2);LCD_WR_REG(0x2B); // Page Address SetLCD_WR_DATA(y1);LCD_WR_DATA(y2);LCD_WR_REG(0x2C); // Memory Write}else if (USE_HORIZONTAL == 1){LCD_WR_REG(0x2A);LCD_WR_DATA(y1);          // 注意:x 和 y 互換LCD_WR_DATA(y2);LCD_WR_REG(0x2B);LCD_WR_DATA(239 - x2);    // 坐標翻轉LCD_WR_DATA(239 - x1);LCD_WR_REG(0x2C);}else if (USE_HORIZONTAL == 2){LCD_WR_REG(0x2A);LCD_WR_DATA(239 - x2);LCD_WR_DATA(239 - x1);LCD_WR_REG(0x2B);LCD_WR_DATA(319 - y2);LCD_WR_DATA(319 - y1);LCD_WR_REG(0x2C);}else if (USE_HORIZONTAL == 3){LCD_WR_REG(0x2A);LCD_WR_DATA(319 - y2);LCD_WR_DATA(319 - y1);LCD_WR_REG(0x2B);LCD_WR_DATA(x1);LCD_WR_DATA(x2);LCD_WR_REG(0x2C);}
}/******************************************************************************函數說明:LCD初始化參數說明:無返回值:  無
******************************************************************************/
void Lcd_Init(void)
{OLED_RES_Clr();HAL_Delay(200);OLED_RES_Set();HAL_Delay(100);//************* Start Initial Sequence **********//LCD_WR_REG(0x36);if (USE_HORIZONTAL == 0) LCD_WR_DATA8(0x00);else if (USE_HORIZONTAL == 1) LCD_WR_DATA8(0xC0);else if (USE_HORIZONTAL == 2) LCD_WR_DATA8(0x70);else LCD_WR_DATA8(0xA0);LCD_WR_REG(0x3A);LCD_WR_DATA8(0x05);LCD_WR_REG(0xB2);LCD_WR_DATA8(0x0C);LCD_WR_DATA8(0x0C);LCD_WR_DATA8(0x00);LCD_WR_DATA8(0x33);LCD_WR_DATA8(0x33);LCD_WR_REG(0xB7);LCD_WR_DATA8(0x35);LCD_WR_REG(0xBB);LCD_WR_DATA8(0x19);LCD_WR_REG(0xC0);LCD_WR_DATA8(0x2C);LCD_WR_REG(0xC2);LCD_WR_DATA8(0x01);LCD_WR_REG(0xC3);LCD_WR_DATA8(0x12);LCD_WR_REG(0xC4);LCD_WR_DATA8(0x20);LCD_WR_REG(0xC6);LCD_WR_DATA8(0x0F);LCD_WR_REG(0xD0);LCD_WR_DATA8(0xA4);LCD_WR_DATA8(0xA1);LCD_WR_REG(0xE0);LCD_WR_DATA8(0xD0);LCD_WR_DATA8(0x04);LCD_WR_DATA8(0x0D);LCD_WR_DATA8(0x11);LCD_WR_DATA8(0x13);LCD_WR_DATA8(0x2B);LCD_WR_DATA8(0x3F);LCD_WR_DATA8(0x54);LCD_WR_DATA8(0x4C);LCD_WR_DATA8(0x18);LCD_WR_DATA8(0x0D);LCD_WR_DATA8(0x0B);LCD_WR_DATA8(0x1F);LCD_WR_DATA8(0x23);LCD_WR_REG(0xE1);LCD_WR_DATA8(0xD0);LCD_WR_DATA8(0x04);LCD_WR_DATA8(0x0C);LCD_WR_DATA8(0x11);LCD_WR_DATA8(0x13);LCD_WR_DATA8(0x2C);LCD_WR_DATA8(0x3F);LCD_WR_DATA8(0x44);LCD_WR_DATA8(0x51);LCD_WR_DATA8(0x2F);LCD_WR_DATA8(0x1F);LCD_WR_DATA8(0x1F);LCD_WR_DATA8(0x20);LCD_WR_DATA8(0x23);LCD_WR_REG(0x21); // 逆顯示LCD_WR_REG(0x11); // Sleep OutHAL_Delay(120);LCD_WR_REG(0x29); // Display On
}/******************************************************************************函數說明:填充指定區域參數說明:xsta,ysta   起始坐標xend,yend   結束坐標返回值:  無
******************************************************************************/
void LCD_Fill(u16 xsta, u16 ysta, u16 xend, u16 yend, u16 *color) {LCD_Address_Set(xsta, ysta, xend, yend);uint32_t total_pixels = (xend - xsta + 1) * (yend - ysta + 1);uint8_t *color_byte = (uint8_t*)color; // 轉換為字節指針uint32_t transferred_pixels = 0;while (transferred_pixels < total_pixels) {// 每次傳輸最大塊(SPI_BUFFER_SIZE字節 = 緩沖區容量)uint32_t chunk_bytes = SPI_BUFFER_SIZE;// 剩余像素對應的字節數 = (總像素 - 已傳像素) * 2uint32_t remaining_bytes = (total_pixels - transferred_pixels) * 2;if (chunk_bytes > remaining_bytes) {chunk_bytes = remaining_bytes;}// 入隊當前塊LCD_Write_Bulk(color_byte + (transferred_pixels * 2), chunk_bytes);transferred_pixels += chunk_bytes / 2; // 字節數轉像素數}
}// 臨時緩沖區:10 行
static u16 fill_buf[LCD_H * 10];/*** @brief 測試:逐塊填充整個屏幕為藍色*/
void test_fill_entire_screen_blue(void)
{uint16_t y;HAL_Delay(500);// 填充當前塊為藍色for (int i = 0; i < LCD_H * 10; i++) {fill_buf[i] = YELLOW;}// 從 Y=0 開始,每次填充 10 行,直到填滿 240 行for (y = 0; y < LCD_H; y += 10) {uint16_t y_end = (y + 9) < LCD_H ? (y + 9) : (LCD_H - 1);LCD_Fill(0, y, LCD_W, y_end, fill_buf);}
}

還有對應的.h文件

#ifndef __OLED_H
#define __OLED_H	   						  #include "main.h"
#include "stdlib.h"	  
#include "string.h"#define USE_HORIZONTAL 0#if USE_HORIZONTAL == 0 || USE_HORIZONTAL == 2#define LCD_W  240#define LCD_H  320
#else#define LCD_W  320#define LCD_H  240
#endif#define	u8 unsigned char
#define	u16 unsigned int
#define	u32 unsigned long#define OLED_CS_Clr() HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_RESET)
#define OLED_CS_Set() HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_SET)#define OLED_DC_Clr() HAL_GPIO_WritePin(LCD_DC_GPIO_Port, LCD_DC_Pin, GPIO_PIN_RESET)
#define OLED_DC_Set() HAL_GPIO_WritePin(LCD_DC_GPIO_Port, LCD_DC_Pin, GPIO_PIN_SET)#define OLED_RES_Clr() HAL_GPIO_WritePin(LCD_RST_GPIO_Port, LCD_RST_Pin, GPIO_PIN_RESET)
#define OLED_RES_Set() HAL_GPIO_WritePin(LCD_RST_GPIO_Port, LCD_RST_Pin, GPIO_PIN_SET)#define OLED_CMD  0	
#define OLED_DATA 1	void Lcd_Init(void); 
void test_fill_entire_screen_blue(void);#define WHITE         	 0xFFFF
#define BLACK         	 0x0000	  
#define BLUE           	 0x001F  
#define BRED             0XF81F
#define GRED 			 0x07E0
#define GBLUE			 0X07FF
#define RED           	 0xF800
#define MAGENTA       	 0xF81F
#define GREEN         	 0x07E0
#define CYAN          	 0x7FFF
#define YELLOW        	 0xFFE0
#define BROWN 			 0XBC40
#define BRRED 			 0XFC07
#define GRAY  			 0X8430 #define DARKBLUE      	 0X01CF	
#define LIGHTBLUE      	 0X7D7C	
#define GRAYBLUE       	 0X5458 #define LIGHTGREEN     	 0X841F 
#define LGRAY 			 0XC618 #define LGRAYBLUE        0XA651 
#define LBBLUE           0X2B12 #endif   		     

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

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

相關文章

AI操作系統語言模型設計 之1 基于意識的Face-Gate-Window的共軛路徑的思維-認知-情感嵌套模型

摘要&#xff08;AI生成&#xff09;本文提出了一種創新的AI操作系統語言模型設計框架&#xff0c;將人類意識活動的分層結構映射到人工智能系統中。該模型包含三個嵌套層次&#xff1a;理性思維層&#xff08;Face層&#xff09;&#xff1a;采用雙面膠隱喻&#xff08;A/B面&…

瘋狂星期四文案網第57天運營日記

網站運營第57天&#xff0c;點擊觀站&#xff1a; 瘋狂星期四 crazy-thursday.com 全網最全的瘋狂星期四文案網站 運營報告 今日訪問量 今日搜索引擎收錄情況

SQLark:一款面向信創應用開發者的數據庫開發和管理工具

SQLark 是一款面向信創應用開發者的數據庫開發和管理工具&#xff0c;用于快速查詢、創建和管理不同類型的數據庫系統&#xff0c;現已支持達夢、Oracle、MySQL、PostgreSQL 數據庫。 SQLark 提供了對多種數據庫的連接支持&#xff0c;實現跨平臺數據庫管理的無縫切換&#xff…

BigDecimal——解決Java浮點數值精度問題:快速入門與使用

在Java開發中&#xff0c;涉及金額計算、科學計數或需要高精度數值處理時&#xff0c;你是否遇到過這樣的困惑&#xff1f;用double計算0.1加0.2&#xff0c;結果竟不是0.3&#xff1b;用float存儲商品價格&#xff0c;小數點后兩位莫名多出幾位亂碼&#xff1b;甚至在金融系統…

wpf之WrapPanel

前言 WrapPanel類似winform中的FlowLayoutPanel&#xff0c;采用流式布局。 1、Orientation 該屬性指定WrapPanel中子空間布局的方向&#xff0c;有水平和垂直方向兩種 1&#xff09;Horizontal 水平方向 子元素Button按照水平方向排列&#xff0c;如果一行排滿了自動換下一…

Woody:開源Java應用性能診斷分析工具

核心價值 Woody是一款專注于Java應用性能問題診斷的工具&#xff0c;旨在幫助開發者 定位高GC頻率問題&#xff0c;識別內存分配熱點分析CPU使用率過高的代碼路徑追蹤接口耗時瓶頸&#xff0c;定位內部操作耗時占比診斷鎖競爭問題&#xff0c;支持精準優化針對特定業務接口/請…

《山東棒球》板球比賽規則·棒球1號位

? Baseball vs Cricket 終極科普&#xff5c;規則異同發展史全解&#xff01;Hey sports babes&#xff01;別再傻傻分不清棒球?和板球&#xff01;全網最清晰雙運動對照指南來啦&#xff5e;? 棒球 Baseball&#xff5c;美式激情風暴Core Goal核心目標擊球員&#xff08;Ba…

【游戲開發】Houdini相較于Blender在游戲開發上有什么優劣勢?我該怎么選擇開發工具?

在游戲開發中&#xff0c;Houdini與Blender的選擇需結合項目規模、技術需求和團隊資源綜合考量。以下是兩者的核心優劣勢對比及決策建議&#xff1a; 一、核心優劣勢對比 Houdini的優勢與局限 優勢&#xff1a;程序化內容生成的統治力 Houdini的節點系統&#xff08;如VEX語言、…

基于開源AI智能名片鏈動2+1模式S2B2C商城小程序的用戶活躍度提升與價值挖掘策略研究

摘要&#xff1a;本文聚焦于在開源AI智能名片鏈動21模式S2B2C商城小程序環境下&#xff0c;探討如何提高用戶活躍度并挖掘用戶價值。在用戶留存的基礎上&#xff0c;通過分析該特定模式與小程序的特點&#xff0c;提出一系列針對性的策略&#xff0c;旨在借助開源AI智能名片以及…

《投資-41》- 自然=》生物=》人類社會=》商業=》金融=》股市=》投資,其層層疊加構建中內在的相似的規律和規則

從自然到投資的層層遞進中&#xff0c;盡管各領域看似差異巨大&#xff0c;但內在遵循著相似的規律和規則。這些規律體現了“底層邏輯的普適性”&#xff0c;即不同系統在動態平衡、資源分配、信息傳遞和反饋調節等方面具有共性。以下是關鍵規律的解析&#xff1a;1. 能量流動與…

VSCode中調試python腳本

VSCode中安裝以下插件 ms-python.python&#xff1a;python調試ms-python.vscode-pylance&#xff1a;代碼跳轉&#xff08;非必要&#xff09; 配置launch.json 在當前工作區&#xff0c;按此路徑.vscode\launch.json新建launch.json文件&#xff0c;并配置以下參數&#x…

動作指令活體檢測通過動態交互驗證真實活人,保障安全

在當今社會&#xff0c;人臉識別技術已深入日常生活的方方面面&#xff0c;從手機解鎖、移動支付到遠程開戶、門禁考勤&#xff0c;人臉識別技術已無處不在。然而&#xff0c;這項技術也面臨著嚴峻的安全挑戰&#xff1a;打印照片、播放視頻、制作3D面具等簡單的“欺騙手段”都…

KingbaseES數據庫:開發基礎教程,從部署到安全的全方位實踐

KingbaseES數據庫&#xff1a;開發基礎教程&#xff0c;從部署到安全的全方位實踐 KingbaseES數據庫&#xff1a;開發基礎教程&#xff0c;從部署到安全的全方位實踐&#xff0c;本文圍繞 KingbaseES 數據庫開發核心基礎展開。先介紹三種部署模式&#xff0c;即單機、雙機熱備、…

安裝nodejs安裝node.js安裝教程(Windows Linux)

文章目錄Linux**一、下載 Node.js**1. **訪問官網**&#xff1a;2. **選擇版本**&#xff1a;**二、安裝 Node.js****方法 1&#xff1a;使用包管理器&#xff08;推薦&#xff09;****Ubuntu/Debian 系統**1. **更新包列表**&#xff1a;2. **安裝 Node.js**&#xff1a;3. **…

shell腳本函數介紹

1. 函數 (Functions)定義與優勢函數是可重復使用的功能模塊優勢&#xff1a;代碼復用&#xff0c;直接調用解決問題分類內置函數&#xff1a;編程語言自帶的函數&#xff08;如 print&#xff09;自定義函數&#xff1a;程序員自己編寫的函數定義語法# 方式一 function 函數名(…

DAY 20 奇異值SVD分解-2025.9.1

奇異值SVD分解 知識點回顧&#xff1a; 線性代數概念回顧奇異值推導奇異值的應用 a. 特征降維&#xff1a;對高維數據減小計算量、可視化 b. 數據重構&#xff1a;比如重構信號、重構圖像&#xff08;可以實現有損壓縮&#xff0c;k 越小壓縮率越高&#xff0c;但圖像質量損失…

《C++——定長內存池》

一、為什么需要內存池&#xff1f; 常規的new/delete操作存在兩個主要問題&#xff1a; 性能開銷大&#xff1a;每次new都需要向操作系統申請內存&#xff0c;delete需要歸還給系統&#xff0c;這涉及內核態與用戶態的切換&#xff0c;在高頻次調用時性能損耗明顯。 內存碎片&a…

【跨境電商】上中下游解釋,以寵物行業為例

上中下游概念及其在寵物行業的應用 在產業鏈分析中&#xff0c;“上中下游”指的是一個產品或服務的不同環節&#xff1a;上游涉及原材料供應和基礎資源&#xff0c;中游負責生產加工和制造&#xff0c;下游則包括銷售、分銷和服務。這種劃分有助于理解整個價值鏈的運作。下面&…

飛牛NAS上部署Markdown文稿編輯器,閱讀.md文件同時還可以跨平臺訪問!

前言前段時間小白在使用.md文件的閱讀器&#xff0c;好像是什么*ypor*&#xff0c;但是這個軟件它收費。&#xff08;也不是找不到PJ版本&#xff0c;只是感覺這是人家的知識產權&#xff0c;就不整了。&#xff09;于是小白在尋找能夠代替這個軟件的其他軟件&#xff0c;而且如…

淺談 SQL 窗口函數:ROW_NUMBER() 與聚合函數的妙用

在日常開發中&#xff0c;我們經常會遇到這樣的需求&#xff1a;既要保留明細數據&#xff0c;又要對數據進行排名、累計、分區統計。如果僅依賴傳統的 GROUP BY&#xff0c;往往需要做多次子查詢或者復雜的 JOIN&#xff0c;既繁瑣又低效。 而 窗口函數&#xff08;Window Fun…