《STM32学习笔记》4——核心功能电路与编程(下)
接上文,文中的图片,大多数来自视频的截图(来自洋桃电子)。
欢迎大家批评指正!
STM32学习笔记-专栏
文章目录
- 一、蜂鸣器驱动
- 1、蜂鸣器介绍
- 2、蜂鸣器电路
- 3、蜂鸣器程序
- 二、 MIDI 音乐播放
- 1、MIDI 简介
- 2、MIDI 放映原理
- 3、MIDI 例程
- 三、USART 串口通信
- 1、简介
- 2、串口电路
- 3 收发程序
- 3.1 相关固件及函数
- 3.2 程序控制
- 4、控制程序
- 5、超级终端
- 5.1 简介
- 5.2 安装
- 5.3 配置
- 5.4 控制程序
- 四、RTC原理驱动程序和日历功能
- 1、RTC(实时时钟)和后备寄存器
- 2、控制程序
- 3、RTC库函数
- 4、超级终端显示日历
- 五、RCC(复位和时钟设置)
- 1、RCC寄存器
- 2、RCC相关函数
- 3、时钟树
一、蜂鸣器驱动
1、蜂鸣器介绍
有源蜂鸣器 | 无源蜂鸣器 |
---|---|
内置频率发生电路 | 内部没有发生电路 |
通电就能发出声音 | 需要外部给予频率 |
声音频率固定 | 可产生不同频率声音 |
成本较高 | 成本低 |
- 两种蜂鸣器外观上无区别
2、蜂鸣器电路
核心板上的蜂鸣器
核心板蜂鸣器电路
- 不用蜂鸣器时,应对 PB5PB5PB5 口拉高(此处电路接有上拉电阻),控制蜂鸣器断开,不工作。
- 给 PB5PB5PB5口拉低电平,蜂鸣器上电导通,但由于是无源蜂鸣器,单纯导通只会发热,不会发声。需要给 PB5PB5PB5 不断高低变换的电平(方波),才可以发出声音。
3、蜂鸣器程序
在 hardwarehardwarehardware 导入 buzzer.cbuzzer.cbuzzer.c 文件
- buzzer.hbuzzer.hbuzzer.h :
- 宏定义:BUZZERBUZZERBUZZER 的端口组和 IOIOIO 端口。
#define BUZZERPORT GPIOB //定义IO接口 #define BUZZER GPIO_Pin_5 //定义IO接口
- 声明:初始化函数和响声函数。
void BUZZER_Init(void); //初始化 void BUZZER_BEEP1(void); //响一声
- 宏定义:BUZZERBUZZERBUZZER 的端口组和 IOIOIO 端口。
buzzer.cbuzzer.cbuzzer.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)); //蜂鸣器接口输出0delay_us(500); //延时 GPIO_WriteBit(BUZZERPORT,BUZZER,(BitAction)(1)); //蜂鸣器接口输出高电平1delay_us(500); //延时 }}
最后一条语句必须是,拉高电平,断开蜂鸣器,保护电路
- 初始化:
main.cmain.cmain.c:
int main (void) {//主程序u16 a; //定义变量//初始化程序RCC_Configuration(); //时钟设置LED_Init(); //LED初始化KEY_Init(); //按键初始化BUZZER_Init(); //蜂鸣器初始化BUZZER_BEEP1(); //蜂鸣器音1a = FLASH_R(FLASH_START_ADDR); //从指定页的地址读FLASHGPIO_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++; //变量加1if(a>3){ //当变量大于3时清0a=0; }GPIO_Write(LEDPORT,a|0xfffc&GPIO_ReadOutputData(LEDPORT)); //直接数值操作将变量值写入LED(LED在GPIOB组的PB0和PB1上) BUZZER_BEEP1(); //蜂鸣器音1FLASH_W(FLASH_START_ADDR,a); //从指定页的地址写入FLASHwhile(!GPIO_ReadInputDataBit(KEYPORT,KEY1)); //等待按键松开 }}}}
功能为:上电响一声,按键每按下一次,响一声,多次按下,灯亮循环
二、 MIDI 音乐播放
1、MIDI 简介
MIDI (MusicalInstrumentDigitalInterface)(Musical Instrument Digital Interface)(MusicalInstrumentDigitalInterface) 乐器数字接口,是20世纪80年代初为解决电声乐器之间的通信问题而提出的。MIDI 是编曲界最广泛的音乐标准格式,可称为“计算机能理解的乐谱”。它用音符的数字控制信号来记录音乐。一首完整的 MIDI 音乐只有几十 KB 大,而能包含数十条音乐轨道。几乎所有的现代音乐都是用 MIDI 加上音色库来制作合成的。MIDI 传输的不是声音信号,而是音符、控制参数等指令,它指示 MIDI 设备要做什么,怎么做,如演奏哪个音符、多大音量等。它们被统一表示成 MIDI 消息。
传输时采用异步串行通信,标准通信波特率为 31.25×( 1±0.01) KBaud。
2、MIDI 放映原理
音符频率对照
- 按音符控制发生频率
- 按节拍调整一个音符的播放时间
3、MIDI 例程
- buzzer.cbuzzer.cbuzzer.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)); //蜂鸣器接口输出0delay_us(500000/music1[i*2]); //延时 GPIO_WriteBit(BUZZERPORT,BUZZER,(BitAction)(1)); //蜂鸣器接口输出高电平1delay_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。
usart.husart.husart.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)串口3extern 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
- 宏定义
- 串口选择使能
- 基本配置
usart.cusart.cusart.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.9GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出GPIO_Init(GPIOA, &GPIO_InitStructure); //USART1_RX PA.10GPIO_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 ;//抢占优先级3NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级3NVIC_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清0if(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++; //数据长度计数加1if(USART1_RX_STA>(USART1_REC_LEN-1)){USART1_RX_STA=0; //接收数据错误,重新开始接收 }} }} } } #endif
- 基本配置——状态
- printf 函数定义
- 串口初始化
- 串口的 TX 和 RX 口,需要进行 IO 的配置,如 IO 初始化、IO端口模式配置等
- 波特率、数据长度、特殊标志位、模式配置、开启中断、使能 等
- 串口中断
main.cmain.cmain.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、控制程序
main.cmain.cmain.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 控制程序
- usart.cusart.cusart.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清0if(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++; //数据长度计数加1if(USART1_RX_STA>(USART1_REC_LEN-1)){USART1_RX_STA=0;//接收数据错误,重新开始接收 }} }}} }
- main.cmain.cmain.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}} }
- if,elseif,elseif,else 判断: 判断数据是不是2个,第一个数据是不是 “1” ,第二个是不是 “y”。
四、RTC原理驱动程序和日历功能
RTC
1、RTC(实时时钟)和后备寄存器
RTC 和后备寄存器通过一个开关供电,在 VDDV_{DD}VDD 有效时该开关选择 VDDV_{DD}VDD 供电,否则由 VBATV_{BAT}VBAT 引脚供电。后备寄存器(10个16位的寄存器)可以用于在关闭 VDDV_{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(); 就可以使能时钟了。
rtc.crtc.crtc.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模块不需要重新配置,且掉电后依靠后备电池依然运行//但是每次上电后,还是要使能RTCCLKRCC_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);//配置完成后,向后备寄存器中写特殊字符0xA5A5RTC_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、超级终端显示日历
- main.cmain.cmain.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);//配置完成后,向后备寄存器中写特殊字符0xA5A5printf("初始化成功! \r\n");//显示初始化成功}else{printf("指令错误! \r\n"); //显示指令错误!} }else if((USART1_RX_STA&0x3FFF)==14){ //判断数据是不是14个//将超级终端发过来的数据换算并写入RTCryear = (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的中断待处理位 |
RCC_Configuration(void)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); //使能PLLwhile(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET); //等待PLL输出稳定 RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); //选择SYSCLK时钟源为PLLwhile(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
左——产生频率
右——分配频率
《STM32学习笔记》4——核心功能电路与编程(下)相关推荐
- 《STM32学习笔记》3——核心功能电路与编程(上)
接上文,文中的图片,大多数来自视频的截图(来自洋桃电子). 欢迎大家批评指正! STM32学习笔记-专栏 文章目录 一.核心板电路分析 二.点灯 LED 1.LED电路 2.LED功能相关初始化配置 ...
- STM32学习笔记——CH340一键下载电路
今天看了一下STM32板子的一键下载功能,对电路理解了一下,做个说明. 1.启动模式 STM32上电后的BOOT1/0的电平状态会决定它的运行模式.很明显,我们如果上电后要进入自己写的程序并运行,那么 ...
- STM32学习笔记1——软硬件基础之keil5编程与GPIO开发
目录 前言 一.Keil uVision5 MDK软件的安装以及一些问题的解决方案 1.1 软件安装 1.2 注意事项 二. keil5编程与GPIO开发 2.1 第一步:使能IO口时钟 2.2 第二 ...
- STM32学习笔记(四)丨TIM定时器及其应用(定时中断、内外时钟源选择)
本篇文章包含的内容 一.TIM 定时器 1.1 TIM 定时器简介 1.2 TIM 定时器类型及其工作原理简介 1.2.1 基本定时器工作原理及其结构 1.2.2 通用定时器工作原理及其结构 1.2. ...
- 《STM32学习笔记》2——开发环境的建立
接上文,文中的图片知识,大多数来自视频的截图(来自洋桃电子). 欢迎大家批评指正! STM32学习笔记-专栏 文章目录 一.接口定义 1 芯片引脚整体简介 2 各部分引脚介绍 二.开发板简介 三.IS ...
- STM32学习笔记 | 引起电源和系统异常复位的原因
关注+星标公众号,不错过精彩内容 每一块处理器都有复位的功能,不同处理器复位的类型可能有差异,引起复位的原因也可能有多种. STM32的复位功能非常强大,可通过软件.硬件和一些事件触发系统复位,而且通 ...
- 【STM32学习笔记-点亮LED灯】
STM32学习笔记-点亮LED灯 文章目录 STM32学习笔记-点亮LED灯 一.原理图分析 二.代码分析 1.mian函数 2.led.c函数 3.led.h函数 4.函数文件整理 5.LED_In ...
- STM32学习笔记(六)丨TIM定时器及其应用(输入捕获丨测量PWM波形的频率和占空比)
本篇文章包含的内容 一.输入捕获 1.1 输入捕获简介 1.2 输入捕获通道的工作原理 1.3 输入捕获的主从触发模式 1.4 输入捕获和PWMI结构 二.频率的测量方法 2.1 测频法 2.2 测周 ...
- STM32学习笔记(八)丨ADC模数转换器(ADC单、双通道转换)
本篇文章包含的内容 一.ADC 模数转换器 1.1 ADC简介 1.2 逐次逼近型ADC工作原理 1.3 STM32中的ADC基本结构 1.4 STM32中ADC的输入通道 1.5 STM32中的AD ...
最新文章
- LeetCode 817. Linked List Components
- C# winform引用com组件,创建AXHOST组件失败解决方案
- ideal2018提示内存不足_IntelliJ IDEA 2018 设置代码超出限制自动换行(最新版)
- 总结了C#中string.format用法。分享给大家供大家参考。具体分析如下:
- 信号与系统郑君里上册pdf_信号与系统(上册)
- php 仿电脑桌面系统,EonerCMS——做一个仿桌面系统的CMS(十-附最新源码)
- Python网络编程——使用TCP方式传输文件
- html img图片等比例缩放_我掏空了各大搜索引擎,整理了HTML图片标签笔记,满满干货...
- PaddlePaddle飞桨OCR文本检测——识别模型训练(三)
- Big Sur系统更新后,运行变慢?使macOS Big Sur更快运行的12个技巧
- knx智能照明控制系统电路图_智能照明控制系统(KNX)讲解
- 版本控制工具——subversion
- 华为云devops认证考试课堂笔记2
- java+mysql实现图书管理系统
- windriver 自动生成pcie驱动
- 2 二十五项反措--防止火灾事故
- 稳定同位素(stable isotope)标记氨基酸,聚氨基酸,PEI,聚合物,抑制剂,离子液体,石墨烯,黑磷,透明质酸,荧光材料,脂质体复合物
- 工信部发布《2018中国区块链产业白皮书》:量子计算机将给密码体系带来重大安全威胁
- 【聚沙成塔】-MYSQL全文索引使用MATCH AGAINST
- Five I/O Models