在网上找了许久,发现FPGA用串口驱动LCD12864程序很少,基本上没有。刚开始窃喜,中间郁闷,最后还是高兴,为什么这样说呢!头一回在没有参考程序的情况下,完全是照时序图写(自信),中间调试过程遇到一点小插曲(郁闷),后来搞定(高兴),也算是对这段时间学习FPGA的一个能力检测吧。废话少说,赶紧步入正题。
首先来看一下串口模式的几个重要管脚:
1、lcd_cs(PIN4),使能信号,高有效(有的资料上写着低有效,高低我都试过,确认是低有效),定义output。
2、lcd_sid(PIN5),数据传输线,相当于I2C的SBDA数据传输线,可定义双向,这里仅只有写,所以定义output。
3、lcd_scl(PIN6),数据传输的时钟,相当于I2C的SBCL时钟线,定义output,这里注意,很多资料上没说这频率多少合适,而有的资料上写的是高脉冲和低脉冲不能小于800ns或其他数据,一开始本以为这个时钟会比并口时钟会快一些,参考“verilog hdl的那些事儿”中,频率设置100KHZ,他的是ST7566P,我的是ST7920,同一个系列,频率应该会差不多吧,以上条件完全让我把频率设置在100KHZ,那么这个实验注定要失败,我就是栽倒在这,中间调试时,把频率往小降,往大增(就是没想到要那么慢的时钟),结果就是不行,要么出现乱码,要么不显示,后来干脆就把这频率设置成与并口一样的时钟,周期6MS(当然这个频率不是最佳值,到底多少只有自己去试试了),喔、喔,终于正确的显示了,高兴阿。
4、PSB(PIN15),串口模式,可以直接接地,程序中是设置为低,定义output。
管脚都清楚了,那么接下来看时序图,是很重要的一步:在发送数据前,CS要为高,发送数据是先连续发送5个1,用来启动一个周期,此时传输计数被重置,并且串行传输被同步。紧接的两个位指定传输方向(RW,确定读还是写)和传输性质(RS,确定是命令寄存器还是数据寄存器),最后的第八位是一个“0”,一个启动字节发送完毕,那么紧接着发送数据或是指令。一个数据或指令要分为两个字节来处理,先发高4bit,紧接着连续发送4个0,在发送低4bit,又紧接着发送4个0,也就是说完整发送完一个数据或命令,lcd_scl得打24个节拍。由于本实验也是只写不读,所以得避开LCD的忙状态,所以每次发送完一个数据或是命令后,在打一个节拍(25个),本程序中一个节拍的时间(6MS)足够避开LCD的忙状态。这里也要注意,当把所有数据处理完之后,lcd_scl得关闭,也就是拉低,否则,屏会一直刷新,覆盖掉原有的内容,最终全屏成乱码。
OK,时序搞定,那么可以利用状态机完成,代码实现
LCD12864_SPI.v
1 module LCD12864_SPI( 2 //input 3 sys_clk, 4 rst_n, 5 6 //output 7 lcd_cs, 8 lcd_sid, 9 lcd_scl, 10 lcd_psb 11 ); 12 input sys_clk;//50MHZ 13 input rst_n; 14 15 output lcd_cs; //enable,H active 16 output lcd_sid; //SPI data 17 output lcd_scl; //SPI clk 18 output lcd_psb; ////H:parallel module L:SPI module 19 /***************************************************/ 20 assign lcd_psb = 1\'b0;//串口模式 21 /***************************************************/ 22 parameter T3MS = 18\'d149_999; 23 parameter IDLE = 4\'d0, //准备状态 24 SEND_1 = 4\'d1, //连续发送5个1 25 SNED_RW = 4\'d2, //是写还是读 26 SEND_RS = 4\'d3, //是指令还是数据 27 SEND_0 = 4\'d4, //发送一个0,代表启动字节结束 28 SEND_DATA_H = 4\'d5, //发送数据的高4bit 29 SEND_FOUR_0 = 4\'d6, //连续发送四个0 30 SEND_DATA_L = 4\'d7, //发送数据的低4bit 31 DELAY = 4\'d8, //延时一个lcd_clk周期,避开LCD的忙状态 32 STOP = 4\'d9; //结束 33 /***************************************************/ 34 ////产生周期为6MS的lcd_clk给LCD 35 reg [17:0] cnt; 36 reg lcd_clk; 37 always @(posedge sys_clk or negedge rst_n) 38 if(!rst_n) begin 39 cnt <= 18\'d0; 40 lcd_clk <= 1\'b0; 41 end 42 else if(cnt == T3MS)begin 43 cnt <= 18\'d0; 44 lcd_clk <= ~lcd_clk; 45 end 46 else 47 cnt <= cnt + 1\'b1; 48 /***************************************************/ 49 assign lcd_scl = en ? lcd_clk : 1\'b0; //发送完之后,必须得停止,否则屏一直刷新,最后成全屏乱码 50 /***************************************************/ 51 //在下降沿设置数据或命令 52 reg lcd_sid; 53 reg lcd_cs; 54 reg [2:0] i; 55 reg [2:0] j; 56 reg [6:0] num; 57 reg [3:0] state; 58 reg [7:0] dis_data; 59 reg flag; //用来标志一个dis_data数据发送完 60 reg en; //lcd_scl的使能信号 61 always @(posedge lcd_clk or negedge rst_n) 62 if(!rst_n) begin 63 lcd_sid <= 1\'bz; 64 lcd_cs <= 1\'b0; 65 i <= 3\'d0; 66 j <= 3\'d0; 67 num <= 7\'d0; 68 state <= IDLE; 69 flag <= 1\'b0; 70 en <= 1\'b1; 71 end 72 else 73 case(state) 74 75 IDLE: 76 begin 77 lcd_cs <= 1\'b1; 78 state <= SEND_1; 79 end 80 81 SEND_1://发送5个1 82 begin 83 i <= i + 1\'b1; 84 lcd_sid <= 1\'b1; 85 if(i == 3\'d4) begin 86 i <= 3\'d0; 87 state <= SNED_RW; 88 end 89 else 90 state <= SEND_1; 91 end 92 93 SNED_RW: 94 begin 95 lcd_sid <= 1\'b0;//写 96 state <= SEND_RS; 97 end 98 99 SEND_RS: 100 begin 101 state <= SEND_0; 102 if((num <= 7\'d5) || (num == 7\'d18) 103 || (num == 7\'d25) || (num == 7\'d34) 104 || (num == 7\'d41) || (num == 7\'d42) 105 || (num == 7\'d75)) 106 lcd_sid <= 1\'b0;//命令 107 else 108 lcd_sid <= 1\'b1;//数据 109 end 110 111 SEND_0: 112 begin 113 lcd_sid <= 1\'b0;//一个启动字节结束 114 state <= SEND_DATA_H; 115 end 116 117 SEND_DATA_H: //进入发送高4bit 118 begin 119 j <= j + 1\'b1; 120 lcd_sid <= dis_data[7-j]; 121 if(j == 3\'d3) 122 state <= SEND_FOUR_0; 123 else 124 state <= SEND_DATA_H; 125 end 126 127 SEND_FOUR_0: //进入连续发送四个0 128 begin 129 i <= i + 1\'b1; 130 lcd_sid <= 1\'b0; 131 if(i == 3\'d3) begin 132 i <= 3\'d0; 133 if(flag) begin 134 num <= num + 1\'b1; 135 flag <= 1\'b0; 136 state <= DELAY; 137 end 138 else 139 state <= SEND_DATA_L; 140 end 141 else 142 state <= SEND_FOUR_0; 143 end 144 145 SEND_DATA_L: //进入连续发送低4bit 146 begin 147 j <= j + 1\'b1; 148 lcd_sid <= dis_data[7-j]; 149 if(j == 3\'d7) begin 150 j <= 3\'d0; 151 flag <= 1\'b1; //标志一个字节发送完 152 state <= SEND_FOUR_0; 153 end 154 else 155 state <= SEND_DATA_L; 156 end 157 158 DELAY: //延时,避开LCD的忙状态 159 if(num <= 7\'d77) 160 state <= SEND_1; 161 else 162 state <= STOP; 163 164 STOP: 165 begin 166 lcd_cs <= 1\'b0; 167 en <= 1\'b0; 168 state <= STOP; 169 end 170 171 endcase 172 /***************************************************/ 173 always @(num) 174 case(num) 175 7\'d0 : dis_data = 8\'h30;//功能设定 176 7\'d1 : dis_data = 8\'h30;//功能设定 177 7\'d2 : dis_data = 8\'h0c;//显示设定 178 7\'d3 : dis_data = 8\'h01;//清屏 179 7\'d4 : dis_data = 8\'h06;//进入设定点 180 7\'d5 : dis_data = 8\'h81;//设置DDRAM地址 181 //欢迎访问博客 182 7\'d6 : dis_data = 8\'hbb; 183 7\'d7 : dis_data = 8\'hb6; 184 7\'d8 : dis_data = 8\'hd3; 185 7\'d9 : dis_data = 8\'had; 186 7\'d10 : dis_data = 8\'hb7; 187 7\'d11 : dis_data = 8\'hc3; 188 7\'d12 : dis_data = 8\'hce; 189 7\'d13 : dis_data = 8\'hca; 190 7\'d14 : dis_data = 8\'hb2; 191 7\'d15 : dis_data = 8\'ha9; 192 7\'d16 : dis_data = 8\'hbf; 193 7\'d17 : dis_data = 8\'hcd; 194 //2line 显示 文少清 195 7\'d18 : dis_data = 8\'h92;//设置DDRAM地址 196 7\'d19 : dis_data = 8\'hce; 197 7\'d20 : dis_data = 8\'hc4; 198 7\'d21 : dis_data = 8\'hc9; 199 7\'d22 : dis_data = 8\'hd9; 200 7\'d23 : dis_data = 8\'hC7; 201 7\'d24 : dis_data = 8\'he5; 202 //3line 显示 LCD12864 203 7\'d25 : dis_data = 8\'h8a;//设置DDRAM命令 204 7\'d26 : dis_data = "L"; 205 7\'d27 : dis_data = "C"; 206 7\'d28 : dis_data = "D"; 207 7\'d29 : dis_data = "1"; 208 7\'d30 : dis_data = "2"; 209 7\'d31 : dis_data = "8"; 210 7\'d32 : dis_data = "6"; 211 7\'d33 : dis_data = "4"; 212 //4Line显示 谢谢! 213 7\'d34 : dis_data = 8\'h9d;//设置DDRAM地址 214 7\'d35 : dis_data = 8\'hd0; 215 7\'d36 : dis_data = 8\'hbb; 216 7\'d37 : dis_data = 8\'hd0; 217 7\'d38 : dis_data = 8\'hbb; 218 7\'d39 : dis_data = 8\'ha3; 219 7\'d40 : dis_data = 8\'ha1; 220 //4Line显示喇叭 221 7\'d41 : dis_data = 8\'h30;//功能设定 222 7\'d42 : dis_data = 8\'h40;//设定CGRAM字符的位置 223 7\'d43 : dis_data = 8\'h00; 224 7\'d44 : dis_data = 8\'h39; 225 7\'d45 : dis_data = 8\'h00; 226 7\'d46 : dis_data = 8\'h6a; 227 7\'d47 : dis_data = 8\'h00; 228 7\'d48 : dis_data = 8\'ha8; 229 7\'d49 : dis_data = 8\'h01; 230 7\'d50 : dis_data = 8\'h29; 231 7\'d51 : dis_data = 8\'h7e; 232 7\'d52 : dis_data = 8\'h2a; 233 7\'d53 : dis_data = 8\'hfc; 234 7\'d54 : dis_data = 8\'h28; 235 7\'d55 : dis_data = 8\'hfc; 236 7\'d56 : dis_data = 8\'h29; 237 7\'d57 : dis_data = 8\'hcc; 238 7\'d58 : dis_data = 8\'h2a; 239 7\'d59 : dis_data = 8\'hcc; 240 7\'d60 : dis_data = 8\'h28; 241 7\'d61 : dis_data = 8\'hfc; 242 7\'d62 : dis_data = 8\'h29; 243 7\'d63 : dis_data = 8\'hfc; 244 7\'d64 : dis_data = 8\'h2a; 245 7\'d65 : dis_data = 8\'h7e; 246 7\'d66 : dis_data = 8\'h28; 247 7\'d67 : dis_data = 8\'h01; 248 7\'d68 : dis_data = 8\'h29; 249 7\'d69 : dis_data = 8\'h00; 250 7\'d70 : dis_data = 8\'haa; 251 7\'d71 : dis_data = 8\'h00; 252 7\'d72 : dis_data = 8\'h68; 253 7\'d73 : dis_data = 8\'h00; 254 7\'d74 : dis_data = 8\'h38; 255 7\'d75 : dis_data = 8\'h99;//设置DDRAM地址 256 7\'d76 : dis_data = 8\'h00;//设置自定义显示字符编码 高8bit 是数据不是命令 257 7\'d77 : dis_data = 8\'h00;//设置自定义显示字符编码 低8bit 258 default: dis_data = 8\'h00; 259 endcase 260 261 endmodule
显示效果:这次把星星月亮换成一个小喇叭