相信能来看本文章必定对CCD有一定深度了解,CCD的背景,用途将不做介绍,具体的细节可查阅其他相关资料,本文将详细的分析CCD的时序,以及代码的分享和后续内容,学习CCD来做拉曼光谱仪,以及拉曼光谱仪相关部分资料的也会在后续更新。
1.CCD的工作原理
CCD的工作原理可以参考视频,传感器与检测技术ccd图像传感器_哔哩哔哩_bilibili
以下图片来自视频20分钟处
三个电极相位相差1/3个周期,会使得信号电荷最终全部积累在电极2、5的势阱中。没错CCD的时序就是需要相位等差(4个电极信号,相位相差1/4个周期)的电极信号,通过电荷的积累,经过垂直转移光栅,存储在电容中,形成电压。
我相信看完CCD的工作原理后将会理解上图的用意。
2.CCD的时序
了解了CCD的工作原理之后,不难看懂上图,P1H----P4H,是4个驱动电极,相位相差1/4个周期,P1V和P2V是垂直转移光栅驱动信号。时序说明如下图所示:
具体时序选择和分析:
P1-P4的高电平持续时间最小是50ns,上升时间和下降时间10ns,因此整个周期持续时间10+50+10+50+10=130ns典型值100ns,10+100+10+100+10=230ns因此取160作为一个周期,占空比为50%;为什么要取160ns,还要考虑到RG信号,RG信号和P1H-P4H都是160ns为一个周期但是Tpwr在5-50ns之间,因此选用Tpwr在高电平期间持续40ns,40/160=25%,占空比为25%,由于本文用的是50MHZ的时钟进行测试,20ns一个周期,40ns刚好是2个周期,没有用PLL 的IP核,所以尽量取整数个周期以便于实现。P1V和P2V相差1/2个周期,一个周期是16us。
3.代码设计
module ccd (
clk ,
rst_n ,
en ,
din ,
p1v , //8us
p2v_tg ,
p1h , //200ns
p2h ,
p3h ,
p4h_sg ,
//160ns, pwm25% 40ns
rg
);
parameter IDLE = 2'b00 ;
parameter IT = 2'b01 ;
parameter VP = 2'b10 ;
parameter RP = 2'b11 ;
input clk ;
input rst_n ;
input en ;
input [10:0] din ;
output reg p1v ;
output reg p2v_tg ;
output reg p1h ;
output reg p2h ;
output reg p3h ;
output reg p4h_sg ;
output reg rg ;
// output os ;
// wire [10:0] din ;
reg flag_tovr ;
reg flag ;
reg [2:0] flag_switch_zt ;
wire idle2it_start ;
wire it2vp_start ;
wire vp2rp_start ;
wire rp2idle_start ;
wire add_cnt0 ;
wire add_cnt1 ;
wire end_cnt0 ;
wire end_cnt1 ;
wire add_cnt2 ;
wire add_cnt3 ;
wire end_cnt2 ;
wire end_cnt3 ;
wire add_cnt4 ;
wire end_cnt4 ;
wire add_cnt5 ;
wire end_cnt5 ;
reg [10: 0] cnt0 ; // 800 800*20=16000ns
reg [3: 0] cnt1 ; // 8 20*8=160ns
reg [5: 0] cnt2 ; // 22 period
reg [12:0] cnt3 ; // 1044 period
reg [8:0] cnt4 ; // 100 period
reg [10:0] cnt5 ; // din period
reg [2: 0] state_c ;
reg [2: 0] state_n ;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
state_c <= IDLE;
end
else begin
state_c <= state_n;
end
end
always@(*)begin
case(state_c)
IDLE:begin
if(idle2it_start)begin
state_n = IT;
end
else begin
state_n = state_c;
end
end
IT:begin
if(it2vp_start)begin
state_n = VP;
end
else begin
state_n = state_c;
end
end
VP:begin
if(vp2rp_start)begin
state_n = RP;
end
else begin
state_n = state_c;
end
end
RP:begin
if(rp2idle_start)begin
state_n = IDLE;
end
else begin
state_n = state_c;
end
end
default:begin
state_n = IDLE;
end
endcase
end
assign idle2it_start = state_c== IDLE && flag ==1 ;
assign it2vp_start = state_c== IT && end_cnt5 ; // din
assign vp2rp_start = state_c== VP && end_cnt4 ; // 22 + Tovr2us=100period
assign rp2idle_start = state_c== RP && end_cnt3 ; // 1044
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
flag_switch_zt <= 3'b000 ; //初始化
end
else if(state_c==IDLE)begin
flag_switch_zt <= 3'b001 ;
end
else if(state_c==IT)begin
flag_switch_zt <= 3'b010 ;
end
else if(state_c==VP) begin
flag_switch_zt <= 3'b011 ;
end
else begin
flag_switch_zt <= 3'b100 ;
end
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt5 <= 0;
end
else if(add_cnt5)begin
if(end_cnt5)
cnt5 <= 0;
else
cnt5 <= cnt5 + 1;
end
end
assign add_cnt5 = state_c == IT && end_cnt1 ; // cnt1=8
assign end_cnt5 = add_cnt5 && cnt5 == din - 1 ;
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
flag <= 0;
end
else if(en)begin
flag <= 1;
end
else if(state_c== RP && end_cnt3 )begin
flag <= 0;
end
end
//20ns*800=16000ns=16us
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt0 <= 0;
end
else if(add_cnt0)begin
if(end_cnt0)
cnt0 <= 0;
else
cnt0 <= cnt0 + 1;
end
end
assign add_cnt0 = state_c == VP ;
assign end_cnt0 = add_cnt0 && cnt0== 800 - 1 ;
//16+6 period
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt2 <= 0;
end
else if(add_cnt2)begin
if(end_cnt2)
cnt2 <= 0;
else
cnt2 <= cnt2 + 1;
end
end
assign add_cnt2 = end_cnt0 && state_c == VP ;
assign end_cnt2 = add_cnt2 && cnt2 == 22 - 1 ;
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
p1v <= 0;
p2v_tg <= 0;
end
else if( state_c == VP )begin
if(cnt0>=0 && cnt0 <400 )
p1v <= 0;
if(cnt0>=400 && cnt0 <800 )
p1v <= 1;
if(cnt0>=400 && cnt0 <800 )
p2v_tg <= 0;
if(cnt0>=0 && cnt0 <400 )
p2v_tg <= 1;
end
else begin
p1v <= 0;
p2v_tg <= 0;
end
end
//20ns * 8=160 ns
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt1 <= 0;
end
else if(add_cnt1)begin
if(end_cnt1)
cnt1 <= 0;
else
cnt1 <= cnt1 + 1;
end
end
assign add_cnt1 = state_c == IT || state_c == RP ;
assign end_cnt1 = add_cnt1 && cnt1 == 8 - 1 ;
//1044 period
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt3 <= 0;
end
else if(add_cnt3)begin
if(end_cnt3)
cnt3 <= 0;
else
cnt3 <= cnt3 + 1;
end
end
assign add_cnt3 = end_cnt1;
assign end_cnt3 = add_cnt3 && cnt3== 1044 - 1 ;
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
flag_tovr <= 0 ;
end
else if(end_cnt2)begin
flag_tovr <= 1 ;
end
else if(end_cnt4)begin
flag_tovr <= 0 ;
end
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt4 <= 0;
end
else if(add_cnt4)begin
if(end_cnt4)
cnt4 <= 0;
else
cnt4 <= cnt4 + 1;
end
end
assign add_cnt4 = flag_tovr == 1;
assign end_cnt4 = add_cnt4 && cnt4 == 100 - 1 ;
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
p1h <= 1;
p2h <= 0;
p3h <= 0;
p4h_sg <= 0;
end
else if( state_c == IT || state_c == RP )begin
if (cnt1>=1 && cnt1<5)
p2h <= 0;
if ((cnt1>=0 && cnt1<2) || (cnt1>=6 && cnt1<7))
p2h <= 1;
if ( cnt1>=0 && cnt1<4 )
p1h <= 0;
if ( cnt1>=4 && cnt1<8 )
p1h <= 1;
if ( (cnt1>=0 && cnt1<2 ) || (cnt1>=6 && cnt1<7) )
p3h <= 0;
if ( (cnt1>=1 && cnt1<5))
p3h <= 1;
if ( cnt1>=4 && cnt1<8 )
p4h_sg <= 0;
if ( cnt1>=0 && cnt1<4 )
p4h_sg <= 1;
end
else begin
p1h <= 1;
p2h <= 0;
p3h <= 0;
p4h_sg <= 0;
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rg <= 1;
end
else if(state_c == RP )begin
if ( cnt1>=2 && cnt1<8 )
rg <= 0;
if ( cnt1>=0 && cnt1<2 )
rg <= 1;
end
else begin
rg <= 1;
end
end
endmodule
4.测试代码
`timescale 1 ns/1 ns
module tb_ccd();
reg clk ;
reg rst_n ;
reg [10:0] din ;
reg en ;
wire p1v ;
wire p2v_tg ;
wire p1h ;
wire p2h ;
wire p3h ;
wire p4h_sg ;
wire rg ;
//时钟周期,单位为ns,可在此修改时钟周期。
parameter CYCLE = 20;
//复位时间,此时表示复位3个时钟周期的时间。
parameter RST_TIME = 3 ;
//待测试的模块例化
ccd uut(
.clk (clk ),
.rst_n (rst_n ),
.en (en ),
.din (din ),
.p1v (p1v ),
.p2v_tg (p2v_tg ),
.p1h (p1h ),
.p2h (p2h ),
.p3h (p3h ),
.p4h_sg (p4h_sg ),
.rg (rg )
);
//生成本地时钟50M
initial begin
clk = 0;
forever
#(CYCLE/2)
clk=~clk;
end
//产生复位信号
initial begin
rst_n = 1;
#2;
rst_n = 0;
#(CYCLE*RST_TIME);
rst_n = 1;
end
initial begin
#1;
en = 0 ;
din = 33; //可调参数
end
initial begin
#(200*CYCLE);
# 1 ;
//赋初值
en = 1;
#(1*CYCLE);
en = 0;
//开始赋值
end
endmodule
代码有2个接口,一个是en,使能信号,整个CCD时序触发信号,驱动电极P1H-P4H,持续的时钟数,用来调节光积分的周期,可用串口URAT方式传递参数,和上位机配合使用。
那么看到这里会发现驱动信号都写好了,那么输出信号OS呢?
看模拟输入和CCD时序的OS输出有没有觉得很像啊,这是AD9826的驱动时序,CCD的输出是模拟信号,经过ADC转换后经AD9826输出,具体里的AD9826时序和驱动代码将会再后续更新,如有错漏之处,敬请指正。如需CCD和AD9826的相关资料,可私信获取。