SCCB Protocol Overview
The SCCB (Serial Camera Control Bus) protocol is commonly used for configuring registers in camera modules, particularly those in the OV series. This protocol evolved from the I2C protocol and shares many similarities with it. A complete transmission in SCCB consists of either two phases (for read operations) or three phases (for write operations).
The protcool includes start bits, data bits, and stop bits, though the format of start and stop bits differs from I2C. This implementation focuses on the two-wire version of SCCB, which uses a clock line (SIO_C) and a data line (SIO_D).
FPGA Design Implementation
The implemantation uses a state machine approach with precise timing control. The design consists of separate modules for read and write operations, with a top-level module to coordinate the functionality.
SCCB Read Module
module sccb_read (
input clk,
input reset_n,
input enable,
output operation_complete,
input sio_data_in,
output reg sio_data_control,
output reg sio_clock,
output reg sio_data_out,
output reg [7:0] received_data
);
// Timing parameters based on SCCB specification
localparam CLK_PERIOD = 3000;
localparam HALF_PERIOD = CLK_PERIOD / 2;
localparam HALF_PERIOD_CNT = HALF_PERIOD / 20;
localparam FULL_PERIOD_CNT = CLK_PERIOD / 20;
localparam DATA_PHASE_TIME = FULL_PERIOD_CNT * 19;
localparam STOP_PHASE_TIME = FULL_PERIOD_CNT * 10;
// State definitions
localparam IDLE = 3'd0;
localparam START_PHASE = 3'd1;
localparam DATA_PHASE_1 = 3'd2;
localparam STOP_PHASE_1 = 3'd3;
localparam START_PHASE_2 = 3'd4;
localparam DATA_PHASE_2 = 3'd5;
localparam STOP_PHASE_2 = 3'd6;
// Device ID and address definitions
wire [8:0] device_id;
wire [8:0] register_addr;
wire [8:0] read_id;
assign device_id = {8'h42, 1'b0};
assign register_addr = {8'h03, 1'b0};
assign read_id = {8'h43, 1'b0};
reg [2:0] current_state;
reg [2:0] next_state;
reg [7:0] delay_counter;
reg [7:0] start_counter;
reg [11:0] data_counter_1;
reg [11:0] stop_counter_1;
reg [7:0] start_counter_2;
reg [11:0] data_counter_2;
reg [11:0] stop_counter_2;
reg [4:0] bit_counter;
reg [7:0] clock_counter;
// State transition logic
always@(posedge clk or negedge reset_n) begin
if(!reset_n) begin
current_state <= IDLE;
end
else begin
case(current_state)
IDLE: if(enable && delay_counter == FULL_PERIOD_CNT-1'b1) current_state <= next_state;
START_PHASE: if(start_counter == FULL_PERIOD_CNT-1'b1) current_state <= next_state;
DATA_PHASE_1: if(data_counter_1 == DATA_PHASE_TIME-1'b1) current_state <= next_state;
STOP_PHASE_1: if(stop_counter_1 == STOP_PHASE_TIME-1'b1) current_state <= next_state;
START_PHASE_2: if(start_counter_2 == FULL_PERIOD_CNT-1'b1) current_state <= next_state;
DATA_PHASE_2: if(data_counter_2 == DATA_PHASE_TIME-1'b1) current_state <= next_state;
STOP_PHASE_2: if(stop_counter_2 == STOP_PHASE_TIME-1'b1) current_state <= next_state;
endcase
end
end
// Next state logic
always@(*) begin
case(current_state)
IDLE: next_state <= START_PHASE;
START_PHASE: next_state <= DATA_PHASE_1;
DATA_PHASE_1: next_state <= STOP_PHASE_1;
STOP_PHASE_1: next_state <= START_PHASE_2;
START_PHASE_2: next_state <= DATA_PHASE_2;
DATA_PHASE_2: next_state <= STOP_PHASE_2;
STOP_PHASE_2: next_state <= IDLE;
endcase
end
// Counter for initial delay
always@(posedge clk or negedge reset_n) begin
if(!reset_n) begin
delay_counter <= 1'b0;
end
else if(delay_counter == FULL_PERIOD_CNT) begin
delay_counter <= 1'b0;
end
else if(current_state == IDLE) begin
if(enable) begin
delay_counter <= delay_counter + 1'b1;
end
end
else begin
delay_counter <= 1'b0;
end
end
// Counter for first start phase
always@(posedge clk or negedge reset_n) begin
if(!reset_n) begin
start_counter <= 1'b0;
end
else if(start_counter == FULL_PERIOD_CNT-1'b1) begin
start_counter <= 1'b0;
end
else if(current_state == START_PHASE) begin
start_counter <= start_counter + 1'b1;
end
else begin
start_counter <= 1'b0;
end
end
// Counter for first data phase
always@(posedge clk or negedge reset_n) begin
if(!reset_n) begin
data_counter_1 <= 1'b0;
end
else if(data_counter_1 == DATA_PHASE_TIME -1'b1) begin
data_counter_1 <= 1'b0;
end
else if(current_state == DATA_PHASE_1) begin
data_counter_1 <= data_counter_1 + 1'b1;
end
else begin
data_counter_1 <= 1'b0;
end
end
// Counter for first stop phase
always@(posedge clk or negedge reset_n) begin
if(!reset_n) begin
stop_counter_1 <= 1'b0;
end
else if(stop_counter_1 == STOP_PHASE_TIME-1'b1) begin
stop_counter_1 <= 1'b0;
end
else if(current_state == STOP_PHASE_1) begin
stop_counter_1 <= stop_counter_1 + 1'b1;
end
else begin
stop_counter_1 <= 1'b0;
end
end
// Counter for second start phase
always@(posedge clk or negedge reset_n) begin
if(!reset_n) begin
start_counter_2 <= 1'b0;
end
else if(start_counter_2 == FULL_PERIOD_CNT-1'b1) begin
start_counter_2 <= 1'b0;
end
else if(current_state == START_PHASE_2) begin
start_counter_2 <= start_counter_2 + 1'b1;
end
else begin
start_counter_2 <= 1'b0;
end
end
// Counter for second data phase
always@(posedge clk or negedge reset_n) begin
if(!reset_n) begin
data_counter_2 <= 1'b0;
end
else if(data_counter_2 == DATA_PHASE_TIME -1'b1) begin
data_counter_2 <= 1'b0;
end
else if(current_state == DATA_PHASE_2) begin
data_counter_2 <= data_counter_2 + 1'b1;
end
else begin
data_counter_2 <= 1'b0;
end
end
// Counter for second stop phase
always@(posedge clk or negedge reset_n) begin
if(!reset_n) begin
stop_counter_2 <= 1'b0;
end
else if(stop_counter_2 == STOP_PHASE_TIME-1'b1) begin
stop_counter_2 <= 1'b0;
end
else if(current_state == STOP_PHASE_2) begin
stop_counter_2 <= stop_counter_2 + 1'b1;
end
else begin
stop_counter_2 <= 1'b0;
end
end
The state machine and timing control logic ensures proper SCCB protocol timing. Each state transition occurs after a specific time period has elapsed, as defined by the protocol specification.
// Clock generation for data phases
always@(posedge clk or negedge reset_n) begin
if(!reset_n) begin
clock_counter <= 1'b0;
end
else if(clock_counter == HALF_PERIOD_CNT-1'b1) begin
clock_counter <= 1'b0;
end
else if(current_state == DATA_PHASE_1 || current_state == DATA_PHASE_2) begin
clock_counter <= clock_counter + 1'b1;
end
else begin
clock_counter <= 1'b0;
end
end
// SIO Clock control
always@(posedge clk or negedge reset_n) begin
if(!reset_n) begin
sio_clock <= 1'b1;
end
else begin
case(current_state)
IDLE: sio_clock <= 1'b1;
START_PHASE: sio_clock <= 1'b1;
DATA_PHASE_1: if(clock_counter == HALF_PERIOD_CNT-1'b1) sio_clock <= ~sio_clock;
STOP_PHASE_1: sio_clock <= 1'b1;
START_PHASE_2: sio_clock <= 1'b1;
DATA_PHASE_2: if(clock_counter == HALF_PERIOD_CNT-1'b1) sio_clock <= ~sio_clock;
STOP_PHASE_2: sio_clock <= 1'b1;
endcase
end
end
// Bit position counter
always@(posedge clk or negedge reset_n) begin
if(!reset_n) begin
bit_counter <= 1'b0;
end
else if(current_state == DATA_PHASE_1 || current_state == DATA_PHASE_2) begin
if(clock_counter == HALF_PERIOD_CNT/2-1'b1 && sio_clock == 1'b0) begin
bit_counter <= bit_counter + 1'b1;
end
else begin
bit_counter <= bit_counter;
end
end
else begin
bit_counter <= 1'b0;
end
end
// Data output control
always@(posedge clk or negedge reset_n) begin
if(!reset_n) begin
sio_data_out <= 1'b1;
end
else begin
case(current_state)
IDLE: sio_data_out <= 1'b1;
START_PHASE: if(start_counter == HALF_PERIOD_CNT-1'b1) sio_data_out <= 1'b0;
DATA_PHASE_1: begin
case(bit_counter)
5'd0: sio_data_out <= sio_data_out;
5'd1: sio_data_out <= device_id[8];
5'd2: sio_data_out <= device_id[7];
5'd3: sio_data_out <= device_id[6];
5'd4: sio_data_out <= device_id[5];
5'd5: sio_data_out <= device_id[4];
5'd6: sio_data_out <= device_id[3];
5'd7: sio_data_out <= device_id[2];
5'd8: sio_data_out <= device_id[1];
5'd9: sio_data_out <= device_id[0];
5'd10: sio_data_out <= register_addr[8];
5'd11: sio_data_out <= register_addr[7];
5'd12: sio_data_out <= register_addr[6];
5'd13: sio_data_out <= register_addr[5];
5'd14: sio_data_out <= register_addr[4];
5'd15: sio_data_out <= register_addr[3];
5'd16: sio_data_out <= register_addr[2];
5'd17: sio_data_out <= register_addr[1];
5'd18: sio_data_out <= register_addr[0];
5'd19: sio_data_out <= 1'b0;
default: sio_data_out <= sio_data_out;
endcase
end
STOP_PHASE_1: if(stop_counter_1 == HALF_PERIOD_CNT-1'b1) sio_data_out <= 1'b1;
START_PHASE_2: if(start_counter_2 == HALF_PERIOD_CNT-1'b1) sio_data_out <= 1'b0;
DATA_PHASE_2: begin
case(bit_counter)
5'd0: sio_data_out <= sio_data_out;
5'd1: sio_data_out <= read_id[8];
5'd2: sio_data_out <= read_id[7];
5'd3: sio_data_out <= read_id[6];
5'd4: sio_data_out <= read_id[5];
5'd5: sio_data_out <= read_id[4];
5'd6: sio_data_out <= read_id[3];
5'd7: sio_data_out <= read_id[2];
5'd8: sio_data_out <= read_id[1];
5'd9: sio_data_out <= read_id[0];
5'd18: sio_data_out <= 1'b1;
5'd19: sio_data_out <= 1'b0;
default: sio_data_out <= sio_data_out;
endcase
end
STOP_PHASE_2: if(stop_counter_2 == HALF_PERIOD_CNT-1'b1) sio_data_out <= 1'b1;
endcase
end
end
// Data direction control
always@(posedge clk or negedge reset_n) begin
if(!reset_n) begin
sio_data_control <= 1'b1;
end
else begin
case(current_state)
IDLE: sio_data_control <= 1'b1;
START_PHASE, DATA_PHASE_1, STOP_PHASE_1, START_PHASE_2, STOP_PHASE_2: sio_data_control <= 1'b1;
DATA_PHASE_2: if(5'd9 < bit_counter && bit_counter < 5'd18) sio_data_control <= 1'b0;
else sio_data_control <= 1'b1;
endcase
end
end
// Data reception
always@(posedge clk or negedge reset_n) begin
if(!reset_n) begin
received_data <= 8'b1111_1111;
end
else if(current_state == DATA_PHASE_2 && clock_counter == HALF_PERIOD_CNT/2-1'b1 && sio_clock == 1'b1) begin
case(bit_counter)
5'd10: received_data[7] <= sio_data_in;
5'd11: received_data[6] <= sio_data_in;
5'd12: received_data[5] <= sio_data_in;
5'd13: received_data[4] <= sio_data_in;
5'd14: received_data[3] <= sio_data_in;
5'd15: received_data[2] <= sio_data_in;
5'd16: received_data[1] <= sio_data_in;
5'd17: received_data[0] <= sio_data_in;
endcase
end
end
// Operation complete signal
assign operation_complete = (current_state == STOP_PHASE_2 && stop_counter_2 == STOP_PHASE_TIME-1'b1);
endmodule
SCCB Write Module
module sccb_write (
input clk,
input reset_n,
input enable,
input [15:0] register_data,
output operation_complete,
output reg sio_data_control,
output reg sio_clock,
output reg sio_data_out
);
// Timing parameters
localparam CLK_PERIOD = 3000;
localparam HALF_PERIOD = CLK_PERIOD / 2;
localparam HALF_PERIOD_CNT = HALF_PERIOD / 20;
localparam FULL_PERIOD_CNT = CLK_PERIOD / 20;
localparam WRITE_PHASE_TIME = FULL_PERIOD_CNT * 28;
localparam STOP_PHASE_TIME = FULL_PERIOD_CNT * 10;
// State definitions
localparam IDLE = 2'd0;
localparam START_PHASE = 2'd1;
localparam DATA_PHASE = 2'd2;
localparam STOP_PHASE = 2'd3;
// Device ID and address/data definitions
wire [8:0] device_id;
wire [8:0] register_addr;
wire [8:0] data_value;
assign device_id = {8'h42, 1'b0};
assign register_addr = {register_data[15:8], 1'b0};
assign data_value = {register_data[7:0], 1'b0};
reg [1:0] current_state;
reg [1:0] next_state;
reg [7:0] delay_counter;
reg [7:0] start_counter;
reg [14:0] data_counter;
reg [11:0] stop_counter;
reg [4:0] bit_counter;
reg [7:0] clock_counter;
// State transition logic
always@(posedge clk or negedge reset_n) begin
if(!reset_n) begin
current_state <= IDLE;
end
else begin
case(current_state)
IDLE: if(enable && delay_counter == FULL_PERIOD_CNT-1'b1) current_state <= next_state;
START_PHASE: if(start_counter == FULL_PERIOD_CNT-1'b1) current_state <= next_state;
DATA_PHASE: if(data_counter == WRITE_PHASE_TIME-1'b1) current_state <= next_state;
STOP_PHASE: if(stop_counter == STOP_PHASE_TIME-1'b1) current_state <= next_state;
endcase
end
end
// Next state logic
always@(*) begin
case(current_state)
IDLE: next_state <= START_PHASE;
START_PHASE: next_state <= DATA_PHASE;
DATA_PHASE: next_state <= STOP_PHASE;
STOP_PHASE: next_state <= IDLE;
endcase
end
// Counter implementations for each state
always@(posedge clk or negedge reset_n) begin
if(!reset_n) begin
delay_counter <= 1'b0;
end
else if(delay_counter == FULL_PERIOD_CNT) begin
delay_counter <= 1'b0;
end
else if(current_state == IDLE) begin
if(enable) begin
delay_counter <= delay_counter + 1'b1;
end
end
else begin
delay_counter <= 1'b0;
end
end
always@(posedge clk or negedge reset_n) begin
if(!reset_n) begin
start_counter <= 1'b0;
end
else if(start_counter == FULL_PERIOD_CNT-1'b1) begin
start_counter <= 1'b0;
end
else if(current_state == START_PHASE) begin
start_counter <= start_counter + 1'b1;
end
else begin
start_counter <= 1'b0;
end
end
always@(posedge clk or negedge reset_n) begin
if(!reset_n) begin
data_counter <= 1'b0;
end
else if(data_counter == WRITE_PHASE_TIME -1'b1) begin
data_counter <= 1'b0;
end
else if(current_state == DATA_PHASE) begin
data_counter <= data_counter + 1'b1;
end
else begin
data_counter <= 1'b0;
end
end
always@(posedge clk or negedge reset_n) begin
if(!reset_n) begin
stop_counter <= 1'b0;
end
else if(stop_counter == STOP_PHASE_TIME-1'b1) begin
stop_counter <= 1'b0;
end
else if(current_state == STOP_PHASE) begin
stop_counter <= stop_counter + 1'b1;
end
else begin
stop_counter <= 1'b0;
end
end
always@(posedge clk or negedge reset_n) begin
if(!reset_n) begin
clock_counter <= 1'b0;
end
else if(clock_counter == HALF_PERIOD_CNT-1'b1) begin
clock_counter <= 1'b0;
end
else if(current_state == DATA_PHASE) begin
clock_counter <= clock_counter + 1'b1;
end
else begin
clock_counter <= 1'b0;
end
end
// Clock generation
always@(posedge clk or negedge reset_n) begin
if(!reset_n) begin
sio_clock <= 1'b1;
end
else begin
case(current_state)
IDLE: sio_clock <= 1'b1;
START_PHASE: sio_clock <= 1'b1;
DATA_PHASE: if(clock_counter == HALF_PERIOD_CNT-1'b1) sio_clock <= ~sio_clock;
STOP_PHASE: sio_clock <= 1'b1;
endcase
end
end
// Bit position counter
always@(posedge clk or negedge reset_n) begin
if(!reset_n) begin
bit_counter <= 1'b0;
end
else if(current_state == DATA_PHASE) begin
if(clock_counter == HALF_PERIOD_CNT/2-1'b1 && sio_clock == 1'b0) begin
bit_counter <= bit_counter + 1'b1;
end
else begin
bit_counter <= bit_counter;
end
end
else begin
bit_counter <= 1'b0;
end
end
// Data output control
always@(posedge clk or negedge reset_n) begin
if(!reset_n) begin
sio_data_out <= 1'b1;
end
else begin
case(current_state)
IDLE: sio_data_out <= 1'b1;
START_PHASE: if(start_counter == HALF_PERIOD_CNT-1'b1) sio_data_out <= 1'b0;
DATA_PHASE: begin
case(bit_counter)
5'd0: sio_data_out <= sio_data_out;
5'd1: sio_data_out <= device_id[8];
5'd2: sio_data_out <= device_id[7];
5'd3: sio_data_out <= device_id[6];
5'd4: sio_data_out <= device_id[5];
5'd5: sio_data_out <= device_id[4];
5'd6: sio_data_out <= device_id[3];
5'd7: sio_data_out <= device_id[2];
5'd8: sio_data_out <= device_id[1];
5'd9: sio_data_out <= device_id[0];
5'd10: sio_data_out <= register_addr[8];
5'd11: sio_data_out <= register_addr[7];
5'd12: sio_data_out <= register_addr[6];
5'd13: sio_data_out <= register_addr[5];
5'd14: sio_data_out <= register_addr[4];
5'd15: sio_data_out <= register_addr[3];
5'd16: sio_data_out <= register_addr[2];
5'd17: sio_data_out <= register_addr[1];
5'd18: sio_data_out <= register_addr[0];
5'd19: sio_data_out <= data_value[8];
5'd20: sio_data_out <= data_value[7];
5'd21: sio_data_out <= data_value[6];
5'd22: sio_data_out <= data_value[5];
5'd23: sio_data_out <= data_value[4];
5'd24: sio_data_out <= data_value[3];
5'd25: sio_data_out <= data_value[2];
5'd26: sio_data_out <= data_value[1];
5'd27: sio_data_out <= data_value[0];
5'd28: sio_data_out <= 1'b0;
default: sio_data_out <= sio_data_out;
endcase
end
STOP_PHASE: if(stop_counter == HALF_PERIOD_CNT-1'b1) sio_data_out <= 1'b1;
endcase
end
end
// Data direction control
always@(posedge clk or negedge reset_n) begin
if(!reset_n) begin
sio_data_control <= 1'b0;
end
else if(enable) begin
sio_data_control <= 1'b1;
end
else begin
sio_data_control <= 1'b1;
end
end
// Operation complete signal
assign operation_complete = (current_state == STOP_PHASE && stop_counter == STOP_PHASE_TIME-1'b1);
endmodule
Top-Level Module
module camera_interface (
input clk,
input reset_n,
inout sio_data,
output sio_clock,
output power_down,
output sensor_reset,
output sensor_clock,
output [7:0] captured_data
);
reg read_enable;
reg write_enable;
wire sio_data_in;
wire read_complete;
wire write_complete;
wire sio_data_control_read;
wire sio_data_control_write;
wire sio_data_out_read;
wire sio_data_out_write;
wire sio_clock_read;
wire sio_clock_write;
reg [15:0] configuration_data;
wire pll_locked;
// Bidirectional data line control
assign sio_data = (sio_data_control_read || sio_data_control_write) ?
(sio_data_out_read & sio_data_out_write) : 1'bz;
assign sio_data_in = sio_data;
// Control signal combining
assign sio_data_out_read = sio_data_out_read;
assign sio_data_out_write = sio_data_out_write;
assign sio_clock = sio_clock_read & sio_clock_write;
assign sio_data_control_read = sio_data_control_read;
assign sio_data_control_write = sio_data_control_write;
// Sensor control signals
assign power_down = 1'b0;
assign sensor_reset = 1'b1;
reg [7:0] operation_counter;
// Operation sequence counter
always@(posedge clk or negedge reset_n) begin
if(!reset_n) begin
operation_counter <= 1'b0;
end
else if(operation_counter == 8'd11) begin
operation_counter <= 8'd11;
end
else if(operation_counter == 8'd10 && read_complete) begin
operation_counter <= 8'd11;
end
else if(write_complete) begin
operation_counter <= operation_counter + 1'b1;
end
end
// Write operation control
always@(posedge clk or negedge reset_n) begin
if(!reset_n) begin
write_enable <= 1'b0;
end
else if(operation_counter < 8'd10 && pll_locked) begin
write_enable <= 1'b1;
end
else begin
write_enable <= 1'b0;
end
end
// Read operation control
always@(posedge clk or negedge reset_n) begin
if(!reset_n) begin
read_enable <= 1'b0;
end
else if(operation_counter == 8'd10 && pll_locked) begin
read_enable <= 1'b1;
end
else begin
read_enable <= 1'b0;
end
end
// Configuration data selection
always@(posedge clk or negedge reset_n) begin
if(!reset_n) begin
configuration_data <= 16'h1280;
end
else begin
case(operation_counter)
8'd0: configuration_data <= 16'h1280; // Reset SCCB registers
8'd1: configuration_data <= 16'h1101; // Configure raw data half clock
8'd2: configuration_data <= 16'h3a04; //
8'd3: configuration_data <= 16'h1201; //
8'd4: configuration_data <= 16'h1716;
8'd5: configuration_data <= 16'h1804;
8'd6: configuration_data <= 16'h1902;
8'd7: configuration_data <= 16'h1a7b;
8'd8: configuration_data <= 16'h3280;
8'd9: configuration_data <= 16'h0306;
default: configuration_data <= configuration_data;
endcase
end
end
// PLL for sensor clock generation
pll clock_generator (
.areset(~reset_n),
.inclk0(clk),
.c0(sensor_clock),
.locked(pll_locked)
);
// SCCB write module instance
sccb_write write_module (
.clk(clk),
.reset_n(reset_n),
.enable(write_enable),
.register_data(configuration_data),
.operation_complete(write_complete),
.sio_data_control(sio_data_control_write),
.sio_clock(sio_clock_write),
.sio_data_out(sio_data_out_write)
);
// SCCB read module instance
sccb_read read_module (
.clk(clk),
.reset_n(reset_n),
.enable(read_enable),
.operation_complete(read_complete),
.sio_data_in(sio_data_in),
.sio_data_control(sio_data_control_read),
.sio_clock(sio_clock_read),
.sio_data_out(sio_data_out_read),
.received_data(captured_data)
);
endmodule
Implementation Results
The implementation was tested using ModelSim simulation. The simulation waveforms confirmed correct timing for both read and write operations according to the SCCB protocol specification.
For testing bidirectional signals in simulation, the inout port was converted to separate input, output, and control signals. During simulation, the slave device was modeled to remain in a receptive state for simplified verification.
Hardware testing on an FPGA development board demonstrated successful communication with camera modules. The implemented SCCB interface correctly wrote configuration data to camera registers and verified the values by reading them back.
This implementation provides a reliable foundation for camera configuration using the SCCB protocol in FPGA designs. The modular approach allows for easy integration into larger camera processing systems.