在线时间1359 小时
UID3338155
注册时间2017-7-26
NXP金币1449
TA的每日心情 | 奋斗 前天 09:28 |
---|
签到天数: 592 天 [LV.9]以坛为家II
金牌会员
- 积分
- 7033
- 最后登录
- 2024-4-24
|
本帖最后由 andeyqi 于 2023-4-8 12:54 编辑
工作中我们经常会遇到跨平台适配的为问题,经常在代码中看到所谓的适配层的接口用于上层应用和底层驱动的解耦分离的设计,本贴的主题是将GPIO驱动抽象成通用的接口方式统一吸收底层的差异话的东西,对于GPIO的驱动rtthread 代码中的PIN设备驱动,是将GPIO 抽象成id 的方式,用户只需要知道操作的id 数值即可完成对GPIO的操作,本贴借鉴rtthread 的管理方式对LPC845平台的GPIO 进行统一的管理。
1.GPIO接口描述
Rtthread 的BSP 目录下已经支持了很多厂家的芯片,很多已经适配了PIN驱动框架,我们从中选取一个参考,我们选取的是\rt-thread\bsp\gd32\arm\libraries\gd32_drivers\drv_gpio.c 作为参考。
Rtthread PIN 驱动框架将GPIO 接口ops 封装成如下的接口,定义如下:
struct rt_pin_ops
{
void (*pin_mode)(struct rt_device *device, rt_base_t pin, rt_uint8_t mode);
void (*pin_write)(struct rt_device *device, rt_base_t pin, rt_uint8_t value);
rt_int8_t (*pin_read)(struct rt_device *device, rt_base_t pin);
rt_err_t (*pin_attach_irq)(struct rt_device *device, rt_base_t pin,
rt_uint8_t mode, void (*hdr)(void *args), void *args);
rt_err_t (*pin_detach_irq)(struct rt_device *device, rt_base_t pin);
rt_err_t (*pin_irq_enable)(struct rt_device *device, rt_base_t pin, rt_uint8_t enabled);
rt_base_t (*pin_get)(const char *name);
};
1.1 pin_mode ops 接口用于设置gpio 工作模式。
void (*pin_mode)(struct rt_device *device, rt_base_t pin, rt_uint8_t mode);
device:表示PIN 设备句柄,此参数我们可以忽略理论上只要根据 pin 参数即可找到对应操作GPIO的资源。pin:上层操作的引脚编号
mode:引脚工作模式,抽象了一下五种模式,已经可以覆盖到常规的引脚配置模式
#define PIN_MODE_OUTPUT 0x00
#define PIN_MODE_INPUT 0x01
#define PIN_MODE_INPUT_PULLUP 0x02
#define PIN_MODE_INPUT_PULLDOWN 0x03
#define PIN_MODE_OUTPUT_OD 0x04
1.2 pin_write: 设置引脚电平
void (*pin_write)(struct rt_device *device, rt_base_t pin, rt_uint8_t value);
1.2 pin_read: 读取引脚电平
rt_int8_t (*pin_read)(struct rt_device *device, rt_base_t pin);
上面两个接口一个根据pin 引脚设置输出电平,一个是读取pin 引进的电平,其他中断相关配置引脚我们暂不关注,本次实验只要实现上述三个接口即可完成对gpio 输入输出控制。
上述三个接口对应的GD32 下的实现如下:
static void gd32_pin_mode(rt_device_t dev, rt_base_t pin, rt_base_t mode)
{
const struct pin_index *index = RT_NULL;
rt_uint32_t pin_mode = 0;
#if defined SOC_SERIES_GD32F4xx
rt_uint32_t pin_pupd = 0, pin_odpp = 0;
#endif
index = get_pin(pin);
if (index == RT_NULL)
{
return;
}
/* GPIO Periph clock enable */
rcu_periph_clock_enable(index->clk);
#if defined SOC_SERIES_GD32F4xx
pin_mode = GPIO_MODE_OUTPUT;
#else
pin_mode = GPIO_MODE_OUT_PP;
#endif
switch(mode)
{
case PIN_MODE_OUTPUT:
/* output setting */
#if defined SOC_SERIES_GD32F4xx
pin_mode = GPIO_MODE_OUTPUT;
pin_pupd = GPIO_PUPD_NONE;
pin_odpp = GPIO_OTYPE_PP;
#else
pin_mode = GPIO_MODE_OUT_PP;
#endif
break;
case PIN_MODE_OUTPUT_OD:
/* output setting: od. */
#if defined SOC_SERIES_GD32F4xx
pin_mode = GPIO_MODE_OUTPUT;
pin_pupd = GPIO_PUPD_NONE;
pin_odpp = GPIO_OTYPE_OD;
#else
pin_mode = GPIO_MODE_OUT_OD;
#endif
break;
case PIN_MODE_INPUT:
/* input setting: not pull. */
#if defined SOC_SERIES_GD32F4xx
pin_mode = GPIO_MODE_INPUT;
pin_pupd = GPIO_PUPD_PULLUP | GPIO_PUPD_PULLDOWN;
#else
pin_mode = GPIO_MODE_IN_FLOATING;
#endif
break;
case PIN_MODE_INPUT_PULLUP:
/* input setting: pull up. */
#if defined SOC_SERIES_GD32F4xx
pin_mode = GPIO_MODE_INPUT;
pin_pupd = GPIO_PUPD_PULLUP;
#else
pin_mode = GPIO_MODE_IPU;
#endif
break;
case PIN_MODE_INPUT_PULLDOWN:
/* input setting: pull down. */
#if defined SOC_SERIES_GD32F4xx
pin_mode = GPIO_MODE_INPUT;
pin_pupd = GPIO_PUPD_PULLDOWN;
#else
pin_mode = GPIO_MODE_IPD;
#endif
break;
default:
break;
}
#if defined SOC_SERIES_GD32F4xx
gpio_mode_set(index->gpio_periph, pin_mode, pin_pupd, index->pin);
if(pin_mode == GPIO_MODE_OUTPUT)
{
gpio_output_options_set(index->gpio_periph, pin_odpp, GPIO_OSPEED_50MHZ, index->pin);
}
#else
gpio_init(index->gpio_periph, pin_mode, GPIO_OSPEED_50MHZ, index->pin);
#endif
}
上述代码是根据传入的pin 值索引到对应GPIO 资源index ,然后调用GD32的库函数实现对应的功能从而完成应用层和底层的依赖解耦。
struct pin_index
{
rt_int16_t index;
rcu_periph_enum clk;
rt_uint32_t gpio_periph;
rt_uint32_t pin;
rt_uint8_t port_src;
rt_uint8_t pin_src;
};
对应的索引表如下:
static const struct pin_index pins[] =
{
#ifdef GPIOA
GD32_PIN(0, A, 0),
GD32_PIN(1, A, 1),
GD32_PIN(2, A, 2),
GD32_PIN(3, A, 3),
GD32_PIN(4, A, 4),
GD32_PIN(5, A, 5),
GD32_PIN(6, A, 6),
}
GD32_PIN(0, A, 0), 其中的第一个0 对应的用户操作的index, 后面的A0 对应的A0端口,同理gd32_pin_write/gd32_pin_read接口也是根据传入的pin id 索引到对应的资源配置后,根据对应的参数操作GPIO.
2.lpc845 GPIO接口实现
参照上面的封装device参数对我们是不需要的,我们之只需要根据pin 的数值索引到LPC845 驱动库依赖的资源即可,LPC485 主要通过GPIO_PinInit 接口初始化GPIO 输入输出属性,其中base 为固定参数GPIO,config 根据是输入输出设置对应的结构即可,port 和 pin 对应的端口和id,我们根据对应的抽象出来的index 能够索引到该配置即可完后GPIO 输入输出属性的配置,gpio 的读写接口也是类似。
/*!
* brief Initializes a GPIO pin used by the board.
* param base GPIO peripheral base pointer(Typically GPIO)
* param port GPIO port number
* param pin GPIO pin number
* param config GPIO pin configuration pointer
*/
void GPIO_PinInit(GPIO_Type *base, uint32_t port, uint32_t pin, const gpio_pin_config_t *config)
static inline void GPIO_PinWrite(GPIO_Type *base, uint32_t port, uint32_t pin, uint8_t output)
{
base->B[port][pin] = output;
}
static inline uint32_t GPIO_PinRead(GPIO_Type *base, uint32_t port, uint32_t pin)
{
return (uint32_t)base->B[port][pin];
}
GPIO 的输入输出上下拉模式的配置是通过如下接口,base 为固定值,ionumber 为GPIO number to mux 我们只要根据抽象出来的id 能索引到对应配置即可。
/**
* @brief Sets I/O Control pin mux
* @param base : The base of IOCON peripheral on the chip
* @param ionumber : GPIO number to mux
* @param modefunc : OR'ed values of type IOCON_*
* @return Nothing
*/
IOCON_PinMuxSet(IOCON_Type *base, uint8_t ionumber, uint32_t modefunc)
根据上述的接口的梳理,我们将抽象的index,port,pin,ionumber 构造成一个结构体,即可完成index 到资源的映射,定义如下结构体。
struct pin_index
{
uint8_t index;
uint8_t port;
uint8_t pin;
uint8_t iocon;
};
参考GD32的代码实现如下三个接口:
void lpc84x_pin_mode(int32_t pin, int32_t mode);
void lpc84x_pin_write(int32_t pin, int32_t value);
int lpc84x_pin_read(int32_t pin);
- #include "drv_gpio.h"
- #include "fsl_device_registers.h"
- #include <stdio.h>
- #include <stdlib.h>
- #include "fsl_iocon.h"
- #include "fsl_gpio.h"
- #define SCON_INDEX(offset) ((offset)/0x04u)
- static const struct pin_index pins[] =
- {
- LPC48X_PIN(0,0,0,SCON_INDEX(0x044u)), /* PIO0_0 */
- LPC48X_PIN(1,0,1,SCON_INDEX(0x02cu)),
- LPC48X_PIN(2,0,2,SCON_INDEX(0x018u)),
- LPC48X_PIN(3,0,3,SCON_INDEX(0x014u)),
- LPC48X_PIN(4,0,4,SCON_INDEX(0x010u)),
- LPC48X_PIN(5,0,5,SCON_INDEX(0x00cu)),
- LPC48X_PIN(6,0,6,SCON_INDEX(0x040u)),
- LPC48X_PIN(7,0,7,SCON_INDEX(0x03cu)),
- LPC48X_PIN(8,0,8,SCON_INDEX(0x038u)),
- LPC48X_PIN(9,0,9,SCON_INDEX(0x034u)),
- LPC48X_PIN(10,0,10,SCON_INDEX(0x020u)),
- LPC48X_PIN(11,0,11,SCON_INDEX(0x01cu)),
- LPC48X_PIN(12,0,12,SCON_INDEX(0x008u)),
- LPC48X_PIN(13,0,13,SCON_INDEX(0x004u)),
- LPC48X_PIN(14,0,14,SCON_INDEX(0x048u)),
- LPC48X_PIN(15,0,15,SCON_INDEX(0x028u)),
- LPC48X_PIN(16,0,16,SCON_INDEX(0x024u)),
- LPC48X_PIN(17,0,17,SCON_INDEX(0x000u)),
- LPC48X_PIN(18,0,18,SCON_INDEX(0x078u)),
- LPC48X_PIN(19,0,19,SCON_INDEX(0x074u)),
- LPC48X_PIN(20,0,20,SCON_INDEX(0x070u)),
- LPC48X_PIN(21,0,21,SCON_INDEX(0x06cu)),
- LPC48X_PIN(22,0,22,SCON_INDEX(0x068u)),
- LPC48X_PIN(23,0,23,SCON_INDEX(0x064u)),
- LPC48X_PIN(24,0,24,SCON_INDEX(0x060u)),
- LPC48X_PIN(25,0,25,SCON_INDEX(0x05cu)),
- LPC48X_PIN(26,0,26,SCON_INDEX(0x058u)),
- LPC48X_PIN(27,0,27,SCON_INDEX(0x054u)),
- LPC48X_PIN(28,0,28,SCON_INDEX(0x050u)),
- LPC48X_PIN(29,0,29,SCON_INDEX(0x0c8u)),
- LPC48X_PIN(30,0,30,SCON_INDEX(0x0ccu)),
- LPC48X_PIN(31,0,31,SCON_INDEX(0x08cu)),
- LPC48X_PIN(32,1,0,SCON_INDEX(0x090u)),
- LPC48X_PIN(33,1,1,SCON_INDEX(0x094u)),
- LPC48X_PIN(34,1,2,SCON_INDEX(0x098u)),
- LPC48X_PIN(35,1,3,SCON_INDEX(0x0a4u)),
- LPC48X_PIN(36,1,4,SCON_INDEX(0x0a8u)),
- LPC48X_PIN(37,1,5,SCON_INDEX(0x0acu)),
- LPC48X_PIN(38,1,6,SCON_INDEX(0x0b8u)),
- LPC48X_PIN(39,1,7,SCON_INDEX(0x0c4u)),
- LPC48X_PIN(40,1,8,SCON_INDEX(0x07cu)),
- LPC48X_PIN(41,1,9,SCON_INDEX(0x080u))
- };
- #define ITEM_NUM(items) sizeof(items) / sizeof(items[0])
- /**
- * @brief get pin
- * @param pin
- * @retval None
- */
- const struct pin_index *get_pin(uint8_t pin)
- {
- const struct pin_index *index;
- if (pin < ITEM_NUM(pins))
- {
- index = &pins[pin];
- if (index->index == -1)
- index = NULL;
- }
- else
- {
- index = NULL;
- }
- return index;
- }
- /**
- * @brief set pin mode
- * @param pin, mode
- * @retval None
- */
- void lpc84x_pin_mode(int32_t pin, int32_t mode)
- {
- const struct pin_index *index = NULL;
- uint32_t pin_mode = 0;
- uint8_t modefunc = 0;
- index = get_pin(pin);
- if (index == NULL)
- {
- return;
- }
- switch(mode)
- {
- case PIN_MODE_OUTPUT:
- IOCON_PinMuxSet(IOCON,index->iocon,IOCON_MODE_PULLUP);
- GPIO_PinInit(GPIO, index->port, index->pin, &(gpio_pin_config_t){kGPIO_DigitalOutput, (0)});
- break;
- case PIN_MODE_OUTPUT_OD:
- IOCON_PinMuxSet(IOCON,index->iocon,IOCON_OPENDRAIN_EN);
- GPIO_PinInit(GPIO, index->port, index->pin, &(gpio_pin_config_t){kGPIO_DigitalOutput, (0)});
- break;
- case PIN_MODE_INPUT:
- GPIO_PinInit(GPIO, index->port, index->pin, &(gpio_pin_config_t){kGPIO_DigitalInput, (0)});
- break;
- case PIN_MODE_INPUT_PULLUP:
- IOCON_PinMuxSet(IOCON,index->iocon,IOCON_MODE_PULLUP);
- /* input setting: pull up. */
- GPIO_PinInit(GPIO, index->port, index->pin, &(gpio_pin_config_t){kGPIO_DigitalInput, (0)});
- break;
- case PIN_MODE_INPUT_PULLDOWN:
- IOCON_PinMuxSet(IOCON,index->iocon,IOCON_MODE_PULLDOWN);
- /* input setting: pull down. */
- GPIO_PinInit(GPIO, index->port, index->pin, &(gpio_pin_config_t){kGPIO_DigitalInput, (0)});
- break;
- default:
- break;
- }
- }
- /**
- * @brief pin write
- * @param pin, valuie
- * @retval None
- */
- void lpc84x_pin_write(int32_t pin, int32_t value)
- {
- const struct pin_index *index = NULL;
- index = get_pin(pin);
- if (index == NULL)
- {
- return;
- }
- GPIO_PinWrite(GPIO,index->port,index->pin,(uint8_t)value);
- }
- /**
- * @brief pin read
- * @param dev, pin
- * @retval None
- */
- int lpc84x_pin_read(int32_t pin)
- {
- int value = PIN_LOW;
- const struct pin_index *index = NULL;
- index = get_pin(pin);
- if (index == NULL)
- {
- return value;
- }
- value = GPIO_PinRead(GPIO,index->port,index->pin);
- return value;
- }
复制代码
3.GPIO接口验证
上面我们已经实现了GPIO的驱动接口,我们编写测试代码针对板子上的GPIO LED进行控制同时对按键输入进行采集。原理图上的PIO1_0 对应LED 输出控制,PIO0_4 对应USER按键输入 分别对应的index 为 32 和 4,如下代码针对此index 操作即可完成GPIO的控制
LPC48X_PIN(4,0,4,SCON_INDEX(0x010u)),
LPC48X_PIN(32,1,0,SCON_INDEX(0x090u)),
void start_task(void *pvParameters)
{
uint8_t out = 0;
lpc84x_pin_mode(32,PIN_MODE_OUTPUT);
while(1)
{
lpc84x_pin_write(32,out);
vTaskDelay(500);
out = out ? 0 : 1;
}
}
void start_task1(void *pvParameters)
{
uint8_t new = 1,old = 1;
lpc84x_pin_mode(4,PIN_MODE_INPUT);
while(1)
{
new = lpc84x_pin_read(4);
if(new != old)
{
old = new;
PRINTF("K3 %s\r\n",(new ? "UP":"DOWN"));
}
vTaskDelay(100);
}
}
上述测试代码会500ms 控制LED闪烁及检测按键的状态,实际运行后发现led 会周期闪烁,按键的压下抬起也能打印输出。
=================代码如下=================
|
|