注:本文主要讲述双线SCCB协议!
初写:2024/11/5/23:33
次写:2024/11/7/
第一部分:SCCB(IIC)协议
SCCB协议,其字面意思为(Serial Camera Control Bus)即串行摄像机控制总线协议,与IIC协议十分相似,但也有一些区别。如下:
- 总线结构方面:
- SCCB:有两线式和三线式两种接口形式。两线式包括 SIO_C(串行时钟输入线)和 SIO_D(串行双向数据线);三线式则在此基础上增加了 SIO_E(片选线),可以实现对多个从器件的控制,但通常在只有一个从机时使用两线式。
- IIC:一般为两根信号线,一根是双向的数据线 SDA,另一根是时钟线 SCL。所有接到 IIC 总线设备上的串行数据 SDA 都接到总线的 SDA 上,各设备的时钟线 SCL 接到总线的 SCL 上。
- 应答机制方面:
- SCCB:应答位称为 “x”,表示 “don't care”(不关心)。从机有可能不发出应答信号,主机可不用判断此处是否有应答,直接默认当前传输完成即可3。
- IIC:应答位称为 ACK。当发送器发送完一个字节的数据后,接收器必须在第 9 个时钟周期拉低 SDA 线表示应答,以确认数据接收成功2。
- 读操作方面:
- SCCB:不支持连续读操作,读操作时在第一次写寄存器地址后必须有结束条件,之后再发起一个新的起始信号进行读操作。
- IIC:支持连续读操作。在写完寄存器地址后,可以通过重复开始(Restart)操作,继续写设备地址(此时读 / 写位改变)然后读取数据。
- 信号驱动方式方面:
- 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的文章。