利用SPI驱动12864液晶

2019-04-15 15:49发布

SPI 发送模块 在这里,我们要在主机上建立,一个向从机写入数据的SPI 发送模块,首先我们先从C语言上了解几个主机在SPI 写操作上容易被疏忽的小细节:我们知道SPI 设备在传输都有一个规则,SCL 时钟信号在“上升沿”的时候是“锁存数据”,SCL 时钟信号在“下降沿”是“设置数据”。在这里我们SPI 主机(FPGA),写操作要干的工作就是在“拉高SCL 时钟信号之前”设置数据(移位数据),设置数据之后,再拉高时钟信号。但是我们常常会忽略了一些具体的细节。 利用SPI驱动12864液晶
上面有两个主机的SPI_Send 函数(写函数),左边的写法是最常用,但是也是最容易忽略小细节。相比右边的写法比较谨慎,在最低的程度上符合一写小细节。 利用SPI驱动12864液晶
再来,我们继续引用ST7565P 芯片的写入时序图,分析并且区分上边的两个写法。关于SCL 信号在空闲的时候总是处于高电平。当主机开始向从机写入数据,主机会先拉低CS 信号,再拉低SCL 信号,然后“设置”数据,亦即主机(FPGA)更新SI 的数据(主机数据移位操作),最后再拉高SCL 信号。同一时间,从机会因为SCL 的上升沿变化,从机(液晶资源)“锁存”(从机读取数据操作)SI 上的数据。很明显左边的写法没有符合这些细节,然而右边的写法却符合这些细节。无论是左边的写法还是右边的写法,都忽略了一个致命的细节,两种写法都无法确定SPI 时钟信号的时钟频率。 module spi_write_module ( CLK,RSTn,Start_Sig,SPI_Data,Done_Sig,SPI_Out ); input CLK; input RSTn; input Start_Sig; output [9:0] SPI_Data; output Done_Sig; output [3:0]SPI_Out;//[3]CS,[2]A0 [1]CLK [0]D0 parameter T0P5US=4'd9;//0.5us always@(posedge CLK or negedge RSTn) if(!RSTn) Count1<=4'd0; else if(Count1==T0P5US) Count1<4'd0; else if(Start_Sig) Count1<=Count1+1'b1; else Count1<=4'd0; reg[4:0]i; reg rCLK; reg rDO; reg isDone; always@(posedge CLK or negedge RSTn) if(!RSTn) begin i<=5'd0; rCLK<=1'b1; rDO<=1'b0; isDone<=1'b0; end else if(Start_Sig) case(i) 5'd0,5'd2,5'd4,5'd6,5'd8,5'd10,5'd12,5'd14: if(Count1==T0P5US)begin rCLK<=1'b0;rDO<=SPI_Data[7-(i>>1)];i<=1+1'b1;end 5'd1,5'd3,5'd5,5'd7,5'd9,5'd11,5'd13,5'd15: if(Count==T0P5US)begin rCLK<=1'b1;i<=i+1'b1;end 5'd16: begin isDone<=1'b1;i<=i+1'b1;end 5'd17: begin isDone<=1'b0;i<=5'd0;end endcase assign Done_Sig=isDone; assign SPI_Out={SPI_Data[9],SPI_Data[8],rCLK,rDO}; endmodule SCL 的时钟频率定义为1Mhz , 也就是说周期时间是1us ,半周期就是0.5us 。如果以20Mhz 来定时,那么计数的结果是10。在19 行定义了0.5us 的常量,第23~33 行是0.5us的定时器。但是比较不同的是,这个定时器平时不工作,当Start_Sig 拉高的时候才开始计数(第30 行)。第37~65 行是spi_write_module.v 的核心功能。i 寄存器表示操作步骤,rCLK 寄存器表示SCL 然而rDO 寄存器表示SI 。如同前面所述那样,SCL 时钟信号,处于空闲状态 时是出于高电平,所以rCLK 复位值是逻辑1(46 行)。 SPI_Data : 第9 位表示CS,第8 位表示A0,第7 .. 0 位表示一字节数据。 SPI_Out : 第3 位表示CS,第2 位表示A0,第1 位表示SCL,第0 位表示SI。 当Start_Sig 拉高的同时,定时器开始计数(30 行),该模块也开始执行(50 行)。 当第一个0.5us 定时产生的时候(54 行),也就是第一个时钟的前半周期,亦即下降沿, rCLK 设置为逻辑0。根据SPI 传输的规则,下降沿的时候主机设置数据,rDO 赋予SPI_Data 信号的第7 位(SPI 传输是从最高位开始,最低位结束),最后i 递增以示下一个步骤。当i 等于1 的时候并且定时产生(56 行),这表示第一个时钟的后半周期,亦即上升沿,rCLK 设置为逻辑1。在SPI 传输的规则中上升沿的时候,从机锁存数据。然后i 递增以示下一个步骤。 上述的步骤会一直重复到第八次,直到一字节的数据发送完毕。最后会产生一个完成信号(59~63 行)。 这里有一个表达式需要说明一下: i >> 1 : 表示i / 2。右移操作也代表了“i / j2”(j 是右移次数)。 假设8 >> 2 ,亦即8 / 22 等于2。8 >> 3, 亦即8 / 23 等于1。 最后还有一个重点就是SPI_Out 的驱动(70 行)。在上面我已经重复过SPI_Out 是占4 位的输出。而且每一个位都有意义。 SPI_Out 第3 位:表示了CS,所以直接由SPI_Data 的第9 位驱动。 SPI_Out 第2 位:表示了A0,同样也是直接由SPI_Data 的第8 位驱动。 SPI_Out 第1 位:表示了SCL,以寄存器rCLK 来驱动。 SPI_Out 第0 位:表示了SI,以寄存器rDO 来驱动。 这样的目的是简化连线的复杂度。我们知道Verilog HDL 语言的位操作是很强大。懂得善用,会对建模提到很大的帮助。