🎉欢迎来到FPGA专栏~基于FPGA的循迹小车
- ☆* o(≧▽≦)o *☆嗨~我是小夏与酒🍹
- ✨博客主页:小夏与酒的博客
- 🎈该系列文章专栏:FPGA学习之旅
- 文章作者技术和水平有限,如果文中出现错误,希望大家能指正🙏
- 📜 欢迎大家关注! ❤️
🎉 基于FPGA的循迹小车
- 一、效果演示
- 二、搭建硬件
- 🥝开发板详细介绍
- 🥝完整的循迹小车
- 三、程序编写
- 🍊整体编程思路
- 🍊PWM模块
- 🍊LCD模块
- 🍊顶层模块
- 四、调试及结果分析
- 🍋调试注意事项
- 🍋结果分析
一、效果演示
基于FPGA的循迹小车【Spirit_V2】
由于场地的问题,打滑现象严重,所以用手轻推辅助小车前进。
对于效果演示的分析,见调试及结果分析。
二、搭建硬件
该系列文章只是作为学习记录,并无其余用途。所发文章内容是经过自己本身操作和记录整理得来。
本篇文章记录基于小精灵V2(Spirit_V2)开发板的循迹小车。
❤️特别鸣谢:小月电子工作室
🔸【小月电子】大佬博客链接:Moon_3181961725
🔸【FPGA】Altera Cyclone IV EP4CE6入门系统板购买链接:EP4CE6
🔸【FPGA】智能小车驱动板购买链接:FPGA小车驱动版
🔸【FPGA】配套电池与充电器购买链接:电池与充电器
❤️有不清楚的地方可以咨询客服:小月电子工作室淘宝店铺
🥝开发板详细介绍
开发板详细介绍:【FPGA-Spirit_V2】小精灵V2开发板初使用
🥝完整的循迹小车
一套完整的循迹小车包括:核心控制板、小车驱动板、电源和循迹模块。
红外循迹模块基本介绍和使用:红外循迹模块使用介绍。
按照店铺提供的资料,组装好小车驱动板,加上循迹模块,效果如下:
小车烧录程序时的连线图:
三、程序编写
🍊整体编程思路
本次项目的编程思路见下图的思维导图:
对于循迹过程,使用红外传感器来判断是否检测到黑线:如果检测到黑线,模块输出低电平;如果没有检测到黑线,模块输出高电平。
使用最简单的电机差速来控制小车的循迹过程:如果左侧的传感器检测到黑线,那么左侧电机停止转动,右侧电机继续转动;如果右侧的传感器检测到黑线,那么右侧电机停止转动,左侧电机继续转动;如果两个传感器都没有检测到黑线,那么两个电机以相同的速度同时运行。
该项目主要需要使用到PWM模块和LCD模块。
🍊PWM模块
PWM是用来控制电机转速的。在使用电机差速来控制循迹时,也可以让一侧的电机转速低于另一侧,而不是使一侧停止转动,使用该方法可以更加丝滑快速地完成转向。
小月电子提供的PWM模块:
//脉冲生成模块,通过控制输出脉冲频率及占空比来控制小车的速度
module ctrl_moto_pwm(
input clk ,//时钟50M
input rst_n ,//复位,低电平有效
input [7:0] spd_high_time,
input [7:0] spd_low_time ,
output period_fini,
output reg pwm //脉冲信号
);
//状态机
parameter idle = 8'h0,//空闲状态
step_high = 8'h1,//脉冲高电平状态,当为该状态时,pwm为高电平
step_low = 8'h2;//脉冲低电平状态,当为该状态时,pwm为低电平
reg [7:0] curr_st ;
reg [7:0] curr_st_ff1 ;
reg [10:0] step_high_time ;
reg [10:0] step_low_time ;
reg [10:0] step_high_cnt ;
reg [10:0] step_low_cnt ;
// wire[10:0] spd_high_time;
// wire[10:0] spd_low_time ;
wire[10:0] hspd_high_time;
wire[10:0] hspd_low_time ;
// assign spd_high_time=20;
// assign spd_low_time =10;
assign period_fini=(step_low_cnt==step_low_time)?1'b1:1'b0;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
step_high_time<=0;
step_low_time<=0;
end
else
begin
step_high_time<=spd_high_time;
step_low_time<=spd_low_time;
end
end
always@(posedge clk)curr_st_ff1<=curr_st ;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
curr_st<=idle;
else case(curr_st)
idle:curr_st<=step_high ;
step_high:
begin
if(step_high_cnt==step_high_time)
curr_st<=step_low;
else
;
end
step_low:
begin
if(step_low_cnt==step_low_time)
curr_st<=step_high;
else
;
end
default:;
endcase
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
step_high_cnt<=0;
else if(curr_st==idle)
step_high_cnt<=0;
else if(curr_st==step_high)
step_high_cnt<=step_high_cnt+1;
else
step_high_cnt<=0;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
step_low_cnt<=0;
else if(curr_st==idle)
step_low_cnt<=0;
else if(curr_st==step_low)
step_low_cnt<=step_low_cnt+1;
else
step_low_cnt<=0;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
pwm<=0;
end
else if(curr_st==idle)
begin
pwm<=0;
end
else if(curr_st==step_high)
pwm<=1;
else if(curr_st==step_low)
pwm<=0;
else
pwm<=1;
end
endmodule
🍊LCD模块
LCD液晶屏模块主要是用来显示数据的,通过显示的数据,可以帮助我们进行更高效的调试。在该项目中,LCD只用来显示字符。
LCD1602模块:
//模块介绍:LCD1602显示驱动
module lcd (
input clk ,//系统时钟输入50M
input rst_n ,//复位,低电平有效
output reg [7:0] dat ,//LCD的8位数据口
output reg rs ,//数据命令选择信号,高电平表示数据,低电平表示命令
output rw ,//读写标志,高电平表示读,低电平表示写,该程序我们只对液晶屏进行写操作
output en //LCD的控制脚
);
reg [15:0] counter ;
reg [ 5:0] current ;
reg clkr ;
reg e ;
//定义了LCD状态机需要的状态。
parameter set0 =6'd0;
parameter set1 =6'd1;
parameter set2 =6'd2;
parameter set3 =6'd3;
parameter set4 =6'd4;
parameter dat0 =6'd5;
parameter dat1 =6'd6;
parameter dat2 =6'd7;
parameter dat3 =6'd8;
parameter dat4 =6'd9;
parameter dat5 =6'd10;
parameter dat6 =6'd11;
parameter dat7 =6'd12;
parameter dat8 =6'd13;
parameter dat9 =6'd14;
parameter dat10=6'd15;
parameter dat11=6'd16;
parameter dat12=6'd17;
parameter dat13=6'd18;
parameter dat14=6'd19;
parameter dat15=6'd20;
parameter fini=6'hF1;
always @(posedge clk or negedge rst_n) //da de data_w1 zhong pinlv
begin
if(!rst_n)
begin
counter<=0;
clkr<=0;
end
else
begin
counter<=counter+1;
if(counter==16'h000f)
clkr=~clkr;
else
;
end
end
always @(posedge clkr or negedge rst_n)
begin
if(!rst_n)
begin
current<=set0;
dat<=0;
rs<=0;
e<=1;
end
else
begin
case(current)
set0: begin e<=0;rs<=0; dat<=8'h38; current<=set1; end //*设置8位格式,2行,5*7*
set1: begin e<=0;rs<=0; dat<=8'h0C; current<=set2; end //*整体显示,关光标,不闪烁*/
set2: begin e<=0;rs<=0; dat<=8'h06; current<=set3; end //*设定输入方式,增量不移位*/
set3: begin e<=0;rs<=0; dat<=8'h01; current<=set4; end //*清除显示*/
set4: begin e<=0;rs<=0; dat<=8'h00; current<=dat0; end //设置显示第一行
dat0: begin e<=0;rs<=1; dat<="H"; current<=dat1; end
dat1: begin e<=0;rs<=1; dat<="E"; current<=dat2; end
dat2: begin e<=0;rs<=1; dat<="L"; current<=dat3; end
dat3: begin e<=0;rs<=1; dat<="L"; current<=dat4; end
dat4: begin e<=0;rs<=1; dat<="O"; current<=dat5; end
dat5: begin e<=0;rs<=1; dat<=" "; current<=dat6; end
dat6: begin e<=0;rs<=1; dat<="S"; current<=dat7; end
dat7: begin e<=0;rs<=1; dat<="p"; current<=dat8; end
dat8: begin e<=0;rs<=1; dat<="i"; current<=dat9; end
dat9: begin e<=0;rs<=1; dat<="r"; current<=dat10 ; end
dat10: begin e<=0;rs<=1; dat<="i"; current<=dat11; end
dat11: begin e<=0;rs<=1; dat<="t"; current<=dat12; end
dat12: begin e<=0;rs<=1; dat<="_"; current<=dat13; end
dat13: begin e<=0;rs<=1; dat<="V"; current<=dat14; end
dat14: begin e<=0;rs<=1; dat<="2"; current<=dat15; end
dat15: begin e<=0;rs<=1; dat<="!"; current<=fini; end
fini: begin e<=1;rs<=0; dat<=8'h00; end
default: current<=set0;
endcase
end
end
assign en=clkr|e;
assign rw=0;
endmodule
上述代码的LCD显示效果如下:
🍊顶层模块
该项目中的顶层模块主要用于控制循迹过程。
同时,通过板载的按键模块来控制小车的启动与停止:
板载按键模块低电平时有效,代码:
//通过标志位flag判断小车是否开启运行
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
flag <= 0;
else if(!key && !flag)
flag <= 1;
else
flag <= flag;
end
最后,加入蜂鸣器来更好地判断小车是否检测到黑线。板载蜂鸣器为有源蜂鸣器,低电平时发出声音,控制代码:
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
beep_key <= 0;
else if(track1 == 1 && track2 == 0)
beep_key <= 1;
else if(track1 == 0 && track2 == 1)
beep_key <= 1;
else if(track3 == 1 && track4 == 0)
beep_key <= 1;
else if(track3 == 0 && track4 == 1)
beep_key <= 1;
else
beep_key <= 0;
end
//加入蜂鸣器来查看小车的转向情况
assign beep = beep_key?1'b0:1'b1;
完整的顶层模块代码:
module car_top(
input clk,//50MHZ
input rst_n,//全局复位,低电平有效
input key,//控制小车是否开始运行
input track1,
input track2,
input track3,
input track4,
output reg dir_l_1=0,//控制左电机正转或者反转
output reg dir_l_2=0,
output reg dir_r_1=0,//控制右电机正转或者反转
output reg dir_r_2=0,
output reg f_pwm_l=0,//左电机pwm值
output reg f_pwm_r=0,//右电机pwm值
output [7:0]udat,//LCD的8位数据口
output urs,//数据命令选择信号,高电平表示数据,低电平表示命令
output urw,//读写标志,高电平表示读,低电平表示写,该程序我们只对液晶屏进行写操作
output uen,//LCD的控制脚
output beep
);
reg [1:0] flag = 0;
reg [1:0] beep_key = 0;
ctrl_moto_pwm uctrl_moto_pwm(
.clk(clk),//时钟50M
.rst_n(rst_n),//复位,低电平有效
.spd_high_time(10),
.spd_low_time(75),
.period_fini(),
.pwm(pwm)//脉冲信号
);
lcd Ulcd(
.clk(clk),
.rst_n(rst_n),
.dat(udat),
.rs(urs),
.rw(urw),
.en(uen)
);
//通过标志位flag判断小车是否开启运行
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
flag <= 0;
else if(!key && !flag)
flag <= 1;
else
flag <= flag;
end
//循迹与差速转向
always@(posedge clk)begin
//当标志位flag为0时,小车静止
if(flag == 0)begin
dir_l_1<=0;
dir_l_2<=0;
f_pwm_l<=0;
dir_r_1<=0;
dir_r_2<=0;
f_pwm_r<=0;
end
//当标志位flag为1时,小车开始循迹
else if(flag==1)begin
//当两侧传感器都没有检测到黑线时,两轮同时行进
if(track1 == 0 && track2 == 0)begin
dir_l_1<=0;
dir_l_2<=1;
f_pwm_l<=pwm;
dir_r_1<=0;
dir_r_2<=1;
f_pwm_r<=pwm;
end
//内-向右调整
else if(track1 == 0 && track2 == 1)begin
dir_l_1<=0;
dir_l_2<=1;
f_pwm_l<=pwm;
dir_r_1<=0;
dir_r_2<=0;
f_pwm_r<=0;
end
//内-向左调整
else if(track1 == 1 && track2 == 0)begin
dir_l_1<=0;
dir_l_2<=0;
f_pwm_l<=0;
dir_r_1<=0;
dir_r_2<=1;
f_pwm_r<=pwm;
end
//外-向右调整
else if(track3 == 1 && track4 == 0 && track1 == 1 && track2 == 1)begin
dir_l_1<=0;
dir_l_2<=0;
f_pwm_l<=0;
dir_r_1<=0;
dir_r_2<=1;
f_pwm_r<=pwm;
end
//外-向左调整
else if(track3 == 0 && track4 == 1 && track1 == 1 && track2 == 1)begin
dir_l_1<=0;
dir_l_2<=1;
f_pwm_l<=pwm;
dir_r_1<=0;
dir_r_2<=0;
f_pwm_r<=0;
end
//当内部两个红外传感器都检测不到黑线时,停止运动
else if(track1 == 1 && track2 == 1)begin
dir_l_1<=0;
dir_l_2<=0;
f_pwm_l<=0;
dir_r_1<=0;
dir_r_2<=0;
f_pwm_r<=0;
end
end
else;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
beep_key <= 0;
else if(track1 == 1 && track2 == 0)
beep_key <= 1;
else if(track1 == 0 && track2 == 1)
beep_key <= 1;
else if(track3 == 1 && track4 == 0)
beep_key <= 1;
else if(track3 == 0 && track4 == 1)
beep_key <= 1;
else
beep_key <= 0;
end
//加入蜂鸣器来查看小车的转向情况
assign beep = beep_key?1'b0:1'b1;
endmodule
RTL视图:
本项目使用了四个循迹模块(四路循迹),在代码编写的过程中也可以把循迹部分和蜂鸣器部分单独封装成模块的形式,方便在顶层模块的调用。
四、调试及结果分析
🍋调试注意事项
🔸需要根据黑线的宽度调整好红外模块的位置和方向;
🔸调整好合适的红外模块灵敏度(电阻值);
🔸选择一个合适的循迹场地,地面平整,不会打滑;
🔸调整好合适的PWM。
🍋结果分析
🔸本次测试场地会导致打滑现象的出现;
🔸没有调整好合适的PWM,导致小车前轮有时几乎不转;
🔸小车在行驶过程中遇到阻碍使轮子停止转动时,需要立刻将小车调入待机状态或者关闭电源,否则会导致电机驱动模块发烫。
🧸结尾
- ❤️ 感谢您的支持和鼓励! 😊🙏
- 📜您可能感兴趣的内容:
- 【FPGA零基础学习之旅#1】 AC620V2开发板测试
- 【Go黑帽子】使用Golang编写一个TCP扫描器(基础篇)
- 【Arduino TinyGo】【最新】使用Go语言编写Arduino-环境搭建和点亮LED灯
- 【Labview-3D虚拟平台】Labview与Solidworks联合仿真(保姆级)(下)装配体、父级与子级