2025寒假练 - 基于小脚丫FPGA实现交通灯控制系统
该项目使用了WebIDE平台,以及Diamond编程软件,实现了交通灯系统的设计,它的主要功能为:红绿灯系统,在红灯亮起时报警,同时检测是否有物体靠近接近传感器,然后驱动蜂鸣器报警,同时路灯亮起。。
标签
verilog
交通灯
参加活动/培训
frangipani
更新2025-03-24
北京理工大学
11

交通灯控制系统项目总结报告

项目介绍

本次项目旨在构建一个交通灯控制系统,主要使用常规的 74 系列元器件,通过 WebIDE 的图形化编程以及 Verilog 语言编程实现多种功能。在硬件方面,涉及到数码管、蜂鸣器、接近传感器以及用于环境光感知的小脚丫核心板上的单色 LED 等元器件。软件编程上,图形化编程用于实现数码管计时信息显示和蜂鸣器报警功能,同时使用Diamond软件实现接近传感器检测人员走近以及环境光感知自动点亮路灯的功能。

项目设计思路

图形化编程思路

我们利用核心板上的rgb_led灯来模拟红绿灯,同时在核心板上的数码管上显示红绿灯的剩余时间,并让蜂鸣器在红绿灯切换时报警

Diamond编程思路

为了整个项目的完整性,我们将图形化编程的部分也整合到了Diamond项目中,当有物体靠近接近传感器时,蜂鸣器会报警,距离越近,音调越高,当有物体遮挡住传感器时,其检测到的亮度变暗,此时板上的led灯会亮起,亮度越暗,led灯亮起的数量越多。

功能框图

关键代码介绍

软件流程图

数码管显示模块

输入数据:模块接收两个 4 位输入信号 seg_data_1 seg_data_2,分别表示两个数码管需要显示的数字(0~9)。

译码表查表:通过 seg 数组,将输入的数字映射为对应的数码管驱动信号。

输出驱动信号:将查表得到的驱动信号输出到 seg_led_1 seg_led_2,用于驱动数码管显示对应的数字。

// Module Function:数码管的译码模块初始化

 

module segment (seg_data_1,seg_data_2,seg_led_1,seg_led_2);

 

       input [3:0] seg_data_1;        //数码管需要显示0~9十个数字,所以最少需要4位输入做译码

       input [3:0] seg_data_2;        //小脚丫上第二个数码管

       output [8:0] seg_led_1;        //在小脚丫上控制一个数码管需要9个信号 MSB~LSB=DIG、DP、G、F、E、D、C、B、A

       output [8:0] seg_led_2;        //在小脚丫上第二个数码管的控制信号  MSB~LSB=DIG、DP、G、F、E、D、C、B、A

 

    reg [8:0] seg [9:0];             //定义了一个reg型的数组变量,相当于一个10*9的存储器,

                                                        //存储器一共有10个数,每个数有9位宽

                                                       

    initial                          //在过程块中只能给reg型变量赋值,

                                                        //Verilog中有两种过程块always和initial

                                //initial和always不同,其中语句只执行一次

       begin

              seg[0] = 9'h3f;         //对存储器中第一个数赋值9'b00_0011_1111,相当于共阴极接地,DP点变低不亮,7段显示数字  0

              seg[1] = 9'h06;         //7段显示数字  1

              seg[2] = 9'h5b;         //7段显示数字  2

              seg[3] = 9'h4f;         //7段显示数字  3

              seg[4] = 9'h66;         //7段显示数字  4

              seg[5] = 9'h6d;         //7段显示数字  5

              seg[6] = 9'h7d;         //7段显示数字  6

              seg[7] = 9'h07;         //7段显示数字  7

              seg[8] = 9'h7f;         //7段显示数字  8

              seg[9] = 9'h6f;         //7段显示数字  9

            end

 

       assign seg_led_1 = seg[seg_data_1];      //连续赋值,这样输入不同四位数,

       assign seg_led_2 = seg[seg_data_2];      //就能输出对于译码的9位输出

 

endmodule

红绿灯倒计时模块

时钟分频:使用 24 位计数器 counter 实现 1 秒的时钟周期。

倒计时逻辑:根据当前时间递减倒计时,并在倒计时结束时切换红绿灯状态。

红绿灯状态切换:通过 is_red_light_R 信号控制红绿灯的切换。

灯光控制:根据红绿灯状态输出对应的 RGB LED 颜色信号。

输出信号:将倒计时秒数和红绿灯状态通过输出信号提供给外部模块。

reg        light_red;        // 表示红灯状态(红灯时为1,绿灯时为0)

reg [23:0] counter;          // 用于12百万周期的24位计数器

reg [4:0] current_time;      // 当前的秒数计时器(最大31秒)

 

always @(posedge clk or negedge rst_n) begin

    if (!rst_n) begin

        counter <= 0;

        sec0_R <= GREEN_SEC0; sec1_R <= GREEN_SEC1;

        light_red <= 0;     // 初始输出绿灯

    end else if (counter == CNT_1s_MAX - 1) begin

        counter <= 0;  // 每秒重置计数器

        if (sec0_R == 0) begin

            if(sec1_R == 0)begin

                case(is_red_light_R)

                    1:begin

                        sec0_R <= RED_SEC0; sec1_R <= RED_SEC1;

                    end

                    default:begin

                        sec0_R <= GREEN_SEC0; sec1_R <= GREEN_SEC1;

                    end

                endcase

            end else begin

                sec1_R <= sec1_R - 1;  // 递减倒计时器

                sec0_R <= 9;

            end

        end else begin

            sec0_R <= sec0_R - 1;  // 递减倒计时器

        end

        light_red <= is_red_light_R;  // 输出当前红绿灯状态(红灯为1,绿灯为0)

       

    end else begin

        counter <= counter + 1;  // 递增计数器

    end

end

always@(posedge clk or negedge rst_n)begin

    if(!rst_n)

        is_red_light_R <= 0;

    else if(counter == CNT_1s_MAX - 2 && sec1_R == 0 && sec0_R == 0)

        // 当当前时间倒计时到0时,切换红绿灯状态

        is_red_light_R <= ~is_red_light_R;

    else

        is_red_light_R <= is_red_light_R;

end

蜂鸣器报警模块

周期性信号生成:通过计数器 cnt 生成周期性信号,用于控制蜂鸣器的报警频率。

蜂鸣器输出控制:根据蜂鸣器使能信号 beeper_en 和周期性信号 cnt 控制蜂鸣器的输出状态。

红绿灯状态检测:通过延迟信号 is_red_light_d1 检测红绿灯状态的变化。

蜂鸣器报警计时:通过计数器 cnt_2s 计时蜂鸣器报警的持续时间(2 秒)。

蜂鸣器使能控制:当红绿灯状态发生变化时,蜂鸣器使能信号 beeper_en 置为 1,蜂鸣器开始报警;当报警持续时间达到 2 秒时,蜂鸣器使能信号清零,蜂鸣器停止报警。

always @(posedge clk or negedge rst_n)

        if(!rst_n)

            cnt <= 1'b1;

        else if(cnt >= L3)

            cnt <= 1'b1;

        else

            cnt <= cnt + 1'b1;

   

   

    always@(posedge clk or negedge rst_n)

        if(!rst_n)

            beeper_R <= 1'b1;

        else if(beeper_en)

            if(cnt < L3 >>1)

                beeper_R <= 1'b1;

            else

                beeper_R <= 1'b0;

        else

            beeper_R <= beeper_R;

 

    always@(posedge clk or negedge rst_n)begin

        if(!rst_n)begin

            is_red_light_d1 <= is_red_light;

        end else begin

            is_red_light_d1 <= is_red_light;

        end

    end

 

 

    // 蜂鸣器响2s

    always@(posedge clk or negedge rst_n)begin

        if(!rst_n)

            cnt_2s <= 32'd0;

        else if(beeper_en)

            cnt_2s <= cnt_2s + 1'b1;

        else

            cnt_2s <= 32'd0;

    end

 

    //上升沿或者下降沿(红绿灯切换的2s时间内)蜂鸣器会工作

    always@(posedge clk or negedge rst_n)begin

        if(!rst_n)

            beeper_en <= 1'b0;

        else if(~is_red_light_d1 & is_red_light)

            beeper_en <= 1'b1;

        else if(is_red_light_d1 & ~is_red_light)

            beeper_en <= 1'b1;

        else if(cnt_2s == CNT_1s_MAX << 1)

            beeper_en <= 1'b0;

        else

            beeper_en <= beeper_en;

end

传感器驱动模块:

I2C通信: 使用I2C协议与RPR0521RS传感器通信。

传感器数据读取: 读取传感器的ALS(环境光传感器)、IR(红外)和Prox(接近传感器)数据。

数据有效脉冲: 提供一个数据有效脉冲dat_valid,用于指示数据读取完成。

状态机控制: 使用有限状态机(FSM)控制通信流程,包括初始化、写配置、读数据等。

always@(posedge clk_400khz or negedge rst_n) begin

              if(!rst_n) begin       //如果按键复位,将相关数据初始化

                     scl <= 1'd1; sda <= 1'd1; ack <= ACK; ack_flag <= 1'b0; cnt <= 1'b0;

                     cnt_main <= 1'b0; cnt_mode1 <= 1'b0; cnt_mode2 <= 1'b0;

                     cnt_start <= 1'b0; cnt_write <= 1'b0; cnt_read <= 1'b0; cnt_stop <= 1'b0;

                     cnt_delay <= 1'b0; num_delay <= 24'd4800;

                     state <= IDLE; state_back <= IDLE;

              end else begin

                     case(state)

                            IDLE:begin     //软件自复位,主要用于程序跑飞后的处理----------------0

                                          scl <= 1'd1; sda <= 1'd1; ack <= ACK; ack_flag <= 1'b0; cnt <= 1'b0;

                                          cnt_main <= 1'b0; cnt_mode1 <= 1'b0; cnt_mode2 <= 1'b0;

                                          cnt_start <= 1'b0; cnt_write <= 1'b0; cnt_read <= 1'b0; cnt_stop <= 1'b0;

                                          cnt_delay <= 1'b0; num_delay <= 24'd4800;

                                          state <= MAIN; state_back <= IDLE;

                                   end

                            MAIN:begin//-------------------------1

                                          if(cnt_main >= 4'd11) cnt_main <= 4'd4;         //写完控制指令后循环读数据

                                          else cnt_main <= cnt_main + 1'b1;   

                                          case(cnt_main)

                                                 4'd0:       begin dev_addr <= 7'h38; reg_addr <= 8'h40; reg_data <= 8'h0a; state <= MODE1; end     //写入配置

                                                 4'd1:       begin dev_addr <= 7'h38; reg_addr <= 8'h41; reg_data <= 8'hc6; state <= MODE1; end      //写入配置

                                                 4'd2:       begin dev_addr <= 7'h38; reg_addr <= 8'h42; reg_data <= 8'h02; state <= MODE1; end     //写入配置

                                                 4'd3:       begin dev_addr <= 7'h38; reg_addr <= 8'h43; reg_data <= 8'h01; state <= MODE1; end     //写入配置

                                                 4'd4:       begin state <= DELAY; dat_valid <= 1'b0; end //12ms延时

                                                 4'd5:       begin dev_addr <= 7'h38; reg_addr <= 8'h44;  state <= MODE2; end    //读取配置

                                                 4'd6:       begin  prox_dat<= {dat_h,dat_l}; end //读取数据

                                                 4'd7:       begin dev_addr <= 7'h38; reg_addr <= 8'h46;  state <= MODE2; end    //读取配置

                                                 4'd8:       begin ch0_dat <= {dat_h,dat_l}; end //读取数据

                                                 4'd9:       begin dev_addr <= 7'h38; reg_addr <= 8'h48;  state <= MODE2; end    //读取配置

                                                 4'd10:     begin ch1_dat <= {dat_h,dat_l}; end //读取数据

                                                 4'd11:     begin dat_valid <= 1'b1; end       //读取数据

                                                 default: state <= IDLE;  //如果程序失控,进入IDLE自复位状态

                                          endcase

                                   end

                            MODE1:begin       //单次写操作------------------------2

                                          if(cnt_mode1 >= 4'd5) cnt_mode1 <= 1'b0;       //对START中的子状态执行控制cnt_start

                                          else cnt_mode1 <= cnt_mode1 + 1'b1;

                                          state_back <= MODE1;

                                          case(cnt_mode1)

                                                 4'd0:       begin state <= START; end  //I2C通信时序中的START

                                                 4'd1:       begin data_wr <= dev_addr<<1; state <= WRITE; end    //设备地址

                                                 4'd2:       begin data_wr <= reg_addr; state <= WRITE; end     //寄存器地址

                                                 4'd3:       begin data_wr <= reg_data; state <= WRITE; end     //写入数据

                                                 4'd4:       begin state <= STOP; end   //I2C通信时序中的STOP

                                                 4'd5:       begin state <= MAIN; end   //返回MAIN

                                                 default: state <= IDLE;  //如果程序失控,进入IDLE自复位状态

                                          endcase

                                   end

                            MODE2:begin       //两次读操作----------------------3

                                          if(cnt_mode2 >= 4'd10) cnt_mode2 <= 1'b0;       //对START中的子状态执行控制cnt_start

                                          else cnt_mode2 <= cnt_mode2 + 1'b1;

                                          state_back <= MODE2;

                                          case(cnt_mode2)

                                                 4'd0:       begin state <= START; end  //I2C通信时序中的START

                                                 4'd1:       begin data_wr <= dev_addr<<1; state <= WRITE; end    //设备地址

                                                 4'd2:       begin data_wr <= reg_addr; state <= WRITE; end     //寄存器地址

                                                 4'd3:       begin state <= START; end  //I2C通信时序中的START

                                                 4'd4:       begin data_wr <= (dev_addr<<1)|8'h01; state <= WRITE; end      //设备地址

                                                 4'd5:      begin ack <= ACK; state <= READ; end //读寄存器数据

                                                 4'd6:       begin dat_l <= data_r; end

                                                 4'd7:       begin ack <= NACK; state <= READ; end     //读寄存器数据

                                                 4'd8:       begin dat_h <= data_r; end

                                                 4'd9:       begin state <= STOP; end   //I2C通信时序中的STOP

                                                 4'd10:     begin state <= MAIN; end   //返回MAIN

                                                 default: state <= IDLE;  //如果程序失控,进入IDLE自复位状态

                                          endcase

                                   end

                            START:begin  //I2C通信时序中的起始START------------------------4

                                          if(cnt_start >= 3'd5) cnt_start <= 1'b0;      //对START中的子状态执行控制cnt_start

                                          else cnt_start <= cnt_start + 1'b1;

                                          case(cnt_start)

                                                 3'd0:       begin sda <= 1'b1; scl <= 1'b1; end //将SCL和SDA拉高,保持4.7us以上

                                                 3'd1:       begin sda <= 1'b1; scl <= 1'b1; end //clk_400khz每个周期2.5us,需要两个周期

                                                 3'd2:       begin sda <= 1'b0; end       //SDA拉低到SCL拉低,保持4.0us以上

                                                 3'd3:       begin sda <= 1'b0; end       //clk_400khz每个周期2.5us,需要两个周期

                                                 3'd4:       begin scl <= 1'b0; end //SCL拉低,保持4.7us以上

                                                 3'd5:       begin scl <= 1'b0; state <= state_back; end     //clk_400khz每个周期2.5us,需要两个周期,返回MAIN

                                                 default: state <= IDLE;  //如果程序失控,进入IDLE自复位状态

                                          endcase

                                   end

                            WRITE:begin  //I2C通信时序中的写操作WRITE和相应判断操作ACK-----------------------------5

                                          if(cnt <= 3'd6) begin    //共需要发送8bit的数据,这里控制循环的次数

                                                 if(cnt_write >= 3'd3) begin cnt_write <= 1'b0; cnt <= cnt + 1'b1; end

                                                 else begin cnt_write <= cnt_write + 1'b1; cnt <= cnt; end

                                          end else begin

                                                 if(cnt_write >= 3'd7) begin cnt_write <= 1'b0; cnt <= 1'b0; end  //两个变量都恢复初值

                                                 else begin cnt_write <= cnt_write + 1'b1; cnt <= cnt; end

                                          end

                                          case(cnt_write)

                                                 //按照I2C的时序传输数据

                                                 3'd0:       begin scl <= 1'b0; sda <= data_wr[7-cnt]; end     //SCL拉低,并控制SDA输出对应的位

                                                 3'd1:       begin scl <= 1'b1; end //SCL拉高,保持4.0us以上

                                                 3'd2:       begin scl <= 1'b1; end       //clk_400khz每个周期2.5us,需要两个周期

                                                 3'd3:       begin scl <= 1'b0; end //SCL拉低,准备发送下1bit的数据

                                                 //获取从设备的响应信号并判断

                                                 3'd4:       begin sda <= 1'bz; end       //释放SDA线,准备接收从设备的响应信号

                                                 3'd5:       begin scl <= 1'b1; end //SCL拉高,保持4.0us以上

                                                 3'd6:       begin ack_flag <= i2c_sda; end       //获取从设备的响应信号并判断

                                                 3'd7:       begin scl <= 1'b0; if(ack_flag)state <= state; else state <= state_back; end //SCL拉低,如果不应答循环写

                                                 default: state <= IDLE;  //如果程序失控,进入IDLE自复位状态

                                          endcase

                                   end

                            READ:begin   //I2C通信时序中的读操作READ和返回ACK的操作----------------------------6

                                          if(cnt <= 3'd6) begin    //共需要接收8bit的数据,这里控制循环的次数

                                                 if(cnt_read >= 3'd3) begin cnt_read <= 1'b0; cnt <= cnt + 1'b1; end

                                                 else begin cnt_read <= cnt_read + 1'b1; cnt <= cnt; end

                                          end else begin

                                                 if(cnt_read >= 3'd7) begin cnt_read <= 1'b0; cnt <= 1'b0; end  //两个变量都恢复初值

                                                 else begin cnt_read <= cnt_read + 1'b1; cnt <= cnt; end

                                          end

                                          case(cnt_read)

                                                 //按照I2C的时序接收数据

                                                 3'd0:       begin scl <= 1'b0; sda <= 1'bz; end //SCL拉低,释放SDA线,准备接收从设备数据

                                                 3'd1:       begin scl <= 1'b1; end //SCL拉高,保持4.0us以上

                                                 3'd2:       begin data_r[7-cnt] <= i2c_sda; end //读取从设备返回的数据

                                                 3'd3:       begin scl <= 1'b0; end //SCL拉低,准备接收下1bit的数据

                                                 //向从设备发送响应信号

                                                 3'd4:       begin sda <= ack; end //发送响应信号,将前面接收的数据锁存

                                                 3'd5:       begin scl <= 1'b1; end //SCL拉高,保持4.0us以上

                                                 3'd6:       begin scl <= 1'b1; end //SCL拉高,保持4.0us以上

                                                 3'd7:       begin scl <= 1'b0; state <= state_back; end     //SCL拉低,返回MAIN状态

                                                 default: state <= IDLE;  //如果程序失控,进入IDLE自复位状态

                                          endcase

                                   end

                            STOP:begin   //I2C通信时序中的结束STOP-----------------------------7

                                          if(cnt_stop >= 3'd5) cnt_stop <= 1'b0;      //对STOP中的子状态执行控制cnt_stop

                                          else cnt_stop <= cnt_stop + 1'b1;

                                          case(cnt_stop)

                                                 3'd0:       begin sda <= 1'b0; end       //SDA拉低,准备STOP

                                                 3'd1:       begin sda <= 1'b0; end       //SDA拉低,准备STOP

                                                 3'd2:       begin scl <= 1'b1; end //SCL提前SDA拉高4.0us

                                                 3'd3:       begin scl <= 1'b1; end //SCL提前SDA拉高4.0us

                                                 3'd4:       begin sda <= 1'b1; end       //SDA拉高

                                                 3'd5:       begin sda <= 1'b1; state <= state_back; end     //完成STOP操作,返回MAIN状态

                                                 default: state <= IDLE;  //如果程序失控,进入IDLE自复位状态

                                          endcase

                                   end

                            DELAY:begin  //12ms延时---------------------------8

                                          if(cnt_delay >= num_delay) begin

                                                 cnt_delay <= 1'b0;

                                                 state <= MAIN;

                                          end else cnt_delay <= cnt_delay + 1'b1;

                                   end

                            default:;

                     endcase

              end

       end

蜂鸣器报警模块

根据物体离传感器的距离控制蜂鸣器的音调

always@(posedge clk or negedge rst_n) begin

        if(!rst_n)begin

            cycle <= L3;

        end else begin

            if(prox_dat2 <= 30) begin cycle <= L3;end      //L1,

            else if(prox_dat2 >  30 && prox_dat2 <=  42) begin cycle <= L2;end    //L2,

            else if(prox_dat2 >  42 && prox_dat2 <=  54) begin cycle <= L3;end    //L3,

            else if(prox_dat2 >  54 && prox_dat2 <=  66) begin cycle <= L4;end    //L4,

            else if(prox_dat2 >  66 && prox_dat2 <=  78) begin cycle <= L5;end    //L5,

            else if(prox_dat2 >  78 && prox_dat2 <=  90) begin cycle <= L6;end    //L6,

            else if(prox_dat2 >  90 && prox_dat2 <= 102) begin cycle <= L7;end    //L7,

            else if(prox_dat2 > 102 && prox_dat2 <= 114) begin cycle <= M1;end    //M1,

            else if(prox_dat2 > 114 && prox_dat2 <= 126) begin cycle <= M2;end    //M2,

            else if(prox_dat2 > 126 && prox_dat2 <= 138) begin cycle <= M3;end    //M3,

            else if(prox_dat2 > 138 && prox_dat2 <= 150) begin cycle <= M4;end    //M4,

            else if(prox_dat2 > 150 && prox_dat2 <= 162) begin cycle <= M5;end    //M5,

            else if(prox_dat2 > 162 && prox_dat2 <= 174) begin cycle <= M6;end    //M6,

            else if(prox_dat2 > 174 && prox_dat2 <= 186) begin cycle <= M7;end    //M7,

            else if(prox_dat2 > 186 && prox_dat2 <= 198) begin cycle <= H1;end    //H1,

            else begin cycle <= H2;end    //H2,

        end

end

实物功能展示图

难题及解决办法

项目中涉及到大量 Verilog 编程,由于不熟悉 Verilog 语法和逻辑,导致难以读懂代码,无法进行有效的调试和修改。

解决方法:

学习 Verilog 基础知识:通过查阅 Verilog 教程、书籍和在线资源,学习 Verilog 的基本语法和常用模块结构。

分析模块功能:将代码分解为多个模块,逐个分析每个模块的功能和输入输出关系,理解其在系统中的作用。

使用仿真工具:利用仿真工具对 Verilog 代码进行仿真,观察信号波形和模块内部状态的变化,帮助理解代码的执行过程和功能实现。

资源占用报告

心得体会

通过本次交通灯控制系统的项目实践,我收获颇丰。在项目实施过程中,遇到了不少难题,但通过查阅资料、请教老师和同学以及自己的不断尝试和探索,最终都得到了解决。这让我深刻体会到,在面对复杂的技术问题时,要有坚持不懈的精神和勇于尝试的态度,同时也要善于利用各种资源和工具,不断学习和积累经验。

总体而言,本次交通灯控制系统项目是一次非常宝贵的学习和实践经历,让我在硬件电路设计、软件编程以及问题解决等方面都得到了很大的提升,也为我今后的学习和工作积累了宝贵的经验。


附件下载
task_12.zip
https://www.stepfpga.com/project/17792/gui?type=top
task_12.jed
beeper_led_rgb-34-1.rar
团队介绍
北京理工大学 吴敬丹
团队成员
吴敬丹
北京理工大学
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号