文章目录:
一:LED灯操作
1.LED灯的点亮和熄灭 延迟闪烁
main.c
led.c
led.h
BitAction枚举
2.LED呼吸灯(灯的强弱交替变化)
main.c
delay.c
3.按键控制LED灯
key.h
key.c
main.c
二:FLASH读写程序(有记忆可以保存断电之前的状态)
flash.h
flash.c
main.c
flash操作注意事项
三:蜂鸣器驱动程序(按键有声音)
buzzer.h
buzzer.c
main.c
四:MIDI音乐播放程序(基于蜂鸣器)
buzzer.c
buzzer.h
main.c
五:USART串口通信驱动程序(串口助手 超级终端)
1.USART发送程序(3种方法)
usart.h
usart.c
main.c
2.USART接收程序(2种方法)
2.1 查询方式接收数据
main.c
2.2 中断方式接收数据
usart.c
3.USART串口控制程序(双向交互):通过串口助手控制LED灯的开关状态和蜂鸣器、在单片机中按键操作在串口助手中进行显示
main.c
4.超级终端串口远程控制LED程序(命令行操作形式)
usart.h
usart.c
mian.c
六:RTC实时时钟原理驱动程序
1.基于RTC的LED走时驱动程序(BKP备用寄存器)
rtc.h
rtc.c
main.c
2.RTC超级终端串口显示日历程序(命令行操作形式)
usart.c
main.c
七:RCC时钟复位和设置程序
rtc.c
mian.c
led.c
区别:秒s、毫秒ms、微秒μs、纳秒ns、皮秒ps、飞秒fs每两级之间的换算以及之间的关系_ps和fs区别
区别:千赫kHz、兆赫MHz、吉赫GHz、太赫THz、拍赫PHz、艾赫EHz每两级之间的换算以及之间的关系_太赫兹与ghz
一:LED灯操作
1.LED灯的点亮和熄灭 延迟闪烁
原理:输出高低电平控制
main.c
#include "stm32f10x.h" #include "sys.h" #include "delay.h" #include "led.h"int main (void){//主程序RCC_Configuration(); //时钟设置LED_Init();while(1){//方法1:采用枚举(BitAction)(1) GPIO_WriteBit设置或者清除指定的数据端口位(端口组、端口名、位控制)GPIO_WriteBit(LEDPORT,LED1,(BitAction)(1)); //LED1接口输出高电平1delay_ms(500); //延时0.5秒GPIO_WriteBit(LEDPORT,LED1,(BitAction)(0)); //LED1接口输出低电平0delay_ms(500); //延时0.5秒//方法2:取IO口当前状态 GPIO_ReadOutputDataBit —— 读取端口输出值GPIO_WriteBit(LEDPORT,LED1,(BitAction)(1-GPIO_ReadOutputDataBit(LEDPORT,LED1))); //取反LED1delay_ms(1000); //延时1秒//方法3:直接设置IO口 GPIO_SetBits设置指定的数据端口位(端口组、端口名)GPIO_SetBits(LEDPORT,LED1); //LED灯都为高电平(1)delay_s(1); //延时1秒GPIO_ResetBits(LEDPORT,LED1); //LED灯都为低电平(0)delay_s(1); //延时1秒//方法4:对_ IO 组_ 的数值写入 GPIO_Write向指定GPIO数据端口整组写入数据(端口清零置1)GPIO_Write(LEDPORT,0x0001); //直接数值操作将变量值写入LEDdelay_s(2); //延时1秒GPIO_Write(LEDPORT,0x0000); //直接数值操作将变量值写入LEDdelay_s(2); //延时1秒//方法5:直接控制端口。PB(1)=1;PB1端口直接赋值} }
led.c
#include "led.h" //导入自己编写的 led.h的头文件,里面有许多自己宏定义的成员(变量)和函数void LED_Init(void){ //LED灯的接口初始化GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC,ENABLE); //高速总线apb2上启动gpio a/b/c GPIO_InitStructure.GPIO_Pin = LED1 | LED2; //选择端口号(0~15或all) 可以使用宏定义名在led.h查看引脚 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //选择IO接口工作方式 GPIO推挽方式输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置IO接口速度(2/10/50MHz) 只有io被设置为输出时才配置速度GPIO_Init(LEDPORT, &GPIO_InitStructure); //将配置的结构体内容写入到ledport-gpiob那一组端口当中 }
led.h
#ifndef __LED_H #define __LED_H #include "sys.h"//#define LED1 PBout(0)// PB0 也可以直接用这 <-容易理解 //#define LED2 PBout(1)// PB1 #define LEDPORT GPIOB //定义IO接口 <- 优点是移植方便 ,越到后面越需要这样编译 #define LED1 GPIO_Pin_0 //定义IO接口 #define LED2 GPIO_Pin_1 //定义IO接口void LED_Init(void); //LED的初始化函数声明 ,就不用再从.c程序中声明#endif
BitAction枚举
typedef enum {Bit_REST = 0;Bit_SET; //默认为1这里 }BitAction;
2.LED呼吸灯(灯的强弱交替变化)
原理:调节LED灯点亮和熄灭时间的比例、呼吸灯的速度来控制
main.c
#include "stm32f10x.h" //STM32头文件 #include "sys.h" #include "delay.h"#include "led.h"int main (void){//主程序//定义需要的变量u8 MENU; //菜单模式 8位无符号u16 t,i; //16位无符号RCC_Configuration(); //时钟设置LED_Init();//led初始化MENU=0; //初始菜单状态t=1; //初始亮状态延时时长//主循环while(1){//菜单0 根据菜单的值不同运行不同的程序if(MENU==0){for(i=0;i<10;i++)//由于延时时间t从0~500 所以是循环变亮 i的值可以调节呼吸灯变化的速度{//同一个亮度循环10次GPIO_WriteBit(LEDPORT,LED1,(BitAction)(1));//LED1接口输出高电平1delay_us(t); GPIO_WriteBit(LEDPORT,LED1,(BitAction)(0));//LED1接口输出高电平1delay_us(501-t); //通过将501换成更大的值可以将亮度细分为更多的等级}t++;if(t==500){//由亮变暗MENU=1;}}//菜单1if(MENU==1){for(i=0;i<10;i++)//由于延时时间t从500~0 所以是循环变暗{GPIO_WriteBit(LEDPORT,LED1,(BitAction)(1));//LED1接口输出高电平1delay_us(t); GPIO_WriteBit(LEDPORT,LED1,(BitAction)(0));//LED1接口输出高电平1delay_us(501-t); }t--;if(t==1){MENU=0;}}} }
delay.c
#include "delay.h"#define AHB_INPUT 72 //请按RCC中设置的AHB时钟频率填写到这里(单位MHz)void delay_us(u32 uS) { //uS微秒级延时程序(参考值即是延时数,72MHz时最大值233015) SysTick->LOAD=AHB_INPUT*uS; //重装计数初值(当主频是72MHz,72次为1微秒)SysTick->VAL=0x00; //清空定时器的计数器SysTick->CTRL=0x00000005; //时钟源HCLK,打开定时器while(!(SysTick->CTRL&0x00010000)); //等待计数到0SysTick->CTRL=0x00000004; //关闭定时器 }
3.按键控制LED灯
原理:通过读取按键端口输入进行控制
key.h
#ifndef __KEY_H #define __KEY_H #include "sys.h"//#define KEY1 PAin(0)// PA0 //#define KEY2 PAin(1)// PA1#define KEYPORT GPIOA //定义IO接口组 #define KEY1 GPIO_Pin_0 //定义IO接口 #define KEY2 GPIO_Pin_1 //定义IO接口void KEY_Init(void);//声明按键初始化#endif
key.c
#include "key.h"void KEY_Init(void)//微动开关的接口初始化 { GPIO_InitTypeDef GPIO_InitStructure; //定义GPIO的初始化枚举结构 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); GPIO_InitStructure.GPIO_Pin = KEY1 | KEY2; //选择端口号(0~15或all) GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //选择IO接口工作方式 //上拉电阻 //GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置IO接口速度,屏蔽掉了,输入时不需要速度配置(2/10/50MHz) GPIO_Init(KEYPORT,&GPIO_InitStructure);//此时才把io的配置进行初始化!!! }
main.c
#include "stm32f10x.h" //STM32头文件 #include "sys.h" #include "delay.h" #include "led.h"#include "key.h" int main (void){//主程序//初始化程序RCC_Configuration(); //时钟设置LED_Init();//LED初始化KEY_Init();//按键初始化//主循环while(1){//GPIO_ReadInputDataBit —— 读取端口输入//示例1:无锁存 按着亮,松开熄灭if(GPIO_ReadInputDataBit(KEYPORT,KEY1)){ //读按键接口的电平GPIO_ResetBits(LEDPORT,LED1); //LED灯都为低电平(0) }else{ GPIO_SetBits(LEDPORT,LED1); //LED灯都为高电平(1) }if(GPIO_ReadInputDataBit(KEYPORT,KEY2)){ //读按键接口的电平GPIO_ResetBits(LEDPORT,LED2); //LED灯都为低电平(0) }else{ GPIO_SetBits(LEDPORT,LED2); //LED灯都为高电平(1) }//示例2:无锁存GPIO_WriteBit(LEDPORT,LED1,(BitAction)(!GPIO_ReadInputDataBit(KEYPORT,KEY1))); //示例3:有锁存 第一次按灯亮,松开还是亮,第二次按熄灭if(!GPIO_ReadInputDataBit(KEYPORT,KEY1)){ //读按键接口的电平delay_ms(20); //延时去抖动if(!GPIO_ReadInputDataBit(KEYPORT,KEY1)){ //读按键接口的电平GPIO_WriteBit(LEDPORT,LED1,(BitAction)(1-GPIO_ReadOutputDataBit(LEDPORT,LED1))); //LED取反while(!GPIO_ReadInputDataBit(KEYPORT,KEY1)); //等待按键松开 }}//示例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); //直接数值操作将变量值写入LED(LED在GPIOB组的PB0和PB1上)//以a的值赋给端口组,实现 “仅D1亮、仅D2亮、都亮、都灭” 的循环// 0 1 2 3while(!GPIO_ReadInputDataBit(KEYPORT,KEY1)); //等待按键松开 }}} }
二:FLASH读写程序(有记忆可以保存断电之前的状态)
原理:通过在指定页擦除和指定地址写入数据进行控制
如何把存储数据到FLASH中?
在Basic文件夹中——>创建一个FLASH文件夹(flash.c flash.h)注意查看lib文件夹下的flash.c是否存在
flash.h
#ifndef __FLASH_H #define __FLASH_H #include "sys.h"#define LEDPORT GPIOB //定义IO接口 <- 优点是移植方便 ,越到后面越需要这样编译 #define KEY1 GPIO_Pin_0 //定义IO接口 #define KEY2 GPIO_Pin_1 //定义IO接口void LED_Init(void); //LED的初始化函数声明 ,就不用再从.c程序中声明 void KEY_Init(void); //声明按键初始化void FLASH_W(u32 add,u16 dat); //void FLASH_W(u32 add,u16 dat,u16 dat2);多个情况;若果过多可以使用数组或指针来解决 u16 FLASH_R(u32 add);#endif
flash.c
#include "flash.h"//导入头文件//FLASH写入数据 void FLASH_W(u32 add,u16 dat){ //参数1:32位FLASH地址。参数2:16位数据 //void FLASH_W(u32 add,u16 dat,u16 dat2){//多个情况// RCC_HSICmd(ENABLE); //打开HSI时钟FLASH_Unlock(); //解锁FLASH编程擦除控制器FLASH_ClearFlag(FLASH_FLAG_BSY|FLASH_FLAG_EOP|FLASH_FLAG_PGERR|FLASH_FLAG_WRPRTERR);//清除标志位FLASH_ErasePage(add); //擦除指定地址页FLASH_ProgramHalfWord(add,dat); //从指定页的addr地址写入//FLASH_ProgramHalfWord(add+1,dat2);//多个情况(是加上这条)FLASH_ClearFlag(FLASH_FLAG_BSY|FLASH_FLAG_EOP|FLASH_FLAG_PGERR|FLASH_FLAG_WRPRTERR);//清除标志位FLASH_Lock(); //锁定FLASH编程擦除控制器 }//FLASH读出数据 u16 FLASH_R(u32 add){ //参数1:32位读出FLASH地址。返回值:16位数据u16 a;a = *(u16*)(add);//从指定页的addr地址开始读return a; }
main.c
#include "stm32f10x.h" //STM32头文件 #include "sys.h" #include "delay.h" #include "led.h" #include "key.h" #include "flash.h" //导入Flash的头文件#define FLASH_START_ADDR 0x0801f000 //写入的起始地址int main (void){//主程序u16 a; //定义变量//初始化程序RCC_Configuration(); //时钟设置LED_Init();//LED初始化KEY_Init();//按键初始化a = FLASH_R(FLASH_START_ADDR);//从指定页的地址读FLASH//b = FLASH_R(FLASH_START_ADDR);多个情况GPIO_Write(LEDPORT,a); //直接数值操作将变量值写入LED(LED在GPIOB组的PB0和PB1上)//整组写入LED(PB组)//GPIO_Write(LEDPORT,a,b);多个情况//主循环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); //直接数值操作将变量值写入LED(LED在GPIOB组的PB0和PB1上)FLASH_W(FLASH_START_ADDR,a); //将led的状态值a写入,指定FLASH起始页的地址内//FLASH_W(FLASH_START_ADDR,a,b);多个情况while(!GPIO_ReadInputDataBit(KEYPORT,KEY1)); //等待按键松开 }}} }
flash操作注意事项
操作一定要先擦后写每页是1024个地址,起始地址Ox08000000擦除操作以页为单位,写操作则必须以16位宽度为单位,允许跨页写入STM32内置FLASH擦或写时,必须打开外部/内部高速振荡器FLASH可多次擦写10万次,不可死循环擦写擦写时要避开用户程序存储区的区域,否则会擦掉用户程序导致错误擦除一页要10ms(对于1k大小的一页),比较慢。而且不能单个字节的擦写
擦除只能以页擦除:临时存储写入地址(空白区域尽量靠后) 不能和 下载用户程序(第0页开始)相互冲突
三:蜂鸣器驱动程序(按键有声音)
原理:通过延迟时间决定频率震动 和 高低电平循环次数决定持续时长 进行控制
核心板蜂鸣器电路
在Hardware文件夹中——>创建一个BUZZER文件夹(buzzer.c buzzer.h)
buzzer.h
#ifndef ——BUZZZER_H #define ——BUZZZER_H #include "sys.h"#define BUZZERPORT GPIOB //定义IO接口 端口组PB5 #define LED1 GPIO_ Pin_o //定义工o接口 #define LED2 GPIO_ Pin_1 //定义工o接口#define BUZZER GPIO_Pin_5 //定义IO接口 IO端口void BUZZER_Init(void); //初始化 void BUZZER_BEEP1(void); //响一声#endif
buzzer.c
#include "buzzer.h" #include "delay.h"//蜂鸣器的接口初始化 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 ,断开线路,保护蜂鸣器不被损坏或烧坏}//蜂鸣器响一声t //延迟时间长度决定总周期的长度(修改声音的频率音调) 循环次数i决定蜂鸣器发出(声音持续时间的长度) 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.c
#include "stm32f10x.h" //STM32头文件 #include "sys.h" #include "delay.h" #include "led.h" #include "key.h" #include "flash.h" #include "buzzer.h" #define FLASH_START_ADDR 0x0801f000 //写入的起始地址int main (void){//主程序u8 a; //定义变量//初始化程序RCC_Configuration(); //时钟设置LED_Init();//LED初始化KEY_Init();//按键初始化BUZZER_Init();//蜂鸣器初始化BUZZER_BEEP1();//蜂鸣器响1khza = FLASH_R(FLASH_START_ADDR);//从指定页的地址读FLASH//处理PB1 PB2//因为led是PB0和PB1蜂鸣器的接口,同时是PB5也是。一设置低电平PB端口就全部为0了(默认是PB0 PB1,但这里加入了PB5 就会使蜂鸣器长时间导致发热)//GPIO_ReadOutputData读取整组(LED在GPIOB组)的io口的电平状态 按位与优先级高//0xfffc&GPIO_ReadOutputData(LEDPORT))高14位保持io口的原来状态,低两位清零//然后与a进行与运算操作变量a的最低两位,再写入PB组的最低两位,其他PB组的其他位不变:将led状态写入,也就是说led在状态写入的时候不会影响其他io电平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++; //变量加1if(a>3){ //当变量大于3时清0a=0; }GPIO_Write(LEDPORT,a|0xfffc&GPIO_ReadOutputData(LEDPORT)); //整组写入不是某个引脚直接数值操作将变量值写入LED(LED在GPIOB组的PB0和PB1上)//a=1是001->pa0=1,a=2是010->pa0=0 pa1=1,a=3是011->pa0=1 pa1=1,a=4是100->pa0=0 pa1=0BUZZER_BEEP1();//蜂鸣器音1FLASH_W(FLASH_START_ADDR,a); //从指定页的地址写入FLASHwhile(!GPIO_ReadInputDataBit(KEYPORT,KEY1)); //等待按键松开 //BUZZER_BEEP2();//蜂鸣器音2}}} }
四:MIDI音乐播放程序(基于蜂鸣器)
原理:通过改变音调频率 和 时间长度使用蜂鸣器进行控制
音调与频率的关系
buzzer.c
uc16 music1[78]={ //音乐的数据表(奇数是音调频率,偶数是时间长度),存于flash中 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 };//MIDI音乐 void MIDI_PLAY(void){ u16 i,e;39个音符循环一次for(i=0;i<39;i++){ //循环不同次数达到让同一个音符播放一段时间长度//music1[i*2]——>0——>330 music1[i*2+1]——>1——>750 (1赫兹=1次/秒) //意思:每秒钟震动330次[Hz],750毫秒[T]震动多少次呢[次数=Hz*T]?for(e=0;e<music1[i*2]*music1[i*2+1]/1000;e++){ //转化为毫秒us 也就是xxx/1000//方波的时间,音效音调高低频率,高电平的延时时间500000us+低电平的延时时间500000us=形成1秒频率周期 GPIO_WriteBit(BUZZERPORT,BUZZER,(BitAction)(0)); //蜂鸣器接口输出0delay_us(500000/music1[i*2]); //延时 1/Hz=sGPIO_WriteBit(BUZZERPORT,BUZZER,(BitAction)(1)); //蜂鸣器接口输出高电平1delay_us(500000/music1[i*2]); //延时 } } }
buzzer.h
void MIDI PLAY(void); //MIDI_PLAY函数声明
main.c
BUZZER_Init ();//蜂鸣器初始化 //BUZZER_BEEP1(0)://蜂鸣器响1khzMIDI_PLAY(); //播放MIDI音乐
五:USART串口通信驱动程序(串口助手 超级终端)
DYS串口助手使用:设置自己对应的端口号(自定义)”C0M4“、波特率(自定义)”115200“、发送模式(自定义)”数值“、接收模式(自定义)”数值“最后点击打开端口(自定义)、之后关闭端口(自定义)”数值“:16进制 ”字符“:ASCLL码 发送和接收数据可以相互转换:ASCLL码与进制对应表在Lib文件交接加入:stm32f10x_usart.c串口助手和FlyMcu:如果公用一个端口,那么就不能同时使用
新建文件夹
Basic——>usart——>usart.cusart.h
1.USART发送程序(3种方法)
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)串口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.c
#include "sys.h" #include "usart.h"//使UASRT串口可用printf函数发送 //在usart.h文件里可更换使用printf函数的串口号 #if 1 #pragma import(__use_no_semihosting) //标准库需要的支持函数 struct __FILE {int handle; }; FILE __stdout; //定义_sys_exit()以避免使用半主机模式 _sys_exit(int x){ x = x; } //重定义fputc函数 int fputc(int ch, FILE *f){ while((USART_n->SR&0X40)==0);//循环发送,直到发送完毕 USART_n->DR = (u8) ch; return ch; } #endif /*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); }//串口1初始化并启动 void USART1_Init(u32 bound){ //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); //使能串口 }//串口1的中断处理程序 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
main.c
#include "stm32f10x.h" //STM32头文件 #include "sys.h" #include "delay.h" #include "usart.h"int main (void) { //主程序u8 a=7,b=8;//初始化程序RCC_Configuration(); //时钟设置USART1_Init(115200); //串口初始化(参数是波特率)发送和接收波特率必须完全相同//主循环while(1){/* 发送方法1 单个 */USART_SendData(USART1, 0x55); //发送单个数值(那个端口,发送什么数据)//USART_SendData(USART1, 'U'); 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); //延时} }
2.USART接收程序(2种方法)
2.1 查询方式接收数据
查询要在usart.c的USART初始化设置中关闭中断(当串口接收到数据时才不会跳到中断函数中)ENABLE——>DISABLE
main.c
u8 a;while(){//查询方式接收(发回去,又接收回来),失去实时性if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE)!= RESET){ //查询串口待处理标志位 接收数据寄存器非空标志位a =USART_ReceiveData(USART1); //读取接收到的数据printf ("%c",a); //把收到的数据发送回电脑} }
2.2 中断方式接收数据
中断要在usart.c的USART初始化设置中关闭中断(当串口接收到数据时才不会跳到中断函数中)DISABLE——>ENABLE
usart.c
//中断方式接收程序解读 void USART1_IRQHandler(void){ //串口1中断服务程序(固定的函数名不能修改) u8 a;if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET){ //接收中断(接收到的数据必须是0x0d 0x0a结尾) Res =USART_ReceiveData(USART1);//(USART1->DR); //读取接收到的数据printf("%c",a); //把收到的数据以 a符号变量 发送回电脑 }
3.USART串口控制程序(双向交互):通过串口助手控制LED灯的开关状态和蜂鸣器、在单片机中按键操作在串口助手中进行显示
这里需要使用超级终端:并且可以改变终端输出的颜色背景
输入输出可以一起显示建立连接1.文件——>新建连接——>选择合适的串口(自定义)COM4——>确定2.修改串口(自定义):COM4波特率(自定义):115200编码:GB23123.最后点击确定断开连接串口名字上——>右键——>关闭
main.c
#include "stm32f10x.h" //STM32头文件 #include "sys.h" #include "delay.h" #include "led.h" #include "key.h" #include "buzzer.h" #include "usart.h" int main (void){//主程序u8 a;//初始化程序RCC_Configuration(); //时钟设置LED_Init();//LED初始化KEY_Init();//按键初始化BUZZER_Init();//蜂鸣器初始化USART1_Init(115200); //串口初始化(参数是波特率)//主循环while(1){//采用查询方式接收 //查询要在usart.c的USART初始化设置中关闭中断(当串口接收到数据时才不会跳到中断函数中)ENABLE——>DISABLE 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 "); printf("KEY1 "); }} if(!GPIO_ReadInputDataBit(KEYPORT,KEY2)){ //读按键接口的电平delay_ms(20); //延时20ms去抖动if(!GPIO_ReadInputDataBit(KEYPORT,KEY2)){ //读按键接口的电平while(!GPIO_ReadInputDataBit(KEYPORT,KEY2)); //等待按键松开printf("\O33[1;40;32m KEY2 "); //改变字体颜色和背景色printf("\O33[1;40;32m KEY2 \O33[om"); //改变字体颜色和背景色,之后又改回来printf("\O33[1;40;32m KEY2 /n/r"); //改变字体颜色和背景色,再次按显示到下一行}} // delay_ms(1000); //延时} }
4.超级终端串口远程控制LED程序(命令行操作形式)
usart.h
#define USART1_REC_LEN 200; //定义USART1最大接收字节数 #define USART2_REC_LEN 200; #define USART3_REC_LEN 200;//全局变量声明 extern u8 USART1_RX_BUF[USART1_REC_LEN]; //接收缓存,最大USART_REC_LEN个字节末字节为换行符 extern u8 USART2_RX_BUF[USART2_REC_LEN]; extern u8 USART3_RX_BUF[USART3_REC_LEN]; extern u16 USART1_RX_STA; //接收状态标记 extern u16 USART2_RX_STA; extern u16 USART3_RX_STA;
usart.c
u8 USART1_RX_BUF[USART1_REC_LEN]; //接收缓冲,最大USART1_REC_LEN个字节 //全局变量 u16 USART1_RX_STA=0; //接收状态标记//通过中断函数接收数据 //中断要在usart.c的USART初始化设置中关闭中断(当串口接收到数据时才不会跳到中断函数中)DISABLE——>ENABLE 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符号变量 发送回电脑//USART1_RX_STA初始值为0 if((USART1_RX_STA&0x8000)==0) { //接收未完成 接收状态标记 最高位第1位 定义了初始为0if(USART1_RX_STA&0x4000){ //接收到了0x0d 最高位第2位//判断是不是0x0a /r if(Res!=0x0a){USART1_RX_STA=0;} //接收错误,重新开始,状态位清零else{USART1_RX_STA|=0x8000;} //接收完成了,是0x0a,那么把最高位状态位置1【11】}else{//还没收到0X0D /n按键 回车键=ox0D+0x0a组合=/n/r //是回车,第二位置1 if(Res==0x0d){USART1_RX_STA|=0x4000;} //0100 0 0 0 最高位第2位 置1【11】//不是回车去除掉最高两位else{USART1_RX_BUF[USART1_RX_STA&0X3FFF]=Res ; //将收到的数据放入数组 最高位的2位去除掉 0011FFFUSART1_RX_STA++; //数据长度计数加1if(USART1_RX_STA>(USART1_REC_LEN-1)) //读到的值超过数组的最大值{USART1_RX_STA=0;} //接收数据错误,重新开始接收,状态标志清零 } }}} }
mian.c
#include "stm32f10x.h" //STM32头文件 #include "sys.h" #include "delay.h" #include "led.h" #include "key.h" #include "buzzer.h" #include "usart.h"int main (void){//主程序RCC_Configuration();LED_Init();//LED初始化KEY_Init();//按键初始化BUZZER_Init();//蜂鸣器初始化USART1_Init(115200); //串口初始化,参数中写波特率USART1_RX_STA=0xC000; //初始值设为有回车的状态,即显示一次欢迎词while(1){//判断最高位和最高位的第二位 是不是1 1100 0 0 0if(USART1_RX_STA&0xC000){ //如果标志位是0xC000表示收到数据串完成,可以处理。//判断数据是0个,从而看是不是按了回车 去掉状态变量最高两位0011FFF(表示没有输入任何字符)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}} }
六:RTC实时时钟原理驱动程序
1.基于RTC的LED走时驱动程序(BKP备用寄存器)
新建文件夹
Basic——>rtc文件夹——>rtc.c rtc.h在Lib文件夹下 添加:stm32f10x_rtc.c
LED1代表秒:奇数点亮,偶数熄灭;LED2代表分钟:奇数点亮,偶数熄灭
rtc.h
#ifndef __RTC_H #define __RTC_H #include "sys.h" //全局变量的声明,在rtc.c文件中定义 //以下2条是使用extern语句声明全局变量,时钟计算出的年月日时分秒 //注意:这里不能给变量赋值extern u16 ryear; extern u8 rmon,rday,rhour,rmin,rsec,rweek; //年月日时分秒//函数声明 u8 RTC_Get(void); //读出当前时间值 u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec); //写入当前时间值void RTC_First_Config(void); //首次启用RTC的设置(全部初始化) void RTC_Config(void); //实时时钟初始化(部分初始化)//下面包含在时间读写函数里面,不需要单独调用 u8 Is_Leap_Year(u16 year); //判断是否是闰年函数 u8 RTC_Get_Week(u16 year,u8 month,u8 day); //按年月日计算星期几#endif
rtc.c
#include "sys.h" #include "rtc.h"//以下2条全局变量--用于RTC时间的读取 u16 ryear; //4位年 u8 rmon,rday,rhour,rmin,rsec,rweek;//2位月日时分秒周//首次启用RTC的初始化设置 void RTC_First_Config(void){ RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);//启用PWR电源管理部分和BKP备用寄存器的时钟(from APB1)PWR_BackupAccessCmd(ENABLE);//后备域解锁 使能或者失能RTC和后备寄存器访问BKP_DeInit();//备份寄存器模块复位 将外设BKP的全部寄存器重设为缺省值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();//等待操作完成}//是否启用rtc的输出功能,一般情况下不使用#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时钟1秒触发中断函数(名称固定不可修改) void RTC_IRQHandler(void){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; } //获取星期几 //按年月日计算星期(只允许1901-2099年)//已由RTC_Get调用 u8 RTC_Get_Week(u16 year,u8 month,u8 day){ 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); //返回星期值 }
main.c
#include "stm32f10x.h" //STM32头文件 #include "sys.h" #include "delay.h" #include "led.h" #include "key.h" #include "buzzer.h" #include "usart.h"#include "rtc.h"int main (void){//主程序RCC_Configuration(); //系统时钟初始化RTC_Config(); //实时时钟初始化LED_Init();//LED初始化KEY_Init();//按键初始化BUZZER_Init();//蜂鸣器初始化USART1_Init(115200); //串口初始化,参数中写波特率USART1_RX_STA=0xC000; //初始值设为有回车的状态,即显示一次欢迎词while(1){if(RTC_Get()==0){ //读出时间值,同时判断返回值是不是0,非0时读取的值是错误的。 GPIO_WriteBit(LEDPORT,LED1,(BitAction)(rsec%2)); //LED1接口 读出秒数,取余表示led的状态 GPIO_WriteBit(LEDPORT,LED2,(BitAction)(rmin%2)); //LED2接口 读出分钟数,取余表示led的状态 }} }
2.RTC超级终端串口显示日历程序(命令行操作形式)
在超级终端按回车键,之后显示更新时间 初始化时钟 输入设置时间
usart.c
//开启中断 USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启ENABLE/关闭DISABLE中断
main.c
#include "stm32f10x.h" //STM32头文件 #include "sys.h" #include "delay.h" #include "led.h" #include "key.h" #include "buzzer.h" #include "usart.h" #include "rtc.h"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);//显示日期时间//分钟和秒钟的各位与十位分开:单独显示 printf(" 现在实时时间:%d-%d-%d %d:%d%d:%d%d ",ryear,rmon,rday,rhour,rmin/10,rmin%10,rsec/10,rsec%10);//显示日期时间 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个//将超级终端发过来的数据换算并写入RTC ox30是偏移量=0(实现1~9的对应) 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时钟复位和设置程序
左边——产生阻频;左边——分配阻频
rtc.c
//RCC时钟的设置 void RCC_Configuration(void){ 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); }
mian.c
RCC_Configuration(); //系统时钟初始化
led.c
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC,ENABLE); //高速总线apb2上启动gpio a/b/c