基于stm32的多功能时钟1——时钟显示

基于stm32的多功能时钟1——时钟显示,第1张

        读者们,大家好!

        接着上一章多功能时钟(绪论)的内容,在这一章中,我将介绍多功能时钟的时钟显示部分。话不多说,我们正式开始吧~

        多功能时钟,时钟显示功能是必不可少的。所以,我们利用stm32的定时器来计时。本来打算采用stm32的RTC实时时钟,但后来想,刚开始弄得时候,尽量简单一些,别一开始就给自己出难题,毕竟RTC实时时钟要配置的东西还挺多的。如果此次做得不错的话,后面可以再加RTC实时时钟。

        stm32不同于51,共有11个定时器,其中2个高级控制定时器(TIM1和TIM8),4个普通定时器(TIM2~TIM5)和2个基本定时器(TIM6和TIM7),以及2个看门狗定时器和1个系统滴答定时器。这里,我们采用普通定时器TIM2,并且开启定时器的中断,中断时间为1s,并且在中断函数里,模拟时钟的计时功能。

(1)配置嵌套中断控制器NVIC

void tim2_nvic_config(void)

{

    NVIC_InitTypeDef NVIC_InitStruct

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2)

    NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn

    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2//抢占优先级为2

    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0//子优先级为0

    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE

    NVIC_Init(&NVIC_InitStruct)

}

        这里,我们只需对NVIC_InitStruct结构体的每个元素赋值,其中TIM2_IRQn为定时器TIM2中断线,设置优先级组为2,即抢占优先级组为4组,这里抢占优先级为2,子优先级为0,然后使能NVIC(优先级不能理解上网查询)。

(2)定时器初始化配置

void tim2_config(void)

{

    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct

    tim2_nvic_config()                                //配置NVIC

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE)//开启时钟

    TIM_DeInit(TIM2)                                      //定时器2复位

    TIM_TimeBaseInitStruct.TIM_Period = 2000-1                  //自动重装载寄存器值

    TIM_TimeBaseInitStruct.TIM_Prescaler = 36000-1              //时钟预分频数

    TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1    //采样分频

    TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up//计数模式

    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStruct)            //初始化TIM2

    TIM_ClearFlag(TIM2, TIM_FLAG_Update)                        //清除溢出中断标志

    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE)

    TIM_Cmd(TIM2, ENABLE)                                      //使能时钟

}

        TIM2初始化,首先配置NVIC,打开TIM2时钟,复位TIM2。然后对TIM_TimeBaseInitStruct结构体的每个元素赋值。这里,主要阐述如何计算定时中断时间。定时器的溢出中断时间由TIM_Period和TIM_Prescaler来决定的。这里,我直接给出公式:发生中断时间=(TIM_Period+1)*(TIM_Prescaler+1)/FCLK,而FCLK为72M,所以定时1s,可以这样:TIM_Period=2000-1,TIM_Prescaler=36000-1;最后清除溢出中断标志,使能时钟即可计时。

(3)编写中断计时函数

void TIM2_IRQHandler(void)

{

    if(TIM_GetITStatus(TIM2 ,TIM_IT_Update)!=RESET)

    {

        sec++

        if(sec>=60)

        {

            sec = 0

            min++

            if(min>=60)

            {

                min = 0

                hour++

                if(hour>=24)

                {

                    hour = 0

                }

            }

        }

    }

    TIM_ClearITPendingBit(TIM2 ,TIM_FLAG_Update)

}

这里,先定义时、分、秒三个变量,然后在中断函数里,对时间变量的关系进行换算即可。

(4)编写时钟显示函数

        这里,我们采用LCD12864实时显示。LCD12864的相关内容,我后面在LCD库函数章节中,会专门介绍的。这里,只将时间显示即可。

    lcd_display_num_m(2, 16, hour/10)

    lcd_display_num_m(2, 24, hour%10)

    lcd_display_string(2,32,"时")

    lcd_display_num_m(2, 48, min/10)

    lcd_display_num_m(2, 56, min%10)

    lcd_display_string(2,64,"分")

    lcd_display_num_m(2, 80, sec/10)

    lcd_display_num_m(2, 88, sec%10)

    lcd_display_string(2,96,"秒")

        LCD12864的驱动函数,我跟着黄老师的视频后面写的,在老师的基础上,增加了汉字字符串显示函数。这里,看成库函数即可,只需简单的调用,显示时间就行。

(5)按键调整时间

        成功显示时间后,我们需要按键来调整时间。 我们需要设置时钟启/停键(K1),时间位选择键(K2),数值增加键(K3),数值减小键(K4)。

1.我们先对按键的GPIO进行配置,开启相应的时钟,选择相关引脚,设置浮空输入模式等。

void key_gpio_init(void)

{

    GPIO_InitTypeDef GPIO_InitStructure

    /*使能GPIO的RCC时钟*/

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE)

    /*配置PB11~PB14引脚*/

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11|GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING

    GPIO_Init(GPIOB,&GPIO_InitStructure)

}

2.配置好按键的GPIO口后,编写按键扫描函数,从而达到调整时间的功能。

u8 flag,mark//flag为定时器启停标志位,mark为位选择标志位

//mark为0表示未选中,mark为1表示选择时位,mark为2表示选择分位,mark为3表示选择秒位

void keyscan(void)

{

    if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11)==RESET)

    {

        delay_ms(10)

        if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11)==RESET)

        {

            flag = ~flag

            if(!flag)

            {

                TIM_Cmd(TIM2, ENABLE)

            }

            else

            {

                TIM_Cmd(TIM2, DISABLE)

                mark = 0

            }

        }while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11)==RESET)

    }

    if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_12)==RESET)

    {

        delay_ms(10)

        if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_12)==RESET)

        {

            mark = mark>=3?0:mark+1

        }while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_12)==RESET)

    }

    if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_13)==RESET)

    {

        delay_ms(10)

        if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_13)==RESET)

        {

            if(flag)

            {

                switch(mark)

                {

                    case 1:hour = hour<23?hour+1:0break

                    case 2:min = min<59?min+1:0break

                    case 3:sec = sec<59?sec+1:0break

                    default:break

                }

            }

        }while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_13)==RESET)

    }

    if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_14)==RESET)

    {

        delay_ms(10)

        if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_14)==RESET)

        {

            if(flag)

            {

                switch(mark)

                {

                    case 1:hour = hour>0?hour-1:23break

                    case 2:min = min>0?min-1:59break

                    case 3:sec = sec>0?sec-1:59break

                    default:break

                }

            }

        }while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_14)==RESET)

    }

}

        至此,我们完成了时钟显示的功能,当然,后期如果可以的话,我们可以使用stm32的RTC实时时钟资源,还可以设置闹钟、整点报时的功能。

         本章,我主要介绍了如何利用stm32的TIM定时器和GPIO资源,实现时钟显示和按键调整的功能。下一章中,我将介绍如何利用DHT11模块来测量温度和湿度,从而实现系统对环境参量的获取。

STM32使用内部RC振荡器时,OSC32_IN,OSC32_OUT接法:

    1)对于100脚或144脚的产品,OSC_IN应接地,OSC_OUT应悬空。

    2)对于少于100脚的产品,有2种接法:

          2.1)OSC_IN和OSC_OUT分别通过10K电阻接地。此方法可提高EMC性能。

          2.2)分别重映射OSC_IN和OSC_OUT至PD0和PD1,再配置PD0和PD1为推挽输出并输出'0'。此方法可以减小功耗并(相对上面2.1)节省2个外部电阻。

例程如下:

//=== 晶振脚重映射到PD0,PD1 并配置为推挽输出输出‘0’

void HSI_Config(void) 

   GPIO_InitTypeDef GPIO_InitStructure 

   RCC_DeInit()/*将外设RCC寄存器重设为缺省值*/  

   RCC_HSICmd(ENABLE)  

   while(RCC_GetFlagStatus(RCC_FLAG_HSIRDY)== RESET)//等待HSI就绪  

   RCC_HCLKConfig(RCC_SYSCLK_Div1)  /*设置AHB时钟(HCLK) RCC_SYSCLK_Div1——AHB时钟 = 系统时*/   

   RCC_PCLK2Config(RCC_HCLK_Div1)  /*设置高速AHB时钟(PCLK2)RCC_HCLK_Div1——APB2时钟= HCLK*/      

   RCC_PCLK1Config(RCC_HCLK_Div2)/*设置低速AHB时钟(PCLK1)RCC_HCLK_Div2——APB1时钟= HCLK /2*/       

   FLASH_SetLatency(FLASH_Latency_2) //FLASH_Latency_2 2延时周期 

   FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable)//预取指缓存使能 

    RCC_PLLConfig(RCC_PLLSource_HSI_Div2,RCC_PLLMul_16)/*设置PLL时钟源及倍频系数,频率为8/2*16=64Mhz*/     

   RCC_PLLCmd(ENABLE)      /*使能PLL */  

   while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET) /*检查指定的RCC标志位(PLL准备好标志)设置与否*/     

   RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK)  /*设置系统时钟(SYSCLK)*/   

   while(RCC_GetSYSCLKSource() != 0x08)    /*0x08:PLL作为系统时钟*/    

   RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD |RCC_APB2Periph_AFIO,ENABLE)//打开重映射时钟,并打开重映射后的IO口 

   GPIO_PinRemapConfig(GPIO_Remap_PD01,ENABLE)//IO口重映射开启 

   /*选择要控制的引脚*/ 

   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 

   /*设置引脚为通用推挽输出*/ 

   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP 

   /*设置引脚速率为50MHz*/ 

   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz 

   /*调用库函数,初始化GPIOC*/ 

   GPIO_Init(GPIOD, &GPIO_InitStructure) 

   /*固定IO口下拉到地*/ 

   GPIO_ResetBits(GPIOD, GPIO_Pin_0 | GPIO_Pin_1) 

    HSI内部8MHz的RC振荡器的误差在1%左右,内部RC振荡器的精度通常比用HSE(外部晶振)要差上十倍以上。STM32的ISP就是用(HSI)内部RC振荡器。

首先在主程序中注释掉SystemInit()

然后使用下面的函数做为系统时钟的初始化函数

void RCC_Configuration(void)

{

RCC_DeInit()//将外设 RCC寄存器重设为缺省值

RCC_HSICmd(ENABLE)//使能HSI

while(RCC_GetFlagStatus(RCC_FLAG_HSIRDY) == RESET)//等待HSI使能成功

//FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable)

//FLASH_SetLatency(FLASH_Latency_2)

RCC_HCLKConfig(RCC_SYSCLK_Div1)

RCC_PCLK1Config(RCC_HCLK_Div2)

RCC_PCLK2Config(RCC_HCLK_Div1)

//设置 PLL 时钟源及倍频系数

RCC_PLLConfig(RCC_PLLSource_HSI_Div2, RCC_PLLMul_2)//使能或者失能 PLL,这个参数可以取:ENABLE或者DISABLE

RCC_PLLCmd(ENABLE)//如果PLL被用于系统时钟,那么它不能被失能

//等待指定的 RCC 标志位设置成功 等待PLL初始化成功

while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)

//设置系统时钟(SYSCLK) 设置PLL为系统时钟源

RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK)//选择想要的系统时钟

//等待PLL成功用作于系统时钟的时钟源

//  0x00:HSI 作为系统时钟

//  0x04:HSE作为系统时钟

//  0x08:PLL作为系统时钟

while(RCC_GetSYSCLKSource() != 0x08)//需与被选择的系统时钟对应起来,RCC_SYSCLKSource_PLL

}

void RCC_Configuration(void)

{

   RCC_DeInit()//将外设 RCC寄存器重设为缺省值

   RCC_HSICmd(ENABLE)//使能HSI  

   while(RCC_GetFlagStatus(RCC_FLAG_HSIRDY) == RESET)//等待HSI使能成功

   //FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable)

   //FLASH_SetLatency(FLASH_Latency_2)

 

   RCC_HCLKConfig(RCC_SYSCLK_Div1) 

   RCC_PCLK1Config(RCC_HCLK_Div2)

   RCC_PCLK2Config(RCC_HCLK_Div1)

   

   //设置 PLL 时钟源及倍频系数

   RCC_PLLConfig(RCC_PLLSource_HSI_Div2, RCC_PLLMul_2)//使能或者失能 PLL,这个参数可以取:ENABLE或者DISABLE

   RCC_PLLCmd(ENABLE)//如果PLL被用于系统时钟,那么它不能被失能

   //等待指定的 RCC 标志位设置成功 等待PLL初始化成功

   while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)

   //设置系统时钟(SYSCLK) 设置PLL为系统时钟源

   RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK)//选择想要的系统时钟

   //等待PLL成功用作于系统时钟的时钟源

   //  0x00:HSI 作为系统时钟

   //  0x04:HSE作为系统时钟

   //  0x08:PLL作为系统时钟  

   while(RCC_GetSYSCLKSource() != 0x08)//需与被选择的系统时钟对应起来,RCC_SYSCLKSource_PLL

}

补充一点:

由图可以看出系统时钟的供给可以有3种方式,HSI,HSE,PLL。如果选用内部时钟作为系统时钟,其倍频达不到72Mhz,最多也就8Mhz/2*16 = 64Mhz。


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/tougao/7700170.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-08
下一篇 2023-04-08

发表评论

登录后才能评论

评论列表(0条)

保存