浅谈Verilog HDL ------ SCCB(IIC)

 注:本文主要讲述双线SCCB协议!

初写:2024/11/5/23:33

次写:2024/11/7/

第一部分:SCCB(IIC)协议

     SCCB协议,其字面意思为(Serial Camera Control Bus)即串行摄像机控制总线协议,与IIC协议十分相似,但也有一些区别。如下:

  1. 总线结构方面
    • SCCB:有两线式和三线式两种接口形式。两线式包括 SIO_C(串行时钟输入线)和 SIO_D(串行双向数据线);三线式则在此基础上增加了 SIO_E(片选线),可以实现对多个从器件的控制,但通常在只有一个从机时使用两线式。
    • IIC:一般为两根信号线,一根是双向的数据线 SDA,另一根是时钟线 SCL。所有接到 IIC 总线设备上的串行数据 SDA 都接到总线的 SDA 上,各设备的时钟线 SCL 接到总线的 SCL 上。
  2. 应答机制方面
    • SCCB:应答位称为 “x”,表示 “don't care”(不关心)。从机有可能不发出应答信号,主机可不用判断此处是否有应答,直接默认当前传输完成即可3。
    • IIC:应答位称为 ACK。当发送器发送完一个字节的数据后,接收器必须在第 9 个时钟周期拉低 SDA 线表示应答,以确认数据接收成功2。
  3. 读操作方面
    • SCCB:不支持连续读操作,读操作时在第一次写寄存器地址后必须有结束条件,之后再发起一个新的起始信号进行读操作。
    • IIC:支持连续读操作。在写完寄存器地址后,可以通过重复开始(Restart)操作,继续写设备地址(此时读 / 写位改变)然后读取数据。
  4. 信号驱动方式方面
    • SCCB:SCLK(在 SCCB 中类似 IIC 的 SCL)被指定为输出且是有源驱动,而非开漏(open-drain)或三态(tri-state)或集电极开路(open-collector)形式。
    • IIC:SCL 线通常要求设备连接到总线的输出端时必须是漏极开路或集电极开路输出,以避免总线信号的混乱。

通俗总结:SCCB协议为霸权主义(无应答),IIC协议为民主主义(有应答),

                  SCCB协议为简易版的IIC,IIC协议可代替SCCB协议,反之不推荐。

主从设备连线及通信协议如图所示:

协议解读:1.sio_c为高,随后sio_d拉低即表示数据开始传输。

                  2.sio_c为高,随后sio_d拉高即表示数据结束传输。

                  3.sio_d只在sio_c为高时有效。

写操作:0+设备地址(厂家设定8bit)+X+寄存器地址(数据手册标注8bit)+X+写入数据(8bit)+X+0+1

:共30bit

---------------------------------------------------------------------------------------------------------------------------------

读操作:1:0+设备地址(厂家设定8bit)+X+寄存器地址(数据手册标注8bit)+X+0+1

               2:0+寄存器地址(数据手册标注8bit)+X+读到的数据(8bit)+X+0+1

:共21bit

:共21bit

第二部分:程序部分

1.定义部分:400khz的IIC --->> 8us

parameter    IDWADD            =     8'H42;       
parameter    IDRADD            =     8'H43;        
parameter    SCLK_TIME         =       400;          
parameter    SCLK_HALF_TIME    =     SCLK_TIME / 2;               
parameter    SCLK_W_TIME       =     SCLK_TIME / 4;           
parameter    SCLK_R_TIME       =     (SCLK_TIME / 4) * 3;  

2.启动条件部分:

assign en = work_flag == 1'b0 && (wr_en || rd_en);
always @(posedge clk or negedge rst_n)begin
        if(rst_n == 1'b0)begin 
		work_flag  <= 1'b0;
        end
        else if(en)begin
        work_flag <= 1'b1;
        end	
		else if(end_cnt_step)begin
        work_flag <= 1'b0;
        end	
end
always @(posedge clk or negedge rst_n)begin
        if(rst_n == 1'b0)begin 
		rd_flag  <= 1'b0;
        end
        else if(rd_en)begin
        rd_flag <= 1'b1;
        end	
		else if(wr_en)begin
        rd_flag <= 1'b0;
        end	
end

当FPAG给到一个读或写使能信号en时,拉高工作信号work_flag,当end_cnt_step计数完时把work_flag

拉低;同时,这个flag信号也是程序执行的标志信号。

3.计数器部分:

计数器是通信协议的灵魂,无计数无协议!本人的计数定义规制:(目标数 - 1);

always @(posedge clk or negedge rst_n)begin
        if(rst_n == 1'b0)begin 
		cnt_sclk  <= 0;
        end
        else if(add_cnt_sclk)begin
             if(end_cnt_sclk)begin
                cnt_sclk <= 0;
             else
                cnt_sclk <= cnt_sclk + 1;
             end
        else begin 
             cnt_sclk <= 0;
        end	
    end		
end

assign add_cnt_sclk = work_flag;              //IIC时钟计数启动
assign end_cnt_sclk = cnt_sclk == SCLK_TIME - 1 && add_cnt_sclk;
 
 
always @(posedge clk or negedge rst_n)begin
        if(rst_n == 1'b0)begin 
		cnt_byte  <= 0;
        end
        else if(add_cnt_byte)begin
             if(end_cnt_byte)begin
                cnt_byte <= 0;
             else
                cnt_byte <= cnt_byte + 1;
             end
        else begin 
             cnt_byte <= 0;
        end	
    end		
end

assign add_cnt_byte = end_cnt_sclk;              //bit计数启动
assign end_cnt_byte = cnt_byte == byte_num - 1 && add_cnt_byte;


always @(posedge clk or negedge rst_n)begin
        if(rst_n == 1'b0)begin 
		cnt_step  <= 0;
        end
        else if(add_cnt_step)begin
             if(end_cnt_step)begin
                cnt_step <= 0;
             else
                cnt_step <= cnt_step + 1;
             end
        else begin 
             cnt_step <= 0;
        end	
    end		
end

assign add_cnt_step = end_cnt_byte;              //step计数启动
assign end_cnt_step = cnt_step == step_num - 1 && add_cnt_step;

程序解读:第一眼看上去是不是很懵呢!那我们先来看看下面的图示:

写:

读:

      看完之后有没有豁然开朗的感觉,从图上我们可以看出:cnt_sclk作为基底计数器,它代表400KHZ的IIC时序;往上一个计数器cnt_byte则代表(D7~D0)这些Bit位的计数,最上面的cnt_step则是代表读操作的一,二阶段。

      本程序的计数器只有3个,首先当根据SCLK_TIME==(400-1)给cnt_clk计次,计满之后cnt_byte+1,当cnt_byte==(设定值-1)时cnt_step+1,当cnt_step==(设定值-1)时结束读写操作。

4.数据传输部分:

数据格式:

assign  wr_state      =   work_flag && rd_flag == 1'b0; 
assign  rd_state      =   work_flag && rd_flag == 1'b1;
assign  rd_0_state    =   rd_state  && cnt_step == 0;
assign  rd_1_state    =   rd_state  && cnt_step == 1;
assign  rd_get_state  =   rd_1_state && cnt_byte >= 10 && cnt_byte <= 18;

always @(posedge clk or negedge rst_n)begin
        if(rst_n == 1'b0)begin 
		subadd    <= 0;
		wdata_ffo <= 0; 
        end
        else if(en)begin
        subadd    <= addr;
		wdata_ffo <= wdata; 
        end	
end

always @(*)begin
        if(wr_state)begin // 8          8             8   
		wdata_tmp = {1'b0,IDWADD,1'b1,subadd,1'b1,wdata_ff0,1'b1,1'b0,1'b1};
		byte_num  = 30;
		step_num  =  1;
        end
        else if(rd_0_state)begin
		wdata_tmp = {1'b0,IDWADD,1'b1,subadd,1'b1,1'b0,1'b1,9'b0};;
		byte_num  = 21;
		step_num  =  2;
        end	
		else begin
		wdata_tmp = {1'b0,IDRADD,1'b1,8'b0,1'b1,1'b0,1'b1,9'b0};;
		byte_num  = 21;
		step_num  =  2;
        end	
end

请先详细阅读程序,可以发现后面两个wdata_tmp{}里面的数据位数不是21而是30,为什么会这样呢?

我们先看一下我之前那篇文章《浅谈Verilog HDL ------ UART协议》里面的知识点:[]与{}的数据对应关系,关于高低位对应的关系。

然后就是byte_num和step_num和前面的计数器之间的巧妙配合,本程序只使用了两个计数器,计数器的结束值由后面的数值设定部分控制,这样可以避免设立过多的计数器,这种方法更适合大型状态机。

数据发送与接收:

assign  start_area = add_cnt_sclk && cnt_byte == 0;
assign  stop_area  = add_cnt_sclk && cnt_byte == byte_num - 1;

assign  sclk_h2l   = add_cnt_sclk && cnt_sclk == 0 && ((!start_area) && (!stop_area)); //high to low
assign  sclk_l2h   = add_cnt_sclk && cnt_sclk == SCLK_HALF_TIME - 1;                   //low  to high
always @(posedge clk or negedge rst_n)begin
        if(rst_n == 1'b0)begin 
		sclk  <= 1'b1;
        end
        else if(sclk_h2l)begin
        sclk <= 1'b0;
        end	
		else if(sclk_l2h)begin
        sclk <= 1'b1;
        end	
end


assign  sio_send = add_cnt_sclk && cnt_sclk == SCLK_W_TIME - 1 && rd_get_state == 0;
assign  sio_get  = add_cnt_sclk && cnt_sclk == SCLK_R_TIME - 1 && rd_get_state == 1;
always @(posedge clk or negedge rst_n)begin
        if(rst_n == 1'b0)begin
		sio_out  <= 1'b1;
		end
		else if(sio_send)begin
		sio_out  <= wdata_tmp[29-cnt_byte]
		end
end

always @(posedge clk or negedge rst_n)begin
        if(rst_n == 1'b0)begin
		sio_out_en  <= 1'b0;
		end
		else if(work_flag && rd_get_state == 1'b0)begin
		sio_out_en  <= 1'b1;
		end
		else begin
		sio_out_en  <= 1'b0;
		end
end

always @(posedge clk or negedge rst_n)begin
        if(rst_n == 1'b0)begin
		rdata  <= 1'b0;
		end
		else if(rd_get_state && sio_get)begin
		rdata  <= {rdata[6:0],sio_in};         //数据接入接口
		end
end

always @(posedge clk or negedge rst_n)begin
        if(rst_n == 1'b0)begin
		rdata_vld  <= 1'b0;
		end
		else if(end_cnt_step && rd_1_state)begin
		rdata_vld  <= 1'b1;
		end
		else begin
		rdata_vld  <= 1'b0;
		end
end

always @(*)begin
        if(work_flag || rd_en || wr_en)
		rdy  <= 1'b0;
		else 
		rdy  <= 1'b1;
end

有点长,不过也要仔细看看!

首先看start_area和stop_area,由文章开始的协议时序图可以看出SCLK只在数据传输时有效,所以我们要标记出开始和结束这两点,那么这两点之间的区间则是有效区域,在有效区域里要产生SCLK时钟(中值反转:SCLK_HALF_TIME - 1)。这样就产生了SCLK时钟。

接着看发送部分,sio_out  <= wdata_tmp[29-cnt_byte],回归[]与{}的数据对应关系,这里给一个结论:对于wdata_tmp = {}来说,[29-cnt_byte]:从前往后发;[cnt_byte]从后往前发,记住这点即可,这也是为什么byter_num=21而{}里面却有30bit的原因,如果{}里面不够30bit则[29-cnt_byte]发送失败。

最后讲讲三态门,如图;

核心:默认就是接收状态,发送数据需要使能。

故输出使能条件为:else if(work_flag && rd_get_state == 1'b0),当此时为工作状态且非接收状态时可启动发送状态,这个逻辑和SDRAM的差不多。

关于串行数据转并行数据的详细讲解这里就不再赘述了,请移步至UART的文章。

总结:1.计数器  2.状态机配置  3.数据发送与接收
      时间仓促,难免有错,望不吝赐教,有错必改,欢迎关注!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值