一、流水灯
1.1流水灯 GPIO 框图
1.1.1输出
(1)推挽与开漏
推挽输出如上图,去掉上图中 P-MOS 管就为开漏输出,内部就无上拉,若需要上拉要在引脚外部处接上拉电阻。
此处由于 LED 灯的硬件结构使用推挽输出(才有高低电平控制通断),在 WIFI 模块 ESP-12S 中有使用开漏输出的例子,如下图的使能和复位引脚,芯片内部有上拉电阻上拉到 3.3V。这时候单片机进行控制必须要设置为开漏输出,不能设置为推挽。
如果单片机使用推挽输出,可能会导致电平冲突。例如,单片机输出高电平时,内部上拉电阻也会将电平拉高;但如果单片机输出低电平时,上拉电阻会将电平拉高,而单片机会试图将电平拉低,这种冲突会导致短路或电流过大。开漏输出可以确保输出信号的一致性,特别是在外部上拉电阻已经确定电平的情况下。开漏输出让单片机只负责拉低电平,而不主动拉高电平,避免了电平冲突。
故设置输出方式由硬件外围电路决定。
推挽可以作为使能输出(20mA),开漏一般作为信号输出。
(2)复用功能
在图 1 中复用功能输出处,是通过片上的外设进行控制,如采用串口、CAN、SPI等口控制这个引脚是推挽还是开漏。
1.1.2输入
(1)上下拉开关
如上图:
模拟输入:ADC 采集。
当上下拉开关都不采用:浮空状态。如 AV 采集,外部就有上下拉输入,故 GPIO 内部输入就设置为浮空状态,不然会影响采集。
中断也需要采用浮空输入。
上拉输入:如按键输入,一般情况是低电平,当按下时高电平。注意手册中写明上拉为弱上拉,上拉电阻值为 40K ,如果线很长,外部干扰很多就要自行接上拉电阻;如果线很短,环境也不恶劣,就使用 GPIO 口的内部上拉。
上拉输入同上。
1.2软件编程
1.2.1 STM32cubeMX 初始化
说明:初始化 3 个 LED 灯为推挽输出,默认高电平,亮灯。
生成工程后编译与烧录,得到 3 个灯全亮。
1.2.2 编程
开始写代码时可以去看一看 stm32f1xx_hal.h,这是HAL库,包含如:ReadPin、WritePin、TogglePin(取反)等函数, stm32f1xx_hal.c 中有讲解、每一个函数的详细过程的定义。
main.c
while (1)
{
System.Run();
}
System.c
static void Run()
{
HAL_Delay(1000);
HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_SET);
HAL_GPIO_WritePin(LED2_GPIO_Port,LED2_Pin,GPIO_PIN_RESET);
HAL_GPIO_WritePin(LED3_GPIO_Port,LED3_Pin,GPIO_PIN_RESET);
HAL_Delay(1000);
HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_RESET);
HAL_GPIO_WritePin(LED2_GPIO_Port,LED2_Pin,GPIO_PIN_SET);
HAL_GPIO_WritePin(LED3_GPIO_Port,LED3_Pin,GPIO_PIN_RESET);
HAL_Delay(1000);
HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_RESET);
HAL_GPIO_WritePin(LED2_GPIO_Port,LED2_Pin,GPIO_PIN_RESET);
HAL_GPIO_WritePin(LED3_GPIO_Port,LED3_Pin,GPIO_PIN_SET);
}
二、串口打印
2.1理论
2.1.1程序功能
实现串口打印功能,实现现象如下:
运行状态机,会实时打印状态机处于哪个状态。
2.1.2硬件电路
集成 TTL 转 USB 驱动,插入 MicroUSB 线即可。
(没错,就是那个小小的既可以供电,又可以烧录程序的端口。)
2.1.3技术讲解
1、串口
UART 是一种通用串行数据总线,用于异步通信,该总线双向通信,可以实现全双工传输和接收。
UART 作为异步串口通信协议的一种,工作原理是将传输数据的每个字符一位接一位地传输。
其中各位的意义如下:
起始位:先发出一个逻辑 "0" 的信号,表示传输字符的开始。
数据位:紧接着起始位之后。数据位的个数可以是 4、5、6、7、8 等,构成一个字符,从最低位开始传送。
奇偶校验位:数据位加上这一位后,使得 "1" 的位数应为偶数(偶校验)或奇数(奇校验),以此来校验资料传送的正确性。(双方可以进行约定,可以有也可以没有,一般不用奇偶校验位,用CRC校验。)
停止位:它是一个字符数据的结束标志。可以是1位、1.5位、2位的高电平。由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也越慢。
空闲位:处于逻辑"1"状态,表示当前线路上没有资料传送。
在RS485硬件电路那里就有上下拉电阻,在总线空闲时就保持上拉,不然电平不稳定。
波特率:衡量数据传送速率的指标。表示每秒钟传送的比特位。(下图中165200修改为115200)
2、STM32 的 UART 串口
2.2软件编程
2.2.1 STM32cubeMX 初始化
在 1 中选择项目的 UART1 引脚,在 2 通信栏选中 UART1 ,在 3 模式中配置成异步模式,在 4 参数设置中配置成如图。
2.2.2代码编写
三、基本定时器
3.1理论讲解
3.1.1程序功能
通过基本定时器 6,让 3 个 LED 灯间隔 1s 闪烁。
3.1.2基本定时器TIM6 和 TIM7
32 单片机的定时器一般有 8 个,1、8是高级定时器,具有一些特殊处理,主要是针对电机使用;2~5 是通用定时器,有 PWM 波控制等功能;6、7 是基本定时器,具有最基本的功能,和 51 中类似,以下是基本定时器TIM6 和 TIM7 框图。
(1)计数器
是该定时器最重要的部分,可以进行向上或者向下的计数,计到指定数后可以产生事件或中断,就可以执行其他定时任务。
(2)预分频器
接收控制器的信号,驱动计数器。
(3)控制器
控制器控制预分频器,控制器可以进行控制复位、控制使能、控制计数。
控制器内部有时钟。
想要控制计数器就要控制预分频器,控制预分频器就要明确控制器内部的时钟。一旦时钟确定,就知道每计数一次是多少时间,再配置计数值,就可以配置一个具体的时间。
3.1.3NVIC
NVIC 即嵌套向量中断控制器(Nested Vectored Interrupt Controller)。STM32 的中有一个强大而方便的 NVIC,它是属于 CM3 内核的器件。
STM32 中有两个优先级的概念:抢占式优先级和响应优先级,响应优先级也称子优先级,每个中断源都需要被指定这两种优先级。抢占式优先级可以嵌套,响应优先级不可以嵌套。
具有高抢占式优先级的中断可以在具有低抢占式优先级的中断处理过程中被响应,即中断嵌套,或者说高抢占式优先级的中断可以嵌套在低抢占式优先级的中断中。
当两个中断源的抢占式优先级相同时,这两个中断将没有嵌套关系,当一个中断到来后,如果正在处理另一个中断,这个后到来的中断就要等到前一个中断处理完之后才能被处理。如果这两个中断同时到达,则中断控制器根据他们的响应优先级高低来决定先处理哪一个;如果他们的抢占式优先级和响应优先级都相等,则根据他们在中断表中的排位顺序决定先处理哪一个。
3.1.4 HAL定时器库函数
Time Base functions :初始化。由于软件会帮我们初始化,所以一般不使用。
Blocking mode: Polling:轮训模式,不会产生中断。
Non-Blocking mode: Interrupt :产生中断,本次主要使用。(是一个在括号中指定定时器的启动函数)
Non-Blocking mode:DMA :DMA 模式,后续讲解。
3.2 Cubemx 初始化
除了3个LED灯像之前一样正常配置以外,在 1 定时器框中选择 TIM6 ,在框 2 中选择激活它,不 启动单脉冲模式,在设置参数的框 3 中设置如下参数:
(1)预分频器(PSC-16 位值)
在基本定时器框图中内部时钟(CLK_INT)就是时钟配中的 APB2 的时钟频率 72M ,后续进入预分频器中的 CK_PSC 也就是 72M 了(没必要以 72M 驱动计数器,不然它太累了)。
预分频器可以进行 0~65535 分频(因为零也算,所以后面要加一),计算预分频器具体分频数 X 的公式如下:
所以在软件配置的值为:72000000/1000000-1(目标频率是 1M )
(2)Counter Mode
计数方式:往上计数
(3)Counter Period (AutoReload Register - 16 bits value)
计数器周期(自动重新加载寄存器- 16 位值):配置寄存器的值。预分频器计数从~65536,这里寄存器的值 CNT 就是预分频器到达这个值就发送中断,比如这里设置的值是 5ms,则:
所以在软件配置的值为:1000000 / 200
其实以上的计算就是把时间转化成频率去计算,找到规律后直接带入即可。
(4)auto-reload preload
自动重新加载预加载:打开。每一次中断后自动装载。
最后是配置中断可以去上面的 NVIC 中配置。
(5)中断配置
在 1 中是设置有多少个 几位二进制的中断,如果是 2 bit 就是 4 种优先级的中断,可以在下面的列表中使能后设置0、1、2、3 种优先级;如果是 4 bit 就是 16 种优先级的中断,可以在下面的列表中使能后设置0、1、2…… 15 种优先级,这里的设置如上。
(6)总结
1、定时器时钟 = 72MHz
2、计算时钟 = 72MHz/(71+1) = 1MHz
3、定时周期 = ((1/1000000Hz)*5000)s = (5/1000)s = 5ms
3.3 编程
3.3.1计数方式
3.3.2软件
(1)新生成 tim.c
根据软件的配置生成定时器初始化、中断初始化、优先级初始化、结构体等代码。
(2)自定义的 Timer6.c
Timer6.c 通过一个结构体来操作定时器,在 Timer6.h 中说明结构体中有一个枚举:
每 5ms 中断一次,10ms 就是 2 个,50ms 就是 10 个 ,这样写的好处是,以后需要10ms 中断一次,以 10ms 为单位时,只需要来修改头文件,而不需要大幅度修改代码,方便移植。
typedef enum
{
TIMER0_10mS = (uint16_t)2,
TIMER0_50mS = (uint16_t)10,
TIMER0_100mS = (uint16_t)20,
TIMER0_200mS = (uint16_t)40,
TIMER0_500mS = (uint16_t)100,
TIMER0_1S = (uint16_t)200,
TIMER0_2S = (uint16_t)400,
TIMER0_3S = (uint16_t)600,
TIMER0_5S = (uint16_t)1000,
TIMER0_10S = (uint16_t)2000,
TIMER0_3min = (uint16_t)36000,
}TIMER0_Value_t;
有一个:定时器每中断一次就计数一次的结构体。
回到 Timer6.c 中,为启动定时器 6 的代码:
#include "MyApplication.h"
static void Timer6_Start_IT(void); //定时器6以中断模式启动
Timer6_t Timer6 =
{
0,
Timer6_Start_IT
};
/*
* @name Timer6_Start_IT
* @brief 定时器6以中断模式启动
* @param None
* @retval None
*/
static void Timer6_Start_IT(void)
{
HAL_TIM_Base_Start_IT(&htim6); //启动定时器6
}
(3)main.c
#include "main.h"
#include "tim.h"
#include "gpio.h"
#include "MyApplication.h"
void SystemClock_Config(void);
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_TIM6_Init();
MyInit.Peripheral_Set();
//我们自己的初始化,写在Myinit.c中,我们的初始化就是启动定时器6
//因为以上没有启动定时器,这里需要启动一下
while (1)
{
System.Run();
//本次自定义的System中的Run函数中什么也没有写,因为这次需要中断唤起
}
}
(4)32 中断讲解
既然 Run 函数中什么也没有,这就需要去了解一点 32 中断的知识。
3.4测试现象
基本定时器6
注意:软件部分我都是简单学习,涉及到一些过程操作,我都写的比较简洁,如果需要请自行购买硬件家园相关课程。
本文推荐:硬件家园公众号