您现在的位置是:首页 >技术交流 >STM32 HAL库开发——入门篇(1)网站首页技术交流

STM32 HAL库开发——入门篇(1)

清园暖歌 2024-09-05 12:01:03
简介STM32 HAL库开发——入门篇(1)

目录

一、GPIO

1.1 什么是GPIO 

1.2 GPIO简介 

1.2.1 GPIO特点 

1.2.2 GPIO电气特性 

1.2.3 GPIO引脚分布

1.3 IO端口基本结构介绍 

1.4 GPIO的八种模式 

1.5 GPIO的寄存器介绍

1.6 通用外设驱动模型(四步法)

1.7 GPIO配置步骤 

1.8 编程实战:点灯

1.9 编程实战:按键点灯 

二、中断

2.1 什么是中断

2.2 NVIC

2.2.1 NVIC基本概念 

2.2.2 NVIC相关寄存器

2.2.3 NVIC工作原理 

2.2.4 STM32中断优先级基本概念

2.2.5 STM32中断优先级分组

2.2.6 STM32 NVIC的使用 

2.3 EXTI

2.3.1 EXTI基本概念

2.3.2 EXTI主要特性

2.3.3 EXTI工作原理 

2.4 EXTI和IO映射关系

2.5 如何使用中断

2.6 通用外设驱动模型

2.7 HAL库中断回调处理机制介绍 

2.8 编程实战:外部中断控制LED

三、串口通信

3.1 数据通信的基本概念

3.2 串口(RS-232)

3.3 STM32的USART

3.3.1 STM32的USART简介

3.3.2 STM32的USART主要特征 

3.3.3 STM32 F1/F4/F7的USART框图 

3.3.4 设置USART/UART波特率(F1)

3.3.5 USART寄存器介绍

3.4 HAL库外设初始化MSP回调机制

3.5 HAL库中断回调机制

3.6 USART/UART异步通信配置步骤

3.7 IO引脚复用功能

3.8 编程实战:串口接收或发送一个字符 

3.9 串口实验源码解读 

四、IWDG独立看门狗

4.1 IWDG简介 

4.2 IWDG工作原理

4.3 IWDG框图

4.4 IWDG寄存器

4.5 IWDG溢出时间计算

4.6 IWDG配置步骤

4.7 编程实战:验证不及时喂狗

五、WWDG窗口看门狗

5.1 WWDG简介 

5.2 WWDG工作原理

5.3 WWDG框图

5.4 WWDG寄存器

5.5 WWDG超时时间计算 

5.6 WWDG配置步骤

5.7 编程实战:验证窗口看门狗功能

5.8 IWDG和WWDG的区别

用的是正点原子STM32F103ZET6精英版

一、GPIO

1.1 什么是GPIO 

1.2 GPIO简介 

1.2.1 GPIO特点 

超频时才会达到最高50MHz

1.2.2 GPIO电气特性 

芯片数据手册中,标了FT的就是TTL端口,TTL端口兼容5v和3.3v

不会每个IO口都是25mA,因为最大电流150mA 

其他用大电流外设要考虑扩流 

1.2.3 GPIO引脚分布

1.3 IO端口基本结构介绍 

F1只有输入才选择上下拉,F4、F7、H7输入输出都可以选择上下拉

 

 

 

1.4 GPIO的八种模式 

前面4个输入。后面4个输出 

IIC:数据SDA,时钟SCL

输入: 

输出:

 

输出控制这里有一个反相器 

4中输出模式下,外接一个5v的上拉电阻才可以输出5v的电平 

1.5 GPIO的寄存器介绍

CRL+CRH一共64位,16个 引脚,每个脚4个位控制

上拉还是下拉输入模式就要通过ODR寄存器控制

后面是F4,F7,H7的 

总的来说,建议使用BSRR寄存器

1.6 通用外设驱动模型(四步法)

1.7 GPIO配置步骤 

 

1.8 编程实战:点灯

       0805贴片发光二极管 

       该电流的计算忽略了发光二极管的内阻,不过内阻也很小 ,得到约2.88mA与官方的5~8mA还是有差距,不过没关系就是亮度暗一些

       该led原理图显示led是共阳的,设置开漏输出的话,因为没有外接的上拉电阻,所以只能输出低电平,输出0的时候可以亮,想输出1的时候就是高阻态,PB5断开,led就灭,所以开漏也可以,如果是共阴则不可以 ,共阴如下 

// PB5; PE5

void led_init(void)
{
    GPIO_InitTypeDef gpio_init_struct;
    __HAL_RCC_GPIOB_CLK_ENABLE();
    
    gpio_init_struct.Pin = GPIO_PIN_5;
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; // F1里面输出是禁止使用上下拉的,设置了也是无效的
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOB, &gpio_init_struct);

    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET);  // 初始led为灭
}

正点原子跑马灯的代码,看的我太舒服了!

 

1.9 编程实战:按键点灯 

按键有物理上的抖动,所以需要延时5~10ms消抖,一般就10ms了 

KEY_UP:下拉输入

KEY0、1、2:上拉输入 

精英版没有KEY2

void key_init(void)
{
    GPIO_InitTypeDef gpio_init_struct;
    __HAL_RCC_GPIOE_CLK_ENABLE();
    
    gpio_init_struct.Pin = GPIO_PIN_3;
    gpio_init_struct.Mode = GPIO_MODE_INPUT;
    gpio_init_struct.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(GPIOE, &gpio_init_struct);


}

/* 按键扫描  */
uint8_t key_scan(void)
{
    if (HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_3) == 0) // 按下了
    {
        delay_ms(10); // 消抖
        if (HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_3) == 0)
        {
            while(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_3) == 0); // 松开后才返回1
            return 1; // 按键按下了
        
        }    
        
    }
    return 0; // 按键没有按下


}

按键输入例程:

二、中断

2.1 什么是中断

 

2.2 NVIC

2.2.1 NVIC基本概念 

中断服务函数被定义在中断向量表里面 

10个内核中断: 

60个外部中断:

 

2.2.2 NVIC相关寄存器

       32*8=256, 一共有240个外部中断,所以有16个位保留了

2.2.3 NVIC工作原理 

2.2.4 STM32中断优先级基本概念

2.2.5 STM32中断优先级分组

如果AIRCR设置优先级分组为3,那么IPR中的bit7、6、5是设定抢占的,bit4是设定响应的 

如果设置了多次,则已最后一次为准 

2.2.6 STM32 NVIC的使用 

 

这里若是 IP[0] 指的就是编号为 0 的WWDG寄存器,共240个,f103只用了60个

2.3 EXTI

2.3.1 EXTI基本概念

因为F1不是互联型开发板,所以没有EXTI19,重点就是0~15

2.3.2 EXTI主要特性

2.3.3 EXTI工作原理 

上升沿触发选择寄存器(EXTI_RTSR) 

下降沿触发选择寄存器(EXTI_FTSR) 

中断屏蔽寄存器(EXTI_IMR) 

挂机寄存器(EXTI_PR) 

2.4 EXTI和IO映射关系

 

参考中文参考手册:9.2.5 

 

2.5 如何使用中断

分:EXTI中断 和 其他外设中断 

共有7个中断服务函数,0~4有5个,5~9共1个,10~15也共1个,一共7个 

2.6 通用外设驱动模型

2.7 HAL库中断回调处理机制介绍 

2.8 编程实战:外部中断控制LED

#include "./BSP/EXTI/exti.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./SYSTEM/usart/usart.h"

void EXTI4_IRQHandler(void)
{
    printf("1
");
    if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_4) != RESET)
    {
        HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_4);
        
    }
}

/*
    按键按下后,触发中断,调用中断服务函数 EXIT4_IRQHandler;
    然后调用公共处理函数 HAL_GPIO_EXTI_IRQHandler;
    公共处理函数再调用 callback 函数;
    此时,按键 KEY0:PE4 已经按下

*/

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    delay_ms(00);  // 消抖
    printf("2
");
    
    if(GPIO_Pin == GPIO_PIN_4) // 判断是否是我们的中断线触发了中断
    {
        if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_4) == 0)
        {
            HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_5);  // LED0,RED
        
        }
        __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_4);
    }    
    

}

void exti_init(void)
{
    GPIO_InitTypeDef gpio_init_struct;
    
    __HAL_RCC_GPIOE_CLK_ENABLE();

    gpio_init_struct.Pin = GPIO_PIN_4;                  
    gpio_init_struct.Mode = GPIO_MODE_IT_FALLING;          
    gpio_init_struct.Pull = GPIO_PULLUP;                   
    HAL_GPIO_Init(GPIOE, &gpio_init_struct);      

    HAL_NVIC_SetPriority(EXTI4_IRQn, 0, 2);
    HAL_NVIC_EnableIRQ(EXTI4_IRQn);  // 使能
}



       注意代码的细节不要出错, 博主就因为EXTI4_IRQHandler函数,写成了EXIT4_IRQHandler,没跑出结果,编译并没有报错还找了好一阵错误

三、串口通信

3.1 数据通信的基本概念

编码方式很多 

码元:在数字通信中常常用时间间隔相同的符号来表示一个二进制数字,这样的时间间隔内的信号称为(二进制)码元。而这个间隔被称为码元长度。值得注意的是当码元的离散状态有大于2个时(如M大于2个)时,此时码元为M进制码元。(百度百科)

  这里说的比较官方,一下子没反应过来。在谢希仁这本书中有一个实例,假定基带信号为101011000110111010…如果直接传送,则每个码元携带的信息是1bit(可以理解为每个二进制都是一个码元),而将上面的信号分为 101 011 000 110 111 010,则视为6个码元,每个码元为3bit,8种表现形式,2^3.这种表现形式就是说接收方要唯一确定这个码元,官方点就是8种不同的振幅或者频率或者相位。你也可以分为1010 1100 0110 1110 10…这种为5个码元,16种表现形式。

  总结:码元说白了就是你以怎样的形式去定义你要发的信息,传输多个bit,还是一个码元。

波特率又称码元率,是指每秒传输码元的数目,单位波特(Band)
比特率为每秒传输的比特(bit)数。

  前面已经解释过,码元的大小可以自己定义,如果码元大小定义为1时,码元率(波特率)= 比特率。

由此可得波特率和比特率的关系

波特率 = 比特率/每符号含的比特数。(比特率也叫数据率)

 

3.2 串口(RS-232)

现在的电脑基本没有 DB9 的接口了,而是使用 USB 接口 

PC端需要安装 CH340 USB 虚拟串口驱动

LSB:最低有效位,这里就是位0

MSB: 最高有效位,这里就是位1

异步通信没有 SCLK 时钟 

3.3 STM32的USART

3.3.1 STM32的USART简介

3.3.2 STM32的USART主要特征 

 

 

3.3.3 STM32 F1/F4/F7的USART框图 

       这里的框图是F1的 

       SW-RX是芯片内部的引脚,不用管他

 

3.3.4 设置USART/UART波特率(F1)

       F4/F7/H7等型号在后面几讲

baud = f_ck / 16 / usartdiv分频 

存放在 USART_BRR 寄存器

+0.5 起到四舍五入的作用

fraction 的 *16 就是左移4位的意思 ,以整数的方式保存小数点后面的部分

 

3.3.5 USART寄存器介绍

如果 CR1 寄存器选择 8 个数据位,那么这里 DR 寄存器就用 0~7 的8位;9位就是 0~8

RXNEIE就是CR1的位5:使能接收缓冲区非空中断

 

3.4 HAL库外设初始化MSP回调机制

       PPP 是指任意外设 ,HAL_PPP_MspInit()实际是个空函数且允许重定义

多个外设共用MSP回调函数时,会有3个 .c 文件,此时就不确定在哪个文件中定义 

3.5 HAL库中断回调机制

 

3.6 USART/UART异步通信配置步骤

 

3.7 IO引脚复用功能

有重映射的IO才进行重映射解决冲突,不然没有办法 

 

3.8 编程实战:串口接收或发送一个字符 

uint8_t g_usart1_rx_flag = 0;   /* 串口接收到数据标志 */
uint8_t g_rx_buffer[1];          /* HAL库使用的串口接收缓冲 ,接收1个字节*/
UART_HandleTypeDef g_uart1_handle;  /* UART句柄 */

/**
 * @brief       串口X初始化函数
 * @param       baudrate: 波特率, 根据自己需要设置波特率值
 * @note        注意: 必须设置正确的时钟源, 否则串口波特率就会设置异常.
 *              这里的USART的时钟源在sys_stm32_clock_init()函数中已经设置过了.
 * @retval      无
 */
 
 
 
 /* 串口1初始化函数 */
void usart_init(uint32_t baudrate)
{
    /*UART 初始化设置*/
    g_uart1_handle.Instance = USART1;                                         /* USART_UX */
    g_uart1_handle.Init.BaudRate = baudrate;                                  /* 波特率 */
    g_uart1_handle.Init.WordLength = UART_WORDLENGTH_8B;                      /* 字长为8位数据格式 */
    g_uart1_handle.Init.StopBits = UART_STOPBITS_1;                           /* 一个停止位 */
    g_uart1_handle.Init.Parity = UART_PARITY_NONE;                            /* 无奇偶校验位 */
    g_uart1_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE;                      /* 无硬件流控 */
    g_uart1_handle.Init.Mode = UART_MODE_TX_RX;                               /* 收发模式 */
    HAL_UART_Init(&g_uart1_handle);                                           /* HAL_UART_Init()会使能UART1 */

    /* 该函数会开启接收中断:标志位UART_IT_RXNE,并且设置接收缓冲以及接收缓冲接收最大数据量 */
    HAL_UART_Receive_IT(&g_uart1_handle, (uint8_t *)g_rx_buffer, 1); // 开启接收中断
    
}






/**
 * @brief       UART底层初始化函数
 * @param       huart: UART句柄类型指针
 * @note        此函数会被HAL_UART_Init()调用
 *              完成时钟使能,引脚配置,中断配置
 * @retval      无
 */
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
    GPIO_InitTypeDef gpio_init_struct;

    if (huart->Instance == USART1)                            /* 如果是串口1,进行串口1 MSP初始化 */
    {
        /* (1)使能USART1和对应IO时钟;(2)初始化IO;(3)使能USART1中断,设置优先级 */

        __HAL_RCC_GPIOA_CLK_ENABLE();                                   /* 使能串口RX、TX脚时钟 */
        __HAL_RCC_USART1_CLK_ENABLE();                                  /* 使能串口时钟 */

        gpio_init_struct.Pin = GPIO_PIN_9;                      /* 串口发送引脚号 */
        gpio_init_struct.Mode = GPIO_MODE_AF_PP;                /* 复用推挽输出 */
        gpio_init_struct.Pull = GPIO_PULLUP;                    /* 上拉 ,输出可以不设置上下拉*/
        gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;          /* IO速度设置为高速 */
        HAL_GPIO_Init(GPIOA, &gpio_init_struct);
                
        gpio_init_struct.Pin = GPIO_PIN_10;                      /* 串口接收引脚号 */
        gpio_init_struct.Mode = GPIO_MODE_AF_INPUT;                /* 复用推挽输入 */
        gpio_init_struct.Pull = GPIO_PULLUP;                    /* 上拉 */
        gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;          /* IO速度设置为高速 ,可以不设置速度*/
        HAL_GPIO_Init(GPIOA, &gpio_init_struct);
        
        HAL_NVIC_SetPriority(USART1_IRQn, 0, 2);
        HAL_NVIC_EnableIRQ(USART1_IRQn);
        
        
        

    }
}




/**
 * @brief       串口数据接收回调函数
                数据处理在这里进行
 * @param       huart:串口句柄
 * @retval      无
 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    g_usart1_rx_flag = 1;
}





/**
 * @brief       串口X中断服务函数
                注意,读取USARTx->SR能避免莫名其妙的错误
 * @param       无
 * @retval      无
 */
void USART1_IRQHandler(void)
{
  

    HAL_UART_IRQHandler(&g_uart1_handle);                               /* 调用HAL库中断处理公用函数 */
    /* 上述函数会清除中断标志位并调用回调函数,这里会失能 接收中断使能函数,所以下面重新调用一次 */
    
    HAL_UART_Receive_IT(&g_uart1_handle, (uint8_t *)g_rx_buffer, 1);
    
//    while (HAL_UART_Receive_IT(&g_uart1_handle, (uint8_t *)g_rx_buffer, RXBUFFERSIZE) != HAL_OK)     /* 重新开启中断并接收数据 */
//    {
//        /* 如果出错会卡死在这里 */
//    }

}

3.9 串口实验源码解读 

2^14 B = 16KB

实际就不用 printf了,可以测试代码的时候用 

四、IWDG独立看门狗

4.1 IWDG简介 

RC振荡器的时间精度会差一点 

4.2 IWDG工作原理

LSI 就是RC振荡器 

4.3 IWDG框图

LSI 输入经过8分频后进入 12位递减计数器 

4.4 IWDG寄存器

psc 为预分频系数

prer 就是低三位的值

 

4.5 IWDG溢出时间计算

 

4.6 IWDG配置步骤

右边的结构体 F1/F4 和 F7/H7 有些许差异,F7/H7 多一个 Window ,就是窗口寄存器 

HAL_IWDG_Init()函数 

4.7 编程实战:验证不及时喂狗

#include "./BSP/WDG/wdg.h"


IWDG_HandleTypeDef g_iwdg_handle;


/* IWDG初始化函数 */
void iwdg_init(uint8_t prer, uint16_t rlr)  /* prer只占用了3位,重装载值是12个位 */
{
    g_iwdg_handle.Instance = IWDG;
    g_iwdg_handle.Init.Prescaler = prer;
    g_iwdg_handle.Init.Reload = rlr;
    HAL_IWDG_Init(&g_iwdg_handle);


}

/* 喂狗函数 */
void iwdg_feed(void)
{
    HAL_IWDG_Refresh(&g_iwdg_handle);
}

int main(void)
{


    HAL_Init();                             /* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9);     /* 设置时钟为72Mhz */
    delay_init(72);                         /* 延时初始化 */
    usart_init(115200);                     /* 串口初始化为115200 */
    led_init();                             /* 初始化LED */
    iwdg_init(IWDG_PRESCALER_32, 1250);     /* 1s的溢出时间,rlr=1*40000/32=1250 */
     
    
    printf("您还没喂狗,请及时喂狗!!!
");
    while (1)
    {
        delay_ms(1000);
        iwdg_feed();
        printf("已经喂狗!
");
            
    }
}

五、WWDG窗口看门狗

5.1 WWDG简介 

窗口看门狗时钟来自系统总线的时钟 

5.2 WWDG工作原理

       窗口下限值是固定的,窗口上限值和计数器初始值是由用户编程决定的 ,计数值低于窗口上限值时才可以进行喂狗,若在窗口上限值上面就喂狗,系统会复位

 

5.3 WWDG框图

T6位时判断是否从64到63,所以是6位递减计数器 

5.4 WWDG寄存器

5.5 WWDG超时时间计算 

 

5.6 WWDG配置步骤

 

5.7 编程实战:验证窗口看门狗功能

#include "./BSP/WDG/wdg.h"
#include "./BSP/LED/led.h"


WWDG_HandleTypeDef g_wwdg_handle;

/* 窗口看门狗初始化函数 */
void wwdg_init(uint8_t tr, uint8_t wr, uint32_t fprer)
{
    
    g_wwdg_handle.Instance = WWDG;
    g_wwdg_handle.Init.Counter = tr;
    g_wwdg_handle.Init.Window = wr;
    g_wwdg_handle.Init.Prescaler = fprer;
    g_wwdg_handle.Init.EWIMode = WWDG_EWI_ENABLE;
    
    HAL_WWDG_Init(&g_wwdg_handle);

}



/* WWDG MSP回调函数 */
void HAL_WWDG_MspInit(WWDG_HandleTypeDef *hwwdg)
{
    __HAL_RCC_WWDG_CLK_ENABLE();
    
    HAL_NVIC_SetPriority(WWDG_IRQn, 2, 3);
    HAL_NVIC_EnableIRQ(WWDG_IRQn);

}


/* WWDG中断服务函数 */
void WWDG_IRQHandler(void)
{
    HAL_WWDG_IRQHandler(&g_wwdg_handle);

}


/* WWDG提前唤醒回调函数 */
void HAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef *hwwdg)
{
    //delay_ms(1); // 会超时,1个周期是0.9ms
    HAL_WWDG_Refresh(&g_wwdg_handle);  // 在 0x40=64的时候喂狗
    LED1_TOGGLE();

}


#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/delay/delay.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
#include "./BSP/WDG/wdg.h"


int main(void)
{
    HAL_Init();                                 /* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9);         /* 设置时钟,72M */
    delay_init(72);                             /* 初始化延时函数 */
    led_init();                                 /* 初始化LED */
    usart_init(115200);
    
    if(__HAL_RCC_GET_FLAG(RCC_FLAG_WWDGRST) != RESET)
    {
        printf("窗口看门狗复位

");
        __HAL_RCC_CLEAR_RESET_FLAGS();
    }
    else
    {
        printf("外部复位
");
    }
    
    delay_ms(500);    
    printf("请在窗口期内喂狗
");
    wwdg_init(0x7F, 0X5F, WWDG_PRESCALER_8);
    
    while(1)
    {
        
        delay_ms(57);
        //HAL_WWDG_Refresh(&g_wwdg_handle);
        
//        delay_ms(90);
//        HAL_WWDG_Refresh(&g_wwdg_handle);
//        LED1_TOGGLE();
        
        
    }
}

5.8 IWDG和WWDG的区别

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。