CW32

围观 11

实时时钟(RTC)是一个专用的计数器 / 定时器,可提供日历信息,包括小时、分钟、秒、日、月份、年份以及星期。RTC 具有两个独立闹钟,时间、日期可组合设定,可产生闹钟中断,并通过引脚输出;支持时间戳功能,可通过引脚触发,记录当前的日期和时间,同时产生时间戳中断;支持周期中断;支持自动唤醒功能,可产生中断并通过引脚输出;支持1Hz 方波和RTCOUT 输出功能;支持内部时钟校准补偿。

CW32L083 内置经独立校准的 32kHz 频率的 RC 时钟源,为 RTC 提供驱动时钟,RTC 可在深度休眠模式下运行, 适用于要求低功耗的应用场合。

1.png

RTC功能框图

RTC 时钟源RTCCLK 通过CR1寄存器进行选择,可选源为LSE、LSI和 HSE分频时钟。

主要功能

实时时钟 (RTC) 主要由专用的高精度 RTC 定时器组成,时钟源可选择外部低速时钟 LSE 或内部低速时钟 LSI,当选择外部高速时钟 HSE 时,因精度受限只能用作一般定时 / 计数器。 

时间寄存器 RTC_TIME 和日期寄存器 RTC_DATE,以 BCD 码格式分别记录当前的时间和日期值,在对其写入时会自动进行合法性检查,任何非法的时间或日期值将不能被写入,如 32 日、2A 时、61 秒、13 月等。

日期寄存器 RTC_DATE 中,YEAR 位域表示年,有效值 0 ~ 99;MONTH 位域表示月,有效值 1 ~ 12;DAY 位域表 示日,有效值 1 ~ 31;WEEK 位域表示星期,有效值 0 ~ 6,其中 0 表示星期日,1 ~ 6 表示星期一至星期六。

时间寄存器 RTC_TIME 中,SECOND 位域表示秒,有效值 0 ~ 59;MINUTE 位域表示分,有效值 0 ~ 59;HOUR 位域代表小时,有效值为 1 ~ 12 或 0 ~ 23;HOUR 位域的最高位代表 AM/PM(上午 / 下午):- ‘0’表示 AM - ‘1’表示 PM HOUR。控制寄存器 RTC_CR0 的 H24 位域用于选择 12 或 24 小时制:• H24 为‘1’时,选择 24 时制 • H24 为‘0’时,选择 12 时制。HOUR位域值含义详细见下表:

2.png

其他功能

1.闹钟 A 和闹钟 B 

RTC 支持 2 个独立闹钟(闹钟 A 和闹钟 B),可在一周内任意时刻产生闹钟事件,并产生闹钟中断,同时将闹钟匹配事件通过外部 RTC_OUT 引脚输出。设置控制寄存器 RTC_CR2 的 ALARMAEN 和 ALARMBEN 位域为 1,可分别单独使能闹钟 A 和闹钟 B。通过设置闹钟 A、B 控制寄存器(RTC_ALARMA 和 RTC_ALARMB)的时、分、秒匹配控制位 HOUREN、 MINUTEEN、SECONDEN 和时、分、秒计数值 HOUR、MINUTE、SECOND,可设定闹钟在‘xx 时 xx 分 xx 秒’, 或‘xx 分 xx 秒’或‘xx 时 xx 分’或‘xx 时’等多种组合产生闹钟事件;闹钟星期使能控制位 WEEKMASK,可选择一周中的任意一天产生闹钟事件,bit0 代表星期日,bit1 ~ 6 代表星期一至星期六。采用 12 或 24 小时制,闹钟控制寄存器 RTC_ALARMx(x = A, B) 的设置值可能不同,示例如下表:

3.png

2.周期中断功能:RTC 内置周期中断模块,可产生固定周期的中断信号。

3.自动唤醒功能

自动唤醒定时器是一个 16 位可编程自动重载减法计数器,计数时钟源为RTCCLK或者RTC1HZ时钟。定时范围为:61μs ~ 145h。当计数器溢出时,可产生自动唤醒中断,并将溢出标志通过 RTC_OUT 引脚输出。设置控制寄存器 RTC_CR2 的 AWTEN 位域为 1 使能自动唤醒功能,该功能专为低功耗应用场合而设计,可工作于 MCU 的全部工作模式。

自动唤醒定时器计数周期由计数时钟源和重载寄存器 RTC_AWTARR 决定,定时时长计算公式为:自动唤醒定时器定时周期 =(RTC_AWTARR+1)/ 唤醒定时器计数时钟频率 最短定时:( 0+1 ) / 16384Hz = 61μs 最长定时:(65535+1) / 0.125Hz = 524288s = 8738min ≈ 145.63h 通过 RTC 中断使能寄存器 RTC_IER 的 AWTIMER 位域,可选择自动唤醒定时器溢出时是否产生中断请求。

4.时间戳功能 

RTC 支持时间戳功能,即通过 RTC_TAMP 引脚触发,将当前时间和日期分别保存到时间戳日期寄存器 RTC_TAMPDATE 和时间戳时间寄存器 RTC_TAMPTIM,同时可产生时间戳中断。控制寄存器 RTC_CR2 的 TAMPEDGE 位域用来选择触发时间戳的信号是上升沿还是下降沿有效,RTC_CR2 寄存 器的 TAMPEN 位域用于使能时间戳功能。用户可灵活选择触发引脚 RTC_TAMP,并需配置该引脚为数字输入和复用功能,具体 RTC_TAMP 引脚请参考数据手册引脚定义。当发生时间戳事件时,时间戳事件标志位 RTC_ISR.TAMP 会被置 1,如果设置了时间戳中断使能位 RTC_IER.TAMP 为 1,将产生中断请求。如果发生第一次时间戳事件后,未通过软件清除 RTC_ISR.TAMP 标志位,又产生了第二次时间戳事件,时间戳溢出标志位 RTC_ISR.TAMPOV 会被置 1,如果设置了时间戳溢出中断使能位 RTC_IER.TAMPOV 为 1,将产生中断请求。

实际例程操作——RTC初始化,日期时间读取,间隔中断,闹钟设置

1.系统时钟初始化设置

void RCC_Configuration(void)
{
    RCC_HSI_Enable(RCC_HSIOSC_DIV6);   //设置系统时钟为8M
    RCC_LSE_Enable(RCC_LSE_MODE_OSC, RCC_LSE_AMP_NORMAL, RCC_LSE_DRIVER_NORMAL); 
    // 打开LSE时钟,作为RTC的计数时钟
    RCC_APBPeriphClk_Enable1(RCC_APB1_PERIPH_RTC, ENABLE);  //打开RTC模块工作时钟
}

2.配置输出时间所需GPIO口以及串口UART配置

void LogInit(void)
{
        SerialInit(LOG_SERIAL_BPS);
}
static void SerialInit(uint32_t BaudRate)
{
    uint32_t PCLK_Freq;
    GPIO_InitTypeDef GPIO_InitStructure = {0};
    UART_InitTypeDef UART_InitStructure = {0};
    PCLK_Freq = SystemCoreClock >> pow2_table[CW_SYSCTRL->CR0_f.HCLKPRS];
    PCLK_Freq >>= pow2_table[CW_SYSCTRL->CR0_f.PCLKPRS];
    // 调试串口使用UART5//    PB8->TX//  PB9<-RX// 时钟使能
    __RCC_GPIOB_CLK_ENABLE();
    __RCC_UART5_CLK_ENABLE();
    // 先设置UART TX RX 复用,后设置GPIO的属性,避免口线上出现毛刺
    PB08_AFx_UART5TXD();
    PB09_AFx_UART5RXD();
    PIO_InitStructure.Pins = GPIO_PIN_8;
    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_Init(CW_GPIOB, &GPIO_InitStructure);
    GPIO_InitStructure.Pins = GPIO_PIN_9;
    GPIO_InitStructure.Mode = GPIO_MODE_INPUT;
    GPIO_Init(CW_GPIOB, &GPIO_InitStructure);

    UART_InitStructure.UART_BaudRate = BaudRate;// 波特率
    UART_InitStructure.UART_Over = UART_Over_16;// 采样方式
    UART_InitStructure.UART_Source = UART_Source_PCLK;// 传输时钟源UCLK
    UART_InitStructure.UART_UclkFreq = PCLK_Freq;// 传输时钟UCLK频率
    UART_InitStructure.UART_StartBit = UART_StartBit_FE;// 起始位判定方式
    UART_InitStructure.UART_StopBits = UART_StopBits_1;// 停止位长度
    UART_InitStructure.UART_Parity = UART_Parity_No;// 校验方式
    UART_InitStructure.UART_HardwareFlowControl = UART_HardwareFlowControl_None;
    //硬件流控
    UART_InitStructure.UART_Mode = UART_Mode_Rx | UART_Mode_Tx; // 发送/接收使能
    UART_Init(CW_UART5, &UART_InitStructure);
}

3.设置输出时间日期格式

void ShowTime(void)
{

    RTC_TimeTypeDef RTC_TimeStruct = {0};
    RTC_DateTypeDef RTC_DateStruct = {0};
    static uint8_t *WeekdayStr[7]= {"SUN","MON","TUE","WED","THU","FRI","SAT"};
    static uint8_t *H12AMPMStr[2][2]= {{"AM","PM"},{"",""}};

    RTC_GetDate(&RTC_DateStruct);// 取用当前日期,BCD格式
    RTC_GetTime(&RTC_TimeStruct);// 获取当前时间,BCD格式
    printf(".Date is 20%02x/%02x/%02x(%s).Time is %02x%s:%02x:%02x\r\n", 
    RTC_DateStruct.Year, RTC_DateStruct.Month, RTC_DateStruct.Day, 
    WeekdayStr[RTC_DateStruct.Week], RTC_TimeStruct.Hour, 
    H12AMPMStr[RTC_TimeStruct.H24][RTC_TimeStruct.AMPM],RTC_TimeStruct.Minute, 
    RTC_TimeStruct.Second);//串口打印数据
}

Void RTC_GetDate(RTC_DateTypeDef* RTC_Date)
{

    uint32_t RegTmp = 0;

    RegTmp = CW_RTC->DATE;
    while (RegTmp != CW_RTC->DATE)
    {

      RegTmp = CW_RTC->DATE;    // 连续两次读取的内容一致,认为读取成功

    }

    RTC_Date->Day = (uint8_t)(RegTmp & RTC_DATE_DAY_Msk);
    RTC_Date->Month = (uint8_t)((RegTmp & RTC_DATE_MONTH_Msk) >> 8);
    RTC_Date->Year = (uint8_t)((RegTmp & RTC_DATE_YEAR_Msk) >> 16);
    RTC_Date->Week = (uint8_t)((RegTmp & RTC_DATE_WEEK_Msk) >> 24);
}

Void RTC_GetTime(RTC_TimeTypeDef* RTC_TimeStruct)
{

    uint32_t RegTmp = 0;
    RTC_TimeStruct->H24 = CW_RTC->CR0_f.H24;  // 读CR0是否需要连读两次,待硬件检测
    RegTmp = CW_RTC->TIME;
    while (RegTmp != CW_RTC->TIME)
    {
      RegTmp = CW_RTC->TIME;    // 连续两次读取的内容一致,认为读取成功
    }

    RTC_TimeStruct->Hour = (uint8_t)((RegTmp & RTC_TIME_HOUR_Msk) >> 16);
    RTC_TimeStruct->Minute = (uint8_t)((RegTmp & RTC_TIME_MINUTE_Msk) >> 8);
    RTC_TimeStruct->Second = (uint8_t)(RegTmp & RTC_TIME_SECOND_Msk);
    if (RTC_TimeStruct->H24 == RTC_HOUR12)

    {
      RTC_TimeStruct->AMPM = RTC_TimeStruct->Hour >> 5;
      RTC_TimeStruct->Hour &= 0x1f;
    }
}

4.RTC模块初始化,ErrorStatus 返回值为SUCCESS或ERROR

ErrorStatus RTC_Init(RTC_InitTypeDef* RTC_InitStruct)
{

     CW_SYSCTRL->APBEN1_f.RTC = 1;            //  启动RTC外设时钟,使能RTC模块

     if ((RCC_GetAllRstFlag() & SYSCTRL_RESETFLAG_POR_Msk) != RCC_FLAG_PORRST)  
      //不是上电复位,直接退出

     { 
       RCC_ClearRstFlag(RCC_FLAG_ALLRST);        
          return SUCCESS; 
      }

     RTC_Cmd(DISABLE);                        //  停止RTC,保证正确访问RTC寄存器

     RTC_SetClockSource(RTC_InitStruct->RTC_ClockSource);        // 设置RTC时钟源, 用户需首先启动RTC时钟源!!!
     RTC_SetDate(&RTC_InitStruct->DateStruct);// 设置日期,DAY、MONTH、YEAR必须为BCD方,星期为0~6,代表星期日,星期一至星期六
     RTC_SetTime(&RTC_InitStruct->TimeStruct); //时间,HOUR、MINIUTE、SECOND必须为BCD方式,用户须保证HOUR、AMPM、H24之间的关联正确性
     RTC_Cmd(ENABLE);
     RCC_ClearRstFlag(RCC_FLAG_ALLRST);  
     return SUCCESS;
}

5.RTC周期中断时间设置

int RTC_SetInterval(uint8_t Period)
{

    uint16_t timeout = 0xffff;
    RTC_UNLOCK();
    if (IS_RTC_START())         // 如果RTC正在运行,则使用WINDOWS、ACCESS访问
    {

      CW_RTC->CR1_f.ACCESS = 1; 
      while ((!CW_RTC->CR1_f.WINDOW) && timeout--);
      if (timeout == 0) return 1;
     }
     CW_RTC->CR0_f.INTERVAL = Period;
     CW_RTC->CR1_f.ACCESS = 0;
     RTC_LOCK();
     return 0;
}

6.设置时钟中断使能

int RTC_ITConfig(uint32_t RTC_IT, FunctionalState NewState)
{

    uint16_t timeout = 0xffff;
    RTC_UNLOCK();
    CW_RTC->CR1_f.ACCESS = 1;
    while ((!CW_RTC->CR1_f.WINDOW) && timeout--);
    if (timeout == 0) return 1;
    if (!NewState)
    {
      CW_RTC->IER &= ~RTC_IT;
    }
    else
    {
      CW_RTC->IER |= RTC_IT;
    }

    CW_RTC->CR1_f.ACCESS = 0;
    RTC_LOCK();
    return 0;
}

void RTC_IRQHandlerCallBack(void)
{

    if (RTC_GetITState(RTC_IT_ALARMA))
    {
      RTC_ClearITPendingBit(RTC_IT_ALARMA);
      printf("*********Alarm!!!!\r\n");

    }
    if (RTC_GetITState(RTC_IT_INTERVAL))
    {
      RTC_ClearITPendingBit(RTC_IT_INTERVAL);
      ShowTime();
    }
 
void NVIC_Configuration(void)
{
      __disable_irq();
      NVIC_EnableIRQ(RTC_IRQn);
      __enable_irq();
}

7.RTC时钟测试,初始化日历,使用间隔中断0.5秒通过Log输出日期时间

int32_t main(void)
{

    RTC_InitTypeDef RTC_InitStruct = {0};
    RTC_AlarmTypeDef RTC_AlarmStruct = {0};
    /*系统时钟配置*/
    RCC_Configuration();
    /* GPIO 口配置*/
    GPIO_Configuration();
    LogInit();//配置输出时间所需GPIO口以及串口UART配置
    printf("RTC Init...\r\n");
    printf(" (RTC CR0:%04x,CR1:%04x,CR2:%04x,RESET FLAG:0x%08x)\r\n",CW_RTC-
    >CR0,CW_RTC->CR1,CW_RTC->CR2,CW_SYSCTRL->RESETFLAG);
    RCC_LSE_Enable(RCC_LSE_MODE_OSC, RCC_LSE_AMP_NORMAL, RCC_LSE_DRIVER_NORMAL);  // 选择LSE为RTC时钟
    RTC_InitStruct.DateStruct.Day = 0x21;             //日
    RTC_InitStruct.DateStruct.Month = RTC_Month_June;//月
    RTC_InitStruct.DateStruct.Week = RTC_Weekday_Monday;//星期
    RTC_InitStruct.DateStruct.Year = 0x21; //年
    //设置日期,DAY、MONTH、YEAR必须为BCD方式,星期为0~6,代表星期日,星期一至星期六
    printf("-------Set Date as 20%x/%x/%x\r\n", RTC_InitStruct.DateStruct.Year,RTC_InitStruct.DateStruct.Month,RTC_InitStruct.DateStruct.Day);
    //打印日期
    RTC_InitStruct.TimeStruct.Hour = 0x11;   //时    
    RTC_InitStruct.TimeStruct.Minute = 0x58;//分
    RTC_InitStruct.TimeStruct.Second = 0x59;//秒
    RTC_InitStruct.TimeStruct.AMPM = 0;
    RTC_InitStruct.TimeStruct.H24 = 0; //采用12小时设置
    //设置时间,HOUR、MINIUTE、SECOND必须为BCD方式,用户须保证HOUR、AMPM、H24之间的关联正确性
    printf("-------Set Time as %02x:%02x:%02x\r\n", RTC_InitStruct.TimeStruct.Hour,RTC_InitStruct.TimeStruct.Minute,RTC_InitStruct.TimeStruct.Second);//打印时间
    RTC_InitStruct.RTC_ClockSource = RTC_RTCCLK_FROM_LSE;
    RTC_Init(&RTC_InitStruct);    // RTC模块初始化, 用户需选定需要使用的时钟源
    printf("=====Set interval period as 0.5s...\r\n");
    RTC_SetInterval(RTC_INTERVAL_EVERY_0_5S);
    //闹钟为工作日上午的6:45
    RTC_AlarmStruct.RTC_AlarmMask = RTC_AlarmMask_WeekMON | RTC_AlarmMask_WeekTUE |
    RTC_AlarmMask_WeekWED | RTC_AlarmMask_WeekTHU |RTC_AlarmMask_WeekFRI;
    //设定时间为周一到周五
    RTC_AlarmStruct.RTC_AlarmTime.Hour = 6;
    RTC_AlarmStruct.RTC_AlarmTime.Minute = 0x45;
    RTC_AlarmStruct.RTC_AlarmTime.Second = 0;
    RTC_SetAlarm(RTC_Alarm_A, &RTC_AlarmStruct);   // 设置闹钟,BCD格式
    RTC_AlarmCmd(RTC_Alarm_A, ENABLE);//使能闹钟
    printf("=====Enable ALRAMA and INTERVAL IT...\r\n");
    RTC_ITConfig(RTC_IT_ALARMA | RTC_IT_INTERVAL, ENABLE);
    //设置中断使能
    While(1){}
}

8.通过UART串口验证RTC工作正常

4.png

以上是CW32L083单片机的RTC设置时间及闹钟部分的介绍,CW32其他型号亦可参考此篇文档。有关芯片购买事宜,请咨询武汉芯源的销售和官方代理商。更多MCU详细信息,请访问武汉芯源官方网站:https://www.whxy.com

来源:武汉芯源半导体

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 38

Cortex-M0+处理器中内置有一个SysTick定时器,通常用于嵌入式操作系统的多任务切换,在不使用操作系统的应用中,亦可作为其它用途,如定时、计时或者为需要周期性执行的任务提供中断源。

1.SysTick工作原理

SysTick定时器内部含有一个24位的递减计数器,当计数减至0时,会从SysTick的重装载寄存器中取值作为计数器的初始值,同时可以选择在这个时候产生中断(异常号:15)。例如设置重装载寄存器为100,那么当计数减为0时,就会重新复位为100继续递减计数。

它的特点是: 

• 24 位递减计数器 

• 自动重装载能力 

• 当计数器达到 0 时产生可屏蔽的系统中断

2.SysTick寄存器介绍

在core_cm0plus.h中展示了四种寄存器,我们将一一介绍:

typedef struct
{
     __IOM uint32_t CTRL;                   
    /*!< Offset: 0x000 (R/W)  SysTick Control and Status Register */
    __IOM uint32_t LOAD;                   
    /*!< Offset: 0x004 (R/W)  SysTick Reload Value Register */

    __IOM uint32_t VAL;                    
    /*!< Offset: 0x008 (R/W)  SysTick Current Value Register */
    __IM  uint32_t CALIB;                 
    /*!< Offset: 0x00C (R/ )  SysTick Calibration Register */
} SysTick_Type;

IOM uint32_t CTRL控制寄存器:

第0位:ENABLE,SysTick使能位(0:关闭SysTick功能,1:开启SysTick功能);

第1位:TICKINT,SysTick中断使能位(0:关闭SysTick中断,1:开启SysTick中断);

第2位:CLKSOURCE,SysTick时钟选择(1:使用HCLK,0:使用参考时钟频率);

第3为:COUNTFLAG,SysTick计数比较标志,如果在上次读取本寄存器后,SysTick已经数到0了,则该位为1,如果读取该位,该位自动清零。

__IOM uint32_t LOAD重载寄存器:

24位的寄存器,最大计数0xFFFFFF。当SysTick计数器递减至0时,重载寄存器中的值就会被重装载,继续开始递减。

__IOM uint32_t VAL当前值寄存器:

24位的寄存器,读取时返回当前计数器的计数值,写任何值都会使之清零,同时还会清除SysTick 控制寄存器中的COUNTFLAG 标志。

__IM  uint32_t CALIB校准值寄存器:

只读寄存器,主要存放10mS校准值,该值和MCU相关。

3.操控SysTick定时器

在MDK开发环境中,我们不必要非得去操作每一个寄存器,可以通过调用CW函数库中的函数来进行相关的操作。

void InitTick(uint32_t HclkFreq)

初始化SysTick滴答定时器,带入的参数为HCLK的频率,如HCLK为24MHz,则带入参数为24000000。该函数会调用uint32_t SysTick_Config(uint32_t ticks)函数完成SysTick定时器的相关配置并启动。SysTick默认为1mS定时器,如果需要修改定时周期,则需要修改uint32_t SysTick_Config(uint32_t ticks)函数中重装载值寄存器配置。

__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)   

初始化系统计时器及其中断,并启动。     

(1) SysTick->LOAD  = (uint32_t)(ticks - 1UL);  

设置SysTick重装载值

(2)NVIC_SetPriority(SysTick_IRQn, 

(1UL << __NVIC_PRIO_BITS) - 1UL);                        

设置SysTick定时器中断优先级

(3) Tick->VAL = 0UL;                          

加载SysTick计数器值

(4) SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk |

                   SysTick_CTRL_TICKINT_Msk   |

                   SysTick_CTRL_ENABLE_Msk;     

设置SysTick IRQ中断使能,并开启SysTick定时器

4.Systick延时功能实现

对于mS级及以上的延时,可在完成SysTick定时器初始化后,通过SysTickDelay(uint32_t Delay)函数来实现,该函数的形参为需要延时的mS数。

对于uS级延时,一般通过调整__NOP 空指令数量来实现,不建议用SysTick定时器来实现,主要原因是M0+系统中固有的中断响应时间(压栈和出栈)、中断处理时间等会影响uS延时精度。

int main(void)
{ 
    __RCC_GPIOC_CLK_ENABLE();   	//设置HCLK为24MHz
    InitTick( 24000000 );         		//初始化SysTick为1mS定时器
    GPIO_InitTypeDef  GPIO_InitStructure1 = {0} ;  //初始化对应GPIO口
    GPIO_InitStructure1.Pins = GPIO_PIN_3 ;		
    GPIO_InitStructure1.IT = GPIO_IT_NONE;
    GPIO_InitStructure1.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_Init(CW_GPIOC, &GPIO_InitStructure1);
    PC03_SETHIGH();   // LED灯置高电平,低电平有效
    while(1)
    {
    PC03_TOG(); //PC03口电平反转
    SysTickDelay (100);//延时100mS
    PC03_TOG();//PC03口电平再次反转
    SysTickDelay (100);//延时100mS 
    }
}

来源:武汉芯源半导体

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 10

CW32系列芯片支持3种工作模式,运行模式、休眠模式以及深度休眠模式,本文以CW32L083为例介绍低功耗模式的特性。MCU上电以后,系统自动进入运行模式,可以通过软件配置,进入休眠或者深度休眠两种低功耗模式,进入低功耗运行状态后,可以通过外设中断触发唤醒机制,使得系统返回到运行模式,三种工作模式的转换机制如下图所示:

1.png

三种模式下CPU、时钟及外设状态:

• 运行模式(Active mode) 

运行模式下 CPU 正常运行,所有模块用户均可正常使用。

• 休眠模式(Sleep mode) 

休眠模式下,CPU 停止运行,所有外设不受影响,所有I/O引脚保持状态不变。

• 深度休眠模式(DeepSleep mode) 

深度休眠模式下,CPU停止运行,高速时钟(HSE、HSIOSC)自动关闭,低速时钟(LSE、 LSI、RC10K、RC150K)保持原状态不变。深度休眠模式的功耗远小于休眠模式。

CW32L083可以使用等待中断专用指令,WFI(Wait for Interrupt),配合系统控制寄存器(SCR, System Control Register)的SLEEPONEXIT和SLEEPDEEP位域,可实现立即进入或退出(中断服务程序)时进入休眠模式或深度休眠模式。

• 立即进入 

执行WFI指令,MCU将立即进入休眠模式(SLEEPDEEP为0时)或深度休眠模式(SLEEPDEEP为1时) 

• 退出时进入 

将SLEEPONEXIT位置1,当退出最低优先级的中断服务程序后,MCU会进入休眠模式(SLEEPDEEP为0时)或深度休眠模式(SLEEPDEEP为 1时),而不需执行WFI指令 。

2.png

注:在深度休眠模式下,系统将自动关闭高速时钟,如果需要在深度休眠模式下使部分外设仍保持运行,则需要在进入深度休眠模式前,启动相应的低速时钟并将该外设时钟设置为此低速时钟。

在休眠模式或深度休眠模式下,均可通过中断来唤醒CPU,返回到运行模式。如果用户在中断服务程序中执行WFI命令进入休眠(包括深度休眠),则需要比此中断更高优先级的中断才能唤醒CPU,因此,强烈建议在准备进入休眠前,应先处理完所有中断服务程序,并且清除所有中断请求和中断标志,以下是配置进入低功耗模式时所需注意的事项。

• 建议在进入低功耗模式前加一段时间的延迟,以免出现上电就进入低功耗模式,无法烧录程序。

• 系统可以配置从Deepsleep唤醒后,系统时钟来源是HSI还是进入休眠前的时钟。

• 系统进入低功耗模式,端口状态不会发生改变,此时需要客户根据实际应用来配置端口状态来达到理想的功耗值,未用端口建议配置为模拟模式。

• 其他的RTC等低功耗运行模块因在深度休眠下高速时钟停止运行,所以如果需要在深度休眠模式下运行RTC等模块,需配置模块时钟源为LSI或LSE。

根据上述内容,可以配置CW32L083的低功耗应用的例程,具体的代码可以查看CW32L083的固件库中PWR_CurrentConsumption这一例程,配置PA04和PA05为引脚输入,并开启下降沿中断,在中断服务函数改变gKeyStatus的值,从而使得MCU在main中进入低功耗休眠模式。

volatile uint8_t gKeyStatus;  
volatile uint32_t gFlagWakeUpIrq = 0;  
int main(void)
{

   RCC_HSI_Enable( RCC_HSIOSC_DIV6); //配置系统时钟为HSI 8M
   InitTick(8000000ul); //初始化SysTick
   LED_Init();         //LED初始化
   BSP_PB_Init();  //按键初始化
   while (1)
   {
      gKeyStatus = 0;//在没有进入低功耗模式前,PC03每间隔1s翻转一次状态
      do
      {
     PC03_TOG();         //翻转LED1
      SysTickDelay(1000);   //延迟1s.
      } while (gKeyStatus == 0);       
      PC03_SETLOW();         //PC03置低
      DeepSleepModeTest();    //进入深度睡眠模式
    }
}

//按键初始化,设置PA05沿下降沿触发中断
void BSP_PB_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure = {0};
    //打开GPIOA时钟
    REGBITS_SET(CW_SYSCTRL->AHBEN, SYSCTRL_AHBEN_GPIOA_Msk);
    GPIO_InitStructure.Pins = GPIO_PIN_5;
    GPIO_InitStructure.Mode = GPIO_MODE_INPUT_PULLUP;
    GPIO_InitStructure.IT = GPIO_IT_FALLING;
    GPIO_Init(CW_GPIOA, &GPIO_InitStructure);
    GPIO_ConfigFilter(CW_GPIOA, GPIO_PIN_5, GPIO_FLTCLK_RC10K);
    //设置GPIOA的中断等级为3
    NVIC_SetPriority(GPIOA_IRQn, 0x03);
    GPIOA_INTFLAG_CLR(GPIOx_ICR_PIN5_Msk );
    NVIC_EnableIRQ(GPIOA_IRQn);
}

//LED I/O初始化
void LED_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure = {0};
    //打开GPIO时钟
    REGBITS_SET(CW_SYSCTRL->AHBEN, SYSCTRL_AHBEN_GPIOC_Msk);
    GPIO_InitStructure.Pins = GPIO_PIN_2 | GPIO_PIN_3;
    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_Init(CW_GPIOC, &GPIO_InitStructure);
    PC03_SETLOW();
}

void DeepSleepModeTest(void)
{
  GPIO_InitTypeDef GPIO_InitStructure = { 0 };
  PWR_InitTypeDef PWR_InitStructure = { 0 };
  
  //打开GPIO时钟
  REGBITS_SET(CW_SYSCTRL->AHBEN,SYSCTRL_AHBEN_GPIOA_Msk|\
  SYSCTRL_AHBEN_GPIOB_Msk | \
  SYSCTRL_AHBEN_GPIOC_Msk | SYSCTRL_AHBEN_GPIOF_Msk);
  
  GPIO_InitStructure.Mode = GPIO_MODE_ANALOG;
  GPIO_InitStructure.IT = GPIO_IT_NONE;
  GPIO_InitStructure.Pins = GPIO_PIN_All;
  GPIO_Init(CW_GPIOA, &GPIO_InitStructure);
  GPIO_Init(CW_GPIOB, &GPIO_InitStructure);
  GPIO_Init(CW_GPIOC, &GPIO_InitStructure);
  GPIO_Init(CW_GPIOF, &GPIO_InitStructure);  
  //关闭GPIO时钟
  REGBITS_CLR(CW_SYSCTRL->AHBEN,SYSCTRL_AHBEN_GPIOA_Msk| \
  SYSCTRL_AHBEN_GPIOB_Msk | \
  SYSCTRL_AHBEN_GPIOC_Msk | SYSCTRL_AHBEN_GPIOF_Msk);
  BSP_PB_Init();             //按键初始化
  // 唤醒后自动使用内部高速时钟(HSI)
  RCC_WAKEUPCLK_Config(RCC_SYSCTRL_WAKEUPCLKEN);
  PWR_InitStructure.PWR_Sevonpend = PWR_Sevonpend_Disable;
  PWR_InitStructure.PWR_SleepDeep = PWR_SleepDeep_Enable;
  PWR_InitStructure.PWR_SleepOnExit = PWR_SleepOnExit_Disable;
  PWR_Config(&PWR_InitStructure);
  PWR_GotoLpmMode();
  SYSCLKConfig_DeepSleep();
  LED_Init();
  SysTickDelay(200);
  PC02_SETHIGH();
  }
}
//GPIOA中断服务函数
void GPIOA_IRQHandler(void)
{
 if(REGBITS_GET(CW_GPIOA->ISR, GPIOx_ISR_PIN5_Msk) > 0)
    {
        gKeyStatus = 1;
        GPIOA_INTFLAG_CLR(GPIOx_ICR_PIN5_Msk);//清除CW_GPIO中断标志
    }
}

上述代码可以看到在未进入低功耗模式之前,LED1每1s翻转一次,通过按键KEY2进入低功耗模式后,LED1灯灭,当再次按下KEY2后,重新回到正常的运行模式,LED1每一秒翻转一次。通过测量可以得到,进入到低功耗模式之后,功耗显著降低。

来源:武汉芯源半导体

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 15

GPIO的意思是通用输入输出端口,可通过软件配置成输入或者输出端。即可以通过GPIO口输出高低电平或者通过GPIO口读入引脚状态是高电平还是低电平。大部分引脚除了当GPIO使用外,还可以复用为外设功能引脚。如UART外设与其他硬件进行数据交互。

CW32L083的每组GPIO都有20个寄存器,除了基础的配置寄存器、数据寄存器、位置位/清零寄存器、位清零寄存器、锁定寄存器、复用功能寄存器外,还配置了位翻转寄存器,中断数字滤波寄存器等,让操作更加快捷。同时提供完善的固件库函数,方便工程师快速开发测试,增强可移植性。

GPIO端口可配置成8种工作模式

4种输入模式

1.浮空输入

2.输入上拉

3.输入下拉

4.模拟输入

4种输出模式

1.开漏输出

2.推挽式输出

3.开漏复用功能

4.推挽式复用功能

GPIO功能框图

1.png

初始化GPIO可使用void GPIO_Init(GPIO_TypeDef  *GPIOx, GPIO_InitTypeDef *GPIO_Init)函数,此函数在cw32l083_gpio.c中有详细的配置,其中的第一个参数GPIO_TypeDef是一个结构体类型,在cw32l083.h中有详细定义,包含着每组GPIO的20个寄存器。

typedef struct {写法DIR_f;              //输入输出方向寄存器OPENDRAIN_f;       //输出模式寄存器PDR_f;              //下拉电阻寄存器PUR_f;              //上拉电阻寄存器AFRH_f;             //复用功能寄存器高段AFRL_f;              //复用功能寄存器低段ANALOG_f;           //模拟数字配置寄存器RISEIE_f;             //上升沿中断使能寄存器FALLIE_f;             //下降沿中断使能寄存器HIGHIE_f;             //高电平中断使能寄存器LOWIE_f;             //低电平中断使能寄存器ISR_f;                //中断标志寄存器ICR_f;                //中断标志清除寄存器LCKR_f;               //配置锁定寄存器FILTER_f;              //中断数字滤波器配置寄存器IDR_f;                //输入数据寄存器ODR_f;               //输出数据寄存器BRR_f;               //位清零寄存器BSRR_f;              //位置位清零寄存器TOG_f;               //位翻转寄存器} GPIO_TypeDef;

第二个参数GPIO_InitTypeDef也是一个结构体,在cw32l083_gpio.h中可查看其定义。

typedef struct{       uint32_t Pins;       uint32_t Mode;       uint32_t IT;} GPIO_InitTypeDef;

下面举一个简单的例子配置一个IO口翻转,可实现闪灯实验。

int32_t main(void)

{

    GPIO_InitTypeDef GPIO_InitStruct = {0};


    RCC_HSI_Enable(RCC_HSIOSC_DIV6);  

    __RCC_GPIOC_CLK_ENABLE();        //打开GPIOC的时钟


    GPIO_InitStruct.IT = GPIO_IT_NONE;

    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;

    GPIO_InitStruct.Pins = GPIO_PIN_2|GPIO_PIN_3;


    GPIO_Init(CW_GPIOC, &GPIO_InitStruct);


    while (1)

    {

        GPIO_TogglePin(CW_GPIOC,GPIO_PIN_2|GPIO_PIN_3);

        Delay(0xFFFF);  //延迟函数

    }

}

void Delay(__IO uint16_t nCount)

{

    /* Decrement nCount value */

    while (nCount != 0)

    {

        nCount--;

    }

}

例程中使用PC02和PC03引脚进行IO口翻转实验,官方CW32L083VCT6的评估板上这两个引脚外接LED灯,可以看到LED周期闪烁的效果。

其中CW_GPIOC,GPIO_PIN_0以及GPIO_MODE_OUTPUT_PP都是定义在头文件中的宏定义,如下面第一行为CW_GPIOC端口地址映射。

#define CW_GPIOC        ((GPIO_TypeDef*)       GPIOC_BASE)

#define GPIOC_BASE       0x48000800UL


#define GPIO_PIN_0       ((uint16_t)0x0001)   /* Pin 0    selected    */

#define GPIO_PIN_1       ((uint16_t)0x0002)   /* Pin 1    selected    */

#define GPIO_PIN_2       ((uint16_t)0x0004)   /* Pin 2    selected    */

#define GPIO_PIN_3       ((uint16_t)0x0008)   /* Pin 3    selected    */

#define GPIO_PIN_4       ((uint16_t)0x0010)   /* Pin 4    selected    */

#define GPIO_PIN_5       ((uint16_t)0x0020)   /* Pin 5    selected    */

#define GPIO_PIN_6       ((uint16_t)0x0040)   /* Pin 6    selected    */

#define GPIO_PIN_7       ((uint16_t)0x0080)   /* Pin 7    selected    */

#define GPIO_PIN_8       ((uint16_t)0x0100)   /* Pin 8    selected    */

#define GPIO_PIN_9       ((uint16_t)0x0200)   /* Pin 9    selected    */

#define GPIO_PIN_10      ((uint16_t)0x0400)   /* Pin 10   selected    */

#define GPIO_PIN_11      ((uint16_t)0x0800)   /* Pin 11   selected    */

#define GPIO_PIN_12      ((uint16_t)0x1000)   /* Pin 12   selected    */

#define GPIO_PIN_13      ((uint16_t)0x2000)   /* Pin 13   selected    */

#define GPIO_PIN_14      ((uint16_t)0x4000)   /* Pin 14   selected    */

#define GPIO_PIN_15      ((uint16_t)0x8000)   /* Pin 15   selected    */

#define GPIO_PIN_All      ((uint16_t)0xFFFF)   /* All pins selected    */


#define GPIO_MODE_ANALOG                    (0X00)

#define GPIO_MODE_INPUT                      (0x10)

#define GPIO_MODE_INPUT_PULLUP               (0x11)

#define GPIO_MODE_INPUT_PULLDOWN            (0x12)

#define GPIO_MODE_OUTPUT_PP                  (0x20)

#define GPIO_MODE_OUTPUT_OD                 (0x21)

上面的例子是使用库函数配置的,也可以用寄存器操作,具体步骤如下。

//开启HSI并6分频,Sysclk=HCLK=PCLK=8MHz

CW_SYSCTRL->HSI=(5<<SYSCTRL_HSI_DIV_Pos)|(*((volatile uint16_t*) RCC_HSI_TRIMCODEADDR));        

CW_SYSCTRL->CR1|=SYSCTRL_BYPASS|SYSCTRL_CR1_HSIEN_Msk; //Enable HSI             

while ((CW_SYSCTRL->HSI & SYSCTRL_HSI_STABLE_Msk) != SYSCTRL_HSI_STABLE_Msk) //Wait stable

    {

        ;

    }

//开启GPIOC的时钟

REGBITS_SET(CW_SYSCTRL->AHBEN, RCC_AHB_PERIPH_GPIOC);


//配置PC02和PC03为数字功能推挽输出

CW_GPIOC->ANALOG_f.PIN2  = 0;           //将PC02设置为数字功能

CW_GPIOC->DIR_f.PIN2  = 0;               //将PC02设置成输出模式

CW_GPIOC->OPENDRAIN_f.PIN2  = 0;        //将PC02设置成推挽模式

CW_GPIOC->ANALOG_f.PIN3  = 0;           //将PC03设置为数字功能

CW_GPIOC->DIR_f.PIN3  = 0;               //将PC03设置为输出模式

CW_GPIOC->OPENDRAIN_f.PIN3  = 0;        //将PC03设置为推挽模式

while (1)

   {

       CW_GPIOC->TOG=bv2;              //PC02翻转

       Delay(0xFFFF);

    CW_GPIOC->TOG=bv3;              //PC03翻转

       Delay(0xFFFF);

   }

下面以CW32L083为例通过配置GPIO引脚来驱动SPI2外设,查阅CW32L083的用户手册可以得知, SPI2可以通过PA00,PA01,PA02,PA03复用实现,这个就是上文提到的GPIO口的复用功能。

PA00_AFx_SPI2MISO()  //将PA00复用成SPI2的MISO线

PA01_AFx_SPI2MOSI()  //将PA01复用成SPI2的MOSI线

PA02_AFx_SPI2SCK()   //将PA02复用成SPI2的时钟线

PA03_AFx_SPI2CS()    //将PA03复用成SPI2的片选线,如需操作片选线,有特定函数


GPIO_InitTypeDef GPIO_InitStructure = {0};

GPIO_InitStructure.Pins =GPIO_PIN_1|GPIO_PIN_2 |GPIO_PIN_3;

GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;

GPIO_InitStructure.IT= GPIO_IT_NONE;

GPIO_Init(CW_GPIOA, &GPIO_InitStructure);

GPIO_InitStructure.Pins =GPIO_PIN_0;

GPIO_InitStructure.Mode = GPIO_MODE_INPUT;

GPIO_Init(CW_GPIOA, &GPIO_InitStructure);

另外我们的GPIO支持外部中断,具体的配置如下,如果有不清楚的小伙伴可以查看下节CW32中断控制器,会有详细讲解。

GPIO_InitTypeDef GPIO_InitStruct = {0};


__RCC_GPIOA_CLK_ENABLE();//开启GPIOA的时钟


GPIO_InitStruct.IT = GPIO_IT_RISING | GPIO_IT_FALLING;  //上升沿或者下降沿触发

GPIO_InitStruct.Mode = GPIO_MODE_INPUT;

GPIO_InitStruct.Pins =GPIO_PIN_1;


GPIO_Init(CW_GPIOA, &GPIO_InitStruct);


//清除PA01中断标志并使能NVIC

GPIOA_INTFLAG_CLR(bv1);

NVIC_EnableIRQ(GPIOA_IRQn);

上面的例程即可实现外部输入中断,PA01在接收到上升沿或者下降沿的信号的时候会进入到中断函数中去执行相关的程序,具体的例程可以参考官方CW32L083的gpio_int_filter例子。

下面介绍的是CW32L083的GPIO所相关的函数及其功能。

1.void GPIO_SWD2GPIO(void);

将PA13/PA14 SWD设置成GPIO


2.void GPIO_GPIO2SWD(void);

将PA13/PA14设置成SWD


3.void GPIO_LockPin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pins);

锁定指定引脚配置项,可同时设置多个引脚


4.void GPIO_DeInit(GPIO_TypeDef *GPIOx, uint32_t GPIO_Pins);

GPIO去初始化,可同时设置多个引脚或单个引脚


5.void GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init);

GPIO 初始化,可同时设置多个引脚或单个引脚


6.void GPIO_ConfigFilter(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pins, uint32_t FltClk);

配置端口滤波


7.void GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pins, GPIO_PinState PinState);

设置指定引脚电平,可同时设置多个引脚


8.void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t Value);

设置指定引脚端口的输出值


9.void GPIO_TogglePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pins);

翻转指定引脚电平,可同时设置多个引脚


10.GPIO_PinState GPIO_ReadPin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin);

获取一个引脚的电平

以上是CW32通用输入输出端口的介绍。

来源:武汉芯源半导体

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 9
订阅 RSS - CW32