概述
一、初识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_STA bit15 bit14 bit13~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,worldn",strlen("hello,worldn"),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("rn");
//重新开始下一次接收
UART1_RX_STA = 0;
}
printf("hello kevinrn");
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已经打开rn");
}
}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已经关闭rn");
}
}else{
if(buf[0] != '