LCD1602
- 功能描述:
通过写入指令或数据,控制内部的驱动芯片,从而实现在LCD显示屏上显示字符串:
- 设计方案:
设计将以有限状态机的形式完成功能设计,其中涉及的状态及相关功能如下图1所示,
![](https://img2023.cnblogs.com/blog/2326690/202211/2326690-20221130230802886-1871222875.png)
CGRAM_ADDR前面均为LCD初始化状态,不多赘述。CGRAM_ADDR状态下,传送指令:要写入CGRAM,地址为00H;下面CGRAM_WRITE状态下,传送数据:其中字模数据为8行八列数据,不能一次传入,故设计一计数器,若计数到7,则跳转下一状态ADDR1,否则继续CGRAM_WRITE状态直到完成一个字符的写入;ADDR1和ADDR2状态下均传送指令:要写入DDRAM,地址分别为00H和40H,完成后均跳转到WRITE_DDRAM状态;WRITE_DDRAM也需要一计数器来监控传到了第几行 以及是否传送完毕,结束后跳转至STOP状态,维持当下显示。
下面将说明具体实现。
①使能信号的生成: 当LCD的时钟频率设置错误时,LCD的显示就会闪,或者没有显示,故选择控制使能信号的周期生成,来达到设计要求的500Hz,这里选择的是计数方法,已知我们的初始时钟是CLOCK_50,若每逢上升沿计数,为生成500Hz的工作时钟,需每逢计数99999,进行一次归零进入下一计数周期,并设置使能信号LCD_EN,每逢49999进行一次翻转,即可对应得到使能信号;
②延时15ms的实现: 在空闲IDLE状态下,需延时15ms进入下一状态,综合考虑电路性能,选择以计数的形式来实现,同上①;
③LCD的显示: 在LCD上显示字符的过程就是向DDRAM的相应地址写入数据的过程,其中DDRAM地址与显示位置的对应关系如下图2所示。若想要在LCD的第一行最左显示字母A,则过程为:传指令DDRAM地址00H\({→}\)传数据字母A(41H)。
![](https://img2023.cnblogs.com/blog/2326690/202211/2326690-20221130230744976-1291687484.png)
④自定义字符的实现: 在芯片中内置了192个常用字符的字模,存于CGROM中,可以实现基本数字和字母的直接调用显示(如上文中字母A),此外还有8个允许用户自定义字符的CGRAM,即为下图3中绿框中所示,我们可以通过先将自定义字符写入CGRAM,再调入写入DDRAM的方法实现自定义字符的显示,下面以字符\({❤}\)为例加以说明:
![](https://img2023.cnblogs.com/blog/2326690/202211/2326690-20221130230740296-67208037.png)
- 取字模: 将图形转换为二进制数,显示出来的部分置1,未显示的部分置0,如下图4所示,可以得到8列八位数据;
![](https://img2023.cnblogs.com/blog/2326690/202211/2326690-20221130230757348-1221727325.png)
- 写入CGRAM: 写入CGRAM的指令如下图5所示,其中CGRAM的地址为00H-0FH,这里我们选择写入地址为00H,将上面得到的8列八位数据依次写入CGRAM,则CGROM中00H就代表了字符\({♥}\);
![](https://img2023.cnblogs.com/blog/2326690/202211/2326690-20221130230808383-2072905023.png)
-
显示: 即将地址指针指向DDRAM即可,写入数据为00H。
-
关键代码:
/***main_FSM***/
//Section1
always@(posedge clk, negedge rstn) begin
if(!rstn) cur_state <= IDLE;
else if(cnt_500hz == 17'b01100001101001111)
cur_state <= next_state;
end
//Section2
always @(cur_state) begin
case(cur_state)
IDLE: begin
if(flag) next_state = CLEAR_LCD;
else next_state = IDLE;
end
CLEAR_LCD: next_state = SET_DISP_MODE;
SET_DISP_MODE: next_state = DISP_ON;
DISP_ON: next_state = SHIFT_DOWN;
SHIFT_DOWN: next_state = CGRAM_ADDR;
CGRAM_ADDR: next_state = WRITE_CGRAM;
WRITE_CGRAM: begin
if(cnt_cgram == 4'd7) next_state = ADDR1;
else next_state = WRITE_CGRAM;
end
ADDR1: next_state <= WRITE_DDRAM;
WRITE_DDRAM: begin
if(cnt_char == 6'd15) next_state = ADDR2;
else if(cnt_char == 6'd31) next_state = STOP;
else next_state = WRITE_DDRAM;
end
ADDR2: next_state = WRITE_DDRAM;
STOP: next_state = STOP;
default: next_state = IDLE;
endcase
end
//Section3
always @(posedge clk, negedge rstn) begin
if(!rstn) begin
lcd_rs <= 1'b0;
lcd_data <= 8'b00000000;
end
else begin
case(cur_state)
IDLE: begin lcd_rs<=1'b0; lcd_data<=8'b00000000; end
CLEAR_LCD: begin lcd_rs<=1'b0; lcd_data<=8'b00000001; end
SET_DISP_MODE: begin lcd_rs<=1'b0; lcd_data<=8'b00111000; end
DISP_ON: begin lcd_rs<=1'b0; lcd_data<=8'b00001100; end
SHIFT_DOWN: begin lcd_rs<=1'b0; lcd_data<=8'b00000110; end
CGRAM_ADDR: begin lcd_rs<=1'b0; lcd_data<=8'b01000000; end
WRITE_CGRAM: begin lcd_rs<=1'b1; lcd_data<=char_heart[cnt_cgram]; end
ADDR1: begin lcd_rs<=1'b0; lcd_data<=8'b10000000; end
WRITE_DDRAM: begin lcd_rs<=1'b1; lcd_data<=char; end
ADDR2: begin lcd_rs<=1'b0; lcd_data<=8'b11000000; end
STOP: begin lcd_rs<=1'b0; lcd_data<=8'b00111000; end
default: begin lcd_rs<=1'b0; lcd_data<=8'b00111000; end
endcase
end
end
- 综合结果:
顶层模块的综合结果如下图6所示,仅有一lcd1602模块;
![](https://img2023.cnblogs.com/blog/2326690/202211/2326690-20221130230723284-757040626.png)
lcd1602模块的综合结果如下图7所示,无LATCH;
![](https://img2023.cnblogs.com/blog/2326690/202211/2326690-20221130230727617-1469055515.png)
得到的状态转移图如下图8所示,与设计思路相符合;
![](https://img2023.cnblogs.com/blog/2326690/202211/2326690-20221130230732078-1409662297.png)
SUMMARY的结果如下图9所示。
![](https://img2023.cnblogs.com/blog/2326690/202211/2326690-20221130230718794-1543059041.png)
- 显示结果: