STM32的SPI口的DMA讀寫[原創www.cnblogs.com/helesheng]

来源:https://www.cnblogs.com/helesheng/archive/2022/10/07/16757245.html
-Advertisement-
Play Games

SPI是我最常用的介面之一,連接管腳僅為4根;在常見的晶元間通信方式中,速度遠優於UART、I2C等其他介面。STM32的SPI口的同步時鐘最快可到PCLK的二分之一,單個位元組或字的通信時間都在us以下,因此大多數情況下我們會使用查詢法控制SPI口的傳輸。但對於大量且連續的通信,再使用查詢法就顯得有 ...


SPI是我最常用的介面之一,連接管腳僅為4根;在常見的晶元間通信方式中,速度遠優於UART、I2C等其他介面。STM32的SPI口的同步時鐘最快可到PCLK的二分之一,單個位元組或字的通信時間都在us以下,因此大多數情況下我們會使用查詢法控制SPI口的傳輸。但對於大量且連續的通信,再使用查詢法就顯得有些浪費CPU的時間,DMA控制SPI的讀寫顯然成為一種不錯的選擇。

為DMA控制SPI批量數據讀寫的功能,參照官方代碼編寫的DMA控制SPI口在主/從兩種模式下,讀寫數據的的代碼,供各位網友直接使用或批評指正。先直接上我得到結論:

1、運用STM32的SPI口的DMA的功能,能夠提升STM32與外設之間通信的速率和實時性。

2、但在STM32的SPI的主機模式下,DMA控制器無法自動產生片選CS信號,只能與無需同步CS信號的外設器件通信。為產生同步的CS信號,只能由軟體控制SPI逐字發送,而DMA僅用於接收SPI數據,這樣做的效率和不使用DMA時一樣。

3、主模式下,軟體控製片選CS信號和SPI讀寫時,存在至少50%的時間空隙,降低了其SPI通信的效率

4、STM32的SPI主機模式下,無法只使用DMA接收,而不發送。原因是沒有觸發SPI的DMA接收的信號。但SPI的發送可以是軟體控制的逐字發送,也可以是DMA控制的連續發送。

5、STM32的SPI若要使用DMA方式,最合適的是讓STM32工作在SPI的從模式,由外部主機(如FPGA)來控制通信的實時性和高速性。

以下原創內容歡迎網友轉載,但請註明出處: https://www.cnblogs.com/helesheng

一、STM32做SPI主機(Master)時的DMA傳輸

STM32做SPI主機進行DMA通信時,尤其需要註意的是:不能單獨使用SPI接收數據DMA,一定要配合SPI發送數據,DMA接收數據通道才能收到數據。道理很簡單:STM32做主機時,如果不主動發送數據將無法產生時鐘和片選等信號,亦無法在傳輸完成後觸發DMA接收數據。但在使用時,這一點非常容易被忽視,從而造成DMA接收SPI數據通道DMA1CH2和DMACH4“不工作”。

圖1、STM32 DMA1各通道功能

 

具體來說,使用SPI口的DMA接收功能有兩種配置方法:

1、SPI口的接收和發送各使用一個DMA通道

這樣做最符合DMA控制大量數據連續發送和接收的設計初衷,此種情況下的SPI口和兩個DMA通道的配置分別如下: 

 1 RCC_APB2PeriphClockCmd(    RCC_APB2Periph_SPI1, ENABLE );    
 2 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;//PA5 6 7是SPI1的SCK MIOS MOSI
 3 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //復用推輓輸出
 4 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 5 GPIO_Init(GPIOA, &GPIO_InitStructure);
 6 GPIO_SetBits(GPIOA , GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7);    //將其置位
 7 SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  //設置SPI單向或者雙向的數據模式:SPI設置為雙線雙向全雙工
 8 SPI_InitStructure.SPI_Mode = SPI_Mode_Master;        //設置SPI工作模式:設置為主SPI
 9 SPI_InitStructure.SPI_DataSize = SPI_DataSize_16b;        //設置SPI的數據大小:SPI發送接收8位幀結構
10 SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;        //空閑時時鐘為低電平
11 SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;    //數據捕獲於第1個時鐘沿
12 SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;        //SPI_NSS_Hard;    ////NSS信號由硬體(NSS管腳)還是軟體(使用SSI位)管理:內部NSS信號有SSI位控制
13 SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;        //定義波特率預分頻的值
14 SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;    //指定數據傳輸從MSB位還是LSB位開始:數據傳輸從MSB位開始
15 SPI_InitStructure.SPI_CRCPolynomial = 7;    //CRC值計算的多項式
16 SPI_Init(SPI1, &SPI_InitStructure);  //根據SPI_InitStruct中指定的參數初始化外設SPIx寄存器
17 SPI_Cmd(SPI1, ENABLE); //使能SPI1外設
SPI的配置
 1 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);    //使能DMA傳輸
 2 ///////以下配置DMA CH2用於接收SPI的DMA通道/////
 3 DMA_DeInit(DMA1_Channel2);   //將DMA的通道1寄存器重設為預設值
 4 DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&(SPI1->DR);  //DMA外設基地址
 5 DMA_InitStructure.DMA_MemoryBaseAddr = (u32)spi_rx_buff;  //DMA記憶體基地址
 6 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;  //數據傳輸方向,從外設讀取數據到記憶體
 7 DMA_InitStructure.DMA_BufferSize = num;  //DMA通道的DMA緩存的大小
 8 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;  //外設地址寄存器不變
 9 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;  //記憶體地址寄存器遞增
10 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;  //數據寬度為16位
11 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //數據寬度為16位
12 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;  //工作在正常模式
13 DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; //DMA通道 x擁有中優先順序 
14 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;  //DMA通道x沒有設置為記憶體到記憶體傳輸
15 DMA_Init(DMA1_Channel2, &DMA_InitStructure);  //根據DMA_InitStruct中指定的參數初始化DMA的通道
16 ///////以下配置DMA的SPI發送通道///////////
17 DMA_DeInit(DMA1_Channel3);  
18 DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&(SPI1->DR); //設置接收外設(0x4001300C) 地址(源地址)
19 DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)spi_tx_buff;   //設置 SRAM 存儲地址(源地址)
20 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;  //傳輸方向 記憶體-外設
21 DMA_InitStructure.DMA_BufferSize = num;  //設置 SPI1 接收長度
22 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外設地址增量(不變)
23 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;  //記憶體地址增量(變化)
24 DMA_InitStructure.DMA_PeripheralDataSize = DMA_MemoryDataSize_HalfWord; //外設傳輸寬度(位元組)
25 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //記憶體傳輸寬度(位元組)
26 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //傳輸方式,一次傳輸完停止,不重新載入
27 DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA優先順序
28 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;   //記憶體到記憶體方式禁止
29 DMA_Init(DMA1_Channel3, &DMA_InitStructure);
收發兩個DMA通道的配置

主程式中收發控製程序如下:

 1 SPIx_Init();//SPI初始化
 2 DMA_Config(256);//配置DMA對應的兩個通道,數據深度設為256
 3 SPI_I2S_DMACmd(SPI1 , SPI_I2S_DMAReq_Rx , ENABLE);
 4 SPI_I2S_DMACmd(SPI1 , SPI_I2S_DMAReq_Tx , ENABLE);
 5 while(1)
 6 {
 7     DMA_SetCurrDataCounter(DMA1_Channel2,256);//必須在每次啟動DMA之前設置
 8     DMA_SetCurrDataCounter(DMA1_Channel3,256);//必須在每次啟動DMA之前設置
 9     DMA_Cmd(DMA1_Channel2, ENABLE);  //使能DMA所指示的通道 
10     DMA_Cmd(DMA1_Channel3, ENABLE);  //使能DMA所指示的通道 
11     while(1)
12     {
13         if(DMA_GetFlagStatus(DMA1_FLAG_TC2) != RESET)    //判斷通道2傳輸完成
14         {
15             DMA_ClearFlag(DMA1_FLAG_TC2);//清除通道2傳輸完成標誌
16             break; 
17         }
18     }
19     DMA_Cmd(DMA1_Channel2, DISABLE);//禁止DMA 
20     DMA_Cmd(DMA1_Channel3, DISABLE);//禁止DMA 
21     delay_ms(1);    
22 }
SPI主機DMA使用流程

這裡我沒有使用DMA中斷,為的是驗證代碼的簡單易懂;在實際使用時,建議讀者使用中斷以提高數據讀寫效率。另外,代碼中值得註意的地方有:

1、    使用DMA傳輸之前,必須使能SPI發送和接收觸發DAM傳輸請求,官方固件庫中的函數分別為:SPI_I2S_DMACmd(SPI1 , SPI_I2S_DMAReq_Rx , ENABLE);和SPI_I2S_DMACmd(SPI1 , SPI_I2S_DMAReq_Tx , ENABLE);

2、    每輪DMA傳輸完成後,需在次啟動一輪DMA傳輸之前,需要重新設置傳輸數據計數器:DMA_SetCurrDataCounter(DMA1_Channel2,256);和DMA_SetCurrDataCounter(DMA1_Channel3,256);

另外,我在使用上述方法的時候,忽然發現一個致命的問題:如果使用DMA控制STM32作為SPI主機輸出數據,那麼誰來產生片選信號CS呢?後來嘗試過將NSS(PA4——SPI1或PB12——SPI2)管腳配置給SPI口,並改由硬體來控制該管腳:  SPI_InitStructure.SPI_NSS = SPI_NSS_Hard;結果發現均不奏效,也就是說:在SPI主模式下使用DMA發送,無法產生有效的片選CS信號!這無疑是致命的缺陷!——也許是我的理解不到位,請各位知道怎麼解決這個問題的大神一定要高速我一下。(當然這一缺陷,對於無需在單次發送位元組/半字之後給出片選CS信號的應用——如大容量SPI介面存儲器,並不成其為問題。

無法在用DMA控制SPI發送時控制CS信號,我只好退而求其次:改由軟體控制SPI發送,並同步產生CS信號。但這樣做已經失去了DMA接收SPI的意義,因為軟體控制SPI發送後,通信的速度和使用查詢法是一樣的!

2、SPI接收使用DMA控制,發送使用軟體控制

儘管我認為發送使用軟體控制後,DMA在接收中帶來的好處已經基本喪失,但在這裡仍然給出主程式中收發控製程序供讀者參考。

 1 SPIx_Init();
 2 DMA_Config(256);//配置DMA的SPI通道,數據深度設為256
 3 SPI_I2S_DMACmd(SPI1 , SPI_I2S_DMAReq_Rx , ENABLE);
 4 delay_ms(300);
 5 while(1)
 6 {
 7     while(n_interrupt != 0);//等到中斷到來
 8     while(n_interrupt == 0);//等到中斷結束
 9     DMA_SetCurrDataCounter(DMA1_Channel2,256);//這部必須在每次啟動DMA之前設置,
10     DMA_Cmd(DMA1_Channel2, ENABLE);  //使能DMA所指示的通道 
11     for(k = 0 ; k < 256 ; k++)
12     {
13         CS = 0;
14         SPIx_ReadWrite16bit(0xaa55);//只使用了DMA接收SPI數據,但接收要由軟體啟動發送數據才能接收,此處只是隨便發送了一個數據
15         CS = 1;
16     }
17     if(DMA_GetFlagStatus(DMA1_FLAG_TC2)!=RESET)    //判斷通道2傳輸完成
18         DMA_ClearFlag(DMA1_FLAG_TC2);//清除通道2傳輸完成標誌
19     DMA_Cmd(DMA1_Channel2, DISABLE);//禁止DMA 
20         //////////以下可以把數據傳輸走//////////
21         
22     }
SPI主機,軟體控制逐字發送,接收用DMA控制

可以看到,當由軟體控制SPI發送後,就可以由軟體產生和發送同步的片選CS了。但這樣做與收發都採用查詢法的效率幾乎一樣了。

特別的,當採用查詢法直接控制SPI口的接收和發送時,硬體的讀寫和軟體的指令總是存在較大時間空隙:向SPI數據寄存器SPI_DR寫入數據到SPI實際發出數據之間存在至少200ns間隔;檢測SPI狀態寄存器SPI_SR中的TXE(發送緩衝區空位)時,TXE位的變化總是比實際發送完成晚至少200ns。例如上面的代碼,函數SPIx_ReadWrite16bit();通過軟體控製片選CS信號和SPI硬體的方式通信,下圖是它所產生的CS信號(藍)和SCK(黃),可以發現該函數用於發送的時間只占了實際耗費時間的一半以下,特別是當發送字長僅為8bits時,時間利用率真的是非常感人。

 

查詢法實現片選CS信號(藍色)和SPI硬體產生的時序

 對於這樣的實時性,我實在是不明白意法半導體的STM32設計師的初衷是什麼。當然,也有可能是筆者才疏學淺,如果有大神知道,煩請轉告,多謝!

二、STM32做SPI從機(Slave)時的DMA傳輸

當然用DMA讀寫SPI,更合理的方式是讓STM32的SPI工作在從機模式,只要主機給出合理的片選CS、時鐘SCK和數據MOSI/MISO信號,作為從機的STM32就能在DMA的支持下,實現高效、實時的數據接收。下麵的代碼中,我將SPI1配置為從機模式,用DMA1CH2接收數據。

 1     /////// DMA CH2配置代碼/////////
 2 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);    //使能DMA傳輸
 3 DMA_DeInit(DMA1_Channel2);   //將DMA的通道1寄存器重設為預設值
 4 DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&(SPI1->DR);  //DMA外設基地址
 5 DMA_InitStructure.DMA_MemoryBaseAddr = (u32)spi_rx_buff;  //DMA記憶體基地址
 6 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;  //數據傳輸方向,從外設讀取數據到記憶體
 7 DMA_InitStructure.DMA_BufferSize = num;  //DMA通道的DMA緩存的大小
 8 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;  //外設地址寄存器不變
 9 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;  //記憶體地址寄存器遞增
10 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;  //數據寬度為16位
11 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //數據寬度為16位
12 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;  //工作在正常模式
13 DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; //DMA通道 x擁有中優先順序 
14 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;  //DMA通道x沒有設置為記憶體到記憶體傳輸
15 DMA_Init(DMA1_Channel2, &DMA_InitStructure);  //根據DMA_InitStruct中指定的參數初始化DMA的通道
16     /////// SPI1配置代碼/////////
17 RCC_APB2PeriphClockCmd(    RCC_APB2Periph_SPI1, ENABLE );    
18 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;//PA4 PA5 6 7是SPI1的CS SCK MIOS MOSI
19 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //復用推輓輸出
20 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
21 GPIO_Init(GPIOA, &GPIO_InitStructure);
22 SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  //設置SPI單向或者雙向的數據模式:SPI設置為雙線雙向全雙工
23 SPI_InitStructure.SPI_Mode = SPI_Mode_Slave;        //設置SPI工作模式:設置為SPI從機
24 SPI_InitStructure.SPI_DataSize = SPI_DataSize_16b;        //設置SPI的數據大小:SPI發送接收8位幀結構
25 SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;        //空閑時時鐘為低電平
26 SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;    //數據捕獲於第1個時鐘沿
27 SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;        //SPI_NSS_Hard;    ////NSS信號由硬體(NSS管腳)還是軟體(使用SSI位)管理:內部NSS信號有SSI位控制
28 SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;        //定義波特率預分頻的值
29 SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;    //指定數據傳輸從MSB位還是LSB位開始:數據傳輸從MSB位開始
30 SPI_InitStructure.SPI_CRCPolynomial = 7;    //CRC值計算的多項式
31 SPI_Init(SPI1, &SPI_InitStructure);  //根據SPI_InitStruct中指定的參數初始化外設SPIx寄存器
SPI從機DMA使用流程

主程式中,控制DMA和讀取緩衝中的程式如下所示。這裡為了代碼的簡單易懂,同樣沒有使用DMA中斷,在實際使用時,建議讀者使用中斷以提高數據讀寫效率。

 1 SPI_Cmd(SPI1, ENABLE); //使能SPI1外設
 2 DMA_SetCurrDataCounter(DMA1_Channel2,256);//這部必須在每次啟動DMA之前設置,
 3 DMA_Cmd(DMA1_Channel2, ENABLE);  //使能DMA所指示的通道 
 4 while(DMA_GetFlagStatus(DMA1_FLAG_TC2)==RESET);    //判斷通道2傳輸完成
 5 DMA_ClearFlag(DMA1_FLAG_TC2);//清除通道2傳輸完成標誌
 6 DMA_Cmd(DMA1_Channel2, DISABLE);//禁止DMA 
 7 SPI_Cmd(SPI1, DISABLE); //禁止SPI,只在開啟SPI時接收數據,防止主機不斷發送
 8 //////////以下可以把數據傳輸走//////////
 9 for(i=0;i<256;i++)
10 data_repo_short[i] = spi_rx_buff[i]; 
SPI從機配置代碼

下圖是我用FPGA作為SPI主機產生的讀寫時序,可以看到此時SPI可以達到很高的通信效率。提高SCK的主頻後,通信速度上限10Mbytes/S左右(主要受限於STM32的接收SCK頻率)。

FPGA產生的SPI主機時序,STM32做從機

 三、總結

STM32的SPI介面並不完美,仍然存在各種小問題,尤其是在SPI作為主機受DMA控制傳輸大量數據時,效率並不能得到很大提升。但當STM32的SPI作為從機時,DMA控制的數據傳輸,能夠較大的提升數據常數效率。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 作者:IT王小二 博客:https://itwxe.com 前面小二介紹過使用Typora+PicGo+LskyPro打造舒適寫作環境,那時候需要使用水印功能,但是小二在升級LskyPro2.x版本發現有很多不如人意的東西,遂棄用LskyPro使用MinIO結合代碼實現自己需要的圖床功能,也適合以後 ...
  • 哈嘍,兄弟們! 最近有許多小伙伴都在吐槽打工好難。 每天都是執行許多重覆的任務 例如閱讀新聞、發郵件、查看天氣、打開書簽、清理文件夾等等, 使用自動化腳本,就無需手動一次又一次地完成這些任務, 非常方便啊有木有?! 而在某種程度上,Python 就是自動化的代名詞。 今天就來和大家一起學習一下, 用 ...
  • 思路分析 登錄頁面,我們還是採用ajax的方式提交用戶數據 唯一需要學習的是如何製作圖片驗證碼! 具體的登錄頁面效果圖如下: 如何製作圖片驗證碼 推導步驟1:在img標簽的src屬性里放上驗證碼的請求路徑 補充1.img的src屬性: 1.圖片路徑 2.url 3.圖片的二進位數據 補充2:字體樣式 ...
  • 思路分析 註冊頁面需要對用戶提交的數據進行校驗,並且需要對用戶輸入錯誤的地方進行提示! 所有我們需要使用forms組件搭建註冊頁面! 平時我們書寫form是組件的時候是在views.py裡面書寫的, 但是為了接耦合,我們需要將forms組件都單獨寫在一個地方,需要用的時候導入就行! 例如,在項目文件 ...
  • 許多情況下我們需要用到攝像頭獲取圖像,進而處理圖像,這篇博文介紹利用pyqt5、OpenCV實現用電腦上連接的攝像頭拍照並保存照片。為了使用和後續開發方便,這裡利用pyqt5設計了個相機界面,後面將介紹如何實現,要點包括界面設計、邏輯實現及完整代碼。 ...
  • 在日常後端Api開發中,我們跟前端的溝通中,通常需要協商好入參的數據類型,和參數是通過什麼方式存在於請求中的,是表單(form)、請求體(body)、地址欄參數(query)、還是說通過請求頭(header)。 當協商好後,我們的介面又需要怎麼去接收這些數據呢?很多小伙伴可能上手就是直接寫一個實體, ...
  • 一:背景 1.講故事 在分析的眾多dump中,經常會遇到各種奇葩的問題,僅通過dump這種快照形式還是有很多問題搞不定,而通過 perfview 這種粒度又太粗,很難找到問題之所在,真的很頭疼,比如本篇的 短命線程 問題,參考圖如下: 我們在 t2 時刻抓取的dump對查看 短命線程 毫無幫助,我根 ...
  • 1.用vmware添加一塊10G的硬碟,且永久掛載到/data01中,寫出詳細的步驟 2.用自己語言描述raid0和raid1的區別 RAID 0: 將兩個或以上相同信號,容量的硬碟組合,磁碟陣列的總容量是多個硬碟的總和,數據依次寫 入物理磁碟,理想狀態下,硬碟讀寫性能會翻倍。但只要壞一塊磁碟,所有 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...