今天在RT-Thread完整版开发过程中引入watchdog,踩到一个坑,系统一直重启,喂狗一直失败,搞了一天才解决,总结一下。
我的RT-Thread完整版系统是最新版4.0.3(截止2020年12月30日),版本信息如下:
\ | /
- RT - Thread Operating System
/ | \ 4.0.3 build Dec 30 2020
2006 - 2020 Copyright by rt-thread team
我去官网参考例程,写了一个watchdog demo,源代码如下:
#define WDT_DEVICE_NAME "wdt" /* 看门狗设备名称 */
static rt_device_t wdg_dev; /* 看门狗设备句柄 */
static void idle_hook(void)
{
/* 在空闲线程的回调函数里喂狗 */
rt_device_control(wdg_dev, RT_DEVICE_CTRL_WDT_KEEPALIVE, NULL);
rt_kprintf("feed the dog!\n ");
}
int main(void)
{
int count = 1;
rt_uint32_t timeout = 1; /* 溢出时间,单位:秒 */
rt_err_t ret = RT_EOK;
char device_name[RT_NAME_MAX];
rt_strncpy(device_name, WDT_DEVICE_NAME, RT_NAME_MAX);
/* 根据设备名称查找看门狗设备,获取设备句柄 */
wdg_dev = rt_device_find(device_name);
if (wdg_dev==RT_NULL)
{
rt_kprintf("find %s failed!\n", device_name);
// return RT_ERROR;
}
/* 设置看门狗溢出时间 */
ret = rt_device_control(wdg_dev, RT_DEVICE_CTRL_WDT_SET_TIMEOUT, &timeout);
if (ret != RT_EOK)
{
rt_kprintf("set %s timeout failed!\n", device_name);
// return RT_ERROR;
}
/* 启动看门狗 */
ret = rt_device_control(wdg_dev, RT_DEVICE_CTRL_WDT_START, RT_NULL);
if (ret != RT_EOK)
{
rt_kprintf("start %s failed!\n", device_name);
// return -RT_ERROR;
}
rt_thread_mdelay(600);
/* 设置空闲线程回调函数 */
rt_thread_idle_sethook(idle_hook);
return RT_EOK;
}
然后下载代码运行,发现系统在反复重启。但是从main()函数以及idle_hook()函数打印看,看门狗的控制,启动都没有问题, rt_kprintf("feed the dog!\n ");也打印出来了,
因此深入系统内核源代码进行分析drv_wdt.c为watchdog的驱动源代码,我在里面加了log进行分析,如下:
static rt_err_t wdt_control(rt_watchdog_t *wdt, int cmd, void *arg)
{
switch (cmd)
{
/* feed the watchdog */
case RT_DEVICE_CTRL_WDT_KEEPALIVE:
rt_kprintf("wdt_control() RT_DEVICE_CTRL_WDT_KEEPALIVE 1\n");
if(HAL_IWDG_Refresh(&stm32_wdt.hiwdg) != HAL_OK)
{ rt_kprintf("wdt_control() RT_DEVICE_CTRL_WDT_KEEPALIVE 2\n");
LOG_E("watch dog keepalive fail.");
}
break;
/* set watchdog timeout */
case RT_DEVICE_CTRL_WDT_SET_TIMEOUT:
rt_kprintf("wdt_control() RT_DEVICE_CTRL_WDT_SET_TIMEOUT 1\n");
#if defined(LSI_VALUE)
rt_kprintf("wdt_control() RT_DEVICE_CTRL_WDT_SET_TIMEOUT 2\n");
if(LSI_VALUE)
{
//rt_kprintf("wdt_control() RT_DEVICE_CTRL_WDT_SET_TIMEOUT 3");
stm32_wdt.hiwdg.Init.Reload = (*((rt_uint32_t*)arg)) * LSI_VALUE / 256 ;
rt_kprintf("wdt_control() RT_DEVICE_CTRL_WDT_SET_TIMEOUT 3 stm32_wdt.hiwdg.Init.Reload=%d LSI_VALUE=%d\n",stm32_wdt.hiwdg.Init.Reload,LSI_VALUE);
}
else
{
rt_kprintf("Please define the value of LSI_VALUE!\n");
}
if(stm32_wdt.hiwdg.Init.Reload > 0xFFF)
{
rt_kprintf("wdg set timeout parameter too large, please less than %ds",0xFFF * 256 / LSI_VALUE);
return -RT_EINVAL;
}
#else
#error "Please define the value of LSI_VALUE!"
#endif
if(stm32_wdt.is_start)
{
rt_kprintf("wdt_control() RT_DEVICE_CTRL_WDT_SET_TIMEOUT 4\n");
if (HAL_IWDG_Init(&stm32_wdt.hiwdg) != HAL_OK)
{
rt_kprintf("wdg set timeout failed.");
return -RT_ERROR;
}
}
break;
case RT_DEVICE_CTRL_WDT_GET_TIMEOUT:
#if defined(LSI_VALUE)
if(LSI_VALUE)
{
(*((rt_uint32_t*)arg)) = stm32_wdt.hiwdg.Init.Reload * 256 / LSI_VALUE;
}
else
{
LOG_E("Please define the value of LSI_VALUE!");
}
#else
#error "Please define the value of LSI_VALUE!"
#endif
break;
case RT_DEVICE_CTRL_WDT_START:
rt_kprintf("wdt_control() RT_DEVICE_CTRL_WDT_START 1\n");
if (HAL_IWDG_Init(&stm32_wdt.hiwdg) != HAL_OK)
{
rt_kprintf("wdt start failed.");
return -RT_ERROR;
}
stm32_wdt.is_start = 1;
break;
default:
LOG_W("This command is not supported.");
return -RT_ERROR;
}
return RT_EOK;
}
从上面的打印看,启动,设置溢出时间,喂狗也没有走到报错流程。
后来和别人交流,发现是因为:
int rt_wdt_init(void)
{
#if defined(SOC_SERIES_STM32H7)
stm32_wdt.hiwdg.Instance = IWDG1;
#else
stm32_wdt.hiwdg.Instance = IWDG;
#endif
stm32_wdt.hiwdg.Init.Prescaler = IWDG_PRESCALER_256;
stm32_wdt.hiwdg.Init.Reload = 0x00000FFF;
#if defined(SOC_SERIES_STM32F0) || defined(SOC_SERIES_STM32L4) || defined(SOC_SERIES_STM32F7) \
|| defined(SOC_SERIES_STM32H7)
stm32_wdt.hiwdg.Init.Window = 0x00000FFF;
#endif
stm32_wdt.hiwdg.Init.Window = 0x00000FFF;//加这一句!!我是stm32L053芯片
stm32_wdt.is_start = 0;
发现缺少stm32_wdt.hiwdg.Init.Window = 0x00000FFF,因为STM32L0的芯片没有走上面的流程,因此这个算RT-Thread内核的一个bug,我猜可能是因为STM32L系列芯片用的人太少了导致的。
再总结一下stm32的独立看门狗的相关知识:
独立看门狗的时钟由独立的RC振荡器LSI提供,即使主时钟发生故障它仍然有效,非常独立。LSI的频率一般在30~60KHZ之间,根据温度和工作场合会有一定的漂移,我们一般取40KHZ,所以独立看门狗的定时时间并一定非常精确,只适用于对时间精度要求比较低的场合。
typedef struct
{
uint32_t Prescaler; /*!< Select the prescaler of the IWDG.
This parameter can be a value of @ref IWDG_Prescaler */
uint32_t Reload; /*!< Specifies the IWDG down-counter reload value.
This parameter must be a number between Min_Data = 0 and Max_Data = 0x0FFF */
uint32_t Window; /*!< Specifies the window value to be compared to the down-counter.
This parameter must be a number between Min_Data = 0 and Max_Data = 0x0FFF */
} IWDG_InitTypeDef;
uint32_t Reload; / *!<指定IWDG减计数器重载值。
此参数必须是介于Min_Data = 0和Max_Data = 0x0FFF之间的数字* /
独立看门狗的计数器是一个12位的递减计数器,最大值为0XFFF,当计数器减到0时,会产生一个复位信号:IWDG_RESET,让程序重新启动运行,如果在计数器减到0之前刷新了计数器的值的话,就不会产生复位信号,重新刷新计数器值的这个动作我们俗称喂狗。
stm32l0xx_hal_iwdg.c
HAL_StatusTypeDef HAL_IWDG_Init(IWDG_HandleTypeDef *hiwdg)
{
uint32_t tickstart;
/* Check the IWDG handle allocation */
if(hiwdg == NULL)
{
return HAL_ERROR;
}
/* Check the parameters */
assert_param(IS_IWDG_ALL_INSTANCE(hiwdg->Instance));
assert_param(IS_IWDG_PRESCALER(hiwdg->Init.Prescaler));
assert_param(IS_IWDG_RELOAD(hiwdg->Init.Reload));
assert_param(IS_IWDG_WINDOW(hiwdg->Init.Window));
/* Enable IWDG. LSI is turned on automaticaly */
__HAL_IWDG_START(hiwdg);
/* Enable write access to IWDG_PR, IWDG_RLR and IWDG_WINR registers by writing
0x5555 in KR */
IWDG_ENABLE_WRITE_ACCESS(hiwdg);
/* Write to IWDG registers the Prescaler & Reload values to work with */
hiwdg->Instance->PR = hiwdg->Init.Prescaler;
hiwdg->Instance->RLR = hiwdg->Init.Reload;
/* Check pending flag, if previous update not done, return timeout */
tickstart = HAL_GetTick();
/* Wait for register to be updated */
while(hiwdg->Instance->SR != RESET)
{
if((HAL_GetTick() - tickstart ) > HAL_IWDG_DEFAULT_TIMEOUT)
{
return HAL_TIMEOUT;
}
}
/* If window parameter is different than current value, modify window
register */
if(hiwdg->Instance->WINR != hiwdg->Init.Window)
{
/* Write to IWDG WINR the IWDG_Window value to compare with. In any case,
even if window feature is disabled, Watchdog will be reloaded by writing
windows register */
hiwdg->Instance->WINR = hiwdg->Init.Window;
}
else
{
/* Reload IWDG counter with value defined in the reload register */
__HAL_IWDG_RELOAD_COUNTER(hiwdg);
}
/* Return function status */
return HAL_OK;
}