给STM32机械臂加点“记忆”:手把手实现动作录制与回放
机械臂的同步控制已经不能满足你的好奇心了吗?想让你的DIY机械臂像工业机器人一样记住动作序列并精准复现?本文将带你深入STM32的外部中断和数组存储技术,实现一个具备动作录制与回放功能的智能机械臂系统。
1. 从同步控制到动作记忆:系统设计思路
传统电位器控制机械臂的方案存在明显局限——每次操作都需要人工实时控制。而加入记忆功能后,系统可以:
- 录制模式:通过按键触发,记录舵机位置数据和时间间隔
- 回放模式:自动复现录制动作,解放操作者双手
- 循环控制:支持多次循环播放,适合重复性任务
硬件架构上,我们在原有电位器+舵机的基础上,增加了两个关键元素:
- 外部中断按键:用于模式切换(KEY1开始录制,KEY0结束录制)
- 数组存储空间:保存多路舵机的PWM数值序列
注意:建议使用STM32F103ZET6等具有足够RAM的型号,录制时长受限于可用内存。
2. 核心代码实现:中断与数据存储
2.1 外部中断配置
// exti.c void EXTIX_Init(void) { EXTI_InitTypeDef EXTI_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; // KEY1(PE3)配置为下降沿触发 GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource3); EXTI_InitStructure.EXTI_Line = EXTI_Line3; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; EXTI_Init(&EXTI_InitStructure); // 设置中断优先级 NVIC_InitStructure.NVIC_IRQChannel = EXTI3_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); }2.2 动作录制与存储
在中断服务函数中实现数据采集:
// 全局变量定义 #define MAX_RECORD_STEPS 500 typedef struct { uint16_t pwm1; uint16_t pwm2; uint16_t pwm4; // TIM3通道3未使用 } ServoPosition; ServoPosition recordedSteps[MAX_RECORD_STEPS]; uint16_t currentStep = 0; uint8_t isRecording = 0; void EXTI3_IRQHandler(void) { delay_ms(10); // 消抖 if(KEY1 == 0 && !isRecording) { isRecording = 1; currentStep = 0; LED1 = 0; // 指示灯亮表示录制中 while(KEY0 != 0 && currentStep < MAX_RECORD_STEPS) { // 记录三路舵机位置 recordedSteps[currentStep].pwm1 = ADCConvertedValue[0][0]/20.475 + 50; recordedSteps[currentStep].pwm2 = ADCConvertedValue[0][1]/20.475 + 50; recordedSteps[currentStep].pwm4 = ADCConvertedValue[0][2]/20.475 + 50; // 实时控制舵机 TIM_SetCompare1(TIM3, recordedSteps[currentStep].pwm1); TIM_SetCompare2(TIM3, recordedSteps[currentStep].pwm2); TIM_SetCompare4(TIM3, recordedSteps[currentStep].pwm4); currentStep++; delay_ms(15); // 采样间隔 } isRecording = 0; LED1 = 1; // 指示灯灭 EXTI_ClearITPendingBit(EXTI_Line3); } }3. 动作回放优化技巧
3.1 流畅回放实现
原始方案直接使用delay_ms()控制节奏,可能导致动作卡顿。改进方案:
- 定时器中断控制:使用另一个定时器产生固定时间间隔的中断
- 双缓冲机制:准备下一帧数据时不影响当前帧执行
// 使用TIM4控制回放节奏 void TIM4_IRQHandler(void) { if(TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET) { static uint16_t playIndex = 0; if(playIndex < currentStep) { TIM_SetCompare1(TIM3, recordedSteps[playIndex].pwm1); TIM_SetCompare2(TIM3, recordedSteps[playIndex].pwm2); TIM_SetCompare4(TIM3, recordedSteps[playIndex].pwm4); playIndex++; } else { playIndex = 0; // 循环播放 } TIM_ClearITPendingBit(TIM4, TIM_IT_Update); } }3.2 存储结构优化对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 二维数组 | 实现简单 | 浪费空间 | 少量短时记录 |
| 结构体数组 | 数据关联性强 | 访问稍复杂 | 多路同步记录 |
| 链表结构 | 动态内存利用 | 实现复杂 | 长时间不确定长度记录 |
| 外部Flash | 容量大 | 需要额外芯片 | 工业级应用 |
4. 进阶功能扩展
4.1 动作编辑功能
在基础录制回放上,可以添加:
- 关键帧删除:去除冗余点位
- 速度调节:整体加快/减慢动作
- 动作拼接:组合多个录制片段
// 示例:删除指定范围内的记录点 void deleteSteps(uint16_t start, uint16_t end) { if(end >= currentStep) end = currentStep - 1; uint16_t moveCount = currentStep - end - 1; for(uint16_t i = 0; i < moveCount; i++) { recordedSteps[start+i] = recordedSteps[end+1+i]; } currentStep -= (end - start + 1); }4.2 掉电保存方案
使用STM32内部Flash或外接EEPROM保存动作数据:
- 数据压缩:将PWM值从uint16_t转换为uint8_t(精度损失可接受)
- 校验机制:添加CRC校验防止数据损坏
- 分页存储:利用Flash的页写入特性
// Flash存储示例 #define FLASH_PAGE_SIZE 1024 #define FLASH_START_ADDR 0x0801F000 // 最后一页 void saveToFlash(void) { FLASH_Unlock(); FLASH_ErasePage(FLASH_START_ADDR); uint32_t addr = FLASH_START_ADDR; for(uint16_t i = 0; i < currentStep; i++) { FLASH_ProgramHalfWord(addr, recordedSteps[i].pwm1); addr += 2; FLASH_ProgramHalfWord(addr, recordedSteps[i].pwm2); addr += 2; // 确保不超过页边界 if(addr >= FLASH_START_ADDR + FLASH_PAGE_SIZE) break; } FLASH_Lock(); }5. 实际应用中的问题排查
调试过程中常见问题及解决方案:
动作抖动不流畅
- 检查定时器配置,确保PWM频率为50Hz
- 增加采样间隔(15ms可能太短)
- 添加软件滤波处理ADC数据
录制数据溢出
- 动态计算剩余存储空间
- 添加存储满提示(LED闪烁频率变化)
回放位置偏差
- 校准电位器与舵机的映射关系
- 检查供电稳定性(舵机电流突变可能导致电压波动)
// ADC数据滤波示例 uint16_t getFilteredADC(uint8_t channel) { static uint16_t filterBuf[3][5] = {0}; static uint8_t index = 0; uint32_t sum = 0; // 更新缓冲区 filterBuf[channel][index] = ADCConvertedValue[0][channel]; index = (index + 1) % 5; // 中值平均滤波 for(uint8_t i = 0; i < 5; i++) { sum += filterBuf[channel][i]; } return sum / 5; }在完成基础功能后,可以尝试为机械臂添加更多智能特性——比如通过加速度传感器记录末端轨迹,或者开发PC端的上位机软件来可视化编辑动作序列。这些扩展都能让你的机械臂项目从简单的玩具升级为真正的可编程控制原型。