一、CUBEMX配置
1、选择芯片,当前示例选择stm32l151c8t6
2、配置RCC及SYS
只需配置高速时钟(HSE)和DEBUG
3、配置时钟树
时钟源采用外部8M晶振,单片机HCLK采用8M,过高运行功耗高,过低程序运行时精准延时(delay_us)误差大,具体视情况定。
4、配置串口,方便调试
当前波特率设为9600,主要考虑系统时钟最低可设为1M,此时功耗最低,不支持高波特率。且调试可不配置串口中断。
5、配置外部中断
4+4按键矩阵,PA0~PA3选择推挽输出,默认下拉,PA4~PA7选择外部中断模式,默认上拉;打开中断,设置中断优先级,尽量避免优先级为0,不然延时消抖时使用HAL_Delay会死循环,因为HAL库SYSTIC优先级为0,或者采用时钟摘取法自己实现ms、us延时,推荐原子哥的延时初始化函数。
6、生成代码
选择MDK5,.c/.h文件分开。
二、运行代码
1、使能printf
将以下代码添加至.c文件
int fputc(int ch,FILE *f)
{
HAL_UART_Transmit(&huart1,(uint8_t *)&ch,1,0xFFFF);
return 0;
}
2、初始化延时
delay_init(); 当前初始化延时存在问题,按键中断延时消抖会使中断外delay直接结束造成误差,有待改进。
void delay_init()
{
uint32_t CLKSource = SYSTICK_CLKSOURCE_HCLK; //选择时钟源
uint32_t DIV = 1; //分频系数
if(SystemCoreClock/1000000 > 16) //系统时钟超过16M则8分频,SysTick->LOAD最大值2^24=16,777,216,16M时钟源延时最大值为1048.576ms
{
CLKSource = SYSTICK_CLKSOURCE_HCLK_DIV8;
DIV = 8;
}
HAL_SYSTICK_CLKSourceConfig(CLKSource); //选择外部时钟
fac_us=SystemCoreClock/1000000/DIV; //为系统时钟的us延时tick数
fac_ms=(u16)fac_us*1000; //非OS下,代表每个ms需要的systick时钟数
}
//延时nus
void delay_us(u32 nus)
{
u32 temp;
SysTick->LOAD=nus*fac_us; //时间加载
SysTick->VAL=0x00; //清空计数器
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数
do
{
temp=SysTick->CTRL;
}while((temp&0x01)&&!(temp&(1<<16))); //等待时间到达
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
//延时nms
//注意nms的范围
//SysTick->LOAD为24位寄存器,所以,最大延时为:
//nms<=0xffffff*8*1000/SYSCLK
//SYSCLK单位为Hz,nms单位为ms
//对72M条件下,nms<=1864
void delay_ms(u16 nms)
{
u32 temp;
SysTick->LOAD=(u32)nms*fac_ms; //时间加载(SysTick->LOAD为24bit)
SysTick->VAL =0x00; //清空计数器
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数
do
{
temp=SysTick->CTRL;
}while((temp&0x01)&&!(temp&(1<<16))); //等待时间到达
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
3、按键扫描
行扫描,通过中断管脚判断,4条中断线(PA4~PA7)对应4行;列扫描,PA0~PA3逐个拉高电平,通过中断管脚电平被拉高判断。最后将PA0~PA3电平重新拉低,此时由于电平高低变化会再次产生中断,记得退出按键扫描后清中断。
/* 4*4按键矩阵,key0~15及对应value */
uint8_t KEY_NUM[4][4] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
uint8_t KEY_DATA[16] = {0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0FF,0xFF};
/* 按键矩阵行对应GPIO管脚 */
#define KEY_PIN_ROW0 GPIO_PIN_4
#define KEY_PIN_ROW1 GPIO_PIN_5
#define KEY_PIN_ROW2 GPIO_PIN_6
#define KEY_PIN_ROW3 GPIO_PIN_7
#define KEY_GPIO_ROW0 GPIOA
#define KEY_GPIO_ROW1 GPIOA
#define KEY_GPIO_ROW2 GPIOA
#define KEY_GPIO_ROW3 GPIOA
/* 按键矩阵列对应GPIO管脚 */
#define KEY_PIN_COLUMN0 GPIO_PIN_0
#define KEY_PIN_COLUMN1 GPIO_PIN_1
#define KEY_PIN_COLUMN2 GPIO_PIN_2
#define KEY_PIN_COLUMN3 GPIO_PIN_3
#define KEY_GPIO_COLUMN0 GPIOA
#define KEY_GPIO_COLUMN1 GPIOA
#define KEY_GPIO_COLUMN2 GPIOA
#define KEY_GPIO_COLUMN3 GPIOA
/* 扫描按键,中断模式下扫描列会反转电平,额外产生一次中断,需清中断 */
uint8_t keyScan(uint16_t GPIO_Pin)
{
GPIO_TypeDef* GPIOx;
uint8_t key_row,key_column;
/* 扫描行 */
switch(GPIO_Pin)
{
case KEY_PIN_ROW0:
key_row = 0;
GPIOx = KEY_GPIO_ROW0;
break;
case KEY_PIN_ROW1:
key_row = 1;
GPIOx = KEY_GPIO_ROW1;
break;
case KEY_PIN_ROW2:
key_row = 2;
GPIOx = KEY_GPIO_ROW2;
break;
case KEY_PIN_ROW3:
key_row = 3;
GPIOx = KEY_GPIO_ROW3;
break;
default:
key_row = 3;
break;
}
/* 扫描列-电平反转,会额外产生一次中断 */
HAL_GPIO_WritePin(KEY_GPIO_COLUMN0,KEY_PIN_COLUMN0,GPIO_PIN_SET);
if(HAL_GPIO_ReadPin(GPIOx,GPIO_Pin))
{
key_column = 0;
}
else
{
HAL_GPIO_WritePin(KEY_GPIO_COLUMN1,KEY_PIN_COLUMN1,GPIO_PIN_SET);
if(HAL_GPIO_ReadPin(GPIOx,GPIO_Pin))
{
key_column = 1;
}
else
{
HAL_GPIO_WritePin(KEY_GPIO_COLUMN2,KEY_PIN_COLUMN2,GPIO_PIN_SET);
if(HAL_GPIO_ReadPin(GPIOx,GPIO_Pin))
{
key_column = 2;
}
else
{
HAL_GPIO_WritePin(KEY_GPIO_COLUMN3,KEY_PIN_COLUMN3,GPIO_PIN_SET);
if(HAL_GPIO_ReadPin(GPIOx,GPIO_Pin))
{
key_column = 3;
}
}
}
}
HAL_GPIO_WritePin(KEY_GPIO_COLUMN0,GPIO_PIN_0,GPIO_PIN_RESET);
HAL_GPIO_WritePin(KEY_GPIO_COLUMN1,GPIO_PIN_1,GPIO_PIN_RESET);
HAL_GPIO_WritePin(KEY_GPIO_COLUMN2,GPIO_PIN_2,GPIO_PIN_RESET);
HAL_GPIO_WritePin(KEY_GPIO_COLUMN3,GPIO_PIN_3,GPIO_PIN_RESET);
printf("key%d\r\n",KEY_NUM[key_row][key_column]);
return KEY_NUM[key_row][key_column];
}
4、进入低功耗
进入低功耗之前关闭所有时钟并将IO口设为输入模式以降低功耗,若接入了外设,则该IO口模式不变以保持电平,使得外设正常工作。
void StopMode_Measure(void)
{
/* Enter Stop Mode */
HAL_UART_MspDeInit(&huart1);
GPIO_RCC_disable();
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
}
/**********关闭GPIO时钟,降低功耗**********/
void GPIO_RCC_disable(void)
{
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOH_CLK_ENABLE();
GPIOA->MODER = 0xFFFFFFFF;
GPIOB->MODER = 0xFFFFFFFF;
GPIOC->MODER = 0xFFFFFFFF;
GPIOH->MODER = 0xFFFFFFFF;
__HAL_RCC_GPIOA_CLK_DISABLE();
__HAL_RCC_GPIOB_CLK_DISABLE();
__HAL_RCC_GPIOC_CLK_DISABLE();
__HAL_RCC_GPIOH_CLK_DISABLE();
}
5、退出低功耗
进入按键中断后,系统采用低速时钟,需SystemClock_Config(),重新配置并打开系统时钟(最重要),打开其它被关闭的时钟等。最简单粗暴就把所有外设初始化。
/* 初始化,打开对应外设时钟 */
SystemClock_Config();
delay_init();
MX_GPIO_Init();
HAL_UART_MspInit(&huart1);
我自己测试时(无其它外设),32M时钟工作电流11mA左右,8M时钟工作电流3.5mA左右,进入低功耗电流2.8uA左右。有问题可讨论,大家一起进步。
来源:CSDN
作者:weixin_41928241
链接:https://blog.csdn.net/weixin_41928241/article/details/103911545