O código deste artigo refere-se à rotina atômica pontual
Diretório de artigos
Recursos experimentais
Código fonte de rotina: (main.c)
Este experimento alimenta o cão pressionando o botão WK_UP. Se o cão não for alimentado dentro do período de tempo especificado, o microcontrolador será reiniciado.
#include "sys.h"
#include "usart.h"
#include "delay.h"
#include "led.h"
#include "key.h"
#include "iwdg.h"
/*********************************************************************************
___ _ _____ _____ _ _ _____ _____ _ __
/ _ \ | | |_ _|| ___|| \ | ||_ _|| ___|| | / /
/ /_\ \| | | | | |__ | \| | | | | |__ | |/ /
| _ || | | | | __| | . ` | | | | __| | \
| | | || |_____| |_ | |___ | |\ | | | | |___ | |\ \
\_| |_/\_____/\___/ \____/ \_| \_/ \_/ \____/ \_| \_/
* ******************************************************************************
* 正点原子 Pandora STM32L475 IoT开发板 实验6
* 独立看门狗实验 HAL库版本
* 技术支持:www.openedv.com
* 淘宝店铺:http://openedv.taobao.com
* 关注微信公众平台微信号:"正点原子",免费获取STM32资料。
* 广州市星翼电子科技有限公司
* 作者:正点原子 @ALIENTEK
* ******************************************************************************/
int main(void)
{
HAL_Init();
SystemClock_Config(); //初始化系统时钟为80M
delay_init(80); //初始化延时函数80M系统时钟
uart_init(115200); //初始化串口,波特率为115200
LED_Init(); //初始化LED
KEY_Init(); //初始化按键
delay_ms(100); //延时100ms再初始化看门狗,LED_B的变化"可见"
IWDG_Init(IWDG_PRESCALER_64, 500); //分频数为64,重载值为500,溢出时间为1s(4*2^4*500/32=1000ms)
LED_B(0);
while(1)
{
if(KEY_Scan(0) == WKUP_PRES) //如果WK_UP按下,喂狗
{
IWDG_Feed(); //喂狗
}
delay_ms(10);
}
}
Análise de código
HAL_Init()
HAL_Init()
A definição é a seguinte: (veja as notas para as funções específicas implementadas)
HAL_StatusTypeDef HAL_Init(void)
{
HAL_StatusTypeDef status = HAL_OK;
/* 配置 Flash 预取,指令缓存,数据缓存 */
/* 默认配置为:预存取关闭 指令缓存和数据缓存开启 */
#if (INSTRUCTION_CACHE_ENABLE == 0) // Flash开启预存取配置,能加速CPU代码的执行
__HAL_FLASH_INSTRUCTION_CACHE_DISABLE();
#endif /* INSTRUCTION_CACHE_ENABLE */
#if (DATA_CACHE_ENABLE == 0)
__HAL_FLASH_DATA_CACHE_DISABLE();
#endif /* DATA_CACHE_ENABLE */
#if (PREFETCH_ENABLE != 0)
__HAL_FLASH_PREFETCH_BUFFER_ENABLE();
#endif /* PREFETCH_ENABLE */
/* Set Interrupt Group Priority */
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_2); // 配置 NVIC 优先级分组
/* Use SysTick as time base source and configure 1ms tick (default clock after Reset is MSI) */
if (HAL_InitTick(TICK_INT_PRIORITY) != HAL_OK) //初始化滴答定时器,时钟节拍设置为 1ms
{
status = HAL_ERROR;
}
else
{
/* Init the low level hardware */
HAL_MspInit(); // 低速的外设初始化,比如 GPIO、中断等的设置(使用 STM32CubeMx 生成代码时会将低速外设初始
// 代码当这类函数里,其他情况下可以忽略这个函数
}
/* Return function status */
return status;
}
HAL_InitTick()
função de inicialização do tique do relógio do temporizador de tiques
__weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
HAL_StatusTypeDef status = HAL_OK;
/*Configure the SysTick to have interrupt in 1ms time basis*/
if (HAL_SYSTICK_Config(SystemCoreClock/1000UL) != 0U) // 系统时钟/1000,中断周期为 1ms
{
status = HAL_ERROR;
}
else
{
/*Configure the SysTick IRQ priority */
HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority, 0); // 将滴答定时器的中断优先级设置为最高
}
/* Return function status */
return status;
}
SystemClock_Config()
SystemClock_Config()
A definição da função é a seguinte: (Para a implementação específica da função, veja os comentários, apenas para referência)
void SystemClock_Config(void)
{
HAL_StatusTypeDef ret = HAL_OK;
RCC_OscInitTypeDef RCC_OscInitStruct; // 定义振荡器初始化结构体变量
RCC_ClkInitTypeDef RCC_ClkInitStruct; // 定义时钟初始化结构体变量
__HAL_RCC_PWR_CLK_ENABLE(); // 使能电源控制时钟
/*Initializes the CPU, AHB and APB busses clocks*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; // 将 HSE(外部高速时钟)作为时钟源
RCC_OscInitStruct.HSEState = RCC_HSE_ON; // 开启 HSE
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; // 开启 PLL(锁相环)
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; // 将 HSE 作为 PLL 的时钟源
RCC_OscInitStruct.PLL.PLLM = 1; // PLL-VCO 输入时钟分频系数,1 表示 2 分频(8 / 2 = 4M,本开发板外部晶振频率为 8MHz)
RCC_OscInitStruct.PLL.PLLN = 20; // PLL-VCO 输出时钟倍频系数,4 * 20 = 80M,即输出时钟频率为 80MHz
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7; // SAI 时钟的分频系数
RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2; // SDMMC1, RNG 和 USB 的时钟分频系数
RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2; // 主系统时钟的分频系数
ret = HAL_RCC_OscConfig(&RCC_OscInitStruct); //初始化时钟配置
if(ret != HAL_OK) while(1);
/*Initializes the CPU, AHB and APB busses clocks*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
| RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; // 将所有时钟同时进行配置
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; // 将 PLL 作为系统时钟源
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; // AHB 不分频
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; // APB1 不分频
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; // APB2 不分频
ret = HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4); // 配置时钟初始结构体变量,
//使用 Flash 延迟4,等待状态(延迟)的数量需要根据CPU时钟(HCLK)的频率和内部电压范围来选择,具体怎么
//选需要参考芯片手册
if(ret != HAL_OK) while(1);
/*Configure the main internal regulator output voltage*/
ret = HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1); //内部寄存器输出电压配置
// 下面是 HAL_PWREx_ControlVoltageScaling() 函数说明的部分内容:
//PWR_REGULATOR_VOLTAGE_SCALE1 Regulator voltage output range 1 mode, typical output voltage
// at 1.2 V, system frequency up to 80 MHz.
if(ret != HAL_OK) while(1);
}
delay_init()
O temporizador de tick HAL_Init()
foi inicializado em , e a função a seguir realmente fac_us
atribui um valor (atualmente não envolvendo o sistema operacional e outros códigos não serão estudados por enquanto).
static u32 fac_us = 0; //us延时倍乘数
/**
* @brief 初始化延迟函数,SYSTICK的时钟固定为AHB时钟
*
* @param SYSCLK 系统时钟频率
*
* @return void
*/
void delay_init(u8 SYSCLK)
{
#if SYSTEM_SUPPORT_OS //如果需要支持OS.
u32 reload;
#endif
HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);//SysTick频率为HCLK
fac_us = SYSCLK; //不论是否使用OS,fac_us都需要使用
#if SYSTEM_SUPPORT_OS //如果需要支持OS.
reload = SYSCLK; //每秒钟的计数次数 单位为K
reload *= 1000000 / delay_ostickspersec; //根据delay_ostickspersec设定溢出时间
//reload为24位寄存器,最大值:16777216,在80M下,约209.7ms左右
fac_ms = 1000 / delay_ostickspersec; //代表OS可以延时的最少单位
SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk; //开启SYSTICK中断
SysTick->LOAD = reload; //每1/OS_TICKS_PER_SEC秒中断一次
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; //开启SYSTICK
#else
#endif
}
LED_Init()
/**
* @brief LED IO初始化函数
*
* @param void
*
* @return void
*/
void LED_Init(void)
{
/*
LED-B PE9
LED-G PE8
LED-R PE7
*/
GPIO_InitTypeDef GPIO_InitStruct; // 定义一个GPIO初始化结构体变量
__HAL_RCC_GPIOE_CLK_ENABLE(); // 使能GPIOE的时钟
GPIO_InitStruct.Pin = GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9; // 同时配置 3 个引脚
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出模式
GPIO_InitStruct.Pull = GPIO_PULLUP; // 默认上拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // 速度设为高速(25 MHz to 50 MHz)
HAL_GPIO_Init(GPIOE, &GPIO_InitStruct); // 初始化结构体变量
// 将 3 个引脚同时置高
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9, GPIO_PIN_SET);
}
KEY_Init()
/**
* @brief 按键初始化函数
*
* @param void
*
* @return void
*/
void KEY_Init(void)
{
/*
KEY0 - PD10
KEY1 - PD9
KEY2 - PD8
WK_UP - PC13
*/
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_GPIOC_CLK_ENABLE(); //开启GPIOC时钟
__HAL_RCC_GPIOD_CLK_ENABLE(); //开启GPIOD时钟
GPIO_Initure.Pin = GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 ; //PD8.9.10
GPIO_Initure.Mode = GPIO_MODE_INPUT; //输入
GPIO_Initure.Pull = GPIO_PULLDOWN; //下拉
GPIO_Initure.Speed = GPIO_SPEED_HIGH; //高速
HAL_GPIO_Init(GPIOD, &GPIO_Initure);
GPIO_Initure.Pin = GPIO_PIN_13; //PC13
GPIO_Initure.Mode = GPIO_MODE_INPUT; //输入
GPIO_Initure.Pull = GPIO_PULLUP; //上拉
GPIO_Initure.Speed = GPIO_SPEED_HIGH; //高速
HAL_GPIO_Init(GPIOC, &GPIO_Initure);
}
delay_ms()
delay_ms()
O que está acontecendo aqui é delay_us()
que o atraso é delay_us()
realizado através de um temporizador de tick. O delay_init()
acima definiu fac_us para 80, e leva de 10 a 6 segundos para o cronômetro contar 80 vezes (o clock do sistema é 80MHz), que é 1us.
/**
* @brief 延时毫秒(ms)函数
*
* @param nms 需要延时多少毫秒
*
* @return void
*/
void delay_ms(u16 nms)
{
u32 i;
for(i = 0; i < nms; i++) delay_us(1000);
}
/**
* @brief 延时微秒(us)函数
*
* @remark nus:0~190887435(最大值即2^32/fac_us@fac_us=22.5)
*
* @param nus 需要延时多少微秒
*
* @return void
*/
void delay_us(u32 nus)
{
u32 ticks;
u32 told, tnow, tcnt = 0;
u32 reload = SysTick->LOAD; //LOAD的值
ticks = nus * fac_us; //需要的节拍数
told = SysTick->VAL; //刚进入时的计数器值
while(1)
{
tnow = SysTick->VAL;
if(tnow != told)
{
if(tnow < told)tcnt += told - tnow; //这里注意一下SYSTICK是一个递减的计数器就可以了.
else tcnt += reload - tnow + told;
told = tnow;
if(tcnt >= ticks)break; //时间超过/等于要延迟的时间,则退出.
}
}
}
IWDG_Init()
A inicialização do watchdog independente é muito simples, afinal, é o código da função de biblioteca (biblioteca HAL), e não precisamos nos preocupar com o código de operação do registrador subjacente.
Há uma opção de configuração abaixo que IWDG_WINDOW_DISABLE
eu não vi antes, mas não parece ser muito importante.
// 全局变量
IWDG_HandleTypeDef IWDG_Handler; //独立看门狗句柄
/**
* @brief 初始化独立看门狗函数
* 时间计算(大概):Tout=((4*2^prer)*rlr)/32 ms
*
* @param prer 分频数:IWDG_PRESCALER_4~IWDG_PRESCALER_256
* @param rlr 自动重装载值,0~0XFFF
*
* @return void
*/
void IWDG_Init(u8 prer, u16 rlr)
{
IWDG_Handler.Instance = IWDG;
IWDG_Handler.Init.Prescaler = prer; //设置IWDG分频系数
IWDG_Handler.Init.Reload = rlr; //重装载值
IWDG_Handler.Init.Window = IWDG_WINDOW_DISABLE;
HAL_IWDG_Init(&IWDG_Handler); //初始化IWDG并开启独立看门狗
}
O fator de divisão de frequência do watchdog só pode ser o seguinte:
/** @defgroup IWDG_Prescaler IWDG Prescaler
* @{
*/
#define IWDG_PRESCALER_4 0x00000000u /*!< IWDG prescaler set to 4 */
#define IWDG_PRESCALER_8 IWDG_PR_PR_0 /*!< IWDG prescaler set to 8 */
#define IWDG_PRESCALER_16 IWDG_PR_PR_1 /*!< IWDG prescaler set to 16 */
#define IWDG_PRESCALER_32 (IWDG_PR_PR_1 | IWDG_PR_PR_0) /*!< IWDG prescaler set to 32 */
#define IWDG_PRESCALER_64 IWDG_PR_PR_2 /*!< IWDG prescaler set to 64 */
#define IWDG_PRESCALER_128 (IWDG_PR_PR_2 | IWDG_PR_PR_0) /*!< IWDG prescaler set to 128 */
#define IWDG_PRESCALER_256 (IWDG_PR_PR_2 | IWDG_PR_PR_1) /*!< IWDG prescaler set to 256 */
/**
* @}
*/
A fórmula de cálculo do tempo de overflow do watchdog independente é Tout=((4*2^prer)*rlr)/32(ms)
, IWDG_PRESCALER_4~IWDG_PRESCALER_256
o é na verdade 0~6, então quando o parâmetro de divisão de frequência é selecionado IWDG_PRESCALER_64
e o valor de recarga é preenchido com 500, o tempo de overflow do watchdog independente é 1s ( 4 * 2 4 * 500 / 32 = 1000 ms).
LED_B()
A função de controle do LED é uma função macro, que usa HAL_GPIO_WritePin()
e HAL_GPIO_TogglePin()
duas funções de biblioteca respectivamente.
#define LED_R(n) (n?HAL_GPIO_WritePin(GPIOE,GPIO_PIN_7,GPIO_PIN_SET):HAL_GPIO_WritePin(GPIOE,GPIO_PIN_7,GPIO_PIN_RESET))
#define LED_R_TogglePin HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_7)
#define LED_G(n) (n?HAL_GPIO_WritePin(GPIOE,GPIO_PIN_8,GPIO_PIN_SET):HAL_GPIO_WritePin(GPIOE,GPIO_PIN_8,GPIO_PIN_RESET))
#define LED_G_TogglePin HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_8)
#define LED_B(n) (n?HAL_GPIO_WritePin(GPIOE,GPIO_PIN_9,GPIO_PIN_SET):HAL_GPIO_WritePin(GPIOE,GPIO_PIN_9,GPIO_PIN_RESET))
#define LED_B_TogglePin HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_9)
KEY_Scan()
As funções mais básicas da varredura de teclas são as quatro seguintes, que são ler o estado de nível do IO correspondente,
#define KEY0 HAL_GPIO_ReadPin(GPIOD,GPIO_PIN_10)
#define KEY1 HAL_GPIO_ReadPin(GPIOD,GPIO_PIN_9)
#define KEY2 HAL_GPIO_ReadPin(GPIOD,GPIO_PIN_8)
#define WK_UP HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_13)
O seguinte mecanismo de verificação de chave deve ser familiar a todos e é muito simples de entender:
/**
* @brief 按键处理函数
*
* @remark 注意此函数有响应优先级,KEY0>KEY1>KEY2>WK_UP!!
*
* @param mode 0:不支持连续按,1:支持连续按
*
* @return u8 返回按键值
* 0:没有任何按键按下,1:KEY0按下,2:KEY1按下,3:KEY2按下,4:WK_UP按下
*/
u8 KEY_Scan(u8 mode)
{
static u8 key_up = 1; //按键松开标志
if(mode == 1)key_up = 1; //支持连按
if(key_up && (KEY0 == 0 || KEY1 == 0 || KEY2 == 0 || WK_UP == 1))
{
delay_ms(10);
key_up = 0;
if(KEY0 == 0) return KEY0_PRES;
else if(KEY1 == 0) return KEY1_PRES;
else if(KEY2 == 0) return KEY2_PRES;
else if(WK_UP == 1) return WKUP_PRES;
}
else if(KEY0 == 1 && KEY1 == 1 && KEY2 == 1 && WK_UP == 0)key_up = 1;
return 0; //无按键按下
}
IWDG_Feed()
O watchdog independente que alimenta a função dog é, na verdade, um encapsulamento de três camadas da operação de registro de gravação. HAL_IWDG_Refresh()
--> __HAL_IWDG_RELOAD_COUNTER()
-->__HAL_IWDG_RELOAD_COUNTER()
/**
* @brief 喂独立看门狗
*
* @param void
*
* @return void
*/
void IWDG_Feed(void)
{
HAL_IWDG_Refresh(&IWDG_Handler); //喂狗
}
/**
* @brief Refresh the IWDG.
* @param hiwdg pointer to a IWDG_HandleTypeDef structure that contains
* the configuration information for the specified IWDG module.
* @retval HAL status
*/
HAL_StatusTypeDef HAL_IWDG_Refresh(IWDG_HandleTypeDef *hiwdg)
{
/* Reload IWDG counter with value defined in the reload register */
__HAL_IWDG_RELOAD_COUNTER(hiwdg);
/* Return function status */
return HAL_OK;
}
#define __HAL_IWDG_RELOAD_COUNTER(__HANDLE__) WRITE_REG((__HANDLE__)->Instance->KR, IWDG_KEY_RELOAD)
Aqui estão algumas bitmasks básicas para watchdogs independentes:
/**
* @brief IWDG Key Register BitMask
*/
#define IWDG_KEY_RELOAD 0x0000AAAAu /*!< 喂狗 IWDG Reload Counter Enable */
#define IWDG_KEY_ENABLE 0x0000CCCCu /*!< 外设使能 IWDG Peripheral Enable */
#define IWDG_KEY_WRITE_ACCESS_ENABLE 0x00005555u /*!< 写使能 IWDG KR Write Access Enable */
#define IWDG_KEY_WRITE_ACCESS_DISABLE 0x00000000u /*!< 写失能 IWDG KR Write Access Disable */