【富瀚微MC632X 软件定时器控制舵机周期运行】
在嵌入式系统开发中,定时器和舵机控制是两个非常基础但又十分重要的功能。本文将基于富瀚微MC632X平台,通过一个完整的实践demo,详细介绍如何使用软件定时器来控制SG90舵机实现周期性运动。
富瀚微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实时操作系统,主要包含以下几个部分:
- 硬件定时器驱动:直接操作MC632X的定时器寄存器
- PWM驱动:通过
/dev/pwm设备文件控制PWM输出 - SG90舵机控制:将角度转换为对应的PWM脉冲宽度
- 多线程测试:使用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 技术要点
- 实时角度显示:使用
\r回车符实现在同一行更新角度,避免屏幕滚动 - 线程安全:通过
g_running标志控制线程的启停 - 精确延时:使用
sleep(1)实现每秒变化,简单可靠 - 边界处理:确保角度在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 舵机不转动
可能原因:
- PWM设备打开失败
- PWM通道配置错误
- 电源供电不足
解决方案:
/* 检查PWM设备 */
if (pwm_fd == -1) {
printf("[SG90] open /dev/pwm failed\n");
return -1;
}
/* 确认PWM通道正确 */
pwm_cfg.id = 0; /* 根据实际连接修改 */
8.2 舵机抖动
可能原因:
- PWM信号不稳定
- 电源纹波过大
- 角度变化过快
解决方案:
- 在电源两端并联100μF电容
- 适当增加角度变化的间隔时间
- 确保PWM频率精确为50Hz
8.3 定时器精度不足
可能原因:
- 系统负载过高
- 中断被其他高优先级任务抢占
解决方案:
- 使用硬件定时器替代软件定时器
- 提高线程优先级
- 减少其他任务的干扰
九、总结与展望
通过本文的实践,我们完成了以下内容:
- 定时器基础:掌握了软件定时器的基本使用方法
- 时间测量:学会了精确测量代码执行时间
- PWM控制:熟悉了通过设备文件控制PWM输出的方法
- 舵机驱动:实现了SG90舵机的精确角度控制
- 系统集成:将所有功能整合成一个完整的测试框架
这个demo不仅展示了MC632X平台的基本外设使用方法,更重要的是提供了一个可扩展的测试框架。基于这个框架,我们可以:
- 添加更多的舵机(如控制多关节机械臂)
- 实现更复杂的运动轨迹(如S曲线加减速)
- 与其他传感器联动(如根据光照调整舵机角度)
希望本文对您在嵌入式开发中有所帮助,欢迎在实际项目中应用和改进这个demo!
附录:完整代码获取
本文的所有代码均已整合在 https://gitee.com/vor2345/mc632-x_hwtimer/tree/master中,可以下载配置直接运行测试。如有问题,欢迎交流讨论。
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐
所有评论(0)