目前已完成超声波测距和LCD显示功能,接下来将进行代码整合,并新增按键与LED控制逻辑。其中,SW2按键用于循环切换显示的距离量程(mm → cm → m),当测量距离在5cm-200cm之间绿灯常亮,低于5cm之间红灯闪烁,测量失败蓝灯亮。
frdmmcxa153ultrasonic_ranginghardware_init.c,注意:要开启相应的时钟,不然运行不起来
void BOARD_InitHardware(void)
{
// 定时器1时钟
CLOCK_SetClockDiv(kCLOCK_DivCTIMER1, 1u);
CLOCK_AttachClk(kFRO_HF_to_CTIMER1);
// I2C时钟
CLOCK_SetClockDiv(kCLOCK_DivLPI2C0, 1u);
CLOCK_AttachClk(kFRO12M_to_LPI2C0);
// 定时器2时钟
CLOCK_SetClockDiv(kCLOCK_DivCTIMER2, 1u);
CLOCK_AttachClk(kFRO_HF_to_CTIMER2);
BOARD_InitPins();
BOARD_InitBootClocks();
BOARD_InitDebugConsole();
}ultrasonic_ranging.c
/*******************************************************************************
* Include
******************************************************************************/
#include "fsl_gpio.h"
#include "fsl_device_registers.h"
#include "fsl_debug_console.h"
#include "board.h"
#include "app.h"
#include "fsl_ctimer.h"
#include "pin_mux.h"
#include "fsl_lpi2c.h"
#include <stdio.h>
#include <string.h>
/*******************************************************************************
* Definitions
******************************************************************************/
// 距离阈值定义(单位:微米 um)
#define CM_TO_UM(cm) ((cm) * 10000)
#define DISTANCE_WARNING_CM 5 // 5cm 警戒距离
// 滤波器参数
// 限幅阈值:真实移动时允许的变化量
#define MAX_REAL_CHANGE 200000 // 20cm(手移动的速度不可能超过这个)
#define MIN_JITTER_THRESHOLD 5000 // 5mm 以下的波动认为是抖动
/*******************************************************************************
* Prototypes
******************************************************************************/
// 定时器相关
static void Timer_Init(void);
static void Timer_Start_Timing(void);
static uint32_t Timer_GetTimestampUs(void);
static void Timer_DelayUs(uint32_t us);
static void Timer_DelayMs(uint32_t ms);
// 超声波相关
static void Echo_Init(void);
static uint32_t Ultrasonic_Ranging_GetUm(void);
static uint32_t ultrasonic_filter(uint32_t new_value);
static void reset_filter(void);
// LCD相关
static void LCD_I2C_init(void);
static void LCD_Device_Init(void);
static void LCD_PrintString(char *str);
static void Get_Show_Distance(char *show_data, uint32_t length);
// 其他
static void Key_SW2_init(void);
static void LED_Ctimer_Init(void);
static void format_distance(int32_t value, char unit, char *buffer);
static int32_t convert_um_to_unit(uint32_t um, char unit);
/*******************************************************************************
* Variables
******************************************************************************/
// 超声波测量变量
volatile uint32_t echo_start_time = 0;
volatile uint32_t echo_pulse_width = 0;
volatile uint8_t echo_ready = 0;
volatile uint8_t sw2_status = 0;
// 定时器回调
static ctimer_callback_t timer_callback_array[1] = {NULL};
// 距离数据结构
typedef struct {
uint32_t um; // 微米原始值
int32_t value; // 转换后的值(放大100倍,保留两位小数)
char unit; // 单位:'m'、'c'、'l'(mm)
char str[20]; // 格式化字符串
} DistanceData;
// LED状态枚举
typedef enum {
LED_GREEN, // 正常(距离安全)
LED_RED_BLINK, // 警告(距离过近,红灯闪烁)
LED_BLUE // 超时/错误
} LedState_t;
static uint32_t last_valid_value = 0;
static uint8_t first_sample = 1;
// 连续稳定计数器:用于判断是否真的稳定下来
static uint8_t stable_count = 0;
static uint32_t candidate_value = 0; // 正在验证的新值
// 系统状态
LedState_t system_state = LED_GREEN;
/*******************************************************************************
* Code
******************************************************************************/
/*!
* @brief Main function
*/
int main(void)
{
/* Init hardware*/
BOARD_InitHardware();
PRINTF("MCUX SDK version: %srn", MCUXSDK_VERSION_FULL_STR);
// 初始化各模块
Echo_Init();
LED_Ctimer_Init();
Timer_Init();
Key_SW2_init();
LCD_I2C_init();
LCD_Device_Init();
// 初始化滤波器
reset_filter();
// 初始显示
LCD_PrintString("Ultrasonic Rng");
Timer_DelayMs(1000);
char lcd_show_buf[20] = {0};
uint32_t raw_dist = 0;
uint32_t filtered_dist = 0;
while (1)
{
// 获取原始测量值
raw_dist = Ultrasonic_Ranging_GetUm();
PRINTF("raw_dist = %urn", raw_dist);
if (raw_dist > 0)
{
// 应用滤波
filtered_dist = ultrasonic_filter(raw_dist);
PRINTF("filtered_dist = %urn", filtered_dist);
// 显示滤波后的数据
memset(lcd_show_buf, 0, sizeof(lcd_show_buf));
Get_Show_Distance(lcd_show_buf, filtered_dist);
LCD_PrintString(lcd_show_buf);
// 状态判断:使用滤波后的数据
if (filtered_dist < CM_TO_UM(DISTANCE_WARNING_CM))
{
system_state = LED_RED_BLINK; // 距离过近,红灯闪烁
}
else
{
system_state = LED_GREEN; // 距离安全,绿灯常亮
}
// 调试打印
// PRINTF("Raw: %d um, Filtered: %d umrn", raw_dist, filtered_dist);
}
else
{
LCD_PrintString("Timeout!!!");
PRINTF("Measurement timeout!rn");
system_state = LED_BLUE; // 超时,蓝灯常亮
}
Timer_DelayMs(500);
}
}
/*******************************************************************************
* 滤波函数
******************************************************************************/
static uint32_t ultrasonic_filter(uint32_t new_value)
{
// 1. 第一次测量
if (first_sample)
{
first_sample = 0;
last_valid_value = new_value;
candidate_value = new_value;
stable_count = 0;
return new_value;
}
// 2. 计算变化量
int32_t diff = (int32_t)new_value - (int32_t)last_valid_value;
uint32_t abs_diff = (diff > 0) ? diff : -diff;
// 3. 小抖动:直接忽略
if (abs_diff < MIN_JITTER_THRESHOLD)
{
// 抖动范围内,保持上次值不变
stable_count = 0; // 重置稳定计数(因为还在抖动)
return last_valid_value;
}
// 4. 大变化:判断是真实移动还是飞点
if (abs_diff > MAX_REAL_CHANGE)
{
// 变化超过20cm,可能是飞点
// 策略:如果连续两次测量都指向同一个方向,才认为是真实移动
// 检查候选值
if (candidate_value == last_valid_value)
{
// 第一次出现大幅变化
candidate_value = new_value;
stable_count = 1;
return last_valid_value; // 先返回旧值
}
else
{
// 已经有候选值了,比较方向是否一致
int32_t diff_candidate = (int32_t)candidate_value - (int32_t)last_valid_value;
int32_t diff_new = (int32_t)new_value - (int32_t)last_valid_value;
// 方向一致(同正或同负)
if ((diff_candidate > 0 && diff_new > 0) || (diff_candidate < 0 && diff_new < 0))
{
stable_count++;
if (stable_count >= 2)
{
// 连续2次向同一方向大幅变化,接受
last_valid_value = new_value;
candidate_value = new_value;
stable_count = 0;
return new_value;
}
// 更新候选值
candidate_value = new_value;
return last_valid_value;
}
else
{
// 方向不一致,可能是飞点,重置
candidate_value = new_value;
stable_count = 1;
return last_valid_value;
}
}
}
// 5. 正常范围内的变化(5mm ~ 20cm):直接接受,并做轻微平滑
stable_count = 0; // 重置候选状态
// 如果变化小于3cm,做一点平滑减少视觉抖动
if (abs_diff < 30000)
{
// 60% 旧值 + 40% 新值
uint32_t smoothed = (uint32_t)(last_valid_value * 0.6f + new_value * 0.4f);
last_valid_value = smoothed;
return smoothed;
}
else
{
// 变化较大,快速响应
last_valid_value = new_value;
return new_value;
}
}
// 重置滤波器
static void reset_filter(void)
{
first_sample = 1;
last_valid_value = 0;
candidate_value = 0;
stable_count = 0;
}
/*******************************************************************************
* 定时器相关 (CTIMER1 - 用于超声波测距)
******************************************************************************/
// 定时器初始化
static void Timer_Init(void)
{
ctimer_config_t config;
CTIMER_GetDefaultConfig(&config);
uint32_t timer_clk = CLOCK_GetCTimerClkFreq(1U);
PRINTF("CTIMER1 input frequency: %u Hzrn", timer_clk);
// 预分频:96MHz -> 1MHz (1 tick = 1us)
config.prescale = (timer_clk / 1000000UL) - 1;
PRINTF("Prescaler value: %urn", config.prescale);
PRINTF("Counting frequency: %u Hz (1 tick = 1 us)rn", timer_clk / (config.prescale + 1));
// 初始化定时器
CTIMER_Init(TIMER, &config);
Timer_Start_Timing();
}
// 开始计时
static void Timer_Start_Timing(void)
{
TIMER->TC = 0;
CTIMER_StartTimer(TIMER);
}
// 获取微秒时间戳
static uint32_t Timer_GetTimestampUs(void)
{
return CTIMER_GetTimerCountValue(TIMER);
}
// 微秒延时
static void Timer_DelayUs(uint32_t us)
{
uint32_t start = CTIMER_GetTimerCountValue(TIMER);
while ((CTIMER_GetTimerCountValue(TIMER) - start) < us);
}
// 毫秒延时
static void Timer_DelayMs(uint32_t ms)
{
Timer_DelayUs(ms * 1000);
}
/*******************************************************************************
* 超声波测距
******************************************************************************/
static void Echo_Init(void)
{
// 配置Echo中断
GPIO_GpioClearInterruptFlags(BOARD_INITPINS_ECHO_GPIO, 1U << BOARD_INITPINS_ECHO_GPIO_PIN);
GPIO_SetPinInterruptConfig(BOARD_INITPINS_ECHO_GPIO, BOARD_INITPINS_ECHO_GPIO_PIN, kGPIO_InterruptEitherEdge);// 设置双边沿触发
EnableIRQ(BOARD_ECHO_IRQ);
}
// 获取超声测距数据(单位:微米)
static uint32_t Ultrasonic_Ranging_GetUm(void)
{
// 清空上次测量数据
echo_ready = 0;
echo_pulse_width = 0;
// 触发超声波模块
GPIO_PortSet(BOARD_INITPINS_TRIG_GPIO, BOARD_INITPINS_TRIG_GPIO_PIN_MASK);
Timer_DelayUs(15);
GPIO_PortClear(BOARD_INITPINS_TRIG_GPIO, BOARD_INITPINS_TRIG_GPIO_PIN_MASK);
// 等待测量完成,超时时间 50ms
uint32_t start_wait = Timer_GetTimestampUs();
while (!echo_ready)
{
if ((Timer_GetTimestampUs() - start_wait) > 50000)
{
return 0; // 超时
}
}
// 计算距离:脉冲宽度(us) * 声速(um/us) / 2
// 声速 343m/s = 34300cm/s = 343000um/ms = 343um/us, 基于20℃
// 距离 = 脉冲宽度 * 343 / 2 = 脉冲宽度 * 171.5 (um)
// 使用整数运算:距离(um) = echo_pulse_width * 1715 / 10
uint32_t distance_um = (uint32_t)(echo_pulse_width * 1715 / 10);
return distance_um;
}
// Echo引脚中断处理
void BOARD_ECHO_IRQ_HANDLER(void)
{
GPIO_GpioClearInterruptFlags(BOARD_INITPINS_ECHO_GPIO, 1U << BOARD_INITPINS_ECHO_GPIO_PIN);
if (GPIO_PinRead(BOARD_INITPINS_ECHO_GPIO, BOARD_INITPINS_ECHO_GPIO_PIN) == 1U)
{
// 上升沿:开始计时
echo_start_time = Timer_GetTimestampUs();
}
else
{
// 下降沿:计算脉冲宽度
uint32_t end_time = Timer_GetTimestampUs();
if (end_time > echo_start_time)
{
echo_pulse_width = end_time - echo_start_time;
echo_ready = 1;
}
}
SDK_ISR_EXIT_BARRIER;
}
/*******************************************************************************
* 距离格式化
******************************************************************************/
// 单位转换:微米转指定单位(放大100倍)
static int32_t convert_um_to_unit(uint32_t um, char unit)
{
switch(unit)
{
case 'm': // 转米:um / 1000000 * 100 = um / 10000
return um / 10000;
case 'c': // 转厘米:um / 10000 * 100 = um / 100
return um / 100;
case 'l': // 转毫米:um / 1000 * 100 = um / 10
return um / 10;
default:
return 0;
}
}
// 格式化输出字符串
static void format_distance(int32_t value, char unit, char *buffer)
{
int32_t integer_part = value / 100;
int32_t decimal_part = value % 100;
switch(unit)
{
case 'm':
sprintf(buffer, "DIST: %ld.%02ld m", integer_part, decimal_part);
break;
case 'c':
sprintf(buffer, "DIST: %ld.%02ld cm", integer_part, decimal_part);
break;
case 'l':
sprintf(buffer, "DIST: %ld.%02ld mm", integer_part, decimal_part);
break;
default:
sprintf(buffer, "Unknown unit");
}
}
// 获取并格式化距离显示字符串
static void Get_Show_Distance(char *show_data, uint32_t length)
{
DistanceData data;
data.um = length;
// 根据按键切换单位
if(sw2_status == 0)
data.unit = 'l';// 毫米
else if(sw2_status == 1)
data.unit = 'c';// 厘米
else if(sw2_status == 2)
data.unit = 'm';// 米
data.value = convert_um_to_unit(data.um, data.unit);
format_distance(data.value, data.unit, data.str);
memcpy(show_data, data.str, strnlen(data.str, 20));
}
/*******************************************************************************
* LCD部分 (I2C + PCF8574T)
******************************************************************************/
// I2C写数据
static status_t PCF8574_WriteByte(uint8_t data)
{
status_t status;
status = LPI2C_MasterStart(EXAMPLE_I2C_MASTER, PCF8574T_I2C_ADDRESS, kLPI2C_Write);
if (status != kStatus_Success)
{
LPI2C_MasterStop(EXAMPLE_I2C_MASTER);
return status;
}
status = LPI2C_MasterSend(EXAMPLE_I2C_MASTER, &data, 1);
if (status != kStatus_Success)
{
LPI2C_MasterStop(EXAMPLE_I2C_MASTER);
return status;
}
status = LPI2C_MasterStop(EXAMPLE_I2C_MASTER);
return status;
}
// 发送4位半字节
static void LCD_SendNibble(uint8_t nibble, uint8_t is_command)
{
uint8_t data = 0x00;
if (nibble & 0x01) data |= LCD_PIN_D4;
if (nibble & 0x02) data |= LCD_PIN_D5;
if (nibble & 0x04) data |= LCD_PIN_D6;
if (nibble & 0x08) data |= LCD_PIN_D7;
if (!is_command)
{
data |= LCD_PIN_RS;
}
data &= ~LCD_PIN_RW;
data |= LCD_PIN_BL;
// 产生使能脉冲
PCF8574_WriteByte(data | LCD_PIN_EN);
Timer_DelayUs(5);
PCF8574_WriteByte(data & ~LCD_PIN_EN);
Timer_DelayUs(2);
}
// 向LCD发送完整字节
static void LCD_WriteByte(uint8_t byte, uint8_t is_command)
{
LCD_SendNibble((byte >> 4) & 0x0F, is_command);
LCD_SendNibble(byte & 0x0F, is_command);
if (is_command)
{
if (byte == 0x01 || byte == 0x02)
{
Timer_DelayMs(2);
}
else
{
Timer_DelayUs(100);
}
}
else
{
Timer_DelayUs(50);
}
}
// I2C初始化
static void LCD_I2C_init(void)
{
lpi2c_master_config_t masterConfig;
LPI2C_MasterGetDefaultConfig(&masterConfig);
masterConfig.baudRate_Hz = LPI2C_BAUDRATE;
LPI2C_MasterInit(EXAMPLE_I2C_MASTER, &masterConfig, LPI2C_MASTER_CLOCK_FREQUENCY);
PRINTF("I2C initialized at %d Hzrn", LPI2C_BAUDRATE);
}
// LCD初始化
static void LCD_Device_Init(void)
{
PRINTF("LCD Init Start...rn");
Timer_DelayMs(50);
// 发送3次0x30唤醒
for (int i = 0; i < 3; i++)
{
LCD_SendNibble(0x03, 1);
Timer_DelayMs(5);
}
// 设置为4-bit模式
LCD_SendNibble(0x02, 1);
Timer_DelayUs(100);
// 配置LCD
LCD_WriteByte(0x28, 1); // 4-bit, 2行, 5x8
LCD_WriteByte(0x08, 1); // 显示关闭
LCD_WriteByte(0x01, 1); // 清屏
Timer_DelayMs(2);
LCD_WriteByte(0x06, 1); // 地址递增
LCD_WriteByte(0x0C, 1); // 显示开,光标关
PRINTF("LCD Init Complete!rn");
}
// 设置光标位置
static void LCD_SetCursor(uint8_t row, uint8_t col)
{
uint8_t address;
if (row == 0)
{
address = 0x00 + col;
}
else
{
address = 0x40 + col;
}
LCD_WriteByte(0x80 | address, 1);
}
// 打印字符串(自动换行)
static void LCD_PrintString(char *str)
{
uint8_t len = 0;
uint8_t col = 0;
LCD_WriteByte(0x01, 1); // 清屏
while (str[len] != '\0')
{
len++;
}
if (len > LCD_COLS)
{
len = LCD_COLS;
}
if (len > 8)
{
LCD_SetCursor(0, 0);
for (col = 0; col < 8; col++)
{
LCD_WriteByte(str[col], 0);
}
LCD_SetCursor(1, 0);
for (col = 8; col < len; col++)
{
LCD_WriteByte(str[col], 0);
}
}
else
{
LCD_SetCursor(0, 0);
for (col = 0; col < len; col++)
{
LCD_WriteByte(str[col], 0);
}
}
}
/*******************************************************************************
* 按键SW2 (单位切换)
******************************************************************************/
static void Key_SW2_init(void)
{
GPIO_GpioClearInterruptFlags(BOARD_SW2_GPIO, 1U << BOARD_SW2_GPIO_PIN);
GPIO_SetPinInterruptConfig(BOARD_SW2_GPIO, BOARD_SW2_GPIO_PIN,
kGPIO_InterruptFallingEdge);
EnableIRQ(BOARD_SW2_IRQ);
}
// 按键中断处理
void BOARD_SW2_IRQ_HANDLER(void)
{
GPIO_GpioClearInterruptFlags(BOARD_SW2_GPIO, 1U << BOARD_SW2_GPIO_PIN);
sw2_status++;
sw2_status = sw2_status % 3;
SDK_ISR_EXIT_BARRIER;
}
/*******************************************************************************
* LED 定时器中断 (100ms)
******************************************************************************/
// 定时器中断回调
void timer_callback(uint32_t flags)
{
if (flags & (1 << TIMER_INTERRUPT_CHANNEL))
{
switch(system_state)
{
case LED_GREEN:
LED_RED_OFF();
LED_BLUE_OFF();
LED_GREEN_ON();
break;
case LED_RED_BLINK:
LED_GREEN_OFF();
LED_BLUE_OFF();
GPIO_PortToggle(BOARD_LED_RED_GPIO, 1u << BOARD_LED_RED_GPIO_PIN);
break;
case LED_BLUE:
LED_RED_OFF();
LED_GREEN_OFF();
LED_BLUE_ON();
break;
default:
break;
}
}
}
// LED 定时器初始化 (CTIMER0 - 100ms中断)
static void LED_Ctimer_Init(void)
{
ctimer_config_t timerConfig;
ctimer_match_config_t matchConfig;
uint32_t srcClock_Hz;
uint32_t timerClock;
srcClock_Hz = CTIMER_CLK_FREQ;
PRINTF("CTIMER source clock frequency: %d Hzrn", srcClock_Hz);
CTIMER_GetDefaultConfig(&timerConfig);
timerConfig.prescale = srcClock_Hz / 1000 - 1; // 96MHz -> 1kHz
timerClock = srcClock_Hz / (timerConfig.prescale + 1);
PRINTF("Timer counting frequency: %d Hz (period: %d ms)rn", timerClock, 1000 / timerClock);
CTIMER_Init(CTIMER, &timerConfig);
// 注册回调
timer_callback_array[0] = timer_callback;
CTIMER_RegisterCallBack(CTIMER, timer_callback_array, kCTIMER_SingleCallback);
// 配置匹配通道
matchConfig.matchValue = TIMER_MS_COUNT; // 100
matchConfig.enableCounterReset = true;
matchConfig.enableCounterStop = false;
matchConfig.outControl = kCTIMER_Output_NoAction;
matchConfig.outPinInitState = false;
matchConfig.enableInterrupt = true;
PRINTF("Match Value: %d (corresponding to %d ms)rn", matchConfig.matchValue, matchConfig.matchValue);
CTIMER_SetupMatch(CTIMER, TIMER_INTERRUPT_CHANNEL, &matchConfig);
CTIMER_StartTimer(CTIMER);
PRINTF("LED timer started! Interrupt every 100 ms.rn");
}
/*******************************************************************************
* End of file
******************************************************************************/ 也尝试使用FreeRTOS,响应更加顺畅,效果更好。
视频演示:
我要赚赏金
