RTC时钟实验
本节的实验任务是通过领航者Zynq
开发板上的PCF8563
实时时钟芯片,在 RGB LCD
液晶屏上来显示时间。
PCF8563 简介
PCF8563 是 PHILIPS 公司推出的一款工业级多功能时钟/日历芯片,具有报警功能、定时器功能、时钟输出功能以及中断输出功能,能完成各种复杂的定时服务。其内部功能模块的框图如下图所示:
PCF8563 有 16 个可寻址的 8 位寄存器, 但不是所有位都有用到。前两个寄存器(内存地址 00H、01H)用作控制寄存器和状态寄存器(CONTROL_STATUS);内存地址 02H~08H 用作 TIME 计时器(秒~年计时器); 地址 09H~0CH 用于报警(ALARM)寄存器(定义报警条件); 地址 0DH 控制 CLKOUT 管脚的输出频率;地址 0EH 和 0FH 分别用于定时器控制寄存器和定时器寄存器。 PCF8563 通过 I2C 接口与 Zynq 进行通信
秒、分钟、小时、日、月、年、分钟报警、小时报警、日报警寄存器中的数据编码格式为 BCD
,用的是8421BCD码 ,只有星期和星期报警寄存器中的数据不以 BCD 格式编码。 BCD 码(Binary-Coded Decimal)是一种二进制的数字编码形式,用四个二进制位来表示一位十进制数(0~9) ,能够使二进制和十进制之间的转换得以快捷的进行 。
硬件设计
领航者开发板上 PCF8563 接口部分的原理图如下图所示
PCF8563 作为 I2C 接口的从器件与 EEPROM 等模块统一挂接在领航者开发板上的 IIC 总线上。OSCI、 OSCO 与外部 32.768KHz 的晶振相连, 为芯片提供驱动时钟; SCL 和 SDA 分别是 I2C 总线的串行时钟接口和串行数据接口。
程序设计
根据实验任务,我们可以大致规划出系统的控制流程: ZYNQ 首先通过 I2C 总线向 PCF8563 写入初始时间值,然后不断地读取时间数据,并将读到的时间数据显示到 LCD 上。 由此画出系统的功能框图如下所示:
由系统框图可知, 顶层模块(rtc_lcd)例化了以下三个模块,分别是 IIC 驱动模块(iic_dri) 、PCF8563 控制模块(pcf8563_ctrl)和 LCD 字符显示模块(lcd_disp_char) 。 其中 LCD 字符显示模块例化了读取 ID 模块(rd_id)、时钟分频模块(clk_div) 、 LCD 显示模块(lcd_display)以及 LCD 驱动模块(lcd_driver)
各模块端口及信号连接如图 23.4.2 所示:
PCF8563 实时时钟控制模块(pcf8563_ctrl)通过与 IIC 驱动模块(iic_dri)进行通信来实现对 PCF8563 实时时钟数据的读取; PCF8563 实时时钟控制模块(pcf8563_ctrl)再将从 IIC 读取的时间数据送给 LCD 字符显示模块(lcd_disp_char),以进行显示。
顶层模块
module rtc_lcd( input sys_clk, //系统时钟 input sys_rst_n, //系统复位 //RGB LCD接口 output lcd_de, //LCD 数据使能信号 output lcd_hs, //LCD 行同步信号 output lcd_vs, //LCD 场同步信号 output lcd_bl, //LCD 背光控制信号 output lcd_clk, //LCD 像素时钟 inout [23:0] lcd_rgb, //LCD RGB888颜色数据 //RTC实时时钟 output iic_scl, //RTC的时钟线scl inout iic_sda //RTC的数据线sda ); //parameter define parameter SLAVE_ADDR = 7'b101_0001 ; //器件地址(SLAVE_ADDR) parameter BIT_CTRL = 1'b0 ; //字地址位控制参数(16b/8b) parameter CLK_FREQ = 26'd50_000_000; //i2c_dri模块的驱动时钟频率(CLK_FREQ) parameter I2C_FREQ = 18'd250_000 ; //I2C的SCL时钟频率 parameter TIME_INIT = 48'h19_01_01_09_30_00;//初始时间 //wire define wire dri_clk ; //I2C操作时钟 wire i2c_exec ; //I2C触发控制 wire [15:0] i2c_addr ; //I2C操作地址 wire [ 7:0] i2c_data_w; //I2C写入的数据 wire i2c_done ; //I2C操作结束标志 wire i2c_ack ; //I2C应答标志 0:应答 1:未应答 wire i2c_rh_wl ; //I2C读写控制 wire [ 7:0] i2c_data_r; //I2C读出的数据 wire [7:0] sec ; //秒 wire [7:0] min ; //分 wire [7:0] hour ; //时 wire [7:0] day ; //日 wire [7:0] mon ; //月 wire [7:0] year ; //年 //***************************************************** //** main code //***************************************************** //i2c驱动模块 i2c_dri #( .SLAVE_ADDR (SLAVE_ADDR), //EEPROM从机地址 .CLK_FREQ (CLK_FREQ ), //模块输入的时钟频率 .I2C_FREQ (I2C_FREQ ) //IIC_SCL的时钟频率 ) u_i2c_dri( .clk (sys_clk ), .rst_n (sys_rst_n ), //i2c interface .i2c_exec (i2c_exec ), .bit_ctrl (BIT_CTRL ), .i2c_rh_wl (i2c_rh_wl ), .i2c_addr (i2c_addr ), .i2c_data_w (i2c_data_w), .i2c_data_r (i2c_data_r), .i2c_done (i2c_done ), .i2c_ack (i2c_ack ), .scl (iic_scl ), .sda (iic_sda ), //user interface .dri_clk (dri_clk ) ); //PCF8563控制模块 pcf8563_ctrl #( .TIME_INIT (TIME_INIT) )u_pcf8563_ctrl( .clk (dri_clk ), .rst_n (sys_rst_n ), //IIC .i2c_rh_wl (i2c_rh_wl ), .i2c_exec (i2c_exec ), .i2c_addr (i2c_addr ), .i2c_data_w (i2c_data_w), .i2c_data_r (i2c_data_r), .i2c_done (i2c_done ), //时间和日期 .sec (sec ), .min (min ), .hour (hour ), .day (day ), .mon (mon ), .year (year ) ); //LCD字符显示模块 lcd_disp_char u_lcd_disp_char( .sys_clk (sys_clk ), .sys_rst_n (sys_rst_n ), //时间和日期 .sec (sec ), .min (min ), .hour (hour ), .day (day ), .mon (mon ), .year (year ), //RGB LCD接口 .lcd_de (lcd_de ), .lcd_hs (lcd_hs ), .lcd_vs (lcd_vs ), .lcd_bl (lcd_bl ), .lcd_clk (lcd_clk ), .lcd_rgb (lcd_rgb ) ); endmodule
IIC驱动模块
module i2c_dri
#(
parameter SLAVE_ADDR = 7'b1010000 , //EEPROM从机地址
parameter CLK_FREQ = 26'd50_000_000, //模块输入的时钟频率
parameter I2C_FREQ = 18'd250_000 //IIC_SCL的时钟频率
)
(
input clk ,
input rst_n ,
//i2c interface
input i2c_exec , //I2C触发执行信号
input bit_ctrl , //字地址位控制(16b/8b)
input i2c_rh_wl , //I2C读写控制信号
input [15:0] i2c_addr , //I2C器件内地址
input [ 7:0] i2c_data_w , //I2C要写的数据
output reg [ 7:0] i2c_data_r , //I2C读出的数据
output reg i2c_done , //I2C一次操作完成
output reg i2c_ack , //I2C应答标志 0:应答 1:未应答
output reg scl , //I2C的SCL时钟信号
inout sda , //I2C的SDA信号
//user interface
output reg dri_clk //驱动I2C操作的驱动时钟
);
//localparam define
localparam st_idle = 8'b0000_0001; //空闲状态
localparam st_sladdr = 8'b0000_0010; //发送器件地址(slave address)
localparam st_addr16 = 8'b0000_0100; //发送16位字地址
localparam st_addr8 = 8'b0000_1000; //发送8位字地址
localparam st_data_wr = 8'b0001_0000; //写数据(8 bit)
localparam st_addr_rd = 8'b0010_0000; //发送器件地址读
localparam st_data_rd = 8'b0100_0000; //读数据(8 bit)
localparam st_stop = 8'b1000_0000; //结束I2C操作
//reg define
reg sda_dir ; //I2C数据(SDA)方向控制
reg sda_out ; //SDA输出信号
reg st_done ; //状态结束
reg wr_flag ; //写标志
reg [ 6:0] cnt ; //计数
reg [ 7:0] cur_state ; //状态机当前状态
reg [ 7:0] next_state; //状态机下一状态
reg [15:0] addr_t ; //地址
reg [ 7:0] data_r ; //读取的数据
reg [ 7:0] data_wr_t ; //I2C需写的数据的临时寄存
reg [ 9:0] clk_cnt ; //分频时钟计数
//wire define
wire sda_in ; //SDA输入信号
wire [8:0] clk_divide ; //模块驱动时钟的分频系数
//*****************************************************
//** main code
//*****************************************************
//SDA控制
assign sda = sda_dir ? sda_out : 1'bz; //SDA数据输出或高阻
assign sda_in = sda ; //SDA数据输入
assign clk_divide = (CLK_FREQ/I2C_FREQ) >> 2'd2;//模块驱动时钟的分频系数
//生成I2C的SCL的四倍频率的驱动时钟用于驱动i2c的操作
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
dri_clk <= 1'b0;
clk_cnt <= 10'd0;
end
else if(clk_cnt == clk_divide[8:1] - 1'd1) begin
clk_cnt <= 10'd0;
dri_clk <= ~dri_clk;
end
else
clk_cnt <= clk_cnt + 1'b1;
end
//(三段式状态机)同步时序描述状态转移
always @(posedge dri_clk or negedge rst_n) begin
if(!rst_n)
cur_state <= st_idle;
else
cur_state <= next_state;
end
//组合逻辑判断状态转移条件
always @(*) begin
next_state = st_idle;
case(cur_state)
st_idle: begin //空闲状态
if(i2c_exec) begin
next_state = st_sladdr;
end
else
next_state = st_idle;
end
st_sladdr: begin
if(st_done) begin
if(bit_ctrl) //判断是16位还是8位字地址
next_state = st_addr16;
else
next_state = st_addr8 ;
end
else
next_state = st_sladdr;
end
st_addr16: begin //写16位字地址
if(st_done) begin
next_state = st_addr8;
end
else begin
next_state = st_addr16;
end
end
st_addr8: begin //8位字地址
if(st_done) begin
if(wr_flag==1'b0) //读写判断
next_state = st_data_wr;
else
next_state = st_addr_rd;
end
else begin
next_state = st_addr8;
end
end
st_data_wr: begin //写数据(8 bit)
if(st_done)
next_state = st_stop;
else
next_state = st_data_wr;
end
st_addr_rd: begin //写地址以进行读数据
if(st_done) begin
next_state = st_data_rd;
end
else begin
next_state = st_addr_rd;
end
end
st_data_rd: begin //读取数据(8 bit)
if(st_done)
next_state = st_stop;
else
next_state = st_data_rd;
end
st_stop: begin //结束I2C操作
if(st_done)
next_state = st_idle;
else
next_state = st_stop ;
end
default: next_state= st_idle;
endcase
end
//时序电路描述状态输出
always @(posedge dri_clk or negedge rst_n) begin
//复位初始化
if(!rst_n) begin
scl <= 1'b1;
sda_out <= 1'b1;
sda_dir <= 1'b1;
i2c_done <= 1'b0;
i2c_ack <= 1'b0;
cnt <= 1'b0;
st_done <= 1'b0;
data_r <= 1'b0;
i2c_data_r<= 1'b0;
wr_flag <= 1'b0;
addr_t <= 1'b0;
data_wr_t <= 1'b0;
end
else begin
st_done <= 1'b0 ;
cnt <= cnt +1'b1 ;
case(cur_state)
st_idle: begin //空闲状态
scl <= 1'b1;
sda_out <= 1'b1;
sda_dir <= 1'b1;
i2c_done<= 1'b0;
cnt <= 7'b0;
if(i2c_exec) begin
wr_flag <= i2c_rh_wl ;
addr_t <= i2c_addr ;
data_wr_t <= i2c_data_w;
i2c_ack <= 1'b0;
end
end
st_sladdr: begin //写地址(器件地址和字地址)
case(cnt)
7'd1 : sda_out <= 1'b0; //开始I2C
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= SLAVE_ADDR[6]; //传送器件地址
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= SLAVE_ADDR[5];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= SLAVE_ADDR[4];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= SLAVE_ADDR[3];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= SLAVE_ADDR[2];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= SLAVE_ADDR[1];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= SLAVE_ADDR[0];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: sda_out <= 1'b0; //0:写
7'd33: scl <= 1'b1;
7'd35: scl <= 1'b0;
7'd36: begin
sda_dir <= 1'b0;
sda_out <= 1'b1;
end
7'd37: scl <= 1'b1;
7'd38: begin //从机应答
st_done <= 1'b1;
if(sda_in == 1'b1) //高电平表示未应答
i2c_ack <= 1'b1; //拉高应答标志位
end
7'd39: begin
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
st_addr16: begin
case(cnt)
7'd0 : begin
sda_dir <= 1'b1 ;
sda_out <= addr_t[15]; //传送字地址
end
7'd1 : scl <= 1'b1;
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= addr_t[14];
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= addr_t[13];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= addr_t[12];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= addr_t[11];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= addr_t[10];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= addr_t[9];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= addr_t[8];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: begin
sda_dir <= 1'b0;
sda_out <= 1'b1;
end
7'd33: scl <= 1'b1;
7'd34: begin //从机应答
st_done <= 1'b1;
if(sda_in == 1'b1) //高电平表示未应答
i2c_ack <= 1'b1; //拉高应答标志位
end
7'd35: begin
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
st_addr8: begin
case(cnt)
7'd0: begin
sda_dir <= 1'b1 ;
sda_out <= addr_t[7]; //字地址
end
7'd1 : scl <= 1'b1;
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= addr_t[6];
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= addr_t[5];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= addr_t[4];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= addr_t[3];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= addr_t[2];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= addr_t[1];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= addr_t[0];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: begin
sda_dir <= 1'b0;
sda_out <= 1'b1;
end
7'd33: scl <= 1'b1;
7'd34: begin //从机应答
st_done <= 1'b1;
if(sda_in == 1'b1) //高电平表示未应答
i2c_ack <= 1'b1; //拉高应答标志位
end
7'd35: begin
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
st_data_wr: begin //写数据(8 bit)
case(cnt)
7'd0: begin
sda_out <= data_wr_t[7]; //I2C写8位数据
sda_dir <= 1'b1;
end
7'd1 : scl <= 1'b1;
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= data_wr_t[6];
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= data_wr_t[5];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= data_wr_t[4];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= data_wr_t[3];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= data_wr_t[2];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= data_wr_t[1];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= data_wr_t[0];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: begin
sda_dir <= 1'b0;
sda_out <= 1'b1;
end
7'd33: scl <= 1'b1;
7'd34: begin //从机应答
st_done <= 1'b1;
if(sda_in == 1'b1) //高电平表示未应答
i2c_ack <= 1'b1; //拉高应答标志位
end
7'd35: begin
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
st_addr_rd: begin //写地址以进行读数据
case(cnt)
7'd0 : begin
sda_dir <= 1'b1;
sda_out <= 1'b1;
end
7'd1 : scl <= 1'b1;
7'd2 : sda_out <= 1'b0; //重新开始
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= SLAVE_ADDR[6]; //传送器件地址
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= SLAVE_ADDR[5];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= SLAVE_ADDR[4];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= SLAVE_ADDR[3];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= SLAVE_ADDR[2];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= SLAVE_ADDR[1];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= SLAVE_ADDR[0];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: sda_out <= 1'b1; //1:读
7'd33: scl <= 1'b1;
7'd35: scl <= 1'b0;
7'd36: begin
sda_dir <= 1'b0;
sda_out <= 1'b1;
end
7'd37: scl <= 1'b1;
7'd38: begin //从机应答
st_done <= 1'b1;
if(sda_in == 1'b1) //高电平表示未应答
i2c_ack <= 1'b1; //拉高应答标志位
end
7'd39: begin
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
st_data_rd: begin //读取数据(8 bit)
case(cnt)
7'd0: sda_dir <= 1'b0;
7'd1: begin
data_r[7] <= sda_in;
scl <= 1'b1;
end
7'd3: scl <= 1'b0;
7'd5: begin
data_r[6] <= sda_in ;
scl <= 1'b1 ;
end
7'd7: scl <= 1'b0;
7'd9: begin
data_r[5] <= sda_in;
scl <= 1'b1 ;
end
7'd11: scl <= 1'b0;
7'd13: begin
data_r[4] <= sda_in;
scl <= 1'b1 ;
end
7'd15: scl <= 1'b0;
7'd17: begin
data_r[3] <= sda_in;
scl <= 1'b1 ;
end
7'd19: scl <= 1'b0;
7'd21: begin
data_r[2] <= sda_in;
scl <= 1'b1 ;
end
7'd23: scl <= 1'b0;
7'd25: begin
data_r[1] <= sda_in;
scl <= 1'b1 ;
end
7'd27: scl <= 1'b0;
7'd29: begin
data_r[0] <= sda_in;
scl <= 1'b1 ;
end
7'd31: scl <= 1'b0;
7'd32: begin
sda_dir <= 1'b1;
sda_out <= 1'b1;
end
7'd33: scl <= 1'b1;
7'd34: st_done <= 1'b1; //非应答
7'd35: begin
scl <= 1'b0;
cnt <= 1'b0;
i2c_data_r <= data_r;
end
default : ;
endcase
end
st_stop: begin //结束I2C操作
case(cnt)
7'd0: begin
sda_dir <= 1'b1; //结束I2C
sda_out <= 1'b0;
end
7'd1 : scl <= 1'b1;
7'd3 : sda_out <= 1'b1;
7'd15: st_done <= 1'b1;
7'd16: begin
cnt <= 1'b0;
i2c_done <= 1'b1; //向上层模块传递I2C结束信号
end
default : ;
endcase
end
endcase
end
end
endmodule
实时时钟控制模块
module lcd_disp_char(
input sys_clk ,
input sys_rst_n,
//时间和日期
input [7:0] sec , // 秒
input [7:0] min , // 分
input [7:0] hour , // 时
input [7:0] day , // 日
input [7:0] mon , // 月
input [7:0] year , // 年
//RGB LCD接口
output lcd_hs , //LCD 行同步信号
output lcd_vs , //LCD 场同步信号
output lcd_de , //LCD 数据输入使能
inout [23:0] lcd_rgb , //LCD RGB565颜色数据
output lcd_bl , //LCD 背光控制信号
output lcd_clk //LCD 采样时钟
);
//wire define
wire [15:0] lcd_id ; //LCD屏ID
wire lcd_pclk ; //LCD像素时钟
wire [10:0] pixel_xpos; //当前像素点横坐标
wire [10:0] pixel_ypos; //当前像素点纵坐标
wire [10:0] h_disp ; //LCD屏水平分辨率
wire [10:0] v_disp ; //LCD屏垂直分辨率
wire [23:0] pixel_data; //像素数据
wire [23:0] lcd_rgb_o ; //输出的像素数据
wire [23:0] lcd_rgb_i ; //输入的像素数据
//*****************************************************
//** main code
//*****************************************************
//像素数据方向切换
assign lcd_rgb = lcd_de ? lcd_rgb_o : {24{1'bz}};
assign lcd_rgb_i = lcd_rgb;
//读LCD ID模块
rd_id u_rd_id(
.clk (sys_clk ),
.rst_n (sys_rst_n),
.lcd_rgb (lcd_rgb_i),
.lcd_id (lcd_id )
);
//时钟分频模块
clk_div u_clk_div(
.clk (sys_clk ),
.rst_n (sys_rst_n),
.lcd_id (lcd_id ),
.lcd_pclk (lcd_pclk )
);
//LCD显示模块
lcd_display u_lcd_display(
.lcd_pclk (lcd_pclk ),
.rst_n (sys_rst_n ),
//时间和日期
.sec (sec ),
.min (min ),
.hour (hour ),
.day (day ),
.mon (mon ),
.year (year ),
//像素点坐标
.pixel_xpos (pixel_xpos),
.pixel_ypos (pixel_ypos),
.pixel_data (pixel_data)
);
//LCD驱动模块
lcd_driver u_lcd_driver(
.lcd_pclk (lcd_pclk ),
.rst_n (sys_rst_n ),
.lcd_id (lcd_id ),
.pixel_data (pixel_data),
.pixel_xpos (pixel_xpos),
.pixel_ypos (pixel_ypos),
.h_disp (h_disp ),
.v_disp (v_disp ),
.lcd_de (lcd_de ),
.lcd_hs (lcd_hs ),
.lcd_vs (lcd_vs ),
.lcd_bl (lcd_bl ),
.lcd_clk (lcd_clk ),
.lcd_rgb (lcd_rgb_o )
);
endmodule
RGB LCD显示日期和时间
module lcd_disp_char(
input sys_clk ,
input sys_rst_n,
//时间和日期
input [7:0] sec , // 秒
input [7:0] min , // 分
input [7:0] hour , // 时
input [7:0] day , // 日
input [7:0] mon , // 月
input [7:0] year , // 年
//RGB LCD接口
output lcd_hs , //LCD 行同步信号
output lcd_vs , //LCD 场同步信号
output lcd_de , //LCD 数据输入使能
inout [23:0] lcd_rgb , //LCD RGB565颜色数据
output lcd_bl , //LCD 背光控制信号
output lcd_clk //LCD 采样时钟
);
//wire define
wire [15:0] lcd_id ; //LCD屏ID
wire lcd_pclk ; //LCD像素时钟
wire [10:0] pixel_xpos; //当前像素点横坐标
wire [10:0] pixel_ypos; //当前像素点纵坐标
wire [10:0] h_disp ; //LCD屏水平分辨率
wire [10:0] v_disp ; //LCD屏垂直分辨率
wire [23:0] pixel_data; //像素数据
wire [23:0] lcd_rgb_o ; //输出的像素数据
wire [23:0] lcd_rgb_i ; //输入的像素数据
//*****************************************************
//** main code
//*****************************************************
//像素数据方向切换
assign lcd_rgb = lcd_de ? lcd_rgb_o : {24{1'bz}};
assign lcd_rgb_i = lcd_rgb;
//读LCD ID模块
rd_id u_rd_id(
.clk (sys_clk ),
.rst_n (sys_rst_n),
.lcd_rgb (lcd_rgb_i),
.lcd_id (lcd_id )
);
//时钟分频模块
clk_div u_clk_div(
.clk (sys_clk ),
.rst_n (sys_rst_n),
.lcd_id (lcd_id ),
.lcd_pclk (lcd_pclk )
);
//LCD显示模块
lcd_display u_lcd_display(
.lcd_pclk (lcd_pclk ),
.rst_n (sys_rst_n ),
//时间和日期
.sec (sec ),
.min (min ),
.hour (hour ),
.day (day ),
.mon (mon ),
.year (year ),
//像素点坐标
.pixel_xpos (pixel_xpos),
.pixel_ypos (pixel_ypos),
.pixel_data (pixel_data)
);
//LCD驱动模块
lcd_driver u_lcd_driver(
.lcd_pclk (lcd_pclk ),
.rst_n (sys_rst_n ),
.lcd_id (lcd_id ),
.pixel_data (pixel_data),
.pixel_xpos (pixel_xpos),
.pixel_ypos (pixel_ypos),
.h_disp (h_disp ),
.v_disp (v_disp ),
.lcd_de (lcd_de ),
.lcd_hs (lcd_hs ),
.lcd_vs (lcd_vs ),
.lcd_bl (lcd_bl ),
.lcd_clk (lcd_clk ),
.lcd_rgb (lcd_rgb_o )
);
endmodule
读取LCD ID模块
module rd_id(
input clk , //时钟
input rst_n , //复位,低电平有效
input [23:0] lcd_rgb, //RGB LCD像素数据,用于读取ID
output reg [15:0] lcd_id //LCD屏ID
);
//reg define
reg rd_flag; //读ID标志
//*****************************************************
//** main code
//*****************************************************
//获取LCD ID M2:B7 M1:G7 M0:R7
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
rd_flag <= 1'b0;
lcd_id <= 16'd0;
end
else begin
if(rd_flag == 1'b0) begin
rd_flag <= 1'b1;
case({lcd_rgb[7],lcd_rgb[15],lcd_rgb[23]})
3'b000 : lcd_id <= 16'h4342; //4.3' RGB LCD RES:480x272
3'b001 : lcd_id <= 16'h7084; //7' RGB LCD RES:800x480
3'b010 : lcd_id <= 16'h7016; //7' RGB LCD RES:1024x600
3'b100 : lcd_id <= 16'h4384; //4.3' RGB LCD RES:800x480
3'b101 : lcd_id <= 16'h1018; //10' RGB LCD RES:1280x800
default : lcd_id <= 16'd0;
endcase
end
end
end
endmodule
时钟分频模块
module clk_div(
input clk, //50Mhz
input rst_n,
input [15:0] lcd_id,
output reg lcd_pclk
);
reg clk_25m;
reg clk_12_5m;
reg div_4_cnt;
//时钟2分频 输出25MHz时钟
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
clk_25m <= 1'b0;
else
clk_25m <= ~clk_25m;
end
//时钟4分频 输出12.5MHz时钟
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
div_4_cnt <= 1'b0;
clk_12_5m <= 1'b0;
end
else begin
div_4_cnt <= div_4_cnt + 1'b1;
if(div_4_cnt == 1'b1)
clk_12_5m <= ~clk_12_5m;
end
end
always @(*) begin
case(lcd_id)
16'h4342 : lcd_pclk = clk_12_5m;
16'h7084 : lcd_pclk = clk_25m;
16'h7016 : lcd_pclk = clk;
16'h4384 : lcd_pclk = clk_25m;
16'h1018 : lcd_pclk = clk;
default : lcd_pclk = 1'b0;
endcase
end
endmodule
LCD驱动模块
module lcd_driver(
input lcd_pclk, //时钟
input rst_n, //复位,低电平有效
input [15:0] lcd_id, //LCD屏ID
input [23:0] pixel_data, //像素数据
output [10:0] pixel_xpos, //当前像素点横坐标
output [10:0] pixel_ypos, //当前像素点纵坐标
output reg [10:0] h_disp, //LCD屏水平分辨率
output reg [10:0] v_disp, //LCD屏垂直分辨率
//RGB LCD接口
output lcd_de, //LCD 数据使能信号
output lcd_hs, //LCD 行同步信号
output lcd_vs, //LCD 场同步信号
output lcd_bl, //LCD 背光控制信号
output lcd_clk, //LCD 像素时钟
output [23:0] lcd_rgb //LCD RGB888颜色数据
);
//parameter define
// 4.3' 480*272
parameter H_SYNC_4342 = 11'd41; //行同步
parameter H_BACK_4342 = 11'd2; //行显示后沿
parameter H_DISP_4342 = 11'd480; //行有效数据
parameter H_FRONT_4342 = 11'd2; //行显示前沿
parameter H_TOTAL_4342 = 11'd525; //行扫描周期
parameter V_SYNC_4342 = 11'd10; //场同步
parameter V_BACK_4342 = 11'd2; //场显示后沿
parameter V_DISP_4342 = 11'd272; //场有效数据
parameter V_FRONT_4342 = 11'd2; //场显示前沿
parameter V_TOTAL_4342 = 11'd286; //场扫描周期
// 7' 800*480
parameter H_SYNC_7084 = 11'd128; //行同步
parameter H_BACK_7084 = 11'd88; //行显示后沿
parameter H_DISP_7084 = 11'd800; //行有效数据
parameter H_FRONT_7084 = 11'd40; //行显示前沿
parameter H_TOTAL_7084 = 11'd1056; //行扫描周期
parameter V_SYNC_7084 = 11'd2; //场同步
parameter V_BACK_7084 = 11'd33; //场显示后沿
parameter V_DISP_7084 = 11'd480; //场有效数据
parameter V_FRONT_7084 = 11'd10; //场显示前沿
parameter V_TOTAL_7084 = 11'd525; //场扫描周期
// 7' 1024*600
parameter H_SYNC_7016 = 11'd20; //行同步
parameter H_BACK_7016 = 11'd140; //行显示后沿
parameter H_DISP_7016 = 11'd1024; //行有效数据
parameter H_FRONT_7016 = 11'd160; //行显示前沿
parameter H_TOTAL_7016 = 11'd1344; //行扫描周期
parameter V_SYNC_7016 = 11'd3; //场同步
parameter V_BACK_7016 = 11'd20; //场显示后沿
parameter V_DISP_7016 = 11'd600; //场有效数据
parameter V_FRONT_7016 = 11'd12; //场显示前沿
parameter V_TOTAL_7016 = 11'd635; //场扫描周期
// 10.1' 1280*800
parameter H_SYNC_1018 = 11'd10; //行同步
parameter H_BACK_1018 = 11'd80; //行显示后沿
parameter H_DISP_1018 = 11'd1280; //行有效数据
parameter H_FRONT_1018 = 11'd70; //行显示前沿
parameter H_TOTAL_1018 = 11'd1440; //行扫描周期
parameter V_SYNC_1018 = 11'd3; //场同步
parameter V_BACK_1018 = 11'd10; //场显示后沿
parameter V_DISP_1018 = 11'd800; //场有效数据
parameter V_FRONT_1018 = 11'd10; //场显示前沿
parameter V_TOTAL_1018 = 11'd823; //场扫描周期
// 4.3' 800*480
parameter H_SYNC_4384 = 11'd128; //行同步
parameter H_BACK_4384 = 11'd88; //行显示后沿
parameter H_DISP_4384 = 11'd800; //行有效数据
parameter H_FRONT_4384 = 11'd40; //行显示前沿
parameter H_TOTAL_4384 = 11'd1056; //行扫描周期
parameter V_SYNC_4384 = 11'd2; //场同步
parameter V_BACK_4384 = 11'd33; //场显示后沿
parameter V_DISP_4384 = 11'd480; //场有效数据
parameter V_FRONT_4384 = 11'd10; //场显示前沿
parameter V_TOTAL_4384 = 11'd525; //场扫描周期
//reg define
reg [10:0] h_sync ;
reg [10:0] h_back ;
reg [10:0] h_total;
reg [10:0] v_sync ;
reg [10:0] v_back ;
reg [10:0] v_total;
reg [10:0] h_cnt ;
reg [10:0] v_cnt ;
//wire define
wire lcd_en;
wire data_req;
//*****************************************************
//** main code
//*****************************************************
//RGB LCD 采用DE模式时,行场同步信号需要拉高
assign lcd_hs = 1'b1; //LCD行同步信号
assign lcd_vs = 1'b1; //LCD场同步信号
assign lcd_bl = 1'b1; //LCD背光控制信号
assign lcd_clk = lcd_pclk; //LCD像素时钟
assign lcd_de = lcd_en; //LCD数据有效信号
//使能RGB888数据输出
assign lcd_en = ((h_cnt >= h_sync + h_back) && (h_cnt < h_sync + h_back + h_disp)
&& (v_cnt >= v_sync + v_back) && (v_cnt < v_sync + v_back + v_disp))
? 1'b1 : 1'b0;
//请求像素点颜色数据输入
assign data_req = ((h_cnt >= h_sync + h_back - 1'b1) && (h_cnt < h_sync + h_back + h_disp - 1'b1)
&& (v_cnt >= v_sync + v_back) && (v_cnt < v_sync + v_back + v_disp))
? 1'b1 : 1'b0;
//像素点坐标
assign pixel_xpos = data_req ? (h_cnt - (h_sync + h_back - 1'b1)) : 11'd0;
assign pixel_ypos = data_req ? (v_cnt - (v_sync + v_back - 1'b1)) : 11'd0;
//RGB888数据输出
assign lcd_rgb = lcd_en ? pixel_data : 24'd0;
//行场时序参数
always @(*) begin
case(lcd_id)
16'h4342 : begin
h_sync = H_SYNC_4342;
h_back = H_BACK_4342;
h_disp = H_DISP_4342;
h_total = H_TOTAL_4342;
v_sync = V_SYNC_4342;
v_back = V_BACK_4342;
v_disp = V_DISP_4342;
v_total = V_TOTAL_4342;
end
16'h7084 : begin
h_sync = H_SYNC_7084;
h_back = H_BACK_7084;
h_disp = H_DISP_7084;
h_total = H_TOTAL_7084;
v_sync = V_SYNC_7084;
v_back = V_BACK_7084;
v_disp = V_DISP_7084;
v_total = V_TOTAL_7084;
end
16'h7016 : begin
h_sync = H_SYNC_7016;
h_back = H_BACK_7016;
h_disp = H_DISP_7016;
h_total = H_TOTAL_7016;
v_sync = V_SYNC_7016;
v_back = V_BACK_7016;
v_disp = V_DISP_7016;
v_total = V_TOTAL_7016;
end
16'h4384 : begin
h_sync = H_SYNC_4384;
h_back = H_BACK_4384;
h_disp = H_DISP_4384;
h_total = H_TOTAL_4384;
v_sync = V_SYNC_4384;
v_back = V_BACK_4384;
v_disp = V_DISP_4384;
v_total = V_TOTAL_4384;
end
16'h1018 : begin
h_sync = H_SYNC_1018;
h_back = H_BACK_1018;
h_disp = H_DISP_1018;
h_total = H_TOTAL_1018;
v_sync = V_SYNC_1018;
v_back = V_BACK_1018;
v_disp = V_DISP_1018;
v_total = V_TOTAL_1018;
end
default : begin
h_sync = H_SYNC_4342;
h_back = H_BACK_4342;
h_disp = H_DISP_4342;
h_total = H_TOTAL_4342;
v_sync = V_SYNC_4342;
v_back = V_BACK_4342;
v_disp = V_DISP_4342;
v_total = V_TOTAL_4342;
end
endcase
end
//行计数器对像素时钟计数
always@ (posedge lcd_pclk or negedge rst_n) begin
if(!rst_n)
h_cnt <= 11'd0;
else begin
if(h_cnt == h_total - 1'b1)
h_cnt <= 11'd0;
else
h_cnt <= h_cnt + 1'b1;
end
end
//场计数器对行计数
always@ (posedge lcd_pclk or negedge rst_n) begin
if(!rst_n)
v_cnt <= 11'd0;
else begin
if(h_cnt == h_total - 1'b1) begin
if(v_cnt == v_total - 1'b1)
v_cnt <= 11'd0;
else
v_cnt <= v_cnt + 1'b1;
end
end
end
endmodule
RGB LCD显示模块
module lcd_display(
input lcd_pclk ,
input rst_n ,
//日历数据
input [7:0] sec, //秒
input [7:0] min, //分
input [7:0] hour, //时
input [7:0] day, //日
input [7:0] mon, //月
input [7:0] year, //年
//LCD数据接口
input [10:0] pixel_xpos, //像素点横坐标
input [10:0] pixel_ypos, //像素点纵坐标
output reg [23:0] pixel_data //像素点数据
);
//parameter define
localparam CHAR_POS_X_1 = 11'd1; //第1行字符区域起始点横坐标
localparam CHAR_POS_Y_1 = 11'd1; //第1行字符区域起始点纵坐标
localparam CHAR_POS_X_2 = 11'd17; //第2行字符区域起始点横坐标
localparam CHAR_POS_Y_2 = 11'd17; //第2行字符区域起始点纵坐标
localparam CHAR_WIDTH_1 = 11'd80; //第1行字符区域的宽度,第1行共10个字符(加空格)
localparam CHAR_WIDTH_2 = 11'd64; //第2行字符区域的宽度,第2行共8个字符(加空格)
localparam CHAR_HEIGHT = 11'd16; //单个字符的高度
localparam WHITE = 24'hffffff; //背景色,白色
localparam BLACK = 24'h000000; //字符颜色,黑色
//reg define
reg [127:0] char [9:0] ; //字符数组
//*****************************************************
//** main code
//*****************************************************
//字符数组初始值,用于存储字模数据(由取模软件生成,单个数字字体大小:16*16)
always @(posedge lcd_pclk ) begin
char[0] <= 128'h00000018244242424242424224180000 ; // "0"
char[1] <= 128'h000000107010101010101010107C0000 ; // "1"
char[2] <= 128'h0000003C4242420404081020427E0000 ; // "2"
char[3] <= 128'h0000003C424204180402024244380000 ; // "3"
char[4] <= 128'h000000040C14242444447E04041E0000 ; // "4"
char[5] <= 128'h0000007E404040586402024244380000 ; // "5"
char[6] <= 128'h0000001C244040586442424224180000 ; // "6"
char[7] <= 128'h0000007E444408081010101010100000 ; // "7"
char[8] <= 128'h0000003C4242422418244242423C0000 ; // "8"
char[9] <= 128'h0000001824424242261A020224380000 ; // "9"
end
//不同的区域绘制不同的像素数据
always @(posedge lcd_pclk or negedge rst_n ) begin
if (!rst_n) begin
pixel_data <= BLACK;
end
//在第一行显示年的千位 固定值"2"
else if( (pixel_xpos >= CHAR_POS_X_1)
&& (pixel_xpos < CHAR_POS_X_1 + CHAR_WIDTH_1/10*1)
&& (pixel_ypos >= CHAR_POS_Y_1)
&& (pixel_ypos < CHAR_POS_Y_1 + CHAR_HEIGHT) ) begin
if(char [2] [ (CHAR_HEIGHT+CHAR_POS_Y_1 - pixel_ypos)*8
- ((pixel_xpos-CHAR_POS_X_1)%8) -1 ] )
pixel_data <= BLACK; //显示字符为黑色
else
pixel_data <= WHITE; //显示字符区域背景为白色
end
//在第一行显示年的百位 固定值"0"
else if( (pixel_xpos >= CHAR_POS_X_1 + CHAR_WIDTH_1/10*1)
&& (pixel_xpos < CHAR_POS_X_1 + CHAR_WIDTH_1/10*2)
&& (pixel_ypos >= CHAR_POS_Y_1)
&& (pixel_ypos < CHAR_POS_Y_1 + CHAR_HEIGHT) ) begin
if(char [0] [ (CHAR_HEIGHT+CHAR_POS_Y_1 - pixel_ypos)*8
- ((pixel_xpos-CHAR_POS_X_1)%8) -1 ] )
pixel_data <= BLACK;
else
pixel_data <= WHITE;
end
//在第一行显示年的十位
else if( (pixel_xpos >= CHAR_POS_X_1 + CHAR_WIDTH_1/10*2)
&& (pixel_xpos < CHAR_POS_X_1 + CHAR_WIDTH_1/10*3)
&& (pixel_ypos >= CHAR_POS_Y_1)
&& (pixel_ypos < CHAR_POS_Y_1 + CHAR_HEIGHT) ) begin
if(char [year[7:4]] [ (CHAR_HEIGHT+CHAR_POS_Y_1 - pixel_ypos)*8
- ((pixel_xpos-CHAR_POS_X_1)%8) -1 ] )
pixel_data <= BLACK;
else
pixel_data <= WHITE;
end
//在第一行显示年的个位
else if( (pixel_xpos >= CHAR_POS_X_1 + CHAR_WIDTH_1/10*3)
&& (pixel_xpos < CHAR_POS_X_1 + CHAR_WIDTH_1/10*4)
&& (pixel_ypos >= CHAR_POS_Y_1)
&& (pixel_ypos < CHAR_POS_Y_1 + CHAR_HEIGHT) ) begin
if(char [year[3:0]] [ (CHAR_HEIGHT+CHAR_POS_Y_1 - pixel_ypos)*8
- ((pixel_xpos-CHAR_POS_X_1)%8) -1 ] )
pixel_data <= BLACK;
else
pixel_data <= WHITE;
end
//在第一行显示空格
else if( (pixel_xpos >= CHAR_POS_X_1 + CHAR_WIDTH_1/10*4)
&& (pixel_xpos < CHAR_POS_X_1 + CHAR_WIDTH_1/10*5)
&& (pixel_ypos >= CHAR_POS_Y_1)
&& (pixel_ypos < CHAR_POS_Y_1 + CHAR_HEIGHT) ) begin
pixel_data <= WHITE;
end
//在第一行显示月的十位
else if( (pixel_xpos >= CHAR_POS_X_1 + CHAR_WIDTH_1/10*5)
&& (pixel_xpos < CHAR_POS_X_1 + CHAR_WIDTH_1/10*6)
&& (pixel_ypos >= CHAR_POS_Y_1)
&& (pixel_ypos < CHAR_POS_Y_1 + CHAR_HEIGHT)) begin
if(char [mon[7:4]] [ (CHAR_HEIGHT+CHAR_POS_Y_1 - pixel_ypos)*8
- ((pixel_xpos-CHAR_POS_X_1)%8) -1 ] )
pixel_data <= BLACK;
else
pixel_data <= WHITE;
end
//在第一行显示月的个位
else if( (pixel_xpos >= CHAR_POS_X_1 + CHAR_WIDTH_1/10*6)
&& (pixel_xpos < CHAR_POS_X_1 + CHAR_WIDTH_1/10*7)
&& (pixel_ypos >= CHAR_POS_Y_1)
&& (pixel_ypos < CHAR_POS_Y_1 + CHAR_HEIGHT) ) begin
if(char [mon[3:0]] [ (CHAR_HEIGHT+CHAR_POS_Y_1 - pixel_ypos)*8
- ((pixel_xpos-CHAR_POS_X_1)%8) -1 ] )
pixel_data <= BLACK;
else
pixel_data <= WHITE;
end
//在第一行显示空格
else if( (pixel_xpos >= CHAR_POS_X_1 + CHAR_WIDTH_1/10*7)
&& (pixel_xpos < CHAR_POS_X_1 + CHAR_WIDTH_1/10*8)
&& (pixel_ypos >= CHAR_POS_Y_1)
&& (pixel_ypos < CHAR_POS_Y_1 + CHAR_HEIGHT) ) begin
pixel_data <= WHITE;
end
//在第一行显示日的十位
else if( (pixel_xpos >= CHAR_POS_X_1 + CHAR_WIDTH_1/10*8)
&& (pixel_xpos < CHAR_POS_X_1 + CHAR_WIDTH_1/10*9)
&& (pixel_ypos >= CHAR_POS_Y_1)
&& (pixel_ypos < CHAR_POS_Y_1 + CHAR_HEIGHT) ) begin
if(char [day[7:4]] [ (CHAR_HEIGHT+CHAR_POS_Y_1 - pixel_ypos)*8
- ((pixel_xpos-CHAR_POS_X_1)%8) -1 ] )
pixel_data <= BLACK;
else
pixel_data <= WHITE;
end
//在第一行显示日的个位
else if( (pixel_xpos >= CHAR_POS_X_1 + CHAR_WIDTH_1/10*9)
&& (pixel_xpos < CHAR_POS_X_1 + CHAR_WIDTH_1)
&& (pixel_ypos >= CHAR_POS_Y_1)
&& (pixel_ypos < CHAR_POS_Y_1 + CHAR_HEIGHT) ) begin
if(char [day[3:0]] [ (CHAR_HEIGHT+CHAR_POS_Y_1 - pixel_ypos)*8
- ((pixel_xpos-CHAR_POS_X_1)%8) -1 ] )
pixel_data <= BLACK;
else
pixel_data <= WHITE;
end
//在第二行显示时的十位
else if( (pixel_xpos >= CHAR_POS_X_2)
&& (pixel_xpos < CHAR_POS_X_2 + CHAR_WIDTH_2/8*1)
&& (pixel_ypos >= CHAR_POS_Y_2)
&& (pixel_ypos < CHAR_POS_Y_2 + CHAR_HEIGHT) ) begin
if(char [hour[7:4]] [ (CHAR_HEIGHT+CHAR_POS_Y_2 - pixel_ypos)*8
- ((pixel_xpos-CHAR_POS_X_2)%8) -1 ] )
pixel_data <= BLACK;
else
pixel_data <= WHITE;
end
//在第二行显示时的个位
else if( (pixel_xpos >= CHAR_POS_X_2 + CHAR_WIDTH_2/8*1)
&& (pixel_xpos < CHAR_POS_X_2 + CHAR_WIDTH_2/8*2)
&& (pixel_ypos >= CHAR_POS_Y_2)
&& (pixel_ypos < CHAR_POS_Y_2 + CHAR_HEIGHT) ) begin
if(char [hour[3:0]] [ (CHAR_HEIGHT+CHAR_POS_Y_2 - pixel_ypos)*8
- ((pixel_xpos-CHAR_POS_X_2)%8) -1 ] )
pixel_data <= BLACK;
else
pixel_data <= WHITE;
end
//在第二行显示空格
else if( (pixel_xpos >= CHAR_POS_X_2 + CHAR_WIDTH_2/8*2)
&& (pixel_xpos < CHAR_POS_X_2 + CHAR_WIDTH_2/8*3)
&& (pixel_ypos >= CHAR_POS_Y_2)
&& (pixel_ypos < CHAR_POS_Y_2 + CHAR_HEIGHT) ) begin
pixel_data <= WHITE;
end
//在第二行显示分的十位
else if( (pixel_xpos >= CHAR_POS_X_2 + CHAR_WIDTH_2/8*3)
&& (pixel_xpos < CHAR_POS_X_2 + CHAR_WIDTH_2/8*4)
&& (pixel_ypos >= CHAR_POS_Y_2)
&& (pixel_ypos < CHAR_POS_Y_2 + CHAR_HEIGHT) ) begin
if(char [min[7:4]] [ (CHAR_HEIGHT+CHAR_POS_Y_2 - pixel_ypos)*8
- ((pixel_xpos-CHAR_POS_X_2)%8) -1 ] )
pixel_data <= BLACK;
else
pixel_data <= WHITE;
end
//在第二行显示分的个位
else if( (pixel_xpos >= CHAR_POS_X_2 + CHAR_WIDTH_2/8*4)
&& (pixel_xpos < CHAR_POS_X_2 + CHAR_WIDTH_2/8*5)
&& (pixel_ypos >= CHAR_POS_Y_2)
&& (pixel_ypos < CHAR_POS_Y_2 + CHAR_HEIGHT) ) begin
if(char [min[3:0]] [ (CHAR_HEIGHT+CHAR_POS_Y_2 - pixel_ypos)*8
- ((pixel_xpos-CHAR_POS_X_2)%8) -1 ] )
pixel_data <= BLACK;
else
pixel_data <= WHITE;
end
//在第二行显示空格
else if( (pixel_xpos >= CHAR_POS_X_2 + CHAR_WIDTH_2/8*5)
&& (pixel_xpos < CHAR_POS_X_2 + CHAR_WIDTH_2/8*6)
&& (pixel_ypos >= CHAR_POS_Y_2)
&& (pixel_ypos < CHAR_POS_Y_2 + CHAR_HEIGHT) ) begin
pixel_data <= WHITE;
end
//在第二行显示秒的十位
else if( (pixel_xpos >= CHAR_POS_X_2 + CHAR_WIDTH_2/8*6)
&& (pixel_xpos < CHAR_POS_X_2 + CHAR_WIDTH_2/8*7)
&& (pixel_ypos >= CHAR_POS_Y_2)
&& (pixel_ypos < CHAR_POS_Y_2 + CHAR_HEIGHT) ) begin
if(char [sec[7:4]] [ (CHAR_HEIGHT+CHAR_POS_Y_2 - pixel_ypos)*8
- ((pixel_xpos-CHAR_POS_X_2)%8) -1 ] )
pixel_data <= BLACK;
else
pixel_data <= WHITE;
end
//在第二行显示秒的个位
else if( (pixel_xpos >= CHAR_POS_X_2 + CHAR_WIDTH_2/8*7)
&& (pixel_xpos < CHAR_POS_X_2 + CHAR_WIDTH_2)
&& (pixel_ypos >= CHAR_POS_Y_2)
&& (pixel_ypos < CHAR_POS_Y_2 + CHAR_HEIGHT) ) begin
if(char [sec[3:0]] [ (CHAR_HEIGHT+CHAR_POS_Y_2 - pixel_ypos)*8
- ((pixel_xpos-CHAR_POS_X_2)%8) -1 ] )
pixel_data <= BLACK;
else
pixel_data <= WHITE;
end
else begin
pixel_data <= WHITE; //屏幕背景为白色
end
end
endmodule
数字时钟显示改进
在RGB LCD显示模块中,我们是在lcd屏幕的左上角显示数字时钟,并且设置的数字大小是 8*16的大小,这个大小在显示屏上太小了(我用的是800x480分辨率的lcd),位置也很别扭。所以对其进行了一定的修改:
. 数字大小改为16*32
. 位置居中
原来的代码如下
//parameter define
localparam CHAR_POS_X_1 = 11'd1; //第1行字符区域起始点横坐标
localparam CHAR_POS_Y_1 = 11'd1; //第1行字符区域起始点纵坐标
localparam CHAR_POS_X_2 = 11'd17; //第2行字符区域起始点横坐标
localparam CHAR_POS_Y_2 = 11'd17; //第2行字符区域起始点纵坐标
localparam CHAR_WIDTH_1 = 11'd80; //第1行字符区域的宽度,第1行共10个字符(加空格)
localparam CHAR_WIDTH_2 = 11'd64; //第2行字符区域的宽度,第2行共8个字符(加空格)
localparam CHAR_HEIGHT = 11'd16; //单个字符的高度
localparam WHITE = 24'hffffff; //背景色,白色
localparam BLACK = 24'h000000; //字符颜色,黑色
//reg define
reg [127:0] char [9:0] ; //字符数组
//不同的区域绘制不同的像素数据
always @(posedge lcd_pclk or negedge rst_n ) begin
if (!rst_n) begin
pixel_data <= BLACK;
end
//在第一行显示年的千位 固定值"2"
else if( (pixel_xpos >= CHAR_POS_X_1)
&& (pixel_xpos < CHAR_POS_X_1 + CHAR_WIDTH_1/10*1)
&& (pixel_ypos >= CHAR_POS_Y_1)
&& (pixel_ypos < CHAR_POS_Y_1 + CHAR_HEIGHT) ) begin
if(char [2] [ (CHAR_HEIGHT+CHAR_POS_Y_1 - pixel_ypos)*8
- ((pixel_xpos-CHAR_POS_X_1)%8) -1 ] )
pixel_data <= BLACK; //显示字符为黑色
else
pixel_data <= WHITE; //显示字符区域背景为白色
end
因为数字大小是8*16=128,因此数组元素的位宽是 128 位。
CHAR_WIDTH_1 = 11’d80 即8*10 ,第1行字符区域的宽度,第1行共10个字符(加空格)
生成的字模为:
//字符数组初始值,用于存储字模数据(由取模软件生成,单个数字字体大小:16*16)
always @(posedge lcd_pclk ) begin
char[0] <= 128'h00000018244242424242424224180000 ; // "0"
char[1] <= 128'h000000107010101010101010107C0000 ; // "1"
char[2] <= 128'h0000003C4242420404081020427E0000 ; // "2"
char[3] <= 128'h0000003C424204180402024244380000 ; // "3"
char[4] <= 128'h000000040C14242444447E04041E0000 ; // "4"
char[5] <= 128'h0000007E404040586402024244380000 ; // "5"
char[6] <= 128'h0000001C244040586442424224180000 ; // "6"
char[7] <= 128'h0000007E444408081010101010100000 ; // "7"
char[8] <= 128'h0000003C4242422418244242423C0000 ; // "8"
char[9] <= 128'h0000001824424242261A020224380000 ; // "9"
end
修改后的代码如下:
//parameter define
localparam CHAR_POS_X_1 = 11'd320; //第1行字符区域起始点横坐标
localparam CHAR_POS_Y_1 = 11'd176; //第1行字符区域起始点纵坐标
localparam CHAR_POS_X_2 = 11'd352; //第2行字符区域起始点横坐标
localparam CHAR_POS_Y_2 = 11'd214; //第2行字符区域起始点纵坐标
localparam CHAR_WIDTH_1 = 11'd160; //第1行字符区域的宽度,第1行共10个字符(加空格)
localparam CHAR_WIDTH_2 = 11'd128; //第2行字符区域的宽度,第2行共8个字符(加空格)
localparam CHAR_HEIGHT = 11'd32; //单个字符的高度
localparam WHITE = 24'hffffff; //背景色,白色
localparam BLACK = 24'h000000; //字符颜色,黑色
//reg define
reg [511:0] char [9:0] ; //字符数组 因为字体大小是 32*16=512
//不同的区域绘制不同的像素数据
always @(posedge lcd_pclk or negedge rst_n ) begin
if (!rst_n) begin
pixel_data <= BLACK;
end
//在第一行显示年的千位 固定值"2"
else if( (pixel_xpos >= CHAR_POS_X_1)
&& (pixel_xpos < CHAR_POS_X_1 + CHAR_WIDTH_1/10*1)
&& (pixel_ypos >= CHAR_POS_Y_1)
&& (pixel_ypos < CHAR_POS_Y_1 + CHAR_HEIGHT) ) begin
if(char [2] [ (CHAR_HEIGHT+CHAR_POS_Y_1 - pixel_ypos)*16
- ((pixel_xpos-CHAR_POS_X_1)%16) -1 ] )
pixel_data <= BLACK; //显示字符为黑色
else
pixel_data <= WHITE; //显示字符区域背景为白色
end
生成的字模为:
需要注意的是:
对于 代码 “if(char [2] [ (CHAR_HEIGHT+CHAR_POS_Y_1 - pixel_ypos)*16
-((pixel_xpos-CHAR_POS_X_1)%16) -1 ] ) ”,
是将原来的8改成16。我们不难理解“16”的由来,因为在查找数组元素的时候, pixel_ypos 的每次变化代表换到下一行扫描, 一行跨过 16 个数据,所有乘以 16。这里总结一下:字符数组一行的 512 个数据从高位到低位,每 16 位代表另一行,分别对应点阵中该行从左向右的每一个像素点 。
如果不改的话会乱码如下所示:
修改最后显示结果如图: