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

news2024/12/27 5:35:30

一、认识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,复位信号为高电平有效:

module i2c_master_interface #(
    parameter ADDR_LEN  =   1,
    parameter IIC_CLOCK =   400000
    ) (
    input           i_clk           ,
    input           i_rst           ,

    input   [7:0]   i_txdat         ,
    output          o_txget         ,
    output  [7:0]   o_rxdat         ,
    output          o_rxvld         ,
    input   [6:0]   i_dev_addr      ,
    input [ADDR_LEN*8-1:0] i_reg_addr,
    input           i_cmd_op        ,
    input   [3:0]   i_op_byte_num   ,
    input           i_iic_work_en   ,
    output          o_busy          ,

//output o_output_en,
    inout           io_scl          ,
    inout           io_sda           
);



endmodule

        第二步,进行参数的定义

localparam  SCL_PERIOD  =   1000/(IIC_CLOCK/100000) ,   // in_clk = 100MHz
            SCL_HALF    =   SCL_PERIOD >> 1         ,
            LOW_HALF    =   SCL_HALF >> 1           ,
            HIGH_HALF   =   (SCL_PERIOD+SCL_HALF)>>1;

localparam  IDLE    =   7'b000_0001,
            START   =   7'b000_0010,
            DEV_CMD =   7'b000_0100,
            ADDR    =   7'b000_1000,
            WDATA   =   7'b001_0000,
            RDATA   =   7'b010_0000,
            STOP    =   7'b100_0000;

localparam  WRITE_BIT   =   1'b0,
            READ_BIT    =   1'b1,
            OP_WRITE    =   1'b0,
            OP_READ     =   1'b1;

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

reg [6:0]   state_c ;
reg [6:0]   state_n ;

reg [7:0]   cnt_scl ;
reg         reg_scl ;
reg [3:0]   cnt_bit ;
wire        end_cnt_bit;
reg [3:0]   bit_num ;
reg         rw_bit  ;

reg [3:0]   cnt_byte;
reg [3:0]   op_byte_num     ;

reg [ADDR_LEN*8-1:0] reg_addr_1;
reg [7:0]   reg_addr_2      ;

reg [7:0]   reg_txdat       ;
reg         txget           ;

reg         reg_sda         ;
reg         reg_i_sda       ;
reg [7:0]   rx_data         ;
reg         reg_ack         ;
reg         rxvld           ;
reg         busy            ;

wire o_sda;
wire i_sda;
wire o_scl;
wire i_scl;
wire output_en;

        第四步,构造有限状态机

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

构造状态机

// FMS
always @(posedge i_clk or posedge i_rst) begin
    if (i_rst) begin
        state_c <= IDLE;
    end
    else begin
        state_c <= state_n;
    end
end

always @(*) begin
    case (state_c)
        IDLE    : begin
            if (i_iic_work_en) begin
                state_n = START;
            end
            else begin
                state_n = state_c;
            end
        end
        START   : begin
            if (end_cnt_bit) begin
                state_n = DEV_CMD;
            end
            else begin
                state_n = state_c;
            end
        end
        DEV_CMD : begin
            if (end_cnt_bit && reg_ack) begin   // NO ACK
                state_n = IDLE;
            end
            else if ((rw_bit == WRITE_BIT) && end_cnt_bit && (reg_ack == 0)) begin   //
                state_n = ADDR;
            end
            else if ((rw_bit == READ_BIT) && end_cnt_bit && (reg_ack == 0)) begin    //
                state_n = RDATA;
            end
            else begin
                state_n = state_c;
            end
        end
        ADDR    : begin
            if (end_cnt_bit && reg_ack) begin
                state_n = IDLE;
            end
            else if ((i_cmd_op == OP_WRITE) && (cnt_byte == (ADDR_LEN - 1)) && end_cnt_bit && (reg_ack == 0)) begin  //
                state_n = WDATA;
            end
            else if ((i_cmd_op == OP_READ) && (cnt_byte == (ADDR_LEN - 1)) && end_cnt_bit && (reg_ack == 0)) begin  //
                state_n = START;
            end
            else begin
                state_n = state_c;
            end
        end
        WDATA   : begin
            if (end_cnt_bit && reg_ack) begin
                state_n = IDLE;
            end
            else if ((cnt_byte == (op_byte_num - 1)) && end_cnt_bit && (reg_ack == 0)) begin  //
                state_n = STOP;
            end
            else begin
                state_n = state_c;
            end
        end
        RDATA   : begin
            if ((cnt_byte == (op_byte_num - 1)) && end_cnt_bit) begin   //
                state_n = STOP;
            end
            else begin
                state_n = state_c;
            end
        end
        STOP    : begin
            if (end_cnt_bit) begin
                state_n = IDLE;
            end
            else begin
                state_n = state_c;
            end
        end
        default: state_n = IDLE;
    endcase
end

        第五步,构造中间逻辑

// cnt_bit  bit_num
always @(posedge i_clk or posedge i_rst) begin
    if (i_rst) begin
        cnt_bit <= 0;
    end
    else if ((state_c != IDLE) && (cnt_scl == (SCL_PERIOD - 1))) begin
        if (end_cnt_bit) begin
            cnt_bit <= 0;
        end
        else begin
            cnt_bit <= cnt_bit + 1;
        end
    end
end
assign end_cnt_bit = (state_c != IDLE) && (cnt_scl == (SCL_PERIOD - 1)) && (cnt_bit == (bit_num - 1));

always @(*) begin
    case (state_c)
        IDLE    : bit_num = 0;
        START   : bit_num = 1;
        DEV_CMD : bit_num = 9;
        ADDR    : bit_num = 9;
        WDATA   : bit_num = 9;
        RDATA   : bit_num = 9;
        STOP    : bit_num = 1;
        default : bit_num = 0;
    endcase
end

// rw_bit
always @(posedge i_clk or posedge i_rst) begin
    if (i_rst) begin
        rw_bit <= WRITE_BIT;
    end
    else begin
        case (i_cmd_op)
            OP_WRITE : rw_bit <= WRITE_BIT;
            OP_READ  : begin
                if ((state_c == STOP) && end_cnt_bit) begin
                    rw_bit <= WRITE_BIT;
                end
                else if ((state_c == DEV_CMD) && (rw_bit == WRITE_BIT) && end_cnt_bit && (reg_ack == 0)) begin   //
                    rw_bit <= READ_BIT;
                end
            end
            default  : rw_bit <= rw_bit;
        endcase
    end
end

// cnt_byte  op_byte_num
always @(posedge i_clk or posedge i_rst) begin
    if (i_rst) begin
        cnt_byte <= 0;
    end
    else if (state_c == IDLE) begin
        cnt_byte <= 0;
    end
    else if ((state_c == ADDR) && end_cnt_bit && (reg_ack == 0)) begin
        if (cnt_byte == (ADDR_LEN - 1)) begin
            cnt_byte <= 0;
        end
        else begin
            cnt_byte <= cnt_byte + 1;
        end
    end
    else if ((state_c == RDATA || state_c == WDATA) && end_cnt_bit && (reg_ack == 0)) begin
        if (cnt_byte == (op_byte_num - 1)) begin
            cnt_byte <= 0;
        end
        else begin
            cnt_byte <= cnt_byte + 1;
        end
    end
end

always @(posedge i_clk or posedge i_rst) begin
    if (i_rst) begin
        op_byte_num <= 1;
    end
    else if ((state_c == START) && end_cnt_bit) begin
        op_byte_num <= i_op_byte_num;
    end
end


// reg_addr_1  reg_addr_2
always @(posedge i_clk or posedge i_rst) begin
    if (i_rst) begin
        reg_addr_1 <= 0;
    end
    else if ((state_c == START) && end_cnt_bit) begin
        reg_addr_1 <= i_reg_addr;
    end
end

always @(posedge i_clk or posedge i_rst) begin
    if (i_rst) begin
        reg_addr_2 <= 0;
    end
    else if (state_c == ADDR) begin
        reg_addr_2 <= reg_addr_1[((ADDR_LEN - cnt_byte)*8-1) -: 8];  //
    end
end

// reg_txdat  txget
always @(posedge i_clk or posedge i_rst) begin
    if (i_rst) begin
        reg_txdat <= 8'hff;
    end
    else if (state_c == WDATA) begin
        reg_txdat <= i_txdat;
    end
end

always @(posedge i_clk or posedge i_rst) begin
    if (i_rst) begin
        txget <= 1'b0;
    end
    else if ((state_c == WDATA) && end_cnt_bit) begin
        txget <= 1'b1;
    end
    else begin
        txget <= 1'b0;
    end
end

        第六步,构建SCL上的逻辑

// cnt_scl  reg_scl
always @(posedge i_clk or posedge i_rst) begin
    if (i_rst) begin
        cnt_scl <= 0;
    end
    else if (state_c != IDLE) begin
        if (cnt_scl == (SCL_PERIOD - 1)) begin
            cnt_scl <= 0;
        end
        else begin
            cnt_scl <= cnt_scl + 1;
        end
    end
end

always @(posedge i_clk or posedge i_rst) begin
    if (i_rst) begin
        reg_scl <= 0;
    end
    else if (cnt_scl == SCL_HALF - 1) begin
        reg_scl <= 1;
    end
    else if (cnt_scl == SCL_PERIOD - 1) begin
        reg_scl <= 0;
    end
    else begin
        reg_scl <= reg_scl;
    end
end

        第七步,构建SDA上的逻辑

// reg_sda
always @(posedge i_clk or posedge i_rst) begin
    if (i_rst) begin
        reg_sda <= 1'b1;
    end
    else begin
        case (state_c)
            START   : begin
                if (cnt_scl == LOW_HALF - 1) begin
                    reg_sda <= 1'b1;
                end
                else if (cnt_scl == HIGH_HALF -1) begin
                    reg_sda <= 1'b0;
                end
            end
            DEV_CMD : begin
                if (cnt_scl == LOW_HALF - 1) begin
                    case (cnt_bit)
                        0 : reg_sda <= i_dev_addr[6];
                        1 : reg_sda <= i_dev_addr[5];
                        2 : reg_sda <= i_dev_addr[4];
                        3 : reg_sda <= i_dev_addr[3];
                        4 : reg_sda <= i_dev_addr[2];
                        5 : reg_sda <= i_dev_addr[1];
                        6 : reg_sda <= i_dev_addr[0];
                        7 : reg_sda <= rw_bit;
                        default: reg_sda <= reg_sda;
                    endcase
                end
            end
            ADDR    : begin
                if (cnt_scl == LOW_HALF - 1) begin
                    case (cnt_bit)
                        0 : reg_sda <= reg_addr_2[7];
                        1 : reg_sda <= reg_addr_2[6];
                        2 : reg_sda <= reg_addr_2[5];
                        3 : reg_sda <= reg_addr_2[4];
                        4 : reg_sda <= reg_addr_2[3];
                        5 : reg_sda <= reg_addr_2[2];
                        6 : reg_sda <= reg_addr_2[1];
                        7 : reg_sda <= reg_addr_2[0];
                        default: reg_sda <= reg_sda;
                    endcase
                end
            end
            WDATA   : begin
                if (cnt_scl == LOW_HALF - 1) begin
                    case (cnt_bit)
                        0 : reg_sda <= reg_txdat[7];
                        1 : reg_sda <= reg_txdat[6];
                        2 : reg_sda <= reg_txdat[5];
                        3 : reg_sda <= reg_txdat[4];
                        4 : reg_sda <= reg_txdat[3];
                        5 : reg_sda <= reg_txdat[2];
                        6 : reg_sda <= reg_txdat[1];
                        7 : reg_sda <= reg_txdat[0];
                        default: reg_sda <= reg_sda;
                    endcase
                end
            end
            RDATA   : begin     // SACK
                if ((cnt_scl == LOW_HALF - 1) && (cnt_byte == op_byte_num - 1) && (cnt_bit == 8)) begin  //
                    reg_sda <= 1'b1;    // NACK
                end
                else if ((cnt_scl == LOW_HALF - 1) && (cnt_bit == 8)) begin
                    reg_sda <= 1'b0;    // ACK
                end
            end
            STOP    : begin
                if (cnt_scl == LOW_HALF - 1) begin
                    reg_sda <= 1'b0;
                end
                else if (cnt_scl == HIGH_HALF -1) begin
                    reg_sda <= 1'b1;
                end
            end
            default : reg_sda <= reg_sda;
        endcase
    end
end

// reg_i_sda  rx_data  reg_ack  rxvld
always @(posedge i_clk or posedge i_rst) begin
    if (i_rst) begin
        reg_i_sda <= 1'b1;
    end
    else begin
        reg_i_sda <= i_sda;
    end
end

always @(posedge i_clk or posedge i_rst) begin
    if (i_rst) begin
        rx_data <= 0;
    end
    else if ((state_c == RDATA) && (cnt_scl == HIGH_HALF - 1)) begin
        case (cnt_bit)
            0 : rx_data[7] <= reg_i_sda;
            1 : rx_data[6] <= reg_i_sda;
            2 : rx_data[5] <= reg_i_sda;
            3 : rx_data[4] <= reg_i_sda;
            4 : rx_data[3] <= reg_i_sda;
            5 : rx_data[2] <= reg_i_sda;
            6 : rx_data[1] <= reg_i_sda;
            7 : rx_data[0] <= reg_i_sda;
            default: rx_data <= rx_data;
        endcase
    end
end

always @(posedge i_clk or posedge i_rst) begin
    if (i_rst) begin
        reg_ack <= 1'b1;
    end
    else if ((state_c == DEV_CMD || state_c == ADDR || state_c == WDATA) && (cnt_bit == 8) && (cnt_scl == HIGH_HALF -1)) begin
        reg_ack <= reg_i_sda;
    end
end

always @(posedge i_clk or posedge i_rst) begin
    if (i_rst) begin
        rxvld <= 1'b0;
    end
    else if ((state_c == RDATA) && end_cnt_bit) begin
        rxvld <= 1'b1;
    end
    else begin
        rxvld <= 1'b0;
    end
end

        第八步,构造busy信号

// busy
always @(posedge i_clk or posedge i_rst) begin
    if (i_rst) begin
        busy <= 1'b0;
    end
    else if (i_iic_work_en) begin
        busy <= 1'b1;
    end
    // else if ((state_c == STOP) && end_cnt_bit) begin
    //     busy <= 1'b0;
    // end
    else if (state_c == IDLE) begin
        busy <= 1'b0;
    end
    else begin
        busy <= 1'b1;
    end
end

        第九步,赋值输出

// output
assign o_scl = (state_c == IDLE)? 1 : reg_scl;
assign o_txget = txget;
assign o_rxdat = rx_data;
assign o_rxvld = rxvld;
assign o_busy = busy;
assign o_sda = reg_sda;
assign output_en = (state_c == RDATA)? ((cnt_bit == 8)? 1:0) : ((cnt_bit == 8)? 0:1);

//assign o_output_en = output_en;

        最后一步,构建三态门

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

IOBUF #(
   .DRIVE(12), // Specify the output drive strength
   .IBUF_LOW_PWR("TRUE"),  // Low Power - "TRUE", High Performance = "FALSE" 
   .IOSTANDARD("DEFAULT"), // Specify the I/O standard
   .SLEW("SLOW") // Specify the output slew rate
) iobuf_inst_sda (
   .O   (   i_sda   ),     // Buffer output
   .IO  (  io_sda   ),   // Buffer inout port (connect directly to top-level port)
   .I   (   o_sda   ),     // Buffer input
   .T   (~output_en )      // 3-state enable input, high=input, low=output
);

IOBUF #(
   .DRIVE(12), // Specify the output drive strength
   .IBUF_LOW_PWR("TRUE"),  // Low Power - "TRUE", High Performance = "FALSE" 
   .IOSTANDARD("DEFAULT"), // Specify the I/O standard
   .SLEW("SLOW") // Specify the output slew rate
) iobuf_inst_scl (
   .O   (   i_scl   ),     // Buffer output
   .IO  (  io_scl   ),   // Buffer inout port (connect directly to top-level port)
   .I   (   o_scl   ),     // Buffer input
   .T   (     0     )      // 3-state enable input, high=input, low=output
);

        5.仿真验证

        写时序

写时序仿真

        读时序:

读时序仿真

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

        起始位:

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

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

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

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

        停止位: 

五、说明

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1123541.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

医院智能电力系统解决方案

摘要&#xff1a;智能电力系统主要体现在“智能”&#xff0c;在医院中智能电力系统主要以数字电力系统为主&#xff0c;它主要表现为信息化、自动化。通过对数据的采集分析&#xff0c;以及反馈传输运行。其中医院智能电力系统优点在于&#xff0c;(1)通过医院的生产数据&…

移动端点击任意下拉框或复选框文本框都会出现绿色背景?

css自带的样式实在太恶心了&#xff0c;下图任意点击下拉框&#xff0c;复选框都会出现绿色背景。 解决方式如下&#xff1a; * {-webkit-tap-highlight-color: rgba(0,0,0,0); }

【数据结构与算法】JavaScript实现队列结构(Queue)

文章目录 JavaScript实现队列结构&#xff08;Queue&#xff09;一、队列简介二、封装队列类2.1.代码实现2.2.队列的应用 三、优先队列3.1.优先级队列的实现3.2.注意点 JavaScript实现队列结构&#xff08;Queue&#xff09; 一、队列简介 队列是是一种受限的线性表&#xff…

ROS1 常用命令行工具

1. 启动ros 主节点 roscore roscore运行成功如图&#xff1a; 1.1 rosrun 启动服务节点 例子&#xff1a;启动一个小乌龟节点 rosrun turtlesim turtlesim_node运行结果如图&#xff1a; 1.2 启动键盘控制 打开新的命令窗口&#xff0c;启动turtle_teleop_key 节点 rosr…

Servlet中乱码解决

总决式:解决乱码.不写中文 创建个Javaweb项目,演示并解决各种乱码 直接next pom.xml里自动引入了servletapi 目录结构 一般创建好项目先设置下maven与Encoding 建个Servlet测试 启动并访问 无论控制台输出用GBK还是UTF8 都是乱码 此时需要修改Tomcat/conf/logging.propertie…

Cesium 实战 - 调整色调、对比度等参数,加载渲染暗黑底图

Cesium 实战 - 调整色调、对比度等参数&#xff0c;加载渲染暗黑底图 渲染暗黑底图核心代码完整代码&#xff1a;在线示例 本文包括渲染暗黑底图核心代码、完整代码以及在线示例。 渲染暗黑底图核心代码 这里放上核心代码&#xff1a; /*** todo 开启暗黑底图* param {Object}…

为何在中国 Navicat 远比 DBeaver 流行

Bytebase 面向全球&#xff0c;通常调研我们产品的 DBA 和开发者之前已经在用可视化 SQL 客户端来操作数据库。我们发现一个现象&#xff0c;在国内 Navicat 的占有率要远远高于其他的 SQL 客户端。而在我们接触的国外客户里&#xff0c;Navicat 的存在感又远没有国内那么高&am…

TinySnippet轻UI组件开发系列教程 — DataGrid选择事件

应用场景 DataGrid选择一条记录后&#xff0c;希望能够 调用纳流或者打开指定的页面 或者当鼠标悬停在一行时弱出一个指定的页面在右下角&#xff08;移开就关闭弹出页&#xff09; 这些都是我们能够实现的 实现过程 本文以调用纳流为例 下载UI组件 TinySnippet-v4.1.2导…

Netty 入门 — Bootstrap,一切从这里开始

上篇文章&#xff08;Netty 入门 — 要想掌握 Netty&#xff0c;你必须知道它的这些核心组件&#xff09;大明哥阐述了 Netty 的整体结构&#xff0c;从这篇文章开始大明哥就将这个整体进行拆分讲解&#xff0c;今天是第一个核心组件&#xff1a;Bootstrap。 一句话来概括 Boot…

在Telegram营销后该如何进行客户管理

与目标客户进行接触之后&#xff0c;我们就要开始考虑后续怎么去销售自己的产品。这个过程可能是很漫长的&#xff0c;我们需要经常去联系对方&#xff0c;回答对方的疑问。但是现实中通常一个员工会手握多个账号&#xff0c;很难及时知道每个账号的信息&#xff0c;管理客户成…

基于Python实现的快速的仿手写文字的图片生成器项目源码

Quick Hand &#x1f4dd; 介绍 快速的仿手写文字的图片生成器。 完整代码下载地址&#xff1a;基于Python实现的快速的仿手写文字的图片生成器 界面预览&#xff1a; &#x1f52e; 使用说明 原理&#xff1a;首先&#xff0c;在水平位置、竖直位置和字体大小三个自由度上…

数据结构与算法之图: Leetcode 133. 克隆图 (Typescript版)

克隆图 https://leetcode.cn/problems/clone-graph/description/ 描述 给你无向 连通 图中一个节点的引用&#xff0c;请你返回该图的 深拷贝&#xff08;克隆&#xff09;。 图中的每个节点都包含它的值 val&#xff08;int&#xff09; 和其邻居的列表&#xff08;list[No…

iPhone怎么屏蔽短信?屏蔽骚扰短信,就用这2招!

如今&#xff0c;信息互联网快速发展&#xff0c;为我们的生活带来了很多好处。但同时我们也随时面临着隐私泄露的风险。大家的手机上是不是经常会收到很多骚扰信息&#xff1f;“叮咚~”&#xff0c;你以为手机收到了重要的信息&#xff0c;但其实只是一则毫无用处的短信。 这…

git提交代码产生冲突的解决方法

1.产生冲突的提示 2.解决&#xff1a;根据你的情况选择保留当前更改或者远程仓库 点击Accept Current Change 或者Accept incoming change 即可

STM32-LCD中英文显示及应用

字符编码 由于计算机只能识别0和1&#xff0c;所以文字需要以0和1的形式在计算机内继续存储&#xff0c;故需要对文字进行编码。最简单的编码就是ASCII码。 ASCII码&#xff08;8位&#xff09; ASCII码分两部分&#xff1a; 0~31&#xff1a;控制字符或通讯字符。没有特定的图…

庆祝创造力和技术:2023年的1024程序员节

2023年的10月24日已经来临&#xff0c;这意味着我们又迎来了一年一度的程序员节。这是一个属于全球程序员社区的节日&#xff0c;一个庆祝创造力、创新和技术的时刻。无论你是一名职业程序员、技术爱好者&#xff0c;还是对编程世界感兴趣的新手&#xff0c;1024程序员节都是一…

园区组网配置实例

项目拓扑与项目需求 项目需求 某企业网络组网如下&#xff1a;vlan10属于办公网络&#xff0c;vlan20外来人员访客网络&#xff0c;vlan30 属于云桌面网络。还包括公共服务器&#xff0c;所属vlan为100. 需要实现如下需求&#xff1a; vlan10 和vlan20 和vlan100属于相同网段…

用Rust和cURL库做一个有趣的爬虫

以下是一个使用 Rust 和 cURL 库的下载器程序&#xff0c;用于从wechat下载音频。此程序使用了 [/get_proxy] 提供的代码。 extern crate curl;use std::io::{self, Read}; use std::process::exit; use curl::easy::Easy;fn main() {let url "https://www.wechat.com/au…

图片base64说明

将一张图片数据编码成一串字符串&#xff0c;使用该字符串代替图片地址url 前端页面中常见的base64图片引入方式&#xff1a; 优点&#xff1a; ①、base64格式的图片是文本格式&#xff0c;占用内存小&#xff0c;转换后的大小比例大概为1/3&#xff0c;降低了资源服务器的…

C#开发的OpenRA游戏之金钱系统(4)

C#开发的OpenRA游戏之金钱系统(4) 前面已经分析怎么样找到资源收割的位置,接着下来就是怎么样移动到资源的坐标,以及怎么样进行收割资源。现在就来分析这个相关的代码,这些功能都在文件HarvestResource.cs里,声明了一个类HarvestResource。 类HarvestResource就是在确定…