天天看点

STM32F1复习笔记一

去年疫情在家学的,现在忘完了。。就准备把一些非常常见的模块功能的使用方法给记录这里,一来写一遍代码有个印象,二来也方便查阅。

主要是整理正点原子的开发指南我用得到的地方

STM32复习笔记一

    • STM32系统架构图
    • 端口复用功能
      • **定义**
      • **复用端口初始化具体步骤**
      • **示例代码**:
    • 端口重映射
      • **说明**
      • **端口复用具体步骤**
      • **示例代码**
    • NVIC 中断优先级管理
      • **说明**
      • **相关寄存器介绍**
      • 中断优先级设置的步骤
      • 示例代码
    • GPIO配置
      • IO端口寄存器
      • 具体步骤
      • 示例代码
    • 串口配置
      • 具体步骤
      • 示例代码
    • 定时器中断配置
      • 相关寄存器
      • 具体步骤
      • 示例代码
    • PWM输出配置
      • 相关寄存器
      • 具体步骤
      • 示例代码
    • 输入捕获配置
      • 相关寄存器
      • 具体步骤
      • 示例代码

STM32系统架构图

STM32F1复习笔记一
STM32F1复习笔记一

端口复用功能

定义

一个 GPIO如果可以复用为内置外设的功能引脚,那么当这个 GPIO 作为内置外设使用的时候,就叫做复用。

复用端口初始化具体步骤

  • GPIO端口时钟使能
  • 复用的外设时钟使能
  • 端口模式配置

示例代码:

// 1 GPIO端口时钟使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

// 2 复用的外设时钟使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);

// 3 端口模式配置
//USART1_TX PA.9 复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure);
//USART1_RX PA.10 浮空输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);
           

端口重映射

说明

把一些复用功能重新映射到其他一些引脚上。简单的讲就是把管脚的外设功能映射到另一个管脚,但不是可以随便映射的,具体对应关系《STM32 中文参考手册 V10》的 P116 页“8.3 复用功能和调试配置”有讲解。

补充1:至于有哪些功能可以重映射,除了查看中文参考手册之外,还可以从 GPIO_PinRemapConfig 函数入手查看第一个入口参数的取值范围可以得知。(还有stm32f10x_gpio.h 文件中的宏定义标识符)

补充2:部分重映射就是部分管脚和默认的是一样的,而部分管脚是重新映射到其他管脚。而完全重映射就是所有管脚都重新映射到其他管脚。

端口复用具体步骤

以串口1为例

  • 使能 GPIOB 时钟
  • 使能串口 1 时钟
  • 使能 AFIO 时钟
  • 开启重映射

示例代码

以UART1为例

// 1)使能 GPIOB 时钟:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
// 2)使能串口 1 时钟:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
// 3)使能 AFIO 时钟:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
// 4)开启重映射:
GPIO_PinRemapConfig(GPIO_Remap_USART1, ENABLE);

           

NVIC 中断优先级管理

说明

STM32 有 84 个中断,包括 16 个内核中断和 68 个可屏蔽中断,具有 16 级可编程的中断优先级。而我们常用的就是这 68 个可屏蔽中断,但是 STM32 的 68 个可屏蔽中断,在 STM32F103 系列上面,又只有 60 个(在 107 系列才有 68 个)。

相关寄存器介绍

  • ISER[8]:ISER 全称是:Interrupt Set-Enable Registers,这是一个中断使能寄存器组。
  • ICER[8]:全称是:Interrupt Clear-Enable Registers,是一个中断除能寄存器组。
  • ISPR[8]:全称是:Interrupt Set-Pending Registers,是一个中断挂起控制寄存器组。
  • ICPR[8]:全称是:Interrupt Clear-Pending Registers,是一个中断解挂控制寄存器组。
  • IABR[8]:全称是:Interrupt Active Bit Registers,是一个中断激活标志位寄存器组。
  • IP[240]:全称是:Interrupt Priority Registers,是一个中断优先级控制的寄存器组。

中断优先级设置的步骤

  • 系统运行开始的时候设置中断分组。确定组号,也就是确定抢占优先级和子优先级的分配位数。调用函数为 NVIC_PriorityGroupConfig();
  • 设置所用到的中断的中断优先级别。对每个中断调用函数为 NVIC_Init();

示例代码

比如我们要使能串口 1 的中断,同时设置抢占优先级为 1,子优先级位 2,初始化的方法:

// 下面的一行代码用于分组,在main函数中。
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);


NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//串口 1 中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1 ;// 抢占优先级为 1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;// 子优先级位 2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ 通道使能
NVIC_Init(&NVIC_InitStructure); //根据上面指定的参数初始化 NVIC 寄存器
           

GPIO配置

IO端口寄存器

STM32 的每个 IO 端口都有 7 个寄存器来控制。他们分别是:

  • 配置模式的 2 个 32 位的端口配置寄存器 CRL 和 CRH(CRL 和 CRH 控制着每个 IO 口的模式及输出速率);
  • 2 个 32 位的数据寄存器 IDR 和 ODR;
  • 1 个 32 位的置位/复位寄存器 BSRR;
  • 一个 16 位的复位寄存器 BRR;
  • 1 个 32 位的锁存寄存器 LCKR。

了解每个寄存器的详细使用方法,可以参考《STM32 中文参考手册 V10》P105~P129。

具体步骤

  • 使能IO口时钟
  • 初始化IO口参数
  • 操作IO口

示例代码

使能PB5和PE5

GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|
RCC_APB2Periph_GPIOE, ENABLE); //使能 PB,PE 端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //LED0-->PB.5 推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB,GPIO_Pin_5); //PB.5 输出高
           

串口配置

具体步骤

  • 串口时钟使能,GPIO 时钟使能
  • 串口复位
  • GPIO 端口模式设置
  • 串口参数初始化
  • 开启中断并且初始化 NVIC(如果需要开启中断才需要这个步骤)
  • 使能串口
  • 编写中断处理函数

示例代码

串口1为例

GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
//①串口时钟使能,GPIO 时钟使能,复用时钟使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|
RCC_APB2Periph_GPIOA, ENABLE); //使能 USART1,GPIOA 时钟
//②串口复位
USART_DeInit(USART1); //复位串口 1
//③GPIO 端口模式设置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //ISART1_TX PA.9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化 GPIOA.9
 
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //USART1_RX PA.10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化 GPIOA.10
//④串口参数初始化
USART_InitStructure.USART_BaudRate = bound; //波特率设置
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长为 8 位
USART_InitStructure.USART_StopBits = USART_StopBits_1; //一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No; //无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl 
= USART_HardwareFlowControl_None; //无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;//收发模式
USART_Init(USART1, &USART_InitStructure); //初始化串口
#if EN_USART1_RX //如果使能了接收
//⑤初始化 NVIC
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ; //抢占优先级 3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级 3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ 通道使能
NVIC_Init(&NVIC_InitStructure); //中断优先级初始化
//⑤开启中断
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //开启中断
#endif
//⑥使能串口
USART_Cmd(USART1, ENABLE); //使能串口


           

中断服务函数(在sys的uart.h中)

void USART1_IRQHandler(void) //串口 1 中断服务程序
{
u8 Res;
#if SYSTEM_SUPPORT_OS //如果 SYSTEM_SUPPORT_OS 为真,则需要支持 OS
OSIntEnter(); 
#endif
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) 
//接收中断(接收到的数据必须是 0x0d 0x0a 结尾) {
Res =USART_ReceiveData(USART1);//(USART1->DR); //读取接收到的数据
if((USART_RX_STA&0x8000)==0)//接收未完成
{
if(USART_RX_STA&0x4000)//接收到了 0x0d
{
if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
else USART_RX_STA|=0x8000; //接收完成了
}
else //还没收到 0X0D
{
if(Res==0x0d)USART_RX_STA|=0x4000;
else
{
USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
USART_RX_STA++;
if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;
//接收数据错误,重新开始接收 
} }
} 
 } 
#if SYSTEM_SUPPORT_OS //如果 SYSTEM_SUPPORT_OS 为真,则需要支持 OS.
OSIntExit(); 
#endif
}
           

EN_USART1_RX 和 USART_REC_LEN 都是在 usart.h 文件里面定义的,当需要使用串口接收的时候,我们只要在 usart.h 里面设置EN_USART1_RX 为 1 就可以了。不使用的时候,设置,EN_USART1_RX 为 0 即可,这样可以省出部分 sram 和 flash,我们默认是设置 EN_USART1_RX为 1,也就是开启串口接收的。

定时器中断配置

STM32F1复习笔记一

相关寄存器

控制寄存器 1(TIMx_CR1)

TIMx_CNT 当前值寄存器

自动重装载寄存器(TIMx_ARR)

具体步骤

  • TIM3 时钟使能
  • 设置 TIM3_DIER 允许更新中断
  • TIM3 中断优先级设置
  • 允许 TIM3 工作,也就是使能 TIM3
  • 编写中断服务函数

示例代码

补充:中断时间计算公式

STM32F1复习笔记一
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //①时钟 TIM3 使能
 
//定时器 TIM3 初始化
TIM_TimeBaseStructure.TIM_Period = arr; //设置自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM 向上计数
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //②初始化 TIM3
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE ); //③允许更新中断
//中断优先级 NVIC 设置
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //TIM3 中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //先占优先级 0 级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //从优先级 3 级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ 通道被使能
NVIC_Init(&NVIC_InitStructure); //④初始化 NVIC 寄存器
TIM_Cmd(TIM3, ENABLE); //⑤使能 TIM3 }
//定时器 3 中断服务程序⑥
void TIM3_IRQHandler(void) //TIM3 中断
{
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //检查 TIM3 更新中断发生与否
{
TIM_ClearITPendingBit(TIM3, TIM_IT_Update ); //清除 TIM3 更新中断标志
LED1=!LED1;
} }
           

PWM输出配置

相关寄存器

除了TIM相关的寄存器,还有:

  • 捕获/比较模式寄存器(TIMx_CCMR1/2)
  • 捕获/比较使能寄存器(TIMx_CCER)
  • 捕获/比较寄存器(TIMx_CCR1~4)

具体步骤

  • 开启 TIM3 时钟以及复用功能时钟,配置 PB5 为复用输出
  • 设置 TIM3_CH2 重映射到 PB5 上
  • 初始化 TIM3,设置 TIM3 的 ARR 和 PSC
  • 设置 TIM3_CH2 的 PWM 模式,使能 TIM3 的 CH2 输出
  • 使能 TIM3
  • 修改 TIM3_CCR2 来控制占空比

示例代码

以PB5为例

GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //①使能定时器 3 时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|
RCC_APB2Periph_AFIO, ENABLE); //①使能 GPIO 和 AFIO 复用功能时钟
GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); //②重映射 TIM3_CH2->PB5 
//设置该引脚为复用输出功能,输出 TIM3 CH2 的 PWM 脉冲波形 GPIOB.5
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //TIM_CH2
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //①初始化 GPIO
//初始化 TIM3
TIM_TimeBaseStructure.TIM_Period = arr; //设置在自动重装载周期值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM 向上计数模式
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //③初始化 TIMx
//初始化 TIM3 Channel2 PWM 模式
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //选择 PWM 模式 2
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性高
TIM_OC2Init(TIM3, &TIM_OCInitStructure); //④初始化外设 TIM3 OC2
TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable); //使能预装载寄存器
TIM_Cmd(TIM3, ENABLE); //⑤使能 TIM3
           

这里提醒下:在配置 AFIO 相关寄存器的时候,必须先开启辅助功能时钟。

输入捕获配置

相关寄存器

TIMx_ARR、

TIMx_PSC、

TIMx_CCMR1、

TIMx_CCER、

TIMx_DIER、

TIMx_CR1、

TIMx_CCR1

具体步骤

  • 开启 TIM5 时钟和 GPIOA 时钟,配置 PA0 为下拉输入
  • 初始化 TIM5,设置 TIM5 的 ARR 和 PSC
  • 设置 TIM5 的输入比较参数,开启输入捕获
  • 使能捕获和更新中断(设置 TIM5 的 DIER 寄存器)
  • 设置中断分组,编写中断服务函数
  • 使能定时器(设置 TIM5 的 CR1 寄存器)

示例代码

定时器5通道1(PA0)

//定时器 5 通道 1 输入捕获配置
TIM_ICInitTypeDef TIM5_ICInitStructure;
void TIM5_Cap_Init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE); //①使能 TIM5 时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //①使能 GPIOA 时钟
//初始化 GPIOA.0 ①
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //PA0 设置 
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA0 输入 
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化 GPIOA.0
GPIO_ResetBits(GPIOA,GPIO_Pin_0); //PA0 下拉
//②初始化 TIM5 参数
TIM_TimeBaseStructure.TIM_Period = arr; //设定计数器自动重装值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //预分频器 
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; // TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM 向上计数模式
TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure); //初始化 TIMx
//③初始化 TIM5 输入捕获通道 1
TIM5_ICInitStructure.TIM_Channel = TIM_Channel_1; //选择输入端 IC1 映射到 TI1 上
TIM5_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //上升沿捕获
TIM5_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //映射到 TI1 上
TIM5_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //配置输入分频,不分频
TIM5_ICInitStructure.TIM_ICFilter = 0x00; //IC1F=0000 配置输入滤波器 不滤波
TIM_ICInit(TIM5, &TIM5_ICInitStructure); //初始化 TIM5 输入捕获通道 1
//⑤初始化 NVIC 中断优先级分组
NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn; //TIM3 中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //先占优先级 2 级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //从优先级 0 级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ 通道被使能
NVIC_Init(&NVIC_InitStructure); //初始化 NVIC
TIM_ITConfig( TIM5,TIM_IT_Update|TIM_IT_CC1,ENABLE);//④允许更新中断捕获中断
TIM_Cmd(TIM5,ENABLE ); //⑥使能定时器 5 }
u8 TIM5CH1_CAPTURE_STA=0; //输入捕获状态 
u16 TIM5CH1_CAPTURE_VAL;//输入捕获值
//⑤定时器 5 中断服务程序
void TIM5_IRQHandler(void)
{ 
if((TIM5CH1_CAPTURE_STA&0X80)==0)//还未成功捕获
{ 
if (TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET)
{ 
if(TIM5CH1_CAPTURE_STA&0X40) //已经捕获到高电平了
{
if((TIM5CH1_CAPTURE_STA&0X3F)==0X3F)//高电平太长了
{
TIM5CH1_CAPTURE_STA|=0X80; //标记成功捕获了一次
TIM5CH1_CAPTURE_VAL=0XFFFF;
}else TIM5CH1_CAPTURE_STA++;
} }
if (TIM_GetITStatus(TIM5, TIM_IT_CC1) != RESET) //捕获 1 发生捕获事件
{
if(TIM5CH1_CAPTURE_STA&0X40) //捕获到一个下降沿
{ 
TIM5CH1_CAPTURE_STA|=0X80; //标记成功捕获到一次上升沿
TIM5CH1_CAPTURE_VAL=TIM_GetCapture1(TIM5);
 TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Rising); //设置为上升沿捕获
}else //还未开始,第一次捕获上升沿
{
TIM5CH1_CAPTURE_STA=0; //清空
TIM5CH1_CAPTURE_VAL=0;
TIM_SetCounter(TIM5,0);
TIM5CH1_CAPTURE_STA|=0X40; //标记捕获到了上升沿
 TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Falling); //设置为下降沿捕获
} 
} 
}
 TIM_ClearITPendingBit(TIM5, TIM_IT_CC1|TIM_IT_Update); //清除中断标志位
}

           

此时,我们就完成一次高电平捕获了,只要TIM5CH1_CAPTURE_STA 的第 7 位一直为 1,那么就不会进行第二次捕获,我们在main函数处理完捕获数据后,将TIM5CH1_CAPTURE_STA置零,就可以开启第二次捕获。

好久没写博客了,说明这一段没有学习。确实心情不好,加油。

2021年3月4日21:03