很久沒在CSDN寫博客了,最近有點空學習了STM8L051F3這塊低功耗單片機。這次做的東西,其主要應用在於控制電源的開關斷操作。主要用到的外設有RTC,PWR,AWU,ADC,WWDG等。整體的功能我先大致介紹一下,這樣的話也方便大家理解下面的程序。首先利用RTC的鬧鐘中斷來控制在指定時間段電源的開關斷操作,ADC用來檢測電量小於一定百分比的時候關閉電源,WWDG用來監控程序,串口用來打印調試信息,AWU用來實現10分鐘自動喚醒。功能介紹完了,下面開始開始逐步介紹每個功能模塊的具體實現。
RTC鬧鐘中斷功能:
從上面的圖中可以看出時鐘源有HSE,HSI,LSE,LSI四種選擇方式,但爲了RTC時鐘的準確性應選擇外部晶振作爲時鐘源較爲合適,所以我們選擇外部低速時鐘LSE(32.768KHz)。如果選擇內部LSI的話,在實際測試中RTC時間有着較大誤差。並且該芯片的RTC還有以下功能
- 一個帶有微秒、秒、分、時(12或24小時格式)、星期x、日、月和年的日曆
- 一個帶中斷的可編程鬧鐘
- 一個自動喚醒單元
這些功能我們剛好全都會用得的到。主要初始化步驟如下:
1)選擇LSE作爲時鐘源
2)打開RTC外設時鐘
3)設置想要的RTC時間
4)初始化日曆
5)初始化時間
void RTC_Init(void)
{
int temp=0;
RTC_InitTypeDef RTC_InitStr;
RTC_DateTypeDef RTC_DateStr;
RTC_TimeTypeDef RTC_TimeStr;
//選擇LSE(32.768KHz)作爲時鐘源
CLK_RTCClockConfig(CLK_RTCCLKSource_LSE, CLK_RTCCLKDiv_1);
/* Wait for LSE clock to be ready */
while (CLK_GetFlagStatus(CLK_FLAG_LSERDY) == RESET);
//打開RTC時鐘
CLK_PeripheralClockConfig(CLK_Peripheral_RTC, ENABLE);
/* RTC時鐘源:LSE,計時時間:32768/128/256 = 1S */
temp = EEPROM_Read_Byte(0);//從內置的EEPROM中讀取數據
if(temp != 0X73) //判斷RTC是否已經設置過時間
{
printf("設置ING\r\n");
RTC_InitStr.RTC_HourFormat = RTC_HourFormat_24;//24小時制
RTC_InitStr.RTC_AsynchPrediv = 0x7F; //異步分頻器 128分頻
RTC_InitStr.RTC_SynchPrediv = 0x00FF; //同步分頻器 256分頻
RTC_Init(&RTC_InitStr); //初始化RTC參數
/* 初始化RTC_DateStr結構體,設置日期數據 */
RTC_DateStructInit(&RTC_DateStr); //初始化RTC_DateStr結構體
RTC_DateStr.RTC_WeekDay = RTC_Weekday_Thursday;//星期四
RTC_DateStr.RTC_Date = 28; //22日
RTC_DateStr.RTC_Month = RTC_Month_September; //9月
RTC_DateStr.RTC_Year = 18; //2018年
RTC_SetDate(RTC_Format_BIN,&RTC_DateStr); //設置日期數據
/* 初始化RTC_TimeStr結構體,設置時間數據 */
RTC_TimeStructInit(&RTC_TimeStr);//初始化RTC_TimeStr結構體
RTC_TimeStr.RTC_Hours = 17; //23H
RTC_TimeStr.RTC_Minutes = 21; //52分
RTC_TimeStr.RTC_Seconds = 0; //0秒
RTC_SetTime(RTC_Format_BIN,&RTC_TimeStr); //設置時間數據
//RTC初始化完畢 以下是EEPROM的寫入操作
FLASH_SetProgrammingTime(FLASH_ProgramTime_Standard);
FLASH_Unlock(FLASH_MemType_Data);
while(FLASH_GetFlagStatus(FLASH_FLAG_DUL)== RESET);
EEPROM_Write_Byte(0,0X73); //寫入0x73
}
else
{
printf("RTC已經設置過了\r\n");
}
}
上面是RTC的初始化函數,有一點是需要注意的,其一就是在初始化前我們進行了一次判斷,該判斷如果不加,那麼當芯片重新上電的或復位的時候,時間都會被重新初始化,這自然是不行的,所以這個判斷是有必要的加的。我們通過向EEPROM中寫入特定數據充當標誌位來實現這個操作,具體的EEPROM的讀取和寫入的子函數會在下方給出,就不加以說明了。
void EEPROM_Write_Byte(uint8_t addr, uint8_t data)
{
/* 往EEPROM地址0x1000 + addr 處寫入數據 */
FLASH_ProgramByte(FLASH_DATA_EEPROM_START_PHYSICAL_ADDRESS + addr, data);
/* 等待寫入完成 */
while(FLASH_GetFlagStatus(FLASH_FLAG_EOP)== RESET);
}
uint8_t EEPROM_Read_Byte(uint8_t addr)
{
uint8_t data;
/* 讀出EEPROM地址0x1000 + addr 處的數據 */
data = FLASH_ReadByte(FLASH_DATA_EEPROM_START_PHYSICAL_ADDRESS + addr);
return data; //返回讀取到的數據
}
這只是把RTC初始化了,鬧鐘還有沒有設置呢,代碼直接給出如下:
void Alarm_Set(int h,int m,int s)
{
RTC_AlarmTypeDef RTC_AlarmStr;
/* 初始化RTC鬧鐘結構體,設置時間數據 */
RTC_AlarmCmd(DISABLE);//先關閉才能使能
RTC_AlarmStructInit(&RTC_AlarmStr);
RTC_AlarmStr.RTC_AlarmTime.RTC_Hours=h;
RTC_AlarmStr.RTC_AlarmTime.RTC_Minutes=m;
RTC_AlarmStr.RTC_AlarmTime.RTC_Seconds=s;
RTC_AlarmStr.RTC_AlarmTime.RTC_H12=RTC_H12_PM;
RTC_AlarmStr.RTC_AlarmMask=RTC_AlarmMask_DateWeekDay;//屏蔽日期
RTC_AlarmStr.RTC_AlarmDateWeekDaySel=RTC_AlarmDateWeekDaySel_Date;
RTC_AlarmStr.RTC_AlarmDateWeekDay=RTC_AlarmDateWeekDaySel_Date;
RTC_SetAlarm(RTC_Format_BIN,&RTC_AlarmStr);
RTC_AlarmCmd(ENABLE);//使能RTC鬧鐘功能
RTC_ClearFlag(RTC_FLAG_ALRAF);//清除一次鬧鐘中斷標誌位
RTC_ITConfig(RTC_IT_ALRA,ENABLE);
}
h,m,s對應小時,分鐘,秒鐘,同樣有兩點需要注意,第一點:在設置鬧鐘時間的時候需要關閉使能才能是指鬧鐘時間。第二點:其結構體成員RTC_AlarmMask是要屏蔽的中斷選項,比如你想要定時10點的鬧鐘,而不想管分和秒是多少那麼,就可以把分和秒給屏蔽掉。
低功耗:
作爲一款低功耗芯片,低功耗的亮點是值得我們瞭解和學習的。接下來我來談談我對STM8L低功耗模式的理解和運用。
STM8L與5種低功耗模式:
1.等待模式(Wait):等待模式又可分爲WFI和WFE,他們的相同點是所有的內部中斷和外部中斷及復位都可以使其退出等待模式。不同的是,WFE還可以通過事件來喚醒。進入該模式是通過執行WFI或者WFE指令來進入。該模式下的耗電量是MVR相較於ULP(極低功耗)是功耗要多一些的。在這種低功耗模式下CPU都是沒有工作的,怎麼理解呢?在實際測試中CPU進入低功耗模式,代碼是沒有在運行的,相當於程序阻塞在了那裏。
2.低功耗運行模式(Low power run mode):進入該模式的方法是通過軟件序列配置,在該模式下,CPU是運行的,Flash與EEPROM是停止的,可以選擇相應的外設運行。RAM的運行是通過低速振盪器時鐘(LSI或LSE)來完成,電壓管理器模式被配置爲超低壓模式(ULP),退出該模式是通過軟件或者復位。
3.低功耗等待模式(Low power wait mode):該模式的進入是通過在低功耗運行模式下執行WFE指令,該模式下CPU是停止運行的,其他的特點與Low power run mode相同。退出該模式的方法是觸發內部或者外部的事件,當被觸發時就返回低功耗運行模式。
4.活躍停機模式(Active-halt mode):該模式就是我這次需要用到的模式,在該模式下除了RTC外CPU和外設時鐘是停止的,可通過觸發一個RTC中斷或外部中斷或復位來退出。
5.地體模式(Halt mode):該模式下,CPU和外設時鐘是停止的,只可通過觸發一個外部中斷或復位來喚醒。
通過介紹以上的5個模式,經過比較選擇活躍停機模式是比較適合我做的這個東西。具體代碼是通過如下的指令來實現的。
halt();
執行以上指令就是單片機進入了活躍停機模式。
自動喚醒:
在活躍停機模式下要實現10分鐘對ADC檢測一次電量就需要使用到RTC的自動喚醒功能。該功能會使單片機退出活躍停機模式,然後檢測電量,之後又進入活躍停機模式,這樣循環往復每次只甦醒檢測一次電量的時間,其他時間段CPU都不工作,這樣才能已達到最低功耗的實現。
以下是自動喚醒的代碼:
void AWU_RTC(int s)
{
RTC_WakeUpClockConfig(RTC_WakeUpClock_CK_SPRE_16bits);
RTC_ITConfig(RTC_IT_WUT,ENABLE);
RTC_SetWakeUpCounter(s);//設定喚醒時間間隔(秒)
RTC_WakeUpCmd(ENABLE);
}
注意一點,設定完一次喚醒時間,是不需要再次調用該函數的。如果不需要喚醒了,那麼把使能DISABLE即可。該自動喚醒的時鐘是16bits所以最長喚醒時間不能超過2800秒;
窗口看門狗:
在低功耗模式下,如果CPU停止工作,這時如果還要加上看門狗的功能,那該怎麼辦呢?那麼窗口看門狗就可以實現,他會在單片機進入低功耗模式下的時候,停止對看門狗的計數,這樣的話,就不怕單片機因COU停止工作而喂不了狗,導致程序復位的情況發生了。
主函數代碼附上:
void main(void)
{
long ADC_Value=0,ADC_Value_1=0;
CLK_SYSCLKDivConfig(CLK_SYSCLKDiv_16); // CPU frequency @ 1MHz
GPIO_Init(GPIOC, GPIO_Pin_4, GPIO_Mode_Out_PP_High_Slow);//繼電器(電源)
GPIO_SetBits(GPIOC,GPIO_Pin_4); //開電源繼電器
/*************未使用到的IO口設置爲低速推輓輸出低*******************/
GPIO_Init(GPIOA,GPIO_Pin_0,GPIO_Mode_Out_PP_Low_Slow);
GPIO_Init(GPIOB,GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7,GPIO_Mode_Out_PP_Low_Slow);
GPIO_Init(GPIOC,GPIO_Pin_1|GPIO_Pin_4,GPIO_Mode_Out_PP_Low_Slow);
GPIO_Init(GPIOD,GPIO_Pin_0,GPIO_Mode_Out_PP_Low_Slow);
/****************************************************************/
UART1_Init();
ADC1_Config();
RTC_Init();
// TIM4_Config();
ADC_Value_1 = Read_ADC_Value();
// IWDG_Config(); //獨立看門狗
WWDG_Config(); //窗口看門狗
enableInterrupts(); //開總中斷
printf("The System Start\r\n");
AWU_RTC(600);//設置10分鐘喚醒一次
while(1)
{
WWDG_SetCounter(COUNTER_INIT);//窗口看門狗喂狗
RTC_Print(); //打印RTC時間
if(Ws_Flag==1) //如果是早上時間則要檢測ADC
{
ADC_Value_1 = Read_ADC_Value();
ADC_Value=(ADC_Value_1*3300)/4096;
if(ADC_Value<2450)
{
GPIO_ResetBits(GPIOC,GPIO_Pin_4);
printf("***檢測到電源不足,已關閉電源***\r\n");
}
else if(ADC_Value>=2540)
{
GPIO_SetBits(GPIOC,GPIO_Pin_4);
printf("***電源充足,已開啓電源***\r\n");
}
printf("ADC:%ldmv\r\n",ADC_Value);
}
else //晚上時間關閉則關閉電源
{
GPIO_ResetBits(GPIOC,GPIO_Pin_4);
}
halt();//進入活躍停機模式,程序在這兒阻塞
}
}
該單片機不像STM32,STM8的單片機要手動開啓中斷,即代碼 enableInterrupts(); //開總中斷 ,否則進入不了鬧鐘中斷。
鬧鐘中斷和喚醒中斷共用一箇中斷服務函數如下:
INTERRUPT_HANDLER(RTC_CSSLSE_IRQHandler,4)
{
if(RTC_GetITStatus(RTC_IT_ALRA))//判斷鬧鐘中斷標誌位
{
if(Close_Flag==1)
{
Time_print();
}
else if(Open_Flag==1)
{
Time_print();
}
}
RTC_ClearITPendingBit(RTC_IT_ALRA);//清除標誌位
RTC->ISR2 &= (uint8_t)(~0x04);
}
其他外設的初始化代碼,可以參考下面壇主的鏈接: