新建工程

image-20221013203145716

GPIO

截屏2022-10-13 21.36.46

GPIO都是挂在APB2总线上的

image-20221013213807716

寄存器32bit,只有低16bit控制驱动器

0BBC2D08F7C73A357EF552048757E05F

输入

默认3.3,有FT可以容忍5V

输入可以接受模拟输入,GPIO无效,直接通过内部ADC模块转换

上拉,下拉电阻:输入浮空的时候容易被影响,提供一个默认电平

截屏2022-10-13 21.52.27

输入数据传入输入数据寄存器

输出

  • 推挽输出:对GPIO的高低电平有绝对的控制能力
  • 开漏输出:高电平没有驱动能力,只驱动低电平,设置1时候相当于断开,交给外部控制。可以外部上拉电阻,设置1时用于输出5v。

八种模式

截屏2022-10-13 22.01.47

在输出模式下,输入是有效的

复用的输出模式,是由片上外设控制的,同时片上外设也可以读取输入。

输出速度

image-20221013221204199

限制输出引脚的翻转速度,为了低功耗和稳定性

LED点亮

系统时钟RCC库函数

主要使用如下三个函数控制三个重要的外设时钟的使能或失能

void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState);
void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);
void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);

GPIO库函数

// 初始化
void GPIO_DeInit(GPIO_TypeDef* GPIOx);
void GPIO_AFIODeInit(void);
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
void GPIO_StructInit(GPIO_InitTypeDef* GPIO_InitStruct);
// 读取
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);
uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);
// 写入
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);

GPIO输入

捕获按键输入

image-20221015135457389

按键存在抖动现象, 程序需要解决这个都抖动

image-20221015140053985

使用第一个方式需要上拉, 防止浮空

中断

image-20221016105308839

image-20221016110348255

image-20221016110353595

image-20221016110411071

image-20221016110419855

NVIC嵌套向量中断控制器

image-20221016105719398

NVIC优先级分组

  • 响应优先级 不打断上一个中断
  • 抢占优先级 打断上一个中断 可以进行中断嵌套

相应优先级高的排在前面, 抢占优先级高的可以嵌套, 二者都一样的按中断号顺序处理

EXTI: Extern Intrupt

检测GPIO的电平变化:

  • 上升沿

  • 下降沿

  • 双边沿

  • 软件触发

    同一个pin,比如PA0 PB0不可以同时触发

触发响应方式;

  • 中断响应
  • 事件响应 通往别的外设

EXTI结构

image-20221016111216560

外部中断9-15 10-15会触发同一个中断函数, 还得在程序里区分

AFIO相关配置

AFIO也是挂在APB2总线上的, 使用前也要开RCC时钟

// 复位AFIO
void GPIO_AFIODeInit(void);
// 配置EFIO的数据选择器
void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);

EXTI相关配置

// 初始化相关
void EXTI_DeInit(void);
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);
void EXTI_StructInit(EXTI_InitTypeDef* EXTI_InitStruct);
// 软件触发一个外部中断
void EXTI_GenerateSWInterrupt(uint32_t EXTI_Line);
// 中断标志位相关
// 外部程序使用 读写一般的标志位
FlagStatus EXTI_GetFlagStatus(uint32_t EXTI_Line);
void EXTI_ClearFlag(uint32_t EXTI_Line);
// 中断函数里使用 读写中断标志位
ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);
void EXTI_ClearITPendingBit(uint32_t EXTI_Line);

NVIC相关配置

内核外设

// 指定中断优先级分组 (抢占, 相应) 对整个芯片
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);
// 配置中断 
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);

TIM

定时器, 对输入的时钟计数, 并且定时触发中断, 本质上就个计数器

image-20221016152143366

基本定时器

时基单元(预分频器, 计数器, 自动重装寄存器)

image-20221016152735653

这三个部分都是16位

定时中断

默认系统时钟72Mhz

经过预分屏, 然后计数, 计到自动重载寄存器的值就触发一个中断

image-20221016154507073

image-20221016154928384

预分屏缓冲机制: 修改了预分屏, 要等下一轮计数才会生效

相关操作

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_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
// 时基单元的时钟选择
void TIM_InternalClockConfig(TIM_TypeDef* TIMx);
void TIM_ITRxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);
void TIM_TIxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_TIxExternalCLKSource,
                                uint16_t TIM_ICPolarity, uint16_t ICFilter);
void TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,
                             uint16_t ExtTRGFilter);
void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, 
                             uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);
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_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);
// 给计数器写入一个值
void TIM_SetCounter(TIM_TypeDef* TIMx, uint16_t Counter);
// 给自动重装器写一个值
void TIM_SetAutoreload(TIM_TypeDef* TIMx, uint16_t Autoreload);

// 获取当前计数器的值
uint16_t TIM_GetCounter(TIM_TypeDef* TIMx);
// 获取当前预分频值
uint16_t TIM_GetPrescaler(TIM_TypeDef* TIMx);

// 标志位相关
FlagStatus TIM_GetFlagStatus(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
void TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);
void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);

主模式触发DAC

通过硬件来直接触发DAC, 不经过软件代码

通用定时器

通用定时器支持

  • 向上计数 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
  • 向下计数 7 6 5 3 4 2 1 0 7 6 5 3 4 2 1 0
  • 中央对其 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 6 5 3 4 2 1 0 7 6 5 3 4 2 1 0

内外时钟源选择

可以选择外部时钟

输入捕获 IC Input Capture

输入出现电平跳变时, 将CNT的值得锁存到CCR中

可配置为PWI模式, 专门接受PWM的输入

可以使用主从触发模式, 全部由硬件实现测量

image-20221016214026651

image-20221016214205191

输出比较 OC Output Compare

image-20221016192359893

工作模式

image-20221016192608723

其中, 置有效电平就是高电平 无效电平就是低电平

image-20221016193135344

OC相关配置

// OC配置相关
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_OC1PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
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);

分别对应一个TIM的4个不同的OC单元

高级定时器

  • 重复次数计数器, 可以多个周期才触发一个中断
  • 死区控制和刹车 控制三相电机

RCC时钟树

image-20221016155350776

SystemInit()函数初始化

ADC

实际上就是个电压表, 能够测量电压值

模拟电压转换成数据, 存在一个寄存器里.

分辨率: 12位

转换时间: 1us 对应1MHz

逐次逼近型ADC

image-20221017170638337

会和一个DAC比较, 使用二分法逐次逼近. 实际上是判断每一位的位权, 从高到低, 每一位是0还是1

STM32 ADC

image-20221017171541867

通道:

  • 注入通道 有独立寄存器
  • 规则通道 共用寄存器 可以配合DMA使用

可以通过硬件自动触发

image-20221017172335187

GPIO引脚对应

image-20221017172647325

转换模式

  • 单次转换 只在转换一次

  • 连续转换 开始以后就一直转换, 想要读取时候直接去寄存器要

  • 扫描模式 可以连续转换一系列的通道, 就需要DMA及时来取

  • 非扫描模式 只读取一次, 不扫描通道

  • 间断模式 扫描固定个数后停掉

触发控制

image-20221017173704540

数据对齐

image-20221017175858694

ADC相关配置

// RCC的时钟分频器
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);

// 中断控制
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);

// 软件触发转换
void ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);

// 间断模式
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);

// 获取转换值
uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);

// 模拟看门狗
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);

// 标志位相关
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);

DMA

分为软件触发和硬件触发两种

STM32存储器

image-20221017203010859

DMA框图

image-20221017203722025

DMA结构图

image-20221017204314597

FLASH是只读的, DMA无法写入东西

数据宽度:

  • 半字(字节)
  • 双字

地址自增:

  • 下一次转运下一个地址

传输计数器:

  • 记录传输次数, 减到0会恢复地址
  • 自动重装器: 可以减到0以后, 重装到5, 循环模式, 比如配合ADC循环模式使用

M2M:

  • 决定触发方式
  • 软件触发: 触发到DMA完成为止

写传输计数器时, 必须关闭DMA, 来自手册的规定

大小端截取

image-20221018102544476

小转大, 高位补0

大转小, 高位截取

DMA相关配置

// 老朋友
void DMA_DeInit(DMA_Channel_TypeDef* DMAy_Channelx);
void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct);
void DMA_StructInit(DMA_InitTypeDef* DMA_InitStruct);
void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState);
void DMA_ITConfig(DMA_Channel_TypeDef* DMAy_Channelx, uint32_t DMA_IT, FunctionalState NewState);
void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber); 

// 传输计数器相关
void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber); 
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);

USART 串口通信

image-20221019085227643

串口通信

USART有两个引脚, 分别是发送引脚, 接受引脚, 要交叉连接

由于并未传时钟, 所以规定波特率, 约定一定的时间采样

image-20221019085537637

TX, RX传的数据是以GND为基准的, 因此传输距离不会很大.

TTL电平标准: 单片机常用, 3.3或5v为高电平, 0v为低电平

image-20221019085941372

串口协议默认高电平, 开始时传以为2低电平开始, 停止时要传若干位高电平分隔

校验位奇偶校验

传输的数据低位先行, 1011传输时候是1101

STM32 USART外设

USART外设, 本质上就是能转换字节和数据帧时序

自带波特率发生器(实际上就是对时钟分屏)

可配置数据位8/9

可配置停止位长度 0.5/1/1.5/2

可选校验位

image-20221019115134911

USART结构图

nRTS, nCTS用于流量控制

image-20221019120222207

输入的一些问题

输入时, 最好能正好在波形的正中间取, 而且最好得处理噪声

STM32采样起始位时, 一位采样16次.

采样3,5,7 8,9,10 要求至少有2个0, 并且有寄存器表示检测到噪声

由于起始位已经对其, 数据位就8, 9, 10采样. 按多数来, 并且也标记噪声.

IIC通信

半双工 通过设备地址选择

SCL是时钟

SDA默认高电平

开始之前会拉低, 结束时候会相应

由于多端通信, 因此会先传地址

image-20221019172025070

HAL_StatusTypeDef HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout);
static HAL_StatusTypeDef I2C_RequestMemoryWrite(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint32_t Timeout)

SPI通讯 串行外设接口

全双工 通过CS片选image-20221019174909662

image-20221019175133322

image-20221019175212515

image-20221019175326695

image-20221019175355040

其实就是指定上升沿, 下降沿

image-20221019175518464

看门狗

本质上就是个定时器, 到时间没重置的话, 就会叫触发事件

独立看门狗

12位的向下计数计数器, 如果计到0, 就会触发复位信号

喂狗: 定期设置计数器

窗口看门狗

7位的向下计数计数

窗口有上限t 有下限64, 只有在这个区间喂狗才可以. 过快或过慢都不行

高于窗口, 不允许喂狗

低于窗口, 自动复位, 并且会触发一个中断, 有一段时间, 用于在复位前保存数据

Q.E.D.