项目背景
为让高校电子/电气/电力等专业的同学们今冬寒假的时间过得更有意义,硬禾学堂特别设计了5个同学们能够在家动手做的小项目,目的是让更多的同学们参与进来、学有成效:完成即免费;加强撰写文档和营销能力,鼓励知识分享;抱团学习比自己探索更高效。
项目要求
- 实现一个可定时时钟的功能,用小脚丫FPGA核心模块的4个按键设置当前的时间,OLED显示数字钟的当前时间,精确到分钟即可,到整点的时候比如8:00,蜂鸣器报警,播放音频信号,最长可持续30秒;
- 实现温度计的功能,小脚丫通过板上的温度传感器实时测量环境温度,并同时间一起显示在OLED的屏幕上;
- 定时时钟整点报警的同时,将温度信息通过UART传递到电脑上,电脑上能够显示当前板子上的温度信息(任何显示形式都可以),要与OLED显示的温度值一致;
- PC收到报警的温度信号以后,将一段音频文件(自己制作,持续10秒钟左右)通过UART发送给小脚丫FPGA,蜂鸣器播放收到的这段音频文件,OLED屏幕上显示的时间信息和温度信息都停住不再更新;
- 音频文件播放完毕,OLED开始更新时间信息和当前的温度信息
项目平台
小脚丫FPGA核心模块的综合训练平台支持Lattice XO2版本,主要构成包括:
1.任意波形/信号发生器的功能:
- 能够通过R-2R阶梯网络生成0-2MHz的任意波形(若更换输出级的运算放大器能够生成更高频率的信号),包括正弦波、三角波、锯齿波、方波等,信号幅度为3Vpp,输出信号的波形、频率以及幅度皆可调节;
-
同时可以能够通过PWM用一根数据线搭配R、C构成的低通滤波器生成0-20KHz的任意波形,可同上述用R-2R构成的高速信号发生器进行对比;
-
板上有一颗SPI接口的串行ADC,可以采集电位计上的电压,旋转电位计,可以观察采集到的电压值的变化
-
也可以将1中的DDS信号发生器产生的波形送到ADC的输入端,构成一个环路,即便在家没有任何测试仪器的情况下也可以通过本地产生的波形信号进行电路和逻辑的调试
-
串行ADC的采样率为200KHz,可以对频率为20KHz以内的信号(音频信号的范围)进行采样并显示在OLED屏上
- 延伸的功能 - 对信号进行频谱分析,通过FFT变换得到被采集信号的基频及多个高次谐波的分量信息
- 能够通过单总线采集温度传感器(使用经典的18B20器件)的数据并显示出来
4. OLED图形化信息显示
- 板上采用了一块128*32分辨率的OLED作为信息显示终端,可以显示温度传感器的温度值、通过ADC采集到的电位计的电压值、DDS信号发生器生成的波形以及必要的菜单信息
5. 蜂鸣器输出
- 板上有一个经三极管驱动的蜂鸣器,可以通过PWM来实现声音的输出,比如播放音乐、声音报警等
-
板载USB-UART芯片CH340,能够实现FPGA和上位机PC的通信
主要代码
这一部分只展示部分代码,整体代码见附件
1.时钟部分
always @ ( posedge clk_in_5hz or negedge rst_n_in )
if (~rst_n_in)
time_shi <= 8'd0;
else if( en && ((time_fen[7:4]==4'd5 && time_fen[3:0]==4'd9 && time_miao[7:4]==4'd5 && time_miao[3:0]==4'd9 && cnt == 3'd4) || ~control[2]) )begin
if( time_shi[7:4]==4'd2 && time_shi[3:0]==4'd3 )
time_shi <= 8'd0;
else if ( time_shi[3:0]==4'd9 ) begin
time_shi[7:4] <= time_shi[7:4] + 1'b1;
time_shi[3:0] <= 4'd0;
end
else
time_shi[3:0] <= time_shi[3:0] + 1'b1;
end
always @ ( posedge clk_in_5hz or negedge rst_n_in )
if (~rst_n_in)
time_fen <= 8'd0;
else if( en && ((time_miao[7:4]==4'd5 && time_miao[3:0]==4'd9 && cnt == 3'd4) || ~control[1]) )begin
if( time_fen[7:4]==4'd5 && time_fen[3:0]==4'd9 )
time_fen <= 8'd0;
else if ( time_fen[3:0]==4'd9 ) begin
time_fen[7:4] <= time_fen[7:4] + 1'b1;
time_fen[3:0] <= 4'd0;
end
else
time_fen[3:0] <= time_fen[3:0] + 1'b1;
end
always @ ( posedge clk_in_5hz or negedge rst_n_in )
if (~rst_n_in)
time_miao <= 8'd0;
else if ( en && (cnt == 3'd4 || ~control[0])) begin
if( time_miao[7:4]==4'd5 && time_miao[3:0]==4'd9)
time_miao <= 8'd0;
else if ( time_miao[3:0]==4'd9 ) begin
time_miao[7:4] <= time_miao[7:4] + 1'b1;
time_miao[3:0] <= 4'd0;
end
else
time_miao[3:0] <= time_miao[3:0] + 1'b1;
end
2.蜂鸣器部分
reg [23:0] cnt;
always@(posedge clk_in or negedge rst_n_in)
if(~rst_n_in)
cnt <= 24'd0;
else if(cnt == 24'd3999999)
cnt <= 24'd0;
else
cnt <= cnt + 1'b1;
reg [9:0] data_length_reg;
always@(posedge clk_in or negedge rst_n_in)
if(~rst_n_in)
data_length_reg <= 10'd0;
else if ( rx_done )
data_length_reg <= data_length;
else if ( data_length_reg && cnt == 24'd3999999)
data_length_reg <= data_length_reg - 1'b1;
reg [959:0] data_buffer_reg;
always@(posedge clk_in or negedge rst_n_in)
if(~rst_n_in)
data_buffer_reg <= 960'd0;
else if ( rx_done )
data_buffer_reg <= data_buffer;
else if ( data_length_reg && cnt == 24'd3999999) begin
data_buffer_reg <= data_buffer_reg>>8;
end
wire [4:0] tone;
assign tone = data_buffer_reg[4:0];
wire play_en;
assign play_en = (data_length_reg == 10'd0)?1'b0:1'b1;
Beeper Beeper_inst(
.clk_in (clk_in ),
.rst_n_in (rst_n_in ),
.tone_en (play_en ),
.tone (tone ),
.piano_out (piano_out )
);
assign play_done = ( data_length_reg == 10'd1 && en)?1'b1:1'b0;
3.UART部分
always @ (posedge clk_in or negedge rst_n_in) begin
if(!rst_n_in) begin
bps_en <= 1'b0;
tx_data_r <= 11'd0;
end else if(tx_en)begin
bps_en <= 1'b1;
tx_data_r <= {1'b1,1'b1,tx_data,1'b0};
end else if(num==4'd11) begin
bps_en <= 1'b0;
end
end
always @ (posedge clk_in or negedge rst_n_in) begin
if(!rst_n_in) begin
num <= 1'b0;
rs232_tx <= 1'b1;
end else if(bps_en) begin
if(bps_clk_in) begin
num <= num + 1'b1;
rs232_tx <= tx_data_r[num];
end else if(num==4'd11)
num <= 4'd0;
end
end
always @ (posedge clk_in or negedge rst_n_in) begin
if(!rst_n_in) begin
rs232_rx0 <= 1'b0;
rs232_rx1 <= 1'b0;
rs232_rx2 <= 1'b0;
end else begin
rs232_rx0 <= rs232_rx;
rs232_rx1 <= rs232_rx0;
rs232_rx2 <= rs232_rx1;
end
end
wire neg_rs232_rx = rs232_rx2 & rs232_rx1 & (~rs232_rx0) & (~rs232_rx);
reg [3:0] num;
always @ (posedge clk_in or negedge rst_n_in) begin
if(!rst_n_in)
bps_en <= 1'b0;
else if(neg_rs232_rx && (!bps_en))
bps_en <= 1'b1;
else if(num==4'd9)
bps_en <= 1'b0;
end
测温部分与显示部分等参考了电子森林代码并进行修改,但本质上相差不大,再次不做展示。
心得体会
这一次的寒假一起练项目对我个人来说是一个不小的挑战,因为我在此之前从未接触过FGPA板的开发以及使用VERILOG语言的编程工作,可以说我在做这个项目是从0开始。在项目进行之初,我面临着许多的困难,包括对板的不了解,包括代码编写的困难等等。经过了很长一段时间的学习,我才渐渐的能够开始项目工作。在项目进行的过程中也感谢电子森林这个平台为我提供了很大的帮助,让我少走了很多的弯路。此外我在编程过程中也和我的同学一起研究探讨来得到最好的方法,真正的体会到了“抱团学习比自己探索更高效”。那么经过了这次的项目,我个人也有很大的收获。首先来说,这个寒假我并没有像之前一样什么都没做就荒废了,在进行项目期间我每天都很充实;此外,我也学会了一些关于FPGA板开发的知识以及Diamond软件的应用;再者就是我学习了新的编程语言verilog,丰富的我的知识;最后也是最重要的就是我能够在面对很大困难的时候没有放弃并努力找到办法最终解决了困难。在这次项目中学到的知识可能暂时用处不大,但是做项目的经历会伴随着我并给我一定的激励。