任务介绍
本次活动中,使用了STEP Baseboard4.0底板+STEP MXO2 LPC核心板,完成了任务一,构建了一个交通灯控制系统。该任务大致分为四个部分。(在通知群中,群主有说不对74元件有硬性要求,我就没有使用相关元器件)
- 在数码管上显示计时信息 - 图形化
- 蜂鸣器报警 - 图形化
- 接近传感器检测人员走近 -Verilog
- 环境光感知,自动点亮路灯(小脚丫核心板上的单色LED)- Verilog
硬件资源介绍
STEP BaseBoard V4.0是专为小脚丫系列FPGA核心板设计的第四代扩展底板,采用161.8mm×100mm标准尺寸,可适配全系小脚丫核心板进行功能扩展。该底板集成了丰富的功能模块,包括存储器、温湿度传感器、红外接近传感器、环境光传感器、矩阵键盘、旋转编码器、HDMI接口、RGB LCD液晶屏、8位7段数码管阵列、蜂鸣器模块、UART通信接口、ADC/DAC转换模块以及WiFi通信单元,为数字逻辑、微机原理、可编程逻辑器件及EDA工具等课程提供了完善的实验平台。
在本项目开发中,主要应用了核心板自带的数码管显示模块、三色LED指示灯和机械按键,以及扩展板上的红外接近传感器、环境光传感器和蜂鸣器模块。其中:数码管驱动代码基于官方例程移植实现;三色LED通过PWM调光实现红黄绿三原色的混色控制,完整复现了交通信号灯的显色特性;传感器模块通过I2C总线协议读取测距数据和光照强度值;蜂鸣器模块则采用可编程PWM波形发生器驱动,实现了音频信号输出功能。
设计分析与实现方式
设计分析
1.基本的交通灯系统
能够实现基本的红、绿、黄三种灯光颜色的转换,能够显示当前状态剩余的时间,以及作为嵌入式项目基本的复位功能。
2.蜂鸣器提示
在现实生活中,由于视觉障碍人士的需求,部分交通灯存在无障碍设计,会在绿灯期间发出特定的声音来提示视觉障碍人士。结合任务二,应该在绿灯期间用蜂鸣器提示,在黄灯期间使用更高频的蜂鸣警示。
3.接近报警
结合任务三,我利用红外接近传感器获取的距离数据,当距离小于一定数值时,触发蜂鸣器蜂鸣。结合到现实的智能交通灯系统中,可以理解为在红灯期间当有东西行出时,触发报警,来警示闯红灯的行人。(马路上有用于检测车辆是否闯红灯的传感器,所以不必用于行车)
4.光线检测路灯
当光线强度小于一定程度时,将会触发路灯亮起。
实现方式
本次任务中,我在webide上实现了任务一与任务二,在lattice diamond上实现了任务二、任务三与任务四。
在下文,我将依次分为第一部分与第二部分来讲解
第一部分
流程框图
图形化模块展示与大致说明
在我设计该图形化程序时,首先,我先确定了需要设置的I/O端口(输入/输出端口),随后布置了左下方的分频器,该分频器用于产生1Hz的计数频率(该核心板的晶振为12MHz,预分频系数为12000000,计数位宽为32),随后编写了light_contrle模块,用于状态机设定、切换并能够输出时间参数、标志位与rbg_led的信号,随后,rbg_led的信号直接接入到输出端,时间参数经过bin_to_bcd编码输出到数码管上,标志为进入一个简单的选择模块,根据当前不同的标志位组合,选择对应的pwm波形,输出到beeper,即无源蜂鸣器。
模块代码
代码链接:stepfpga
light_contrle
reg [5:0] out;
reg [3:0] timecount;
reg host_flag = 0,branch_flag = 0;//标志位,用于区分当前是绿灯还是黄灯,当前者为1后者为0时为绿灯,后者为1前者为0时为黄灯
parameter S1 = 2'b00, // 状态机的编码,用于第二、三部分的case选择与状态选择
S3 = 2'b10,
S4 = 2'b11;
parameter time_s1 = 4'd15, // 时间参数
time_s3 = 4'd7,
time_s4 = 4'd3;
// 交通灯控制信号
parameter led_s1 = 6'b011011, // LED2 红色 LED1 红色
led_s3 = 6'b101101, // LED2 绿色 LED1 绿色
led_s4 = 6'b001001; // LED2 黄色 LED1 黄色
reg [1:0] cur_state, next_state;
// 第一部分:时序逻辑——同步状态更新
always @ (posedge clk or negedge rst_n) begin
if (!rst_n == 1)
cur_state <= S1;
else
cur_state <= next_state;
end
// 第二部分:组合逻辑——状态跳转
//该部分大致逻辑为:通过case获取当前状态,当时间参数大于1时,不断将下一个状态赋值为当前状态,当时间等于1时,将当前状态赋值为循环中的下一种状态
always @ (cur_state or rst_n or timecount) begin // 修正敏感信号列表,改为 always @ (*)
if(!rst_n) begin
next_state = S1;
end
else begin
case(cur_state)
S1:begin
host_flag <= 0;
branch_flag <= 0;
if(timecount==1)
next_state = S3;
else
next_state = S1;
end
S3:begin
host_flag <= 1;
branch_flag <= 0;
if(timecount==1)
next_state = S4;
else
next_state = S3;
end
S4:begin
host_flag <= 0;
branch_flag <= 1;
if(timecount==1)
next_state = S1;
else
next_state = S4;
end
default: next_state = S1;
endcase
end
end
// 第三部分:时序逻辑——输出与计时
//根据接下来的状态,赋值时间常数,这样可以避免时间跳转到0
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
out <= led_s1;
timecount <= time_s1;
end
else begin
case (next_state)
S1: begin
out <= led_s1;
if (timecount == 1)
timecount <= time_s1;
else
timecount <= timecount - 1;
end
S3: begin
out <= led_s3;
if (timecount == 1)
timecount <= time_s3;
else
timecount <= timecount - 1;
end
S4: begin
out <= led_s4;
if (timecount == 1)
timecount <= time_s4;
else
timecount <= timecount - 1;
end
default: begin
out <= led_s1;
timecount <= time_s1;
end
endcase
end
end
//该模块由所给例程修改,合并了支路与主路,且为了方便,直接删去了第二种状态,没有修改数字,不过不影响使用
time_seperate and bin_to_bcd
reg [3:0] time_ge;
reg [3:0] time_shi;
always@(*)begin
time_ge = timecount % 10; // 取模10,范围0~9
// 计算十位
if (timecount >= 10)
time_shi = 4'd1; // 当timecount >= 10时,十位为1
else
time_shi = 4'd0; // 当timecount < 10时,十位为0
end
//一个简单的获取个位与十位的程序,用于输出到两个数码管上
reg [6:0] seg2;
always @(*) begin
case(a)
4'd0: seg2 <= 7'b1111110; // '0'
4'd1: seg2 <= 7'b0110000; // '1'
4'd2: seg2 <= 7'b1101101; // '2'
4'd3: seg2 <= 7'b1111001; // '3'
4'd4: seg2 <= 7'b0110011; // '4'
4'd5: seg2 <= 7'b1011011; // '5'
4'd6: seg2 <= 7'b1011111; // '6'
4'd7: seg2 <= 7'b1110000; // '7'
4'd8: seg2 <= 7'b1111111; // '8'
4'd9: seg2 <= 7'b1111011; // '9'
default: seg2 <= 7'b1111110; // 默认显示'0'
endcase
end
//数码管的编码程序,让数字与数码管上的七段发光二极管对应
beeper_chooser
reg beeper;
always @ (*) begin
if(host_flag == 0 && branch_flag == 0)
begin
beeper <= 0;
end
else if(host_flag == 1 && branch_flag == 0)
begin
beeper <= clk_1;
end
else if(host_flag == 0 && branch_flag == 1)
begin
beeper <= clk_2;
end
else
begin
beeper <= 0;
end
end
//根据不同的标志值,选择对应的pwm波
资源占用报告
第二部分
流程框图
关键代码展示与说明
parameter THRESHOLD_LUX = 32'd1000;//光强阈值定义
// 三色LED亮灭控制逻辑
always @(lux) begin
if (lux > THRESHOLD_LUX)
rgb_led = 3'b111; // 光强过低,红绿蓝灯全亮
else
rgb_led = 3'b000; // 光强正常,灯灭
end
在decoder中,通过访问I2C总线,获得需要的传感器的数据,在该project中,获取的数据是接近传感器与光线传感器获取的数据,我对光线检测的部分做了简单的改写和延拓,删除了原有的光强显示功能,保留光强数据的换算部分,通过预先设定的阈值,与实际换算得到的光强数据进行对比,来判断是否接通三色灯。
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
counter <= 16'd0;
beeper <= 1'b0;
end else begin
if (prox_dat [11:9] > 3'b011) begin // 当 prox_dat 大于 3'b011 时启动蜂鸣器
if (counter < PWM_PERIOD / 2)
beeper <= 1'b1;
else
beeper <= 1'b0;
if (counter >= PWM_PERIOD - 1)
counter <= 16'd0;
else
counter <= counter + 1'b1;
end else begin
beeper <= 1'b0; // 关闭蜂鸣器
counter <= 16'd0;
end
end
end
此外,我也对距离检测部分做了一定的延拓,原有程序中的led灯列会根据距离传感器的数据改变亮起数量,大致是越接近亮起的灯数量越多,而我以此程序中设定的距离参数为依据,当第四个led亮起时,蜂鸣器会发出蜂鸣。在程序中,我直接以距离参数作为判断依据,这也是第四个led灯亮起时对应的距离参数,与按照led灯的输出为判断依据等价
资源占用报告
实物展示
第一部分展示
以上是三种灯颜色的切换与倒计时显示
第二部分展示
离接近传感器远时,亮起较少的灯
接近时,亮起更多的灯
光线传感器被遮挡,模拟环境光变暗时,灯亮起
总结
项目障碍
实话说,任务一中的代码逻辑并不算复杂,对于我这样一名并没有嵌入式项目开发经验的人来说,同样如此,可以说是一次比较友好的活动。但这也并不意味着在完成项目的过程中,并没有遇到任何困难,而这其中,最主要的困难是来自于lattice diamond的调试。就lattice diamond而言,该编译器功能完善且强大,但缺点在于没什么知名度,网上能找到的信息很少,给的资料中能给的帮助也有点有限制。这一点尤其体现在配置该软件的许可时,不知道是什么原因,我申请到的许可文件名后面多了一个空格,导致我怎么配置软件都显示检测不到license文件。事实上,我完成这个项目的绝大多数时间都在和license斗智斗勇。
在我完成第二部分内容时,也遇到了lattice diamond无法正确识别我设置的顶层文件的问题,这个问题也困扰了我许久,而最终的解决方案也有些简单粗暴,那就是我把改编的程序全部塞到了原有的头文件里面。。。。
心得
最后的部分,很感谢硬禾学堂给我们的这次实战机会,让我逐渐走入了嵌入式开发的世界。以及一点点的建议,webide的图形化编程似乎有些奇怪的问题,保存自定义模块的时候按回车键会闪退。