如何在不使其成为全局的情况下共享硬件抽象结构
How to share a hardware abstraction struct without making it global
这是一个关于共享数据的问题 "global",模仿任何函数都可以访问的一块可寻址内存。
我正在为一个嵌入式项目编写代码,在该项目中我已将我的物理 gpio 引脚与应用程序分离。应用程序与 "virtual" 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 current tick" 函数而不是读取全局 tick 变量)。
我的问题是,考虑到我希望保持语法尽可能简单,定义这些结构变量然后使它们可供其他模块中的函数使用的最佳方法是什么?可以将这些结构变量用作全局变量吗?我应该使用某种指向我拥有的每个虚拟端口的主指针数组并使用 getter 函数来避免使用外部变量吗?
要在不使用引用给定硬件集或全局地址集的全局结构的情况下执行此操作,您可以在开始时在所需位置创建 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 函数调用来获取内存块。您应该能够围绕传递更改信息和抽象映射内存的想法构建更复杂的功能,这样您仍然可以使用 "virtual" 端口提到的功能。
这种方法的好处是关注点分离(你的 GPIO 单元只关注 GPIO,不关注内存,其他东西可以处理),封装(只有 GPIO 源需要关注自身的成员) GPIO 端口结构)和 no/few 全局变量(句柄可以根据需要实例化和传递)。
就我个人而言,我发现这种模式在单元测试方面非常方便。在发布中,我传递了真实硬件的地址,但在测试中,我传递了内存中某处结构的地址,并测试成员是否按照 GPIO 单元的预期进行了更改——不涉及硬件。
我喜欢这样做:
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);
顺便说一句,我没有编译这个...:)
这是一个关于共享数据的问题 "global",模仿任何函数都可以访问的一块可寻址内存。
我正在为一个嵌入式项目编写代码,在该项目中我已将我的物理 gpio 引脚与应用程序分离。应用程序与 "virtual" 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 current tick" 函数而不是读取全局 tick 变量)。
我的问题是,考虑到我希望保持语法尽可能简单,定义这些结构变量然后使它们可供其他模块中的函数使用的最佳方法是什么?可以将这些结构变量用作全局变量吗?我应该使用某种指向我拥有的每个虚拟端口的主指针数组并使用 getter 函数来避免使用外部变量吗?
要在不使用引用给定硬件集或全局地址集的全局结构的情况下执行此操作,您可以在开始时在所需位置创建 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 函数调用来获取内存块。您应该能够围绕传递更改信息和抽象映射内存的想法构建更复杂的功能,这样您仍然可以使用 "virtual" 端口提到的功能。
这种方法的好处是关注点分离(你的 GPIO 单元只关注 GPIO,不关注内存,其他东西可以处理),封装(只有 GPIO 源需要关注自身的成员) GPIO 端口结构)和 no/few 全局变量(句柄可以根据需要实例化和传递)。
就我个人而言,我发现这种模式在单元测试方面非常方便。在发布中,我传递了真实硬件的地址,但在测试中,我传递了内存中某处结构的地址,并测试成员是否按照 GPIO 单元的预期进行了更改——不涉及硬件。
我喜欢这样做:
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);
顺便说一句,我没有编译这个...:)