目录
一、项目准备
1. 工程模板
本篇项目所用模板包含以下模块,声明函数见头文件,模块添加和函数功能详见往期记录。
2. 器件接线
主装置:ST-Link
仿真器,STM32
系统板,MB102
面包板,OLED
显示屏(接线详见往期记录)
除了A15
,B3
,B4
是JLINK
的调试端口,其他端口可随意使用。各类器件默认接线方式如下。
器件 | 端口/电源 |
---|---|
3.3/VCC | + |
GND | - |
LED | A1 | A2 |
Encoder-A | B0 |
Encoder-B | B1 |
OLED-SCL | B8 |
OLED-SDA | B9 |
Buzzer-I/O | B12 |
Sensor-DO | B13 |
二、基础时钟模块
1. 时钟电路
STM32
微控制器的电路基本单元包含:电源电路、复位电路、时钟电路。其中,时钟电路指的是像时钟一样准确运动的振荡电路,它可以产生稳定、精确的时间基准信号,任何工作都按时间顺序进行。
时钟电路的电源称为时钟,它是一个频率精确且稳定的脉冲信号发生器。脉冲信号是按一定电压幅度连续发出的周期性循环信号。在单位时间内产生的脉冲个数称为频率,单位是赫兹(Hz
)。指令的执行,寄存器的写、读、复位,数据从寄存器到硬件上的移位,都是基于时钟来进行同步和处理。
时钟电路保证了STM32
的可编程性和灵活性,使其能够适应各种不同的应用场景,对于STM32
的正常运行至关重要,它的主要作用如下:
- 同步操作:产生时间信号,提供同步基准,确保各个功能模块(如CPU、存储器、输入/输出接口等)按预定的时序协同工作。
- 数据传输:时钟信号使得数据在正确的时间被读取或写入。确保数据的可靠性和一致性。
- 功耗管理:时钟电路可以切换不同频率的时钟,控制整体功耗。
2. 时钟源
STM32
微控制器的时钟系统有多个时钟源,可以根据需要进行选择和配置。其中,独立时钟源有:
- HSI(
High-Speed Internal
):内部高速时钟,频率为8MHz
。 - HSE(
High-Speed External
):外部高速时钟,频率为4MHz~16MHz
。 - LSI(
Low-Speed Internal
):内部低速时钟。频率为40kHz
。 - LSE(
Low-Speed External
):外部低速时钟。频率为32.768kHz
。
内部时钟的时钟信号由芯片内部的RC
振荡器产生,起振较快,但精度不高,通常在芯片刚上电时作为默认时钟源使用;而外部时钟的时钟信号由外部的晶体(石英)谐振器或陶瓷谐振器产生,精度和稳定性更高,通常在芯片上电后通过软件配置使用。
高速时钟功耗较高,一般提供给芯片主体的主时钟;而低速时钟功耗较低,只是提供给芯片中的实时时钟RTC
(Real-Time Clock
)及独立看门狗IWGD
(Independent Watchdog
)使用。
此外,还有系统时钟SYSCLK
,可以驱动它的时钟源有:
- HSI振荡器时钟
- HSE振荡器时钟
- PLL锁相环时钟:以
HSI
或HSE
作为输入时钟源,可以倍频或分频输出时钟信号(频率不超过72MHz
),作为系统时钟的时钟源。
3. 时钟树
STM32
微控制器的各个模块和外设通过一个复杂的时钟树与时钟源相连。复位和时钟控制模块RCC
(Reset and Clock Control
)负责管理这个时钟树,其功能包括:
- 使能或禁止时钟:外围设备的寄存器需要电源(时钟)才能工作。当外设时钟没有启用时,软件不能读出外设寄存器的数值,返回值始终是
0x0
。 - 配置时钟分频:兼容不同速度的设备,降低芯片功耗,实现精细化的功率管理。
- 管理复位系统:当
STM32
发生错误或异常时,如电源故障、系统错误等,复位系统可以将微控制器重置到初始状态,以保证系统的稳定运行。每次芯片复位后,所有外设时钟都被关闭(SRAM
和Flash
接口除外)。
`
4. 总线
使用外设前,必须在RCC_AHBxENR
或RCC_APBxENR
寄存器中使能其时钟,也就是说,RCC
给外设总线配置外设时钟后,才能通过外设总线操作外围设备。
- AHB
(Advanced High-performance Bus)
:高级高性能总线,是STM32
的高速总线,用于连接主要的内部总线和外设,频率通常等于SYSCLK
,但可以通过时钟分频器进行分频。 - APB
(Advanced Peripheral Bus)
:高级外设总线,其中,APB1
是低速总线,用于连接低速外设,频率通常是AHB
的一半;APB2
是高速总线,用于连接高速外设,频率通常等于AHB
,都可以通过时钟分频器进行分频。
三、定时中断
1. 定时器
定时器(Timer
)可以对输入的时钟信号进行计数,并在计数值达到设定值时触发中断,根据复杂度和应用场景分为三种类型:
类型 | 编号 | 总线 | 功能 |
---|---|---|---|
基本定时器 | TIM6、TIM7 | APB1 | 定时中断、主模式触发DAC |
通用定时器 | TIM2、TIM3、TIM4、TIM5 | APB1 | (额外新增)内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式 |
高级定时器 | TIM1、TIM8 | APB2 | (额外新增)重复计数器、死区生成、互补输出、刹车输入 |
复杂定时器具有简单定时器的全部功能,STM32F103C8T6
的定时器资源包括:TIM1
、TIM2
、TIM3
、TIM4
.
2. 时基单元
时基单元是计时器中最基本的计数计时电路,通用计时器的时基单元由三个寄存器组成:预分频器(Prescaler
)、计数器(Counter
)、自动重装载寄存器(Auto-Reload Register
)。
- 预分频器(
PSC
):内部时钟SYSCLK
输入预分频器的时钟频率CK_PSC
一般是主频72MHz
。该寄存器的值PSC
比分频系数少1
,也就是说,向计数器输出的时钟频率CK_CNT = CK_PSC/(PSC+1)
- 计数器(
CNT
):该寄存器输入频率为CK_CNT
的时钟信号后,每检测到一个上边沿,寄存器的值CNT
就加1
- 自动重装载寄存器(
ARR
):在上述过程中,该寄存器不断将目标值ARR
与CNT
比较,一旦CNT = ARR+1
,就产生一个更新事件U
和一个更新中断信号UI
,触发DAC
转换,实现定时中断
例如,PSC
设为7199
,则分频系数为7200
,计数频率为CK_CNT = 72MHz/7200 = 10000Hz
,即每秒输入10000
个脉冲信号,也就是CNT
每秒计10000
次;若ARR
设为9999
,则每秒触发一次定时中断,从而实现读秒计数。
其中,数模转换器(Digital-to-Analog Converter
)用于将数字信号转换为模拟信号,即:将寄存器的数字输入值,模拟输出为波形信号。
在从模式下,DAC
转换的触发需要更新中断信号引发,并按时在中断程序中调用代码手动实现;在主模式下,更新事件可直接映射到触发输出(Trigger Out
)触发DAC
转换,实现硬件自动化——这就是主从触发DAC功能。
此外,计数器具有三种计数模式:
- 向上计数模式:
CNT
初始值为0
,每检测到一个CK_CNT
脉冲CNT
加1
,直至与ARR-1
相等,CNT
恢复初始值,并产生一次上溢事件 - 向下计数模式:
CNT
初始值为ARR
,每检测到一个CK_CNT
脉冲CNT
减1
,直至归零,CNT
恢复初始值,并产生一次下溢事件 - 中心对齐模式:在向上计数模式下,产生上溢事件后,转换为向下计数模式;在向上计数模式下,产生下溢事件后,转换为向上计数模式
3. 滤波器
滤波器用于过滤外部时钟信号中混杂的高频抖动干扰信号,它可以对输入的时钟信号按一定的采样频率f
进行采样,并比较一定数目N
个采样点。若采样点相同,则正常输出;若采样点不同,则保持原输出或输出低电平。
其中,采样频率来自内部时钟的分频信号,通用计时器的可选分频参数ClockDivision
有:一分频(TIM_CKD_DIV1
)、二分频(TIM_CKD_DIV2
)、四分频(TIM_CKD_DIV4
),分别对应72MHz
、36MHz
、18MHz
。
4. 流程示意
因为使用内部时钟RCC
给定时器供能,所以无需配置GPIO
端口,只需开启外设时钟,初始化时基单元和NVIC
,最后启动定时器即可。
四、定时器模块
Timer.c
#include "stm32f10x.h"
void Timer_Init(void)
{
// 开启通用定时器TIM2外设的时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
// 使用内部时钟
TIM_InternalClockConfig(TIM2);
// 配置时基单元参数
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; // 滤波器采样频率:1分频
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; // 计数器模式:向上计数
TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1; // 自动重装载寄存器参数ARR
TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1; // 预分频器寄存器参数PSC
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; // 重复计数器寄存器参数:仅供高级计数器使用
// 初始化时基单元
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
// 重置标志位:初始化参数时会产生更新中断
TIM_ClearFlag(TIM2, TIM_IT_Update);
// 使能更新中断
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
// 配置中断优先级:分组2
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
// 配置NVIC参数
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; // 中断请求通道:TIM2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 中断请求通道状态:使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; // 抢占优先级:2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; // 响应优先级:1
// 初始化嵌套中断向量控制器
NVIC_Init(&NVIC_InitStructure);
// 启动定时器TIM2
TIM_Cmd(TIM2, ENABLE);
}
// TIM2中断请求通道控制
/*void TIM2_IRQHandler(void)
{
// 检查TIM2中断线请求状态寄存器的标志位
if(TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) // 更新中断触发
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update); // 重置标志位
}
}*/
Timer.h
#ifndef __TIMER_H
#define __TIMER_H
void Timer_Init(void);
#endif
五、读秒计数
#include "stm32f10x.h" // 器件模块
#include "OLED.h" // OLED模块
#include "Timer.h" // 定时器模块
uint16_t Num; // 数值
int main(void)
{
// 初始化
OLED_Init();
Timer_Init();
// 显示计数
OLED_ShowString(1, 1, "Num:");
while(1)
{
OLED_ShowNum(1, 5, Num, 5);
}
}
// TIM2中断请求通道控制
void TIM2_IRQHandler(void)
{
// 检查TIM2中断线请求状态寄存器的标志位
if(TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) // 更新中断触发
{
Num ++; // 计数
TIM_ClearITPendingBit(TIM2, TIM_IT_Update); // 重置标志位
}
}