这是一个关于共享" global"的数据的问题,模仿任何函数可以访问的可寻址内存。
我正在为嵌入式项目编写代码,我将物理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;
这在我迄今为止编写的代码中运行良好,但问题是我觉得我必须将地址分享给每个定义的虚拟端口作为全局。函数调用看起来像这样,模仿我使用常规内存映射的方式。
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和互联网上搜索了共享变量的最佳实践,我觉得我理解为什么我应该避免全局变量(无法确定代码在数据中出现的位置)并且最好使用带有指针和接口函数的局部变量(比如"得到当前刻度"函数而不是读取全局刻度变量)。
我的问题是,鉴于我希望保持语法尽可能简单,定义这些结构变量然后使其可供其他模块中的函数使用的最佳方法是什么?是否可以将这些结构变量用作全局变量?我应该使用某种指针的主数组到我拥有的每个虚拟端口并使用getter函数来避免使用extern变量吗?
答案 0 :(得分:0)
要在不使用引用给定硬件集或全局地址集的全局结构的情况下执行此操作,请在启动时在所需位置创建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);
对于init函数,您可以使用GPIO寄存器的实际硬件位置传入存储器的地址,或者您可以分配一些新的RAM块并传递该地址。
您使用的内存的地址不必是全局的,它们只是从调用代码传递给GPIO_Init()
,因此最终可能来自任何地方,对象句柄接管任何后续引用到该块的内存通过传递给后续的GPIO函数调用。您应该能够围绕传递更改的信息和抽象的映射内存来构建更复杂的函数,这样您仍然可以使用“虚拟”端口提供功能。
这种方法具有分离关注点的好处(您的GPIO单元只关注GPIO,而不是内存,其他东西可以处理),封装(只有GPIO源需要关注GPIO端口的成员) struct)和没有/几个全局变量(句柄可以实例化并根据需要传递)。
就个人测试而言,我个人认为这种模式非常方便。在发行版中,我传递了真实硬件的地址,但是在测试中,我在内存中的某个地方传递了一个结构的地址,并测试成员是否按照GPIO单元的预期进行了更改 - 没有涉及硬件。
答案 1 :(得分:0)
我喜欢这样做:
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);
是的,我没有编译这个......:)