实验目的:
让蜂鸣器每隔一秒响一次;
实验步骤:
实验程序:
- /************************led.c***********************/
- #include "stm32f4xx.h" //在SYSTEM目录下可以找到
- #include "sys.h"
- void LED_Init(void){
- RCC->AHB1ENR |= 1<<5; //使能GPIO端口的F时钟
- GPIO_Set(GPIOF,PIN9|PIN10,GPIO_MODE_OUT,GPIO_OTYPE_PP,GPIO_SPEED_25M,GPIO_PUPD_PU);
- PFout(9) = 1;
- PFout(10) = 1;
- }
- /************************led.h***********************/
- #ifndef _LED_H
- #define _LED_H
- void LED_Init(void);
- #endif
- /************************beep.c***********************/
- #include "sys.h"
- #include "beep.h"
- void Beep_Init(void){
- RCC->AHB1ENR |= 1<<5; //使能PORTE时钟
- GPIO_Set(GPIOF,PIN8,GPIO_MODE_OUT,GPIO_OTYPE_PP,GPIO_SPEED_100M,GPIO_PUPD_PD); //PF8设置,下拉
- PFout(8) =0; //关闭蜂鸣器
- }
- /************************beep.h***********************/
- #include "sys.h"
- #ifndef _BEEP_H
- #define _BEEP_H
- void Beep_Init(void);
- #endif
- /************************timer.c***********************/
- #include "sys.h"
- #include "stm32f4xx.h"
- /*
- 本示例的作用就是,
- 让蜂鸣器每隔一秒响一次;
- */
- /*
- 关于定时器的操作可参考寄存器版的步骤
- */
- /*
- 中断初始化函数:
- 主要是关于寄存器的相关配置
- */
- void TIM3_Init(void){
- /*************************
- 定时器中断的设置:
- *************************/
- //使能TIM3时钟;
- RCC->APB1ENR |= 1 << 1;
- /*预分频器*/
- TIM3->PSC = 8400 - 1;
- /*ARR为自动重载寄存器*/
- TIM3->ARR = 10000 -1 ;
- //使能更新中断
- TIM3->DIER |= 1;
- //使能计数器
- TIM3->CR1 |= 1;
- /*TIM中断分组设置*/
- //SCB和NVIC,可参考STM32F3与STM32F4系列Cortex M4内核编程手册.pdf
- SCB->AIRCR |= 0x5 << 8; //设置分组
- NVIC->IP[29] |= 0; //设置优先级,具体可分析MY_NVIC_Init()函数;
- //若不使能,则中断不会发生
- NVIC->ISER[0] |= 1 << 29; //使能中断;
- }
- /*每1000ms产生一次中断*/
- void TIM3_IRQHandler(void){
- /*
- 中断处理函数:
- */
- if(TIM3->SR&0X0001){
- PFout(8) = !PFout(8);
- }
- /*
- 在中断里边最后记得清中断标志:
- */
- TIM3->SR &= ~(1 << 0);
- }
- /*************************timer.h**********************/
- #ifndef _TIMER_H
- #define _TIMER_H
- void TIM3_Init(void);
- #endif
- /*************************test.c**********************/
- #include "sys.h"
- #include "delay.h"
- #include "beep.h"
- #include "timer.h"
- #include "led.h"
- int main(void){
- Stm32_Clock_Init(336,8,2,7);//设置时钟,168Mhz //思考一下为啥要设置整个时钟(到时可参考时钟那章节);
- delay_init(168); //初始化延时函数
- LED_Init();
- Beep_Init();
- TIM3_Init();
- while(1){
- PFout(9) = 0;
- delay_ms(1000);
- PFout(9) = 1;
- delay_ms(1000);
- }
- }
实验分析:
我们主要分析一下定时器中断中,关于寄存器的设置问题
1. RCC->APB1ENR |= 1 << 1;
这条语句的作用就是:使能TIM3时钟;
外设这么多,我咋知道,我们应该在哪条总线上去使能响应的外设?
第一种办法就是查看RCC寄存器,看其每个寄存器的每位的解释;
第二种办法就是看对应芯片的整个框图,如:
由上述视图,我们可知:TIM3是挂在APB1总线上的,所以我们只需使能APB1ENR寄存器上的响应的位就可以达到我们使能响应的外设的时钟的效果;
查看APB1ENR寄存器,我们可知:
所以在这里就写成了RCC->APB1ENR |= 1 << 1
2. TIM3->PSC = 8400 - 1;
3. TIM3->ARR = 10000 -1 ;
第2条语句和第3条语句的作用就是设置预分频器和自动重载寄存器;
但是在设置这两个寄存器之前,我们必须要搞清楚一点就是:计数器的时钟频率从哪里来?
查看定时器的框图:
注:由上述的定时器框图,我们可以得知:CK_INT也就是来自RCC的TIMxCLK
再结合第5条语句,其作用就是将CEN位写入1,所以,这样,我们就将预分频器的时钟CK_PSC设置为了内部时钟CK_INT;
此时,我们需要搞清楚CK_INT的时钟多少,在test.c中,我们看到:Stm32_Clock_Init()这个函数,其函数体如下:
- void Stm32_Clock_Init(u32 plln,u32 pllm,u32 pllp,u32 pllq)
- {
- RCC->CR|=0x00000001; //设置HISON,开启内部高速RC振荡
- RCC->CFGR=0x00000000; //CFGR清零
- RCC->CR&=0xFEF6FFFF; //HSEON,CSSON,PLLON清零
- RCC->PLLCFGR=0x24003010; //PLLCFGR恢复复位值
- RCC->CR&=~(1<<18); //HSEBYP清零,外部晶振不旁路
- RCC->CIR=0x00000000; //禁止RCC时钟中断
- Sys_Clock_Set(plln,pllm,pllp,pllq);//设置时钟
- //配置向量表
- #ifdef VECT_TAB_RAM
- MY_NVIC_SetVectorTable(1<<29,0x0);
- #else
- MY_NVIC_SetVectorTable(0,0x0);
- #endif
- }
接着再追踪Sys_Clock_Set()函数,进到其函数体内,我们可以看到:
- //时钟设置函数
- //Fvco=Fs*(plln/pllm);
- //Fsys=Fvco/pllp=Fs*(plln/(pllm*pllp));
- //Fusb=Fvco/pllq=Fs*(plln/(pllm*pllq));
- //Fvco:VCO频率
- //Fsys:系统时钟频率
- //Fusb:USB,SDIO,RNG等的时钟频率
- //Fs:PLL输入时钟频率,可以是HSI,HSE等.
- //plln:主PLL倍频系数(PLL倍频),取值范围:64~432.
- //pllm:主PLL和音频PLL分频系数(PLL之前的分频),取值范围:2~63.
- //pllp:系统时钟的主PLL分频系数(PLL之后的分频),取值范围:2,4,6,8.(仅限这4个值!)
- //pllq:USB/SDIO/随机数产生器等的主PLL分频系数(PLL之后的分频),取值范围:2~15.
- //外部晶振为8M的时候,推荐值:plln=336,pllm=8,pllp=2,pllq=7.
- //得到:Fvco=8*(336/8)=336Mhz
- // Fsys=336/2=168Mhz
- // Fusb=336/7=48Mhz
- //返回值:0,成功;1,失败。
- u8 Sys_Clock_Set(u32 plln,u32 pllm,u32 pllp,u32 pllq)
- {
- u16 retry=0;
- u8 status=0;
- RCC->CR|=1<<16; //HSE 开启
- while(((RCC->CR&(1<<17))==0)&&(retry<0X1FFF))retry++;//等待HSE RDY
- if(retry==0X1FFF)status=1; //HSE无法就绪
- else
- {
- RCC->APB1ENR|=1<<28; //电源接口时钟使能
- PWR->CR|=3<<14; //高性能模式,时钟可到168Mhz
- RCC->CFGR|=(0<<4)|(5<<10)|(4<<13);//HCLK 不分频;APB1 4分频;APB2 2分频.
- RCC->CR&=~(1<<24); //关闭主PLL
- RCC->PLLCFGR=pllm|(plln<<6)|(((pllp>>1)-1)<<16)|(pllq<<24)|(1<<22);//配置主PLL,PLL时钟源来自HSE
- RCC->CR|=1<<24; //打开主PLL
- while((RCC->CR&(1<<25))==0);//等待PLL准备好
- FLASH->ACR|=1<<8; //指令预取使能.
- FLASH->ACR|=1<<9; //指令cache使能.
- FLASH->ACR|=1<<10; //数据cache使能.
- FLASH->ACR|=5<<0; //5个CPU等待周期.
- RCC->CFGR&=~(3<<0); //清零
- RCC->CFGR|=2<<0; //选择主PLL作为系统时钟
- while((RCC->CFGR&(3<<2))!=(2<<2));//等待主PLL作为系统时钟成功.
- }
- return status;
- }
从芯片手册中,我们还可以找到一句话:
而在Sys_Clock_Set()函数中,我们可以对照寄存器的设置,得知:
AHB不分频,APB1 4分频,APB2 2分频,且可得知:AHB的频率为168MHZ;
而定时器3又是挂在APB1总线上的,所以由于APB1预分频器为4,并不为1,所以,
定时器的时钟频率等于APB1的频率的2倍,而APB1的时钟频率等于AHB频率的1/4,
所以可知:定时器的时钟频率为84MHZ;而定时器时钟也就是RCC的TIM3CLK的时钟,
所以,内部时钟CK_INT的时钟频率为84MHZ;由此,我们设置预分频器和计数器才有意义;
注:在这里再唠叨一句的是:在这里将预分频器减一的原因是:查看TIMxPSC寄存器,其下方的
解释可以得知:计数器时钟频率CK_CNT等于CK_PSC的频率/(PSC[15:0] + 1),所以在这里就将
预分频器减一;而将自动装载的值减一的原因是:若我们设置自动装载的值为5000,那么计数器
从0开始计数,然后计数到5000,然后又从5000跳到0,当跳到0时,其中断才会触发,所以,
此时,计数器实际上是计了5001次,所以,我们需要将其减一;
4. TIM3->DIER |= 1;
其作用就是使能更新中断
5. TIM3->CR1 |= 1;
其作用就是使能计数器
6. SCB->AIRCR |= 0x5 << 8;
7. NVIC->IP[29] |= 0;
8. NVIC->ISER[0] |= 1 << 29;
第6,7,8条语句的解释,可参考STM32的外部中断那篇博客中对应部分的解释;
注意事项:
1.在上述中断函数中,若没有加if(TIM3->SR&0X0001)这条语句,
那么蜂鸣器始终不会响,若加了if(TIM3->SR&0X0001)这条语句,
那么蜂鸣器才会如程序中所示,正常工作;理论上,不加这条if语句,
蜂鸣器也会隔一段时间响一次的,但看到的 事实是蜂鸣器根本不会响;
至于其原因,暂时还没有找到合理的解释,现在这里载一笔;