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

news2024/9/23 7:33:31

文章目录:

一: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  

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据安全能力框架模型-详细解读(二)

数据安全能力框架构成 1&#xff09; 数据安全治理 管理视角&#xff1a;从组织制度流程上提出要求&#xff0c;由于数据在各业务系统之间流转&#xff0c;需要设立高级管理层参与决策的数据安全管理部门&#xff0c;统筹和规划多部门之间的工作&#xff1b;需要设立跨组织的…

34.利用matlab解 多变量多目标规划问题(matlab程序)

1.简述 学习目标&#xff1a;适合解 多变量多目标规划问题&#xff0c;例如 收益最大&#xff0c;风险最小 主要目标法&#xff0c;线性加权法&#xff0c;权值我们可以自己设定。 收益函数是 70*x(1)66*x(2) &#xff1b; 风险函数是 0.02*x(1)^20.01*x(2)^20.04*(x…

RabbitMQ 备份交换机和死信交换机

为处理生产者生产者将消息推送到交换机中&#xff0c;交换机按照消息中的路由键即自身策略无法将消息投递到指定队列中造成消息丢失的问题&#xff0c;可以使用备份交换机。 为处理在消息队列中到达TTL的过期消息&#xff0c;可采用死信交换机进行消息转存。 通过上述描述可知&…

c高级day2shell指令

一编程语言 编程语言面向过程c面向对象c编译型语言c\c解释性语言shell脚本\python 需要解析器 二shell脚本 2.1操作系统的结构 应用层:app\代码 应用层通过shell解析器完成和内核层的交互 --------------------------------------- 内核层:内核的五大功能:1、设…

Django的FBV和CBV

Django的FBV和CBV 基于django开发项目时&#xff0c;对于视图可以使用 FBV 和 CBV 两种模式编写。 FBV&#xff0c;function base views&#xff0c;其实就是编写函数来处理业务请求。 from django.contrib import admin from django.urls import path from app01 import view…

SIT3232E——高静电防护 3.3V 单电源供电双通道 RS232 收发器,可替代MAX3232

SIT3232E 是一款 3.0V~5.5V 供电、双通道、低功耗、高静电防护 ESD 保护&#xff0c;完全满足 TIA/EIA-232 标准要求的 RS-232 收发器。 SIT3232E 包括两个驱动器和两个接收器&#xff0c;具有增强形 ESD 保护功能&#xff0c;达到 15kV 以上 HBM ESD 、 8kV …

64 # 实现一个 http-server

准备工作 上一节实现了通过 commander 的配置获取到用户的参数&#xff0c;下面完成借用 promise 写成类的方法一节没有完成的任务&#xff0c;实现一个 http-server&#xff0c;https://www.npmjs.com/package/http-server&#xff0c;http-server 是一个简单的零配置命令行静…

centos7 yum安装mysql5.7

卸载mysql 以下指令查看是否安装过 rpm -qa | grep -i mysql 如果发现已经安装&#xff0c;需要卸载了再安装&#xff08;据说&#xff0c;这样的卸载是不彻底的。&#xff09; rpm -e mysql 卸载 mariadb yum -y remove mariadb-libs-1:5.5.68-1.el7.x86_64 下载和安装mys…

Liunx:进程

冯诺依曼体系结构: 目前我们使用的计算机,包括笔记本,台式电脑,或者后端服务器,都是一堆硬件的集合,他们不是无序的组合在一起,首先它们要有协同能力,要求它们有协同能力,这就意味着它们之间一定要组织好,构成一个系统,他们才能对外输出,提供计算服务. 当代计算机都是由冯诺依曼…

漏洞扫描工具-goby(九)

什么是Goby&#xff1f; Goby是一款基于网络空间测绘技术的新一代网络安全工具&#xff0c;它通过给目标网络建立完整的资产知识库&#xff0c;进行网络安全事件应急与漏洞应急。 Goby可提供最全面的资产识别&#xff0c;目前预置了超过10万种规则识别引擎&#xff0c;能够针对…

Unity CanvasGroup组件

文章目录 1. 简介2. 组件属性2.1 Alpha(透明度)2.2 Interactable(是否为可交互)2.3 Blocks Raycasts(是否接受射线监测)2.4 Ignore Parent Groups(忽视上层的画布组带来的影响) 1. 简介 CanvasGroup(画布组) 组件&#xff0c;可集中控制整组 UI 元素(自身和所有子物体)的某些属…

Microsoft Message Queuing Denial-of-Service Vulnerability

近期官方公布了一个MSMQ的拒绝服务漏洞&#xff0c;可能因为网络安全设备的更新&#xff0c;影响业务&#xff0c;值得大家关注。 漏洞具体描述参见如下&#xff1a; Name: Microsoft Message Queuing Denial-of-Service Vulnerability Description: Microsoft Message Queuing…

jar命令的安装与使用

场景&#xff1a; 项目中经常遇到使用WinR软件替换jar包中的文件&#xff0c;有时候存在WinRAR解压替换时提示没有权限&#xff0c;此时winRAR不能用还有有什么方法替换jar包中的文件。 方法&#xff1a; 使用jar命令进行修改替换 问题&#xff1a; 执行jar命令报错jar 不…

Macbook恢复出厂设置2023详细步骤

Macbook随着用户的使用时间增长&#xff0c;文件的不断添加和各种应用程序的安装之后可能会开始变得迟缓卡顿&#xff0c;这时候选择将设备还原为默认出厂设置可以有效的解决Macbook的使用卡顿以及缓慢的问题。那么Macbook怎么恢复出厂设置?Macbook如何恢复出厂设置?一起来了…

【雕爷学编程】MicroPython动手做(37)——驱动LCD与图文显示4

MixPY——让爱(AI)触手可及 MixPY布局 主控芯片&#xff1a;K210&#xff08;64位双核带硬件FPU和卷积加速器的 RISC-V CPU&#xff09; 显示屏&#xff1a;LCD_2.8寸 320*240分辨率&#xff0c;支持电阻触摸 摄像头&#xff1a;OV2640&#xff0c;200W像素 扬声器&#…

卸载本机已安装的node.js(v.16.13.0版本)

因为要用多版本的node&#xff0c;准备安装一个nvm管理&#xff0c;所以需要先卸载掉原来安装的v.16.13.0版本。 记录一下卸载过程 1、在系统设置-应用里卸载node 妈蛋这样卸载报错。。找了下根本没有这个路径 那就只能最简单的方法了&#xff0c;全部删掉 1、删除node的安装…

Tensorflow2-初识

TensorFlow2是一个深度学习框架&#xff0c;可以理解为一个工具&#xff0c;有谷歌的全力支持&#xff0c;具有易用、灵活、可扩展、性能优越、良好的社区资源等优点。 1、环境的搭建 1.1 Anaconda3的安装 https://www.anaconda.com/ Python全家桶&#xff0c;包括Python环境和…

python爬虫(六)_urllib2:handle处理器和自定义opener

本文将介绍handler处理器和自定义opener&#xff0c;更多内容请参考:python学习指南 opener和handleer 我们之前一直使用的是urllib2.urlopen(url)这种形式来打开网页&#xff0c;它是一个特殊的opener(也就是模块帮我们建好的)&#xff0c;opener是urllib2.OpenerDirectory的…

网络安全之原型链污染

目录&#xff1a; 目录&#xff1a; 一、概念 二、举例 三、 实操了解 总结 四、抛出原题&#xff0c;历年原题复现 第一题&#xff1a; 五、分析与原理 第二题&#xff1a; 八、分析与原理 九、具体操作&#xff0c;payload与结果 结果&#xff1a; 一、概念 Java…

Spring接口InitializingBean的作用和使用介绍

在Spring框架中&#xff0c;InitializingBean接口是一个回调接口&#xff0c;用于在Spring容器实例化Bean并设置Bean的属性之后&#xff0c;执行一些自定义的初始化逻辑。实现InitializingBean接口的Bean可以在初始化阶段进行一些必要的操作&#xff0c;比如数据的初始化、资源…