基于nexys4开发板的超声波测距设计

本文详述了一项使用nexys4开发板和HC-SR04超声波模块实现的测距系统设计。系统能实时显示障碍物距离,并通过数码管、RGBLED和蜂鸣器进行反馈。用户可通过开关设定距离门限,调整单位和蜂鸣器频率。遇到的问题包括数码管显示不稳定和蜂鸣器工作时的干扰,通过调整触发周期和优化中断处理得以解决。
摘要由CSDN通过智能技术生成

基本实验要求

1.控制超声波发送和接收传感器
2.通过数码管实时显示障碍物距离
3.低于或高于门限距离LED显示蓝、红色,正常距离范围内显示绿色
4.门限距离可自由设定
5.在此基础上进行创新

实现功能and拓展功能

1.数码管适时显示到障碍物距离;
2.打开sw0开关后,RGB LED使能启动,此时低于门限显示蓝色、高于门限显示红色、正常范围内显示绿色;
(因为这灯太亮又懒得写代码调亮度于是就这样好了)
3.距离低于门限时数码管显示CLOSE,高于门限时显示FA;
(nexys4的八段数码管显示不了“R”,岂可修)
4.打开sw1开关后,数码管显示数值固定,方便记录数码管所显示的数据;
5.打开sw2开关后,16位led根据所测距离的增大,点亮数目依次增多;
6.打开sw3开关后,蜂鸣器工作,根据所测距离的增大,发出频率变高;
7.btn center用于切换数码管显示数据单位为厘米或英寸;
8.btn left与btn right分别控制下阈值的增减,下阈值最小为4cm;
9.btn up与btn down分别控制上阈值的增减,上阈值最大为3m;
10.当距离门限小于10cm时,数码管高三位显示到最近阈值的距离。

(做了好多鸡肋功能…

实验环境

  实验软件使用Vivado 2018.3,用于搭建所需硬件平台,vivado自带SDK用以编写实现功能的C语言程序代码,FPGA开发板采用nexys4开发板,配合超声波模块HC-SR04(工作电压为3.3V~5V)。

超声波模块HC-SR04原理

HC-SR04
  向超声波模块的TRIG引脚中周期性地发送持续时间至少10us的脉冲,此时超声波模块内部将产生8个周期的40khz声波脉冲,通过模块发送端发出,此时超声波模块的ECHO引脚将变为高电平。ECHO引脚是数据引脚,用于进行距离测量。发送超声波脉冲串后,ECHO引脚将变为高电平,之后保持高电平,直到检测到超声波回波信号为止。故ECHO引脚高电平的持续时间近似等于声波经过两倍距离所消耗时间。

硬件平台搭建

整体框图
  整体硬件部分如上,下面是几个比较重要的模块:

  ①计数器捕获模式来计算echo高电平持续时间

time2
  捕获模式下采取对上升沿敏感的capturing0计录上升沿时的计数值,采取对下降沿敏感的capturing1引脚记录下降沿时的计数值,二者计数值相减即为高电平持续时间对应的计数值。(关于在高电平期间计数值满的情况在代码中进行判断)

  ②计数器输出PWM脉宽调制信号
pwm
  设置计数器装载值控制脉冲周期与脉宽。

  ③其余模块的简要说明:
  一个计数器采用pwm波输出频率与测得距离成正比的正方波,连接到外置蜂鸣器的正极;三个中断源,捕获模式的计数器以及连接FPGA开发板的button和switch。
  GPIO模块:GPIO0控制led的输出和switch的输入、GPIO1控制数码管的段选码和位选码的输出、GPIO2控制button的输入及三色灯的输出。

软件部分设计

①超声波模块HC-SR04运作:
  采用AXI TIMER产生周期为100ms、脉宽为15us的PWM脉宽调制信号作为超声波模块的trig触发信号,每0.1s发射一次信号的原因,一是减小程序总体运行负担,其次为了防止相关程序占用CPU时间过长导致数码管显示出现黯淡、数据无法即使更新、数据跳动过快等问题;采用另一个AXI TIMER模块的捕获模式对echo脉冲进行时间脉宽测量,按超声波在空气中的声速为340m/s计算距离。
  计数器初始化部分:

/*触发周期信号计数器的初始化(PWM)*/
//触发信号周期为100ms(相比于10ms,100ms更加稳定)
int TRIG_VALUE_TIMER = 10000000-2;
int trig_VALUE_TIMER = 1500-2;	//15us的脉冲宽度
Xil_Out32(XPAR_TMRCTR_1_BASEADDR+XTC_TLR_OFFSET,TRIG_VALUE_TIMER);
Xil_Out32(XPAR_TMRCTR_1_BASEADDR+XTC_TCSR_OFFSET,XTC_CSR_AUTO_RELOAD_MASK|XTC_CSR_DOWN_COUNT_MASK|XTC_CSR_LOAD_MASK|XTC_CSR_EXT_GENERATE_MASK|XTC_CSR_ENABLE_PWM_MASK);
status=Xil_In32(XPAR_TMRCTR_1_BASEADDR+XTC_TCSR_OFFSET);
status=(status&(~XTC_CSR_LOAD_MASK))|XTC_CSR_ENABLE_TMR_MASK;
Xil_Out32(XPAR_TMRCTR_1_BASEADDR+XTC_TCSR_OFFSET,status);

Xil_Out32(XPAR_TMRCTR_1_BASEADDR+XTC_TIMER_COUNTER_OFFSET+XTC_TLR_OFFSET,trig_VALUE_TIMER);
Xil_Out32(XPAR_TMRCTR_1_BASEADDR+XTC_TIMER_COUNTER_OFFSET+XTC_TCSR_OFFSET,XTC_CSR_AUTO_RELOAD_MASK|XTC_CSR_DOWN_COUNT_MASK|XTC_CSR_EXT_GENERATE_MASK|XTC_CSR_LOAD_MASK|XTC_CSR_ENABLE_PWM_MASK);
status=Xil_In32(XPAR_TMRCTR_1_BASEADDR+XTC_TIMER_COUNTER_OFFSET+XTC_TCSR_OFFSET);
status=(status&(~XTC_CSR_LOAD_MASK))|XTC_CSR_ENABLE_TMR_MASK;
Xil_Out32(XPAR_TMRCTR_1_BASEADDR+XTC_TIMER_COUNTER_OFFSET+XTC_TCSR_OFFSET,status);

/*echo宽度测量(捕获)*/
Xil_Out32(XPAR_TMRCTR_2_BASEADDR+XTC_TCR_OFFSET,0);
status=Xil_In32(XPAR_TMRCTR_2_BASEADDR+XTC_TCSR_OFFSET);
Xil_Out32(XPAR_TMRCTR_2_BASEADDR+XTC_TCSR_OFFSET,status|XTC_CSR_LOAD_MASK);
status=status|XTC_CSR_EXT_CAPTURE_MASK|XTC_CSR_EXT_GENERATE_MASK|XTC_CSR_CAPTURE_MODE_MASK|XTC_CSR_INT_OCCURED_MASK|XTC_CSR_ENABLE_TMR_MASK|XTC_CSR_ENABLE_INT_MASK;
Xil_Out32(XPAR_TMRCTR_2_BASEADDR+XTC_TCSR_OFFSET,status);

Xil_Out32(XPAR_TMRCTR_2_BASEADDR+XTC_TIMER_COUNTER_OFFSET+XTC_TCR_OFFSET,0);
status=Xil_In32(XPAR_TMRCTR_2_BASEADDR+XTC_TIMER_COUNTER_OFFSET+XTC_TCSR_OFFSET);
Xil_Out32(XPAR_TMRCTR_2_BASEADDR+XTC_TIMER_COUNTER_OFFSET+XTC_TCSR_OFFSET,status|XTC_CSR_LOAD_MASK);
status=status|XTC_CSR_EXT_CAPTURE_MASK|XTC_CSR_EXT_GENERATE_MASK|XTC_CSR_CAPTURE_MODE_MASK|XTC_CSR_INT_OCCURED_MASK|XTC_CSR_ENABLE_TMR_MASK|XTC_CSR_ENABLE_INT_MASK;
Xil_Out32(XPAR_TMRCTR_2_BASEADDR+XTC_TIMER_COUNTER_OFFSET+XTC_TCSR_OFFSET,status);

  中断函数:

void T2Handler()
{
	if((Xil_In32(XPAR_TMRCTR_2_BASEADDR+XTC_TCSR_OFFSET)&0x0100) == 0x0100)
	{
		ECHO_BEGIN_TAG = 1;
		Xil_Out32(XPAR_TMRCTR_2_BASEADDR+XTC_TCSR_OFFSET,Xil_In32(XPAR_TMRCTR_2_BASEADDR+XTC_TCSR_OFFSET));
	}
	else
	{
		ECHO_END_TAG = 1;
		Xil_Out32(XPAR_TMRCTR_2_BASEADDR+XTC_TIMER_COUNTER_OFFSET+XTC_TCSR_OFFSET,Xil_In32(XPAR_TMRCTR_2_BASEADDR+XTC_TCSR_OFFSET));
	}
}

  距离计算:

/*收集ECHO信号时间信息*/
if(ECHO_BEGIN_TAG==1)
{
	ECHO_BEGIN_VALUE = Xil_In32(XPAR_TMRCTR_2_BASEADDR+XTC_TLR_OFFSET);
	ECHO_BEGIN_TAG = 0;
}
if(ECHO_END_TAG==1)
{
	ECHO_END_VALUE = Xil_In32(XPAR_TMRCTR_2_BASEADDR+XTC_TIMER_COUNTER_OFFSET+XTC_TLR_OFFSET);
	ECHO_END_TAG = 0;
}
if(ECHO_END_VALUE > ECHO_BEGIN_VALUE)
{
	timers = ECHO_END_VALUE - ECHO_BEGIN_VALUE;
}
distance = timers*17/10000;

②数码管适时显示到障碍物距离:
  在每一次循环中将各个位所需显示的字符编码存储于数组中,在一遍循环的最后于for循环中输出,输出后立即将各位显示清零,防止上一次循环数码管的余辉对之后产生影响。

for (int i=0;i<8;i++)
{
	//数码管的片选与段选输出
	Xil_Out8(XPAR_AXI_GPIO_1_BASEADDR+XGPIO_DATA_OFFSET,poscode[i]);
	//厘米与英寸小数点的位置不同,需要做调整
	//数码管低五位显示距离值,高三位显示接近阈值时的警示值
	if (btn_Center_TAG == 1 && i==2) Xil_Out8(XPAR_AXI_GPIO_1_BASEADDR+XGPIO_DATA2_OFFSET,(segcode[i]&0x7f));
	else if (btn_Center_TAG == 0 && i==1) Xil_Out8(XPAR_AXI_GPIO_1_BASEADDR+XGPIO_DATA2_OFFSET,(segcode[i]&0x7f));
	else Xil_Out8(XPAR_AXI_GPIO_1_BASEADDR+XGPIO_DATA2_OFFSET,segcode[i]);
	//消除余辉
	Xil_Out8(XPAR_AXI_GPIO_1_BASEADDR+XGPIO_DATA_OFFSET,poscode[i]);
	Xil_Out8(XPAR_AXI_GPIO_1_BASEADDR+XGPIO_DATA2_OFFSET,0xff);
}

③RGB LED显示当前是否超过门限:
  采用CPIO模块控制RGB LED,当检测到当前距离低于下门限时输出电平使其亮蓝灯,当检测到当前距离高于上门限时输出电平使其亮红灯,否则亮绿灯。

//当前距离大于上限值
if((sw&0x0001) == 0x0001) Xil_Out16(XPAR_AXI_GPIO_2_BASEADDR+XGPIO_DATA2_OFFSET,0x0001);
//当前距离于正常范围中
if((sw&0x0001) == 0x0001) Xil_Out16(XPAR_AXI_GPIO_2_BASEADDR+XGPIO_DATA2_OFFSET,0x0004);
//当前距离小于下限值
if((sw&0x0001) == 0x0001) Xil_Out16(XPAR_AXI_GPIO_2_BASEADDR+XGPIO_DATA2_OFFSET,0x0002);

④数码管高三位对警示值的显示:
  当检测到当前距离离上门限或下门限小于10cm时,将之间的间隔编码后存储于待显示数组的高三位中,否则进行消隐。

//数码管前三位显示警示值
int distance_interval;
int three_on;
if(distance - threshold_MIN < 100)
{
	distance_interval = distance - threshold_MIN;
	three_on = 1;
}
else if(threshold_MAX - distance < 100)
{
	distance_interval = threshold_MAX - distance;
	three_on = 1;
}
else three_on = 0;
//段码存储数组的计算与储存
b10=100;
for(int digit_index=7;digit_index>=5;digit_index--)
{
	if (three_on == 1)
	{
		mask=temp_distance/b10;
		if (btn_Center_TAG == 1 && digit_index == 7) segcode[digit_index] = (segtable[mask]&0x7f);
		else if (btn_Center_TAG == 0 && digit_index == 6) segcode[digit_index] = (segtable[mask]&0x7f);
		else segcode[digit_index] = segtable[mask];
		temp_distance=temp_distance-mask*b10;
		b10=b10/10;
	}
	else segcode[digit_index]=0xff;
}

⑤调节上下门限距离:
  GPIO连接开发板的button按键,当按下时发生中断,检测按下按键,对当前上下门限阈值进行增减操作,当上门限到达最大值时,不可继续增加,否则将在终端显示警告,同样,当下门限到达最小值时也不可继续减小,当上下门限之间的距离为30cm时,二者间距也不可继续减小。
  上下限每次调整值为1cm,上阈值的上限为3m,下阈值的下限为4cm,上下阈值间的距离需大于30cm,以上功能均由下方代码块中的if语句判断实现。

if (threshold_MAX_UP_TAG == 1)
{
	threshold_MAX_UP_TAG = 0;
	if (threshold_MAX<3000) threshold_MAX += 10;
	else xil_printf("It's the limit.You cannot continue to adjust it !\n");
}
if (threshold_MAX_DOWN_TAG == 1)
{
	threshold_MAX_DOWN_TAG = 0;
	if (threshold_MAX-10 >= threshold_MIN+300) threshold_MAX -= 10;
	else xil_printf("It's the limit.You cannot continue to adjust it !\n");
}
if (threshold_MIN_UP_TAG == 1)
{
	threshold_MIN_UP_TAG = 0;
	if (threshold_MIN+10 <= threshold_MAX-300) threshold_MIN += 10;
	else xil_printf("It's the limit.You cannot continue to adjust it !\n");
}
if (threshold_MIN_DOWN_TAG == 1)
{
	threshold_MIN_DOWN_TAG = 0;
	if (threshold_MIN>40) threshold_MIN -= 10;
	else xil_printf("It's the limit.You cannot continue to adjust it !\n");
}

  中断函数:

void BtnHandler()
{
	btn = Xil_In16(XPAR_AXI_GPIO_2_BASEADDR+XGPIO_DATA_OFFSET); //读取按键状态
	if((btn&0x0010) == 0x0010)
	{
		if(btn_Center_TAG == 0) btn_Center_TAG = 1;
		else btn_Center_TAG = 0;
	}
	else if((btn&0x0001) == 0x0001) threshold_MAX_UP_TAG = 1;
	else if((btn&0x0004) == 0x0004) threshold_MAX_DOWN_TAG = 1;
	else if((btn&0x0008) == 0x0008) threshold_MIN_UP_TAG = 1;
	else if((btn&0x0002) == 0x0002) threshold_MIN_DOWN_TAG = 1;
	Xil_Out16(XPAR_AXI_GPIO_2_BASEADDR+XGPIO_ISR_OFFSET,0x01); //清除通道中断状态标志
}

⑥开关控制数码管显示值固定:
  打开相应开关后,数码管段选待显示数组将不会发生变化,始终沿用打开开关前的数值,而不发生变化。

⑦16位led跟随测量距离依次点亮:
  将上下阈值间距分为16等分,根据当前距离所处位置确立该点亮LED的数目。

if ((sw&0x0004) == 0x0004)
{
	//led灯控制
	unsigned int threshold_interval = threshold_MAX - threshold_MIN;
	u16 LED_OUT = 0x0000;
	if (distance < (threshold_MIN + threshold_interval/16)) LED_OUT = 0x8000;
	else if (distance < (threshold_MIN + threshold_interval*2/16)) LED_OUT = 0xc000;
	else if (distance < (threshold_MIN + threshold_interval*3/16)) LED_OUT = 0xe000;
	else if (distance < (threshold_MIN + threshold_interval*4/16)) LED_OUT = 0xf000;
	else if (distance < (threshold_MIN + threshold_interval*5/16)) LED_OUT = 0xf800;
	else if (distance < (threshold_MIN + threshold_interval*6/16)) LED_OUT = 0xfc00;
	else if (distance < (threshold_MIN + threshold_interval*7/16)) LED_OUT = 0xfe00;
	else if (distance < (threshold_MIN + threshold_interval*8/16)) LED_OUT = 0xff00;
	else if (distance < (threshold_MIN + threshold_interval*9/16)) LED_OUT = 0xff80;
	else if (distance < (threshold_MIN + threshold_interval*10/16)) LED_OUT = 0xffc0;
	else if (distance < (threshold_MIN + threshold_interval*11/16)) LED_OUT = 0xffe0;
	else if (distance < (threshold_MIN + threshold_interval*12/16)) LED_OUT = 0xfff0;
	else if (distance < (threshold_MIN + threshold_interval*13/16)) LED_OUT = 0xfff8;
	else if (distance < (threshold_MIN + threshold_interval*14/16)) LED_OUT = 0xfffc;
	else if (distance < (threshold_MIN + threshold_interval*15/16)) LED_OUT = 0xfffe;
	else LED_OUT = 0xffff;
	Xil_Out32(XPAR_AXI_GPIO_0_BASEADDR+XGPIO_DATA2_OFFSET,LED_OUT);
}

⑧蜂鸣器工作:
  蜂鸣器输入信号采用AXI TIMER的PWM脉冲调制功能输出的方波信号,根据距离控制信号的周期,距离越远蜂鸣器发出频率越高,当距离超过上下阈值时,蜂鸣器停止工作。蜂鸣器输出频率范围为200HZ~4000HZ(经测验,这个波段的方波信号音调比较好听),通过判断当前距离在上下阈值间的位置,等比计算出蜂鸣器输出频率在200HZ~4000HZ中的值,计算出计数器装载值,控制正方波周期来控制输出方波频率。
  为避免主函数循环中运算过多导致数码管显示黯淡,故设计当计算出的频率与上一频率差值大于100HZ时才进行频率更新,减少运算量。

//蜂鸣器最高频率为4000hz(25000),最低频率为200hz(500000)
if((sw&0x0008) == 0x0008)
{
	recent_HZ = (distance - threshold_MIN)*(4000-200)/(threshold_MAX - threshold_MIN)+200;
	if (((recent_HZ > temp_HZ)&&(recent_HZ - temp_HZ < 100))||((recent_HZ < temp_HZ)&&(temp_HZ - recent_HZ < 100))) {}
	else
	{
		temp_HZ = recent_HZ;
		BUZZER_VALUE_TIMER = 100000000/recent_HZ - 2;
		buzzer_VALUE_TIMER = BUZZER_VALUE_TIMER/2 - 1;
		//计数器装载值重新配置
		Xil_Out32(XPAR_TMRCTR_3_BASEADDR+XTC_TLR_OFFSET,BUZZER_VALUE_TIMER);
		Xil_Out32(XPAR_TMRCTR_3_BASEADDR+XTC_TCSR_OFFSET,XTC_CSR_AUTO_RELOAD_MASK|XTC_CSR_DOWN_COUNT_MASK|XTC_CSR_LOAD_MASK|XTC_CSR_EXT_GENERATE_MASK|XTC_CSR_ENABLE_PWM_MASK);
		int status=Xil_In32(XPAR_TMRCTR_3_BASEADDR+XTC_TCSR_OFFSET);
		status=(status&(~XTC_CSR_LOAD_MASK))|XTC_CSR_ENABLE_TMR_MASK;
		Xil_Out32(XPAR_TMRCTR_3_BASEADDR+XTC_TCSR_OFFSET,status);
		Xil_Out32(XPAR_TMRCTR_3_BASEADDR+XTC_TIMER_COUNTER_OFFSET+XTC_TLR_OFFSET,buzzer_VALUE_TIMER);
		Xil_Out32(XPAR_TMRCTR_3_BASEADDR+XTC_TIMER_COUNTER_OFFSET+XTC_TCSR_OFFSET,XTC_CSR_AUTO_RELOAD_MASK|XTC_CSR_DOWN_COUNT_MASK|XTC_CSR_EXT_GENERATE_MASK|XTC_CSR_LOAD_MASK|XTC_CSR_ENABLE_PWM_MASK);
		status=Xil_In32(XPAR_TMRCTR_3_BASEADDR+XTC_TIMER_COUNTER_OFFSET+XTC_TCSR_OFFSET);
		status=(status&(~XTC_CSR_LOAD_MASK))|XTC_CSR_ENABLE_TMR_MASK;
		Xil_Out32(XPAR_TMRCTR_3_BASEADDR+XTC_TIMER_COUNTER_OFFSET+XTC_TCSR_OFFSET,status);
	}
}

所遇问题及解决方案

  ①超声波模块触发信号周期为10ms时,数码管显示不稳定,轻微扰动,特别是障碍物的轻微倾斜即可能导致数码管显示数据发生变化,故将触发信号周期改为100ms,减少对距离的采样次数,此时数码管显示数值变化较为平稳。

  ②为避免中断服务函数中程序占用时间过长导致下一中断到来时无法执行,故将几乎所有中断服务程序移至主函数中,在中断服务函数中仅激活中断标志与清除中断状态位。

  ③蜂鸣器工作时数码管显示出现问题,具体表现为蜂鸣器与数码管无法同时工作,估测原因为蜂鸣器工作占用时间较长,数码管显示消隐后下一段选数据无法及时得到显示。故当输出频率改变量大于100hz时才进行蜂鸣器频率更新,减少其运行次数,此外在蜂鸣器频率更新代码段后添加数码管显示循环代码,提高数码管显示频率,使其小于人眼识别最大频率。

  PS:有的版本vivado SDK在将程序烧入FPGA时不仅需点击Program FPGA,还需要点击相应工程文件的run as -> launch on hardware

这部分该叫个什么?尾声吗?!

  本项目前前后后忙活了3天,一所普通大学的普通实验课的综合项目作业,代码也不含什么复杂的算法,几乎全是一言不合就拿if往上怼,收获最大的感觉还是找bug,软件的功能实现不代表在硬件上也能够实现,需要修改好多东西…啊,不写了,碎觉去了…

  • 34
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值