用C语言从零写一个RT-Thread设备驱动:适配STM32的完整流程
RT-Thread的设备驱动框架将硬件抽象为统一的设备对象,应用程序通过标准接口(open/read/write/control)操作外设,无需关心底层寄存器。本文以STM32上的一个虚拟GPIO输出设备为例,演示从零编写RT-Thread设备驱动的完整流程——定义设备结构体、实现操作函数集、注册到内核。
一、定义设备结构体
RT-Thread要求每个设备派生自rt_device,并在私有数据中存放硬件相关信息。
#include <rtthread.h>
#include <rtdevice.h>
/* 私有数据:保存GPIO引脚号 */
struct my_gpio_device {
rt_base_t pin; /* 引脚编号 */
rt_uint8_t active; /* 当前电平 */
};
/* 设备结构体:包含rt_device和私有数据 */
struct my_gpio_device my_gpio;
static rt_device_t my_gpio_dev;
二、实现操作函数集
需要实现rt_device_ops中的init、open、close、write等函数。这里只实现write用于控制GPIO输出。
static rt_err_t my_gpio_init(rt_device_t dev) {
struct my_gpio_device *priv = (struct my_gpio_device *)dev->user_data;
/* 初始化GPIO为推挽输出 */
rt_pin_mode(priv->pin, PIN_MODE_OUTPUT);
rt_pin_write(priv->pin, PIN_LOW);
priv->active = 0;
return RT_EOK;
}
static rt_size_t my_gpio_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size) {
struct my_gpio_device *priv = (struct my_gpio_device *)dev->user_data;
rt_uint8_t value = *(rt_uint8_t *)buffer; /* 取第一个字节:0或1 */
rt_pin_write(priv->pin, value ? PIN_HIGH : PIN_LOW);
priv->active = value;
return 1; /* 写入1字节 */
}
static const struct rt_device_ops my_gpio_ops = {
.init = my_gpio_init,
.open = RT_NULL, /* 无需特殊处理 */
.close = RT_NULL,
.read = RT_NULL, /* 只写设备 */
.write = my_gpio_write,
};
三、注册设备到内核
在系统初始化时调用rt_device_register将设备加入设备管理器,并指定设备类型和标志。
int my_gpio_hw_init(void) {
rt_device_t dev;
struct my_gpio_device *priv;
/* 分配设备对象(也可静态定义) */
dev = (rt_device_t)rt_malloc(sizeof(struct rt_device));
if (dev == RT_NULL) return -RT_ENOMEM;
/* 初始化设备基本属性 */
dev->type = RT_Device_Class_Char;
dev->rx_indicate = RT_NULL;
dev->tx_complete = RT_NULL;
dev->ops = &my_gpio_ops;
dev->user_data = &my_gpio; /* 关联私有数据 */
/* 配置私有数据 */
priv = (struct my_gpio_device *)dev->user_data;
priv->pin = GET_PIN(B, 0); /* PB0 */
priv->active = 0;
/* 注册设备,名字为"mygpio" */
rt_device_register(dev, "mygpio", RT_DEVICE_FLAG_RDWR);
return RT_EOK;
}
/* 使用INIT_DEVICE_EXPORT自动初始化 */
INIT_DEVICE_EXPORT(my_gpio_hw_init);
四、应用程序调用
用户程序只需通过rt_device_find获取设备句柄,然后调用rt_device_write即可控制GPIO。
void led_test(void) {
rt_device_t dev = rt_device_find("mygpio");
if (dev) {
rt_device_init(dev);
rt_uint8_t val = 1;
rt_device_write(dev, 0, &val, 1); /* 点亮LED */
rt_thread_mdelay(1000);
val = 0;
rt_device_write(dev, 0, &val, 1); /* 熄灭 */
}
}
五、三个翻车高发点
忘记调用rt_device_init:注册后设备处于未初始化状态,直接读写会失败。需在首次使用前调用rt_device_init或由框架自动调用。
user_data未正确设置:操作函数中通过dev->user_data获取私有数据,若未赋值则访问野指针。
中断上下文中使用设备驱动:RT-Thread的设备驱动默认不是线程安全的,若在中断中调用rt_device_write可能引发死锁。需使用中断安全版本或信号量保护。





