官方论坛
官方淘宝
官方博客
微信公众号
点击联系吴工 点击联系周老师

【案例】SPI接口设计

发布时间:2021-06-24   作者:admin 浏览量:

SPI(Serial Peripheral Interface--串行外设接口)总线系统是一种同步串行外设接口,它可以使MCU与各种外围设备以串行方式进行通信以交换信息。本项目我们通过SPI接口实现FPGAEEPROM芯片——AT93C46的通信。示意图如图3-16

fpga就业

3-16 SPI接口传输结构图

先查阅AT93C46datasheet,了解各引脚的作用。

cs:从器件使能信号,由主器件控制;

sk:时钟信号,由主器件产生;

do:主器件数据输入,从器件数据输出;

di:主器件数据输出,从器件数据输入。

再明确实现相互通信需要哪些操作。

1. 三种基本的操作

1EWEN时序(图3-17

3-17 EWEN时序图

EWEN可以理解为激活设备,写、擦除(这里没有用到,有兴趣的读者可以自行查阅datasheet)操作执行之前必须要先执行完EWEN操作。其操作过程为:

1) cs在开始操作时拉高,等到读操作完成,拉低;

2) skCS拉高时,产生10个脉冲,脉冲周期大于400ns

3) diSK的周期内输出数据,前面输出固定的指令“10011”,指示是EWEN操作,后面则继续输出5比特的“0”

2WRITE时序(图3-18

3-18 WRITE时序图

写操作:

1) cs先拉高一段时间,然后再拉低TCS时间,最后再拉高CS,直至检测到DO1,拉低;

2) skCS第一段拉高时,产生18个脉冲,脉冲周期大于400ns

3) disk的周期内输出数据,前面输出固定的指令“101”,指示是写操作,后面则继续输出地址“AN~A0”和数据“DN~D0”

3READ时序(图3-19

fpga就业前景

3-19 ERAD时序图

1) cs在开始操作时拉高,等到读操作完成,拉低;

2) skcs第一段拉高时,产生18个脉冲,脉冲周期大于400ns

3) disk的周期内输出数据,前面输出固定的指令“110”,指示是读操作,后面则继续输出地址“AN~A0”和固定的“0”

4) 模块从sk的第11个周期开始从do中读取数据,每个周期读取1比特,共8比特。

实现一个AT93C46的接口,该接口能够根据命令,实现EWENWRITEREAD功能。具体功能如下:

明德扬在变化范围内取了1us作为tcs的值;AT93C46时钟取1MHz

上游模块在rdy=1时,给出start命令,开始进行EWENWRITE或者READ操作。在rdy=0期间,start命令无效。

start有效时,如果mode=0表示进行EWEN操作;mode=1表示进行WRITE操作;mode=2表示进行READ操作。

start有效时,addrwdata有效。

当进行EWEN操作时,模块将按at93c46 EWEN的要求,将addr写入at93c46

当进行WRITE操作时,模块将按at93c46 WRITE的要求,将addrwdata写入at93c46

当进行READ操作时,模块将按at93c46 READ的要求,将addr写入at93c46,并从at93c46读到数据,通过rdatardata_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状态划分

EWENREADWRITE的时序图我们可以发现在不同操作中有很多阶段是相似的,总结起来有4个状态:

IDLE:初始状态,模块在等待start信号有效。

WR_RD:读写状态。

TCS:片选信号拉低。

DO:等待写入操作完成。

4状态转移

状态转移图请见3-21

至简设计法

3-21 状态转移图


5转移条件

确定了状态转移图后,我们需要明确状态转移条件:

wr_rd_start:在IDLE状态下收到start有效。

tcs_start:在WR_RD状态结束。

idle_start1:,处于EWENREAD模式,在TCS状态结束(1us)。

do_start:处于WRITE模式,在TCS结束(1us)。

idle_start2:处于WRITE模式,在TCS状态下,收到do==1


6完整性检查

至简设计法

7状态机代码

第一段,用同步时序,将次态的值赋给现态。注意此时直接套用模块,不要做任何更改。

1 always  @(posedge clk or negedge rst_n)begin
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

第二段,用组合逻辑描述状态转移条件。注意转移条件用信号来表示,信号名要按明德扬规则来命名。

 1 always  @(*)begin
 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定义转移条件。注意条件一定要加上现态。

1 assign idl2wrd_start = state_c == IDLE && start == 1;
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;

第四段,则是输出信号设计,在功能代码部分。

8功能代码

  1 //根据第六步第1点,写出cnt的代码
  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

技术交流QQ群:764574006

更多FPGA技术资讯:明德扬科教



   拓展阅读