《STM32学习笔记》4——核心功能电路与编程(下)

接上文,文中的图片,大多数来自视频的截图(来自洋桃电子)。
欢迎大家批评指正!

STM32学习笔记-专栏

一、蜂鸣器驱动

1、蜂鸣器介绍

有源蜂鸣器无源蜂鸣器
内置频率发生电路内部没有发生电路
通电就能发出声音需要外部给予频率
声音频率固定可产生不同频率声音
成本较高成本低
  • 两种蜂鸣器外观上无区别

2、蜂鸣器电路


核心板上的蜂鸣器


核心板蜂鸣器电路

  • 不用蜂鸣器时,应对 P B 5 PB5 PB5 口拉高(此处电路接有上拉电阻),控制蜂鸣器断开,不工作。
  • P B 5 PB5 PB5口拉低电平,蜂鸣器上电导通,但由于是无源蜂鸣器,单纯导通只会发热,不会发声。需要给 P B 5 PB5 PB5 不断高低变换的电平(方波),才可以发出声音。

3、蜂鸣器程序

h a r d w a r e hardware hardware 导入 b u z z e r . c buzzer.c buzzer.c 文件

  • b u z z e r . h buzzer.h buzzer.h
    • 宏定义: B U Z Z E R BUZZER BUZZER 的端口组和 I O IO IO 端口。
      #define BUZZERPORT	GPIOB	//定义IO接口 
      #define BUZZER	GPIO_Pin_5	//定义IO接口
      
    • 声明:初始化函数和响声函数。
      void BUZZER_Init(void);   //初始化
      void BUZZER_BEEP1(void);  //响一声
      

  • b u z z e r . c buzzer.c buzzer.c

    • 初始化:
      void BUZZER_Init(void)
          {                                                //蜂鸣器接口初始化
          GPIO_InitTypeDef  GPIO_InitStructure; 	
          GPIO_InitStructure.GPIO_Pin = BUZZER;            //选择端口号                        
          GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //选择IO接口工作方式       
          GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//设置IO接口速度(2/10/50MHz)    
          GPIO_Init(BUZZERPORT, &GPIO_InitStructure);	
          
          GPIO_WriteBit(BUZZERPORT,BUZZER,(BitAction)(1)); //蜂鸣器接口输出高电平1	,断开蜂鸣器,保护
          }
      
    • 响声函数:
      void BUZZER_BEEP1(void)                                  //1kHz
          {                                                    //蜂鸣器响一声
          u16 i;
          for(i=0;i<200;i++)
              {
              GPIO_WriteBit(BUZZERPORT,BUZZER,(BitAction)(0)); //蜂鸣器接口输出0
              delay_us(500);                                   //延时		
              GPIO_WriteBit(BUZZERPORT,BUZZER,(BitAction)(1)); //蜂鸣器接口输出高电平1
              delay_us(500);                                   //延时		
              }
          }
      
      最后一条语句必须是,拉高电平,断开蜂鸣器,保护电路

  • m a i n . c main.c main.c

    int main (void)
    {//主程序
      u16 a;              //定义变量
    
      //初始化程序
      RCC_Configuration();    //时钟设置
      LED_Init();             //LED初始化
      KEY_Init();             //按键初始化
      BUZZER_Init();          //蜂鸣器初始化
      BUZZER_BEEP1();         //蜂鸣器音1
    
      a = FLASH_R(FLASH_START_ADDR); //从指定页的地址读FLASH
    
      GPIO_Write(LEDPORT,a|0xfffc&GPIO_ReadOutputData(LEDPORT));  //直接数值操作将变量值写入LED(LED在GPIOB组的PB0和PB1上)
    
      //主循环
      while(1)
          {                                           //示例4:有锁存
          if(!GPIO_ReadInputDataBit(KEYPORT,KEY1))
             {                                        //读按键接口的电平
              delay_ms(20);                           //延时20ms去抖动
              if(!GPIO_ReadInputDataBit(KEYPORT,KEY1))
                {                                     //读按键接口的电平
                                                      //在2个LED上显示二进制加法
                a++;                                  //变量加1
                if(a>3)
                  {                                   //当变量大于3时清0
                      a=0; 
                  }
                GPIO_Write(LEDPORT,a|0xfffc&GPIO_ReadOutputData(LEDPORT));   //直接数值操作将变量值写入LED(LED在GPIOB组的PB0和PB1上)         
                BUZZER_BEEP1();                              //蜂鸣器音1
                FLASH_W(FLASH_START_ADDR,a);                 //从指定页的地址写入FLASH
                while(!GPIO_ReadInputDataBit(KEYPORT,KEY1)); //等待按键松开 
                }
             }
          }
      }
    

    功能为:上电响一声,按键每按下一次,响一声,多次按下,灯亮循环

二、 MIDI 音乐播放

1、MIDI 简介

MIDI ( M u s i c a l I n s t r u m e n t D i g i t a l I n t e r f a c e ) (Musical Instrument Digital Interface) (MusicalInstrumentDigitalInterface) 乐器数字接口,是20世纪80年代初为解决电声乐器之间的通信问题而提出的。MIDI 是编曲界最广泛的音乐标准格式,可称为“计算机能理解的乐谱”。它用音符的数字控制信号来记录音乐。一首完整的 MIDI 音乐只有几十 KB 大,而能包含数十条音乐轨道。几乎所有的现代音乐都是用 MIDI 加上音色库来制作合成的。MIDI 传输的不是声音信号,而是音符、控制参数等指令,它指示 MIDI 设备要做什么,怎么做,如演奏哪个音符、多大音量等。它们被统一表示成 MIDI 消息。
传输时采用异步串行通信,标准通信波特率为 31.25×( 1±0.01) KBaud。

2、MIDI 放映原理


音符频率对照

  • 按音符控制发生频率
  • 按节拍调整一个音符的播放时间

3、MIDI 例程

  • b u z z e r . c buzzer.c buzzer.c
    uc16 music1[78]=
    { //音乐1的数据表(奇数是音调,偶数是长度)
    330,750,
    440,375,
    494,375,
    523,750,
    587,375,
    659,375,
    587,750,
    494,375,
    392,375,
    440,1500,
    330,750,
    440,375,
    494,375,
    523,750,
    587,375,
    659,375,
    587,750,
    494,375,
    392,375,
    784,1500,
    659,750,
    698,375,
    784,375,
    880,750,
    784,375,
    698,375,
    659,750,
    587,750,
    659,750,
    523,375,
    494,375,
    440,750,
    440,375,
    494,375,
    523,750,
    523,750,
    494,750,
    392,750,
    440,3000
    };
    
    void MIDI_PLAY(void)
      {                                         //MIDI音乐
      u16 i,e;
      for(i=0;i<39;i++)
        {
        for(e=0;e<music1[i*2]*music1[i*2+1]/1000;e++)
          {
          GPIO_WriteBit(BUZZERPORT,BUZZER,(BitAction)(0)); //蜂鸣器接口输出0
          delay_us(500000/music1[i*2]);                    //延时		
          GPIO_WriteBit(BUZZERPORT,BUZZER,(BitAction)(1)); //蜂鸣器接口输出高电平1
          delay_us(500000/music1[i*2]);                    //延时	
          }	
        }
      }
    

通过修改数组数据(左频率,右时长),修改循环长度,可以任意放映曲谱。

三、USART 串口通信

1、简介

利用串口芯片,可以实现USB与串口之间的转换,实现计算机和单片机的通讯。需安装串口驱动。
F103C8T6有三组串口,USART1、USART2、USART3

2、串口电路


核心板串口芯片


核心板串口电路

3 收发程序

3.1 相关固件及函数
函数名描述
USART_DeInit将外设 USARTx寄存器重设为缺省值
USART_Init根据 USART_InitStruct 中指定的参数初始化外设 USARTx 寄存器
USART_StructInit把 USART_InitStruct 中的每一个参数按缺省值填入
USART_Cmd使能或者失能 USART 外设
USART_ITConfig使能或者失能指定的 USART 中断
USART_DMACmd使能或者失能指定 USART 的 DMA 请求
USART_SetAddress设置 USART 节点的地址
USART_WakeUpConfig选择 USART 的唤醒方式
USART_ReceiverWakeUpCmd检查 USART 是否处于静默模式
USART_LINBreakDetectLengthConfig设置 USART LIN 中断检测长度
USART_LINCmd使能或者失能 USARTx 的 LIN 模式
USART_SendData通过外设 USARTx 发送单个数据
3.2 程序控制
  • 加入相关 usart 的c,h文件,在 lib 文件夹加入固件 stm32f10x_usrat.c。

  • u s a r t . h usart.h usart.h

    #ifndef __USART_H
    #define __USART_H
    #include <stdarg.h>
    #include <stdlib.h>
    #include <string.h>
    #include "stdio.h"	
    #include "sys.h" 
    
    #define USART_n		USART1  //定义使用printf函数的串口,其他串口要使用USART_printf专用函数发送
    
    #define USART1_REC_LEN  			200  	//定义USART1最大接收字节数
    #define USART2_REC_LEN  			200  	//定义USART2最大接收字节数
    #define USART3_REC_LEN  			200  	//定义USART3最大接收字节数
    
    //不使用某个串口时要禁止此串口,以减少编译量
    #define EN_USART1 			1		//使能(1)/禁止(0)串口1
    #define EN_USART2 			0		//使能(1)/禁止(0)串口2
    #define EN_USART3 			0		//使能(1)/禁止(0)串口3
          
    extern u8  USART1_RX_BUF[USART1_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.末字节为换行符 
    extern u8  USART2_RX_BUF[USART2_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.末字节为换行符
    extern u8  USART3_RX_BUF[USART3_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.末字节为换行符
    
    extern u16 USART1_RX_STA;         		//接收状态标记	
    extern u16 USART2_RX_STA;         		//接收状态标记	
    extern u16 USART3_RX_STA;         		//接收状态标记	
    
    //函数声明
    void USART1_Init(u32 bound);//串口1初始化并启动
    void USART2_Init(u32 bound);//串口2初始化并启动
    void USART3_Init(u32 bound);//串口3初始化并启动
    void USART1_printf(char* fmt,...); //串口1的专用printf函数
    void USART2_printf(char* fmt,...); //串口2的专用printf函数
    void USART3_printf(char* fmt,...); //串口3的专用printf函数
    
    #endif
    
    • 宏定义
    • 串口选择使能
    • 基本配置

  • u s a r t . c usart.c usart.c
    USART1 串口相关程序:(2、3与1基本相同

    /*USART1串口相关程序*/
    #if EN_USART1   //USART1使用与屏蔽选择
    u8 USART1_RX_BUF[USART1_REC_LEN];     //接收缓冲,最大USART_REC_LEN个字节.
                                          //接收状态
                                          //bit15,	接收完成标志
                                          //bit14,	接收到0x0d
                                          //bit13~0,	接收到的有效字节数目
    u16 USART1_RX_STA=0;                  //接收状态标记	  
    
    /*USART1专用的printf函数
    当同时开启2个以上串口时,printf函数只能用于其中之一,其他串口要自创独立的printf函数
    调用方法:USART1_printf("123"); //向USART2发送字符123*/
    void USART1_printf (char *fmt, ...)
    { 
      char buffer[USART1_REC_LEN+1];  // 数据长度
      u8 i = 0;	
      va_list arg_ptr;
      va_start(arg_ptr, fmt);  
      vsnprintf(buffer, USART1_REC_LEN+1, fmt, arg_ptr);
      while ((i < USART1_REC_LEN) && (i < strlen(buffer)))
      {
        USART_SendData(USART1, (u8) buffer[i++]);
        while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET); 
      }
      va_end(arg_ptr);
    }
    
    
    void USART1_Init(u32 bound){ //串口1初始化并启动
      //GPIO端口设置
      GPIO_InitTypeDef GPIO_InitStructure;
      USART_InitTypeDef USART_InitStructure;
      NVIC_InitTypeDef NVIC_InitStructure;	 
      RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);	//使能USART1,GPIOA时钟
    
      //USART1_TX   PA.9
      GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
      GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出
      GPIO_Init(GPIOA, &GPIO_InitStructure);  
    
      //USART1_RX	  PA.10
      GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
      GPIO_Init(GPIOA, &GPIO_InitStructure); 
    
      //Usart1 NVIC 配置
      NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
      NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3
      NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;		//子优先级3
      NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
      NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器 
    
      //USART 初始化设置
      USART_InitStructure.USART_BaudRate = bound;//一般设置为9600;
      USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
      USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
      USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
      USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
      USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;	//收发模式
      USART_Init(USART1, &USART_InitStructure); //初始化串口
      USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启ENABLE/关闭DISABLE中断
      USART_Cmd(USART1, ENABLE);                    //使能串口 
    }
    
    
    void USART1_IRQHandler(void)
    {                       //串口1中断服务程序(固定的函数名不能修改)	
      u8 Res;
                            //以下是字符串接收到USART1_RX_BUF[]的程序,(USART1_RX_STA&0x3FFF)是数据的长度(不包括回车)
                            //当(USART1_RX_STA&0xC000)为真时表示数据接收完成,即超级终端里按下回车键。
                            //在主函数里写判断if(USART1_RX_STA&0xC000),然后读USART1_RX_BUF[]数组,读到0x0d 0x0a即是结束。
                            //注意在主函数处理完串口数据后,要将USART1_RX_STA清0
      if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
      {                                     //接收中断(接收到的数据必须是0x0d 0x0a结尾)		
        Res =USART_ReceiveData(USART1);     //(USART1->DR);	//读取接收到的数据
        printf("%c",Res);                   //把收到的数据以 a符号变量 发送回电脑		
        if((USART1_RX_STA&0x8000)==0)
        {                                   //接收未完成			
          if(USART1_RX_STA&0x4000)
          {                                 //接收到了0x0d				
            if(Res!=0x0a)
            {
              USART1_RX_STA=0;              //接收错误,重新开始
            }
            else
            {
              USART1_RX_STA|=0x8000;	    //接收完成了 
            }
          }
          else
          {                                 //还没收到0X0D					
            if(Res==0x0d)USART1_RX_STA|=0x4000;
            else
            {
              USART1_RX_BUF[USART1_RX_STA&0X3FFF]=Res ; //将收到的数据放入数组
              USART1_RX_STA++;	            //数据长度计数加1
              if(USART1_RX_STA>(USART1_REC_LEN-1))
              {
                USART1_RX_STA=0;            //接收数据错误,重新开始接收	  
              }
            }		 
          }
        }   		 
      } 
    } 
    #endif	
    
    • 基本配置——状态
    • printf 函数定义
    • 串口初始化
      • 串口的 TX 和 RX 口,需要进行 IO 的配置,如 IO 初始化、IO端口模式配置等
      • 波特率、数据长度、特殊标志位、模式配置、开启中断、使能 等
    • 串口中断

  • m a i n . c main.c main.c :

    int main (void)
    {                        //主程序
      u8 a=7,b=8;
                             //初始化程序
      RCC_Configuration();   //时钟设置
      USART1_Init(115200);   //串口初始化(参数是波特率)
    
      //主循环
      while(1)
      {
        /* 发送方法1 */
        USART_SendData(USART1 , 0x55);    //发送单个数值
        while(USART_GetFlagStatus(USART1, USART_FLAG_TC)==RESET); //检查发送中断标志位
    
        /* 发送方法2 */
        //	printf("STM32F103 ");          //纯字符串发送数据到串口
        //	printf("STM32 %d %d ",a,b);    //纯字符串和变量发送数据到串口,a符号变量
            
        /* 发送方法3 */
        //	USART1_printf("STM32 %d %d ",a,b);
    
        delay_ms(1000);                    //延时
      }
    }
    
    • printf - c语言自带的显示函数,在单片机中默认发送给串口
    • 固件库函数 —— USART_ SendData
    函数名USART_ SendData
    函数原形void USART_SendData(USART_TypeDef* USARTx, u8 Data)
    功能描述通过外设 USARTx 发送单个数据
    输入参数 1USARTx:x 可以是 1,2 或者 3,来选择 USART 外设
    输入参数 2Data: 待发送的数据
    输出参数
    返回值
    先决条件
    被调用函数

4、控制程序

  • m a i n . c main.c main.c
    主循环:

    while(1)
    {
        //查询方式接收
        if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) != RESET)
        {                                                               //查询串口待处理标志位
          a =USART_ReceiveData(USART1);                           //读取接收到的数据
          switch (a)
          {
            case '0':
            GPIO_WriteBit(LEDPORT,LED1,(BitAction)(0));                 //LED控制
              printf("%c:LED1 OFF ",a); //
              break;
            case '1':
            GPIO_WriteBit(LEDPORT,LED1,(BitAction)(1));                 //LED控制
              printf("%c:LED1 ON ",a); //
              break;
            case '2':
              BUZZER_BEEP1();                                 //蜂鸣一声
              printf("%c:BUZZER ",a);                         //把收到的数据发送回电脑
              break;
              default:
              break;
          }		  
        }
    
        //按键控制
        if(!GPIO_ReadInputDataBit(KEYPORT,KEY1))
        {                                                        //读按键接口的电平
          delay_ms(20);                                    //延时20ms去抖动
          if(!GPIO_ReadInputDataBit(KEYPORT,KEY1))
          {                                                      //读按键接口的电平
            while(!GPIO_ReadInputDataBit(KEYPORT,KEY1)); //等待按键松开 
            printf("KEY1 "); //
          }
        }		 
        if(!GPIO_ReadInputDataBit(KEYPORT,KEY2))
        {                                                        //读按键接口的电平
          delay_ms(20);                                    //延时20ms去抖动
          if(!GPIO_ReadInputDataBit(KEYPORT,KEY2))
          {                                                      //读按键接口的电平
            while(!GPIO_ReadInputDataBit(KEYPORT,KEY2)); //等待按键松开 
            printf("KEY2 "); //
          }
        }		 
    //      delay_ms(1000); //延时
    }
    
    • 发送串口指令,控制LED、蜂鸣器,并串口显示
    • 按键按下,串口显示

5、超级终端

5.1 简介

可以进行更强大的串口开发

5.2 安装

安装 hyper_terminal_latest

5.3 配置
  • 设置串口,波特率,编码类型(可中文)
  • 设置文本、背景等颜色
5.4 控制程序
  • u s a r t . c usart.c usart.c:
    中断函数:
    void USART1_IRQHandler(void)
    { //串口1中断服务程序(固定的函数名不能修改)	
      u8 Res;
      //以下是字符串接收到USART_RX_BUF[]的程序,(USART_RX_STA&0x3FFF)是数据的长度(不包括回车)
      //当(USART_RX_STA&0xC000)为真时表示数据接收完成,即超级终端里按下回车键。
      //在主函数里写判断if(USART_RX_STA&0xC000),然后读USART_RX_BUF[]数组,读到0x0d 0x0a即是结束。
      //注意在主函数处理完串口数据后,要将USART_RX_STA清0
      if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
      {  //接收中断(接收到的数据必须是0x0d 0x0a结尾)		
        Res =USART_ReceiveData(USART1);//(USART1->DR);	//读取接收到的数据
        printf("%c",Res); //把收到的数据以 a符号变量 发送回电脑		
        if((USART1_RX_STA&0x8000)==0)
        {//接收未完成			
          if(USART1_RX_STA&0x4000)
          {//接收到了0x0d				
            if(Res!=0x0a)
            {
              USART1_RX_STA=0;//接收错误,重新开始
            }
            else
            {
              USART1_RX_STA|=0x8000;	//接收完成了 
            }
          }
          else
          { //还没收到0X0D					
            if(Res==0x0d)
            {
              USART1_RX_STA|=0x4000;
            }
            else
            {
              USART1_RX_BUF[USART1_RX_STA&0X3FFF]=Res ; //将收到的数据放入数组
              USART1_RX_STA++;	//数据长度计数加1
              if(USART1_RX_STA>(USART1_REC_LEN-1))
              {
                USART1_RX_STA=0;//接收数据错误,重新开始接收	  
              }
            }		 
          }
        }
      }
    }
    
  • m a i n . c main.c main.c :
    主函数:
    int main (void)
    {//主程序
      RCC_Configuration();
      LED_Init();//LED初始化
      KEY_Init();//按键初始化
      BUZZER_Init();//蜂鸣器初始化
      USART1_Init(115200); //串口初始化,参数中写波特率
      USART1_RX_STA=0xC000; //初始值设为有回车的状态,即显示一次欢迎词
      while(1)
      {
        if(USART1_RX_STA&0xC000)
        {                                         //如果标志位是0xC000表示收到数据串完成,可以处理。
          if((USART1_RX_STA&0x3FFF)==0)
          {                                       //单独的回车键再显示一次欢迎词
            printf("\033[1;47;33m\r\n");          //设置颜色(参考超级终端使用)
            printf(" 1y--开LED1灯      1n--关LED1灯 \r\n");
            printf(" 2y--开LED2灯      2n--关LED2灯 \r\n");
            printf(" 请输入控制指令,按回车键执行! \033[0m\r\n");
          }
          else if((USART1_RX_STA&0x3FFF)==2 && USART1_RX_BUF[0]=='1' && USART1_RX_BUF[1]=='y')
          {                                       //判断数据是不是2个,第一个数据是不是“1”,第二个是不是“y”
            GPIO_SetBits(LEDPORT,LED1);           //LED灯都为高电平(1)
            printf("1y -- LED1灯已经点亮!\r\n");
          }
          else if((USART1_RX_STA&0x3FFF)==2 && USART1_RX_BUF[0]=='1' && USART1_RX_BUF[1]=='n')
          {
            GPIO_ResetBits(LEDPORT,LED1); //      //LED灯都为低电平(0)
            printf("1n -- LED1灯已经熄灭!\r\n");
          }
          else if((USART1_RX_STA&0x3FFF)==2 && USART1_RX_BUF[0]=='2' && USART1_RX_BUF[1]=='y')
          {
            GPIO_SetBits(LEDPORT,LED2);           //LED灯都为高电平(1)
            printf("2y -- LED2灯已经点亮!\r\n");
          }
          else if((USART1_RX_STA&0x3FFF)==2 && USART1_RX_BUF[0]=='2' && USART1_RX_BUF[1]=='n')
          {
            GPIO_ResetBits(LEDPORT,LED2);         //LED灯都为低电平(0)
            printf("2n -- LED2灯已经熄灭!\r\n");
          }
          else
          {                                       //如果以上都不是,即是错误的指令。
            printf("指令错误!\r\n"); 
          }
          USART1_RX_STA=0;                        //将串口数据标志位清0
        }
      }
    }
    
    • i f , e l s e if,else ifelse 判断: 判断数据是不是2个,第一个数据是不是 “1” ,第二个是不是 “y”。

四、RTC原理驱动程序和日历功能


RTC

1、RTC(实时时钟)和后备寄存器

RTC 和后备寄存器通过一个开关供电,在 V D D V_{DD} VDD 有效时该开关选择 V D D V_{DD} VDD 供电,否则由 V B A T V_{BAT} VBAT 引脚供电。后备寄存器(10个16位的寄存器)可以用于在关闭 V D D V_{DD} VDD 时,保存 20 个字节的用户应用数据。RTC 和后备寄存器不会被系统或电源复位源复位;当从待机模式唤醒时,也不会被复位。
实时时钟具有一组连续运行的计数器,可以通过适当的软件提供日历时钟功能,还具有闹钟中断和阶段性中断功能。RTC 的驱动时钟可以是一个使用外部晶体的 32.768kHz 的振荡器、内部低功耗 RC 振荡器或高速的外部时钟经 128 分频。内部低功耗 RC 振荡器的典型频率为 40KHz。为补偿天然晶体的偏差,可以通过输出一个 512Hz 的信号对 RTC 的时钟进行校准。RTC 具有一个 32 位的可编程计数器,使用比较寄存器可以进行长时间的测量。有一个 20 位的预分频器用于时基时钟,默认情况下时钟为 32.768kHz 时,它将产生一个 1秒长的时间基准。

2、控制程序

  • 时间读写设置说明:

    • main 函数开头放入 RTC_Config(); 就可以使能时钟了。
      RTC_Config(); 函数中自带判断是不是首次使用RTC
    • 使用 RTC_Get(); 读出时间。读出的数据存放在:
      年 ryear(16位)月 rmon(以下都是8位)日 rday时 rhour分 rmin秒 rsec周 rweek
    • 使用 RTC_Set(4位年,2位月,2位日,2位时,2位分,2位秒); 写入时间。 例如: RTC_Get(2017,08,06,21,34,00);
  • r t c . c rtc.c rtc.c :

    • rtc 启动配置(初次启动和初始化)):
      void RTC_First_Config(void)
      { //首次启用RTC的设置
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);//启用PWR和BKP的时钟(from APB1)
        PWR_BackupAccessCmd(ENABLE);                                            //后备域解锁
        BKP_DeInit();                                                           //备份寄存器模块复位
        RCC_LSEConfig(RCC_LSE_ON);                                              //外部32.768KHZ晶振开启   
        while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET);                    //等待稳定    
        RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);                                 //RTC时钟源配置成LSE(外部低速晶振32.768KHZ)    
        RCC_RTCCLKCmd(ENABLE);                                                  //RTC开启    
        RTC_WaitForSynchro();                                                   //开启后需要等待APB1时钟与RTC时钟同步,才能读写寄存器    
        RTC_WaitForLastTask();                                                  //读写寄存器前,要确定上一个操作已经结束
        RTC_SetPrescaler(32767);//设置RTC分频器,使RTC时钟为1Hz,RTC period = RTCCLK/RTC_PR = (32.768 KHz)/(32767+1)   
        RTC_WaitForLastTask();                                                  //等待寄存器写入完成	
        
        //当不使用RTC秒中断,可以屏蔽下面2条
        RTC_ITConfig(RTC_IT_SEC, ENABLE);//使能秒中断   
        RTC_WaitForLastTask();//等待写入完成
      }
      
      void RTC_Config(void)
      {   //实时时钟初始化
          //在BKP的后备寄存器1中,存了一个特殊字符0xA5A5
          //第一次上电或后备电源掉电后,该寄存器数据丢失,表明RTC数据丢失,需要重新配置
        if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5)
        { //判断寄存数据是否丢失       
          RTC_First_Config();                         //重新配置RTC        
          BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);   //配置完成后,向后备寄存器中写特殊字符0xA5A5
        }
        else
        {
          //若后备寄存器没有掉电,则无需重新配置RTC
          //这里我们可以利用RCC_GetFlagStatus()函数查看本次复位类型
          if (RCC_GetFlagStatus(RCC_FLAG_PORRST) != RESET)
          {
                  //这是上电复位
          }
          else if (RCC_GetFlagStatus(RCC_FLAG_PINRST) != RESET)
          {
                  //这是外部RST管脚复位
          }       
          RCC_ClearFlag();                            //清除RCC中复位标志
      
          //虽然RTC模块不需要重新配置,且掉电后依靠后备电池依然运行
          //但是每次上电后,还是要使能RTCCLK
          RCC_RTCCLKCmd(ENABLE);                      //使能RTCCLK        
          RTC_WaitForSynchro();                       //等待RTC时钟与APB1时钟同步
      
          //当不使用RTC秒中断,可以屏蔽下面2条
          RTC_ITConfig(RTC_IT_SEC, ENABLE);           //使能秒中断        
          RTC_WaitForLastTask();                      //等待操作完成
        }
        #ifdef RTCClockOutput_Enable   
          RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
          PWR_BackupAccessCmd(ENABLE);   
          BKP_TamperPinCmd(DISABLE);   
          BKP_RTCOutputConfig(BKP_RTCOutputSource_CalibClock);
        #endif
      }
          ```
      
  • 中断:

    void RTC_IRQHandler(void)
    { //RTC时钟1秒触发中断函数(名称固定不可修改)
    if (RTC_GetITStatus(RTC_IT_SEC) != RESET)
    {}
    RTC_ClearITPendingBit(RTC_IT_SEC); 
    RTC_WaitForLastTask();
    }
    
    void RTCAlarm_IRQHandler(void)
    {	//闹钟中断处理(启用时必须调高其优先级)
    if(RTC_GetITStatus(RTC_IT_ALR) != RESET)
    {}
    RTC_ClearITPendingBit(RTC_IT_ALR);
    RTC_WaitForLastTask();
    } 
    
  • 时间读写计算:

    //判断是否是闰年函数
    //月份   1  2  3  4  5  6  7  8  9  10 11 12
    //闰年   31 29 31 30 31 30 31 31 30 31 30 31
    //非闰年 31 28 31 30 31 30 31 31 30 31 30 31
    //输入:年份
    //输出:该年份是不是闰年.1,是.0,不是
    u8 Is_Leap_Year(u16 year)
    {                    
      if(year%4==0)
      { //必须能被4整除
        if(year%100==0)
        {		
          if(year%400==0)
          {
            return 1;//如果以00结尾,还要能被400整除          
          } 
          else
          { 
            return 0;             
          } 
        }
        else
        {
          return 1; 
        } 
      }
      else
      { 
        return 0;             
      } 
    }                           
    //设置时钟
    //把输入的时钟转换为秒钟
    //以1970年1月1日为基准
    //1970~2099年为合法年份
    
    //月份数据表                                                                       
    u8 const table_week[12]={0,3,3,6,1,4,6,2,5,0,3,5}; //月修正数据表  
    const u8 mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31};//平年的月份日期表
    
    //写入时间
    u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec)
    { //写入当前时间(1970~2099年有效),
      u16 t;
      u32 seccount=0;
      if(syear<2000||syear>2099)
      {
        return 1;//syear范围1970-2099,此处设置范围为2000-2099       
      }
      for(t=1970;t<syear;t++)
      { //把所有年份的秒钟相加
        if(Is_Leap_Year(t))seccount+=31622400;//闰年的秒钟数
        else seccount+=31536000;                    //平年的秒钟数
      }
      smon-=1;
      for(t=0;t<smon;t++)
      {         //把前面月份的秒钟数相加
        seccount+=(u32)mon_table[t]*86400;//月份秒钟数相加
        if(Is_Leap_Year(syear)&&t==1)
        {
          seccount+=86400;//闰年2月份增加一天的秒钟数        
        }
      }
      seccount+=(u32)(sday-1)*86400;//把前面日期的秒钟数相加
      seccount+=(u32)hour*3600;//小时秒钟数
      seccount+=(u32)min*60;      //分钟秒钟数
      seccount+=sec;//最后的秒钟加上去
      RTC_First_Config(); //重新初始化时钟
      BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);//配置完成后,向后备寄存器中写特殊字符0xA5A5
      RTC_SetCounter(seccount);//把换算好的计数器值写入
      RTC_WaitForLastTask(); //等待写入完成
      return 0; //返回值:0,成功;其他:错误代码.    
    }
    
    //读出时间
    u8 RTC_Get(void)
    {//读出当前时间值 //返回值:0,成功;其他:错误代码.
      static u16 daycnt=0;
      u32 timecount=0;
      u32 temp=0;
      u16 temp1=0;
      timecount=RTC_GetCounter();		
      temp=timecount/86400;   //得到天数(秒钟数对应的)
      if(daycnt!=temp)
      {//超过一天了
        daycnt=temp;
        temp1=1970;  //从1970年开始
        while(temp>=365)
        {
          if(Is_Leap_Year(temp1))
          {//是闰年
            if(temp>=366)
            {
              temp-=366;//闰年的秒钟数
            }
            else 
            {
              temp1++;
              break;
            } 
          }
          else 
          {
            temp-=365;       //平年
          }
          temp1++; 
        }  
        ryear=temp1;//得到年份
        temp1=0;
        while(temp>=28)
        {//超过了一个月
          if(Is_Leap_Year(ryear)&&temp1==1)
          {//当年是不是闰年/2月份
            if(temp>=29)
            {
              temp-=29;//闰年的秒钟数
            }
            else 
            {
              break;
            }
          }
          else
          {
            if(temp>=mon_table[temp1])
            {
              temp-=mon_table[temp1];//平年
            }
            else 
            {
              break;
            }
          }
          temp1++; 
        }
        rmon=temp1+1;//得到月份
        rday=temp+1;  //得到日期
      }
      temp=timecount%86400;     //得到秒钟数      
      rhour=temp/3600;     //小时
      rmin=(temp%3600)/60; //分钟     
      rsec=(temp%3600)%60; //秒钟
      rweek=RTC_Get_Week(ryear,rmon,rday);//获取星期  
      return 0;
    }    
    
    u8 RTC_Get_Week(u16 year,u8 month,u8 day)
    { //按年月日计算星期(只允许1901-2099年)//已由RTC_Get调用    
      u16 temp2;
      u8 yearH,yearL;
      yearH=year/100;     
      yearL=year%100;
      // 如果为21世纪,年份数加100 
      if (yearH>19)
      {
        yearL+=100;
      }
      // 所过闰年数只算1900年之后的 
      temp2=yearL+yearL/4;
      temp2=temp2%7;
      temp2=temp2+day+table_week[month-1];
      if (yearL%4==0&&month<3)
      {
        temp2--;
      }
      return(temp2%7); //返回星期值
    }
    

3、RTC库函数

函数名描述
RTC_ITConfig使能或者失能指定的RTC中断
RTC_EntcrConfigModc进入RTC配置模式
RTC_ExitConfigMode退出RTC配置模式
RTC_GetCounter获取RTC计数器的值
RTC_SetCounter设置RTC计数器的值
RTC_SetPrescaler设置RTC预分频的值
RTC_SetAlarm设置RTC闹钟的值
RTC_GetDivider获取RTC预分频分频因子的值
RTC_WaitForLastTask等待最近一次对RTC寄存器的写操作完成
RTC_WaitForSynchro等待RTC寄存器(RTC_CNT,RTC_ALR and RTC_PRL)与RTC的APB时钟同步
RTC_GetFlagStatus检查指定的RTC标志位设置与否
RTC_ClearFlag清除RTC的待处理标志位
RTC_GetITStatus检查指定的RTC中断发生与否
RTC_ClearITPendingBit清除RTC的中断待处理位

4、超级终端显示日历

  • m a i n . c main.c main.c :
    int main (void){//主程序
      u8 bya;
      RCC_Configuration(); //系统时钟初始化
      RTC_Config(); //实时时钟初始化
      LED_Init();//LED初始化
      KEY_Init();//按键初始化
      BUZZER_Init();//蜂鸣器初始化
      USART1_Init(115200); //串口初始化,参数中写波特率
      USART1_RX_STA=0xC000; //初始值设为有回车的状态,即显示一次欢迎词
      while(1)
      {
        if(USART1_RX_STA&0xC000)
        { //如果标志位是0xC000表示收到数据串完成,可以处理。
          if((USART1_RX_STA&0x3FFF)==0)
          { //单独的回车键再显示一次欢迎词
            if(RTC_Get()==0)
            { //读出时间值,同时判断返回值是不是0,非0时读取的值是错误的。
              printf(" 洋桃开发板STM32实时时钟测试程序   \r\n");
              printf(" 现在实时时间:%d-%d-%d %d:%d:%d  ",ryear,rmon,rday,rhour,rmin,rsec);//显示日期时间
              if(rweek==0)printf("星期日   \r\n");//rweek值为0时表示星期日
              if(rweek==1)printf("星期一   \r\n");
              if(rweek==2)printf("星期二   \r\n");
              if(rweek==3)printf("星期三   \r\n");
              if(rweek==4)printf("星期四   \r\n");
              if(rweek==5)printf("星期五   \r\n");
              if(rweek==6)printf("星期六   \r\n");
              printf(" 单按回车键更新时间。输入字母C初始化时钟 \r\n");
              printf(" 请输入设置时间,格式20170806120000,按回车键确定! \r\n");
            }
            else
            {
              printf("读取失败!\r\n");
            }
          }
          else if((USART1_RX_STA&0x3FFF)==1)
          { //判断数据是不是2个
            if(USART1_RX_BUF[0]=='c' || USART1_RX_BUF[0]=='C')
            {
              RTC_First_Config(); //键盘输入c或C,初始化时钟
              BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);//配置完成后,向后备寄存器中写特殊字符0xA5A5
              printf("初始化成功!      \r\n");//显示初始化成功
            }
            else
            {
              printf("指令错误!          \r\n"); //显示指令错误!
            } 
          }
          else if((USART1_RX_STA&0x3FFF)==14)
          { //判断数据是不是14个
            //将超级终端发过来的数据换算并写入RTC
            ryear = (USART1_RX_BUF[0]-0x30)*1000+(USART1_RX_BUF[1]-0x30)*100+(USART1_RX_BUF[2]-0x30)*10+USART1_RX_BUF[3]-0x30;
            rmon = (USART1_RX_BUF[4]-0x30)*10+USART1_RX_BUF[5]-0x30;//串口发来的是字符,减0x30后才能得到十进制0~9的数据
            rday = (USART1_RX_BUF[6]-0x30)*10+USART1_RX_BUF[7]-0x30;
            rhour = (USART1_RX_BUF[8]-0x30)*10+USART1_RX_BUF[9]-0x30;
            rmin = (USART1_RX_BUF[10]-0x30)*10+USART1_RX_BUF[11]-0x30;
            rsec = (USART1_RX_BUF[12]-0x30)*10+USART1_RX_BUF[13]-0x30;
            bya=RTC_Set(ryear,rmon,rday,rhour,rmin,rsec); //将数据写入RTC计算器的程序
            if(bya==0)printf("写入成功!      \r\n");//显示写入成功 
            else printf("写入失败!       \r\n"); //显示写入失败
          }
          else
          { //如果以上都不是,即是错误的指令。
            printf("指令错误!          \r\n"); //如果不是以上正确的操作,显示指令错误!
          }
          USART1_RX_STA=0; //将串口数据标志位清0
        }
      }
    }
    

五、RCC(复位和时钟设置)

1、RCC寄存器

寄存器描述
CR时钟控制寄存器
CFGR时钟配置寄存器
CIR时钟中断寄存器
APB2RSTRAPB2外设复位寄存器
APB1RSTRAPB1外设复位寄存器
AHBENRAHB外设时钟使能寄存器
APB2ENRAPB2外设时钟使能寄存器
APB1ENRAPB1外设时钟使能寄存器
BDCR备份域控制寄存器
CSR控制/状态寄存器

2、RCC相关函数

函数名描述
RCC_DeInit将外设RCC寄存器重设为缺省值
RCC_HSEConfig设置外部高速晶振(HSE)
RCc_WaitForHSEStartUp等待HSE起振
RCC_AdjustHSICalibrationValue调整内部高速晶振(HSI)校准值
RCC_ HSICmd使能或者失能内部高速晶振(HSI)
RCC_PLLConfig设置PLL时钟源及倍频系数
RCC_PLLCmd使能或者失能PLL
RCC_sYsCLKConfig设置系统时钟(sYSCLK)
RCC GetsYSCLKSource返回用作系统时钟的时钟源
RCC_HCLKConfig设置AHB时钟(HCLK)
RCC_PCLK1Config设置低速AHB时钟(PCLK1)
RCC_PCLK2Config设置高速AHB时钟(PCLK2)
RCC_ITConfig使能或者失能指定的RCC中断
RCC_USBCLKConfig设置USB时钟(USBCLK)
RCC_ADCCLKConfig设置ADC时钟(ADCCLK)
RCC_LSEConfig设置外部低速晶振(LSE)
RCC_LSICmd使能或者失能内部低速晶振(LSI)
RCC_RTCCLKConfig设置RTC时钟(RTCCLK)
RCC_RTCCLKCmd使能或者失能RTC时钟
RCC_GetClocksFreq返回不同片上时钟的频率
RCC_AHBPeriphClockCmd使能或者失能AHB外设时钟
RCC_APB2PeriphClockCmd使能或者失能APB2外设时钟
RCC_APB1PeriphClockCmd使能或者失能APB1外设时钟
RCC_APB2PeriphResetCmd强制或者释放高速APB (APB2)外设复位
RCC_APBlPeriphResetCmd强制或者释放低速APB (APB1)外设复位
RCC_BackupResetCmd强制或者释放后备域复位
RCC_ClockSecuritySystemCmd使能或者失能时钟安全系统
RCC_MCOConfig选择在MCO管脚上输出的时钟源
RCC_GetFlagStatus检查指定的RCC标志位设置与否
RCC_ClearFlag清除RCC的复位标志位
RCC_GetITStatus检查指定的RCC中断发生与否
RCC_ClearITPendingBit清除RCC的中断待处理位
  • R C C _   C o n f i g u r a t i o n ( v o i d ) RCC\_ \ Configuration(void){} RCC_ Configuration(void) 配置时钟树:

      void RCC_Configuration(void)
      { //RCC时钟的设置  
        ErrorStatus HSEStartUpStatus;   
        RCC_DeInit();              /* RCC system reset(for debug purpose) RCC寄存器恢复初始化值*/   
        RCC_HSEConfig(RCC_HSE_ON); /* Enable HSE 使能外部高速晶振*/   
        HSEStartUpStatus = RCC_WaitForHSEStartUp(); /* Wait till HSE is ready 等待外部高速晶振使能完成*/   
        if(HSEStartUpStatus == SUCCESS)
        {   
          /*设置PLL时钟源及倍频系数*/   
          RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9); //RCC_PLLMul_x(枚举2~16)是倍频值。当HSE=8MHZ,RCC_PLLMul_9时PLLCLK=72MHZ   
          /*设置AHB时钟(HCLK)*/   
          RCC_HCLKConfig(RCC_SYSCLK_Div1); //RCC_SYSCLK_Div1——AHB时钟 = 系统时钟(SYSCLK) = 72MHZ(外部晶振8HMZ)   
          /*注意此处的设置,如果使用SYSTICK做延时程序,此时SYSTICK(Cortex System timer)=HCLK/8=9MHZ*/   
          RCC_PCLK1Config(RCC_HCLK_Div2); //设置低速AHB时钟(PCLK1),RCC_HCLK_Div2——APB1时钟 = HCLK/2 = 36MHZ(外部晶振8HMZ)   
          RCC_PCLK2Config(RCC_HCLK_Div1); //设置高速AHB时钟(PCLK2),RCC_HCLK_Div1——APB2时钟 = HCLK = 72MHZ(外部晶振8HMZ)   
          /*注:AHB主要负责外部存储器时钟。APB2负责AD,I/O,高级TIM,串口1。APB1负责DA,USB,SPI,I2C,CAN,串口2,3,4,5,普通TIM */  
          FLASH_SetLatency(FLASH_Latency_2); //设置FLASH存储器延时时钟周期数   
          /*FLASH时序延迟几个周期,等待总线同步操作。   
          推荐按照单片机系统运行频率:
          0—24MHz时,取Latency_0;   
          24—48MHz时,取Latency_1;   
          48~72MHz时,取Latency_2*/   
          FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable); //选择FLASH预取指缓存的模式,预取指缓存使能   
          RCC_PLLCmd(ENABLE);	//使能PLL
          while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET); //等待PLL输出稳定   
          RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); //选择SYSCLK时钟源为PLL
          while(RCC_GetSYSCLKSource() != 0x08); //等待PLL成为SYSCLK时钟源   
        }  
      }
    
  • 开启需要使用的外设时钟

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB |RCC_APB2Periph_GPIOC| RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE, ENABLE); //APB2外设时钟使能      
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); //APB1外设时钟使能  
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);   
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);   	 
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);  
    

3、时钟树


RTC

左——产生频率
右——分配频率

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

frozendure

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值