目录

一、待机模式——标准库函数

1、注意事项

2、待机模式进入

3、唤醒复位原因

(1)、WKUP_PA0唤醒

(2)、复位判断

二、待机模式——HAL库

1、待机模式进入

2、唤醒复位原因

(1)、WKUP_PA0唤醒

(2)、复位判断

三、待机模式——用户侧

四、PVD管理——标准库函数

1、PVD设定

2、PVD中断配置

3、中断服务函数

五、PVD管理——HAL库

1、PVD设定

2、PVD中断配置

3、中断服务函数

六、PVD管理——用户侧

1、工作原理和硬件支持

2、响应速度和实时性

3、精度和应用场景

4、功耗和持续监测

5、应用举例

总结:


一、待机模式——标准库函数

1、注意事项

        待机模式,它除了关闭所有的时钟,还把 1.8V 区域的电源也完全关闭了,也就是说,从待机模式唤醒后,由于没有之前代码的运行记录,只能对芯片复位,重新检测 boot 条件,从头开始执行程序。它有四种唤醒方式,分别是 WKUP(PA0) 引脚的上升沿, RTC 闹钟事件, NRST 引脚的复位和 IWDG(独立看门狗) 复位。虽然默认还是WFI,但是不会回到前面待机的部分了,而是从代码的头部开始。

2、待机模式进入

void PWR_EnterSTANDBYMode(void)
{
  /* Clear Wake-up flag */
  PWR->CR |= PWR_CR_CWUF;
  /* Select STANDBY mode */
  PWR->CR |= PWR_CR_PDDS;
  /* Set SLEEPDEEP bit of Cortex System Control Register */
  SCB->SCR |= SCB_SCR_SLEEPDEEP;
/* This option is used to ensure that store operations are completed */
#if defined ( __CC_ARM   )
  __force_stores();
#endif
  /* Request Wait For Interrupt */
  __WFI();
}

3、唤醒复位原因

(1)、WKUP_PA0唤醒

        有人会问,PA0是否需要初始化配置等,最终设置为浮空或者下拉输入呢?不需要,这个是硬件层面的代码。

void PWR_WakeUpPinCmd(FunctionalState NewState)
{
  /* Check the parameters */
  assert_param(IS_FUNCTIONAL_STATE(NewState));
  *(__IO uint32_t *) CSR_EWUP_BB = (uint32_t)NewState;
}

(2)、复位判断

        如下函数放在main的开始,即可每次复位后判断当前复位状态,可以选择设置PWR_FLAG_WU、PWR_FLAG_SB、PWR_FLAG_PVDO三个状态。

FlagStatus PWR_GetFlagStatus(uint32_t PWR_FLAG)
{
  FlagStatus bitstatus = RESET;
  /* Check the parameters */
  assert_param(IS_PWR_GET_FLAG(PWR_FLAG));
  
  if ((PWR->CSR & PWR_FLAG) != (uint32_t)RESET)
  {
    bitstatus = SET;
  }
  else
  {
    bitstatus = RESET;
  }
  /* Return the flag status */
  return bitstatus;
}

二、待机模式——HAL库

1、待机模式进入

void HAL_PWR_EnterSTANDBYMode(void)
{
  /* Select Standby mode */
  SET_BIT(PWR->CR, PWR_CR_PDDS);

  /* Set SLEEPDEEP bit of Cortex System Control Register */
  SET_BIT(SCB->SCR, ((uint32_t)SCB_SCR_SLEEPDEEP_Msk));

  /* This option is used to ensure that store operations are completed */
#if defined ( __CC_ARM)
  __force_stores();
#endif
  /* Request Wait For Interrupt */
  __WFI();
}

2、唤醒复位原因

(1)、WKUP_PA0唤醒

void HAL_PWR_EnableWakeUpPin(uint32_t WakeUpPinx)
{
  /* Check the parameter */
  assert_param(IS_PWR_WAKEUP_PIN(WakeUpPinx));
  /* Enable the EWUPx pin */
  *(__IO uint32_t *) CSR_EWUP_BB(WakeUpPinx) = (uint32_t)ENABLE;
}

(2)、复位判断

/** @brief  Check PWR flag is set or not.
  * @param  __FLAG__: specifies the flag to check.
  *           This parameter can be one of the following values:
  *            @arg PWR_FLAG_WU: Wake Up flag. This flag indicates that a wakeup event
  *                  was received from the WKUP pin or from the RTC alarm
  *                  An additional wakeup event is detected if the WKUP pin is enabled
  *                  (by setting the EWUP bit) when the WKUP pin level is already high.
  *            @arg PWR_FLAG_SB: StandBy flag. This flag indicates that the system was
  *                  resumed from StandBy mode.
  *            @arg PWR_FLAG_PVDO: PVD Output. This flag is valid only if PVD is enabled
  *                  by the HAL_PWR_EnablePVD() function. The PVD is stopped by Standby mode
  *                  For this reason, this bit is equal to 0 after Standby or reset
  *                  until the PVDE bit is set.
  * @retval The new state of __FLAG__ (TRUE or FALSE).
  */
#define __HAL_PWR_GET_FLAG(__FLAG__) ((PWR->CSR & (__FLAG__)) == (__FLAG__))

三、待机模式——用户侧

int main(void)
{	
		/* 使能电源管理单元的时钟,必须要使能时钟才能进入待机模式 */
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR , ENABLE);

	LED_GPIO_Config();	
	
  /*初始化USART1*/
  USART_Config();			

	/*初始化按键,不需要中断,仅初始化KEY2即可,只用于唤醒的PA0引脚不需要这样初始化*/
	Key_GPIO_Config();   
	
	printf("\r\n 欢迎使用野火  STM32  开发板。\r\n");
  printf("\r\n 野火STM32 待机模式例程\r\n");
	
	printf("\r\n 实验说明:\r\n");

	printf("\r\n 1.本程序中,绿灯表示本次复位是上电或引脚复位,红灯表示即将进入待机状态,蓝灯表示本次是待机唤醒的复位\r\n");
	printf("\r\n 2.长按KEY2按键后,会进入待机模式\r\n");
	printf("\r\n 3.在待机模式下,按KEY1按键可唤醒,唤醒后系统会进行复位,程序从头开始执行\r\n");
	printf("\r\n 4.可通过检测WU标志位确定复位来源\r\n");
	
	printf("\r\n 5.在待机状态下,DAP下载器无法给STM32下载程序,需要唤醒后才能下载");

	
	//检测复位来源
	if(PWR_GetFlagStatus(PWR_FLAG_WU) == SET)
	{
		LED_BLUE;
		printf("\r\n 待机唤醒复位 \r\n");
	}
	else
	{
		LED_GREEN;
		printf("\r\n 非待机唤醒复位 \r\n");
	}
	
  while(1)
  {			
		// K2 按键长按进入待机模式
		if(KEY2_LongPress())
		{
			
			printf("\r\n 即将进入待机模式,进入待机模式后可按KEY1唤醒,唤醒后会进行复位,程序从头开始执行\r\n");
			LED_RED;	
			Delay(0xFFFFFF);
			
			/*清除WU状态位*/
			PWR_ClearFlag (PWR_FLAG_WU);
			
			/* 使能WKUP引脚的唤醒功能 ,使能PA0*/
			PWR_WakeUpPinCmd (ENABLE);
			
			/* 进入待机模式 */
			PWR_EnterSTANDBYMode();
		}

  }
}
int main(void) 
{
	/* 初始化系统时钟为72MHz */
	SystemClock_Config();
	/* 初始化LED */
	LED_GPIO_Config();	
	/* 初始化调试串口,一般为串口1 */
	DEBUG_USART_Config();	
	/*初始化按键,不需要中断,仅初始化KEY2即可,只用于唤醒的PA0引脚不需要这样初始化*/
	Key_GPIO_Config(); 
	
	printf("\r\n 欢迎使用野火  STM32 F103 开发板。\r\n");
	printf("\r\n 野火F103 待机模式例程\r\n");
	
	printf("\r\n 实验说明:\r\n");

	printf("\r\n 1.本程序中,绿灯表示本次复位是上电或引脚复位,红灯表示即将进入待机状态,蓝灯表示本次是待机唤醒的复位\r\n");
	printf("\r\n 2.长按KEY2按键后,会进入待机模式\r\n");
	printf("\r\n 3.在待机模式下,按KEY1按键可唤醒,唤醒后系统会进行复位,程序从头开始执行\r\n");
	printf("\r\n 4.可通过检测WU标志位确定复位来源\r\n");
	
	printf("\r\n 5.在待机状态下,DAP下载器无法给STM32下载程序,需要唤醒后才能下载");

	//检测复位来源
	if(__HAL_PWR_GET_FLAG(PWR_FLAG_SB) == SET)
	{
		__HAL_PWR_CLEAR_FLAG(PWR_FLAG_SB);
		LED_BLUE;
		printf("\r\n 待机唤醒复位 \r\n");
	}
	else
	{
		LED_GREEN;
		printf("\r\n 非待机唤醒复位 \r\n");
	}
	
  while(1)
  {			
		// K2 按键长按进入待机模式
		if(KEY2_LongPress())
		{
			
			printf("\r\n 即将进入待机模式,进入待机模式后可按KEY1唤醒,唤醒后会进行复位,程序从头开始执行\r\n");
			LED_RED;	
			HAL_Delay(1000);
			
			/*清除WU状态位*/
			__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
			
			/* 使能WKUP引脚的唤醒功能 ,使能PA0*/
			HAL_PWR_EnableWakeUpPin( 0x00000100U);
			
			//暂停滴答时钟,防止通过滴答时钟中断唤醒
			HAL_SuspendTick();			
			/* 进入待机模式 */
			HAL_PWR_EnterSTANDBYMode();
		}

  }

}

四、PVD管理——标准库函数

1、PVD设定

        由之前手册介绍和代码比对可以看到,PVD可以设定电压的范围。

/**
  * @brief  Configures the voltage threshold detected by the Power Voltage Detector(PVD).
  * @param  PWR_PVDLevel: specifies the PVD detection level
  *   This parameter can be one of the following values:
  *     @arg PWR_PVDLevel_2V2: PVD detection level set to 2.2V
  *     @arg PWR_PVDLevel_2V3: PVD detection level set to 2.3V
  *     @arg PWR_PVDLevel_2V4: PVD detection level set to 2.4V
  *     @arg PWR_PVDLevel_2V5: PVD detection level set to 2.5V
  *     @arg PWR_PVDLevel_2V6: PVD detection level set to 2.6V
  *     @arg PWR_PVDLevel_2V7: PVD detection level set to 2.7V
  *     @arg PWR_PVDLevel_2V8: PVD detection level set to 2.8V
  *     @arg PWR_PVDLevel_2V9: PVD detection level set to 2.9V
  * @retval None
  */
void PWR_PVDLevelConfig(uint32_t PWR_PVDLevel)
{
  uint32_t tmpreg = 0;
  /* Check the parameters */
  assert_param(IS_PWR_PVD_LEVEL(PWR_PVDLevel));
  tmpreg = PWR->CR;
  /* Clear PLS[7:5] bits */
  tmpreg &= CR_PLS_MASK;
  /* Set PLS[7:5] bits according to PWR_PVDLevel value */
  tmpreg |= PWR_PVDLevel;
  /* Store the new value */
  PWR->CR = tmpreg;
}

2、PVD中断配置

        占用EXTI16中断线。

  /*使能 PWR 时钟 */
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);

  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
  
  /* 使能 PVD 中断 */
  NVIC_InitStructure.NVIC_IRQChannel = PVD_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
      
  /* 配置 EXTI16线(PVD 输出) 来产生上升下降沿中断*/
  EXTI_ClearITPendingBit(EXTI_Line16);
  EXTI_InitStructure.EXTI_Line = EXTI_Line16;
  EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
  EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling;
  EXTI_InitStructure.EXTI_LineCmd = ENABLE;
  EXTI_Init(&EXTI_InitStructure);

3、中断服务函数

        可以提醒电量低等。

void PVD_IRQHandler(void)
{
		/*检测是否产生了PVD警告信号*/
		if(PWR_GetFlagStatus (PWR_FLAG_PVDO)==SET)			
		{
			/* 亮红灯,实际应用中应进入紧急状态处理 */
			LED_RED; 
			
		}
    /* 清除中断信号*/
    EXTI_ClearITPendingBit(EXTI_Line16);

}

五、PVD管理——HAL库

1、PVD设定

#define PWR_PVDLEVEL_0                  PWR_CR_PLS_2V2
#define PWR_PVDLEVEL_1                  PWR_CR_PLS_2V3
#define PWR_PVDLEVEL_2                  PWR_CR_PLS_2V4
#define PWR_PVDLEVEL_3                  PWR_CR_PLS_2V5
#define PWR_PVDLEVEL_4                  PWR_CR_PLS_2V6
#define PWR_PVDLEVEL_5                  PWR_CR_PLS_2V7
#define PWR_PVDLEVEL_6                  PWR_CR_PLS_2V8
#define PWR_PVDLEVEL_7                  PWR_CR_PLS_2V9 
#define PWR_PVD_MODE_NORMAL                 0x00000000U   /*!< basic mode is used */
#define PWR_PVD_MODE_IT_RISING              0x00010001U   /*!< External Interrupt Mode with Rising edge trigger detection */
#define PWR_PVD_MODE_IT_FALLING             0x00010002U   /*!< External Interrupt Mode with Falling edge trigger detection */
#define PWR_PVD_MODE_IT_RISING_FALLING      0x00010003U   /*!< External Interrupt Mode with Rising/Falling edge trigger detection */
#define PWR_PVD_MODE_EVENT_RISING           0x00020001U   /*!< Event Mode with Rising edge trigger detection */
#define PWR_PVD_MODE_EVENT_FALLING          0x00020002U   /*!< Event Mode with Falling edge trigger detection */
#define PWR_PVD_MODE_EVENT_RISING_FALLING   0x00020003U   /*!< Event Mode with Rising/Falling edge trigger detection */
typedef struct
{
  uint32_t PVDLevel;   /*!< PVDLevel: Specifies the PVD detection level.
                            This parameter can be a value of @ref PWR_PVD_detection_level */

  uint32_t Mode;      /*!< Mode: Specifies the operating mode for the selected pins.
                           This parameter can be a value of @ref PWR_PVD_Mode */
}PWR_PVDTypeDef;
void HAL_PWR_ConfigPVD(PWR_PVDTypeDef *sConfigPVD)
{
  /* Check the parameters */
  assert_param(IS_PWR_PVD_LEVEL(sConfigPVD->PVDLevel));
  assert_param(IS_PWR_PVD_MODE(sConfigPVD->Mode));

  /* Set PLS[7:5] bits according to PVDLevel value */
  MODIFY_REG(PWR->CR, PWR_CR_PLS, sConfigPVD->PVDLevel);
  
  /* Clear any previous config. Keep it clear if no event or IT mode is selected */
  __HAL_PWR_PVD_EXTI_DISABLE_EVENT();
  __HAL_PWR_PVD_EXTI_DISABLE_IT();
  __HAL_PWR_PVD_EXTI_DISABLE_FALLING_EDGE(); 
  __HAL_PWR_PVD_EXTI_DISABLE_RISING_EDGE();

  /* Configure interrupt mode */
  if((sConfigPVD->Mode & PVD_MODE_IT) == PVD_MODE_IT)
  {
    __HAL_PWR_PVD_EXTI_ENABLE_IT();
  }
  
  /* Configure event mode */
  if((sConfigPVD->Mode & PVD_MODE_EVT) == PVD_MODE_EVT)
  {
    __HAL_PWR_PVD_EXTI_ENABLE_EVENT();
  }
  
  /* Configure the edge */
  if((sConfigPVD->Mode & PVD_RISING_EDGE) == PVD_RISING_EDGE)
  {
    __HAL_PWR_PVD_EXTI_ENABLE_RISING_EDGE();
  }
  
  if((sConfigPVD->Mode & PVD_FALLING_EDGE) == PVD_FALLING_EDGE)
  {
    __HAL_PWR_PVD_EXTI_ENABLE_FALLING_EDGE();
  }
}

2、PVD中断配置

	/*使能 PWR 时钟 */
	__HAL_RCC_PWR_CLK_ENABLE();
	/* 配置 PVD 中断 */
	/*中断设置,抢占优先级0,子优先级为0*/
	HAL_NVIC_SetPriority(PVD_IRQn, 0 ,0);
	HAL_NVIC_EnableIRQ(PVD_IRQn);  

3、中断服务函数

void PVD_IRQHandler(void)
{
  HAL_PWR_PVD_IRQHandler();
}

void HAL_PWR_PVDCallback(void)
{
  LED_RED;;
}

六、PVD管理——用户侧

无需其他设置,通过PVD_Config即可。

void PVD_Config(void)
{
  NVIC_InitTypeDef NVIC_InitStructure;
  EXTI_InitTypeDef EXTI_InitStructure;

  /*使能 PWR 时钟 */
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);

  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
  
  /* 使能 PVD 中断 */
  NVIC_InitStructure.NVIC_IRQChannel = PVD_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
      
  /* 配置 EXTI16线(PVD 输出) 来产生上升下降沿中断*/
  EXTI_ClearITPendingBit(EXTI_Line16);
  EXTI_InitStructure.EXTI_Line = EXTI_Line16;
  EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
  EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling;
  EXTI_InitStructure.EXTI_LineCmd = ENABLE;
  EXTI_Init(&EXTI_InitStructure);

  /* 配置PVD级别PWR_PVDLevel_2V6 (PVD检测电压的阈值为2.6V,VDD电压低于2.6V时产生PVD中断) */
	/*具体级别根据自己的实际应用要求配置*/
  PWR_PVDLevelConfig(PWR_PVDLevel_2V6);

  /* 使能PVD输出 */
  PWR_PVDCmd(ENABLE);
}
void PVD_Config(void)
{
	PWR_PVDTypeDef sConfigPVD;
	
	/*使能 PWR 时钟 */
	__HAL_RCC_PWR_CLK_ENABLE();
	/* 配置 PVD 中断 */
	/*中断设置,抢占优先级0,子优先级为0*/
	HAL_NVIC_SetPriority(PVD_IRQn, 0 ,0);
	HAL_NVIC_EnableIRQ(PVD_IRQn);  

	/* 配置PVD级别5 (PVD检测电压的阈值为2.8V,
	   VDD电压低于2.8V时产生PVD中断,具体数据
	   可查询数据手册获知) 具体级别根据自己的
	   实际应用要求配置*/
	sConfigPVD.PVDLevel = PWR_PVDLEVEL_5;
	sConfigPVD.Mode = PWR_PVD_MODE_IT_RISING_FALLING;
	HAL_PWR_ConfigPVD(&sConfigPVD);
	/* 使能PVD输出 */
	HAL_PWR_EnablePVD();
}

        PVD检测电压的阈值根据不同级别可以设定,那么可以检测VDD电压从而产生PVD中断,例如可以提醒电量低,这个和用ADC采集,用户层面判断低电压有什么区别嘛。

        PVD (Power Voltage Detector) 和 ADC (模数转换器) 采集电压用于检测电压水平,它们的主要区别在于工作原理、响应速度、使用场景和应用场合。以下是二者的一些关键区别:

1、工作原理和硬件支持

  • PVD (Power Voltage Detector):

    • PVD 是一个硬件电压监测电路,可以监测电源电压(如 VDD)并比较它与预设的阈值。
    • 当 VDD 电压下降到低于设定阈值时,PVD 会触发中断或其他事件。这个过程是硬件级别的,即当电压低于阈值时,硬件自动处理,立即生成中断或报警。
    • PVD 可以设置不同的电压阈值,以便在电压达到危险水平时提醒系统,如低电量提醒。
    • 优点:响应速度快、可靠性高,因为是硬件级别的检测,且不需要软件参与。可以持续地在低功耗模式下进行电压监测,适用于实时电压监控。
  • ADC (Analog-to-Digital Converter):

    • ADC 是一个将模拟电压(如 VDD)转换为数字信号的模块,通常用于测量较为精确的电压值。
    • 用户可以通过 ADC 获取当前 VDD 电压值,然后在软件中判断是否低于设定的阈值,来决定是否执行低电量相关的操作。
    • 优点:ADC 可以提供更高的精度和可编程性,适合进行精确的电压测量。
    • 缺点:由于是软件驱动的,通常需要定期或在某些条件下手动触发 ADC 采样,可能会有较长的延迟。且 ADC 的采样速度不如 PVD 快,可能会错过一些快速变化的电压状态。

2、响应速度和实时性

  • PVD

    • PVD 是硬件级别的电压检测,它的响应速度非常快。当电压低于设定阈值时,PVD 立即产生中断,不需要软件的参与。
    • 由于是硬件中断,它的响应时间基本为中断处理时间,通常是微秒级别
  • ADC

    • ADC 需要采样电压并将模拟信号转换为数字信号,通常具有较长的采样周期(比如几个微秒到几十微秒),并且结果需要通过软件进行处理才能判断电压是否低于阈值。
    • 在一些低功耗应用中,ADC 可能不频繁触发,导致响应速度相对较慢

3、精度和应用场景

  • PVD

    • PVD 提供的是一个比较粗略的电压检测(通常是固定的几个电压阈值,如 2.4V、2.8V 等),它不能提供非常精确的电压值,仅适合判断电源是否低于某个安全阈值。
    • 通常用于判断电池电量是否足够或者是否需要进入低功耗模式。
  • ADC

    • ADC 提供的电压值更加精确,可以测量更细微的电压变化(比如毫伏级的变化),适合用于电池电量的精确测量和动态监测。
    • 适合用在对电压变化较为敏感的应用,如电池电量精确管理、动态电压调节等场合。

4、功耗和持续监测

  • PVD

    • PVD 是一个非常低功耗的硬件功能,通常不会占用太多资源。它可以在系统处于低功耗模式时运行,持续监控电压。
    • 适合需要长时间连续监测电源电压的场景,尤其是低功耗设备。
  • ADC

    • ADC 通常需要更高的功耗,尤其是高精度的 ADC。频繁采样时会导致较高的功耗,这对于低功耗应用不太适合。
    • 如果需要实时或定期监控电压,ADC 会相对消耗更多的能量,尤其是在低功耗模式下需要唤醒和采样。

5、应用举例

  • PVD 适用场景:

    • 电池电量监控:当电池电压下降到某个阈值时,触发中断,提醒系统电量低,或者触发设备进入省电模式。
    • 系统电源监控:电池或电源电压低于设定的安全阈值时,触发保护或关闭系统。
  • ADC 适用场景:

    • 精确电池电量测量通过 ADC 获取电池电压值,然后通过软件算法推算剩余电量,适用于需要精确电量估算的场景。
    • 电压调节:例如,动态电压调节(DVS),通过实时监测 VDD 电压来调整系统电压,以优化功耗或性能。

总结:

  • PVD 是硬件级别的电压检测器,快速且适合用于低功耗设备中的实时电压监测,特别是用于电池电量低的预警。
  • ADC 提供更精确的电压测量,但需要软件判断,响应速度较慢,适合用于需要精确电压监控的场合。

        如果需求只是检测电压是否低于某个阈值,使用 PVD 会更加简单高效。如果你需要精确的电池电量估算,或者更灵活的电压监控,ADC 会更加适用。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐