一、初识SMT32单片机

什么是单片机

单片机(Single-Chip Microcomputer)是一种集成电路芯片,把具有数据处理能力的中央处理器CPU、随机存储器RAM(随机读写,断电不保持)、只读存储器ROM(只读,断电保持)、多种I/O口和中断系统、定时器/计数器等功能(可能还包括驱动电路、脉宽调制电路、模拟多路转换器、A/D转换器等电路)集成到一块硅片上构成的一个小而完善的微型计算机系统,在工业控制领域广泛使用。

STM系列单片机命名规则

ST--意法半导体(一家公司名字)

M --Microelectronics微电子

32--总线宽度(32位的芯片)

ESP32透传模式什么意思_单片机

STM32F103C8T6单片机简介

项目

介绍

内核

Cortex-M3

Flash

64K*8bit

SRAM

20K*8bit

GPIO

37个GPIO,分别为PA0-PA15、PB0-PB15、PC13-PC、PD0-PD1

ADC

2个12bit ADC合计12路通道,外部通道:PA0到PA7+PB0到PB1,内部通道:温度传感器通道ADC Channel 16和内部参考电压通道ADC Channel 17

定时器/计数器

4个16bit定时器/计数器,分别为TIM1、TIM2、TIM3、TIM4,TIM1带死区插入,常用于产生PWM控制电机

看门狗定时器

2个看门狗定时器(独立看门狗IWDG、窗口看门狗WWDG)

滴答定时器

1个24bit向下计数的滴答定时器systick

工作电压、温度

2V-3.6V,-40°C-85°C

通信串口

2*IIC,2*SPI,3*USART,1*CAN

系统时钟

内部8MHZ时钟HSI最高可倍频到64MHZ,外部8MHZ时钟HSE最高可倍频到72MHZ

  

寄存器、标准库和HAL库区别

寄存器

1、寄存器众多,需要经常翻阅芯片手册,费时费力

2、更大灵活性,可以随心所欲的达到自己的目的

3、深入理解单片机的运行原理,知其然更知其所以然

标准库

1、将寄存器底层操作封装起来,提供一整套接口(API)供开发者调用

2、每款芯片都编写了一份库文件,也就是工程文件里的stm32F1xx...之类的

3、配置结构体变量成员就可以修改外设的配置寄存器,从而选择不同的功能

4、大大降低单片机开发难度,但是在不用芯片间不方便移植

HAL库

1、ST公司目前主力推的开发方式,新的芯片已经不再提供标准库

2、为了实现在不同芯片之间移植代码

3、为了兼容所有芯片,导致代码量庞大,执行效率低下

二、开发软件搭建

Keil5安装

ESP32透传模式什么意思_优先级_02

ESP32透传模式什么意思_嵌入式硬件_03

ESP32透传模式什么意思_stm32_04

 

ESP32透传模式什么意思_stm32_05

 固件包安装

ESP32透传模式什么意思_单片机_06

程序模板

ESP32透传模式什么意思_优先级_07

ST-LINK驱动安装

ESP32透传模式什么意思_stm32_08

STM32CubeMX安装

ESP32透传模式什么意思_优先级_09

ESP32透传模式什么意思_单片机_10

 

ESP32透传模式什么意思_ESP32透传模式什么意思_11

ST-LINK V2接线图

ESP32透传模式什么意思_嵌入式硬件_12

三、GPIO

什么是GPIO

GPIO是通用输入输出端口的简称,简单来说就说STM32可控制的引脚,STM32芯片的GPIO引脚与外部设备连接起来,从而实现与外部通讯、控制以及数据采集的功能

命名规则

组编号+引脚编号

组编号:GPIOA,GPIOB,GPIOC,GPIOD...GPIOG

引脚编号0,1,2,3,4...15

组合起来:

PA0,PA1,PA2...PA15

PB0,PB1,PB2...PB15

PC0,PC1,PC2...PC15

...

有一些特殊功能的引脚是不能做IO口的

推挽输出和开漏输出

ESP32透传模式什么意思_stm32_13

推挽输出:可以真正的输出高电平和低电平

开漏输出:无法真正输出高电平,即高电平时没有驱动能力,需要借助外部上拉电阻完成对外驱动

点亮一盏灯

代码示例

__HAL_RCC_GPIOA_CLK_ENABLE();//打开A组IO口时钟
__HAL_RCC_GPIOB_CLK_ENABLE();//打开B组IO口时钟


HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);//设置IO口电平

GPIO_InitTypeDef GPIO_InitStruct = {0};//IO口配置结构体

GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9;//引脚
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;//模式
GPIO_InitStruct.Pull = GPIO_NOPULL;//上拉,下拉或不拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;//响应速度,高、中、低

HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);//IO口初始化

常用的GPIO HAL库函数 

void HAL_GPIO_Init(GPIO_TypeDef  *GPIOx, GPIO_InitTypeDef *GPIO_Init);

void HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState);

void HAL_GPIO_TogglePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin);

GPIO_InitTypeDef结构体

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

  uint32_t Mode;      /*!< Specifies the operating mode for the selected pins.
                           This parameter can be a value of @ref GPIO_mode_define */

  uint32_t Pull;      /*!< Specifies the Pull-up or Pull-Down activation for the selected pins.
                           This parameter can be a value of @ref GPIO_pull_define */

  uint32_t Speed;     /*!< Specifies the speed for the selected pins.
                           This parameter can be a value of @ref GPIO_speed_define */
} GPIO_InitTypeDef;

按键控制灯(轮询法)

#define Key_On  1
#define Key_Off 0

uint8_t Key_Scan(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
{
	if(HAL_GPIO_ReadPin(GPIOx,GPIO_Pin) == GPIO_PIN_RESET){//判断按键是否被按下
		while(HAL_GPIO_ReadPin(GPIOx,GPIO_Pin) == GPIO_PIN_RESET);//循环判断按键是否松开
		return Key_On;
	}else{
		return Key_Off;
	}
}
while (1)
  {
		if(Key_Scan(GPIOA,GPIO_PIN_0) == Key_On){//判断按键是否被按下
			HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_8);//灯翻转
		}
		if(Key_Scan(GPIOA,GPIO_PIN_1) == Key_On){
			HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_9);
		}
  }

四、复位和时钟控制(RCC)(Reset Clock Control)

复位

系统复位

当发送以下任一事件时,产生一个系统复位:

1、NRST引脚上的低电平(外部复位)

2、窗口看门狗计数终止(WWDG复位),严格的时间把控

3、独立看门狗计数终止(IWDG复位)

4、软件复位(SW复位)

5、低功耗管理复位

电源复位

当以下事件之一发生时,产生电源复位:

1、上电、掉电复位(POR/PDR复位)

2、从待机模式中返回

备份区复位

备份区域拥有两个专门的复位,它们只影响备份区域

当以下事件之一发生时,产生备份区域复位

1、软件复位。备份区域复位可由设置备份区域控制寄存器(RCC_BDCR)中的BDRST位产生

2、在VDD和VBAT两者掉电的前提下,VDD和VBAT上电将引发备份区域复位。

时钟控制

什么是时钟?

时钟打开了。对应的设备才会工作

时钟的来源

三种不同的时钟源可被用来驱动系统时钟(SYSCLK)

1、HSI振荡器时钟(高速内部时钟)

2、HSE振荡器时钟(高速外部时钟)

3、PLL时钟(锁相环倍频时钟)

二级时钟源

1、40KHZ低速内部RC(LSIRC)振荡器

2、32.768KHZ低速外部晶体(LSE晶体)

五、中断和事件

中断概述

什么是中断?

        中断是指计算机运行过程中,出现某些意外情况需要主机干预时,机器能自动停止正在运行的程序并转入处理新情况的程序,处理完毕后又返回原先被暂停的程序继续运行。

什么是EXTI?

        外部中断/事件控制器(EXTI)管理了控制器的23个中断/事件线。每个中断/事件线都对应有一个边沿检测器,可以实现输入信号的上升沿检测和下降沿的检测。EXTI可以实现对每个中断/事件线进行单独配置,可以单独配置为中断或者事件,以及触发事件的属性。

        EXTI可分为两大部分功能,一个是产生中断,另一个是产生事件,这两个功能从硬件上就有所不同。

        产生中断线路目的是把输入信号输入到NVIC,进一步会运行中断服务函数,实现功能,这样是软件级的。而产生事件线路目的就是传输一个脉冲信号给其他外设使用,并且是电路级别的信号传输,属于硬件级的。

EXIT初始化结构体

typedef struct
{
    //中断/事件线
    uint32_t EXTI_Line;

    //EXTI模式
    EXIT_Mode_TypeDef EXTI_Mode;

    //触发类型
    EXTITrigger_TypeDef EXTI_Trigger;

    //EXTI控制
    FunctionalState EXTI_LineCmd;
}EXTI_InitTypeDef;

中断/事件线

#define EXTI_Line0    ((uint32_t)0x00001)
#define EXTI_Line1    ((uint32_t)0x00002)
#define EXTI_Line2    ((uint32_t)0x00004)
#define EXTI_Line3    ((uint32_t)0x00008)
#define EXTI_Line4    ((uint32_t)0x00010)
#define EXTI_Line5    ((uint32_t)0x00020)
#define EXTI_Line6    ((uint32_t)0x00040)
#define EXTI_Line7    ((uint32_t)0x00080)
#define EXTI_Line8    ((uint32_t)0x00100)
#define EXTI_Line9    ((uint32_t)0x00200)
#define EXTI_Line10    ((uint32_t)0x00400)
#define EXTI_Line11    ((uint32_t)0x00800)
#define EXTI_Line12    ((uint32_t)0x01000)
#define EXTI_Line13    ((uint32_t)0x02000)
#define EXTI_Line14    ((uint32_t)0x04000)
#define EXTI_Line15    ((uint32_t)0x08000)
#define EXTI_Line16    ((uint32_t)0x10000)
#define EXTI_Line17    ((uint32_t)0x20000)
#define EXTI_Line18    ((uint32_t)0x40000)
#define EXTI_Line19    ((uint32_t)0x80000)
#define EXTI_Line20    ((uint32_t)0x00100000)
#define EXTI_Line21    ((uint32_t)0x00200000)
#define EXTI_Line22    ((uint32_t)0x00400000)

EXTI模式

typedef enum
{
    EXTI_Mode_Interrupt = 0x00,    //产生中断
    EXTI_Mode_Event = 0x04         //产生事件
}EXTIMode_TypeDef;

触发类型 

typedef enum
{
    EXTI_Trigger_Rising         = 0x08,    //上升沿
    EXTI_Trigger_Falling        = 0x0C,    //下降沿
    EXTI_Trigger_Rising_Falling = 0x10     //上升沿和下降沿都触发
}EXTITringger_TypeDef;

EXTI控制 

使能EXTI,一般都是使能,ENABLE

什么是优先级?

1、抢占优先级

2、响应优先级

优先级有级别之分的,数值越小,优先级越高,负数优先级高于正数

抢占优先级和响应优先级的区别:

1、高优先级的抢占优先级是可以打断正在进行的低抢占优先级中断的

2、抢占优先级相同的中断,高响应优先级不可以打断低响应优先级的中断

3、抢占优先级相同的中断,当两个中断同时发生的情况下,哪个响应优先级高,哪个先执行

4、如果两个中断的抢占优先级和响应优先级都是一样的话,则看哪个中断先发生就先执行

什么是优先级分组?

Cortex-M3允许具有较少中断源时使用较少的寄存器位指定中断源的优先级,因此,STM32把指定中断优先级的寄存器减少到4位,这4个寄存器位的分组方式如下:

第0组:所有4位用于指定响应优先级

第1组:最高1位用于指定抢占式优先级,最低3位用于指定响应优先级

第2组:最高2位用于指定抢占式优先级,最低2位用于指定响应优先级

第3组:最高3位用于指定抢占式优先级,最低1位用于指定响应优先级

第4组:所有4位用于指定抢占式优先级

什么是NVIC?

        STM32通过中断控制器NVIC(Nested Vectored Interrupt Controller)进行中断的管理。NVIC是属于Cortex内核的器件,不可屏蔽中断(NMI)和外部中断都由它来处理,但是SYSTICK不是由NVIC控制的。

typedef struct
{    
    uint8_t NVIC_IRQChannel;                    //配置的渠道,比如说EXTI0,EXTI1等等
    uint8_t NVIC_IRQChannelPreemptionPriority;  //抢断优先级
    uint8_t NVIC_IRQChannelSubPriority;         //响应优先级
    FunctionalState NVIC_IRQChannelCmd;         //ENABLE
}NVIC_InitTypeDef;

什么是中断向量表?

        每个中断源都有对应的处理程序,这个处理程序称为中断服务程序,其入口地址称为中断向量。所有中断的中断服务程序入口地址构成一个表,称为中断向量表;也有的机器把中断服务程序入口的跳转指令构成一张表,称为中断向量跳转表。

按键控制灯(中断法)

1、配置时钟

ESP32透传模式什么意思_嵌入式硬件_14

ESP32透传模式什么意思_单片机_15

 

ESP32透传模式什么意思_ESP32透传模式什么意思_16

2、配置GPIO口

ESP32透传模式什么意思_优先级_17

ESP32透传模式什么意思_ESP32透传模式什么意思_18

ESP32透传模式什么意思_嵌入式硬件_19

3、使能中断

4、配置工程

ESP32透传模式什么意思_ESP32透传模式什么意思_20

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)//将中断处理服务函数进行重写
{
	if(GPIO_Pin == GPIO_PIN_0){//对触发中断的引脚进行判断
		HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_8);
	}
	if(GPIO_Pin == GPIO_PIN_1){
		HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_9);
	}
		
}

//或者如下

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	HAL_Delay(1000);
	switch(GPIO_Pin){
		case GPIO_PIN_0:
			if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET)
				HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_8);
		break;
		case GPIO_PIN_1:
			if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET)
				HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_9);
		break;
	}
		
}
//此时HAL_Delay函数使用的是滴答定时器,由于此时是在中断服务函数内调用,必须保证HAL_Delay的抢占优先级高于输入中断才能使服务函数不卡住

//默认滴答定时器的优先级是15,是最低的,可在main函数中加入如下函数修改优先级

HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);

六、摩托车报警器(项目一)

功能概述

1、通过遥控器可给摩托车上锁(进入警戒状态),此时有人摇晃车辆,触发震动器,蜂鸣器响

2、通过遥控器可给摩托车解锁(退出警戒状态),报警消除

振动器的使用

//实现震动传感器触发灯亮3S
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	if(GPIO_Pin == GPIO_PIN_4){
		if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_4) == GPIO_PIN_RESET){
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8|GPIO_PIN_9, GPIO_PIN_RESET);
			HAL_Delay(3000);
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8|GPIO_PIN_9, GPIO_PIN_SET);
		}else{
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8|GPIO_PIN_9, GPIO_PIN_SET);
		}
	}
}

//此时HAL_Delay函数使用的是滴答定时器,由于此时是在中断服务函数内调用,必须保证HAL_Delay的抢占优先级高于输入中断才能使服务函数不卡住

//默认滴答定时器的优先级是15,是最低的,可在main函数中加入如下函数修改优先级

HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);

加入继电器

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	if(GPIO_Pin == GPIO_PIN_4){
		if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_4) == GPIO_PIN_RESET){
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8|GPIO_PIN_9, GPIO_PIN_RESET);
			HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);
			HAL_Delay(3000);
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8|GPIO_PIN_9, GPIO_PIN_SET);
			HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);
		}
	}
}

加入433M无线模块

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	switch(GPIO_Pin){
		case GPIO_PIN_6:
			if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_6) == GPIO_PIN_SET){
				HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);
				HAL_Delay(3000);
				HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);
			}
		break;
		case GPIO_PIN_7:
			if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_7) == GPIO_PIN_SET){
				HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET);
				HAL_Delay(3000);
				HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_SET);
			}
		break;			
	}
			
}

摩托车报警器实现

#define J_OFF 0
#define J_ON  1

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	static int mark = J_OFF;
	
	switch(GPIO_Pin){
		case GPIO_PIN_4:
			if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_4) == GPIO_PIN_RESET && mark == J_ON){
				HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);
				HAL_Delay(10000);
				HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);
			}
		break;
		case GPIO_PIN_6:
			if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_6) == GPIO_PIN_SET){
				HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);
				HAL_Delay(2000);
				HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);
				mark = J_ON;
			}
		break;
		case GPIO_PIN_7:
			if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_7) == GPIO_PIN_SET){
				HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);
				HAL_Delay(1000);
				HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);
				mark = J_OFF;
			}
		break;			
	}
			
}

//这里的取消报警按钮的中断优先级并没有高于滴答报警器的优先级,但是却可以打断HAL_Delay函数,暂时没搞懂,后续学习完善

七、定时器

定时器介绍

软件定时,比如C51单片机使用软件数数的方式去计算时间

缺点:不准确、占用CPU资源

为了使定时更准确更高效,我们需要使用精准的时基,通过硬件的方式,实现定时功能。定时器的核心就是计数器

定时器原理

ESP32透传模式什么意思_优先级_21

定时器分类

基本定时器(TIM6~TIM7):没有输入输出通道,常用做时基,即定时功能

通用定时器(TIM2~TIM5):具有多路独立通道,可用于输入捕获/输出比较,也可用作时基

高级定时器(TIM1和TIM8):除具备通用定时器所有功能外,还具备带死区控制的互补信号输出、刹车输入等功能(可用于电机控制、数字电源设计等)

ESP32透传模式什么意思_单片机_22

STM32F103C8T6定时器资源 

ESP32透传模式什么意思_嵌入式硬件_23

通用定时器介绍

1、16位向上、向下、向上/向下自动装载计数器(TIMx_CNT)

2、16位可编程(可实时修改)预分频器(TIMx_PSC),计数器时钟频率的分频系数为1~65535

3、4个独立通道(TIMx_CH1~4),这些通道分别可作为:

        A、输入捕获

        B、输出比较

        C、PWM生成(边缘或中间对齐模式)

        D、单脉冲模式输出

4、可使用外部信号(TIMx_ETR)控制定时器和定时器互连(可以用1个定时器控制另一个定时器)

5、如下时间发生时产生中断/DMA:

        A、更新:计数器向上溢出\向下溢出,计数器初始化(通过软件或者内部/外部触发)

        B、触发事件(定时器启动、停止、初始化或者由内部/外部触发计数)

        C、输入捕获

        D、输出比较

        E、支持针对定位的增量(正交)编码器和霍尔传感器电路

        F、触发输入作为外部时钟或者按周期的电流管理

定时器计数模式

计数模式

计数器溢出值

计数器重装值

向上计数

CNT=ARR

CNT=0

向下计数

CNT=0

CNT=ARR

中心对齐计数

CNT=ARR-1

CNT=ARR

CNT=1

CNT=0

定时器时钟源

ESP32透传模式什么意思_嵌入式硬件_24

定时器溢出时间计算公式

Tout = ((arr+1)*(psc+1))/Tclk 

arr:计数次数

psc:预分频系数

Tclk:定时器的输入时钟频率(单位MHZ)

Tout:定时器溢出时间(单位为us)

以500ms为例子:Tout = ((4999+1)×(7199+1))/72000000 = 0.5s = 500ms

定时器点亮灯

需求:使用定时器中断方法,每500ms翻转一次LED1状态

1、RCC配置

ESP32透传模式什么意思_嵌入式硬件_25

2、LED1配置

ESP32透传模式什么意思_stm32_26

3、时钟配置

ESP32透传模式什么意思_单片机_27

4、TIM2配置

ESP32透传模式什么意思_单片机_28

ESP32透传模式什么意思_ESP32透传模式什么意思_29

5、配置工程 

6、重写更新中断回调函数

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if(htim->Instance == TIM2)
        HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_8);
}

7、main函数中启动定时器

HAL_TIM_Base_Start_IT(&htim2);

8、代码

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	if(htim->Instance == TIM2)
		HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_8);
}

int main(void)
{
  
  HAL_Init();


  SystemClock_Config();

  MX_GPIO_Init();
  MX_TIM2_Init();
 
	HAL_TIM_Base_Start_IT(&htim2);

  while (1)
  {

  }

}

八、PWM 

STM32F103C8T6 PWM资源

1、高级定时器(TIM1):7路

2、通用定时器(TIM2~TIM4):4路

PWM输出模式

1、在向上计数时,一旦CNT<CCRx时输出为有效电平,否则输出为无效电平

     在向下计数时,一旦CNT>CCRx时输出为无效电平,否则输出为有效电平

2、在向上计数时,一旦CNT<CCRx时输出为无效电平,否则输出为有效电平

     在向下计数时,一旦CNT>CCRx时输出为有效电平,否则输出为无效电平

PWM周期和频率计算公式

Tout = ((arr+1)*(psc+1))/Tclk 

PWM占空比

由TIMx_CCRx寄存器决定

PWM实现呼吸灯

需求:使用PWM电亮LED1实现呼吸灯效果

LED灯的亮暗程度由什么决定

由不同的占空比决定

如何计算周期/频率

假如频率是2KHZ,则PSC=71,ARR=499

LED1连接到哪个定时器的哪一路

学会看产品手册

ESP32透传模式什么意思_ESP32透传模式什么意思_30

代码实现

ESP32透传模式什么意思_单片机_31

 修改占空比函数

__HAL_TIM_SetCompare(&htim4,TIM_CHANNEL_3,pwmVal);

启动PWM输出的函数

HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_3);

uint16_t pwmVal = 0;//调整占空比
    uint8_t dir = 1;//改变方向 1越来越亮 0越来越暗
    HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_3);

    while (1)
  {
        HAL_Delay(1);
  		if(dir)
			pwmVal++;
		else
			pwmVal--;
		
		if(pwmVal > 500)
			dir = 0;
		else if(pwmVal == 0)
			dir = 1;
		__HAL_TIM_SetCompare(&htim4,TIM_CHANNEL_3,pwmVal);
  }

PWM控制舵机SG90

ESP32透传模式什么意思_stm32_32

 PWM波的频率不能太高,大约50HZ,即周期=1/频率=1/50=0.02s,20ms左右

Tout = ((arr+1)*(psc+1))/Tclk 

根据上面公式计算

如果周期为20ms,psc=7199,arr=199

角度控制

0.5ms--------------0°,2.5%对应函数中CCRx为5

1.0ms--------------45°,5%对应函数中CCRx为10

1.5ms--------------90°,7.5%对应函数中CCRx为15

2.0ms--------------135°,10%对应函数中CCRx为20

2.5ms--------------180°,12.5%对应函数中CCRx为25

编程实现

需求:每隔1S转动一个角度:0°->45°->90°->125°->180°->0°

配置定时器PWM功能

ESP32透传模式什么意思_stm32_33

HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_3);

while(1)
{
    HAL_Delay(1000);
    __HAL_TIM_SetCompare(&htim4,TIM_CHANNEL_3,5);
    HAL_Delay(1000);
    __HAL_TIM_SetCompare(&htim4,TIM_CHANNEL_3,10);
    HAL_Delay(1000);
    __HAL_TIM_SetCompare(&htim4,TIM_CHANNEL_3,15);
    HAL_Delay(1000);
    __HAL_TIM_SetCompare(&htim4,TIM_CHANNEL_3,20);
    HAL_Delay(1000);
    __HAL_TIM_SetCompare(&htim4,TIM_CHANNEL_3,25);
}

九、超声波传感器实战

简介

型号:HC-SR04

ESP32透传模式什么意思_优先级_34

        超声波测距模块是用来测量距离的一种产品,通过发送和收超声波,利用时间差和声音传播速度,计算出模块到前方障碍物的距离。

怎么让它发送波
Trig ,给Trig端口至少10us的高电平
怎么知道它开始发了
Echo信号,由低电平跳转到高电平,表示开始发送波
怎么知道接收了返回波
Echo,由高电平跳转回低电平,表示波回来了
怎么算时间
Echo引脚维持高电平的时间!
波发出去的那一下,开始启动定时器
波回来的拿一下,我们开始停止定时器,计算出中间经过多少时间
怎么算距离
距离 = 速度 (340m/s)* 时间/2

超声波时序图

ESP32透传模式什么意思_单片机_35

编程实战

需求

使用超声波测距,当距离小于5cm时,LED1灯亮,否则灯灭

接线

Trig--PB6

Echo-PB7

LED1-PB8

定时器配置

使用TIM2,只做计数功能,不用做计时

将PSC配置为71,则计数一次代表1us

主函数

//1. Trig ,给Trig端口至少10us的高电平 

//2. echo由低电平跳转到高电平,表示开始发送波 while(Echo == 0);

//波发出去的那一下,开始启动定时器

//3. 由高电平跳转回低电平,表示波回来了 while(Echo == 1);

//波回来的那一下,我们开始停止定时器 

//4. 计算出中间经过多少时间 //us为单位

//5. 距离 = 速度 (340m/s)* 时间/2

//其中340m/s=0.034cm/s

编写us级函数

void TIM2_Delay_us(uint16_t n_us)
{
	__HAL_TIM_ENABLE(&htim2);
	__HAL_TIM_SetCounter(&htim2,0);
	while(__HAL_TIM_GetCounter(&htim2) < n_us);
	__HAL_TIM_DISABLE(&htim2);
	
}

主函数

uint16_t count = 0;
	float distance = 0;
 while (1)
  {
		//1. Trig ,给Trig端口至少10us的高电平?
		HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET);
		TIM2_Delay_us(20);
		HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET);
		//2. echo由低电平跳转到高电平,表示开始发送波 while(Echo == 0);
		//波发出去的那一下,开始启动定时器
		while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7) == GPIO_PIN_RESET);
		__HAL_TIM_ENABLE(&htim2);
		__HAL_TIM_SetCounter(&htim2,0);
		//3. 由高电平跳转回低电平,表示波回来了 while(Echo == 1);
		//波回来的那一下,我们开始停止定时器?
		while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7) == GPIO_PIN_SET);
		__HAL_TIM_DISABLE(&htim2);

		//4. 计算出中间经过多少时间 //us为单位
		count = __HAL_TIM_GetCounter(&htim2);
		//5. 距离 = 速度 (340m/s)* 时间/2
		//其中340m/s=0.034cm/s
		distance = 0.034*count/2;
		
		if(distance <5)
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);
		else
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);
		
		HAL_Delay(500);
  }

启动和停止定时器还可以用以下函数

HAL_TIM_Base_Start(&htim);

HAL_TIM_Base_Stopt(&htim);

十、智能垃圾桶

待完善

十一、串口

常用函数介绍

HAL_UART_Transmit();串口发送数据,使用超时管理机制

HAL_UART_Receive();串口接收数据,使用超时管理机制

HAL_UART_Transmit_IT();串口中断模式发送

HAL_UART_Receive_IT();串口中断模式接收

HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart,

                                                                uint8_t *pData,uint16_t Size,uint32_t Timeout);

作用:以阻塞的方式发送指定的字节数据

形参1:UART_HandleTypeDef结构体类型指针变量

形参2:指向要发送的数据地址

形参3:要发送的数据大小,以字节为单位

形参4:设置超时时间,以ms单位

HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart,

                                                                        uint8_t *pData,uint16_t Size);

作用:以中断的方式接收指定的字节数据
形参1:UART_HandleTypeDef结构体类型指针变量
形参2:指向接收数据缓存区

形参3:要接收的数据大小,以字节为单位

此函数指向完后将清除中断,需要再次调用以重新开启中断

串口中断回调函数

HAL_UART_IRQHandler(UART_HandleTypeDef *huart);//串口中断处理函数

HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);//发送中断回调函数

HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);//接收中断回调函数

状态标记变量

USART_RX_STA

从0开始,串口中断接收到一个数据(一个字节)就自增1。当数据读取全部OK时候(回车和换行符来的时候),那么USART_RX_STA的最高位置1,表示串口数据接收全部完毕了,然后main函数里面可以处理数据了
 

uint16_t USART_RX_STAbit15bit14bit13~0接收完成标志接收到0x0D标志收到有效数据个数


串口接收中断流程 

收到中断


中断处理函数

USART_IRQHandler


HAL_UART_IRQHandler


接收中断处理

UART_Receive_IT


HAL_UART_RxCpltCallback

串口实验(非中断)

需求:接收串口工具发送的字符串,并将其发送回串口工具

接线:PA9--TXD1   PA10--RXD1  记住一定要交叉接线

工程配置: 

ESP32透传模式什么意思_嵌入式硬件_36

使用MicroLib库,对printf函数进行重映射

 

ESP32透传模式什么意思_嵌入式硬件_37

重写printf函数中调用的打印函数

 int fputc(int ch,FILE *f)
{
    unsigned char temp[1] = {ch};
    HAL_UART_Transmit(&huart1,temp,1,0xffff);
    return ch;
}

#include <string.h>
#include <stdio.h>


int fputc(int ch,FILE *f)
{
	unsigned char temp[1] = {ch};
	HAL_UART_Transmit(&huart1,temp,1,0xffff);
	return ch;
}


HAL_UART_Transmit(&huart1,(unsigned char *)"hello,world\n",strlen("hello,world\n"),100);

while (1)
  {
		HAL_UART_Receive(&huart1,buf,sizeof(buf),100);
		//HAL_UART_Transmit(&huart1,buf,strlen((char *)buf),100);
		printf("%s",buf);
		memset(buf, 0, sizeof(buf));
  }

串口中断实验

需求:通过中断的方法接收串口工具发送的字符串,并将其发送回串口工具

工程配置:

前面的配置一样,多了一步打开中断

ESP32透传模式什么意思_ESP32透传模式什么意思_38

//重写printf函数
int fputc(int ch,FILE *f)
{
	unsigned char temp[1] = {ch};
	HAL_UART_Transmit(&huart1,temp,1,0xFFFF);
	return ch;
}

//接收完成回调函数,收到一个数据后,在这里处理
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	if(huart->Instance == USART1){//判断中断是由哪个串口触发的
		if((UART1_RX_STA & 0x8000) == 0){//判断接收是否完成(UART1_RX_STA bit15位是否为1)
			if(UART1_RX_STA & 0x4000){//判断是否已经收到0x0d(回车)
				if(buf == 0x0a){//前面判断收到了0x0d(回车),则紧接着判断当前收到是否为0x0a(换行)
					UART1_RX_STA |=0x8000;//0x0d(回车)和0x0a(换行)都收到,则置bit15完成标志位位1
				}else{
					UART1_RX_STA = 0;//认为接收错误,重新开始
				}
			}else{//如果没有收到0x0d(回车)
				if(buf == 0x0d){//则先判断收到的字符是否为0x0d(回车)
					//是的话则将bit14置为1
					UART1_RX_STA |= 0x4000;
				}else{
					//否则将收到的数据保存在缓存区数组里
					UART1_RX_Buffer[UART1_RX_STA & 0x3FFF] = buf;
					UART1_RX_STA++;
					
					//如果接收数据大于UART1_REC_LEN(200字节),则重新开始接收
					if(UART1_RX_STA > UART1_REC_LEN - 1)
						UART1_RX_STA = 0;
				}
			}
		}
		//重新开启中断
		HAL_UART_Receive_IT(&huart1,&buf,1);
	}
}






int main(void)
{
  
	HAL_UART_Receive_IT(&huart1,&buf,1);//开启接收串口中断

  while (1)
  {
		if(UART1_RX_STA & 0x8000){//判断串口是否接收完成
			printf("收到的数据:");
			//将接收到的数据发送到串口
			HAL_UART_Transmit(&huart1,UART1_RX_Buffer,UART1_RX_STA & 0x3fff,0xffff);
			//等待发送完成
			while(huart1.gState != HAL_UART_STATE_READY);
			printf("\r\n");
			//重新开始下一次接收
			UART1_RX_STA = 0;
		}
		printf("hello kevin\r\n");
		HAL_Delay(1000);
  }
}

十二、蓝牙插座_风扇_灯

项目需求

通过蓝牙模块,实现手机控制蓝牙/风扇/灯

本质:

1、采用蓝牙透传功能,发送数据给stm32f103c8t6串口

2、stm32f103c8t6通过接收到的数据判断控制IO口输出

硬件配置

1、HC01模块

2、CH340

3、杜邦线

项目接线设计

HC01_TX -- stm32_RX1

HC01_RX -- stm32_TX1

非中断法

unsigned char buf[20] = {0};
while (1)
{
    	HAL_UART_Receive(&huart1,buf,sizeof(buf),100);
		printf("%s",buf);
		if(!strcmp((char *)buf,"open")){
			HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_RESET);
			if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_8) == GPIO_PIN_RESET){
				printf("LED1已经打开\r\n");
			}
		}else if(!strcmp((char *)buf,"close")){
			HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_SET);
			if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_8) == GPIO_PIN_SET){
				printf("LED1已经关闭\r\n");
			}
		}else{
			if(buf[0] != '\0')
				printf("指令发送错误:%s\r\n",buf);
		}
		memset(buf, 0, sizeof(buf));
}

中断法

//串口接收缓存(1字节)
uint8_t buf = 0;

//定义最大接收字节数,可根据需求调整
#define UART1_REC_LEN 200

//接收缓存,串口接收到数据放到这个数组里,最大UART1_REC_LEN个字节
uint8_t UART1_RX_Buffer[UART1_REC_LEN]={0};

//接收状态
//bit15    接收完成标志
//bit14    接收到0d(回车)
//bit13~0  接收到有效字节数
uint8_t UART1_RX_STA = 0;

int fputc(int ch,FILE *f)
{
	unsigned char temp[1] = {ch};
	HAL_UART_Transmit(&huart1,temp,1,0xFFFF);
	return ch;
}

//接收完成回调函数,收到一个数据后,在这里处理
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	if(huart->Instance == USART1){//判断中断是由哪个串口触发的
		if((UART1_RX_STA & 0x8000) == 0){//判断接收是否完成(UART1_RX_STA bit15位是否为1)
			if(UART1_RX_STA & 0x4000){//判断是否已经收到0x0d(回车)
				if(buf == 0x0a){//前面判断收到了0x0d(回车),则紧接着判断当前收到是否为0x0a(换行)
					UART1_RX_STA |=0x8000;//0x0d(回车)和0x0a(换行)都收到,则置bit15完成标志位位1
				}else{
					UART1_RX_STA = 0;//认为接收错误,重新开始
				}
			}else{//如果没有收到0x0d(回车)
				if(buf == 0x0d){//则先判断收到的字符是否为0x0d(回车)
					//是的话则将bit14置为1
					UART1_RX_STA |= 0x4000;
				}else{
					//否则将收到的数据保存在缓存区数组里
					UART1_RX_Buffer[UART1_RX_STA & 0x3FFF] = buf;
					UART1_RX_STA++;
					
					//如果接收数据大于UART1_REC_LEN(200字节),则重新开始接收
					if(UART1_RX_STA > UART1_REC_LEN - 1)
						UART1_RX_STA = 0;
				}
			}
		}
		//重新开启中断
		HAL_UART_Receive_IT(&huart1,&buf,1);
	}
}



HAL_UART_Receive_IT(&huart1,&buf,1);//开启接收串口中断

  while (1)
  {
		if(UART1_RX_STA & 0x8000){//判断串口是否接收完成
			printf("收到的数据:");
			//将接收到的数据发送到串口
			HAL_UART_Transmit(&huart1,UART1_RX_Buffer,UART1_RX_STA & 0x3fff,0xffff);
			//等待发送完成
			while(huart1.gState != HAL_UART_STATE_READY);
			printf("\r\n");
			
			if(!strcmp((char *)UART1_RX_Buffer,"open")){
				HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_RESET);
				if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_8) == GPIO_PIN_RESET){
					printf("LED1已经打开\r\n");
				}
			}else if(!strcmp((char *)UART1_RX_Buffer,"close")){
				HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_SET);
				if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_8) == GPIO_PIN_SET){
					printf("LED1已经关闭\r\n");
				}
			}else{
				if(UART1_RX_Buffer[0] != '\0')
				printf("指令发送错误:%s\r\n",UART1_RX_Buffer);
			}
			//重新开始下一次接收
			UART1_RX_STA = 0;
		}
		printf("hello kevin\r\n");
		HAL_Delay(50);
	}

十三、ESP8266 WIFI模块实现串口通讯

项目需求

串口1用于与ESP8266通讯,串口2连接PC,用于打印log,查看系统状态

注意点:

1、工作中一般不直接在中断服务函数里处理数据,而是在收到数据后直接丢给队列,再处理数据

2、在中断服务函数里尽量减少使用延时函数及打印函数

待完善

十四、看门狗

独立看门狗介绍

        在单片机构成的微型计算机系统中,由于单片机的工作常常会受到来自外界电磁场的干扰,造成程序跑飞,而陷入死循环,程序的正常运行被打断,由单片机控制的系统无法继续工作,会造成整个系统陷入停滞的状态,造成步科预料的后果,所以出于对单片机运行状态进行实时检测的考虑,便产生了一种专门用于检测单片机程序运行状态的模块或芯片,俗称“看门狗(watchdog)”

        独立看门狗工作在主程序之外,能够完全独立工作,它的时钟是专用的低速时钟(LSI),由VDD电压供电,在停止模式和待机模式下仍能工作。

独立看门狗的本质

        本质是一个12位的递减计数器,当计数器的值从某个值一直减到0的时候,系统就会产生一个复位信号,即IWDG_RESET.

        如果在计数没减到0之前,刷新了计数器的值的话,那么就不会产生复位信号,这个动作就是我们常说的喂狗

独立看门狗框图

ESP32透传模式什么意思_stm32_39

独立看门狗时钟

        独立看门狗的时钟由独立的RC振荡器LSI提供,即使主时钟发送故障它仍然有效,非常独立,启用IWDG后,LSI时钟会自动开启,LSI时钟频率并不精确,F1用40KHZ

ESP32透传模式什么意思_stm32_40

 分频系数算法:PSC=4*2^prer

prer是IWDG_PR的值

重装载寄存器

        重装载寄存器是一个12位的寄存器,用于存放重装载值,低12位有效,即最大值为4096,这个值的大小决定这独立看门狗的溢出时间

ESP32透传模式什么意思_ESP32透传模式什么意思_41

键寄存器

键起存器IWDG_KR可以说是独立看门狗的一个控制寄存器,只要有三种控制方式,往这个寄存器写入下面三个不同的值有不同的效果

ESP32透传模式什么意思_单片机_42

 溢出时间计算公式:Tout = PSC*RLR/fIWDG

PSC是预分频系数

RLR是重装载寄存器数值

fIWDG是LSI时钟频率

ESP32透传模式什么意思_ESP32透传模式什么意思_43

独立看门狗实验

需求

开启独立看门狗,溢出时间为2秒,使用按键1进行喂狗

硬件接线

KEY1--PA0

UART1 --PA9\PA10

溢出时间计算

PSC=64,RLR=625

喂狗函数

HAL_IWDG_Refresh(&hiwdg);

配置工程 

ESP32透传模式什么意思_stm32_44

编程实现 

#include <string.h>


HAL_UART_Transmit(&huart1,(unsigned char *)"程序启动~\r\n",strlen("程序启动~"),100);

  while (1)
  {
		if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET){
			HAL_IWDG_Refresh(&hiwdg);//喂狗函数
		}
  }

窗口看门狗(WWDG)介绍

什么是窗口看门狗?

窗口看门狗用于检测单片机程序运行时效是否精准,主要检测软件异常,一般用于需要精准检测程序运行时间的场合

窗口看门狗的本质是一个能产生系统复位信号提前唤醒中断7位计数器

产生复位条件:

1、当递减计数器值从0x40减到0x3F时复位(即T6位跳变为0)

2、计数器的值大于W[6:0]值时喂狗会复位

产生中断条件:

当递减计数器等于0x40时可产生提前唤醒中断(EWI)

在窗口期内重新装载计数器的值,防止复位,也就是所谓的喂狗

窗口看门狗的工作原理

ESP32透传模式什么意思_优先级_45

ESP32透传模式什么意思_嵌入式硬件_46

 控制寄存器

ESP32透传模式什么意思_单片机_47

配置寄存器 

ESP32透传模式什么意思_ESP32透传模式什么意思_48

状态寄存器

ESP32透传模式什么意思_嵌入式硬件_49

窗口看门狗实验

需求

开启窗口看门狗,计数器值设置为0x7F,窗口值设置为0x5F,预分频系数为8,。程序启动时点亮LED1,300ms后熄灭。在提前唤醒中断服务函数进行喂狗,同时反转LED2状态。

ESP32透传模式什么意思_stm32_50

根据计算公式可以算出

定时器每计数一次的时间为4096*8/36000ms=0.91ms

则定时器刚启动到窗口期的时间为(0x7F-0x5F)*0.91ms=29.13ms

则定时器刚启动到系统复位的时间为(0x7F-0x3F)*0.91=58.25ms

配置工程

ESP32透传模式什么意思_stm32_51

编程实现 

void HAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef *hwwdg)
{
	HAL_WWDG_Refresh(hwwdg);
	HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_9);
}


	HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_RESET);
	HAL_Delay(300);



  while (1)
  {
		HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_SET);
		HAL_Delay(40);
  }

独立看门狗和窗口看门狗的异同点

对比点

独立看门狗

窗口看门狗

时钟源

独立时钟LSI(40KHZ),不准确

PCLK1或PCLK3,精准

复位条件

递减计数到0

窗口期外喂狗或者减到0x3F

中断

没有中断

计数值减到0x40可产生中断

递减计数器位数

12位(4096~0)

7位(127~63)

应用场合

防止程序跑飞,死循环,死机

检测程序时效,防止软件异常

十五、DMA

什么是DMA

DMA(Direct Memory Access,直接存储器访问)提供在外设与内存、存储器和存储器、外设和外设之间的高速数据传输使用。它允许不同速度的硬件装置来沟通,而不需要依赖于CPU,在这个时间中,CPU对于内存的工作来说就无法使用

简单来说:DMA就是一个数据的搬运工

DMA的意义

代替CPU搬运数据,为CPU减负

1、数据搬运的工作比较耗时间

2、数据搬运工作时效要求高(有数据来就要搬走)

3、没啥技术含量(CPU节约出来的时间可以处理更重要的事)

搬运什么数据?

存储器、外设

这里的外设指的是spi、usart、iic、adc等基于APB1、APB2、或AHB时钟的外设,而这里的存储器包括自身的闪存(flash)或者内存(SRAM)以及外设的存储设备都可以作为访问地源或者目的

三种搬运方式

1、存储器->存储器(例如:复制某特别大的数据buf)

2、存储器->外设(例如:将某数据buf写入串口TDR寄存器)

3、外设->存储器(例如:将串口TDR寄存器写入某数据buf)

DMA控制器

STM32F103有2个DMA控制器,DMA1有7个通道,DMA2有5个通道

一个通道每次只能搬运一个外设数据!!如果同时有多个外设的DMA请求,则按照优先级进行响应

ESP32透传模式什么意思_stm32_52

ESP32透传模式什么意思_优先级_53

DMA及通道的优先级

优先级管理采用软件+硬件:

软件:每个通道的优先级可以在DMA_CCRx寄存器中设置,有4个等级

           最高级>高级>中级>低级

硬件:如果两个请求,他们的软件优先级相同,则较低编号的通道比较高编号的通道有较高的优先级

比如:如果软件优先级相同,通道2优先于通道4

DMA传输方式

DMA_Mode_Normal(正常模式)

一次DMA数据传输完后,停止DMA传送,也就是只传输一次

DMA_Mode_Circular(循环传输模式)

当传输结束时,硬件自动会将传输数据量寄存器进行重装,进行下一轮的数据传输。也就是多次传输模式

指针递增模式

1、源地址递增,目标地址递增

2、源地址递增,目标地址只有一个,不进行递增(比如DMA将数据传输给串口)

实验1、内存到内存搬运

需求

使用DMA的方式将数组A的内容复制到数组B中,搬运完后将数组B的内容打印到屏幕上

CubeMX配置

ESP32透传模式什么意思_ESP32透传模式什么意思_54

ESP32透传模式什么意思_嵌入式硬件_55

用到的函数 

HAL_StatusTypeDef HAL_DMA_Start(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength)
参数一:DMA_HandleTypeDef *hdma        DMA通道句柄

参数二:uint32_t SrcAddress        源内存地址

参数三:uint32_t DstAddress        目标内存地址

参数四:uint32_t DataLength        传输数据长度。注意需要乘以sizeof(uint32_t)

返回值:HAL_StatusTypeDef        HAL状态(OK、busy、ERROR、TIMEOUT)

__HAL_DMA_GET_FLAG(__HANDLE__, __FLAG__)   (DMA1->ISR & (__FLAG__)) 

参数1:__HANDLE__        DMA通道句柄

参数2:__FLAG__             数据传输标志,DMA_FLAG_TCx表示数据传输完成标志

返回值:FLAG的值(SET或RESET)

编程实现

#define BUF_SIZE 16

//源数组
uint32_t srcBuf[BUF_SIZE] ={
	0x00000000,0x11111111,0x22222222,0x33333333,
	0x44444444,0x55555555,0x66666666,0x77777777,
	0x88888888,0x99999999,0xAAAAAAAA,0xBBBBBBBB,
	0xCCCCCCCC,0xDDDDDDDD,0xEEEEEEEE,0xFFFFFFFF
};

//目标数组
uint32_t desBuf[BUF_SIZE];

	//开启数据传输
	HAL_DMA_Start(&hdma_memtomem_dma1_channel1,(uint32_t)srcBuf,(uint32_t)desBuf,BUF_SIZE*sizeof(uint32_t));
	//等待数据传输完成
	while(__HAL_DMA_GET_FLAG(&hdma_memtomem_dma1_channel1,DMA_FLAG_TC1) == RESET);
	//打印目标数组到串口1
	for(i = 0;i < BUF_SIZE;i++)
		printf("Buf[%d] = %x\r\n",i,desBuf[i]);

实验2、内存到外设搬运

需求

使用DMA的方式将内存的数据搬运到串口1发送寄存器,同时闪烁LED1,证明DMA搬运数据不占用CPU

CubeMX配置

ESP32透传模式什么意思_ESP32透传模式什么意思_56

用到的函数 

HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

参数一:UART_HandleTypeDef *huart        串口句柄

参数二:uint8_t *pData                                待发送数据首地址

参数三:uint16_t Size                                  待发送数据长度

返回值:HAL_StatusTypeDef        HAL状态(OK、busy、ERROR、TIMEOUT)

编程实现

//发送缓存区大小
#define BUF_SIZE 1000
//发送缓存区数组
unsigned char sendBuf[BUF_SIZE] = {0};

int main(void)
{
	int i = 0;

	for(i = 0 ; i < BUF_SIZE; i++)//缓存区赋值
		sendBuf[i] = 'A';            
	HAL_UART_Transmit_DMA(&huart1,sendBuf,sizeof(sendBuf));//内存发送到串口1并发送
  while (1)
  {
		HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_8);//翻转LED灯
		HAL_Delay(100);
  }
}

实验3、外设到内存搬运

需求

使用DMA的方式将串口接收缓存寄存器的值搬运到内存中,同时闪烁LED1

用到的库函数

使能串口中断函数

#define __HAL_UART_ENABLE_IT(__HANDLE__, __INTERRUPT__)   
参数1:__HANDLE__                串口句柄
参数2:__INTERRUPT__          需要使能的中断  

返回值:                                     无

打开串口接收的DMA

HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

参数一:UART_HandleTypeDef *huart        串口句柄

参数二:uint8_t *pData                                接收缓存首地址

参数三:uint16_t Size                                  接收缓存长度

返回值:HAL_StatusTypeDef        HAL状态(OK、busy、ERROR、TIMEOUT)

查看串口标志位状态
__HAL_UART_GET_FLAG(__HANDLE__, __FLAG__) (((__HANDLE__)->Instance->SR & (__FLAG__)) == (__FLAG__))

参数1:__HANDLE__        串口句柄

参数2:__FLAG__             想要查看的FLAG,UART_FLAG_IDLE表示串口的空闲状态

返回值:FLAG的值(SET或RESET)

清除串口空闲标志位
__HAL_UART_CLEAR_IDLEFLAG(__HANDLE__)

参数1:__HANDLE__        串口句柄
返回值:无

关闭掉串口DMA
HAL_StatusTypeDef HAL_UART_DMAStop(UART_HandleTypeDef *huart)
参数1:UART_HandleTypeDef *huart        串口句柄
返回值:HAL_StatusTypeDef        HAL状态(OK、busy、ERROR、TIMEOUT)

__HAL_DMA_GET_COUNTER(__HANDLE__) ((__HANDLE__)->Instance->CNDTR) 

参数1:__HANDLE__        串口句柄
返回值:未传输数据大小

CubeMX配置

ESP32透传模式什么意思_stm32_57

编程实现

如何判断串口接收是否完成?如何知道串口收到数据的长度?

使用串口空闲中断IDLE!!!

1、串口空闲时,触发空闲中断

2、空闲中断标志位由硬件置1,软件清零

利用串口空闲中断,可以用如下流程实现DMA控制的任意长数据接收:

1、使能IDLE空闲中断

2、使能DMA接收中断

3、收到串口接收中断,DMA不断传输数据到缓存区

4、一帧数据接收完毕,串口暂时空闲,触发串口空闲中断

5、在中断服务函数中,清除串口空闲中断标志位,关闭DMA传输(防止干扰)

6、计算刚才收到了多少个字节的数据

7、处理缓存区数据,开启DMA传输,开始下一帧接收

代码需要在如下3处修改

main.c

uint8_t rcvBuf[BUF_SIZE] = {0};//接收缓存区数组
    uint8_t rcvLen = 0;//接收到的数据长度

    __HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);//使能IDLE空闲中断
	HAL_UART_Receive_DMA(&huart1,rcvBuf,BUF_SIZE);//使能串口DMA接收

  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
		HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_8);
		HAL_Delay(500);
  }

main.h

#define BUF_SIZE 100//接收缓存区数组大小

stm32f1xx_it.c 

extern uint8_t rcvBuf[BUF_SIZE];
extern uint8_t rcvLen;

void USART1_IRQHandler(void)//串口接收中断处理函数
{

  HAL_UART_IRQHandler(&huart1);
	if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE) == SET){//判断串口是否处于空闲状态
		__HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除空闲标志位
		HAL_UART_DMAStop(&huart1);//停止串口DMA数据的传输,防止干扰
		rcvLen = BUF_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);//读取到的数据大小 = 设置的缓存区大小-串口DMA接收未接收的数据大小
		HAL_UART_Transmit_DMA(&huart1,rcvBuf,rcvLen);
		HAL_UART_Receive_DMA(&huart1,rcvBuf,BUF_SIZE);//使能串口DMA接收
		
	}
}

十六、ADC

ADC介绍

ADC是什么?

Analog-to-Digital Converter,指模拟/数字转换器

温度传感器

电压信号


模拟量信号

转换为数字量信号

单片机

压力传感器

ADC

光敏传感器

ADC的性能指标

量程:能测量的电压范围

分辨率:ADC能分辨的最小模拟量,通常以输出二进制的位数表示,比如:8、10、12、16位等,位数越多,分辨率越高,一般来说分辨率越高,转化时间越长

转化时间:从转换开始到获得稳定的数字量输出所需要的事件称为转换时间

ADC特性

1、12位精度下转换速度可达到1MHZ

2、供电电压Vssa = 0V,Vdda = 2.4V~3.6V

3、ADC输入范围:Vref- ≤ VIN ≤ Vref+

4、采样时间可配置,采样时间越长,转换结果相对越准确,但是转换速度就越慢

5、ADC的结果可以左对齐或右对齐方式存储在16位数据寄存器中

ADC通道

总共2个ADC(ADC1、ADC2),每个ADC有18个转换通道:16个外部通道、2个内部通道(温度传感器、内部参考电压)

ADC1

IO

ADC2

IO

通道0

PA0

通道0

PA0

通道1

PA1

通道1

PA1

通道2

PA2

通道2

PA2

通道3

PA3

通道3

PA3

通道4

PA4

通道4

PA4

通道5

PA5

通道5

PA5

通道6

PA6

通道6

PA6

通道7

PA7

通道7

PA7

通道8

PB0

通道8

PB0

通道9

PB1

通道9

PB1

通道10

PC0

通道10

PC0

通道11

PC1

通道11

PC1

通道12

PC2

通道12

PC2

通道13

PC3

通道13

PC3

通道14

PC4

通道14

PC4

通道15

PC5

通道15

PC5

通道16

连接内部温度传感器

通道16

连接内部Vss

通道17

连接内部Vrefint

内部参考电压

通道17

连接内部Vss

外部的16路通道在转换时又分为规则通道注入通道,其中规则通道最多有16路,注入通道最多有4路

规则组:正常排队的人

注入组:有特权的人(军人、孕妇)

ADC转换顺序

每个ADC只有一个数据寄存器,16个通道共用这个寄存器,所以需要指定规则转换通道的转换顺序

规则通道中的转换顺序由3个寄存器控制:SQR1、SQR2、SQR3,他们都是32位寄存器。SQR寄存器控制着转换通道的数目和转换顺序,只要在对应的寄存器为SQx中写入相应的通道,这个通道就是第x个转换

ESP32透传模式什么意思_嵌入式硬件_58

ESP32透传模式什么意思_ESP32透传模式什么意思_59

ESP32透传模式什么意思_单片机_60

和规则通道转换顺序的控制一样,注入通道的转换也是通过注入寄存器来控制,只不过只有一个JSQR寄存器来控制,控制关系如下:

ESP32透传模式什么意思_stm32_61

ADC触发方式

1、通过向控制器寄存器ADC-CR2的ADON位写1来开启转换,写0停止转换

2、可以通过外部事件(如定时器)进行转换

ADC转化时间

ADC是挂载到APB2总线(PCLK2)上的,经过分频器得到ADC时钟(ADCCLK),最高14MHZ

转换时间=采样时间+12.5个周期

12.5个周期是固定的,一般我们设置PCLK2=72M,经过ADC预分频器能分频到最大的时钟只能是12M,采样周期设置为1.5个周期,算出的最短转换时间为1.17us

ADC转化模式

扫描模式

关闭扫描模式:只转换ADC_SQRx或ADC_JSQR选中的第一个通道

打开扫描模式:扫描ADC_SQRx或ADC_JSQR选中的所有通道

单次转换/连续转换

单次转换:只转换一次

连续转换:转换完一次,立马进行下一次转换

使用ADC读取烟雾传感器的值

CubeMX配置

ESP32透传模式什么意思_优先级_62

ESP32透传模式什么意思_单片机_63

代码实现

#include <stdio.h>    
int fputc(int ch,FILE *f)
{
	unsigned char temp[1] = {ch};
	HAL_UART_Transmit(&huart1,temp,1,0xffff);
	return ch;
}
    uint32_t smoke_value = 0;
  while (1)
  {
		HAL_ADC_Start(&hadc1);//打开ADC转换
		HAL_ADC_PollForConversion(&hadc1,50);//等待ADC转换完成
		smoke_value = HAL_ADC_GetValue(&hadc1);//获取烟雾报警器的电压值
		printf("smoke_value = %f\r\n",3.3/4096*smoke_value);
		HAL_Delay(1000);
  }

十七、IIC协议

CubeMX配置 

ESP32透传模式什么意思_单片机_64

用到的库函数

HAL_StatusTypeDef HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c,

uint16_t DevAddress,

uint16_t MemAddress,

uint16_t MemAddSize,

uint8_t *pData,

uint16_t Size,

uint32_t Timeout)
参数1:I2C_HandleTypeDef *hi2c        i2c设备句柄

参数2:uint16_t DevAddress                目标器件的地址,七位地址必须左对齐
参数3:uint16_t MemAddress              目标器件的目标寄存器地址

参数4:uint16_t MemAddSize              目标器件内部寄存器地址数据长度
参数5:uint8_t *pData                           待写的数据首地址
参数6:uint16_t Size                             待写的数据长度
参数7:uint32_t Timeout                       超时时间
返回值:HAL_StatusTypeDef        HAL状态(OK、busy、ERROR、TIMEOUT)

IIC协议控制OLED屏

void Oled_Write_Cmd(uint8_t Cmd)//写入命令函数
{
    //0x00是写入命令 0x40是写入数据
    HAL_I2C_Mem_Write(&hi2c1,0x78,0x00,I2C_MEMADD_SIZE_8BIT,&Cmd,1,0xff);
}

void Oled_Write_Data(uint8_t Data)//写入数据函数
{
    //0x00是写入命令 0x40是写入数据
    HAL_I2C_Mem_Write(&hi2c1,0x78,0x40,I2C_MEMADD_SIZE_8BIT,&Data,1,0xff);
}

/*--  文字:  我  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char W1[16]={0x20,0x24,0x24,0x24,0xFE,0x23,0x22,0x20,0x20,0xFF,0x20,0x22,0x2C,0xA0,0x20,0x00};
char W2[16]={0x00,0x08,0x48,0x84,0x7F,0x02,0x41,0x40,0x20,0x13,0x0C,0x14,0x22,0x41,0xF8,0x00};
 
/*--  文字:  爱  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char AI1[16]={0x80,0x64,0x2C,0x34,0x24,0x24,0xEC,0x32,0x22,0x22,0x32,0x2E,0x23,0xA2,0x60,0x00};
char AI2[16]={0x00,0x41,0x21,0x91,0x89,0x87,0x4D,0x55,0x25,0x25,0x55,0x4D,0x81,0x80,0x80,0x00};
 
/*--  文字:  佩  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char Pei1[16]={0x80,0x60,0xF8,0x07,0x00,0xFE,0x02,0x92,0x92,0xF2,0x92,0x92,0x02,0xFE,0x00,0x00};
char Pei2[16]={0x00,0x00,0xFF,0x00,0x80,0x7F,0x00,0x1F,0x00,0xFF,0x10,0x1F,0x00,0x7F,0xE0,0x00};
 
/*--  文字:  欣  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char X1[16]={0x00,0x00,0xFC,0x44,0x44,0xC2,0x43,0x42,0x20,0x18,0x0F,0xC8,0x08,0x28,0x18,0x00};
char X2[16]={0x80,0x60,0x1F,0x00,0x00,0x7F,0x00,0x80,0x40,0x30,0x0C,0x03,0x1C,0x60,0x80,0x00};
/*--  调入了一幅图像:C:\Users\11038\Desktop\无标题.bmp  --*/
/*--  宽度x高度=128x64  --*/
char image[128*8] ={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xC0,
0x60,0x20,0xA0,0xA0,0xF0,0x50,0x50,0x50,0x50,0x50,0x50,0x50,0x50,0x50,0x50,0x50,
0xD0,0xD0,0xD0,0x50,0x70,0x30,0x10,0x10,0x10,0x30,0x30,0x30,0x50,0x50,0x70,0xB0,
0xB0,0xF0,0x50,0x60,0xE0,0xE0,0xC0,0xC0,0x80,0x80,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3E,0xE3,0x1C,
0x06,0x03,0x81,0xC0,0x70,0x18,0x08,0x0C,0x04,0xC6,0x62,0x1A,0x0A,0xCF,0x77,0x1F,
0x07,0x03,0x00,0x00,0x00,0xC0,0x40,0x40,0xC0,0x00,0x00,0x00,0x00,0x00,0xE0,0x20,
0x60,0xC1,0x03,0x02,0x0E,0x1C,0x7C,0xD9,0x71,0xC3,0x07,0xFE,0x06,0x0C,0x18,0x30,
0x60,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x01,0x00,
0x00,0x0C,0x07,0x00,0x00,0x00,0x60,0x38,0x0F,0x01,0x00,0x00,0x00,0x3F,0xE0,0x80,
0x00,0x00,0x00,0x00,0x00,0x01,0x61,0xC1,0x81,0x80,0x00,0x00,0x00,0x80,0x81,0x81,
0xC1,0x61,0x00,0x00,0x00,0x00,0xE0,0x3F,0x00,0x01,0x0F,0x00,0x00,0x00,0x00,0x00,
0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,
0x06,0x04,0x84,0x64,0x3C,0x08,0x08,0x08,0x08,0x09,0x09,0x09,0x09,0x19,0x30,0x70,
0xD0,0x90,0x98,0x88,0x0C,0x07,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0x70,
0x1C,0x06,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x01,0x01,0x03,0x02,0x02,0x06,0x04,0x0C,0x30,0x60,0xC0,0xC0,0x40,
0x40,0x80,0x80,0x80,0x00,0x00,0x00,0xFE,0x02,0xF2,0x0E,0x00,0xC8,0xB8,0x8E,0x88,
0xE8,0x88,0x88,0x88,0x00,0x80,0x60,0xF8,0x02,0xFE,0x0A,0xCA,0x4A,0xFA,0x4A,0xCA,
0xCA,0xFE,0x00,0x00,0x00,0xFC,0x44,0x44,0x44,0xC4,0x44,0x20,0x38,0x0E,0xE8,0x08,
0x08,0x38,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x80,0x80,0xC0,0x40,0x20,0x30,0x18,0x0C,0xE6,0x3E,0x03,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x3C,
0xE0,0x00,0x00,0x01,0x01,0x03,0x02,0x3F,0x08,0x08,0x07,0x10,0x08,0x26,0x20,0x20,
0x1F,0x00,0x06,0x08,0x10,0x00,0x00,0x3F,0x30,0x1F,0x00,0x0F,0x00,0x3F,0x08,0x0F,
0x07,0x1F,0x20,0x1C,0x00,0x3F,0x00,0x00,0x00,0x7F,0x00,0x60,0x30,0x0C,0x07,0x0E,
0x18,0x20,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3F,0x60,0xC0,0x80,
0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,
0x1F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x01,0x01,0x01,0x03,0x02,0x02,0x02,0xFE,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x7E,0xC6,0x02,0x02,0x02,0x03,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};


void Oled_Write_Cmd(uint8_t Cmd)//写入命令函数
{
	//0x00是写入命令 0x40是写入数据
	HAL_I2C_Mem_Write(&hi2c1,0x78,0x00,I2C_MEMADD_SIZE_8BIT,&Cmd,1,0xff);
}

void Oled_Write_Data(uint8_t Data)//写入数据函数
{
	//0x00是写入命令 0x40是写入数据
	HAL_I2C_Mem_Write(&hi2c1,0x78,0x40,I2C_MEMADD_SIZE_8BIT,&Data,1,0xff);
}

void Oled_Init()//OLED初始化
{
	Oled_Write_Cmd(0xAE);//--display off
	Oled_Write_Cmd(0x00);//---set low column address
	Oled_Write_Cmd(0x10);//---set high column address
	Oled_Write_Cmd(0x40);//--set start line address
	Oled_Write_Cmd(0xB0);//--set page address
	Oled_Write_Cmd(0x81); // contract control
	Oled_Write_Cmd(0xFF);//--128
	Oled_Write_Cmd(0xA1);//set segment remap
	Oled_Write_Cmd(0xA6);//--normal / reverse
	Oled_Write_Cmd(0xA8);//--set multiplex ratio(1 to 64)
	Oled_Write_Cmd(0x3F);//--1/32 duty
	Oled_Write_Cmd(0xC8);//Com scan direction
	Oled_Write_Cmd(0xD3);//-set display offset
	Oled_Write_Cmd(0x00);//
	Oled_Write_Cmd(0xD5);//set osc division
	Oled_Write_Cmd(0x80);//
	Oled_Write_Cmd(0xD8);//set area color mode off
	Oled_Write_Cmd(0x05);//
	Oled_Write_Cmd(0xD9);//Set Pre-Charge Period
	Oled_Write_Cmd(0xF1);//
	Oled_Write_Cmd(0xDA);//set com pin configuartion
	Oled_Write_Cmd(0x12);//
	Oled_Write_Cmd(0xDB);//set Vcomh
	Oled_Write_Cmd(0x30);//
	Oled_Write_Cmd(0x8D);//set charge pump enable
	Oled_Write_Cmd(0x14);//
	Oled_Write_Cmd(0xAF);//--turn on oled panel
 
}

void Oled_Clear()//清屏
{
	int i,j;
	for(i = 0;i < 8;i++){
		Oled_Write_Cmd(0xB0+i);
		for(j = 0;j < 128;j++){
		Oled_Write_Data(0x00);
		}
	}
}

void Oled_Image_Show(char *image)//展示图片
{
	int i,j;
	for(i = 0;i < 8;i++){
		Oled_Write_Cmd(0xB0+i);
		Oled_Write_Cmd(0x00);
		Oled_Write_Cmd(0x10);
		for(j = 0;j < 128;j++){
		Oled_Write_Data(*image++);
		}
	}
}

	//1.Oled初始化
	Oled_Init();
	//2.选择一个位置
	//2.1 页寻址模式
	Oled_Write_Cmd(0x20);
	Oled_Write_Cmd(0x02);
	
	//2.3清屏
	Oled_Clear();
	//2.2 选择Page0 
	Oled_Write_Cmd(0x00);
	Oled_Write_Cmd(0x10);
	Oled_Write_Cmd(0xB0);
	#ifdef __stdio_h 
	for(i = 0;i < 16;i++){
			Oled_Write_Data(W1[i]);
	}
	for(i = 0;i < 16;i++){
			Oled_Write_Data(AI1[i]);
	}
	for(i = 0;i < 16;i++){
			Oled_Write_Data(Pei1[i]);
	}
	for(i = 0;i < 16;i++){
			Oled_Write_Data(X1[i]);
	}
	
	Oled_Write_Cmd(0x00);
	Oled_Write_Cmd(0x10);
	Oled_Write_Cmd(0xB1);
	for(i = 0;i < 16;i++){
			Oled_Write_Data(W2[i]);
	}
	for(i = 0;i < 16;i++){
			Oled_Write_Data(AI2[i]);
	}
	for(i = 0;i < 16;i++){
			Oled_Write_Data(Pei2[i]);
	}
	for(i = 0;i < 16;i++){
			Oled_Write_Data(X2[i]);
	}
	#else
	
	Oled_Image_Show(image);
	#endif