• 【FPGA】IIC协议主机接口的设计与实现详解


    一、认识IIC

            IIC(I2C)协议是一种串行通信协议,用于连接微控制器和外围设备。IIC协议只需要两根信号线(时钟线SCL和数据线SDA)就能完成设备之间的通信;支持多主机和多从机通信,通过设备地址区分不同的设备;标准模式下可达100Kbit/s,快速模式下可达400Kbit/s,高速模式下可达3.4Mbit/s;具有应答机制,可以检测数据的正确性和设备的存在性。

    二、协议详解

            在闲置状态时,时钟线和数据线都会保持高电平IIC协议的具体传输过程如下:

            1. 主机发送起始信号,即在时钟线SCL保持高电平的情况下,数据线SDA由高电平向低电平跳变。
            2. 主机发送从机设备地址读写控制位,从机的设备地址一般为7位,读写控制位为1位,0表示写操作,1表示读操作。主机在发送完8位数据后,释放数据线SDA,等待从机的应答信号。
            3. 从机接收到地址和控制位后,进行地址识别,如果匹配,则在下一个时钟周期内,将数据线SDA拉低,表示应答信号。如果不匹配,则保持数据线SDA为高电平,等待主机发送停止信号或新的起始信号。
            4. 如果主机发送的是写操作,那么主机继续发送数据字节,每发送一个字节后,释放数据线SDA,等待从机的应答信号。如果从机接收到数据字节后,将数据线SDA拉低,表示应答信号。如果从机无法接收数据字节或者发生错误,那么保持数据线SDA为高电平,等待主机发送停止信号或新的起始信号。
            5. 如果主机发送的是读操作,那么主机释放数据线SDA,由从机发送数据字节。每接收一个字节后,主机根据需要发送应答信号或非应答信号。应答信号表示主机需要继续接收数据字节,非应答信号表示主机已经接收完毕或者发生错误。非应答信号由主机在最后一个时钟周期内将数据线SDA拉高实现。
            6. 主机发送停止信号,即在时钟线SCL保持高电平的情况下,数据线SDA由低电平向高电平跳变。停止信号表示一次IIC通信的结束。

            重要的起始位和结束位如下图所示,且在IIC协议中,控制数据线的设备会在SCL为低电平时控制SDA数据线,在SCL为高电平时对SDA数据线进行采样

    IIC协议的起始位与结束位

    数据的变化与采集

    三、具体读写时序

            具体读写时序图类似下图,这是我从一个支持IIC协议的设备的手册上截取下来的,值得一提的是,在下图中,灰色部分表示的是数据总线SDA由主机操纵,白色部分表示数据总线SDA由从机操纵。

            写数据时(如下图的I2C Register Wr):首先是主机发送一个起始位,接着是七位宽的设备地址加上一位宽的写控制位,从机响应一次;然后主机发送一字节的寄存器地址,从机响应一次;然后由主机发送一字节以上的数据,从机响应;最后发送一个停止位

           读数据时(如下图的I2C Register Rd):首先是主机发送一个起始位,接着是七位宽的设备地址加上一位宽的写控制位;然后是主机发送寄存器地址,从机应答;再重新发送一次起始位设备地址读控制位,用于表示接下来是要进行读操作,从机应答;从机发送数据,主机应答,在得到最后一字节数据后,由主机发送一个非应答信号;最后发送一个停止位

    IIC协议

    四、设计部分

            网站上大家对于iic的设计已经有非常详细的讲解了,这里就不再做太简单的说明,以下主要是我对于IIC的个人方面的一些理解和设计。

            1.接口设计

            作为被广泛使用的协议,我们在设计时应该尽可能的设计成通用模块,以便下一次能够直接使用。因此,模块的接口定义应为如图所示:

    IIC模块接口

            clk  为输入时钟;

            rst  为复位信号(高有效);

            txdat  表示需要通过IIC输出的数据;

            txget  表示已输出该字节的信号;

            rxdat  表示从sda获得的数据;

            rxvld  表示数据有效;

            dev_addr  表示设备地址;

            reg_addr  表示读写地址(寄存器地址);

            cmd_op  表示读写指令;

            op_byte_num  表示读写字节数;

            iic_work_en  表示模块开始工作信号;

            busy  表示工作状态信号;

            scl  表示IIC时钟;

            sda  表示IIC数据;

            2.状态机设计

            因为要做一个IIC协议会比较复杂,所以说这里采用状态机的形式,以理清每一个操作。但是一般我们看到的IIC状态设计、包括在之前我设计IIC时,都会将每一个操作都分为一个状态,例如,起始位是一个状态,发送设备地址是一个状态,读写控制位是一个状态,接收应答和发送非应答分别为一个状态等等,这样的划分十分的细致,每一个操作都能一目了然,但我认为这样做的同时会使得状态的跳转更加的复杂,使得整个状态机变得十分的臃肿,如下图。

    以前的IIC状态框图设计
    我以前的IIC状态框图设计

            于是,我便重新构思了一下设计方案:以9位(9bit)为一个进行周期设计,即每9位或1位为一个状态。具体状态如下:

            状态一:IDLE  表示闲置状态;

            状态二:START  表示起始位;

            状态三:DEV_CMD  表示设备地址位、读写控制位宽和从机的应答位;

            状态四:ADDR  表示发送寄存器地址位和接收从机的应答位;

            状态五:WDATA  表示写数据部分;

            状态六:RDATA  表示读数据部分;

            状态七:STOP  表示停止位;

    重新设计的IIC状态框图
    重新设计的IIC状态框图

            3.时序设计

            写数据时序:

    写时序1

    写时序2

            读数据时序:

    读时序1

    读时序2

            4.代码设计

            首先,定义模块

            其中,我使用的时钟为100Mhz,复位信号为高电平有效:

    1. module i2c_master_interface #(
    2. parameter ADDR_LEN = 1,
    3. parameter IIC_CLOCK = 400000
    4. ) (
    5. input i_clk ,
    6. input i_rst ,
    7. input [7:0] i_txdat ,
    8. output o_txget ,
    9. output [7:0] o_rxdat ,
    10. output o_rxvld ,
    11. input [6:0] i_dev_addr ,
    12. input [ADDR_LEN*8-1:0] i_reg_addr,
    13. input i_cmd_op ,
    14. input [3:0] i_op_byte_num ,
    15. input i_iic_work_en ,
    16. output o_busy ,
    17. //output o_output_en,
    18. inout io_scl ,
    19. inout io_sda
    20. );
    21. endmodule

            第二步,进行参数的定义

    1. localparam SCL_PERIOD = 1000/(IIC_CLOCK/100000) , // in_clk = 100MHz
    2. SCL_HALF = SCL_PERIOD >> 1 ,
    3. LOW_HALF = SCL_HALF >> 1 ,
    4. HIGH_HALF = (SCL_PERIOD+SCL_HALF)>>1;
    5. localparam IDLE = 7'b000_0001,
    6. START = 7'b000_0010,
    7. DEV_CMD = 7'b000_0100,
    8. ADDR = 7'b000_1000,
    9. WDATA = 7'b001_0000,
    10. RDATA = 7'b010_0000,
    11. STOP = 7'b100_0000;
    12. localparam WRITE_BIT = 1'b0,
    13. READ_BIT = 1'b1,
    14. OP_WRITE = 1'b0,
    15. OP_READ = 1'b1;

            第三步,定义需要用到的信号

    1. reg [6:0] state_c ;
    2. reg [6:0] state_n ;
    3. reg [7:0] cnt_scl ;
    4. reg reg_scl ;
    5. reg [3:0] cnt_bit ;
    6. wire end_cnt_bit;
    7. reg [3:0] bit_num ;
    8. reg rw_bit ;
    9. reg [3:0] cnt_byte;
    10. reg [3:0] op_byte_num ;
    11. reg [ADDR_LEN*8-1:0] reg_addr_1;
    12. reg [7:0] reg_addr_2 ;
    13. reg [7:0] reg_txdat ;
    14. reg txget ;
    15. reg reg_sda ;
    16. reg reg_i_sda ;
    17. reg [7:0] rx_data ;
    18. reg reg_ack ;
    19. reg rxvld ;
    20. reg busy ;
    21. wire o_sda;
    22. wire i_sda;
    23. wire o_scl;
    24. wire i_scl;
    25. wire output_en;

            第四步,构造有限状态机

            依照逻辑,状态机的跳转条件如下:

    构造状态机

    1. // FMS
    2. always @(posedge i_clk or posedge i_rst) begin
    3. if (i_rst) begin
    4. state_c <= IDLE;
    5. end
    6. else begin
    7. state_c <= state_n;
    8. end
    9. end
    10. always @(*) begin
    11. case (state_c)
    12. IDLE : begin
    13. if (i_iic_work_en) begin
    14. state_n = START;
    15. end
    16. else begin
    17. state_n = state_c;
    18. end
    19. end
    20. START : begin
    21. if (end_cnt_bit) begin
    22. state_n = DEV_CMD;
    23. end
    24. else begin
    25. state_n = state_c;
    26. end
    27. end
    28. DEV_CMD : begin
    29. if (end_cnt_bit && reg_ack) begin // NO ACK
    30. state_n = IDLE;
    31. end
    32. else if ((rw_bit == WRITE_BIT) && end_cnt_bit && (reg_ack == 0)) begin //
    33. state_n = ADDR;
    34. end
    35. else if ((rw_bit == READ_BIT) && end_cnt_bit && (reg_ack == 0)) begin //
    36. state_n = RDATA;
    37. end
    38. else begin
    39. state_n = state_c;
    40. end
    41. end
    42. ADDR : begin
    43. if (end_cnt_bit && reg_ack) begin
    44. state_n = IDLE;
    45. end
    46. else if ((i_cmd_op == OP_WRITE) && (cnt_byte == (ADDR_LEN - 1)) && end_cnt_bit && (reg_ack == 0)) begin //
    47. state_n = WDATA;
    48. end
    49. else if ((i_cmd_op == OP_READ) && (cnt_byte == (ADDR_LEN - 1)) && end_cnt_bit && (reg_ack == 0)) begin //
    50. state_n = START;
    51. end
    52. else begin
    53. state_n = state_c;
    54. end
    55. end
    56. WDATA : begin
    57. if (end_cnt_bit && reg_ack) begin
    58. state_n = IDLE;
    59. end
    60. else if ((cnt_byte == (op_byte_num - 1)) && end_cnt_bit && (reg_ack == 0)) begin //
    61. state_n = STOP;
    62. end
    63. else begin
    64. state_n = state_c;
    65. end
    66. end
    67. RDATA : begin
    68. if ((cnt_byte == (op_byte_num - 1)) && end_cnt_bit) begin //
    69. state_n = STOP;
    70. end
    71. else begin
    72. state_n = state_c;
    73. end
    74. end
    75. STOP : begin
    76. if (end_cnt_bit) begin
    77. state_n = IDLE;
    78. end
    79. else begin
    80. state_n = state_c;
    81. end
    82. end
    83. default: state_n = IDLE;
    84. endcase
    85. end

            第五步,构造中间逻辑

    1. // cnt_bit bit_num
    2. always @(posedge i_clk or posedge i_rst) begin
    3. if (i_rst) begin
    4. cnt_bit <= 0;
    5. end
    6. else if ((state_c != IDLE) && (cnt_scl == (SCL_PERIOD - 1))) begin
    7. if (end_cnt_bit) begin
    8. cnt_bit <= 0;
    9. end
    10. else begin
    11. cnt_bit <= cnt_bit + 1;
    12. end
    13. end
    14. end
    15. assign end_cnt_bit = (state_c != IDLE) && (cnt_scl == (SCL_PERIOD - 1)) && (cnt_bit == (bit_num - 1));
    16. always @(*) begin
    17. case (state_c)
    18. IDLE : bit_num = 0;
    19. START : bit_num = 1;
    20. DEV_CMD : bit_num = 9;
    21. ADDR : bit_num = 9;
    22. WDATA : bit_num = 9;
    23. RDATA : bit_num = 9;
    24. STOP : bit_num = 1;
    25. default : bit_num = 0;
    26. endcase
    27. end
    28. // rw_bit
    29. always @(posedge i_clk or posedge i_rst) begin
    30. if (i_rst) begin
    31. rw_bit <= WRITE_BIT;
    32. end
    33. else begin
    34. case (i_cmd_op)
    35. OP_WRITE : rw_bit <= WRITE_BIT;
    36. OP_READ : begin
    37. if ((state_c == STOP) && end_cnt_bit) begin
    38. rw_bit <= WRITE_BIT;
    39. end
    40. else if ((state_c == DEV_CMD) && (rw_bit == WRITE_BIT) && end_cnt_bit && (reg_ack == 0)) begin //
    41. rw_bit <= READ_BIT;
    42. end
    43. end
    44. default : rw_bit <= rw_bit;
    45. endcase
    46. end
    47. end
    48. // cnt_byte op_byte_num
    49. always @(posedge i_clk or posedge i_rst) begin
    50. if (i_rst) begin
    51. cnt_byte <= 0;
    52. end
    53. else if (state_c == IDLE) begin
    54. cnt_byte <= 0;
    55. end
    56. else if ((state_c == ADDR) && end_cnt_bit && (reg_ack == 0)) begin
    57. if (cnt_byte == (ADDR_LEN - 1)) begin
    58. cnt_byte <= 0;
    59. end
    60. else begin
    61. cnt_byte <= cnt_byte + 1;
    62. end
    63. end
    64. else if ((state_c == RDATA || state_c == WDATA) && end_cnt_bit && (reg_ack == 0)) begin
    65. if (cnt_byte == (op_byte_num - 1)) begin
    66. cnt_byte <= 0;
    67. end
    68. else begin
    69. cnt_byte <= cnt_byte + 1;
    70. end
    71. end
    72. end
    73. always @(posedge i_clk or posedge i_rst) begin
    74. if (i_rst) begin
    75. op_byte_num <= 1;
    76. end
    77. else if ((state_c == START) && end_cnt_bit) begin
    78. op_byte_num <= i_op_byte_num;
    79. end
    80. end
    81. // reg_addr_1 reg_addr_2
    82. always @(posedge i_clk or posedge i_rst) begin
    83. if (i_rst) begin
    84. reg_addr_1 <= 0;
    85. end
    86. else if ((state_c == START) && end_cnt_bit) begin
    87. reg_addr_1 <= i_reg_addr;
    88. end
    89. end
    90. always @(posedge i_clk or posedge i_rst) begin
    91. if (i_rst) begin
    92. reg_addr_2 <= 0;
    93. end
    94. else if (state_c == ADDR) begin
    95. reg_addr_2 <= reg_addr_1[((ADDR_LEN - cnt_byte)*8-1) -: 8]; //
    96. end
    97. end
    98. // reg_txdat txget
    99. always @(posedge i_clk or posedge i_rst) begin
    100. if (i_rst) begin
    101. reg_txdat <= 8'hff;
    102. end
    103. else if (state_c == WDATA) begin
    104. reg_txdat <= i_txdat;
    105. end
    106. end
    107. always @(posedge i_clk or posedge i_rst) begin
    108. if (i_rst) begin
    109. txget <= 1'b0;
    110. end
    111. else if ((state_c == WDATA) && end_cnt_bit) begin
    112. txget <= 1'b1;
    113. end
    114. else begin
    115. txget <= 1'b0;
    116. end
    117. end

            第六步,构建SCL上的逻辑

    1. // cnt_scl reg_scl
    2. always @(posedge i_clk or posedge i_rst) begin
    3. if (i_rst) begin
    4. cnt_scl <= 0;
    5. end
    6. else if (state_c != IDLE) begin
    7. if (cnt_scl == (SCL_PERIOD - 1)) begin
    8. cnt_scl <= 0;
    9. end
    10. else begin
    11. cnt_scl <= cnt_scl + 1;
    12. end
    13. end
    14. end
    15. always @(posedge i_clk or posedge i_rst) begin
    16. if (i_rst) begin
    17. reg_scl <= 0;
    18. end
    19. else if (cnt_scl == SCL_HALF - 1) begin
    20. reg_scl <= 1;
    21. end
    22. else if (cnt_scl == SCL_PERIOD - 1) begin
    23. reg_scl <= 0;
    24. end
    25. else begin
    26. reg_scl <= reg_scl;
    27. end
    28. end

            第七步,构建SDA上的逻辑

    1. // reg_sda
    2. always @(posedge i_clk or posedge i_rst) begin
    3. if (i_rst) begin
    4. reg_sda <= 1'b1;
    5. end
    6. else begin
    7. case (state_c)
    8. START : begin
    9. if (cnt_scl == LOW_HALF - 1) begin
    10. reg_sda <= 1'b1;
    11. end
    12. else if (cnt_scl == HIGH_HALF -1) begin
    13. reg_sda <= 1'b0;
    14. end
    15. end
    16. DEV_CMD : begin
    17. if (cnt_scl == LOW_HALF - 1) begin
    18. case (cnt_bit)
    19. 0 : reg_sda <= i_dev_addr[6];
    20. 1 : reg_sda <= i_dev_addr[5];
    21. 2 : reg_sda <= i_dev_addr[4];
    22. 3 : reg_sda <= i_dev_addr[3];
    23. 4 : reg_sda <= i_dev_addr[2];
    24. 5 : reg_sda <= i_dev_addr[1];
    25. 6 : reg_sda <= i_dev_addr[0];
    26. 7 : reg_sda <= rw_bit;
    27. default: reg_sda <= reg_sda;
    28. endcase
    29. end
    30. end
    31. ADDR : begin
    32. if (cnt_scl == LOW_HALF - 1) begin
    33. case (cnt_bit)
    34. 0 : reg_sda <= reg_addr_2[7];
    35. 1 : reg_sda <= reg_addr_2[6];
    36. 2 : reg_sda <= reg_addr_2[5];
    37. 3 : reg_sda <= reg_addr_2[4];
    38. 4 : reg_sda <= reg_addr_2[3];
    39. 5 : reg_sda <= reg_addr_2[2];
    40. 6 : reg_sda <= reg_addr_2[1];
    41. 7 : reg_sda <= reg_addr_2[0];
    42. default: reg_sda <= reg_sda;
    43. endcase
    44. end
    45. end
    46. WDATA : begin
    47. if (cnt_scl == LOW_HALF - 1) begin
    48. case (cnt_bit)
    49. 0 : reg_sda <= reg_txdat[7];
    50. 1 : reg_sda <= reg_txdat[6];
    51. 2 : reg_sda <= reg_txdat[5];
    52. 3 : reg_sda <= reg_txdat[4];
    53. 4 : reg_sda <= reg_txdat[3];
    54. 5 : reg_sda <= reg_txdat[2];
    55. 6 : reg_sda <= reg_txdat[1];
    56. 7 : reg_sda <= reg_txdat[0];
    57. default: reg_sda <= reg_sda;
    58. endcase
    59. end
    60. end
    61. RDATA : begin // SACK
    62. if ((cnt_scl == LOW_HALF - 1) && (cnt_byte == op_byte_num - 1) && (cnt_bit == 8)) begin //
    63. reg_sda <= 1'b1; // NACK
    64. end
    65. else if ((cnt_scl == LOW_HALF - 1) && (cnt_bit == 8)) begin
    66. reg_sda <= 1'b0; // ACK
    67. end
    68. end
    69. STOP : begin
    70. if (cnt_scl == LOW_HALF - 1) begin
    71. reg_sda <= 1'b0;
    72. end
    73. else if (cnt_scl == HIGH_HALF -1) begin
    74. reg_sda <= 1'b1;
    75. end
    76. end
    77. default : reg_sda <= reg_sda;
    78. endcase
    79. end
    80. end
    81. // reg_i_sda rx_data reg_ack rxvld
    82. always @(posedge i_clk or posedge i_rst) begin
    83. if (i_rst) begin
    84. reg_i_sda <= 1'b1;
    85. end
    86. else begin
    87. reg_i_sda <= i_sda;
    88. end
    89. end
    90. always @(posedge i_clk or posedge i_rst) begin
    91. if (i_rst) begin
    92. rx_data <= 0;
    93. end
    94. else if ((state_c == RDATA) && (cnt_scl == HIGH_HALF - 1)) begin
    95. case (cnt_bit)
    96. 0 : rx_data[7] <= reg_i_sda;
    97. 1 : rx_data[6] <= reg_i_sda;
    98. 2 : rx_data[5] <= reg_i_sda;
    99. 3 : rx_data[4] <= reg_i_sda;
    100. 4 : rx_data[3] <= reg_i_sda;
    101. 5 : rx_data[2] <= reg_i_sda;
    102. 6 : rx_data[1] <= reg_i_sda;
    103. 7 : rx_data[0] <= reg_i_sda;
    104. default: rx_data <= rx_data;
    105. endcase
    106. end
    107. end
    108. always @(posedge i_clk or posedge i_rst) begin
    109. if (i_rst) begin
    110. reg_ack <= 1'b1;
    111. end
    112. else if ((state_c == DEV_CMD || state_c == ADDR || state_c == WDATA) && (cnt_bit == 8) && (cnt_scl == HIGH_HALF -1)) begin
    113. reg_ack <= reg_i_sda;
    114. end
    115. end
    116. always @(posedge i_clk or posedge i_rst) begin
    117. if (i_rst) begin
    118. rxvld <= 1'b0;
    119. end
    120. else if ((state_c == RDATA) && end_cnt_bit) begin
    121. rxvld <= 1'b1;
    122. end
    123. else begin
    124. rxvld <= 1'b0;
    125. end
    126. end

            第八步,构造busy信号

    1. // busy
    2. always @(posedge i_clk or posedge i_rst) begin
    3. if (i_rst) begin
    4. busy <= 1'b0;
    5. end
    6. else if (i_iic_work_en) begin
    7. busy <= 1'b1;
    8. end
    9. // else if ((state_c == STOP) && end_cnt_bit) begin
    10. // busy <= 1'b0;
    11. // end
    12. else if (state_c == IDLE) begin
    13. busy <= 1'b0;
    14. end
    15. else begin
    16. busy <= 1'b1;
    17. end
    18. end

            第九步,赋值输出

    1. // output
    2. assign o_scl = (state_c == IDLE)? 1 : reg_scl;
    3. assign o_txget = txget;
    4. assign o_rxdat = rx_data;
    5. assign o_rxvld = rxvld;
    6. assign o_busy = busy;
    7. assign o_sda = reg_sda;
    8. assign output_en = (state_c == RDATA)? ((cnt_bit == 8)? 1:0) : ((cnt_bit == 8)? 0:1);
    9. //assign o_output_en = output_en;

            最后一步,构建三态门

            这里使用vivado中的原语IO_BUF来构建:

    1. IOBUF #(
    2. .DRIVE(12), // Specify the output drive strength
    3. .IBUF_LOW_PWR("TRUE"), // Low Power - "TRUE", High Performance = "FALSE"
    4. .IOSTANDARD("DEFAULT"), // Specify the I/O standard
    5. .SLEW("SLOW") // Specify the output slew rate
    6. ) iobuf_inst_sda (
    7. .O ( i_sda ), // Buffer output
    8. .IO ( io_sda ), // Buffer inout port (connect directly to top-level port)
    9. .I ( o_sda ), // Buffer input
    10. .T (~output_en ) // 3-state enable input, high=input, low=output
    11. );
    12. IOBUF #(
    13. .DRIVE(12), // Specify the output drive strength
    14. .IBUF_LOW_PWR("TRUE"), // Low Power - "TRUE", High Performance = "FALSE"
    15. .IOSTANDARD("DEFAULT"), // Specify the I/O standard
    16. .SLEW("SLOW") // Specify the output slew rate
    17. ) iobuf_inst_scl (
    18. .O ( i_scl ), // Buffer output
    19. .IO ( io_scl ), // Buffer inout port (connect directly to top-level port)
    20. .I ( o_scl ), // Buffer input
    21. .T ( 0 ) // 3-state enable input, high=input, low=output
    22. );

            5.仿真验证

            写时序

    写时序仿真

            读时序:

    读时序仿真

             其中,我们可以清楚的看到以下关键信息:

            起始位:

            状态二,设备地址与写控制位:

             状态三,发送寄存器地址:

            写数据操作,写了5个字节:

            读操作时的RESTART状态,设备地址、读控制位以及读出的1字节数据:

            停止位: 

    五、说明

            由以上设计可以看出,我设计的SCL是由低到高的,同时,在起始位的状态时,SCL也同样会由低到高,这与通常我们所理解的SCL在起始位保持高电平不符,如下图所示:

             但经过我的上板实测,该方案可行。

            第二点, 由以上设计可以看出,由于时序逻辑的影响,导致busy信号的输出会比state信号延迟一个周期,这意味这最好使用检测busy下降沿的方法来判断IIC接口是否还在工作,或者更改busy信号的赋值逻辑。

  • 相关阅读:
    Shell编程之正则表达式
    【Linux初阶】信号入门2 | 信号阻塞、捕捉、保存
    PaaS平台的应用趋势是什么?
    计算机毕业设计Java校园教务系统登录(源码+系统+mysql数据库+Lw文档)
    LLaMA的解读与其微调:Alpaca-LoRA/Vicuna/BELLE/中文LLaMA/姜子牙/LLaMA 2
    你的网站还在使用HTTP? 免费升级至HTTPS吧
    面试总结-Redis篇章(十二)——Redis是单线程的,为什么还那么快
    yuv420并转为bgr
    cmakelist中查找boost和eigen3
    基于Java公益志愿捐赠管理系统设计与实现(源码+LW+调试+开题报告)
  • 原文地址:https://blog.csdn.net/nazonomaster/article/details/133964227