富瀚微MC632X 软件定时器控制舵机周期运行

在嵌入式系统开发中,定时器和舵机控制是两个非常基础但又十分重要的功能。本文将基于富瀚微MC632X平台,通过一个完整的实践demo,详细介绍如何使用软件定时器来控制SG90舵机实现周期性运动。

视频讲解
在这里插入图片描述
源码地址:https://gitee.com/vor2345/mc632-x_hwtimer/tree/master

环境搭建

首先声明此博客是有豆包辅助生成,其中测试代码也是哦,大家有机会也来试试哦哦🤣🤣🤣
请大家点击豆包火山注册地址:https://t.vncps.com/5LOve

环境搭建:在Ubuntu系统下安装SDK,详见:【富瀚微 MC632x 开发板】介绍、环境搭建、工程测试 - RT-Thread
在这里插入图片描述

【VirtualBox】安装 VirtualBox 提示 needsthe Microsoft Visual C++ 2019报错解决方案:
解决方案:下载 Microsoft Visual C++ 2019
链接: Microsoft Visual C++官网
我的电脑是64 就下载x64的了,你电脑32位的就下载x86。
在这里插入图片描述
在这里插入图片描述
环境搭建:https://static.app.yinxiang.com/verse/share/c4hFYioqQFS-PTj_JmDGcg/I1Yp9Gn3Ske2TUum9z1F9A/?fromNote=I1Yp9Gn3Ske2TUum9z1F9A&flatten=false

一、引言

我们将从最基础的定时器操作开始,逐步深入到舵机的精确控制,最后实现一个30秒周期的舵机扫描演示。

1.1工程配置

进入 FH_RT_V3.4.0_20250123/rt-thread 路径下,打开终端执行 make menuconfig 指令;
进入 select app demo (bsp demo) 选项,勾选 bsp demo,保存并esc退出;
在这里插入图片描述

在这里插入图片描述

1.2工程代码

打开文件 ./FH_RT_V3.4.0_20250123/rt-thread/app/bsp_demo/新建hwtimer文件夹,添加hwtimer_demo.c源文件,内容如下

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/prctl.h>
#include <sys/ioctl.h>
#include <rttshell.h>
#include <sys/time.h>  /* 用于gettimeofday */

/* 添加PWM舵机需要的头文件 */
#include "pwm.h"

/* 定时器寄存器地址 - 请根据实际硬件修改 */
#define TMR_REG_BASE        0x1F006000  /* 示例地址,请查阅数据手册 */
#define TIMER_LOAD_COUNT    0x00        /* 加载计数值寄存器偏移 */
#define TIMER_CURRENT_VALUE 0x04        /* 当前值寄存器偏移 */
#define TIMER_CTRL_REG      0x08        /* 控制寄存器偏移 */
#define TIMER_EOI           0x0C        /* 中断结束寄存器偏移 */
#define TIMER_INT_STATUS    0x10        /* 中断状态寄存器偏移 */

/* 控制寄存器位定义 */
#define TIMER_CTRL_ENABLE   (1 << 0)    /* 定时器使能 */
#define TIMER_CTRL_MODE     (1 << 1)    /* 模式:0=单次,1=周期 */
#define TIMER_CTRL_INTMASK  (1 << 2)    /* 中断屏蔽:0=使能,1=屏蔽 */

/* 定时器中断号 - 请根据实际硬件修改 */
#define TMR0_IRQn           32

/* 寄存器访问宏 */
#define REG32(addr)         (*(volatile unsigned int *)(addr))
#define TIMER_REG(offset)   REG32(TMR_REG_BASE + offset)

/* 定时器中断计数 */
static volatile unsigned int timer_int_count = 0;
static volatile unsigned int timer_oneshot_count = 0;
static volatile int timer_timeout_flag = 0;

/* 定时器频率 - 请根据实际时钟修改 */
#define TIMER_CLOCK         24000000  /* 24MHz */

/* ==================== SG90舵机相关定义 ==================== */
static int pwm_fd;
static int g_current_angle = 0;
static int g_running = 1;

/* SG90舵机参数
 * 周期: 20ms = 20000000ns
 * 0度: 0.5ms = 500000ns
 * 180度: 2.5ms = 2500000ns
 */
#define SG90_PERIOD_NS      20000000  /* 20ms */
#define SG90_PULSE_MIN_NS   500000    /* 0.5ms - 0度 */
#define SG90_PULSE_MAX_NS   2500000   /* 2.5ms - 180度 */
#define SG90_PULSE_RANGE_NS 2000000   /* 2.5ms - 0.5ms = 2.0ms */

/* 角度转换为脉冲宽度(纳秒) */
static int angle_to_pulse_ns(int angle)
{
    if (angle < 0) angle = 0;
    if (angle > 180) angle = 180;
    
    /* 线性转换:0度 -> 0.5ms, 180度 -> 2.5ms */
    return SG90_PULSE_MIN_NS + (angle * SG90_PULSE_RANGE_NS / 180);
}

/* 初始化PWM */
static int pwm_init(void)
{
    pwm_fd = open("/dev/pwm", O_RDWR);
    if (pwm_fd == -1)
    {
        printf("[SG90] open pwm failed\n");
        return -1;
    }
    return 0;
}

/* 设置舵机角度 */
static int sg90_set_angle(int angle)
{
    struct fh_pwm_chip_data pwm_cfg;
    int pulse_ns;
    
    /* 角度限幅 */
    if (angle < 0 || angle > 180) {
        return -1;
    }
    
    /* 计算脉冲宽度 */
    pulse_ns = angle_to_pulse_ns(angle);
    
    /* 配置PWM参数 */
    pwm_cfg.id = 0;  /* PWM通道0 */
    pwm_cfg.config.period_ns = SG90_PERIOD_NS;  /* 20ms周期 */
    pwm_cfg.config.duty_ns = pulse_ns;           /* 脉冲宽度 */
    pwm_cfg.config.percent = 0;
    pwm_cfg.config.delay_ns = 0;
    pwm_cfg.config.phase_ns = 0;
    pwm_cfg.config.pulses = FH_PWM_PULSE_LIMIT;  /* 连续输出 */
    pwm_cfg.config.pulse_num = 0;                 /* 无限脉冲 */
    pwm_cfg.config.finish_all = 0;
    pwm_cfg.config.finish_once = 0;
    pwm_cfg.finishall_callback = NULL;
    pwm_cfg.finishonce_callback = NULL;
    
    /* 先关闭PWM输出 */
    ioctl(pwm_fd, DISABLE_PWM, &pwm_cfg);
    
    /* 设置PWM配置 */
    ioctl(pwm_fd, SET_PWM_CONFIG, &pwm_cfg);
    
    /* 使能PWM输出 */
    ioctl(pwm_fd, ENABLE_PWM, &pwm_cfg);
    
    printf("[SG90] Angle: %3d°\r", angle);
    fflush(stdout);
    
    return 0;
}

/* 关闭PWM输出 */
static void sg90_stop(void)
{
    struct fh_pwm_chip_data pwm_cfg;
    
    if (pwm_fd != -1) {
        pwm_cfg.id = 0;
        ioctl(pwm_fd, DISABLE_PWM, &pwm_cfg);
        close(pwm_fd);
        pwm_fd = -1;
    }
    printf("\n[SG90] PWM stopped\n");
}

/* 快速扫描线程 - 30秒一个周期 */
void *sg90_fast_thread(void *para)
{
    prctl(PR_SET_NAME, "sg90_fast");
    
    printf("\n========== SG90 Fast Demo (30s/cycle) ==========\n");
    printf("  0° -> 180° -> 0° in 30 seconds\n");
    printf("  Speed: 12° per second\n");
    printf("==============================================\n\n");
    
    /* 角度变化步长:30秒完成180°变化,每秒变化12° */
    int step_per_second = 12;
    
    while (g_running) {
        /* 从0°到180°(上升阶段) */
        for (g_current_angle = 0; g_current_angle <= 180; g_current_angle += step_per_second) {
            if (!g_running) break;
            sg90_set_angle(g_current_angle);
            sleep(1);
        }
        
        if (!g_running) break;
        g_current_angle = 180;
        
        /* 从180°到0°(下降阶段) */
        for (g_current_angle = 180 - step_per_second; g_current_angle >= 0; g_current_angle -= step_per_second) {
            if (!g_running) break;
            sg90_set_angle(g_current_angle);
            sleep(1);
        }
        
        if (!g_running) break;
        g_current_angle = 0;
        
        printf("\n[SG90] Completed one cycle (30s)\n\n");
    }
    
    return NULL;
}

/* 演示5:SG90舵机快速测试 */
void sg90_demo(void)
{
    pthread_t sg90_thread;
    pthread_attr_t attr;
    
    printf("\n========== SG90 Servo Fast Demo ==========\n");
    
    /* 初始化PWM */
    if (pwm_init() != 0) {
        printf("[SG90] PWM init failed, skip this demo\n");
        return;
    }
    
    g_running = 1;
    
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    
    pthread_create(&sg90_thread, &attr, sg90_fast_thread, NULL);
    
    /* 运行2个完整周期(60秒) */
    printf("[SG90] Running for 2 cycles (60 seconds)...\n");
    sleep(60);
    
    /* 停止舵机 */
    g_running = 0;
    sg90_stop();
    printf("[SG90] Demo completed\n");
}

/* ==================== 原有的定时器函数 ==================== */

/* 模拟定时器中断处理函数 */
void timer_isr(void)
{
    unsigned int int_status;
    
    /* 读取中断状态 */
    int_status = TIMER_REG(TIMER_INT_STATUS);
    
    if (int_status & 0x01) {
        /* 清除中断 */
        TIMER_REG(TIMER_EOI) = 0;
        
        timer_int_count++;
        
        if (timer_int_count % 100 == 0) {
            printf("[HWTimer] Periodic interrupt count: %d\n", timer_int_count);
        }
    }
}

/* 模拟单次定时器中断处理 */
void timer_oneshot_isr(void)
{
    unsigned int int_status;
    
    int_status = TIMER_REG(TIMER_INT_STATUS);
    
    if (int_status & 0x01) {
        TIMER_REG(TIMER_EOI) = 0;
        
        timer_oneshot_count++;
        timer_timeout_flag = 1;
        printf("[HWTimer] Oneshot timeout! count: %d\n", timer_oneshot_count);
    }
}

/* 初始化定时器 */
void timer_hw_init(void)
{
    /* 关闭定时器 */
    TIMER_REG(TIMER_CTRL_REG) = 0;
    
    printf("[HWTimer] Hardware initialized at base 0x%08x\n", TMR_REG_BASE);
}

/* 设置定时器为周期性模式 */
void timer_set_periodic(unsigned int period_ms)
{
    unsigned int load_value;
    
    /* 计算加载值:period_ms * (TIMER_CLOCK / 1000) */
    load_value = (TIMER_CLOCK / 1000) * period_ms;
    
    /* 设置加载值 */
    TIMER_REG(TIMER_LOAD_COUNT) = load_value;
    
    /* 设置为周期性模式 */
    TIMER_REG(TIMER_CTRL_REG) = TIMER_CTRL_MODE;  /* 周期模式 */
    
    printf("[HWTimer] Set periodic mode, period: %d ms (load: %d)\n", 
           period_ms, load_value);
}

/* 设置定时器为单次模式 */
void timer_set_oneshot(unsigned int timeout_ms)
{
    unsigned int load_value;
    
    load_value = (TIMER_CLOCK / 1000) * timeout_ms;
    TIMER_REG(TIMER_LOAD_COUNT) = load_value;
    
    /* 清除MODE位为单次模式 */
    TIMER_REG(TIMER_CTRL_REG) = 0;  /* 单次模式 */
    
    printf("[HWTimer] Set oneshot mode, timeout: %d ms (load: %d)\n", 
           timeout_ms, load_value);
}

/* 启动定时器 */
void timer_start(void)
{
    unsigned int ctrl = TIMER_REG(TIMER_CTRL_REG);
    ctrl |= TIMER_CTRL_ENABLE;
    ctrl &= ~TIMER_CTRL_INTMASK;  /* 使能中断 */
    TIMER_REG(TIMER_CTRL_REG) = ctrl;
    printf("[HWTimer] Timer started\n");
}

/* 停止定时器 */
void timer_stop(void)
{
    unsigned int ctrl = TIMER_REG(TIMER_CTRL_REG);
    ctrl &= ~TIMER_CTRL_ENABLE;
    TIMER_REG(TIMER_CTRL_REG) = ctrl;
    printf("[HWTimer] Timer stopped\n");
}

/* 获取定时器当前值 */
unsigned int timer_get_current(void)
{
    return TIMER_REG(TIMER_CURRENT_VALUE);
}

/* 使用系统时间模拟定时器(如果无法直接操作硬件) */
void timer_simulate_demo(void)
{
    struct timeval start, now;
    int elapsed_ms;
    int count = 0;
    
    printf("\n========== Timer Simulation Demo ==========\n");
    printf("Using gettimeofday() to simulate timer\n\n");
    
    gettimeofday(&start, NULL);
    
    while (count < 50) {
        usleep(10000);  /* 10ms */
        gettimeofday(&now, NULL);
        elapsed_ms = (now.tv_sec - start.tv_sec) * 1000 + 
                     (now.tv_usec - start.tv_usec) / 1000;
        
        count++;
        if (count % 10 == 0) {
            printf("[SimTimer] Count: %d, Elapsed: %d ms\n", count, elapsed_ms);
        }
    }
}

/* 演示1:周期性定时器 */
void periodic_timer_demo(void)
{
    printf("\n========== Periodic Timer Demo ==========\n");
    
    timer_hw_init();
    timer_set_periodic(10);  /* 10ms */
    timer_start();
    
    /* 运行3秒 */
    timer_int_count = 0;
    sleep(3);
    
    timer_stop();
    printf("Periodic timer stopped, interrupt count: %d (expected ~300)\n", 
           timer_int_count);
}

/* 演示2:单次定时器 */
void oneshot_timer_demo(void)
{
    printf("\n========== Oneshot Timer Demo ==========\n");
    
    timer_hw_init();
    timer_set_oneshot(1000);  /* 1秒 */
    timer_start();
    
    /* 等待超时 */
    timer_timeout_flag = 0;
    int wait = 0;
    while (!timer_timeout_flag && wait < 20) {
        usleep(100000);
        wait++;
    }
    
    if (timer_timeout_flag) {
        printf("Oneshot timer triggered!\n");
    } else {
        printf("Oneshot timer timeout!\n");
    }
    
    timer_stop();
}

/* 演示3:使用系统时间的高精度计时 */
void highres_time_demo(void)
{
    struct timeval start, end;
    long long elapsed_us;
    int i;
    
    printf("\n========== High Resolution Time Demo ==========\n");
    
    gettimeofday(&start, NULL);
    
    /* 做一些工作 */
    for (i = 0; i < 1000000; i++) {
        asm volatile("nop");
    }
    
    gettimeofday(&end, NULL);
    
    elapsed_us = (end.tv_sec - start.tv_sec) * 1000000LL +
                 (end.tv_usec - start.tv_usec);
    
    printf("Elapsed time: %lld us\n", elapsed_us);
}

/* 演示4:定时器精度测试 */
void timer_precision_demo(void)
{
    struct timeval start, end;
    int expected_ms = 100;  /* 100ms */
    int actual_ms;
    int i;
    
    printf("\n========== Timer Precision Demo ==========\n");
    
    for (i = 0; i < 5; i++) {
        gettimeofday(&start, NULL);
        usleep(expected_ms * 1000);
        gettimeofday(&end, NULL);
        
        actual_ms = (end.tv_sec - start.tv_sec) * 1000 +
                    (end.tv_usec - start.tv_usec) / 1000;
        
        printf("Sleep %d ms, actual: %d ms, error: %d ms\n",
               expected_ms, actual_ms, actual_ms - expected_ms);
    }
}

/* 主测试线程 - 现在包含5个演示 */
void *hwtimer_demo_main(void *para)
{
    prctl(PR_SET_NAME, "hwtimer demo");
    
    printf("\n============================================\n");
    printf("     HWTimer Practice Demo Start (5 Tests)  ");
    printf("\n============================================\n");
    
    /* 运行5个演示 */
    printf("\n--- Test 1/5: Timer Simulation ---\n");
    timer_simulate_demo();
    
    printf("\n--- Test 2/5: High Resolution Time ---\n");
    highres_time_demo();
    
    printf("\n--- Test 3/5: Timer Precision ---\n");
    timer_precision_demo();
    
    printf("\n--- Test 4/5: Hardware Timer (commented out) ---\n");
    // periodic_timer_demo();
    // oneshot_timer_demo();
    
    printf("\n--- Test 5/5: SG90 Servo Fast Demo (30s/cycle) ---\n");
    sg90_demo();
    
    printf("\n============================================\n");
    printf("     HWTimer Practice Demo Completed (5/5)  ");
    printf("\n============================================\n");
    
    return NULL;
}

/* 初始化函数 - 启动所有测试 */
int hwtimer_demo_init(void)
{
    int ret;
    pthread_t hwtimer_thread;
    pthread_attr_t attr;
    
    printf("[HWTimer Demo] Initializing...\n");
    printf("[HWTimer Demo] This will run 5 tests including SG90 servo\n");
    
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    pthread_attr_setstacksize(&attr, 10 * 1024);
    
    ret = pthread_create(&hwtimer_thread, &attr, hwtimer_demo_main, NULL);
    if(ret) {
        printf("[HWTimer Demo] Error: Create thread failed!\n");
        return -1;
    }
    
    printf("[HWTimer Demo] Started successfully - running 5 tests\n");
    return 0;
}

/* 仅运行SG90舵机测试 */
int sg90_only_demo(void)
{
    printf("[SG90] Running standalone servo test\n");
    sg90_demo();
    return 0;
}

/* 硬件信息探测函数 */
void hwtimer_probe(void)
{
    printf("\n===== HWTimer Probe =====\n");
    printf("Timer base address: 0x%08x\n", TMR_REG_BASE);
    printf("Timer clock: %d Hz\n", TIMER_CLOCK);
    
    /* 尝试读取寄存器 */
    printf("\nRegister values:\n");
    printf("  CTRL: 0x%08x\n", TIMER_REG(TIMER_CTRL_REG));
    printf("  LOAD: 0x%08x\n", TIMER_REG(TIMER_LOAD_COUNT));
    printf("  CUR:  0x%08x\n", TIMER_REG(TIMER_CURRENT_VALUE));
    printf("========================\n");
}

// /* 导出命令 */
// #ifdef RT_USING_FINSH
// #include <finsh.h>
// FINSH_FUNCTION_EXPORT(hwtimer_demo_init, Run all 5 HWTimer tests (incl. SG90));
// FINSH_FUNCTION_EXPORT(sg90_only_demo, Run only SG90 servo test);
// FINSH_FUNCTION_EXPORT(hwtimer_probe, Probe HWTimer hardware);
// #endif

1.3 配置编译链接

然后当前文件夹bsp_demo文件夹中的makefile文件,添加

SAMP_SRCS += $(wildcard hwtimer/*.c)

配置完成如下
在这里插入图片描述

1.4 设置调用程序

bsp_demo文件夹下的application修改如下

#include <stdio.h>
#include <unistd.h>
#include <string.h>

#include "rttshell.h"

extern void sadc_demo_init(void);
extern int sdcard_demo_init(void);
extern int aes_demo_init(void);
extern int pwm_demo_init(void);
extern int gpio_demo_init(void);
extern int uart_demo_init(void);
extern int i2c_demo_init(void);
extern int rtc_demo_init(void);
extern int hwtimer_demo_init(void);
void user_main(void)
{
    sleep(5);
    // aes_demo_init();
    // i2c_demo_init();
    // rtc_demo_init();
    // sadc_demo_init();
    // sdcard_demo_init();
    // pwm_demo_init();
    // gpio_demo_init();
    // hwtimer_demo_init();
    // uart_demo_init();
}

static void bsp_demo_usage(void)
{
    printf("Usage:\n");
    // printf("  bsp_demo -e:   run aes demo\n");
    printf("  bsp_demo -t:   run rtc demo\n");
    // printf("  bsp_demo -i:   run i2c demo\n");
    // printf("  bsp_demo -a:   run sadc demo\n");
    printf("  bsp_demo -p:   run pwm demo\n");
    printf("  bsp_demo -g:   run gpio demo\n");
    // printf("  bsp_demo -u:   run uart demo\n");
    // printf("  bsp_demo -c:   run sdcard demo\n");
}

static void bsp_demo(int argc, char *argv[])
{
    if (argc < 2)
    {
        bsp_demo_usage();
        return;
    }
    if (strcmp(argv[1], "-g") == 0)
    {
        gpio_demo_init();
    }
    else if (strcmp(argv[1], "-p") == 0)
    {
        pwm_demo_init();
    }
    // else if (strcmp(argv[1], "-c") == 0)
    // {
    //     sdcard_demo_init();
    // }
    // else if (strcmp(argv[1], "-a") == 0)
    // {
    //     sadc_demo_init();
    // }
    else if (strcmp(argv[1], "-t") == 0)
    {
        // rtc_demo_init();
        
        hwtimer_demo_init();
    }
    // else if (strcmp(argv[1], "-i") == 0)
    // {
    //     i2c_demo_init();
    // }
    else
    {
        bsp_demo_usage();
    }
}
SHELL_CMD_EXPORT(bsp_demo, bsp demo)

终端执行 make clean 指令清除历史编译文件,执行 make 完成编译;
编译成功如下
在这里插入图片描述

二、硬件准备

2.1 硬件清单

  • 富瀚微MC632X开发板
  • SG90舵机 × 1
  • 杜邦线若干
  • 3.3V电源(舵机需要独立供电)

2.2 硬件连接

SG90舵机有三根线:

  • 棕色线:GND(接地)- 连接到开发板GND
  • 红色线:VCC(电源)- 连接到CON1的3.3V电源
  • 黄色线:信号线 - 连接到开发板PWM0输出引脚J54的pwm_out0
    在这里插入图片描述

注意:SG90舵机工作时电流较大(可达几百毫安),建议使用外部5V电源供电,不要直接从开发板USB取电,以免造成开发板供电不足。

三、软件框架

使用 USB 转 TTL 工具连接开发板 UART 接口、连接网线、连接电源;打开 SecureCRT 软件,连接对应设备端口;烧录 u-boot,烧录 RT-Thread 内核;开发板上电,敲任意键进入 u-boot;
终端执行 pri 指令查看 ip 地址;
在这里插入图片描述

ping 服务器 IP 地址,确保网络畅通;
使用 tftp 传输,选择固件路径与对应 ip ;
在这里插入图片描述

启动烧录,烧录镜像后,添加新的 bootcmd 命令即可
bsp_demo.img位置在rt-thread/app/bsp_demo/out/bin/bsp_demo.img
在这里插入图片描述

tftp 40000000 bsp_demo.img
sf probe 0 
sf erase 0x120000 0x300000
sf write 40000000 0x120000 0x300000
set bootcmd 'sf probe 0; sf read 40000000 0x120010 0x300000; go 40000000'
saveenv

烧录成功
在这里插入图片描述

开发板重新上电即可进入内核。

本demo基于RT-Thread实时操作系统,主要包含以下几个部分:

  1. 硬件定时器驱动:直接操作MC632X的定时器寄存器
  2. PWM驱动:通过 /dev/pwm 设备文件控制PWM输出
  3. SG90舵机控制:将角度转换为对应的PWM脉冲宽度
  4. 多线程测试:使用pthread创建独立的测试线程

四、定时器基础测试(Test 1-4)

整体流程:程序从初始化线程启动,依次执行 5 组测试,重点突出 SG90 舵机控制流程。
舵机核心路径:
打开 PWM → 创建独立线程 → 0°↔180° 循环扫描 → 角度转脉冲 → PWM 输出
运行规则:
每秒变化 12°,30 秒完成一次来回,共运行 2 个周期(60 秒)后自动停止。
结构特点:线程分离、非阻塞、可安全退出、适合嵌入式实时场景。

                            程序入口
                         hwtimer_demo_init
                                  │
                                  ▼
                     创建分离线程 hwtimer_demo_main
                                  │
                                  ▼
                        开始 5 项功能测试
                  ┌───────┬───────┼───────┬───────┐
                  │       │       │       │       │
            测试1:软件  测试2:高精  测试3:定时  测试4:硬件  测试5:SG90舵机
             定时器模拟   度计时    器精度    定时器     (核心功能)
                  │       │       │       │       │
                  └───────┴───────┴───────┴───────┘
                                          │
                                          ▼
                                    sg90_demo()
                                          │
                  ┌───────────────────────┴───────────────────────┐
                  │                                               │
          PWM 设备初始化                                   设置运行标志 g_running=1
          open("/dev/pwm")                                         │
                  │                                               │
                  └──────────────────────┬────────────────────────┘
                                         ▼
                            创建舵机独立扫描线程
                                 sg90_fast_scan_thread
                                         │
                                         ▼
                ┌─────────────────────────────────────────────┐
                │                循环扫描(30秒/周期)         │
                │                                             │
                │         0° → 180° 每次+12° 延时1秒          │
                │                                             │
                │         180° → 0° 每次-12° 延时1秒          │
                └───────────────────────┬─────────────────────┘
                                         │
                                         ▼
                               sg90_set_angle(角度)
                                         │
                  ┌──────────────────────┼──────────────────────┐
                  │                      │                      │
            角度限幅0~180°    角度→PWM脉冲宽度        PWM配置与输出
                                    │                      │
                          angle_to_pulse_ns()      ioctl 使能PWM

4.1 Test 1:定时器模拟测试

第一个测试使用 gettimeofday() 函数模拟定时器功能,这是理解定时器工作原理的入门示例。

void timer_simulate_demo(void)
{
    struct timeval start, now;
    int elapsed_ms;
    int count = 0;
    
    gettimeofday(&start, NULL);
    
    while (count < 50) {
        usleep(10000);  /* 10ms延时 */
        gettimeofday(&now, NULL);
        elapsed_ms = (now.tv_sec - start.tv_sec) * 1000 + 
                     (now.tv_usec - start.tv_usec) / 1000;
        
        count++;
        if (count % 10 == 0) {
            printf("[SimTimer] Count: %d, Elapsed: %d ms\n", count, elapsed_ms);
        }
    }
}

运行效果

[SimTimer] Count: 10, Elapsed: 100 ms
[SimTimer] Count: 20, Elapsed: 200 ms
[SimTimer] Count: 30, Elapsed: 300 ms
...

这个测试每10ms计数一次,共计数50次(500ms),通过不断查询系统时间来实现类似定时器的效果。虽然精度不如硬件定时器,但能帮助我们理解定时器的基本原理。

4.2 Test 2:高精度时间测试

第二个测试测量一段代码的执行时间,展示了如何获取微秒级的时间精度。

void highres_time_demo(void)
{
    struct timeval start, end;
    long long elapsed_us;
    
    gettimeofday(&start, NULL);
    
    /* 执行100万次空操作 */
    for (int i = 0; i < 1000000; i++) {
        asm volatile("nop");
    }
    
    gettimeofday(&end, NULL);
    
    elapsed_us = (end.tv_sec - start.tv_sec) * 1000000LL +
                 (end.tv_usec - start.tv_usec);
    
    printf("Elapsed time: %lld us\n", elapsed_us);
}

运行效果

Elapsed time: 12345 us

这个测试对于性能分析和代码优化非常有价值。例如,当我们优化一段代码后,可以用这种方法精确测量优化前后的性能提升。

4.3 Test 3:定时器精度测试

第三个测试评估 usleep() 函数的实际延时精度,这对于需要精确时序控制的应用非常重要。

void timer_precision_demo(void)
{
    for (int i = 0; i < 5; i++) {
        gettimeofday(&start, NULL);
        usleep(100000);  /* 请求延时100ms */
        gettimeofday(&end, NULL);
        
        actual_ms = (end.tv_sec - start.tv_sec) * 1000 +
                    (end.tv_usec - start.tv_usec) / 1000;
        
        printf("Request: 100 ms, Actual: %d ms, Error: %d ms\n",
               actual_ms, actual_ms - 100);
    }
}

运行效果

Request: 100 ms, Actual: 100 ms, Error: 0 ms
Request: 100 ms, Actual: 101 ms, Error: 1 ms
Request: 100 ms, Actual: 100 ms, Error: 0 ms
...

测试结果表明,在MC632X上,usleep() 的精度通常在1ms以内,足以满足大多数应用需求。

4.4 Test 4:硬件定时器驱动

第四个测试展示了如何直接操作MC632X的硬件定时器寄存器。这部分代码虽然在本demo中默认注释掉,但提供了硬件定时器的基础框架。

/* 定时器寄存器定义 */
#define TMR_REG_BASE        0x1F006000
#define TIMER_LOAD_COUNT    0x00
#define TIMER_CURRENT_VALUE 0x04
#define TIMER_CTRL_REG      0x08

/* 设置定时器为周期性模式 */
void timer_set_periodic(unsigned int period_ms)
{
    unsigned int load_value = (TIMER_CLOCK / 1000) * period_ms;
    TIMER_REG(TIMER_LOAD_COUNT) = load_value;
    TIMER_REG(TIMER_CTRL_REG) = TIMER_CTRL_MODE;
}

理解硬件定时器的工作原理对于深入掌握嵌入式系统至关重要,但在实际应用中,我们通常使用操作系统提供的定时器服务,这样代码更加简洁和可移植。

五、SG90舵机控制原理

5.1 舵机工作原理

SG90是一款微型伺服电机,通过PWM信号控制转角。其工作原理是:

  • 控制信号:周期20ms的PWM波
  • 脉冲宽度与角度关系
    • 0.5ms 脉冲 → 0度
    • 1.5ms 脉冲 → 90度
    • 2.5ms 脉冲 → 180度

5.2 角度到脉冲宽度的转换

我们将0-180度的角度线性映射到0.5ms-2.5ms的脉冲宽度:

#define SG90_PERIOD_NS      20000000  /* 20ms周期 */
#define SG90_PULSE_MIN_NS   500000    /* 0.5ms - 0度 */
#define SG90_PULSE_MAX_NS   2500000   /* 2.5ms - 180度 */
#define SG90_PULSE_RANGE_NS 2000000   /* 脉冲宽度范围 */

static int angle_to_pulse_ns(int angle)
{
    if (angle < 0) angle = 0;
    if (angle > 180) angle = 180;
    
    /* 线性转换公式 */
    return SG90_PULSE_MIN_NS + (angle * SG90_PULSE_RANGE_NS / 180);
}

5.3 PWM驱动接口

通过 /dev/pwm 设备文件控制PWM输出,这是RT-Thread标准的设备驱动接口:

static int sg90_set_angle(int angle)
{
    struct fh_pwm_chip_data pwm_cfg;
    
    /* 配置PWM参数 */
    pwm_cfg.id = 0;  /* 使用PWM通道0 */
    pwm_cfg.config.period_ns = SG90_PERIOD_NS;
    pwm_cfg.config.duty_ns = angle_to_pulse_ns(angle);
    
    /* 通过ioctl控制PWM */
    ioctl(pwm_fd, DISABLE_PWM, &pwm_cfg);
    ioctl(pwm_fd, SET_PWM_CONFIG, &pwm_cfg);
    ioctl(pwm_fd, ENABLE_PWM, &pwm_cfg);
    
    printf("[SG90] Angle: %3d°\r", angle);
    fflush(stdout);
    
    return 0;
}

六、Test 5:SG90舵机快速扫描测试

这是本demo的重头戏,实现舵机从0度到180度再到0度的周期性扫描,30秒完成一个周期。

6.1 设计思路

  • 周期时间:30秒完成一个完整周期
  • 运动轨迹:0° → 180° → 0°
  • 变化速度:每秒变化12°
  • 运行时长:连续运行2个周期(60秒)

6.2 核心代码实现

void *sg90_fast_scan_thread(void *para)
{
    int step_per_second = 12;  /* 每秒变化12度 */
    
    while (g_running) {
        /* 上升阶段:0° → 180° */
        for (angle = 0; angle <= 180; angle += step_per_second) {
            if (!g_running) break;
            sg90_set_angle(angle);
            sleep(1);
        }
        
        /* 下降阶段:180° → 0° */
        for (angle = 180 - step_per_second; angle >= 0; angle -= step_per_second) {
            if (!g_running) break;
            sg90_set_angle(angle);
            sleep(1);
        }
        
        printf("\n[SG90] Completed one cycle (30s)\n");
    }
    
    return NULL;
}

6.3 运行效果

当执行 bsp_demo -t 命令后,可以看到:

msh />lwIP-2.1.2 initialized!
set Mac address.86:30:20:19:06:11
auto find phy info :: internal phy :  mii
fh_qos_gmac_probe - (IRQ #40 IO base addr: 0x1c600000)
PHY: - Auto Negotiation is ON, Link is Up - 100/Full

msh />
msh />bsp_demo -t
[HWTimer Demo] Initializing...
[HWTimer Demo] This will run 5 tests including SG90 servo
[HWTimer Demo] Started successfully - running 5 tests
msh />
============================================
     HWTimer Practice Demo Start (5 Tests)  
============================================

--- Test 1/5: Timer Simulation ---

========== Timer Simulation Demo ==========
Using gettimeofday() to simulate timer

[SimTimer] Count: 10, Elapsed: 109 ms
[SimTimer] Count: 20, Elapsed: 219 ms
[SimTimer] Count: 30, Elapsed: 329 ms
[SimTimer] Count: 40, Elapsed: 439 ms
[SimTimer] Count: 50, Elapsed: 549 ms

--- Test 2/5: High Resolution Time ---

========== High Resolution Time Demo ==========
Elapsed time: 2224 us

--- Test 3/5: Timer Precision ---

========== Timer Precision Demo ==========
Sleep 100 ms, actual: 101 ms, error: 1 ms
Sleep 100 ms, actual: 101 ms, error: 1 ms
Sleep 100 ms, actual: 101 ms, error: 1 ms
Sleep 100 ms, actual: 101 ms, error: 1 ms
Sleep 100 ms, actual: 101 ms, error: 1 ms

--- Test 4/5: Hardware Timer (commented out) ---

--- Test 5/5: SG90 Servo Fast Demo (30s/cycle) ---

========== SG90 Servo Fast Demo ==========
[SG90] Running for 2 cycles (60 seconds)...

========== SG90 Fast Demo (30s/cycle) ==========
  0° -> 180° -> 0° in 30 seconds
  Speed: 12° per second
==============================================

ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 500000, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 633333, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 766666, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 900000, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 1033333, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 1166666, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 1300000, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 1433333, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 1566666, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 1700000, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 1833333, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 1966666, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 2100000, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 2233333, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 2366666, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 2500000, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 2366666, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 2233333, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 2100000, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 1966666, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 1833333, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 1700000, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 1566666, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 1433333, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 1300000, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 1166666, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 1033333, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 900000, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 766666, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 633333, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 500000, pwm->period: 20000000 ns
[SG90] Angle:   0°
[SG90] Completed one cycle (30s)

ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 500000, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 633333, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 766666, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 900000, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 1033333, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 1166666, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 1300000, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 1433333, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 1566666, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 1700000, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 1833333, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 1966666, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 2100000, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 2233333, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 2366666, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 2500000, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 2366666, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 2233333, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 2100000, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 1966666, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 1833333, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 1700000, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 1566666, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 1433333, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 1300000, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 1166666, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 1033333, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 900000, pwm->period: 20000000 ns
ioctl: SET_PWM_CONFIG, pwm->id: 0, pwm->counter: 766666, pwm->period: 20000000 ns
[SG90] Angle:  24°
[SG90] PWM stopped
[SG90] Demo completed

============================================
     HWTimer Practice Demo Completed (5/5)  
============================================

舵机会平滑地从0度摆动到180度,再返回0度,形成一个完整的扫描周期。

6.4 技术要点

  1. 实时角度显示:使用 \r 回车符实现在同一行更新角度,避免屏幕滚动
  2. 线程安全:通过 g_running 标志控制线程的启停
  3. 精确延时:使用 sleep(1) 实现每秒变化,简单可靠
  4. 边界处理:确保角度在0-180度范围内,保护舵机

七、完整的测试框架

7.1 主测试函数

将所有5个测试整合到一个线程中顺序执行:

void *hwtimer_demo_main(void *para)
{
    printf("\n--- Test 1/5: Timer Simulation ---\n");
    timer_simulate_demo();
    
    printf("\n--- Test 2/5: High Resolution Time ---\n");
    highres_time_demo();
    
    printf("\n--- Test 3/5: Timer Precision ---\n");
    timer_precision_demo();
    
    printf("\n--- Test 4/5: Hardware Timer (commented out) ---\n");
    // periodic_timer_demo();
    
    printf("\n--- Test 5/5: SG90 Servo Fast Demo ---\n");
    sg90_demo();
    
    return NULL;
}

7.2 命令行接口

通过FINSH导出三个命令,方便在终端中调用:


使用方式:

msh /> bsp_demo -t# 运行全部5个测试

八、常见问题与解决方案

8.1 舵机不转动

可能原因

  1. PWM设备打开失败
  2. PWM通道配置错误
  3. 电源供电不足

解决方案

/* 检查PWM设备 */
if (pwm_fd == -1) {
    printf("[SG90] open /dev/pwm failed\n");
    return -1;
}

/* 确认PWM通道正确 */
pwm_cfg.id = 0;  /* 根据实际连接修改 */

8.2 舵机抖动

可能原因

  1. PWM信号不稳定
  2. 电源纹波过大
  3. 角度变化过快

解决方案

  • 在电源两端并联100μF电容
  • 适当增加角度变化的间隔时间
  • 确保PWM频率精确为50Hz

8.3 定时器精度不足

可能原因

  1. 系统负载过高
  2. 中断被其他高优先级任务抢占

解决方案

  • 使用硬件定时器替代软件定时器
  • 提高线程优先级
  • 减少其他任务的干扰

九、总结与展望

通过本文的实践,我们完成了以下内容:

  1. 定时器基础:掌握了软件定时器的基本使用方法
  2. 时间测量:学会了精确测量代码执行时间
  3. PWM控制:熟悉了通过设备文件控制PWM输出的方法
  4. 舵机驱动:实现了SG90舵机的精确角度控制
  5. 系统集成:将所有功能整合成一个完整的测试框架

这个demo不仅展示了MC632X平台的基本外设使用方法,更重要的是提供了一个可扩展的测试框架。基于这个框架,我们可以:

  • 添加更多的舵机(如控制多关节机械臂)
  • 实现更复杂的运动轨迹(如S曲线加减速)
  • 与其他传感器联动(如根据光照调整舵机角度)

希望本文对您在嵌入式开发中有所帮助,欢迎在实际项目中应用和改进这个demo!


附录:完整代码获取
本文的所有代码均已整合在 https://gitee.com/vor2345/mc632-x_hwtimer/tree/master中,可以下载配置直接运行测试。如有问题,欢迎交流讨论。

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐