之前写的:我们前面已经讲解了新建工程文件以及STM32的时钟配置。 相信大家对STM32的代码大致框架以及内部时钟都有了一定的了解和深刻的了解。 本次介绍的第一部分是讲最基本的就是GPIO.1端口的应用。 1.1原理介绍原理图
首先我们看一下我们自己的STM32开发板的原理图。 以我使用的为例如图:
STM32F103VET6微控制器有完整的100个引脚。 这些端口是 GPIOA、GPIOB、GPIOC、GPIOD 和 GPIOE。 每个端口共有 GPIO_Pin0 到 GPIO_Pin15 共 16 个引脚。 引脚有什么用? 熟悉51单片机的同学可能会比较熟悉。 可以作为输入输出引脚,也可以作为串口等通信引脚,STM32上引脚的功能有了很大的提高,功能比51更加强大。
1.2 GPIO功能说明介绍
可以简单看一下GPIO的功能说明,如图(该图来自STM32参考手册):
可以看到控制引脚的寄存器很多,引脚模式也很多,对于初学者来说不太友好。 我们将在下一节代码中分析这些寄存器。 在下一节中,我们将了解代码如何实现这些。 对于寄存器配置,这里我们首先了解GPIO寄存器。 每个IO端口有7个寄存器需要控制,分别是:配置模式下的两个32位端口配置寄存器GPIOx_CRL和GPIOx_CRH,以及两个32位数据寄存器。 GPIOx_IDR和GPIOx_ODR,32位设置/复位寄存器GPIOx_BSRR,16位复位寄存器GPIOx_BRR; 32 位锁存寄存器 GPIOx_LCKR。 本节我们主要关注和使用的寄存器是 CRL、CRH、BRR 和 BSRR 寄存器。
1.3 硬件连接
我们在本文中要完成的任务是点亮 LED。 从最简单的角度思考,我们只需让一个端口输出高电平或低电平即可达到点亮或关闭LED灯的效果。 我们先来看看硬件。 连接图:
我购买的开发板只有一颗可以编程的LED灯,所以我只需要配置和操作PB0、GPIOB.0引脚即可。 PB0 设置低电平 LED 亮,高电平 LED 灭。
2. 代码实现及原理分析(详情) 2.1 代码实现
按照我们之前博文新建项目的格式规范,我们首先在项目文件的user文件夹下新建一个名为led的文件夹,并创建led.c和led.h文件,如下图所示:
代码如下所示:
led.c:
#include "led.h"
//LED 初始化程序
void led_init(void)
{
GPIO_InitTypeDef GPIO_LED; //定义GPIO结构体变量
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //使能GPIOB端口的时钟
GPIO_LED.GPIO_Pin = GPIO_Pin_0; //LED端口配置
GPIO_LED.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_LED.GPIO_Speed = GPIO_Speed_2MHz; //IO口速度为2MHz
GPIO_Init(GPIOB, &GPIO_LED); //根据设定参数初始化GPIOB0
GPIO_SetBits(GPIOB,GPIO_Pin_0); //GPIOB0输出高电平,初始化LED灭
}
led.h:
#ifndef __LED_H
#define __LED_H
#include "main.h"
void led_init(void);
#endif
主要.c:
/*
STM32F103VET6
SYSCLK = 72M
HCLK = 72M
APB1 = 36M
APB2 = 72M
*/
#include "main.h"
int i,j;
int main(void)
{
led_init(); //LED初始化
while(1)
{
GPIO_ResetBits(GPIOB,GPIO_Pin_0); //点亮LED
for(i=0;i<=1000;i++) for(j=0;j<=2000;j++); //软件延时一段时间
GPIO_SetBits(GPIOB,GPIO_Pin_0); //熄灭LED
for(i=0;i<=1000;i++) for(j=0;j<=2000;j++); //软件延时一段时间
}
}
主要.h:
#ifndef __MAIN_H
#define __MAIN_H
//标准头文件
#include "stm32f10x.h"
//用户自定义头文件
#include "led.h"
#endif
这是我们需要用来实现本文的代码。 编译测试没有错误和警告,可以正常使用。
2.2 配置步骤
步骤1:首先,我们看一下led.c中初始化LED的函数led_init(); 如果我们想使用该引脚,我们需要初始化该引脚。 我们从 ST 标准库中找到了 GPIO 相关的头文件。 文件:stm32f10x_gpio.h。 按照之前说的方法,先打开头文件,拉到文件底部,找到函数。 一眼就发现了这个函数叫GPIO_Init,如图:
从中文翻译我们知道这是GPIO口的初始化函数。 完成对该函数的调用,我们可以看到该函数的入口参数包括GPIOx(端口号)和一个GPIO结构体,所以在led_init()中我们首先定义一个结构体变量:
GPIO_InitTypeDef GPIO_LED; //定义GPIO结构体变量
选择结构并跳转到定义。 可以看到结构体中的成员变量有:
您也可以通过下面的官方评论了解得更清楚。 该结构体共有三个成员变量,分别是:引脚号、引脚输出速度、引脚模式。
然后我们初始化我们需要的函数的结构。 那么我们如何知道应该填写的参数是什么样的呢? 我们可以看一下GPIO_Init函数的本体,如图:
可以看到,在GPIO_Init函数内部,通过框架中的三个函数来验证输入参数的合法性。 通过跳转①②③,可以查看我们需要输入的参数的规格。 以下是配置的 LED 引脚:
GPIO_LED.GPIO_Pin = GPIO_Pin_0; //LED端口配置
GPIO_LED.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_LED.GPIO_Speed = GPIO_Speed_2MHz; //IO口速度为2MHz
引脚号为GPIO_Pin_0(我们的LED的引脚为GPIOB.0),引脚模式为GPIO_Mode_Out_PP(推挽输出),引脚速度为GPIO_Speed_2MHz(2MHz)
引脚方式说明
GPIO_模式_AIN
模拟输入
GPIO_模式_IN_FLOATING
输入浮点数
GPIO_模式_IPD
输入下拉菜单
GPIO_模式_IPU
输入上拉
GPIO_模式_输出_OD
开漏输出
GPIO_模式_输出_PP
推挽输出
GPIO_模式_AF_OD
多路开漏输出
GPIO_模式_AF_PP
多路推挽输出
这次我们选择推挽输出。 推挽输出和开漏输出最大的区别是推挽输出可以输出高电平和低电平,而开漏输出只能输出低电平。 如果需要输出高电平,需要外接。 拉电阻,我们需要在高低电平之间切换来点亮LED灯,所以我们选择推挽输出。 后续会写关于这两种输出方式的文章。
对于输出速度的选择,我们使用最低的2MHz。 由于控制LED对信号带宽没有要求,所以我们选择低速、低功耗。
最后只需要调用初始化函数即可初始化pin端口:
GPIO_Init(GPIOB, &GPIO_LED); //根据设定参数初始化GPIOB0
步骤2:在使用GPIO端口和任何STM32外设之前,我们需要打开相应外设的时钟。 通过上一讲时钟的讲解,我们知道GPIOB是挂载在APB2下的,于是我们在stm32f10x_rcc.h中找到了设置APB2时钟的函数RCC_APB2PeriphClockCmd,如图,打开GPIOB的时钟港口:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //使能GPIOB端口的时钟
第三步,完成上面两步之后,我们的LED引脚初始化就完成了。 最后,我们将 LED 引脚设置为高电平,以使其在初始化后保持关闭状态。
2.3 原理分析 2.3.1 GPIOx_CRL、GPIOx_CRH
在上面的功能配置中,我们并没有直接操作我们所说的GPIO寄存器。 这是因为ST标准库已经封装了寄存器。 其实我们底层调用的函数就是对寄存器进行读写操作。 接下来我们看一下在初始化引脚时我们对哪些寄存器进行了哪些操作。
我们来看看GPIO_Init(GPIOB, &GPIO_LED);到底是什么做
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
{
uint32_t currentmode = 0x00, currentpin = 0x00, pinpos = 0x00, pos = 0x00;
uint32_t tmpreg = 0x00, pinmask = 0x00;
/* Check the parameters */
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
assert_param(IS_GPIO_MODE(GPIO_InitStruct->GPIO_Mode));
assert_param(IS_GPIO_PIN(GPIO_InitStruct->GPIO_Pin));
/*---------------------------- GPIO Mode Configuration -----------------------*/
currentmode = ((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x0F);
if ((((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00)
{
/* Check the parameters */
assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed));
/* Output mode */
currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed;
}
/*---------------------------- GPIO CRL Configuration ------------------------*/
/* Configure the eight low port pins */
if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00)
{
tmpreg = GPIOx->CRL;
for (pinpos = 0x00; pinpos < 0x08; pinpos++)
{
pos = ((uint32_t)0x01) << pinpos;
/* Get the port pins position */
currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;
if (currentpin == pos)
{
pos = pinpos << 2;
/* Clear the corresponding low control register bits */
pinmask = ((uint32_t)0x0F) << pos;
tmpreg &= ~pinmask;
/* Write the mode configuration in the corresponding bits */
tmpreg |= (currentmode << pos);
/* Reset the corresponding ODR bit */
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
{
GPIOx->BRR = (((uint32_t)0x01) << pinpos);
}
else
{
/* Set the corresponding ODR bit */
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
{
GPIOx->BSRR = (((uint32_t)0x01) << pinpos);
}
}
}
}
GPIOx->CRL = tmpreg;
}
/*---------------------------- GPIO CRH Configuration ------------------------*/
/* Configure the eight high port pins */
if (GPIO_InitStruct->GPIO_Pin > 0x00FF)
{
tmpreg = GPIOx->CRH;
for (pinpos = 0x00; pinpos < 0x08; pinpos++)
{
pos = (((uint32_t)0x01) << (pinpos + 0x08));
/* Get the port pins position */
currentpin = ((GPIO_InitStruct->GPIO_Pin) & pos);
if (currentpin == pos)
{
pos = pinpos << 2;
/* Clear the corresponding high control register bits */
pinmask = ((uint32_t)0x0F) << pos;
tmpreg &= ~pinmask;
/* Write the mode configuration in the corresponding bits */
tmpreg |= (currentmode << pos);
/* Reset the corresponding ODR bit */
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
{
GPIOx->BRR = (((uint32_t)0x01) << (pinpos + 0x08));
}
/* Set the corresponding ODR bit */
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
{
GPIOx->BSRR = (((uint32_t)0x01) << (pinpos + 0x08));
}
}
}
GPIOx->CRH = tmpreg;
}
}
首先,进入函数时,初始化几个局部参数并检查输入参数的合法性,然后
currentmode = ((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x0F);
以我们之前设置的GPIOB.0为例。 操作完成后currentmode=0x00000000;
接下来进入if语句执行:
currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed;
当前模式=0x00000002
那么我们的pin号是在0~7之间,所以我们要输入下面的if来配置CRL
if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00)
进入后,首先读取GPIOB端口CRL寄存器的内容(tmpreg = GPIOx->CRL;)。 我们看一下这个CRL寄存器的定义:
32 位寄存器分别初始化低 8 个端口:引脚端口 0 至 7。前两位是配置位,后两位是模式位。
tmpreg读取到的GPIOB端口0~7的值为0x44444444,与参考手册中提到的端口默认值一致。 默认为浮动输入状态。
之后,程序进入for循环来判断我们配置的是哪个引脚:
for (pinpos = 0x00; pinpos < 0x08; pinpos++)
{
pos = ((uint32_t)0x01) << pinpos;
/* Get the port pins position */
currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;
......
......
}
通过我们设置的参数进行一系列的位操作。 您可以将操作步骤一步步写在纸上。 最后,我们得到 tmpreg=0x44444442。 通过对比寄存器表,我们可以看到PIN0引脚设置为2Mhz推送速度。 拉动输出。
最后写入寄存器值完成配置:GPIOx->CRL = tmpreg; GPIO_Init()完成配置
同理,如果我们配置8~15脚端口,CRH寄存器的操作过程也是一样的。
2.3.2 GPIOx_BRR、GPIOx_BSRR
我们配置引脚的过程在 CRL 和 CRH 寄存器上进行。 当我们写入高低电平时,我们对BRR和BSRR寄存器进行操作,如下代码所示:
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
/* Check the parameters */
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
assert_param(IS_GPIO_PIN(GPIO_Pin));
GPIOx->BSRR = GPIO_Pin;
}
这是函数 GPIO_SetBits(GPIOB,GPIO_Pin_0); 在引脚端口上操作的 ST 注释库中。 从名称中可以看出,它设置选定的引脚端口。 设置期间操作BSRR寄存器:
当我们设置该位时,输入参数是0x0001。 从表中可以看到PIN0设置为1。
我们来看看复位寄存器和功能。 函数为 GPIO_ResetBits(GPIOB,GPIO_Pin_0); 函数体如下:
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
/* Check the parameters */
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
assert_param(IS_GPIO_PIN(GPIO_Pin));
GPIOx->BRR = GPIO_Pin;
}
BRR 寄存器在复位期间操作:
输入参数为0x0001。 从表中可以看到PIN0被重置为0。
这就是我们如何使用LED灯,初始化引脚配置CRL/CRH,并翻转LED来操作BRR和BSRR。 接下来我们开始测试我们的代码是否按预期运行。
3. 仿真测试 3.1 软件逻辑分析仪DEBUG
在本次仿真中我们将使用KEIL5中的逻辑分析仪功能。 步骤如下:
步骤1:点击①Magic Package,选择Targrt选项卡并正确设置我们微控制器系统板外部内置的外部晶体振荡器。 我的板子使用的是外部8M晶振。 这里填写8.0。 这样做的目的是在模拟过程中提供信息。 正确的时间信息。
步骤2:选择Debug选项卡,选择Use Simulator,勾选Run to main(),然后填写以下Dialog DLL:DARMSTM.DLL,并填写以下参数:-pSTM32F103VE。 前者是固定的,后者根据你自己的单片机型号而定,例如如果是F103C8T6,则填写-pSTM32F103C8,以此类推。
第三步:然后就可以进入DEBUG界面,点击①进入,然后点击②呼出逻辑分析仪。 调用逻辑分析仪界面如图:
步骤4:点击①创建信号,然后点击②创建一个新信号。 填写portb.0代表GPIOB0。 如果使用GPIOA11等其他引脚,请填写porta.11。 填写完毕后,点击空白处即可完成书写。 输入,然后继续配置portb.0:
第五步:选择刚刚创建的信号,点击②将其改为Bit,最后点击③完成创建,如图:
第六步:信号配置完成。 接下来,单击①处的运行按钮,然后单击②处的停止按钮。 我们的GPIOB.0引脚的电平变化将显示在逻辑分析仪上以完成此软件模拟。 模拟测试。
3.2 硬件效果
我们将代码下载到微控制器并运行它。 下面是运行的效果。 LED小灯按预期安装完毕,并循环亮灭:
4. 总结
本节我们实现了单片机来控制LED灯,并学习了最基本的引脚配置和使用。 可以看到,本节我们并没有控制时间,只是使用了软件for循环做的延时函数。 我相信你一定不喜欢。 下一节将讨论使用 STM32 节拍时钟配置我们的延迟功能! 通过 us、ms 和 s 级延迟进行精确的延迟控制。