typora-copy-images-to: asserts

STM32_Learn

// 让 PB0 输出低/高电平
// 输出低电平
GPIO_ODR &= ~(1<<0);
// 输出高电平
GPIO_ODR |= (1<<0);

PB3 4 PA 15 JTAG 使用时需要 再进行配置

GPIO引脚功能

image-20231011090616546

GPIO 模式

image-20230920082514498

1、4种输入模式

(1)GPIO_Mode_IN_FLOATING 浮空输入
(2)GPIO_Mode_IPU 上拉输入
(3)GPIO_Mode_IPD 下拉输入
(4)GPIO_Mode_AIN 模拟输入

2、4种输出模式

(5)GPIO_Mode_Out_OD 开漏输出(带上拉或者下拉)
(6)GPIO_Mode_AF_OD 复用开漏输出(带上拉或者下拉)
(7)GPIO_Mode_Out_PP 推挽输出(带上拉或者下拉)
(8)GPIO_Mode_AF_PP 复用推挽输出(带上拉或者下拉)
3、4种最大输出速度
(1)2MHZ (低速)
(2)25MHZ (中速)
(3)50MHZ (快速)
(4)100MHZ (高速)

typedef   signed           char int8_t;//给有符号char,取别名为int8_t
typedef   signed short     int  int16_t;//给有符号短整型short int,取别名int16_t
typedef   signed           int  int32_t;//给有符号整型short int,取别名int32_t
typedef   signed       __INT64  int64_t;

    /* exact-width unsigned integer types */
typedef unsigned          char uint8_t;//给无符号char,取别名为uint8_t
typedef unsigned short     int uint16_t;//给无符号短整型short int,取别名为uint16_t
typedef unsigned           int uint32_t;//给无符号整型 int,取别名为uint32_t

EXTI外部中断

NVIC 的名字叫做嵌套中断向量控制器,在 STM32 中,它是用来统一分配中断优先级和管理中断的,NVIC 是一个内核外设,是 CPU 的小助手

中断响应是正常的流程,引脚电平变化触发中断。

事件响应不会触发中断,而是触发别的外设操作,属于外设之间的联合工作

EXIT可以检测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXIT将立即系那个NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使COU执行EXIT对应的中断程序

  • 支持的触发方式:上升沿/下降沿/双边沿/软件触发
  • 支持的GPIO口:所有GPIO口,但相同的Pin不能同时触发中断
  • 通道数:16个GPIO_Pin,外加PVD输出,RTC闹钟、USB唤醒、以太网唤醒
  • 触发相应方式:中断响应/事件相应
  • 如果选择触发事件响应,外部中断的信号就不会通向CPU,而是通向其他外设,用来触发其他外设的操作

外部中断的9\~5,和15\~10 分别分到同一个通道,外部中断的9\~5会触发同一个中断函数。15\~10也会触发同一个中断函数,需要根据标志位,区分是哪个中断

image-20230914094347273

EXTI 函数:

// 调用函数 可以把 EXTI 的配置都清除  恢复成上电默认的状态
void EXTI_DeInit(void);
// 调用函数 可以根据结构体里的参数配置 EXTI 外设
void EXTI_Init(EXTI_InitTypeDef * EXTI_InitStruct);
// 调用函数 可以把参数传递的结构体变量赋一个默认值
void EXTI_StructInit(EXTI_InitTypeDef* EXTI_InitStruct);
// 软件触发外部中断 调用这个函数 参数给一个指定的中断线 就能软件触发一次这个外部中断
void EXTI_GenerateSWInterrupt(uint32_t EXTI_Line);
// 可以获取指定的标志位是否被置1了
FlagStatus EXTI_GetFlagStatus(uint32_t EXTI_Line);
// 可以对置1的标志位进行清除
void EXTI_ClearFlag(uint32_t EXTI_Line);
// 获取中断标志位是否被置1了
ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);
// 清除中断挂起标志位
void EXTI_ClearITPendingBit(uint32_t EXTI_Line);

NVIC库:

// 调用函数 用来中断分组 参数是中断分组的方式
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);
// 根据结构体里面指定的参数初始化 NVIC
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
// 设置中断向量表
void NVIC_SetVectorTable(uint32_t NVIC_VectTab, uint32_t Offset);
// 系统低功耗配置
void NVIC_SystemLPConfig(uint8_t LowPowerMode, FunctionalState NewState);
void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource);

例子:

    // 设置中断分组2
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; 
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_Init(&NVIC_InitStructure);

ADC模拟数字转换器

ADC的基本介绍

image-20230712145431124 最大转换频率为 1 MHz

ADC的基本结构

输入通道

image-20230712151039961

转换模式

  • 单次转换,非扫描模式

image-20231018085735283

非扫描模式,菜单列表只用第一个,ADC 对通道 2 进行模数转换,转换完成,转换结果放在数据寄存器里,同时给 EOC 标志位置1,转换过程结束。再启动转换,再进行一次。

  • 连续转换,非扫描模式

image-20231018090046774

非扫描模式,菜单列表只用第一个,与单次转换不同的是,它在一次转换结束后不会停止,而是立刻开始下一轮转换,然后一直持续下去。

模式的好处:开始转换之后不需要等待一段时间,一直在转换,不需要手动开始转换。想到读取 AD 值的时候,直接从数据寄存器取就是了。

  • 单次转换,扫描模式

image-20231018090404466

这个模式也是单次转换,每触发一次就会停下来。扫描模式,会用到菜单列表,这里每个位置是通道几可以任意指定,并且也是可以重复的。初始化结构体会有一个参数,就是通道数目。转换结果都放在数据寄存器里,为了数据被覆盖就需要用 DMA 将数据挪走。

  • 连续转换,扫描模式

image-20231018090758681

同样连续转换,当转换结束后,直接进行下一次转换。还可以有一种模式,间断模式。

作用是,在扫描过程中,每隔几个转换,就暂停一次,需要再次触发,才能继续。

触发控制

image-20231018091011295

数据对齐

image-20231018091112234

如果觉得 0 ~ 4095 数太大了,就可以做个简单的判断,不需要这么高分辨率,可以选择左对齐,然后把数据的高 8 位取出来,这样就舍弃了后面 4 位的精度,这个 12 位的 ADC 转换为了 8 位 ADC。

转换时间

image-20231018091455378

一个 ADC 周期为 (1 / 14MHz) us

校准

image-20231018092256626

案例-单通道AD转换

image-20231018094711237

  • 步骤
  • 开启 RCC 时钟,包括 ADC 时钟和 GPIO 的时钟,配置 ADCCLK 的分频器
  • 配置 GPIO,把需要用的 GPIO 配置成模拟输入的模式
  • 配置这里的多路开关,把左边的通道接入到右边的规则组列表里。
  • 配置 ADC 转换器 结构体配置
  • 开关控制 调用 ADC_Cmd 函数 开启 ADC

基本函数介绍:

// 配置 ADCCLK 分频器
void RCC_ADCCLKConfig(uint32_t RCC_PCLK2);


void ADC_DeInit(ADC_TypeDef* ADCx);
void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct);
void ADC_StructInit(ADC_InitTypeDef* ADC_InitStruct);
// 用于 ADC 上电
void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState);
// 开启 DMA 输出信号的
void ADC_DMACmd(ADC_TypeDef* ADCx, FunctionalState NewState);
// 中断输出控制 控制某个中断 能不能通往 NVIC
void ADC_ITConfig(ADC_TypeDef* ADCx, uint16_t ADC_IT, FunctionalState NewState);
// 复位校准
void ADC_ResetCalibration(ADC_TypeDef* ADCx);
// 获取复位校准
FlagStatus ADC_GetResetCalibrationStatus(ADC_TypeDef* ADCx);
// 开始校准
void ADC_StartCalibration(ADC_TypeDef* ADCx);
// 获取校准状态
FlagStatus ADC_GetCalibrationStatus(ADC_TypeDef* ADCx);
// ADC_软件开始转换控制 用于软件触发的函数
void ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
// ADC_获取软件开始转换状态 就是给 SWSTART位置1,以开始转换的
FlagStatus ADC_GetSoftwareStartConvStatus(ADC_TypeDef* ADCx);
// 配置间断模式 每隔几个通道间隔一次
void ADC_DiscModeChannelCountConfig(ADC_TypeDef* ADCx, uint8_t Number);
// 是不是启动间断模式
void ADC_DiscModeCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
// 填写 菜单 配置 输入通道
void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);
// ADC_外部触发转换控制,就是是否允许外部触发转换
void ADC_ExternalTrigConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
// ADC_获取转化值 获取 AD 转换的数据寄存器 读取转换结果就要使用这个函数
uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);
// ADC_获取双模式转换值,这个是 双 ADC 模式读取转换结果的函数
uint32_t ADC_GetDualModeConversionValue(void);
// 配置 注入组 模式
void ADC_AutoInjectedConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
void ADC_InjectedDiscModeCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
void ADC_ExternalTrigInjectedConvConfig(ADC_TypeDef* ADCx, uint32_t ADC_ExternalTrigInjecConv);
void ADC_ExternalTrigInjectedConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
void ADC_SoftwareStartInjectedConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
FlagStatus ADC_GetSoftwareStartInjectedConvCmdStatus(ADC_TypeDef* ADCx);
void ADC_InjectedChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);
void ADC_InjectedSequencerLengthConfig(ADC_TypeDef* ADCx, uint8_t Length);
void ADC_SetInjectedOffset(ADC_TypeDef* ADCx, uint8_t ADC_InjectedChannel, uint16_t Offset);
uint16_t ADC_GetInjectedConversionValue(ADC_TypeDef* ADCx, uint8_t ADC_InjectedChannel);
// 对模拟看门狗进行配置的
// 是否启动看门狗
void ADC_AnalogWatchdogCmd(ADC_TypeDef* ADCx, uint32_t ADC_AnalogWatchdog);
// 配置高低阈值
void ADC_AnalogWatchdogThresholdsConfig(ADC_TypeDef* ADCx, uint16_t HighThreshold, uint16_t LowThreshold);
// 配置看门的通道
void ADC_AnalogWatchdogSingleChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel);
// 开启内部的两个通道
void ADC_TempSensorVrefintCmd(FunctionalState NewState);
// 获取标志位状态 参数给 EOC 的标志位,判断 EOC 标志位是不是置1了
FlagStatus ADC_GetFlagStatus(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);
// 清除标志位
void ADC_ClearFlag(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);
// 获取中断标志位
ITStatus ADC_GetITStatus(ADC_TypeDef* ADCx, uint16_t ADC_IT);
// 清除中断标志位
void ADC_ClearITPendingBit(ADC_TypeDef* ADCx, uint16_t ADC_IT);

案例:

void AD_Init(void)
{
     RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    // 在 AIN 模式下,断开GPIO,防止 GPIO 口的输入输出对模拟电压造成干扰
    // AIN 模式 就是 ADC 的专属模式
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    // Rank 规则组序列器里的次序
    // 在规则组菜单列表的第一个位置 写入通道1 这个通道
    // 每个通道可以设置不通的采样时间
    ADC_RegularChannelConfig(ADC1,ADC_Channel_1, 1, ADC_SampleTime_55Cycles5);
    //ADC_RegularChannelConfig(ADC1,ADC_Channel_2, 2, ADC_SampleTime_55Cycles5);

    ADC_InitTypeDef ADC_InitStructure;
    ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; // 转换模式
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; // 右对齐
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None ;// 外部触发源 软件触发
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; // 工作模式
    ADC_InitStructure.ADC_NbrOfChannel = 1;// 扫描模式用到几个通道
    ADC_InitStructure.ADC_ScanConvMode = DISABLE;// 扫描模式
    ADC_Init(ADC1, &ADC_InitStructure);
    ADC_Cmd(ADC1, ENABLE);

    ADC_ResetCalibration(ADC1); // 复位校准
    while(ADC_GetResetCalibrationStatus(ADC1) == SET); // 等待复位校准完成
    ADC_StartCalibration(ADC1);// 开始校准
    while(ADC_GetResetCalibrationStatus(ADC1) == SET); // 等待校准是否完成

}

uint16_t AD_GetValue(void)
{
    ADC_SoftwareStartInjectedConvCmd(ADC1, ENABLE);// ADC 转换
    while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET); // 规则组转换结束标志位 等待转换完成
    return ADC_GetConversionValue(ADC1); 
}

DMA

DMA简介

image-20231025090811157

存储器映像

image-20231025092020539

  • ROM是只读存储器,是一种非易失性、掉电不丢失的存储器
  • RAM是随机存储器,是一种易失性、掉电丢失的存储器

DMA框图

image-20231025093514308

DMA基本结构

image-20231025093819029

  • 起始地址,决定了数据是从哪里来,到哪里去
  • 数据宽度,指定一次转运要按照多大的数据宽度来进行,它可以选择字节Byte(8位)、半字HalfWord(16位)和字Word(32位)
  • 地址是否自增,指定一次转运完成后,下一次转运,是不是要把地址移动到下一个位置去
  • 传输计数器,用来指定我总共需要转运几次,自减计数器,比如5就是,转运5次 到0不再转运,之前的自增地址,也会恢复到起始地址的位置
  • 自动重装器,传输计数器减到0之后,是否要自动恢复到初始值,不用就是单次模式,用就是循环模式

注意:写传输计数器时,必须要先关闭 DMA,再进行。不能再 DMA 开启时,写传输计数器,这是手册里的规定

DMA请求

图片1

  • EN位 代表 开关控制 1 —> 启动 DMA 0 –> 关闭DMA
  • MEM2MEM ——> 1 时 为软件触发 0 是 为硬件触发

数据宽度与对齐

图片2

  • 如果把小的数据转到大的里面去,高位就会补0,如果把大的数据转到小的里面去,高位会被舍弃掉。数据宽度一样,就不会影响。

ADC扫描模式+DMA

image-20231027172638070

  • ADC的转运时机,需要和 ADC 单个通道转换完成同步,所以 DMA 的触发选择 ADC 的触发。

  • ADC 的扫描模式,在每个单独的通道转换完成之后,没有任何标志位,也不会触发中断。虽然单个通道转换完成之后,不产生任何标志位和中断,但是它应该会产生 DMA 请求,去触发 DMA 转运。单个通道的DMA肯定有有。

DMA基本库函数

// 恢复缺省配置
void DMA_DeInit(DMA_Channel_TypeDef* DMAy_Channelx);
// DMA 初始化
void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct);
// DMA 结构体初始化
void DMA_StructInit(DMA_InitTypeDef* DMA_InitStruct);
// 开启 DMA
void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState);
// 中断输出使能
void DMA_ITConfig(DMA_Channel_TypeDef* DMAy_Channelx, uint32_t DMA_IT, FunctionalState NewState);
// DMA_设置当前数据寄存器
void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber); 
// DMA_获取当前数据寄存器 返回传输计数器的值
uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);
// 获取标志位状态
FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);
// 清除标志位
void DMA_ClearFlag(uint32_t DMAy_FLAG);
// 获取中断状态
ITStatus DMA_GetITStatus(uint32_t DMAy_IT);
// 清除中断挂起位
void DMA_ClearITPendingBit(uint32_t DMAy_IT);

案例1:数据转运

如果选择的是硬件触发,不要忘了在对应的外设调用一下XXX_DMACmd,开启一下触发信号的输出。如果需要 DMA 的中断,那就调用 DMA_ITConfig,开启输出,再在NVIC里,配置相应的中断通道,然后写中断函数就行了。最后,在运行的过程中,如果转运完成。传输计数器清0了。 这时想再给传输计数器赋值的话,就 DMA 失能、写传输计数器、DMA使能,这样就行了。

串口接收多字节数据包

HEX数据包接收——状态机

image-20230712102110173

文本数据包接收

image-20230712102233627

定时器

定时器简介

  • Timer 定时器
  • 定时器可以对输入的时钟 进行计数,并在计算值达到设定值时触发中断
  • 16位 计数器 预分频器 自动重装寄存器的时基单元,在 72MHz计数时钟下可以实现最大 59.65s 的定时
  • 不仅具备基本的定时中断功能,而且还包含内外时钟源的选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能
  • 根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型

定时器类型

image-20230913110028844

  • 定时器的基本框架图

image-20230913110447674

自动重装值是固定不变的,当计数值等于自动重装值时,也就是计时时间到了

  • 主模式触发 DAC 功能 数模转换器 ( Digital to Analog Converter )
  • 能让内部的硬件在不受程序的控制下实现自动运行

通用定时器和高级定时器还支持向下技术模式和中央对齐模式

  • 向下计数模式就是从重装值开始,向下自减,减到0之后,回到重装值同时申请中断

RCC时钟树

图片1

定时中断的基本结构

image-20230713104335328

  • 计数器时序

image-20230920113200737

基本函数介绍

// 恢复 缺省配置
void TIM_DeInit(TIM_TypeDef* TIMx);
// 时基单元初始化 配置时基单元
void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);

// 结构体变量初始化
void TIM_TimeBaseStructInit(TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
// 使能控制器 运行控制
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);
void TIM_CtrlPWMOutputs(TIM_TypeDef* TIMx, FunctionalState NewState);
// 用来使能中断输出信号 中断输出控制
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);。
// 选择内部时钟
void TIM_InternalClockConfig(TIM_TypeDef* TIMx);
// 选择 ITRx 其它定时器的时钟
void TIM_ITRxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);
// 选择 TIx 捕获通道的时钟
void TIM_TIxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_TIxExternalCLKSource,
               uint16_t TIM_ICPolarity, uint16_t ICFilter); // 极性选择和滤波器
// 选择 ETR 通过外部时钟模式1输入的时钟
void TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,
                             uint16_t ExtTRGFilter);
// 选择 ETR 通过外部时钟模式2输入的时钟 
void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, 
             uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);
// 单独用来配置 ETR 引脚的预分频器 极性 滤波器这些参数
void TIM_ETRConfig(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler,     uint16_t TIM_ExtTRGPolarity,uint16_t ExtTRGFilter);
// 写预分频值
void TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler,         uint16_t TIM_PSCReloadMode);
// 改变计数器的计数模式
void TIM_CounterModeConfig(TIM_TypeDef* TIMx, uint16_t TIM_CounterMode);
void TIM_SelectInputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);
// 自动重装器预装功能配置
void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);
// 给计数器写入一个值
void TIM_SetCounter(TIM_TypeDef* TIMx, uint16_t Counter);
// 手动设置一个自动重装值
void TIM_SetAutoreload(TIM_TypeDef* TIMx, uint16_t Autoreload);

输出比较简介

image-20231011082431540

PWM比较

image-20231011083013749

image-20231011083127208

  • TS 是 高低电平变换的一个周期
  • 如果一个 高电平是 5V 低电平是 0 V 那么在 占空比 50% 情况下 大约的平均电压为 2.5V
  • 分辨率就是占空比变化的精细程度 比如 占空比 1.1% 1.2% 1.3%… 以分辨率为 1% 变化

输出比较通道(通用)

image-20231011083910724

输出比较模式

image-20231011083956363

  • 冻结:本质是 CNT、CCR 无效,使用情况 -> 如果正在输出 PWM 波,突然想暂停一会儿,就可以设置为冻结模式,输出暂停,高低电平保持不变
  • 匹配时电平翻转:可以输出高低电平是相等时间,占空比为 50% 的波形。输出波形的频率 = 更新频率 / 2
  • PWM模式:通过设置 CCR 和 重装值 设置占空比

PWM的输出结构

image-20231011085009815

参数计算

  • PWM 频率 = 计数器的更新频率
  • 上图中0 - 29 为高电平

舵机介绍

image-20231011092309354

配置函数补充

// 输出比较配置
void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC2Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC3Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC4Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);
// 用来给输出比较结构体赋一个默认值
void TIM_OCStructInit(TIM_OCInitTypeDef* TIM_OCInitStruct);
// 用来配置强制输出模式
void TIM_ForcedOC1Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction);
void TIM_ForcedOC2Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction);
void TIM_ForcedOC3Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction);
void TIM_ForcedOC4Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction);
// 用来配置 CCR 寄存器的预装功能
// 影子寄存器 写入的值不会立即生效 而是在更新时间才会生效
void TIM_OC1PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
void TIM_OC2PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
void TIM_OC3PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
void TIM_OC4PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
// 用来配置 快速使能的
void TIM_OC1FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast);
void TIM_OC2FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast);
void TIM_OC3FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast);
void TIM_OC4FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast);
// 外部事件 清除 REF 信号
void TIM_ClearOC1Ref(TIM_TypeDef* TIMx, uint16_t TIM_OCClear);
void TIM_ClearOC2Ref(TIM_TypeDef* TIMx, uint16_t TIM_OCClear);
void TIM_ClearOC3Ref(TIM_TypeDef* TIMx, uint16_t TIM_OCClear);
void TIM_ClearOC4Ref(TIM_TypeDef* TIMx, uint16_t TIM_OCClear);
// 单独设置输出比较的极性
void TIM_OC1PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
// 这里带 N 的就是高级定时器互补通道的配置
void TIM_OC1NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);
void TIM_OC2PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
void TIM_OC2NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);
void TIM_OC3PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
void TIM_OC3NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);
void TIM_OC4PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
// 单独修改输出使能参数的
void TIM_CCxCmd(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_CCx);
void TIM_CCxNCmd(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_CCxN);
// 选择输出比较模式
void TIM_SelectOCxM(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_OCMode);
// 用来单独更改 CCR 寄存器值的函数 重要
void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1);
void TIM_SetCompare2(TIM_TypeDef* TIMx, uint16_t Compare2);
void TIM_SetCompare3(TIM_TypeDef* TIMx, uint16_t Compare3);
void TIM_SetCompare4(TIM_TypeDef* TIMx, uint16_t Compare4);
// 这个函数仅高级定时器使用 在使用高级定时器输出 PWM 时,需要调用这个函数 使能主输出 否则 PWM 将不能正常输出
void TIM_CtrlPWMOutputs(TIM_TypeDef* TIMx, FunctionalState NewState);

补充:

// 引脚重映射
void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState);

// PA0 换到 PA15
GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2,ENABLE);
// GPIO_Remap_SWJ_JTAGDisable 解除引脚复用 
// 解除 PA15 的引脚复用功能 才能使用 GPIO
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);

如果重映射的引脚正好是调试端口 三句话都得加上

输入捕获

  • 简介

image-20231013201047041

  • 频率测量

image-20231013201915004

测频法适合测量高频信号,测周法适合测量低频信号

I2C通信

基本介绍

image-20231115091405252

硬件电路

image-20231115091419230

I2C时序基本单元

image-20231115091444331

image-20231115091503442

  • SCL低电平主机放数据,高电平从机读数据

image-20231115091646256

  • SCL低电平从机放数据,高电平主机读数据。

SPI通信

基本介绍

image-20231211203637153

  • SCK( SCLK,CLK,CK ):串行时钟线
  • MOSI( DI ):主机输出 从机输入
  • MISO( DO ):主机输入 从机输出
  • SS( NSS,CS ):从机选择

SPI 没有应答机制的设计,发送就发送接收就接收,不管你是否存在。

有几个从机 就开几条SS,所有人一人一条SS线,需要用到哪一根,就接到那一根的SS线,低电平代表我要找你了.

硬件电路

image-20231211205857573

  • 高阻态:高阻态这是一个数字电路里常见的术语,指的是电路的一种输出状态,既不是高电平也不是低电平。如果高阻态再输入下一级电路的话,对下级电路无任何影响,和没接一样,如果用万用表测的话有可能是高电平也有可能是低电平,随它后面接的东西定。

  • 高阻态的实质:电路分析时高阻态可做开路理解,你可以把它看作输出(输入)电阻非常大。

    它的极限可以认为悬空,也就是说理论上高阻态不是悬空,它是对地或对电源电阻极大的状态。而实际应用上与引脚的悬空几乎是一样的。

移位示意图

image-20231211205920312

SPI 高位先行 向左移位

SPI 时序基本单元

  • 起始条件:SS从高电平切换到低电平
  • 终止条件:SS从低电平切换到高电平

image-20231211212305394

模式

image-20231211212331445

image-20231211212340118

SPI 没有硬性规定 MOSI 是低电平还是高电平

上拉输入 MISO 默认输出高电平

浮空输入 MISO 默认输出低电平

image-20231211212350415

image-20231211212356815

SPI时序

  • 发送指令
  • 向SS指定的设备,发送指令(0x06)

1231

  • 指定地址写
  • 向SS指定的设备,发送写指令(0x02, 随后在指定地址(Address[23:0])下,写入指定数据(Data)

xx

  • 指定地址读
  • 向SS指定的设备,发送读指令(0x03),随后在指定地址(Address[23:0])下,读取从机数据(Data)

111

SPI外设

基本介绍

image-20231213080901516

SPI 框图

222

基本结构框图

image-20231213080929202

主模式全双工连续传输

333

非连续传输模式

112121

WDG看门狗

WDG简介

image-20231108084446056

WWDG 使用 APB1时钟,比较精确,慢了快了都不行。

IWDG

IWDG框图

图片1

IWDG键寄存器

image-20231108090713572

IWDG超时时间

image-20231108091121784

WWDG

WWDG框图

图片2

WWDG_CR 看门狗控制寄存器,T0~T5是自减计数器,T6为标志位,等于0时,表示计数器溢出,等于1时,表示计数器没有溢出。

WWDG_CFR 看门狗配置寄存器,喂狗时间的最早界限。计算最早界限的计数值写入 W6~W0 中。

写入WWDG_CR 时,比较器开始工作,一旦比较,我们当前的计数器T6:T0 > 窗口值W6:W0,比较结果为1,通过或门,去申请复位,

WWDG工作特性

image-20231108092611625

图片3

WWDG 超时时间

image-20231108093128456

图片4

IWDG和WWDG对比

image-20231108093542367

案例1-IWDG

基本库函数介绍:

// 写入使能控制
void IWDG_WriteAccessCmd(uint16_t IWDG_WriteAccess);
// 写预分频器
void IWDG_SetPrescaler(uint8_t IWDG_Prescaler);
// 写重装值
void IWDG_SetReload(uint16_t Reload);
// 重新装载寄存器
void IWDG_ReloadCounter(void);
// 启动看门狗
void IWDG_Enable(void);
// 获取标志位状态
FlagStatus IWDG_GetFlagStatus(uint16_t IWDG_FLAG);

案例2-WWDG

基本库函数:

// 恢复缺省配置
void WWDG_DeInit(void);
// 写入预分频器
void WWDG_SetPrescaler(uint32_t WWDG_Prescaler);
// 写入窗口值
void WWDG_SetWindowValue(uint8_t WindowValue);
// 写入中断
void WWDG_EnableIT(void);
// 写入计数器
void WWDG_SetCounter(uint8_t Counter);
// 使能看门狗 当看门狗被启用时,T6位必须被设置,以防止立即产生一个复位。
void WWDG_Enable(uint8_t Counter);
// 获取标志位
FlagStatus WWDG_GetFlagStatus(void);
// 清除标志位
void WWDG_ClearFlag(void);

BKP备份寄存器

简介

image-20231129082753734

如果系统不供电,VBAT不供电,BKP数据依然会清空。

三个功能,用一时间只能用一个。

BKP基本结构

image-20231129083552034

基本库函数

// 恢复 缺省配置 手动清空BKP所有的数据寄存器
void BKP_DeInit(void);
// 侵入检测 配置有效电平
void BKP_TamperPinLevelConfig(uint16_t BKP_TamperPinLevel);
// 是否开启缺省配置
void BKP_TamperPinCmd(FunctionalState NewState);
// 中断配置 是否开启中断
void BKP_ITConfig(FunctionalState NewState);
// 时钟输出功能配置
void BKP_RTCOutputConfig(uint16_t BKP_RTCOutputSource);
// 设置 RTC 时钟
void BKP_SetRTCCalibrationValue(uint8_t CalibrationValue);
// 写备份寄存器
void BKP_WriteBackupRegister(uint16_t BKP_DR, uint16_t Data);
// 读备份寄存器
uint16_t BKP_ReadBackupRegister(uint16_t BKP_DR);
FlagStatus BKP_GetFlagStatus(void);
void BKP_ClearFlag(void);
ITStatus BKP_GetITStatus(void);
void BKP_ClearITPendingBit(void);

// 设置 PWR_CR 寄存器里的 DBP位
void PWR_BackupAccessCmd(FunctionalState NewState);

案例:演示

OLED_Init();                        //OLED初始化
Key_Init();

// 打开 PWR BKP时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE);

// 使能
PWR_BackupAccessCmd(ENABLE);
// 写 BKP
// 1 - 10
BKP_WriteBackupRegister(BKP_DR1, 0x1234);
// 读 BKP
OLED_ShowNum(BKP_ReadBackupRegister(BKP_DR1), 4);

RTC实时时钟

简介

image-20231129083613816

RTC框图

图片2

  • RTC_ALR 是闹钟 当 RTC_ALR == RTC_CNT 触发中断 唤醒

  • RTC_Second 每秒触发一次中断

  • RTC_Overflow 每溢出一次发生中断

  • RTC_Alarm 每触发一次闹钟 发生一次

  • RTC_PRL 预装值

  • RTC_DIV 自减计数器 存储当前计数值

RTC_PRL 和 RTC_DIV 负责分频,RTCCLK 为 32.768kHz 时钟,RTC_PRL 自动重装载 32767 ,将时钟分频为 1Hz,就可以计秒。RTC_CNT 通过 UNix 时间戳 转化为 年月日时分秒。

RTC基本结构

image-20231129085652439

  • 配置重装计数器,选择分频系数
  • 配置32位计数器,进行时间日期的读写
  • 配置闹钟 -> ALR
  • 配置中断 先允许中断 再配置 NVIC

RTC操作注意事项

  • 执行以下操作将使能对BKP和RTC的访问:
  • ​ 设置RCC_APB1ENR的PWREN和BKPEN,使能PWR和BKP时钟
  • ​ 设置PWR_CR的DBP,使能对BKP和RTC的访问
  • 若在读取RTC寄存器时,RTC的APB1接口曾经处于禁止状态,则软件首先必须等待RTC_CRL寄存器中的RSF位(寄存器同步标志)被硬件置1
  • 必须设置RTC_CRL寄存器中的CNF位,使RTC进入配置模式后,才能写入RTC_PRL、RTC_CNT、RTC_ALR寄存器
  • 对RTC任何寄存器的写操作,都必须在前一次写操作结束后进行。可以通过查询RTC_CR寄存器中的RTOFF状态位,判断RTC寄存器是否处于更新中。仅当RTOFF状态位是1时,才可以写入RTC寄存器

RTC基本库函数

// 配置中断模式
void RTC_ITConfig(uint16_t RTC_IT, FunctionalState NewState);
// 进入配置模式
void RTC_EnterConfigMode(void);
// 退出配置模式 CNF 清0
void RTC_ExitConfigMode(void);
// 获取 CNT计数器的值
uint32_t  RTC_GetCounter(void);
// 设置时间
void RTC_SetCounter(uint32_t CounterValue);
// 写入预分频器的 PRL
void RTC_SetPrescaler(uint32_t PrescalerValue);
// 写入闹钟值
void RTC_SetAlarm(uint32_t AlarmValue);
// 读取预分频器中的 DIV 余数寄存器
uint32_t  RTC_GetDivider(void);
// 等待上一次操作完成 等待前一次写操作结束
void RTC_WaitForLastTask(void);
// 等待同步
void RTC_WaitForSynchro(void);

FlagStatus RTC_GetFlagStatus(uint16_t RTC_FLAG);
void RTC_ClearFlag(uint16_t RTC_FLAG);
ITStatus RTC_GetITStatus(uint16_t RTC_IT);
void RTC_ClearITPendingBit(uint16_t RTC_IT);

PWR电源控制

简介

image-20231221100636883

电源框图

xxx

  • VDDA 供电区域,主要负责模拟部分的供电,其中包括 A/D 转换器,温度传感器,复位模块,PLL锁相环。供电正极是 VDDA ,负极 VSSA
  • CPU 核心,存储器,内置数字外设 都属于 1.8V 供电区域
  • 待机电路( 唤醒逻辑,IWDG ) 都属于 VDD 供电电路
  • 电压调节器 给 1.8V 供电区域 供电
  • LSE ,后备寄存器,RCC_BDCR寄存器( RCC的寄存器,叫备份域控制寄存器 ),RTC 都属于后备供电区域

低功耗模式

123

  • WFI 任一中断 唤醒-> 所有外设的中断都行
  • WFE 唤醒事件 -> 通过外部的事件唤醒
  • 要想进入 停机模式 PDDS 事先 置 0
  • 要求:只有外部中断才能唤醒(PVD RTC闹钟 USB唤醒 ETH唤醒 也可以唤醒停止模式)
  • 要想进入 待机模式 PDDS 事先 置 1
  • LPDS 设置 电压调节器 -> 开启还是进入 低功耗模式
  • LPDS = 1 进入 低功耗
  • LPDS = 0 开启电源调节器
  • 待机模式:SLEEPDEEP 和 PDDS 置 1 表示即将进入待机模式,调用 WFI / WFE 就进入待机模式了

模式选择

image-20231225192823777