FPGA ALARM FPGA多功能闹钟 完整项目 内含上位机
一、项目简述
本项目使用苏州硬禾信息科技有限公司设计的小脚丫FPGA开发板设计了一个完成定时、测温、报警、控制的小项目,并通过上位机显示、下发音乐配置数据。
本项目B站介绍:https://www.bilibili.com/video/BV1Vh411k7QV/
二、研究进展
(一)研究内容:
l 实现一个可定时时钟的功能,用小脚丫FPGA核心模块的4个按键设置当前的时间,OLED显示数字钟的当前时间,精确到分钟即可,到整点的时候比如8:00,蜂鸣器报警,播放音频信号,最长可持续30秒;
l 实现温度计的功能,小脚丫通过板上的温度传感器实时测量环境温度,并同时间一起显示在OLED的屏幕上;
l 定时时钟整点报警的同时,将温度信息通过UART传递到电脑上,电脑上能够显示当前板子上的温度信息(任何显示形式都可以),要与OLED显示的温度值一致;
l PC收到报警的温度信号以后,将一段音频文件(自己制作,持续10秒钟左右)通过UART发送给小脚丫FPGA,蜂鸣器播放收到的这段音频文件,OLED屏幕上显示的时间信息和温度信息都停住不再更新;
l 音频文件播放完毕,OLED开始更新时间信息和当前的温度信息
(二)研究方法
l 查找资料,从原理层研究如何实现功能。
l 比较、寻找能够符合项目需求的方案。
l 自顶向下编程实现功能。
l 综合测试系统性能和参数。
l 开发上位机软件,根据需求显示数据并下发音乐数据
(三)取得的成果
项目需求全部实现,软件开发也已完成。
图2.1 上位机助手截图
图2.2 开发板演示照片
(四)核心代码展示
//========================================================================== // Author: ChanRa1n // Description: Alarm for Step Training Board with Step-max10 // Web: www.myfpga.cn //========================================================================== module clock(clk_in,data_time_a,data_time_b,data_time_c,data_time_d,pause,tone_en,tone,rx_Sig,rx_data,key);//总程序 input clk_in; //OLED //时间按照AB:CD的格式 output reg [3:0] data_time_a; output reg [3:0] data_time_b; output reg [3:0] data_time_c; output reg [3:0] data_time_d; inout reg pause;//暂停更新标志位 //PWM output reg tone_en; //蜂鸣器使能信号 output reg [4:0] tone; //蜂鸣器音节控制 //UART input [1:0] rx_Sig;//00内置音乐 11开始 10结束 input [7:0] rx_data; //private reg[5:0] time_min; reg[5:0] time_hour; reg[8:0] time_sec; reg[8:0] pwm_sec; //KEY input [3:0] key;//按键 initial begin //OLED //时间按照AB:CD的格式 data_time_a = 0; data_time_b = 5; data_time_c = 5; data_time_d = 9; //计时自05:59:50开始 time_sec = 250; time_min = 59; time_hour = 5; pause = 0; //PWM tone_en=0; tone=1; pwm_sec = 0; end always @(posedge clk_in) begin //过去了0.2s,增加计数 //时间按照AB:CD的格式 time_sec<=time_sec+1; if(time_sec>=300) begin time_sec<=0; time_min<=time_min+1; end if(time_min>=60) begin time_min<=0; time_hour<=time_hour+1; end if(time_hour>=24) begin time_hour<=0; end //检测按键 if(!key[0])time_hour<=time_hour+1; if(!key[1])time_hour<=time_hour-1; if(!key[2])time_min<=time_min+1; if(!key[3])time_min<=time_min-1; //刷新显示 //时间按照AB:CD的格式 data_time_a <= time_hour/10; data_time_b <= time_hour%10; data_time_c <= time_min/10; data_time_d <= time_min%10; //整点到 if(time_min==0 && time_sec==1)pause<=1;//如果分钟为0,并且秒数为0 if(pause && rx_Sig==2'b0)//使用内置音乐模式 begin if(pause) begin tone_en<=1;//准备唱歌了 pwm_sec<=pwm_sec+1;//增加0.2秒 end if(pwm_sec%5==0 && tone_en==1)//过去一秒 begin tone<=(tone<=5'd21)?tone+1:1;//瞧这美丽动听的音乐 //咱也是有音乐细菌的 end else if(pwm_sec>=35 && tone_en==1)//过去七秒,想要唱多久,此处就改为秒数*5 begin tone_en<=0;//咱别唱了 pause <=0;//继续计时 pwm_sec<=0;//为下次计时做准备 end end if(pause && rx_Sig==2'b11)//串口开始 begin tone_en<=1; tone[4:0]<=rx_data[4:0]; end if(pause && rx_Sig==2'b10)//串口结束 begin tone_en<=0; tone<=0; pause<=0; end end Endmodule
该代码主要用于计时,并与蜂鸣器、串口、温度传感器进行通信。并没有采用严格的面向对象和模块化开发,降低了开发的难度和周期。
module uart_t( clk, //12Mhz C1 tx, //ouptut TX_En_Sig, data_tx, TX_Done_Sig ); input clk; output reg tx; input [7:0]data_tx; reg [7:0]data_temp; input TX_En_Sig; output reg TX_Done_Sig; //-------------------波特率控制------------ wire bps_start; reg [3:0] tx_num; // 2^4-1=15 always@(posedge clk) begin if(tx_num==10) TX_Done_Sig<=1'b0; end assign bps_start = TX_En_Sig; //------------------产生波特率-------------- //9600 parameter bps_cnt=1250; //12Mhz / 9600 = 12.5 parameter bps_cnt_half=625; reg [12:0] cnt; always@(posedge clk) begin if(cnt==bps_cnt) cnt<=13'b0; else if(bps_start) cnt<=cnt+1'b1; else cnt<=13'b0; end //---------------------发送数据-------------- //transport always@(posedge clk) begin if(cnt==bps_cnt_half) begin if(TX_En_Sig && data_temp!=data_tx) begin tx_num<=tx_num+1'b1; case(tx_num) 0:tx<=1'b0; 1:tx<=data_tx[0]; 2:tx<=data_tx[1]; 3:tx<=data_tx[2]; 4:tx<=data_tx[3]; 5:tx<=data_tx[4]; 6:tx<=data_tx[5]; 7:tx<=data_tx[6]; 8:tx<=data_tx[7]; 9:tx<=1'b1; endcase end else tx<=1'b1; end else if(tx_num==10) begin tx_num<=4'b0; data_temp<=data_tx; end end endmodule
串口发送模块,将时间、温度信息发送给上位机。
module uart_r( clk, //12Mhz rst_n, //reset rx, //input rx_data, rx_Sig ); input clk,rst_n; input rx; output reg [7:0] rx_data; output reg [1:0] rx_Sig;//00内置音乐 11开始 10结束 initial begin rx_Sig<=2'b0; end //-----------------检测是否有数据来-------- //边沿检测 wire rx_start; reg rx1; always@(posedge clk or negedge rst_n) begin if(!rst_n) rx1<=1'b0; else rx1<=rx; end assign rx_start = ~rx & rx1; //-------------------波特率控制------------ wire bps_start; reg bps_start_rx; reg [3:0] rx_num; always@(posedge clk or negedge rst_n) begin if(!rst_n) begin bps_start_rx<=1'b0; end else if(rx_start) bps_start_rx<=1'b1; //接收完数据后 开始发送数据 else if(rx_num==10) begin bps_start_rx<=1'b0; end end assign bps_start = bps_start_rx; //------------------产生波特率-------------- //9600 parameter bps_cnt=1250; //50Mhz / 9600 = 5208.3...... parameter bps_cnt_half=625; reg [12:0] cnt; always@(posedge clk or negedge rst_n) begin if(!rst_n) cnt<=13'b0; else if(cnt==bps_cnt) cnt<=13'b0; else if(bps_start) cnt<=cnt+1'b1; else cnt<=13'b0; end //---------------------接收数据-------------- //receive always@(posedge clk or negedge rst_n) begin if(!rst_n) begin rx_num<=4'b0; rx_data<=8'b0; end else if(cnt==bps_cnt_half) begin if(bps_start_rx) begin rx_num<=rx_num+1'b1; case(rx_num) 1:rx_data[0]<=rx; 2:rx_data[1]<=rx; 3:rx_data[2]<=rx; 4:rx_data[3]<=rx; 5:rx_data[4]<=rx; 6:rx_data[5]<=rx; 7:rx_data[6]<=rx; 8:rx_data[7]<=rx; 9:begin if(rx_data==0) rx_Sig<=2'b10;else rx_Sig<=2'b11; end//如果收到了串口信息,则代表使用串口音乐 endcase end end else if(rx_num==10) rx_num<=4'b0; end endmodule
串口接收模块,用于将上位机发送的音乐配置信息发送至Clock模块,并产生声音。
module oled ( inputClkIn,//12MHz系统时钟 inputrst_n,//系统复位,低有效 input[3:0]data_time_a, input[3:0]data_time_b, input[3:0]data_time_c, input[3:0]data_time_d, input[3:0]data_temp_a, input[3:0]data_temp_b, input[3:0]data_temp_c, inputpause,//暂停更新标志位 //outputoled_busy,//没有设置显示忙标志位,因为刷新频率很高,看不到影响 //利用OLED RAM暂存,依次刷新数据 outputregOledCs,//OLCD液晶屏使能 outputregOledRes,//OLCD液晶屏复位 outputregOledDc,//OLCD数据指令控制 outputregOledClk,//OLCD时钟信号 outputregOledDin//OLCD数据信号 ); localparam INIT_DEPTH = 16'd25; //LCD初始化的命令的数量 localparam IDLE = 6'h1, MAIN = 6'h2, INIT = 6'h4, SCAN = 6'h8, WRITE = 6'h10, DELAY = 6'h20; localparam HIGH= 1'b1, LOW = 1'b0; localparam DATA= 1'b1, CMD = 1'b0; reg [7:0] cmd [24:0]; reg [39:0] mem [122:0]; reg[7:0]y_p, x_ph, x_pl; reg[(8*21-1):0] char; reg[7:0]num, char_reg;// reg[4:0]cnt_main, cnt_init, cnt_scan, cnt_write; reg[15:0]num_delay, cnt_delay, cnt; reg[5:0] state, state_back; always@(posedge ClkIn or negedge rst_n) begin if(!rst_n) begin cnt_main <= 1'b0; cnt_init <= 1'b0; cnt_scan <= 1'b0; cnt_write <= 1'b0; y_p <= 1'b0; x_ph <= 1'b0; x_pl <= 1'b0; num <= 1'b0; char <= 1'b0; char_reg <= 1'b0; num_delay <= 16'd5; cnt_delay <= 1'b0; cnt <= 1'b0; OledCs <= HIGH; OledRes <= HIGH; OledDc <= CMD; OledClk <= HIGH; OledDin <= LOW; state <= IDLE; state_back <= IDLE; end else begin case(state) IDLE:begin cnt_main <= 1'b0; cnt_init <= 1'b0; cnt_scan <= 1'b0; cnt_write <= 1'b0; y_p <= 1'b0; x_ph <= 1'b0; x_pl <= 1'b0; num <= 1'b0; char <= 1'b0; char_reg <= 1'b0; num_delay <= 16'd5; cnt_delay <= 1'b0; cnt <= 1'b0; OledCs <= HIGH; OledRes <= HIGH; OledDc <= CMD; OledClk <= HIGH; OledDin <= LOW; state <= MAIN; state_back <= MAIN; end MAIN:begin if(cnt_main >= 5'd11) cnt_main <= 5'd5; else cnt_main <= cnt_main + 1'b1; case(cnt_main)//MAIN状态 5'd0:begin state <= INIT; end 5'd1:begin y_p <= 8'hb0; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " FPGA ALARM ";state <= SCAN; end 5'd2:begin y_p <= 8'hb1; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " : ";state <= SCAN; end 5'd3:begin y_p <= 8'hb2; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " . * ";state <= SCAN; end 5'd4:begin y_p <= 8'hb3; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "MyFPGA ChanRa1n";state <= SCAN; end 5'd5:begin if(!pause) begin y_p <= 8'hb1; x_ph <= 8'h12; x_pl <= 8'h00; num <= 5'd 1; char <= data_time_a; state <= SCAN; end end 5'd6:begin if(!pause) begin y_p <= 8'hb1; x_ph <= 8'h13; x_pl <= 8'h00; num <= 5'd 1; char <= data_time_b; state <= SCAN; end end 5'd7:begin if(!pause) begin y_p <= 8'hb1; x_ph <= 8'h14; x_pl <= 8'h00; num <= 5'd 1; char <= data_time_c; state <= SCAN; end end 5'd8:begin if(!pause) begin y_p <= 8'hb1; x_ph <= 8'h15; x_pl <= 8'h00; num <= 5'd 1; char <= data_time_d; state <= SCAN; end end 5'd9:begin if(!pause) begin y_p <= 8'hb2; x_ph <= 8'h12; x_pl <= 8'h00; num <= 5'd 1; char <= data_temp_a; state <= SCAN; end end 5'd10:begin if(!pause) begin y_p <= 8'hb2; x_ph <= 8'h13; x_pl <= 8'h00; num <= 5'd 1; char <= data_temp_b; state <= SCAN; end end 5'd11:begin if(!pause) begin y_p <= 8'hb2; x_ph <= 8'h14; x_pl <= 8'h00; num <= 5'd 1; char <= data_temp_c; state <= SCAN; end end default: state <= IDLE; endcase end INIT:begin//初始化状态 case(cnt_init) 5'd0:begin OledRes <= LOW; cnt_init <= cnt_init + 1'b1; end//复位有效 5'd1:begin num_delay <= 16'd25000; state <= DELAY; state_back <= INIT; cnt_init <= cnt_init + 1'b1; end//延时大于3us 5'd2:begin OledRes <= HIGH; cnt_init <= cnt_init + 1'b1; end//复位恢复 5'd3:begin num_delay <= 16'd25000; state <= DELAY; state_back <= INIT; cnt_init <= cnt_init + 1'b1; end//延时大于220us 5'd4:begin if(cnt>=INIT_DEPTH) begin//当25条指令及数据发出后,配置完成 cnt <= 1'b0; cnt_init <= cnt_init + 1'b1; end else begin cnt <= cnt + 1'b1; num_delay <= 16'd5; OledDc <= CMD; char_reg <= cmd[cnt]; state <= WRITE; state_back <= INIT; end end 5'd5:begin cnt_init <= 1'b0; state <= MAIN; end//初始化完成,返回MAIN状态 default: state <= IDLE; endcase end SCAN:begin//刷屏状态,从RAM中读取数据刷屏 if(cnt_scan == 5'd11) begin if(num) cnt_scan <= 5'd3; else cnt_scan <= cnt_scan + 1'b1; end else if(cnt_scan == 5'd12) cnt_scan <= 1'b0; else cnt_scan <= cnt_scan + 1'b1; case(cnt_scan) 5'd 0:begin OledDc <= CMD; char_reg <= y_p; state <= WRITE; state_back <= SCAN; end//定位列页地址 5'd 1:begin OledDc <= CMD; char_reg <= x_pl; state <= WRITE; state_back <= SCAN; end//定位行地址低位 5'd 2:begin OledDc <= CMD; char_reg <= x_ph; state <= WRITE; state_back <= SCAN; end//定位行地址高位 5'd 3:begin num <= num - 1'b1;end 5'd 4:begin OledDc <= DATA; char_reg <= 8'h00; state <= WRITE; state_back <= SCAN; end//将5*8点阵编程8*8 5'd 5:begin OledDc <= DATA; char_reg <= 8'h00; state <= WRITE; state_back <= SCAN; end//将5*8点阵编程8*8 5'd 6:begin OledDc <= DATA; char_reg <= 8'h00; state <= WRITE; state_back <= SCAN; end//将5*8点阵编程8*8 5'd 7:begin OledDc <= DATA; char_reg <= mem[char[(num*8)+:8]][39:32]; state <= WRITE; state_back <= SCAN; end 5'd 8:begin OledDc <= DATA; char_reg <= mem[char[(num*8)+:8]][31:24]; state <= WRITE; state_back <= SCAN; end 5'd 9:begin OledDc <= DATA; char_reg <= mem[char[(num*8)+:8]][23:16]; state <= WRITE; state_back <= SCAN; end 5'd10:begin OledDc <= DATA; char_reg <= mem[char[(num*8)+:8]][15: 8]; state <= WRITE; state_back <= SCAN; end 5'd11:begin OledDc <= DATA; char_reg <= mem[char[(num*8)+:8]][ 7: 0]; state <= WRITE; state_back <= SCAN; end 5'd12:begin state <= MAIN; end default: state <= IDLE; endcase end WRITE:begin//WRITE状态,将数据按照SPI时序发送给屏幕 if(cnt_write >= 5'd17) cnt_write <= 1'b0; else cnt_write <= cnt_write + 1'b1; case(cnt_write) 5'd 0:begin OledCs <= LOW; end//9位数据最高位为命令数据控制位 5'd 1:begin OledClk <= LOW; OledDin <= char_reg[7]; end//先发高位数据 5'd 2:begin OledClk <= HIGH; end 5'd 3:begin OledClk <= LOW; OledDin <= char_reg[6]; end 5'd 4:begin OledClk <= HIGH; end 5'd 5:begin OledClk <= LOW; OledDin <= char_reg[5]; end 5'd 6:begin OledClk <= HIGH; end 5'd 7:begin OledClk <= LOW; OledDin <= char_reg[4]; end 5'd 8:begin OledClk <= HIGH; end 5'd 9:begin OledClk <= LOW; OledDin <= char_reg[3]; end 5'd10:begin OledClk <= HIGH; end 5'd11:begin OledClk <= LOW; OledDin <= char_reg[2]; end 5'd12:begin OledClk <= HIGH; end 5'd13:begin OledClk <= LOW; OledDin <= char_reg[1]; end 5'd14:begin OledClk <= HIGH; end 5'd15:begin OledClk <= LOW; OledDin <= char_reg[0]; end//后发低位数据 5'd16:begin OledClk <= HIGH; end 5'd17:begin OledCs <= HIGH; state <= DELAY; end// default: state <= IDLE; endcase end DELAY:begin//延时状态 if(cnt_delay >= num_delay) begin cnt_delay <= 16'd0; state <= state_back; end else cnt_delay <= cnt_delay + 1'b1; end default:state <= IDLE; endcase end end //OLED配置指令数据 always@(posedge rst_n) begin cmd[ 0] = {8'hae}; cmd[ 1] = {8'h00}; cmd[ 2] = {8'h10}; cmd[ 3] = {8'h00}; cmd[ 4] = {8'hb0}; cmd[ 5] = {8'h81}; cmd[ 6] = {8'hff}; cmd[ 7] = {8'ha1}; cmd[ 8] = {8'ha6}; cmd[ 9] = {8'ha8}; cmd[10] = {8'h1f}; cmd[11] = {8'hc8}; cmd[12] = {8'hd3}; cmd[13] = {8'h00}; cmd[14] = {8'hd5}; cmd[15] = {8'h80}; cmd[16] = {8'hd9}; cmd[17] = {8'h1f}; cmd[18] = {8'hda}; cmd[19] = {8'h00}; cmd[20] = {8'hdb}; cmd[21] = {8'h40}; cmd[22] = {8'h8d}; cmd[23] = {8'h14}; cmd[24] = {8'haf}; end //5*8点阵字库数据 always@(posedge rst_n) begin mem[ 0] = {8'h3E, 8'h51, 8'h49, 8'h45, 8'h3E}; // 48 0 mem[ 1] = {8'h00, 8'h42, 8'h7F, 8'h40, 8'h00}; // 49 1 mem[ 2] = {8'h42, 8'h61, 8'h51, 8'h49, 8'h46}; // 50 2 mem[ 3] = {8'h21, 8'h41, 8'h45, 8'h4B, 8'h31}; // 51 3 mem[ 4] = {8'h18, 8'h14, 8'h12, 8'h7F, 8'h10}; // 52 4 mem[ 5] = {8'h27, 8'h45, 8'h45, 8'h45, 8'h39}; // 53 5 mem[ 6] = {8'h3C, 8'h4A, 8'h49, 8'h49, 8'h30}; // 54 6 mem[ 7] = {8'h01, 8'h71, 8'h09, 8'h05, 8'h03}; // 55 7 mem[ 8] = {8'h36, 8'h49, 8'h49, 8'h49, 8'h36}; // 56 8 mem[ 9] = {8'h06, 8'h49, 8'h49, 8'h29, 8'h1E}; // 57 9 mem[ 10] = {8'h7C, 8'h12, 8'h11, 8'h12, 8'h7C}; // 65 A mem[ 11] = {8'h7F, 8'h49, 8'h49, 8'h49, 8'h36}; // 66 B mem[ 12] = {8'h3E, 8'h41, 8'h41, 8'h41, 8'h22}; // 67 C mem[ 13] = {8'h7F, 8'h41, 8'h41, 8'h22, 8'h1C}; // 68 D mem[ 14] = {8'h7F, 8'h49, 8'h49, 8'h49, 8'h41}; // 69 E mem[ 15] = {8'h7F, 8'h09, 8'h09, 8'h09, 8'h01}; // 70 F mem[ 32] = {8'h00, 8'h00, 8'h00, 8'h00, 8'h00}; // 32 sp mem[ 33] = {8'h00, 8'h00, 8'h2f, 8'h00, 8'h00}; // 33 ! mem[ 34] = {8'h00, 8'h07, 8'h00, 8'h07, 8'h00}; // 34 mem[ 35] = {8'h14, 8'h7f, 8'h14, 8'h7f, 8'h14}; // 35 # mem[ 36] = {8'h24, 8'h2a, 8'h7f, 8'h2a, 8'h12}; // 36 $ mem[ 37] = {8'h62, 8'h64, 8'h08, 8'h13, 8'h23}; // 37 % mem[ 38] = {8'h36, 8'h49, 8'h55, 8'h22, 8'h50}; // 38 & mem[ 39] = {8'h00, 8'h05, 8'h03, 8'h00, 8'h00}; // 39 ' mem[ 40] = {8'h00, 8'h1c, 8'h22, 8'h41, 8'h00}; // 40 ( mem[ 41] = {8'h00, 8'h41, 8'h22, 8'h1c, 8'h00}; // 41 ) mem[ 42] = {8'h14, 8'h08, 8'h3E, 8'h08, 8'h14}; // 42 * mem[ 43] = {8'h08, 8'h08, 8'h3E, 8'h08, 8'h08}; // 43 + mem[ 44] = {8'h00, 8'h00, 8'hA0, 8'h60, 8'h00}; // 44 , mem[ 45] = {8'h08, 8'h08, 8'h08, 8'h08, 8'h08}; // 45 - mem[ 46] = {8'h00, 8'h60, 8'h60, 8'h00, 8'h00}; // 46 . mem[ 47] = {8'h20, 8'h10, 8'h08, 8'h04, 8'h02}; // 47 / mem[ 48] = {8'h3E, 8'h51, 8'h49, 8'h45, 8'h3E}; // 48 0 mem[ 49] = {8'h00, 8'h42, 8'h7F, 8'h40, 8'h00}; // 49 1 mem[ 50] = {8'h42, 8'h61, 8'h51, 8'h49, 8'h46}; // 50 2 mem[ 51] = {8'h21, 8'h41, 8'h45, 8'h4B, 8'h31}; // 51 3 mem[ 52] = {8'h18, 8'h14, 8'h12, 8'h7F, 8'h10}; // 52 4 mem[ 53] = {8'h27, 8'h45, 8'h45, 8'h45, 8'h39}; // 53 5 mem[ 54] = {8'h3C, 8'h4A, 8'h49, 8'h49, 8'h30}; // 54 6 mem[ 55] = {8'h01, 8'h71, 8'h09, 8'h05, 8'h03}; // 55 7 mem[ 56] = {8'h36, 8'h49, 8'h49, 8'h49, 8'h36}; // 56 8 mem[ 57] = {8'h06, 8'h49, 8'h49, 8'h29, 8'h1E}; // 57 9 mem[ 58] = {8'h00, 8'h36, 8'h36, 8'h00, 8'h00}; // 58 : mem[ 59] = {8'h00, 8'h56, 8'h36, 8'h00, 8'h00}; // 59 ; mem[ 60] = {8'h08, 8'h14, 8'h22, 8'h41, 8'h00}; // 60 < mem[ 61] = {8'h14, 8'h14, 8'h14, 8'h14, 8'h14}; // 61 = mem[ 62] = {8'h00, 8'h41, 8'h22, 8'h14, 8'h08}; // 62 > mem[ 63] = {8'h02, 8'h01, 8'h51, 8'h09, 8'h06}; // 63 ? mem[ 64] = {8'h32, 8'h49, 8'h59, 8'h51, 8'h3E}; // 64 @ mem[ 65] = {8'h7C, 8'h12, 8'h11, 8'h12, 8'h7C}; // 65 A mem[ 66] = {8'h7F, 8'h49, 8'h49, 8'h49, 8'h36}; // 66 B mem[ 67] = {8'h3E, 8'h41, 8'h41, 8'h41, 8'h22}; // 67 C mem[ 68] = {8'h7F, 8'h41, 8'h41, 8'h22, 8'h1C}; // 68 D mem[ 69] = {8'h7F, 8'h49, 8'h49, 8'h49, 8'h41}; // 69 E mem[ 70] = {8'h7F, 8'h09, 8'h09, 8'h09, 8'h01}; // 70 F mem[ 71] = {8'h3E, 8'h41, 8'h49, 8'h49, 8'h7A}; // 71 G mem[ 72] = {8'h7F, 8'h08, 8'h08, 8'h08, 8'h7F}; // 72 H mem[ 73] = {8'h00, 8'h41, 8'h7F, 8'h41, 8'h00}; // 73 I mem[ 74] = {8'h20, 8'h40, 8'h41, 8'h3F, 8'h01}; // 74 J mem[ 75] = {8'h7F, 8'h08, 8'h14, 8'h22, 8'h41}; // 75 K mem[ 76] = {8'h7F, 8'h40, 8'h40, 8'h40, 8'h40}; // 76 L mem[ 77] = {8'h7F, 8'h02, 8'h0C, 8'h02, 8'h7F}; // 77 M mem[ 78] = {8'h7F, 8'h04, 8'h08, 8'h10, 8'h7F}; // 78 N mem[ 79] = {8'h3E, 8'h41, 8'h41, 8'h41, 8'h3E}; // 79 O mem[ 80] = {8'h7F, 8'h09, 8'h09, 8'h09, 8'h06}; // 80 P mem[ 81] = {8'h3E, 8'h41, 8'h51, 8'h21, 8'h5E}; // 81 Q mem[ 82] = {8'h7F, 8'h09, 8'h19, 8'h29, 8'h46}; // 82 R mem[ 83] = {8'h46, 8'h49, 8'h49, 8'h49, 8'h31}; // 83 S mem[ 84] = {8'h01, 8'h01, 8'h7F, 8'h01, 8'h01}; // 84 T mem[ 85] = {8'h3F, 8'h40, 8'h40, 8'h40, 8'h3F}; // 85 U mem[ 86] = {8'h1F, 8'h20, 8'h40, 8'h20, 8'h1F}; // 86 V mem[ 87] = {8'h3F, 8'h40, 8'h38, 8'h40, 8'h3F}; // 87 W mem[ 88] = {8'h63, 8'h14, 8'h08, 8'h14, 8'h63}; // 88 X mem[ 89] = {8'h07, 8'h08, 8'h70, 8'h08, 8'h07}; // 89 Y mem[ 90] = {8'h61, 8'h51, 8'h49, 8'h45, 8'h43}; // 90 Z mem[ 91] = {8'h00, 8'h7F, 8'h41, 8'h41, 8'h00}; // 91 [ mem[ 92] = {8'h55, 8'h2A, 8'h55, 8'h2A, 8'h55}; // 92 . mem[ 93] = {8'h00, 8'h41, 8'h41, 8'h7F, 8'h00}; // 93 ] mem[ 94] = {8'h04, 8'h02, 8'h01, 8'h02, 8'h04}; // 94 ^ mem[ 95] = {8'h40, 8'h40, 8'h40, 8'h40, 8'h40}; // 95 _ mem[ 96] = {8'h00, 8'h01, 8'h02, 8'h04, 8'h00}; // 96 ' mem[ 97] = {8'h20, 8'h54, 8'h54, 8'h54, 8'h78}; // 97 a mem[ 98] = {8'h7F, 8'h48, 8'h44, 8'h44, 8'h38}; // 98 b mem[ 99] = {8'h38, 8'h44, 8'h44, 8'h44, 8'h20}; // 99 c mem[100] = {8'h38, 8'h44, 8'h44, 8'h48, 8'h7F}; // 100 d mem[101] = {8'h38, 8'h54, 8'h54, 8'h54, 8'h18}; // 101 e mem[102] = {8'h08, 8'h7E, 8'h09, 8'h01, 8'h02}; // 102 f mem[103] = {8'h18, 8'hA4, 8'hA4, 8'hA4, 8'h7C}; // 103 g mem[104] = {8'h7F, 8'h08, 8'h04, 8'h04, 8'h78}; // 104 h mem[105] = {8'h00, 8'h44, 8'h7D, 8'h40, 8'h00}; // 105 i mem[106] = {8'h40, 8'h80, 8'h84, 8'h7D, 8'h00}; // 106 j mem[107] = {8'h7F, 8'h10, 8'h28, 8'h44, 8'h00}; // 107 k mem[108] = {8'h00, 8'h41, 8'h7F, 8'h40, 8'h00}; // 108 l mem[109] = {8'h7C, 8'h04, 8'h18, 8'h04, 8'h78}; // 109 m mem[110] = {8'h7C, 8'h08, 8'h04, 8'h04, 8'h78}; // 110 n mem[111] = {8'h38, 8'h44, 8'h44, 8'h44, 8'h38}; // 111 o mem[112] = {8'hFC, 8'h24, 8'h24, 8'h24, 8'h18}; // 112 p mem[113] = {8'h18, 8'h24, 8'h24, 8'h18, 8'hFC}; // 113 q mem[114] = {8'h7C, 8'h08, 8'h04, 8'h04, 8'h08}; // 114 r mem[115] = {8'h48, 8'h54, 8'h54, 8'h54, 8'h20}; // 115 s mem[116] = {8'h04, 8'h3F, 8'h44, 8'h40, 8'h20}; // 116 t mem[117] = {8'h3C, 8'h40, 8'h40, 8'h20, 8'h7C}; // 117 u mem[118] = {8'h1C, 8'h20, 8'h40, 8'h20, 8'h1C}; // 118 v mem[119] = {8'h3C, 8'h40, 8'h30, 8'h40, 8'h3C}; // 119 w mem[120] = {8'h44, 8'h28, 8'h10, 8'h28, 8'h44}; // 120 x mem[121] = {8'h1C, 8'hA0, 8'hA0, 8'hA0, 8'h7C}; // 121 y mem[122] = {8'h44, 8'h64, 8'h54, 8'h4C, 8'h44}; // 122 z end endmodule
Oled代码根据电子森林内提供的示例代码进行了少许的修改,能够正常、稳定地显示数据。
三、项目实施经验
串口模块非常容易产生误码,并且不适宜进行长时间的持续通信,我认为可以采用的方法有两种,一种是增加冗余信息,校验数据。另外一种是降低通信频率,减少积累效应。
我采用的方法是降低通信频率并重复发送数据,每0.2秒发送一次数据,降低了积累的延迟导致的数据错误问题。事实证明这种方法非常有效,数据非常稳定。