pikascript GPIO接口如何基于rtt pin设备实现呢?

电子说

1.2w人已加入

描述

介绍

Pikascript 是 RT-Thread 软件包中心 - 编程语言 中的一个包,是一个对单片机友好的轻量级 python 脚本支持工具,类似 micropython。

在 Pikascript 中,架构如下:

python

对于不同的平台,我们需要手动为平台适配 pika_hal 的设备抽象层接口。今天以 packages/pikascript-latest/pikaRTDevice/pika_hal_RTT_GPIO.c 为例,讲解 Pikascript GPIO 接口如何基于 RT-Thread Pin 设备 rt-thread/components/misc/pin.c 实现。

讲解

模型如下:

python

所有设备均遵循类 linux 文件的编程模型,所有类型的设备均使用 pika_dev 结构体来作为设备句柄。

pika_dev 类型定义:

typedef struct {
PIKA_HAL_DEV_TYPE type;
PIKA_BOOL is_enabled;
void* ioctl_config;
void* platform_data;
} pika_dev;

在 RT-Thread 的文档中可以得知,应用程序通过 RT-Thread 提供的 PIN 设备管理接口来访问 GPIO,相关接口如下所示:

python

// 通过设备名,返回 pin num
rt_base_t rt_pin_get(const char *name);
// 通过 pin num,返回该 pin 的数据
int rt_pin_read(rt_base_t pin);
// 把 value 电平信息写到对应 pin 上
void rt_pin_write(rt_base_t pin, rt_base_t value);
// 把 pin 的模式设置为 mode
void rt_pin_mode(rt_base_t pin, rt_base_t mode)
所以经过分析,不难看出我们在 open 中要通过 rt_pin_get() 获取引脚编号,获取设备信息;在 read 中要通过 rt_pin_read() 读取引脚电平;在 write 中要通过 rt_pin_write() 设置引脚电平。

在 pika_hal_RTT_GPIO.c 中,我们一共有 7 个接口要实现:

int pika_hal_platform_GPIO_open(pika_dev* dev, char* name);
int pika_hal_platform_GPIO_close(pika_dev* dev);
int pika_hal_platform_GPIO_read(pika_dev* dev, void* buf, size_t count);
int pika_hal_platform_GPIO_write(pika_dev* dev, void* buf, size_t count);
int pika_hal_platform_GPIO_ioctl_enable(pika_dev* dev);
int pika_hal_platform_GPIO_ioctl_disable(pika_dev* dev);
int pika_hal_platform_GPIO_ioctl_config(pika_dev* dev, pika_hal_GPIO_config* cfg);
下面我们依次进行讲解。

dev->platform_io 中存储的数据
首先定义一个结构体 platform_data_GPIO 用来存在 dev->platform_data 中,由上面对 rtt pin 接口的简单调用分析可知,只需要 pin 的数据:

typedef struct platform_data_GPIO {
uint32_t pin_num;
} platform_data_GPIO;
pika_hal_platform_GPIO_open
函数的原型为: int pika_hal_platform_GPIO_open(pika_dev* dev, char* name);

参数:

pika_dev* dev: 要操作的设备句柄
char* name: GPIO 设备名
函数功能:

根据 GPIO 设备名,找到对应的 GPIO 设备的 pin
把设备 pin 数据存在 dev->platform_data 里面
实现:

int pika_hal_platform_GPIO_open(pika_dev* dev, char* name) {
// 打印当前信息
rt_kprintf("rn=%s==%s=%d=name:%s==rn", FILE , FUNCTION , LINE ,name);
// 打印一下日志信息,当前正在打开哪个设备
__platform_printf("Open: %s rn", name);
// 调用 pikaMalloc 分配内存,创建一个 platform_data_GPIO 结构体,用来存放这个 GPIO 设备的信息
platform_data_GPIO* data = pikaMalloc(sizeof(platform_data_GPIO));
// 在 RT_USING_PIN 这个宏定义存在时,通过 rt_pin_get 函数获取这个 GPIO 设备的引脚号,存放在 platform_data_GPIO 结构体的 pin_num 成员中。
#ifdef RT_USING_PIN
data->pin_num = rt_pin_get(name) ;
#endif
// 将创建的 platform_data_GPIO 结构体赋值给 dev->platform_data
dev->platform_data = data;
return 0;
}
pika_hal_platform_GPIO_close
函数的原型为:int pika_hal_platform_GPIO_close(pika_dev* dev);

参数:
pika_dev* dev: 要操作的设备句柄

函数功能:
清除这个 GPIO 设备的信息,即清空 dev->platform_data 中的数据

实现:

int pika_hal_platform_GPIO_close(pika_dev* dev) {
rt_kprintf("rn=%s==%s=%d===rn", FILE , FUNCTION , LINE );
// 如果现在有 GPIO 设备数据,就清空
if (NULL != dev->platform_data) {
pikaFree(dev->platform_data, sizeof(platform_data_GPIO));
dev->platform_data = NULL;
}
return 0;
}
pika_hal_platform_GPIO_read
函数的原型为:int pika_hal_platform_GPIO_write(pika_dev* dev, void* buf, size_t count);

参数:
pika_dev dev:要操作的设备句柄
void buf:读取缓冲区
size_t count:读取数据长度,对于 GPIO、ADC 这样只能读取单个数据的设备,长度为 sizeof(uint32_t)

函数功能:

根据之前存到 dev->platform_data 中的 pin num 数据,调用 rt_pin_read() 函数来获取该 pin 的数据
把读取到的数据存到 buf 缓冲区中
实现:

int pika_hal_platform_GPIO_read(pika_dev* dev, void* buf, size_t count) {
// 获取之前存放的platform_data_GPIO结构体指针
platform_data_GPIO* data = dev->platform_data;
uint32_t level;
rt_kprintf("rn=%s==%s=%d=gpio:%d==rn", FILE , FUNCTION , LINE ,data->pin_num);
#ifdef RT_USING_PIN
// 根据 pin num 读取电平
level = rt_pin_read(data->pin_num);
#endif
// 只有可能是 0 或 1
if (level != 1 && level != 0) {
return -1;
}
// 把 &level 处的 count 个(sizeof(uint32_t) 个)数据拷贝到 buf 缓存区,memcpy 函数不关心 buf 和 src 指向的内存是什么类型,它只根据 count 拷贝内存
memcpy(buf, &level, count);
return 0;
}
注意:
在文档中指出,GPIO 设备 read 时读取的数据 count 应该为 sizeof(uin32_t),在 pika_hal_platform_GPIO_read 中,level 的类型设置为 uint32_t,这是和文档要求一致的。而给 level 赋值的 rt_pin_read() 函数的返回类型为 int,这里进行了一个隐式类型转换。

pika_hal_platform_GPIO_write
函数的原型为:int pika_hal_platform_GPIO_write(pika_dev* dev, void* buf, size_t count);

参数:
pika_dev dev:要操作的设备句柄
oid buf:写入缓冲区
size_t count:写入数据长度,对于 GPIO、ADC 这样只能读取单个数据的设备,长度为 sizeof(uint32_t)

函数功能:
1.根据之前存储的 dev->platform_data 信息获取 pin num
2.获取之前 buf 中存储的电平信息
3.把电平信息写到对应 pin 上

实现:

int pika_hal_platform_GPIO_write(pika_dev* dev, void* buf, size_t count) {
// 获取之前 platform_data 数据
platform_data_GPIO* data = dev->platform_data;
// 获取 buf 缓存区存储的高低电平信息
uint32_t level = 0;
memcpy(&level, buf, count);
// 把电平写到对应 pin 上
#ifdef RT_USING_PIN
if (level == 0) {
rt_pin_write(data->pin_num, PIN_LOW);
return 0;
}
if (level == 1) {
rt_pin_write(data->pin_num, PIN_HIGH);
return 0;
}
#endif
return 0;
}
pika_hal_platform_GPIO_ioctl_enable
函数的原型为:int pika_hal_platform_GPIO_ioctl_enable(pika_dev* dev);

参数:
pika_dev* dev:被操作的设备句柄

函数功能:
这个函数其实对应的是 pika_hal_ioctl(pika_dev* dev, PIKA_HAL_IOCTL_ENABLE) 这里的情况,使能了这个配置函数
所以要初始化一下 GPIO 的 pin num、输入输出模式、推挽模式、波特率等数据

实现:

目前只实现了打印日志

int pika_hal_platform_GPIO_ioctl_enable(pika_dev* dev) {
platform_data_GPIO* data = dev->platform_data;
rt_kprintf("rn=%s==%s=%d=pin_num:%x==rn", FILE , FUNCTION , LINE ,data->pin_num);
/* TODO /
return 0;
}
pika_hal_platform_GPIO_ioctl_disable
函数的原型为:int pika_hal_platform_GPIO_ioctl_disable(pika_dev
dev);

参数:
pika_dev* dev:被操作的设备句柄

函数功能:
这个函数其实对应的是 pika_hal_ioctl(pika_dev* dev, PIKA_HAL_IOCTL_DISABLE) 这里的情况

实现:

同 enable 部分,disable 也只是打印了日志

int pika_hal_platform_GPIO_ioctl_disable(pika_dev* dev) {
rt_kprintf("rn=%s==%s=%d===rn", FILE , FUNCTION , LINE );
platform_data_GPIO* data = dev->platform_data;
return -1;
}
pika_hal_platform_GPIO_ioctl_config
函数的原型为:int pika_hal_platform_GPIO_ioctl_config(pika_dev* dev, pika_hal_GPIO_config* cfg);

参数:
pika_dev dev:被操作的设备句柄
pika_hal_GPIO_config cfg:GPIO 配置,具体定义如下:

typedef struct {
PIKA_HAL_GPIO_DIR dir;//输入输出
PIKA_HAL_GPIO_PULL pull;//推挽模式
PIKA_HAL_GPIO_SPEED speed;//数据传输速率
void (event_callback)(pika_dev dev, PIKA_HAL_GPIO_EVENT_SIGNAL signal);//事件回调函数
PIKA_HAL_GPIO_EVENT_SIGNAL event_callback_filter;//上升沿还是下降沿
//事件回调是否使能
PIKA_HAL_EVENT_CALLBACK_ENA event_callback_ena;
} pika_hal_GPIO_config;
函数功能:
1.对输入输出进行讨论,分为 PIKA_HAL_GPIO_DIR_OUT、PIKA_HAL_GPIO_DIR_IN 两种
2.对推挽模式进行讨论,分为 PIKA_HAL_GPIO_PULL_NONE、PIKA_HAL_GPIO_PULL_UP、PIKA_HAL_GPIO_PULL_DOWN 三种
3.讨论事件回调是否使能、是否设置了回调函数
4.讨论回调函数是上升沿触发还是下降沿触发(PIKA_HAL_GPIO_EVENT_SIGNAL_RISING 以及 PIKA_HAL_GPIO_EVENT_SIGNAL_FALLING)

实现:

现有实现中并没有管回调函数的部分,所以相对简单

RT-Thread 文档中关于 void rt_pin_mode(rt_base_t pin, rt_base_t mode); 函数的 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 /
开漏输出 */
所以最外层讨论输入输出,内层讨论上拉下拉即可。

int pika_hal_platform_GPIO_ioctl_config(pika_dev* dev,
pika_hal_GPIO_config* cfg) {
rt_kprintf("rn=%s==%s=%d=dir:%d==rn", FILE , FUNCTION , LINE ,cfg->dir);
platform_data_GPIO* data = dev->platform_data;
uint8_t pinMode = 0;
// 对 cfg 中各项分类讨论,从而确定 pinMode
switch (cfg->dir) {
case PIKA_HAL_GPIO_DIR_IN:
switch(cfg->pull)
{
case PIKA_HAL_GPIO_PULL_UP:
pinMode = PIN_MODE_INPUT_PULLUP;
break;
case PIKA_HAL_GPIO_PULL_DOWN:
pinMode = PIN_MODE_INPUT_PULLDOWN;
break;
default:
pinMode = PIN_MODE_INPUT;
}
break;
case PIKA_HAL_GPIO_DIR_OUT:
pinMode = PIN_MODE_OUTPUT;
break;
default:
pinMode = PIN_MODE_OUTPUT;
}
// 将 pin 的模式设置为 pinMode
#ifdef RT_USING_PIN
rt_pin_mode(data->pin_num, pinMode);
#endif
return 0;
}
Todo

作者也在阅读源码的过程中发现了一些问题:

1.对失败的情况有时候没有做讨论,如 pika_hal_platform_GPIO_open 函数中,显然 rt_pin_get() 是可能获取不到的,此时应该返回 -1 表示出错并打印有关日志信息,但现有代码中没有这部分处理

2.pika_hal_platform_GPIO_ioctl_config 中没有配置为开漏模式的情况,这意味着无法使用 pikascript 脚本将 GPIO 模式设置为开漏模式。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分