今天开始添加一个继电器模块,功能:继电器释放吸合,后续充当门锁的作用。之前一直没添加,是手头上没有这个模块,后续在tb上购买了一块1路光耦隔离继电器模块,支持高/低电平触发,详细信息可以参考这个博主:继电器模块的基本使用(高低电平触发)_继电器高平断开吗-CSDN博客

同时开始编写 AP3216C(三合一:光照、距离、照射强度)传感器驱动,目标:实现读取ALS(数字型环境光线感应 sensor),PS(测距sensor)IR(照射sensor)三者信息。

有关AP3216传感器信息以及I2C协议等信息可以在网上查询到,我是借鉴了以下几个博主的信息:

详解AP3216C(三合一sensor: 光照、距离、照射强度)驱动开发-CSDN博客

Linux驱动 | AP3216C驱动(I2C)_ap3216c 三合一整合型光感测器驱动移植-CSDN博客

1路光耦隔离继电器模块信息如下:

1.编写继电器设备树

继电器的驱动比较简单,改一下设备树信息就🆗。我采用高电平触发,前面想着使用低电平触发,这样和LED、蜂鸣器一样都是低电平触发,但是设置成低电平触发,一直无法按照设想方式运转,故改为高电平触发。

stm32mp157-100ask-pinctrl.dtsi中添加引脚PC3配置:

stm32mp157c-100ask-512d-lcd-v1.dts中添加继电器设备节点:

驱动程序是在LED和蜂鸣器驱动程序基础上添加继电器模块:

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/gpio.h>
#include <linux/uaccess.h>
#include <linux/string.h>
#include <linux/gpio/consumer.h>

int major;
static struct class *my_dev_class;
int dev_cnt;
char dev_names[10][20] = {};
struct gpio_desc *my_dev_gpio[10];

static ssize_t my_drv_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
    struct inode *inode = file_inode(filp);
    int minor = iminor(inode);
    char status;
    int err;

    printk("my_drv_read: minor = %d\n", minor);
    status = gpiod_get_value(my_dev_gpio[minor]);
    err = copy_to_user(buf, &status, 1);
    return 1;
}

static ssize_t my_drv_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{
    struct inode *inode = file_inode(filp);
    int minor = iminor(inode);
    char status;
    int err;
    int gpio_value;
    
    printk("my_drv_write: minor = %d\n", minor);
    err = copy_from_user(&status, buf, 1);
    
    // 所有设备使用相同的逻辑:应用层0->低电平,1->高电平
    gpio_value = status;
    
    gpiod_set_value(my_dev_gpio[minor], gpio_value);
    return 1;
}

static int my_drv_open(struct inode *node, struct file *filp)
{
    printk("my_drv_open called\n");
    return 0;
}

static int my_drv_release(struct inode *node, struct file *filp)
{
    printk("my_drv_release called\n");
    return 0;
}

static struct file_operations my_dev_ops = {
    .owner = THIS_MODULE,
    .read = my_drv_read,
    .write = my_drv_write,
    .open = my_drv_open,
    .release = my_drv_release,
};

/* 关键修改:支持继电器设备 */
static int my_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct device_node *np = dev->of_node;
    char a[20];
    const char *my_name = a;
    int ret;
    enum gpiod_flags flags;
    
    printk("=== my_probe START ===\n");
    printk("Device: %s\n", dev_name(dev));
    
    if (!np) {
        printk("ERROR: Device node is NULL\n");
        return -EINVAL;
    }
    
    printk("Device node name: %s\n", np->name);
    printk("Device node full name: %s\n", np->full_name);
    
    // 读取设备树属性
    ret = of_property_read_string(np, "my_name", &my_name);
    if (ret < 0) {
        printk("ERROR: Failed to read my_name property: %d\n", ret);
        return ret;
    }
    
    printk("my_name property: %s\n", my_name);
    
    // 根据设备类型设置不同的GPIO标志
    if (of_device_is_compatible(np, "hc-jdq")) {
        printk("Detected jdq device, setting initial state to HIGH (released)\n");
        flags = GPIOD_OUT_HIGH;  // 继电器初始状态为高电平(释放状态)
    } else {
        printk("Detected LED/beeper device, setting initial state to LOW\n");
        flags = GPIOD_OUT_LOW;   // LED和蜂鸣器初始状态为低电平
    }
    
    // 保存设备名字
    strcpy(dev_names[dev_cnt], my_name);
    printk("Saved device name: %s at index %d\n", dev_names[dev_cnt], dev_cnt);
    
    // 获取GPIO描述符
    my_dev_gpio[dev_cnt] = gpiod_get(dev, NULL, flags);
    if (IS_ERR(my_dev_gpio[dev_cnt])) {
        ret = PTR_ERR(my_dev_gpio[dev_cnt]);
        printk("ERROR: Failed to get GPIO: %d\n", ret);
        return ret;
    }
    printk("GPIO obtained successfully\n");
    
    // 创建设备节点
    printk("Creating device with major=%d, minor=%d, name=%s\n", major, dev_cnt, my_name);
    device_create(my_dev_class, NULL, MKDEV(major, dev_cnt), NULL, my_name);
    dev_cnt++;
    
    printk("=== my_probe SUCCESS ===\n");
    return 0;
}

static int my_remove(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct device_node *np = dev->of_node;
    int i;
    char a[20];
    const char *my_name = a;
    int ret;
    
    printk("my_remove called\n");
    
    ret = of_property_read_string(np, "my_name", &my_name);
    if (ret < 0) {
        printk("ERROR: Failed to read my_name in remove\n");
        return ret;
    }
    
    for(i = 0; i < dev_cnt; i++){
        if(strcmp(dev_names[i], my_name) == 0){
            strcpy(dev_names[i], "");
            gpiod_put(my_dev_gpio[i]);
            device_destroy(my_dev_class, MKDEV(major, i));
            printk("Device %s removed\n", my_name);
            break;
        }
    }
    return 0;
}

static const struct of_device_id my_dev_match[] = {
    { .compatible = "hc-led" },
    { .compatible = "hc-beeper" },
    { .compatible = "hc-jdq" },  // 添加继电器兼容性
    { /* sentinel */ }
};

static struct platform_driver dev_driver = {
    .probe = my_probe,
    .remove = my_remove,
    .driver = {
        .name = "my_platform_driver",
        .of_match_table = my_dev_match,
        .owner = THIS_MODULE,
    },
};

static int __init dev_init(void)
{	
    int ret;
    
    printk("=== dev_init START ===\n");
    
    major = register_chrdev(0, "hc_dev_drv", &my_dev_ops);
    if(major < 0){
        printk("ERROR: register_chrdev failed: %d\n", major);
        return major;
    }
    printk("Character device registered with major: %d\n", major);
    
    my_dev_class = class_create(THIS_MODULE, "my_dev_class");
    if(IS_ERR(my_dev_class)){
        unregister_chrdev(major, "hc_dev_drv");
        printk("ERROR: class_create failed\n");
        return PTR_ERR(my_dev_class);
    }
    printk("Class created successfully\n");
    
    ret = platform_driver_register(&dev_driver);
    if (ret) {
        class_destroy(my_dev_class);
        unregister_chrdev(major, "hc_dev_drv");
        printk("ERROR: platform_driver_register failed: %d\n", ret);
        return ret;
    }
    printk("Platform driver registered successfully\n");
    
    printk("=== dev_init SUCCESS ===\n");
    return 0;
}

static void __exit dev_exit(void)
{
    printk("dev_exit called\n");
    platform_driver_unregister(&dev_driver);
    class_destroy(my_dev_class);
    unregister_chrdev(major, "hc_dev_drv");
    printk("Driver unloaded successfully\n");
}

module_init(dev_init);
module_exit(dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("STM32MP157 LED/Beeper/jdq Driver");

测试程序如下:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

char buf[10];

int main(int argc, char *argv[])
{
    int fd_led, fd_beeper, fd_jdq;
    int value;
    int use_device_arg = 0;
    
    if(argc < 2 || argc > 3){
        printf("Usage: %s  [0|1|on|off]\n", argv[0]);
        printf("或者: %s <设备名> [0|1|on|off]\n", argv[0]);
        printf("设备逻辑:\n");
        printf("  LED: 低电平点亮, 高电平熄灭\n");
        printf("  蜂鸣器: 低电平响, 高电平停\n");
        printf("  继电器: 高电平吸合, 低电平释放\n");  // 更新说明
        printf("Examples:\n");
        printf("  %s on        # 点亮LED,蜂鸣器响,继电器吸合\n", argv[0]);
        printf("  %s off       # 熄灭LED,蜂鸣器停,继电器释放\n", argv[0]);
        return 0;
    }

    if (argc == 2) {
        // 模式1: 同时控制三个设备
        const char *control_value = argv[1];
        
        fd_led = open("/dev/led1", O_RDWR);
        fd_beeper = open("/dev/beeper", O_RDWR);
        fd_jdq = open("/dev/jdq", O_RDWR);

        if (strcmp(control_value, "0") == 0 || strcmp(control_value, "on") == 0) {
            // LED和蜂鸣器: 低电平激活
            buf[0] = 0;
            if (fd_led >= 0) write(fd_led, buf, 1);
            if (fd_beeper >= 0) write(fd_beeper, buf, 1);
            
            // 继电器: 高电平激活
            buf[0] = 1;
            if (fd_jdq >= 0) write(fd_jdq, buf, 1);
            
            printf("激活所有设备\n");
        } else if (strcmp(control_value, "1") == 0 || strcmp(control_value, "off") == 0) {
            // LED和蜂鸣器: 高电平关闭
            buf[0] = 1;
            if (fd_led >= 0) write(fd_led, buf, 1);
            if (fd_beeper >= 0) write(fd_beeper, buf, 1);
            
            // 继电器: 低电平释放
            buf[0] = 0;
            if (fd_jdq >= 0) write(fd_jdq, buf, 1);
            
            printf("关闭所有设备\n");
        }
        
        if (fd_led >= 0) close(fd_led);
        if (fd_beeper >= 0) close(fd_beeper);
        if (fd_jdq >= 0) close(fd_jdq);
        
    } else {
        // 模式2: 控制指定设备
        int fd;
        const char *device_name = argv[1];
        const char *control_value = argv[2];
        
        fd = open(device_name, O_RDWR);
        if(fd < 0){
            printf("设备 %s 打开失败\n", device_name);
            return 1;
        }

        // 为不同设备设置不同的电平逻辑
        if (strcmp(device_name, "/dev/jdq") == 0) {
            // 继电器: 高电平激活,低电平释放
            if (strcmp(control_value, "0") == 0 || strcmp(control_value, "on") == 0) {
                buf[0] = 1;  // 继电器on=高电平
                printf("继电器吸合 (高电平)\n");
            } else {
                buf[0] = 0;  // 继电器off=低电平
                printf("继电器释放 (低电平)\n");
            }
        } else {
            // LED和蜂鸣器: 低电平激活,高电平关闭
            if (strcmp(control_value, "0") == 0 || strcmp(control_value, "on") == 0) {
                buf[0] = 0;  // LED/蜂鸣器on=低电平
                printf("激活 %s (低电平)\n", device_name);
            } else {
                buf[0] = 1;  // LED/蜂鸣器off=高电平
                printf("关闭 %s (高电平)\n", device_name);
            }
        }
        
        write(fd, buf, 1);
        close(fd);
    }
    return 0;
}

2.编写AP3216C设备树

在编写AP3216C驱动前,首先要纠正一下韦东山百问网对STM32MP157开发板的一个描述错误,最初了解到AP3216C传感器在正点原子IMX6ULL开发板上集成了该传感器,想着是不是百问网STM32MP157上也集成了这个传感器,上tb官网看到对开发板信息描述上没有AP3216传感模块,但是有个AP6216传感器模块,上网查询这个模块,也没有相关信息;后续又看了百问网IMX6ULL开发板的板载资源,发现6ull上有AP3216传感器模块,最后去查阅百问网提供的STM32MP157底板电路原理图,发现没有AP6216C电路图,但是有AP3216C电路图如下图,传感器SCL、SDA、INT分别与引脚PF14、PF15、PF13相连。AP3216C的I2C地址为0x1e(7位地址)

stm32mp157-100ask-pinctrl.dtsi中添加引脚配置:

&pinctrl {
    // AP3216C中断引脚配置
    pinctrl_ap3216c: ap3216c {
        pins {
            pinmux = <STM32_PINMUX('F', 13, GPIO)>;  /* AP3216C INT */
            bias-pull-up;
            input-enable;
        };
    };

// I2C1引脚配置(如果已有则不需要重复)
    i2c1_pins_a: i2c1-0 {
        pins {
            pinmux = <STM32_PINMUX('F', 14, AF5)>, /* I2C1_SCL */
                     <STM32_PINMUX('F', 15, AF5)>; /* I2C1_SDA */
            bias-disable;
            drive-open-drain;
            slew-rate = <0>;
        };
    };

    i2c1_pins_sleep_a: i2c1-1 {
        pins {
            pinmux = <STM32_PINMUX('F', 14, ANALOG)>, /* I2C1_SCL */
                     <STM32_PINMUX('F', 15, ANALOG)>; /* I2C1_SDA */
        };
    };
};

stm32mp157-100ask.dts中i2c1后添加ap3216c设备节点:

/* AP3216C环境光/距离/心率传感器 */
    ap3216c@1e {
        compatible = "hc-ap3216c";
        reg = <0x1e>;
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_ap3216c>;
        interrupt-parent = <&gpiof>;
        interrupts = <13 IRQ_TYPE_EDGE_FALLING>;  /* PF13, 下降沿触发 */
        status = "okay";
    };

驱动编写的关键在于传感器数据的读取,根据AP3216C的数据手册,读取的数据是16位的,并且是高位在前(即先传输高8位,再传输低8位)。假设AP3216C发送的两个字节是:0x12(高8位)和0x34(低8位),那么实际的16位数据应该是0x1234。

在驱动程序中:

/* read IR */
	var = i2c_smbus_read_word_data(ap3216_client, 0xa);
	data[0] = (var >> 8) & 0xff; // 高字节
	data[1] = var & 0xff;       // 低字节

	/* read light */
	var = i2c_smbus_read_word_data(ap3216_client, 0xc);
	data[2] = (var >> 8) & 0xff; 
	data[3] = var & 0xff;

	/* read dis */
	var = i2c_smbus_read_word_data(ap3216_client, 0xe);
	data[4] = (var >> 8) & 0xff; 
	data[5] = var & 0xff;

i2c_smbus_read_word_data读取时,先收到0x12,后收到0x34,它会将先收到的0x12作为data[0]高8位,后收到的0x34作为data[1]低8位。

后续在测试程序中组合成IR=0x1234:

printf("IR = %d, light = %d, dis = %d\n\n",
				(data[0] << 8) | data[1], // 高字节左移8位 + 低字节
				(data[2] << 8) | data[3],
				(data[4] << 8) | data[5]);

3.AP3216C上机测试

我将IR(照射sensor)、ALS(数字型环境光线感应 sensor),PS(测距sensor)三个传感器数据分别按照变量名IR、light、dis打印输出。

AP3216C驱动代码:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/uaccess.h>
#include <linux/mod_devicetable.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/delay.h>

static int major;
static struct class *ap3216_class;
static struct i2c_client * ap3216_client;

static int ap3216_open (struct inode *node, struct file *filp)
{
	/* reset: write 0x4 to reg 0 */
	i2c_smbus_write_byte_data(ap3216_client, 0, 0x4);   
	/* delay for reset */
	mdelay(15);

	/* enable: write 0x3 to reg 0 */
	i2c_smbus_write_byte_data(ap3216_client, 0, 0x3);
	return 0;
}

static ssize_t ap3216_read (struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
	int var, err;
	char data[6];

	if(size != 6)
		return -EINVAL;
	
	/* read IR */
	var = i2c_smbus_read_word_data(ap3216_client, 0xa);
	data[0] = (var >> 8) & 0xff; // 高字节
	data[1] = var & 0xff;       // 低字节

	/* read light */
	var = i2c_smbus_read_word_data(ap3216_client, 0xc);
	data[2] = (var >> 8) & 0xff; 
	data[3] = var & 0xff;

	/* read dis */
	var = i2c_smbus_read_word_data(ap3216_client, 0xe);
	data[4] = (var >> 8) & 0xff; 
	data[5] = var & 0xff;

	err = copy_to_user(buf, data, size);

	if (err)
        return -EFAULT;

    return size;
}

//用于和设备树匹配
static const struct of_device_id ap3216_dt_match[] = {
	{ .compatible = "hc-ap3216c", },
	{ }, 
};

//用于和一般的i2c设备匹配,不管i2c设备来自设备树还是手工创建
static const struct i2c_device_id ap3216_i2c_id[] = {
	{ "ap3216c", },
	{ }
};

static struct file_operations ap3216_fops = {
	.owner	= 	THIS_MODULE,
	.open 	= 	ap3216_open,
	.read 	= 	ap3216_read,
};

static int ap3216_i2c_probe(struct i2c_client *client, const struct i2c_device_id * i2c_id)
{
	struct device *result;
	ap3216_client = client;
	
	/* register chrdev */
	major = register_chrdev(0, "ap3216", &ap3216_fops);
	
	ap3216_class = class_create(THIS_MODULE, "ap3216_class");
	if (IS_ERR(ap3216_class)){
		printk("ap3216 class_create failed!\n");
		
		unregister_chrdev(major, "ap3216");
		return PTR_ERR(ap3216_class);
	}
	result = device_create(ap3216_class, NULL, MKDEV(major, 0), NULL, "ap3216");  /* /dev/ap3216 */
	if (IS_ERR(result)){
		printk("ap3216 device_create failed\n");
		class_destroy(ap3216_class);
		unregister_chrdev(major, "ap3216");
		return -ENODEV;
	}
			
	printk("ap3216_i2c probe\n");

	return 0;
}

static int ap3216_i2c_remove(struct i2c_client *client)
{
	device_destroy(ap3216_class, MKDEV(major, 0));
	class_destroy(ap3216_class);
	unregister_chrdev(major, "ap3216");
	return 0;
}


static struct i2c_driver ap3216_i2c_driver = {
	.driver = {
		.owner = THIS_MODULE,
		.name = "ap3216",
		.of_match_table	= ap3216_dt_match,
	},
	.probe = ap3216_i2c_probe,
	.remove = ap3216_i2c_remove,
	.id_table = ap3216_i2c_id,
};


static int __init ap3216_i2c_init(void)
{
	int ret;
	ret = i2c_add_driver(&ap3216_i2c_driver);
	if (ret != 0)
		pr_err("Failed to register ap3216 I2C driver: %d\n", ret);
	
	return 0;
}

static void __exit ap3216_i2c_exit(void)
{
	i2c_del_driver(&ap3216_i2c_driver);
}

module_init(ap3216_i2c_init);
module_exit(ap3216_i2c_exit);
MODULE_LICENSE("GPL");

AP3216C测试程序:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>


int main(int argc, char *argv[])
{
	char data[6];
	int fd;

	fd = open("/dev/ap3216", O_RDWR);
	if(fd < 0)
	{
		printf("open /dev/ap3216 failed\n");
		return -1;
	}

	while(1)
	{
		read(fd, data, 6);
		printf("IR = %d, light = %d, dis = %d\n\n",
				(data[0] << 8) | data[1], // 高字节左移8位 + 低字节
				(data[2] << 8) | data[3],
				(data[4] << 8) | data[5]);
		sleep(1);
	}
	return 0;
}

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐