**这是本文档旧的修订版!**
基于小脚丫STEP MXO2的温度显示系统
一、项目简介
基于小脚丫STEP MXO2的温度显示系统的核心控制模块为小脚丫STEP MXO2开发板,采用由MicroUSB输入的5V供电,温度传感器选用的是DALLAS的经典传感器——DS18B20,一个封装和常见三极管(TO-92)相同的温度传感器,而显示模块采用LCD1602,相信读者对这两个模块一定是极为熟悉。
二、项目框图
1.控制核心
温度计项目控制核心为小脚丫STEP MXO2 V2版本FPGA开发板,FPGA芯片为Lattice Semiconductor的MachXO2 400HC系列FPGA。
2.温度采集模块
温度采集模块采用Dallas的经典产品——DS18B20,是一个高精度,占用空间小,硬件连接简单,价格低廉的数字温度传感器,采用单总线驱动方式,更为节省开发板资源。
3.温度显示系统
温度显示模块采用集成了ASCII字库的LCD1602,省去了自建字库的麻烦。
三、硬件电路图
温度计的硬件电路比较简单,首先在供电方面,作为控制核心的小脚丫开发板由于具备完善的下载与供电方案,故不必在设计下载电路,只需要一根MicroUSB数据线即可满足整体系统的供电与下载;
在温度采集部分,DS18B20共有三个引脚,我们参照硬件手册,可发现该芯片的1号引脚接地,2号引脚为数据信号DQ,接到小脚丫的任意引脚上(下图接到了小脚丫STEP MXO2的“SI”引脚上),3号引脚为电源脚,参照手册,DS18B20的输入电压为3.0V-5.5V,此处我们采用了3.3V供电。
温度显示部分,LCD1602共有16个引脚,下图为LCD1602的引脚简介,对应连接即可:
四、Verilog代码
====1.Verilog代码:LCD1602显示部分====
// -------------------------------------------------------------------- // >>>>>>>>>>>>>>>>>>>>>>>>> COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<< // -------------------------------------------------------------------- // Module:LCD1602 // // Author: STEP // // Description: Display the temperature by LCD1602 // // Web: www.stepfpga.com // // -------------------------------------------------------------------- // Code Revision History : // -------------------------------------------------------------------- // Version: |Mod. Date: |Changes Made: // V1.0 |2017.3.8 |Initial ver // -------------------------------------------------------------------- module LCD_1602(clk,LCD_EN,RS,RW,DB8,one_wire,rst); input clk,rst; //系统时钟与复位,系统时钟==12M output LCD_EN; //LCD_EN为LCD模块的使能信号(下降沿触发) output RS; //RS=0时为写指令;RS=1时为写数据 output RW; //RW=0时对LCD模块执行写操作;RW=1时对LCD模块执行读操作 output [7:0] DB8; //8位指令或数据总线 inout one_wire; //例化DS18B20模块单总线 reg RS; reg LCD_EN_Sel; reg [7:0] DB8; reg [127:0] data_row1; reg [127:0] data_row2; reg [7:0] result_unit; reg [7:0] result_decade; reg [7:0] result_hundred; reg [7:0] result_dec; reg [7:0] result_dec2; reg [7:0] result_dec3; reg [7:0] result_dec4; reg [7:0] sign; reg [3:0] num_unit; reg [3:0] num_decade; reg [3:0] num_hundred; reg [3:0] num_dec; reg [3:0] num_dec2; reg [3:0] num_dec3; reg [3:0] num_dec4; //若想显示小数点第四位,添加至显示内容并调整即可 reg[19:0] cnt_ref; //LCD1602更新计数器 reg ref; //更新标志位 always@(posedge clk_2ms) //产生LCD1602更新所需信号 begin if(cnt_ref==220) begin cnt_ref<=0; ref<=1; ref<=0; end else begin cnt_ref<=cnt_ref+1; ref<=1; end end always@(*) //1602输入数据接口处理 begin case(num_unit) //个位 4'd0:result_unit<=8'b00110000; 4'd1:result_unit<=8'b00110001; 4'd2:result_unit<=8'b00110010; 4'd3:result_unit<=8'b00110011; 4'd4:result_unit<=8'b00110100; 4'd5:result_unit<=8'b00110101; 4'd6:result_unit<=8'b00110110; 4'd7:result_unit<=8'b00110111; 4'd8:result_unit<=8'b00111000; 4'd9:result_unit<=8'b00111001; default:result_unit<=result_unit; endcase case(num_decade) //十位 4'd0:result_decade<=8'b00110000; 4'd1:result_decade<=8'b00110001; 4'd2:result_decade<=8'b00110010; 4'd3:result_decade<=8'b00110011; 4'd4:result_decade<=8'b00110100; 4'd5:result_decade<=8'b00110101; 4'd6:result_decade<=8'b00110110; 4'd7:result_decade<=8'b00110111; 4'd8:result_decade<=8'b00111000; 4'd9:result_decade<=8'b00111001; default:result_decade<=result_decade; endcase case(num_hundred) //百位 4'd0:result_hundred<=8'b00110000; 4'd1:result_hundred<=8'b00110001; 4'd2:result_hundred<=8'b00110010; 4'd3:result_hundred<=8'b00110011; 4'd4:result_hundred<=8'b00110100; 4'd5:result_hundred<=8'b00110101; 4'd6:result_hundred<=8'b00110110; 4'd7:result_hundred<=8'b00110111; 4'd8:result_hundred<=8'b00111000; 4'd9:result_hundred<=8'b00111001; default:result_hundred<=result_hundred; endcase case(num_dec) //小数位 4'd0:result_dec<=8'b00110000; 4'd1:result_dec<=8'b00110001; 4'd2:result_dec<=8'b00110010; 4'd3:result_dec<=8'b00110011; 4'd4:result_dec<=8'b00110100; 4'd5:result_dec<=8'b00110101; 4'd6:result_dec<=8'b00110110; 4'd7:result_dec<=8'b00110111; 4'd8:result_dec<=8'b00111000; 4'd9:result_dec<=8'b00111001; default:result_dec<=result_dec; endcase case(num_dec4) //小数位 4'd0:result_dec4<=8'b00110000; 4'd1:result_dec4<=8'b00110001; 4'd2:result_dec4<=8'b00110010; 4'd3:result_dec4<=8'b00110011; 4'd4:result_dec4<=8'b00110100; 4'd5:result_dec4<=8'b00110101; 4'd6:result_dec4<=8'b00110110; 4'd7:result_dec4<=8'b00110111; 4'd8:result_dec4<=8'b00111000; 4'd9:result_dec4<=8'b00111001; default:result_dec4<=result_dec4; endcase case(num_dec2) //小数位 4'd0:result_dec2<=8'b00110000; 4'd1:result_dec2<=8'b00110001; 4'd2:result_dec2<=8'b00110010; 4'd3:result_dec2<=8'b00110011; 4'd4:result_dec2<=8'b00110100; 4'd5:result_dec2<=8'b00110101; 4'd6:result_dec2<=8'b00110110; 4'd7:result_dec2<=8'b00110111; 4'd8:result_dec2<=8'b00111000; 4'd9:result_dec2<=8'b00111001; default:result_dec2<=result_dec2; endcase case(num_dec3) //小数位 4'd0:result_dec3<=8'b00110000; 4'd1:result_dec3<=8'b00110001; 4'd2:result_dec3<=8'b00110010; 4'd3:result_dec3<=8'b00110011; 4'd4:result_dec3<=8'b00110100; 4'd5:result_dec3<=8'b00110101; 4'd6:result_dec3<=8'b00110110; 4'd7:result_dec3<=8'b00110111; 4'd8:result_dec3<=8'b00111000; 4'd9:result_dec3<=8'b00111001; default:result_dec3<=result_dec3; endcase end //-------------------------------------// //输入时钟12MHz 输出周期2ms //division12MHz_2ms.v reg [15:0]count; reg clk_2ms; always @ (posedge clk) begin if(count == 16'd12_000) begin count <= 16'b1; clk_2ms <= ~clk_2ms; end else count <= count + 1'b1; end //---------------------------------------// reg [127:0] Data_Buf; //液晶显示的数据缓存 reg [4:0] disp_count; reg [3:0] state; //状态机 parameter Clear_Lcd = 4'b0000; //清屏并光标复位 parameter Set_Disp_Mode = 4'b0001; //设置显示模式:8位2行5x7点阵 parameter Disp_On = 4'b0010; //显示器开、光标不显示、光标不允许闪烁 parameter Shift_Down = 4'b0011; //文字不动,光标自动右移 parameter Write_Addr = 4'b0100; //写入显示起始地址 parameter Write_Data_First = 4'b0101; //写入第一行显示的数据 parameter Write_Data_Second = 4'b0110; //写入第二行显示的数据 assign RW = 1'b0; //RW=0时对LCD模块执行写操作(一直保持写状态) assign LCD_EN = LCD_EN_Sel ? clk_2ms : 1'b0; //通过LCD_EN_Sel信号来控制LCD_EN的开启与关闭 //--------------------------------显示模块----------------------------// always @(posedge clk_2ms or negedge rst or negedge ref) begin //-----------------------复位并更新显示数据--------------------// if(!rst || !ref) begin state <= Clear_Lcd; //复位:清屏并光标复位 RS <= 1'b1; //复位:RS=1时为读指令; DB8 <= 8'b0; //复位:使DB8总线输出全0 LCD_EN_Sel <= 1'b0; //复位:关夜晶使能信号 disp_count <= 5'b0; data_row1 <= { //输入第一行要显示的数据 8'b01010011, //S 8'b01010100, //T 8'b01000101, //E 8'b01010000, //P 8'b00100000, //SPACE 8'b01000110, //F 8'b01010000, //P 8'b01000111, //G 8'b01000001, //A 8'b00100000, //SPACE 8'b00100000, //SPACE 8'b00100000, //SPACE 8'b00100000, //SPACE 8'b00100000, //SPACE 8'b00100000, //SPACE 8'b00100000 //SPACE }; data_row2 <= { 8'b00100000, //输入第二行要显示的数据 8'b00100000, //SPACE 8'b01010100, //T 8'b01100101, //e 8'b01101101, //m 8'b00111010, //冒号 sign, result_unit, result_decade, result_hundred, 8'b00101110, //. result_dec, result_dec2, result_dec3, 8'hdf, //℃ 8'b01000011 }; end else begin case(state) //-------------------------------初始化LCD------------------------------------// Clear_Lcd : begin LCD_EN_Sel <= 1'b1; //开使能 RS <= 1'b0; //写指令 DB8 <= 8'b00000001; //清屏并光标复位 state <= Set_Disp_Mode; end Set_Disp_Mode : begin DB8 <= 8'b00111000; //设置显示模式:8位2行5x8点阵 state <= Disp_On; end Disp_On : begin DB8 <= 8'b00001100; //显示器开、光标不显示、光标不允许闪烁 state <= Shift_Down; end Shift_Down : begin DB8 <= 8'b00000110; //文字不动,光标自动右移 state <= Write_Addr; end //---------------------------------显示循环------------------------------------// Write_Addr : begin RS <= 1'b0; //写指令 DB8 <= 8'b10000000; //写入第一行显示起始地址:第一行第1个位置 Data_Buf <= data_row1; //将第一行显示的数据赋给Data_First_Buf state <= Write_Data_First; end Write_Data_First : begin //写第一行数据 if(disp_count == 16) //disp_count等于15时表示第一行数据已写完 begin RS <= 1'b0; //写指令 DB8 <= 8'b11000000; //送入写第二行的指令,第2行第1个位置 disp_count <= 5'b0; //计数清0 Data_Buf <= data_row2; //将第2行显示的数据赋给Data_First_Buf state <= Write_Data_Second;end //写完第一行进入写第二行状态 else //没写够16字节 begin RS <= 1'b1; //RS=1表示写数据 DB8 <= Data_Buf[127:120]; Data_Buf <= (Data_Buf << 8); disp_count <= disp_count + 1'b1; state <= Write_Data_First; end end Write_Data_Second : begin //写第二行数据 if(disp_count == 16)//数据发送完毕 begin RS <= 1'b0; //写指令 DB8 <= 8'b10000000; //写入第一行显示起始地址:第一行第1个位置 disp_count <= 5'b0; state <= Write_Addr; //重新循环 end else begin RS <= 1'b1; DB8 <= Data_Buf[127:120]; Data_Buf <= (Data_Buf << 8); disp_count <= disp_count + 1'b1; state <= Write_Data_Second; end end //--------------------------------------------------------------------------// default : state <= Clear_Lcd; //若state为其他值,则将state置为Clear_Lcd endcase end end //--------------------------------------------------------------------------// wire clk_in; wire rst_n_in; wire [15:0] data_out; wire tem_flag=data_out[15:11]?1'b0:1'b1; wire [10:0] tem_code=tem_flag?data_out[10:0]:(~data_out[10:0])+1'b1; wire [20:0] tem_data=tem_code*625; reg [27:0] bcd_code; DS18B20Z DS18B20Z_uut ( .one_wire(one_wire), .clk_in(clk), .rst_n_in(rst), .data_out(data_out) ); reg [48:0] shift_reg; always@(posedge clk or negedge rst)begin shift_reg= {28'h0,tem_data}; if(!rst) bcd_code = 0; else begin repeat(21)//repeat B_SIZE times begin if (shift_reg[24:21] >= 5) shift_reg[24:21] = shift_reg[24:21] + 2'b11; if (shift_reg[28:25] >= 5) shift_reg[28:25] = shift_reg[28:25] + 2'b11; if (shift_reg[32:29] >= 5) shift_reg[32:29] = shift_reg[32:29] + 2'b11; if (shift_reg[36:33] >= 5) shift_reg[36:33] = shift_reg[36:33] + 2'b11; if (shift_reg[40:37] >= 5) shift_reg[40:37] = shift_reg[40:37] + 2'b11; if (shift_reg[44:41] >= 5) shift_reg[44:41] = shift_reg[44:41] + 2'b11; if (shift_reg[48:45] >= 5) shift_reg[48:45] = shift_reg[48:45] + 2'b11; if (tem_flag==0) sign<=8'b00101101; if (tem_flag==1) sign<=8'b00100000; shift_reg = shift_reg << 1; end bcd_code=shift_reg[48:21]; num_unit <= bcd_code[27:24]; num_decade <= bcd_code[23:20]; num_hundred<= bcd_code[19:16]; num_dec <= bcd_code[15:12]; num_dec2 <= bcd_code[11:8]; num_dec3 <= bcd_code[7:4]; num_dec4 <= bcd_code[3:0]; end end endmodule