FPGA实现IIC接口协议

  • IIC协议简介

通讯协议(Inter-Integrated Circuit)是由 Philips 公司开发的一种简单、双向二线制同步串行总线,只需要两根线即可在连接于总线上的器件之间传送信息。IIC 总线只使用两条总线:一条双向串行数据线(SDA),一条双向串行时钟线 (SCL)。数据线SDA用来表示数据,时钟线SCL用于同步数据收发。每个连接到总线的设备都有一个独立的地址,主机可以利用这个地址进行不同设备之间的访问。

总线通过上拉电阻接到电源。当 IIC 设备空闲时,会输出高阻态,而当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平。IIC总线是一个支持多设备的总线,所有设备都共享一根数据线SDA。所以需要每个设备都有一个唯一的地址,每个 I2C 设备都具备7位/10位器件地址。在7-bit地址格式中,在起始条件后,主机发送的第1个字节,该字节高7位为从机地址,最低位为R/W位-“0”表示发送(写),主机向从机写数据,“1”表示请求数据(读)主机读取从机数据。

主机在与从机建立通讯时,主机会将控制命令直接发送到串行数据线 SDA 上,与主机硬件相连的从机设备都会接收到主机发送的控制命令。所有从机设备在接收到主机发送的控制命令后会与自身器件地址做对比;若两者地址相同,该从机设备会回应一个应答信号告知主机设备,主机设备接收到应答信号后,主从设备建立通讯连接,两者即可开始进行数据通讯。

常见的IIC协议的传输速率有100kbps,400kbps,3.4Mbps。

  • IIC协议时序图

IIC协议的写时序如下图所示。

图片来源:孤独的单刀

       起始信号到达后,SDA数据线拉低,表示开始通信,然后发送7位器件地址的最高位+0(0表示写,1表示读),发送完成后释放总线,等待从机的应答信号,应答成功后开始依次发送8位寄存器地址的最高位与最低位,发送完成后再次等待从机应答,然后发送待写入的8位数据,等待应答,结束应答信后。将总线拉高,表示一次写入完成。

    IIC协议的读时序如下图所示。

图片来源:孤独的单刀

    读时序与写时序的不同在于发送完寄存器地址后需要再次发送7位器件地址+1(表示读操作),然后释放总线,等待从机发送数据。

  • 设计思路
  1. 根据系统时钟频率与iic_clk驱动时钟频率计算出分频系数,以产生iic驱动时钟,通信频率就是SCL的频率,根据时序图可以看出,iic_clk的频率为SCL频率的四倍
  2. 利用计算出的分频系数生成计数器,从而产生iic_clk驱动时钟
  3. 对iic_clk驱动时钟进行计数,注意此时应该用iic_clk的上升沿,计数四次以产生SCL
  4. 编写状态机来实现主体功能,一定要注意状态跳转的条件还有每个状态的输出
  5. 设计一个8位计数器对传输数据进行计数
  • 代码实现

IIC驱动的代码实现如下:

`timescale 1ns / 1ps

module IIC_drive(
    input                                   clk,                                    //系统时钟
    input                                   rst_n,                                  //系统复位
    input                                   iic_rw,                                 //IIC读写控制信号
    input                 [7:0]             w_data,                                 //写入从机的8位数据
    input                                   iic_start,                              //开始信号
    input                 [7:0]             iic_addr,                               //IIC字节地址

    output              reg                 iic_end,                                //结束信号
    output              reg[7:0]            r_data,                                 //读取8位数据
    output              reg                 scl,                                    //IIC时钟
    output              reg                 iic_clk,                                //IIC驱动时钟
    
    inout                                   sda                                     //IIC数据线
);

    parameter SYS_CLK           =   28'd100_000_000             ;                   //系统时钟频率
    parameter IIC_FREQ          =   20'd400_000                 ;                   //IIC通信频率,即SCL频率
    parameter DIV_CNT           =   SYS_CLK / IIC_FREQ >> 2'd3  ;                   //分频系数
    parameter DEVICE_ADDR       =   7'b1010_000;

    localparam IDLE             =   4'd0                        ,                   //空闲状态
               IIC_START        =   4'd1                        ,                   //开始状态
               SEND_D_ADDR_W    =   4'd2                        ,                   //发送7位器件码+写操作
               ACK1             =   4'd3                        ,                   //从机响应
               SEND_R_ADDR      =   4'd4                        ,                   //发送8位地址码
               ACK2             =   4'd5                        ,                   //从机响应
               W_DATA           =   4'd6                        ,                   //写入8位数据
               ACK3             =   4'd7                        ,                   //从机响应
               IIC_START2       =   4'd8                        ,                   //第二次开始状态
               SEND_D_ADDR_R    =   4'd9                        ,                   //发送7位器件码+读操作
               ACK4             =   4'd10                       ,                   //从机响应
               R_DATA           =   4'd11                       ,                   //读取8位数据
               NACK             =   4'd12                       ,                   //非应答状态
               STOP             =   4'd13                       ;                   //结束状态
    
    reg                 [3:0]               state , next_state  ;                   //现态与次态

    reg                 [4:0]               iic_cnt;                                //产生IIC驱动时钟计数器
    reg                 [1:0]               iic_clk_cnt;                            //对驱动时钟计数以产生SCL           
    reg                                     iic_clk_cnt_en;                         //驱动时钟计数使能信号,拉高开始计数
    reg                 [2:0]               bit_cnt;                                //发送或读取数据计数器
    reg                                     ack_flag;                               //从机响应信号   
    reg                                     sda_en;                                 //三态门使能
    reg                                     sda_out;                                //三态门输出
    reg                 [7:0]               r_data_temp;                            //暂存读取到的8位数据         

    wire                                    sda_in;                                 //三态门输入
    wire                [7:0]               addr_w;                                 //器件码+写操作
    wire                [7:0]               addr_r;                                 //器件码+读操作

    assign  addr_w = {DEVICE_ADDR,1'b0};            
    assign  addr_r = {DEVICE_ADDR,1'b1};    

    assign sda_in = sda;                                                            //双向端口处理
    assign sda = sda_en ? sda_out : 1'bz;    

    always @(posedge clk or negedge rst_n) begin                                    //产生驱动时钟
        if(!rst_n)begin
            iic_cnt <= 5'd0;
            iic_clk <= 1'b0;
        end
        else if(iic_cnt == DIV_CNT - 1'b1)begin                                     //计数到分频系数,计数器清零,时钟翻转
            iic_cnt <= 5'd0;
            iic_clk <= ~iic_clk;
        end
        else begin
            iic_cnt <= iic_cnt + 1'b1;                                              //否则保持不变
            iic_clk <= iic_clk;
        end
    end

    always @(posedge iic_clk or negedge rst_n) begin                                    
        if(!rst_n)
            iic_clk_cnt_en <= 1'b0;
        else if((state == IDLE && !iic_start) || (state == STOP && iic_clk_cnt == 2'd3))    //没有收到开始信号或者发送完结束信号时拉低
            iic_clk_cnt_en <= 1'b0;
        else
            iic_clk_cnt_en <= 1'b1;                                                 //否则拉高
    end

    always @(posedge iic_clk or negedge rst_n) begin                                    
        if(!rst_n)
            iic_clk_cnt <= 2'd0;
        else if(iic_clk_cnt_en)
            iic_clk_cnt <= iic_clk_cnt + 1'b1;                                      //使能信号拉高开始计数
        else
            iic_clk_cnt <= 2'd0;
    end

    always @(posedge iic_clk or negedge rst_n) begin                                //三段式状态机第一段                 
        if(!rst_n)
            state <= IDLE;
        else
            state <= next_state;
    end

    always@(*)begin
        next_state = IDLE;
        case (state)
            IDLE : 
                if(iic_start)
                    next_state = IIC_START;                                         //接收到开始信号,跳转到开始状态
                else
                    next_state = IDLE;
            IIC_START : 
                if(iic_clk_cnt == 2'd3)
                    next_state = SEND_D_ADDR_W;                                     //iic_clk_cnt计数到3,跳转到发送器件地址和写操作
                else
                    next_state = IIC_START;
            SEND_D_ADDR_W :
                if(iic_clk_cnt == 2'd3 && bit_cnt == 3'd7)                          //成功写入器件码和写操作,进入响应状态
                    next_state = ACK1;
                else
                    next_state = SEND_D_ADDR_W;
            ACK1 :
                if(ack_flag && iic_clk_cnt == 2'd3)                                 //响应有效
                    next_state = SEND_R_ADDR;
                else if(iic_clk_cnt == 2'd3)                                        //响应无效或者响应不及时
                    next_state = IDLE;
                else
                    next_state = ACK1;
            SEND_R_ADDR : 
                if(iic_clk_cnt == 2'd3 && bit_cnt == 3'd7)                          //成功写入8位地址码
                    next_state = ACK2;
                else
                    next_state = SEND_R_ADDR;
            ACK2 :
                if(ack_flag && iic_clk_cnt == 2'd3)
                    if(!iic_rw)                                                     //iic_rw低电平表示写操作,跳转到写入8位数据状态
                        next_state = W_DATA;
                    else                                                            //iic_rw高电平表示读操作,跳转到重新发送7位器件码+读操作
                        next_state = IIC_START2;
                else if(iic_clk_cnt == 2'd3)                                        //响应不及时或响应无效
                    next_state = IDLE;
                else
                    next_state = ACK2;
            W_DATA : 
                if(bit_cnt == 3'd7 && iic_clk_cnt == 2'd3)                          //成功写入8位数据
                    next_state = ACK3;
                else
                    next_state = W_DATA;
            ACK3 :
                if(ack_flag && iic_clk_cnt == 2'd3)
                    next_state = STOP;
                else if(iic_clk_cnt == 2'd3)                                        //响应不及时或响应无效
                    next_state =  IDLE;
                else
                    next_state = ACK3;
            IIC_START2 :
                if(iic_clk_cnt == 2'd3)
                    next_state <= SEND_D_ADDR_R;
                else
                    next_state <= IIC_START2;
            SEND_D_ADDR_R :
                if(bit_cnt == 3'd7 && iic_clk_cnt == 2'd3)
                    next_state = ACK4;
                else
                    next_state = SEND_D_ADDR_R;
            ACK4 :
                if(ack_flag && iic_clk_cnt == 2'd3)
                    next_state = R_DATA;
                else if(iic_clk_cnt == 2'd3)                                       //响应不及时或响应无效
                    next_state =  IDLE;
                else
                    next_state = ACK4;
            R_DATA :
                if(bit_cnt == 3'd7 && iic_clk_cnt == 2'd3)                         //成功读取8位数据
                    next_state = NACK;
                else
                    next_state = R_DATA;
            NACK :
                 if(iic_clk_cnt == 2'd3)                                            //非响应信号
                    next_state = STOP;
                else
                    next_state = NACK;
            STOP :
                if(iic_clk_cnt == 2'd3)                                            //停止状态结束,跳转IDLE状态
                    next_state = IDLE;
                else
                    next_state = STOP;
            default: next_state = IDLE;                                            //默认为IDLE状态
        endcase
    end

    always @(posedge iic_clk or negedge rst_n) begin
        if(!rst_n)begin
            sda_en <= 1'b1;
            sda_out <= 1'b1;
            iic_end <= 1'b0;
            r_data <= 8'd0;
            r_data_temp <= 8'd0;
            bit_cnt <= 3'd0;
        end
        else begin
            iic_end = 1'b0;                                                     //iic_end信号一直保持为低电平,STOP状态才拉高
            case (state)
                IDLE : begin
                    sda_en <= 1'b1;                                             //空闲状态时sda保持高电平
                    sda_out <= 1'b1;
                end 
                IIC_START : begin
                    if(iic_clk_cnt == 2'd3)begin                                //开始信号发送完成
                        if(addr_w[7])begin                                      //下一个状态表将要发送的第一个数据,提前拉高总线,方便下一个状态发送
                            sda_out <= 1'b1;
                            sda_en <= 1'b1;
                        end
                        else begin
                            sda_out <= 1'b0;
                            sda_en <= 1'b1;
                        end
                    end
                    else begin
                        sda_out <= 1'b0;                                        //进入开始状态时sda拉低,表示发送开始
                        sda_en <= 1'b1;
                    end
                end
                SEND_D_ADDR_W : begin
                    if(bit_cnt == 3'd7)
                        if(iic_clk_cnt == 2'd3)begin
                            sda_en <= 1'b0;                                     //8位数据发送完成,释放总线等待从机响应
                            bit_cnt <= 3'd0;
                        end
                        else begin
                            sda_en <= 1'b1;
                            bit_cnt <= bit_cnt;
                        end
                    else if(iic_clk_cnt == 2'd3)begin
                        sda_en <= 1'b1;
                        bit_cnt <= bit_cnt + 1'b1;
                        sda_out <= addr_w[6 - bit_cnt];                         //将8位数据传输给sda总线
                    end
                    else begin
                        sda_en <= 1'b1;
                        bit_cnt <= bit_cnt;
                        sda_out <= sda_out;   
                    end
                end
                ACK1 :  begin
                    if(iic_clk_cnt == 2'd3)begin                                     //成功接收响应信号
                        if(iic_addr[7])begin
                            sda_en <= 1'b1;
                            sda_out <= 1'b1;
                        end
                        else begin
                            sda_en <= 1'b1;
                            sda_out <= 1'b0;
                        end
                    end
                    else  
                        sda_en <= 1'b0;                                        //响应信号还未接收完成
                end
                SEND_R_ADDR :begin
                    if(bit_cnt == 3'd7)begin                                    //成功发送8位寄存器码
                        if(iic_clk_cnt == 2'd3)begin
                            sda_en <= 1'b0;                                     //发送完成释放总线,等待应答
                            bit_cnt <= 3'd0;
                        end
                    end
                    else if(iic_clk_cnt == 2'd3)begin
                        sda_en <= 1'b1;
                        sda_out <= iic_addr[6 - bit_cnt];                   //发送过程
                        bit_cnt <= bit_cnt + 1'b1;
                    end
                end
                ACK2 : begin
                    bit_cnt <= 3'd0;
                    if(!iic_rw)begin                
                        if(iic_clk_cnt == 2'd3)begin                         
                            if(w_data[7])begin                                   //iic_rw为低电平,主机往从机写入数据              
                                sda_en <= 1'b1;
                                sda_out <= 1'b1;
                            end
                            else begin
                                sda_en <= 1'b1;
                                sda_out <= 1'b0;
                            end
                        end
                        else begin                                             //iic_rw为高电平,再次发送开始信号,拉高sda_out
                            sda_en <= 1'b1;
                            sda_out <= 1'b0;
                        end
                    end
                    else begin
                        if(iic_clk_cnt == 2'd3)begin
                            sda_en <= 1'b1;   
                            sda_out <= 1'b1;                                     //未接收到响应信号
                        end
                        else begin
                            sda_en <= 1'b1;
                            sda_out <= 1'b0;
                        end
                    end
                end
                W_DATA : begin
                    if(bit_cnt == 3'd7)begin                            //发送完成释放总线,等待应答
                        if(iic_clk_cnt == 2'd3)begin
                            sda_en <= 1'b0;
                            bit_cnt <= 3'd0;
                        end
                    end
                    else if(iic_clk_cnt == 2'd3)begin
                        sda_en <= 1'b1;
                        sda_out <= w_data[6 - bit_cnt];
                        bit_cnt <= bit_cnt + 1'b1;
                    end
                end
                ACK3 : begin
                    bit_cnt <= 3'd0;
                    if(iic_clk_cnt == 2'd3)begin                        //成功接收响应信号,拉低sda_out
                        sda_en <= 1'b1;
                        sda_out <= 1'b0; 
                    end
                    else
                        sda_en <= 1'b0;                        
                end
                IIC_START2 : begin
                    if(iic_clk_cnt == 2'd3)begin
                        if(addr_r[7])begin
                            sda_en <= 1'b1;
                            sda_out <= 1'b1;
                        end
                        else begin
                            sda_en <= 1'b1;
                            sda_out <= 1'b0;
                        end
                    end
                    else if(iic_clk_cnt == 2'd1)begin                                                  //开始信号还未发送完成
                        sda_en <= 1'b1;
                        sda_out <= 1'b0;
                    end
                end
                SEND_D_ADDR_R : begin
                    if(bit_cnt == 3'd7)begin
                        if(iic_clk_cnt == 2'd3)begin
                            sda_en <= 1'b0;                                     //8位数据发送完成,释放总线等待从机响应
                            bit_cnt <= 3'd0;
                        end
                        else begin
                            sda_en <= 1'b1;
                            bit_cnt <= bit_cnt;
                            sda_out <= 1'b1;
                        end
                    end
                    else if(iic_clk_cnt == 2'd3)begin
                        sda_en <= 1'b1;
                        bit_cnt <= bit_cnt + 1'b1;
                        sda_out <= addr_r[6 - bit_cnt];                         //将8位数据传输给sda总线
                    end
                    else begin
                        sda_en <= 1'b1;
                        sda_out <= sda_out;
                        bit_cnt <= bit_cnt;
                    end
                end
                ACK4 : begin
                        sda_en <= 1'b0;                                       //应答信号结束,释放总线,准备读取数据
                end
                R_DATA : begin
                    if(iic_clk_cnt == 2'd3)begin
                        if(bit_cnt == 3'd7)begin
                            r_data <= r_data_temp;                          //8位数据读取完成
                            sda_en <= 1'b1;
                            sda_out <= 1'b1;                                //后续为非应答信号
                        end
                        else
                            bit_cnt <= bit_cnt + 1'b1;
                    end
                    else if(iic_clk_cnt == 2'd1)begin
                        r_data_temp[7 - bit_cnt] <= sda_in;
                    end
                end
                NACK : begin
                    bit_cnt <= 8'd0;
                    r_data_temp <= 8'd0;
                    if(iic_clk_cnt == 2'd3)begin
                        sda_en <= 1'b1;
                        sda_out <= 1'b0;                                            //将总线拉低,方便后续发送结束信号
                    end
                    else begin
                        sda_en <= 1'b1;
                        sda_out <= 1'b1;                                            //总线处于高电平为非应答状态
                    end
                end
                STOP : begin
                    if(iic_clk_cnt == 2'd2 && bit_cnt == 3'd0)begin
                        sda_en <= 1'b1;
                        sda_out <= 1'b1;                                            //拉高总线作为结束标志
                    end
                    else if(iic_clk_cnt == 2'd3)begin
                        iic_end <= 1'b1;
                    end
                end
                default:;
            endcase
        end
    end

    always @(posedge iic_clk or negedge rst_n) begin                                    
        if(!rst_n)
            scl <= 1'b1;
        else if(state != STOP)begin
            if(iic_clk_cnt == 2'd2)
                scl <= 1'b0;
            else if(iic_clk_cnt == 2'd0)
                scl <= 1'b1;
        end
        else
            scl <= 1'b1;
    end

    always @(posedge iic_clk or negedge rst_n) begin                                    
        if(!rst_n)
            ack_flag <= 1'b0;
        else begin
            case (state)
                ACK1,ACK2,ACK3,ACK4:
                    if(!sda_in && iic_clk_cnt == 2'd1)
                        ack_flag <= 1'b1;
                    else if(iic_clk_cnt == 2'd3)
                        ack_flag <= 1'b0; 
                default: ack_flag <= 1'b0;
            endcase
        end
    end

endmodule

测试代码如下:

`timescale 1ns / 1ps
module tb_IIC_drive;

// IIC_drive Parameters
parameter PERIOD       = 10                        ;
parameter SYS_CLK      = 28'd100_000_000           ;
parameter IIC_FREQ     = 20'd400_000               ;
parameter DIV_CNT      = SYS_CLK / IIC_FREQ >> 2'd3;
parameter DEVICE_ADDR  = 7'b1010_000               ;

// IIC_drive Inputs
reg   clk                                   ;
reg   rst_n                                 ;
reg   iic_rw                                ;
reg   [7:0]  w_data                         ;
reg   iic_start                             ;
reg   [7:0]  iic_addr                       ;

// IIC_drive Outputs
wire  iic_end                              ;
wire  [7:0]               r_data           ;
wire  scl                                  ;
wire  iic_clk                              ;

// IIC_drive Bidirs
wire  sda                                  ;


initial
begin
    forever #(PERIOD/2)  clk=~clk;
end

initial
begin
    #(PERIOD*2) rst_n  =  1;
end

IIC_drive #(
    .SYS_CLK     ( SYS_CLK     ),
    .IIC_FREQ    ( IIC_FREQ    ),
    .DIV_CNT     ( DIV_CNT     ),
    .DEVICE_ADDR ( DEVICE_ADDR ))
 u_IIC_drive (
    .clk                         ( clk                               ),
    .rst_n                       ( rst_n                             ),
    .iic_rw                      ( iic_rw                            ),
    .w_data                      ( w_data                            ),
    .iic_start                   ( iic_start                         ),
    .iic_addr                    ( iic_addr                          ),

    .iic_end                     ( iic_end                           ),
    .r_data                      (r_data                             ),
    .scl                         ( scl                               ),
    .iic_clk                     ( iic_clk                           ),

    .sda                         ( sda                               )
);

EEPROM_AT24C64 u_EEPROM_AT24C64_inst(
    .scl         (scl),
    .sda         (sda)
    );

initial
begin
    clk = 1'b1;
    rst_n = 1'b0;
    iic_rw = 1'b0;
    iic_start = 1'b0;
    w_data = 8'd0;
    iic_addr = 8'd0;
    #100;
    iic_start = 1'b1;
    #3000;
    iic_start = 1'b0;
    w_data = 8'd99;
    iic_addr = 8'b1010_1111;
    #20000000;
    iic_rw = 1'b1;
    iic_start = 1'b1;
    #1000;
    iic_start = 1'b0;
    #100000;
    $stop;
end

reg             [103:0]          state_tb;
always@(*)begin
    case (u_IIC_drive.state)
    4'd0 :   state_tb = "IDLE"; 
    4'd1 :   state_tb = "IIC_START"; 
    4'd2 :   state_tb = "SEND_D_ADDR_W"; 
    4'd3 :   state_tb = "ACK1"; 
    4'd4 :   state_tb = "SEND_R_ADDR"; 
    4'd5 :   state_tb = "ACK2"; 
    4'd6 :   state_tb = "W_DATA"; 
    4'd7 :   state_tb = "ACK3"; 
    4'd8 :   state_tb = "IIC_START"; 
    4'd9 :   state_tb = "SEND_D_ADDR_R"; 
    4'd10 :   state_tb = "ACK4"; 
    4'd11 :   state_tb = "R_DATA"; 
    4'd12 :   state_tb = "NACK"; 
    4'd13 :   state_tb = "STOP"; 
    default: state_tb = "IDLE";
    endcase
end

endmodule

仿真过程中遇到了各种各样的问题,虽然有刀哥的代码作参考,但还是耗费了很长时间才完成了仿真。首先是状态机的第三段复位信号为高电平复位,导致计数器也不计数,开始信号到达以后SDA也不拉低,还是我在群里寻求帮助,一位大佬帮我发现了问题。问题改正以后还是出现了各种各样的问题,比如状态跳转不对或者响应信号不对等问题,主要是状态机的第三段出了问题,对比刀哥的代码检查了好多遍终于写操作没有问题了,但是读操作中第二次发送器件地址+读操作,最后发送的一个是1,但是SDA却出现了未知态,折腾半天原来是EEPROM的寄存器地址位数不对,刀哥给的寄存器地址是16位,而我的代码设计的是只有8位的,我有参照夏宇闻老师的书把EEPROM的代码改了一遍,终于成功实现了仿真。

代码思路参考自:@孤独的单刀,也是本人强烈推荐的一位博主。本文章仅用于记录学习,如有侵权,请联系博主。

  • 10
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值