目录
概述
1 理论分析
1.1 红外编码简介
1.2 NEC协议编码
1.3 编码波形分析
2 实现节点参数
2.1 添加节点数据至.dts
2.2 编译.dts文件
2.3 板卡更新.dtb文件
3. 代码实现
3.1 驱动代码实现
3.2 测试代码实现
4 测试
4.1 开发环境
4.1.1 硬件系统参数
4.1.2 编译环境:Ubuntu
4.2 验证驱动代码功能
5 使用逻辑分析仪分析波形
本文介绍在linux架构下,如何实现一个单总线驱动程序的详细过程,以HS0038为例,实现该芯片的解码功能,并把它加载在linux驱动框架下。然后编写测试程序验证驱动程序的功能。
在完成驱动程序验证后,又使用逻辑分析仪工具,验证波形数据和驱动代码采集到的数据是否一致。
红外线遥控是利用近红外光传送遥控指令的,波长为 0.76um~1.5um。用近红外作为遥控光源,是因为目前红外发射器件(红外发光管)与红外接收器件(光敏二极管、三极管及光电池)的发光与受光峰值波长一般为0.8um~0.94um, 在近红外光波段内,二者的光谱正好重合,能够很好地匹配,可以获得较高的传输效率及较高的可靠性。
红外遥控的编码协议种类繁多,如: NEC、 Philips RC-5、 Philips RC-6、 Sony SIRC等,而使用最多的是 NEC 协议,下面介绍NEC 协议的编码格式。
NEC 协议采用的是 PPM(Pulse Position Modulation,脉冲位置调制)进行编码。当我们按下遥控器的一个按键时,会发送一帧的数据。这一帧数据由引导码、地址码、地址反码、数据码、数据反码以及一位结束位(可忽略)组成。
由上图所示: 9ms 的高电平脉冲和之后的 4.5ms 的低电平组成了引导码。接着后面是地址码及数据码,低位在前,高位在后。最后是 562.5um 脉冲突发以表示消息传输的结束。 其中地址码加上地址反码的时间和数据码加上数据反码的时间是固定不变的(如数据码为 00000111,则数据反码则为 11111000),总是由 8 个“1”和 8 个“0”组成。地址码和数据码都是由“0”、“1”组成的,下面介绍逻辑“0”和逻辑“1”的编码方式,如图 :
红外遥控按下时发送的数据顺序如下:
那如果一只按着同一个按键不放,发送完一帧数据之后又会发送什么呢?
1) 使用 i.MX Pins Tool v6 配置IO Pin
2) 在板卡相关的.dts文件中,添加接口数据和配置参数。该文件地址:
/home/mftang/linux_workspace/study_atk_dl6y2c/kernel/atk-dl6u2c/arch/arm/boot/dts/imx6ull-14x14-evk.dts
step-1: 更新MUX_CTL参数。代码如下:
// mftang: user's hs0038 pin
pinctrl_gpio_mftanghs0038: gpio-mftanghs0038 {
fsl,pins = <
MX6UL_PAD_CSI_MCLK__GPIO4_IO17 0x000090B0
>;
};
该部分代码在.dts文件中的位置:
step-2: 添加节点数据,代码如下:
//mftang: user's HS0038, 2024-1-30
// IO: GPIO-4-PIN17
mftanghs0038 {
compatible = "atk-dl6y2c,hs0038";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_gpio_mftanghs0038>;
gpios = <&gpio4 17 GPIO_ACTIVE_HIGH>;
status = "okay";
};
该部分代码在.dts文件中的位置:
编译.dts文件,并把编译生成的.dtb文件发送到NFS共享目录下,便于在板卡中操作该文件。
1)在内核根目录下使用如下命令编译.dts文件
make dtbs
2) 复制 .dtb 文件至NFS共享目录
cp arch/arm/boot/dts/imx6ull-14x14-emmc-4.3-480x272-c.dtb /home/mftang/nfs/atk_dl6y2c/
复制.dtb文件到相应的运行目录,然后重新板卡
在/proc/device-tree中可以看见device节点,然后可以在driver中使用该节点。
1)编写驱动代码
/***************************************************************
Copyright 2024-2029. All rights reserved.
文件名 : drv_09_tree_hs0038.c
作者 : [email protected]
版本 : V1.0
描述 : HS0038 驱动程序, GPIO4_PIN17-----HS0038 IO port
其他 : 无
日志 : 初版V1.0 2024/1/30
使用方法:
1) 在.dts文件中定义节点信息
//mftang: user's HS0038, 2024-1-30
// IO: GPIO-4-PIN17
mftanghs0038 {
compatible = "atk-dl6y2c,hs0038";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_gpio_mftanghs0038>;
gpios = <&gpio4 17 GPIO_ACTIVE_HIGH>;
status = "okay";
};
2) 在驱动匹配列表
static const struct of_device_id hs0038_of_match[] = {
{ .compatible = "atk-dl6y2c,hs0038" },
{ } // Sentinel
};
驱动程序用法:
typedef struct{
int code;
int sign; // -1: error, 0: success
}HS0038Stru;
HS0038Stru stru_hs0038;
if (read(fd, &stru_hs0038, sizeof( HS0038Stru )) == sizeof( HS0038Stru ))
{
if( !stru_hs0038.sign )
{
printf("get hs0038 : 0x%x \r\n", stru_hs0038.code);
}
}
***************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define DEVICE_NAME "treehs0038" // dev/treehs0038
#define OFF 0
#define ON 1
/* hs0038dev设备结构体 */
struct hs0038stru_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
struct device_node *node; /* hs0038设备节点 */
int userhs0038; /* hs0038 GPIO标号*/
struct gpio_desc *pin;
};
struct hs0038stru_dev hs0038dev; /* hs0038设备 */
typedef struct{
int code;
int sign; // -1: error, 0: sucess
}HS0038Stru;
HS0038Stru stru_hs0038;
static u64 hs0038_edge_time[100];
static wait_queue_head_t hs0038_wq;
static int irq;
static int hs0038_edge_cnt = 0;
static unsigned int hs0038_data = 0;
static unsigned int hs0038_data_buf[8];
static int r, w;
static void put_data(unsigned int val)
{
if (((w+1) & 7) != r)
{
hs0038_data_buf[w] = val;
w = (w + 1) & 7;
}
}
static int get_data(unsigned int *val)
{
if (r == w)
{
return -1;
}
else
{
*val = hs0038_data_buf[r];
r = (r + 1) & 7;
return 0;
}
}
static int has_data(void)
{
if (r == w)
return 0;
else
return 1;
}
int hs0038_parse_data(unsigned int *val)
{
u64 tmp;
unsigned char data[4];
int i, j, m;
/* 判断是否重复码 */
if (hs0038_edge_cnt == 4)
{
tmp = hs0038_edge_time[1] - hs0038_edge_time[0];
if (tmp > 8000000 && tmp < 10000000)
{
tmp = hs0038_edge_time[2] - hs0038_edge_time[1];
if (tmp < 3000000)
{
/* 获得了重复码 */
*val = hs0038_data;
return 0;
}
}
}
/* 接收到了68次中断 */
m = 3;
if (hs0038_edge_cnt >= 68)
{
/* 解析到了数据 */
for (i = 0; i < 4; i++)
{
data[i] = 0;
/* 先接收到bit0 */
for (j = 0; j < 8; j++)
{
/* 数值: 1 */
if (hs0038_edge_time[m+1] - hs0038_edge_time[m] > 1000000)
data[i] |= (1<= 2)
{
if (hs0038_edge_time[hs0038_edge_cnt-1] - hs0038_edge_time[hs0038_edge_cnt-2] > 60000000)
{
/* 超时 */
hs0038_edge_time[0] = hs0038_edge_time[hs0038_edge_cnt-1];
hs0038_edge_cnt = 1;
return IRQ_HANDLED; // IRQ_WAKE_THREAD;
}
}
ret = hs0038_parse_data(&val);
if (!ret)
{
/* 解析成功 */
hs0038_edge_cnt = 0;
printk("%s line %d : get ir code = 0x%x \r\n", __FUNCTION__, __LINE__, val);
put_data(val);
wake_up(&hs0038_wq);
stru_hs0038.code = val;
stru_hs0038.sign = 0;
}
else if (ret == -2)
{
/* 解析失败 */
stru_hs0038.sign = -1;
hs0038_edge_cnt = 0;
}
return IRQ_HANDLED; // IRQ_WAKE_THREAD;
}
/*
linux driver 驱动接口:
实现对应的open/read/write等函数,填入file_operations结构体
*/
static ssize_t hs0038_drv_read (struct file *file, char __user *buf,
size_t size, loff_t *offset)
{
unsigned int val;
int err;
printk(" %s line %d \r\n", __FUNCTION__, __LINE__);
wait_event_interruptible(hs0038_wq, has_data());
get_data(&val);
err = copy_to_user(buf, &stru_hs0038, sizeof(HS0038Stru));
return sizeof(HS0038Stru);
}
static int hs0038_drv_close(struct inode *node, struct file *file)
{
printk(" %s line %d \r\n", __FUNCTION__, __LINE__);
return 0;
}
static int hs0038_drv_open(struct inode *inode, struct file *filp)
{
filp->private_data = &hs0038dev; /* 设置私有数据 */
return 0;
}
/*
定义driver的file_operations结构体
*/
static struct file_operations hs0038_fops = {
.owner = THIS_MODULE,
.read = hs0038_drv_read,
.open = hs0038_drv_open,
.release = hs0038_drv_close,
};
/* 1. 从platform_device获得GPIO
* 2. gpio=>irq
* 3. request_irq
*/
static int hs0038_probe(struct platform_device *pdev)
{
int result;
printk("hs0038 driver and device was matched!\r\n");
/* 1. 获得硬件信息 */
hs0038dev.pin = gpiod_get(&pdev->dev, NULL, 0);
if (IS_ERR(hs0038dev.pin))
{
printk("%s line %d get pin parameter error! \n", __FUNCTION__, __LINE__);
}
/* register irq*/
irq = gpiod_to_irq(hs0038dev.pin);
result = request_irq(irq, hs0038_isr, IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING, "hs0038", NULL);
/* 2. device_create */
device_create( hs0038dev.class, NULL,
MKDEV( hs0038dev.major, 0 ), NULL,
DEVICE_NAME); // device name
return 0;
}
static int hs0038_remove(struct platform_device *pdev)
{
printk("%s line %d\n", __FUNCTION__, __LINE__);
device_destroy( hs0038dev.class, MKDEV( hs0038dev.major, 0));
free_irq(irq, NULL);
gpiod_put(hs0038dev.pin);
return 0;
}
static const struct of_device_id atk_dl6y2c_hs0038[] = {
{ .compatible = "atk-dl6y2c,hs0038" },
{ },
};
/* 1. 定义platform_driver */
static struct platform_driver hs0038_driver = {
.probe = hs0038_probe,
.remove = hs0038_remove,
.driver = {
.name = "atk_hs0038",
.of_match_table = atk_dl6y2c_hs0038,
},
};
/*
2. 在入口函数注册platform_driver
*/
static int __init hs0038_init(void)
{
int err;
printk("%s line %d\n",__FUNCTION__, __LINE__);
/* register file_operations */
hs0038dev.major = register_chrdev( 0,
DEVICE_NAME, /* device name */
&hs0038_fops);
/* create the device class */
hs0038dev.class = class_create(THIS_MODULE, "hs0038_class");
if (IS_ERR(hs0038dev.class)) {
printk("%s line %d\n", __FUNCTION__, __LINE__);
unregister_chrdev( hs0038dev.major, DEVICE_NAME);
return PTR_ERR( hs0038dev.class );
}
init_waitqueue_head(&hs0038_wq);
err = platform_driver_register(&hs0038_driver);
return err;
}
/* 3. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数
* 卸载platform_driver
*/
static void __exit hs0038_exit(void)
{
printk("%s line %d\n", __FUNCTION__, __LINE__);
platform_driver_unregister(&hs0038_driver);
class_destroy(hs0038dev.class);
unregister_chrdev(hs0038dev.major, DEVICE_NAME);
}
/*
4. 驱动入口和出口函数
*/
module_init(hs0038_init);
module_exit(hs0038_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("[email protected]");
2)编写驱动代码Makefile
PWD := $(shell pwd)
KERNEL_DIR=/home/mftang/linux_workspace/study_atk_dl6y2c/kernel/atk-dl6u2c
ARCH=arm
CROSS_COMPILE=/home/ctools/gcc-linaro-4.9.4-arm-linux-gnueabihf/bin/arm-linux-gnueabihf-
export ARCH CROSS_COMPILE
obj-m:= drv_09_tree_hs0038.o
all:
$(MAKE) -C $(KERNEL_DIR) M=$(PWD) modules
clean:
rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions *.order *.symvers
3)编译代码,并将生成的.ko复制到NFS共享目录中,然后装载到板卡中
1)编写测试代码
/***************************************************************
Copyright 2024-2029. All rights reserved.
文件名 : test_09_tree_hs0038.c
作者 : [email protected]
版本 : V1.0
描述 : hs0038 测试程序,用于测试 drv_09_tree_hs0038
日志 : 初版V1.0 2024/1/29
typedef struct{
int code;
int sign; // -1: error, 0: success
}HS0038Stru;
HS0038Stru stru_hs0038;
if (read(fd, &stru_hs0038, sizeof( HS0038Stru )) == sizeof( HS0038Stru ))
{
if( !stru_hs0038.sign )
{
printf("get hs0038 : 0x%x \r\n", stru_hs0038.code);
}
}
***************************************************************/
#include
#include
#include
#include
#include
#include
#define DEV_NAME "/dev/treehs0038"
typedef struct{
int code;
int sign; // -1: error, 0: sucess
}HS0038Stru;
HS0038Stru stru_hs0038;
int main(int argc, char **argv)
{
int fd;
int len = sizeof(HS0038Stru);
fd = open(DEV_NAME, O_RDWR);
if (fd < 0){
printf("can not open file %s \r\n", DEV_NAME);
return -1;
}
while(1){
if (read(fd, &stru_hs0038, len) == len)
{
if( !stru_hs0038.sign )
{
printf("get hs0038 : 0x%x \r\n", stru_hs0038.code);
}
}
}
close(fd);
return 0;
}
2)编写测试代码的Makefile
CFLAGS= -Wall -O2
CC=/home/ctools/gcc-linaro-4.9.4-arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc
STRIP=/home/ctools/gcc-linaro-4.9.4-arm-linux-gnueabihf/bin/arm-linux-gnueabihf-strip
test_09_tree_hs0038: test_09_tree_hs0038.o
$(CC) $(CFLAGS) -o test_09_tree_hs0038 test_09_tree_hs0038.o
$(STRIP) -s test_09_tree_hs0038
clean:
rm -f test_09_tree_hs0038 test_09_tree_hs0038.o
3) 编译测试代码,并复制到NFS挂载目录下
使用Linux内核: linux-imx-4.1.15-2.1.0-g3dc0a4b-v2.7.tar.bz2
硬件: ATK-DL6Y2C开发板(芯片型号: IMX6LL)
内核启动位置: eMMC
版本信息: 20.04.2
Linux version 5.15.0-84-generic (buildd@lcy02-amd64-005) (gcc (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0, GNU ld (GNU Binutils for Ubuntu) 2.34) #93~20.04.1-Ubuntu SMP Wed Sep 6 16:15:40 UTC 2023
linux kernel 版本信息: 4.1.15
交叉编译器版本信息: gcc version 4.9.4
运行代码,使用遥控器按下不同的按键,可以得到不同的键值。
遥控器一次发码的完整波形
数据码分析,注意:低位在前,高位在后,可得,当前发送的遥控码是:0x0c
.查看repeat code: