STM32學習筆記(八)

36.USART控制程序

用串口實現遠程控制
通過串口助手點亮LED;
USART串口控制程序

#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){

		//查詢方式接收 (有電腦端控制指示燈亮滅)
		if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) != RESET){ 
		 //查詢串口待處理標誌位,檢查串口是否收到數據
			a =USART_ReceiveData(USART1);//讀取接收到的數據
			switch (a){ 
			//a='0' 表示ASCAL碼錶中的,而不是十六進制的數字0
				case '0':
					GPIO_WriteBit(LEDPORT,LED1,(BitAction)(0)); 
					//LED控制,改變IO端口的狀態,將LED變爲低電平,關閉指示燈。
					printf("%c:LED1 OFF ",a);
					 //表示燈關閉
					break;
				case '1':
					GPIO_WriteBit(LEDPORT,LED1,(BitAction)(1));
					 //LED控制,LED1輸出高電平,點亮
					printf("%c:LED1 ON ",a); //
					break;
				case '2':
					BUZZER_BEEP1(); //蜂鳴一聲
					printf("%c:BUZZER ",a); //把收到的數據發送回電腦
					break;
				default:
					break;
			}		  
		}

		//按鍵控制
		if(!GPIO_ReadInputDataBit(KEYPORT,KEY1)){ //讀按鍵接口的電平
			delay_ms(20); //延時20ms去抖動
			if(!GPIO_ReadInputDataBit(KEYPORT,KEY1)){ //讀按鍵接口的電平
				while(!GPIO_ReadInputDataBit(KEYPORT,KEY1)); //等待按鍵鬆開 
				printf("KEY1 "); //
			}
		}		 
		if(!GPIO_ReadInputDataBit(KEYPORT,KEY2)){ //讀按鍵接口的電平
			delay_ms(20); //延時20ms去抖動
			if(!GPIO_ReadInputDataBit(KEYPORT,KEY2)){ //讀按鍵接口的電平
				while(!GPIO_ReadInputDataBit(KEYPORT,KEY2)); //等待按鍵鬆開 
				printf("KEY2 "); //
			}
		}		 

//      delay_ms(1000); //延時
	}
}

超級終端 HyperTerminal

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

37.超級終端串口控制程序

在這裏插入圖片描述
超級終端串口控制程序

#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){
		if(USART1_RX_STA&0xC000){ //如果標誌位是0xC000表示收到數據串完成,可以處理。
			if((USART1_RX_STA&0x3FFF)==0){ 
			//單獨的回車鍵再顯示一次歡迎詞
				printf("\033[1;47;33m\r\n"); 
				//設置顏色(參考超級終端使用)
				printf(" 1y--開LED1燈      1n--關LED1燈 \r\n");
				printf(" 2y--開LED2燈      2n--關LED2燈 \r\n");
				printf(" 請輸入控制指令,按回車鍵執行! \033[0m\r\n");
			}else if((USART1_RX_STA&0x3FFF)==2 && USART1_RX_BUF[0]=='1' && USART1_RX_BUF[1]=='y'){ //判斷數據是不是2個,第一個數據是不是“1”,第二個是不是“y”
				GPIO_SetBits(LEDPORT,LED1); //LED燈都爲高電平(1)
				printf("1y -- LED1燈已經點亮!\r\n");
			}else if((USART1_RX_STA&0x3FFF)==2 && USART1_RX_BUF[0]=='1' && USART1_RX_BUF[1]=='n'){
				GPIO_ResetBits(LEDPORT,LED1); ////LED燈都爲低電平(0)
				printf("1n -- LED1燈已經熄滅!\r\n");
			}else if((USART1_RX_STA&0x3FFF)==2 && USART1_RX_BUF[0]=='2' && USART1_RX_BUF[1]=='y'){
				GPIO_SetBits(LEDPORT,LED2); //LED燈都爲高電平(1)
				printf("2y -- LED2燈已經點亮!\r\n");
			}else if((USART1_RX_STA&0x3FFF)==2 && USART1_RX_BUF[0]=='2' && USART1_RX_BUF[1]=='n'){
				GPIO_ResetBits(LEDPORT,LED2); ////LED燈都爲低電平(0)
				printf("2n -- LED2燈已經熄滅!\r\n");
			}else{ //如果以上都不是,即是錯誤的指令。
				printf("指令錯誤!\r\n"); 
			}
			USART1_RX_STA=0; //將串口數據標誌位清0
		}
	}
}

38.RTC原理與驅動程序

RTC實時時鐘,系統時間計時,作爲鬧鐘喚醒低功耗模式。
在這裏插入圖片描述
STM32的RTC只用一個32位計數器來計時,而不是年月日時分秒的分組寄存器。
通過設置可以讓計數器1秒加1,從0-0XFFFFFFFF大約計時136年
時間起點一般設置爲 1970-01-01 00:00:00(因現有函數如此定義)
如果要讀當前的年月日時分秒,先獨處32位TRC計數器值,
然後以1970-01-01 00:00:00爲起點,加上計數器中的秒數,再換算成年月日時分秒,即可得出當 前時間。

LED燈顯示RTC走時程序
LED分別以一定頻率閃爍LED2以分鐘爲間隔,LED1以秒爲間隔,奇偶分。
增加了rtc.hrtc.c文件
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接口
			GPIO_WriteBit(LEDPORT,LED2,(BitAction)(rmin%2)); //LED2接口
		}
	}
}

39.RTC驅動程序分析

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);//讀出當前時間值	
void RTC_First_Config(void);//首次啓用RTC的設置,對RTC完全初始化
void RTC_Config(void);//實時時鐘初始化,如果備用電池沒有斷開就不需要全部初始化
//兩個初始化函數,因爲不僅有主電源供電還有後備電源,
u8 Is_Leap_Year(u16 year);//判斷是否是閏年函數,   
u8 RTC_Get(void); //讀取當前時間值             
u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec);//寫入當前時間
u8 RTC_Get_Week(u16 year,u8 month,u8 day);//按年月日計算星期




#endif

rtc.c程序

/* 
	//時間讀寫與設置說明//
1,在mani函數開頭放入RTC_Config();就可以使能時鐘了。
在RTC_Config();函數中自帶判斷是不是首次使用RTC
2,使用 RTC_Get();讀出時間。讀出的數據存放在:
年 ryear (16位)
月 rmon	 (以下都是8位)
日 rday
時 rhour
分 rmin
秒 rsec
周 rweek

3,使用 RTC_Set(4位年,2位月,2位日,2位時,2位分,2位秒); 寫入時間。例如:RTC_Get(2017,08,06,21,34,00);

其他函數都是幫助如上3個函數的,不需要調用。 
注意要使用RTC_Get和RTC_Set的返回值,爲0時表示讀寫正確。

*/


#include "sys.h"
#include "rtc.h"


//以下2條全局變量--用於RTC時間的讀取
u16 ryear; //4位年
u8 rmon,rday,rhour,rmin,rsec,rweek;//2位月日時分秒周



void RTC_First_Config(void){ 
//首次啓用RTC的設置(假設RTC第一次使用,或者備用電池斷開,數據重新設置)
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
    //啓用PWR和BKP的時鐘(from APB1)
    PWR_BackupAccessCmd(ENABLE);//後備域解鎖 (p189頁,使能或者失能RTC和後備寄存器訪問)
    BKP_DeInit();//備份寄存器模塊復位(p64,將全部寄存器設爲缺省值)
    RCC_LSEConfig(RCC_LSE_ON);//外部32.768KHZ晶振開啓   
    while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET);//等待穩定    
    RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);//RTC時鐘源配置成LSE(外部低速晶振32.768KHZ)    
    RCC_RTCCLKCmd(ENABLE);//RTC開啓    
    RTC_WaitForSynchro();//開啓後需要等待APB1時鐘與RTC時鐘同步,才能讀寫寄存器    
    RTC_WaitForLastTask();//讀寫寄存器前,要確定上一個操作已經結束
    RTC_SetPrescaler(32767);//設置RTC分頻器,使RTC時鐘爲1Hz,RTC period = RTCCLK/RTC_PR = (32.768 KHz)/(32767+1)   
    RTC_WaitForLastTask();//等待寄存器寫入完成	
    //當不使用RTC秒中斷,可以屏蔽下面2條
//    RTC_ITConfig(RTC_IT_SEC, ENABLE);//使能秒中斷   
//    RTC_WaitForLastTask();//等待寫入完成
}

void RTC_Config(void){ //實時時鐘初始化
    //在BKP的後備寄存器1中,存了一個特殊字符0xA5A5
    //第一次上電或後備電源掉電後,該寄存器數據丟失,表明RTC數據丟失,需要重新配置
    if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5){//判斷寄存數據是否丟失       
        RTC_First_Config();//重新配置RTC        
        BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);//配置完成後,向後備寄存器中寫特殊字符0xA5A5
    }else{
		//若後備寄存器沒有掉電,則無需重新配置RTC
        //這裏我們可以利用RCC_GetFlagStatus()函數查看本次復位類型
        if (RCC_GetFlagStatus(RCC_FLAG_PORRST) != RESET){
            //這是上電覆位
        }
        else if (RCC_GetFlagStatus(RCC_FLAG_PINRST) != RESET){
            //這是外部RST管腳復位
        }       
        RCC_ClearFlag();//清除RCC中復位標誌

        //雖然RTC模塊不需要重新配置,且掉電後依靠後備電池依然運行
        //但是每次上電後,還是要使能RTCCLK
        RCC_RTCCLKCmd(ENABLE);//使能RTCCLK        
        RTC_WaitForSynchro();//等待RTC時鐘與APB1時鐘同步

        //當不使用RTC秒中斷,可以屏蔽下面2條
//        RTC_ITConfig(RTC_IT_SEC, ENABLE);//使能秒中斷        
//        RTC_WaitForLastTask();//等待操作完成
    }
	#ifdef RTCClockOutput_Enable   //判斷是否使用RTC輸出功能,一般不使用。
	    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
	    PWR_BackupAccessCmd(ENABLE);   
	    BKP_TamperPinCmd(DISABLE);   
	    BKP_RTCOutputConfig(BKP_RTCOutputSource_CalibClock);
	#endif
}

void RTC_IRQHandler(void){ //RTC時鐘1秒觸發中斷函數(名稱固定不可修改)
	if (RTC_GetITStatus(RTC_IT_SEC) != RESET){

	}
	RTC_ClearITPendingBit(RTC_IT_SEC); 
	RTC_WaitForLastTask();
}

void RTCAlarm_IRQHandler(void){	//鬧鐘中斷處理(啓用時必須調高其優先級)
	if(RTC_GetITStatus(RTC_IT_ALR) != RESET){
	
	}
	RTC_ClearITPendingBit(RTC_IT_ALR);
	RTC_WaitForLastTask();
}

//判斷是否是閏年函數
//月份   1  2  3  4  5  6  7  8  9  10 11 12
//閏年   31 29 31 30 31 30 31 31 30 31 30 31
//非閏年 31 28 31 30 31 30 31 31 30 31 30 31
//輸入:年份
//輸出:該年份是不是閏年.1,是.0,不是
u8 Is_Leap_Year(u16 year){                    
	if(year%4==0){ //必須能被4整除
		if(year%100==0){		
			if(year%400==0)return 1;//如果以00結尾,還要能被400整除          
			else return 0;  
		}else return 1;  
	}else return 0;
}                           
//設置時鐘
//把輸入的時鐘轉換爲秒鐘
//以1970年1月1日爲基準
//1970~2099年爲合法年份

//月份數據表                                                                       
u8 const table_week[12]={0,3,3,6,1,4,6,2,5,0,3,5}; //月修正數據表  
const u8 mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31};//平年的月份日期表

//寫入時間
u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec){
 //寫入當前時間(1970~2099年有效),
	u16 t;
	u32 seccount=0;
	if(syear<2000||syear>2099)return 1;//syear範圍1970-2099,此處設置範圍爲2000-2099       
	for(t=1970;t<syear;t++){ //把所有年份的秒鐘相加
		if(Is_Leap_Year(t))seccount+=31622400;//閏年的秒鐘數
		else seccount+=31536000;                    //平年的秒鐘數
	}
	smon-=1;
	for(t=0;t<smon;t++){         //把前面月份的秒鐘數相加
		seccount+=(u32)mon_table[t]*86400;//月份秒鐘數相加
		if(Is_Leap_Year(syear)&&t==1)seccount+=86400;//閏年2月份增加一天的秒鐘數        
	}
	seccount+=(u32)(sday-1)*86400;//把前面日期的秒鐘數相加
	seccount+=(u32)hour*3600;//小時秒鐘數
	seccount+=(u32)min*60;      //分鐘秒鐘數
	seccount+=sec;//最後的秒鐘加上去
	RTC_First_Config(); //重新初始化時鐘
	BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);//配置完成後,向後備寄存器中寫特殊字符0xA5A5
	RTC_SetCounter(seccount);//把換算好的計數器值寫入
	RTC_WaitForLastTask(); //等待寫入完成
	return 0; //返回值:0,成功;其他:錯誤代碼.    
}

//讀出時間
u8 RTC_Get(void){//讀出當前時間值 //返回值:0,成功;其他:錯誤代碼.
	static u16 daycnt=0;
	u32 timecount=0;
	u32 temp=0;
	u16 temp1=0;
	timecount=RTC_GetCounter();		
	temp=timecount/86400;   //得到天數(秒鐘數對應的)
	if(daycnt!=temp){//超過一天了
		daycnt=temp;
		temp1=1970;  //從1970年開始
		while(temp>=365){
		     if(Is_Leap_Year(temp1)){//是閏年
			     if(temp>=366)temp-=366;//閏年的秒鐘數
			     else {temp1++;break;} 
		     }
		     else temp-=365;       //平年
		     temp1++; 
		}  
		ryear=temp1;//得到年份
		temp1=0;
		while(temp>=28){//超過了一個月
			if(Is_Leap_Year(ryear)&&temp1==1){//當年是不是閏年/2月份
				if(temp>=29)temp-=29;//閏年的秒鐘數
				else break;
			}else{
	            if(temp>=mon_table[temp1])temp-=mon_table[temp1];//平年
	            else break;
			}
			temp1++; 
		}
		rmon=temp1+1;//得到月份
		rday=temp+1;  //得到日期
	}
	temp=timecount%86400;     //得到秒鐘數      
	rhour=temp/3600;     //小時
	rmin=(temp%3600)/60; //分鐘     
	rsec=(temp%3600)%60; //秒鐘
	rweek=RTC_Get_Week(ryear,rmon,rday);//獲取星期值 
	return 0;
}    

u8 RTC_Get_Week(u16 year,u8 month,u8 day){ //按年月日計算星期(只允許1901-2099年)//已由RTC_Get調用    
	u16 temp2;
	u8 yearH,yearL;
	yearH=year/100;     
	yearL=year%100;
	// 如果爲21世紀,年份數加100 
	if (yearH>19)yearL+=100;
	// 所過閏年數只算1900年之後的 
	temp2=yearL+yearL/4;
	temp2=temp2%7;
	temp2=temp2+day+table_week[month-1];
	if (yearL%4==0&&month<3)temp2--;
	return(temp2%7); //返回星期值
}

重點觀看固件庫RTC庫函數63-69 備用寄存器相關函數和215-222頁RTC功能相關庫函數。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章