一、初识SMT32单片机
什么是单片机
单片机(Single-Chip Microcomputer)是一种集成电路芯片,把具有数据处理能力的中央处理器CPU、随机存储器RAM(随机读写,断电不保持)、只读存储器ROM(只读,断电保持)、多种I/O口和中断系统、定时器/计数器等功能(可能还包括驱动电路、脉宽调制电路、模拟多路转换器、A/D转换器等电路)集成到一块硅片上构成的一个小而完善的微型计算机系统,在工业控制领域广泛使用。
STM系列单片机命名规则
ST--意法半导体(一家公司名字)
M --Microelectronics微电子
32--总线宽度(32位的芯片)
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安装
固件包安装
程序模板
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=ARR | CNT=0 |
向下计数 | CNT=0 | CNT=ARR |
中心对齐计数 | CNT=ARR-1 | CNT=ARR |
CNT=1 | CNT=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_STAbit15bit14bit13~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个内部通道(温度传感器、内部参考电压)
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个转换
和规则通道转换顺序的控制一样,注入通道的转换也是通过注入寄存器来控制,只不过只有一个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