接上文,文中的图片,大多数来自视频的截图(来自洋桃电子)。
欢迎大家批评指正!
文章目录
一、蜂鸣器驱动
1、蜂鸣器介绍
有源蜂鸣器 | 无源蜂鸣器 |
---|---|
内置频率发生电路 | 内部没有发生电路 |
通电就能发出声音 | 需要外部给予频率 |
声音频率固定 | 可产生不同频率声音 |
成本较高 | 成本低 |
- 两种蜂鸣器外观上无区别
2、蜂鸣器电路
![](https://i-blog.csdnimg.cn/blog_migrate/f1daef8e1a78878e5c65981ccecf4eb4.png)
核心板上的蜂鸣器
![](https://i-blog.csdnimg.cn/blog_migrate/e7ac167690b4b3bf346a222d6307c6b6.png)
核心板蜂鸣器电路
- 不用蜂鸣器时,应对 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
BUZZER
BUZZER 的端口组和
I
O
IO
IO 端口。
-
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 放映原理
![](https://i-blog.csdnimg.cn/blog_migrate/245f465d18ae8b560485e4e8216fb418.png)
音符频率对照
- 按音符控制发生频率
- 按节拍调整一个音符的播放时间
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、串口电路
![](https://i-blog.csdnimg.cn/blog_migrate/37407e50b8f119c4e2384f2dd1e18951.png)
核心板串口芯片
![](https://i-blog.csdnimg.cn/blog_migrate/f5b8c7385d264e29db78ad93d0da8711.png)
核心板串口电路
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 发送单个数据 输入参数 1 USARTx:x 可以是 1,2 或者 3,来选择 USART 外设 输入参数 2 Data: 待发送的数据 输出参数 无 返回值 无 先决条件 无 被调用函数 无
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 if,else 判断: 判断数据是不是2个,第一个数据是不是 “1” ,第二个是不是 “y”。
四、RTC原理驱动程序和日历功能
![](https://i-blog.csdnimg.cn/blog_migrate/0c2708a7207763582936b554c7f987c9.png)
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);
- 在 main 函数开头放入 RTC_Config(); 就可以使能时钟了。
-
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 } ```
- rtc 启动配置(初次启动和初始化)):
-
中断:
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 | 时钟中断寄存器 |
APB2RSTR | APB2外设复位寄存器 |
APB1RSTR | APB1外设复位寄存器 |
AHBENR | AHB外设时钟使能寄存器 |
APB2ENR | APB2外设时钟使能寄存器 |
APB1ENR | APB1外设时钟使能寄存器 |
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、时钟树
![](https://i-blog.csdnimg.cn/blog_migrate/4f83188a4f4289e7fa9e2171c8034688.png)
RTC
左——产生频率
右——分配频率