【STM32】江协科技STM32入门教程学习笔记汇总

目录

1-1 课程简介

1-2 STM32简介

01.简介

02.ARM

03.STM32F103C8T6

04.片上资源/外设

05.命名规则

06.系统结构

07.引脚定义

08.启动配置

09.最小系统电路

10.软件安装

11.型号分类及缩写

12.新建工程步骤

13.工程架构

2-1 软件安装

2-2 新建工程

01. 创建STM32工程

01. 固件库概述

02. STM32工程编译和下载

03. LED测试

04. 型号分类及缩写

05. 工程结构

3-1 GPIO输出

01. GPIO简介

02. GPIO基本结构

03. GPIO位结构

04. GPIO模式

4.1 输入浮空

4.2 输入上拉

4.3 输入下拉

4.4 模拟输入

4.6 开漏复用功能

4.8 推挽式复用功能

05. LED和蜂鸣器简介

06. 面包板

3-2 GPIO相关API概述

01.GPIO概述

02.GPIO_Exported_Types

03.GPIOSpeed_TypeDef

04.GPIOMode_TypeDef

05. GPIO_InitTypeDef

06. BitAction

07. GPIO_pins_define

08. GPIO_Pin_sources

09. GPIO_Port_Sources

10. GPIO相关函数汇总

11. GPIO_DeInit

12. GPIO_AFIODeInit

13. GPIO_Init

14. GPIO_StructInit

15. GPIO_ReadInputDataBit

16. GPIO_ReadInputData

17. GPIO_ReadOutputDataBit

18. GPIO_ReadOutputData

19. GPIO_SetBits

20. GPIO_ResetBits

21. GPIO_WriteBit

22. GPIO_Write

23. RCC_APB2PeriphClockCmd

3-3 LED闪烁&LED流水灯&蜂鸣器

3-4 GPIO输入

01.按键简介

02.传感器模块简介

03.光敏电阻传感器

04.按键电路图

05.C语言数据类型

06.C语言宏定义

07.C语言typedef

08.C语言结构体

09.C语言枚举

3-5 按键控制LED&光敏传感器&控制蜂鸣器

4-1 OLED调试工具

01.STM32调试方式

02.OLED简介

03.0.96寸OLED模块

SPI模块:

IIC接口模块:

04.0.96寸OLED驱动IC

05.0.96寸OLED原理图

SPI版本

IIC版本

06.硬件电路

07.OLED驱动函数

4-2 OLED显示屏

5-1 EXTI外部中断

01.中断系统

02.中断执行流程

03.STM32中断

04.NVIC基本结构

05.NVIC优先级分组

06.EXTI简介

07.EXTI基本结构

08.AFIO复用IO口

09.EXTI框图

10.计数器模块

11.旋转编码器简介

5-2 对射式红外传感器计次&旋转编码器计次

8-1 DMA直接存储器存取

01.DMA简介

9-1 USART串口协议

01.串行通讯与并行通讯

02.全双工、半双工及单工通讯

03.同步通讯与异步通讯

04.通讯速率

05.通信接口

06.串口通信

07.硬件电路

08.电平标准

09.串口参数及时序

10.串口时序

9-2 USART串口外设

01.串口简介

02.串口协议

03.USART简介

04.USART框图

USART框图核心模块解析

05.USART基本结构

06.数据帧

07.起始位侦测

08.数据采样

09.波特率发生器

10.USB转串口模块的内部电路图

9-3 串口发送&串口发送+接收

01.串口简介

02.串口相关API

2.1 USART_Init

2.2 USART_InitTypeDef

2.3 USART_Cmd

2.4 USART_SendData

2.5 USART_ReceiveData

03. 串口发送接线图

04. USB转串口模块

05. 串口发送程序示例

06. 串口发送支持printf

07. 串口发送支持printf_v2

08. 串口发送和接收接线图

09. 串口接收示例(轮询模式)

10. 串口接收示例(中断模式)

9-4 USART串口数据包

01.串口简介

02.HEX数据包

03.文本数据包

04.HEX数据包接收

05.文本数据包接收

9-5 串口收发HEX数据包&串口收发文本数据包

9-6 FlyMCU串口下载和STLINK Utility

01.串口连接电路图

STM32串口下载程序与Bootloader配置教程

一、工程配置与H1X文件生成

二、FlyMcu串口下载操作流程

三、Bootloader原理与串口下载机制

四、优化下载流程方案

五、选项字节配置与保护功能

六、ST-Link Utility使用指南

七、常见问题解决方案

八、开发建议

02.FlyMCU软件下载程序

2.1 生成hex文件

2.2 STM32进入下载程序模式

2.3 打开hex文件,点击开始编程

03.串口下载原理

04.FlyMCU软件其它操作

4.1 读Flash文件

4.2 清除芯片

4.3 选项字节

05.STLINK Utility软件

5.1 连接到STM32

5.2 选项字节配置

10-1 I2C通信协议

01.I2C简介

02.I2C主要特点

03.I2C硬件电路

04.I2C时序基本单元

05.I2C时序波形图

一、时序操作

指定地址写

当前地址读

指定地址读

二、设备地址机制

1. 地址分配原则

2. 地址配置示例

三、基本操作模式

1. 指定地址写(Write to Register)

2. 当前地址读(Current Address Read)

3. 指定地址读(Read from Register)

四、高级操作模式

1. 连续写入

2. 连续读取

五、关键时序解析

1. 起始/停止条件

2. 数据有效性

3. 应答机制

六、典型应用场景

七、调试建议

10-2 MPU6050简介

01.MPU6050简介

11-1 SPI通信协议

01. SPI简介

02. SPI特征

03. SPI通信

04. 硬件电路

05. 移位示意图

06. SPI时序基本单元

07. SPI时序

11-2 W25Q64简介

01.SPI简介

02.W25Q64简介

03.硬件电路

04.W25Q64框图

05.Flash操作注意事项

06.W25Q64BV手册查看

11-3 软件SPI读写W25Q64

11-4 SPI通信外设

01.SPI外设简介

04.SPI框图

05.SPI基本结构

06.主模式全双工连续传输

07.非连续传输

08.软件/硬件波形对比

1-1 课程简介

套件购买:[UP主官方店铺] STM32入门套件 配套B站江协科技STM32视频-淘宝网

资料下载:资料下载

教程相关问题及解答:疑难解答

官方网站:https://jiangxiekeji.com

  • 程序纯手打,手把手教学

  • STM32最小系统板+面包板硬件平台

  • STM32面包板入门套件

  • Windows电脑

  • 万用表、示波器、镊子、剪刀等

软件设备

1-2 STM32简介

01.简介

  • STM32是ST公司基于ARM Cortex-M内核开发的32位微控制器

  • STM32常应用在嵌入式领域,如智能车、无人机、机器人、无线通信、物联网、工业控制、娱乐电子产品等

  • STM32功能强大、性能优异、片上资源丰富、功耗低,是一款经典的嵌入式微控制器

02.ARM

  • ARM既指ARM公司,也指ARM处理器内核

  • ARM公司是全球领先的半导体知识产权(IP)提供商,全世界超过95%的智能手机和平板电脑都采用ARM架构

  • ARM公司设计ARM内核,半导体厂商完善内核周边电路并生产芯片

03.STM32F103C8T6

  • 系列:主流系列STM32F1

  • 内核:ARM Cortex-M3

  • 主频:72MHz

  • RAM:20K(SRAM)

  • ROM:64K(Flash)

  • 供电:2.0~3.6V(标准3.3V)

  • 封装:LQFP48

04.片上资源/外设

05.命名规则

06.系统结构

07.引脚定义

 

08.启动配置

09.最小系统电路

10.软件安装

  • 安装Keil5

  • MDK安装器件支持包

  • 软件注册

  • 安装STLINK驱动

  • 安装USB转串口驱动

11.型号分类及缩写

12.新建工程步骤

  • 建立工程文件夹,Keil中新建工程,选择型号

  • 工程文件夹里建立Start、Library、User等文件夹,复制固件库里面的文件到工程文件夹

  • 工程里对应建立Start、Library、User等同名称的分组,然后将文件夹内的文件添加到工程分组里

  • 工程选项,C/C++,Include Paths内声明所有包含头文件的文件夹

  • 工程选项,C/C++,Define内定义USE_STDPERIPH_DRIVER

  • 工程选项,Debug,下拉列表选择对应调试器,Settings,Flash Download里勾选Reset and Run

13.工程架构

STM32F103系列虽然基于相同的Cortex-M3内核,但不同型号的引脚数量差异(如26~80个GPIO)主要通过以下技术实现:

1. 引脚复用(Alternate Function)

  • 原理: 每个物理引脚可配置为 多种功能(默认GPIO、外设接口、模拟输入等),通过寄存器切换。

  • 示例:

    • PA9引脚:默认是GPIO,也可复用为 USART1_TXTIM1_CH2

    • PB6引脚:可配置为 I2C1_SCLTIM4_CH1

  • 优势: 在有限引脚下支持更多外设(如SPI、CAN、USB),无需增加物理引脚。

2. 不同封装形式

STM32F103系列提供多种封装,封装越大,引脚数量越多

封装类型总引脚数GPIO数量适用场景
VFQFP363626小型设备(如遥控器)
LQFP484837消费电子(如智能插座)
LQFP10010080复杂系统(如工控主板)
  • 设计逻辑:

    更大封装通过扩展芯片边缘的物理焊盘,将内核支持的更多外设信号引出。例如:

    • LQFP100封装:额外引脚用于 CAN总线、USB OTG、更多ADC通道

    • 小封装型号:仅保留基础外设引脚(如USART、SPI)。

3. 总线扩展(外部芯片辅助)

若物理引脚仍不足,可通过总线扩展更多功能:

  • I²C/SPI扩展芯片: 例如,使用 MCP23017(I²C GPIO扩展器),仅需2根I²C引脚即可扩展16个GPIO。

  • 并行总线扩展: 通过FSMC(Flexible Static Memory Controller)接口连接外部RAM/FPGA,扩展数据总线(D0-D15)和地址总线(A0-A23)。

实际场景对比

  • 简单控制(LQFP48): 驱动LED+按键+1个串口,复用PA9/PA10为USART即可。

  • 复杂系统(LQFP100): 同时运行 USB+CAN+3个SPI+5个USART,需更多独立引脚减少复用冲突。

选型建议

  • 引脚需求 ≤ 40:选VFQFP36/LQFP48,成本低、体积小。

  • 引脚需求 > 50:选LQFP100,避免频繁复用导致功能冲突。

  • 扩展需求:若需驱动大量外设(如显示屏),优先选大封装型号。

同一Cortex-M3内核的STM32F103系列,通过 引脚复用 + 不同封装物理扩展 实现引脚数量差异化。设计时需根据 外设数量、复用频率、成本 综合权衡选型。

img

引脚定义

类型:S代表电源,I代表输入,O代表输出,IO代表输入输出

有FT表示只能容忍5V的电压,没有FT表示只能容忍3.3v的电压,如果没有的需要接5V的电平,就需要加装电平转换电路了

最小系统板

  • 这个中间的黑色小芯片,就是STMB2F103C8T6

  • 左边这两个跳线帽,是用来配置BOOT引脚的

  • 下面是复位按键

  • 再左边是这个USB接口,它可以进行USB通信,也可以为板子供电

  • 右边这个金属外壳的是8MHz的主时钟晶振

  • 这个黑色的是32.768KHz的RTC晶振

  • 然后再右边是两个LED,上面这个是PWR电源指示灯

  • 下面这个是接在PC13口的测试灯

  • 最右边是SWD的调试接口,用来下载程序的

  • 上下两排是用于接线的排针

  • 这个5个脚的小芯片,就是3.3V稳压芯片

  • 剩下的这些就是电容电阻这些小元件了

2-1 软件安装

软件安装

  1. 安装Keil5 MDK

  2. 安装器件支持包

  3. 软件注册

  4. 安装STLINK驱动

  5. 安装USB转串口驱动

2-2 新建工程

目前STM32的开发方式主要有基于寄存器的方式、基于标准库也就是库函数的方式和基于NL库的方式

01. 创建STM32工程

【STM32】STM32F103C8T6 创建工程模版详解(固件库)

01. 固件库概述

ST(意法半导体)为了方便用户开发程序,提供了一套丰富的 STM32标准外设函数库库,简称固件库

固件库版本

STM32F10x_StdPeriph_Lib_V3.5.0

02. STM32工程编译和下载

2.1 选择下载器位ST-Link Debugger 在这里插入图片描述

2.2 勾选上电自动复位 在这里插入图片描述

2.3 取消一下勾选

在这里插入图片描述

2.4 编译和下载

在这里插入图片描述

03. LED测试

main.c

 #include "stm32f10x.h"
 ​
 ​
 ​
 void led_init(void)
 {
     GPIO_InitTypeDef GPIO_InitStruct;
     GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13;
     GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz;
     GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
     
     //1. 使能时钟
     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
     
     //2. 初始化GPIO
     GPIO_Init(GPIOC, &GPIO_InitStruct);
     
     //3. 设置灯灭
     GPIO_SetBits(GPIOC, GPIO_Pin_13);
     
 }
 ​
 //低电平
 void led_on(void)
 {
     GPIO_ResetBits(GPIOC, GPIO_Pin_13);
 }
 ​
 ​
 //高电平
 void led_off(void)
 {
     GPIO_SetBits(GPIOC, GPIO_Pin_13);
 ​
 }
 ​
 ​
 void mydelay(int msec)
 {
     int i = 0;
     int j = 0;
     
     for (i = 0; i < msec; i++)
     {
         for (j = 0; j < 1000; j++)
         {
             /*do nothing*/;
         }
     }
 }
 ​
  int main(void)
  {  
      //初始化
      led_init();
      
      
      while(1)
      {
          led_off();
          mydelay(1500);
          
          led_on();
          mydelay(1500);
      }
       
      return 0;
  }

下载之后,LED一闪一闪表示OK。

04. 型号分类及缩写

在这里插入图片描述

05. 工程结构

在这里插入图片描述

3-1 GPIO输出

  • GPIO(General Purpose Input Output)通用输入输出口

  • 可配置为8种输入输出模式

  • 引脚电平:0V~3.3V,部分引脚可容忍5V

  • 输出模式下可控制端口输出高低电平,用以驱动LED、控制蜂鸣器模拟通信协议输出时序等

  • 输入模式下可读取端口的高低电平或电压,用于读取按键输入、外接模块电平信号输入、ADC电压采集、模拟通信协议接收数据等

image-20250323162101015

每个GPIO外设,总共有16的引脚,编号是从0到15,那GPIOA的第0号引脚,我们一般把它称作PA0,接着第1号就是PA1,以此类推

在每个GPIO模块内,主要包含了寄存器和驱动器这些东西,寄存器就是一段特殊的存储器,内核可以通过APB2总线对奇存器进行读写,这样就可以完成输出电平和读取电平的功能了,输入寄存器读取为1,就证明对应的端口目前是高电平,读取为0,就是低电平,因为STM32是32位的单片机,所以STM32内部的寄存器都是32位的,但这个端口只有16位,所以这个寄存器只有低16位对应的有端口,高16位是没有用到的。

这个驱动器是用来增加信号的驱动能力的

01. GPIO简介

  • GPIO(General Purpose Input Output)通用输入输出口

  • 可配置为8种输入输出模式

  • 引脚电平:0V~3.3V,部分引脚可容忍5V

  • 输出模式下可控制端口输出高低电平,用以驱动LED、控制蜂鸣器、模拟通信协议输出时序等

  • 输入模式下可读取端口的高低电平或电压,用于读取按键输入、外接模块电平信号输入、ADC电压采集、模拟通信协议接收数据等

每个GPI/O端口有两个32位配置寄存器(GPIOx_CRL,GPIOx_CRH),两个32位数据寄存器(GPIOx_IDR和GPIOx_ODR),一个32位置位/复位寄存器(GPIOx_BSRR),一个16位复位寄存器(GPIOx_BRR)和一个32位锁定寄存器(GPIOx_LCKR)。 根据数据手册中列出的每个I/O端口的特定硬件特征, GPIO端口的每个位可以由软件分别配置成多种模式。

  • 输入浮空

  • 输入上拉

  • 输入下拉

  • 模拟输入

  • 开漏输出

  • 推挽式输出

  • 推挽式复用功能

  • 开漏复用功能

每个I/O端口位可以自由编程,然而I/0端口寄存器必须按32位字被访问(不允许半字或字节访问)。GPIOx_BSRR和GPIOx_BRR寄存器允许对任何GPIO寄存器的读/更改的独立访问;这样,在读和更改访问之间产生IRQ时不会发生危险。

02. GPIO基本结构

系统结构

在这里插入图片描述

基本结构

image-20250323162134640

03. GPIO位结构

I/O端口位的基本结构

image-20250323162146821

5伏兼容I/O端口位的基本结构

在这里插入图片描述

04. GPIO模式

通过配置GPIO的端口配置寄存器,端口可以配置成以下8种模式

image-20250323162227527

4种输入模式:

  • 输入浮空

  • 输入上拉

  • 输入下拉

  • 模拟输入

4种输出模式

  • 开漏输出

  • 开漏复用功能

  • 推挽式输出

  • 推挽式复用功能

3种最大翻转速度:

  • 最大输出速度为2MHz

  • 最大输出速度为10MHz

  • 最大输出速度为50MHz

4.1 输入浮空

浮空输入模式下,I/O端口的电平信号直接进入输入数据寄存器。也就是说,I/O的电平状态是不确定的,完全由外部输入决定;如果在该引脚悬空(在无信号输入)的情况下,读取该端口的电平是不确定的。所以在要读取外部信号时通常配置IO口为浮空输入模式。 在这里插入图片描述

4.2 输入上拉

上拉输入模式下,I/O端口的电平信号直接进入输入数据寄存器。但是在I/O端口悬空(在无信号输入)的情况下,输入端的电平可以保持在高电平;并且在I/O端口输入为低电平的时候,输入端的电平为低电平。

在这里插入图片描述

4.3 输入下拉

下拉输入模式下,IO口工作方式刚好和上拉模式相反。I/O端口的电平信号直接进入输入数据寄存器。但是在I/O端口悬空(在无信号输入)的情况下,输入端的电平可以保持在低电平;并且在I/O端口输入为高电平的时候,输入端为高电平。 在这里插入图片描述

4.4 模拟输入

模拟输入模式下,I/O端口的模拟信号(电压信号,而非电平信号)直接模拟输入到片上外设模块,比如ADC模块等等。

在这里插入图片描述

4.5 开漏输出 开漏输出模式下,通过设置位设置/清除寄存器或者输出数据寄存器的值,控制MOS管的导通。这里要注意N-MOS管,当设置输出的值为高电平的时候,N-MOS管处于关闭状态,此时I/O端口的电平就不会由输出的高低电平决定,而是由I/O端口外部的上拉或者下拉决定;当设置输出的值为低电平的时候,N-MOS管处于开启状态,此时I/O端口的电平就是低电平。同时,I/O端口的电平也可以通过输入电路进行读取;注意,I/O端口的电平不一定是输出的电平。通常使用开漏输出时外部要加一个上拉电阻。

在这里插入图片描述

4.6 开漏复用功能

开漏复用输出模式,与开漏输出模式很是类似。只是输出的高低电平的来源,不是让CPU直接写输出数据寄存器,取而代之利用片上外设模块的复用功能输出来决定的。

在这里插入图片描述

4.7 推挽式输出 推挽输出模式下,通过设置位设置/清除寄存器或者输出数据寄存器的值,控制P-MOS管和N-MOS管的导通来控制IO口输出高电平还是低电平。这里要注意P-MOS管和N-MOS管,当设置输出的值为1的时候,P-MOS管处于开启状态,N-MOS管处于关闭状态,此时I/O端口的电平就由P-MOS管决定为高电平;当设置输出的值为0的时候,P-MOS管处于关闭状态,N-MOS管处于开启状态,此时I/O端口的电平就由N-MOS管决定为低电平。同时,I/O端口的电平也可以通过输入电路进行读取;注意,此时I/O端口的电平一定是输出的电平。

在这里插入图片描述

4.8 推挽式复用功能

推挽复用输出模式,与推挽输出模式很是类似。只是输出的高低电平的来源,不是让CPU直接写输出数据寄存器,取而代之利用片上外设模块的复用功能输出来决定的。

在这里插入图片描述

05. LED和蜂鸣器简介

  • LED:发光二极管,正向通电点亮,反向通电不亮

  • 有源蜂鸣器:内部自带振荡源,将正负极接上直流电压即可持续发声,频率固定

  • 无源蜂鸣器:内部不带振荡源,需要控制器提供振荡脉冲才可发声,调整提供振荡脉冲的频率,可发出不同频率的声音 在这里插入图片描述

硬件电路 在这里插入图片描述

06. 面包板

在这里插入图片描述

面包板描述

在这里插入图片描述

面包板使用示例

在这里插入图片描述

在这里插入图片描述

image-20250314193450583

3-2 GPIO相关API概述

01.GPIO概述

文件:stm32f10x_gpio.h和stm32f10x_gpio.c

02.GPIO_Exported_Types

/** @defgroup GPIO_Exported_Types
  * @{
  */

#define IS_GPIO_ALL_PERIPH(PERIPH) (((PERIPH) == GPIOA) || \
                                    ((PERIPH) == GPIOB) || \
                                    ((PERIPH) == GPIOC) || \
                                    ((PERIPH) == GPIOD) || \
                                    ((PERIPH) == GPIOE) || \
                                    ((PERIPH) == GPIOF) || \
                                    ((PERIPH) == GPIOG))

03.GPIOSpeed_TypeDef

/** 
  * @brief  Output Maximum frequency selection  
  */

typedef enum
{ 
  GPIO_Speed_10MHz = 1,
  GPIO_Speed_2MHz, 
  GPIO_Speed_50MHz
}GPIOSpeed_TypeDef;

04.GPIOMode_TypeDef

/** 
  * @brief  Configuration Mode enumeration  
  */

typedef enum
{ GPIO_Mode_AIN = 0x0,			//模拟输入
  GPIO_Mode_IN_FLOATING = 0x04,	//浮空输入
  GPIO_Mode_IPD = 0x28,			//下拉输入
  GPIO_Mode_IPU = 0x48,			//上拉输入
  GPIO_Mode_Out_OD = 0x14,		//开漏输出
  GPIO_Mode_Out_PP = 0x10,		//推挽输出
  GPIO_Mode_AF_OD = 0x1C,		//复用功能开漏输出
  GPIO_Mode_AF_PP = 0x18		//复用功能推挽输出
}GPIOMode_TypeDef;

05. GPIO_InitTypeDef

/** 
  * @brief  GPIO Init structure definition  
  */

typedef struct
{
  uint16_t GPIO_Pin;             /*!< Specifies the GPIO pins to be configured.
                                      This parameter can be any value of @ref GPIO_pins_define */

  GPIOSpeed_TypeDef GPIO_Speed;  /*!< Specifies the speed for the selected pins.
                                      This parameter can be a value of @ref GPIOSpeed_TypeDef */

  GPIOMode_TypeDef GPIO_Mode;    /*!< Specifies the operating mode for the selected pins.
                                      This parameter can be a value of @ref GPIOMode_TypeDef */
}GPIO_InitTypeDef;

06. BitAction

/** 
  * @brief  Bit_SET and Bit_RESET enumeration  
  */

typedef enum
{ Bit_RESET = 0,
  Bit_SET
}BitAction;

07. GPIO_pins_define

/** @defgroup GPIO_pins_define 
  * @{
  */

#define GPIO_Pin_0                 ((uint16_t)0x0001)  /*!< Pin 0 selected */
#define GPIO_Pin_1                 ((uint16_t)0x0002)  /*!< Pin 1 selected */
#define GPIO_Pin_2                 ((uint16_t)0x0004)  /*!< Pin 2 selected */
#define GPIO_Pin_3                 ((uint16_t)0x0008)  /*!< Pin 3 selected */
#define GPIO_Pin_4                 ((uint16_t)0x0010)  /*!< Pin 4 selected */
#define GPIO_Pin_5                 ((uint16_t)0x0020)  /*!< Pin 5 selected */
#define GPIO_Pin_6                 ((uint16_t)0x0040)  /*!< Pin 6 selected */
#define GPIO_Pin_7                 ((uint16_t)0x0080)  /*!< Pin 7 selected */
#define GPIO_Pin_8                 ((uint16_t)0x0100)  /*!< Pin 8 selected */
#define GPIO_Pin_9                 ((uint16_t)0x0200)  /*!< Pin 9 selected */
#define GPIO_Pin_10                ((uint16_t)0x0400)  /*!< Pin 10 selected */
#define GPIO_Pin_11                ((uint16_t)0x0800)  /*!< Pin 11 selected */
#define GPIO_Pin_12                ((uint16_t)0x1000)  /*!< Pin 12 selected */
#define GPIO_Pin_13                ((uint16_t)0x2000)  /*!< Pin 13 selected */
#define GPIO_Pin_14                ((uint16_t)0x4000)  /*!< Pin 14 selected */
#define GPIO_Pin_15                ((uint16_t)0x8000)  /*!< Pin 15 selected */
#define GPIO_Pin_All               ((uint16_t)0xFFFF)  /*!< All pins selected */

08. GPIO_Pin_sources

 /** @defgroup GPIO_Pin_sources 
   * @{
   */
 ​
 #define GPIO_PinSource0            ((uint8_t)0x00)
 #define GPIO_PinSource1            ((uint8_t)0x01)
 #define GPIO_PinSource2            ((uint8_t)0x02)
 #define GPIO_PinSource3            ((uint8_t)0x03)
 #define GPIO_PinSource4            ((uint8_t)0x04)
 #define GPIO_PinSource5            ((uint8_t)0x05)
 #define GPIO_PinSource6            ((uint8_t)0x06)
 #define GPIO_PinSource7            ((uint8_t)0x07)
 #define GPIO_PinSource8            ((uint8_t)0x08)
 #define GPIO_PinSource9            ((uint8_t)0x09)
 #define GPIO_PinSource10           ((uint8_t)0x0A)
 #define GPIO_PinSource11           ((uint8_t)0x0B)
 #define GPIO_PinSource12           ((uint8_t)0x0C)
 #define GPIO_PinSource13           ((uint8_t)0x0D)
 #define GPIO_PinSource14           ((uint8_t)0x0E)
 #define GPIO_PinSource15           ((uint8_t)0x0F)

09. GPIO_Port_Sources

 /** @defgroup GPIO_Port_Sources 
   * @{
   */
 ​
 #define GPIO_PortSourceGPIOA       ((uint8_t)0x00)
 #define GPIO_PortSourceGPIOB       ((uint8_t)0x01)
 #define GPIO_PortSourceGPIOC       ((uint8_t)0x02)
 #define GPIO_PortSourceGPIOD       ((uint8_t)0x03)
 #define GPIO_PortSourceGPIOE       ((uint8_t)0x04)
 #define GPIO_PortSourceGPIOF       ((uint8_t)0x05)
 #define GPIO_PortSourceGPIOG       ((uint8_t)0x06)
 ​

10. 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);
 void GPIO_PinLockConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
 void GPIO_EventOutputConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);
 void GPIO_EventOutputCmd(FunctionalState NewState);
 void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState);
 void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);
 void GPIO_ETH_MediaInterfaceConfig(uint32_t GPIO_ETH_MediaInterface);
 ​

11. GPIO_DeInit

void GPIO_DeInit(GPIO_TypeDef* GPIOx);
功能:
	将外设 GPIOx 寄存器重设为缺省值
参数:
	GPIOx:x 可以是 A,B,C,D 或者 E,来选择 GPIO 外设
返回值:
	无

12. GPIO_AFIODeInit

void GPIO_AFIODeInit(void);
功能:
	将复用功能(重映射事件控制和 EXTI 设置)重设为缺省值
参数:
	无
返回值:
	无

13. GPIO_Init

void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
功能:
	根据 GPIO_InitStruct 中指定的参数初始化外设 GPIOx 寄存器
参数:
	GPIOx:x 可以是 A,B,C,D 或者 E,来选择 GPIO 外设
    GPIO_InitStruct:指向结构 GPIO_InitTypeDef 的指针,包含了外设 GPIO 的配置信息    
返回值:
	无

14. GPIO_StructInit

void GPIO_StructInit(GPIO_InitTypeDef* GPIO_InitStruct);
功能:
	把 GPIO_InitStruct 中的每一个参数按缺省值填入
参数:
	GPIO_InitStruct:指向结构 GPIO_InitTypeDef 的指针,待初始化   
返回值:
	无

参考实现

 /**
   * @brief  Fills each GPIO_InitStruct member with its default value.
   * @param  GPIO_InitStruct : pointer to a GPIO_InitTypeDef structure which will
   *         be initialized.
   * @retval None
   */
 void GPIO_StructInit(GPIO_InitTypeDef* GPIO_InitStruct)
 {
   /* Reset GPIO init structure parameters values */
   GPIO_InitStruct->GPIO_Pin  = GPIO_Pin_All;
   GPIO_InitStruct->GPIO_Speed = GPIO_Speed_2MHz;
   GPIO_InitStruct->GPIO_Mode = GPIO_Mode_IN_FLOATING;
 }
 ​

15. GPIO_ReadInputDataBit

 uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
 功能:
     读取指定端口管脚的输入
 参数:
     GPIOx:x 可以是 A,B,C,D 或者 E,来选择 GPIO 外设
     GPIO_Pin:待读取的端口位    
 返回值:
     输入端口管脚值
 ​

16. GPIO_ReadInputData

 uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);
 功能:
     读取指定的 GPIO 端口输入
 参数:
     GPIOx:x 可以是 A,B,C,D 或者 E,来选择 GPIO 外设
 返回值:
     GPIO输入数据端口值
 ​

17. GPIO_ReadOutputDataBit

 uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
 功能:
     读取指定端口管脚的输出
 参数:
     GPIOx:x 可以是 A,B,C,D 或者 E,来选择 GPIO 外设
     GPIO_Pin:待读取的端口位    
 返回值:
     输出端口管脚值
 ​

18. GPIO_ReadOutputData

uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);
功能:
	读取指定的 GPIO端口输出
参数:
	GPIOx:x 可以是 A,B,C,D 或者 E,来选择 GPIO 外设 
返回值:
	GPIO输出数据端口值

19. GPIO_SetBits

void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
功能:
	设置指定的数据端口位
参数:
	GPIOx:x 可以是 A,B,C,D 或者 E,来选择 GPIO 外设
    GPIO_Pin:待设置的端口位    
返回值:
	无

20. GPIO_ResetBits

void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
功能:
	清除指定的数据端口位
参数:
	GPIOx:x 可以是 A,B,C,D 或者 E,来选择 GPIO 外设
    GPIO_Pin:待设置的端口位    
返回值:
	无

21. GPIO_WriteBit

void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);
功能:
	设置或者清除指定的数据端口位
参数:
	GPIOx:x 可以是 A,B,C,D 或者 E,来选择 GPIO 外设
    GPIO_Pin:待设置或者清除指的端口位 
返回值:
	无

22. GPIO_Write

void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);
功能:
	向指定 GPIO 数据端口写入数据
参数:
	GPIOx:x 可以是 A,B,C,D 或者 E,来选择 GPIO 外设
    PortVal: 待写入端口数据寄存器的值
返回值:
	无

23. RCC_APB2PeriphClockCmd

/**
  * @brief  Enables or disables the High Speed APB (APB2) peripheral clock.
  * @param  RCC_APB2Periph: specifies the APB2 peripheral to gates its clock.
  *   This parameter can be any combination of the following values:
  *     @arg RCC_APB2Periph_AFIO, RCC_APB2Periph_GPIOA, RCC_APB2Periph_GPIOB,
  *          RCC_APB2Periph_GPIOC, RCC_APB2Periph_GPIOD, RCC_APB2Periph_GPIOE,
  *          RCC_APB2Periph_GPIOF, RCC_APB2Periph_GPIOG, RCC_APB2Periph_ADC1,
  *          RCC_APB2Periph_ADC2, RCC_APB2Periph_TIM1, RCC_APB2Periph_SPI1,
  *          RCC_APB2Periph_TIM8, RCC_APB2Periph_USART1, RCC_APB2Periph_ADC3,
  *          RCC_APB2Periph_TIM15, RCC_APB2Periph_TIM16, RCC_APB2Periph_TIM17,
  *          RCC_APB2Periph_TIM9, RCC_APB2Periph_TIM10, RCC_APB2Periph_TIM11     
  * @param  NewState: new state of the specified peripheral clock.
  *   This parameter can be: ENABLE or DISABLE.
  * @retval None
  */
void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState)
功能:
	使能或者失能 APB2 外设时钟
参数:
	RCC_APB2Periph: 门控 APB2 外设时钟
    NewState:指定外设时钟的新状态
返回值:
	无    

3-3 LED闪烁&LED流水灯&蜂鸣器

3-4 GPIO输入

01.按键简介

  • 按键:常见的输入设备,按下导通,松手断开

  • 按键抖动:由于按键内部使用的是机械式弹簧片来进行通断的,所以在按下和松手的瞬间会伴随有一连串的抖动

image-20250319105205914

02.传感器模块简介

传感器模块:传感器元件(光敏电阻/热敏电阻/红外接收管等)的电阻会随外界模拟量的变化而变化,通过与定值电阻分压即可得到模拟电压输出,再通过电压比较器进行二值化即可得到数字电压输出 实物图

image-20250319105332963

电路原理图

image-20250319105340380

提供了4种传感器模块,分别是光敏电阻传感器,热敏电阻传感器。 对,设是红外传感器,反射式红外传感器。 他们的电路结构和工作原理都差不多,那这些传感器模块呢它们都是利用传感器元件,比如光敏电阻、 热敏电阻、红外接收管等这些元件的电阻会随外界的模拟量变化而变化。 比如光线越强,光敏电阻的阻值就越小,温度越高,热敏电阻的柱子就越小, 红外光线越强,红外接收管的阻值就越小,但是电阻的变化不容易直接被观察, 所以我们通常将传感器元件与定值电阻进行串联分压,这样就可以得到模拟电压的输出了。 对电路来说检测电压就非常容易了。 另外这个模块还可以通过电压比较器来对这个模拟电压进行二值化, 这样就可以得到数字电压输出了。 那我们看一下下面这个电路图,这个就是传感器模块的基本电路, 我们先看一下这个部分,这个n1就是传感器原件所代表的可变电阻, 它的阻值可以根据环境的光线温度等模拟量进行变化。 上面这个I1是和n 1进行分压的定值电阻, IE和n一串联,一端接在vc ce,正极一端接在jd负极, 这就构成了基本的分压电路。 左边这个C, 2是一个滤波电容,它是为了给中间的电压输出进行滤波的, 用来滤除一些干扰,保证输出电压波形的平滑。 一般我们在电路里遇到这种异端接在电路中另一端接地的电容都可以考虑一下这个是不是滤波电容的作用。 如果是滤过电容的作用,那这个电容就是用来保证电路稳定的,并不是电路的主要框架, 这时候我们在分析电路的时候,就可以先把这个电容给抹掉, 这样就可以使我们的电路分析更加简单。 那我们把这个电容抹掉,整个电路的主要框架就是定值电阻和传感器电阻的分压电路了。 在这里可以用分压定理来分析一下传感器电阻的阻值变化, 对输出电压的影响。 当然我们还可以用上下拉电阻的思维来分析,当这个n一组织变小时下拉作用就会增强。 中间的ao端的电压就会拉低。 极端情况下n一组织为0, ao输出被完全下拉输出0伏,单一组织变大,下拉作用就会减弱, 中间的引脚由于R1的上来作用,电压就会升高。 极端情况下n一主值无穷大,相当于段路输出电压被I一拉高至vcc, 这是用上下拉电阻来分析电路的,我可以举个例子来说明上下拉电阻的工作逻辑。 Ao这个输出端你可以把它想象成一个放在屋里的水平杆子, 二一上来电阻相当于栓在屋顶的弹簧,将杆子往上拉, n一下来电阻相当于栓在地面的弹簧将杆子往下拉, 这个电阻的阻值越小,弹簧的拉力就越强。 这个杆子的高度就相当于电路中的电压。 如果只有上拉弹簧或者下拉弹簧,那杆子肯定被拉到了屋顶或者地面。 在电路中就相当于中间点的电压为VCc或者gnd。 那当两个弹簧相互拉扯的时候,中间的输出端就会像拉力墙的一端偏移。 至于偏移多少就取决于两个弹簧的弹力之差了。 如果上下拉弹簧的弹力一致,则杆子会处于居中的位置,也就是电路输出2/vcc的电压, 如果上面的组织小拉一墙,那输出电压就会变高,反之下面的组织小输出电压就会变低。 如果组织为0,在电路中就是短接的状态,那就相当于拉力无穷大了。 如果上下拉电阻的组织都为0,就是两个无穷大的利益在对抗,在电路中呈现的状态就是电源短路, 所以这种情况应该避免。 这个上拉电阻和下拉电阻,在单片机电路中会经常出现。 比如若上拉若下拉墙上拉墙下拉等,这里强和弱就指电阻,阻值的大小也就是这个弹簧弹力的大小, 上来和下拉就指是街道VCc还是jd,也就是这个杆子是拉向屋顶还是拉向地面? 最终的输出电压就是在弹簧拉扯下最终感知的高低,那大家以后再遇到上下电阻和下拉电阻的分析, 就可以尝试一下用我这个感知和弹簧的模型来分析一下,相信你会有更加深刻的理解的。 那我们回到这个电路继续来看,在这两个电阻的分压下, ao就是我们想要的模拟电压输出了, 所以这里可以看到这个ao电压就直接通过这个排针输出了,这就是ao电压的由来仅需两个电阻分压即可得到。 那接下来这个模块还支持有数字输出,这个数字输出就是对ao进行二值化的输出, 这里二值化是通过这个芯片lm393来完成的。 这个lm393是一个电压比较器芯片,里面有两个独立的电压比较级电路, 然后剩下的是vc c和GNP供电。 那我们vcc就接到电路的VCc,今天D也接到了电路的gd,这里有个电容是一个电源供电的滤波电容, 这个电压比较器其实就是一个运算放大器。 有关运算放大器的知识,我在51单片机视频的adda那一节有讲过, 大家不会的可以去看一下,那我画一下这个运算放大器当做比较器的情况。 当这个铜像输入端的电压大于反向输入端的电压时,输出就会瞬间升为最大值, 也就是输出阶VCc。 反之当铜像输入端的电压小于反向输入端的电压时,输出就会瞬间降为最小值, 也就是输出界间d。 这样就可以对一个模拟电压进行二值化了。 我们看一下实际的应用,这里同样输入端音正接到了ao,这里就是模拟电压端。 您付了接了一个电位器,这个定位器的接法也是分压电阻的原理, 领动电位器n负就会生成一个可调的阈值电压。 两个电压进行比较,最终输出结果就是do数字电压输出, dio最终就接到了引脚的输出端。 这就是数字电压的由来。 然后右边这里还有两个指示灯,链路左边的是电源指示灯,通电就亮, 右边的是do输出指示灯,它可以指示do的输出电平,低电平点亮高电平熄灭, 那右边do这里还多了个I,五上来电阻,这个是为了保证默认输出为高电平的, 然后就是PE的排针,分别是vccjddo和ao。 那右上角这个图就是4个传感器模块了。 对于光敏电阻传感器来说,这个n 1就是光敏电阻,对于热敏电阻传感器来说, 这个n1就是热敏电阻。 对于这个红外传感器来说,这个n1就是一个红外接收管。 当然对应还会多一个点亮红外发射管的电路在这里,发射管发射,红外光接收管接收红外光模拟电压就表示的是接触光的强度。 那这个模块的这里定位器是直接换成了两个电阻进行分压,这样数字输出就是固定。 英语子的儿子的话了,这个模块通常用来检测同段,所以阈值也不需要过多的调整, 那最后一个模块也是一个红外发射管和接收管,只不过它是向下发射红外光,然后检测反射光的这个可以用来做循迹小车。 接下来我们就来看一下按键和传感器模块的硬件电路吧。 首先按键这里我给了4种解法。

03.光敏电阻传感器

在这里插入图片描述

传感器接线图

image-20250319111147199

04.按键电路图

image-20250319111217290

接下来我们就来看一下按键和传感器模块的硬件电路吧。 首先按键这里我给了4种解法。 传感器模块的硬件电路吧。 首先按键这里我给了4种解法,上面两个是下接按键的方式, 下面两个是上接按键的方式,一般来说我们的按键都是用上两种方式, 也就是下接的方式。 这个原因跟LED的接法类似是电路设计的习惯和规范。 那我们先来看一下第1个图,这种减法是按键的最常用的接法了, 在这里随便选取一个gpl口,比如P0,然后通过K1接到d,当按键按下时pa0被直接下达到jd, 此时读取pa领口的电压就是低电平。 当案件松手时pa0被悬空,悬空会出现什么情况呢? 就是引脚的电压不确定对吧? 所以在这种接法下必须要求pa0是上拉输入的模式,否则就会出现引脚电压不确定的错误现象。 如果P0是上拉输入的模式,那我们之前讲了引脚在悬空, a0就是高电平,所以这种方式下按下按键引脚为低电平松手引脚为高电平。 接着再看一下第2个图,相比较第1个图在这里外部接了一个三大电图, 这个意思大家就清楚了吧? 这个上来大家可以想象成一个弹簧,把这个端口往屋顶上拉, 当案件松手时引脚由于上来作用自然保持为高电平,当按键按下时引脚直接接到CNd, 也就是一股无穷大的力,把这个引脚往下拉,那弹簧肯定对抗不了无缝大的力, 所以引脚就为低电平。 这种状态下银角不会出现悬空状态,所以此时P10银角可以配置为浮空输入或者上拿输入。 如果是上达输入,那就是内外两个上拉电阻共同作用了。 这时高电平就会更强一些。 对应高电平就更加稳定。 当然这样的话当引脚被强行拉到低时,损耗也就会大一些。 那接着第3个图, K 0通过按键接到3.3伏,这样也是可以的, 不过要求pa0必须要配置成下拉输入的模式。 当按键按下时引脚为高电平,松手时引脚回到默认值低电平,再要求单片机的引脚可以配置为下拉输入的模式。 一般单片机可能不一定有下拉速度的模式,所以最好还是用上面的解法, 下面的作为扩展部分,大家了解一下即可。 那最后一种解法呢就是在刚才的这种解法下面,在外界一个下拉电阻这个解法P 0需要配置为下拉输入模式或者浮空输入模式, 自己分析大家应该已经清楚了,我就不再细讲了。 那总结一下就是上面这两种接法按键按下时引脚是低音频,松手时高电平, 下面这两种接法按键按下时是高电平,松手时低电平。 左边两种减法必须要求引脚是上拉或者下拉输入的模式,右边两种解法可以允许引脚式复工输入的模式, 因为已经外置了上拉电阻和下拉电阻,一般我们都用上面两种解法, 下面两种解法用的比较少,那到这里按键的硬件电路就介绍完了。

05.C语言数据类型

06.C语言宏定义

关键字:#define

用途:用一个字符串代替一个数字,便于理解,防止出错;提取程序中经常出现的参数,便于快速修改

定义宏定义:

#define ABC 12345 1 引用宏定义:

int a = ABC; //等效于int a = 12345; 1

07.C语言typedef

关键字:typedef

用途:将一个比较长的变量类型名换个名字,便于使用

定义typedef:

typedef unsigned char uint8_t; 1 引用typedef:

uint8_t a; //等效于unsigned char a; 1

08.C语言结构体

•关键字:struct

用途:数据打包,不同类型变量的集合

•定义结构体变量:

struct{char x; int y; float z;} StructName; 1 因为结构体变量类型较长,所以通常用typedef更改变量类型名

•引用结构体成员:

StructName.x = 'A';

StructName.y = 66;

StructName.z = 1.23; 1 2 3 4 5 或 pStructName->x = ‘A’; //pStructName为结构体的地址 pStructName->y = 66;

pStructName->z = 1.23; 1

09.C语言枚举

关键字:enum

用途:定义一个取值受限制的整型变量,用于限制变量取值范围;宏定义的集合

定义枚举变量:

enum{FALSE = 0, TRUE = 1} EnumName; 1 因为枚举变量类型较长,所以通常用typedef更改变量类型名

引用枚举成员:

EnumName = FALSE; EnumName = TRUE;

3-5 按键控制LED&光敏传感器&控制蜂鸣器

4-1 OLED调试工具

01.STM32调试方式

  • 串口调试:通过串口通信,将调试信息发送到电脑端,电脑使用串口助手显示调试信息

  • 显示屏调试:直接将显示屏连接到单片机,将调试信息打印在显示屏上

  • Keil调试模式:借助Keil软件的调试模式,可使用单步运行、设置断点、查看寄存器及变量等功能

02.OLED简介

OLED,即有机发光二极管( Organic Light Emitting Diode )。 OLED 由于同时具备自发光,不需背光源、对比度高、厚度薄、视角广、反应速度快、可用于挠曲性面板、使用温度范围广、构造及制程较简单等优异之特性,被认为是下一代的平面显示器新兴应用技术。

LCD 都需要背光,而 OLED 不需要,因为它是自发光的。这样同样的显示 OLED 效果要来得好一些。以目前的技术,OLED 的尺寸还难以大型化,但是分辨率确可以做到很高。在此我们使用的是0.96寸OLED显示屏,该屏有以下特点:

  1. 0.96 寸 OLED 有黄蓝,白,蓝三种颜色可选;其中黄蓝是屏上 1/4 部分为黄光,下 3/4 为蓝;而且是固定区域显示固定颜色,颜色和显示区域均不能修改;白光则为纯白,也就是黑底白字;蓝色则为纯蓝,也就是黑底蓝字。

  2. 分辨率为 128*64

  3. 多种接口方式;OLED 裸屏总共种接口包括:6800、8080 两种并行接口方式、3 线或 4 线的串行 SPI 接口方式、 IIC 接口方式(只需要 2 根线就可以控制 OLED 了!),这五种接口是通过屏上的 BS0~BS2 来配置的。

  4. 本屏开发了两种接口的 Demo 板,接口分别为七针的 SPI/IIC 兼容模块,四针的IIC 模块。两种模块都很方便使用;希望大家根据实际需求来选择不同的模块。

image-20250319143639314

03.0.96寸OLED模块

SPI模块:
  1. GND 电源地

  2. VCC 电源正(3~5.5V)

  3. D0 OLED 的 D0 脚,在 SPI 和 IIC 通信中为时钟管脚

  4. D1 OLED 的 D1 脚,在 SPI 和 IIC 通信中为数据管脚

  5. RES OLED 的 RES#脚,用来复位(低电平复位)

  6. DC OLED 的 D/C#E 脚,数据和命令控制管脚

  7. CS OLED 的 CS#脚,也就是片选管脚

image-20250319144458479

IIC接口模块:
  1. GND 电源地

  2. VCC 电源正(3~5.5V)

  3. SCL OLED 的 D0 脚,在 IIC 通信中为时钟管脚

  4. SDA OLED 的 D1 脚,在 IIC 通信中为数据管脚

image-20250319144438317

04.0.96寸OLED驱动IC

本屏所用的驱动 IC 为 SSD1306;其具有内部升压功能;所以在设计的时候不需要再专一设计升压电路;当然了本屏也可以选用外部升压,具体的请详查数据手册。SSD1306 的每页包含了128 个字节,总共 8 页,这样刚好是 128*64 的点阵大小。这点与 1.3 寸 OLED 驱动 IC SSD1106稍有不同,SSD1106 每页是 132 个字节,也是 8 页。所以在用 0.96 寸 OLED 移植 1.3 寸 OLED 程序的时候需要将 0.96 寸的显示地址向右偏移 2,这样显示就正常了;否则在用 1.3 寸的时候 1.3寸屏右边会有 4 个像素点宽度显示不正常或是全白,这点大家注意一下。其它的 SSD1306 和SSD1106 区别不大。

05.0.96寸OLED原理图

SPI版本

在这里插入图片描述

IIC版本

在这里插入图片描述

06.硬件电路

image-20250319144605186

07.OLED驱动函数

image-20250319144818442

相关函数和作用

image-20250319144832195

4-2 OLED显示屏

5-1 EXTI外部中断

01.中断系统

  • 中断:在主程序运行过程中,出现了特定的中断触发条件(中断源),使得CPU暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续运行

  • 中断优先级:当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急进行裁决,优先响应更加紧急的中断源

  • 中断嵌套:当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU再次暂停当前中断程序,转而去处理新的中断程序,处理完成后依次进行返回

02.中断执行流程

image-20250319154135162

03.STM32中断

68个可屏蔽中断通道(中断源),包含EXTI、TIM、ADC、USART、SPI、I2C、RTC等多个外设

使用NVIC统一管理中断,每个中断通道都拥有16个可编程的优先等级,可对优先级进行分组,进一步设置抢占优先级和响应优先级

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

中断地址:为什么需要中断地址呢?这个是因为我们程序中的中断函数它的地址是由编译器来分配的,是不固定的,但是我们的中断跳转,由于硬件的限制,只能跳到固定的地址执行程序,所以为了能让硬件跳转到一个不固定的中断函数里,这里就需要在内存中定义一个地址的列表,这个列表地址是固定的,中断发生后,就跳到这个固定位置,然后在这个固定位置,由编译器,再加上一条跳转到中断函数的代码,这样中断跳转就可以跳转到任意位置了,这个中断地址的列表,就叫中断问量表,相当于中断跳转的一个跳板,不过我们用C语言编程的话,是不需要管这个中断向量表的,因为编译器都帮我们做好了,所以我们还是很省心的

04.NVIC基本结构

这个NVIC的名字叫做嵌套中断向量控制器,在STM32中,它是用来统一分配中断优先级和管理中断的。Nvic是一个内核外设,是CPU的小助手。我们刚才看到了stm32的中段非常多,如果把这些中段全都接到CPU上那CPU还得引出很多线进行适配,设计上就很麻烦,并且如果很多中断同时申请或者中断很多产生的拥堵,CPU也会很难处理。毕竟CPU主要是用来运算的,中断分配的任务就放到别的地方吧,所以nvic就出现了,nvic有很多输入口,你有多少个中断线路都可以接过来,比如这里可以接到exti,usart等等,这里线上画了个斜杠,上面写个n,这个意思是一个外设可能会同时占用多个中断通道。所以这里有n条线,然后nvic只有一个输出口,nvsa根据每个中断的优先级分配中断的先后顺序。之后通过右边这一个输出口告诉CPU,你该处理哪个中断。对于中断先后顺序分配的任务,CPU不需要知道。

image-20250319164255698

05.NVIC优先级分组

NVIC的中断优先级由优先级寄存器的4位(0~15)决定,这4位可以进行切分,分为高n位的抢占优先级和低4-n位的响应优先级

抢占优先级高的可以中断嵌套,响应优先级高的可以优先排队,抢占优先级和响应优先级均相同的按中断号排队

在这里插入图片描述

06.EXTI简介

EXTI(Extern Interrupt)外部中断

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

  • 支持的触发方式:上升沿/下降沿/双边沿/软件触发

  • 支持的GPIO口:所有GPIO口,但相同的Pin不能同时触发中断(比如PA1和PB1就不可以)

    • 为什么呢?因为只有其中一个能接到EXTI的通道0上,我理解的是下面的AFIO一共只能接收16个引脚,也是从0-15,0这个引脚只能选择PA0,PB0,PC0三个其中的一个,所以相同的Pin不能同时触发中断,选择PA0,PB1,PC3......这样是可以的

  • 通道数:16个GPIO_Pin,外加PVD输出、RTC闹钟、USB唤醒、以太网唤醒

  • 触发响应方式:中断响应/事件响应

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

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

07.EXTI基本结构

在这里插入图片描述

从上面的这些接到了AFIO,就是用来触发中断的。这就是所有GPIO口都能触发中断,但相同的pin不能同时触发中断的原因。然后通过AFIO选择之后的16个通道,就接到了exca边缘检测及控制电路上。同时下面这是个蹭网的外设,也是并列进来的。这些加起来就组成了exti的20个输入信号。然后经过exti电路之后,分为了两种输出,其中上面的这些接到了NVIC,就是用来触发中断的。这里注意一下,本来20路输出应该有20路中断的输出,但是可能ST公司觉得这20个输出太多了比较占用NVIC的通道资源,所以就把其中外部中断的9-5和15-10给分配到了一个通道里。也就是说外部中断的9-5会触发同一个中断函数,15-10也会触发同一个中断函数。在边上的时候,我们在这两个中断函数里需要再根据标志位来区分到底是哪个中断进来的

08.AFIO复用IO口

AFIO主要用于引脚复用功能的选择和重定义

在STM32中,AFIO主要完成两个任务:

  • 复用功能引脚重映射

  • 中断引脚选择

外部中断 外部中断/ 事件线路映像

112通用I/O端口以下图的方式连接到16个外部中断/事件线上:

在这里插入图片描述

09.EXTI框图

在这里插入图片描述

我们刚才提到的中断引脚选择的,这个也是用afl来完成的。 好, afl讲完了,下面就是exti的内部框图了。 这里可以看到Excel的右边就是20根输入线,然后输入线首先进入边缘检测电路, 在上面的上升沿寄存器和下降沿寄存器可以选择是。 上身也触发,还是下降要出发,或者两个都触发,接着出发信号就进入到这个货门的输入端了。 简单介绍一下,这种弯弯的符号是或门,它可以有多个输入。 但只能有一个输出,执行的是货的逻辑。 在输入端只要有一个是高电平1,输出就是高电平1,只有全部输入低电平0, 输出才为0。 然后还有这种带点直边的符号是雨门。 他同样也可以有多个输入,但只能有一个输出,执行的是雨的逻辑。 在食物端只要有一个是低电平0输出就是0,只有全部输入一输出才为1。 另外还有一种三角号加个圈的是非门,他只有一个输入一个输出。 19450输入0就输出1,自信的是飞的逻辑。 最后还有一种就是刚才这里说的数据选择器,它的符号是一个梯形, 有多个输入一个输出,在侧面有选择控制端,根据。 控制端的数据,从输入选择一个接到输出,这些就是常见的逻辑符号了解一下。 在html32的模块框图里还是非常常见的。 那我们接着这里说,在这里硬件出发和软件中断寄存器的值接到了这个货门上,也就是任意一个为1,后门就可以输出1, 所以在这里我们说支持的触发方式使上升沿下降沿双边沿和软件出发。 然后回到这里,触发信号通过这个货门之后,就兵分两路,上一路是触发中断的, 下路是触发事件的,触发中断首先会置一个挂起寄存器。 这就相当于是一个中断的标志位了。 我们可以读取这个寄存器判断是哪个通道出发的中断,如果中断挂起寄存器至1,他就会继续向左走,和中断屏蔽寄存器,共同进入一个与门。 然后是自nvh。 这里的雨门实际上就是开关的作用,因为对于语文来说,一与上任意的数x等于这个任意的数x, 您遇上任意的数X都=0,这就相当于中断屏蔽寄存器给一。 那另一个输入就是直接输出,也就是允许终端中断屏蔽寄存器给您。 那另一个输入无论是什么输出都是0,相当于屏蔽了这个中断。 这就是这个与门的作用,相当于一个开关控制。 接着看一下下一路事件的输出部分,首先也是一个事件屏蔽寄存器进行开关控制, 最后通过一个脉冲发生器到其他外设,这个脉冲发生器就是给一个电平脉冲, 用来错发其他外设的动作。 然后这个框图剩下的部分还有这些画一个斜杠写在20表示的就是这是20根线,代表20个通道, 上面这些就是外设接口

10.计数器模块

用途

广泛用于电机转速检测,脉冲计数,位置限位等。

模块特色

1、使用进口槽型光耦传感器,槽宽度5mm。

2、有输出状态指示灯,输出高电平灯灭,输出低电平灯亮。

3、有遮挡,输出高电平;无遮挡,输出低电平。

4、比较器输出,信号干净,波形好,驱动能力强,超过15mA。

5、工作电压3.3V-5V

6、输出形式 :数字开关量输出(0和1)

7、设有固定螺栓孔,方便安装

8、小板PCB尺寸:3.2cm x 1.4cm

9、使用宽电压LM393比较器

模块使用说明

1.模块槽中无遮挡时,接收管导通,模块DO输出低电平,遮挡时,DO输出高电平;

2、DO输出接口可以与单片机IO口直接相连,检测传感器是否有遮档,如用电机码盘则可检测电机的转速。

2.模块DO可与继电器相连,组成限位开关等功能,也可以与有源蜂鸣器模块相连,组成报警器。

产品接线说明

1、VCC 接电源正极3.3-5V

2、GND 接电源负极

3、DO TTL开关信号输出

4、AO 此模块不起作用

电路原理图

在这里插入图片描述

11.旋转编码器简介

旋转编码器:用来测量位置、速度或旋转方向的装置,当其旋转轴旋转时,其输出端可以输出与旋转速度和方向对应的方波信号,读取方波信号的频率和相位信息即可得知旋转轴的速度和方向

类型:机械触点式/霍尔传感器式/光栅式

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

5-2 对射式红外传感器计次&旋转编码器计次

8-1 DMA直接存储器存取

01.DMA简介

  • DMA(Direct Memory Access)直接存储器存取

  • DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源

  • 12个独立可配置的通道:DMA1(7个通道)、DMA2(5个通道)

  • 每个通道都支持软件触发和特定的硬件触发

STM32F103C8T6 DMA资源:DMA1(7个通道)

9-1 USART串口协议

01.串行通讯与并行通讯

按数据传送的方式,通讯可分为串行通讯与并行通讯,串行通讯是指设备之间通过少量数据信号线(一般是8根以下), 地线以及控制信号线,按数据位形式一位一位地传输数据的通讯方式。而并行通讯一般是指使用8、16、32及64根或更多的数据线进行传输的通讯方式, 它们的通讯传输对比说明见图 并行通讯与串行通讯的对比图 ,并行通讯就像多个车道的公路, 可以同时传输多个数据位的数据,而串行通讯,而串行通讯就像单个车道的公路,同一时刻只能传输一个数据位的数据。

在这里插入图片描述

很明显,因为一次可传输多个数据位的数据 ,在数据传输速率相同的情况下,并行通讯传输的数据量要大得多, 而串行通讯则可以节省数据线的硬件成本(特别是远距离时)以及PCB的布线面积, 串行通讯与并行通讯的特性对比见表 串行通讯与并行通讯的特性对比。

在这里插入图片描述

不过由于并行传输对同步要求较高,且随着通讯速率的提高,信号干扰的问题会显著影响通讯性能,现在随着技术的发展,越来越多的应用场合采用高速率的串行差分传输。

02.全双工、半双工及单工通讯

根据数据通讯的方向,通讯又分为全双工、半双工及单工通讯,它们主要以信道的方向来区分,见图 全双工-半双工及单工通讯 及表 通讯方式说明。

在这里插入图片描述

仍以公路来类比,全双工的通讯就是一个双向车道,两个方向上的车流互不相干;半双工则像乡间小道那样,同一时刻只能让一辆小车通过, 另一方向的来车只能等待道路空出来时才能经过;而单工则像单行道,另一方向的车辆完全禁止通行。

在这里插入图片描述

03.同步通讯与异步通讯

根据通讯的数据同步方式,又分为同步和异步两种,可以根据通讯过程中是否有使用到时钟信号进行简单的区分。

在同步通讯中,收发设备双方会使用一根信号线表示时钟信号,在时钟信号的驱动下双方进行协调, 同步数据,见图 同步通讯。 通讯中通常双方会统一规定在时钟信号的上升沿或下降沿对数据线进行采样。

在这里插入图片描述

在异步通讯中不使用时钟信号进行数据同步,它们直接在数据信号中穿插一些同步用的信号位,或者把主体数据进行打包, 以数据帧的格式传输数据,见图 某种异步通讯 ,某些通讯中还需要双方约定数据的传输速率,以便更好地同步。

在这里插入图片描述

在同步通讯中,数据信号所传输的内容绝大部分就是有效数据,而异步通讯中会包含有帧的各种标识符,所以同步通讯的效率更高, 但是同步通讯双方的时钟允许误差较小,而异步通讯双方的时钟允许误差较大。

04.通讯速率

衡量通讯性能的一个非常重要的参数就是通讯速率,通常以比特率(Bitrate)来表示,即每秒钟传输的二进制位数, 单位为比特每秒(bit/s)。容易与比特率混淆的概念是“波特率”(Baudrate),它表示每秒钟传输了多少个码元。 而码元是通讯信号调制的概念,通讯中常用时间间隔相同的符号来表示一个二进制数字,这样的信号称为码元。 如常见的通讯传输中,用0V表示数字0,5V表示数字1,那么一个码元可以表示两种状态0和1,所以一个码元等于一个二进制比特位, 此时波特率的大小与比特率一致;如果在通讯传输中,有0V、2V、4V以及6V分别表示二进制数00、01、10、11, 那么每个码元可以表示四种状态,即两个二进制比特位,所以码元数是二进制比特位数的一半,这个时候的波特率为比特率的一半。 因为很多常见的通讯中一个码元都是表示两种状态,人们常常直接以波特率来表示比特率,虽然严格来说没什么错误,但希望您能了解它们的区别。

05.通信接口

通信的目的:将一个设备的数据传送到另一个设备,扩展硬件系统

通信协议:制定通信的规则,通信双方按照协议规则进行数据收发

在这里插入图片描述

同步的理解:

  • 同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去;

  • 同步就相当于是当客户端发送请求给服务端,在等待服务端响应的请求时,客户端不做其他的事情。当服务端做完了才返回到客户端。这样的话客户端需要一直等待。用户使用起来会有不友好。

异步的理解:

  • 异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率。

  • 异步就相当于当客户端发送给服务端请求时,在等待服务端响应的时候,客户端可以做其他的事情,这样节约了时间,提高了效率。

06.串口通信

  • 串口是一种应用十分广泛的通讯接口,串口成本低、容易使用、通信线路简单,可实现两个设备的互相通信

  • 单片机的串口可以使单片机与单片机、单片机与电脑、单片机与各式各样的模块互相通信,极大地扩展了单片机的应用范围,增强了单片机系统的硬件实力

在这里插入图片描述

  1. 左图是USB转串口模块,上面有个芯片,型号是CH340,这个芯片可以把串口协议转换为USB协议,它一边是USB口,可以插在电脑上,另一边是串口的引脚,可以和支持串口的芯片接在一起

  2. 中间这个图是一个陀螺仪传感器的模块,可以测量角速度、加速度这些姿态参数,它左右各有4个引脚,一边是串口的引脚,另一边是I2C的引脚

  3. 右边这个图是蓝牙串口模块,下面4个脚是串口通信的引脚,上面的芯片可以和手机互联,实现手机遥控单片机的功能

07.硬件电路

  • 简单双向串口通信有两根通信线(发送端TX和接收端RX)

  • TX与RX要交叉连接

  • 当只需单向的数据传输时,可以只接一根通信线

  • 当电平标准不一致时,需要加电平转换芯片

在这里插入图片描述

08.电平标准

  • 电平标准是数据1和数据0的表达方式,是传输线缆中人为规定的电压与数据的对应关系,串口常用的电平标准有如下三种:

  • TTL电平:+3.3V或+5V表示1,0V表示0

  • RS232电平:-3到-15V表示1,+3到+15V表示0

  • RS485电平:两线压差+2到+6V表示1,-2到-6V表示0(差分信号)

像单片机这种低压小型设备,使用的都是TTL电平

09.串口参数及时序

  • 波特率:串口通信的速率(每秒传输波特的个数),或者说每秒传输的比特数,单位是bit/s

  • 起始位:标志一个数据帧的开始,固定为低电平

  • 数据位:数据帧的有效载荷,1为高电平,0为低电平,低位先行

    • 可以把校验位和数据位放在一起,此时数据位9个bit,当然也可以分开,数据位就是有效载荷,校验位就是独立的1位

  • 校验位:用于数据验证,根据数据位计算得来

    • 分为三种,无校验,奇校验,偶校验,第一个图就是无校验

    • 奇校验:如果使用了奇校验,那么包括校验位在内的9位数据会出现奇数个1,比如如果你传输0000 1111,目前总共4个1,是偶数个,那么校验位就需要再补一个1,连同校验位就是0000 11111,总共5个1,保证1为奇数,如果数据是0000 1110,此时3个1,是奇数个,那么校验位就补一个0,连同校验位就是0000 11100,总共还是3个1,1的个数为奇数

    • 偶校验:与奇校验相反

  • 停止位:用于数据帧间隔,固定为高电平

在这里插入图片描述

低位先行:LSB->MSB

第一个图是1帧共10位,没有奇偶校验位,第二个图是1帧共11位,有奇偶校验位

10.串口时序

在这里插入图片描述

9-2 USART串口外设

01.串口简介

串口通讯(Serial Communication)是一种设备间非常常用的串行通讯方式,因为它简单便捷,因此大部分电子设备都支持该通讯方式, 电子工程师在调试设备时也经常使用该通讯方式输出调试信息。

在计算机科学里,大部分复杂的问题都可以通过分层来简化。如芯片被分为内核层和片上外设;STM32标准库则是在寄存器与用户代码之间的软件层。 对于通讯协议,我们也以分层的方式来理解,最基本的是把它分为物理层和协议层。物理层规定通讯系统中具有机械、电子功能部分的特性, 确保原始数据在物理媒体的传输。协议层主要规定通讯逻辑,统一收发双方的数据打包、解包标准。 简单来说物理层规定我们用嘴巴还是用肢体来交流,协议层则规定我们用中文还是英文来交流。

物理层

串口通讯的物理层有很多标准及变种,我们主要讲解RS-232标准 ,RS-232标准主要规定了信号的用途、通讯接口以及信号的电平标准。

使用RS-232标准的串口设备间常见的通讯结构见图 串口通讯结构图。

在这里插入图片描述

在上面的通讯方式中,两个通讯设备的“DB9接口”之间通过串口信号线建立起连接,串口信号线中使用“RS-232标准”传输数据信号。 由于RS-232电平标准的信号不能直接被控制器直接识别,所以这些信号会经过一个“电平转换芯片”转换成控制器能识别的“TTL标准”的电平信号,才能实现通讯。

电平标准

根据通讯使用的电平标准不同,串口通讯可分为TTL标准及RS-232标准,见表 TTL电平标准与RS232电平标准。

在这里插入图片描述

我们知道常见的电子电路中常使用TTL的电平标准,理想状态下,使用5V表示二进制逻辑1,使用0V表示逻辑0; 而为了增加串口通讯的远距离传输及抗干扰能力,它使用-15V表示逻辑1,+15V表示逻辑0。 使用RS232与TTL电平校准表示同一个信号时的对比见图 RS-232与TTL电平标准下表示同一个信号。

在这里插入图片描述

因为控制器一般使用TTL电平标准,所以常常会使用MAX3232芯片对TTL及RS-232电平的信号进行互相转换。

RS-232信号线

在最初的应用中,RS-232串口标准常用于计算机、路由与调制调解器(MODEN,俗称“猫”)之间的通讯 ,在这种通讯系统中, 设备被分为数据终端设备DTE(计算机、路由)和数据通讯设备DCE(调制调解器)。我们以这种通讯模型讲解它们的信号线连接方式及各个信号线的作用。

在旧式的台式计算机中一般会有RS-232标准的COM口(也称DB9接口),见图 电脑主板上的COM口及串口线。

在这里插入图片描述

其中接线口以针式引出信号线的称为公头,以孔式引出信号线的称为母头。在计算机中一般引出公头接口,而在调制调解器设备中引出的一般为母头,使用上图中的串口线即可把它与计算机连接起来。通讯时,串口线中传输的信号就是使用前面讲解的RS-232标准调制的。

在这种应用场合下,DB9接口中的公头及母头的各个引脚的标准信号线接法见图 DB9标准的公头及母头接法 及表 DB9信号线说明。

在这里插入图片描述

在这里插入图片描述

上表中的是计算机端的DB9公头标准接法,由于两个通讯设备之间的收发信号(RXD与TXD)应交叉相连, 所以调制调解器端的DB9母头的收发信号接法一般与公头的相反,两个设备之间连接时,只要使用“直通型”的串口线连接起来即可, 见图 计算机与调制调解器的信号线连接。

在这里插入图片描述

串口线中的RTS、CTS、DSR、DTR及DCD信号,使用逻辑 1表示信号有效,逻辑0表示信号无效。 例如,当计算机端控制DTR信号线表示为逻辑1时,它是为了告知远端的调制调解器,本机已准备好接收数据,0则表示还没准备就绪。

在目前的其它工业控制使用的串口通讯中,一般只使用RXD、TXD以及GND三条信号线, 直接传输数据信号,而RTS、CTS、DSR、DTR及DCD信号都被裁剪掉了。

02.串口协议

协议层lsr

串口通讯的数据包由发送设备通过自身的TXD接口传输到接收设备的RXD接口。在串口通讯的协议层中, 规定了数据包的内容,它由启始位、主体数据、校验位以及停止位组成,通讯双方的数据包格式要约定一致才能正常收发数据, 其组成见图 串口数据包的基本组成。

在这里插入图片描述

波特率

本章中主要讲解的是串口异步通讯,异步通讯中由于没有时钟信号(如前面讲解的DB9接口中是没有时钟信号的), 所以两个通讯设备之间需要约定好波特率,即每个码元的长度,以便对信号进行解码, 图 串口数据包的基本组成 中用虚线分开的每一格就是代表一个码元。常见的波特率为4800、9600、115200等。

通讯的起始和停止信号

串口通讯的一个数据包从起始信号开始,直到停止信号结束。数据包的起始信号由一个逻辑0的数据位表示, 而数据包的停止信号可由0.5、1、1.5或2个逻辑1的数据位表示,只要双方约定一致即可。

有效数据

在数据包的起始位之后紧接着的就是要传输的主体数据内容,也称为有效数据,有效数据的长度常被约定为5、6、7或8位长。

数据校验

在有效数据之后,有一个可选的数据校验位。由于数据通信相对更容易受到外部干扰导致传输数据出现偏差, 可以在传输过程加上校验位来解决这个问题。校验方法有奇校验(odd)、偶校验(even)、0校验(space)、1校验(mark)以及无校验(noparity)。

奇校验要求有效数据和校验位中“1”的个数为奇数,比如一个8位长的有效数据为:01101001,此时总共有4个“1”, 为达到奇校验效果,校验位为“1”,最后传输的数据将是8位的有效数据加上1位的校验位总共9位。

偶校验与奇校验要求刚好相反,要求帧数据和校验位中“1”的个数为偶数, 比如数据帧:11001010,此时数据帧“1”的个数为4个,所以偶校验位为“0”。

0校验是不管有效数据中的内容是什么,校验位总为“0”,1校验是校验位总为“1”。

03.USART简介

  • USART(Universal Synchronous/Asynchronous Receiver/Transmitter)通用同步/异步收发器

  • USART是STM32内部集成的硬件外设,可根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可自动接收RX引脚的数据帧时序,拼接为一个字节数据,存放在数据寄存器里

  • 自带波特率发生器,最高达4.5Mbits/s

  • 可配置数据位长度(8/9)、停止位长度(0.5/1/1.5/2)

  • 可选校验位(无校验/奇校验/偶校验)

  • 支持同步模式、硬件流控制、DMA、智能卡、IrDA、LIN

    • 同步模式:这个同步模式,就是多了个时钟CLK的输出

    • 硬件流控制:这个是,比如A设备有个TX向B设备的RX发送数据,A设备一直在发,发的太快了,B处理不过来,如果没有硬件流控制,那B就只能抛弃新数据或者覆盖原数据了,如果有硬件流控制,在硬件电路上,会多出一根线,如果B没准备好接收,就置高电平,如果准备好了,就置低电平,A接收到了B反馈的准备信号,就只会在B准备好的时候,才发数据,如果B没准备好,那数据就不会发送出去,这就是硬件流控制,可以防止因为B处理慢而导致数据丢失的问题

  • STM32F103C8T6 USART资源: USART1、 USART2、 USART3

04.USART框图

在这里插入图片描述

USART框图核心模块解析

以下是USART框图核心模块的简洁解析:

一、数据传输主干

  1. 发送通道(右上)

    • CPU/DMA → 发送数据寄存器(TDR) → 发送移位寄存器 → TX引脚输出

    • 支持IrDA红外编码(左侧编解码模块)

  2. 接收通道(左上)

    • RX引脚 → 接收移位寄存器 → 接收数据寄存器(RDR) → CPU/DMA读取

二、控制中枢(中部)

  • CR控制寄存器组:

    • CR1:总开关(收发使能/校验)

    • CR2:停止位/时钟配置

    • CR3:DMA/智能卡模式

  • 中断控制器(中下):协调TC(发送完成)、RXNE(接收就绪)等中断事件

三、速率控制(右下)

  • USART_BRR波特率模块:

    • 分频公式:USARTDIV = 整数部分 + 小数/16

    • 独立控制收发两端时钟

四、硬件流控(左下)

  • nRTS/nCTS引脚联动(n是低电平有效):

    • nRTS:本端准备好接收

    • nCTS:对方允许本端发送

五、特殊功能

  • 唤醒单元:低功耗模式恢复机制

  • SCLK控制:同步通信时钟生成

  • 状态寄存器(SR):实时反馈工作状态

流程

  • 发送:比如你在某时刻给TDR写入了0x55这个数据,在寄存器里就是二进制存储,0101 0101.那么此时,硬件检测到你写入数据了,它就会检查,当前移位寄存器是不是有数据正在移位,如果没有,这个0101 0101就会立刻全部移动到发送移位寄存器,准备发送,当数据从TDR移动到移位寄存器时,会置一个标志位,叫TXE((TXEmpty),发送寄存器空,我们检查这个标志位,如果置1了,我们就可以在TDR写人下一个数据了,注意一下,当TXE标志位置1时,数据其实还没有发送出去,只要数据从TDR转移到发送移位寄存器了,TXE就会置1,我们就可以写入新的数据了,然后发送移位寄存器就会在下面这里的发生器控制的驱动下,向右移位,然后一位一位地,把数据输出到引脚,这里是向右移位的,所以正好和串口协议规定的低位先行,是一致的,当数据移位完成后,新的数据就会再次自动地从TDR转移到发送移位寄存器里来,如果当前移位寄存器移位还没有完成,TDR的数据就会进行等待,但移位完成,就会立刻转移过来,有了TDR和移位寄存器的双重缓存,可以保证连续发送数据的时候,数据帧之间不会有空闲,提高了工作效率

  • 接收:RXNE(RX Not Empty),接收数据寄存器非空,当我们检测到RXNE置1之后,就可以把数据读走了

当然发送还需要加上帧头帧尾,接收还需要剔除帧头帧尾,这些操作,它内部有电路会自动执行

硬件流控(左下)

  • nRTS/nCTS引脚联动(n是低电平有效):

    • nRTS:本端准备好接收

    • nCTS:对方允许本端发送

  • 流程:nCTS是清除发送是输入角,也就是用于接收别人nRTS的信号的。这里前面加个n意思是低电平有效,那这两个引脚怎么玩的呢?首先得找另一个支持流控的串口,它的gx接到我的x,然后我的rts要输出一个能不能接收的反馈信号,接到对方的nCTS,当我能接受的时候nRTS就自低电平请求对方发送,对方的nCTS接收到之后就可以一直发。 当我处理不过来时,比如接收数据进入系我一直没有读,又有新的数据过来了现在就代表我没有及时处理。那nRTS就会自高电平,对方nCTS接收到之后就会暂停发送,直到这里接收数据进入系被读走nRTS自低电平,新的数据才会继续发送。那反过来当我的tx给别人发送数据时,我们nCTS就要接到对方的nRTS,用于判断对方能不能接受,tx和nCTS是一对的,X和nRTS是一对的,cgs和nRTS也要交叉连接,这就是流控的工作模式,当然我们一般不使用流控。

时钟SCLK:

这部分电路用于产生同步的时钟信号,它是配合发送一位进行器输出的函,发送计算器每一位一次同步时钟电平就跳变一个周期,始终告诉对方我移出去一位了,你看要不要让我这个时钟信号来指导你接收一下。当然这个始终只支持输出,不支持输入,所以两个USA之间不能实现同步的串口通信,那这个始终信号有什么用呢?第1个用途就是兼容别的协议,比如串口加上始终之后就跟spi协议特别像,所以有了时钟输出的窗口就可以进入spi。另外这个始终也可以做自适应波特率,比如接收设备不确定发送设备给的是什么波特率,那就可以测量一下这个时钟的周期,然后再计算得到波特率,不过这就需要另外写程序来实现这个功能了这个时钟功能我们一般不用,所以也是了解一下就行。

唤醒单元:

然后继续看中间这个唤醒单元,这部分的多种是实现串口挂载多设备,我们这样说串口一般是点对点的通信,点对点只支撑两个设备互相通信,少发数据直接发就行而多设备在一条总线上可以接多个重设备,每个设备分配一个地址我想跟某个设备通信就先进行寻址确定通信对象,再进行数据收盘。那回到这里,这个唤醒单元就可以用来实现多设备的功能。在这里可以给串口分配个地址,当你发送指定地址时,此设备唤醒开始工作。当你发动别的设备地址时,别的设备就换新工作,这个设备没收到地址就会保持沉默这样就可以实现多设备的窗口中心了。这部分工人我们一般不用大家也了解一下就行。

中断输出控制

这部分是中断输出控制。中段申请VR就是状态寄存器这里的各种标志。各位状态寄存器这里有两个标志位比较重要,一个是TXE发送寄存器空另一个是RXNE接收寄存器非空。 这两个是判断发生状态和接收状态的必要标志为刚才也都讲过剩下的标志位了解一下就行。中断输出控制这里就是配置终端是不是能通向NVIC,这个应该好理解。

波特率发生器:

然后最下面这里是波特率发生器部分,之前我也大概说过,波特率发动机其实就是分频器,APB始终进行分频,得到发送和接收移位的时钟。 看一下,这里始终输入是fpclkx,X=1或2,usart1挂载在APB2,所以就是pclk2的时钟一般是72M,其他的usart都挂载在ABB1,所以是pclk1的时钟一般是36M。之后这个时钟进行一个分频,除一个usartdiv的分频系数,usartdiv里面就是右边这样是一个数值,并且分为了整数部分和小数部分,因为有些波特率用72兆除以个整数的话,可能除不进会有误差。所以这里分屏系数是支持小数点后4位的,分屏就更加精准了。之后分屏完之后还要再除个16。得到发送器时钟和接收器时钟,通向控制部分,然后右边这里,如果TE(TXEnable)为1,就是发送器使能了,发送部分的波特率就有效,如果RE(RX Enable)为1,就是接收器使能了,接收部分的波特率就有效

05.USART基本结构

在这里插入图片描述

波特率发生器用来分频的

流程:这个简化结构图,这就是USART最基本的结构。最左边这里是波特率发生器,用于产生约定的通信速率,始终来源是pcrk2或1,经过波特率发生器分频后产生的时钟通向发送控制器和接收控制器,发送控制器和接收控制器用来控制发送移位和接收移位。之后由发送数据进入器和发送移位进行器这两个进行器的配合,将数据一位的移出去。通过gpio口的复用输出,输出的tx引脚,产生串口协议规定的波形,这里换来几个右移的符号,就是代表这个移位寄存器是往右移的,是低位先行。当数据由数据运气转移到一位进行器时,会自己一个TXT的标志位。我们判断这个标志位就可以知道是不是可以写下个数据了。然后接收部分也是类似的。RX引脚的波形,通过gpio口输入,在接收控制器的控制下,一位的移入接收移位进行器,这里换了右移的符号也是右移的,因为是低位线型,所以要从左边开始移进来, 接收到帧数据后,数据就会统一转运到接收数据寄存器。在转移的同时是一个RXNE标志为我们检查这个标志位就可以知道是不是收到数据的。同时这个标志位也可以去申请中断,这样就可以在收到数据时直接进入中断函数,然后快速的读取和保存数据。那右边这实际上有4个寄存器,但是在软件层面只有一个DR寄存器可以供我们读写,写入DR时,数据走上面这条路进行发送,读取DR,数据走下面这条路进行接收。这就是USART进行串口数据收发的过程。最后右下角是一个开关控制,就是配置完成之后,用CMD开启一下外设,这个也是常规操作了。

06.数据帧

在这里插入图片描述

空闲帧和断开帧

然后下面这两个波形,一个是空闲帧,就是从头到尾都是1,还有一个是断开帧,从头到尾都是0,这两个数据帧,是局域网协议用的,我们串口用不着,不用管的

在这里插入图片描述

07.起始位侦测

在这里插入图片描述

这是多一位bit数据进行16次采样

这里展示的是USA rt的起始位侦测。当输入电路侦测的一个数据帧的起始位后,就会以波特率的频率连续采量一增数据。同时从起始位开始,采样位置就要对齐到位的正中间,只要第1位对起来,后面就肯定都是对齐的。那为了实现这些功能,首先输入的这部分电路对采样始终进行了细分,它会以波特率的16倍频率进行采样,也就是在一位的时间里可以进行16次采样,然后它的策略是最开始空闲状态高电平,那采样就一直是1在某个位置突然踩到一个0,那么就说明在这两次采样之间出现了下降,沿。如果没有任何噪声,那之后就应该是起始位了。在74位会进行连续16次采样,没有造成的话,这16次采样肯定就都是0,这没问题,但是实际电路还是会存在一些造成的。所以这里即使出现下降也呢后续也要再采样机制,以防万一。那根据手册的描述,这个接收电路还会在下电源之后的第3次、 5次、七次进行一批采样。在第8次、九次、十次再进行一批采样,且这两批材料都要要求每三位里面至少应该有两个0,如果没有造成,那肯定全是临海满足情况。如果有一些轻微的噪声导致这里三位里面只有两个0,另一个是1,那也算是检测到了70位。但是在状态禁用器里会自己一个nee,噪声标志为就是提醒你一下,数据我是收到了,但是有噪声你悠着来用。如果这里三位里面只有一个0,那就不算检测到了,其实为可能前面那个下降沿是造成导致的。这时的电路就忽略前面的数据,重新开始捕捉下两眼。这就是stm32的串口,在接收过程中对噪声的处理。如果通过了这个起始位侦测,那接收状态就由空闲变为接收起始位。同时第八九十次采样的位置,就正好是骑士位的正中间,之后接收数据位时就都在第八九十次进行采样,这样就能保证采样位置在位的正中间了。这就是骑手侦测和采样位置对齐的策略。那紧跟着我们就可以看这个数据采样的流程了。这里从1~16是一个数据位的时间长度,在一个数据位有16个采样时钟,由于其所为侦测已经对齐了采样时钟,所以里就直接在第八九十次采样数据位,未来保证数据的可靠性。这里是连续采样三次,没有造成了理想情况下,这三次肯定全为1或者全为0,权威1就认为收到了1,权威0就认为收到了0。如果有造成导致三次采样不是权威1或者权威0,那它就按照2:1的规则来,两次为一就认为收到了1,两次为0就认为收到了0。在这种情况下噪声标志位nee也会致一,告诉你我收到数据了,但是有噪声你悠着点用。这就是检测噪声的数据采样。可见stm32对这个电路的设计考虑还是很充分的。那最后我们再来看一下波特率发生器。

08.数据采样

在这里插入图片描述

09.波特率发生器

在这里插入图片描述

波特率发生器

那最后我们再来看一下波特率发生器,波特率发生器就是分频器,看一下发生器和接收器的波特率由波特率寄存器brr的div确定。下面这个图就是br计算器,里面就是分频系数div, DIY分为整数部分和小数部分,可以实现更细腻的分屏。那波特率和分频系数的关系可以由这个计算公式进行计算,波特率等于pclk2或1的时钟频率除以16倍的div,为什么这里多个16?看上面这个图就明白了吧?因为它内部还有一个16倍波特率的采样时钟,所以这里输入时钟除以div,等于16倍的波特率,最终计算波特率自然要多出一个16了。举个例子,比如我呀配置USA ID1为9600的battery,那如何配置这个br计算器呢?我们带入公司就是9600等于USA rt1的始终是72兆除16倍的div。借得DIY=72兆,除9600除16。然后可以用计算器算一下。72兆除9600除16,最终等于468.75。这是一个带小数的分屏系数,最终写了禁用器还需要转换成二进制函。这个问时代计算器居然不支持带小数的近似转换,那我们就从网上随便找个精子转换器,输入十进制的468.75,转换成二进制,最终得到二进制数是111010100.11,所以最终写的这个寄存器就是整数部分为111010100,前面多出来的补0享受部分为11,后面多出来的布林,这就是根据波特率写br计算机的方法,了解一下。不过我们用空函数配置的话就非常方便。需要多少波特率直接写就行了。库函数会自动帮我们算。

10.USB转串口模块的内部电路图

image-20250317143310043

先看一下这个USB转串口模块的原理图案,这个图展示的就是我们套件里USB转串口模块的内部电路图,这里还有几点注意事项,需要跟大家说明一下,大概看一下,最左边这里是USB的端口, USB有4根儿线,D+,D-、 vcc, USB标准供电是5符号,然后中间地震和低负是通信线,走的也是USB协议。所以这里需要加一个C去340芯片转换一下,转换之后输出的就是TXT和xd,是串口协议,最后通过这里的排针引出来,那需要注意的就是这边的供电策略。首先所有的店都是从这个vcc正5幅来的,然后vcc正五幅,通过这个稳压管电路进行稳压,得到vcc正3.3伏。之后vcc正5伏和vcc正3.3伏都通过排针引出来了。所以这个第6角和第4角是分别有5伏和3.3伏输出的,那很多人迷惑的是这个第五角,板子上标的是vcc,这个赢家通过原理图可以看到,它是通向了C去340芯片的vcc上。所以这个第5讲实际上是C去340的电源输入角,一般我们这个模块的排针会有一个跳线帽,这个跳线报需要插在四五角或者五六角上,右边这里也有文字说明,短路五伏到vcc, C区340供电为5伏, ttl电平为5伏,短路三维三到位是谁?Cg340的供电为3.3伏, ttl电平为3.3负,所以这个跳线报是用来选择通信电平的,也是给ch340芯片供电的,所以最好不要拿掉。如果你拿掉了,就相当于这整个芯片没有供电。不过神奇的是我试了一下,即使把天线帽拔掉,不跟芯片供电,这个串口还是能正常工作,可能是从别的地方汲取的一些电流吧。当然还有可能是别的原因,我观察了一下,我们这个模块的电路是有些变动的,如果不查抢险帽,我测试了通信电平为3.3伏,不过为了稳定,最好还是要插上掉线,包含那我们SDm32通信需要3.3伏,所以把跳键帽插在这里的四五角上就行了。那供电就只剩一个5伏角了。所以这个供电有点折磨人,要么选择5伏电瓶,剩下供电角就只有3.3伏,要么选择3.3伏电瓶,剩下供电角就是5伏。所以这个攻坚点的设计有点不太方便。不过考虑到电瓶的5伏和3.3伏可以互相兼容。所以如果你既需要通信又需要供电,那就要保证供电是正确的就行了。通信电瓶没法一致,这应该也没问题。然后右边还有引脚说明看一下, 5伏甲是从USB取出来的, 5伏电源输出, vcc是模块供电的。3V34模块稳压出来的3.3伏电源输出, txd串口数据输出, X里串口速率输入,建立模块负极,这就是这些引脚说明。最后右下角这些是指示灯和电源滤波。这里有pwr,电源指示灯和TXT。2xd的指示灯。如果intel上有数据传输,这两个字的灯会对应闪烁,方便我们观察。这就是这整个模块的内部电路图。

9-3 串口发送&串口发送+接收

串口接收数据的时候可以选择是轮询模式还是中断模式,每个基本都会有对应的中断,可以在对应的startup_stm32f10x_md.s文件里面看到,比如usart的中断函数就是USART1_IRQHandler,这个函数需要自己在文件里面重新定义,填充自己需要的逻辑

01.串口简介

串口通讯(Serial Communication)是一种设备间非常常用的串行通讯方式,因为它简单便捷,因此大部分电子设备都支持该通讯方式, 电子工程师在调试设备时也经常使用该通讯方式输出调试信息。

在计算机科学里,大部分复杂的问题都可以通过分层来简化。如芯片被分为内核层和片上外设;STM32标准库则是在寄存器与用户代码之间的软件层。 对于通讯协议,我们也以分层的方式来理解,最基本的是把它分为物理层和协议层。物理层规定通讯系统中具有机械、电子功能部分的特性, 确保原始数据在物理媒体的传输。协议层主要规定通讯逻辑,统一收发双方的数据打包、解包标准。 简单来说物理层规定我们用嘴巴还是用肢体来交流,协议层则规定我们用中文还是英文来交流。

02.串口相关API

2.1 USART_Init
/**
  * @brief  Initializes the USARTx peripheral according to the specified
  *         parameters in the USART_InitStruct .
  * @param  USARTx: Select the USART or the UART peripheral. 
  *   This parameter can be one of the following values:
  *   USART1, USART2, USART3, UART4 or UART5.
  * @param  USART_InitStruct: pointer to a USART_InitTypeDef structure
  *         that contains the configuration information for the specified USART 
  *         peripheral.
  * @retval None
  */
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct)
功能:
	根据 USART_InitStruct 中指定的参数初始化外设 USARTx 寄存器
参数:
   USARTx:x 可以是 1,2 或者 3,来选择 USART 外设
   USART_InitStruct:指向结构 USART_InitTypeDef 的指针,包含了外设 USART 的配置信息。
返回值:
	无      
   
2.2 USART_InitTypeDef
/** 
  * @brief  USART Init Structure definition  
  */ 
typedef struct
{
  uint32_t USART_BaudRate;            /*!< This member configures the USART communication baud rate.
                                           The baud rate is computed using the following formula:
                                            - IntegerDivider = ((PCLKx) / (16 * (USART_InitStruct->USART_BaudRate)))
                                            - FractionalDivider = ((IntegerDivider - ((u32) IntegerDivider)) * 16) + 0.5 */

  uint16_t USART_WordLength;          /*!< Specifies the number of data bits transmitted or received in a frame.
                                           This parameter can be a value of @ref USART_Word_Length */

  uint16_t USART_StopBits;            /*!< Specifies the number of stop bits transmitted.
                                           This parameter can be a value of @ref USART_Stop_Bits */

  uint16_t USART_Parity;              /*!< Specifies the parity mode.
                                           This parameter can be a value of @ref USART_Parity
                                           @note When parity is enabled, the computed parity is inserted
                                                 at the MSB position of the transmitted data (9th bit when
                                                 the word length is set to 9 data bits; 8th bit when the
                                                 word length is set to 8 data bits). */
 
  uint16_t USART_Mode;                /*!< Specifies wether the Receive or Transmit mode is enabled or disabled.
                                           This parameter can be a value of @ref USART_Mode */

  uint16_t USART_HardwareFlowControl; /*!< Specifies wether the hardware flow control mode is enabled
                                           or disabled.
                                           This parameter can be a value of @ref USART_Hardware_Flow_Control */
} USART_InitTypeDef;

USART_WordLength

/** @defgroup USART_Word_Length 
  * @{
  */ 
  
#define USART_WordLength_8b                  ((uint16_t)0x0000)
#define USART_WordLength_9b                  ((uint16_t)0x1000)

USART_StopBits

/** @defgroup USART_Stop_Bits 
  * @{
  */ 
  
#define USART_StopBits_1                     ((uint16_t)0x0000)
#define USART_StopBits_0_5                   ((uint16_t)0x1000)
#define USART_StopBits_2                     ((uint16_t)0x2000)
#define USART_StopBits_1_5                   ((uint16_t)0x3000)

USART_Parity

/** @defgroup USART_Parity 
  * @{
  */ 
  
#define USART_Parity_No                      ((uint16_t)0x0000)
#define USART_Parity_Even                    ((uint16_t)0x0400)
#define USART_Parity_Odd                     ((uint16_t)0x0600) 

USART_Mode

/** @defgroup USART_Mode 
  * @{
  */ 
  
#define USART_Mode_Rx                        ((uint16_t)0x0004)
#define USART_Mode_Tx                        ((uint16_t)0x0008)

USART_HardwareFlowControl

/** @defgroup USART_Hardware_Flow_Control 
  * @{
  */ 
#define USART_HardwareFlowControl_None       ((uint16_t)0x0000)
#define USART_HardwareFlowControl_RTS        ((uint16_t)0x0100)
#define USART_HardwareFlowControl_CTS        ((uint16_t)0x0200)
#define USART_HardwareFlowControl_RTS_CTS    ((uint16_t)0x0300)
2.3 USART_Cmd
/**
  * @brief  Enables or disables the specified USART peripheral.
  * @param  USARTx: Select the USART or the UART peripheral. 
  *         This parameter can be one of the following values:
  *           USART1, USART2, USART3, UART4 or UART5.
  * @param  NewState: new state of the USARTx peripheral.
  *         This parameter can be: ENABLE or DISABLE.
  * @retval None
  */
void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState)
功能:
	使能或者失能 USART 外设
参数:
   USARTx:x 可以是 1,2 或者 3,来选择 USART 外设
   NewState: 外设 USARTx 的新状态这个参数可以取:ENABLE 或者 DISABLE
返回值:
	无           
2.4 USART_SendData
/**
  * @brief  Transmits single data through the USARTx peripheral.
  * @param  USARTx: Select the USART or the UART peripheral. 
  *   This parameter can be one of the following values:
  *   USART1, USART2, USART3, UART4 or UART5.
  * @param  Data: the data to transmit.
  * @retval None
  */
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data)
功能:
   通过外设 USARTx 发送单个数据
参数:
   USARTx:x 可以是 1,2 或者 3,来选择 USART 外设
   Data: 待发送的数据
返回值:
	无      
2.5 USART_ReceiveData
/**
  * @brief  Returns the most recent received data by the USARTx peripheral.
  * @param  USARTx: Select the USART or the UART peripheral. 
  *   This parameter can be one of the following values:
  *   USART1, USART2, USART3, UART4 or UART5.
  * @retval The received data.
  */
uint16_t USART_ReceiveData(USART_TypeDef* USARTx)
功能:
   返回 USARTx 最近接收到的数据
参数:
   USARTx:x 可以是 1,2 或者 3,来选择 USART 外设
返回值:
	接收到的字      

03. 串口发送接线图

在这里插入图片描述

04. USB转串口模块

在这里插入图片描述

05. 串口发送程序示例

uart.h

#ifndef __UART_H__
#define __UART_H__

#include "stm32f10x.h"           

void uart_init(void);

void uart_send_byte(uint8_t byte);


#endif /**/

uart.c

#include "uart.h"

void uart_init(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;
	USART_InitTypeDef USART_InitStruct;
	
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

	//GPIO初始化  PA9 TX
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStruct);

	USART_InitStruct.USART_BaudRate = 9600;
	USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USART_InitStruct.USART_Mode = USART_Mode_Tx;
	USART_InitStruct.USART_Parity = USART_Parity_No;
	USART_InitStruct.USART_StopBits = USART_StopBits_1;
	USART_InitStruct.USART_WordLength = USART_WordLength_8b;
	
	USART_Init(USART1, &USART_InitStruct);
	
	USART_Cmd(USART1, ENABLE);
}

void uart_send_byte(uint8_t byte)
{
	USART_SendData(USART1, byte);
	
	while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}

main.c

#include "stm32f10x.h"

#include "delay.h"
#include "oled.h"
#include "uart.h"

 int main(void)
 {	 
	 
	 //初始化
	 OLED_Init();
	 
	 uart_init();

	 //显示一个字符
	 OLED_ShowChar(1, 1, 'A');

	 uart_send_byte(0x41);
	 
	 while(1)
	 {
		 
	 }
	 
	 return 0;
 }

运行结果

在这里插入图片描述

06. 串口发送支持printf

配置:

在这里插入图片描述

uart.h

#ifndef __UART_H__
#define __UART_H__

#include "stm32f10x.h"           

void uart_init(void);

void uart_send_byte(uint8_t byte);

void uart_send_array(uint8_t *arr, uint16_t len);


void uart_send_string(char *str);

void uart_send_number(uint32_t num, uint8_t len);

#endif /**/


uart.c

#include "uart.h"

#include <stdio.h>

void uart_init(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;
	USART_InitTypeDef USART_InitStruct;
	
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

	//GPIO初始化  PA9 TX
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStruct);

	USART_InitStruct.USART_BaudRate = 9600;
	USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USART_InitStruct.USART_Mode = USART_Mode_Tx;
	USART_InitStruct.USART_Parity = USART_Parity_No;
	USART_InitStruct.USART_StopBits = USART_StopBits_1;
	USART_InitStruct.USART_WordLength = USART_WordLength_8b;
	
	USART_Init(USART1, &USART_InitStruct);
	
	USART_Cmd(USART1, ENABLE);
}

void uart_send_byte(uint8_t byte)
{
	USART_SendData(USART1, byte);
	
	while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}


void uart_send_array(uint8_t *arr, uint16_t len)
{
	uint16_t i;
	for (i = 0; i < len; i++)
	{
		uart_send_byte(arr[i]);
	}
}


void uart_send_string(char *str)
{
	uint16_t i = 0;
	
	while(*(str + i) != '\0')
	{
		uart_send_byte(str[i]);
		i++;
	}
}

//x的y次方
uint32_t uart_pow(uint32_t x, uint32_t y)
{
	uint32_t result = 1;
	
	while(y)
	{
		result *= x;
		y--;
	}
	
	return result;
}

void uart_send_number(uint32_t num, uint8_t len)
{
	uint8_t i;
	for (i = 0; i < len; i++)
	{
		uart_send_byte(num / uart_pow(10, len - i - 1) % 10 + '0');
	}
	
}

int fputc(int ch, FILE *fp)
{
	uart_send_byte(ch);
	
	return ch;
}

main.c

#include "stm32f10x.h"
#include <stdio.h>
#include "delay.h"
#include "oled.h"
#include "uart.h"

 int main(void)
 {	
	 
	 uint8_t arr[] = {0x42, 0x43, 0x44, 0x45, 0x46};

	 //初始化
	 OLED_Init();
	 
	 uart_init();

	 //显示一个字符
	 OLED_ShowChar(1, 1, 'A');

#if 0
	 uart_send_byte('B');
	 
	 //发送数组
	 uart_send_array(arr, 5);
	 
	 //发送字符串
	 uart_send_string("hello world\r\n");
	 uart_send_string("1234567890\r\n");

	 uart_send_number(1234, 4);
#endif

	printf("num = %d\r\n", 6666);
	
	 while(1)
	 {
		 
	 }
	 
	 return 0;
 }


运行结果

在这里插入图片描述

07. 串口发送支持printf_v2

uart.h

#ifndef __UART_H__
#define __UART_H__

#include "stm32f10x.h"           

void uart_init(void);

void uart_send_byte(uint8_t byte);

void uart_send_array(uint8_t *arr, uint16_t len);


void uart_send_string(char *str);

void uart_send_number(uint32_t num, uint8_t len);

void uart_printf(char *format, ...);

#endif /**/

uart.c

 #include "uart.h"
 ​
 #include <stdio.h>
 #include <stdarg.h>
 ​
 void uart_init(void)
 {
     GPIO_InitTypeDef GPIO_InitStruct;
     USART_InitTypeDef USART_InitStruct;
     
     
     RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
 ​
     //GPIO初始化  PA9 TX
     GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
     GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
     GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
     GPIO_Init(GPIOA, &GPIO_InitStruct);
 ​
     USART_InitStruct.USART_BaudRate = 9600;
     USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
     USART_InitStruct.USART_Mode = USART_Mode_Tx;
     USART_InitStruct.USART_Parity = USART_Parity_No;
     USART_InitStruct.USART_StopBits = USART_StopBits_1;
     USART_InitStruct.USART_WordLength = USART_WordLength_8b;
     
     USART_Init(USART1, &USART_InitStruct);
     
     USART_Cmd(USART1, ENABLE);
 }
 ​
 void uart_send_byte(uint8_t byte)
 {
     USART_SendData(USART1, byte);
     
     while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
 }
 ​
 ​
 void uart_send_array(uint8_t *arr, uint16_t len)
 {
     uint16_t i;
     for (i = 0; i < len; i++)
     {
         uart_send_byte(arr[i]);
     }
 }
 ​
 ​
 void uart_send_string(char *str)
 {
     uint16_t i = 0;
     
     while(*(str + i) != '\0')
     {
         uart_send_byte(str[i]);
         i++;
     }
 }
 ​
 //x的y次方
 uint32_t uart_pow(uint32_t x, uint32_t y)
 {
     uint32_t result = 1;
     
     while(y)
     {
         result *= x;
         y--;
     }
     
     return result;
 }
 ​
 void uart_send_number(uint32_t num, uint8_t len)
 {
     uint8_t i;
     for (i = 0; i < len; i++)
     {
         uart_send_byte(num / uart_pow(10, len - i - 1) % 10 + '0');
     }
     
 }
 ​
 int fputc(int ch, FILE *fp)
 {
     uart_send_byte(ch);
     
     return ch;
 }
 ​
 ​
 void uart_printf(char *format, ...)
 {
     char str[128];
     
     va_list arg;
     va_start(arg, format);
     vsprintf(str, format, arg);
     va_end(arg);
     
     uart_send_string(str);
 }
 ​
 ​

main.c

 #include "stm32f10x.h"
 #include <stdio.h>
 #include "delay.h"
 #include "oled.h"
 #include "uart.h"
 ​
  int main(void)
  {  
     char string[100];
      uint8_t arr[] = {0x42, 0x43, 0x44, 0x45, 0x46};
 ​
      //初始化
      OLED_Init();
      
      uart_init();
 ​
      //显示一个字符
      OLED_ShowChar(1, 1, 'A');
 ​
 #if 0
      uart_send_byte('B');
      
      //发送数组
      uart_send_array(arr, 5);
      
      //发送字符串
      uart_send_string("hello world\r\n");
      uart_send_string("1234567890\r\n");
 ​
      uart_send_number(1234, 4);
 ​
 ​
     printf("num = %d\r\n", 6666);
 #endif
 ​
 ​
     sprintf(string, "\r\nnum=%d", 3333);
     uart_send_string(string);
 ​
 ​
     uart_printf("\r\nnum = %d\r\n", 4444);
     uart_printf("\r\n");
 ​
      while(1)
      {
          
      }
      
      return 0;
  }
 ​

测试结果

num=3333
num = 4444

08. 串口发送和接收接线图

在这里插入图片描述

09. 串口接收示例(轮询模式)

uart.h

#ifndef __UART_H__
#define __UART_H__

#include "stm32f10x.h"           

void uart_init(void);

void uart_send_byte(uint8_t byte);

void uart_send_array(uint8_t *arr, uint16_t len);


void uart_send_string(char *str);

void uart_send_number(uint32_t num, uint8_t len);

void uart_printf(char *format, ...);

#endif /**/

uart.c

#include "uart.h"

#include <stdio.h>
#include <stdarg.h>

void uart_init(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;
	USART_InitTypeDef USART_InitStruct;
	
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

	//GPIO初始化  PA9 TX
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStruct);
	
	//GPIO初始化  PA10 RX
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStruct);

	USART_InitStruct.USART_BaudRate = 9600;
	USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
	USART_InitStruct.USART_Parity = USART_Parity_No;
	USART_InitStruct.USART_StopBits = USART_StopBits_1;
	USART_InitStruct.USART_WordLength = USART_WordLength_8b;
	
	USART_Init(USART1, &USART_InitStruct);
	
	USART_Cmd(USART1, ENABLE);
}

void uart_send_byte(uint8_t byte)
{
	USART_SendData(USART1, byte);
	
	while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}


void uart_send_array(uint8_t *arr, uint16_t len)
{
	uint16_t i;
	for (i = 0; i < len; i++)
	{
		uart_send_byte(arr[i]);
	}
}


void uart_send_string(char *str)
{
	uint16_t i = 0;
	
	while(*(str + i) != '\0')
	{
		uart_send_byte(str[i]);
		i++;
	}
}

//x的y次方
uint32_t uart_pow(uint32_t x, uint32_t y)
{
	uint32_t result = 1;
	
	while(y)
	{
		result *= x;
		y--;
	}
	
	return result;
}

void uart_send_number(uint32_t num, uint8_t len)
{
	uint8_t i;
	for (i = 0; i < len; i++)
	{
		uart_send_byte(num / uart_pow(10, len - i - 1) % 10 + '0');
	}
	
}

int fputc(int ch, FILE *fp)
{
	uart_send_byte(ch);
	
	return ch;
}


void uart_printf(char *format, ...)
{
	char str[128];
	
	va_list arg;
	va_start(arg, format);
	vsprintf(str, format, arg);
	va_end(arg);
	
	uart_send_string(str);
}

main.c

#include "stm32f10x.h"
#include <stdio.h>
#include "delay.h"
#include "oled.h"
#include "uart.h"

 int main(void)
 {	
	 uint16_t data = 0;
	 
	 OLED_Init();
	 
	 uart_init();

	 OLED_ShowChar(1, 1, 'A');


	 while(1)
	 {
		 if (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET)
		 {
			 data = USART_ReceiveData(USART1);
			 
			 OLED_ShowHexNum(1, 1, data, 2);
		 }
		 
	 }
	 
	 return 0;
 }

10. 串口接收示例(中断模式)

uart.h

 #ifndef __UART_H__
 #define __UART_H__
 ​
 #include "stm32f10x.h"           
 ​
 void uart_init(void);
 ​
 void uart_send_byte(uint8_t byte);
 ​
 void uart_send_array(uint8_t *arr, uint16_t len);
 ​
 ​
 void uart_send_string(char *str);
 ​
 void uart_send_number(uint32_t num, uint8_t len);
 ​
 void uart_printf(char *format, ...);
 ​
 uint8_t uart_getRxFlag(void);
 ​
 ​
 uint8_t uart_getRxData(void);
 ​
 ​
 #endif /**/
 ​
 ​
 ​

uart.c

 #include "uart.h"
 ​
 #include <stdio.h>
 #include <stdarg.h>
 ​
 uint8_t recvData;
 uint8_t recvFlag;
 ​
 void uart_init(void)
 {
     GPIO_InitTypeDef GPIO_InitStruct;
     USART_InitTypeDef USART_InitStruct;
     NVIC_InitTypeDef NVIC_InitStruct;
     
     
     RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
 ​
     //GPIO初始化  PA9 TX
     GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
     GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
     GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
     GPIO_Init(GPIOA, &GPIO_InitStruct);
     
     //GPIO初始化  PA10 RX
     GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
     GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
     GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
     GPIO_Init(GPIOA, &GPIO_InitStruct);
 ​
     USART_InitStruct.USART_BaudRate = 9600;
     USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
     USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
     USART_InitStruct.USART_Parity = USART_Parity_No;
     USART_InitStruct.USART_StopBits = USART_StopBits_1;
     USART_InitStruct.USART_WordLength = USART_WordLength_8b;
     
     USART_Init(USART1, &USART_InitStruct);
     
     //设置串口中断
     USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
     
     //设置中断分组
     NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
     NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
     NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
     NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
     NVIC_Init(&NVIC_InitStruct);
     
     USART_Cmd(USART1, ENABLE);
 }
 ​
 void uart_send_byte(uint8_t byte)
 {
     USART_SendData(USART1, byte);
     
     while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
 }
 ​
 ​
 void uart_send_array(uint8_t *arr, uint16_t len)
 {
     uint16_t i;
     for (i = 0; i < len; i++)
     {
         uart_send_byte(arr[i]);
     }
 }
 ​
 ​
 void uart_send_string(char *str)
 {
     uint16_t i = 0;
     
     while(*(str + i) != '\0')
     {
         uart_send_byte(str[i]);
         i++;
     }
 }
 ​
 //x的y次方
 uint32_t uart_pow(uint32_t x, uint32_t y)
 {
     uint32_t result = 1;
     
     while(y)
     {
         result *= x;
         y--;
     }
     
     return result;
 }
 ​
 void uart_send_number(uint32_t num, uint8_t len)
 {
     uint8_t i;
     for (i = 0; i < len; i++)
     {
         uart_send_byte(num / uart_pow(10, len - i - 1) % 10 + '0');
     }
     
 }
 ​
 int fputc(int ch, FILE *fp)
 {
     uart_send_byte(ch);
     
     return ch;
 }
 ​
 ​
 void uart_printf(char *format, ...)
 {
     char str[128];
     
     va_list arg;
     va_start(arg, format);
     vsprintf(str, format, arg);
     va_end(arg);
     
     uart_send_string(str);
 }
 ​
 void USART1_IRQHandler(void)
 {
     if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
     {
         recvData = USART_ReceiveData(USART1);
         recvFlag = 1;
     
         USART_ClearITPendingBit(USART1, USART_IT_RXNE);
     }
 ​
 }
 ​
 uint8_t uart_getRxFlag(void)
 {
     if (1 == recvFlag)
     {
         recvFlag = 0;
         return 1;
     }
     return 0;
 }
 ​
 ​
 uint8_t uart_getRxData(void)
 {
     return recvData;
 }
 ​

main.c

 #include "stm32f10x.h"
 #include <stdio.h>
 #include "delay.h"
 #include "oled.h"
 #include "uart.h"
 ​
 ​
  int main(void)
  {  
      uint16_t data = 0;
      
      OLED_Init();
      
      uart_init();
      
      //中断分组
      NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
 ​
      OLED_ShowChar(1, 1, 'A');
 ​
 ​
      while(1)
      {
         if (1 == uart_getRxFlag())
         {
             data = uart_getRxData();
             uart_send_byte(data);
             OLED_ShowHexNum(1, 1, data, 2);
         }
          
      }
      
      return 0;
  }
 ​
 ​
  
 ​

9-4 USART串口数据包

01.串口简介

串口通讯(Serial Communication)是一种设备间非常常用的串行通讯方式,因为它简单便捷,因此大部分电子设备都支持该通讯方式, 电子工程师在调试设备时也经常使用该通讯方式输出调试信息。

在计算机科学里,大部分复杂的问题都可以通过分层来简化。如芯片被分为内核层和片上外设;STM32标准库则是在寄存器与用户代码之间的软件层。 对于通讯协议,我们也以分层的方式来理解,最基本的是把它分为物理层和协议层。物理层规定通讯系统中具有机械、电子功能部分的特性, 确保原始数据在物理媒体的传输。协议层主要规定通讯逻辑,统一收发双方的数据打包、解包标准。 简单来说物理层规定我们用嘴巴还是用肢体来交流,协议层则规定我们用中文还是英文来交流。

02.HEX数据包

在这里插入图片描述

03.文本数据包

在这里插入图片描述

04.HEX数据包接收

在这里插入图片描述

05.文本数据包接收

在这里插入图片描述

我是和hex这两种模式。再看组长里面作品都是原始的字节数据本身,而在文本数据包里面,每个字节就经过了一层编码和译码,这种表现出来的就是文本格式,但实际上每个文本字符背后其实都还是一个字节的hack数据,对吧?那么看一下,这里我同样给出了固定包装和可变包装这两种模式。由于数据译码成了字符形式,这就会存在大量的字符可以作为包头包围,可以有效避免载荷和bottle包围重复的问题。比如我这里规定的就是以at这个字符作为包头,你一范锡刚、范锡刚n也就是换行,这两个字符作为包围,在载荷数据中间可以出现除了包头包尾的任意字符,这很容易做到。所以文本数据包基本不用担心载荷和bottle包围重复的问题,使用非常灵活,可变包场各种字母符号、数字都可以随意使用。当我们接收到载荷数据之后,得到的就是一个字符串,在软件中再对字符串进行操作和判断,就可以实现各种指令控制的功能了。而且字符串数据包表达的意义很明显,可以把字符串数据包直接打印到串口助手上,什么指令什么数据一眼就能看明白。所以这个文本数据包通常会以换行作为包围,这样在打印的时候就可以一行的显示了非常方便。那H,数据包和文本数据包这两种对比下来,其实也是各有优缺点。汉克数据包优点是传输最直接,解析数据非常简单,比较适合一些模块发送原始的数据,比如一些使用串口通信的陀螺仪,温湿度传感器,缺点就是灵活性不足,再和容易和bottom包围重复,文本数据包呢优点是数据直观易理解,非常灵活,比较适合一些输入指令进行人机交互的场合。比如蓝牙模块常用的at指令, cnc和3D打印机常用的g代码都是文本数据包的格式,那缺点就是解析效率低,比如你发送一个数100,看这个数据包就是一个字节100完事。文本数据包就得是三个字节的字符, 100,收到之后还要把字符转换成数据,才能得到100。所以说我们需要根据实际场景来选择和设计数据包格式。好,数据包格式的定义讲完了,接下来我们就来学一下数据包的收发流程。首先是数据包的发送,其实数据包的发送非常简单,不用说大家应该也都能想到在hex数据包这里,我如果想发送这样一个数据包,就定义一个宿主填充数据,然后用上下节我们写过的森德尔瑞一发就完事了,对吧?文本数据包这里也很简单,写一个字符串,然后第2种生的string一发送也完事了对吧?所以说发送这个数据包是很简单的,因为发生过程是完全自主可控的,小方啥就发啥,我们写代码的时候也能感受到串口发送比接收简单多了。那接下来接收一个数据包,这就比较复杂了。我们来学习一下,我这里演示了固定包场hex数据包的结合方法和可变包场文本数据包的接收方法。其他的数据包也都可以套用这个形式。等会儿我们写程序就会根据这里面的流程来,我们先看一下如何来接收这个固定包场的hive数据包。首先根据之前的代码,我们知道每收到一个字节,程序都会进一遍中断,在中断函数里我们可以拿到这一个字节,但拿到之后我们就得退出中断了。所以每拿到一个数据都是一个独立的过程。而对于数据包来说,很明显它具有前后关联性。包头之后是数据之后是8尾,对包头数据和包围这三种状态,我们都需要有不同的处理逻辑。所以在程序中我们需要设计一个能记住不同状态的机制,在不同状态执行不同的操作,同时还要进行状态的合理转移。这种程序设计思维就叫做状态机。在这里我们就使用状态机的方法来接收一个数据包。要想设计一个好的状态机程序,画一个这样的状态转移图是必要的。我们看一下,对于上面这样一个固定包长hex数据包来说,我们可以定义三个状态,第1个状态是等待包头,第2个状态是接收数据,第3个状态是等待包围,每个状态需要用一个变量来标志一下。比如我这里用变量s来标志,三个状态依次为s=0, s=1, s=2,这一点类似于治标志未含,只不过标志位只有0和1,而状态机是多标志位状态的一种方式,然后执行流程是最开始s=0,收到一个数据进中断,根据s=0进入第1个状态的程序,判断数据是不是包头ff。如果4ff则代表收到报酬,之后自s=1,退出中断结束,这样下次再进中断。根据s=1就可以进行接收数据的程序了。那在第1个状态,如果收到的不是ff,就证明数据包没有对齐,我们应该等待数据包包头的出现,这时状态就仍然是0,下次进中断就还是判断包头的逻辑,直到出现ff才能转到下一个状态。那之后出现了ff,我们就可以转移到接收数据的状态了。这时再收到数据,我们就直接把它存在数组中。另外再用一个变量记录收纳多少个数据,如果没收购4个数据,就一直是接受状态。如果收购了就致s=2,下次进入那时就可以进入下一个状态了。那最后一个状态就是等待包围了,判断数据是不是fe,正常情况应该是F1,这样就可以治s=0。回到最初的状态,开始下一个轮回。当然也有可能这个数据不是F1,比如数据和包头重复导致包头位置判断错了。那这个包围位置就有可能不是F1这时就可以进入重复等待包围的状态,直到接收到真正的包围。这样加入包围的判断,更能预防因数据和包头重复造成的错误,还这就是使用状态机接收数据包的思路。这个状态机其实是一种很广泛的编程思路。在很多地方都可以用到。使用的基本步骤是先根据项目要求定义状态,换几个圈,然后考虑好各个状态在什么情况下会进行转移,如何转移,画好线和转移条件。最后根据这个图来进行编程,这样思维就会非常清晰了。比如你要做个菜单,就可以用到状态机的思维,按什么键切换什么菜单,执行什么样的程序,还有一些芯片内部逻辑也会用到状态机,比如芯片什么情况下进入待机状态,什么情况下进入工作状态?这也是状态机的应用。希望大家可以研究一下,这里的编程肯定会有帮助。那接下来继续我们来看一下这个可变包装文本数据包的接收流程,同样也是利用状态机定义三个状态,第1个状态等待包头判断收到的是不是我们规定的at符号。如果收到at就进入接收状态。在这个状态下依次接收数据。同时这个状态还应该要兼具等待包围的功能,因为这是可变包场。我们接收数据的时候也要时刻监视,是不是收到包围了,一旦收到包围了就结束。那这里这个状态的逻辑就应该是收到一个数据判断是不是杠r,如果不是这正常接收,如果是则不接收同时跳到下一个状态,等待包围感恩。因为我这里数据包有两个包围杠-n,所以需要第3个状态。如果只有一个包围,那在出现包围之后就可以直接回到初始状态了。只需要两个状态就行。因为接收数据和等待包围需要在一个状态里同时进行下,由于串口的bottle包围不会出现在数据中,所以基本不会出现数据错位的现象。这就是使用状态机接收文本数据包的方法。好,到这里,我们这个数据包的定义分裂优缺点和注意事项就讲完了。

9-5 串口收发HEX数据包&串口收发文本数据包

9-6 FlyMCU串口下载和STLINK Utility

01.串口连接电路图

在这里插入图片描述

image-20250318213007469

串口下载只能使用UART1

我们需要配置工程生成一个HEX文件,打开工程选项进入Output选项卡勾选“创建HEX文件”的选项,编译后在工程目录的Objects文件夹里会生成对应的HEX文件,这就是串口下载所需的程序文件。整个过程和51单片机的操作流程类似。接下来使用FlyMcu软件进行下载,首先在软件界面选择对应的COM口号,波特率保持默认的115200(和串口助手的配置一致),点击三个点的按钮找到刚才生成的HEX文件并打开,其他配置暂时保持默认即可。开始下载前必须配置STM32的Boot引脚:第一步找到开发板上的Boot0跳线帽,将其从默认位置(左边两个针脚)拔下来,插到右边两个针脚使Boot0引脚置为高电平;第二步按下复位键让芯片重新启动,这里要注意每次切换Boot引脚后必须复位才能生效,因为STM32只在复位瞬间读取Boot引脚状态。此时芯片会进入系统存储器中的Bootloader程序,这个程序固化在ROM区0x1FFFF000地址处,专门负责通过串口接收数据并写入主闪存(0x08000000)。返回FlyMcu点击开始编程,软件就会通过串口发送HEX文件数据,下载完成后将Boot0跳线帽恢复原位并再次复位,LED闪烁程序就能正常运行了。 每次下载都要手动切换跳线帽确实麻烦,这个问题可以通过设计“一键下载电路”解决。观察串口模块的硬件电路,除了TXD和RXD通信引脚,还有RTS和DTR流控引脚。虽然我们不需要流控功能,但可以将RTS和DTR引脚改造成控制信号:用RTS控制Boot0引脚电平,DTR控制复位引脚。具体实现需要外围电路支持,通常使用三极管搭建电平转换电路(网上搜索“STM32一键下载电路”能找到详细方案)。在FlyMcu软件的下拉菜单里配置DTR低电平触发复位、RTS高电平进入Bootloader模式,配合硬件电路后,点击下载时软件会自动拉低DTR复位芯片,同时拉高RTS使Boot0置位,整个过程无需手动操作跳线帽。 Bootloader本质上是ST公司预烧录在芯片内部的一段特殊程序,类似于手机的刷机模式。当Boot0=1时,芯片从系统存储器启动,执行这段程序循环监听串口数据,将接收到的程序代码写入主闪存区域。这就像机器人自我更新时需要启动一个小型辅助程序来完成核心程序替换,避免出现“拆掉旧电池就无法安装新电池”的悖论。主闪存(0x08000000)存放用户程序,系统存储器(0x1FFFF000)存放Bootloader,启动源的选择由Boot引脚决定:Boot0=0从主闪存启动,Boot0=1从系统存储器启动。特别注意芯片只在复位瞬间检测Boot引脚电平,因此每次切换后必须按复位键才能生效。 选项字节是存储在芯片内部特殊区域(0x1FFFF800-0x1FFFF80F)的配置参数,包含四大功能模块:读保护(设置为0xAA时禁止读取芯片程序,防止代码被窃取)、写保护(锁定指定Flash扇区防止误修改)、硬件参数(配置看门狗、停机模式等特性)和用户数据区(存储产品序列号等自定义信息)。在FlyMcu中勾选“编程时写入选项字节”可将配置同步到芯片,但若设置了写保护导致无法下载,就必须使用ST-Link Utility这类支持独立配置选项字节的工具来解除保护。例如当读保护开启时,Keil下载会报错,此时需要用ST-Link Utility连接芯片,在Target > Option Bytes界面解除读保护(操作后会自动擦除整个Flash)。 使用ST-Link Utility时,通过SWD接口连接开发板即可直接读写芯片内容。点击连接按钮后,软件会显示从0x08000000开始的Flash数据(刚擦除的芯片显示全FF),点击保存按钮可将程序导出为HEX或BIN格式(FlyMcu只能导出BIN却无法重新下载BIN的问题在此解决)。编程时支持HEX/BIN文件,还能单独修改选项字节参数,比FlyMcu更灵活。此外软件提供固件升级功能(ST-Link > Firmware Update),例如将固件从J29升级到J37版本。读取芯片信息时偶尔会遇到Flash容量显示异常(如C8T6显示256KB),这是因为ST生产线统一封装晶圆,实际使用只需遵循标称的64KB容量即可。 开发过程中常见问题包括:下载卡在初始阶段(检查Boot引脚是否置1并复位)、程序无法运行(Boot引脚未恢复原位)、读写保护冲突(用ST-Link Utility解除)。建议量产产品务必开启读保护,敏感数据存入选项字节用户区。对于频繁调试的场景,强烈推荐设计一键下载电路,或者利用FlyMcu的“编程后执行”功能(在软件添加跳转指令强制从0x08000000启动,但复位后失效)。理解Bootloader机制后,可进一步研究IAP在线升级技术,通过主程序调用Bootloader实现无线固件更新,这对物联网设备开发尤为重要。

STM32串口下载程序与Bootloader配置教程
一、工程配置与H1X文件生成
  1. 工程设置

    • 打开工程选项,进入"Output"选项卡

    • 勾选"创建H1X文件"选项

    • 编译工程后,在工程目录的Objects文件夹中生成H1X文件(串口下载所需程序文件)

  2. 编译验证

    • 编译成功后,查看编译信息中是否出现"创建H1X文件"提示

    • 确认Objects文件夹内已生成H1X文件

二、FlyMcu串口下载操作流程
  1. 硬件准备

    • 使用USB-TTL模块连接STM32开发板

    • 确认串口COM号(设备管理器中查看)

  2. Boot引脚配置

    • 将开发板上的Boot0跳线帽移至右侧(配置为高电平)

    • 按下复位键使配置生效(STM32仅在复位时读取Boot引脚状态)

  3. 软件操作

    • 打开FlyMcu软件

    • 端口选择对应COM号,波特率保持115200

    • 选择生成的H1X文件路径(工程目录/Objects文件夹)

    • 保持其他配置默认,点击"开始编程"

  4. 运行验证

    • 下载完成后,将Boot0跳线帽恢复原位(左边配置)

    • 再次复位开发板,观察LED闪烁程序运行

三、Bootloader原理与串口下载机制
  1. 存储器分区

    • 主闪存 (0x08000000):存储用户程序

    • 系统存储器 (0x1FFFF000):固化Bootloader程序(ST官方提供)

    • SRAM (0x20000000):临时运行空间

  2. 启动模式选择

    Boot0Boot1启动模式
    0X主闪存
    10系统存储器(Bootloader)
    11SRAM
  3. 下载流程原理

    • 通过Bootloader接收串口数据

    • 将接收数据写入主闪存区域

    • 类似手机刷机模式,完成程序自我更新

四、优化下载流程方案
  1. 一键下载电路

    • 利用串口模块的RTS/DTR信号控制Boot0和复位引脚

    • 典型电路设计:

      • RTS控制Boot0(高电平进入Bootloader)

      • DTR控制复位引脚(低电平复位)

    • 支持自动切换启动模式,免除手动跳线操作

  2. FlyMcu配置优化

    • 在软件中配置DTR/RTS电平组合:

      Plaintext
      
      
      
      
      DTR低电平复位,RTS高电平进Bootloader
    • 搭配硬件电路实现"点击即下载"

五、选项字节配置与保护功能
  1. 功能概述

    • 存储独立于程序代码的配置参数

    • 地址范围:0x1FFFF800 - 0x1FFFF80F

  2. 主要配置项

    功能作用
    读保护防止程序被读取(开启后需全片擦除解除)
    写保护保护指定Flash扇区
    硬件参数配置看门狗、停机模式等
    用户数据存储自定义参数(如产品序列号)
  3. 配置方法

    • 在FlyMcu中勾选"编程到FLASH时写选项字节"

    • 使用ST-Link Utility单独配置(Target > Option Bytes)

六、ST-Link Utility使用指南
  1. 基础操作

    • 连接开发板后读取芯片信息

    • 支持HEX/BIN文件烧录

    • 提供全片擦除功能

  2. 高级功能

    • Flash内容实时查看(0x08000000起始)

    • 程序读取与保存(支持BIN格式)

    • 固件更新(ST-Link > Firmware Update)

  3. 优势特点

    • 独立配置选项字节

    • 支持解除读/写保护

    • 无需进入Bootloader模式

七、常见问题解决方案
  1. 下载卡顿问题

    • 确认Boot引脚配置正确

    • 检查复位操作是否执行

    • 验证串口线连接稳定性

  2. 保护功能异常处理

    Mermaid
  3. Flash容量显示异常

    • 现象:C8T6显示256KB(实际64KB)

    • 原因:ST芯片生产工艺特性

    • 解决方案:仅使用标称容量区域

八、开发建议
  1. 安全建议

    • 量产产品务必开启读保护

    • 重要参数存储在选项字节用户数据区

  2. 效率优化

    • 推荐设计一键下载电路

    • 使用ST-Link进行快速调试

    • 定期备份选项字节配置

  3. 扩展学习

    • 研究IAP(在应用编程)技术

    • 了解DFU(设备固件升级)协议

    • 探索SWD/JTAG调试接口

本教程完整保留了原始操作流程与技术原理,通过结构化整理使内容更易理解与查阅。建议开发者根据实际需求选择下载方式,并重视程序保护功能的应用。

02.FlyMCU软件下载程序

2.1 生成hex文件

在这里插入图片描述

编译信息

 main.c: 2 warnings, 0 errors
 compiling stm32f10x_wwdg.c...
 compiling delay.c...
 compiling stm32f10x_it.c...
 compiling system_stm32f10x.c...
 compiling stm32f10x_usart.c...
 linking...
 Program Size: Code=1140 RO-data=252 RW-data=0 ZI-data=1632  
 FromELF: creating hex file...
 "..\OBJ\Project.axf" - 0 Error(s), 2 Warning(s).
 Build Time Elapsed:  00:00:03
 ​
2.2 STM32进入下载程序模式

在这里插入图片描述

2.3 打开hex文件,点击开始编程

在这里插入图片描述

03.串口下载原理

存储器映像

在这里插入图片描述

启动配置

在这里插入图片描述

程序下载之后自动执行配置

在这里插入图片描述

04.FlyMCU软件其它操作

4.1 读Flash文件

在这里插入图片描述

4.2 清除芯片

在这里插入图片描述

4.3 选项字节

在这里插入图片描述

在这里插入图片描述

05.STLINK Utility软件

5.1 连接到STM32

在这里插入图片描述

5.2 选项字节配置

在这里插入图片描述

10-1 I2C通信协议

01.I2C简介

  • I2C(Inter-Integrated Circuit)总线是一种由NXP(原PHILIPS)公司开发的两线式串行总线,用于连接微控制器及其外围设备。多用于主控制器和从器件间的主从通信,在小数据量场合使用,传输距离短,任意时刻只能有一个主机等特性。

  • 串行的 8 位双向数据传输位速率在标准模式下可达 100kbit/s,快速模式下可达 400kbit/s,高速模式下可达 3.4Mbit/s。

  • I2C是一个多主机的总线,每个设备既可以当主控器或被控器,又可作为发送器或接收器,一条总线上可以有多个主机,但同一时刻只允许一个主机工作。

  • 可以用来连接存储器(EEPROM、FLASH)、A/D、D/A转换器、LCD驱动器、传感器等等。

  • 本视频采用MPU6050这个陀螺仪、加速度传感器来学习12C

在这里插入图片描述

image-20250321102811299

  • 第1个图片,是MPU6050,这是个陀螺仪、加速度传感器

  • 第2个图片,是我们套件里的OLED模块

  • 第3个图片,是AT24C02,存储器模块

  • 第四个图片,是DS3231,实时时钟模块

I2C一共有只有两个总线:

  • 一条是双向的串行数据线SDA:SDA(Serial data)是数据线,D代表Data也就是数据,Send Data 也就是用来传输数据的

  • 一条是串行时钟线SCL:SCL(Serial clock line)是时钟线,C代表Clock 也就是时钟 也就是控制数据发送的时序的

所有接到I2C总线设备上的串行数据SDA都接到总线的SDA上,各设备的时钟线SCL接到总线的SCL上。I2C总线上的每个设备都自己一个唯一的地址,来确保不同设备之间访问的准确性。

02.I2C主要特点

通常我们为了方便把I2C设备分为主设备和从设备,基本上谁控制时钟线(即控制SCL的电平高低变换)谁就是主设备。

  • I2C主设备功能:主要产生时钟,产生起始信号和停止信号

  • I2C从设备功能:可编程的I2C地址检测,停止位检测

I2C的一个优点是它支持多主控(multimastering), 其中任何一个能够进行发送和接收的设备都可以成为主总线。一个主控能够控制信号的传输和时钟频率。当然,在任何时间点上只能有一个主控。串行的 8 位双向数据传输位速率在标准模式下可达 100kbit/s 快速模式下可达 400kbit/s 高速模式下可达 3.4Mbit/s,SCL和SDA都需要接上拉电阻保证数据的稳定性,减少干扰。

I2C是半双工,而不是全双工 ,同一时间只可以单向通信,为了避免总线信号的混乱,要求各设备连接到总线的输出端时必须是漏极开路(OD)输出或集电极开路(OC)输出。

03.I2C硬件电路

• 所有I2C设备的SCL连在一起,SDA连在一起

• 设备的SCL和SDA均要配置成开漏输出模式

• SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右

image-20250321111849298

04.I2C时序基本单元

  • 起始条件:SCL高电平期间,SDA从高电平切换到低电平

  • 终止条件:SCL高电平期间,SDA从低电平切换到高电平

image-20250321142153271

发送一个字节

SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节

我的理解:下面这个图,已经是在起始条件满足之后了,scl处于低电平,允许sda电平变化,还有就是这期间没有达到终止条件。比如从高电平到低电平互相转换,在高电平期间,是从机读取SDA的时候,所以高电平期间,SDA不允许变化,SCL处于高电平之后,从机需要尽快地读取SDA,一般都是在上升沿这个时刻,从机就已经读取完成了,因为时钟是主机控制的,从机并不知道什么时候就会产生下降沿了,你从机要是磨磨唧唧的,主机可不会等你的,所以从机在上升沿时,就会立刻把数据读走,那主机在放手SCL一段时间后,就可以继续拉低SCL,传输下一位了,主机也需要在SCL下降沿之后尽快把数据放在SDA上

另外由于这里有时钟线进行同步,所以如果主机一个字节发送一半突然进中断了,不操作SCI和sda的,那时序就会在中断的位置不断拉长scl和sda电平都暂停变化,传输也完全暂停,等中断结束后,主机回来继续操作传输仍然不会出问题,这就是同步时序的好处。最后就是由于这整个时序是主机发送一个字节,所以在这个单元里scl和sda全程都有主机掌控,统计只能被动读取,就是发送一个字节的时序。

下面的图实线是主机,虚线是从机

image-20250321142815505

接收一个字节

SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)

image-20250321150004440

  • 发送应答:主机在接收完一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答

  • 接收应答:主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)

发送应答和接受应答,他们的时序分别和发送一个字节,接收一个字节的其中一位是相同的。 你可以理解成发送移位和接收移位,这一位就用来作为应答。 看一下首先是发送应答,是祖籍在接收完一个字节之后,在下一个时钟发送一位数据, 数据您表示应答,数据一表示非应答,然后是接收应答,是主机在发送完一个字节之后,在下一个时钟接收一位数据,判断重击是否应答,数据零表示订单数据一表示非应的, 然后同样主机在接头之前需要释放sda,这个意思就是我们在调用发送一个字节之后,加紧跟着调用接收应答的时序, 用来判断重机有没有收到刚才给大家的数据。 如果重新收到了,那在应答位置里主机释放sda的时候,从机就应该立刻把SD拉下来, 然后在hcl高点屏区间,主机独取韵达为如果一拿为为0,就说明冲击确实受到了这个场景就是主机刚发送一个字节, 然后说有没有人收到,我现在把sda放手了,如果有人收到的话,你就把SD转下来, 然后主机高电平读取数据,发现唉确实有人给他拽下来了,那就说明有人收到了。 如果主题发现我松手了,结果这个sda就跟着回弹到高天平了,那就说明没有人回应我,刚发的一个字节可能没人收的, 或者他收到了,但是没给我回应,这是发送一个字节接收应答的流程。 同理在接收一个子节之后,我们也要给重击发送一个应答为发送应用单位的目的是告诉从积,你是不是还要继续发? 如果统计发送一个数据后得到了主机的应答,那统计就还会继续发送。 如果统计没得到逐级的应答,那统计就会认为我发生了一个数据, 但是祖籍不理我,可能祖籍不想要了吧。 这时冲击就会乖乖的释放SDA,交出SDA的控制权,防止干扰阻击之后的操作,就使劲打位的执行逻辑。 好,来到这里,我们I2C的6块拼图就已经集齐了,分别是提示条件、 终值条件。

05.I2C时序波形图

一、时序操作
指定地址写
  • 对于指定设备(Slave Address),在指定地址(Reg Address)下,写入指定数据(Data)

image-20250321165144803

当前地址读
  • 对于指定设备(Slave Address),在当前地址指针指示的地址下,读取从机数据(Data)

image-20250321165149983

指定地址读
  • 对于指定设备(Slave Address),在指定地址(Reg Address)下,读取从机数据(Data)

image-20250321165156526

二、设备地址机制
1. 地址分配原则
  • 唯一性:同一总线上所有设备的地址必须唯一

  • 地址结构

    • 7位地址(主流方案):高4-5位由厂商固定,低位可通过硬件配置

    • 10位地址(扩展方案)

2. 地址配置示例
设备型号基础地址可变位控制引脚地址范围
MPU60501101000AD0引脚1101000/1101001
AT24C021010000A0/A1/A2引脚1010000-1010111
三、基本操作模式
1. 指定地址写(Write to Register)

功能:向指定设备的特定寄存器写入数据

时序流程

  1. 起始条件(SCL高电平期间SDA下降沿)

  2. 发送从机地址(7位) + 写标志位(0)

  3. 接收从机应答(ACK)

  4. 发送寄存器地址(8位)

  5. 接收从机应答

  6. 发送待写入数据(8位)

  7. 接收从机应答

  8. 停止条件(SCL高电平期间SDA上升沿)

示例:向MPU6050(地址0x68)的0x19寄存器写入0xAA

2. 当前地址读(Current Address Read)

功能:读取从机当前指针指向寄存器的数据

时序特点

  • 依赖地址指针自动递增机制

  • 无需指定寄存器地址

时序流程

  1. 起始条件

  2. 发送从机地址 + 读标志位(1)

  3. 接收ACK

  4. 主机接收数据(8位)

  5. 发送NACK终止传输

  6. 停止条件

3. 指定地址读(Read from Register)

复合时序结构

  1. 先执行指定地址写(不发送数据)设置寄存器指针

  2. 重复起始条件(Repeated Start)

  3. 执行当前地址读操作

四、高级操作模式
1. 连续写入
  • 在指定地址写时序后继续发送多个数据字节

  • 地址指针自动递增

  • 每个数据后需接收ACK

2. 连续读取
  • 在发送ACK后继续接收后续字节

  • 最后一个字节需发送NACK终止

五、关键时序解析
1. 起始/停止条件
条件类型SCL状态SDA变化
起始条件高电平下降沿
停止条件高电平上升沿
2. 数据有效性
  • 数据变化必须在SCL低电平期间完成

  • 数据采样在SCL高电平期间进行

3. 应答机制
应答类型SDA电平含义
ACK0成功接收
NACK1传输终止/错误指示
六、典型应用场景
  1. 传感器数据采集(如MPU6050陀螺仪)

  2. EEPROM存储操作(如AT24C02)

  3. 多设备系统管理(LCD+传感器+RTC组合)

七、调试建议
  1. 使用逻辑分析仪捕获实际波形

  2. 重点关注:

    • 地址匹配情况

    • 应答位是否正确

    • 时序间隔是否符合规格

  3. 典型问题排查:

    • 上拉电阻配置(通常4.7KΩ)

    • 总线冲突检测

    • 电源干扰问题

注:文中地址示例采用十六进制表示,实际开发需参考具体器件手册。不同厂商设备可能存在特殊时序要求,建议结合官方协议文档进行开发。

10-2 MPU6050简介

01.MPU6050简介

  • MPU6050是一个6轴姿态传感器,可以测量芯片自身X、Y、Z轴的加速度、角速度参数,通过数据融合,可进一步得到姿态角,常应用于平衡车、飞行器等需要检测自身姿态的场景

  • 3轴加速度计(Accelerometer):测量X、Y、Z轴的加速度

  • 3轴陀螺仪传感器(Gyroscope):测量X、Y、Z轴的角速度

image-20250321165752769

11-1 SPI通信协议

01. SPI简介

在大容量产品和互联型产品上,SPI接口可以配置为支持SPI协议或者支持I 2 S音频协议。SPI接口默认工作在SPI方式,可以通过软件把功能从SPI模式切换到I2S模式。

在小容量和中容量产品上,不支持I 2 S音频协议。

串行外设接口(SPI)允许芯片与外部设备以半/全双工、同步、串行方式通信。此接口可以被配置成主模式,并为外部从设备提供通信时钟(SCK)。接口还能以多主配置方式工作。

它可用于多种用途,包括使用一条双向数据线的双线单工同步传输,还可使用CRC校验的可靠通信。

I2S也是一种3引脚的同步串行接口通讯协议。它支持四种音频标准,包括飞利浦I 2 S标准,MSB和LSB对齐标准,以及PCM标准。它在半双工通讯中,可以工作在主和从2种模式下。当它作为主设备时,通过接口向外部的从设备提供时钟信号。

02. SPI特征

  • 3线全双工同步传输

  • 带或不带第三根双向数据线的双线单工同步传输

  • 8或16位传输帧格式选择

  • 主或从操作

  • 支持多主模式

  • 8个主模式波特率预分频系数(最大为f PCLK /2)

  • 从模式频率 (最大为f PCLK /2)

  • 主模式和从模式的快速通信

  • 主模式和从模式下均可以由软件或硬件进行NSS管理:主/从操作模式的动态改变

  • 可编程的时钟极性和相位

  • 可编程的数据顺序,MSB在前或LSB在前

  • 可触发中断的专用发送和接收标志

  • SPI总线忙状态标志

  • 支持可靠通信的硬件CRC

─ 在发送模式下,CRC值可以被作为最后一个字节发送

─ 在全双工模式中对接收到的最后一个字节自动进行CRC校验

  • 可触发中断的主模式故障、过载以及CRC错误标志

  • 支持DMA功能的1字节发送和接收缓冲器:产生发送和接受请求

03. SPI通信

  • SPI(Serial Peripheral Interface)是由Motorola公司开发的一种通用数据总线

  • 四根通信线:SCK(Serial Clock)、MOSI(Master Output Slave Input)、MISO(Master Input Slave Output)、SS(Slave Select)

  • 同步,全双工

  • 支持总线挂载多设备(一主多从)

说明:

  1. SCK:有的地方可能叫作SCLK、CLK、CK

  2. MOSI和MISO:有的地方可能直接叫作DO(Data Output)和Dl(Data Input)

  3. SS:有的地方也可能叫作NSS(Not Slave Select)、CS(Chip select),每个设备一个CS线

image-20250314202426073

spi没有应答机制,发送数据就发送,接收数据就接收

04. 硬件电路

  • 所有SPI设备的SCK、MOSI、MISO分别连在一起

  • 主机另外引出多条SS控制线,分别接到各从机的SS引脚

  • 输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入

在这里插入图片描述

当然SPI所有通信线都是单端信号,它们的高低电平都是相对GND的电压差,所以,单端信号,所有的设备还需要共地,这里GND的线没画出来,但是是必须要接的,然后如果从机没有独立供电的话, 主机还需要再额外引出电源正极VCC,给从机供电,这两根电源线VCC和GND,也要注意接好

sck由主机掌握,对于主机来说是输出,从机是输入,这样主机的同步时钟,就能送到各个从机了。刚开始的时候,所有的SS都为高电平,当主机想指定谁,就把对应的SS输出线置低电平就行了,当把SS设置为高电平的时候就表示要结束通信了

所以在SPI协议里,有一条规定,就是,当从机的SS引脚为高电平,也就是从机未被选中时,它的MISO引脚,必须切换为高阻态,高阻态就相当于引脚断开,不输出任何电平,这样就可以防止,一条线有多个输出,而导致的电平冲突的问题了,在SS为低电平时,MISO才允许变为推挽输出,这就是SPIl对这个可能的冲突做出的规定

05. 移位示意图

在这里插入图片描述

SPl的数据收发,都是基于字节交换,这个基本单元来进行的

接下来,我来演示一下这个电路如何工作:首先,我们规定,波特率发生器时钟的上升沿,所有移位寄存器向左移动一位,移出去的位放引脚上,波特率发生器时钟的下降沿,引脚上的位,采样输入到移位寄存器的最低位

06. SPI时序基本单元

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

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

在这里插入图片描述

极性(CPOL)

极性决定了SPI时钟信号(SCK)在空闲状态时的电平。具体来说:

  • CPOL=0:表示SCK信号在空闲时为低电平。

  • CPOL=1:表示SCK信号在空闲时为高电平。

相位(CPHA)

相位决定了在时钟信号的哪个跳变沿开始采样数据。具体来说:

  • CPHA=0:表示数据在时钟信号的第一个跳变沿采样。

  • CPHA=1:表示数据在时钟信号的第二个跳变沿采样。

模式0(Mode 0)

模式0是SPI通信中的一种模式,其对应的极性和相位组合为:

  • CPOL=0:SCK信号在空闲时为低电平。

  • CPHA=0:数据在时钟信号的第一个跳变沿(上升沿)采样。

在模式0下,数据传输的时序如下:

  • SCK信号在空闲时为低电平。

  • 数据在SCK信号的上升沿采样,并在下降沿移出。

可以将时钟信号比作一个有规律跳动的心脏,极性就像心脏在不工作时的静止状态,而相位则像是心脏每次跳动时血液流动的关键时刻。模式0就是心脏在静止时处于低电平状态,而数据采样的关键时刻是心脏第一次跳动的上升沿。

交换一个字节(模式0)

  • CPOL(极性)=0:空闲状态时,SCK为低电平

  • CPHA(相位)=0:SCK第一个边沿移入数据,第二个边沿移出数据

刚开始SS没有被选中,MISO被设置为高阻态,SS下降沿之后,从机的MISO被允许开启输出,SS上升沿之后,从机的MISO必须置回高阻态

在这里插入图片描述

这里的B0~B7对应的是每一个位bit(这里只是一个标识位的图,里面的数据根据实际情况来),就像上面移位寄存器所示,主机和从机是一个圈循环,波特率发生器时钟的上升沿,所有移位寄存器向左移动一位,移出去的位放引脚上,波特率发生器时钟的下降沿,引脚上的位,采样输入到移位寄存器的最低位,但是在模式0的情况下,SCK第一个边沿移入数据,在移入之前肯定要移出数据,所以当SS变为低电平的时候就要开始移出了,主机和从机同时移出,当SCK开始变为上升沿的时候,就要开始移入了,同样也是主机和从机同时移入,所以就对应上图中的MOSI和MISO都是从B7开始

交换一个字节(模式1)

  • CPOL=0:空闲状态时,SCK为低电平

  • CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据

在这里插入图片描述

交换一个字节(模式2)

  • CPOL=1:空闲状态时,SCK为高电平

  • CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据

在这里插入图片描述

交换一个字节(模式3)

  • CPOL=1:空闲状态时,SCK为高电平

  • CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据

在这里插入图片描述

07. SPI时序

spi发送的第一个字节数据是指令

发送指令

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

这是模式0,MISO是高阻态,在高电平,从机没有移出

在这里插入图片描述

指定地址写

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

随后在指定地址(Address[23:0])下,写入指定数据(Data)

在这里插入图片描述

因为我们这个W25Q64芯片有8M字节的存储空间,一个字节的8位地址肯定不够,所以这里地址是24位的,分3个字节传输

第一个字节是写指令0x02,后面紧跟的[23,16],[15,8],[7,0],分别是0x12,0x34,0x56,最后一个字节就是要发送指定地址的内容了,表示要在0x123456这个地址下面写入0x55这个数据

说明:

别的芯片可能与W25Q64芯片不一样,比如第一个字节是指令+地址等,地址只有一位等,要根据不同的芯片类型,看芯片手册来查看

指定地址读

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

随后在指定地址(Address[23:0])下,读取从机数据(Data)

在这里插入图片描述

第一个字节是读指令0x03,后面紧跟的[23,16],[15,8],[7,0],分别是0x12,0x34,0x56,最后一个字节就是要接收指定地址的内容了,表示要在0x123456这个地址下面读取数据

说明:

由于MISO是硬件控制的波形,所以它的数据变化,都可以紧贴时钟下降沿,实际上是在上一个字节,最后一个下降沿,提前发生的,因为这是SPI模式0,所以数据变化都要提前半个周期

我们只需要关注一下每一个字节的功能定义,就能很方便地利用SPI控制设备了

11-2 W25Q64简介

01.SPI简介

在大容量产品和互联型产品上,SPI接口可以配置为支持SPI协议或者支持I 2 S音频协议。SPI接口默认工作在SPI方式,可以通过软件把功能从SPI模式切换到I2S模式。

在小容量和中容量产品上,不支持I 2 S音频协议。

串行外设接口(SPI)允许芯片与外部设备以半/全双工、同步、串行方式通信。此接口可以被配置成主模式,并为外部从设备提供通信时钟(SCK)。接口还能以多主配置方式工作。

它可用于多种用途,包括使用一条双向数据线的双线单工同步传输,还可使用CRC校验的可靠通信。

I2S也是一种3引脚的同步串行接口通讯协议。它支持四种音频标准,包括飞利浦I 2 S标准,MSB和LSB对齐标准,以及PCM标准。它在半双工通讯中,可以工作在主和从2种模式下。当它作为主设备时,通过接口向外部的从设备提供时钟信号。

02.W25Q64简介

  • W25Qxx系列是一种低成本、小型化、使用简单的非易失性存储器,常应用于数据存储、字库存储、固件程序存储等场景

  • 存储介质:Nor Flash(闪存)

  • 时钟频率:80MHz / 160MHz (Dual SPI) / 320MHz (Quad SPI)

    • Dual SPI:就是MISO不仅可以收数据也可以发数据,MOSI也是,不仅可以发数据,也可以收数据 80 * 2 = 160

    • Quad SPI:利用多余的/wo和HOLD引脚来进行传输,四根线,(80 + 80) * 2 = 320

  • 存储容量(24位地址)

在这里插入图片描述

03.硬件电路

在这里插入图片描述

C1:是一个电源滤波

R1和D1:也是直接接到VCC和GND,显然,是一个电源指示灯,通电就亮

在这里插入图片描述

/CS:表示是低电平有效,CS上面加一个横杠也是低电平有效

WP:写保护低电平有效,低电平不让写,高电平可以写

HOLD:的意思是当spi数据读写的时候,突然出现了中断,此时spi去读取别的从设备数据,此设备用HOLD来保持状态,保持在低电平,当操作完别的设备的时候,可以回过来,继续操作当前设备,HOLD设置为高电平

04.W25Q64框图

在这里插入图片描述

那整块蛋糕是8MB,以64KB为一块进行划分,最后分得的块数,就是8MB/64KB = 128块,我们还要再对每一块进行更细的划分,分为多个扇区Sector,所以在每一块里,都可以分为 直到扇区15,每一个扇区大小是4KB,一个扇区里,可以分为16页,每一页是256字节

写入的数据,我先放在页缓存区里存着,因为缓存区是RAM,所以它的速度非常快,可以跟得上SPI总线的速度

这个框图,有几个重点部分

  • 第一个是这整个Flash的空间划分,会划分为块、扇区和页

  • 第二个是SPI控制逻辑,它就是整个芯片的管理员,执行指令、读写数据都靠它

  • 第三个是,这个状态寄存器,它和忙状态、写使能、写保护等功能有关

  • 第四个是,这个256字节的页缓存,它会对一次性写入的数据量,产生限制

05.Flash操作注意事项

写入操作时:

  • 写入操作前,必须先进行写使能

  • 每个数据位只能由1改写为0,不能由0改写为1

    • 技术原因或者别的成本原因等等

  • 写入数据前必须先擦除,擦除后,所有数据位变为1

    • 变为FFFFF...

  • 擦除必须按最小擦除单元进行

    • 不可以一个字节去擦除,可以选择整个芯片,块擦除,或者按扇区擦除,再小就没有了

    • 如果说你只想操作一个字节,就只能以扇区4096字节为单位去进行操作,就算是把这个扇区别的地方有存储的数据,那就得提前读出来然后再写进去

  • 连续写入多字节时,最多写入一页的数据(256字节),超过页尾位置的数据,会回到页首覆盖写入

    • 因为页缓冲区只有256字节

  • 写入操作结束后,芯片进入忙状态,不响应新的读写操作

    • 因为每次写操作都是对页缓冲区操作的,所以每次写入后,都有一段时间的忙状态,要想看能不能写入了,就要看BUSY位是否为1,芯片不忙了,再进行操作

读取操作时:

  • 直接调用读取时序,无需使能,无需额外操作,没有页的限制,读取操作结束后不会进入忙状态,但不能在忙状态时读取

06.W25Q64BV手册查看

W25Q64BV.PDF

一个写使能,只能保证后续的一条写指令可以执行,就是只能保证后面的一条时序有效,一条时序结束后,会顺手关门,所以我们在每次写之前都加一条写使能,写完之后,就不用再写失能了

11-3 软件SPI读写W25Q64

用stm32的任意gpio口来模拟spi线,因为gpio是io引脚,可以用来模拟任意的io传输

image-20250315183147602

数据寄存器手册里面有很多的dummy,也就是无用数据,它这里写了dummy,你就给它发个FF就行了,他的返回值也是无用数据,相当于抛砖引砖了,发送和接收的数据都没有意义,它这么干,有的情况是为内部电路延时做准备的,就是交换几个无用数据,相当于有一段延时,当然我推测,也可能是通过dummy的这段时序,给内部电路产生一些额外的时钟,也可以做一些准备工作,或者是其他原因,咱也不用管,总之见到这个dummy是按规定交换一个无用数据就行了

我们还要在每次写操作时序结束后,调用一下WaitBusy,当然这里还涉及一个事前等待还是事后等待的考虑,

事前等待:

  • 而是在每次操作之前,在函数的最前面,没有读写操作之前,进行等待,就是写入前,先等待,等不忙了,再写入

事后等待

  • 在每次写入后,都等BUSY清零了,再退出,这样是最保险的,函数结束后,芯片肯定是不忙的状态,就是写入后,立刻等待,不忙了再退出

11-4 SPI通信外设

01.SPI外设简介

  • STM32内部集成了硬件SPI收发电路,可以由硬件自动执行时钟生成、数据收发等功能,减轻CPU的负担

  • 可配置8位/16位数据帧、高位先行/低位先行

    • 其实就是收发数据的时候是以一个字节还是两个字节收发的,上面软件使用的是一个字节

    • 一般用8位即可,16位的用的很少

    • 高位先行:数据是从高位到低位开始发送的

    • 低位先行:与高位先行相反,数据是从低位到高位开始发送的

    • 我们SPI和I2C,都采用了高位先行的策略,与之不同的是,串口是低位先行的

  • 时钟频率: f_{PCLK} / (2, 4, 8, 16, 32, 64, 128, 256)

    • PCLK是外设时钟的意思

  • 支持多主机模型、主或从操作

  • 可精简为半双工/单工通信

  • 支持DMA

  • 兼容I2S协议

  • STM32F103C8T6 硬件SPI资源:SPI1、SPI2

04.SPI框图

MSB:高位的意思

LSB:低位的意思

在这里插入图片描述

img

05.SPI基本结构

在这里插入图片描述

06.主模式全双工连续传输

在这里插入图片描述

07.非连续传输

在这里插入图片描述

如果在高频率下传输数据,最好使用连续传输,或者使用dma

08.软件/硬件波形对比

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值