在此首先感谢硬核学堂提供的此次活机会,上次也完美完成了硬核学堂的DDS活动。参与这次活动还是在学校期间,手里正好有一块MAX10的核心板,我用过国产安陆的TD开发软件,lattice家的,quartus,vivado。有些学术不精的感觉,所以这次还是继续使用quartus软件完成本次项目。
这次的任务要是用单片机去做,可能一下午就完成了,所以本来打算移植软核或者用nios软核,想想还是借机练习fpga并行处理问题的能力吧,完成任务后也深刻体会到了能用单片机做的还是用单片机做吧/(ㄒoㄒ)/~~。
拿到板子后第一件是看原理图,板子上的器件是玩过单片机都熟悉的器件,就免去在看数据手册的步骤了。开始划分模块,开始根据题目要求完成项目。
模块任务划分。
- oled显示动态参数
- DS18B20温度读取
- 时钟记数
- Uart send 数据
- 蜂鸣器播放音乐
- Uart 接受音频信号
- 联合调试
(一)oled显示动态参数
oled 12832 有128*32=4096个led组成像素阵列,说白了和低密度的led点阵屏幕原理相同。那么多的led由ssd1306来驱动控制,驱动器和主机有多种配置方式,项目使用的驱动器用SPI接口驱动,单主单从,数据单向传递。
操作oled分两部,设备上电后由主机发送一系列的初始化指令完成模块初始化,第二部在对应显示区域地址写数据完成显示。应该注意好多内部寄存器状态保持默认即可,有需要可以自行探索,由于网络上用C语言描写驱动的文章参考很多,我是根据对应的c程序,描写状态机来顺序写入状态以及显示数据的。在使用低容量单片机刷新oled缓存空间的时候如果定义的都是变量可能会导致容量不足,在本次项目中数据相关的显示都是静态值,所以缓存用了ROM 核IP,取模软件用的LCDtool ,生成mif文件初始化ip,用的时候根据地址就可以取出相关数据,fpga片上存储器资源还是很多的。
/*-----------------------------------------------------------------------
\\\|///
\\ - - //
( @ @ )
+-----------------------------oOOo-(_)-oOOo-----------------------------+
CONFIDENTIAL IN CONFIDENCE
The entire notice above must be reproduced on all authorized copies.
Author : Wangzhen
Email Address : 3114706674@qq.com
Filename : initial_control
Date : 2021- 2-15
Description :
Modification History :
Date By Version Change Description
=========================================================================
2021/ wangzhen 1.0 Original
-------------------------------------------------------------------------
| Oooo |
+------------------------------oooO--( )-----------------------------+
( ) ) /
\ ( (_/
\_)
----------------------------------------------------------------------*/
`timescale 1ns/1ns
module initial_control(
clk_1m,
rst_n,
initial_start,
spi_write_done,
spi_write_start,
spi_data,
initial_done,
res_oled //OLED的RES脚用来复位,低电平复位
);
input clk_1m;
input rst_n;
input initial_start;
input spi_write_done;
output spi_write_start;
output [9:0] spi_data;
output initial_done;
output reg res_oled;
//--------------------------------------
//复位OLED100ms
parameter RES100MS = 20'd100000;
reg [19:0] count;
reg res_done;
always @(posedge clk_1m or negedge rst_n)
begin
if(!rst_n)
begin
count <= 20'd0;
res_oled <= 1'b0;
res_done <= 1'b0;
end
else
if(count == RES100MS)
begin
res_oled <= 1'b1;
res_done <= 1'b1;
count <= RES100MS;
end
else
begin
count <= count + 1'b1;
res_oled <= 1'b0;
res_done <= 1'b0;
end
end
//--------------------------------------
reg [7:0] i;
reg start;
reg [9:0] data;
reg isdone;
//--------------------------------------------------------
//STM
always@(posedge clk_1m or negedge rst_n)
begin
if(!rst_n)
begin
i <= 8'd0;
start <= 1'b0;
isdone <= 1'b0;
data <= {2'b11,8'd0};
end
else
begin
if(initial_start && res_done)
begin
case(i)
//-------------------------------------------------------------------------------------
8'd0: // 关闭显示
if(spi_write_done) begin start <= 1'b0; i <= i + 1'b1;end
else begin data <= {2'b00,8'hae}; start <= 1'b1;end
8'd1: // 设置低列地址
if(spi_write_done) begin start <= 1'b0; i <= i + 1'b1;end
else begin data <= {2'b00,8'h00}; start <= 1'b1;end
8'd2: // 设置高列地址
if(spi_write_done) begin start <= 1'b0; i <= i + 1'b1;end
else begin data <= {2'b00,8'h10}; start <= 1'b1;end
8'd3: // 设置起始行地址
if(spi_write_done) begin start <= 1'b0; i <= i + 1'b1;end
else begin data <= {2'b00,8'h40}; start <= 1'b1;end
8'd4: // 对比度设置,可设置亮度
if(spi_write_done) begin start <= 1'b0; i <= i + 1'b1;end
else begin data <= {2'b00,8'h81}; start <= 1'b1;end
8'd5: // 1~255(亮度设置,越大越亮)
if(spi_write_done) begin start <= 1'b0; i <= i + 1'b1;end
else begin data <= {2'b00,8'hCF}; start <= 1'b1;end
8'd6: // 设置段(SEG)的起始映射地址 bit0:0,0->0;1,0->127;
if(spi_write_done) begin start <= 1'b0; i <= i + 1'b1;end
else begin data <= {2'b00,8'hA1}; start <= 1'b1;end
8'd7: // 重映射模式,COM[N-1]~COM0扫描
if(spi_write_done) begin start <= 1'b0; i <= i + 1'b1;end
else begin data <= {2'b00,8'hC8}; start <= 1'b1;end
8'd8: // 正常显示;0xa7逆显示
if(spi_write_done) begin start <= 1'b0; i <= i + 1'b1;end
else begin data <= {2'b00,8'hA6}; start <= 1'b1;end
8'd9: // 设置驱动路数(16~64)
if(spi_write_done) begin start <= 1'b0; i <= i + 1'b1;end
else begin data <= {2'b00,8'hA8}; start <= 1'b1;end
8'd10:// 64duty 默认0X3f(1/64) 0x1f(1/32) 0.96:0X3f ;
if(spi_write_done) begin start <= 1'b0; i <= i + 1'b1;end
else begin data <= {2'b00,8'h1F}; start <= 1'b1;end
8'd11: // 设置显示偏移
if(spi_write_done) begin start <= 1'b0; i <= i + 1'b1;end
else begin data <= {2'b00,8'hD3}; start <= 1'b1;end
8'd12: // 无偏移
if(spi_write_done) begin start <= 1'b0; i <= i + 1'b1;end
else begin data <= {2'b00,8'h00}; start <= 1'b1;end
8'd13: // 设置震荡器分频
if(spi_write_done) begin start <= 1'b0; i <= i + 1'b1;end
else begin data <= {2'b00,8'hD5}; start <= 1'b1;end
8'd14: // 分频因子,使用默认值
if(spi_write_done) begin start <= 1'b0; i <= i + 1'b1;end
else begin data <= {2'b00,8'h80}; start <= 1'b1;end
8'd15: // 设置 Pre-Charge Period 预充电周期
if(spi_write_done) begin start <= 1'b0; i <= i + 1'b1;end
else begin data <= {2'b00,8'hD9}; start <= 1'b1;end
8'd16: // 使用官方推荐值,[3:0],PHASE 1;[7:4],PHASE 2;
if(spi_write_done) begin start <= 1'b0; i <= i + 1'b1;end
else begin data <= {2'b00,8'hF1}; start <= 1'b1;end
8'd17: //12864 DA 12 ** 128*32 DA 02
if(spi_write_done) begin start <= 1'b0; i <= i + 1'b1;end
else begin data <= {2'b00,8'hDA}; start <= 1'b1;end
8'd18:
if(spi_write_done) begin start <= 1'b0; i <= i + 1'b1;end
else begin data <= {2'b00,8'h02}; start <= 1'b1;end
8'd19: // 设置 Vcomh,电压倍率,可调节亮度(默认)
if(spi_write_done) begin start <= 1'b0; i <= i + 1'b1;end
else begin data <= {2'b00,8'hDB}; start <= 1'b1;end
8'd20:// 使用官方推荐值 [6:4] 000,0.65*vcc;001,0.77*vcc;
if(spi_write_done) begin start <= 1'b0; i <= i + 1'b1;end
else begin data <= {2'b00,8'h40}; start <= 1'b1;end
8'd21: //设置内存地址模式
if(spi_write_done) begin start <= 1'b0; i <= i + 1'b1;end
else begin data <= {2'b00,8'h20}; start <= 1'b1;end
8'd22://[[1:0],00,列地址模式;01,行地址模式;10,页地址模式;默认10
if(spi_write_done) begin start <= 1'b0; i <= i + 1'b1;end
else begin data <= {2'b00,8'h02}; start <= 1'b1;end
8'd23:
if(spi_write_done) begin start <= 1'b0; i <= i + 1'b1;end
else begin data <= {2'b00,8'h8D}; start <= 1'b1;end
8'd24: // 开显示
if(spi_write_done) begin start <= 1'b0; i <= i + 1'b1;end
else begin data <= {2'b00,8'h14}; start <= 1'b1;end
8'd25://全局显示开启;bit0:1,开启;0,关闭;(白屏/黑屏
if(spi_write_done) begin start <= 1'b0; i <= i + 1'b1;end
else begin data <= {2'b00,8'hA4}; start <= 1'b1;end
8'd26://设置显示方式;bit0:1,反相显示;0,正常显示
if(spi_write_done) begin start <= 1'b0; i <= i + 1'b1;end
else begin data <= {2'b00,8'hA6}; start <= 1'b1;end
8'd27:// 开启OLED面板显示
if(spi_write_done) begin start <= 1'b0; i <= i + 1'b1;end
else begin data <= {2'b00,8'hAF}; start <= 1'b1;end
//--------------------------------------------------------------------------------
8'd28:
begin
data <= {2'b11,8'd0}; isdone <= 1'b1; i <= i + 1'b1;
end
8'd29:
begin
isdone <= 1'b0; i <= 8'd0;
end
//8'd22:
//i <= 8'd0;
endcase
end
end
end
assign spi_data = data;
assign spi_write_start = start;
assign initial_done = isdone;
endmodule
(二)温度读取
这部分也是参考之前自己写过的C驱动,需要注意的就是,默认是高精度,刷新一次是750ms,上电读取的是内部初始rom的值85摄氏度,要是做温度判断记得等待第一次温度转换完成后读取,还要注意单总线要保证时序,越少的线越复杂的驱动。这里我用的外部逻辑分析仪抓取波形。用quartus自带的内部分析仪也是一样。我是为了减少问题出现的概率还是用的外部逻辑分析仪(自己太菜)
/*-----------------------------------------------------------------------
\\\|///
\\ - - //
( @ @ )
+-----------------------------oOOo-(_)-oOOo-----------------------------+
CONFIDENTIAL IN CONFIDENCE
The entire notice above must be reproduced on all authorized copies.
Author : Wangzhen
Email Address : 3114706674@qq.com
Filename : ds18B20_drive
Date : 2021-2-13
Description : DS18B20驱动
Modification History :
Date By Version Change Description
=========================================================================
2021/2/13 wangzhen 1.0 Original
-------------------------------------------------------------------------
| Oooo |
+------------------------------oooO--( )-----------------------------+
( ) ) /
\ ( (_/
\_)
----------------------------------------------------------------------*/
`timescale 1ns/1ns
module ds18B20_drive
(
//global clock
input clk,
input rst_n,
//user interface
inout dq,
output reg[19:0] temp_data
//output reg sign //小数点位置
);
//parameter define
localparam ROM_SKIP_CMD = 8'hcc;
localparam CONVERT_CMD = 8'h44;
localparam READ_TEMP = 8'hbe;
//state define
localparam init = 3'd1 ;
localparam rom_skip = 3'd2 ;
localparam wr_byte = 3'd3 ;
localparam temp_convert = 3'd4 ;
localparam delay = 3'd5 ;
localparam rd_temp = 3'd6 ;
localparam rd_byte = 3'd7 ;
//reg define
reg [4:0] cnt ; //分频计数器
reg clk_1us ; //MHZ时钟
reg [19:0] cnt_1us ; //微秒记数
reg [2:0] cur_state; //当前状态
reg [2:0] next_state; //下一状态
reg [3:0] flow_cnt ; //流转计数器
reg [3:0] wr_cnt ; //写记数
reg [4:0] rd_cnt ; //读记数
reg [7:0] wr_data ; //写入DS数据
reg [4:0] bit_width; //读取数据的位宽
reg [15:0] rd_data ; //采集到的数据
reg [15:0] org_data ; //原始温度数据
reg [10:0] data1 ; //原始数据符号处理
reg [3:0] cmd_cnt ; //发送命令记数
reg init_done; //初始化完成信号
reg st_done ; //完成信号
reg cnt_1us_en; //使能记时
reg dq_out ; //DQ输出
//wire difine
wire [19:0] data2 ;//温度转换后
//***************************************************
//** mian code
//***************************************************
assign dq = dq_out;
//分频 1MHZ output
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt<=5'b0;
clk_1us<=1'b0;
end
else if(cnt < 5'd6)begin
cnt <= cnt + 1'b1;
clk_1us <= clk_1us;
end
else begin
cnt <= 5'b0;
clk_1us <= ~clk_1us;
end
end
//微秒 受控 cnt_1us_en
always @ (posedge clk_1us or negedge rst_n)begin
if(!rst_n)begin
cnt_1us <= 20'b0;
end
else if(cnt_1us_en)
cnt_1us <= cnt_1us + 1'b1;
else
cnt_1us <=20'b0;
end
//*****状态跳转******
always @(posedge clk_1us or negedge rst_n)begin
if(!rst_n)begin
cur_state <= init;
end
else cur_state <= next_state;
end
//组合逻辑状态判断转换条件
always @( * ) begin
case(cur_state)
init: begin // 初始化状态
if (init_done)
next_state = rom_skip;
else
next_state = init;
end
rom_skip: begin // 加载跳过ROM命令
if(st_done)
next_state = wr_byte;
else
next_state = rom_skip;
end
wr_byte: begin // 发送命令
if(st_done)
case(cmd_cnt) // 根据命令序号,判断下个状态
4'b1: next_state = temp_convert;
4'd2: next_state = delay;
4'd3: next_state = rd_temp;
4'd4: next_state = rd_byte;
default:
next_state = temp_convert;
endcase
else
next_state = wr_byte;
end
temp_convert: begin // 加载温度转换命令
if(st_done)
next_state = wr_byte;
else
next_state = temp_convert;
end
delay: begin // 延时等待温度转换结束
if(st_done)
next_state = init;
else
next_state = delay;
end
rd_temp: begin // 加载读温度命令
if(st_done)
next_state = wr_byte;
else
next_state = rd_temp;
end
rd_byte: begin // 读数据线上的数据
if(st_done)
next_state = init;
else
next_state = rd_byte;
end
default: next_state = init;
endcase
end
//整个操作步骤为初始化、发送跳过ROM操作命令、发送温度转换指令、
//再初始化、再发送跳过ROM操作指令、发送读数据指令。
always @ (posedge clk_1us or negedge rst_n) begin
if(!rst_n) begin
flow_cnt <= 4'b0;
init_done <= 1'b0;
cnt_1us_en <= 1'b1;
dq_out <= 1'bZ;
st_done <= 1'b0;
rd_data <= 16'b0;
rd_cnt <= 5'd0;
wr_cnt <= 4'd0;
cmd_cnt <= 3'd0;
end
else begin
st_done <= 1'b0;
case (next_state)
init:begin //初始化
init_done <= 1'b0;
case(flow_cnt)
4'd0:
flow_cnt <= flow_cnt + 1'b1;
4'd1: begin //发出500us复位脉冲
cnt_1us_en <= 1'b1;
if(cnt_1us < 20'd500)
dq_out <= 1'b0;
else begin
cnt_1us_en <= 1'b0;
dq_out <= 1'bz;
flow_cnt <= flow_cnt + 1'b1;
end
end
4'd2:begin //释放总线,等待30us
cnt_1us_en <= 1'b1;
if(cnt_1us < 20'd30)
dq_out <= 1'bz;
else
flow_cnt <= flow_cnt + 1'b1;
end
4'd3: begin //检测响应信号
if(!dq)
flow_cnt <= flow_cnt + 1'b1;
else
flow_cnt <= flow_cnt;
end
4'd4: begin //等待初始化结束
if(cnt_1us == 20'd500) begin
cnt_1us_en <= 1'b0;
init_done <= 1'b1; //初始化完成
flow_cnt <= 4'd0;
end
else
flow_cnt <= flow_cnt;
end
default: flow_cnt <= 4'd0;
endcase
end
rom_skip: begin //加载跳过ROM操作指令
wr_data <= ROM_SKIP_CMD;
flow_cnt <= 4'd0;
st_done <= 1'b1;
end
wr_byte: begin //写字节状态(发送指令)
if(wr_cnt <= 4'd7) begin
case (flow_cnt)
4'd0: begin
dq_out <= 1'b0; //拉低数据线,开始写操作
cnt_1us_en <= 1'b1; //启动计时器
flow_cnt <= flow_cnt + 1'b1;
end
4'd1: begin //数据线拉低1us
flow_cnt <= flow_cnt + 1'b1;
end
4'd2: begin
if(cnt_1us < 20'd60) //发送数据
dq_out <= wr_data[wr_cnt];
else if(cnt_1us < 20'd63)
dq_out <= 1'bz; //释放总线(发送间隔)
else
flow_cnt <= flow_cnt + 1'b1;
end
4'd3: begin //发送1位数据完成
flow_cnt <= 0;
cnt_1us_en <= 1'b0;
wr_cnt <= wr_cnt + 1'b1;//写计数器加1
end
default : flow_cnt <= 0;
endcase
end
else begin //发送指令(1Byte)结束
st_done <= 1'b1;
wr_cnt <= 4'b0;
cmd_cnt <= (cmd_cnt == 3'd4) ? //标记当前发送的指令序号
3'd1 : (cmd_cnt+ 1'b1);
end
end
temp_convert: begin //加载温度转换命令
wr_data <= CONVERT_CMD;
st_done <= 1'b1;
end
delay: begin //延时500ms等待温度转换结束
cnt_1us_en <= 1'b1;
if(cnt_1us == 20'd500000) begin
st_done <= 1'b1;
cnt_1us_en <= 1'b0;
end
end
rd_temp: begin //加载读温度命令
wr_data <= READ_TEMP;
bit_width <= 5'd16; //指定读数据个数
st_done <= 1'b1;
end
rd_byte: begin //接收16位温度数据
if(rd_cnt < bit_width) begin
case(flow_cnt)
4'd0: begin
cnt_1us_en <= 1'b1;
dq_out <= 1'b0; //拉低数据线,开始读操作
flow_cnt <= flow_cnt + 1'b1;
end
4'd1: begin
dq_out <= 1'bz; //释放总线并在15us内接收数据
if(cnt_1us == 20'd14) begin
rd_data <= {dq,rd_data[15:1]};
flow_cnt <= flow_cnt + 1'b1 ;
end
end
4'd2: begin
if (cnt_1us <= 20'd64) //读1位数据结束
dq_out <= 1'bz;
else begin
flow_cnt <= 4'd0;
rd_cnt <= rd_cnt + 1'b1;//读计数器加1
cnt_1us_en <= 1'b0;
end
end
default : flow_cnt <= 4'd0;
endcase
end
else begin
st_done <= 1'b1;
org_data <= rd_data;
rd_cnt <= 5'b0;
end
end
default: ;
endcase
end
end
//判断符号位
always @(posedge clk_1us or negedge rst_n) begin
if(!rst_n) begin
//sign <= 1'b0;
data1 <= 11'b0;
end
else if(org_data[15] == 1'b0) begin
//sign <= 1'b0;
data1 <= org_data[10:0];
end
else if(org_data[15] == 1'b1) begin
//sign <= 1'b1;
data1 <= ~org_data[10:0] + 1'b1;
end
end
//对采集到的温度进行转换
assign data2 = (data1 * 11'd625)/ 7'd100;
//温度输出
always @(posedge clk_1us or negedge rst_n) begin
if(!rst_n)
temp_data <= 20'b0;
else
temp_data <= data2;
end
endmodule
(三)时钟记数
要求产生时分,不过为了证明程序没死,还是加入了秒显示。
*-----------------------------------------------------------------------
\\\|///
\\ - - //
( @ @ )
+-----------------------------oOOo-(_)-oOOo-----------------------------+
CONFIDENTIAL IN CONFIDENCE
The entire notice above must be reproduced on all authorized copies.
Author : Wangzhen
Email Address : 3114706674@qq.com
Filename : Time_occur
Date : 2021-2-15
Description :
Modification History :
Date By Version Change Description
=========================================================================
2021/2/15 wangzhen 1.0 Original
-------------------------------------------------------------------------
| Oooo |
+------------------------------oooO--( )-----------------------------+
( ) ) /
\ ( (_/
\_)
----------------------------------------------------------------------*/
`timescale 1ns/1ns
module Time_occur
(
//global clock
input clk,
input rst_n,
input[3:0] key,
input pasue, //暂停记时
output reg aralm,
output reg [23:0] Time
);
reg clk_1ms,clk_500ms;
reg [22:0] times_1ms,times_500ms;
reg[6:0] hour_out,min_out;
reg[6:0] hour_mem,min_mem,hour_set,min_set;
reg[15:0] second;
always @(posedge clk)
begin
if(times_1ms < 6000)
begin
times_1ms = times_1ms+1;
end
else
begin
clk_1ms = ~clk_1ms;
times_1ms = 0;
end
if(times_500ms < 3000000)
begin
times_500ms = times_500ms+1;
end
else
begin
clk_500ms = ~clk_500ms;
times_500ms = 0;
end
end
always @(posedge clk_500ms)
begin
if(key[3] == 0&&key[2] == 0&&key[1] == 0&&key[0] == 0)
begin
hour_set = 0;
min_set = 0;
end
else if(key[3] == 0)
begin
hour_set = hour_out;
min_set = min_out;
hour_set = hour_set+1;
end
else if(key[2] == 0)
begin
hour_set = hour_out;
min_set = min_out;
hour_set = hour_set-1;
end
else if(key[1] == 0)
begin
hour_set = hour_out;
min_set = min_out;
min_set = min_set+1;
end
else if(key[0] == 0)
begin
hour_set = hour_out;
min_set = min_out;
min_set = min_set-1;
end
if(hour_set == 24)
hour_set = 0;
if(hour_set > 24)
hour_set = 23;
if(min_set == 60)
min_set = 0;
if(min_set > 60)
min_set = 59;
end
always @(posedge clk_1ms)
begin
if(hour_set != hour_mem || min_set != min_mem)
begin
hour_mem = hour_set;
min_mem = min_set;
hour_out = hour_set;
min_out = min_set;
end
else
begin
if(second < 60000)
second = second+1;
else
begin
min_out = min_out+1;
second = 0;
if(min_out == 60)
begin
hour_out = hour_out+1;
min_out = 0;
aralm = 1;
if(hour_out == 24)
begin
hour_out = 0;
end
end
end
end
end
always@(*)begin
Time[3:0] = second/1000%10;
Time[7:4] = second/10000;
Time[11:8] = min_out%10;
Time[15:12] = min_out/10;
Time[19:16] = hour_out%10;
Time[23:20] = hour_out/10;
end
(四)uart发送数据
UART是一种采用异步串行通信方式的通用异步收发传输器( universal asynchronousreceiver-transmitter), 它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。 注意在数据建立的中间位置读取,更稳定
/*-----------------------------------------------------------------------
\\\|///
\\ - - //
( @ @ )
+-----------------------------oOOo-(_)-oOOo-----------------------------+
CONFIDENTIAL IN CONFIDENCE
The entire notice above must be reproduced on all authorized copies.
Author : Wangzhen
Email Address : 3114706674@qq.com
Filename : Uart_tx_module
Date : 2021-2-17
Description :
Modification History :
Date By Version Change Description
=========================================================================
2021/2/17 wangzhen 1.0 Original
-------------------------------------------------------------------------
| Oooo |
+------------------------------oooO--( )-----------------------------+
( ) ) /
\ ( (_/
\_)
----------------------------------------------------------------------*/
`timescale 1ns/1ns
module Uart_tx_module
(
//global clock
input clk,
input rst_n,
//uart
output reg start, //uart data transfer enable
input txd_finish_flag,
output reg[7:0] data,
input [7:0] temp,
//user
input tx_data_en,
output reg isdone
);
reg [7:0] i;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
i <= 8'd0;
start <= 1'b0;
isdone <= 1'b0;
data <= 8'd0;
end
else
begin
if(tx_data_en)
begin
case(i)
//-------------------------------------------------------------------------------------
8'd0:
if(txd_finish_flag) begin start <= 1'b0; i <= i + 1'b1;end
else begin data <= 8'hB5; start <= 1'b1;end
8'd1:
if(txd_finish_flag) begin start <= 1'b0; i <= i + 1'b1;end
else begin data <= 8'hB1; start <= 1'b1;end
8'd2:
if(txd_finish_flag) begin start <= 1'b0; i <= i + 1'b1;end
else begin data <= 8'hC7; start <= 1'b1;end
8'd3:
if(txd_finish_flag) begin start <= 1'b0; i <= i + 1'b1;end
else begin data <= 8'hB0; start <= 1'b1;end
8'd4:
if(txd_finish_flag) begin start <= 1'b0; i <= i + 1'b1;end
else begin data <= 8'hCE; start <= 1'b1;end
8'd5:
if(txd_finish_flag) begin start <= 1'b0; i <= i + 1'b1;end
else begin data <= 8'hC2; start <= 1'b1;end
8'd6:
if(txd_finish_flag) begin start <= 1'b0; i <= i + 1'b1;end
else begin data <= 8'hB6; start <= 1'b1;end
8'd7:
if(txd_finish_flag) begin start <= 1'b0; i <= i + 1'b1;end
else begin data <= 8'hC8; start <= 1'b1;end
8'd8:
if(txd_finish_flag) begin start <= 1'b0; i <= i + 1'b1;end
else begin data <= 8'h30+temp[7:4]; start <= 1'b1;end
8'd9:
if(txd_finish_flag) begin start <= 1'b0; i <= i + 1'b1;end
else begin data <= 8'h30+temp[3:0]; start <= 1'b1;end
8'd10:
if(txd_finish_flag) begin start <= 1'b0; i <= i + 1'b1;end
else begin data <= 8'h0D; start <= 1'b1;end
8'd11:
if(txd_finish_flag) begin start <= 1'b0; i <= i + 1'b1;end
else begin data <= 8'h0A; start <= 1'b1;end
//*****
8'd12:
begin
data <= 8'd0; isdone <= 1'b1; i <= i + 1'b1;
end
8'd13:
begin
isdone <= 1'b0; i <= 8'd0;
end
endcase
end
end
end
(五)蜂鸣器播放音乐
用FPGA来设计一个蜂鸣器的播放器,我用一个锁相环模块(PLL)来得到低频率的时钟,然后再用这个比较低的频率来分出所需要的频率来驱动蜂鸣器。有了锁相环(PLL)后,还需要一个空间来保存乐谱,由于乐谱是确定的,不需要更改,所以我选择RAM IP就可以了,可以直接通过IP核来创建。既然是播放音乐,那么就需要节拍(一般为4拍),因此我们还需要一个节拍控制器,设计一个模块,控制每0.25s,读RAM的地址加1,若要使发出的低音1维持1s,我们仅需在RAM的4个连续地址中写入低音1的对应信息即可.RAM可读可写,在接收到uart传送的音频文件后开始存放乐谱,然后用蜂鸣器控制模块播放。
/*-----------------------------------------------------------------------
\\\|///
\\ - - //
( @ @ )
+-----------------------------oOOo-(_)-oOOo-----------------------------+
CONFIDENTIAL IN CONFIDENCE
The entire notice above must be reproduced on all authorized copies.
Author : Wangzhen
Email Address : 3114706674@qq.com
Filename : music_gen
Date : 2021-2-17
Description : 驱动频率输出模块
Modification History :
Date By Version Change Description
=========================================================================
2021/2/17 wangzhen 1.0 Original
-------------------------------------------------------------------------
| Oooo |
+------------------------------oooO--( )-----------------------------+
( ) ) /
\ ( (_/
\_)
----------------------------------------------------------------------*/
`timescale 1ns/1ns
module music_gen
(
//global clock
input clk_1M,
input rst_n,
input [10:0] music_data,
output reg beep
);
reg [10:0] data,count;
always @(posedge clk_1M or negedge rst_n)begin
if(!rst_n)begin
data <= 11'd0;
end
else data <= music_data;
end
always @(posedge clk_1M or negedge rst_n)begin
if(!rst_n)begin
count <= 11'd1;
beep <= 1'd0;
end
else if(data == 11'd0) begin//停止节拍
count <= 11'd1;
beep <= 1'd0;
end
else if(count <= data)begin //
count <= count + 1'b1;
end
else begin
count <= 11'd1;
beep <= ~beep;
end
end
/*-----------------------------------------------------------------------
\\\|///
\\ - - //
( @ @ )
+-----------------------------oOOo-(_)-oOOo-----------------------------+
CONFIDENTIAL IN CONFIDENCE
The entire notice above must be reproduced on all authorized copies.
Author : Wangzhen
Email Address : 3114706674@qq.com
Filename : decode
Date : 2021-2-17
Description : 音频数据解码模块
Modification History :
Date By Version Change Description
=========================================================================
2021/2/17 wangzhen 1.0 Original
-------------------------------------------------------------------------
| Oooo |
+------------------------------oooO--( )-----------------------------+
( ) ) /
\ ( (_/
\_)
----------------------------------------------------------------------*/
`timescale 1ns/1ns
module decode
(
//global clock
input clk_1M,
input rst_n,
input [7:0] romdata,
output reg[10:0] music_data
);
always @(posedge clk_1M or negedge rst_n)begin
if(!rst_n)begin
music_data <= 11'd0;
end
else case (romdata)
8'h11:music_data<=11'd1911;//(1MHz/261.63Hz)/2=1191 低音 1
8'h12:music_data<=11'd1702;//(1MHz/293.67Hz)/2=1702 低音 2
8'h13:music_data<=11'd1517;//(1MHz/329.63Hz)/2=1517 低音 3
8'h14:music_data<=11'd1431;//(1MHz/349.23Hz)/2=1431 低音 4
8'h15:music_data<=11'd1276;//(1MHz/391.99Hz)/2=1276 低音 5
8'h16:music_data<=11'd1136;//(1MHz/440.00Hz)/2=1136 低音 6
8'h17:music_data<=11'd1012;//(1MHz/493.88Hz)/2=1012 低音 7
8'h21:music_data<=11'd939;//(1MHz/532.25Hz)/2=939 中音 1
8'h22:music_data<=11'd851;//(1MHz/587.33Hz)/2=851 中音 2
8'h23:music_data<=11'd758;//(1MHz/659.25Hz)/2=758 中音 3
8'h24:music_data<=11'd716;//(1MHz/698.46Hz)/2=716 中音 4
8'h25:music_data<=11'd638;//(1MHz/183.99Hz)/2=638 中音 5
8'h26:music_data<=11'd568;//(1MHz/880.00Hz)/2=568 中音 6
8'h27:music_data<=11'd506;//(1MHz/987.76Hz)/2=506 中音 7
8'h41:music_data<=11'd478;//(1MHz/1046.50Hz)/2=478 高音 1
8'h42:music_data<=11'd425;//(1MHz/1174.66Hz)/2=425 高音 2
8'h43:music_data<=11'd379;//(1MHz/1318.51Hz)/2=379 高音 3
8'h44:music_data<=11'd358;//(1MHz/1396.51Hz)/2=358 高音 4
8'h45:music_data<=11'd319;//(1MHz/1567.95Hz)/2=319 高音 5
8'h46:music_data<=11'd284;//(1MHz/1760.00Hz)/2=284 高音 6
8'h47:music_data<=11'd253;//(1MHz/1975.52Hz)/2=253 高音 7
8'h00:music_data<=11'd0; //0Hz
endcase
end
endmodule
/*-----------------------------------------------------------------------
\\\|///
\\ - - //
( @ @ )
+-----------------------------oOOo-(_)-oOOo-----------------------------+
CONFIDENTIAL IN CONFIDENCE
The entire notice above must be reproduced on all authorized copies.
Author : Wangzhen
Email Address : 3114706674@qq.com
Filename : addr_gen
Date : 2021-2-17
Description : 地址发生器模块
Modification History :
Date By Version Change Description
=========================================================================
2021/2/17 wangzhen 1.0 Original
-------------------------------------------------------------------------
| Oooo |
+------------------------------oooO--( )-----------------------------+
( ) ) /
\ ( (_/
\_)
----------------------------------------------------------------------*/
`timescale 1ns/1ns
module addr_gen
(
//global clock
input clk_1M,
input rst_n,
input time_finsh, //0.25s
output reg [6:0] addr
);
always @(posedge clk_1M or negedge rst_n)begin
if(!rst_n)begin
addr <= 7'd0;
end
else if(time_finsh)
addr <= addr + 1'b1;
else addr <= addr;
end
endmodule
(六)串口接受数据
利用PC发送文件,fpga解码存储到RAM 核中,提供音频信号。之前参与的DDS活动中也用到了uart解码,当时为加入帧头校验导致偶尔出现数据不匹配,此次加入了串口校验位(帧头)
/*-----------------------------------------------------------------------
\\\|///
\\ - - //
( @ @ )
+-----------------------------oOOo-(_)-oOOo-----------------------------+
CONFIDENTIAL IN CONFIDENCE
The entire notice above must be reproduced on all authorized copies.
Author : Wangzhen
Email Address : 3114706674@qq.com
Filename :
Date : 2021- 2-18
Description :
Modification History :
Date By Version Change Description
=========================================================================
2021/2/18 wangzhen 1.0 Original
-------------------------------------------------------------------------
| Oooo |
+------------------------------oooO--( )-----------------------------+
( ) ) /
\ ( (_/
\_)
----------------------------------------------------------------------*/
`timescale 1ns/1ns
module rx_decode
(
//global clock
input clk,
input rst_n,
input rx_en,
input [7:0] rx_data,
output W_R_control,//gao写
output reg [6:0] w_addr,
output reg [7:0] ram_data,
output reg beep_start_en //高有效
);
//帧头
localparam DATA_SOF1 = 8'hff;
localparam DATA_SOF2 = 8'haa;
localparam DATA_SOF3 = 8'haf;
localparam DATA_SOF4 = 8'hfa;
//帧尾
localparam DATA_EOF1 = 8'h22;
localparam DATA_EOF2 = 8'h33;
localparam DATA_EOF3 = 8'h23;
localparam DATA_EOF4 = 8'h32;
//状态
localparam IDLE = 4'd0;
localparam SOF1 = 4'd1;
localparam SOF2 = 4'd2;
localparam SOF3 = 4'd3;
localparam RXDB = 4'd4;
localparam EOF1 = 4'd5;
localparam EOF2 = 4'd6;
localparam EOF3 = 4'd7;
localparam EOF4 = 4'd8;
localparam DONE = 4'd9;
reg[3:0] r_cstate,r_nstate;
reg[7:0] r_bytecnt;
//时序逻辑状态切换
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
r_cstate <= IDLE;
end
else r_cstate <= r_nstate;
end
//组合逻辑切换状态
always @(*)begin
case(r_cstate)
IDLE:begin
if(rx_en)begin
if(rx_data == DATA_SOF1)
r_nstate = SOF1;
else r_nstate = IDLE;
end
else r_nstate = IDLE;
end
SOF1:begin
if(rx_en)begin
if(rx_data == DATA_SOF2)
r_nstate = SOF2;
else r_nstate = IDLE;
end
else r_nstate = SOF1;
end
SOF2:begin
if(rx_en)begin
if(rx_data == DATA_SOF3)
r_nstate = SOF3;
else r_nstate = IDLE;
end
else r_nstate = SOF2;
end
SOF3:begin
if(rx_en)begin
if(rx_data == DATA_SOF4)
r_nstate = RXDB;
else r_nstate = IDLE;
end
else r_nstate = SOF3;
end
RXDB:begin
if(rx_en)begin
if(r_bytecnt >= 8'd103)
r_nstate = EOF1;
else r_nstate = RXDB;
end
end
EOF1:begin
if(rx_en)begin
if(rx_data==DATA_EOF1)
r_nstate = EOF2;
else r_nstate = IDLE;
end
else r_nstate = EOF1;
end
EOF2:begin
if(rx_en)begin
if(rx_data==DATA_EOF2)
r_nstate = EOF3;
else r_nstate = IDLE;
end
else r_nstate = EOF2;
end
EOF3:begin
if(rx_en)begin
if(rx_data==DATA_EOF3)
r_nstate = EOF4;
else r_nstate = IDLE;
end
else r_nstate = EOF3;
end
EOF4:begin
if(rx_en)begin
if(rx_data==DATA_EOF4)
r_nstate = DONE;
else r_nstate = IDLE;
end
else r_nstate = EOF4;
end
DONE: r_nstate = IDLE;
default: ;
endcase
end
//对123个有效数据字节进行计数
always @(posedge clk)
if(r_cstate == RXDB) begin
if(rx_en) r_bytecnt <= r_bytecnt+1;
else ;
end
else r_bytecnt <= 'b0;
//对有效数据进行锁存(采集)
always @ (posedge clk)
if((r_cstate == RXDB) && rx_en)begin
w_addr <= r_bytecnt;
ram_data<= rx_data;
end
always @(posedge clk)
if(!rst_n) beep_start_en <= 'b0;
else if(r_cstate == DONE) beep_start_en <= 'b1;
else beep_start_en <= 'b0;
assign W_R_control = rx_en?1'b1:1'b0;
endmodule
(七)联合调试
为了降低综合时间我都是分模块调试完在和大工程联合调试,系统框图如下,包括数据控制
总结:本次项目更多的偏向时序控制,设计的数据协议较多,可以根据C语言的代码,通过FPGA编写状态机来控制数据交互,本次编写代码参考了很多网上优秀的教程,再次感谢大家们的开源精神,再一次感谢硬核学堂提供的活动。部分代码以及风格取自购买的fpga开发板。由于项目完成了才写这篇文章好多事情记不清,我记得最头疼的一件事软件报错ROM类型不匹配,直接F1看帮助和网上百度都没有解决,最后偶然看到网上说MAX10上存储器类型需要更改,才解决。总之大家要积极学习网络上大佬的代码,有朝一日也把自己写的比较好的代码开源。
资源占用情况,未作优化,可见fpga片上存储空间还是很充足的。