尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

FPGA实战:LCMXO2驱动Nokia5110实现数字时钟

FPGA实战:LCMXO2驱动Nokia5110实现数字时钟 简介采用莱迪思小脚丫LCMXO2开发板与诺基亚5110液晶屏实现的数字时钟设计是一份适合FPGA初学者、电子竞赛备赛者和数字逻辑课程实践的完整工程包能解决在LCMXO2平台上驱动液晶屏、完成时分秒显示、按键调时等常见问题也可作为课程设计与竞赛模板。压缩包共包含71个文件大小约1.32MB核心由Verilog源码、原理图PDF、设计文档以及工程配置文件构成源码按功能拆分为时钟分频、按键消抖、液晶驱动和顶层连接等模块工程内还包括约束文件、综合报告、位流文件和构建日志可打开工程后直接从综合、布局布线并下载到小脚丫开发板运行。目前已有383人学习下载目录结构清晰各模块独立性强。配合原理图与说明文档阅读既能理清开发板与液晶屏之间的引脚对应关系也能借鉴其状态机与模块划分思路为进一步扩展闹钟、温度显示或菜单交互等设计提供了清晰的可复用基础与二次开发空间。1. 小脚丫LCMXO2驱动Nokia5110做数字时钟到底在练什么把小脚丫LCMXO2开发板和一块Nokia 5110屏接起来做数字时钟看着是个“花活”实际上把FPGA开发里最常踩的一整条链路都走了一遍板载晶振分频成1Hz脉冲、BCD计数器维护时分秒、按键消抖校时、SPI时序驱动PCD8544控制器、字库点阵存储最后还要在Lattice Diamond里做引脚约束并烧进片上Flash让它上电自启动。MCU方案里几行库函数就完成的工作换到Verilog之后每一项都得自己用寄存器搭出来。这个项目很适合理清“硬件描述语言到底在描述什么”的阶段去练手也顺便验证一块开发板的外部晶振、IO电平、下载链路是否都正常。2. 先看原理图小脚丫LCMXO2资源和Nokia5110接口分析LCMXO2和Nokia5110都不是新东西但各自的特性决定了这个项目的电路接法和约束写法。先把两者接口对齐再去看PDF原理图后面写Verilog才不会返工。2.1 小脚丫LCMXO2上数字时钟用到的硬件资源小脚丫LCMXO2开发板的核心是Lattice MachXO2系列里的LCMXO2-1200ZE属于Flash-based FPGA。这意味着配置数据可以烧录到芯片内部的Flash里上电直接从Flash加载不需要像Xilinx或Intel的SRAM型FPGA那样外挂SPI Flash配置芯片。放在数字时钟这种需要“上电就跑”的场景里非常合适断电再上电不用重新下载。LCMXO2-1200ZE的逻辑资源并不算多大概千余个LUT4和对应的寄存器块RAM也只有几十Kbit级别但驱动一块84x48分辨率的单色液晶屏绰绰有余。Nokia5110的显存是504字节字库存几个ASCII字符也就一二百字节显示逻辑全部用寄存器也能放得下。真正要注意的不是资源够不够而是时钟从哪里来。板上通常有一颗外部晶振频率常见12MHz或者25MHz原理图上丝印会写明具体数值。数字时钟的基准就是这颗晶振分频参数必须和它对应。MachXO2内部还有一个可配置频率的OSCH振荡器可以省掉外部晶振但内部振荡器的频率精度和温漂都不如外部晶振做时钟不建议用否则一天下来误差会相当可观。2.2 PCD8544接口分析SPI模式0和命令时序Nokia5110的驱动芯片是PCD8544接口本质是SPI从机但比普通SPI设备多了两根控制线。一共六根信号线除了电源和背光之外分别是信号方向作用RST输入复位低电平有效最少保持100ns低电平SCE输入片选低电平有效整个传输过程必须保持低D/C输入低电平写命令高电平写显示数据DIN输入MOSI数据线MSB先行SCLK输入串行时钟上升沿采样数据PCD8544要求时钟空闲时保持低电平数据在SCLK上升沿被采样这就是标准的SPI Mode 0即CPOL0、CPHA0。最大SCLK频率是4MHzFPGA侧必须做个分频不能直接把12MHz或25MHz时钟送给屏幕。上电之后不能直接写显示数据要先发一串初始化命令。关键命令序列是命令字节含义0x21进入扩展指令集0xB8设置VOP对比度范围0x80到0xBF0x04设置温度系数0x14设置偏置电压0x20退回标准指令集0x0C正常显示模式0x08全灭0x09全亮初始化完成之后D/C拉高写入的每个字节都直接映射到显存。PCD8544的显存按列寻址逻辑上分成6行每行8个像素高列坐标从0到83。写满84乘48的屏幕需要连续发送504个数据字节发完自然换行排列。2.3 从PDF原理图提取引脚连接整理引脚约束表拿到别人整理的资料时第一步不是打开Verilog源码而是打开PDF原理图对照开发板的引脚命名。小脚丫LCMXO2的板级引脚定义在Lattice Diamond里体现为芯片封装上的物理引脚名比如某个信号在原理图网络标号叫LCD_DIN对应到芯片的SITE就是类似P23这样的封装位置。常见的接法是把LCD六根控制线分配到FPGA的普通IO上按键分配到一个带独立网络名的IO上时钟和复位也都单独引出。参考接法如下Nokia5110信号FPGA网络名示例约束写法示例RSTlcd_rstLOCATE COMP lcd_rst SITE P20;SCElcd_sceLOCATE COMP lcd_sce SITE P21;D/Clcd_dcLOCATE COMP lcd_dc SITE P22;DINlcd_dinLOCATE COMP lcd_din SITE P23;SCLKlcd_sclkLOCATE COMP lcd_sclk SITE P24;注意上表引脚只是示例实际必须以自己手头开发板的PDF原理图为准。尤其要确认板上哪些引脚已经被LED、按键、扩展排针之外的器件占用比如LCMXO2的JTAG引脚在下载时会被占用如果屏幕信号恰好接到这些脚上下载后可能出现屏幕花屏或者完全无响应的问题。3. 数字时钟的计时Verilog分频器、BCD计数器和按键消抖计时逻辑是整个数字时钟的骨架。LCMXO2上要做到秒级准确核心是把板载高频时钟降成1Hz脉冲再让BCD计数器消费这些脉冲。这里三个模块缺一不可分频器、BCD计数器、按键消抖。3.1 参数化Verilog分频器把12MHz变成1Hz分频不能直接生成50%占空比的1Hz方波那会浪费大量寄存器。更常见的做法是生成一个单周期高电平的tick信号每一个tick代表一秒。后续计数器只在tick_1hz为高的那个时钟周期里做一次累加。module clk_div #( parameter CLK_HZ 12_000_000 )( input wire clk, input wire rst_n, output reg tick_1hz ); localparam CNT_MAX CLK_HZ - 1; reg [23:0] counter; always (posedge clk or negedge rst_n) begin if (!rst_n) begin counter d0; tick_1hz 1b0; end else if (counter CNT_MAX) begin counter d0; tick_1hz 1b1; // 只拉高一个时钟周期 end else begin counter counter 1b1; tick_1hz 1b0; end end endmoduleCLK_HZ是个可综合的参数如果板载晶振是25MHz就把参数改成25_000_000模块本身不用动。CNT_MAX是分频上限计数到CLK_HZ - 1回零并输出一个高电平脉冲这样每秒钟正好产生一个tick_1hz。这里的实现刻意让tick_1hz在一个时钟周期内拉高而不是一直保持到下一秒。原因在于后面接的BCD计数器要把这个信号当作使能而不是时钟使用always (posedge tick_1hz)这种写法在多级计数器中会产生隐式多时钟域时序收敛困难也容易在综合时产生警告。3.2 BCD计数器的进位链和verilog计数器实现数字时钟显示用BCD格式最方便因为每位数字正好对应四个二进制位送到字库模块直接作为索引。秒和分都是0到59时是0到23。下面给出秒计数器的核心逻辑分和时的结构完全一致只是进制不同。module bcd_counter_60 ( input wire clk, input wire rst_n, input wire tick_in, input wire load_en, // 校时写入使能 input wire [7:0] load_value, // 校时写入值高四位十位低四位个位 output reg [3:0] unit, output reg [3:0] ten, output reg carry ); always (posedge clk or negedge rst_n) begin if (!rst_n) begin unit 4d0; ten 4d0; carry 1b0; end else if (load_en) begin unit load_value[3:0]; ten load_value[7:4]; carry 1b0; end else if (tick_in) begin carry 1b0; if (unit 4d9) begin unit 4d0; if (ten 4d5) begin ten 4d0; carry 1b1; // 秒满60向分钟进一位 end else begin ten ten 1b1; end end else begin unit unit 1b1; end end end endmoduleunit是个位BCDten是十位BCD。当计数到“59”的下一个tick到来时unit从9回到0ten从5回到0同时拉高carry一个周期。这个carry接到分钟计数器的tick_in上分钟计数器再产生小时进位就形成了完整的HH:MM:SS链路。小时计数器不需要这么完整因为24进制只在ten2且unit3时归零其余情况按普通BCD累加。设计时可以把小时单独写一个模块也可以用一个额外的if-else分支处理。实际项目里我更愿意把小时模块单独例化参数化MAX_VALUE这样既能显示24小时制也能改成12小时制显示PM/AM。3.3 按键消抖与校时逻辑板上按键按下去的时候机械触点会产生几毫秒的抖动不处理的话一次按键可能被识别成好几次。常见做法是在FPGA内部做边沿检测加延时计数检测到按键变化后开始计数持续稳定一段时间才认为有效。module key_debounce #( parameter CLK_HZ 12_000_000, parameter DELAY_MS 10 )( input wire clk, input wire rst_n, input wire key_in, output reg key_press ); localparam DELAY_CNT CLK_HZ * DELAY_MS / 1000; reg [19:0] cnt; reg key_sync, key_stable; always (posedge clk or negedge rst_n) begin if (!rst_n) begin key_sync 1b1; key_stable 1b1; key_press 1b0; cnt d0; end else begin key_sync key_in; if (key_sync ! key_stable) begin key_stable key_sync; cnt d0; end else if (key_stable 1b0 cnt DELAY_CNT) begin cnt cnt 1b1; if (cnt DELAY_CNT - 1) key_press 1b1; // 稳定低电平超过10ms确认按下 end else begin key_press 1b0; end end end endmodulekey_press只会拉高一个时钟周期所以在校时的状态机里可以直接用这个信号触发“当前调节位加一”。实际校时最多需要三个按键一个进入设置模式一个切换时分秒一个加一。要是板子上只有两个按键可以把切换和加一合并成“短按切换、长按加一”但逻辑复杂度会明显上升建议第一版先保证三键方案跑通。4. Nokia5110的Verilog驱动三段式状态机、字库和显示布局LCD驱动是另一个大头。PCD8544要求一次完整的8位传输期间SCE保持低电平SCLK按模式0翻转D/C在整个传输过程中保持稳定。用Verilog实现时最稳妥的写法还是三段式状态机把时序完全拆开。4.1 SPI发送状态机用verilog三段式状态机写PCD8544时序下面这个模块负责往PCD8544发送一个字节sclk在空闲时为低每个位先是din输出数据然后sclk拉高让屏幕采样再拉低准备下一位。module spi_tx_8 ( input wire clk, input wire rst_n, input wire start, input wire [7:0] tx_data, output reg sclk, output reg din, output reg done ); localparam IDLE 3d0, START_BIT 3d1, SCLK_HIGH 3d2, STOP 3d3; reg [3:0] state, next_state; reg [2:0] bit_cnt; always (posedge clk or negedge rst_n) begin if (!rst_n) state IDLE; else state next_state; end always (*) begin next_state state; case (state) IDLE: if (start) next_state START_BIT; START_BIT: next_state SCLK_HIGH; SCLK_HIGH: if (bit_cnt 3d7) next_state STOP; else next_state START_BIT; STOP: next_state IDLE; default: next_state IDLE; endcase end always (posedge clk or negedge rst_n) begin if (!rst_n) begin sclk 1b0; din 1b0; done 1b0; bit_cnt 3d0; end else begin done 1b0; case (state) IDLE: begin bit_cnt 3d0; din 1b0; end START_BIT: begin din tx_data[3d7 - bit_cnt]; // 高位先发 sclk 1b0; end SCLK_HIGH: begin sclk 1b1; // 上升沿从机采样 bit_cnt bit_cnt 1b1; end STOP: begin sclk 1b0; done 1b1; end endcase end end endmodulesclk在每个数据位稳定后拉高一个周期严格满足PCD8544的上升沿采样要求。tx_data[3d7 - bit_cnt]把最高位先发出去符合MSB First约定。这个状态机本身不带分频实际使用时外层驱动模块用一个更低速的使能信号周期性触发start保证SCLK频率不超过4MHz就可以。4.2 字库ROM用case存储数字和冒号的点阵Nokia5110的显存一个字节对应一列8个像素显示字符时最常用的做法是5x8字体每个字符占5列列与列之间空一列用来产生间隔。下面用case实现一个简单字符索引输入索引输出40位点阵数据。function [39:0] get_font; input [3:0] idx; begin case (idx) 4d0: get_font 40h3E5149453E; // 0 4d1: get_font 40h00427F4000; // 1 4d2: get_font 40h4261514946; // 2 4d3: get_font 40h2141494B36; // 3 4d4: get_font 40h1814127F10; // 4 4d5: get_font 40h2749494931; // 5 4d6: get_font 40h3E4949497A; // 6 4d7: get_font 40h0101016163; // 7 4d8: get_font 40h3649494936; // 8 4d9: get_font 40h264949493E; // 9 4d10: get_font 40h0000363600; // : default: get_font 40h0000000000; endcase end endfunction这组点阵数据是5x8的字形每个40位数字的高8位表示最左边一列。实际显示时每写一个字符要发送6个字节前5字节是字形数据第6字节固定写0作为字符间距。数字字符本身正好在ASCII里从0x30到0x39连续排列BCD计数器的值加上0x30就是对应的字符索引冒号单独定义成索引10布局起来非常顺手。4.3 显示布局HH:MM:SS居中排布84像素的宽度下用5x8字体加1像素间距每个字符占6列。显示“HH:MM:SS”需要8个字符正好48列。屏幕总宽84列起点设在18列就能水平居中。第二行可以显示日期或者星期。比如“04-18 FRI”也是8个字符放在Y1的bank里。PCD8544的Y坐标取值范围是0到5每个Y对应一整个8像素行所以两行文字分别写在Y0和Y1彼此不会重叠。写字符前最好先清屏。清屏不需要单独命令连续往X0到83、Y0到5的每个地址写0x00即可。实际工程里可以做一个lcd_set_pos小模块每次写字符前先发送X和Y的命令字节再连续发送6个数据字节这样布局函数只需要维护字符坐标。5. 在Lattice Diamond里过一遍综合、LDC引脚约束和片上Flash下载Verilog写完之后就要进Lattice Diamond做综合和下载。LCMXO2的开发流程跟Xilinx Vivado或者Intel Quartus有相似之处但几个关键操作完全不同尤其是配置方式。5.1 器件选择与LSE综合设置新建工程时唯一必须选对的就是器件型号。小脚丫LCMXO2开发板常见的是LCMXO2-1200ZE系列封装是TQFP100或者QFN48具体型号以板卡丝印为准。Lattice Diamond的工程向导里Synthesis Tool选择LSE这是软件自带的综合器对这个项目完全够用。没必要选Synplify Pro那个综合器更适合大工程跑这个小设计反而耗时。点击综合后如果出现语法错误绝大多数问题出在端口宽度不匹配或者reg和wire混用。比如tick_1hz在分频器里声明成output reg在顶层连接时挂在wire上综合器对这类写法很敏感报错信息会直接指向具体行号按提示修改即可。5.2 用LDC文件约束引脚和时钟Lattice Diamond的引脚约束文件是LDC格式内容包括引脚位置、IO电平标准和时钟频率。数字时钟项目里所有LCD信号都是3.3V电平IO约束统一写LVCMOS33。LOCATE COMP clk SITE P3; LOCATE COMP rst_n SITE P11; LOCATE COMP key_set SITE P14; LOCATE COMP key_add SITE P15; LOCATE COMP key_mode SITE P16; LOCATE COMP lcd_rst SITE P20; LOCATE COMP lcd_sce SITE P21; LOCATE COMP lcd_dc SITE P22; LOCATE COMP lcd_din SITE P23; LOCATE COMP lcd_sclk SITE P24; IOBUF PORT clk IO_TYPELVCMOS33; IOBUF PORT rst_n IO_TYPELVCMOS33; IOBUF PORT key_set IO_TYPELVCMOS33; IOBUF PORT key_add IO_TYPELVCMOS33; IOBUF PORT key_mode IO_TYPELVCMOS33; IOBUF PORT lcd_rst IO_TYPELVCMOS33; IOBUF PORT lcd_sce IO_TYPELVCMOS33; IOBUF PORT lcd_dc IO_TYPELVCMOS33; IOBUF PORT lcd_din IO_TYPELVCMOS33; IOBUF PORT lcd_sclk IO_TYPELVCMOS33; FREQUENCY NET clk 12 MHz;所有SITE后的引脚号都是示意值实际工程中需要对着PDF原理图逐个填入。LOCATE COMP必须写在顶层模块里出现过的端口名上写错一个字符综合也会通过但布局布线后会报错。FREQUENCY NET那一行虽然不加也能跑但加了以后软件会知道时钟约束后续时序报告才有效。5.3 Download到片上Flash让时钟上电自启动下载这一步是LCMXO2最容易被忽视的优势。打开Lattice Diamond的Programmer工具识别到下载器之后Operation选项通常可以选SRAM Programming和FLASH Programming。SRAM模式下载快但掉电丢失只适合调试。FLASH模式把配置写入芯片内部Flash掉电之后再上电FPGA直接从内部加载程序不需要额外配置芯片。下载过程中如果提示找不到器件先检查下载线是否插紧再确认Programmer中选中的扫描链设备编号。综合前用示波器确认晶振有输出也很重要很多“下载失败”问题其实出在目标板根本没起振。Programmer选项适用场景掉电后是否保留SRAM Programming临时调试否FLASH Programming正式烧录是6. 把数字时钟走准误差补偿、对比度调整和验证技巧分频器写对了时钟能走但不一定走得准。板载晶振的实际频率跟标称值总有偏差误差累积一天可能差出几秒到十几秒。常用校准方法是实测日误差然后修正常数。记录一天下来时钟快了N秒说明晶振实际频率比标称值高大约N/86400把分频器里CLK_HZ的常数按比例调大即可。比如12MHz晶振每天快2秒新的分频计数目标大约是12000000 * 86400 / (86400 2)取整后写成参数。这是最简单也最有效的校正方式比在计数器里加跳秒逻辑更直接。Nokia5110的显示对比度也是常调参数。初始化命令里的0xB8直接控制VOP电压数值越大对比度越高。不同批次屏幕差异很大有的0xB8就清楚有的要0xBC才够深。调对比度时把命令字节改成0x80到0xBF之间的值单独烧录测试确认合适后再固定写入源码。最后验证SPI时序时示波器大概是最快的工具。先把SCLK和DIN同时接到示波器上触发沿设在SCLK上升沿上电后应能看到初始化命令序列然后是连续不断的数据字节。如果DIN始终为低先检查start信号有没有周期性拉高如果DIN有数据但屏幕仍然白屏多半是RST或者SCE的极性反了回原理图确认那两个信号是否接在FPGA的普通IO上而不是被板上其他外设占用。本文还有配套的精品资源点击获取
返回列表