FPGA對EEPROM驅動控制(I2C協議)

来源:https://www.cnblogs.com/handat/p/18265240
-Advertisement-
Play Games

本文摘要:本文首先對I2C協議的通信模式和AT24C16-EEPROM晶元時序控制進行分析和理解,設計了一個i2c通信方案。人為按下寫操作按鍵後,FPGA(Altera EP4CE10)對EEPROM指定地址寫入位元組數據,並接後按下讀操作按鍵,讀取該地址上的一個位元組數據在數位管低兩位顯示出來。其中包 ...


本文摘要:本文首先對I2C協議的通信模式和AT24C16-EEPROM晶元時序控制進行分析和理解,設計了一個i2c通信方案。人為按下寫操作按鍵後,FPGA(Altera EP4CE10)對EEPROM指定地址寫入位元組數據,並接後按下讀操作按鍵,讀取該地址上的一個位元組數據在數位管低兩位顯示出來。其中包括了對此方案的Modelsim模擬測試,並且接續完成板級驗證。(過程筆記)

關鍵詞:EEPROM、I2C協議、Verilog HDL、FPGA

框圖設計:

輸入埠包括系統時鐘、複位信號、寫操作和讀操作按鍵,輸出埠包括IIC通信介面、數位管段選和位選信號。

image

共計用到了四種子模塊,分別是按鍵消抖、i2c通信數據處理、i2c通信控制、2khz時鐘分配。按邏輯鏈接成體,完成本文設計的測試方案。在調試過程中,為方便觀察操作反應,另外加入了兩個LED燈介面,具體框圖如下。


【I2C協議通信模式】

I2C協議(通常稱為IIC協議)是一種串列、同步、半雙工通信協議。I2C協議支持多主機功能,允許多個具備主控能力的設備在同一匯流排上競爭控制權,並通過硬體仲裁機制避免衝突。其使用串列數據線(SDA)和串列時鐘線(SCL)進行通信,標準模式的傳輸速率為100 kbit/s(Standard-mode, Sm),快速模式下的I2C傳輸速率提高至400 kbit/s(Fast-mode,Fm),在高速模式下可達3.4Mbit/s

發送到SDA線上的每個位元組必須為8位,且每次傳輸可以發送的位元組數量不受限制,每個位元組後必須跟一個ACK響應,位首先傳輸的是數據的最高位MSB,SDA線上的數據必須在SCL時鐘的高電平周期保持穩定,數據線的高或低電平狀態只有在SCL時鐘是低電平時才能改變。

image

傳輸信號類型:(見上圖)

  • 啟動信號(START)(S條件):在SCL線處於高電平,SDA上的數據由高向低轉換,則表示啟動IIC匯流排;
  • 應答信號(ACK):在接收到了8bit的信息後, 接收數據一方需要向發送信息的另一方傳遞預設的低電平脈衝作為信號,表明已經獲取了數據;
  • 結束信號(STOP)(P條件):在SCL線處於高電平,SDA上的數據由低向高轉換,則表示停止IIC匯流排。

起始(S)和停止(P)條件一般由主機產生,匯流排在起始條件後被認為處於忙的狀態,在停止條件的某段時間後匯流排被認為再次處於空閑狀態 。

由於I2C 匯流排沒有中央控制,其控制只能由地址或主機碼以及競爭主機發送的數據決定,匯流排也沒有任何定製的優先權。所以,當發生多主機通信時,需要一個控制仲裁機制來決定通信優先。

仲裁過程中的時鐘同步:

在I2C通信中,所有主機在SCL線上生成自己的時鐘信號以傳輸數據。數據僅在時鐘高電平時有效,因此需要同步時鐘以實現逐位仲裁。時鐘同步通過線與連接實現,即SCL線狀態由所有設備共同決定。

image

①見上圖中,當SCL線從高變低時,所有設備開始計數其低電平周期。若某設備時鐘先變低,則它會保持SCL線在低電平直到其時鐘再次變高。若此時有其他設備仍處於低電平周期,它們的時鐘變化不會改變SCL線狀態,直至最長低電平周期的設備完成計數。

②一旦所有設備完成低電平周期,SCL線釋放並變為高電平,隨後所有設備同步開始計數高電平周期。

③首先完成高電平周期的設備將再次拉低SCL線。因此,SCL時鐘的低電平周期由最長低電平周期的設備決定,而高電平周期由最短高電平周期的設備決定。(巧記:保證最長低電平周期)

仲裁判定優先:

主機只能在匯流排空閑時啟動傳輸,兩個或多個主機可能在起始條件的最小持續時間內產生一個起始條件,結果在匯流排上產生一個規範的起始條件(如下框S條件內)。當SCL線是高電平時,仲裁在SDA線發生,在其他主機保持低電平狀態時,首先拉高電平的主機將斷開數據輸出級,如下圖DATA1失去了通信的優先權。

image

仲裁可以持續多位,第一個階段是比較地址位。 如果各主機都嘗試定址同一器件,仲裁機制會持續到數據位。在仲裁過程中不會丟失信息(仲裁區間有效信息一致),丟失仲裁的主機可以產生時鐘脈衝直到丟失仲裁的該位元組末尾 。

補充:如果主機也結合了從機功能,並且在定址階段丟失仲裁,由於它存在是贏得仲裁的主機所定址的器件。因此,丟失仲裁的主機必須立即切換到從機模式。

【AT24C16時序分析】

AT24C16 EEPROM存儲晶元的相關信息:存儲容量為16Kbit,即2048位元組。晶元內部分成128頁,每頁16位元組,讀寫操作都是以位元組為基本單位。AT24C16具有低電壓工作、高速串列通信和硬體防寫等特點。(下圖為晶元引腳說明)

image

  • 地址組成:AT24C16的器件地址包括高4位固定地址(1010)和用戶需設置的低3位地址(A0、A1、A2);
  • 地址設置:通過連接晶元的A0、A1、A2這3個引腳到VCC或GND來實現地址的低3位設置。例如這3個引腳均連接到VCC,則器件地址為1010_111。由於該 3 位只能組合出 8 種情況,因此,一個主機最多可以連接8個AT24C16存儲晶元。

1、EEPROM驅動寫時序進行分析

image

位元組寫入時序 (Byte Write):通信開始,由主機發送一個起始條件。緊接著,主機發送EEPROM的設備地址,選擇目標EEPROM晶元。如果EEPROM支持字地址定址,主機將接續發送一個或多個位元組的字地址來指定要寫入數據的記憶體位置。然後,主機寫入數據位元組,最高有效位(MSB)首先發送。 EEPROM在接收到每個位元組後,返回一個應答信號(ACK),應答信號是低電平脈衝,表示已成功接收位元組並準備接收下一個位元組或停止信號。待所有數據位元組都發送完畢後,主機發送停止信號(STOP)來結束目前的通信。

頁寫入時序 (Page Write):與位元組寫入時序相比,頁寫入時序類似,但數據被分組為多個位元組,作為一個連續的數據塊發送。 發送起始設備地址後,接續發送一系列的數據位元組(DATA (n), DATA (n + 1),......, DATA (n + x))單個位元組發送完成,EEPROM都會返回一個應答信號(ACK)。

補充:所有I2C設備均支持單位元組數據寫入操作,但只有部分I2C設備支持頁寫操作;且支持頁寫操作的設備,一次頁寫操作寫入的位元組數不能超過設備單頁包含的存儲單元數。

2、讀時序進行分析

IIC協議支持三種EEPROM讀時序。首先是指定地址單位元組讀取方式:操作時序和寫時序類似,不同的是,在寫入目標地址後,主機的操作方式換為讀取。

image

順序讀取時序:主機發送一個起始地址後,EEPROM開始連續發送數據(DATA n, DATA n+1, DATA n+2, ... DATA n+X)。在每個數據位元組的末尾,EEPROM同樣等待主機的應答信號(ACK)。主機在接收到每個數據位元組後發送ACK信號,直到所有數據都被接收。當所有數據發送完畢或主機決定停止時,它會發送一個停止信號(STOP),結束順序讀取操作。

隨機讀取時序:起始條件後,發送設備地址和讀寫位(R/W=1),接著發送隨機地址。EEPROM發送數據,主機接收後發送ACK。所有數據發送完畢後,發送停止信號。EEPROM在接收到有效地址後,通過SDA線連續發送數據(DATA n, DATA n+1, ...)。在每個數據位元組的末尾,EEPROM等待主機的應答信號(ACK)。主機待接收到數據位元組後,通過發送ACK信號來確認接收,並告知EEPROM可以繼續發送下一個數據位元組。

【時序邏輯設計方案】

1、時鐘處理與i2c通信啟動

系統時鐘頻率為50Mhz,頻率很高,這裡首先需要從系統時鐘分頻提供一個1Mhz的i2c_clk時鐘用於i2c通信處理。下圖中cnt_clk為一個時鐘分頻計數器。

image

以寫操作為例,待寫觸發信號write發生,拉高寫有效信號write_valid,並且為使i2c_clk時鐘信號要在上升沿檢測到其高電平,write觸發要保持≥2個時鐘周期,對應100個系統時鐘周期。這裡設定有效計數器cnt_wr,計數150個系統周期,即300ns,滿足要求。計數完成,拉低寫有效信號write_valid完成觸發操作,具體時序見下圖,當然,寫有效時序也是如此。

image

寫有效信號write_valid觸發,在其拉高的下一個i2c_clk時鐘上升沿,觸發寫使能wr_en信號,並且,啟動cnt_start計數器,計數值為5000(1Mhz ->1us)≈ 5ms。這是因為AT64C16單次操作間隔周期需要保持5ms空閑狀態。完成計數後,啟動i2c_start起始信號觸發,後面就是i2c通信的具體流程。當完成操作後,i2c_end結束信號標誌通信完成,從而拉低寫使能wr_en信號。過程中,設定了寫操作的目標地址為16'H00_4D,寫入數據為8'H8A。

image

註意,大部分信號時鐘觸發源都是i2c_clk,而不是系統時鐘。當i2c_start起始信號觸發,i2c_clk啟動,作為i2c_scl通信時鐘的觸發源。同時,根據下圖邏輯,狀態機狀態由IDLE轉到START1。起始信號僅占一個i2c_clk周期。cnt_i2c_clki2c_scl通信時鐘線的計數器,計數值範圍0-3,使得i2c_scl周期為250khz。

image

完成後,進入SEND_ADDR狀態,向IIC匯流排發生器件地址1010011+控制位0,表示寫入。比特位計數器cnt_bit用於比特位0-7的計數。在ACK1狀態,等待匯流排回應。ACK是從機,即EEPROM,傳回來的低電平信號。對於主機來說SDA線此時是高阻態,匯流排的上拉電阻將電位鉗位在高電位(sda為inout類型)。所以在後面,可以看到這裡是處於高阻態的。通信後面的流程根據狀態機的跳轉,在時序圖表現得明顯。在STOP停止狀態,FPGA向EEPROM發送停止信號,一次單位元組數據寫操作完成,拉低使能信號i2c_clk_enwe_en,並且及時觸發i2c_end,隨後狀態機跳回IDLE初始狀態。

image

讀控制時序處理與寫時序處理流程類似,具體見上圖,不同在ACK3狀態的跳轉,接收一個周期回應信號後,進入的是起始狀態START2,從而觸發第二次起始信號,再次寫入器件地址,讀取一位元組數據。rd_data_reg作為一個暫存器,存儲讀取到的位元組數據,待完成後,轉錄到rd_data。在N_ACK等待回應一個高電平回應信號,將i2c_scl由地拉高產生一個停止信號,同時觸發i2c_end。兩個操作過程具體狀態判斷條件,需具體分析,但都類似。

2、STATE狀態機轉換邏輯

i2c整個通信的過程可以分為很多階段,將它們細分開:空閑、起始、器件地址寫入、字/位元組地址寫入、各級響應、位元組數據寫入、讀取位元組數據、停止。主機視角對從設備操作過程,具體可見如下。

image

設備啟動,主機處於空閑狀態IDLE,待檢測到寫/讀操作信號後,程式轉入到第一個起始狀態START_1,表現為SDA線產生一個由高到低的電平轉換信號。接續進入器件地址寫入狀態SEND_ADDR,根據硬體圖,這裡預先設定器件是1010_011。器件確認後,向主機發生一個低電平回應ACK_1。寄存器存儲地址類型分為字地址和位元組地址,分別對應不同的狀態切換。完成後ACK_3響應後,寫操作,進入位元組數據寫入狀態WR_DATA,寫入一個預先數據,並回應ACK_4進入結束停止狀態STOP。而ACK_3響應後,進入讀操作流程,需再次發生起始START_2,接後寫入讀取目標地址,等待從機完成數據發送後,進入結束停止狀態STOP。最終保持一定周期,再次轉換至空閑狀態IDLE

根據時序圖邏輯,確定狀態機狀態轉換條件:

當前狀態 目標跳轉狀態 跳轉條件 操作類型
IDLE START1 i2c_start拉高 讀/寫
START1 SEND_ADDR cnt_i2c_clk == 2'd3(一個i2c_clk周期 ) 讀/寫
SEND_ADDR ACK1 (cnt_i2c_clk == 2'd3)&&(!ack) 讀/寫
ACK1 SEND_BH (cnt_bit == 3'd7)&&(cnt_i2c_clk == 2'd3) 讀/寫
SEND_BH ACK2 (cnt_bit == 3'd7)&&(cnt_i2c_clk == 2'd3) 讀/寫
ACK2 SEND_BL (cnt_i2c_clk == 2'd3)&&(!ack) 讀/寫
SEND_BL ACK3 (cnt_bit == 3'd7)&&(cnt_i2c_clk == 2'd3) 讀/寫
ACK3 WR_DATA (cnt_i2c_clk == 2'd3)&&(!ack) 讀/寫
WR_DATA ACK4 (cnt_bit == 3'd7)&&(cnt_i2c_clk == 2'd3)
ACK4 STOP (cnt_i2c_clk == 2'd3)&&(!ack)
ACK3 START2 rd_en拉高
START2 SEND_RA cnt_i2c_clk == 2'd3
SEND_RA ACK5 (cnt_bit == 3'd7)&&(cnt_i2c_clk == 2'd3)
ACK5 RD_DATA (cnt_i2c_clk == 2'd3)&&(!ack)
RD_DATA N_ACK (cnt_bit == 3'd7)&&(cnt_i2c_clk == 2'd3)
N_ACK STOP cnt_i2c_clk == 2'd3
STOP IDLE (cnt_bit == 3'd3)&&(cnt_i2c_clk == 2'd3) 讀/寫

對上面的表格可以總結,ACK1 - ACK5條件都是相同的,只是跳轉目標不同,單位元組讀/寫結束後狀態跳轉判斷條件相同,兩個起始狀態跳轉判斷條件也是相同的 ,N_ACKSTOP的跳轉註意區別。跳轉機Verilog HDL具體程式如下:

//dispose state condition
always @(posedge i2c_clk or negedge sys_rst)begin
    if(!sys_rst) state <= IDLE;
    else case(state)
        IDLE:   if(i2c_start) state <= START1;else state<= state;
        START1:  if(cnt_i2c_clk == 2'd3) state <= SEND_ADDR;
                else state <= state;
        SEND_ADDR:  
                if((cnt_bit == 3'd7)&&(cnt_i2c_clk == 2'd3))
                        state <= ACK1;
                else state <= state;
        ACK1:   if((cnt_i2c_clk == 2'd3)&&(!ack))begin
                    if(addr_num)state <= SEND_BH;
                    else state<= SEND_BL;
                end else state <= state;
        SEND_BH:if((cnt_bit == 3'd7)&&(cnt_i2c_clk == 2'd3))
                    state <= ACK2;
                else state<= state;
        ACK2:   if((cnt_i2c_clk == 2'd3)&&(!ack))
                    state <= SEND_BL;
                else state <= state;
        SEND_BL:if((cnt_bit == 3'd7)&&(cnt_i2c_clk == 2'd3))
                    state <= ACK3;
                else state <= state;
        ACK3:   if((cnt_i2c_clk == 2'd3)&&(!ack))begin
                    if(wr_en) state <= WR_DATA;
                    else if(rd_en) state <= START2;
                end
                else state <= state;
        WR_DATA:if((cnt_bit == 3'd7)&&(cnt_i2c_clk == 2'd3))
                    state <= ACK4;
                else state <= state;
        ACK4:   if((cnt_i2c_clk == 2'd3)&&(!ack))
                    state <= STOP;
                else state <= state;
        START2: if(cnt_i2c_clk == 2'd3) state <= SEND_RA;
                else state <= state;
        SEND_RA:if((cnt_bit == 3'd7)&&(cnt_i2c_clk == 2'd3))
                    state <= ACK5;
                else state <= state;
        ACK5:   if((cnt_i2c_clk == 2'd3)&&(!ack))
                    state <= RD_DATA;
                else state <= state;
        RD_DATA:if((cnt_bit == 3'd7)&&(cnt_i2c_clk == 2'd3))
                    state <= N_ACK;
                else state <= state;
        N_ACK:  if(cnt_i2c_clk == 2'd3)
                    state <= STOP;
                else state <= state;
        STOP:   if((cnt_bit == 3'd3)&&(cnt_i2c_clk == 2'd3))
                    state <= IDLE;
                else state <= state;
    endcase
end

STATE狀態機在整個程式過程中十分重要,i2c_scl時鐘電平和i2c_sda輸出電平情況,也要根據其狀態的不同做出具體分析。

//dispose i2c_scl sequence
always@(*)begin
    case(state)
        IDLE: i2c_scl <= 1'b1;
        START1:
            if(cnt_i2c_clk == 2'd3) i2c_scl <= 1'b0;
            else i2c_scl <= 1'b1;
        SEND_ADDR,ACK1,SEND_BH,ACK2,SEND_BL,
        ACK3,WR_DATA,ACK4,START2,SEND_RA,ACK5,RD_DATA,N_ACK:
            if((cnt_i2c_clk == 2'd1) || (cnt_i2c_clk == 2'd2)) i2c_scl <= 1'b1;
            else i2c_scl <= 1'b0;
        STOP:
            if((cnt_bit == 3'd0) &&(cnt_i2c_clk == 2'd0)) i2c_scl <=  1'b0;
            else i2c_scl <=  1'b1;
        default:    i2c_scl <=  1'b1;
    endcase
end

//dispose i2c_sda_reg & rd_data_reg sequence
always @(*)begin
    case(state)
        IDLE:       begin
                        i2c_sda_reg <= 1'b1;
                        rd_data_reg  <= 8'd0;
                    end
        START1:     if(cnt_i2c_clk == 2'd0) i2c_sda_reg <= 1'b1;
                    else i2c_sda_reg <= 1'b0;
        SEND_ADDR:  if(cnt_bit <= 3'd6) i2c_sda_reg<= DEVICE_ADDR[6-cnt_bit];
                    else i2c_sda_reg<= 1'b0;
        ACK1:       i2c_sda_reg<= 1'b1;
        SEND_BH:    i2c_sda_reg<= byte_addr[15-cnt_bit];
        ACK2:       i2c_sda_reg<= 1'b1;
        SEND_BL:    i2c_sda_reg<= byte_addr[7-cnt_bit];
        ACK3:       i2c_sda_reg<= 1'b1;
        WR_DATA:    i2c_sda_reg<= wr_data[7-cnt_bit];
        ACK4:       i2c_sda_reg<= 1'b1;
        START2:     if(cnt_i2c_clk <= 2'd1)i2c_sda_reg <= 1'b1;
                    else i2c_sda_reg <= 1'b0;
        SEND_RA:    if(cnt_bit <= 3'd6)i2c_sda_reg<= DEVICE_ADDR[6-cnt_bit];
                    else i2c_sda_reg<= 1'b1;
        ACK5:       i2c_sda_reg<= 1'b1;
        RD_DATA:    if(cnt_i2c_clk  == 2'd2) rd_data_reg[7-cnt_bit] <= sda_in;
                    else rd_data_reg <= rd_data_reg;
        N_ACK:      i2c_sda_reg<= 1'b1;
        STOP:       if((cnt_bit==3'd0)&&(cnt_i2c_clk<2'd3))i2c_sda_reg <= 1'b0;
                    else i2c_sda_reg<= 1'b1;
        default:    begin
                        i2c_sda_reg<= 1'b1;
                        rd_data_reg <=  rd_data_reg;
                    end
    endcase
end

其他信號在此不再列舉,它們的判斷條件相對狀態機來說處理起來比較簡單,邏輯也很清晰。

【階段模擬驗證】

下麵看下程式的模擬結果,查看四個:讀/寫操作啟動時刻、寫操作整體時序、讀操作整體時序、讀/寫操作結束時序。

操作啟動:write信號發生,write_valid拉高,併在下一i2c_clk上升沿拉高wr_en使能信號,等待cnt_wr完成計數150後,放低write_validcnt_startwr_en拉高後,開始計數。

image

寫操作(這裡器件地址設定1010_011):cnt_start計數到4900處,發起i2c_start觸發,state值變為4'h01,表示IDLE進入START1狀態,依次完成後續的狀態切換。STOP狀態下,待cnt_bit保持到3‘h3,結束停止狀態返回IDLEwr_en拉低,i2c_end拉高一個周期後放下。從下麵的模擬圖可以看到,整個寫操作的時序表現正常。

image

讀操作及讀操作結束模擬圖如下:

image

image

【最終上機驗證】

image

Quartus II生成框圖如下,兩個按鍵做為輸入觸發,輸出包括IIC通信匯流排SDA和SCL,數位管段選SEG_SEL和位選SEG_LED,以及外部掛載的兩個LED。程式燒錄後,設備低二位數位管顯示00,LED均處於熄滅狀態,按下key_wr後,led_wr點亮,表示寫操作啟動;接續,按下key_rd後,led_rd點亮,表示讀操作啟動,並且數位管顯示讀取數據8A。最終得到的現象與預期一致。

image

文獻參考:

[1] I2C匯流排規範 (https://sumcu.suda.edu.cn/_upload/article/files/74/e5/d4eb93de45808d71ad8aad542ede/a3cb5873-aaf4-4af0-9e5f-521793fbba46.pdf);

[2] 基於I2C協議的EEPROM驅動控制(https://doc.embedfire.com/fpga/altera/ep4ce10_mini/zh/latest/fpga/I2C.html);

[3] 王榮華. 可配置的IIC協議控制器IP核的設計[D]. 黑龍江:哈爾濱理工大學,2011.( DOI:10.7666/d.y2012472);


本篇文章中使用的Verilog程式模塊,若有需見網頁左欄Gitee倉庫鏈接:https://gitee.com/silly-big-head/little-mouse-funnyhouse/tree/FPGA-Verilog/


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

-Advertisement-
Play Games
更多相關文章
  • ‍ 寫在開頭 點贊 + 收藏 學會 前端常用的截圖保存的方法 利用 Blob 對象和 URL.createObjectURL:可以將截圖數據轉換為 Blob 對象,然後使用 URL.createObjectURL 方法生成一個臨時的 URL,將這個 URL 賦值給 <a> 標簽的 ...
  • 主要對面向過程編程與面向對象編程進行對比,介紹了軟體危機的背景,講解了面向對象編程設計思想的由來,對面向對象方法學:OOA-OOD-OOP進行簡單介紹。 ...
  • 表格配置屬性說明文檔 頁面添加引用: import BaseTable from ‘@/components/BaseTable/index.vue 1、grid-edit-width 表格操作欄寬度 例如:grid-edit-width:250 2、gridOtherConfig 屬性 說明 示例 ...
  • Spring Cloud是一個相對比較成熟的微服務框架。雖然,Spring Cloud於2016年才推出1.0的release版本, 時間最短, 但是相比Dubbo等RPC框架, Spring Cloud提供的全套的分散式系統解決方案。 Spring Cloud是一系列框架的有序集合。它利用Spri ...
  • Redis 的單線程與多線程之爭 為什麼 Redis 使用單線程 Redis 單線程為什麼還那麼快 Redis 6.0 引入多線程的原因 Redis 的網路模型 結語 ...
  • 一、如何使用代理方式打開網頁 在 playwright.chromium.launch() 中傳入 proxy 參數即可,示例代碼如下: 1、同步寫法: from playwright.sync_api import sync_playwright proxy = {'server': 'http: ...
  • Charapter 1: 端倪 最近一直在用Pyglet做一個小的案例,但是實際運行起來時發現了嚴重的記憶體泄漏。經調查後發現平均每秒會爆出80-120不等的頁面錯誤。且可以觀察到記憶體正在不斷地以0.2-0.4mb不等的速度增長。 能夠複原此問題的代碼如下: # 導入庫 import pyglet w ...
  • 壓縮 PDF 文件能有效減小文件大小並提高文件傳輸的效率,同時還能節省電腦存儲空間。除了使用一些專業工具對PDF文件進行壓縮,我們還可以通過 Python 來執行該操作,實現自動化、批量處理PDF文件。 本文將分享一個簡單有效的使用 Python 壓縮 PDF 文件的方法。需要用到 Spire.P ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...