基于韦东山STM32MP157开发板实现智能家居项目(继电器驱动-AP3216C驱动)
摘要:本文详细介绍了STM32MP157开发板上继电器模块和AP3216C三合一传感器的驱动开发过程。继电器模块采用高电平触发方式,通过修改设备树和驱动程序实现控制,初始状态设为释放。AP3216C传感器驱动实现了对光照(ALS)、距离(PS)和照射强度(IR)数据的读取,采用I2C协议通信,数据读取时注意处理16位数据的高低字节顺序。文章包含完整的设备树配置、驱动程序代码和测试程序,并修正了开发
今天开始添加一个继电器模块,功能:继电器释放吸合,后续充当门锁的作用。之前一直没添加,是手头上没有这个模块,后续在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;
}
更多推荐
所有评论(0)