STM32基础入门学习笔记:核心板 电路原理与驱动编程

文章目录:

一: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接口输出高电平1
		delay_ms(500); //延时0.5秒
		GPIO_WriteBit(LEDPORT,LED1,(BitAction)(0)); //LED1接口输出低电平0
		delay_ms(500); //延时0.5秒
		
    	//方法2:取IO口当前状态            GPIO_ReadOutputDataBit —— 读取端口输出值
		GPIO_WriteBit(LEDPORT,LED1,(BitAction)(1-GPIO_ReadOutputDataBit(LEDPORT,LED1))); //取反LED1
		delay_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); //直接数值操作将变量值写入LED
		delay_s(2); //延时1秒
		GPIO_Write(LEDPORT,0x0000); //直接数值操作将变量值写入LED
		delay_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接口输出高电平1
				    delay_us(t);	
				GPIO_WriteBit(LEDPORT,LED1,(BitAction)(0));//LED1接口输出高电平1
				    delay_us(501-t);	 //通过将501换成更大的值可以将亮度细分为更多的等级
			}
			t++;
			if(t==500){//由亮变暗
				MENU=1;
			}
		}
		//菜单1
		if(MENU==1)
		{
			for(i=0;i<10;i++)//由于延时时间t从500~0 所以是循环变暗
			{
				GPIO_WriteBit(LEDPORT,LED1,(BitAction)(1));//LED1接口输出高电平1
				delay_us(t);	
				GPIO_WriteBit(LEDPORT,LED1,(BitAction)(0));//LED1接口输出高电平1
				delay_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)); //等待计数到0
	SysTick->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++; //变量加1
				if(a>3){ //当变量大于3时清0
					a=0; 
				}
				GPIO_Write(LEDPORT,a); //直接数值操作将变量值写入LED(LED在GPIOB组的PB0和PB1上)
                                       //以a的值赋给端口组,实现 “仅D1亮、仅D2亮、都亮、都灭” 的循环
                                       //                          0       1       2     3
				while(!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++; //变量加1
				if(a>3){ //当变量大于3时清0
					a=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)); //蜂鸣器接口输出0
            delay_us(500);                               //延时		
        GPIO_WriteBit(BUZZERPORT,BUZZER,(BitAction)(1)); //蜂鸣器接口输出高电平1
            delay_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();//蜂鸣器响1khz

	a = 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++; //变量加1
				if(a>3){ //当变量大于3时清0
					a=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=0
				BUZZER_BEEP1();//蜂鸣器音1

				FLASH_W(FLASH_START_ADDR,a); //从指定页的地址写入FLASH

				while(!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)); //蜂鸣器接口输出0
			    delay_us(500000/music1[i*2]); //延时    1/Hz=s
			GPIO_WriteBit(BUZZERPORT,BUZZER,(BitAction)(1)); //蜂鸣器接口输出高电平1
			    delay_us(500000/music1[i*2]); //延时	
		}	
	}
}

buzzer.h

void MIDI PLAY(void);    //MIDI_PLAY函数声明

main.c

BUZZER_Init ();//蜂鸣器初始化
//BUZZER_BEEP1(0)://蜂鸣器响1khz

MIDI_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)串口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

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.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);                    //使能串口 
}


//串口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清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	

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
        编码:GB2312
    3.最后点击确定


断开连接
    串口名字上——>右键——>关闭

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位	    定义了初始为0
      if(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位去除掉	    0011FFF

          USART1_RX_STA++;	//数据长度计数加1
          if(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 0
		if(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模块不需要重新配置,且掉电后依靠后备电池依然运行
        //但是每次上电后,还是要使能RTCCLK
        RCC_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);//配置完成后,向后备寄存器中写特殊字符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;
}    


//获取星期几
//按年月日计算星期(只允许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);//配置完成后,向后备寄存器中写特殊字符0xA5A5
          printf("初始化成功!      \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);	//使能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);  
  }

mian.c

RCC_Configuration(); //系统时钟初始化

led.c

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC,ENABLE);      //高速总线apb2上启动gpio a/b/c  

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/64030.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C语言预处理命令 #error 学习

#error命令是C/C语言的预处理命令之一&#xff0c;当预处理器预处理到#error命令时将停止编译并输出用户自定义的错误消息。 如下代码输出数字1000&#xff0c;如果加了 #error&#xff0c;构建时不会通过&#xff0c;提示出错如下&#xff1b; 这可能在大型项目中比较有用&am…

Ubuntu 22.04安装和使用ROS1可行吗

可行。 测试结果 ROS1可以一直使用下去的&#xff0c;这一点不用担心。Ubuntu会一直维护的。 简要介绍 Debian发行版^_^ AI&#xff1a;在Ubuntu 22.04上安装ROS1是可行的&#xff0c;但需要注意ROS1对Ubuntu的支持只到20.04。因此&#xff0c;如果要在22.04上安装ROS1&am…

使用webpack插件webpack-dev-server 出现Cannot GET/的解决办法

问题描述 文档地址深入浅出webpack 使用 DevServer运行webpack&#xff0c;跑起来之后提示Cannot GET/&#xff1a; 解决方案&#xff1a; 查阅官方文档 根据目录结构修改对应的配置&#xff1a; 然后就可以成功访问&#xff1a;

帆软设计器报表加载不出折线图的原因

最近在用帆软设计器做可视化图表。偶有遇到因为数据集的字段类型导致加载不出折线&#xff0c;现记录如下。做报表的同行可以参考。 数据库使用了 Oracle 11g。数据集的 SQL 代码片是之前用在另一个单元格报表里面的。页面上有一个率是直接计算得出&#xff0c;我为了方便、就…

Java开发 - Redis事务怎么用?

前言 最近博主感觉捅了Redis窝&#xff0c;从Redis主从&#xff0c;哨兵&#xff0c;集群&#xff0c;集群原理纷纷讲了一遍&#xff0c;不知道大家都学会了多少&#xff0c;想着送佛送到西&#xff0c;不如再添一把火&#xff0c;所以今天带给大家的博客是Redis事务&#xff…

Linux基本开发工具(一)

文章目录 Linux基本开发工具&#xff08;一&#xff09;Linux安装和卸载软件Linux 软件包管理器 yum关于sudo命令关于yum源的换源问题 vim编辑器的使用vim三种模式&#xff08;常见&#xff09;vim的基本操作vim配置 Linux基本开发工具&#xff08;一&#xff09; Linux安装和…

探秘手机隐藏的望远镜功能:开启后,观察任何你想看的地方

当今的智能手机不仅仅是通信工具&#xff0c;它们蕴藏着各种隐藏的功能&#xff0c;其中之一就是让你拥有望远镜般的观察能力。是的&#xff0c;你没有听错&#xff01;今天我们将探秘手机中隐藏的望远镜功能&#xff0c;这项神奇的功能可以让你打开后&#xff0c;轻松观察任何…

/usr/bin/ld: 找不到 can‘t find -xxx++

ld是一个链接器文件&#xff0c;后面报错一般都是什么库找不到&#xff0c;so文件 我们去根目录下全局找一下该文件 cd / find -name *libstdc.so*一般都会在lib64下面找到&#xff0c;注意后面带版本号的是实际文件 我们需要把他复制到lib文件下&#xff0c;ld找的是软连接文…

3.01 用户在确认订单页收货地址操作

用户在确认订单页面&#xff0c;可以针对收货地址做如下操作&#xff1a; 1. 查询用户的所有收货地址列表 2. 新增收货地址 3. 删除收货地址 4. 修改收货地址 5. 设置默认地址步骤1&#xff1a;创建对应用户地址BO public class AddressBO {private String addressId;private…

封装上传文件组件(axios,onUploadProgress,取消请求)

目录 定时模拟进度条 方法 A.axios B.xhr 取消请求​​​​​​​ 完整代码 A.自定义上传组件 B.二次封装组件 情况 增加cancelToken不生效&#xff0c;刷新页面 进度条太快->设置浏览器网速 定时模拟进度条 startUpload() {if (!this.file) return;const totalS…

flink kafka消费者如何处理kafka主题的rebalance

背景&#xff1a; 我们日常使用kafka客户端消费kafka主题的消息时&#xff0c;当消费者退出/加入消费者组&#xff0c;kafka主题分区数有变等事件发生时&#xff0c;都会导致rebalance的发生&#xff0c;此时一般情况下&#xff0c;如果我们不自己处理offset&#xff0c;我们不…

Vue3+Vite+Pinia+Naive后台管理系统搭建之九:layout 动态路由布局

前言 如果对 vue3 的语法不熟悉的&#xff0c;可以移步Vue3.0 基础入门&#xff0c;快速入门。 1. 系统页面结构 由 menu&#xff0c;面包屑&#xff0c;用户信息&#xff0c;页面标签&#xff0c;页面内容构建 ​ 2. 创建页面 创建 src/pages/layout.vue 布局页 创建 sr…

python 封装sql 增删改查连接MySQL

select * from Teacher limit 10 连接字符串配置MysqlConfig.py class MysqlConfig:HOST 192.168.56.210PORT 3306USER rootPASSWORD 1qaz0987654321DBStudentDBCHARSET utf8封装增删改查MysqlConnection.py Author: tkhywang 2810248865qq.com Date: 2023-06-19 15:44:48 Las…

VMware Workstation及CentOS-7虚机安装

创建新的虚机&#xff1a; 选择安装软件&#xff08;这里选的是桌面版&#xff0c;也可以根据实际情况进行选择&#xff09; 等待检查软件依赖关系 选择安装位置&#xff0c;自主配置分区 ​​​​​​​ 创建一个普通用户 安装完成后重启 点击完成配置&#xff0c;进入登陆界面…

Vue3 列表渲染简单应用

去官网学习→列表渲染 | Vue.js 运行示例&#xff1a; 代码&#xff1a;HelloWorld.vue <template><div class"hello"><h1>Vue 列表渲染</h1><p v-for"item in dataList">{{item}}</p><p v-for"(item,index)…

ros tf

欢迎访问我的博客首页。 tf 1. tf 命令行工具1.1 发布 tf1.2 查看 tf 2.参考 1. tf 命令行工具 1.1 发布 tf 我们根据 cartographer_ros 的 launch 文件 backpack_2d.launch 写一个 tf.launch&#xff0c;并使用命令 roslaunch cartographer_ros tf.launch 启动。该 launch 文件…

认识所有权

专栏简介&#xff1a;本专栏作为Rust语言的入门级的文章&#xff0c;目的是为了分享关于Rust语言的编程技巧和知识。对于Rust语言&#xff0c;虽然历史没有C、和python历史悠远&#xff0c;但是它的优点可以说是非常的多&#xff0c;既继承了C运行速度&#xff0c;还拥有了Java…

zookeeper的部署

一 先下载zookeeper 二 解压包 三 修改配置文件 四 把配好文件传到其他的节点上面 五 在每个节点的dataDir指定的目录下创建一个 myid 的文件 六 配置zook的启动脚本 七 设置开机自启 八 分别启动 九查看当前状态service zookeeper status 十 总结 一 先下载zookeeper …

Vue常见的事件修饰符

1.prevent:阻止默认事件(常用) 2. stop:阻止事件冒泡(常用) 3. once:事件只触发一次(常用) 4.captrue:使用事件的捕捉模式(不常用) 5.self:只有event.target是当前操作的元素时才触发事件(不常用) 6.passive:事件的默认行为立即执行&#xff0c;无需等待事件回调执行完毕(不常用…

网关 GateWay 的使用详解、路由、过滤器、跨域配置

一、网关的基本概念 SpringCloudGateway网关是所有微服务的统一入口。 1.1 它的主要作用是&#xff1a; 反向代理&#xff08;请求的转发&#xff09; 路由和负载均衡 身份认证和权限控制 对请求限流 1.2 相比于Zuul的优势&#xff1a; SpringCloudGateway基于Spring5中…