一、前言
最近花费很多精力在算法仿真和实现上,外设接口的调试略有生疏。本文以FPGA控制OLED中的SPI接口为例,重新夯实下基础。重点内容为SPI时序的RTL设计以及AXI-Lite总线分析。当然做些项目时可以直接调用Xilinx提供的SPI IP核,这里仅出于练习的目的考虑。
二、接口时序分析
本项目用的OLED型号为UG-2832HSWEG04,核心控制器是SSD1306。该芯片支持并口、I2C以及SPI接口,这里采用4线SPI作为数据总线。4线SPI接口包括:
SCLK:串行时钟,SSD1306上升沿采集数据
SDIN:串行数据输入,数据顺序为MSB
D/C:数据命令控制,高电平为数据,低电平为控制命令
CS:片选信号,低电平有效
时序图如下:
片选信号有效期间,每第8个时钟周期上升沿时刻,控制芯片会采样D/C并同时将进入的一字节数据写入到显示缓存GDDRAM或控制寄存器中。
根据datasheet中的AC Characteristics中参数,选择SPI串行时钟周期为200ns,占空比为50%以保证足够的时序裕量。此时传输速率为:5MHZ/8 = 625KHZ。
三、SPI接口模块设计
根据上述分析,很容易可以设计出用于传输数据或命令的SPI时序接口模块。接口定义如下:
用户侧:clk rst_n com din busy,依次是系统时钟,复位,指令信号(1为发送控制信息,2则发送数据),待传输字节以及忙等待指示。
外设侧:SCLK SDIN CS D/C
逻辑状态分为:IDLE SEND和DONE,具体时序如下:
直接对照上图编写HDL:


1 `timescale 1ns / 1ps 2 3 module spi_4wire#(parameter DIV_CYC = 20) 4 ( 5 //本地接口 6 input clk,//100MHZ 7 input rst_n, 8 input [2-1:0] com,//1发送控制信息,2发送数据 其他无效 9 input [8-1:0] din, 10 output busy, 11 //芯片侧接口 12 output reg sclk = 0, 13 output reg sdin = 0, 14 output reg cs = 1'b1, 15 output reg dc = 0//1是数据,0是控制命令 16 ); 17 //**************************参数定义******************************************** 18 function integer clogb2 (input integer bit_depth); 19 begin 20 for(clogb2=0; bit_depth>0; clogb2=clogb2+1) 21 bit_depth = bit_depth >> 1; 22 end 23 endfunction 24 25 localparam DIV_CNT_W = clogb2(DIV_CYC-1), 26 BIT_CNT_W = clogb2(8-1); 27 28 localparam IDLE = 0 , 29 SEND = 1 , 30 DONE = 2 ; 31 32 //************************变量定义**************************************** 33 reg [ (DIV_CNT_W-1):0] div_cnt =0 ; 34 wire add_div_cnt ; 35 wire end_div_cnt ; 36 reg [ (BIT_CNT_W-1):0] bit_cnt =0 ; 37 wire add_bit_cnt ; 38 wire end_bit_cnt ; 39 reg [2-1:0] state_c = IDLE,state_n = IDLE; 40 wire idle2send,send2done,done2idle; 41 reg [8+2-1:0] data_tmp = 0; 42 wire din_vld; 43 wire start_send; 44 reg busy_flag = 0; 45 //************************逻辑**************************************** 46 //sclk时钟分频 T:10ns --> 200ns 20倍 47 //分频计数器 48 always @(posedge clk or negedge rst_n) begin 49 if (rst_n==0) begin 50 div_cnt <= 0; 51 end 52 else if(add_div_cnt) begin 53 if(end_div_cnt) 54 div_cnt <= 0; 55 else 56 div_cnt <= div_cnt+1 ; 57 end 58 end 59 assign add_div_cnt = (1); 60 assign end_div_cnt = add_div_cnt && div_cnt == (DIV_CYC)-1 ; 61 62 //比特计数器 63 always @(posedge clk or negedge rst_n) begin 64 if (rst_n==0) begin 65 bit_cnt <= 0; 66 end 67 else if(add_bit_cnt) begin 68 if(end_bit_cnt) 69 bit_cnt <= 0; 70 else 71 bit_cnt <= bit_cnt+1 ; 72 end 73 end 74 assign add_bit_cnt = (state_c == SEND && end_div_cnt); 75 assign end_bit_cnt = add_bit_cnt && bit_cnt == (8)-1 ; 76 77 //控制状态机 78 always @(posedge clk or negedge rst_n) begin 79 if (rst_n==0) begin 80 state_c <= IDLE ; 81 end 82 else begin 83 state_c <= state_n; 84 end 85 end 86 87 always @(*) begin 88 case(state_c) 89 IDLE :begin 90 if(idle2send ) 91 state_n = SEND ; 92 else 93 state_n = state_c ; 94 end 95 SEND :begin 96 if(send2done ) 97 state_n = DONE ; 98 else 99 state_n = state_c ; 100 end 101 DONE :begin 102 if(done2idle ) 103 state_n = IDLE ; 104 else 105 state_n = state_c ; 106 end 107 default : state_n = IDLE ; 108 endcase 109 end 110 111 assign idle2send = state_c==IDLE && (end_div_cnt && data_tmp[10-1 -:2] != 0); 112 assign send2done = state_c==SEND && (end_bit_cnt); 113 assign done2idle = state_c==DONE && (end_div_cnt); 114 115 116 //输入命令/数据寄存 117 always @(posedge clk or negedge rst_n)begin 118 if(rst_n==1'b0)begin 119 data_tmp <= 0; 120 end 121 else if(din_vld)begin 122 data_tmp <= {com,din}; 123 end 124 else if(done2idle)begin 125 data_tmp <= 0; 126 end 127 end 128 129 assign din_vld = busy_flag == 1'b0 && com != 2'd0; 130 131 //SPI输出信号 132 always @(posedge clk or negedge rst_n)begin 133 if(rst_n==1'b0)begin 134 sdin <= 0; 135 end 136 else if(add_bit_cnt)begin 137 sdin <= data_tmp[8-1-bit_cnt]; 138 end 139 end 140 141 always @(posedge clk or negedge rst_n)begin 142 if(rst_n==1'b0)begin 143 cs <= 1'b1; 144 end 145 else if(start_send)begin 146 cs <= 0; 147 end 148 else if(done2idle)begin 149 cs <= 1'b1; 150 end 151 end 152 153 assign start_send = add_bit_cnt && bit_cnt == 0; 154 155 always @(posedge clk or negedge rst_n)begin 156 if(rst_n==1'b0)begin 157 dc <= 1'b0; 158