C语言的使用技巧--在IO操作中的移位和快速配置

在WB32F103(ARM cortex m3内核,96Mhz)的gpio初始化中有一段代码,充分的结合了硬件特征并使用C语言的技巧来快速的配置对应的GPIO的功能,堪称经典和楷模,代码异常简洁,执行速度快,配置任意IO方便快捷。
我们先来看看这一段源代码:

void GPIO_Init(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, uint32_t PinConfig)
{
  uint32_t tmp = PinConfig;
  /* Check the parameters */
  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
  assert_param(IS_GPIO_PIN(GPIO_Pin));

  GPIOx->CFGMSK = ~GPIO_Pin;
  GPIOx->MODER = ((tmp >> 28) & 0x3) * 0x55555555U;
  GPIOx->OTYPER = ((tmp >> 24) & 0x1) * 0xFFFFFFFFU;
  GPIOx->OSPEEDR = ((tmp >> 20) & 0x3) * 0x55555555U;
  GPIOx->PUPDR = ((tmp >> 16) & 0x3) * 0x55555555U;

  tmp = (tmp & 0xF) * 0x11111111U;
  GPIOx->AFRL = tmp;
  GPIOx->AFRH = tmp;
}

相应的调用方式如下:

GPIO_Init(GPIOB, GPIO_Pin_8|GPIO_Pin_9, GPIO_MODE_IN | GPIO_PUPD_UP|GPIO_SPEED_LOW);

一条简单的函数调用代码,就可以完成对同一组IO的多个相同功能IO的同时配置,代码不可谓不经典和优雅,高效易读易懂。
但是很多人在阅读GPIO_Init函数的源代码的时候,对里面的一些运算操作确感觉到懵懵懂懂,理解不了,搞不明白。下面我们结合该硬件的特征讲解和说明一下(注意:不同的MCU的硬件寄存器会有不同的操作方式,需要紧密结合你使用的MCU的硬件寄存器的特征来理解),可以起到举一反三和触类旁通的作用:
要完成这个“一步到位”的操作,首先要对相关的宏进行合理的有技巧的定义,看看相关IO操作的宏定义如下:

/** @defgroup GPIO_MODE_define
  * @{
  */
#define GPIO_MODE_IN          0x00000000U     /*!< Input mode */
#define GPIO_MODE_OUT         0x10000000U     /*!< Output mode */
#define GPIO_MODE_AF          0x20000000U     /*!< Alternate function mode */
#define GPIO_MODE_ANA         0x30000000U     /*!< Analog mode */
/**
  * @}
  */
  
  /** @defgroup GPIO_OTYPE_define
  * @{
  */
#define GPIO_OTYPE_PP         0x00000000U     /*!< Output push-pull */
#define GPIO_OTYPE_OD         0x01000000U     /*!< Output open-drain */
/**
  * @}
  */
  /** @defgroup GPIO_SPEED_define
  * @{
  */
#define GPIO_SPEED_LOW        0x00100000U     /*!< Low speed */
#define GPIO_SPEED_HIGH       0x00000000U     /*!< High speed */
/**
  * @}
  */
  /** @defgroup GPIO_PUPD_define
  * @{
  */
#define GPIO_PUPD_NOPULL      0x00000000U     /*!< No pull resistor */
#define GPIO_PUPD_UP          0x00010000U     /*!< Pull up resistor enabled */
#define GPIO_PUPD_DOWN        0x00020000U     /*!< Pull down resistor enabled */
/**
  * @}
  */
 /** @defgroup GPIO_Pin_sources 
  * @{
  */
#define GPIO_PinSource0            ((uint8_t)0x00)
#define GPIO_PinSource1            ((uint8_t)0x01)
#define GPIO_PinSource2            ((uint8_t)0x02)
#define GPIO_PinSource3            ((uint8_t)0x03)
#define GPIO_PinSource4            ((uint8_t)0x04)
#define GPIO_PinSource5            ((uint8_t)0x05)
#define GPIO_PinSource6            ((uint8_t)0x06)
#define GPIO_PinSource7            ((uint8_t)0x07)
#define GPIO_PinSource8            ((uint8_t)0x08)
#define GPIO_PinSource9            ((uint8_t)0x09)
#define GPIO_PinSource10           ((uint8_t)0x0A)
#define GPIO_PinSource11           ((uint8_t)0x0B)
#define GPIO_PinSource12           ((uint8_t)0x0C)
#define GPIO_PinSource13           ((uint8_t)0x0D)
#define GPIO_PinSource14           ((uint8_t)0x0E)
#define GPIO_PinSource15           ((uint8_t)0x0F)
/**
  * @}
  */

宏里面分别定义了GPIO的pin引脚序号(GPIO_PinSource0-15),输入输入模式配置(输入,输出,特殊功能,模拟),输出类型(推挽,开漏),IO速度(高速,低速),输入电阻配置(无上下拉,上拉,下拉)。他们的定义bit位置是经过精心的安排和计算的(比如不同的功能定义占用的bit位置不重叠,方便进行移位运算,和对应的寄存器的操作有一一的对应关系),以便于后续代码设计和简化代码的操作。好了,准备好这些原材料后,我们具体看看代码的实现过程:

  GPIOx->CFGMSK = ~GPIO_Pin;

这第一行代码,非常关键,要明白它的作用,要对应的查看mcu的规格书,我们发现该mcu有一个操作gpio配置寄存器的特殊功能,其说明如下:
在这里插入图片描述
以上说明再翻译一下:这一行代码的作用,就是允许后续写其他IO配置寄存器的时候,只对本次要配置的gpio的对应bit进行写操作,不影响无需配置的其他bit(后面代码解释再说明)。
对应的寄存器定义如下:
在这里插入图片描述好了,接下来看看第二行代码的作用和操作技巧:

  GPIOx->MODER = ((tmp >> 28) & 0x3) * 0x55555555U;

在这里插入图片描述
在这里插入图片描述

该代码操作的对象是端口模式寄存器,对应的寄存器功能如上图(用一个32bit的寄存器来表示16个io的模式配置,每一个io的模式配置位占2bit,并且按照顺序排列).

#define GPIO_MODE_IN          0x00000000U     /*!< Input mode */
#define GPIO_MODE_OUT         0x10000000U     /*!< Output mode */
#define GPIO_MODE_AF          0x20000000U     /*!< Alternate function mode */
#define GPIO_MODE_ANA         0x30000000U     /*!< Analog mode */

结合前面模式定义的宏来理解:
(tmp >> 28) & 0x3-— 该代码的作用就是取到传入的模式配置数据(模式配置定义在最高4个bit,所以先右移位28bit,然后与3,取出来其值),其结果可能的数据为:0,1,2,3,刚好和寄存器的2个bit的4种组合对应:
00 --对应输入模式
01–对应输出模式
10–复用功能模式
11–模拟模式
比较让人疑惑或者难以理解就是后续这个乘以0x55555555U,要理解这个作用,我们把代码换一种写法来看看:

GPIOx->MODER = 0x55555555U  * ((tmp >> 28) & 0x3);

0x55555555U用二进制来看看是什么样子:0101 0101 0101 0101 0101 0101 0101 0101
我们结合寄存器的每两个bit表示一个gpio的模式配置来看看,也就是对应于每一个gpio的配置位初始值为01,如果把这个01和前面的模式值(((tmp >> 28) & 0x3))进行运算,我们发现得到如下结果:
0101 0101 0101 0101 0101 0101 0101 0101: 和0相乘,结果为0000 0000 0000 0000 0000 0000 0000 0000 ,所有GPIO的配置bit为输入模式(00)
0101 0101 0101 0101 0101 0101 0101 0101: 和1相乘,结果不变,所有GPIO的配置bit为输出模式(01)
0101 0101 0101 0101 0101 0101 0101 0101: 和2相乘,相当于左移1位,结果为:1010 1010 1010 1010 1010 1010 1010 1010 所有GPIO的配置bit为复用功能模式(10)
0101 0101 0101 0101 0101 0101 0101 0101: 和3相乘,结果为:1111 1111 1111 1111 1111 1111 1111 1111 ,所有GPIO的配置bit为模拟模式(11)
这真是一个非常高效和简洁,优雅的设计技巧(包括硬件和软件)。
关键点来了,这个值的修改是对所有16个gpio进行同时操作的,如果我只是设置某一个gpio,会不会影响到其他gpio的配置呢?答案是肯定不会。
回到前面我们看看有一个关键寄存器GPIOx->CFGMSK,英文全称应该是config mask,中文翻译为配置辅助寄存器,直译为配置屏蔽寄存器可能更容易理解一些。
GPIOx->CFGMSK = ~GPIO_Pin;通过前面这一条代码的操作,屏蔽了不需要操作的gpio配置位(也就是说关闭了对无关gpio的bit写的作用),比如你本次只是操作gpio0,这条代码就会把对gpio1-15的操作屏蔽,以后写其他配置寄存器(比如前面的MODER寄存器),就只有gpio0对应的bit起作用,其他bit不会影响原来的值。
接下来的其他几条语句的作用类似,参考规格书就可以分析和看明白。
这再一次说明了一个道理,嵌入式开发,软件和硬件要充分结合,才能设计高效的代码。
根据硬件特征,设计合理和简洁的操作代码,也算是一种算法–一种针对硬件进行操作优化的算法。

文章为原创,欢迎转载,请注明出处

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值