一、项目概述与核心任务

1.1 项目简介

本项目旨在设计并开发一套以STM32F407VET6单片机为主控的六轴机械臂控制系统。系统核心在于提供两种工作模式:示教编程模式自动复现模式。在示教模式下,操作者可以手动控制机械臂的每一个关节,记录下期望的运动姿态;在复现模式下,机械臂能够自动、精确地重复之前录制的动作序列。该系统适用于教学演示、重复性点位操作等场景,具有成本适中、控制灵活、易于二次开发的特点。

1.2 核心任务拆解

为确保系统达到设计目标,开发任务被分解为以下关键模块:

  • 系统架构搭建:基于STM32 HAL库构建前后台程序框架,实现两种核心模式的逻辑切换与数据流管理。
  • 运动平稳性优化:设计并集成S形速度曲线算法,消除机械臂启动和停止时的冲击与抖动。
  • 多任务实时调度:利用定时器中断将高优先级的运动控制任务与主循环的人机交互任务(按键扫描、屏幕刷新)解耦,确保运动响应的实时性。
  • 舵机精准控制:建立0~270°舵机角度与PCA9685 PWM寄存器值的精确线性映射,并实现可靠的边界保护。
  • 可靠人机交互:实现4×4矩阵键盘的稳定扫描与软件消抖,并优化OLED屏幕的刷新逻辑以消除显示闪烁。

二、系统方案设计

2.1 示教编程模式

示教模式是系统进行轨迹自定义的基础,其功能设计如下:

  • 关节选择:通过键盘切换控制0~5号共六个关节,实现单关节独立调试。
  • 连续角度调节:选中关节后,长按按键可连续平滑地增减该关节角度,范围覆盖舵机全行程0~270°。
  • 姿态存储:调整好目标姿态后,一键保存当前六轴所有角度值,系统可连续存储多组姿态(本设计支持20组),形成运动序列。
  • 状态实时显示:OLED屏幕实时刷新当前选中的关节编号、各关节实时角度、已存储的姿态数量,实现可视化操作。

2.2 自动复现模式

复现模式基于示教存储的数据,实现自动化作业:

  • 一键启动运行:触发后,系统按存储顺序依次驱动机械臂完成各姿态切换。
  • 运行中途暂停:支持在复现过程中紧急停止,机械臂保持当前位置,增强安全性。
  • 自动复位:所有姿态执行完毕后,机械臂自动回归初始零位姿态。
  • 进度实时显示:OLED屏幕展示当前执行的姿态序号和剩余任务量。

2.3 高精度运动控制策略

为保障运动质量,系统从三个层面进行了优化:

  1. S形平滑速度曲线:采用公式 pos = 0.5 × (1 - cos(π × t)) 进行位置插补,使启停速度为零,运动过程速度连续变化,彻底消除阶跃突变带来的机械冲击。
  2. 定时器中断驱动:使用TIM4定时器产生5ms固定周期中断,专门处理运动位置更新和舵机驱动,确保运动控制周期稳定,不受主循环其他任务影响。
  3. 分关节顺序运动:每次中断只计算和更新一个关节的角度,六个关节依次进行。此策略大幅降低了单次中断的CPU计算负荷,避免了多关节同步计算可能导致的程序卡顿。

三、硬件设计与软件环境

3.1 硬件平台与I/O分配

硬件模块 型号/接口 作用
主控芯片 STM32F407VET6 (Cortex-M4, 72MHz) 系统核心控制器
舵机驱动板 PCA9685 (I2C接口) 提供16路PWM输出,驱动6路舵机
舵机 AT-TBSK20 × 6 执行机构,270°转动范围
OLED显示屏 0.96寸 SSD1306 (I2C接口) 人机交互界面,显示状态信息
输入设备 4×4 矩阵键盘 用户指令输入(模式切换、关节选择、角度调节等)

通信架构

  • I2C1 → PCA9685 (地址 0x80)
  • I2C2 → OLED (地址 0x78)
  • TIM3 → 辅助PWM输出
  • TIM4 → 5ms 定时中断,专用于运动更新

3.2 软件开发环境与配置

开发工具

  • STM32CubeMX 6.x:用于外设配置、时钟树设置、引脚分配及代码生成。
  • Keil MDK-ARM V5.36:用于代码编译、链接、下载与调试。
  • 编译器:ARM Compiler V6.22。

关键软件配置

  • 时钟配置:HSI 8MHz作为时钟源,经PLL倍频至72MHz系统主频(SYSCLK)。AHB总线(HCLK)为72MHz,APB1总线(PCLK1)为36MHz(挂载TIM3, TIM4, I2C1, I2C2),APB2总线(PCLK2)为72MHz。SysTick配置为1ms时基。
  • 外设初始化
    • I2C1 & I2C2:均配置为标准模式,100kHz速率,7位地址。
    • TIM3:配置为PWM模式,生成20ms标准舵机信号周期。
    • TIM4:配置为基础定时器,产生5ms周期中断,用于运动控制。
    • GPIO:8个引脚用于4×4矩阵键盘的行列扫描。

四、软件架构与核心代码分析

4.1 整体软件架构

系统采用前后台(中断+主循环)架构:

  • 后台(主循环):负责低实时性要求的任务,包括矩阵键盘扫描、按键指令解析、工作模式判断、OLED界面刷新等。
  • 前台(TIM4中断):负责高实时性要求的核心运动任务,包括S形速度曲线插补计算、舵机角度更新、自动复现逻辑推进等。定时中断确保了运动控制的周期性和稳定性。

这种设计实现了运动控制与人机交互任务的解耦,有效避免了因屏幕刷新或复杂按键处理导致运动卡顿的问题。

4.2 关键模块代码剖析

4.2.1 主程序调度 (main.c)

主函数负责系统初始化和主循环调度:

int main(void) {
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_I2C1_Init();
    MX_I2C2_Init();
    MX_TIM3_Init();
    MX_TIM4_Init();

    // 外设启动
    HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
    PCA9685_Init(); // 初始化舵机驱动板
    OLED_Init();    // 初始化OLED
    Teach_Init();   // 初始化示教/复现模块
    Keyboard_Init();// 初始化键盘
    HAL_TIM_Base_Start_IT(&htim4); // 启动5ms运动定时中断

    // 主循环
    while (1) {
        uint8_t key = Keyboard_ScanRelease(); // 扫描按键(释放检测)
        if(key != KEY_NONE) {
            Teach_ProcessKey(key); // 处理按键指令
        }
        // 每100ms更新一次OLED显示,避免频繁刷新
        static uint32_t last_oled = 0;
        if(HAL_GetTick() - last_oled >= 100) {
            Teach_UpdateOLED(); // 更新界面信息
            last_oled = HAL_GetTick();
        }
    }
}

// TIM4 5ms中断服务函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
    if(htim->Instance == TIM4) {
        PCA9685_ServoUpdate();   // 更新舵机角度(S曲线插补)
        Teach_ReplayUpdate();    // 推进自动复现逻辑
        Teach_HeldUpdate();      // 处理长按连续调节
    }
}
4.2.2 示教与复现逻辑 (teach.c)

示教模式按键处理:负责模式切换、关节选择、姿态存储。

void Teach_ProcessKey(uint8_t key) {
    // 按键消抖与防连击
    static uint32_t last_key_time = 0;
    static uint8_t last_key_code = KEY_NONE;
    if(key == last_key_code && HAL_GetTick() - last_key_time < 200) {
        return;
    }
    last_key_code = key;
    last_key_time = HAL_GetTick();

    // 模式切换键 (KEY_6)
    if(key == KEY_6) {
        mode = (mode == MODE_TEACH) ? MODE_REPLAY : MODE_TEACH;
        replay_running = 0; // 切换到复现模式时停止运行
        return;
    }

    if(mode == MODE_TEACH) {
        // 关节选择 (KEY_3)
        if(key == KEY_3) {
            current_joint = (current_joint + 1) % 6;
        }
        // 姿态存储 (KEY_4)
        if(key == KEY_4 && point_count < MAX_POINTS) {
            for(int i=0; i<6; i++) {
                points[point_count].angles[i] = PCA9685_GetAngle(i);
            }
            point_count++;
        }
    }
    // 复现模式启动/停止 (KEY_5)
    if(mode == MODE_REPLAY && key == KEY_5) {
        if(replay_running) {
            replay_running = 0; // 停止
        } else if(point_count > 0) {
            replay_index = 0;
            replay_running = 1; // 启动
        }
    }
}

自动复现更新函数:在TIM4中断中调用,顺序执行存储的姿态。

void Teach_ReplayUpdate(void) {
    if(!replay_running || mode != MODE_REPLAY) return;

    static uint8_t step = 0; // 当前正在运动的关节(0-5)
    static uint8_t going_home = 0; // 是否正在回零标志

    // 如果当前关节已运动到位
    if(!PCA9685_IsMoving(step)) {
        step++;
        // 如果6个关节都已运动到位
        if(step >= 6) {
            step = 0;
            if(!going_home) {
                // 切换到下一组姿态
                replay_index++;
                if(replay_index >= point_count) {
                    // 所有姿态执行完毕,开始回零
                    going_home = 1;
                    replay_index = 0;
                }
            } else {
                // 回零完成,停止运行
                replay_running = 0;
                going_home = 0;
                return;
            }
        }
        // 设置下一个关节的目标角度
        float target_angle = going_home ? home_angles[step] : points[replay_index].angles[step];
        PCA9685_SetServoAngle(step, target_angle, REPLAY_SPEED);
    }
}
4.2.3 矩阵键盘驱动 (keyboard.c)

采用行扫描法三次采样消抖算法,确保按键识别稳定可靠。

uint8_t Keyboard_ScanRelease(void) {
    static uint8_t last_stable = KEY_NONE;
    static uint8_t confirm = 0;
    uint8_t cur = KEY_NONE;

    // 逐行扫描
    for(uint8_t row=0; row<4; row++) {
        // 输出行信号
        HAL_GPIO_WritePin(ROW0_GPIO_Port, ROW0_Pin, (row==0)?GPIO_PIN_RESET:GPIO_PIN_SET);
        // ... 其他行类似
        // 读取列信号
        if(HAL_GPIO_ReadPin(COL0_GPIO_Port, COL0_Pin) == GPIO_PIN_RESET)
            cur = KeyMap[row][0];
        // ... 其他列类似
    }

    // 三次采样消抖:连续3次读到相同键值才确认
    if(cur == last_stable && cur != KEY_NONE) {
        confirm++;
        if(confirm >= 3) {
            confirm = 0;
            last_stable = KEY_NONE;
            return cur; // 返回确认的按键
        }
    } else {
        last_stable = cur;
        confirm = (cur != KEY_NONE) ? 1 : 0;
    }
    return KEY_NONE; // 未检测到稳定按键
}

五、系统调试与问题解决

本项目采用自底向上的分层调试策略:

  1. 底层外设调试:首先确保系统时钟、I2C通信、定时器、GPIO等基础驱动正常工作。例如,通过示波器验证TIM4是否精确产生5ms中断,I2C总线是否能与PCA9685和OLED正常通信。
  2. 核心算法验证:单独测试S形速度曲线插补函数,通过串口打印或LED指示观察位置计算是否平滑;验证角度到PWM寄存器的映射公式是否正确。
  3. 功能模块联调:分别调试示教模式(关节选择、角度调节、存储)和复现模式(启动、停止、回零),确保各功能独立运行正常。
  4. 整机稳定性测试:进行长时间连续运行测试,排查多任务冲突(如I2C总线抢占)、电磁干扰导致的按键误触发等问题,并优化代码逻辑。

典型问题与解决方案

  • 问题:机械臂启停时抖动明显。 解决:引入S形速度曲线算法,替换原有的匀速或阶跃运动,使速度平滑过渡。
  • 问题:屏幕刷新时运动卡顿。 解决:将运动更新任务移至TIM4定时中断,与主循环的屏幕刷新任务解耦。
  • 问题:按键偶尔误触发或连击。 解决:在键盘扫描函数中实现“三次采样消抖”算法,有效过滤机械抖动。
  • 问题:OLED显示闪烁。 解决:改全屏刷新为局部刷新,只更新变化的数字和字符区域。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐