2025寒假练 - 基于iCE40UP5K的FPGA学习平台设计可定时的音乐时钟
该项目使用了基于iCE40UP5K的FPGA学习平台,实现了可定时的音乐时钟的设计,它的主要功能为:12颗彩灯显示小时信息,按键设置现在/闹钟时间,闹钟可通过音频播放。
标签
FPGA
ICE40UP5K
数字逻辑
寒假练
Enriedo
更新2025-03-19
北京理工大学
22

一、项目介绍

本项目基于iCE40UP5KFPGA学习平台设计可定时的音乐时钟。任务的要求为:

1.使用扩展板上的12颗彩灯对应于12个小时;

2.核心板上的FPGA产生时钟,在OLED显示屏上通过模拟或者数字的方式显示当前的时间 - 小时、分、秒;

3.将“小时”的信息通过12颗彩灯来显示,效果自行设计;

4.具有定时的功能,通过扩展板上的按键设置时间,到该时间点即(彩灯闪烁 + 音频播放),持续5秒钟时间;

5.音频播放通过扩展板上的蜂鸣器来实现。

二、硬件介绍

2.1 核心板

核心板为基于LatticeFPGA - iCE40UP5K,板上有一颗RGB三色LED,分别连接FPGA的三根专用于驱动LED的管脚394041,可以用于状态显示及数字逻辑实验;一个RESET按键,用于对RISC-V系统进行复位;总计28IO用于扩展使用;板上晶体振荡器时钟产生12MHzFPGA工作,FPGA可以通过内部锁相环工作于48MHz

2.2 扩展板

扩展板上包括:2个按键输入;4个单色LED12WS2812B RGB三色灯;1个姿态传感器;1128*64 OLED显示屏;1个蜂鸣器等。

2.3 板卡特性

具有灵活的逻辑架构,拥有280052804输入LUT、自定义I/O、多达80 Kb1Mb的嵌入式存储器;

超低功耗的先进工艺,睡眠电流低至75 uA,工作电流仅为1-10mA

使用DSP模块实现高性能信号处理,支持乘法和累加功能;

神经网络软IP和编译器实现灵活的机器学习/人工智能应用。

三、方案框图和项目设计思路

image.png


上图展示了项目代码的组织结构,top为主模块;debounce为按键消抖模块;ws281212个彩灯显示模块;oled为文字显示模块;key为按键设置模块;buzzer_module为蜂鸣器定义模块;time_cal为时间计算模块,其中又用到了time_difference时间差计算与beeper_ctrl蜂鸣器控制两个模块的内容。

四、软件流程图及关键代码介绍


//当前时间设置模式
    always @(posedge clk or negedge rst) begin
        if (!rst) begin
            counter <= 0; sec <= 0; min <= 0; hour <= 0;    // 初始默认为0时0分0秒
            curr_hour0 <= 0; curr_hour1 <= 0; curr_min0 <= 0; curr_min1 <= 0;
            curr_sec0 <= 0; curr_sec1 <= 0;
        end else begin
            if (counter == MAX_1s - 1) begin counter <= 0;
                if(key2_pulse)begin
                    case(oled_mode)    // 根据模式调整当前时间
                        3:begin
                            if (sec == 58) begin sec <= 0;
                                if (min == 59) begin min <= 0;
                                    hour <= (hour == 11) ? 0 : hour + 1;
                                end else min <= min + 1;
                            end else sec <= sec + 2;
                        end
                        2:begin
                            if (sec == 59) begin sec <= 0;
                                if (min == 58) begin min <= 0;
                                    hour <= (hour == 11) ? 0 : hour + 1;
                                end else min <= min + 1;
                            end else begin
                                sec <= sec + 1;
                                min <= min + 1;
                            end
                        end
                        1:begin
                            if (sec == 59) begin sec <= 0;
                                if (min == 59) begin min <= 0;
                                    hour <= (hour == 10) ? 0 : hour + 1;
                                end else min <= min + 1;
                            end else begin
                                sec <= sec + 1;
                                hour <= hour + 1;
                            end
                        end
                        default:begin
                            if (sec == 59) begin sec <= 0;
                                if (min == 59) begin min <= 0;
                                    hour <= (hour == 11) ? 0 : hour + 1;
                                end else min <= min + 1;
                            end else sec <= sec + 1;
                        end
                    endcase
                end else begin
                    if (sec == 59) begin sec <= 0;
                        if (min == 59) begin min <= 0;
                            hour <= (hour == 11) ? 0 : hour + 1;
                        end else min <= min + 1;
                    end else sec <= sec + 1;
                end
               


            end else begin counter <= counter + 1;
                if (key2_pulse == 1) begin
                    case (oled_mode)
                        3: begin
                            if (sec == 59) begin sec <= 0;
                                if (min == 59) begin min <= 0;
                                    hour <= (hour == 11) ? 0 : hour + 1;
                                end else min <= min + 1;
                            end else sec <= sec + 1;
                        end
                        2: begin
                            if (min == 59) begin min <= 0;
                                hour <= (hour == 11) ? 0 : hour + 1;
                            end else min <= min + 1;
                        end
                        1: begin
                            hour <= (hour == 11) ? 0 : hour + 1;
                        end


                        default: begin
                        end
                    endcase
                end
            end
           
            curr_hour1 <= hour / 10; curr_hour0 <= hour % 10;
            curr_min1 <= min / 10; curr_min0 <= min % 10;
            curr_sec1 <= sec / 10; curr_sec0 <= sec % 10;
        end
    end
   


    // 闹钟时间设置模式(模式4-6)
    always @(posedge clk or negedge rst) begin
        if (!rst) begin
            next_sec <= 0;next_min <= 0;next_hour <= 0;
            next_hour0 <= 0;next_hour1 <= 0;next_min0 <= 0;next_min1 <= 0;
            next_sec0 <= 0;next_sec1 <= 0;
        end else begin
            if (key2_pulse == 1) begin
                case (oled_mode)
                    6: begin      // 模式6:设置闹钟秒
                        if (next_sec == 59) begin next_sec <= 0;
                            if (next_min == 59) begin next_min <= 0;
                                next_hour <= (next_hour == 11) ? 0 : next_hour + 1;
                            end else next_min <= next_min + 1;
                        end else next_sec <= next_sec + 1;    
                    end
                    5: begin     // 模式5:设置闹钟分
                        if (next_min == 59) begin next_min <= 0;
                            next_hour <= (next_hour == 11) ? 0 : next_hour + 1;
                        end else next_min <= next_min + 1;
                    end
                    4: begin     // 模式4:设置闹钟小时
                        next_hour <= (next_hour == 11) ? 0 : next_hour + 1;
                    end


                    default: begin
                    end
                endcase
            end


            next_hour1 <= next_hour / 10;next_hour0 <= next_hour % 10;
            next_min1 <= next_min / 10;next_min0 <= next_min % 10;
            next_sec1 <= next_sec / 10;next_sec0 <= next_sec % 10;
        end
    end

上面展示了time_cal时间计算模块的部分代码,主要功能是实现现在时间与闹钟时间的设置。代码的重点是实现时间的时分秒之间的转换,初始的默认时间均为000秒。


    mem_chinese[107] = {8'h04,8'h84,8'h84,8'hFC,8'h84,8'h84,8'h00,8'hFE};/*"现",0*/
    mem_chinese[108] = {8'h02,8'h02,8'hF2,8'h02,8'h02,8'hFE,8'h00,8'h00};
    mem_chinese[109] = {8'h20,8'h60,8'h20,8'h1F,8'h10,8'h90,8'h40,8'h23};
    mem_chinese[110] = {8'h18,8'h06,8'h01,8'h7E,8'h80,8'h83,8'hE0,8'h00};
    mem_chinese[111] = {8'h08,8'h08,8'h88,8'hC8,8'h38,8'h0C,8'h0B,8'h08};/*"在",1*/
    mem_chinese[112] = {8'h08,8'hE8,8'h08,8'h08,8'h08,8'h08,8'h08,8'h00};
    mem_chinese[113] = {8'h02,8'h01,8'h00,8'hFF,8'h40,8'h41,8'h41,8'h41};
    mem_chinese[114] = {8'h41,8'h7F,8'h41,8'h41,8'h41,8'h41,8'h40,8'h00};
    mem_chinese[115] = {8'h00,8'hFC,8'h84,8'h84,8'h84,8'hFC,8'h00,8'h10}; // 时
    mem_chinese[116] = {8'h10,8'h10,8'h10,8'hFF,8'h10,8'h10,8'h00,8'h00};
    mem_chinese[117] = {8'h00,8'h3F,8'h10,8'h10,8'h10,8'h3F,8'h00,8'h00};
    mem_chinese[118] = {8'h01,8'h06,8'h40,8'h80,8'h7F,8'h00,8'h00,8'h00};
    mem_chinese[119] = {8'h00,8'hF8,8'h01,8'h06,8'h00,8'hF0,8'h12,8'h12}; // 间
    mem_chinese[120] = {8'h12,8'hF2,8'h02,8'h02,8'h02,8'hFE,8'h00,8'h00};
    mem_chinese[121] = {8'h00,8'hFF,8'h00,8'h00,8'h00,8'h1F,8'h11,8'h11};
    mem_chinese[122] = {8'h11,8'h1F,8'h00,8'h40,8'h80,8'h7F,8'h00,8'h00};
    mem_chinese[123] = {8'h00,8'hF8,8'h01,8'h22,8'h20,8'h22,8'h2A,8'hF2};/*"闹",0*/
    mem_chinese[124] = {8'h22,8'h22,8'h22,8'h22,8'h02,8'hFE,8'h00,8'h00};
    mem_chinese[125] = {8'h00,8'hFF,8'h00,8'h00,8'h1F,8'h01,8'h01,8'h7F};
    mem_chinese[126] = {8'h09,8'h11,8'h0F,8'h40,8'h80,8'h7F,8'h00,8'h00};
    mem_chinese[127] = {8'h20,8'h10,8'h2C,8'hE7,8'h24,8'h24,8'h00,8'hF0};/*"钟",1*/
    mem_chinese[128] = {8'h10,8'h10,8'hFF,8'h10,8'h10,8'hF0,8'h00,8'h00};
    mem_chinese[129] = {8'h01,8'h01,8'h01,8'h7F,8'h21,8'h11,8'h00,8'h07};
    mem_chinese[130] = {8'h02,8'h02,8'hFF,8'h02,8'h02,8'h07,8'h00,8'h00};

上面展示了部分项目中用到的汉字字库代码,其中字模的编码是通过教程中的PCtoLCD2002软件生成的,打开软件设置好模式要求后,输入想要的汉字,便可以生成相应的字模编码,如下图所示。

image.png


always@(tone) begin
    case(tone)
        5'd1:   time_end =  16'd22935;  //L1,
        5'd2:   time_end =  16'd20428;  //L2,
        5'd3:   time_end =  16'd18203;  //L3,
        5'd4:   time_end =  16'd17181;  //L4,
        5'd5:   time_end =  16'd15305;  //L5,
        5'd6:   time_end =  16'd13635;  //L6,
        5'd7:   time_end =  16'd12147;  //L7,
        5'd8:   time_end =  16'd11464;  //M1,
        5'd9:   time_end =  16'd10215;  //M2,
        5'd10:  time_end =  16'd9100;   //M3,
        5'd11:  time_end =  16'd8589;   //M4,
        5'd12:  time_end =  16'd7652;   //M5,
        5'd13:  time_end =  16'd6817;   //M6,
        5'd14:  time_end =  16'd6073;   //M7,
        5'd15:  time_end =  16'd5740;   //H1,
        5'd16:  time_end =  16'd5107;   //H2,
        5'd17:  time_end =  16'd4549;   //H3,
        5'd18:  time_end =  16'd4294;   //H4,
        5'd19:  time_end =  16'd3825;   //H5,
        5'd20:  time_end =  16'd3408;   //H6,
        5'd21:  time_end =  16'd3036;   //H7,
        default:time_end =  16'd65535;  
    endcase
end

always@(posedge rst_n_in) begin//音符表
   
    note[0] = {5'd8};
    note[1] = {5'd9};
    note[2] = {5'd10};
    note[3] = {5'd11};
    note[4] = {5'd12};
    note[5] = {5'd13};
    note[6] = {5'd14};
    note[7] = {5'd15};
    //2
    note[8] = {5'd16};
    note[9] = {5'd15};
    note[10] = {5'd13};
    note[11] = {5'd16};
    note[12] = {5'd17};
    note[13] = {5'd16};
    note[14] = {5'd15};
    note[15] = {5'd12};
    note[16] = {5'd16};
    note[17] = {5'd17};
    //3
    note[18] = {5'd16};
    note[19] = {5'd15};
    note[20] = {5'd13};
    note[21] = {5'd15};
    note[22] = {5'd16};
    note[23] = {5'd13};
    note[24] = {5'd17};
    note[25] = {5'd15};
    note[26] = {5'd16};
    note[27] = {5'd17};
    note[28] = {5'd19};
    //4
    note[29] = {5'd19};
    note[30] = {5'd17};
    note[31] = {5'd17};
    note[32] = {5'd16};
    note[33] = {5'd15};
    note[34] = {5'd16};
    note[35] = {5'd15};
    note[36] = {5'd13};
    note[37] = {5'd15};
    note[38] = {5'd16};
    note[39] = {5'd17};
    //5
    note[40] = {5'd19};
    note[41] = {5'd0};
    note[42] = {5'd9};
    note[43] = {5'd8};
    note[44] = {5'd6};
    //6
    note[45] = {5'd8};
    note[46] = {5'd8};
    note[47] = {5'd6};
    note[48] = {5'd8};
    note[49] = {5'd8};
    note[50] = {5'd6};
    note[51] = {5'd8};
    note[52] = {5'd6};
    note[53] = {5'd5};
    note[54] = {5'd0};
    note[55] = {5'd9};
    note[56] = {5'd8};
    note[57] = {5'd6};
    //7
    note[58] = {5'd8};
    note[59] = {5'd8};
    note[60] = {5'd6};
    note[61] = {5'd8};
    note[62] = {5'd8};
    note[63] = {5'd10};
    note[64] = {5'd9};
    note[65] = {5'd8};
    note[66] = {5'd8};
    note[67] = {5'd0};
    note[68] = {5'd5};
    note[69] = {5'd6};
    note[70] = {5'd10};
   
    end

上面展示了buzzer_module蜂鸣器模块的部分代码,其中参考了板卡开源平台中音频播放项目的一些实现方式。模块定义了音调、节拍等信息,构建了一一对应的音符表与节拍表,来实现音频播放。

五、功能展示图及说明

向板卡中拖入项目的rbt文件后,按下运行键,板卡默认显示当前时间与闹钟时间均为000秒,ws2812彩灯显示当前时间为0时也即12时的位置。

1默认.jpg

按下左边按键,可以切换模式,分别设置现在时间与闹钟的时、分、秒。

2设置现在时间.jpg3设置闹钟时间.jpg

用右边的按键来调整数字,可以看到,当调整现在时间的小时的时候,彩灯会如钟表一样显示出当前的小时。

image.png

左边按键切换到闹钟设置模式后,同样用右面按键来设置闹钟具体时间。设置好后,切换回显示模式,等待闹钟响起。

4设置闹钟时间.jpg

可以看到,到了设定的时间后,蜂鸣器产生音频的同时伴随着彩灯的闪烁。音频结束后,按下右边按键可结束彩灯闪烁,进行下一次设置;按下运行键可复位至初始状态。

5闹钟.jpg

六、项目中遇到的难题和解决方法

由于我之前没有做过这种难度的项目,一开始确实会有种无从下手的感觉。为此,我首先仔细分析了项目实现的要求,在将任务分解成几个不同的模块之后逐一完成。

具体实现的过程中,我认为闹钟逻辑的编写与OLED的显示最为困难。为了实现闹钟的播放,我通过计算现在时间与闹钟时间的时间差来控制蜂鸣器的音频。在OLED显示中,字库的建构与信息的显示也用了很长时间来编写。在一些开源代码的参考以及大语言模型的代码生成功能辅助下,我得以解决大部分问题,实现了任务要求的基本功能。

然而,项目还存在着一些不足之处,比如音频播放时的控制还不完善。后续我也会针对特定问题来继续优化项目。

七、对本次活动的心得体会

本次寒假练活动让我受益匪浅。通过编写项目,我掌握了Verilog语言和FPGA的开发流程,理解了硬件思维与软件编程的差异。活动中调试时序问题和代码编写的过程极大提升了我的分析能力。虽然初期因不熟悉相关细节而遇到了很多阻碍,但开源平台等多种资源的帮助让我最终完成了项目。这次经历不仅让我巩固了数电知识,更让我体会到了硬件开发的乐趣,为后续的发展打下了坚实基础。

八、FPGA资源占用报告

image.png

附件下载
alarm_yyc.zip
团队介绍
北京理工大学本科生
团队成员
Enriedo
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号