机械臂的动作总是令人着迷——关节的倾斜、平移与伸缩,能像人类手臂一样完成灵巧的操作,带来独特的视觉美感。在工厂流水线上,机械臂早已是标配,焊接、钻孔、喷漆等高强度任务交给它们最合适不过。近年来,精密外科手术中也开始引入机械臂。今天,我们就用一块 ARM7-LPC2148 微控制器,搭配几个电位器,亲手搭建一台能完成“抓取-放置”动作的 DIY 机械臂,体验嵌入式开发的乐趣。
所需组件
3D 打印机械臂
ARM7-LPC2148 微控制器
SG-90 伺服电机 ×4
10k 电位器 ×4
按钮 ×4
发光二极管 ×4
5V(1A)直流电源适配器
电阻器(10k ×4、2.2k ×4)
面包板
连接电线
准备好 3D 打印机械臂
本次使用的机械臂基于 Thingiverse 上的 EEZYbotARM 设计制作。详细的打印和组装流程,可参考该链接中的视频教程。下图是组装完成后的实物展示:

四个伺服电机各司其职,机械臂结构已经初具规模,看起来相当专业。
电路原理图
下面的电路图展示了完整的连接方案:

整体来看,电路连接并不复杂。需要特别注意的是:伺服电机必须使用独立的 5V 直流电源适配器供电。电位器和按钮则可以直接使用 LPC2148 提供的 3.3V 电压。
我们选用了 LPC2148 的 4 个 ADC 引脚来连接对应的 4 个电位器,同时使用 4 个 PWM 引脚控制伺服电机。4 个按钮负责选择当前要操纵的电机——按下按钮后,旋转对应的电位器,就能改变该伺服电机的位置。
每个按钮一端通过 10k 电阻下拉,另一端接 3.3V,并连接到 LPC2148 的 GPIO。此外,还配置了 4 个 LED,用于直观显示当前选中了哪个伺服电机。



编程 LPC2148 的步骤
开始编程之前,需要掌握两项核心技能:如何在 LPC2148 中生成 PWM 信号,以及如何使用其 ADC 模块。相关基础知识可参考之前的教程“伺服电机与 LPC2148 连接”和“如何在 LPC2148 中使用 ADC”。
使用 LPC2148 进行 ADC 转换
我们需要通过 ADC 读取电位器的值,然后将其转换为占空比,从而生成控制伺服电机位置的 PWM 信号。四个电位器对应四个伺服电机,因此需要四个 ADC 通道。这里分别选用 LPC2148 的 ADC 通道 4、1、2、3,对应的引脚为 P0.25、P0.28、P0.29、P0.30。
为伺服电机生成 PWM 信号
PWM 信号是控制伺服电机位置的关键。四个伺服电机对应四个 PWM 通道,这里采用 LPC2148 的 PWM 通道 3、2、4、5,引脚分别为 P0.1、P0.7、P0.8、P0.21。
将十六进制文件编程并闪存到 LPC2148
编程环境使用 Keil uVision 编写代码并生成 hex 文件,然后通过 Flash Magic 工具将 hex 文件烧录到 LPC2148。烧录时通过微型 USB 端口连接 ARM7 Stick。如果对 Keil 和 Flash Magic 的安装与使用不太熟悉,可查阅“Getting Started With ARM7 LPC2148 Microcontroller and Program it using Keil uVision”这篇入门教程。
编码说明
完整程序在文末给出。下面重点解析关键步骤的实现细节。
配置 LPC2148 的 PORT
首先使用 PINSEL1 寄存器,将 P0.25、P0.28、P0.29、P0.30 配置为 ADC 通道 ADC0.4、ADC0.1、ADC0.2、ADC0.3;同时将 P0.21 配置为 PWM5 功能。
#define AD04 (1<<18) // 为 P0.25 选择 AD0.4 功能 #define AD01 (1<<24) // 为 P0.28 选择 AD0.1 功能 #define AD02 (1<<26) // 为 P0.29 选择 AD0.2 功能 #define AD03 (1<<28) // 为 P0.30 选择 AD0.3 功能 PINSEL1 |= AD04 | AD01 | AD02 | AD03 | (1<<10);
然后通过 PINSEL0 寄存器,将 P0.1、P0.7、P0.8 分别配置为 PWM3、PWM2、PWM4 引脚:
PINSEL0 = 0x000A800A;
再使用 PINSEL2 寄存器,将 PORT1 中连接 LED 和按钮的引脚设置为通用 GPIO 功能:
PINSEL2 = 0x00000000;
接下来通过 IODIR1 寄存器,将 LED 引脚设为输出模式,按钮引脚设为输入模式:
IODIR1 = ((0<<17)|(0<<18)|(0<<19)|(0<<20)|(1<<28)|(1<<29)|(1<<30)|(1<<31));
引脚号定义如下:
#define SwitchPinNumber1 17 // P1.17 #define SwitchPinNumber2 18 // P1.18 #define SwitchPinNumber3 19 // P1.19 #define SwitchPinNumber4 20 // P1.20 #define LedPinNumber1 28 // P1.28 #define LedPinNumber2 29 // P1.29 #define LedPinNumber3 30 // P1.30 #define LedPinNumber4 31 // P1.31
配置 ADC 转换设置
使用 AD0CR_setup 寄存器来设定 ADC 的转换模式和时钟频率:
unsigned long AD0CR_setup = (CLKDIV<<8) | BURST_MODE_OFF | PowerUP;
其中 CLKDIV、Burst Mode 和 PowerUP 的定义如下:
#define CLKDIV (15-1) #define BURST_MODE_OFF (0<<16) #define PowerUP (1<<21)
CLKDIV 用于产生 ADC 时钟。ADC 时钟频率 = PCLK / CLKDIV,这里 PCLK = 60MHz,目标让 ADC 工作在约 4MHz(实际使用 CLKDIV-1,即 15-1)。
Burst Mode(Bit-16):置位时 ADC 会对 SEL 位中选中的所有通道依次转换;清零则禁用 Burst 模式。
断电模式(Bit-21):置位使 ADC 退出掉电模式并开始工作,清零则关闭 ADC。
配置 PWM 转换设置
首先使用 PWMTCR 寄存器复位并禁用 PWM 计数器,同时通过 PWMPR 设置预分频值:
PWMTCR = 0x02; PWMPR = 0x1D;
然后在匹配寄存器 0(PWMMR0)中设定一个周期的最大计数值。这里设为 20000,对应 20ms 的 PWM 波周期:
PWMMR0 = 20000;
接着在 PWMMR4、PWMMR2、PWMMR3、PWMMR5 中写入占空比值,初始全部设为 0:
PWMMR4 = 0; PWMMR2 = 0; PWMMR3 = 0; PWMMR5 = 0;
然后设置 PWM 匹配控制寄存器,使得当匹配事件发生时,计数器复位:
PWMMCR = 0x00000002; // 在 MR0 匹配时复位
之后,通过 PWM 锁存使能寄存器(PWMLER)使能匹配值的生效:
PWMLER = 0x7C; // 使能 PWM2、PWM4、PWM4 和 PWM5 的锁存
用 PWMTCR 的一个位来复位定时器计数器并启用 PWM:
PWMTCR = 0x09; // 启用 PWM 和计数器
最后,在 PWM 控制寄存器(PWMPCR)中使能 PWM 输出,并设置为单边沿控制模式:
PWMPCR = 0x7C00; // 使能 PWM2、PWM4、PWM4 和 PWM5,单边沿控制
用按钮选择要旋转的伺服电机
四个按钮分别对应四个伺服电机。按下某个按钮后,旋转对应的电位器,ADC 值会随之改变,进而调节占空比,让对应的伺服电机转动到新的位置。
获取按钮开关状态的方法:
switchStatus1 = (IOPIN1>>SwitchPinNumber1) & 0x01;
根据哪个开关被按下,会触发相应的 ADC 转换。转换完成后(ADC 值范围 0~1023),将其映射到 0~2450 的范围,然后写入对应的 PWMMRx 寄存器。同时,对应的 LED 会亮起,提示当前正在操作哪个电机。以下是第一个按钮的处理逻辑示例:
if(switchStatus1 == 1)
{
IOPIN1 = (1<>6) & 0x3ff; // 获取 ADC 值
result1 = map(convert1,0,1023,0,2450); // 映射到 PWM 占空比
PWMMR5 = result1; // 设置占空比到 PWM5
PWMLER = 0x20; // 使能 PWM5
delay_ms(2);
}
}
else
{
IOPIN1 = (0<
机械臂的工作方式
代码上传到 LPC2148 之后,按下任意一个开关,旋转对应的电位器,机械臂就会随之改变姿态,实现灵活控制。

每个开关与电位器的组合,分别控制着每一个伺服电机的运动:底座左右旋转、大臂上下摆动、小臂前后伸展、夹爪的抓取与释放。整套动作流畅而精准,充分展现了 DIY 机械臂的魅力。
以下是完整的程序代码:
// 使用 LPC2148 的机械臂程序
#include
#include
#define SwitchPinNumber1 17
#define SwitchPinNumber2 18
#define SwitchPinNumber3 19
#define SwitchPinNumber4 20
#define LedPinNumber1 28
#define LedPinNumber2 29
#define LedPinNumber3 30
#define LedPinNumber4 31
#define AD04 (1<<18)
#define AD01 (1<<24)
#define AD02 (1<<26)
#define AD03 (1<<28)
#define SEL_AD04 (1<<4)
#define SEL_AD01 (1<<1)
#define SEL_AD02 (1<<2)
#define SEL_AD03 (1<<3)
#define CLKDIV (15-1)
#define BURST_MODE_OFF (0<<16)
#define PowerUP (1<<21)
#define START_NOW ((0<<26)|(0<<25)|(1<<24))
#define ADC_DONE (1UL<<31)
#define VREF 3.3
volatile int result1 = 0;
volatile int result2 = 0;
volatile int result3 = 0;
volatile int result4 = 0;
volatile int convert1 = 0;
volatile int convert2 = 0;
volatile int convert3 = 0;
volatile int convert4 = 0;
volatile unsigned int switchStatus1;
volatile unsigned int switchStatus2;
volatile unsigned int switchStatus3;
volatile unsigned int switchStatus4;
long map(long x, long in_min, long in_max, long out_min, long out_max)
{
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
void delay_ms(uint16_t j)
{
uint16_t x,i;
for(i=0;i>SwitchPinNumber1) & 0x01;
switchStatus2 = (IOPIN1>>SwitchPinNumber2) & 0x01;
switchStatus3 = (IOPIN1>>SwitchPinNumber3) & 0x01;
switchStatus4 = (IOPIN1>>SwitchPinNumber4) & 0x01;
if(switchStatus1 == 1)
{
IOPIN1 = (1<>6) & 0x3ff;
result1 = map(convert1,0,1023,0,2450);
PWMMR5 = result1;
PWMLER = 0x20;
delay_ms(2);
}
}
else
{
IOPIN1 = (0<>6) & 0x3ff;
result2 = map(convert2,0,1023,0,2450);
PWMMR4 = result2;
PWMLER = 0x10;
delay_ms(2);
}
}
else
{
IOPIN1 = (0<>6) & 0x3ff;
result3 = map(convert3,0,1023,0,2450);
PWMMR3 = result3;
PWMLER = 0x08;
delay_ms(2);
}
}
else
{
IOPIN1 = (0<>6) & 0x3ff;
result4 = map(convert4,0,1023,0,2450);
PWMMR2 = result4;
PWMLER = 0x04;
delay_ms(2);
}
}
else
{
IOPIN1 = (0<
