单片机(Single-Chip Microcomputer)是一种集成电路芯片,把具有数据处理能力的中央处理器CPU、随机存储器RAM(随机读写,断电不保持)、只读存储器ROM(只读,断电保持)、多种I/O口和中断系统、定时器/计数器等功能(可能还包括驱动电路、脉宽调制电路、模拟多路转换器、A/D转换器等电路)集成到一块硅片上构成的一个小而完善的微型计算机系统,在工业控制领域广泛使用。
ST--意法半导体(一家公司名字)
M --Microelectronics微电子
32--总线宽度(32位的芯片)
项目 | 介绍 |
内核 | 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 |
1、寄存器众多,需要经常翻阅芯片手册,费时费力
2、更大灵活性,可以随心所欲的达到自己的目的
3、深入理解单片机的运行原理,知其然更知其所以然
1、将寄存器底层操作封装起来,提供一整套接口(API)供开发者调用
2、每款芯片都编写了一份库文件,也就是工程文件里的stm32F1xx...之类的
3、配置结构体变量成员就可以修改外设的配置寄存器,从而选择不同的功能
4、大大降低单片机开发难度,但是在不用芯片间不方便移植
1、ST公司目前主力推的开发方式,新的芯片已经不再提供标准库
2、为了实现在不同芯片之间移植代码
3、为了兼容所有芯片,导致代码量庞大,执行效率低下
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);
}
}
当发送以下任一事件时,产生一个系统复位:
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)管理了控制器的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位用于指定抢占式优先级
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;
每个中断源都有对应的处理程序,这个处理程序称为中断服务程序,其入口地址称为中断向量。所有中断的中断服务程序入口地址构成一个表,称为中断向量表;也有的机器把中断服务程序入口的跳转指令构成一张表,称为中断向量跳转表。
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);
}
}
}
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):除具备通用定时器所有功能外,还具备带死区控制的互补信号输出、刹车输入等功能(可用于电机控制、数字电源设计等)
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状态
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM2)
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_8);
}
HAL_TIM_Base_Start_IT(&htim2);
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)
{
}
}
1、高级定时器(TIM1):7路
2、通用定时器(TIM2~TIM4):4路
1、在向上计数时,一旦CNT 在向下计数时,一旦CNT>CCRx时输出为无效电平,否则输出为有效电平 2、在向上计数时,一旦CNT 在向下计数时,一旦CNT>CCRx时输出为有效电平,否则输出为无效电平 Tout = ((arr+1)*(psc+1))/Tclk 由TIMx_CCRx寄存器决定 需求:使用PWM电亮LED1实现呼吸灯效果 由不同的占空比决定 假如频率是2KHZ,则PSC=71,ARR=499 学会看产品手册 修改占空比函数 __HAL_TIM_SetCompare(&htim4,TIM_CHANNEL_3,pwmVal); 启动PWM输出的函数 HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_3); 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功能 型号:HC-SR04 超声波测距模块是用来测量距离的一种产品,通过发送和收超声波,利用时间差和声音传播速度,计算出模块到前方障碍物的距离。 怎么让它发送波 超声波时序图 使用超声波测距,当距离小于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 启动和停止定时器还可以用以下函数 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); 形参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函数里面可以处理数据了 需求:接收串口工具发送的字符串,并将其发送回串口工具 接线:PA9--TXD1 PA10--RXD1 记住一定要交叉接线 工程配置: 使用MicroLib库,对printf函数进行重映射 重写printf函数中调用的打印函数 int fputc(int ch,FILE *f) 需求:通过中断的方法接收串口工具发送的字符串,并将其发送回串口工具 工程配置: 前面的配置一样,多了一步打开中断 通过蓝牙模块,实现手机控制蓝牙/风扇/灯 本质: 1、采用蓝牙透传功能,发送数据给stm32f103c8t6串口 2、stm32f103c8t6通过接收到的数据判断控制IO口输出 1、HC01模块 2、CH340 3、杜邦线 HC01_TX -- stm32_RX1 HC01_RX -- stm32_TX1 串口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); 窗口看门狗用于检测单片机程序运行时效是否精准,主要检测软件异常,一般用于需要精准检测程序运行时间的场合 窗口看门狗的本质是一个能产生系统复位信号和提前唤醒中断的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 DMA(Direct Memory Access,直接存储器访问)提供在外设与内存、存储器和存储器、外设和外设之间的高速数据传输使用。它允许不同速度的硬件装置来沟通,而不需要依赖于CPU,在这个时间中,CPU对于内存的工作来说就无法使用 简单来说:DMA就是一个数据的搬运工 代替CPU搬运数据,为CPU减负 1、数据搬运的工作比较耗时间 2、数据搬运工作时效要求高(有数据来就要搬走) 3、没啥技术含量(CPU节约出来的时间可以处理更重要的事) 存储器、外设 这里的外设指的是spi、usart、iic、adc等基于APB1、APB2、或AHB时钟的外设,而这里的存储器包括自身的闪存(flash)或者内存(SRAM)以及外设的存储设备都可以作为访问地源或者目的 三种搬运方式 1、存储器->存储器(例如:复制某特别大的数据buf) 2、存储器->外设(例如:将某数据buf写入串口TDR寄存器) 3、外设->存储器(例如:将串口TDR寄存器写入某数据buf) STM32F103有2个DMA控制器,DMA1有7个通道,DMA2有5个通道 一个通道每次只能搬运一个外设数据!!如果同时有多个外设的DMA请求,则按照优先级进行响应 优先级管理采用软件+硬件: 软件:每个通道的优先级可以在DMA_CCRx寄存器中设置,有4个等级 最高级>高级>中级>低级 硬件:如果两个请求,他们的软件优先级相同,则较低编号的通道比较高编号的通道有较高的优先级 比如:如果软件优先级相同,通道2优先于通道4 DMA_Mode_Normal(正常模式) 一次DMA数据传输完后,停止DMA传送,也就是只传输一次 DMA_Mode_Circular(循环传输模式) 当传输结束时,硬件自动会将传输数据量寄存器进行重装,进行下一轮的数据传输。也就是多次传输模式 1、源地址递增,目标地址递增 2、源地址递增,目标地址只有一个,不进行递增(比如DMA将数据传输给串口) 使用DMA的方式将数组A的内容复制到数组B中,搬运完后将数组B的内容打印到屏幕上 HAL_StatusTypeDef HAL_DMA_Start(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength) 参数二:uint32_t SrcAddress 源内存地址 参数三:uint32_t DstAddress 目标内存地址 参数四:uint32_t DataLength 传输数据长度。注意需要乘以sizeof(uint32_t) __HAL_DMA_GET_FLAG(__HANDLE__, __FLAG__) (DMA1->ISR & (__FLAG__)) 参数1:__HANDLE__ DMA通道句柄 参数2:__FLAG__ 数据传输标志,DMA_FLAG_TCx表示数据传输完成标志 返回值:FLAG的值(SET或RESET) 使用DMA的方式将内存的数据搬运到串口1发送寄存器,同时闪烁LED1,证明DMA搬运数据不占用CPU 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) 使用DMA的方式将串口接收缓存寄存器的值搬运到内存中,同时闪烁LED1 使能串口中断函数 #define __HAL_UART_ENABLE_IT(__HANDLE__, __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) 查看串口标志位状态 参数1:__HANDLE__ 串口句柄 参数2:__FLAG__ 想要查看的FLAG,UART_FLAG_IDLE表示串口的空闲状态 返回值:FLAG的值(SET或RESET) 清除串口空闲标志位 参数1:__HANDLE__ 串口句柄 关闭掉串口DMA __HAL_DMA_GET_COUNTER(__HANDLE__) ((__HANDLE__)->Instance->CNDTR) 参数1:__HANDLE__ 串口句柄 如何判断串口接收是否完成?如何知道串口收到数据的长度? 使用串口空闲中断IDLE!!! 1、串口空闲时,触发空闲中断 2、空闲中断标志位由硬件置1,软件清零 利用串口空闲中断,可以用如下流程实现DMA控制的任意长数据接收: 1、使能IDLE空闲中断 2、使能DMA接收中断 3、收到串口接收中断,DMA不断传输数据到缓存区 4、一帧数据接收完毕,串口暂时空闲,触发串口空闲中断 5、在中断服务函数中,清除串口空闲中断标志位,关闭DMA传输(防止干扰) 6、计算刚才收到了多少个字节的数据 7、处理缓存区数据,开启DMA传输,开始下一帧接收 代码需要在如下3处修改 main.c main.h stm32f1xx_it.c Analog-to-Digital Converter,指模拟/数字转换器 转换为数字量信号 → 量程:能测量的电压范围 分辨率:ADC能分辨的最小模拟量,通常以输出二进制的位数表示,比如:8、10、12、16位等,位数越多,分辨率越高,一般来说分辨率越高,转化时间越长 转化时间:从转换开始到获得稳定的数字量输出所需要的事件称为转换时间 1、12位精度下转换速度可达到1MHZ 2、供电电压Vssa = 0V,Vdda = 2.4V~3.6V 3、ADC输入范围:Vref- ≤ VIN ≤ Vref+ 4、采样时间可配置,采样时间越长,转换结果相对越准确,但是转换速度就越慢 5、ADC的结果可以左对齐或右对齐方式存储在16位数据寄存器中 总共2个ADC(ADC1、ADC2),每个ADC有18个转换通道:16个外部通道、2个内部通道(温度传感器、内部参考电压) 连接内部Vrefint 内部参考电压 外部的16路通道在转换时又分为规则通道和注入通道,其中规则通道最多有16路,注入通道最多有4路 规则组:正常排队的人 注入组:有特权的人(军人、孕妇) 每个ADC只有一个数据寄存器,16个通道共用这个寄存器,所以需要指定规则转换通道的转换顺序 规则通道中的转换顺序由3个寄存器控制:SQR1、SQR2、SQR3,他们都是32位寄存器。SQR寄存器控制着转换通道的数目和转换顺序,只要在对应的寄存器为SQx中写入相应的通道,这个通道就是第x个转换 和规则通道转换顺序的控制一样,注入通道的转换也是通过注入寄存器来控制,只不过只有一个JSQR寄存器来控制,控制关系如下: 1、通过向控制器寄存器ADC-CR2的ADON位写1来开启转换,写0停止转换 2、可以通过外部事件(如定时器)进行转换 ADC是挂载到APB2总线(PCLK2)上的,经过分频器得到ADC时钟(ADCCLK),最高14MHZ 转换时间=采样时间+12.5个周期 12.5个周期是固定的,一般我们设置PCLK2=72M,经过ADC预分频器能分频到最大的时钟只能是12M,采样周期设置为1.5个周期,算出的最短转换时间为1.17us 扫描模式 关闭扫描模式:只转换ADC_SQRx或ADC_JSQR选中的第一个通道 打开扫描模式:扫描ADC_SQRx或ADC_JSQR选中的所有通道 单次转换/连续转换 单次转换:只转换一次 连续转换:转换完一次,立马进行下一次转换 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) 参数2:uint16_t DevAddress 目标器件的地址,七位地址必须左对齐 参数4:uint16_t MemAddSize 目标器件内部寄存器地址数据长度 void Oled_Write_Cmd(uint8_t Cmd)//写入命令函数 void Oled_Write_Data(uint8_t Data)//写入数据函数 PWM周期和频率计算公式
PWM占空比
PWM实现呼吸灯
LED灯的亮暗程度由什么决定
如何计算周期/频率
LED1连接到哪个定时器的哪一路
代码实现
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
角度控制
编程实现
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);
}
九、超声波传感器实战
简介
Trig ,给Trig端口至少10us的高电平
怎么知道它开始发了
Echo信号,由低电平跳转到高电平,表示开始发送波
怎么知道接收了返回波
Echo,由高电平跳转回低电平,表示波回来了
怎么算时间
Echo引脚维持高电平的时间!
波发出去的那一下,开始启动定时器
波回来的拿一下,我们开始停止定时器,计算出中间经过多少时间
怎么算距离
距离 = 速度 (340m/s)* 时间/2编程实战
需求
接线
定时器配置
主函数
编写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);
}
十、智能垃圾桶
待完善
十一、串口
常用函数介绍
作用:以阻塞的方式发送指定的字节数据
作用:以中断的方式接收指定的字节数据
形参1:UART_HandleTypeDef结构体类型指针变量
形参2:指向接收数据缓存区
此函数指向完后将清除中断,需要再次调用以重新开启中断
uint16_t USART_RX_STA
bit15
bit14
bit13~0
接收完成标志
接收到0x0D标志
收到有效数据个数
串口接收中断流程
收到中断
↓
中断处理函数
USART_IRQHandler
↓
HAL_UART_IRQHandler
↓
接收中断处理
UART_Receive_IT
↓
HAL_UART_RxCpltCallback
串口实验(非中断)
{
unsigned char temp[1] = {ch};
HAL_UART_Transmit(&huart1,temp,1,0xffff);
return ch;
}#include
串口中断实验
//重写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);
}
}
十二、蓝牙插座_风扇_灯
项目需求
硬件配置
项目接线设计
非中断法
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模块实现串口通讯
项目需求
十四、看门狗
独立看门狗介绍
独立看门狗的本质
独立看门狗框图
独立看门狗时钟
重装载寄存器
键寄存器
独立看门狗实验
需求
硬件接线
溢出时间计算
喂狗函数
配置工程
编程实现
#include
窗口看门狗(WWDG)介绍
什么是窗口看门狗?
窗口看门狗的工作原理
控制寄存器
配置寄存器
状态寄存器
窗口看门狗实验
需求
配置工程
编程实现
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的意义
搬运什么数据?
DMA控制器
DMA及通道的优先级
DMA传输方式
指针递增模式
实验1、内存到内存搬运
需求
CubeMX配置
用到的函数
参数一:DMA_HandleTypeDef *hdma DMA通道句柄
返回值:HAL_StatusTypeDef HAL状态(OK、busy、ERROR、TIMEOUT)
编程实现
#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、内存到外设搬运
需求
CubeMX配置
用到的函数
编程实现
//发送缓存区大小
#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、外设到内存搬运
需求
用到的库函数
参数1:__HANDLE__ 串口句柄
参数2:__INTERRUPT__ 需要使能的中断
__HAL_UART_GET_FLAG(__HANDLE__, __FLAG__) (((__HANDLE__)->Instance->SR & (__FLAG__)) == (__FLAG__))
__HAL_UART_CLEAR_IDLEFLAG(__HANDLE__)
返回值:无
HAL_StatusTypeDef HAL_UART_DMAStop(UART_HandleTypeDef *huart)
参数1:UART_HandleTypeDef *huart 串口句柄
返回值:HAL_StatusTypeDef HAL状态(OK、busy、ERROR、TIMEOUT)
返回值:未传输数据大小CubeMX配置
编程实现
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);
}
#define BUF_SIZE 100//接收缓存区数组大小
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是什么?
温度传感器
电压信号
→
模拟量信号
单片机
压力传感器
ADC
光敏传感器
ADC的性能指标
ADC特性
ADC通道
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
通道17
连接内部Vss
ADC转换顺序
ADC触发方式
ADC转化时间
ADC转化模式
使用ADC读取烟雾传感器的值
CubeMX配置
代码实现
#include
十七、IIC协议
CubeMX配置
用到的库函数
参数1:I2C_HandleTypeDef *hi2c i2c设备句柄
参数3:uint16_t MemAddress 目标器件的目标寄存器地址
参数5:uint8_t *pData 待写的数据首地址
参数6:uint16_t Size 待写的数据长度
参数7:uint32_t Timeout 超时时间
返回值:HAL_StatusTypeDef HAL状态(OK、busy、ERROR、TIMEOUT)IIC协议控制OLED屏
{
//0x00是写入命令 0x40是写入数据
HAL_I2C_Mem_Write(&hi2c1,0x78,0x00,I2C_MEMADD_SIZE_8BIT,&Cmd,1,0xff);
}
{
//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