STM32F103C8T6单片机硬核原理篇:讨论GPIO的基本原理篇章1——只讨论我们的GPIO简单输入和输出
目录
前言
输出时的GPIO控制部分
标准库是如何操作寄存器完成GPIO驱动的初始化的?
问题1:如何掌握GPIO的编程细节——跟寄存器如何打交道
问题2:哪些寄存器,去哪里找呢?
问题三,寄存器的含义,写入的值的含义是如何的?我们想要完成功能需要写什么东西才能达到我们的目的
输入的IDR寄存器
最直接的输出寄存器 ODR寄存器
BRR和BSRR寄存器
标准库/HAL库是如何操作寄存器初始化GPIO的?
标准库GPIO_Init初始化函数
标准库的习惯——检查我们的参数合法性
设置我们的GPIO的Mode
GPIO Mode Binary Encoding Table
区分和判断到底是设置哪一个CR寄存器
HAL库如何初始化引脚的
对GPIO触发写操作,设置GPIO的电平
标准库的GPIO_WriteBit函数
HAL库的HAL_GPIO_WritePin函数
对GPIO触发读操作
GPIO_ReadInputDataBit
HAL_GPIO_ReadPin
前言
你玩够了前面的实验,现在就要准备竖起耳朵听听原理了。GPIO是单片机中很重要的基础外设,是单片机跟外界沟通的桥梁。
查看原理,一个非常好的习惯就是学会阅读我们的单片机手册。
上面的两张图像是我们亲爱的单片机GPIO机制全家福,是我们分析源代码和原理的一个重要的辅助手段,哦,我想你应该再说——这我分析鸡毛啊,对,这个图是8种GPIO模式混合在了一起的图像,而且你发现,实际上,第一张图和第二章图在物理上只有一个区别,那就是最右侧的上拉部分的二极管从VDD换成了VDD_FT,还得的笔者的分析资源手册的部分吗?你会看到一些引脚被标记上了FT,这个意思,是说明我们的单片机是耐压为5V的引脚,凡是没有标记FT的,你需要小心,他们是耐压3.3V的,一旦给大了电压,您的开发板可能就要报废了(芯片引脚烧坏)。
我们上面谈到了——“8种GPIO模式”这个组合,一个很自然的想法在你的脑袋里Rise up了,哪八种啊?不是我回答,我没那个权力,是手册回答的——
Mode | 模式 | 说明 |
---|---|---|
Input floating | 浮空输入 | 输入模式的一个子集:浮空的,换而言之,当没有明确的电平输入的时候,你的GPIO引脚的电平输入猜猜乐状态,如果没有非常明确的理由,不要使用这个模式!!! |
Input pull-up | 输入上拉 | 输入模式的一个子集:上拉的一个输入,换而言之,没有输入的时候,我们的电平就是出于高电平,你读取这个GPIO的电平就是高电平 |
Input-pull-down | 输入下拉 | 输入模式的一个子集:下拉的一个输入,换而言之,没有输入的时候,我们的电平就是出于低电平,你读取这个GPIO的电平就是低电平 |
Analog | 模拟输入 | 输入模式的一个子集:电路层上的模拟,换而言之,我们直接读取了电平的模拟量,也就是能通过一定手段拿到这个GPIO引脚上的电压!(笔者到ADC会好好说明这个事情) |
Output open-drain | 开漏输出 | 输出模式的一个子集:开漏是MOS管模电的术语,不太懂的朋友我打一个比方:就像是门缝里漏进来了高电平,这个高电平必须是外部别人给的,不是自己给的,这个时候的输出更像是输出一个控制,它本身不提供高电压,他对提供者(比如说你自己接的电池)进行控制,让他输出高电压。 |
Output push-pull | 推挽输出 | 输出模式的一个子集:还记得笔者说的——强势的输出能力吗,这里,我们的GPIO自己就在使劲的驱动外设,这就是单片机自己强势的控制外部外设而不是依赖于给定的电源,这种就适合驱动小外设,比如说LED,常见的最简单传感器等等 |
Alternate function push-pull | 复用的推挽输出 | 输出模式的一个子集:推挽输出上面刚说过,在这个事情上没有半毛钱变化.问题在于修饰词AF上,AF就是Alternate Function,也就是复用功能.这个事情对于大部分萌新而言会一脸懵逼,复用?什么复用?答案是,单片机之所以不菜,就是因为他有强大的复用能力,很快你就知道,片上的IIC芯片,SPI芯片等等如何对外沟通的?利用引脚跟外面打交道的,这个时候,GPIO就被征用,用来完成更加详细特定的外部功能了! |
Alternate function open-drain | 复用的开漏输出 | 输出模式的一个子集:开漏输出还是一样的,复用笔者上一个也说过了,不再重复。 |
现在的学习阶段上,我们不去特别深究电路原理,等到我们真正涉及到电路设计的时候再去了解。目前为之,我们只是接触了最简单的输入和输出,为此,我们来看看手册对这些地方的描述。
输出时的GPIO控制部分
很多文档说的太过于失去把控,将电路实现细节和编程细节全部混合在一起,笔者初学的时候也是非常的头大,这里笔者尝试说明白。
注意,在输出的时候,只有一个地方我们是真正关心的。
笔者告诉你的是。当我们调用GPIO_WriteBits的时候,写入的是Output data寄存器,Outputdata寄存器将我们的行为传递给Output driver输出驱动器子系统,输出驱动器,最关心的就是两个MOS管,也就是P-MOS管和N-MOS管。作为初学,不要立马钻到P-MOS和N-MOS的实现细节里去了,你需要知道的是,当我们处于强势的推挽输出的时候,两个MOS管都工作,共同根据我们的设置切换对应的工作状态,顺着线给出我们的高低电平,从而从I/O Pin上给出单片机自己的高低电平原则。
那开漏呢?答案是PMOS管就不工作了!只有NMOS管干活,也就是他只能输出低电平,高电平呢?不是单片机管的!我上面就说过了,实际上是控制外部电源输出他自己的大电压。电路部分点到位置。
我们作为软件开发,可以不用如此的关心这些细节(让那些硬件佬们操心吧,我每一次看到硬件✌都会开磕),我们关心的是,对于软件编程,库寄存器到底在做什么。
标准库是如何操作寄存器完成GPIO驱动的初始化的?
按照逻辑顺序,我们假设我们已经开启了GPIO时钟,现在,GPIO已经就绪,我们需要安排它的工作任务,我们这里的部分就是在探讨——我们委派GPIO任务的全部编程细节。
回到我们如何使用标准库完成这个事情的:
// 初始化GPIO PA0 完成设置PA0为八大模式下的推挽输出状态gpio_init.GPIO_Mode = GPIO_Mode_Out_PP;gpio_init.GPIO_Speed = GPIO_Speed_50MHz;gpio_init.GPIO_Pin = GPIO_Pin_0;GPIO_Init(GPIOA, &gpio_init);
问题1:如何掌握GPIO的编程细节——跟寄存器如何打交道
江科大的笔者听过,大佬是如何分析寄存器的,说真的把我吓跑了,因为真的不太理解。我们知道,单片机的核心实际上就是操作寄存器,甚至广义的说——你的电脑现在的工作,就是我们无时不刻疯狂的操作寄存器设置处理器的状态,接受和传递处理器的结果。所以,我们在硬件和软件上的衔接,就是寄存器,理解了寄存器要如何操作,我们实际上就可以完成对我们手头器件的编程了,看过笔者手搓操作系统系列的朋友应该会深有体会。
问题2:哪些寄存器,去哪里找呢?
数据手册的第一个重要含义,就是告诉你,当你面对一个外设的时候,他有哪些基本我们需要关心的寄存器,哪些我们需要关心的概念。比如说八大寄存器的模式啊,需要操作的寄存器啊,等等内容
约定俗称的讲,我们的单片机往往分为控制寄存器和数据寄存器,控制寄存器控制我们的片上外设/片外外设的行为,数据寄存器那就是真往里写东西的。换而言之,数据寄存器是货,控制寄存器是人,你指挥人,让他们正确的处理货物。所以,我们实际上就是看——如何编程这些寄存器呢?
我们的手册上给出了我们想要的东西,所以任何问题,先找手册,不是没有道理的,请看STM32F1xx系列的手册,在我们的第159页上的9.1,也就是第一大点,存在这样的一段话,笔者认为非常的关键,需要放到这里
Each of the general-purpose I/O ports has two 32-bit configuration registers (GPIOx_CRL, GPIOx_CRH), two 32-bit data registers (GPIOx_IDR, GPIOx_ODR), a 32-bit set/reset register (GPIOx_BSRR), a 16-bit reset register (GPIOx_BRR) and a 32-bit locking register (GPIOx_LCKR).
每个通用 I/O 端口都有两个 32 位配置寄存器(GPIOx_CRL、GPIOx_CRH)、两个 32 位数据寄存器(GPIOx_IDR、GPIOx_ODR)、一个 32 位置位/复位寄存器(GPIOx_BSRR)、一个 16 位复位寄存器(GPIOx_BRR)和一个 32 位锁定寄存器(GPIOx_LCKR)。
啊哈,这个没啥好说的,首先就是我们需要说的,关心的寄存器上,就是控制寄存器GPIOx_CRL和GPIOx_CRH,这就是GPIO为什么分组了,我们对每一个组的GPIO都配备了专门的控制寄存器和输入输出的缓存寄存器。我们对GPIO的编程,实际上就是围绕着这七个寄存器来说话的。
问题三,寄存器的含义,写入的值的含义是如何的?我们想要完成功能需要写什么东西才能达到我们的目的
这个问题就是数据手册存在的第二个重要的含义,举个例子,我们进一步阅读手册
Each I/O port bit is freely programmable, however the I/O port registers have to be accessed as 32-bit words (half-word or byte accesses are not allowed). The purpose of the GPIOx_BSRR and GPIOx_BRR registers is to allow atomic read/modify accesses to any of the GPIO registers. This way, there is no risk that an IRQ occurs between the read and the modify access.
每个 I/O 端口位均可自由编程,但 I/O 端口寄存器必须以 32 位字的形式进行访问(不允许半字或字节访问)。GPIOx_BSRR 和 GPIOx_BRR 寄存器的目的是允许对任何 GPIO 寄存器进行原子读取/修改访问。这样,就不会有在读取和修改访问之间发生 IRQ 的风险。
因此,我们知道,实际上我们不需要太关心ODR寄存器,他的作用实际上是作为BRR和BSRR寄存器的实际执行官,或者说,充当一个输出的影子。毕竟中断的存在的确会带来打断前后状态不一致的风险,我们自己操作寄存器的时候,也要操作的是GPIOx_BSRR 寄存器和GPIOx_BRR!不要更改ODR寄存器!
先关心的是控制,控制寄存器分为两个:低32位的GPIOx_CRL和高32位的GPIOx_CRH寄存器。我们在配置模式的时候,实际上也是针对我们的控制寄存器进行编程,请看171页的手册:
笔者下面教授我们如何思考和阅读这样的手册,第一个问题,想清楚我们要干啥。“我们现在要点LED”,”我们现在要让GPIO读取外部的电平...”,想明白这些事情,我们才能以正确的姿势阅读手册。
我们看,对于低32位的寄存器,我们实际上每一个Pin的信息占据4位。举个例子,CNF0和MODE0位就是针对我们的PIN_0说的,因此,当我们想要配置PA0的时候,我们实际上就只需要操作GPIOA的最低四位,这样就完事了。
那我们应该如何正确的写入值呢?看到上面的表格,上面的表格中,我们就完成了寄存器的模式设置。举个例子,在点灯部分的时候,我们就说过需要设置GPIO为推挽输出,因此,我们就需要设置我们的比特位是:CNF00,然后Mode呢,我们选择的是50MHZ的速度,因此就是0011.你可以使用自己写的点亮小灯的例程中进入调试模式来确认额这个事情
非常好!你已经迈出了学习看手册和验证的第一步了。下一步,请你试一试求证一下按钮输入输出的控制寄存器的值如何查找!
提示:我们学习按钮的时候,GPIO是按照输入上拉进行配置的。此外,你需要考证下我们的ODR寄存器的值,这个算额外的挑战
下一个问题就是,我们编程的时候,GPIO的输入和输出寄存器又是怎么一回事呢?请看手册
输入的IDR寄存器
IDR寄存器负责处理我们的输入,我们实际上ReadPin的时候,也是读取的IDR寄存器,他会存储我们的读取的电平的状态。
The Input Data register (GPIOx_IDR) captures the data present on the I/O pin at every APB2 clock cycle.
输入数据寄存器(GPIOx_IDR)在每个 APB2 时钟周期捕获 I/O 引脚上的数据。
关于何为APB2,这个事情笔者放到时钟树的介绍的时候,我们好好聊聊.
所以,我们想要得到GPIO外部引脚的电平的状态,只需要读取我们的IDR寄存器即可.我们后面分析标准库的ReadGPIOPinBits和HAL_GPIO_ReadPin函数的时候,还会回来好好看人家如何使用C语言代码完成这个工作的
最直接的输出寄存器 ODR寄存器
When configured as output, the value written to the Output Data register (GPIOx_ODR) is output on the I/O pin. It is possible to use the output driver in Push-Pull mode or Open-Drain mode (only the N-MOS is activated when outputting 0).
当配置为输出时,写入输出数据寄存器 (GPIOx_ODR) 的值将在 I/O 引脚上输出。输出驱动器可以在推挽模式或开漏模式下使用(输出 0 时仅激活 N-MOS)。
需要注意的是,我们的写入是非常直接的,最需要操作的安全办法是使用位操作来办事情.
These bits can be read and written by software and can be accessed in Word mode only. Note: For atomic bit set/reset, the ODR bits can be individually set and cleared by writing to the GPIOx_BSRR register (x = A .. G).
这些位可以由软件读取和写入,并且只能在字模式下访问。 注意:对于原子位设置/重置,可以通过写入 GPIOx_BSRR 寄存器(x = A .. G)单独设置和清除 ODR 位。
一般而言,我们也只会操作一个pin一个pin的操作,而且往往不希望中断打断我们的设置导致非常不安全的写入,因此,让我们进一步学习BSRR寄存器的写和BRR寄存器的写.
BRR和BSRR寄存器
BSRR寄存器是只能写入的,如手册所说!这里我们看到,这个32位的寄存器分成了上下两部分,高16位是Port Reset Bit,也就是说,往这里写东西的时候,对应的Pin上的ODR的位就会被复位为1,反之,则是0.
BRR寄存器的功能是对我们的指定的位进行清零.嗯,好像跟BSRR重复了?是这样的!
哦对了,新一点的STM32系列是没有BRR寄存器了,下面是笔者的STM32F407ZGT6对GPIO操作的手册,所以,ST半导体也认为这个寄存器实在是没有存在的必要,也就被精简掉了.
基本上我们的寄存器也就说完了.
标准库/HAL库是如何操作寄存器初始化GPIO的?
标准库GPIO_Init
初始化函数
我们看看GPIO_Init函数针对设置GPIO为一个推挽输出的时候的程序流。
-
模式设置
-
如果我们的Pin是低8个,则设置设置Control Register的低位寄存器. 反之,设置Control Register的高位寄存器
标准库的习惯——检查我们的参数合法性
我们编程的时候,也最好检查一下参数合法性质的问题,不然程序出现了异常,就会导致整个系统崩溃重启,这可就不好了。
/*** @brief Initializes the GPIOx peripheral according to the specified* parameters in the GPIO_InitStruct.* @param GPIOx: where x can be (A..G) to select the GPIO peripheral.* @param GPIO_InitStruct: pointer to a GPIO_InitTypeDef structure that* contains the configuration information for the specified GPIO peripheral.* @retval None*/
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
/*---------------------------- 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速度!嗯,你想想看,设置速度的信息,必须是输出,对吧。我们的(((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00
实际上就是在检查我们的第5位。嗯,整个需要请出来我们的GPIO库对标准库的抽象
typedef enum
{ GPIO_Mode_AIN = 0x0,GPIO_Mode_IN_FLOATING = 0x04,GPIO_Mode_IPD = 0x28,GPIO_Mode_IPU = 0x48,GPIO_Mode_Out_OD = 0x14,GPIO_Mode_Out_PP = 0x10,GPIO_Mode_AF_OD = 0x1C,GPIO_Mode_AF_PP = 0x18
}GPIOMode_TypeDef;
GPIO Mode Binary Encoding Table
Enum Name | Hex Value | Binary (8-bit) | CNF[1:0] | MODE[1:0] | Description |
---|---|---|---|---|---|
GPIO_Mode_AIN | 0x0 | 0000 0000 | 00 | 00 | Analog Input (ADC/DAC) |
GPIO_Mode_IN_FLOATING | 0x04 | 0000 0100 | 01 | 00 | Floating Input (no pull-up/down) |
GPIO_Mode_IPD | 0x28 | 0010 1000 | 10 | 00 | Input with Pull-Down |
GPIO_Mode_IPU | 0x48 | 0100 1000 | 10 | 00 | Input with Pull-Up |
GPIO_Mode_Out_OD | 0x14 | 0001 0100 | 01 | 11 | Output Open-Drain (max 50MHz) |
GPIO_Mode_Out_PP | 0x10 | 0001 0000 | 00 | 11 | Output Push-Pull (max 50MHz) |
GPIO_Mode_AF_OD | 0x1C | 0001 1100 | 01 | 11 | Alternate Function Open-Drain |
GPIO_Mode_AF_PP | 0x18 | 0001 1000 | 00 | 11 | Alternate Function Push-Pull |
区分和判断到底是设置哪一个CR寄存器
我们的标准库的设置方法比较的,嗯,天才
#define GPIO_Pin_0 ((uint16_t)0x0001) /*!< Pin 0 selected */
#define GPIO_Pin_1 ((uint16_t)0x0002) /*!< Pin 1 selected */
#define GPIO_Pin_2 ((uint16_t)0x0004) /*!< Pin 2 selected */
#define GPIO_Pin_3 ((uint16_t)0x0008) /*!< Pin 3 selected */
#define GPIO_Pin_4 ((uint16_t)0x0010) /*!< Pin 4 selected */
#define GPIO_Pin_5 ((uint16_t)0x0020) /*!< Pin 5 selected */
#define GPIO_Pin_6 ((uint16_t)0x0040) /*!< Pin 6 selected */
#define GPIO_Pin_7 ((uint16_t)0x0080) /*!< Pin 7 selected */
#define GPIO_Pin_8 ((uint16_t)0x0100) /*!< Pin 8 selected */
#define GPIO_Pin_9 ((uint16_t)0x0200) /*!< Pin 9 selected */
#define GPIO_Pin_10 ((uint16_t)0x0400) /*!< Pin 10 selected */
#define GPIO_Pin_11 ((uint16_t)0x0800) /*!< Pin 11 selected */
#define GPIO_Pin_12 ((uint16_t)0x1000) /*!< Pin 12 selected */
#define GPIO_Pin_13 ((uint16_t)0x2000) /*!< Pin 13 selected */
#define GPIO_Pin_14 ((uint16_t)0x4000) /*!< Pin 14 selected */
#define GPIO_Pin_15 ((uint16_t)0x8000) /*!< Pin 15 selected */
#define GPIO_Pin_All ((uint16_t)0xFFFF) /*!< All pins selected */
发现规律了把,实际上就是GPIO_Pin_X就是左移了一个X位,所以你看:我们下面对位检测的处理是这样做的:
/*---------------------------- GPIO CRL Configuration ------------------------*//* Configure the eight low port pins */// 如果是低8pin,那么。。。if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00){// 我们需要处理的寄存器就是CRL寄存器tmpreg = GPIOx->CRL;for (pinpos = 0x00; pinpos < 0x08; pinpos++){// 我们遍历一下,看看对应GPIO_PIN的位是不是被置1了pos = ((uint32_t)0x01) << pinpos;/* Get the port pins position */currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;if (currentpin == pos){// 是的,我们放上了1,这个时候,我们需要做的就是萃取控制的4个位。// 对于Pin0,则是0~3位,对于Pin1,则是4~7位。。。一次类推,所以我们才需要左移2,相当于x4了pos = pinpos << 2;/* Clear the corresponding low control register bits */pinmask = ((uint32_t)0x0F) << pos;// 本来是1111,这一下翻转了成了0000tmpreg &= ~pinmask;/* Write the mode configuration in the corresponding bits */// current mode是只取了低4位的valuetmpreg |= (currentmode << pos);/* Reset the corresponding ODR bit */// 如果是下拉的输入,那么我们对对应的BRR寄存器置1,等价于清0了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;}
剩下的CRH寄存器的逻辑完全一致,请看官自行分析,笔者的代码放到下面了
/*---------------------------- 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;}
}
HAL库如何初始化引脚的
void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init)
{// 一些无关的事情,笔者略去了
/* 配置端口引脚 - 遍历所有需要配置的引脚 */while (((GPIO_Init->Pin) >> position) != 0x00u){/* 获取当前引脚位置掩码 */ioposition = (0x01uL << position);
/* 获取当前需要配置的引脚 */iocurrent = (uint32_t)(GPIO_Init->Pin) & ioposition;
/* 如果当前引脚需要配置 */if (iocurrent == ioposition){// 这里是我们终点分析的逻辑 // < --------}
position++; // 检查下一个引脚}
}
我们先看看大致的框架,这里我们的代码写的更加明白一些,那就是当我们当然的与结果为1,说明我们的确需要配置GPIO的引脚,这个时候,我们就会开始进行配置。跟标准库的流程是完全一致的是,我们首先会设置好4位GPIO的configurations
/* 检查复用功能参数 */assert_param(IS_GPIO_AF_INSTANCE(GPIOx));/* 根据所需模式,填充config变量中的MODE[1:0]和CNF[1:0]位 */// 在这里,我们实际上是做自己的config向寄存器位的映射switch (GPIO_Init->Mode){/* 配置为推挽输出模式 */case GPIO_MODE_OUTPUT_PP:/* 检查速度参数 */assert_param(IS_GPIO_SPEED(GPIO_Init->Speed));config = GPIO_Init->Speed + GPIO_CR_CNF_GP_OUTPUT_PP; // 速度模式 + 推挽输出配置break;/* 配置为开漏输出模式 */case GPIO_MODE_OUTPUT_OD:assert_param(IS_GPIO_SPEED(GPIO_Init->Speed));config = GPIO_Init->Speed + GPIO_CR_CNF_GP_OUTPUT_OD; // 速度模式 + 开漏输出配置break;/* 配置为复用功能推挽模式 */case GPIO_MODE_AF_PP:assert_param(IS_GPIO_SPEED(GPIO_Init->Speed));config = GPIO_Init->Speed + GPIO_CR_CNF_AF_OUTPUT_PP; // 速度模式 + 复用推挽配置break;/* 配置为复用功能开漏模式 */case GPIO_MODE_AF_OD:assert_param(IS_GPIO_SPEED(GPIO_Init->Speed));config = GPIO_Init->Speed + GPIO_CR_CNF_AF_OUTPUT_OD; // 速度模式 + 复用开漏配置break;/* 配置为输入模式(也适用于事件和中断模式) */case GPIO_MODE_INPUT:case GPIO_MODE_IT_RISING:case GPIO_MODE_IT_FALLING:case GPIO_MODE_IT_RISING_FALLING:case GPIO_MODE_EVT_RISING:case GPIO_MODE_EVT_FALLING:case GPIO_MODE_EVT_RISING_FALLING:/* 检查上拉/下拉参数 */assert_param(IS_GPIO_PULL(GPIO_Init->Pull));if (GPIO_Init->Pull == GPIO_NOPULL){config = GPIO_CR_MODE_INPUT + GPIO_CR_CNF_INPUT_FLOATING; // 浮空输入}else if (GPIO_Init->Pull == GPIO_PULLUP){config = GPIO_CR_MODE_INPUT + GPIO_CR_CNF_INPUT_PU_PD; // 上拉输入/* 设置对应的ODR位(上拉) */GPIOx->BSRR = ioposition;}else /* GPIO_PULLDOWN */{config = GPIO_CR_MODE_INPUT + GPIO_CR_CNF_INPUT_PU_PD; // 下拉输入/* 清除对应的ODR位(下拉) */GPIOx->BRR = ioposition;}break;/* 配置为模拟输入模式 */case GPIO_MODE_ANALOG:config = GPIO_CR_MODE_INPUT + GPIO_CR_CNF_ANALOG; // 模拟输入break;/* 默认情况(参数已通过assert_param检查) */default:break;}
组合好了4个位之后,就是做实际上的寄存器修改了
/* 判断当前引脚属于低8位(CRL)还是高8位(CRH) */configregister = (iocurrent < GPIO_PIN_8) ? &GPIOx->CRL : &GPIOx->CRH;registeroffset = (iocurrent < GPIO_PIN_8) ? (position << 2u) : ((position - 8u) << 2u);/* 将新的引脚配置应用到寄存器 */MODIFY_REG((*configregister), ((GPIO_CRL_MODE0 | GPIO_CRL_CNF0) << registeroffset), (config << registeroffset));// 后面,我们略去了对中断部分的设置
MODIFY_REG实际上是HAL库封装的一个通用的宏,这个宏的原型如下:
#define SET_BIT(REG, BIT) ((REG) |= (BIT))#define CLEAR_BIT(REG, BIT) ((REG) &= ~(BIT))#define READ_BIT(REG, BIT) ((REG) & (BIT))#define CLEAR_REG(REG) ((REG) = (0x0))#define WRITE_REG(REG, VAL) ((REG) = (VAL))#define READ_REG(REG) ((REG))#define MODIFY_REG(REG, CLEARMASK, SETMASK) WRITE_REG((REG), (((READ_REG(REG)) & (~(CLEARMASK))) | (SETMASK)))
嗯,实际上就是合并了之前的操作,从语句直接编程了宏,编译速度的确会得到一定的提升。
对GPIO触发写操作,设置GPIO的电平
标准库的GPIO_WriteBit函数
void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal)
{/* Check the parameters */assert_param(IS_GPIO_ALL_PERIPH(GPIOx));assert_param(IS_GET_GPIO_PIN(GPIO_Pin));assert_param(IS_GPIO_BIT_ACTION(BitVal)); if (BitVal != Bit_RESET){GPIOx->BSRR = GPIO_Pin;}else{GPIOx->BRR = GPIO_Pin;}
}
很容易想到吧!注意了,因为BSRR和BRR寄存器可以做到直接原子化的设置我们的GPIO状态,因此使用BSRR和BRR寄存器显然更加的安全
HAL库的HAL_GPIO_WritePin函数
void HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)
{/* Check the parameters */assert_param(IS_GPIO_PIN(GPIO_Pin));assert_param(IS_GPIO_PIN_ACTION(PinState));if (PinState != GPIO_PIN_RESET){GPIOx->BSRR = GPIO_Pin;}else{GPIOx->BSRR = (uint32_t)GPIO_Pin << 16u;}
}
看到HAL库为什么新了吧,我们前面说过,BRR寄存器已经淘汰了,这就是一个体现,即使是旧单片机,我们也不去操作过时的外设,保证尽可能少的修改代码。
对GPIO触发读操作
GPIO_ReadInputDataBit
我认为无需多言,读取IDR寄存器即可
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{uint8_t bitstatus = 0x00;/* Check the parameters */assert_param(IS_GPIO_ALL_PERIPH(GPIOx));assert_param(IS_GET_GPIO_PIN(GPIO_Pin)); if ((GPIOx->IDR & GPIO_Pin) != (uint32_t)Bit_RESET){bitstatus = (uint8_t)Bit_SET;}else{bitstatus = (uint8_t)Bit_RESET;}return bitstatus;
}
HAL_GPIO_ReadPin
/*** @brief Reads the specified input port pin.* @param GPIOx: where x can be (A..G depending on device used) to select the GPIO peripheral* @param GPIO_Pin: specifies the port bit to read.* This parameter can be GPIO_PIN_x where x can be (0..15).* @retval The input port pin value.*/
GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
{GPIO_PinState bitstatus;/* Check the parameters */assert_param(IS_GPIO_PIN(GPIO_Pin));if ((GPIOx->IDR & GPIO_Pin) != (uint32_t)GPIO_PIN_RESET){bitstatus = GPIO_PIN_SET;}else{bitstatus = GPIO_PIN_RESET;}return bitstatus;
}
如你所见,一样是直接读取了我们的GPIO的IDR寄存器作为一个返回值
相关文章:
STM32F103C8T6单片机硬核原理篇:讨论GPIO的基本原理篇章1——只讨论我们的GPIO简单输入和输出
目录 前言 输出时的GPIO控制部分 标准库是如何操作寄存器完成GPIO驱动的初始化的? 问题1:如何掌握GPIO的编程细节——跟寄存器如何打交道 问题2:哪些寄存器,去哪里找呢? 问题三,寄存器的含义ÿ…...
FreeRTOS源码下载分享
FreeRTOS源码下载分享 官网下载太慢了,分享下FreeRTOSv202411 FreeRTOSv202411.00.zip 链接: https://pan.baidu.com/s/1P4sVS5WroYEl0WTlPD7GXg 提取码: g6aq...
PyArrow 核心技术与应用:高效数据处理与跨生态集成实践
Apache Arrow 作为列式内存数据格式的行业标准,其 Python 接口 PyArrow 正在重塑数据科学生态。本文深入解析 PyArrow 的核心计算能力,涵盖统计函数、分组聚合、窗口操作及跨库集成,通过完整代码示例演示如何利用其高性能特性优化数据处理流程…...
机试题——PCB印刷电路板布线
题目描述 在 PCB 印刷电路板设计中,器件之间的连线需要避免线路的阻抗值增大,而且器件之间还可能存在其他干扰源。为了简化问题,我们将电路板简化为一个 ( M * N ) 的矩阵,每个位置(单元格)的值表示其源干…...
数据化管理(一)---什么是数据化管理
目录 一、什么是数据化管理1.1 “聪明”的销售人员1.2 数据化管理的概念1.3 数据化管理的意义1.4 数据化管理的四个层次1.4.1 业务指导管理1.4.2 营运指导管理1.4.3 经营策略管理1.4.4 战略规划管理 1.5 数据化管理流程图1.5.1 分析需求1.5.2 收集数据1.5.3 整理数据1.5.4 分析…...
Android 10.0 通过广播控制systemui状态栏动态显示和隐藏功能实现
1.前言 在10.0的系统rom定制化开发中,在某些特定的产品开发中,需要通过接口来控制系统状态栏的显示和隐藏, 所以就需要了解systemui状态栏的显示构造过程,然后通过相关接口来显示和隐藏状态栏,接下来就来 实现相关的功…...
Linux服务器安装MinerU
安装MinerU 为了确保项目的稳定性和可靠性,我们在开发过程中仅对特定的软硬件环境进行优化和测试。这样当用户在推荐的系统配置上部署和运行项目时,能够获得最佳的性能表现和最少的兼容性问题。 这里我们以基础的 [[Linux服务器部署PaddleX实战教程]] 使…...
深挖 DeepSeek 隐藏玩法·智能炼金术2.0版本
前引:屏幕前的你还在AI智能搜索框这样搜索吗?“这道题怎么写”“苹果为什么红”“怎么不被发现翘课” ,。看到此篇文章的小伙伴们!请准备好你的思维魔杖,开启【霍格沃茨模式】,看我如何更新秘密的【知识炼金…...
Vite 内联 CSS 和 JS 的解决方案
使用 vite-plugin-singlefile(推荐) 这个插件专门用于将整个 Vite 应用打包成单个 HTML 文件,内联所有 JS 和 CSS。 安装 pnpm i vite-plugin-singlefile -D配置 vite.config.js import { defineConfig } from vite import { viteSingleF…...
致敬生物信息学先驱:玛格丽特·戴霍夫(Margaret Dayhoff,1925-1983)
李升伟 编译 社论 发布于:2025年3月11日 《自然-计算科学》第五卷 第187页(2025年) 在玛格丽特戴霍夫(Margaret Dayhoff,1925-1983)百年诞辰之际,我们聚焦这位先驱在生物信息学领域留下的不朽…...
Knife4j文档请求异常 空指针
打开swagger文档报空指针异常 java.lang.NullPointerException: nullat springfox.documentation.oas.mappers.SchemaMapper.model(SchemaMapper.java:97)at springfox.documentation.oas.mappers.SchemaMapper.mapModel(SchemaMapper.java:85)at springfox.documentation.oas…...
笔记2——网络参考模型
一、OSI参考模型: 应用层: 报文 给应用程序提供接口 表示层: 进行数据格式的转换 会话层: 在通讯双方之间建立、管理和终止会话 传输层: 数据段;建立、维护、取消一次端到端的数据传输过程;控制…...
Spring AOP + Redis缓存设计实战:基于注解的优雅三防方案(击穿/穿透/雪崩)
文章目录 摘要 正文一、缓存设计的痛点与破局二、核心代码拆解:四层防御设计1. 注解驱动(ZywCacheable)2. 缓存击穿防护:双重检查锁3. 缓存穿透防护:空值标记4. 缓存雪崩防护:TTL随机算法 三、生产环境最佳…...
洛谷题单3-P5720 【深基4.例4】一尺之棰-python-流程图重构
题目描述 《庄子》中说到,“一尺之棰,日取其半,万世不竭”。第一天有一根长度为 a a a 的木棍,从第二天开始,每天都要将这根木棍锯掉一半(每次除 2 2 2,向下取整)。第几天的时候木…...
jdk21新特性详解使用总结
jdk21新特性详解总结 1.StringBuilder和StringBuffer新增了一个repeat方法 /*** Java 21的StringBuilder和StringBuffer新增了一个repeat方法*/public static void repeatStr(){var sbnew StringBuilder().repeat("*",10);System.out.println(sb);}运行结果如下&…...
解码 collections.Counter - 频率统计的利器
文章目录 前言一、什么是 collections.Counter?二、 基本用法:从创建到访问2.1 创建 Counter 对象2.2 访问计数三、 核心功能:更新与排序3.1 更新计数3.2 获取常见元素四、高级用法:数学运算与转换4.1 数学运算4.2 类型转换五、 实际应用:Counter 的威力5.1 词频统计5.2 在…...
Mysql基础笔记
# 1.SQL数据类型 可以去这篇文章看看: 最全 SQL 字段类型(4种)、属性(6种)总结:https://blog.csdn.net/weixin_45654582/article/details/119157403 ### 一.整数类型 ### 二.小数类型(2种) 1、浮点型:…...
HttpClient-03.入门案例-发送POST方式请求
一.发送POST方式请求 编写代码: 1.创建一个HttpClient对象 2.创建一个HttpGet请求 3.发送http的get请求并获得响应对象 4.通过发送GET请求获取的CloseableHttpResponse响应对象来获取状态码以及响应数据 package com.sky.test;import com.alibaba.fastjson.JS…...
Oracle数据库数据编程SQL<3.6 PL/SQL 包(Package)>
包是Oracle数据库中一种重要的PL/SQL程序结构,它将逻辑相关的变量、常量、游标、异常、过程和函数组织在一起,提供了更好的封装性和模块化。在大型项目中,可能有很多模块,而每一个模块又有自己的存过、函数等。而这些存过、函数默…...
每日一题---买卖股票的最好时机(一)、(二)
目录 买卖股票的最好时机(一) 一、题目链接:买卖股票的最好时机(一)_牛客题霸_牛客网 二、解题思路 三、代码实现 买卖股票的最好时机(二) 一、题目链接:买卖股票的最好时机(二)_牛客题霸_牛客网 编辑 二、解题思路 …...
XSS漏洞的分类解释和演示实验
XSS漏洞:跨站脚本攻击(cross site scripting),为了不和CSS混淆而改名。攻击者网web插入恶意script代码,当用户浏览页面时,嵌入的代码会被执行。 危害:盗取各类用户,强制发送电子邮件,网站挂马等…...
【Pandas】pandas DataFrame info
Pandas2.2 DataFrame Attributes and underlying data 方法描述DataFrame.index用于获取 DataFrame 的行索引DataFrame.columns用于获取 DataFrame 的列标签DataFrame.dtypes用于获取 DataFrame 中每一列的数据类型DataFrame.info([verbose, buf, max_cols, …])用于提供 Dat…...
JP1 Systemwalker 和 unirita的A-AUTO制品对比
以下是 JP1 SystemWalker(日立) 与 Unirita A-AUTO 的对比分析。两者均为日本企业开发的IT运维自动化工具,但在功能定位、技术架构和适用场景上存在显著差异: 1. 产品背景与市场定位 维度JP1 SystemWalkerUnirita A-AUTO开发商日…...
探索鸿蒙操作系统:迎接万物互联新时代
# 探索鸿蒙操作系统:迎接万物互联新时代 在科技飞速发展的当下,万物互联的时代浪潮正席卷而来。在这个全新的时代背景下,移动应用开发领域面临着前所未有的挑战,同时也迎来了诸多机遇。而鸿蒙操作系统(HarmonyOS&…...
NOIP2010提高组.引水入城
*前置题目 901. 滑雪 #include <iostream> #include <algorithm> #include <cstring>using namespace std;const int N 310, INF 0x3f3f3f3f; const int dx[4] {0, -1, 0, 1}, dy[4] {1, 0, -1, 0};int n, m, h[N][N]; int f[N][N]; int ans;int dfs(i…...
NLP高频面试题(二十九)——大模型解码常见参数解析
在大语言模型的实际应用中,如何更有效地控制文本生成的质量与多样性,一直是热门研究话题。其中,模型解码(decode)策略至关重要,涉及的主要参数包括 top_k、top_p 和 temperature 等。本文将详细介绍这些常见…...
【AI产品分享】面向图片的原始位置翻译功能
1. 背景 在撰写文字材料时,往往需要配套图像以增强表达效果。然而,有时自己绘制的图可能达不到理想的质量,而在其他文献材料中却能发现更清晰、直观的示例。希望在“站在巨人的肩膀上”优化自己的图像时,通常希望在保留原始图像的…...
为什么要为 REST API 添加认证
在不断发展的 Web 服务领域,REST API 在各种软件系统之间的通信中扮演着至关重要的角色。然而,强大的功能也伴随着巨大的责任。确保敏感数据的安全性和通信的可靠性是至关重要的。这时,认证就显得尤为重要。通过使用认证,我们可以…...
AI 数字人短视频数字人源码部署揭秘:开启虚拟内容创作新纪元
在当下短视频盛行的时代,AI 数字人短视频以其独特的魅力吸引着大众的目光。虚拟偶像在舞台上活力四射,电商平台中数字人不知疲倦地推荐产品,这些令人瞩目的表现背后,源码的部署起着至关重要的作用。它如同幕后的神奇工匠ÿ…...
佳能imageRUNNER 2206N基本参数及管理员密码
基本参数: 产品类型 激光数码复合机 颜色类型 黑白 涵盖功能 复印/打印/扫描 速度类型 低速 最大原稿尺寸 A3 复印/打印方式 激光静电转印方式 感光材料 OPC 显影系统 干式单组分显影 定影…...
【Linux篇】探索进程地址空间:计算机背后的虚拟世界
进程地址空间的奥秘:让你理解程序如何在计算机中生存 一. 程序地址空间1.1 基本概念1.2 虚拟内存管理1.3 为什么存在虚拟地址空间1.3.1 意义 2. 最后 本文将介绍进程地址空间的基本概念与结构,帮助读者理解操作系统如何管理和分配内存。进程地址空间指的…...
Docker部署sprintboot后端项目
创建Docker网络 docker network create icjs 部署Redis docker run -d \--network icjs \--name redis \-p 6379:6379 \redis:latest数据持久化 docker run --restartalways --network icjs -p 6379:6379 --name redis -v /opt/docker/redis/redis.conf:/etc/redis/redis.c…...
1.4 基于模拟退火改进蛇算法优化VGG13SE网络超参数的故障诊断模型
本博客来源于CSDN机器鱼,未同意任何人转载。 更多内容,欢迎点击本专栏,查看更多内容。 目录 0 引言 1 改进原理 2 本文改进方法 3 改进蛇优化VGG13SE的故障诊断模型 4 结语 0 引言 在【博客】中,我们采用了蛇算法来对VGG1…...
Vue + Scss项目中实现自定义颜色主题的动态切换
当时面试的时候遇到面试官问的一个问题如何实现自定义颜色主题切换,当时我做的只是elementUIPlus提供的暗黑和默认主题切换 theme.scss // 增加自定义主题类型 $themes: (light: (/* 原有配置保持不变 */),dark: (/* 原有配置保持不变 */),custom: () // 空映射…...
C#实现HiveQL建表语句中特殊数据类型的包裹
用C#实现搜索字符串中用’(‘和’)‘包裹的最外层的里面里面的字符串,将里面的记录按一个或多个空格、换行或tab,或者是它的在一起的组合作为分隔,分隔出多个字符串组,如果组中有字符串中同时包含’<‘和’>’,则…...
27 python 标准库概览
在办公室里,每个员工都有一套预装的办公软件:Word 处理文档、Excel 制作表格、Outlook 收发邮件... Python 的标准库就像公司预装的 "办公全家桶",包含 100 多个模块,覆盖文件操作、时间管理、数据分析等日常需求,无需额外安装即可直接使用。 一、文件管理 1.…...
whisper 语音识别的安装与使用
Whisper 是由OpenAI开发的开源自动语音识别(ASR)模型,不仅支持音频转录,还可以用于视频转录。通过调用ffmpeg处理视频,支持主流音视频格式的转录。 安装 安装ffmpeg:下载ffmpeg,Releases B…...
搜广推校招面经六十四
滴滴搜推算法 一、定义一个树结构、特征结构。写一个决策树对样本打分 逆天啊,上来就是暴击 import numpy as np class TreeNode:def __init__(self, feature_indexNone, thresholdNone, leftNone, rightNone, scoreNone):self.feature_index feature_index #…...
zabbix监控网站(nginx、redis、mysql)
目录 前提准备: zabbix-server主机配置: 1. 安装数据库 nginx主机配置: 1. 安装nginx redis主机配置: 1. 安装redis mysql主机配置: 1. 安装数据库 zabbix-server: 1. 安装zabbix 2. 编辑配置文…...
动态规划,如何应用动态规划解决实际问题?
一、动态规划核心概念 动态规划是一种分阶段解决问题的数学方法,它将复杂问题分解为更小的子问题,通过存储子问题的解来避免重复计算。 关键特征: 最优子结构:问题的最优解包含子问题的最优解重叠子问题:问题可…...
常见操作系统特点及区别对比
操作系统名称类型特点主要用途许可证类型内核类型Windows桌面/服务器图形界面友好,软件生态丰富,闭源个人电脑、企业办公专有商业许可混合内核macOS桌面 (Unix-like)高度优化的硬件整合,Unix基础,闭源创意设计、开发专有商业许可混…...
【资讯分享】为Apple Intelligence打造的有效屏障:“隐私保护气泡”
导读:苹果在WWDC大会上推出Apple Intelligence,主打个性化智能服务,深度整合iOS生态,支持跨App操作与内容感知。通过本地计算与私密云计算(PCC)技术实现端到端加密,确保数据匿名化处理与高透明度…...
AT_abc306_b [ABC306B] Base 2
题目描述 给定一个长度为64的序列A(A\_0,A\_1,\dots,A\_{63})A(A_0,A_1,…,A_63),由0和1组成。 求A\_0 2^0 A\_1 2^1 \dots A\_{63} 2^{63}A_020A_121⋯A_63263。 约束条件 A\_iA_i是0或1。 输入 从标准输入中以以下格式给出输入: A_0A0 A_1A…...
C++IO流类库
一、输入输出流(I/O strea) 编译系统已经以运算符或函数的形式做好了对标准外设(键盘、屏幕、打印机、文件)的接口,使用时只需按照要求的格式调用即可。 cin>>x; cout<<x; cin.get(ch); C语言的I/O系统向用户提供一个统一…...
常见的锁策略+synchronized(特性解释)
该篇文章主要是对常见的锁策略的总结(主要的作用是扫盲),如想要了解其他部分,这部分可以不用看 目录 一、常见的锁策略1. 悲观锁vs乐观锁举例: 2. 重量级锁vs轻量级锁3. 挂起等待锁vs自旋锁举例 4.普通互斥锁vs读写锁…...
spring打包,打包错误
打包(idea) 通过点击井盖样式的符号可以将test测试类取消打包进去 点击“M”,双击package即可打包 打包出错 ❯ java -jar /home/ying/Documents/java_workspace/spring-01-ioc/target/spring-01-ioc-0.0.1-SNAPSHOT.jar Error: LinkageError occurred while loadi…...
【Linux系统】进程间通信-System V消息队列
🎁个人主页:我们的五年 🔍系列专栏:Linux网络编程 🌷追光的人,终会万丈光芒 🎉欢迎大家点赞👍评论📝收藏⭐文章 Linux网络系列文章计算机网络(Linux网…...
DeepSeek×擎创科技:当智能运维遇见大模型「懂行」革命
运维人最懂「动态阈值」的痛 在数字化转型浪潮中,运维监控正经历从"人工经验"到"智能决策"的跃迁。传统动态阈值设置依赖人工分析历史数据、反复调整规则的模式,既难以应对业务波动性,又消耗大量技术资源。 擎创科技基…...
手绘风格流程图工具:简单高效的在线流程图绘制工具
手绘风格流程图:简单高效的在线流程图绘制工具 🎉 项目介绍 大家好!我很高兴向大家分享我最近开发的一个项目 —— 在线绘制手绘风格流程图,这是一个简单高效的在线流程图绘制工具。无论是整理思路、规划项目还是准备演示&#…...
leetcode287.寻找重复数
与寻找链表环的起始点一样 ,用快慢指针让二者相遇后,慢指针回到起始点二者以同样速度移动最终会在环的起始点相遇 class Solution {public int findDuplicate(int[] nums) {int slow nums[0], fast nums[0];do {slow nums[slow];fast nums[nums[fas…...