Linux驱动-①电容屏触摸屏②音频③CAN通信
Linux驱动-①电容屏②音频③CAN通信
- 一,电容屏触摸屏
- 1.GT9147芯片驱动
- 二,音频
- 2.1 I2S总线
- 2.2 --host=arm-linux-gnueabihf
- 三,CAN通信
- 3.1 CAN
- 3.2 UART/CAN为什么不需要设备驱动,而SPI和I2C需要
一,电容屏触摸屏
1.GT9147芯片驱动
电容触摸屏内部芯片采用的传输方式为I2C通信,因此总线驱动已经有了,要写的是电容触摸屏GT9147的驱动,包括根据电路连接方式,对设备树进行修改,将其挂载到I2C2节点上,设置好各个属性。触摸方式有单点触摸和多点触摸:
①单点触摸,仅支持一个触摸点,比如只能一个手指去触摸,同时放第二个手指,第二个手指触摸信息没反应。检测到 一个触点 后,会更新其寄存器中的坐标数据,通过INT引脚触发中断,通知主机读取数据。上报一个点,就相当于把此轮信息上报完了,就会发一个sync信号,然后继续上报下一个信息。(比如手划过后,会有1,2,3,4,5,点信息,上报完1,给一个sync,再上报2,再给一个sync信号…)
// 单点触控上报(仅X/Y坐标)
input_report_abs(input_dev, ABS_X, x_pos);
input_report_abs(input_dev, ABS_Y, y_pos);
input_sync(input_dev);
②多点触摸,可以检测多个手指触摸信息,比如五个手指同时触摸,1号手指触摸1.1,1.2-1.5点,2号手指触摸2.1-2.6点,3号手指触摸3.1-3.3点…,当上报时,会依次先报1号手指1.1,2号手指2.1点,3号手指3.1,4号手指4.1点,5号手指5.1点,信息进行上报,然后发送一个sync信号,继续把*.2点信息上报,发送sync信号。GT9147 会按顺序更新所有触点的寄存器(如触点1在 0x8140~0x8147,触点2在 0x8148~0x814F,依此类推)。所有有效触点的信息会被 一次性读取,然后通过输入子系统上报。
// 遍历所有触点(GT9147 支持最多5点)
for (i = 0; i < max_touches; i++) {
status = read_reg(0x8140 + i * 8); // 触点状态
if (status & TOUCH_ACTIVE) {
x = read_reg(0x8140 + i * 8 + 2); // X坐标
y = read_reg(0x8140 + i * 8 + 4); // Y坐标
// 上报单个触点
input_mt_slot(input_dev, i); // 选择触点槽
input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, true);
input_report_abs(input_dev, ABS_MT_POSITION_X, x);
input_report_abs(input_dev, ABS_MT_POSITION_Y, y);
} else {
// 触点释放
input_mt_slot(input_dev, i);
input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, false);
}
}
input_sync(input_dev); // 所有触点上报完成后发送 SYN_REPORT
采用的是typeB方式,相当于每次将触摸点变成一个slot,用这个slot来上报触摸点的信息,slot其中的内容包括,第几个点,ID,x轴和y轴的坐标值,完成所有点的上报后,上报一个sync信息,这个作用感觉就是把信息之间给间隔起来,有个分页符。
**总体流程:**当触摸后,触发了中断,进入中断处理函数中,先设置出了slot即id,第几次按到了,然后设置其state状态,虽然在前面,但是这些数据要先放到内核的缓冲区中,这个函数的上下和最终数据输出并不是一样的顺序,然后上报x y值,然后调用这个sync一下,就是发送数据,然后当触摸屏释放,还会触发一次中断,从而引起 input_mt_slot(dev->input, id); input_mt_report_slot_state(dev->input, MT_TOOL_FINGER, false)。
驱动:
/*4.1日内容主要有i2c,中断,input结构,电容屏反馈原理*/
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/regmap.h>
#include <linux/gpio/consumer.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/input/mt.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/input/mt.h>
#include <linux/input/touchscreen.h>
#include <linux/i2c.h>
#include <asm/unaligned.h>
#define LCD_NAME "lcd"
#define GT_CTRL_REG 0X8040 /* GT9147控制寄存器 */
#define GT_MODSW_REG 0X804D /* GT9147模式切换寄存器 */
#define GT_9xx_CFGS_REG 0X8047 /* GT9147配置起始地址寄存器 */
#define GT_1xx_CFGS_REG 0X8050 /* GT1151配置起始地址寄存器 */
#define GT_CHECK_REG 0X80FF /* GT9147校验和寄存器 */
#define GT_PID_REG 0X8140 /* GT9147产品ID寄存器 */
#define GT_GSTID_REG 0X814E /* GT9147当前检测到的触摸情况 */
#define GT_TP1_REG 0X814F /* 第一个触摸点数据地址 */
#define GT_TP2_REG 0X8157 /* 第二个触摸点数据地址 */
#define GT_TP3_REG 0X815F /* 第三个触摸点数据地址 */
#define GT_TP4_REG 0X8167 /* 第四个触摸点数据地址 */
#define GT_TP5_REG 0X816F /* 第五个触摸点数据地址 */
#define MAX_SUPPORT_POINTS 5 /* 最多5点电容触摸 */
const u8 irq_table[] = {IRQ_TYPE_EDGE_RISING, IRQ_TYPE_EDGE_FALLING, IRQ_TYPE_LEVEL_LOW, IRQ_TYPE_LEVEL_HIGH}; /* 触发方式 */
struct lcd_dev{
int irqtype; //中断类型
int ret_pin,irq_pin;
struct device dev;
struct input_dev *input; /* input结构体 */
struct i2c_client *client;
};
struct lcd_dev lcd;
static int lcd_read_regs(struct lcd_dev *dev,u16 reg,u8 *buf ,int len)
{
int ret;
u8 reg_adress[2]={0};
struct i2c_client *client = lcd.client;//传递数据
struct i2c_msg msg[2];
reg_adress[0] = reg >> 8;
reg_adress[1] = reg & 0xff;
msg[0].addr = client->addr;//设备地址
msg[0].flags = 0;//写操作
msg[0].len = 2;
msg[0].buf = reg_adress;
msg[1].addr = client->addr;//设备地址
msg[1].flags = I2C_M_RD;//读
msg[1].len = len;//这样就能连续读数据
msg[1].buf = buf;
ret = i2c_transfer(client->adapter, msg, 2);
if (ret < 0) {
printk(KERN_ERR "error %d\n", ret);
ret = -EREMOTEIO;
}else ret = 0;
return ret;
}
static void lcd_write_regs(struct lcd_dev *dev,u16 reg,u8 *buf,u8 len)
{
u8 b[6];
struct i2c_client *client = lcd.client;//传递数据
struct i2c_msg msg[1];
b[0] = reg >> 8;//寄存器地址
b[1] = reg & 0xff;
memcpy(&b[2],buf,len);//每次写入一个数据
msg[0].addr = client->addr;//设备地址
msg[0].flags = 0;//写操作
msg[0].len = len+2;//长度为2
msg[0].buf = b;//b中放的先是寄存器地址,后面是数据
i2c_transfer(client->adapter, msg, 1);
}
// static void lcd_write_reg(struct lcd_dev *dev,u16 reg,u8 data)
// {
// lcd_write_regs(&lcd,reg,&data,1);
// }
// static int gt9147_read_firmware(struct i2c_client *client, struct lcd_dev *dev)
// {
// int ret = 0, version = 0;
// u16 id = 0;
// u8 data[7]={0};
// char id_str[5];
// ret = lcd_read_regs(dev, GT_PID_REG, data, 6);
// if (ret) {
// dev_err(&client->dev, "Unable to read PID.\n");
// return ret;
// }
// memcpy(id_str, data, 4);
// id_str[4] = 0;
// if (kstrtou16(id_str, 10, &id))
// id = 0x1001;
// version = get_unaligned_le16(&data[4]);
// dev_info(&client->dev, "ID %d, version: %04x\n", id, version);
// switch (id) { /* 由于不同的芯片配置寄存器地址不一样需要判断一下 */
// case 1151:
// case 1158:
// case 5663:
// case 5688: /* 读取固件里面的配置信息 */
// ret = lcd_read_regs(dev, GT_1xx_CFGS_REG, data, 7);
// break;
// default:
// ret = lcd_read_regs(dev, GT_9xx_CFGS_REG, data, 7);
// break;
// }
// if (ret) {
// dev_err(&client->dev, "Unable to read Firmware.\n");
// return ret;
// }
// // dev->max_x = (data[2] << 8) + data[1];
// // dev->max_y = (data[4] << 8) + data[3];
// dev->irqtype = data[6] & 0x3;
// printk("irq type = %d\r\n", dev->irqtype);
// return 0;
// }
static irqreturn_t lcd_irq_handler(int irq,void *dev_id)
{
int touch_num = 0;
int input_x, input_y;
int id = 0;
int ret = 0;
u8 data;
u8 touch_data[5];
struct lcd_dev *dev = (struct lcd_dev *)dev_id;
ret = lcd_read_regs(dev,GT_GSTID_REG,&data,1);
if(data!=0)
{
touch_num = data & 0x0f;
}else
{
goto irq_fail;
}
if(touch_num)
{
lcd_read_regs(dev,GT_TP1_REG,touch_data,5);
id = touch_data[0] & 0x0f;
if(id == 0)
{
input_x = touch_data[1] | (touch_data[2] << 8);
input_y = touch_data[3] | (touch_data[4] << 8);
input_mt_slot(dev->input, id);
input_mt_report_slot_state(dev->input, MT_TOOL_FINGER, true);
input_report_abs(dev->input, ABS_MT_POSITION_X, input_x);
input_report_abs(dev->input, ABS_MT_POSITION_Y, input_y);
}
}else if(touch_num ==0)
{
input_mt_slot(dev->input, id);
input_mt_report_slot_state(dev->input, MT_TOOL_FINGER, false);
}
input_mt_report_pointer_emulation(dev->input, true);
input_sync(dev->input);
data = 0x00; /* 向0X814E寄存器写0 */
lcd_write_regs(dev, GT_GSTID_REG, &data, 1);
irq_fail:
return IRQ_HANDLED; // 中断已处理
}
static int lcd_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int ret = 0;
u8 data;
//u8 *reslu = NULL;
lcd.client = client;
/*1.中断以及引脚*/
lcd.irq_pin = of_get_named_gpio(client->dev.of_node,"interrupt-gpios", 0);
lcd.ret_pin = of_get_named_gpio(client->dev.of_node,"reset-gpios", 0);
if(!lcd.irq_pin||! lcd.ret_pin)
{
printk("get gpio error !\r\n");
}
ret = devm_gpio_request_one(&client->dev,lcd.irq_pin,GPIOF_OUT_INIT_HIGH,"lcd_irq");
if(ret<0)
{
printk("gpio request error !\r\n");
}
/*复位*/
ret = devm_gpio_request_one(&client->dev,lcd.ret_pin, GPIOF_OUT_INIT_HIGH,"ret_pin");
if(ret<0)
{
printk("ret request error !\r\n");
}
gpio_set_value(lcd.ret_pin,0);
msleep(10);
gpio_set_value(lcd.ret_pin,1);
msleep(10);
gpio_set_value(lcd.irq_pin,0);
msleep(50);
gpio_direction_input(lcd.irq_pin);
/*2.初始化lcd*/
data = 0x02;
lcd_write_regs(&lcd,GT_CTRL_REG,&data,1);
mdelay(100);
// lcd_read_regs(&lcd,GT_CTRL_REG,reslu,1);
// printk("read data = %d\r\n",reslu[0]);
data = 0;
lcd_write_regs(&lcd,GT_CTRL_REG, &data,1);
mdelay(100);
/*3.input初始化及注册*/
// ret = gt9147_read_firmware(client, &lcd);
// if(ret!=0)
// {
// printk("firmware error\r\n");
// }
lcd.input = devm_input_allocate_device(&client->dev);
lcd.input->name = client->name;
lcd.input->id.bustype = BUS_I2C;
lcd.input->dev.parent = &client->dev;//还是不太懂,为什么要加parent
__set_bit(EV_KEY,lcd.input->evbit);
__set_bit(EV_ABS,lcd.input->evbit);
__set_bit(BTN_TOUCH,lcd.input->keybit);
input_set_abs_params(lcd.input,ABS_X,0,800,0,0);
input_set_abs_params(lcd.input,ABS_Y,0,480,0,0);
input_set_abs_params(lcd.input,ABS_MT_POSITION_X,0,800,0,0);
input_set_abs_params(lcd.input,ABS_MT_POSITION_Y,0,480,0,0);
ret = input_mt_init_slots(lcd.input, MAX_SUPPORT_POINTS, 0);
if(ret<0)
{
printk("input_mt_init_slots error !\r\n");
}
ret = input_register_device(lcd.input);
/*client->irq表示这是i2c引起的中断*/
ret = devm_request_threaded_irq(&client->dev,client->irq,NULL,lcd_irq_handler,
IRQ_TYPE_EDGE_FALLING| IRQF_ONESHOT ,client->name, &lcd);
if(ret<0)
{
printk("irq request error !\r\n");
}
printk("probe probe probe !\r\n");
return ret;
}
static int lcd_remove(struct i2c_client *client)
{
input_unregister_device(lcd.input);
return 0;
}
static const struct i2c_device_id lcd_id[] = {
{ "www", 0 },
{ }
};
const struct of_device_id lcd_of_match[]={
{.compatible = "wyt,lcd"},
{ /*sentinel*/ },
};
static struct i2c_driver lcd_driver = {
.probe = lcd_probe,
.remove = lcd_remove,
.driver = {
.owner = THIS_MODULE,
.name = "lcd666",//1.无设备树,匹配名字
.of_match_table = lcd_of_match,//2.有设备树,直接利用设备树中compatible属性
},
.id_table = lcd_id,
};
static int __init lcd_init(void)
{
int ret = 0;
ret = i2c_add_driver(&lcd_driver);
return ret;
}
static void __exit lcd_exit(void)
{
i2c_del_driver(&lcd_driver);
}
/*驱动入口和出口*/
module_init(lcd_init);
module_exit(lcd_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wyt");
二,音频
2.1 I2S总线
同步串行通信,全双工,用于控制音频,三根基本线SCK,WS,SD,可选MCLK,主模式下,主设备提供时钟,从设备同步接收。SCLK:位时钟,频率=采样率 × 位数 × 通道数。WS:字选择(Word Select)/左右声道时钟,0=左声道,1=右声道。SD:串行数据(Serial Data),传输 PCM 样本。MCLK:主时钟,提供系统参考时钟。
2.2 --host=arm-linux-gnueabihf
–host:指定编译生成的代码在哪个平台上运行(目标平台)。
arm:目标 CPU 架构为 ARM(如树莓派、嵌入式开发板等)。
linux:目标操作系统为 Linux。
gnueabihf ABI 类型:GNU 嵌入式应用二进制接口,支持硬件浮点(Hard-Float)加速。
三,CAN通信
3.1 CAN
CAN使用消息ID区分不同的数据帧,不是设备地址,相当于给每个ID设置一种类型,比如0X100表示发动机转速,0x200表示刹车,CAN设备有自己的过滤器,比如单元1想往单元二发消息,单元二只接受ID为0x200的数据,其他的就会过滤掉。其次是优先级,当多个单元同时工作,谁发出去的ID号越小,优先级越高。
CAN 协议提供了 5 种帧格式来传输数据:数据帧、遥控帧、错误帧、过载帧和帧间隔。数据帧:①、帧起始,表示数据帧开始的段。②、仲裁段,表示该帧优先级的段。③、控制段,表示数据的字节数及保留位的段。④、数据段,数据的内容,一帧可发送 0~8 个字节的数据。⑤、CRC 段,检查帧的传输错误的段。⑥、ACK 段,表示确认正常接收的段。⑦、帧结束,表示数据帧结束的段。
异步串行通信(异步是因为没有时钟线),差分信号CAN_H/CAN_L,,CAN 总线电平分为显性电平和隐性电平两种。显性电平表示逻辑“0”,此时 CAN_H 电平比 CAN_L 高,分别为 3.5V 和 1.5V,电位差为 2V。隐形电平表示逻辑“1”,此时 CAN_H 和 CAN_L 电压都为 2.5V 左右,电位差为 0V。
3.2 UART/CAN为什么不需要设备驱动,而SPI和I2C需要
RS485,422,232最后都要归属到UART驱动,CAN通信采用单独的驱动框架,这两种驱动框架,无论什么设备,都直接用这个驱动就行,不需要额外再写设备的驱动,因为UART/CAN设备简单,直接读取数据就行,设置一下波特率,传输速率就行了,I2C和SPI不一样,设备复杂,不同设备有不同的寄存器访问方式,需要设置设备驱动。