STM32F407 学习 (0) 各种外设功能 (上)
本文对正点原子STM32F4探索者的基本功能及外设作最基本的介绍,随笔者本人的学习进程(基本按照正点原子)而不定时更新,起到总结的作用。
一、HAL库编写程序的运行逻辑
HAL库函数(如stm32f4xx_hal_dma.c/.h):提供最基本的初始化函数和一些基本寄存器操作。
库函数(如led.c.h、dma.c.h):在HAL库之上,需要由用户编写的库函数进行时钟使能和端口配置。大部分情况下,中断函数也要写到用户的库函数里。
在main.c函数里:while(1)之外主要写一些Init()函数(如HAL_Init、LED_Init等)启用一些外设的功能,while(1)之内主要实现对外部相应的及时控制(如串口输入了一个字符串、外部有按键输入、触摸屏输入等)
二、LED灯
位于PF9和PF10两个端口。
1.led_init()
;
使能
2.void HAL_GPIO_WritePin (GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin,GPIO_PinState PinState);
用于设置引脚输出高电平或者低电平
3.void HAL_GPIO_TogglePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin);
用于设置引脚的电平翻转
如何使用:把led.c放入路径中,在main函数里,while前声明led_init()。
用途:可以来判断系统是否正常运行。
三、蜂鸣器
位于PF8
1.beep_init();
使能:BEEP(0)停止蜂鸣/BEEP(1)开始蜂鸣
2.BEEP_TOGGLE()
;
状态反转
如何使用:把beep.c放入路径中,在main函数里,while前声明beep_init()。
用途:没啥用。
四、按键输入
KEY0:PE4
KEY1:PE3
KEY2:PE2
KEY_UP:PA0
1.key_init();
按键初始化
2.key_scan();
按键扫描函数
如何使用:把key.c放入路径中,在main函数里,while前声明key_init()。
在while(1)中使用key=key_scan(0)
用途:可以当作四个外部信号输入。比如检测到某按键按下之后,执行特定的功能。
五、外部中断
NVIC:暂略
EXTI:外部中断和事件控制器
EXTI 的两大部分功能,产生中断与产生事件
1.产生中断
最终信号是流入 NVIC 控制器中。输入线是线路的信息输入端,它可以通过配置寄存器设置为任何一个 GPIO 口,或者是一些外设的事件。输入线一般都是存在电平变化的信号。(如按键按下,串口输入等等)。
2.产生事件
产生中断线路目的使把输入信号输入到 NVIC,进一步运行中断服务函数,实现功能。
而产生事件线路目的是传输一个脉冲信号给其他外设使用,属于硬件级功能。
每个中断线对应了最多 7 个 IO 口,以线 0 为例:它对应了 GPIOA.0、GPIOB.0---GPIOF.0 和 GPIOG.0(如KEY0对应PE4,那么应设置线4的中断)。
HAL 库的 EXTI 外部中断的设置功能整合到 HAL_GPIO_Init 函数里面,而不是单独独立一个文件。
3.中断的使用:(exti.c)
1)exitx_init()外部中断初始化
(1).端口使能:
与正常使能有区别的地方在于
gpio_init_struct.Mode = GPIO_MODE_IT_FALLING;
GPIO_MODE_IT_RISING:上升沿触发
GPIO_MODE_IT_FALLING:下降沿触发
GPIO_MODE_IT_RISING_FALLING:上升沿和下降沿都能触发
(2).使能之后,还要配置NIVIC优先级和使能中断线
void HAL_NVIC_SetPriority(IRQn_Type IRQn, uint32_t PreemptPriority,uint32_t SubPriority);
设置中断的抢占优先级和响应优先级(子优先级)。
形参 1 是中断号,可以选择范围:IRQn_Type 定义的枚举类型,定义在stm32f407xx.h。与exti有关的IRQN有:
EXTI0_IRQn、EXTI1_IRQn、EXTI2_IRQn、EXTI3_IRQn、EXTI4_IRQn、EXTI9_5_IRQn和EXTI15_10_IRQn。
其中,5-9共用一个,10-15共用一个。
形参 2 是抢占优先级,可以选择范围:0 到 15。抢占>响应
形参 3 是响应优先级,可以选择范围:0 到 15。
void HAL_NVIC_EnableIRQ(IRQn_Type IRQn);
使能中断线,形参同上
void HAL_NVIC_DisableIRQ(IRQn_Type IRQn);
用于中断除能。例程中没有用到,还是写在这里
2)写在初始化函数之外
(1).
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
包含中断标志位清除和调用回调函数
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin)
中断标志位清除
(2).void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
中断服务程序中需要做的事情,在HAL库中所有的外部中断服务函数都会调用此函数。内部可以使用switch来控制对不同的中断作出反应。
3)使用方法
把exti.c放入路径中,在main函数里,while前声明extix_init()。
六、串口通信(USART1)
串口USART1:
发送引脚TX:PA9;
接收引脚RX:PA10;
正点原子将PA9和PA10通过USB转串口芯片CH340C实现与电脑通讯。
使用方法
把usart.c放入路径中,在main函数里,while前声明usart_init(115200)。使用正点原子提供的串口通信助手就可以进行通信。
其他的通信方式放到之后再介绍,目前重点是如何对接收到的数据进行处理。
usart.c:
1.printf()
向上位机发送字符串
2.g_usart_rx_buf
从上位机接收到的消息,是一个数组
七、独立看门狗(IWDG)
本质上是一个定时器,并且有一个输出端,可以产生复位信号。在计数器没减到0之前重置计数器的值,就不会产生复位信号,即“喂狗”。
为了防止程序受到不可控因素干扰,维护系统的稳定性。
复位信号:相当于重启系统(复位信号触发会造成什么?)
驱动代码:stm32f4xx_hal_iwdg.c
驱动源码:wdg.c/.h
看门狗溢出时间:
Tout=((4×2^prer) ×rlr) /32
prer 为看门狗时钟预分频值,范围为 0~7。
rlr 为看门狗的重装载值。
比如我们设定 prer 值为 4(4 代表的是 64 分频,HAL 库中可以使用宏定义标识符IWDG_PRESCALER_64),rlr 值为 500,那么就可以得到 Tout=64×500/32=1000ms,这样,看门狗的溢出时间就是 1s,只要你在一秒钟之内,有一次写入 0xAAAA 到 IWDG_KR,就不会导致看门狗复位(当然写入多次也是可以的)。
iwdg_init(prer,rlr)
看门狗初始化
iwdg_feed()
喂狗函数
使用方法:把wdg.c放入路径中。在主函数里,while前声明iwdg.init(),并设置喂狗函数触发条件。
窗口门狗(WWDG)
通常被用来检测由外部干扰或者不可预见的逻辑条件造成的应用程序背离正常运行序列而产生的软件故障。
驱动代码:stm32f4xx_hal_wwdg.c
驱动源码:wdg.c/.h
窗口门狗溢出时间:TWWDG = TPCLK1×4096×2^WDGTB×(T[5:0] + 1)
上限时间和下限时间:
调用 wwdg_init(0X7F,0X5F,WWDG_PRESCALER_8)这个语句,就设置计数器值为 0x7F,窗口寄存器为 0x5F,分频数为 8,然后可由前面的公式得到窗口上限时间 Twwdg=4096×8×(0x7F-0x5F)/42MHz=24.98ms,窗口下限时间 Twwdg=4096×8×(0x7F-0x3F)/42MHz=49.97ms,即喂狗的窗口区间为 24.98~49.97ms。
void wwdg_init(uint8_t tr, uint8_t wr, uint32_t fprer)
tr:计数器值 wr:窗口值 fprer:分频系数
使用方法:把wdg.c放入路径中。在主函数里,while前声明wwdg.init(),设置窗口上限(下限无法设置!),并设置喂狗函数触发后要执行的任务。
八、基本定时器
STM32F407有两个基本定时器TIM6和TIM7,功能完全相同,可以同时使用。
触发DAC的同步电路,以及生成中断/DMA请求
定时器属于STM32F407的内部资源,只要软件设置好即可正常工作。
驱动代码:stm32f4xx_hal_tim.c和stm32f4xx_hal_ex.c
驱动源码:btim.c/.h
void btim_timx_int_init(uint16_t arr, uint16_t psc)
定时器时间周期:Tout= ((arr+1)(psc+1))/Tclk
btim_timx_int_init(5000 - 1, 8400 - 1):
Tout = ((4999+1)(8399+1))/84000000 = 0.5s = 500ms
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
设置中断优先级,使能中断
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
回调函数,在里面设置要执行的任务
如何使用:把btim.c添加到路径,然后再主函数里,while前使用btim_timx_int_init(),适当选择arr和psc设置定时器时间周期,在回调函数中设置要执行的任务。
是先执行完中断再计时,还是执行中断的同时进行计时?
九、通用定时器
10个:TIM2-TIM5,TIM9-TIM14,彼此独立
利用定时器进行输入捕获:测量输入信号的脉冲宽度、测量PWM输入信号的频率和占空比等。(很有用!)
基本定时器只能是递增计数模式,通用定时器可以递增计数模式、递减计数模式和中心对齐模式
驱动源码:gtim.c/.h
(一)、通用定时器中断
定时器的计数器值是可以读取的
void gtim_timx_int_init(uint16_t arr, uint16_t psc)
通用定时器定时中断初始化
void GTIM_TIMX_INT_IRQHandler(void)
定时器中断服务函数
(二)、通用定时器PWM输出
脉冲宽度调制(PWM),是英文“Pulse Width Modulation”的缩写,简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。
在这个循环中,改变 CCRx 的值,就可以改变 PWM 的占空比,改变 ARR 的值,就可以改变 PWM 的频率
STM32F407的定时器除了TIM6和TIM7,其他的定时器都可以用来产生PWM输出,我们使用TIM3的CH2产生一路PWM输出为例
TIM14通道1:PF9复用(LED0)
HAL 库为定时器的针对 PWM 输出定义了单独的 MSP 回调函数HAL_TIM_PWM_MspInit
void gtim_timx_pwm_chy_init(uint16_t arr,uint16_t psc)
初始化函数,频率从这里调整
对于gtim_timx_pwm_chy_init(500 - 1, 84 - 1);:
84M/84=1M 的计数频率,自动重装载为 500,那么 PWM 频率为 1M/500=2kHz
__HAL_TIM_SET_COMPARE(&g_timx_pwm_chy_handle, GTIM_TIMX_PWM_CHY,ledrpwmval);
修改比较值ledrpwmval控制占空比,实际上修改的是寄存器的CCR1
(三)、通用定时器输入捕获
输入捕获模式可以用来测量脉冲宽度或者频率
使用TIM5_CH1做输入捕获,捕获PA0上的高电平脉宽
计数器计数的个数计算方法为:N(ARR+1)+ CCRx2
N为溢出次数,CCRx2 表示 t2 时间点,捕获/比较寄存器的值
TIM5_CH1:PA0复用
void gtim_timx_cap_chy_init(uint16_t arr, uint16_t psc)
初始化
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
定时器输入捕获中断处理回调函数
uint8_t g_timxchy_cap_sta = 0; / 输入捕获状态 / 可以看做一个寄存器
uint16_t g_timxchy_cap_val = 0; / 输入捕获值 /
最大捕获时间为63定时器溢出时间
对于gtim_timx_cap_chy_init(0XFFFF, 84 - 1);:
这两个形参分别设置自动重载寄存器的值为 65535,以及预分频器寄存器的值为 83。定时器 5 是 32 位的计数器,为了通用性,我们只使用 16 位,所以计数器设置为 65535。预分频系数,我们设置为 84 分频,定时器 5 的时钟频率是 2 倍的 APB1 总线时钟频率,即 84MHz,可以得到计数器的计数频率是 1MHz,即 1us计数一次,所以我们的捕获时间精度是 1us。这里可以知道定时器的溢出时间是 65536us。
这两个参数既出现在gtim.c中,又需要在main函数中随时使用。出现这种情况有两种应对方法,一是想方法把main.c中有关该参数的的命令在gtim.c中整合成函数。另一种更简便的方法是在gtim.c中声明,并使用extern外部变量调用(写在main函数之前)
(四)、通用定时器脉冲计数实验
外部时钟模式1的外部输入引脚只能是通道一或者通道二对应的IO,通道三或者通道四是不可以的。
定时器 2,使用 TIM2 通道 1,PA0 复用为 TIM2_CH1。
void gtim_timx_cnt_chy_init(uint16_t psc)
通用定时器TIMX通道Y脉冲计数初始化函数