SPI(Serial Peripheral Interface--串行外设接口)总线系统是一种同步串行外设接口,它可以使MCU与各种外围设备以串行方式进行通信以交换信息。本项目我们通过SPI接口实现FPGA与EEPROM芯片——AT93C46的通信。示意图如图3-16。
图3-16 SPI接口传输结构图
先查阅AT93C46的datasheet,了解各引脚的作用。
cs:从器件使能信号,由主器件控制;
sk:时钟信号,由主器件产生;
do:主器件数据输入,从器件数据输出;
di:主器件数据输出,从器件数据输入。
再明确实现相互通信需要哪些操作。
1. 三种基本的操作
(1)EWEN时序(图3-17)
图3-17 EWEN时序图
EWEN可以理解为激活设备,写、擦除(这里没有用到,有兴趣的读者可以自行查阅datasheet)操作执行之前必须要先执行完EWEN操作。其操作过程为:
1) cs在开始操作时拉高,等到读操作完成,拉低;
2) sk在CS拉高时,产生10个脉冲,脉冲周期大于400ns;
3) di在SK的周期内输出数据,前面输出固定的指令“10011”,指示是EWEN操作,后面则继续输出5比特的“0”。
(2)WRITE时序(图3-18)
图3-18 WRITE时序图
写操作:
1) cs先拉高一段时间,然后再拉低TCS时间,最后再拉高CS,直至检测到DO为1,拉低;
2) sk在CS第一段拉高时,产生18个脉冲,脉冲周期大于400ns;
3) di在sk的周期内输出数据,前面输出固定的指令“101”,指示是写操作,后面则继续输出地址“AN~A0”和数据“DN~D0”。
(3)READ时序(图3-19)
图3-19 ERAD时序图
1) cs在开始操作时拉高,等到读操作完成,拉低;
2) sk在cs第一段拉高时,产生18个脉冲,脉冲周期大于400ns;
3) di在sk的周期内输出数据,前面输出固定的指令“110”,指示是读操作,后面则继续输出地址“AN~A0”和固定的“0”;
4) 模块从sk的第11个周期开始从do中读取数据,每个周期读取1比特,共8比特。
实现一个AT93C46的接口,该接口能够根据命令,实现EWEN、WRITE和READ功能。具体功能如下:
明德扬在变化范围内取了1us作为tcs的值;AT93C46时钟取1MHz。
上游模块在rdy=1时,给出start命令,开始进行EWEN、WRITE或者READ操作。在rdy=0期间,start命令无效。
当start有效时,如果mode=0表示进行EWEN操作;mode=1表示进行WRITE操作;mode=2表示进行READ操作。
当start有效时,addr和wdata有效。
当进行EWEN操作时,模块将按at93c46 EWEN的要求,将addr写入at93c46。
当进行WRITE操作时,模块将按at93c46 WRITE的要求,将addr和wdata写入at93c46。
当进行READ操作时,模块将按at93c46 READ的要求,将addr写入at93c46,并从at93c46读到数据,通过rdata和rdata_vld返回给上游模块。
2. 设计过程
(1)明确功能
根据题目可以画出设备与AT93C46之间的具体通信框图。如图3-20。
图3-20 信号传输架构图
表3.5 信号列表
(2)输出分析
cs:在WRITE操作时,写入一个地址和数据后拉低,间隔tcs拉高,等待写操作完成;READ操作和EWEN操作都是在操作期间为高,操作结束拉低。
sk:引入计数器,通过计数产生1MHz的芯片时钟。
rdy:从操作开始到结束一直为低电平,其他时刻为高电平。
di:根据操作的不同输出相应的值
rdata:仅在READ操作时do的值从高位到低位,一比特一比特地给rdata赋值。
rdata_vld:在rdata赋值结束后,拉高一个时钟周期,表示此时rdata有效。
(3)状态划分
从EWEN、READ和WRITE的时序图我们可以发现在不同操作中有很多阶段是相似的,总结起来有4个状态:
IDLE:初始状态,模块在等待start信号有效。
WR_RD:读写状态。
TCS:片选信号拉低。
DO:等待写入操作完成。
(4)状态转移
状态转移图请见3-21。
图3-21 状态转移图
(5)转移条件
确定了状态转移图后,我们需要明确状态转移条件:
wr_rd_start:在IDLE状态下收到start有效。
tcs_start:在WR_RD状态结束。
idle_start1:,处于EWEN或READ模式,在TCS状态结束(1us)。
do_start:处于WRITE模式,在TCS结束(1us)。
idle_start2:处于WRITE模式,在TCS状态下,收到do==1。
(6)完整性检查
(7)状态机代码
第一段,用同步时序,将次态的值赋给现态。注意此时直接套用模块,不要做任何更改。
2 if(rst_n==1'b0)begin
3 state_c <= IDLE;
4 end
5 else begin
6 state_c <=state_n;
7 end
8 end
第二段,用组合逻辑描述状态转移条件。注意转移条件用信号来表示,信号名要按明德扬规则来命名。
2 case(state_c)
3 IDLE:begin
4 if(idl2wrd_start)begin
5 state_n = WR_RD;
6 end
7 else begin
8 state_n = state_c;
9 end
10 end
11 WR_RD:begin
12 if(wrd2tcs_start)begin
13 state_n = TCS;
14 end
15 else begin
16 state_n = state_c;
17 end
18 end
19 TCS:begin
20 if(tcs2do_start)begin
21 state_n = DO;
22 end
23 else if(tcs2idl_start)begin
24 state_n = IDLE;
25 end
26 else begin
27 state_n = state_c;
28 end
29 end
30 DO:begin
31 if(do2idl_start)begin
32 state_n = IDLE;
33 end
34 else begin
35 state_n = state_c;
36 end
37 end
38 default:begin
39 state_n = IDLE;
40 end
41 endcase
42 end
43
第三段,用assign定义转移条件。注意条件一定要加上现态。
2 assign wrd2tcs_start = state_c == WR_RD&& end_cnt1;
3 assign tcs2idl_start = state_c == TCS && end_cnt
4 && (mode_reg==EWEN||mode_reg==READ);
5 assign tcs2do_start = state_c == TCS && mode_reg == WRITE && end_cnt;
6 assign do2idl_start = state_c == DO && mode_reg == WRITE && do_ff1 == 1;
7
第四段,则是输出信号设计,在功能代码部分。
(8)功能代码
2 always @(posedge clk or negedge rst_n)begin
3 if(rst_n==1'b0)begin
4 cnt <= 0;
5 end
6 else if(add_cnt) begin
7 if(end_cnt)
8 cnt <= 0;
9 else
10 cnt <= cnt + 1;
11 end
12 end
13 assign add_cnt = state_c == WR_RD || state_c == TCS;
14 assign end_cnt = add_cnt && cnt==100 - 1 ;
15
16 //根据第六步第2点,写出cnt1的代码
17 always @(posedge clk or negedge rst_n)begin
18 if(rst_n==1'b0)begin
19 cnt1<= 0;
20 end
21 else if(add_cnt1) begin
22 if(end_cnt1)
23 cnt1 <= 0;
24 else
25 cnt1 <= cnt1 + 1;
26 end
27 end
28 assign add_cnt1 = end_cnt;
29 assign end_cnt1 = add_cnt1 && cnt1==x -1 ;
30
31 always @(*)begin
32 if(mode_reg == EWEN)begin
33 x = 10;
34 end
35 else if(mode_reg == WRITE)begin
36 x = 18;
37 end
38 else if(mode_reg == READ)begin
39 x = 18;
40 end
41 else begin
42 x = 0;
43 end
44 end
45 //根据第六步第3点,写出do的代码
46 always @(posedge clk or negedge rst_n)begin
47 if(rst_n==1'b0)begin
48 do_ff0<=0;
49 do_ff1<=0;
50 end
51 else begin
52 do_ff0<=d0;
53 do_ff1<=do_ff0;
54 end
55 end
56
57 //根据第六步第4点,写出sk的代码
58 always @(posedge clk or negedge rst_n)begin
59 if(rst_n==1'b0)begin
60 sk <= 0;
61 end
62 else if(sk_high)begin
63 sk <= 1;
64 end
65 else if(sk_low)begin
66 sk <= 0;
67 end
68 end
69 assign sk_high = state_c == WR_RD && add_cnt && cnt == 50-1;
70 assign sk_low = state_c == WR_RD && end_cnt;
71
72 //根据第六步第5点,写出di的代码
73 always @(posedge clk or negedge rst_n)begin
74 if(rst_n==1'b0)begin
75 di <= 0;
76 end
77 else if(di_en)begin
78 di <= dout[17-cnt1];
79 end
80 end
81 assign di_en = cnt==0 && state_c == WR_RD;
82
83 //根据第六步第6点,写出cs的代码
84 always @(posedge clk or negedge rst_n)begin
85 if(rst_n==1'b0)begin
86 cs <= 0;
87 end
88 else if(cs_high)begin
89 cs <= 1;
90 end
91 else if(cs_low)begin
92 cs <= 0;
93 end
94 end
95 assign cs_high = idl2wrd_start || tcs2do_start;
96 assign cs_low = wrd2tcs_start || do2idl_start;
97
98 //根据第六步第7点,写出rdy的代码
99 always @(*)begin
100 if(rdy_low)
101 rdy = 0;
102 else
103 rdy = 1;
104 end
105 assign rdy_low = start || state_c != IDLE;
106
107 //根据第六步第8点,写出rdata的代码
108 always @(posedge clk or negedge rst_n)begin
109 if(rst_n==1'b0)begin
110 rdata <= 0;
111 end
112 else if(rdata_en)begin
113 rdata <= {rdata[6:0],do};
114 end
115 end
116 assign rdata_en = mode_reg == READ && state_c == WR_RD && end_cnt
117 && cnt1 >= 10 && cnt1 < 18;
118
119 //根据第六步第9点,写出rdata_vld的代码
120 always @(posedge clk or negedge rst_n)begin
121 if(rst_n==1'b0)begin
122 rdata_vld <= 0;
123 end
124 else if(rdata_vld_en)begin
125 rdata_vld <= 1;
126 end
127 else begin
128 rdata_vld <= 0;
129 end
130 end
131 assign rdata_vld_en = mode_reg == READ && wrd2tcs_start;
132
133 //根据第六步第10点,写出dout的代码
134 always @(posedge clk or negedge rst_n)begin
135 if(rst_n==1'b0)begin
136 dout <= 0;
137 end
138 else if(start && mode==0)begin
139 dout <= {3'b100,2’b11,13'b0};
140 end
141 else if(start && mode==1)begin
142 dout <= {3'b101,addr,wdata};
143 end
144 else if(start && mode==2)begin
145 dout <= {3'b110,addr,8'b0};
146 end
147 end
148
149 always @(posedge clk or negedge rst_n)begin
150 if(rst_n==1'b0)begin
151 mode_reg <= 0;
152 end
153 else if(start && mode==0)begin
154 mode_reg <= EWEN;
155 end
156 else if(start && mode==1)begin
157 mode_reg <= WRITE;
158 end
159 else if(start && mode==2)begin
160 mode_reg <= READ;
161 end
162 end
163