寒假一起练(4)项目演示(赵博)
本项目是一个基于Altera Max10的一个可以定时,显示,测温,报警的小项目。
标签
嵌入式系统
FPGA
数字逻辑
显示
啊啊赵
更新2021-02-25
944

项目介绍

       本项目是基于小脚丫FPGA训练平台的一个具有计时,实时测温,OLED显示,异步串口通信的项目。

系统框图
       本项目是由按键,实时时钟,测定温度,OLED显示,蜂鸣器,串口通信等模块实现的,按键主要用于复位和设置当前时间,实时时钟主要用于时间正常运行,温度模块主要用于测定环境温度,显示模块主要用于显示当前时间和环境温度,蜂鸣器主要用播放音频,通信模块主要用于上位机和板子之间的通信。当时间通过按键设定好后会和温度一起在OLED上面实时刷新,当时间到达准点时开始报警,时间,温度停止刷新。板子将温度发送给上位机,上位机把音频文件发送给板子,然后蜂鸣器播放该文件。播放结束后。时间,温度继续刷新。

FmXwDMUzBzeE3k-KXERCeY0-I8dk

具体功能

  1. 可定时时钟功能:可通过板子上的拨码开关进入设置模式或者计时模式。在设置模式中停止时钟,并可通过按键对时间进行修改,计时模式则时钟正常运行。OLED不停刷新时间。
  2. 实时测温功能:可通过小脚丫板载温度传感器测定周围环境实时温度,并在OLED上面实时显示。
  3. 报警功能:当时间达到整点时,板子开始报警,时钟停止运行,OLED上的温度数据停止刷新,并将信号通过异步串口通信传递给上位机。当蜂鸣器播放完音频文件后,时钟恢复运行,OLED恢复刷新。
  4. 通信功能:当时间达到整点时,将此刻环境温度通过异步串口通信传递给上位机,上位机接收到温度数据后显示在电脑上,并将自己制作的音频文件通过串口传递给板子,蜂鸣器开始播放。

项目背景

        FPGA是一种数字集成电路芯片,英文全称为Field Programmable Gate Array,中文名称为“现场可编程逻辑门阵列”。FPGA是数字电路的物理实现方式之一,它是高端的CPLD,本身是一些触发器。逻辑门,与非门,或非门登记本数字器件。它可实现高速的并行计算,且具有GPU缺乏的低功耗特性。设计FPGA的常用编程语言有Verilog HDL和VHDL,其中Verilog HDL为国内常用。与C/C++, Python等计算机编程语言不同的是,FPGA编程语言属于硬件编程语言,因此在使用FPGA编程语言时不能照搬软件设计思路。单片机和FPGA的区别,本质是软件和硬件的区别。单片机设计属软件范畴;它的硬件(单片机芯片)是固定的,通过软件编程语言描述软件指令在硬件芯片上的执行;FPGA设计属硬件范畴,它的硬件(FPGA)是可编程的。还有一个最大的区别,单片机程序是顺序执行,而FPGA程序是并行执行。因此,FPGA在有很大的前景。

设计思路

         本人通过查阅各种资料,网站视频,例程代码,逐步掌握板子上的一些外设使用方法,并学习了Verilog语言并学会了与单片机不同的并行计算思维。借助之前学习单片机的经验,我将该项目分成了时钟,按键,OLED,DS18B20,蜂鸣器,串口通信几个大的模块。先将各个模块代码完,成然后将各个模块合在一起,实现该项目功能。

时钟模块

  1. 采用传统的12M晶振达到12M次为一秒,并产生的一个上升沿,便于每秒秒钟加一。
  2. 当检测到拨码开关进入设置模式时,时钟停止运行,通过按键模块检测三个轻触式微动按键消抖后的键值变化修改时钟时间。
  3. 当到达整点时,板子进入报警模式,时钟停止。当蜂鸣器播放完音频以及串口发送完数据后时钟继续运行。
    reg [1:0] cnt_1s;	
    reg [23:0] counts;	
    			
    always@(posedge clk or negedge rst)begin    //时钟周期
    	if(!rst)begin
    		counts <= 1'b0;
    	end
    	else if(counts >= 24'd11_999_999)begin
    		cnt_1s <= 1'b1;
    		counts <= 1'b0;
    	end
    	else begin
    		counts <= counts + 1'b1;
    		cnt_1s <= 1'b0;
    	end	
    end	​
    reg [7:0] Min;						//分钟(二进制)
    reg [7:0] Sec;						//秒钟(二进制)
    reg alarm;						//报警控制位 1:报警
    reg beepover;				                //蜂鸣器响铃完成标志位	
    always@(posedge clk or negedge rst) begin               //时间到达00:00报警
    	if(!rst) begin
    		alarm <= 1'b0;
    	end
    	else if(Min == 8'd0 && Sec == 8'd0) begin
    				alarm<= 1'b1;	
    			if(beepover)begin	 alarm<= 1'b0;end		
    	end
    	else
    		alarm <= 1'b0;	
    end	
    	​

温度模块

  1. 借鉴单片机中DS18B20的使用经验以及芯片手册,再通过引用电子森林给的例程(电子森林温度模块),完成了温度模块部分的代码。这里就不展示了。
  2. DS18B20将温度以16位数据传递给板子,高八位的前五位为符号位,其他均为数据位。这里需要注意的是由于数据位的低四位为小数位,也就是说得到的数据是环境温度左移四位的数据,因此当需要使用温度时要把数据右移四位。

显示模块

  1. 显示模块的代码引用电子森林给的例程(电子森林显示模块)并加以修改,增加了1s时钟上升沿用于时间分隔符的闪烁,接收板子传递的报警信号,实时时间以及实时温度。
  2. 当进入报警模式时,显示屏上的温度停止刷新。
  3. 由于时间,温度以二进制格式存储,而显示模块需要BCD码显示。因此这里我查阅了CSDN上的博客选择了最简单的加三移位法(移位加三法)。这里不进行过多介绍。

以下为我将二进制转化为BCD码的程序:

always@(posedge clk or negedge rst)begin    //时钟,分钟,秒钟,温度 二级制转BCD码
	if(!rst) begin
		shift_hou <= {20{1'b0}};
		shift_min <= {20{1'b0}};
		shift_tem <= {20{1'b0}};
	end
	else  begin
	shift_hou = {12'h0,Hou};
	shift_min = {12'h0,Min};
	shift_tem = {16'h0,temp_data[12:0]};
		if(mode) begin shift_min = {12'h0,SetMin}; shift_hou = {12'h0,SetHou}; end
		repeat(13) begin
			if(shift_tem[16:13]>=5)shift_tem[16:13] = shift_tem[16:13] + 2'b11;
			if(shift_tem[20:17]>=5)shift_tem[20:17] = shift_tem[20:17] + 2'b11;
			if(shift_tem[24:21]>=5)shift_tem[24:21] = shift_tem[24:21] + 2'b11;
			if(shift_tem[28:25]>=5)shift_tem[28:25] = shift_tem[28:25] + 2'b11;
			shift_tem = shift_tem<<1;	
		end
		repeat(8) begin
			if(shift_hou[11:8]>=5)shift_hou[11:8] = shift_hou[11:8] + 2'b11;
			if(shift_hou[15:12]>=5)shift_hou[15:12] = shift_hou[15:12] + 2'b11;
			if(shift_hou[19:16]>=5)shift_hou[19:16] = shift_hou[19:16] + 2'b11;
			shift_hou = shift_hou<<1;
		end
		repeat(8) begin
			if(shift_min[11:8]>=5)shift_min[11:8] = shift_min[11:8] + 2'b11;
			if(shift_min[15:12]>=5)shift_min[15:12] = shift_min[15:12] + 2'b11;
			if(shift_min[19:16]>=5)shift_min[19:16] = shift_min[19:16] + 2'b11;
			shift_min = shift_min<<1;
		end
		time_hou = shift_hou[19:8];
		time_min = shift_min[19:8];
		if(alarm) tem = tem;
		else tem = shift_tem[28:13];
	end
end

以下为我的显示部分的的代码:

if(cnt_main >= 5'd6) cnt_main <= 5'd5;//  重复显示数据改变数字
else cnt_main <= cnt_main + 1'b1;

case(cnt_main)	
 5'd0:	begin state <= INIT; end
 5'd1: begin y_p <= 8'hb0; x_ph <= 8'h10; x_pl <= 8'h0; num <= 5'd16; char <= "TIME:           ";state <= SCAN; end
 5'd2: begin y_p <= 8'hb1; x_ph <= 8'h10; x_pl <= 8'h0; num <= 5'd16; char <= "TEM :           ";state <= SCAN; end
 5'd3:	begin y_p <= 8'hb2; x_ph <= 8'h18; x_pl <= 8'h0; num <= 5'd16; char <= "                ";state <= SCAN;end
 5'd4:	begin	y_p <= 8'hb3; x_ph <= 8'h17; x_pl <= 8'h0; num <= 5'd16;char <= "                ";state <= SCAN; end
 5'd5:	begin y_p <= 8'hb0; x_ph <= 8'h13; x_pl <= 8'h0; num <= 5'd8; char[7:0] <= " ";  char[15:8] <= " "; char[23:16] <= " "; char[31:24] <= time_min[3:0]; char[39:32] <= time_min[7:4]; 										 
       if(!mode && cnts >= 24'd5_999_999 && !salarm)char[47:40] <= " "; 
       else char[47:40] <= 8'd58;char[55:48] <= time_hou[3:0]; char[63:56]<=time_hou[7:4]; state <= SCAN; end
 5'd6: begin y_p <= 8'hb1; x_ph <= 8'h13; x_pl <= 8'h0; num <= 5'd5; char[7:0] <= temp_data[3:0];char[15:8] <= temp_data[7:4];char[23:16] <= 8'd46; char[31:24] <= temp_data[11:8];char[39:32] <= temp_data[15:12]; state <= SCAN;  end
 default: state <= IDLE;
endcase

 

蜂鸣器模块

  1. 蜂鸣器运用了PWM控制高低电平时间发出不同声音的原理。借助于电子森林的例程(电子森林蜂鸣器模块)中的不同音调对应的PWM占空比,并将经典的小星星曲目的每个音调转换成16位数据,当板子传递给上位机报警信号时,上位机将14个音调数据提供异步通信串口传递给板子。当音频播放完成后,板子退出报警模式。
  2. 这里需要注意的是,由于最初没有注意板子资源大小,过多的音调数据占用了大量空间,这个地方之后有待改善。
always@(posedge clk or negedge rst) begin
	if(!rst) begin   flag <= 1'b0; cycle <= 16'd0;beepover <= 1'b0;end
	else if(flags) begin	
	case(cnt[26:22])
	5'd0:  begin  flag <= 1'b0; end
	5'd1:  begin cycle <= cyc[223:208]; flag <= 1'b0; end //L1,
	5'd3:  begin cycle <= cyc[207:192]; flag <= 1'b0; end //L1,		
	5'd5:  begin cycle <= cyc[191:176]; flag <= 1'b0; end //L5,
	5'd7:  begin cycle <= cyc[175:160]; flag <= 1'b0; end //L5,
	5'd9:  begin cycle <= cyc[159:144]; flag <= 1'b0; end//L6,
	5'd11: begin cycle <= cyc[143:128]; flag <= 1'b0; end//L6,
	5'd13: begin cycle <= cyc[127:112]; flag <= 1'b0; end//L5,
	5'd15: begin cycle <= cyc[111:96]; flag <= 1'b0; end//L4,
	5'd17: begin cycle <= cyc[95:80]; flag <= 1'b0; end//L4,
	5'd19: begin cycle <= cyc[79:64]; flag <= 1'b0; end//L3,
	5'd21: begin cycle <= cyc[63:48]; flag <= 1'b0; end//L3,
	5'd23: begin cycle <= cyc[47:32]; flag <= 1'b0; end//L2,
	5'd25: begin cycle <= cyc[31:16]; flag <= 1'b0; end//L2,
	5'd27: begin cycle <= cyc[15:0]; flag <= 1'b0; end//L1,
	5'd29: begin flag <= 1'b1; beepover <= 1'b1;      end
	default: begin cycle <= 16'd0;	flag <= 1'b0; end//cycle为0,PWM占空比为0,低电平
	endcase
    end
	else begin
        cycle <= cycle;
	end
end

按键消抖模块

  1. 该部分程序引用了电子森林的官方例程(电子森林程序例程)并加以修改。
  2. 这里需要注意的是程序中是以三位寄存器保存键值,但是在调试程序发现有时按键总是没有反应,后来将键值赋值给三个测试led时发现了问题,键值在按下的时候只在一个周期内改变,在下一个周期到来后就复位了。这个问题困扰了我很久,只有在一步步仿真以及测试的过程中才找到了问题所在。

通信模块

  1. 该板子采用了异步串口通信(电子森林串口通信)。当板子检测到报警信号时,将此刻温度传递给上位机,上位机将音频文件以14个16位数据的格式传给上位机。
  2. 上位机我采用的传统的C#语言编写的窗体程序,并根据项目需求进行了部分修改。
    private void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
            {
                receiveover = 1;
                int re;
                if (!radioButton4.Checked)
                {
                    string str = serialPort1.ReadExisting();            
                    textBox1.AppendText(Convert.ToString(str));
                }
                else
                {
                    byte data;
                    data = (byte)serialPort1.ReadByte();
                    string str = Convert.ToString(data).ToUpper();
                    textBox1.AppendText((str.Length == 1 ? "0" + str : str) + "℃");
                    receiveover = 1;
                }
            }
            try
           {
             for(x=0; x<14;x++)
             {
               for (j = 0; j < 2; j++)
                {
                 if (j % 2 == 1)
                    {
                       serialPort1.Write(Convert.ToString(tone[x] % 256, 16).ToUpper());                           
                    }
                 else
                    {
                       serialPort1.Write(Convert.ToString(tone[x] / 256, 16).ToUpper());
                    }
                 }
                     receiveover = 0;
              }
            }
            catch (Exception err)
            {
                    MessageBox.Show("串口数据写入错误", "错误");
                    serialPort1.Close();
                    button1.Enabled = true;
                    button2.Enabled = false;
            }​

占用资源

Total logic elements 1950/2304
Total registers        609
Total Pin                 18/112
Total memory bits   0
Total Plls                0
由于需要存储14个16位的音调文件以及OLED的字库,但是经过优化还是占用了一些空间,以后会继续完善。
FjGFDwjnW8E0bolZvWJP3026djGG
管脚定义
Fqj8KIjZgekbTvrFdx3h7dQ0HCyU

心得体会

本人通过“寒假在家一起练”该活动开始学习FPGA以及Verilog语言。从最开始不知如何创建工程,添加芯片,到后来慢慢接触一些例程,翻阅书籍,观看一些视频教程,然后能点亮一个灯,让灯“流”起来,能用按键控制,再到后面能够写一些小项目,最后一步步实现活动要求的功能。每实现一个功能就感觉又向前迈了一大步。由于第一次接触FPGA,最初许多程序的思路都是以单片机的思维去编写,最后发现很多错误,问了许多学长,老师,一起学习的同学,查阅了许多网站和书籍,花费了大量时间才解决。也通过该活动学习了状态机,加法器,二进制与BCD码的转化方法……最后理解了FPGA是并行执行程序,而单片机是顺序执行。最后,感谢帮助过我的老师,学长以及同学!!!

附件下载
setup.exe
上位机
TEST.sof
团队介绍
成都信息工程大学电子工程学院
团队成员
赵博
一大二学生
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号