STM32单片机开发笔记

一、初识SMT32单片机

什么是单片机

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

STM系列单片机命名规则

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

M --Microelectronics微电子

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

STM32F103C8T6单片机简介

项目介绍
内核Cortex-M3
Flash64K*8bit
SRAM20K*8bit
GPIO37个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安装

 

 固件包安装

程序模板

ST-LINK驱动安装

STM32CubeMX安装

 

ST-LINK V2接线图

三、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口的

推挽输出和开漏输出

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

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

点亮一盏灯

代码示例

__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、配置时钟

 

2、配置GPIO口

3、使能中断

4、配置工程


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资源

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

定时器原理

定时器分类

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

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

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

STM32F103C8T6定时器资源 

通用定时器介绍

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=ARRCNT=0
向下计数CNT=0CNT=ARR
中心对齐计数CNT=ARR-1CNT=ARR
CNT=1CNT=0

定时器时钟源

定时器溢出时间计算公式

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配置

2、LED1配置

3、时钟配置

4、TIM2配置

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连接到哪个定时器的哪一路

学会看产品手册

代码实现

 修改占空比函数

__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

 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功能

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

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

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

超声波时序图

编程实战

需求

使用超声波测距,当距离小于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_STA
bit15bit14bit13~0
接收完成标志接收到0x0D标志收到有效数据个数

串口接收中断流程 

收到中断
中断处理函数
USART_IRQHandler
HAL_UART_IRQHandler
接收中断处理
UART_Receive_IT
HAL_UART_RxCpltCallback

串口实验(非中断)

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

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

工程配置: 

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

 

重写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));
  }

串口中断实验

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

工程配置:

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

//重写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之前,刷新了计数器的值的话,那么就不会产生复位信号,这个动作就是我们常说的喂狗

独立看门狗框图

独立看门狗时钟

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

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

prer是IWDG_PR的值

重装载寄存器

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

键寄存器

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

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

PSC是预分频系数

RLR是重装载寄存器数值

fIWDG是LSI时钟频率

独立看门狗实验

需求

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

硬件接线

KEY1--PA0

UART1 --PA9\PA10

溢出时间计算

PSC=64,RLR=625

喂狗函数

HAL_IWDG_Refresh(&hiwdg);

配置工程 

编程实现 

#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)

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

窗口看门狗的工作原理

 控制寄存器

配置寄存器 

状态寄存器

窗口看门狗实验

需求

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

根据计算公式可以算出

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

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

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

配置工程

编程实现 

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请求,则按照优先级进行响应

DMA及通道的优先级

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

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

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

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

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

DMA传输方式

DMA_Mode_Normal(正常模式)

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

DMA_Mode_Circular(循环传输模式)

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

指针递增模式

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

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

实验1、内存到内存搬运

需求

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

CubeMX配置

用到的函数 

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配置

用到的函数 

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配置

编程实现

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

使用串口空闲中断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个内部通道(温度传感器、内部参考电压)

ADC1IOADC2IO
通道0PA0通道0PA0
通道1PA1通道1PA1
通道2PA2通道2PA2
通道3PA3通道3PA3
通道4PA4通道4PA4
通道5PA5通道5PA5
通道6PA6通道6PA6
通道7PA7通道7PA7
通道8PB0通道8PB0
通道9PB1通道9PB1
通道10PC0通道10PC0
通道11PC1通道11PC1
通道12PC2通道12PC2
通道13PC3通道13PC3
通道14PC4通道14PC4
通道15PC5通道15PC5
通道16连接内部温度传感器通道16连接内部Vss
通道17

连接内部Vrefint

内部参考电压

通道17连接内部Vss

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

规则组:正常排队的人

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

ADC转换顺序

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

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

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

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配置

代码实现

#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配置 

用到的库函数

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

 

  • 1
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值