寒假一起练项目(四)-王震
项目要求全部完成,由于用的是资源量稍多的max 10系列fpga,有些代码没优化也能综合。后期会继续优化代码逻辑代码风格。
标签
FPGA
wangzhen
更新2021-02-22
996

在此首先感谢硬核学堂提供的此次活机会,上次也完美完成了硬核学堂的DDS活动。参与这次活动还是在学校期间,手里正好有一块MAX10的核心板,我用过国产安陆的TD开发软件,lattice家的,quartus,vivado。有些学术不精的感觉,所以这次还是继续使用quartus软件完成本次项目。

这次的任务要是用单片机去做,可能一下午就完成了,所以本来打算移植软核或者用nios软核,想想还是借机练习fpga并行处理问题的能力吧,完成任务后也深刻体会到了能用单片机做的还是用单片机做吧/(ㄒoㄒ)/~~。

拿到板子后第一件是看原理图,板子上的器件是玩过单片机都熟悉的器件,就免去在看数据手册的步骤了。开始划分模块,开始根据题目要求完成项目。

模块任务划分。

  1. oled显示动态参数
  2. DS18B20温度读取
  3. 时钟记数
  4. Uart send 数据
  5. 蜂鸣器播放音乐
  6. Uart 接受音频信号
  7. 联合调试

(一)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自带的内部分析仪也是一样。我是为了减少问题出现的概率还是用的外部逻辑分析仪(自己太菜)FnLAgtyE6rZxpVkr3zibVPIs9RvB

/*-----------------------------------------------------------------------
								 \\\|///
							   \\  - -  //
								(  @ @  )
+-----------------------------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解码,当时为加入帧头校验导致偶尔出现数据不匹配,此次加入了串口校验位(帧头)

FmLE_IZna0QgDrgXTN-kbQSYdFgd

 

/*-----------------------------------------------------------------------

                                                              \\\|///

                                                        \\  - -  //

                                                             (  @ @  )

+-----------------------------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

(七)联合调试Fk0TzRJsiOaxYH206FvKErzp4uFY

为了降低综合时间我都是分模块调试完在和大工程联合调试,系统框图如下,包括数据控制

总结:本次项目更多的偏向时序控制,设计的数据协议较多,可以根据C语言的代码,通过FPGA编写状态机来控制数据交互,本次编写代码参考了很多网上优秀的教程,再次感谢大家们的开源精神,再一次感谢硬核学堂提供的活动。部分代码以及风格取自购买的fpga开发板。由于项目完成了才写这篇文章好多事情记不清,我记得最头疼的一件事软件报错ROM类型不匹配,直接F1看帮助和网上百度都没有解决,最后偶然看到网上说MAX10上存储器类型需要更改,才解决。总之大家要积极学习网络上大佬的代码,有朝一日也把自己写的比较好的代码开源。

 

FuejW10mTkpp5wFkJoIW-2Uad0CF

资源占用情况,未作优化,可见fpga片上存储空间还是很充足的。

附件下载
stepwz.sof
可执行文件
stepwz.pof
可执行文件
团队介绍
东北林业大学-信息与计算机工程学院
团队成员
王震
不会FPGA的小菜鸟,欢迎交流,指导. QQ:3114706674
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号