2025寒假练 - 基于iCE40UP5K的FPGA学习平台设计可定时的音乐时钟
该项目使用了Lattice Radiant软件、Verilog语言,实现了一个可定时的音乐闹钟设计的设计,它的主要功能为:基于iCE40UP5K的FPGA学习平台,通过板卡自带时钟在OLED上显示当前时间和闹钟时间,以及用ws2812灯显示出当前时间的小时信息,闹钟时间可以通过板卡上的两个按键进行调节,当前时间到达设计的闹钟时,蜂鸣器播放ws2812彩灯旋转。
标签
FPGA
ICE40UP5K
2025寒假在家练
可定时的音乐闹钟
Aulin_02
更新2025-03-20
北京理工大学
18

项目描述

项目介绍

本文中,我们将通过硬禾学堂的“基于iCE40UP5K的FPGA学习平台”开发板来实现一个可定时的音乐闹钟设计。

项目需求

  1. 在OLED显示屏上通过模拟或者数字的方式显示当前的时间 - 小时、分、秒
  2. 使用扩展板上的12颗彩灯对应12个小时将“小时”的信息通过12颗彩灯来显示
  3. 具有定时的功能,通过扩展板上的按键设置时间,到该时间点即(彩灯闪烁 + 音频播放),持续5秒钟时间

硬件介绍

该项目硬件使用的是基于ICE40UP5K的FPGA学习平台。核心板为基于Lattice的ICE40UP5K FPGA,板载LPC11U35下载器,可以通过U盘形式拖拽rbt文件烧录代码方便快捷。拓展板上主要使用到的器件包含12个WS2812B RGB三色灯,一个6针4线128*64 OLED显示屏,两个按键输入和1个复位键输入(Run键),1个蜂鸣器;部分器件的原理图如下:

  • WS2812流水灯:


97ed252ec2acad3eb9ca538a3f93f8d.png

  • 蜂鸣器:

bb26ef6c96f315f3011e795060fb2fc.png



设计思路

首先利用板卡自带时钟利用时钟模块CLOCK生成一个时间信息(24小时制),再设计一个alarm模块,在该模块中通过CLOCK模块输入的时间信息和经过debounce模块消抖后的按键输入,生成闹钟的时间信息以及alarm_flag到达设定闹钟时间的标志,alarm_flag传递给WS2812和BEEP模块用来驱动流水灯闪烁和蜂鸣器播放音乐,此外WS2812模块还接受CLOCK模块的小时信息以供流水灯显示对应的小时信息;OLED模块则接受CLOCK和ALARM两个模块的时间信息并在屏幕上显示,大致流程如下框图所示:

4f33b10d621c6b7a0a64c33f93107a6.png

功能展示

项目实现OLED显示当前时间和闹钟时间,ws2812流水灯根据小时信息点亮对应的序号的彩灯,0-11小时彩灯为绿色,12-23小时彩灯显示红色。

e85ad19cf9633bb74451c71bdd4abb8.jpg

时钟时间到达闹钟设定的时间时,灯圈闪烁旋转,蜂鸣器播放音乐
0348af51babd61e7d3739cbb0dcd004.jpg

软件流程图和主要代码介绍

软件流程图:

主要代码介绍:

本设计的代码共7个模块,分别为顶层例化模块top、时钟生成模块clock_generator、闹钟生成模块alarm_generator、按键消抖模块key_debounce、WS2812流水灯驱动模块ws2812_driver、BEEP音频播放模块beep_music以及OLED显示模块oled_display;

本节重点介绍alarm_generator模块,对于clock_generatorbeep_music、key_debounce模块由于代码比较简洁思路也很清晰不做过多赘述,而对于ws2812模块和OLED显示模块由于是基于示例进行修改的只重点讲解修改的部分。

时钟生成模块:主要思路就是根据系统时钟频率12MHz和一个计数器来逐步递增秒、分、时,可以设定按下复位后的时间初始值。

module clock_generator (
input wire clk, // 系统时钟输入 12 MHz
input wire rst_n, // 系统复位输入(低电平有效)
output reg [5:0] second, // 秒计数输出
output reg [5:0] minute, // 分钟计数输出
output reg [4:0] hour // 秒计数输出
);

reg [23:0] tick_counter; // 计时器,计时到tick_max,即tick_max+1个周期second增加1

parameter tick_max = 11999999;//11999999原速、199999加快60倍1s=1minute、9999加快1200倍即3s=1h

always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
tick_counter <= 0;
second <= 6'd50;
minute <= 6'd59;
hour <= 5'd5;
end else begin
tick_counter <= tick_counter + 1;
if (tick_counter >= tick_max) begin // 1 Hz 计数周期
tick_counter <= 0;
second <= second + 1;
if (second == 6'd59) begin
second <= 0;
minute <= minute + 1;
if (minute == 6'd59) begin
minute <= 0;
hour <= hour + 1;
if (hour == 5'd23) begin
hour <= 0;
end
end
end
end
end
end
endmodule

按键消抖模块:只有两个按键输入,输入key_in消抖后的输出key_out

module key_debounce(
input clk, // 时钟12MHz
input rst_n, // 低电平复位
input [1:0] key_in, // 按键输入(低电平有效)
output reg [1:0] key_out // 消抖后上升沿脉冲
);

// 参数定义
localparam KEY_WIDTH = 1; // 表示2位按键([1:0])
localparam DEBOUNCE_CYCLES = 240_000; // 消抖时间20ms,系统时钟12MHz

// 寄存器定义
reg [KEY_WIDTH:0] key_sync, key_stable, key_prev;
reg [19:0] cnt;

//计数器与采样控制
always @(posedge clk or negedge rst_n) begin
if (!rst_n) cnt <= 0;
else cnt <= (cnt == DEBOUNCE_CYCLES-1) ? 0 : cnt + 1;
end

//按键消抖逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
key_sync <= ~0;
key_stable <= ~0;
key_prev <= ~0;
end else begin
// 低频采样(每20ms采样一次)
if (cnt == DEBOUNCE_CYCLES-1) begin
key_sync <= key_in; // 当前采样值
key_stable <= (key_sync == key_in) ? key_in : key_stable; // 稳定性检查
end
key_prev <= key_stable; // 延迟一拍用于边沿检测
end
end

//边沿检测
always @(posedge clk or negedge rst_n) begin
if (!rst_n) key_out <= 0;
else key_out <= key_stable & ~key_prev; // 上升沿脉冲
end

endmodule

beep_music模块,根据闹钟模块生成的alarm_flag作为蜂鸣器播放的标志,主要设计点见注释。

module beep_music (
input clk, // 12MHz 系统时钟
input rst_n, // 系统复位,低有效
input alarm_flag, //闹钟标识
output reg beep //蜂鸣器输出
);

parameter SPEED = 3'd2; //控制演奏速度,如果是2,频率就是4Hz,一个音符的持续时间为1/4 = 0.25s
parameter COUNTER_4 = 12000000/SPEED/2-1;// = 1499999,演奏频率分频比
parameter LENGTH = 6'd20; //总共演奏音符的数量
//------音阶分频预置数------
//预置数 = 32767-(12000000/音阶频率/2-1)
//分频比 = 12000000/音阶频率/2-1
//15位是由于:低音1的频率为261.6Hz,最大的分频比为12MHz/261.6Hz/2-1 = 22935,2^15是满足要求的最小位数
localparam REST = 15'd32767, //休止符,不发出声音2^15-1 = 32767

C_LOW = 15'd9832, // 低音C,32767 - 22935 = 9832
D_LOW = 15'd12339, // 低音D,32767 - 20428 = 12339
E_LOW = 15'd14564, // 低音E,32767 - 18203 = 14564
F_LOW = 15'd15586, // 低音F,32767 - 17181 = 15586
G_LOW = 15'd17462, // 低音G,32767 - 15305 = 17462
A_LOW = 15'd19132, // 低音A,32767 - 13635 = 19132
B_LOW = 15'd20620, // 低音B,32767 - 12147 = 20620

C_MID = 15'd21303, // 中音C,32767 - 11464 = 21303
D_MID = 15'd22552, // 中音D,32767 - 10215 = 22552
E_MID = 15'd23667, // 中音E,32767 - 9100 = 23667
F_MID = 15'd24178, // 中音F,32767 - 8589 = 24178
G_MID = 15'd25115, // 中音G,32767 - 7652 = 25115
A_MID = 15'd25950, // 中音A,32767 - 6817 = 25950
B_MID = 15'd26694, // 中音B,32767 - 6073 = 26694

C_HIGH = 15'd27027, // 高音C,32767 - 5740 = 27027
D_HIGH = 15'd27660, // 高音D,32767 - 5107 = 27660
E_HIGH = 15'd28218, // 高音E,32767 - 4549 = 28218
F_HIGH = 15'd28473, // 高音F,32767 - 4294 = 28473
G_HIGH = 15'd28942, // 高音G,32767 - 3825 = 28942
A_HIGH = 15'd29359, // 高音A,32767 - 3408 = 29359
B_HIGH = 15'd29731; // 高音B,32767 - 3036 = 29731

// 主控制逻辑
reg [23:0] note_cnt; // 音符持续时间计数器
reg [5:0] cnt_l; // 当前音符索引
reg [4:0] music_scale; // 当前音阶
reg playing; // 播放状态

always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
playing <= 0;
cnt_l <= LENGTH;
music_scale <= 0;
note_cnt <= 0;
end else begin
//alarm_flag为0立即停止播放
if (!alarm_flag) begin
playing <= 0;
cnt_l <= LENGTH; // 停止播放
music_scale <= 0; // 设为休止符
note_cnt <= 0;
end
//alarm_flag为1时
else begin
// 若未在播放,立即启动
if (!playing) begin
playing <= 1;
cnt_l <= 0;
note_cnt <= 0;
music_scale <= 5'd17; // 提前设置下一个音符(避免时钟延迟)
end
// 播放中
else if (note_cnt == COUNTER_4) begin
note_cnt <= 0;
if (cnt_l < LENGTH) begin
cnt_l <= cnt_l + 1;
//乐谱:威斯敏斯特钟声
case (cnt_l + 1)
6'd0 : music_scale <= 5'd17;
6'd1 : music_scale <= 5'd17;
6'd2 : music_scale <= 5'd15;
6'd3 : music_scale <= 5'd15;
6'd4 : music_scale <= 5'd16;
6'd5 : music_scale <= 5'd16;
6'd6 : music_scale <= 5'd12;
6'd7 : music_scale <= 5'd12;
6'd8 : music_scale <= 5'd12;
6'd9 : music_scale <= 1'b0 ;
6'd10: music_scale <= 5'd12;
6'd11: music_scale <= 5'd12;
6'd12: music_scale <= 5'd16;
6'd13: music_scale <= 5'd16;
6'd14: music_scale <= 5'd17;
6'd15: music_scale <= 5'd17;
6'd16: music_scale <= 5'd15;
6'd17: music_scale <= 5'd15;
6'd18: music_scale <= 5'd15;
6'd19: music_scale <= 5'd15;
6'd20: music_scale <= 1'b0 ;//休止符不发声
default: music_scale <= 0;
endcase
end else begin
playing <= 0; // 播放完成
end
end else begin
note_cnt <= note_cnt + 1;
end
end
end
end

// 蜂鸣器驱动(实时更新频率)
reg [14:0] tone_cnt;

always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
tone_cnt <= REST;
beep <= 0;
end else begin
if (tone_cnt == REST) begin
tone_cnt <= (music_scale == 0 ) ? REST : // 休止符
(music_scale == 1 ) ? C_LOW : // 低音C
(music_scale == 2 ) ? D_LOW : // 低音D
(music_scale == 3 ) ? E_LOW : // 低音E
(music_scale == 4 ) ? F_LOW : // 低音F
(music_scale == 5 ) ? G_LOW : // 低音G
(music_scale == 6 ) ? A_LOW : // 低音A
(music_scale == 7 ) ? B_LOW : // 低音B
(music_scale == 8 ) ? C_MID : // 中音C
(music_scale == 9 ) ? D_MID : // 中音D
(music_scale == 10) ? E_MID : // 中音E
(music_scale == 11) ? F_MID : // 中音F
(music_scale == 12) ? G_MID : // 中音G
(music_scale == 13) ? A_MID : // 中音A
(music_scale == 14) ? B_MID : // 中音B
(music_scale == 15) ? C_HIGH : // 高音C
(music_scale == 16) ? D_HIGH : // 高音D
(music_scale == 17) ? E_HIGH : // 高音E
(music_scale == 18) ? F_HIGH : // 高音F
(music_scale == 19) ? G_HIGH : // 高音G
(music_scale == 20) ? A_HIGH : // 高音A
(music_scale == 21) ? B_HIGH : // 高音B
REST; // 休止符
beep <= ~beep;
end else
tone_cnt <= tone_cnt + 1;
end
end

endmodule

闹钟生成模块alarm_generator,该模块根据时钟模块输入的时间信息和消抖模块消抖后的按键输入信息生成闹钟标志和闹钟时间信息。

这是输入、输出变量的定义

module alarm_generator(
input clk,
input rst_n,
input [1:0] key_out,
input [5:0] second,
input [5:0] minute,
input [4:0] hour,
output reg [2:0] Model,
output reg [4:0] alarm_hour,
output reg [5:0] alarm_minute,
output reg [5:0] alarm_second,
output reg alarm_flag
);

在该时序逻辑中,通过检测按键按下的次数来改变model的值切换不同状态。

//状态控制逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
Model <= 0;
else begin
case ({key_out[0], Model})
// 模式切换键按下时循环递增状态
4'b1_000: Model <= 3'd1; // 0 → 1
4'b1_001: Model <= 3'd2; // 1 → 2
4'b1_010: Model <= 3'd3; // 2 → 3
4'b1_011: Model <= 3'd0; // 3 → 0
default: Model <= Model; // 保持状态
endcase
end
end

在该时序逻辑中,可以设置闹钟时间的初始值,通过另一按键的输出次数以及model的状态来改变对应的时间信息

//时间设置逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
alarm_hour <= 5'd6; // 默认6点
alarm_minute <= 6'd0;
alarm_second <= 6'd5;
end else if (key_out[1]) begin // 增量键按下
case (Model)
3'd1: alarm_hour <= (alarm_hour >= 23) ? 0 : alarm_hour + 1; // 小时设置
3'd2: alarm_minute <= (alarm_minute >= 59) ? 0 : alarm_minute + 1; // 分钟设置
3'd3: alarm_second <= (alarm_second >= 59) ? 0 : alarm_second + 1; // 秒设置
endcase
end
end

在下面这段中生成闹钟的标识信息alarm_flag和控制其持续时间题目要求为5s

// 参数定义(系统时钟为12MHz)
parameter CLOCKS_PER_SECOND = 12000000; // 12MHz下的1秒计数值
parameter ALARM_DURATION = 4; // 闹铃持续5秒,从0开始计数

reg [25:0] alarm_timer; // 26-bit以保证足够计数5秒(12M*5=60M < 2^26=67M)
//闹铃触发逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
alarm_flag <= 0;
alarm_timer <= 0;
end else begin
// 触发条件:时间匹配且不在设置模式
if ((alarm_hour == hour) && (alarm_minute == minute) && (alarm_second == second) && (Model == 0)) begin
alarm_flag <= 1;
alarm_timer <= 0;
end
// 持续计数直到5秒
else if (alarm_flag) begin
if (alarm_timer == CLOCKS_PER_SECOND * ALARM_DURATION-1) begin
alarm_flag <= 0;
alarm_timer <= 0;
end else begin
alarm_timer <= alarm_timer + 1;
end
end
end
end

ws2812_driver驱动模块

主要修改地方如下:

1、多定义了些颜色变量,以及基于hour生成了一个num表示显示的ws2812流水灯的序号

//颜色常量的定义,反向取值从0-23为高位至低位GRB的顺序
localparam Red = 24'h00FF00, //红
Orange = 24'h00FFA5, //橙
Green = 24'h000001, //绿
Cyan = 24'hFF00FF, //青
Blue = 24'hFF0000, //蓝
Purple = 24'h010100; //紫
//我所增加的变量
wire [4:0] num = (hour < 5'd12) ? hour : (hour - 5'd12); //由于时间有24个小时0-23,但灯只有12个,故增加变量num表示灯的序号0对应12,其余一一对应
reg [23:0] ledcolor [5:0];//储存6个彩灯颜色

2、
修改了状态机中FSM2的转移条件,增加了限制当alarm_flag为1时,随着cnt的计数,变换0/1;否则根据num的值点亮对应小时的灯,0-11点为绿色,12-23点为红色

shift <= (alarm_flag == 1) ? ledcolor[1][bit_cnt] : ((num != 1) ? 0 : ((hour < 12) ? Green[bit_cnt] : Red[bit_cnt]));


oled_display显示模块

对于显示模块的修改主要有如下两处

1、主状态的修改,扩充了汉字库的RAM,增加了显示闹钟调整时的闪烁功能主要用到的参数为blink的最高位

		//控制闹钟时间调整时的闪烁,根据最高位控制亮灭
reg [23:0] blink;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)begin
blink <= 0;
end else begin
blink <= blink+1;
end
end

always @ (posedge clk 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;cnt_chinese <= 1'b0;cnt_digit <= 1'b0;
y_p <= 1'b0; x_ph <= 1'b0; x_pl <= 1'b0;
char <= " "; char_reg <= 1'b0;
num_delay <= 16'd5; cnt_delay <= 1'b0; cnt <= 1'b0;
oled_rst <= HIGH; oled_dcn <= CMD; oled_clk <= HIGH; oled_dat <= LOW;
state <= IDLE; state_back <= IDLE;
end else begin
case(state)
IDLE:begin
cnt_main <= 1'b0; cnt_init <= 1'b0; cnt_write <= 1'b0;
y_p <= 1'b0; x_ph <= 1'b0; x_pl <= 1'b0;
num <= 1'b0; char <= " "; char_reg <= 1'b0; cnt <= 1'b0;mem_hanzi_num<=8'd0;
num_delay <= 16'd5; cnt_delay <= 1'b0;
oled_rst <= HIGH; oled_dcn <= CMD; oled_clk <= HIGH; oled_dat <= LOW;
state <= MAIN; state_back <= MAIN;
end
MAIN:begin
if(cnt_main >= 6'd32) cnt_main <= 6'd17;//
else cnt_main <= cnt_main + 1'b1;
case(cnt_main) //MAIN状态
6'd0 : begin state <= INIT; end

6'd1 : begin y_p <= 8'hb0; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " ";state <= SCAN; end
6'd2 : begin y_p <= 8'hb1; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " ";state <= SCAN; end
6'd3 : begin y_p <= 8'hb2; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " ";state <= SCAN; end
6'd4 : begin y_p <= 8'hb3; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " ";state <= SCAN; end
6'd5 : begin y_p <= 8'hb4; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " ";state <= SCAN; end
6'd6 : begin y_p <= 8'hb5; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " ";state <= SCAN; end
6'd7 : begin y_p <= 8'hb6; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " ";state <= SCAN; end
6'd8 : begin y_p <= 8'hb7; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " ";state <= SCAN; end
6'd9 : begin y_p <= 8'hb0; x_ph <= 8'h12; x_pl <= 8'h00; mem_hanzi_num <= 8'd0; state <= CHINESE; end
6'd10: begin y_p <= 8'hb0; x_ph <= 8'h13; x_pl <= 8'h00; mem_hanzi_num <= 8'd2; state <= CHINESE; end
6'd11: begin y_p <= 8'hb0; x_ph <= 8'h14; x_pl <= 8'h00; mem_hanzi_num <= 8'd4; state <= CHINESE; end
6'd12: begin y_p <= 8'hb0; x_ph <= 8'h15; x_pl <= 8'h00; mem_hanzi_num <= 8'd6; state <= CHINESE; end
6'd13: begin y_p <= 8'hb4; x_ph <= 8'h12; x_pl <= 8'h00; mem_hanzi_num <= 8'd8; state <= CHINESE; end
6'd14: begin y_p <= 8'hb4; x_ph <= 8'h13; x_pl <= 8'h00; mem_hanzi_num <= 8'd10; state <= CHINESE; end
6'd15: begin y_p <= 8'hb4; x_ph <= 8'h14; x_pl <= 8'h00; mem_hanzi_num <= 8'd12; state <= CHINESE; end
6'd16: begin y_p <= 8'hb4; x_ph <= 8'h15; x_pl <= 8'h00; mem_hanzi_num <= 8'd14; state <= CHINESE; end

6'd17: begin y_p <= 8'hb2; x_ph <= 8'h12; x_pl <= 8'h00; num <= hour/10; state <= DIGIT; end
6'd18: begin y_p <= 8'hb2; x_ph <= 8'h12; x_pl <= 8'h08; num <= hour%10; state <= DIGIT; end
6'd19: begin y_p <= 8'hb2; x_ph <= 8'h13; x_pl <= 8'h00; num <= 8'd10; state <= DIGIT; end
6'd20: begin y_p <= 8'hb2; x_ph <= 8'h13; x_pl <= 8'h08; num <= minute/10; state <= DIGIT; end
6'd21: begin y_p <= 8'hb2; x_ph <= 8'h14; x_pl <= 8'h00; num <= minute%10; state <= DIGIT; end
6'd22: begin y_p <= 8'hb2; x_ph <= 8'h14; x_pl <= 8'h08; num <= 8'd10; state <= DIGIT; end
6'd23: begin y_p <= 8'hb2; x_ph <= 8'h15; x_pl <= 8'h00; num <= second/10; state <= DIGIT; end
6'd24: begin y_p <= 8'hb2; x_ph <= 8'h15; x_pl <= 8'h08; num <= second%10; state <= DIGIT; end
6'd25: begin y_p <= 8'hb6; x_ph <= 8'h12; x_pl <= 8'h00; num <= (Model==1 && blink[23]) ? 11 : alarm_hour/10; state <= DIGIT; end
6'd26: begin y_p <= 8'hb6; x_ph <= 8'h12; x_pl <= 8'h08; num <= (Model==1 && blink[23]) ? 11 : alarm_hour%10; state <= DIGIT; end
6'd27: begin y_p <= 8'hb6; x_ph <= 8'h13; x_pl <= 8'h00; num <= 8'd10; state <= DIGIT; end
6'd28: begin y_p <= 8'hb6; x_ph <= 8'h13; x_pl <= 8'h08; num <= (Model==2 && blink[23]) ? 11 : alarm_minute/10; state <= DIGIT; end
6'd29: begin y_p <= 8'hb6; x_ph <= 8'h14; x_pl <= 8'h00; num <= (Model==2 && blink[23]) ? 11 : alarm_minute%10; state <= DIGIT; end
6'd30: begin y_p <= 8'hb6; x_ph <= 8'h14; x_pl <= 8'h08; num <= 8'd10; state <= DIGIT; end
6'd31: begin y_p <= 8'hb6; x_ph <= 8'h15; x_pl <= 8'h00; num <= (Model==3 && blink[23]) ? 11 : alarm_second/10; state <= DIGIT; end
6'd32: begin y_p <= 8'hb6; x_ph <= 8'h15; x_pl <= 8'h08; num <= (Model==3 && blink[23]) ? 11 : alarm_second%10; state <= DIGIT; end

default: state <= IDLE; //如果你需要动态刷新一些信息,此行应该取消注释
endcase
end


2、新增状态以显示时钟信息和闹钟信息:

由于直接用示例代码显示会出现汉字和数字不同同时显示的问题,新增了一个状态用以专门显示时间信息,该状态的代码参考原示例的chinense结构,并将显示的字符大小改为8*16

DIGIT:begin				//显示时间字符		
if(cnt_digit == 6'd22) cnt_digit <= 1'b0;
else cnt_digit <= cnt_digit+1'b1;
case(cnt_digit)
6'd 0: begin oled_dcn <= CMD; char_reg <= y_p; state <= WRITE; state_back <= DIGIT; end
6'd 1: begin oled_dcn <= CMD; char_reg <= x_pl; state <= WRITE; state_back <= DIGIT; end
6'd 2: begin oled_dcn <= CMD; char_reg <= x_ph; state <= WRITE; state_back <= DIGIT; end
6'd3 : begin oled_dcn <= DATA; char_reg <= words[num][127:120]; state <= WRITE; state_back <= DIGIT; end
6'd4 : begin oled_dcn <= DATA; char_reg <= words[num][119:112]; state <= WRITE; state_back <= DIGIT; end
6'd5 : begin oled_dcn <= DATA; char_reg <= words[num][111:104]; state <= WRITE; state_back <= DIGIT; end
6'd6 : begin oled_dcn <= DATA; char_reg <= words[num][103:96] ; state <= WRITE; state_back <= DIGIT; end
6'd7 : begin oled_dcn <= DATA; char_reg <= words[num][95:88] ; state <= WRITE; state_back <= DIGIT; end
6'd8 : begin oled_dcn <= DATA; char_reg <= words[num][87:80] ; state <= WRITE; state_back <= DIGIT; end
6'd9 : begin oled_dcn <= DATA; char_reg <= words[num][79:72] ; state <= WRITE; state_back <= DIGIT; end
6'd10: begin oled_dcn <= DATA; char_reg <= words[num][71:64] ; state <= WRITE; state_back <= DIGIT; end

6'd11: begin oled_dcn <= CMD; char_reg <= y_p+1; state <= WRITE; state_back <= DIGIT; end
6'd12: begin oled_dcn <= CMD; char_reg <= x_pl; state <= WRITE; state_back <= DIGIT; end
6'd13: begin oled_dcn <= CMD; char_reg <= x_ph; state <= WRITE; state_back <= DIGIT; end
6'd14: begin oled_dcn <= DATA; char_reg <= words[num][63:56]; state <= WRITE; state_back <= DIGIT; end
6'd15: begin oled_dcn <= DATA; char_reg <= words[num][55:48]; state <= WRITE; state_back <= DIGIT; end
6'd16: begin oled_dcn <= DATA; char_reg <= words[num][47:40]; state <= WRITE; state_back <= DIGIT; end
6'd17: begin oled_dcn <= DATA; char_reg <= words[num][39:32]; state <= WRITE; state_back <= DIGIT; end
6'd18: begin oled_dcn <= DATA; char_reg <= words[num][31:24]; state <= WRITE; state_back <= DIGIT; end
6'd19: begin oled_dcn <= DATA; char_reg <= words[num][23:16]; state <= WRITE; state_back <= DIGIT; end
6'd20: begin oled_dcn <= DATA; char_reg <= words[num][15: 8]; state <= WRITE; state_back <= DIGIT; end
6'd21: begin oled_dcn <= DATA; char_reg <= words[num][ 7: 0]; state <= WRITE; state_back <= DIGIT; end

6'd22: begin state <= MAIN; end
default: state <= IDLE;
endcase
end

资源报告及管脚分配

资源利用报告

8ba288ec40737838139a53524c75ae6.png

db7d74f37fb016ad3d12166d5d5c04b.png

管脚分配

56218b9a6a2bb318ef2797ec40efcda.png

遇见的问题及心得体会

在完成项目的过程中我遇到了许多问题,第一个便是软件安装以及使用由于Radiant推出时间并不太长,网上教程比较少在软件的安装上花费了几天的时间,最后在官网的帮助里找到了相应的解决方案。其次便是OLED显示模块的撰写遇到了较多问题,由于电子森林给出的示例代码有些小错误加之我对于verilog驱动OLED不是很熟悉,在OLED显示模块花费了大量的时间查找文献,一步步学习OLED的显示驱动,最终得以完成项目。经过本次活动,我对于verilog语法有了更深的理解,能将自身所学用以实际-同时也提升了我对FPGA的设计能力,希望以后能多多参与此类活动。

附件下载
project.zip
基于ICE40UP5K的工程代码
团队介绍
张云凯
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号