很久没在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中写入特定数据来充当标志位,这样就实现了RTC只初始化一次。具体的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);
}
其他外设的初始化代码,可以参考下面坛主的链接: