查看: 2805|回复: 2

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

[复制链接]
  • TA的每日心情
    慵懒
    2014-11-28 09:29
  • 签到天数: 3 天

    连续签到: 1 天

    [LV.2]偶尔看看I

    发表于 2017-8-3 15:53:16 | 显示全部楼层 |阅读模式
    分享到:
    在工作中,经常遇到大量使用GPIO作为数字量输入输出来控制设备或采集状态,每次定义操作不同的GPIO针脚既麻烦又容易出错,于是就想要简化操作过程。对于数字量输入来说就是采集对应针脚的状态;而输出则是根据逻辑关系置位或复位对应得针脚。
    为了使用方便,我们按可复用和经常变化的部分叫软件的实现划分为2个部分。相对固定的部分我们封装成操作函数供调用,对于经常变化的部分(如硬件配置等)我们另外实现,并调用前面封装的函数实现功能。
          现在我们只要实现了通用性较好的函数封装,剩下就是调用来实现具体控制的问题。那么怎么封装这些函数呢?
          我们首先定义两个枚举类型分别定义如下:
    1. //定义数字量输出通道枚举类型,规定通道的范围
    2. typedef enum {
    3.   DOChannel1,
    4.   DOChannel2,
    5.   DOChannel3,
    6.   DOChannel4,
    7.   DOChannel5,
    8.   DOChannelNum
    9. } DigitalOutput;

    10. //定义数字量输入通道枚举类型,规定通道的范围
    11. typedef enum {
    12.   DIChannel1,
    13.   DIChannel2,
    14.   DIChannel3,
    15.   DIChannel4,
    16.   DIChannel5,
    17.   DIChannelNum
    18. } DigitalInput;
    复制代码
    数字量输入输出的枚举主要是为了方便操作和识别,通道数量出现变化时只需要增加枚举两种的通道定义即可。此处数字量输入输出均定义了5个通道。枚举量的最后一个成员代表了通道的数量,在枚举全部通道时能够很好的避免超出范围的错误。
          同时还要定义如下的结构体,用于定义需要操作GPIO目标。
    1. //定义用于针脚操作的目标针脚类型
    2. typedef struct{
    3.   GPIO_TypeDef* GPIOx;
    4.   uint16_t GPIO_Pin;
    5. }TargetPin;
    复制代码
         有了上述的定义则可以实现前面设想的操作了,接下来我们还需要定义两个数字量输入输出通道的TargetPin类型的数组,用于存放想要操作的目标通道,和前面枚举两种定义的通道一致,此处也是5个通道。
    1. //定义DI通道的全部目标针脚数组
    2. TargetPin diPin[]={{GPIOE,GPIO_Pin_2},{GPIOE,GPIO_Pin_3},{GPIOE,GPIO_Pin_4}
    3.                   ,{GPIOE,GPIO_Pin_5},{GPIOE,GPIO_Pin_6}};

    4. //定义DO通道的全部目标针脚数组
    5. TargetPin doPin[]={{GPIOD,GPIO_Pin_3},{GPIOD,GPIO_Pin_4},{GPIOD,GPIO_Pin_5}
    6.                   ,{GPIOD,GPIO_Pin_6},{GPIOD,GPIO_Pin_7}};
    复制代码
      有了以上2个数组就可以在避免在操作过程中大量使用条件分支语句(Switch或if语句),简化编码和避免在增加通道时号要修改函数的情况。现在如果通道数量出现变化则只需要修改枚举量和数组的值就可。或者操作的管脚出现变化则只需要修改数组的值就可以了。而不需要去修改函数体,而且函数体的编码也非常简单。
          对数字量输出的操作如下,在操作全部通道时,以枚举变量作为循环变量,以枚举的最后定义的数量来控制,并以枚举量的取值作为数组下标,有效避免出现超出范围的错误,同时在通道数量和通道对应的具体针脚发生变化时,无需修改函数。
    1. //操作全部继电器DO通道
    2. //输入参数TargetPin *doPin为要操作的DO通道列表
    3. //输入参数BOOL *commands欲写给DO通道的值列表
    4. void OperationAllRelayChannel(TargetPin *doPin,BOOL *commands)
    5. {
    6.   DigitalOutput DOChannel;
    7.   for(DOChannel=DOChannel1;DOChannel<DOChannelNum;DOChannel++)
    8.   {
    9.     OperationSingleRelayChannel(doPin[DOChannel],commands[DOChannel]);
    10.   }
    11. }

    12. //操作单个继电器DO通道
    13. //输入参数TargetPin doPin为要操作的DO通道
    14. //输入参数BOOL command欲写给DO通道的值
    15. void OperationSingleRelayChannel(TargetPin doPin,BOOL command)
    16. {
    17.   if(command==True)
    18.   {
    19.     GPIO_SetBits(doPin.GPIOx,doPin.GPIO_Pin);
    20.   }
    21.   else
    22.   {
    23.     GPIO_ResetBits(doPin.GPIOx,doPin.GPIO_Pin);
    24.   }
    25. }
    复制代码
    对数字量输入的操作函数的编写采用与数字量输出相同的思路。对于枚举之所以可以用作数组下标,是因为枚举没被指定值时,总是从0开始向上累加,正好与数组下标是一致的。这要做还有一个好处是,通道与具体的GPIO引脚是由TargetPin数组的赋值顺序决定的,修改非常方便。
    1. //获取全部DI量状态输入值
    2. //输入参数TargetPin *diPin为需要读取的DI通道列表
    3. //输入参数BOOL *result为读取的通道值返回列表
    4. void GetAllDIStatusInput(TargetPin *diPin,BOOL *result)
    5. {
    6.   DigitalInput DIChannel;
    7.   for(DIChannel=DIChannel1;DIChannel<DIChannelNum;DIChannel++)
    8.   {
    9.     result[DIChannel]=GetSingleDIStatusInput(diPin[DIChannel]);
    10.   }
    11. }

    12. //获取单个DI量状态输入值
    13. //输入参数TargetPin diPin是需要读取的DI通道
    14. //返回值为读取的通道值
    15. BOOL GetSingleDIStatusInput(TargetPin diPin)
    16. {
    17.   uint8_t readValue;
    18.   readValue = GPIO_ReadInputDataBit(diPin.GPIOx,diPin.GPIO_Pin);
    19.   return (readValue>0)?True:False;
    20. }
    复制代码
    通过以上的编码操作DI、DO已经很方便了,但在操作单个DO通道的函数中还有一个if…else语句给人的感觉比较不太好。因为操作简单就是置位和复位,所以我们定义一个指向函数的指针数组,如下:
    1. /*定义操作GPIO管脚的函数指针*/
    2. void (*OperationGPIOBits[])(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)={GPIO_ResetBits,GPIO_SetBits};
    复制代码
          有了这个指向函数的指针数组我们可以将上面的操作单个DO通道的函数简化为如下:
    1. //操作单个继电器DO通道
    2. //输入参数TargetPin doPin为要操作的DO通道
    3. //输入参数BOOL command欲写给DO通道的值
    4. void OperationSingleRelayChannel(TargetPin doPin,BOOL command)
    5. {
    6.   OperationGPIOBits[command](doPin.GPIOx,doPin.GPIO_Pin);
    7. }
    复制代码
    其中command是一个布尔变量取值为0和1,正好与指向函数的指针数组对应,实现在command取不同值时,调用复位或置位函数。
          以上代码在IAR EWARM和STM32F103VET平台测试正确。


    回复

    使用道具 举报

  • TA的每日心情
    开心
    2019-10-11 13:43
  • 签到天数: 147 天

    连续签到: 1 天

    [LV.7]常住居民III

    发表于 2017-8-8 10:03:15 | 显示全部楼层
    厉害,受教了,这么写真的简洁
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    2019-10-11 13:43
  • 签到天数: 147 天

    连续签到: 1 天

    [LV.7]常住居民III

    发表于 2017-8-8 10:04:04 | 显示全部楼层
    佩服佩服~ C语言功底真扎实,我咋没想到呢,还是我思维逻辑不行啊
    回复 支持 反对

    使用道具 举报

    您需要登录后才可以回帖 注册/登录

    本版积分规则

    关闭

    站长推荐上一条 /1 下一条



    手机版|小黑屋|与非网

    GMT+8, 2025-1-27 08:51 , Processed in 0.141308 second(s), 21 queries , MemCache On.

    ICP经营许可证 苏B2-20140176  苏ICP备14012660号-2   苏州灵动帧格网络科技有限公司 版权所有.

    苏公网安备 32059002001037号

    Powered by Discuz! X3.4

    Copyright © 2001-2024, Tencent Cloud.