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
  • 問題 有很多應用程式在驗證JSON數據的時候用到了JSON Schema。 在微服務架構下,有時候各個微服務由於各種歷史原因,它們所生成的數據對JSON Object屬性名的大小寫規則可能並不統一,它們需要消費的JSON數據的屬性名可能需要大小寫無關。 遺憾的是,目前的JSON Schema沒有這方 ...
  • 首先下載centos07鏡像,建議使用阿裡雲推薦的地址: https://mirrors.aliyun.com/centos/7.9.2009/isos/x86_64/?spm=a2c6h.25603864.0.0.59b5f5ad5Nfr0X 其實這裡就已經出現第一個坑了 centos 07 /u ...
  • 相信很多.NETer看了標題,都會忍不住好奇,點進來看看,並且順便準備要噴作者! 這裡,首先要申明一下,作者本人也非常喜歡Linq,也在各個項目中常用Linq。 我愛Linq,Linq優雅萬歲!!!(PS:順便吐槽一下,隔壁Java從8.0版本推出的Streams API,抄了個四不像,一點都不優雅 ...
  • 在人生的重要時刻,我站在了畢業的門檻上,望著前方的道路,心中涌動著對未來的無限憧憬與些許忐忑。面前,兩條道路蜿蜒伸展:一是繼續在職場中尋求穩定,一是勇敢地走出一條屬於自己的創新之路。儘管面臨年齡和現實的挑戰,我仍舊選擇勇往直前,用技術這把鑰匙,開啟新的人生篇章。 迴首過去,我深知時間寶貴,精力有限。 ...
  • 單元測試 前言 時隔多個月,終於抽空學習了點新知識,那麼這次來記錄一下C#怎麼進行單元測試,單元測試是做什麼的。 我相信大部分剛畢業的都很疑惑單元測試是乾什麼的?在小廠實習了6個月後,我發現每天除了寫CRUD就是寫CRUD,幾乎用不到單元測試。寫完一個功能直接上手去測,當然這隻是我個人感受,僅供參考 ...
  • 一:背景 1. 講故事 最近在分析dump時,發現有程式的卡死和WeakReference有關,在以前只知道怎麼用,但不清楚底層邏輯走向是什麼樣的,藉著這個dump的契機來簡單研究下。 二:弱引用的玩法 1. 一些基礎概念 用過WeakReference的朋友都知道這裡面又可以分為弱短和弱長兩個概念 ...
  • 最近想把ET打表工具的報錯提示直接調用win系統彈窗,好讓策劃明顯的知道表格哪裡填錯數據,彈窗需要調用System.Windows.Forms庫。操作如下: 需要在 .csproj 文件中添加: <UseWindowsForms>true</UseWindowsForms> 須將目標平臺設置為 Wi ...
  • 從C#3開始,拓展方法這一特性就得到了廣泛的應用。 此功能允許你能夠使用實例方法的語法調用某個靜態方法,以下是一個獲取/創建文件的靜態方法: public static async Task<StorageFile> GetOrCreateFileAsync(this StorageFolder f ...
  • 在Windows 11下,使用WinUI2.6以上版本的ListView長這樣: 然而到了Win10上,儘管其他控制項的樣式沒有改變,但ListViewItem變成了預設樣式(初代Fluent) 最重大的問題是,Win10上的HorizontalAlignment未被設置成Stretch,可能造成嚴重 ...
  • 前言 周六在公司加班,幹完活後越顯無聊,想著下載RabbiitMQ做個小項目玩玩。然而這一下就下載了2個小時,真讓人頭痛。 簡單的講一下如何安裝吧,網上教程和踩坑文章還是很多的,我講我感覺有用的文章放在本文末尾。 安裝地址 erlang 下載 - Erlang/OTP https://www.erl ...