目录
【1】STM32的时钟系统
1.时钟基本概念
时钟源:
2.G030时钟源
3.时钟树
4.STM32CubeMX时钟树配置
【2】Systick定时器
1. 概念:
工作原理
时钟基准
【3】HAL_Delay函数分析
【4】定时器
基本概念
定时器分类
定时器组成
1.计数器
2.自动重装寄存器
3.预分频器
定时器计数原理
实验
2.PWM
定义
参数
工作原理
应用
练习:通过PWM信号调节LED灯亮度
练习:实现呼吸灯效果
3.蜂鸣器
简介:
分类:
蜂鸣器发声实验
【5】ADC
定义:
2.ADC简介
3.ADC特性
4.ADC时钟
5.工作模式
6.单通道单次转换实验
练习:实现光控灯。
1) 时钟是嵌入式系统的脉搏,处理器内核在时钟驱动下完成指令执行,状态变换等动作,
外设部件在时钟的驱动下完成各种工作,例如:串口数据的发送、AD转换、定时器计数等
因此时钟对于计算机系统是至关重要的,通常时钟系统出现问题也是致命的,比如振荡器不起振、振荡不稳、停振等。 时钟信号推动单片机内各个部分执行相应的指令,时钟就像人的心跳一样。
2)时钟系统的组成:时钟源(振荡源)、唤醒定时器、倍频器、分频器
Tips:晶体振荡器和RC振荡器的区别?
晶体振荡器
采用石英晶体设计的振荡器
优点:晶体振荡器信号稳定、质量好,连接方式简单。
缺点:价格高,需要较长的启动时间(起振时间)
晶体振荡器分类:
无源晶振是有2个引脚的无极性元件,需要借助于时钟电路才能产生振荡信号,自身无法振荡起来。
有源晶振有4只引脚,是一个完整的振荡器,其中除了石英晶体外,还有晶体管和阻容元件,因此体积较大。有源晶振不需要CPU的内部振荡器,信号稳定,质量较好,而且连接方式比较简单。
RC振荡器
由电阻和电容设计的振荡电路,能够将直流电转换成具有一定频率的交流信号输出。
优点:实现的成本比较低,仅由电阻电容构成。
缺点:精度存在问题,振荡频率会存在误差。
倍频器:CPU需要更高频率,晶体振荡器制作成本较高(而且自身不稳定),不易直接生产高频振荡器,可以利用倍频器对现有时钟频率进行放大。
分频器:外设需要不同频率,为了降低功耗,可以进行分频以提供不同频率时钟信号。
Tips:为什么要设计倍频器和分频器?
答:1)为了降低成本(CPU需要更高的时钟频率)
2)减少功耗 (外设需要不同的时钟频率)
HSI : 高速内部时钟,由RC振荡电路产生16Mhz的时钟频率。
HSE :高速外部时钟,由外部晶体/陶瓷谐振器产生4-48Mhz的时钟信号,一般外接8Mhz晶振。
LSI :低速内部时钟,由低速RC振荡电路产生32Khz的时钟频率。
LSE:低速外部时钟,由低速晶振产生32.768Khz的时钟频率。
RTC实时时钟是一个独立的定时器。STM32 的 RTC 模块拥有一组连续计数的计数器,在相应软件配置下,可提供时钟日历的功能。只要电源电压保持在工作范围内,RTC永远不会停止。
Hclk为高性能总线(AHB bus peripherals )供给时钟信号(AHB为advanced high-performance bus) ; 由系统时钟SYSCLK分频得到,一般不分频,等于系统时钟,HCLK是高速外设时钟,是给外部设备的,比如内存,flash。
Pclk为低速总线外设总线(APB busperipherals)供给时钟信号
PLL为锁相环倍频输出, 如果希望有一个比较大的时钟频率,可选择 PLLCLK 作为系统时钟。其时钟输入源可选择为HSI,HSE、倍频可选择为8~86倍,但是其输出频率最大不超过64MHz
SysTick又称滴答定时器。是一个定时设备,位于Cortex-M0内核中,和NVIC捆绑,产生SysTick异常(IRQ异常号15)可以对输入的时钟进行计数,系统定时器一般用于操作系统,用于产生时基,维持操作系统的心跳。
相当于CPU,STM32相当于包含CPU的微型机
工作原理:
滴答定时器是一个24位递减定时器,也就是最多能计数2^24(0xFFFFFF)。
SysTick设定初值并使能后,每来一个时钟信号,计数值就减1。
计数到0时,触发中断,SysTick计数器自动重装初值并继续减一,循环不断。
系统默认开启滴答定时器。
SysTick位于内核中,属于内部中断即异常。
探究:systick多久触发一次异常?
首先看Systick每次触发异常在异常处理程序中做了哪些工作
每次systick触发中断后会让uwTick自加1
这个uwTick的值就作为我们系统的时钟基准。
然后再来研究一下,uwTick多久会被加1.
在main.c中main函数首个执行的函数HAL_Init(),会对systick进行初始化。
在HAL_Init中通过调用HAL_InitTick函数,对Tick进行初始化
TICK_INT_PRIORITY = 0 作为systick的中断优先级
调用HAL_SYSTICK_Config函数用于配置systick时基
SystemCoreClock / (1000U /(uint32_t)uwTickFreq)
uint32_t SystemCoreClock = 16000000UL;
16000000 / 1000 / 1 = 16000
将16000作为参数传递给了HAL_SYSTICK_Config函数
Systic_Config函数对Systick的重装值、初值、优先级和校准值都进行了配置。
if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk) //如果systick初值大于最大值0xFFFFFF
{
return (1UL); //返回1 说明配置失败
}
SysTick->LOAD = (uint32_t)(ticks - 1UL); /* 重载的计数值 */ 16000 - 1
NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL); /* set Priority for Systick Interrupt */
SysTick->VAL = 0UL; /* Load the SysTick Counter Value */
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk;
return (0UL);
先来看一下每次重装的值为多少
SysTick->LOAD = (uint32_t)(ticks - 1UL); //即16000 - 1
从16000 - 1开始递减到0,总共需要计数16000次
当前时钟频率16Mhz,说明计一个数用时1/16M s
计16000个数需要用时 16000 * 1/16000000 s = 1/1000 s = 1ms
因此Systick每隔1ms会触发一次异常。
【练习】利用Systick异常,实现1秒打印“helloworld”
每次Systick触发异常时让flag自加1,
在主函数中判断,当flag加到1000时,说明systick异常触发了1000次,此时刚好用时1s,
打印“helloworld”,
__weak void HAL_Delay(uint32_t Delay)
{
uint32_t tickstart = HAL_GetTick(); //获取当前时间
uint32_t wait = Delay; //获取用户要延时的时间
/* Add a freq to guarantee minimum wait */
if (wait < HAL_MAX_DELAY)
{
wait += (uint32_t)(uwTickFreq);
}
while ((HAL_GetTick() - tickstart) < wait)
{ //如果当前系统时间 - 初始时间 < 延时时间 则继续循环
//当达到延时条件时,循环结束。
}
}
GetTick函数返回当前系统uwTick的值
uwTick每隔1ms会自加1
因此HAL_Delay的实现依靠Systick,以达到毫秒为单位的延时效果。
定时器本质上是一个计数器,可对输入的时钟进行计数,并在计数值达到设定值时触发中断,当这个计数器的输入是一个准确可靠的基准时钟时,对基准时钟计数的过程就是计时的过程。
定时器的基本结构是通用的,很多模块电路都能用到,所以STM32定时上扩展了非常多的功能,根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型。
向上计数模式:计数器从0开始计数,当达到自动装载寄存器(TIMx_ARR)里的值时,自动清零且产生一个溢出事件,然后再从0开始向上计数。
向下计数模式:计数器从自动装载寄存器(TIMx_ARR)里的值开始递减计数,当计数值达到0时产生一个定时器溢出事件,并重装初值,继续向下计数。
中央对齐模式:又称为向上/向下计数,计数器从0开始递增达到ARR的值,产生一个定时器溢出事件,再从ARR的值递减到0,产生一个定时器溢出事件。
时钟频率配置成了48Mhz,如何让定时器产生1s中断?
分频值写0相当于不分频 48/1 => 48
先对主频进行48分频得到1Mhz的频率,则分频值为48-1
48Mhz / 48 => 1Mhz
1Mhz的时钟频率,相当于计一个数需要1/1000000秒,
所以如果想得到1s中断,则需要从0开始计数到1000000-1,即计1000000个数需要1秒。
1000000个数 * 1/1000000s = 1s
频率是时间的倒数 1Mhz = 1/1000000s
利用定时器中断实现1s打印一个“hellowolrd”
在main.c中重写定时器溢出中断回调函数
PWM(Pulse Width Modulation)简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。
周期
高低电平变化所需要的时间,单位:ms
T=1/f T是周期,f是频率。
频率
在1秒钟内,信号从高电平到低电平再回到高电平的次数,也就是说一秒钟PWM有多少个周期,单位Hz。
例如:如果频率为50Hz ,也就是说一个周期是20ms,那么一秒钟就有50次PWM周期。
占空比
在一个脉冲周期内,高电平的时间占整个周期时间的比例,单位是% (0%-100%)。
计数器寄存器 (TIMx_CNT)
自动装载寄存器 (TIMx_ARR)
捕获/比较寄存器(TIMx_CCRx)
向上计数模式:
输出过程:
当0-t1这段时间,计数器寄存器的CNT的值是小于CCR,输出高电平。
当t1-t2这段时间,计数器寄存器的CNT的值是大于CCR且小于ARR的,输出低电平。
当CNT的值达到ARR里的值时,产生溢出事件,自动清零再次从0开始向上计数。
LED 照明调光、电机转速控制、舵机转向控制、蜂鸣器控制等。
频率 : 1/T = 1/1ms = 1/0.001s = 1000HZ
周期 : 1ms
占空比 : 50%
在TIM找到PWM信号生成函数:
在main.c中调用
蜂鸣器是采用直流电压供电的一个电子讯响器。
有源蜂鸣器
内部带有震荡源,一通电就可以震荡发出响声,驱动较容易。
因为是内部集成好的震荡电路,所以频率是固定的。
无源蜂鸣器
内部没有震荡源,直流电无法驱动,所以用一个方波信号来进行驱动,
价格便宜,且频率可控。需要通过编程控制声调和响度,驱动稍麻烦。
蜂鸣器模块电路图
驱动蜂鸣器发声
方法一:让单片机通过D1口交替输出高低电平
方法二:利用定时器输出PWM信号控制蜂鸣器
思考:声调和响度分别由什么决定?
ARR -> 周期 -> 1/频率
周期越大,频率越小,声调越低。
周期越小,频率越大,声调越高。
CCR -> 脉冲 - 占空比
占空比越高,通电时间越长,响度越大。
占空比越低,通电时间越短,响度越小。
模拟数字转换器即A/D转换器,或简称ADC,通常是指一个将模拟信号转变为数字信号的电子元件。
模拟信号:电压、温度、光照、压力....(传感器可以将非电学量转换成电学量)
最直观的体现,模拟信号是连续变化的曲线,而数字量是不连续的一个个离散的点。
12 位 ADC 是一种逐次逼近型模拟数字转换器。
它有多达 19 个通道,可测量 16 个外部通道(从外部GPIO口连接的16通道模拟输入)
3个内部信号源,分别为内部温度传感 (VSENSE) 输入、内部参考电压 (VREFINT) 输入、外部电池 VBAT 供电引脚输入
各通道的 A/D 转换可以单次、连续、扫描或间断模式执行。
ADC的结果可以左对齐或右对齐方式存储在 16 位数据寄存器中。
量程:能测量的电压范围 0 ~ 3.6V
分辨率:ADC的分辨率通常以输出二进制数的位数表示,位数越多,分辨率越高,一般来说分辨率越高,转化时间越长。
可配置的转换精度:6位,8位,10位,12位
转化时间:模拟输入电压在允许的最大变化范围内,从转换开始到获得稳定的数字量输出所需要的时间称为转换时间
SYSCLK 系统时钟
HSI 16Mhz 高速内部时钟
PLL 锁相环倍频器
EOC:通道转换结束信号
EOS:序列转换结束信号
单次转换模式:ADC只执行一次转换;
连续转换模式:转换结束之后马上开始新的转换;
Tips:
ADC的单次模式和连续模式。这两中模式的概念是相对应的。这里的单次模式并不是指一个通道。假如你同时开了ch0,ch1,ch4,ch5这四个通道。单次模式转换模式下会把这四个通道采集一遍就停止了。而连续模式就是这四个通道转换完以后再循环过来再从ch0开始。
扫描模式:ADC扫描选中的所有通道,在每个组的每个通道上执行单次转换。在每个转换结束时,这一组的下一个通道被自动转换。如果设置了CONT位(开启了连续转换模式),转换不会在选择组的最后一个通道上停止,而是再次从选择组的第一个通道继续转换。
间断模式:触发一次,转换一个通道,在触发,在转换。在所选转换通道循环,由触发信号启动新一轮的转换,直到转换完成为止。
ADC单通道:
单次转换:只进行一次ADC转换:配置为“单次转换模式”,扫描模式关闭。ADC通道转换一次后,就停止转换。等待再次使能后才会重新转换
连续转换:进行连续ADC转换:配置为“连续转换模式”,扫描模式关闭。ADC通道转换一次后,接着进行下一次转换,不断连续。
ADC多通道:
单次转换: 只进行一次ADC转换:配置为“单次转换模式”,扫描模式使能。ADC的多个通道,按照配置的顺序依次转换一次后,就停止转换。等待再次使能后才会重新转换
连续转换:进行连续ADC转换:配置为“连续转换模式”,扫描模式使能。ADC的多个通道,按照配置的顺序依次转换一次后,接着进行下一次转换,不断连续。
采集光照/火焰/可燃气传感器数值
打开ADC对应通道
使能串口
编程实现
1.启动ADC
HAL_StatusTypeDef HAL_ADC_Start (ADC_HandleTypeDef * hadc)
功能:启动ADC开始转换
参数:ADC_HandleTypeDef * hadc 句柄
返回值: 状态
2.等待转换结束
HAL_StatusTypeDef HAL_ADC_PollForConversion (ADC_HandleTypeDef * hadc,
uint32_t Timeout)
功能:等待转换完成
参数: ADC_HandleTypeDef * hadc 句柄
uint32_t Timeout 超时时间
返回值:转换状态
3.获取转换结果
uint32_t HAL_ADC_GetValue (ADC_HandleTypeDef * hadc)
功能:获取转换结果
参数:ADC_HandleTypeDef * hadc 句柄
返回值:转换结果
4.停止ADC
HAL_StatusTypeDef HAL_ADC_Stop (ADC_HandleTypeDef * hadc)
功能:停止ADC
参数:ADC_HandleTypeDef * hadc 句柄
返回值: 状态
5.串口输出转换结果
重定向printf到串口,再通过printf输出转换结果
当光照强度高时,三盏LED灯灭,当光照强度低时,三盏LED亮。
#include "main.h"
#include "adc.h"
#include "gpio.h"
uint32_t buf=0;//将获取的值赋给buf
void SystemClock_Config(void);
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
SystemClock_Config();
MX_GPIO_Init();
MX_ADC1_Init();
while (1)
{
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1,100);
buf=HAL_ADC_GetValue(&hadc1);
if(buf>3000)//光照强度越低值越高
{
//灯亮
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2,GPIO_PIN_RESET);
}
else
{
//灯灭
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2,GPIO_PIN_SET);
}
HAL_ADC_Stop(&hadc1);
}
}