单片机学习

一、学前准备

1.1:STM32与Cortex M系列

STM32是单片机,它是由意法半导体(ST)制造的,CPU采用ARM公司的Cortex-m系列的内核设计。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.2:基本概念

  • MCU ( Microcontroller Unit)即微控制器,它是一个包含 CPU(中央处理器)以及存储器、定时器、通信接口等其他必要组件的完整系统,集成在一块芯片上。CPU 是 MCU 中负责执行指令、进行数据处理的核心部分,相当于 MCU 的大脑。例如 STM32 单片机,它以 32 位的 ARM Cortex-M 系列内核构建 CPU,整个 MCU 围绕这个 CPU 运作,CPU 依据预设程序执行各种任务,像控制外部设备、处理数据等。简言之,MCU 包含 CPU,CPU 是 MCU 的关键组成部分,决定着 MCU 的运算能力和处理速度等核心性能参数。

1.3:CMSIS标准

在这里插入图片描述
在这里插入图片描述

原文链接:https://blog.csdn.net/m0_37845735/article/details/105829019

1.3.1: startup_stm32f103xb.s

  1. 文件内容和功能

    • 初始化硬件环境
      • 在程序开始运行时,该文件首先会对处理器的硬件环境进行初始化。这包括设置堆栈指针(SP)。在嵌入式系统中,堆栈用于存储函数调用时的返回地址、局部变量等信息。它会根据具体的存储配置,将堆栈指针指向一个合适的内存区域的末尾,通常是 RAM 的高端地址,以便从高地址向低地址生长。
    • 初始化中断向量表
      • 中断向量表是一组存放中断服务程序入口地址的列表。在 startup_stm32f103xb.s 中,会定义中断向量表的位置。当发生中断或异常事件时,处理器会根据中断向量表中的对应地址跳转到相应的中断服务程序执行。例如,当发生外部中断时,处理器会查找中断向量表中对应的外部中断向量,然后跳转到该中断对应的处理函数地址,执行预先编写的中断处理代码。
    • 复位处理程序
      • 文件中的复位处理程序是一个关键部分。当微控制器上电复位或者发生系统复位时,程序会从复位处理程序开始执行。它会完成一些基本的初始化操作,如对系统时钟的初步配置,使能必要的外设时钟等。例如,它可能会配置系统时钟源为内部高速时钟(HSI)或者外部高速时钟(HSE),并设置系统时钟的倍频等参数,以确保微控制器以合适的时钟频率运行。
    • 调用用户初始化代码
      • 在完成了硬件和基本环境的初始化后,启动文件会调用用户编写的初始化函数(如 SystemInitmain 函数)。SystemInit 函数通常用于进一步的系统配置,如配置系统时钟的详细参数、设置存储器映射等。而 main 函数则是用户应用程序的入口,在这里用户可以编写自己的应用程序代码,如对 GPIO 的配置、串口通信的、初始化定时器的设置等操作。
  2. 其他知识

  3. .s 文件是汇编语言源文件,通常用于编写启动代码、中断处理程序等底层功能。它在编译过程中会被汇编器转换为机器码。

  4. 在汇编语言中:

  • DCD 是“Define Constant Data”的缩写,用于定义32位的常数数据,常用于初始化数据或设置内存中的值。
  • LDR 是“Load Register”的缩写,用于将内存中的数据加载到寄存器中,或加载一个立即数到寄存器。

1.3.2: SystemInit函数(在system_stm32f1xx.c文件中)

1. 外部存储器配置(DATA_IN_ExtSRAM)
  • 作用 :根据是否定义了宏 DATA_IN_ExtSRAM 以及是否为特定的 STM32 系列芯片(如 STM32F100xE、STM32F101xE 等),决定是否调用 SystemInit_ExtMemCtl() 函数。SystemInit_ExtMemCtl() 函数用于初始化外部存储器控制,当系统需要使用外部 SRAM 作为数据存储区域时,会通过这个函数进行配置。
2. 中断向量表配置(USER_VECT_TAB_ADDRESS)
  • 作用 :如果定义了 USER_VECT_TAB_ADDRESS,则通过设置 SCB->VTOR 寄存器来改变中断向量表的基地址。其中,VECT_TAB_BASE_ADDRESS 是中断向量表的基地址,VECT_TAB_OFFSET 是偏移量。
  • 意义 :通常在默认情况下,中断向量表位于闪存的起始地址。但在某些情况下,例如使用外部存储器或者进行一些特殊的系统配置时,可能需要将中断向量表重定位到其他地址,比如内部 SRAM。通过这种方式,可以灵活地根据系统需求调整中断向量表的位置。

1.3.3: main函数(用户函数入口)

二、开发环境

2.1: keil Mdk和keil uVision5一样吗

== 不一样 ==

‌**Keil MDK 和 Keil uVision5 是不同维度的概念:前者是一个完整的微控制器开发套件,后者是集成开发环境(IDE)的版本名称。**‌ 具体区别如下:

  1. 定义与功能
  • Keil MDK‌(Microcontroller Development Kit):
    是专为基于 ARM 处理器的微控制器(如 Cortex-M、ARM7/9 等)设计的 ‌完整开发套件‌,包含编译器、调试器、中间库、设备支持包等核心工具。
  • uVision5‌:
    是 Keil 公司开发的 ‌**集成开发环境(IDE)**‌,提供代码编辑、工程管理、调试等功能。uVision5 是 MDK 套件中的一个组件,类似于其他开发工具中的“开发界面”。
  1. 层级关系
  • uVision5 是 Keil MDK 套件的 ‌核心交互界面‌,而 MDK 包含更广泛的功能模块(如编译器、软件包支持等)。
  • 类比‌:
    uVision5 相当于“操作界面”,而 MDK 是“包含界面的工具箱”。
  1. 版本对应
  • Keil MDK 的不同版本(如 MDK5)会 ‌集成特定版本的 uVision IDE‌(如 MDK5 对应 uVision5)。
    例如,MDK5 将架构分为 MDK Core(含 uVision5)和 Software Packs,支持灵活更新组件。

总结‌:若使用 Keil MDK5 进行开发,实际操作界面是 uVision5,但 MDK 本身包含的远不止 IDE 工具。

2.2:安装

2.2.1:安装keil

2.2.2:安装芯片包Stm32f1xx

2.3:开发板资源介绍

2.3.1:原理图

2.3.2:单片机开发模式

2.3.2.1:汇编操作寄存器
  • 执行效率高
  • 编写难度大,移植性差
2.3.2.2:使用C语言操作寄存器
2.3.2.3:库开发
  • 标准库
  • HAL库

2.4:创建工程的方式

2.4.1: keil创建工程模板

  • 建立新项目流程
    • 先在USER下创建文件夹和对应.c和.h文件
    • 双击USER加入对应.c文件
    • 在.c文件中引用.h文件并编译
    • 在main函数中引用并且此时要在C++选项中新建路径,这样才能找得到对应的.h文件

2.4.2: STM32CubeMX

2.5:烧录程序STM32

三、基本GPIO控制

3.1: GPIO基本知识

  • General-Purpose input/output(通用输入/输出接口)

  • 用于感知外部设备(输入模式)和控制外部设备(输出模式)

  • 有的需要多个引脚组成“协议”传输数据,USART、IIC、SPI等协议

  • STM32的GPIO引脚通常可以配置为以下几种模式:

    • 普通输入/输出模式(GPIO):引脚作为普通的数字输入或输出。
    • 复用功能模式(Alternate Function):引脚作为其他外设(如UART、I2C、SPI、PWM等)的信号引脚。
    • 模拟模式(Analog):引脚作为ADC或DAC的输入/输出。
  • 每个GPI/O端口有两个32位配置寄存器(GPIOx_CRL,GPIOx_CRH),两个32位数据寄存器

    (GPIOx_IDR和GPIOx_ODR),一个32位置位/复位寄存器(GPIOx_BSRR),一个16位复位寄存

    器(GPIOx_BRR)和一个32位锁定寄存器(GPIOx_LCKR)。

3.2: IO的八种工作模式

3.2.1:输出模式

在这里插入图片描述

  • 推挽输出(push-pull,PP):引脚可以直接输出高电平和低电平

  • 开漏输出(open-drain,OD):P-MOS无效,N-MOS有效,写0时正常,写1时因为P-MOS无效,所以是高阻态(相当于啥都没有),需要外部上拉电阻才能实现高电平

  • 复用推挽输出(alternate function,AF ):

  • 复用开漏输出(alternate function,AF ):

GPIO除了可以设置为通用输入输出引脚以外,还可以作为片上外设(USART、IIC、SPI)专用引脚,即一个引脚有多个用途,但同一时刻一个引脚只能使用复用功能中的一个

3.2.2:输入模式

在这里插入图片描述

  • 上拉输入:VDD开关闭合,接入上拉电阻,默认输入高电平;当输入低电平的时候上面开关断开,下面开关闭合,用来检测低电平。

  • 下拉输入:VSS开关闭合,接入上拉电阻,默认输入低电平;用来检测高电平。

  • 浮空输入:上下拉电阻开关均断开,此时IO引脚浮空,读取的电平是不确定的;MCU上电之后默认为浮空输入模式

  • 模拟输入:上下拉电阻开关断开,同时TTL肖特基触发器也断开,引脚信号直接进入模拟输入,实现对外部信号的采集

在这里插入图片描述

3.2.3:GPIO输出速度

  • 在输出模式下要配置IO输出的速度,这个速度不是输出型号的速度,而是IO驱动电路(也就是上图中的电路)的响应速度;

  • STM32提供了三个速度:2MHZ 10MHZ 50MHZ

  • 高速度:功耗大、噪声大、电磁感染强(用于对响应速度要求高的,如IIC、SPI)

  • 低速度:功耗小、噪声小、电磁感染弱(用于对响应速度低,但要求稳定的,如LED灯)

3.3:寄存器点灯

			 //寄存器点灯
		 //1.使能GPIOA的时钟  起始地址+偏移地址  0x4002 1000 + 0x18 
		 
		 *(unsigned int *)0x40021018 |= 0x01<<2;//先强制转换成指针然后再取地址
		 
		 //2.配置PA1为输出模式 速度10MHZ
		 
		 *(unsigned int *)0x40010800 |= 0x01<<4;
		 
		 //3.PA1输出低电平
		 
			*(unsigned int *)0x4001080C &= ~(0X01<<1); 

3.3:固件库实现LED点灯

在这里插入图片描述

  • 根据上图可知,我们只需要控制GPIOA1引脚为低电平就可以点亮,高电平熄灭
  • 一般要使用一个项目。在USER下创建对应的文件夹,并且放入.c和.h文件,在.c文件中手动包含对应的库

3.3.1:使用到的固件库

/** 

  * @brief  GPIO Init structure definition  GPIO结构体
    */

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;

3.3.2:初始化+设置电平流程

  • 初始化系统

    • 初始化GPIO时钟

      在这里插入图片描述

    • 初始化GPIO引脚(LED引脚)

    void LED_Init(void){
    
    	GPIO_InitTypeDef led_initstruct;
    	
    	//1、时钟初始化
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    	
    	//2、GPIOA1引脚初始化
    	
    	led_initstruct.GPIO_Pin = GPIO_Pin_1;
    	led_initstruct.GPIO_Speed = GPIO_Speed_2MHz;
    	led_initstruct.GPIO_Mode = GPIO_Mode_Out_PP;
    	GPIO_Init(GPIOA, &led_initstruct);
    }
    
    //变量声明一定要在最前面
    
  • 输出电平

GPIO_ResetBits(GPIOA, GPIO_Pin_1 );

3.3.3:闪烁(粗延时)

在这里插入图片描述

3.4:蜂鸣器

在这里插入图片描述

  • 我的是低电平触发

3.4.1:初始化+设置电平

  • 时钟初始化

  • 端口初始化

  • 设置电平

3.5:按键基础知识

在这里插入图片描述

3.5.1:初始化




//同一个GPIO结构体可以重复利用初始化引脚

	GPIO_InitTypeDef Key_Initstruct;
	
//这种写法是可行的
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOA, ENABLE);

	
	Key_Initstruct.GPIO_Pin = GPIO_Pin_13;
	//Key_Initstruct.GPIO_Speed = GPIO_Speed_2MHz;
	Key_Initstruct.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_Init(GPIOC, &Key_Initstruct);
	
	Key_Initstruct.GPIO_Pin = GPIO_Pin_0;
	//Key_Initstruct.GPIO_Speed = GPIO_Speed_2MHz;
	Key_Initstruct.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_Init(GPIOA, &Key_Initstruct);

3.5.2:读取电平

					if(GPIO_ReadInputDataBit(GPIOA,  GPIO_Pin_0)==0){
					
						GPIO_ResetBits(GPIOA, GPIO_Pin_1 );
						
					}

3.5.3:遇到的问题

  • 在初始化LED引脚为推挽输出后,如果不显式的进行引脚定位之前,状态一般是输出低电平,所以第一次下载之后会发现LED初始的时候就是亮着的

3.6:继电器(3.3)

  • 给IN 输入低电平(0)[灯亮]
    • 继电器线圈不通电,继电器处于未激活状态。
    • COMNC 之间的触点闭合。
    • COMNO 之间的触点断开。
  • 给IN 输入高电平(1)【灯灭】:
    • 继电器线圈通电,继电器处于激活状态。
    • COMNC 之间的触点断开。
    • COMNO 之间的触点闭合。

3.7:震动传感器(3.3)

  • VCC GND DO(输出震动信号)

  • 引脚输入模式:上拉、下拉、浮空都可以

  • 震动传感器正常是高电平,有震动会变为低电平

  • 		 if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1)==0){ //震动传感器正常是高电平,有震动低电平,有震动闪灯
      				GPIO_ResetBits(GPIOA, GPIO_Pin_1 );
      			  delay(1000);
      			  GPIO_SetBits(GPIOA, GPIO_Pin_1 );
      				//delay(1000);
      		 }else{
      		 GPIO_SetBits(GPIOA, GPIO_Pin_1 );
      		 }
    

3.8:小项目:震动感应灯/蜂鸣器

PB1:震动传感器发送信息PB1接收信息

PB2:引脚输出继电器IN输入,连通COMNC 之间的触点闭合,COM连接高电平,KC连接到蜂鸣器电源VCC

PA0:蜂鸣器,低电平触发

		 if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1)==0){ //震动传感器正常是高电平,有震动低电平
			 GPIO_ResetBits(GPIOA, GPIO_Pin_0 );//蜂鸣器开
			 GPIO_ResetBits(GPIOB, GPIO_Pin_2 );//继电器通`COM` 和 `NO` 之间的触点闭合
			 delay(200);
			 GPIO_SetBits(GPIOA, GPIO_Pin_0 );//蜂鸣器关
			 delay(200);
		 }else{
			 GPIO_SetBits(GPIOB, GPIO_Pin_2 );//继电器通`COM` 和 `NO` 之间的触点断开
			 GPIO_SetBits(GPIOA, GPIO_Pin_0 );//蜂鸣器关
		 }

3.9:433M无线模块

  • 接收到信号之后,接收信号对应针脚输出高电平

  • 		 if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0)==1){ //433M对应引脚接收高电平
      			 GPIO_ResetBits(GPIOA, GPIO_Pin_1 );//LED开
    
       }
       else if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_2)==1){ //433M对应引脚接收高电平
      	 GPIO_SetBits(GPIOA, GPIO_Pin_1 );//LED关
      	 
       }
    
    
    

四、外部中断EXTI

4.1:外部中断基础知识

4.1.1:STM32外部中断框架

  • 在主程序运行过程中,触发特定中断触发条件,使得CPU暂停当前执行的程序,转而去处理中断程序,处理完之后又回到原程序暂停位置继续执行
  • 有20条外部中断线
  • 有16条是GPIO中断线(0-15),同一个引脚对一个line,比如PA1和PB1同对一个EXTI1
  • 第十九条line即EXTI19只有在ETH互联设备才有
  • ??

在这里插入图片描述

4.1.2:STM32外部中断机制框架

在这里插入图片描述

​```c
typedef struct
{
uint32_t EXTI_Line; /*!< Specifies the EXTI lines to be enabled or disabled.
This parameter can be any combination of @ref EXTI_Lines */

EXTIMode_TypeDef EXTI_Mode; /*!< Specifies the mode for the EXTI lines.
This parameter can be a value of @ref EXTIMode_TypeDef */

EXTITrigger_TypeDef EXTI_Trigger; /*!< Specifies the trigger signal active edge for the EXTI lines.
This parameter can be a value of @ref EXTIMode_TypeDef */

FunctionalState EXTI_LineCmd; /*!< Specifies the new state of the selected EXTI lines.
This parameter can be set either to ENABLE or DISABLE */
}EXTI_InitTypeDef;

void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);


- 边沿检测:上升沿、下降沿或双边沿

- 软件配置中断或事件寄存器
- 中断和事件区别
  - **即时性** :中断具有即时性,它能在事件发生时立即打断当前任务的执行,确保对紧急事件的快速响应;而事件不一定会立即处理,它通常需要等待当前任务完成或在适当的时候进行处理。
  - **处理方式** :中断通过中断服务程序进行处理,中断服务程序的执行具有高优先级;事件通常通过事件处理函数或状态机进行处理,处理方式相对灵活,但优先级一般低于中断。
  - **触发机制** :中断由硬件信号或特定的软件指令触发;事件可以由多种因素触发,包括硬件信号、软件标志、定时器溢出等。
  - **对系统的影响** :中断可能会对系统的实时性和确定性产生较大影响,因为中断的随机性和抢占性可能会导致任务调度的复杂性增加;事件对系统的影响相对较小,它通常在任务的执行流程中自然地进行处理。

- 屏蔽中断或事件寄存器
- 请求挂起寄存器
- 中断发送给NVIC中断控制器
- 事件则产生一个脉冲响应

## 4.2:复用功能

### 4.2.1:什么是复用功能?

处理器的引脚本身是一个普通的GPIO引脚,但是它还可以被复用成其他功能,我们称之为一个引脚的复用功能 

![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/9b09652e228a49a18854d2e3e2400199.png#pic_center)


## 4.3:重映射

### 4.3.1:概念

- 重映射:重映射属于复用功能的另外一个功能,可以把具有特殊功能的引脚分配到其他引脚上去
- 如果某个功能被重映射了,那么不再遵循其默认的默认配置
- 比如下表中,通常默认USART1的输出和输入分别是PA9和PA10,但在参数设置下进行重映射,PB6和PB7也具有相同的功能,如果进行了重映射,那么不遵循其默认的PA9/10配置

![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/27eb2df2e7554cc3997bd5dacad1a5a7.png#pic_center)


## 4.4:中断嵌套控制器NVIC

### 4.4.1:中断向量表

- cortex-M3包含256个中断,包含16个内核中断(异常)和240个外部中断,并且具有256级的可编程中断设置

- **优先级号越小优先级越高**
- 而在STM32F103系列上面,16个内核中断(异常)不变,而可屏蔽中断只有60个

![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/d5cb8be9fff34020adefae9e64d32c25.png#pic_center)


### 4.4.2:中断优先级分组

```c
/**
@code  
The table below gives the allowed values of the pre-emption priority and subpriority according
to the Priority Grouping configuration performed by NVIC_PriorityGroupConfig function
============================================================================================================================
  NVIC_PriorityGroup   | NVIC_IRQChannelPreemptionPriority | NVIC_IRQChannelSubPriority  | Description
============================================================================================================================
 NVIC_PriorityGroup_0  |                0                  |            0-15             |   0 bits for pre-emption priority
                       |                                   |                             |   4 bits for subpriority
----------------------------------------------------------------------------------------------------------------------------
 NVIC_PriorityGroup_1  |                0-1                |            0-7              |   1 bits for pre-emption priority
                       |                                   |                             |   3 bits for subpriority
----------------------------------------------------------------------------------------------------------------------------    
 NVIC_PriorityGroup_2  |                0-3                |            0-3              |   2 bits for pre-emption priority
                       |                                   |                             |   2 bits for subpriority
----------------------------------------------------------------------------------------------------------------------------    
 NVIC_PriorityGroup_3  |                0-7                |            0-1              |   3 bits for pre-emption priority
                       |                                   |                             |   1 bits for subpriority
----------------------------------------------------------------------------------------------------------------------------    
 NVIC_PriorityGroup_4  |                0-15               |            0                |   4 bits for pre-emption priority
                       |                                   |                             |   0 bits for subpriority                       
============================================================================================================================
@endcode
*/
  • Cortex3中定义了8bit用于中断源的优先级,而STM32只选用其中的4bit进行判断

  • 抢占优先级高于响应优先级,数值越小代表优先级越高

  • 抢占优先级和响应优先级区别

    • 高抢占优先级中断可以打断正在执行的低抢占优先级中断
    • 抢占优先级相同的中断不可以互相打断
    • 抢占优先级相同的中断同时发生时,响应优先级(子优先级)哪个高哪个先执行
    • 如果抢占、响应优先级相同的情况下,哪个先发生哪个先执行
  • 除此之外要注意:

    • 打断只与抢占优先级有关,和响应优先级无关(中断嵌套)
    • 一般在系统执行时只设置一次中断优先级分组(比如分组2),之后不再更改组别,随意更改会导致中断管理混乱,程序出错

4.4.3:中断优先级控制函数体(misc.h)



/** 
  * @brief  NVIC Init Structure definition  
  */

typedef struct
{
  uint8_t NVIC_IRQChannel;                    /*!< Specifies the IRQ channel to be enabled or disabled.
                                                   This parameter can be a value of @ref IRQn_Type 
                                                   (For the complete STM32 Devices IRQ Channels list, please
                                                    refer to stm32f10x.h file) */

  uint8_t NVIC_IRQChannelPreemptionPriority;  /*!< Specifies the pre-emption priority for the IRQ channel
                                                   specified in NVIC_IRQChannel. This parameter can be a value
                                                   between 0 and 15 as described in the table @ref NVIC_Priority_Table */

  uint8_t NVIC_IRQChannelSubPriority;         /*!< Specifies the subpriority level for the IRQ channel specified
                                                   in NVIC_IRQChannel. This parameter can be a value
                                                   between 0 and 15 as described in the table @ref NVIC_Priority_Table */

  FunctionalState NVIC_IRQChannelCmd;         /*!< Specifies whether the IRQ channel defined in NVIC_IRQChannel
                                                   will be enabled or disabled. 
                                                   This parameter can be set either to ENABLE or DISABLE */   
} NVIC_InitTypeDef;




/**
  * @brief  Configures the priority grouping: pre-emption priority and subpriority.
  * @param  NVIC_PriorityGroup: specifies the priority grouping bits length. 
  *   This parameter can be one of the following values:
  *     @arg NVIC_PriorityGroup_0: 0 bits for pre-emption priority
  *                                4 bits for subpriority
  *     @arg NVIC_PriorityGroup_1: 1 bits for pre-emption priority
  *                                3 bits for subpriority
  *     @arg NVIC_PriorityGroup_2: 2 bits for pre-emption priority
  *                                2 bits for subpriority
  *     @arg NVIC_PriorityGroup_3: 3 bits for pre-emption priority
  *                                1 bits for subpriority
  *     @arg NVIC_PriorityGroup_4: 4 bits for pre-emption priority
  *                                0 bits for subpriority
  * @retval None
  */

void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
{
  /* Check the parameters */
  assert_param(IS_NVIC_PRIORITY_GROUP(NVIC_PriorityGroup));
  
  /* Set the PRIGROUP[10:8] bits according to NVIC_PriorityGroup value */
  SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;
}





/**
  * @brief  Initializes the NVIC peripheral according to the specified
  *         parameters in the NVIC_InitStruct.
  * @param  NVIC_InitStruct: pointer to a NVIC_InitTypeDef structure that contains
  *         the configuration information for the specified NVIC peripheral.
  * @retval None
  */
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct)
{
  uint32_t tmppriority = 0x00, tmppre = 0x00, tmpsub = 0x0F;
  
  /* Check the parameters */
  assert_param(IS_FUNCTIONAL_STATE(NVIC_InitStruct->NVIC_IRQChannelCmd));
  assert_param(IS_NVIC_PREEMPTION_PRIORITY(NVIC_InitStruct->NVIC_IRQChannelPreemptionPriority));  
  assert_param(IS_NVIC_SUB_PRIORITY(NVIC_InitStruct->NVIC_IRQChannelSubPriority));
    
  if (NVIC_InitStruct->NVIC_IRQChannelCmd != DISABLE)
  {
    /* Compute the Corresponding IRQ Priority --------------------------------*/    
    tmppriority = (0x700 - ((SCB->AIRCR) & (uint32_t)0x700))>> 0x08;
    tmppre = (0x4 - tmppriority);
    tmpsub = tmpsub >> tmppriority;

    tmppriority = (uint32_t)NVIC_InitStruct->NVIC_IRQChannelPreemptionPriority << tmppre;
    tmppriority |=  NVIC_InitStruct->NVIC_IRQChannelSubPriority & tmpsub;
    tmppriority = tmppriority << 0x04;
        
    NVIC->IP[NVIC_InitStruct->NVIC_IRQChannel] = tmppriority;
    
    /* Enable the Selected IRQ Channels --------------------------------------*/
    NVIC->ISER[NVIC_InitStruct->NVIC_IRQChannel >> 0x05] =
      (uint32_t)0x01 << (NVIC_InitStruct->NVIC_IRQChannel & (uint8_t)0x1F);
  }
  else
  {
    /* Disable the Selected IRQ Channels -------------------------------------*/
    NVIC->ICER[NVIC_InitStruct->NVIC_IRQChannel >> 0x05] =
      (uint32_t)0x01 << (NVIC_InitStruct->NVIC_IRQChannel & (uint8_t)0x1F);
  }
}
  • NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
    • 中断优先级控制函数,设置中断优先级分组
  • NVIC_InitTypeDef:中断优先级控制结构体
    • NVIC_IRQChannel:中断源,可以在10x.h文件中具体查看
    • 抢占优先级、响应优先级、使能
  • void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct)
    • 用结构体初始化NVIC寄存器

4.4.4:中断优先级设置步骤

  • 先设置中断优先级分组
  • 针对每个中断设置响应和抢占优先级
  • 如果需要挂起/解挂,查看中断当前激活状态,分别调用相关函数即可

4.5:外部中断按键控制LED灯

4.5.1:软件流程设计(EXTI设置在exti.h,NVIC设置在misc.h,IRQn通道在stm32f10x.h)

  • 初始化系统

    • 初始化GPIO和EXTI外部时钟

    • 初始化按键和LED引脚

    • 指定的 GPIO 引脚配置为 EXTI 线

      • void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource) //将指定的 GPIO 引脚配置为 EXTI 线的来源
        
      • 为什么要进行这一步? GPIO需要绑定 EXTI 线是因为其外部中断功能需要通过 EXTI 线向中断控制器发出请求,而USART不需要绑定 EXTI 线是因为它有自己的中断机制,虽然USART不需要进行EXTI配置以及绑定,但USART还是需要进行一个中断初始化,参数包括串口、需要触发的标志位、使能位。

    • 初始化EXTI外部中断条件

      • 前四步就是在之前GPIO配置的基础上加入了一个EXTI配置(EXTI时钟配置和EXTI结构体初始化寄存器)
      • RCC_APB2Periph_AFIO其中也包含了外部中断时钟
    • 初始化NVIC嵌套向量中断控制器及分组

      • 先配置分组,再进行NVIC配置
      • EXTI_Initstruct.EXTI_Line 和 NVIC_Initstruct.NVIC_IRQChannel 有什么关系?
        • 外部中断线和中断通道的映射 :每个外部中断线对应一个或多个 GPIO 引脚,而多个外部中断线可以共享一个中断通道。例如,外部中断线 10 到 15 共享 EXTI15_10_IRQn 中断通道。
        • 配置流程中的关联 :在配置外部中断时,EXTI_Initstruct.EXTI_Line 用于指定具体的外部中断线,而 NVIC_Initstruct.NVIC_IRQChannel 用于指定处理该中断线的中断通道。两者的配置需要协调一致,以确保中断能够正确地被识别和处理。
        • 中断处理函数的关联 :在中断处理函数中,NVIC_IRQChannel 对应的中断通道决定了哪个中断处理函数会被调用。例如,EXTI15_10_IRQn 中断通道的中断处理函数是 EXTI15_10_IRQHandler
  • 编写外部中断函数

weak:弱定义;如果用户没有自己定义的中断函数,那么系统执行自己默认定义的中断函数,如果用户有定义中断函数那么执行用户自定义的中断函数

两个函数区别:

FlagStatus EXTI_GetFlagStatus(uint32_t EXTI_Line)

  • 功能 :检查指定的 EXTI 线是否发生了事件或中断请求。
  • 参数EXTI_Line 指定要检查的 EXTI 线。
  • 返回值 :返回 SETRESET,表示是否检测到事件或中断请求。
  • 用途 :通常用于轮询模式下检测 EXTI 线的状态,判断是否有事件发生。
ITStatus EXTI_GetITStatus(uint32_t EXTI_Line)
  • 功能 :检查指定的 EXTI 线是否触发了中断请求。
  • 参数EXTI_Line 指定要检查的 EXTI 线。
  • 返回值 :返回 SETRESET,表示是否检测到中断请求。
  • 用途 :在中断服务程序中使用,用于确认中断是由指定的 EXTI 线触发的
void EXTI0_IRQHandler()
{
	
	if(EXTI_GetITStatus(EXTI_Line0) != RESET){
			GPIO_ResetBits(GPIOA, GPIO_Pin_1 );
		delay(1000);//一般在中断中不加延迟
		GPIO_SetBits(GPIOA, GPIO_Pin_1  );
			EXTI_ClearITPendingBit(EXTI_Line0);//如果不进行这一步中断只能进行一次
	}
}
/*为什么还要进行中断状态检测?
原因1:确保中断的准确性
void EXTI0_1_IRQHandler(void)
{
    if (EXTI_GetITStatus(EXTI_Line0) != RESET)
    {
        // 处理设备 A 的中断
        EXTI_ClearITPendingBit(EXTI_Line0);
    }
    else if (EXTI_GetITStatus(EXTI_Line1) != RESET)
    {
        // 处理设备 B 的中断
        EXTI_ClearITPendingBit(EXTI_Line1);
    }
}
原因2:避免误触发
例子:在嘈杂的工业环境中,一个外部传感器通过外部中断线连接到微控制器。由于环境噪声可能导致 GPIO 引脚上的信号波动,从而产生误触发。通过检查中断状态位,可以避免执行不必要的中断处理代码。
原因3:支持中断嵌套
例子:假设系统中有两个中断源,一个是高优先级的定时器中断,另一个是低优先级的外部中断。当外部中断正在执行时,定时器中断到来,系统需要优先处理定时器中断。在两个中断服务函数中检查中断状态位,可以确保正确的中断嵌套处理。(确保当前处理的是正确的中断源)
原因4:调试和维护
例子:在开发过程中,如果系统出现意外的中断触发,检查中断状态位可以帮助快速定位问题。例如,如果发现某个中断频繁触发但不应该发生,可以通过检查中断状态位来确定是硬件问题还是软件配置错误。
*/

4.6:小作业:外部中断震动感应灯/蜂鸣器

void Shake_Init(void){
	
	GPIO_InitTypeDef Shake_Initstruct;
	EXTI_InitTypeDef EXTI1_Initstruct;
	NVIC_InitTypeDef NVIC_Initstruct;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB , ENABLE);//GPIO口时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);//EXTI时钟
	
	Shake_Initstruct.GPIO_Pin = GPIO_Pin_1;
	//Key_Initstruct.GPIO_Speed = GPIO_Speed_2MHz;
	Shake_Initstruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(GPIOB, &Shake_Initstruct);

	//EXTI初始化,一定要记着绑定引脚为中断源
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1);
	
	EXTI1_Initstruct.EXTI_Line = EXTI_Line1;
	EXTI1_Initstruct.EXTI_Mode = EXTI_Mode_Interrupt;
	EXTI1_Initstruct.EXTI_Trigger = EXTI_Trigger_Falling;
	EXTI1_Initstruct.EXTI_LineCmd = ENABLE;
	EXTI_Init(&EXTI1_Initstruct);
	
	//NVIC初始化不需要时钟,一定要记着先设置分组(就是各占几位)
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	NVIC_Initstruct.NVIC_IRQChannel = EXTI1_IRQn;
	NVIC_Initstruct.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_Initstruct.NVIC_IRQChannelSubPriority = 0;
	NVIC_Initstruct.NVIC_IRQChannelCmd = ENABLE;
	
	NVIC_Init(&NVIC_Initstruct);
	
}

void EXTI1_IRQHandler ()
{

if(EXTI_GetFlagStatus(EXTI_Line1) != RESET)//中断触发
{
			 GPIO_ResetBits(GPIOA, GPIO_Pin_0 );//蜂鸣器开
			 GPIO_ResetBits(GPIOB, GPIO_Pin_2 );//继电器通`COM` 和 `NO` 之间的触点闭合
			 delay(200);
			 GPIO_SetBits(GPIOA, GPIO_Pin_0 );//蜂鸣器关
			 delay(200);
	
	EXTI_ClearFlag(EXTI_Line1);

}
}

五、串口USART

5.1:常见的通信相关概念

5.1.1:通信方式:串行通信和并行通信

在这里插入图片描述

5.1.2:通信方向:全双工、半双工和单工通信

在这里插入图片描述
在这里插入图片描述

5.1.3:同步通信和异步通信

  • 同步通信和异步通信都是针对串行通信来说的

  • 异步通信

    • 1、数据是以字符为单位组成字符帧传输的
    • 2、字符帧由发送端一帧一帧的发送,每帧的数据都是低位在前、高位在后,通过传输线被接收端一帧一帧的接收
    • 3、发送端和接收端可以有各自独立的时钟来控制数据的接收和发送,这两个时钟各自独立、互不同步
    • 4、接收端依靠字符帧格式来判断发送端是何时开始和结束发送的
    • 字符帧也叫做数据帧,由起始位、数据位、奇偶校验位、停止位等部分组成,是异步通信的一个重要指标,同步通信的另一个指标是波特率
  • 同步通信

    • 同步是指在约定的通信速率下,发送端和接收端的时钟信号和相位始终保持一致,保证通信双方在发送和接收数据时具有完全一致的定时关系
    • 同步通信把许多字符组成一个信息帧,每帧的开始用同步字符来表示
    • 在绝大多数场合下,发送端和接收端用的是同一个时钟,所以在传送数据的时候还要传送时钟信号,以便接收端可以用时钟信号来确定每一个信息位
    • 同步通信 一次通信只能传送一帧数据

5.1.4:通信速率

对于同步通信,通信速率由时钟信号决定,时钟信号越快,传输速度越快

对于异步通信,需要收发双方提前统一通信速率,这也就是我们在串口调试时波特率不对出现乱码的原因

  • 比特率:单位时间内传输的比特率个数,通常用Rb表示,单位是bit/s,缩写为bps
  • 波特率,单位时间内传输的码元个数,通常用RB表示,单位是Bd
  • 码元有N个状态时,波特率和比特率关系是:RB=Rb * log2N

5.1.5:常见的通信协议

在这里插入图片描述

5.2:串口基础知识

5.2.1:电平特性

在这里插入图片描述
在这里插入图片描述

5.2.2:串口传输协议

串口设备连接示意图
在这里插入图片描述

参数概念:

  • 波特率:一般选9600、19200、115200,意思时每秒传输的码元数
  • 起始位:先发出一个逻辑0代表开始传输数据
  • 数据位:可以有5-8位逻辑0/1,先传输低位再传输高位
  • 校验位:加入这一位逻辑‘1’变成奇数或者偶数(奇校验或者偶校验),可以选择不传输
  • 停止位:它是一个字符数据传输结束标志,数据线变成逻辑‘1’

5.2.3:STM32F103的USART资源

  • STM32F103有三个USART和两个UART(USART1-3;UART4-5)Universal Synchronous Asynchronous Receiver Transmitter

  • USART也可以当UART使用

  • 通常使用的是UART功能

  • 通过电平转化芯片变成RS232/RS485电平

在这里插入图片描述

5.2.4:STM32F103的UART框图

在这里插入图片描述

5.2.4.1:端口引脚

在这里插入图片描述

  • TX:数据发送端口

  • RX:数据接收端口

  • SW_RX:在单线和智能卡模式下接收数据,属于内部端口,没有实际的外部引脚

  • RTS:在硬件流控制下用于指示设备准备好可以接收数据了,低电平表示可以接收数据

  • CTS:在硬件流控制下用于指示设备已经发送完数据了,如果是高电平阻塞下一次发送,只有低电平才会允许下一次发送

  • SCLK:同步时钟端口,在同步模式下使用,用于输出同步时钟信号

5.2.4.2:数据寄存器单元

在这里插入图片描述

  • 发送数据(写操作)工作流程总结

    1. 数据写入发送寄存器 :处理器将要发送的数据字节写入发送寄存器。
    2. 数据转移到发送移位寄存器 :发送逻辑将发送寄存器中的数据逐位移到发送移位寄存器中。
    3. 数据发送 :发送移位寄存器按照设定的波特率和数据格式,将数据位依次发送到通信线路上。
    4. 空闲状态 :发送完成后,发送移位寄存器可能回到空闲状态,等待下一次发送操作。
5.2.4.2:发送接收控制单元

在这里插入图片描述

  • CR1、CR2、CR3:控制寄存器,控制各种使能,比如UART使能、收发中断使能、DMA使能等等
  • SR:用于表示UART的收发状态和错误状态等等
5.2.4.3:波特率发生器

在这里插入图片描述

  • fPCLK:外设总线时钟,USART1在APB2(最高72MHZ),其他(USART2-3,UART4-5)在APB1(最高36MHZ)
  • USARTDIV:USART/UART时钟分频器
  • DIV_Mantissa:这个寄存器存储了波特率计算公式中的整数部分,BRR的高12bit。
  • DIV_Fraction:这个寄存器存储了波特率计算公式中的小数部分,BRR的低4bit,每一位对应的精度是1/2的四次方=0.0625。
  • 波特率计算公式:baudrate = fPCLK / (USARTDIV * 16)
5.2.4.4:串口框图配置步骤
  • 选择需要使用的USART/UART(根据地址映射表得到地址)
  • 根据需要的波特率设置BRR寄存器
  • 根据需求配置CR中的控制位(停止位和校验位)
  • 根据需求配置同步时钟使能
  • 使能USART的发送和接收位
  • 根据需求使能发送和接收的中断位
  • 使能USART/UART的时钟
  • 使能USART/UART
  • 写USART_DR寄存器发数据,读USART_DR寄存器收数据

5.3:实现串口发送

5.3.1:软件流程设计

  • 初始化系统
    • 初始化GPIO、串口外设时钟
    • 初始化串口引脚
    • 初始化串口外设(一定要记着使能外设)
  • 串口发送

使能机制

  • GPIO :GPIO 通常不需要显式使能,因为它在复位后即可用,只需要通过配置寄存器来设置引脚模式(输入/输出、上拉/下拉等)即可使用。虽然在某些微控制器中,GPIO 的时钟可能需要使能,但通常在系统初始化时已经完成。
  • UART :UART 需要显式使能,是因为它涉及更多的内部资源和配置。在使用 UART 之前,需要通过设置寄存器来使能 UART 外设的时钟,并进行一系列的初始化配置,如设置波特率、数据格式等。
void my_uartinit()
{
	//初始化GPIO口 PA9是USART1的TX PA10是USART1的RX
	
	GPIO_InitTypeDef uart_initstruct;
	USART_InitTypeDef	my_usart_Initstruct;
	
	//1、时钟初始化
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1 , ENABLE);//PA9 PA10 USART1都是在APB2
	
	//2、GPIOA9、A10引脚初始化
	
	uart_initstruct.GPIO_Pin = GPIO_Pin_9;
	uart_initstruct.GPIO_Speed = GPIO_Speed_10MHz;
	uart_initstruct.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_Init(GPIOA, &uart_initstruct);
	
	
	uart_initstruct.GPIO_Pin = GPIO_Pin_10;
	uart_initstruct.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_Init(GPIOA, &uart_initstruct);
	
	
	//3、串口初始化
	my_usart_Initstruct.USART_BaudRate = 115200;
	my_usart_Initstruct.USART_Mode = USART_Mode_Tx |USART_Mode_Rx;
	my_usart_Initstruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None ;
	my_usart_Initstruct.USART_Parity = USART_Parity_No;
	my_usart_Initstruct.USART_StopBits = USART_StopBits_1 ;
	my_usart_Initstruct.USART_WordLength = USART_WordLength_8b;
	USART_Init(USART1, &my_usart_Initstruct);
	USART_Cmd(USART1, ENABLE);
}


USART_SendData(USART1, 'a');
//自定义发送字符串函数
void My_Usart_Send_Bit(USART_TypeDef* USARTx, uint16_t Data)
{
		USART_SendData(USARTx, Data);
		while(USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET);//发送寄存器不为空一直等待
	
}
void My_Usart_Send_String(USART_TypeDef* USARTx, char *str)
{
		uint16_t i=0; 
		while(*(str+i)!='\0'){
			My_Usart_Send_Bit(USARTx, *(str+i));
			i++;
		
		}
		while(USART_GetFlagStatus(USARTx, USART_FLAG_TC) == RESET);//发送未完成一直等待,直到影子寄存器(发送移位寄存器)为空
	
	
}

5.4:printf重定向

5.4.1、printf重定向介绍

  • C语言中的printf函数默认输出设备是显示器,如果要实现printf函数输出在串口或者LCD显示屏上,必须要重新定义标准库函数调用的与输出设备相关的函数,比如printf要输出到串口,那么就要将fputc里面的输出指向串口

5.4.2:printf重定向串口实现

//fputc 是一个标准的 C 库函数,通常用于向指定的文件流输出一个字符。
//而且fputs是一个若定义,详细解释见串口中断函数
int fputc(int ch, FILE * p)
{
	USART_SendData(USART1, (uint8_t)ch);
	
	while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
	return ch;


}


在嵌入式开发中,勾选 “Use MicroLIB” 选项可以将标准输入输出函数重定向到 MicroLIB,适用于资源受限的嵌入式系统。具体作用和意义如下:

提供基础的 C 标准库功能

  • MicroLIB 是一个轻量级的 C 标准库,为嵌入式系统提供了基础的 I/O 和字符串处理功能。它包含了如 printfscanfstrlenstrcpy 等常用函数的实现。
  • 允许开发者在嵌入式系统中使用标准的 C 库函数进行开发,而无需手动实现这些功能,提高了开发效率。

减小代码体积

  • 传统的 C 标准库通常体积较大,包含了许多不必要的功能和依赖。MicroLIB 通过裁剪和优化,去掉了不适用于嵌入式系统的部分,减小了代码体积。
  • 有助于优化系统资源的使用,特别适用于对代码大小有严格限制的嵌入式项目。

适应嵌入式环境

  • MicroLIB 的函数实现经过优化,适合在嵌入式环境中运行,具有较低的内存占用和较高的执行效率。
  • 提供了与嵌入式硬件交互的接口,方便开发者将标准输入输出重定向到特定的硬件设备,如 USART、UART 等。

简化开发流程

  • 使用 MicroLIB 可以简化嵌入式系统的开发流程,使开发者能够更专注于应用程序的开发,而不是底层硬件的驱动编写。
  • 配合 fputcfgetc 等函数的重定向,可以轻松实现标准输入输出与硬件设备的对接,如通过 USART 进行调试输出。

勾选 “Use MicroLIB” 选项有助于在嵌入式系统中高效地使用标准 C 库函数,优化资源占用,并简化开发流程。

5.5:串口的中断接收

5.5.1:软件流程设计

  • 初始化系统
    • 初始化GPIO、串口时钟
    • 初始化GPIO、串口引脚
    • 初始化串口中断、NVIC嵌套中断控制器
  • 编写串口中断函数
	//初始化GPIO口 PA9是USART1的TX PA10是USART1的RX
	
	GPIO_InitTypeDef uart_initstruct;
	USART_InitTypeDef	my_usart_Initstruct;
	NVIC_InitTypeDef NVIC_Initstruct;
	//1、时钟初始化
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1 , ENABLE);//PA9 PA10 USART1都是在APB2
	
	//2、GPIOA9、A10引脚初始化
	
	uart_initstruct.GPIO_Pin = GPIO_Pin_9;
	uart_initstruct.GPIO_Speed = GPIO_Speed_10MHz;
	uart_initstruct.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_Init(GPIOA, &uart_initstruct);
	
	
	uart_initstruct.GPIO_Pin = GPIO_Pin_10;
	uart_initstruct.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_Init(GPIOA, &uart_initstruct);
	
	
	//3、串口初始化
	
	my_usart_Initstruct.USART_BaudRate = 115200;
	my_usart_Initstruct.USART_Mode = USART_Mode_Tx |USART_Mode_Rx;
	my_usart_Initstruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None ;
	my_usart_Initstruct.USART_Parity = USART_Parity_No;
	my_usart_Initstruct.USART_StopBits = USART_StopBits_1 ;
	my_usart_Initstruct.USART_WordLength = USART_WordLength_8b;
	USART_Init(USART1, &my_usart_Initstruct);
	USART_Cmd(USART1, ENABLE);//一定要记着使能
	
	//4、初始化NVIC
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	NVIC_Initstruct.NVIC_IRQChannel = USART1_IRQn;
	NVIC_Initstruct.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Initstruct.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_Initstruct.NVIC_IRQChannelSubPriority = 0;
	NVIC_Init(&NVIC_Initstruct);
	
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);

5.5.2:细节

  • USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
    
    //虽然不需要进行EXTI设置,但是中断源绑定还是必要的
    
  •   *     @arg USART_IT_TXE:  Tansmit Data Register empty interrupt  //发送寄存器为空,用于发送中断,表示发送完成,在发送操作进行之后进行一个while循环等待,直到发送完成之后进行下一步
      *     @arg USART_IT_TC:   Transmission complete interrupt			//发送移位寄存器为空,用于发送中断,表示发送完成,同上
      *     @arg USART_IT_RXNE: Receive Data register not empty interrupt //接受数据寄存器不为空,表示接收到了数据,用于接收中断判定,即但这个寄存器不为空的时候,进入中断
      *     @arg USART_IT_IDLE: Idle line detection interrupt //?总线空闲?
    

5.6:蓝牙模块HC-05

5.6.1:硬件连接

在这里插入图片描述

5.6.2:AT设置工作模式

5.6.2.1:两种模式

1、自动连接模式,又称为透传模式

2、AT模式(AT mode)

  • 法一:按住引脚或者EN变高(接入3.3V),此时灯是慢闪,进入AT模式,默认波特率是38400;这种叫原始模式,原始模式下一直处于AT命令模式

  • 法二:HC-05上电开机,红灯快闪,按住引脚或者EN变高(接入3.3V),HC-05进入AT命令模式,这种模式是正常模式(默认波特率是9600),在这种情况下只有按住引脚或者EN变高(接入3.3V)才处于AT命令模式

    注意一定要正确设置波特率

5.6.2.2:AT模式基本配置(所有AT命令都得换行)
  • 正常模式下波特率是9600,AT命令模式下是38400,8位数据位,1位停止位,无奇偶校验的通信方式

在这里插入图片描述

5.6.2.3:AT命令的详细说明

在这里插入图片描述

5.6.2.4:蓝牙项目配置步骤
  • 1、蓝牙AT模式基础配置

    • 蓝牙连接CH340模块接线TX RX VCC GND EN五根引脚
    • AT指令配置波特率、蓝牙名称、从模式、配对密码
  • 2、蓝牙硬件连接STM32单片机

  • 3、初始化蓝牙连接的串口的 时钟、引脚和串口配置

  • 4、串口接收中断服务函数实现数据的接收和发送

在这里插入图片描述

此处串口1的作用是运用了printf重定位,向串口1发送调试信息

void USART2_IRQHandler()
{
if(USART_GetITStatus(USART2, USART_IT_RXNE)!=RESET)
{
	
	char ch =  USART_ReceiveData(USART2);
	printf("receive:	%c!!\r\n",ch);
	if(ch == '0'){
		GPIO_SetBits(GPIOA, GPIO_Pin_1 );
		printf("LED OFF!!\r\n");
		
	}
	if(ch == '1'){
		GPIO_ResetBits(GPIOA, GPIO_Pin_1 );
		printf("LED ON!!\r\n");
	}
	USART_ClearITPendingBit(USART2, USART_IT_RXNE);		

}

}

void my_uart2init()
{
	//初始化GPIO口 PA2是USART2的TX PA3是USART2的RX
	
	GPIO_InitTypeDef uart_initstruct;
	USART_InitTypeDef	my_usart_Initstruct;
	NVIC_InitTypeDef NVIC_Initstruct;
	//1、时钟初始化
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE);//PA2 PA3 
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);//
	
	//2、GPIOA9、A10引脚初始化
	
	uart_initstruct.GPIO_Pin = GPIO_Pin_2;
	uart_initstruct.GPIO_Speed = GPIO_Speed_10MHz;
	uart_initstruct.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_Init(GPIOA, &uart_initstruct);
	
	
	uart_initstruct.GPIO_Pin = GPIO_Pin_3;
	uart_initstruct.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_Init(GPIOA, &uart_initstruct);
	
	
	//3、串口初始化
	
	my_usart_Initstruct.USART_BaudRate = 9600;
	my_usart_Initstruct.USART_Mode = USART_Mode_Tx |USART_Mode_Rx;
	my_usart_Initstruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None ;
	my_usart_Initstruct.USART_Parity = USART_Parity_No;
	my_usart_Initstruct.USART_StopBits = USART_StopBits_1 ;
	my_usart_Initstruct.USART_WordLength = USART_WordLength_8b;
	USART_Init(USART2, &my_usart_Initstruct);
	USART_Cmd(USART2, ENABLE);//一定要记着使能
	
	
	//4、初始化NVIC
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	NVIC_Initstruct.NVIC_IRQChannel = USART2_IRQn;
	NVIC_Initstruct.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Initstruct.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_Initstruct.NVIC_IRQChannelSubPriority = 0;
	NVIC_Init(&NVIC_Initstruct);
	USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//串口中断使能
	
	
}

int fputc(int ch, FILE * p)
{
	USART_SendData(USART1, (uint8_t)ch);
	
	while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
	return ch;


}


5.7:ESP8266-01S wifi模块

在这里插入图片描述

5.7.1:概念

  • ESP8266是实现WIFI通信的一个模块种类(包括ESP8266-12 -12E -01S ;ESP32),ESP8266-01S由一颗ESP8266作为主控,再由一块FLASH作为存储芯片组成,3.3V电压进行烧写程序和AT指令调试;
  • 注意芯片一旦烧写便不可再使用AT指令集,需要重新刷回AT指令固件才能使用AT指令集
  • 用的是2.4G免费频段,WIFI芯片由乐鑫公司设计,模组由安信可公司生产
  • 其内部集成了802.11b/g/n协议栈和TCP/IP协议栈,具有完整的WIFI和网络功能。但一般喜欢把它当作WIFI通信使用

在这里插入图片描述

5.7.2:esp8266-01s的三种工作模式

在这里插入图片描述

我们使用AT指令进行配置的是AP模式

5.7.3:引脚接线

在这里插入图片描述
在这里插入图片描述

5.7.4:烧录固件

在这里插入图片描述

5.7.5:AT指令

在这里插入图片描述

5.7.6:WIFI功能

在这里插入图片描述

透传是指数据从一个端口直接传输到另一个端口,而不需要对数据进行复杂的处理或解析。

特性 TCP 单连接 TCP 多连接
连接数量 一个服务器与一个客户端建立一个连接。 一个服务器可以与多个客户端建立多个连接。
资源占用 较低,适合资源有限的设备。 较高,需要更多的内存和计算资源。
适用场景 一对一通信,如简单的传感器数据采集。 多用户同时通信,如 Web 服务器、聊天服务器。
实现复杂度 实现简单,逻辑清晰。 实现复杂,需要处理多个连接的并发。
数据传输顺序性 数据按顺序传输,无需处理数据交织问题。 每个连接独立,数据传输顺序性由各连接自身保证。
5.7.6.1:TCP Client单连接

详见AT指令使用示例2

先ESP连接WIFI,然后进行PC服务器设置,再进行后续TCP连接

每发一次数据就要重新设置一次AT+CIPSEND=4

TCP为什么可以多连接??????

5.7.6.2:固定远端的UDP通信(可以是多连接)

详见AT指令使用示例3

先ESP连接WIFI,然后进行PC设置,再进行后续UDP连接

  • <link ID> 是一个用于标识特定网络连接的唯一编号。在多连接模式下,每个连接都会被分配一个不同的 <link ID>,以便能够区分和管理多个连接。
  • UDP的无连接性
    • UDP是一个无连接的协议,这意味着它不建立或维护连接状态。因此,任何设备只要知道目标设备的IP地址和端口号,就可以向该目标发送数据。
  • 接收所有发送到端口的数据
    • 当你在ESP8266上设置了一个UDP服务器监听特定端口(例如端口1112)时,它会接收所有发送到该端口的数据,无论这些数据来自哪个设备。这是因为UDP协议本身的设计就是允许这种任意来源的数据接收。
  • 固定远端地址的意义
    • 为某个<link ID>设置固定的远端地址,主要是为了在发送数据时,数据被发送到指定的目标地址。这并不会限制其他设备向该端口发送数据,因为UDP协议允许这种行为。

AT+CIPSEND=4,7 // Send 7 bytes to transmission NO.4 前面一个参数是连接的ID号,因为设置的是多连接

5.7.6.3:远端可变的UDP通信(单连接)

先ESP连接WIFI,然后进行PC设置,再进行后续UDP连接

远端可变指的是端口可变,并不是IP可变,所以是单连接

发送数据到其他指定远端。例例如,发数据到 192.168.101.111, 端⼝口 1000。 一旦建立起UDP远端可变的连接,那么可以发送到相同IP但不同端口上

5.7.6.4:TCP Client 单连接透传

在上述几种方式的传输过程中,必须要先指定发送长度,并且在接收数据的时候给出的提示可以看出有一定的数据处理

  • 透传模式,仅⽀支持 TCP 单连接和 UDP 固定通信对端的情况

  • 发送+++(不要用换行符)只会退出透传的传输,并不会退出透传模式(注意此时即使成功退出也不会有任何提示)

  • 一旦进入透传之后,即使你发送对应的AT指令他也只会原样发送,不会执行命令,只有退出透传传输,在退出透传模式才能继续正常执行AT命令

5.7.6.5:固定远端的UDP透传

手册

5.7.6.6:TCP多连接

注意:ESP8266当作TCP服务器的时候默认端口是333,在PC端网络调试接口时要注意,不然不能连接

六、DMA(直接存储器存取-Direct Memory Access)

6.1:DMA简介

6.1.1:简介

可以把数据从一个地方转移到另一个地方,而且不占用CPU

6.1.2:传输场景

  • 数据传输不经过 Flash 或 SRAM 的场景:

    • 直接数据传输:在某些情况下,CPU 和外设之间可以直接传输数据,无需经过 Flash 或 SRAM。例如,通过 PIO(并行输入/输出)引脚进行数据传输时,数据直接在 CPU 和外设之间传输,不涉及存储器。
    • DMA(直接内存访问)传输:在 DMA 操作中,数据可以从外设直接传输到内存(如 SRAM),或者从内存直接传输到外设,而不需要 CPU 的干预。在这种情况下,数据经过 SRAM,但不经过 Flash。

    数据传输经过 SRAM 的场景:

    • 数据缓冲和处理:当 CPU 需要对接收到的数据进行处理时,通常会将数据先存储到 SRAM 中。SRAM 作为临时存储器,用于暂存数据以便 CPU 进行读取和处理。
    • 程序存储和数据处理:如果 CPU 需要执行某些操作或运行程序代码来处理外设数据,这些代码通常存储在 Flash 中。CPU 会将代码从 Flash 加载到 SRAM 中执行,数据也可能在处理过程中暂时存储在 SRAM 中。

    数据传输经过 Flash 的场景:

    • 程序代码存储:Flash 通常用于存储程序代码和静态数据。当 CPU 需要执行存储在 Flash 中的程序时,会将代码指令读取到 SRAM 中执行。
    • 数据存储:在某些情况下,外设数据可能需要长期存储,此时数据会写入 Flash。例如,嵌入式系统中的配置参数或日志数据可能会存储在 Flash 中。

6.1.3:DMA通道

  • 在这里插入图片描述
    在这里插入图片描述

  • DMA1:M-M;M-P;P-M

6.2:配置

6.2.1:结构体

typedef struct
{
  uint32_t DMA_PeripheralBaseAddr; /*!< Specifies the peripheral base address for DMAy Channelx. */

  uint32_t DMA_MemoryBaseAddr;     /*!< Specifies the memory base address for DMAy Channelx. */

  uint32_t DMA_DIR;                /*!< Specifies if the peripheral is the source or destination.
                                        This parameter can be a value of @ref DMA_data_transfer_direction */

  uint32_t DMA_BufferSize;         /*!< Specifies the buffer size, in data unit, of the specified Channel. 
                                        The data unit is equal to the configuration set in DMA_PeripheralDataSize
                                        or DMA_MemoryDataSize members depending in the transfer direction. */

  uint32_t DMA_PeripheralInc;      /*!< Specifies whether the Peripheral address register is incremented or not.
                                        This parameter can be a value of @ref DMA_peripheral_incremented_mode */

  uint32_t DMA_MemoryInc;          /*!< Specifies whether the memory address register is incremented or not.
                                        This parameter can be a value of @ref DMA_memory_incremented_mode */

  uint32_t DMA_PeripheralDataSize; /*!< Specifies the Peripheral data width.
                                        This parameter can be a value of @ref DMA_peripheral_data_size */

  uint32_t DMA_MemoryDataSize;     /*!< Specifies the Memory data width.
                                        This parameter can be a value of @ref DMA_memory_data_size */

  uint32_t DMA_Mode;               /*!< Specifies the operation mode of the DMAy Channelx.
                                        This parameter can be a value of @ref DMA_circular_normal_mode.
                                        @note: The circular buffer mode cannot be used if the memory-to-memory
                                              data transfer is configured on the selected Channel */

  uint32_t DMA_Priority;           /*!< Specifies the software priority for the DMAy Channelx.
                                        This parameter can be a value of @ref DMA_priority_level */

  uint32_t DMA_M2M;                /*!< Specifies if the DMAy Channelx will be used in memory-to-memory transfer.
                                        This parameter can be a value of @ref DMA_memory_to_memory */
}DMA_InitTypeDef;
  • 数据传输

    • DMA_PeripheralBaseAddr 配置在 DMA通道x外设地址寄存器(DMA_CPARx)

    • DMA_MemoryBaseAddr配置在DMA通道x存储器地址寄存器(DMA_CMARx)

    • DMA_DIR配置在DMA通道x配置寄存器(DMA_CCRx)中的DIR

    • 在这里插入图片描述

  • 数据大小及传输单位

    • DMA通道x传输数量寄存器(DMA_CNDTRx)(x = 1…7)
    • 在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
  • DMA模式选择(DMA通道x配置寄存器(DMA_CCRx)(x = 1…7) 中的CIRC位)

    在这里插入图片描述

6.2.2:实践1:M->M(内部Flash(code)数据传输到SRAM(变量))

	

void DMA_MTM_InitTypeDef()
{
	DMA_InitTypeDef DMA_Initstruct;
	
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
	
	
	 
	//数据传输:地址和方向
	DMA_Initstruct.DMA_DIR = DMA_DIR_PeripheralSRC;
	DMA_Initstruct.DMA_MemoryBaseAddr = (uint32_t)DST_BUFFER;
	DMA_Initstruct.DMA_PeripheralBaseAddr = (uint32_t)SRC_BUFFER;
	//数据大小及传输单位
	DMA_Initstruct.DMA_BufferSize =	BUFFER_SIZE;
	DMA_Initstruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
	DMA_Initstruct.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
	DMA_Initstruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Word ;
	DMA_Initstruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word ;
	//模式选择
	DMA_Initstruct.DMA_Mode = DMA_Mode_Normal;
	//??
	DMA_Initstruct.DMA_M2M = DMA_M2M_Enable ;
	DMA_Initstruct.DMA_Priority = DMA_Priority_High;

	DMA_Init(DMA1_Channel6, &DMA_Initstruct);
	DMA_ClearFlag(DMA1_FLAG_TC6);
	DMA_Cmd(DMA1_Channel6, ENABLE);

}

uint8_t Buffercmp(const uint32_t *pBuffer1,uint32_t *pBuffer2,uint32_t buffersize)
{
	uint16_t i=0;
	for(;i<buffersize;i++){
		if(*(pBuffer1+i)!=*(pBuffer2+i)){
			return 0;
		
		}
	return 1;
	
	}
  • //如果想要在主函数中调用其他文件中的变量,格式如下:
    extern const uint32_t SRC_BUFFER[BUFFER_SIZE];
    extern uint32_t DST_BUFFER[BUFFER_SIZE];
    
  • 初始化时一定要记得清除发送标志位??

6.2.3:实践2:M->P(内部SRAM数据传输到串口;+LED,延时DMA传输不需要占用CPU)

void my_uartinit()
{
	//初始化GPIO口 PA9是USART1的TX PA10是USART1的RX
	
	GPIO_InitTypeDef uart_initstruct;
	USART_InitTypeDef	my_usart_Initstruct;
	NVIC_InitTypeDef NVIC_Initstruct;
	//1、时钟初始化
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1 , ENABLE);//PA9 PA10 USART1都是在APB2
	
	//2、GPIOA9、A10引脚初始化
	
	uart_initstruct.GPIO_Pin = GPIO_Pin_9;
	uart_initstruct.GPIO_Speed = GPIO_Speed_10MHz;
	uart_initstruct.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_Init(GPIOA, &uart_initstruct);
	
	
	uart_initstruct.GPIO_Pin = GPIO_Pin_10;
	uart_initstruct.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_Init(GPIOA, &uart_initstruct);
	
	
	//3、串口初始化
	
	my_usart_Initstruct.USART_BaudRate = 115200;
	my_usart_Initstruct.USART_Mode = USART_Mode_Tx |USART_Mode_Rx;
	my_usart_Initstruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None ;
	my_usart_Initstruct.USART_Parity = USART_Parity_No;
	my_usart_Initstruct.USART_StopBits = USART_StopBits_1 ;
	my_usart_Initstruct.USART_WordLength = USART_WordLength_8b;
	USART_Init(USART1, &my_usart_Initstruct);
	USART_Cmd(USART1, ENABLE);//一定要记着使能
	
	/*
	//4、初始化NVIC
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	NVIC_Initstruct.NVIC_IRQChannel = USART1_IRQn;
	NVIC_Initstruct.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Initstruct.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_Initstruct.NVIC_IRQChannelSubPriority = 0;
	NVIC_Init(&NVIC_Initstruct);
	
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
	*/
}

uint8_t SenBuffer[SEND_SIZE];
void USART_DMA_Init(void)
{
	DMA_InitTypeDef DMA_Initstruct;
	
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
	//数据传输:地址和方向
	DMA_Initstruct.DMA_DIR = DMA_DIR_PeripheralDST;
	DMA_Initstruct.DMA_MemoryBaseAddr = (uint32_t)SenBuffer;
	DMA_Initstruct.DMA_PeripheralBaseAddr = (uint32_t)DMA_DR_ADDR;
	//数据大小及传输单位
	DMA_Initstruct.DMA_BufferSize =	SEND_SIZE;
	DMA_Initstruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
	DMA_Initstruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable  ;
	DMA_Initstruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte ;
	DMA_Initstruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte ;	
	//模式选择
	DMA_Initstruct.DMA_Mode = DMA_Mode_Normal;
	//??
	DMA_Initstruct.DMA_M2M = DMA_M2M_Disable ;
	DMA_Initstruct.DMA_Priority = DMA_Priority_High;
	
	DMA_Init(DMA1_Channel4, &DMA_Initstruct);
	DMA_ClearFlag(DMA1_FLAG_TC4);
	DMA_Cmd(DMA1_Channel4, ENABLE);

}

//main
extern uint8_t SenBuffer[SEND_SIZE];
	uint16_t i=0;
	for(;i<SEND_SIZE;i++)
	{
			SenBuffer[i]='0';
			
	
	}
	//串口初始化
	my_uartinit();
	//DMA 内存到外设,这儿一定要有USART_DMACmd
	USART_DMA_Init();
	USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);//使能USART的DMA接口
  • 从内存发往外设,发送到的是外设的数据寄存器

  • 当 USART1 的发送数据寄存器为空时,会触发发送 DMA 请求;

  • 在 DMA 传输中,从内存到外设 USART 的发送操作需要使用 USART 的 TX(发送)对应的 DMA 通道。这是因为 DMA 通道的选择取决于数据传输的方向:在内存到外设的发送操作中,数据是从内存传输到 USART 的发送缓冲区,然后通过 USART 的 TX 引脚发送出去。

    具体原因

    • 发送操作:当使用 DMA 从内存发送数据到 USART 时,数据是从内存传输到 USART 的发送缓冲区,然后通过 TX 引脚发送出去。因此,需要使用与 USART 的 TX 引脚对应的 DMA 通道。
    • 接收操作:相反,如果从 USART 接收数据到内存,则需用 RX 引脚对应的 DMA 通道,把数据从接收缓冲区传到内存。

    总之,选择 DMA 通道的依据是数据传输的方向。发送用 TX 通道,接收用 RX 通道。这样可确保数据正确传输至目标外设引脚。

  • 串口在使能之后,只要数据寄存器中有值就会发出

  • DMA_Cmd(DMA1_Channel4, ENABLE);在DMA通道使能之后必须在在主函数中有USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);

    • 在初始化 DMA 并使能之后,还需要启用 USART 的 DMA 请求,以确保 USART 能够自动向 DMA 发送数据传输请求。这一步骤是实现 DMA 与 USART 协同工作的必要环节。

七、定时器TIM

7.1:定时器基本知识

  • 简单来说就是定时的机器,是存在于STM32的一个外设;STM32总共有8个定时器,分别是2个高级定时器(TIM1和TIM8),4个通用寄存器(TIM2、3、4、5),2个基本定时器(TIM6、7);

  • 三种定时器的区别:在这里插入图片描述

7.2:基本定时器

在这里插入图片描述

7.2.1:时钟源:

在这里插入图片描述

7.2.2:控制器:

控制定时器的功能

  • 复位 :将定时器的相关寄存器和状态重置到初始状态,以便重新开始计时或配置。
  • 使能 :启动定时器,允许其开始计数。
  • 计数 :控制定时器的计数操作,包括计数方向(向上计数或向下计数)、计数模式(如边缘对齐模式、中心对齐模式等)。
  • 触发 ADC :在特定条件下触发模数转换器(ADC),例如定时器达到特定值时,可用于周期性地采集模拟信号。

涉及到的寄存器

  • CR1/CR2(Control Register 1/2) :用于控制定时器的各种功能和模式。
    • CR1 通常包含一些基本的控制位,如定时器使能位(CEN)、方向位(DIR)、计数模式选择位(CMS)等。
    • CR2 用于控制更高级的功能,如重复计数器的使能和配置等。
  • DIER(DMA/Interrupt Enable Register) :用于使能定时器的中断和 DMA 请求。
    • 包含多个中断使能位,如更新中断使能位(UIE)、捕获/比较中断使能位(CCxE)等。
    • 也包含 DMA 请求使能位,如更新 DMA 请求使能位(UDE)、捕获/比较 DMA 请求使能位(CCxDE)等。
  • EGR(Event Generation Register) :用于触发定时器的事件。
    • 可以软件触发更新事件(UG)、捕获/比较事件(CCxG)等。
    • 更新事件可以重置计数器和预分频器,捕获/比较事件可用于捕获或比较操作。
  • SR(Status Register) :用于表示定时器的状态信息。
    • 包含多个状态位,如更新标志位(UIF)、捕获/比较标志位(CCxF)等,用于指示定时器的当前状态。

7.2.3:时基单元(定时器的心脏)

在这里插入图片描述

影子寄存器:PSC和ARR寄存器都有个影子寄存器(可以从图中看到阴影),影子寄存器起到了一个缓冲的作用,用户值->寄存器->影子寄存器->起作用;

7.2.4:定时时间的计算

定时器时间=(PSC+1)*(ARR+1)/72M

  • 怎么理解?

    72M是TIM的时钟,72M/(PSC+1)就是当前的实际时钟,倒数就是时间,再*计数值(ARR+1),就是当前计数器用到的时间;

7.2.5:基本定时器的功能

  • TIM6、7,只能向上计数,计数器16bit
  • 没有外部的GPIO,所以只能是内部资源用来定时
  • 时钟来自于PCLK1,为72M,可以实现1-65536分频

7.3:通用定时器

7.3.1:计数模式

  • 向上计数
  • 向下计数
  • 中央对齐模式

在这里插入图片描述

7.3.2:框图解释

在这里插入图片描述

四个部分:时钟产生器部分、时基单元部分、输入捕获部分、输出比较部分

7.3.2.1:时钟产生器部分
  • 内部时钟CK_INT
  • 在这里插入图片描述
7.3.2.2:时基部分

在这里插入图片描述

7.3.2.3:输入捕获通道
7.3.2.3:输出比较通道

7.3.3:常用库函数

void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
typedef struct
{
  uint16_t TIM_Prescaler;         /*!< 分频因子Specifies the prescaler value used to divide the TIM clock.
                                       This parameter can be a number between 0x0000 and 0xFFFF */

  uint16_t TIM_CounterMode;       /*!< 计数模式Specifies the counter mode.
                                       This parameter can be a value of @ref TIM_Counter_Mode */

  uint16_t TIM_Period;            /*!< 自动重装载值Specifies the period value to be loaded into the active
                                       Auto-Reload Register at the next update event.
                                       This parameter must be a number between 0x0000 and 0xFFFF.  */ 

  uint16_t TIM_ClockDivision;     /*!<外部输入时钟分频因子,基本定时器没有 Specifies the clock division.
                                      This parameter can be a value of @ref TIM_Clock_Division_CKD */
								  //ARR和分频数最大是65535,写的时候一定要注意不要超
  uint8_t TIM_RepetitionCounter;  /*!< 重复计数值,基本定时器没有高级定时器专用Specifies the repetition counter value. Each time the RCR downcounter
                                       reaches zero, an update event is generated and counting restarts
                                       from the RCR value (N).
                                       This means in PWM mode that (N+1) corresponds to:
                                          - the number of PWM periods in edge-aligned mode
                                          - the number of half PWM period in center-aligned mode
                                       This parameter must be a number between 0x00 and 0xFF. 
                                       @note This parameter is valid only for TIM1 and TIM8. */
} TIM_TimeBaseInitTypeDef;   



void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);   
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);







7.3.4:定时器控制LED灯软件流程设计

  • 初始化
    • 初始化LED灯
    • 初始化定时器(包括初始化、中断配置、使能定时器)
  • 编写定时器中断函数

7.4:定时器中断应用

7.4.1:内部时钟选择

在这里插入图片描述

7.4.2:计数器模式(见手册)

7.4.3:超声波模块

7.4.3.1:工作原理
  • TRIG引脚触发,给该引脚至少10us的高电平信号启动测距

  • 测距启动之后自动发送8个40KHZ的方波,自动检测是否有信号返回

  • 在测距启动之后,ECHO引脚自动变为高电平,直到收到返回的信号之后变为低电平,所以距离就是这段高电平时间/2再乘上速度系数

  • VCC最好是5V

7.4.3.2:代码流程思路

TRIG启动

等待ECHO高电平

开启定时器{这是整个计时的起点,所以要有很多预备操作}

(中断计时)

等待高电平结束

关闭定时器

计算时间(包括两部分)

打印

7.4.3.3:代码
#include "tim.h"
#include "stm32f10x.h"
//#include "delay.h"

void delay_us(uint32_t us)
{
	us *=8;
	while(us--);


}
void delay_ms(uint32_t ms)
{
	
	while(ms--){
	
	delay_us(1000);
	
	}


}

int mscount=0;

void TIM_Base_Init()
{
	
	NVIC_InitTypeDef time_nvic_struct;
	TIM_TimeBaseInitTypeDef tim_initstruct;
	//TIM2是通用定时器,在APB1
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
	//TIMXCLK是72MHZ
	tim_initstruct.TIM_ClockDivision =TIM_CKD_DIV1;//外部时钟分频,无用    
	tim_initstruct.TIM_CounterMode = TIM_CounterMode_Up ;
	tim_initstruct.TIM_Period = 1000-1;
	tim_initstruct.TIM_Prescaler = 72-1;//定时器时间=(PSC+1)*(ARR+1)/TIMXCLK,此时算是一次计数结束代表1ms
	tim_initstruct.TIM_RepetitionCounter = 0;//高级定时器专属
	
	
	
	TIM_TimeBaseInit(TIM2, &tim_initstruct);
	TIM_Cmd(TIM2, DISABLE);
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);

	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	time_nvic_struct.NVIC_IRQChannel = TIM2_IRQn;
	time_nvic_struct.NVIC_IRQChannelCmd =ENABLE;
	time_nvic_struct.NVIC_IRQChannelPreemptionPriority = 0;
	time_nvic_struct.NVIC_IRQChannelSubPriority = 0;
	NVIC_Init(&time_nvic_struct);


}


void HC04_Init()
{
	GPIO_InitTypeDef Key_Initstruct;

	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

	//PB11为输出引脚,TRIG启动超声波,推挽输出
	//PB10为输入引脚,高电平时间获得距离,下拉输入
	Key_Initstruct.GPIO_Pin = GPIO_Pin_11;
	Key_Initstruct.GPIO_Speed = GPIO_Speed_10MHz;
	Key_Initstruct.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_Init(GPIOB, &Key_Initstruct);
	
	Key_Initstruct.GPIO_Pin = GPIO_Pin_10;
	//Key_Initstruct.GPIO_Speed = GPIO_Speed_2MHz;
	Key_Initstruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(GPIOB, &Key_Initstruct);
	
	


}

void open_tim(void)
{
	//记着一定要清除上一次测量遗留的数据,避免对这次测量产生影响
	mscount=0;
	TIM_SetCounter(TIM2, 0)	;	
	
	TIM_Cmd(TIM2, ENABLE);
	
	

}	
void close_tim(void)
{
	TIM_Cmd(TIM2, DISABLE);

}	

uint16_t Get_Echo_time(void)//最终时间=定时器中断次数+定时器不足一个中断,单位统一返回值是微秒
{
	uint16_t t = TIM_GetCounter(TIM2);
	return mscount*1000+t;

}
float Get_Length()
{
	float t=0;//时间
	float length=0;//距离
	int i =0;
	float sum = 0;
	while(i!=5)		
	{
	
	
				//1.启动超声波模块,给TRIG不低于10us的高电平
				GPIO_SetBits(GPIOB, GPIO_Pin_11 );
				delay_us(20);								//10us延时
				GPIO_ResetBits(GPIOB, GPIO_Pin_11 );




				//2.启动定时器计算高电平时间
				while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_10)==0);//变为高电平说明启动
				open_tim();				//打开定时器
				while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_10)==1);//等待高电平结束说明测量结束
				close_tim();//关闭定时器
				t=Get_Echo_time();//获取时间
				TIM_SetCounter(TIM2, 0)	;							//这次计算完成之后为了下次计数的准确寄存器要置0
				delay_ms(15);														//写寄存器需要时间


				//3.利用时间获得距离
				length = ((float)t/58.0);//单位是mm
				sum = sum +length;
				i++;
	
	}
	
	
	length = sum /5.0;
	return length;





}
void TIM2_IRQHandler()
{
	if(TIM_GetITStatus(TIM2, TIM_IT_Update)!=RESET)
	{
		mscount++;
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);

	}


}




7.5:定时器输出PWM应用

7.5.1:通用PWM原理

在这里插入图片描述

ARR寄存器确定周期(频率),CCR寄存器确定占空比

CNT是计数值,CCRX是比较值

7.5.2:输出部分

在这里插入图片描述

7.5.3:PWM模式

  • 模式1当前值小于比较值为有效电平,模式2则为当前值大于比较值才为有效电平(有效电平是高电平还是低电平还是要看具体的配置)

7.5.4:定时器PWM库函数配置

PWM波的形成就是输出比较(OC),所以选用的初始化用的是TIM_OCInitTypeDef

在这里插入图片描述

//启用或禁用某个通道的比较寄存器(CCRx)预装载功能,避免输出波形在周期中间被意外修改。

void TIM_OC1PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
void TIM_OC2PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
void TIM_OC3PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
void TIM_OC4PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);

//✅ 预装载(Preload)是什么?
未启用预装载时:你对 TIMx->CCR1 的写操作会立即生效,可能导致 PWM 波形在当前周期内被打断或抖动。
启用预装载后:你对 TIMx->CCR1 的写操作会先写入影子寄存器,只有**下一个更新事件(UEV)**到来时才真正生效,波形更稳定。

场景 是否需要启用预装载
普通输出比较(单次触发) ❌ 不需要
PWM 输出(连续波形) ✅ 强烈建议开启
动态修改占空比(如呼吸灯) ✅ 必须开启

7.5.5:SG90舵机控制

在这里插入图片描述

周期为20ms,相关计算;20ms=7200*200/72 000 ,所以得出PSC是7200且ARR是200

什么时候得打开AFIO时钟?

功能 举例 是否需要开启 AFIO 时钟
引脚重映射 GPIO_PinRemapConfig(...) ✅ 必须
关闭 JTAG/SWD GPIO_PinRemapConfig(GPIO_Remap_SWJ_Disable, ENABLE) ✅ 必须
事件输出引脚 GPIO_EventOutputConfig(...) ✅ 必须
EXTI 外部中断线映射 GPIO_EXTILineConfig(...) ✅ 必须
普通 GPIO 输入输出 GPIO_Init(...) ❌ 不需要
普通外设功能(如 USART、SPI、I2C) USART_Init(...) ❌ 不需要
#include "pwm.h"
#include "stm32f10x.h"

void PWM_Init(void)
{
//用TIM3-CH2的PWM功能,包含三个初始化,PA7、基本定时器、OC
	
	GPIO_InitTypeDef pwmout_initstruct;
	TIM_TimeBaseInitTypeDef tim_initstruct;
	TIM_OCInitTypeDef PWM_Initstruct;
	
	//PB5,因为是部分重映射,所以还要有时钟和重映射函数,当然也可以直接选择PA7,正常配置就行
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB , ENABLE);
	pwmout_initstruct.GPIO_Pin = GPIO_Pin_5;
	pwmout_initstruct.GPIO_Speed = GPIO_Speed_10MHz;
	pwmout_initstruct.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_Init(GPIOB, &pwmout_initstruct);
	//引脚重映射
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
	GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3 , ENABLE);
	
	//基本定时器
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
	tim_initstruct.TIM_ClockDivision =TIM_CKD_DIV1;//用于设置定时器内部滤波器(如输入捕获滤波器)的采样时钟分频系数。   
	tim_initstruct.TIM_CounterMode = TIM_CounterMode_Up ;
	tim_initstruct.TIM_Period = 200-1;
	tim_initstruct.TIM_Prescaler = 7200-1;//定时器时间=(PSC+1)*(ARR+1)/TIMXCLK,此时算是一次计数结束代表1ms
	tim_initstruct.TIM_RepetitionCounter = 0;//高级定时器专属
	TIM_TimeBaseInit(TIM3, &tim_initstruct);
	TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);//启用预装载功能
	
	
	//OC
	
	PWM_Initstruct.TIM_OCMode = TIM_OCMode_PWM1;
	PWM_Initstruct.TIM_OCPolarity = TIM_OCPolarity_High;
	PWM_Initstruct.TIM_OutputState =TIM_OutputState_Enable;
	//PWM_Initstruct.TIM_Pulse  = 5;//占空比2.5%,200*%2.5=5,不在此处设置,后自己写一个函数去改变角度
	TIM_OC2Init(TIM3, & PWM_Initstruct);

	//使能定时器


	TIM_Cmd(TIM3, ENABLE);



}

void SG90_angle(uint16_t angle)
{
	
	
	/*switch(angle)
	{
		case 180:  TIM_SetCompare2(TIM3, 175 );
								break;
		case 135:  TIM_SetCompare2(TIM3, 180 );
								break;
		case 90:  TIM_SetCompare2(TIM3, 185 );
								break;
		case 45:  TIM_SetCompare2(TIM3, 190 );
								break;
		case 0:  TIM_SetCompare2(TIM3, 195 );
								break;
	}*/

	uint16_t k=(uint16_t)((0.5+angle/45*0.5)*5/100*200);	
	
	TIM_SetCompare2(TIM3, k );



}

八、系统定时器SysTick

8.1:概念

  • 24位系统定时器,只能递减,存在于内核嵌套在NVIC中,所有的Cortex-M中都有这个系统定时器
  • 在这里插入图片描述
    在这里插入图片描述

8.2:配置

/** @addtogroup CMSIS_CM3_SysTick CMSIS CM3 SysTick
  memory mapped structure for SysTick
  @{
 */
typedef struct
{
  __IO uint32_t CTRL;                         /*!< Offset: 0x00  SysTick Control and Status Register */
  __IO uint32_t LOAD;                         /*!< Offset: 0x04  SysTick Reload Value Register       */
  __IO uint32_t VAL;                          /*!< Offset: 0x08  SysTick Current Value Register      */
  __I  uint32_t CALIB;                        /*!< Offset: 0x0C  SysTick Calibration Register        */
} SysTick_Type;

static __INLINE uint32_t SysTick_Config(uint32_t ticks)
{ 
  if (ticks > SysTick_LOAD_RELOAD_Msk)  return (1);            /* Reload value impossible */
                                                               
  SysTick->LOAD  = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;      /* set reload register */
  NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);  /* set Priority for Cortex-M0 System Interrupts */
  SysTick->VAL   = 0;                                          /* Load the SysTick Counter Value */
  SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk | 
                   SysTick_CTRL_TICKINT_Msk   | 
                   SysTick_CTRL_ENABLE_Msk;                    /* Enable SysTick IRQ and SysTick Timer */
  return (0);                                                  /* Function successful */
}
//外部时钟源(HSE LSE MCO)经过PPL倍频变成SYSCLK
//没有预分频系数,直接是LOAD计算,默认时钟是72M(内核HCLK),所以72000/72000000是1ms,72/72000000是1us
#include "SysTick.h"
#include "stm32f10x.h"

void ms_delay(uint32_t ms)
{
	int i	;	
		SysTick_Config(72000);
		
		for(i=0;i<ms;i++){
		
		//怎么判断当前是不是一次计时结束 while一直判断SysTick的控制寄存器(CTRL)寄存器的COUNTFLAG标志位是否为0,为1则一直在循环内等待
		while(!((SysTick->CTRL) &(1<<16)));
		
		
		}
		
		//延时之后失能系统定时器
		SysTick->CTRL &=~SysTick_CTRL_ENABLE_Msk;


}
void us_delay(uint32_t us)
{
		int i=0;
		SysTick_Config(72);
	
		for(;i<us;i++){
		
		//怎么判断当前是不是一次计时结束 while一直判断SysTick的控制寄存器(CTRL)寄存器的COUNTFLAG标志位是否为0,为1则一直在循环内等待
		while(!((SysTick->CTRL) &(1<<16)));
		
		
		}
		
		//延时之后失能系统定时器
		SysTick->CTRL &=~SysTick_CTRL_ENABLE_Msk;  //和使能掩码或操作就是使能,和使能掩码非操作再与操作就是失能


}


九、LCD1602液晶显示屏

9.1:概念

  • 显示16X2个字符,每个字符为5X7的点阵
  • 在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

9.2:配置

在这里插入图片描述
在这里插入图片描述

9.3:显示数字

在这里插入图片描述

十、SPI

10.1:概念

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

10.1.1:时钟组合

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

10.1.2:接口相关

在这里插入图片描述

10.1.3:配置流程

在这里插入图片描述
在这里插入图片描述

10.2:配置

#include "spi.h"
#include "stm32f10x.h"

void SPI2_Init()
{

    GPIO_InitTypeDef  GPIO_InitStructure;
		SPI_InitTypeDef SPI_InitStructure;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    

    //SPI2			PB13 – SPI2_SCK	PB14 – SPI2_MISO		PB15 – SPI2_MOSI
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //????为什么MOSI也是推挽输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 |	GPIO_Pin_15;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
		
	
		
		SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;
		SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
		SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
		SPI_InitStructure.SPI_CRCPolynomial = 7;
		SPI_InitStructure.SPI_DataSize =	SPI_DataSize_8b;
		SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
		SPI_InitStructure.SPI_FirstBit =	SPI_FirstBit_MSB;
		SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
		SPI_InitStructure.SPI_NSS	= SPI_NSS_Hard;
	
	
		
		SPI_Init(SPI2, &SPI_InitStructure);//这儿只是进行了初始化并未进行使能


}

	
//SPI2读一个字节
uint8_t SPI2_read_write_byte(uint8_t data)
{
	uint8_t t;
	//先发送后接收
	while(!SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE))//等待发送缓冲区为空
	{
		t++;
		if(t>=200)//超时返回
		{
		return 0;
		}	
	}
	SPI_I2S_SendData(SPI2, data);
	while(!SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE))//等待接收缓冲区不为空
	{
		t++;
		if(t>=200)
		{
		return 0;
		}	
	}
	
	return SPI_I2S_ReceiveData(SPI2);





}


void 	SPI_SetSpeed(uint16_t SPI_BaudRatePrescaler)
{

	
	 assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));

		SPI2->CR1 &= 0xFFC7;//清除BR位
		SPI2->CR1 |= SPI_BaudRatePrescaler;
		SPI_Cmd(SPI2, ENABLE);//使能

}





















10.3:NRF24L01无线模块

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

十一、温湿度DHT11

11.1:简介

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

总结时序:

启动信号:DATA输出低电平,持续超过18ms,再输出高电平,延时20-40us

响应信号:DATA自动输入低电平,持续80us,再输入高电平,持续80us,响应信号结束

数据读取阶段:先是变低再变高,0/1的主要区别在于高电平的持续时间

11.2:代码复现

#include "dht11.h"
#include "stm32f10x.h"
//#include "tim.h"
#include "stdio.h"
#include "delay.h" 
#include "usart.h"


#define DHT11_Port GPIOB
#define DHT11_Pin GPIO_Pin_12

#define DHT11_Data_DOWN() GPIO_ResetBits(DHT11_Port, DHT11_Pin)
#define DHT11_Data_UP() GPIO_SetBits(DHT11_Port, DHT11_Pin)
#define DHT11_GET() GPIO_ReadInputDataBit(DHT11_Port, DHT11_Pin)

void DHT11_Init()
{

	GPIO_InitTypeDef dht11_initstruct;
	
	//1、时钟初始化
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB , ENABLE);
	
	//2、GPIOA1引脚初始化
	
	dht11_initstruct.GPIO_Pin = GPIO_Pin_12;
	dht11_initstruct.GPIO_Speed = GPIO_Speed_50MHz;
	dht11_initstruct.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_Init(GPIOB, &dht11_initstruct);
	DHT11_Data_UP();


}


void Data_Mode_IN()
{

	GPIO_InitTypeDef dht11_initstruct;
	
	//1、时钟初始化
	
	
	//2、GPIOA1引脚初始化
	
	dht11_initstruct.GPIO_Pin = GPIO_Pin_12;
	//dht11_initstruct.GPIO_Speed = GPIO_Speed_2MHz;
	dht11_initstruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(GPIOB, &dht11_initstruct);




}
void Data_Mode_OUT()
{



	GPIO_InitTypeDef dht11_initstruct;
	
	//1、时钟初始化
	//RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB , ENABLE);
	
	//2、GPIOA1引脚初始化
	
	dht11_initstruct.GPIO_Pin = GPIO_Pin_12;
	dht11_initstruct.GPIO_Speed = GPIO_Speed_50MHz;
	dht11_initstruct.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_Init(GPIOB, &dht11_initstruct);
	


}

void DHT11_Start()
{

	Data_Mode_OUT();//转换为输出模式
	DHT11_Data_DOWN();
	delay_ms(18);
	DHT11_Data_UP();
	delay_us(30);


}


/* 读取 1 bit:先等待低电平结束,再测高电平宽度 */
/*static uint8_t DHT11_ReadBit(void)
{
    while (DHT11_GET() == Bit_SET);   // 等待 50 us 低电平;等待低电平开始
    while (DHT11_GET() == Bit_RESET); // 等待高电平开始
    delay_us(40);                      // 延时 40 us
    if (DHT11_GET() == Bit_SET)       // 仍高 -> 70 us → bit1
    {
        while (DHT11_GET() == Bit_SET); // 等待高电平结束
        return 1;
    }
    else                                // 已低 -> 26~28 us → bit0
        return 0;
}*/

uint8_t DHT11_Read_Byte()
{
	int i=0;
	uint8_t data=0;

	for(i=0;i<8;i++)
	{
		
			while(DHT11_GET()==1);	//等待低电平开始
			while(DHT11_GET()==0);	//等待高电平开始
			delay_us(40);//0/1区别主要在高电平的持续时间
			
			data=data<<1;
			//data |= DHT11_ReadBit();
			if(DHT11_GET()==1)
			{
				
				data=data+1;
				while(DHT11_GET()==1);	//等待高电平结束
			}
			
	}

	return data;

}

uint8_t  DHT11_ReadData(uint8_t * humi_h,uint8_t * humi_l,uint8_t * temp_h,uint8_t * temp_l)
{
	
	int i = 0;
	uint8_t data[5]={0};//一定要进行初始化
	
	//启动信号
	DHT11_Start();
	
	Data_Mode_IN();//转换为输入模式
	
	if (DHT11_GET() != Bit_RESET) return 0;
	
	//等待启动信号结束
	//while(DHT11_GET()==1);	//等待低电平开始
	
	while(DHT11_GET()==0);	//等待高电平开始
	//printf("hello kai!");	
	while(DHT11_GET()==1);	//等待低电平开始
	//每次读取1个字节,读取5次
	
	
	
	for(i=0;i<5;i++)
	{
		
		data[i]=DHT11_Read_Byte();
		printf("%d/r",data[i]);

	
	}
	*(humi_h)=data[0];
	*(humi_l)=data[1];
	*(temp_h)=data[2];
	*(temp_l)=data[3];
		
	//进行校验
	if((data[0]+data[1]+data[2]+data[3])!=data[4])
	{
	return 0;
	}
	
	return 1;
}


十二、IIC总线

12.1:简介

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

下图中黑色是主机,白色是从机

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

12.2:通信过程

在这里插入图片描述
在这里插入图片描述

12.3:库函数

typedef struct
{
  uint32_t I2C_ClockSpeed;          /*!< Specifies the clock frequency.
                                         This parameter must be set to a value lower than 400kHz */

  uint16_t I2C_Mode;                /*!< Specifies the I2C mode.
                                         This parameter can be a value of @ref I2C_mode 
                                         此处不区分主从模式*/

  uint16_t I2C_DutyCycle;           /*!< Specifies the I2C fast mode duty cycle.
                                         This parameter can be a value of @ref I2C_duty_cycle_in_fast_mode */

  uint16_t I2C_OwnAddress1;         /*!< Specifies the first device own address.
                                         This parameter can be a 7-bit or 10-bit address.
                                         此处还有一个知识点:双地址*/

  uint16_t I2C_Ack;                 /*!< Enables or disables the acknowledgement.
                                         This parameter can be a value of @ref I2C_acknowledgement */

  uint16_t I2C_AcknowledgedAddress; /*!< Specifies if 7-bit or 10-bit address is acknowledged.
                                         This parameter can be a value of @ref I2C_acknowledged_address */
}I2C_InitTypeDef;

void I2C_Init(I2C_TypeDef* I2Cx, I2C_InitTypeDef* I2C_InitStruct);
void I2C_Cmd(I2C_TypeDef* I2Cx, FunctionalState NewState);

void I2C_Send7bitAddress(I2C_TypeDef* I2Cx, uint8_t Address, uint8_t I2C_Direction);
void I2C_OwnAddress2Config(I2C_TypeDef* I2Cx, uint8_t Address);

void I2C_SendData(I2C_TypeDef* I2Cx, uint8_t Data);
uint8_t I2C_ReceiveData(I2C_TypeDef* I2Cx);



在这里插入图片描述

12.3:OLED

12.3.1:简介

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

12.3.2:硬件IIC驱动OLED屏

#ifndef _OLED_H
#define _OLED_H

#include "stm32f10x.h"
#include "stdio.h"


#define OLED_ADDRESS 0x78    // 或 0x3D,取决于模块

void I2C_config(void);
void I2C_Write_Byte(uint8_t addr,uint8_t data);

void WriteCmd(uint8_t command);


void WriteData(uint8_t data);
void OLED_Init(void);
void oled_setpos(uint8_t x,uint8_t y);
void oled_fill(uint8_t data);
void  oled_cls(void);
void oled_open(void);
void oled_close(void);
void ShowStr(uint8_t x,uint8_t y,uint8_t ch[],uint8_t Textsize);
	

#endif



#include "stm32f10x.h"
#include "oled.h"
#include "delay.h"
#include "codetab.h"
#include "SysTick.h"
void I2C_config()
{
	
    I2C_InitTypeDef    I2C_InitStructure;
    GPIO_InitTypeDef  GPIO_InitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);

    //PB6--SCL: PB7--SDA
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    I2C_DeInit(I2C1);
    I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
    I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; 
    I2C_InitStructure.I2C_ClockSpeed = 400000;
    I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
    I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
    I2C_InitStructure.I2C_OwnAddress1 = 0x30;
    I2C_Init(I2C1, &I2C_InitStructure);
    I2C_Cmd(I2C1, ENABLE);
	


}

//addr是从机中的寄存器地址,详见通讯复合格式
void I2C_Write_Byte(uint8_t addr,uint8_t data)
{

	
	
	
	
		while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));//监听总线状态是否繁忙
		I2C_GenerateSTART(I2C1, ENABLE);//发送起始信号
	
		while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));//检查事件状态 EV5事件	
		I2C_Send7bitAddress(I2C1, OLED_ADDRESS, I2C_Direction_Transmitter);//发送从地址
		
		while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));//检查事件状态 EV6事件
		I2C_SendData(I2C1, addr);//发送从机读写地址(寄存器)
		
		while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING));//检查事件状态 EV8事件
		I2C_SendData(I2C1, data);//发送数据
		
		while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING));//检查事件状态 EV8事件
		
		I2C_GenerateSTOP(I2C1, ENABLE); //发送结束信号
		
		
		
		
//		while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));//检查I2C总线是否繁忙	
//    I2C_GenerateSTART(I2C1, ENABLE);           //开启I2C1

//    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); //EV5,主模式
//    I2C_Send7bitAddress(I2C1, OLED_ADDRESS, I2C_Direction_Transmitter); //发送器件地址

//    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));	
//    I2C_SendData(I2C1, addr);  //寄存器地址

//    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING));
//    I2C_SendData(I2C1, data);

//    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING));

//    I2C_GenerateSTOP(I2C1, ENABLE);  //关闭I2C1总线
		
		
}

void WriteCmd(uint8_t command)
{
	
	I2C_Write_Byte(0x00,command);

}

void WriteData(uint8_t data)
{
	
	I2C_Write_Byte(0x40,data);

}

void OLED_Init()
{

  delay_ms(100);
    
    WriteCmd(0xAE); //display off
    WriteCmd(0x20);	//Set Memory Addressing Mode	
    WriteCmd(0x10);	//00,Horizontal Addressing Mode;01,Vertical Addressing Mode;10,Page Addressing Mode (RESET);11,Invalid
    WriteCmd(0xb0);	//Set Page Start Address for Page Addressing Mode,0-7
    WriteCmd(0xc8);	//Set COM Output Scan Direction
    WriteCmd(0x00); //---set low column address
    WriteCmd(0x10); //---set high column address
    WriteCmd(0x40); //--set start line address
    WriteCmd(0x81); //--set contrast control register
    WriteCmd(0xff); //亮度调节 0x00~0xff
    WriteCmd(0xa1); //--set segment re-map 0 to 127
    WriteCmd(0xa6); //--set normal display
    WriteCmd(0xa8); //--set multiplex ratio(1 to 64)
    WriteCmd(0x3F); //
    WriteCmd(0xa4); //0xa4,Output follows RAM content;0xa5,Output ignores RAM content
    WriteCmd(0xd3); //-set display offset
    WriteCmd(0x00); //-not offset
    WriteCmd(0xd5); //--set display clock divide ratio/oscillator frequency
    WriteCmd(0xf0); //--set divide ratio
    WriteCmd(0xd9); //--set pre-charge period
    WriteCmd(0x22); //
    WriteCmd(0xda); //--set com pins hardware configuration
    WriteCmd(0x12);
    WriteCmd(0xdb); //--set vcomh
    WriteCmd(0x20); //0x20,0.77xVcc
    WriteCmd(0x8d); //--set DC-DC enable
    WriteCmd(0x14); //
    WriteCmd(0xaf); //--turn on oled panel


}
void oled_setpos(uint8_t x,uint8_t y)//y是页号,x是列号
{
	
	WriteCmd(0xb0+y);
	WriteCmd((x&0x0f)|0x00);
	WriteCmd((x&0xf0)>>4|0x10);
	

}

void oled_fill(uint8_t data)
{
	uint8_t m,n;
	
	for(m=0;m<8;m++)
	{
		
		WriteCmd(0xb0+m);
		WriteCmd(0x00);
		WriteCmd(0x10);
		
		
		for(n=0;n<128;n++)
		{
			
			WriteData(data);
		
		}
	
	
	}


}

void  oled_cls()
{
	oled_fill(0x00);
	
}


void oled_open()
{
	WriteCmd(0x8D);
	WriteCmd(0x14);
	WriteCmd(0xAF);


}

void oled_close()
{
	WriteCmd(0x8D);
	WriteCmd(0x10);
	WriteCmd(0xAE);


}



void ShowStr(uint8_t x,uint8_t y,uint8_t ch[],uint8_t TextSize)//x代表列地址,y代表页地址
{
    unsigned char c = 0, i = 0, j = 0;
    switch (TextSize)
    {
        case 1:
        {
            while (ch[j] != '\0')
            {
                c = ch[j] - 32;
                if (x > 126)
                {
                    x = 0;
                    y++;
                }
                oled_setpos(x, y);
                for (i = 0; i < 6; i++)
                {
                    WriteData(F6x8[c][i]);
                    
                }
								x = x+6;
                j++; // 增加 j 的值
            }
        }
        break;
        case 2:
        {
            while (ch[j] != '\0')
            {
                c = ch[j] - 32;
                if (x > 120)
                {
                    x = 0;
                    y++;
                }
                oled_setpos(x, y);
                for (i = 0; i < 8; i++)
                    WriteData(F8X16[c * 16 + i]);
                oled_setpos(x, y + 1);
                for (i = 0; i < 8; i++)
                    WriteData(F8X16[c * 16 + i + 8]);
                x += 8;
                j++;
            }
        }
        break;
    }


}



//main函数
	InitSysTick();
	I2C_config();
	OLED_Init();
	delay_ms(2000);
	//WriteCmd(0xAF);//(开显示)
	oled_fill(0XFF);
	delay_ms(2000);
	oled_cls();
	delay_ms(2000);
	ShowStr(0,3,"hello world",1);
	ShowStr(0,4,"hello world",2);
//显示中文
void  ShowCN(uint8_t x,uint8_t y,uint8_t F16x16[],uint8_t count)//起始坐标 xy 和汉字模和汉字个数
{
	
		unsigned char c = 0, i = 0, j = 0;
	

	
		
		for(j=0;j<count;j++)
		{
		oled_setpos(x+16*j, y);//第一页
		for (i = 0; i < 16; i++)
    WriteData(F16x16[32*j+i]);
	
	
		oled_setpos(x+16*j, y+1);//第二页
		for (i = 0; i < 16; i++)
    WriteData(F16x16[32*j+16+i]);


		}


}

12.3.3:软件模拟IIC

#include "oled_soft.h"
#include "stm32f10x.h"
#include "delay.h"
#include "codetab.h"
extern const unsigned char F6x8[][6];
extern const unsigned char F8X16[];
void IIC_soft_config()
{
		 
    GPIO_InitTypeDef  GPIO_InitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    

    //PB0--SCL: PB1--SDA
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
	
		//初始设置为空闲状态
		SCLK_Set();
		SDA_Set();
	
		
	
}


//模拟起始信号
void IIC_soft_start()
{
	SCLK_Set();
	delay_us(1);
	SDA_Set();
	delay_us(1);
	SDA_Clr();
	delay_us(1);
	//?这儿是否需要SCL变低  没发现影响!!
	/*SCLK_Clr();
	delay_us(1);*/

}

//模拟结束信号
void IIC_soft_stop()
{
	SCLK_Set();
	delay_us(1);
	SDA_Clr();
	delay_us(1);
	SDA_Set();
	delay_us(1);

	


}
//模拟IIC读取来自从机的应答信号
uint8_t IIC_soft_ack()
{
		uint8_t ack;
		SCLK_Clr();
		delay_us(1);
		SDA_Set();
		delay_us(1);
		SCLK_Set();
		delay_us(1);
		if(SDA_Read()==1)
		{
			ack = IIC_Nack;
		}
		else
		{
		
			ack = IIC_Ack;
		}
		//这儿需要把信号线置低吗? 需要!!
		/*I2C 的 SCL 必须在第 9 个时钟结束后被主机拉低并保持低电平,
		否则从机以为 ACK 周期还没结束,下一次你再把 SDA 拉低/拉高时,
		从机会把它当成第 9 位 ACK 的延续,而不是第 1 位数据,整条总线就瞬间“跑飞”——表现出来就是 OLED 花屏或闪屏*/
		SCLK_Clr();
		delay_us(1);
		return ack;
}

void IIC_soft_write_byte(uint8_t data)//先发高位再发低位
{
	
	
	uint8_t c,i;
	for(i=0;i<8;i++)
	{
		SCLK_Clr();//信号线拉低
		delay_us(1);
		c=data>>7;
		data = data<<1;
		if(c==1)
		{
		SDA_Set();
		delay_us(1);
		
		}
		else
		{
		SDA_Clr();
		delay_us(1);
		}
		SCLK_Set();//信号线拉高
		delay_us(1);
	
	}

	SCLK_Clr();
	delay_us(1);
	IIC_soft_ack();//因为这个地方是第九个脉冲


}

//IIC写命令
void IIC_soft_command(uint8_t command)
{
	IIC_soft_start();
	IIC_soft_write_byte(0x78);
	IIC_soft_write_byte(0x00);
	IIC_soft_write_byte(command);
	IIC_soft_stop();

}


//IIC写数据
void IIC_soft_data(uint8_t data)
{
	IIC_soft_start();
	IIC_soft_write_byte(0x78);
	IIC_soft_write_byte(0x40);
	IIC_soft_write_byte(data);
	IIC_soft_stop();

}

/*void OLED_Write_Byte(uint8_t data,uint8_t cmd)
{
	if(cmd)
	{
		IIC_soft_command(data);
	}
	else
	{
			IIC_soft_data(cmd);
	
	}
}*/

//设置坐标
void OLED_setpoint(uint8_t x,uint8_t y)
{

	IIC_soft_command(0xb0+y);
	IIC_soft_command((x&0x0f)|0x00);
	IIC_soft_command((x&0xf0)>>4|0x10);


}

void OLED_open()
{
	IIC_soft_command(0x8D);
	IIC_soft_command(0x14);
	IIC_soft_command(0xAF);


}


void OLED_close()
{
	IIC_soft_command(0x8D);
	IIC_soft_command(0x10);
	IIC_soft_command(0xAE);


}

void 	OLED_fill(uint8_t data)
{
	uint8_t m,n;
	
	for(m=0;m<8;m++)
	{
		
		IIC_soft_command(0xb0+m);
		IIC_soft_command(0x00);
		IIC_soft_command(0x10);
		
		
		for(n=0;n<128;n++)
		{
			
			IIC_soft_data(data);
		
		}
	
	
	}


}

void OLED_clr()
{

	OLED_fill(0x00);
	
}


void OLED_Showchr(uint8_t x,uint8_t y,uint8_t chr)
{
	  unsigned char c = 0, i = 0, j = 0;
    switch (SIZE)
    {
        case 8:
        {
            
                c = chr - 32;
                if (x > 126)
                {
                    x = 0;
                    y++;
                }
                OLED_setpoint(x, y);
                for (i = 0; i < 6; i++)
                {
                    IIC_soft_data(F6x8[c][i]);
                    
                }
								
                
            
        }
        break;
        case 16:
        {
           
                c = chr - 32;
                if (x > 120)
                {
                    x = 0;
                    y++;
                }
                OLED_setpoint(x, y);
                for (i = 0; i < 8; i++)
                    IIC_soft_data(F8X16[c * 16 + i]);
                OLED_setpoint(x, y + 1);
                for (i = 0; i < 8; i++)
                    IIC_soft_data(F8X16[c * 16 + i + 8]);
                
            
        }
        break;
    }




}

void OLED_INIT()
{
	IIC_soft_config();
  delay_ms(200);
    
    IIC_soft_command(0xAE); //display off
    IIC_soft_command(0x20);	//Set Memory Addressing Mode	
    IIC_soft_command(0x10);	//00,Horizontal Addressing Mode;01,Vertical Addressing Mode;10,Page Addressing Mode (RESET);11,Invalid
    IIC_soft_command(0xb0);	//Set Page Start Address for Page Addressing Mode,0-7
    IIC_soft_command(0xc8);	//Set COM Output Scan Direction
    IIC_soft_command(0x00); //---set low column address
    IIC_soft_command(0x10); //---set high column address
    IIC_soft_command(0x40); //--set start line address
    IIC_soft_command(0x81); //--set contrast control register
    IIC_soft_command(0xff); //亮度调节 0x00~0xff
    IIC_soft_command(0xa1); //--set segment re-map 0 to 127
    IIC_soft_command(0xa6); //--set normal display
    IIC_soft_command(0xa8); //--set multiplex ratio(1 to 64)
    IIC_soft_command(0x3F); //
    IIC_soft_command(0xa4); //0xa4,Output follows RAM content;0xa5,Output ignores RAM content
    IIC_soft_command(0xd3); //-set display offset
    IIC_soft_command(0x00); //-not offset
    IIC_soft_command(0xd5); //--set display clock divide ratio/oscillator frequency
    IIC_soft_command(0xf0); //--set divide ratio
    IIC_soft_command(0xd9); //--set pre-charge period
    IIC_soft_command(0x22); //
    IIC_soft_command(0xda); //--set com pins hardware configuration
    IIC_soft_command(0x12);
    IIC_soft_command(0xdb); //--set vcomh
    IIC_soft_command(0x20); //0x20,0.77xVcc
    IIC_soft_command(0x8d); //--set DC-DC enable
    IIC_soft_command(0x14); //
    IIC_soft_command(0xaf); //--turn on oled panel






}


void  OLED_ShowCN(uint8_t x,uint8_t y,uint8_t F16x16[],uint8_t count)//起始坐标 xy 和汉字模和汉字个数
{
	
		unsigned char c = 0, i = 0, j = 0;
	

	
		
		for(j=0;j<count;j++)
		{
		OLED_setpoint(x+16*j, y);//第一页
		for (i = 0; i < 16; i++)
    IIC_soft_data(F16x16[32*j+i]);
	
	
		OLED_setpoint(x+16*j, y+1);//第二页
		for (i = 0; i < 16; i++)
    IIC_soft_data(F16x16[32*j+16+i]);


		}

	}

十三、RFID(radio frequence identification 无线射频识别)

13.1:概念

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐