开箱贴中已经说明本次活动的主要开发器件,包括FRDM-MCXA153,OLED屏幕,HC-SR04超声波测距模块。
FRDM-MCXA153的外设资源极其丰富,对部分外设还做了Low Power处理。
1.模块详情分析
通过阅读FRDM-MCXA153的datasheet,发现并没有注明开发板的IO口有5V耐压,所以我们在电路接线时,务必注意这个问题。
市面上有很多款HC-SR04超声波模块,精度略有微小差别,也有带晶振的不带晶振的,还有宽电压3V3--5V输入的。因为之前买过超声波模块,这边我就照旧使用了5V额定电压,带R200SKS512晶振的超声波模块。echo引脚5V输出难道不会烧坏开发板的IO口吗?是的,所以我的需要加上电阻进行分压,我这边使用一个1k和2k的电阻分压,以防烧坏引脚。那为什么不直接使用3V3电压进行供电呢,这样echo就是3V3输出了,因为3V3供电时,模块功率不足,测量效果不够好。那为什么需要晶振?目前市场上有部分模块,是不带晶振的,可靠性一致性较差,如果不带晶振,那么模块只能使用内部RC振荡器,性能相比较差,受温度导致的漂移影响较大,所以我们选择带晶振的超声波模块。如下图(图源来自优信电子)
OLED直接配合官方给的I2C例程进行开发,我们选择了开发板上的P3-28和P3-27引脚,使用硬件IIC,不再使用CPU去翻转电平,降低开发难度。
2.开发环境的选择
NXP官网为我们提供了多种开发平台,包括但不限于MCUXpresso for Visual Studio Code(vscode),MCUXpresso IDE,Arm Keil MDK。
MCUXpresso for Visual Studio Code(vscode)当属现今主流开发环境之一,在vscode上开发,直接按照恩智浦提供的扩展,实现快速灵活的开发,同时vscode集成了大量扩展,配合AI工具,开发效率大步提升。但是环境配置流程较为繁琐。
MCUXpresso IDE作为基于Eclipse的定制型IDE,适用于恩智浦MCU,上手简单,简便易用。
Arm Keil MDK具有广泛中间件的uVision IDE和Arm编译器。作为经典开发平台之一,烧录调试方便快捷。
3.基础功能实现方案
本项目使用MCUXpresso for Visual Studio Code开发,结合MCUXpresso Config Tool图形化配置降低开发难度,采用模块化写法,可读性更强。引脚配置如下图

初始化HC-SR04初始化需要用到的引脚
void HCSR04_Init(void)
{
gpio_pin_config_t trigConfig = {
.pinDirection = kGPIO_DigitalOutput,
.outputLogic = 0U,
};
gpio_pin_config_t echoConfig = {
.pinDirection = kGPIO_DigitalInput,
.outputLogic = 0U,
};
/* trig = output, echo = input */
GPIO_PinInit(HCSR04_TRIG_GPIO, HCSR04_TRIG_PIN, &trigConfig);
GPIO_PinInit(HCSR04_ECHO_GPIO, HCSR04_ECHO_PIN, &echoConfig);
/* 把一个 CTIMER 配成 1 MHz、自由运行的计数器,于是"1 个计数 = 1 微秒",
* 后面就不必再做任何单位换算。
*
* CTIMER0 的输入时钟是 FRO12M(12 MHz,在 hardware_init.c 里接的),
* prescale 寄存器写 11 表示分频系数为 11+1=12,所以 12 MHz / 12 = 1 MHz。
*
* 自由运行(而不是每次测量清零)是为了不改动计数器状态:只要前后各取一次
* 计数值相减即可,配合下面的回绕处理,32 位计数器转一圈约 71 分钟,正常
* 测量间隔下不会产生歧义。 */
ctimer_config_t ctimerConfig;
CTIMER_GetDefaultConfig(&ctimerConfig);
ctimerConfig.prescale = 11U;
CTIMER_Init(HCSR04_TIMER, &ctimerConfig);
CTIMER_StartTimer(HCSR04_TIMER);
}trig先发送一个大于10us的高电平信号,模块内部就会发射出8个40khz的脉冲,echo从低电平变为高电平,直到收到返回的超声波,echo就被拉低为低电平,一轮传输结束。
那么距离该如何计算呢?常温常压下声速是340m/s,大致计算每cm往返58us,所以就有了以下程序。
uint32_t HCSR04_MeasureEchoUs(void)
{
uint32_t tStart;
uint32_t tEnd;
uint32_t timeout;
/* 触发脉冲。手册要求 >= 10us,这里给 20us 留余量。 */
GPIO_PinWrite(HCSR04_TRIG_GPIO, HCSR04_TRIG_PIN, 1U);
SDK_DelayAtLeastUs(20U, SDK_DEVICE_MAXIMUM_CPU_CLOCK_FREQUENCY);
GPIO_PinWrite(HCSR04_TRIG_GPIO, HCSR04_TRIG_PIN, 0U);
/* 等 ECHO 变高,即回波的上升沿——计时从这里开始。
* 一直等不到说明模块没被触发、或者 ECHO 被卡在低电平。 */
timeout = HCSR04_POLL_TIMEOUT;
while (0U == GPIO_PinRead(HCSR04_ECHO_GPIO, HCSR04_ECHO_PIN))
{
if (0U == --timeout)
{
return 0U;
}
}
tStart = CTIMER_GetTimerCountValue(HCSR04_TIMER);
/* 等 ECHO 变低,即下降沿——计时在这里结束。
* 只取了 tStart 却等不到下降沿,说明 ECHO 被卡在高电平。 */
timeout = HCSR04_POLL_TIMEOUT;
while (1U == GPIO_PinRead(HCSR04_ECHO_GPIO, HCSR04_ECHO_PIN))
{
if (0U == --timeout)
{
return 0xFFFFFFFFU; /* Echo went high but never fell back low. */
}
}
tEnd = CTIMER_GetTimerCountValue(HCSR04_TIMER);
/* 计数器是 32 位且自由运行,测量跨度内可能发生回绕(从 0xFFFFFFFF 翻到 0),
* 直接相减会得到一个巨大的错误值。这里判断一下:一旦 tEnd 比 tStart 小,
* 就说明转过一圈了,补上一个完整周期再算。 */
return (tEnd >= tStart) ? (tEnd - tStart) : (0xFFFFFFFFU - tStart + tEnd + 1U);
}本人因OLED屏幕购置时间过久,导致丝印不清晰,不知道OLED芯片是什么型号,最后通过种种尝试才得出了型号,所以大家在购买器件时,务必找商家获取datasheet。而且在代码编写时还遇到了i2c7位寻址错误的问题,导致i2c无法初始化,消耗了大量的时间。最后通过串口在上位机就打印输出解决了bug。还有一定要注意接线的问题,还有IO引脚复用的问题,如果默认是普通IO口,不要忘记复用引脚。
OLED代码,主要分为clear清屏,防止上次断电时的文字缓存,影响显示效果,update不断更新数据,ssd1306_draw_char显示字符,ssd1306_DrawString显示字符串以实现基本功能。
static void ssd1306_draw_char(uint8_t x, uint8_t y, char c, uint8_t scale)
{
const uint8_t *glyph;
uint8_t col;
uint8_t row;
if (scale == 0U)
{
scale = 1U;
}
/* Clamp the character into the font range, fall back to space. */
if ((c < 0x20) || (c > 0x5A))
{
c = ' ';
}
glyph = s_font5x7[(uint8_t)c - 0x20U];
/* 逐行取字形数据:row 是行号,bits 是该行的 5 个像素(低 5 位)。 */
for (row = 0U; row < 7U; row++)
{
uint8_t bits = glyph[row];
for (col = 0U; col < 5U; col++)
{
/* bit4 是最左边的像素,所以每往右一列就右移一位。 */
if (bits & (0x10U >> col))
{
uint8_t sx;
uint8_t sy;
/* 放大:这一个点要占 scale x scale 个屏幕像素。 */
for (sy = 0U; sy < scale; sy++)
{
for (sx = 0U; sx < scale; sx++)
{
uint8_t px = x + (uint8_t)(col * scale) + sx;
uint8_t py = y + (uint8_t)(row * scale) + sy;
if ((px < SSD1306_WIDTH) && (py < SSD1306_HEIGHT))
{
s_framebuffer[py >> 3U][px] |= (uint8_t)(1U << (py & 0x07U));
}
}
}
}
}
}
}
void SSD1306_DrawString(uint8_t x, uint8_t y, const char *str, uint8_t scale)
{
if (scale == 0U)
{
scale = 1U;
}
while (*str != '�')
{
ssd1306_draw_char(x, y, *str, scale);
str++;
x += (uint8_t)(6U * scale); /* 5 px glyph + 1 px spacing, scaled */
}
}到此OLED和HC-SR04模块化代码的讲解基本结束,成果贴将为大家带来主函数介绍和功能展示。
我要赚赏金
