如何共享硬件抽象结构而不使它成为全局的
这是一个关于共享“全局”数据的问题,它模仿任何函数都可以访问的可寻址内存。如何共享硬件抽象结构而不使它成为全局的
我正在为一个嵌入式项目编写代码,我已经从应用程序中分离出物理gpio引脚。应用程序与“虚拟”gpio端口进行通信,然后设备驱动程序与实际硬件进行通信。这样做的主要动机是让我可以轻松地在开发时将哪些引脚连接到外设,以及包括使用较少物理引脚的按钮矩阵等东西,同时仍将它们作为常规gpio设备寄存器进行处理。
typedef struct GPIO_PinPortPair
{
GPIO_TypeDef *port; /* STM32 GPIO Port */
uint16_t pin; /* Pin number */
} GPIO_PinPortPair;
typedef struct GPIO_VirtualPort
{
uint16_t reg; /* Virtual device register */
uint16_t edg; /* Flags to signal edge detection */
GPIO_PinPortPair *grp; /* List of physical pins associated with vport */
int num_pins; /* Number of pins in vport */
} GPIO_VirtualPort;
这在我到目前为止已经编写的代码一直运作良好,但问题是,我觉得我必须分享地址到每个定义的虚拟端口作为一个全球性的。函数调用看起来像这样,模仿如果我使用常规内存映射io的样子。
file1.c中
GPIO_VirtualPort LEDPort;
/* LEDPort init code that associates it with a list of physical pins */
file2.c中
extern GPIO_VirtualPort LEDPort;
vgpio_write_pin(&LEDPort, PIN_1, SET_PIN);
我已经搜查两个SO和最佳做法的互联网,当涉及到共享变量,我觉得就像我明白为什么我应该避免使用全局变量(无法确定代码在数据中发生什么),并且最好使用带指针和接口函数的局部变量(比如“get当前滴答“功能而不是读取全局滴答变量)。
我的问题是,鉴于我希望保持语法尽可能简单,定义这些结构变量然后使其可用于其他模块中的函数的最佳方法是什么?可以将这些结构变量用作全局变量吗?我应该使用某种指向每个虚拟端口的指针的主数组,并使用getter函数来避免使用extern变量?
我喜欢做这种方式:
file1.h
typedef enum
{
VirtualPortTypeLED
} VirtualPortType;
typedef struct GPIO_PinPortPair
{
GPIO_TypeDef *port; /* STM32 GPIO Port */
uint16_t pin; /* Pin number */
} GPIO_PinPortPair;
typedef struct GPIO_VirtualPort
{
uint16_t reg; /* Virtual device register */
uint16_t edg; /* Flags to signal edge detection */
GPIO_PinPortPair *grp; /* List of physical pins associated with vport */
int num_pins; /* Number of pins in vport */
} GPIO_VirtualPort;
file1.c中
GPIO_VirtualPort LEDPort;
void VirtualPortInit()
{
/* fill in all structures and members here */
LEDPort.reg = 0x1234;
...
}
GPIO_VirtualPort *VirtualPortGet(VirtualPortType vpt)
{
switch(vpt) {
case VirtualPortTypeLED:
return &LEDPort;
}
return NULL;
}
file2.c中
#include file1.h
GPIO_VirtualPort *myLed;
VirtualPortInit();
myLed = VirtualPortGet(VirtualPortTypeLED);
顺便说一句,我没编译这个...... :)
虽然我明白了意图!这正是我正在寻找的。我特别喜欢这个枚举,它让我可以写出我想要抓取的句柄! –
太棒了,很高兴我可以帮助:) – staringlizard
为了在不使用引用给定硬件或全局地址集的全局结构的情况下执行此操作,可以在开始时在所需的位置创建GPIO结构的句柄。
我不确定STM32是如何布局的,因为我没有使用该系列设备的经验,但是我已经在您描述的情况下看到并使用了这种方法。
如果您的硬件位于内存中的某个特定地址,例如:0x50
,那么您的调用代码会要求GPIO_Init()
对该位置的内存进行处理。这仍然可以让你的结构在不同的位置分配,如果你需要,例如:
/* gpio.h */
#include <stdef.h>
#include <stdint.h>
#include <bool.h>
typedef struct GPIO_Port GPIO_Port; // forward declare the struct definition
GPIO_Port *GPIO_Init(void *memory, const size_t size);
GPIO_Write_Pin(GPIO_Port *port_handle, uint8_t pin number, bool state);
一个简单实现GPIO_Init()
功能可能是:
/* gpio.c */
#include "gpio.h"
struct GPIO_Port // the memory mapped struct definition
{
uint16_t first_register;
uint16_t second_register;
// etc, ordered to match memory layout of GPIO registers
};
GPIO_Port *GPIO_Init(void *memory, const size_t size)
{
// if you don't feel the need to check this then the
// second function parameter probably won't be necessary
if (size < sizeof(GPIO_Port *))
return (GPIO_Port *)NULL;
// here you could perform additional operations, e.g.
// clear the memory to all 0, depending on your needs
// return the handle to the memory the caller provided
return (GPIO_Port *)memory;
}
GPIO_Write_Pin(GPIO_Port *port_handle, uint8_t pin number, bool state)
{
uint16_t mask = 1u << pin_number;
if (state == true)
port_handle->pin_register |= mask; // set bit
else
port_handle->pin_register &= ~mask; // clear bit
}
凡结构本身是定义只在源文件中,并且没有单个全局实例。然后你可以使用此类似:
// this can be defined anywhere, or for eg, returned from malloc(),
// as long as it can be passed to the init function
#define GPIO_PORT_START_ADDR (0x50)
// get a handle at whatever address you like
GPIO_Port *myporthandle = GPIO_init(GPIO_PORT_START_ADDR, sizeof(*myporthandle));
// use the handle
GPIO_Write_Pin(myporthandle, PIN_1, SET_HIGH);
对于初始化功能,您可以在真正的硬件位置的存储器的地址通过GPIO寄存器,或者你可以分配的RAM一些新的模块,并通过地址那个。
您使用的内存地址不一定是全局的,它们只是从调用代码传递到GPIO_Init()
,因此最终可能来自任何地方,对象句柄通过传递接管任何后续引用到该块内存到后续的GPIO功能调用。你应该能够围绕这个想法来构建你的更复杂的功能,即传递更改的信息和抽象的映射内存,这样你仍然可以允许你提到的“虚拟”端口的功能。
此方法的优点是分离问题(您的GPIO单元只关心GPIO,而不是内存,其他的东西可以处理),封装(只有GPIO源需要关注自己的GPIO成员端口结构)并且没有/几个全局变量(该句柄可以被实例化并根据需要传递)。
我个人觉得这个模式在单元测试中非常方便。在发行版中,我传递了真实硬件的地址,但在测试中,我传递了一个结构中某个结构的地址,并测试了GPIO单元所期望的成员变化 - 不涉及硬件。
我明白,如果文本不清楚。 LEDPort应该被暗示为在另一个源文件中定义。这不是任何实际的生产代码,我想保持较短的篇幅,因为它主要是一个概念性的问题。 –
如果你想定义一个'struct'在多个C源代码中可用,只需将它放入一个头文件中,并将头文件包含在每个C源文件或其他所需的头文件中。由于标题可能被多次包含(间接),因此您应该使用[包括卫兵](https://en.wikipedia.org/wiki/Include_guard)。 (有些人更喜欢'#pragma once',但我一般不会喜欢'#pragma'的粉丝。) – Scheff
这是不是使它成为全局变量呢?如果是这种情况,我想知道这是否是一种很好的做法,或者如果我由于初学者的错误而冒着代码味道。 –