通过数组和枚举简化GPIO操作编码

32 篇文章 10 订阅
26 篇文章 12 订阅


        在工作中,经常遇到大量使用GPIO作为数字量输入输出来控制设备或采集状态,每次定义操作不同的GPIO针脚既麻烦又容易出错,于是就想要简化操作过程。对于数字量输入来说就是采集对应针脚的状态;而输出则是根据逻辑关系置位或复位对应得针脚。

        为了使用方便,我们按可复用和经常变化的部分叫软件的实现划分为2个部分。相对固定的部分我们封装成操作函数供调用,对于经常变化的部分(如硬件配置等)我们另外实现,并调用前面封装的函数实现功能。

        现在我们只要实现了通用性较好的函数封装,剩下就是调用来实现具体控制的问题。那么怎么封装这些函数呢?

        我们首先定义两个枚举类型分别定义如下:

//定义数字量输出通道枚举类型,规定通道的范围
typedef enum {
 DOChannel1,
 DOChannel2,
 DOChannel3,
 DOChannel4,
 DOChannel5,
 DOChannelNum
} DigitalOutput;

//定义数字量输入通道枚举类型,规定通道的范围
typedef enum {
 DIChannel1,
 DIChannel2,
 DIChannel3,
 DIChannel4,
 DIChannel5,
 DIChannelNum
} DigitalInput;

        数字量输入输出的枚举主要是为了方便操作和识别,通道数量出现变化时只需要增加枚举两种的通道定义即可。此处数字量输入输出均定义了5个通道。枚举量的最后一个成员代表了通道的数量,在枚举全部通道时能够很好的避免超出范围的错误。

        同时还要定义如下的结构体,用于定义需要操作GPIO目标。

//定义用于针脚操作的目标针脚类型
typedef struct{
  GPIO_TypeDef* GPIOx;
  uint16_tGPIO_Pin;
}TargetPin;

        有了上述的定义则可以实现前面设想的操作了,接下来我们还需要定义两个数字量输入输出通道的TargetPin类型的数组,用于存放想要操作的目标通道,和前面枚举两种定义的通道一致,此处也是5个通道。

//定义DI通道的全部目标针脚数组
TargetPindiPin[]={{GPIOE,GPIO_Pin_2},{GPIOE,GPIO_Pin_3},{GPIOE,GPIO_Pin_4}
                 ,{GPIOE,GPIO_Pin_5},{GPIOE,GPIO_Pin_6}};

//定义DO通道的全部目标针脚数组
TargetPindoPin[]={{GPIOD,GPIO_Pin_3},{GPIOD,GPIO_Pin_4},{GPIOD,GPIO_Pin_5}
                 ,{GPIOD,GPIO_Pin_6},{GPIOD,GPIO_Pin_7}};

        有了以上2个数组就可以在避免在操作过程中大量使用条件分支语句(Switch或if语句),简化编码和避免在增加通道时号要修改函数的情况。现在如果通道数量出现变化则只需要修改枚举量和数组的值就可。或者操作的管脚出现变化则只需要修改数组的值就可以了。而不需要去修改函数体,而且函数体的编码也非常简单。

        对数字量输出的操作如下,在操作全部通道时,以枚举变量作为循环变量,以枚举的最后定义的数量来控制,并以枚举量的取值作为数组下标,有效避免出现超出范围的错误,同时在通道数量和通道对应的具体针脚发生变化时,无需修改函数。

//操作全部继电器DO通道
//输入参数TargetPin *doPin为要操作的DO通道列表
//输入参数BOOL *commands欲写给DO通道的值列表
void OperationAllRelayChannel(TargetPin*doPin,BOOL *commands)
{
  DigitalOutputDOChannel;
 for(DOChannel=DOChannel1;DOChannel<DOChannelNum;DOChannel++)
  {
   OperationSingleRelayChannel(doPin[DOChannel],commands[DOChannel]);
  }
}

//操作单个继电器DO通道
//输入参数TargetPin doPin为要操作的DO通道
//输入参数BOOL command欲写给DO通道的值
void OperationSingleRelayChannel(TargetPindoPin,BOOL command)
{
  if(command==True)
  {
   GPIO_SetBits(doPin.GPIOx,doPin.GPIO_Pin);
  }
  else
  {
   GPIO_ResetBits(doPin.GPIOx,doPin.GPIO_Pin);
  }
}

        对数字量输入的操作函数的编写采用与数字量输出相同的思路。对于枚举之所以可以用作数组下标,是因为枚举没被指定值时,总是从0开始向上累加,正好与数组下标是一致的。这要做还有一个好处是,通道与具体的GPIO引脚是由TargetPin数组的赋值顺序决定的,修改非常方便。

//获取全部DI量状态输入值
//输入参数TargetPin *diPin为需要读取的DI通道列表
//输入参数BOOL *result为读取的通道值返回列表
void GetAllDIStatusInput(TargetPin *diPin,BOOL*result)
{
  DigitalInputDIChannel;
 for(DIChannel=DIChannel1;DIChannel<DIChannelNum;DIChannel++)
  {
   result[DIChannel]=GetSingleDIStatusInput(diPin[DIChannel]);
  }
}

//获取单个DI量状态输入值
//输入参数TargetPin diPin是需要读取的DI通道
//返回值为读取的通道值
BOOL GetSingleDIStatusInput(TargetPin diPin)
{
  uint8_treadValue;
  readValue= GPIO_ReadInputDataBit(diPin.GPIOx,diPin.GPIO_Pin);
  return(readValue>0)?True:False;
}

        通过以上的编码操作DI、DO已经很方便了,但在操作单个DO通道的函数中还有一个if…else语句给人的感觉比较不太好。因为操作简单就是置位和复位,所以我们定义一个指向函数的指针数组,如下:

/*定义操作GPIO管脚的函数指针*/
void (*OperationGPIOBits[])(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin)={GPIO_ResetBits,GPIO_SetBits};

        有了这个指向函数的指针数组我们可以将上面的操作单个DO通道的函数简化为如下:

//操作单个继电器DO通道
//输入参数TargetPin doPin为要操作的DO通道
//输入参数BOOL command欲写给DO通道的值
void OperationSingleRelayChannel(TargetPindoPin,BOOL command)
{
 OperationGPIOBits[command](doPin.GPIOx,doPin.GPIO_Pin);
}

        其中command是一个布尔变量取值为0和1,正好与指向函数的指针数组对应,实现在command取不同值时,调用复位或置位函数。

        以上代码在IAR EWARM和STM32F103VET平台测试正确。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Linux中,可以通过c语言的编程来操作GPIO,具体方法如下: 1. 包含相应的头文件 在程序开头,需要包含相应的头文件,如下所示: ```c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <sys/mman.h> #include <stdint.h> ``` 2. 定义GPIO相关的宏 需要定义GPIO相关的宏,如下所示: ```c #define GPIO_BASE_ADDR 0x20200000 #define GPIO_BLOCK_SIZE 4096 #define GPIO_IN 0 #define GPIO_OUT 1 #define GPIO_HIGH 1 #define GPIO_LOW 0 ``` 其中,GPIO_BASE_ADDR是GPIO的基地址,GPIO_BLOCK_SIZE是GPIO区域的大小,GPIO_IN和GPIO_OUT分别表示GPIO的输入输出模式,GPIO_HIGH和GPIO_LOW分别表示GPIO的高低电平。 3. 打开/dev/mem文件 需要打开/dev/mem文件,以便对GPIO进行内存映射,如下所示: ```c int mem_fd; void *gpio_map; volatile uint32_t *gpio; if ((mem_fd = open("/dev/mem", O_RDWR | O_SYNC)) < 0) { printf("Error: can't open /dev/mem \n"); exit(-1); } gpio_map = mmap( NULL, // Any address in our space will do GPIO_BLOCK_SIZE, // Map length PROT_READ | PROT_WRITE, // Enable reading & writting to mapped memory MAP_SHARED, // Shared with other processes mem_fd, // File to map GPIO_BASE_ADDR // Offset to GPIO peripheral ); close(mem_fd); if (gpio_map == MAP_FAILED) { printf("Error: mmap failed\n"); exit(-1); } gpio = (volatile uint32_t *)gpio_map; ``` 4. 设置GPIO的输入输出模式 需要设置GPIO的输入输出模式,如下所示: ```c int pin_num = 17; // 设置GPIO的引脚号 int pin_mode = GPIO_OUT; // 设置GPIO的输出模式 // 设置GPIO的输入输出模式 *(gpio + ((pin_num) / 10)) &= ~(7 << (((pin_num) % 10) * 3)); *(gpio + ((pin_num) / 10)) |= (pin_mode << (((pin_num) % 10) * 3)); ``` 其中,pin_num表示GPIO的引脚号,pin_mode表示GPIO的输入输出模式。 5. 控制GPIO的高低电平 需要控制GPIO的高低电平,如下所示: ```c int pin_num = 17; // 设置GPIO的引脚号 int pin_value = GPIO_HIGH; // 设置GPIO的电平值 // 控制GPIO的高低电平 if (pin_value == GPIO_HIGH) { *(gpio + 7) = 1 << pin_num; } else { *(gpio + 10) = 1 << pin_num; } ``` 其中,pin_num表示GPIO的引脚号,pin_value表示GPIO的电平值,GPIO_HIGH表示高电平,GPIO_LOW表示低电平。 6. 关闭/dev/mem文件 需要在程序结束时,关闭/dev/mem文件,如下所示: ```c munmap(gpio_map, GPIO_BLOCK_SIZE); ``` 以上就是在Linux中通过c语言操作GPIO的方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值