【Digikey得捷好物畅享】ADI MAX78000FTHR 智能抽屉项目设计与步进电机控制实现
摘要:本文介绍我基于MAX78000FTHR开发板的智能抽屉项目硬件设计与软件实现全过程。重点阐述利用ULN2003驱动板控制28BYJ-48步进电机实现抽屉开关的硬件选型、电路连接、GPIO控制程序,并给出完整可编译运行的示例代码。开发过程中遇到了MXC_Delay头文件缺失、串口工具无响应、GPIO配置等多个实际问题,一并记录下来供大家参考。
一、项目背景与设计目标
智能抽屉是一种常见的物联网应用场景——通过传感器检测授权用户,实现抽屉的自动开启和关闭,避免传统机械钥匙的繁琐,同时提升物品管理的安全性与智能化水平。我入手MAX78000FTHR时申请理由就写的是要做智能抽屉,现在开发板到手了,说干就干。
本项目的核心目标是:
- 利用MAX78000FTHR的GPIO输出,通过ULN2003驱动板控制28BYJ-48步进电机,实现抽屉锁舌的自动开闭
- 通过串口指令手动控制,作为功能演示
- 程序架构预留AI扩展接口,后续可接入摄像头人脸识别或语音识别模块实现无接触开锁
二、系统总体设计
2.1 硬件选型
| 组件 | 选型 | 说明 |
|---|---|---|
| 主控 | MAX78000FTHR开发板 | 边缘AI开发平台,本次主角 |
| 驱动板 | ULN2003模块 | 达林顿管阵列,可直接驱动步进电机 |
| 执行器 | 28BYJ-48 步进电机 | 5V四相五线,减速比64:1,扭矩约0.3kg·cm |
| 供电 | USB 5V + 外部5V电源 | 开发板USB供电,电机独立5V供电 |
2.2 系统框图
用户操作(串口指令 'o'/'c')
↓
MAX78000FTHR GPIO输出
↓
ULN2003 驱动板放大电流
↓
28BYJ-48 步进电机转动
↓
锁舌旋转执行开/闭动作
↓
串口返回状态信息
2.3 硬件连接总览
MAX78000FTHR ULN2003驱动板
─────────── ──────────────
P1_1 ──────────────────────→ IN1 → LED1
P1_0 ──────────────────────→ IN2 → LED2
P0_7 ──────────────────────→ IN3 → LED3
P0_5 ──────────────────────→ IN4 → LED4
GND ──────────────────────→ GND
│
外部5V电源正极 ─────────────→ COM
外部5V电源负极 ──→ GND ─────→ GND(共地)
│
28BYJ-48电机 ──────────────→ 蓝色5针排母接口
[!WARNING]
共地是必须的! ULN2003的GND、外部电源负极、开发板GND三者必须用杜邦线连在一起,否则信号无法正确识别。
三、硬件连接详解
3.1 ULN2003驱动板简介
ULN2003是7路达林顿管阵列芯片,每路最大输出500mA,内部自带续流二极管,非常适合驱动步进电机、继电器等感性负载。模块上通常有4个LED对应IN1~IN4,方便调试。
3.2 28BYJ-48步进电机简介
28BYJ-48是5V四相五线步进电机,参数如下:
| 参数 | 数值 |
|---|---|
| 工作电压 | 5V DC |
| 相数 | 4相 |
| 减速比 | 64:1 |
| 步距角 | 5.625°/64(全步) |
| 每圈步数 | 4096步(8拍驱动) |
| 驱动方式 | 1-2相励磁(8拍) |
3.3 详细接线表
| ULN2003侧 | 连接目标 | 说明 |
|---|---|---|
| IN1 | MAX78000FTHR P1_1 | 步进相A控制信号 |
| IN2 | MAX78000FTHR P1_0 | 步进相B控制信号 |
| IN3 | MAX78000FTHR P0_7 | 步进相C控制信号 |
| IN4 | MAX78000FTHR P0_5 | 步进相D控制信号 |
| GND | 开发板GND + 外部电源负极 | 共地,缺一不可 |
| COM | 外部5V电源正极 | 电机工作电源,不要接开发板 |
| 蓝色排母 | 28BYJ-48电机插头 | 直接插入即可 |
3.4 供电方案说明
方案A(推荐,测试用):
电脑USB ──→ MAX78000FTHR USB-C接口(板载LDO自动出3.3V)
│
└──→ ULN2003的COM ──→ 28BYJ-48电机(USB 5V直供)
│
└──→ GND共地
方案B(更稳,正式用):
电脑USB ──→ MAX78000FTHR(独立供电)
│
外部5V/2A电源 ──→ ULN2003的COM + 电机(独立供电)
│
└──→ GND与开发板共地
[!TIP]
单颗28BYJ-48工作电流约100~200mA,电脑USB口(500mA)足够驱动。但如果后续扩展多颗电机或加装其他外设,建议用方案B独立供电。
上图:MAX78000FTHR开发板、ULN2003驱动板、28BYJ-48步进电机完整接线实物。开发板P1_1/P1_0/P0_7/P0_5四根信号线连接ULN2003 IN1~IN4,外部5V电源给ULN2003 COM和电机供电,GND三端共地。
四、软件实现
4.1 步进电机驱动原理
28BYJ-48采用**1-2相励磁(8拍)**驱动方式,8个状态构成一个完整循环:
| 拍数 | IN4(D) | IN3(C) | IN2(B) | IN1(A) | 励磁状态 |
|---|---|---|---|---|---|
| 1 | 0 | 0 | 0 | 1 | A |
| 2 | 0 | 0 | 0 | 1 | AB |
| 3 | 0 | 0 | 1 | 0 | B |
| 4 | 0 | 1 | 1 | 0 | BC |
| 5 | 0 | 1 | 0 | 0 | C |
| 6 | 1 | 1 | 0 | 0 | CD |
| 7 | 1 | 0 | 0 | 0 | D |
| 8 | 1 | 0 | 0 | 1 | DA |
正转:1→2→3→4→5→6→7→8→1…
反转:8→7→6→5→4→3→2→1→8…
4.2 第一个坑:MXC_Delay 头文件找不到
写好初版代码编译时,遇到了这个错误:
main.c:185:5: error: implicit declaration of function 'MXC_Delay'
我第一反应是加 #include "delay.h",结果又报:
fatal error: delay.h: No such file or directory
排查结果:MSDK v2023_10 版本里,延时函数的正确头文件是 mxc_delay.h,不是 delay.h。
// ✅ 正确
#include "mxc_delay.h"
// ❌ 错误(该文件不存在)
#include "delay.h"
另外,MXC_Delay() 的参数需要用 MXC_DELAY_USEC() 或 MXC_DELAY_MSEC() 宏包裹,不能直接传数字。
4.3 第二个坑:GPIO配置写法不兼容
我最初用C99复合字面量写法配置GPIO,结果编译器报错:
// ❌ 这种写法在某些MSDK版本会出问题
MXC_GPIO_Config(&(mxc_gpio_cfg_t){
.port = IN1_PORT, .mask = IN1_PIN,
...
});
解决办法:老老实实先定义 mxc_gpio_cfg_t 变量,再传地址:
// ✅ 正确写法
mxc_gpio_cfg_t cfg;
cfg.port = IN1_PORT;
cfg.mask = IN1_PIN;
cfg.pad = MXC_GPIO_PAD_NONE;
cfg.func = MXC_GPIO_FUNC_OUT;
MXC_GPIO_Config(&cfg);
4.4 第三个坑:串口工具无法发送字符
程序烧录后,串口输出了启动信息,但我发送 o 完全没有反应。排查后发现:
我用的串口调试助手没有正确发送字符到开发板,只是显示在发送区,并没有真正发出去。
解决办法:换用 PuTTY 直接连接串口,键盘输入直接发送,不需要点"发送"按钮:
# MSYS2 里直接执行
putty -serial COM3 -sercfg 115200,8,n,1
或者在代码里用 scanf("%c", &cmd) 替代 getchar(),有些串口工具对 getchar() 支持不好。
4.5 完整代码实现
以下是经过实测可以正常编译和运行的完整 main.c:
/*******************************************************************************
* Smart Drawer - ULN2003 步进电机控制
* 平台:MAX78000FTHR
* 电机:28BYJ-48 + ULN2003 驱动板
* 引脚:IN1=P1_1 IN2=P1_0 IN3=P0_7 IN4=P0_5
* 编译:make clean && make -j && make program
******************************************************************************/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "mxc_device.h"
#include "mxc_pins.h"
#include "gpio.h"
#include "board.h"
#include "uart.h"
#include "mxc_delay.h"
// ===== 引脚定义 =====
#define IN1_PORT MXC_GPIO1
#define IN1_PIN MXC_GPIO_PIN_1
#define IN2_PORT MXC_GPIO1
#define IN2_PIN MXC_GPIO_PIN_0
#define IN3_PORT MXC_GPIO0
#define IN3_PIN MXC_GPIO_PIN_7
#define IN4_PORT MXC_GPIO0
#define IN4_PIN MXC_GPIO_PIN_5
// ===== 步进电机参数 =====
#define STEPS_PER_REV 4096 // 28BYJ-48 全步一圈(8拍模式)
// 8拍步进序列:A-AB-B-BC-C-CD-D-DA
static const uint8_t step_seq[8][4] = {
{1,0,0,0}, // Step 0: A
{1,1,0,0}, // Step 1: AB
{0,1,0,0}, // Step 2: B
{0,1,1,0}, // Step 3: BC
{0,0,1,0}, // Step 4: C
{0,0,1,1}, // Step 5: CD
{0,0,0,1}, // Step 6: D
{1,0,0,1}, // Step 7: DA
};
// ===== 抽屉状态 =====
typedef enum {
DRAWER_CLOSED = 0,
DRAWER_OPEN = 1,
} DrawerState_t;
static DrawerState_t g_state = DRAWER_CLOSED;
static uint8_t g_step_idx = 0;
// ===== GPIO 初始化 =====
static void motor_gpio_init(void)
{
mxc_gpio_cfg_t cfg;
// IN1 - P1_1
cfg.port = IN1_PORT;
cfg.mask = IN1_PIN;
cfg.pad = MXC_GPIO_PAD_NONE;
cfg.func = MXC_GPIO_FUNC_OUT;
MXC_GPIO_Config(&cfg);
// IN2 - P1_0
cfg.port = IN2_PORT;
cfg.mask = IN2_PIN;
MXC_GPIO_Config(&cfg);
// IN3 - P0_7
cfg.port = IN3_PORT;
cfg.mask = IN3_PIN;
MXC_GPIO_Config(&cfg);
// IN4 - P0_5
cfg.port = IN4_PORT;
cfg.mask = IN4_PIN;
MXC_GPIO_Config(&cfg);
// 初始全部拉低(电机失能)
MXC_GPIO_OutClr(IN1_PORT, IN1_PIN);
MXC_GPIO_OutClr(IN2_PORT, IN2_PIN);
MXC_GPIO_OutClr(IN3_PORT, IN3_PIN);
MXC_GPIO_OutClr(IN4_PORT, IN4_PIN);
}
// ===== 输出一步 =====
static void motor_step(uint8_t idx)
{
if (step_seq[idx][0]) MXC_GPIO_OutSet(IN1_PORT, IN1_PIN);
else MXC_GPIO_OutClr(IN1_PORT, IN1_PIN);
if (step_seq[idx][1]) MXC_GPIO_OutSet(IN2_PORT, IN2_PIN);
else MXC_GPIO_OutClr(IN2_PORT, IN2_PIN);
if (step_seq[idx][2]) MXC_GPIO_OutSet(IN3_PORT, IN3_PIN);
else MXC_GPIO_OutClr(IN3_PORT, IN3_PIN);
if (step_seq[idx][3]) MXC_GPIO_OutSet(IN4_PORT, IN4_PIN);
else MXC_GPIO_OutClr(IN4_PORT, IN4_PIN);
}
// ===== 步进 N 步(正转 steps>0,反转 steps<0)=====
static void motor_move(int32_t steps, uint32_t step_delay_ms)
{
int dir = (steps > 0) ? 1 : -1;
int32_t cnt = (steps > 0) ? steps : -steps;
for (int32_t i = 0; i < cnt; i++) {
g_step_idx = (g_step_idx + (dir > 0 ? 1 : 7)) & 7;
motor_step(g_step_idx);
MXC_Delay(MXC_DELAY_MSEC(step_delay_ms));
}
}
// ===== 停止电机(所有线圈断电,防止发热)=====
static void motor_stop(void)
{
MXC_GPIO_OutClr(IN1_PORT, IN1_PIN);
MXC_GPIO_OutClr(IN2_PORT, IN2_PIN);
MXC_GPIO_OutClr(IN3_PORT, IN3_PIN);
MXC_GPIO_OutClr(IN4_PORT, IN4_PIN);
}
// ===== 抽屉打开 =====
static void drawer_open(void)
{
printf(">>> Opening drawer...\n");
// 正转2圈,每步2ms,约16秒完成
motor_move(STEPS_PER_REV * 2, 2);
motor_stop();
g_state = DRAWER_OPEN;
printf(">>> Drawer OPENED\n");
}
// ===== 抽屉关闭 =====
static void drawer_close(void)
{
printf(">>> Closing drawer...\n");
// 反转2圈
motor_move(-(int32_t)(STEPS_PER_REV * 2), 2);
motor_stop();
g_state = DRAWER_CLOSED;
printf(">>> Drawer CLOSED\n");
}
// ===== 主函数 =====
int main(void)
{
// 上电稳定延时
MXC_Delay(MXC_DELAY_MSEC(100));
// 串口输出启动信息
printf("\n======= Smart Drawer (ULN2003) =======\n");
printf("Pins: P1.1/P1.0/P0.7/P0.5\n");
printf("Motor: 28BYJ-48 (4096 steps/rev)\n");
printf("=======================================\n");
// GPIO初始化
motor_gpio_init();
g_state = DRAWER_CLOSED;
printf("State: CLOSED\n");
printf(">>> Smart Drawer Ready <<<\n");
printf("Commands: [o]pen [c]lose [s]tatus\n");
// 主循环:串口指令控制
char cmd;
while (1) {
cmd = getchar();
switch (cmd) {
case 'o':
case 'O':
if (g_state == DRAWER_CLOSED) {
drawer_open();
} else {
printf("Already OPEN\n");
}
break;
case 'c':
case 'C':
if (g_state == DRAWER_OPEN) {
drawer_close();
} else {
printf("Already CLOSED\n");
}
break;
case 's':
case 'S':
printf("State: %s\n",
g_state == DRAWER_CLOSED ? "CLOSED" : "OPEN");
break;
default:
printf("Unknown: '%c' (use o/c/s)\n", cmd);
break;
}
}
}
4.6 Makefile 配置
确保工程目录下的 Makefile 或 project.mk 包含:
PROJECT = SmartDrawer
TARGET = MAX78000
# 不需要额外链接库,GPIO操作是MSDK内置的
编译和烧录命令:
cd $MAXIM_PATH/Examples/MAX78000/SmartDrawer
make clean
make -j
make program
五、功能测试与验证
5.1 测试步骤
- 接好线,开发板USB连电脑,ULN2003独立供电
- 打开 PuTTY 连接串口(115200, 8, n, 1)
- 按开发板 RESET 键,看到启动信息:
======= Smart Drawer (ULN2003) ======= Pins: P1.1/P1.0/P0.7/P0.5 ======================================= State: CLOSED >>> Smart Drawer Ready <<< Commands: [o]pen [c]lose [s]tatus - 键盘输入
o并回车,观察 ULN2003 上4个LED依次点亮,电机转动,串口输出>>> OPENED - 输入
c并回车,电机反转,串口输出>>> CLOSED
上图:PuTTY串口输出截图,程序启动后输入
o命令,电机开始转动,串口依次输出>>> Opening drawer...和>>> Drawer OPENED。输入c则执行关闭动作。
5.2 实测现象
上图:ULN2003驱动板通电后,4个LED指示灯随步进电机相序依次点亮,说明GPIO信号输出正常,驱动板工作正常。
[!SUCCESS]
- ULN2003 上4个LED按顺序循环点亮,说明GPIO输出正常
- 28BYJ-48 电机平稳转动,无失步现象
- 串口指令响应及时,状态切换正确
- 电机停止后所有线圈断电,无发热问题
5.3 参数调整建议
| 参数 | 当前值 | 调整建议 |
|---|---|---|
| 转动圈数 | 2圈 | 根据实际锁舌行程调整,改 STEPS_PER_REV * 2 中的 2 |
| 每步延时 | 2ms | 减小可加速(如改1ms),增大可减速(如改5ms) |
| 步进方向 | 正转=开 | 如果方向反了,把 motor_move 里的 steps 符号取反 |
六、电路原理图
6.1 系统整体连接图
MAX78000FTHR 开发板
┌───────────────────────┐
│ │
│ P1_1 ───────┐ │
│ P1_0 ───────┼──┐ │
│ P0_7 ───────┼──┼──┼─→ GND ───┐
│ P0_5 ───────┼──┼──┼─→ 3.3V │
│ │ │ │ │
│ USB-C─┘ │ │ │
└──────────────────┼──┼───────────┘
│ │
┌────────────────┘ │
│ │
┌─────────┴─────────┐ │
│ ULN2003 驱动板 │ │
│ IN1 ← P1_1 │ │
│ IN2 ← P1_0 │ │
│ IN3 ← P0_7 │ │
│ IN4 ← P0_5 │ │
│ GND ←─────────────┼─────────┘
│ COM ← 外部5V电源+ │
│ 电机接口 ← 28BYJ-48 │
└────────────────────┘
6.2 引脚功能对照表
MAX78000FTHR Header 排针位置(以板子丝印为准):
| 信号 | 开发板引脚 | ULN2003侧 | 备注 |
|---|---|---|---|
| IN1 | P1.1 | IN1 | 排针 J4 或 J5,具体看板子丝印 |
| IN2 | P1.0 | IN2 | 同上 |
| IN3 | P0.7 | IN3 | 同上 |
| IN4 | P0.5 | IN4 | 同上 |
| GND | 任意GND | GND | 共地必须接 |
[!TIP]
如果不确定排针位置,用万用表蜂鸣档量一下:开发板排针和芯片引脚之间的连通性,或者查 MAX78000FTHR 原理图(Maxim 官网可下载)。
七、后续扩展方向
当前实现为基础版本,我计划从以下几个方向继续推进:
- 物联网接入:通过ESP8266/ESP32 WiFi模块,接入Home Assistant实现远程开锁
- 3D打印结构:设计锁舌和电机支架,用3D打印制作完整的机械结构
附录:完整物料清单
| 物料 | 型号/规格 | 数量 | 参考价格 |
|---|---|---|---|
| MAX78000FTHR开发板 | ADI官方 | 1 | 活动申请 |
| ULN2003驱动板 | 带LED指示 | 1 | ¥2~5 |
| 28BYJ-48步进电机 | 5V四相 | 1 | ¥5~10 |
| 杜邦线 | 母对母 | 6根 | ¥若干 |
| 外部5V电源 | USB或适配器 | 1 | 已有 |
| USB Type-C线 | 数据+供电 | 1 | 已有 |


