在嵌入式开发中,时钟和定时器是最基础也最重要的外设模块。SWM320作为一款Cortex-M内核的微控制器,其时钟系统和定时器功能设计遵循ARM架构标准,同时加入了华芯微特特有的优化设计。
SWM320的时钟树主要包含以下几个关键部分:
在实际应用中,我们通过SystemInit()函数完成时钟树的初始化配置。这个函数会根据芯片型号和开发板设计,选择最优的时钟源和分频系数。例如在官方例程中,SystemCoreClock被定义为20MHz,这意味着CPU和外设的运行频率为20MHz。
提示:SystemCoreClock是一个全局变量,存储了当前系统时钟频率(Hz)。所有定时器的配置计算都需要基于这个值。
SWM320提供了多种定时器资源,每种都有其特定用途:
| 定时器类型 | 位数 | 主要用途 | 中断能力 | 备注 |
|---|---|---|---|---|
| SysTick | 24位 | 系统节拍定时 | 是 | Cortex-M内核标配 |
| 基本定时器 | 16位 | 简单定时 | 是 | TIMR0/TIMR1等 |
| 通用定时器 | 16/32位 | PWM/输入捕获等 | 是 | 功能最丰富 |
| 看门狗 | 12位 | 系统复位 | 否 | 独立时钟源 |
其中SysTick是一个特殊的定时器,它由Cortex-M内核直接提供,主要用途包括:
与通用定时器相比,SysTick具有更高的优先级(通常为-1),这使得它的定时更加精确可靠。
SysTick的配置核心是计算重装载值(LOAD)。这个值决定了定时器的中断频率。计算公式为:
code复制重装载值 = SystemCoreClock / 期望中断频率 - 1
例如要实现0.25秒(4Hz)的中断:
但在实际代码中,我们更常使用分频写法:
c复制SysTick_Config(SystemCoreClock / 4); // 20MHz/4 = 5MHz, 周期0.25s
这种写法更直观,计算结果与上面一致(20,000,000 / 4 = 5,000,000,实际LOAD寄存器值为5,000,000-1)。
在嵌入式系统中,毫秒级定时是最常用的功能。实现1ms定时的计算过程如下:
因此代码实现为:
c复制SysTick_Config(SystemCoreClock / 1000); // 1ms定时
在中断服务函数中,我们可以维护一个全局计数器:
c复制volatile uint32_t vSysTick = 0;
void SysTick_Handler(void) {
vSysTick++;
}
uint32_t get_sysytick(void) {
return vSysTick;
}
这样,vSysTick就成为了系统的"心跳"计数器,每个计数值代表1ms的时间流逝。
基于SysTick的毫秒计数器,我们可以实现非阻塞的精准延时函数:
c复制void delay_ms(uint32_t ms) {
uint32_t start = get_sysytick();
while((get_sysytick() - start) < ms) {
// 空循环等待
}
}
这个函数的原理是:
注意事项:由于vSysTick是32位变量,约49天后会溢出。在编写长时间运行的延时逻辑时,需要考虑溢出情况,使用差值比较法(如上述代码)可以自动处理溢出。
在裸机编程中,直接使用delay_ms()会导致CPU在延时期间被完全占用,无法执行其他任务。例如:
c复制while(1) {
task1();
delay_ms(1000); // 这1秒内CPU什么也做不了
task2();
}
这种阻塞式编程严重降低了CPU利用率,在复杂的应用中是不可接受的。
状态机(State Machine)是一种将程序分解为多个状态的设计模式,每个状态处理特定的逻辑,并通过事件触发状态转移。基于SysTick的时间基准,我们可以实现非阻塞的状态机:
c复制typedef enum {
STATE_IDLE,
STATE_TASK1,
STATE_TASK2,
STATE_DELAY
} SystemState;
SystemState currentState = STATE_IDLE;
uint32_t stateEnterTime;
void system_task(void) {
uint32_t currentTime = get_sysytick();
switch(currentState) {
case STATE_IDLE:
// 初始化工作
stateEnterTime = currentTime;
currentState = STATE_TASK1;
break;
case STATE_TASK1:
// 执行任务1
stateEnterTime = currentTime;
currentState = STATE_DELAY;
break;
case STATE_DELAY:
if(currentTime - stateEnterTime >= 1000) {
currentState = STATE_TASK2;
}
break;
case STATE_TASK2:
// 执行任务2
stateEnterTime = currentTime;
currentState = STATE_TASK1; // 循环执行
break;
}
}
这种设计使得CPU在等待延时期间可以处理其他任务,大大提高了系统效率。
示例优化后的水泵控制状态机:
c复制#define PUMP_ON_TIME (20 * 60 * 1000) // 20分钟
#define PUMP_OFF_TIME (2 * 60 * 1000) // 2分钟
typedef enum {
PUMP_STATE_INIT,
PUMP_STATE_ON,
PUMP_STATE_OFF
} PumpState;
PumpState pumpState = PUMP_STATE_INIT;
uint32_t pumpStateTime;
void pump_control(void) {
uint32_t now = get_sysytick();
switch(pumpState) {
case PUMP_STATE_INIT:
pumpStateTime = now;
pumpState = PUMP_STATE_ON;
printf("Pump INIT -> ON\n");
break;
case PUMP_STATE_ON:
if(now - pumpStateTime >= PUMP_ON_TIME) {
pumpStateTime = now;
pumpState = PUMP_STATE_OFF;
printf("Pump ON -> OFF\n");
}
break;
case PUMP_STATE_OFF:
if(now - pumpStateTime >= PUMP_OFF_TIME) {
pumpStateTime = now;
pumpState = PUMP_STATE_ON;
printf("Pump OFF -> ON\n");
}
break;
}
}
SWM320的通用定时器(如TIMR0)提供了更灵活的功能。基本配置步骤如下:
c复制// 定时器初始化
TIMR_Init(TIMR0, // 定时器实例
TIMR_MODE_TIMER, // 定时器模式
SystemCoreClock, // 时钟源频率
1); // 分频系数
// 启动定时器
TIMR_Start(TIMR0);
这个配置将TIMR0设置为:
如果要实现1秒定时,还需要设置自动重装载值(ARR):
code复制定时周期 = (ARR + 1) / 定时器时钟频率
1秒 = (ARR + 1) / 20,000,000
ARR = 19,999,999
通用定时器的中断服务函数需要手动清除中断标志:
c复制void TIMR0_Handler(void) {
// 必须清除中断标志
TIMR_INTClr(TIMR0);
// 用户代码
GPIO_InvBit(GPIOB, PIN12);
}
重要提示:忘记清除中断标志会导致定时器不断触发中断,使系统瘫痪。这是新手常见的错误。
SWM320的定时器支持多种工作模式:
| 模式 | 说明 | 典型应用 |
|---|---|---|
| TIMER | 基本定时 | 延时、周期性任务 |
| PWM | 脉冲宽度调制 | 电机控制、LED调光 |
| INPUT_CAPTURE | 输入捕获 | 脉冲宽度测量 |
| OUTPUT_COMPARE | 输出比较 | 精确时间触发 |
选择模式时需要考虑:
在复杂系统中,建议按照以下原则分配定时资源:
定时器不工作:
定时不准:
中断不触发:
在实际项目中,我通常会添加调试代码来监控定时器状态:
c复制void TIMR0_Handler(void) {
static uint32_t lastTime = 0;
uint32_t currentTime = get_sysytick();
TIMR_INTClr(TIMR0);
printf("TIMR0 Int, Interval: %dms\n", currentTime - lastTime);
lastTime = currentTime;
// 实际处理代码...
}
这种设计可以帮助快速发现定时器是否按预期工作,以及测量实际的中断间隔。