精华内容
下载资源
问答
  • OLED显示屏驱动8080并口,IIC,SPI三种驱动方式

    万次阅读 多人点赞 2019-07-01 14:18:45
    本文介绍了OLED驱动的基本原理,又介绍了OLED的驱动方式,包括8080串口驱动,IIC驱动,SPI驱动方式,并附上完整源码,亲测好用,供大家参考,谢谢!

    本文介绍了对OLED的几种驱动方式,8080并口,IIC,SPI三种驱动方式,采用的单片机是STM32F407.

    一.OLED驱动原理介绍

    OLED模块的驱动芯片为SSD1306,其显存大小总共为 12864bit 大小,SSD1306 将
    这些显存分为了 8 页,其对应关系如表 17.1.3 所示:
    SSD1306显存表
    可以看出,SSD1306 的每页包含了 128 个字节,总共 8 页,这样刚好是 128
    64 的点阵大小。因为每次写入都是按字节写入的,这就存在一个问题,如果我们使用只写方式操作模块,那么,每次要写 8 个点,这样,我们在画点的时候,就必须把要设置的点所在的字节的每个位都搞清楚当前的状态(0/1?),否则写入的数据就会覆盖掉之前的状态,结果就是有些不需要显示的点,显示出来了,或者该显示的没有显示了。这个问题在能读的模式下,我们可以先读出来要写入的那个字节,得到当前状况,在修改了要改写的位之后再写进 GRAM,这样就不会影响到之前的状况了。但是这样需要能读 GRAM,对于 4 线 SPI 模式/IIC 模式,模块是不支持读的,而且读->改->写的方式速度也比较慢。
    所以我们采用的办法是在 STM32F4 的内部建立一个 OLED 的 GRAM(共 128*8 个字节),在每次修改的时候,只是修改 STM32F4 上的 GRAM(实际上就是 SRAM),在修改完了之后,一次性把 STM32F4 上的 GRAM 写入到 OLED 的 GRAM。当然这个方法也有坏处,就是对于那些 SRAM 很小的单片机(比如 51 系列)就比较麻烦了。
    SSD1306 的命令比较多,这里我们仅介绍几个比较常用的命令,这些命令如表 17.1.4 所示:
    SSD1306常用命令表
    我们再来介绍一下 OLED 模块的初始化过程,SSD1306 的典型初始化框图如下图:
    在这里插入图片描述

    二.8080并口驱动方式

    介绍一下模块的 8080 并行接口,8080 并行接口的发明者是 INTEL,该总线也被广泛应用于各类液晶显示器,使得 MCU 可以快速的访问 OLED。ALIENTEK OLED 模块的 8080 接口方式需要如下一些信号线:
    CS:OLED 片选信号。
    WR:向 OLED 写入数据。
    RD:从 OLED 读取数据。
    D[7:0]:8 位双向数据线。
    RST(RES):硬复位 OLED。
    DC:命令/数据标志(0,读写命令;1,读写数据)。
    模块的 8080 并口读/写的过程为:先根据要写入/读取的数据的类型,设置 DC 为高(数据)/低(命令),然后拉低片选,选中 SSD1306,接着我们根据是读数据,还是要写数据置 RD/WR为低,然后:
    在 RD 的上升沿, 使数据锁存到数据线(D[7:0])上;
    在 WR 的上升沿,使数据写入到 SSD1306 里面;
    SSD1306 的 8080 并口写时序图如图 17.1.3 所示:
    在这里插入图片描述
    在这里插入图片描述
    OLED配置及驱动程序:

    /*******************OLED.c代码*************************/
    //OLED显存
    //[0]0 1 2 3 ... 127	
    //[1]0 1 2 3 ... 127	
    //[2]0 1 2 3 ... 127	
    //[3]0 1 2 3 ... 127	
    //[4]0 1 2 3 ... 127	
    //[5]0 1 2 3 ... 127	
    //[6]0 1 2 3 ... 127	
    //[7]0 1 2 3 ... 127 		   
    u8 OLED_GRAM[128][8];//定义SRAM缓存区
    
    //更新显存到LCD		 
    void OLED_Refresh_Gram(void)
    {
    	u8 i, n;
    	for (i = 0; i < 8; i++)
    	{
    		OLED_WR_Byte(0xb0 + i, OLED_CMD);    //设置页地址(0-7)
    		OLED_WR_Byte(0x00, OLED_CMD);      //设置显示位置-列低地址
    		OLED_WR_Byte(0x10, OLED_CMD);      //设置显示位置-列高地址   
    		for (n = 0; n < 128; n++)OLED_WR_Byte(OLED_GRAM[n][i], OLED_DATA); //更新到OLED
    	}
    }
    //通过拼凑的方法向OLED输出一个8位数据
    //data:输出的数据
    void OLED_Data_Out(u8 data)
    {
    	u16 dat = data & 0X0F;
    	GPIOC->ODR &= ~(0XF << 6);		//清空6~9
    	GPIOC->ODR |= dat << 6;			   //D[3:0]-->PC[9:6]
    	GPIO_Write(GPIOC, dat << 6);
    	PCout(11) = (data >> 4) & 0X01;	//D4
    	PBout(6) = (data >> 5) & 0X01;	//D5
    	PEout(5) = (data >> 6) & 0X01;	//D6
    	PEout(6) = (data >> 7) & 0X01;	//D7 
    }
    //向SSD1306写入一个字节
    //dat:写入的数据/命令
    //cmd:0:命令,1:数据
    void OLED_WR_Byte(u8 dat, u8 cmd)
    {
    	OLED_Data_Out(dat);
    	OLED_RS = cmd;
    	OLED_CS = 0;
    	OLED_WR = 0;
    	OLED_WR = 1;
    	OLED_CS = 1;
    	OLED_RS = 1;
    }
    //开启OLED显示    
    void OLED_Display_On(void)
    {
    	OLED_WR_Byte(0X8D, OLED_CMD);  //SET DCDCÃüÁî
    	OLED_WR_Byte(0X14, OLED_CMD);  //DCDC ON
    	OLED_WR_Byte(0XAF, OLED_CMD);  //DISPLAY ON
    }
    //关闭OLED显示
    void OLED_Display_Off(void)
    {
    	OLED_WR_Byte(0X8D, OLED_CMD);  //SET DCDCÃüÁî
    	OLED_WR_Byte(0X10, OLED_CMD);  //DCDC OFF
    	OLED_WR_Byte(0XAE, OLED_CMD);  //DISPLAY OFF
    }
    // OLED清屏
    void OLED_Clear(void)
    {
    	u8 i, n;
    	for (i = 0; i < 8; i++)for (n = 0; n < 128; n++)OLED_GRAM[n][i] = 0X00;
    	OLED_Refresh_Gram();//¸更新显存到LCD	
    }
    //画点 
    //x:0~127
    //y:0~63
    //t:1填充,0清空			   
    void OLED_DrawPoint(u8 x, u8 y, u8 t)
    {
    	u8 pos, bx, temp = 0;
    	if (x > 127 || y > 63)return;//超出范围
    	pos = 7 - y / 8;
    	bx = y % 8;
    	temp = 1 << (7 - bx);
    	if (t)OLED_GRAM[x][pos] |= temp;
    	else OLED_GRAM[x][pos] &= ~temp;
    }
    //按区域填充
    void OLED_Fill(u8 x1, u8 y1, u8 x2, u8 y2, u8 dot)
    {
    	u8 x, y;
    	for (x = x1; x <= x2; x++)
    	{
    		for (y = y1; y <= y2; y++)OLED_DrawPoint(x, y, dot);
    	}
    	OLED_Refresh_Gram();//更新显存到OLED
    }
    //在指定位置显示一个字符(需要调用字符显示字库数组,自行建立)
    //x:0~127
    //y:0~63
    //mode:0反白显示 1正常显示			 
    //size:字体 12/16/24
    void OLED_ShowChar(u8 x, u8 y, u8 chr, u8 size, u8 mode)
    {
    	u8 temp, t, t1;
    	u8 y0 = y;
    	u8 csize = (size / 8 + ((size % 8) ? 1 : 0))*(size / 2);		//得到一个字符所占字节数
    	chr = chr - ' ';                                    //得到偏移后的值		 
    	for (t = 0; t < csize; t++)
    	{
    		if (size == 12)temp = asc2_1206[chr][t]; 	 	//调用1206字体
    		else if (size == 16)temp = asc2_1608[chr][t];	//调用1608字体
    		else if (size == 24)temp = asc2_2412[chr][t];	//调用2412字体
    		else return;								//没有的字库
    		for (t1 = 0; t1 < 8; t1++)
    		{
    			if (temp & 0x80)OLED_DrawPoint(x, y, mode);
    			else OLED_DrawPoint(x, y, !mode);
    			temp <<= 1;
    			y++;
    			if ((y - y0) == size)
    			{
    				y = y0;
    				x++;
    				break;
    			}
    		}
    	}
    }
    //字符串显示函数
    void OLED_ShowString(u8 x, u8 y, const u8 *p, u8 size)
    {
    	while ((*p <= '~') && (*p >= ' '))       //判断是不是非法字符
    	{
    		if (x > (128 - (size / 2))) { x = 0; y += size; }
    		if (y > (64 - size)) { y = x = 0; OLED_Clear(); }
    		OLED_ShowChar(x, y, *p, size, 1);
    		x += size / 2;
    		p++;
    	}
    }
    //m^n函数
    u32 mypow(u8 m, u8 n)
    {
    	u32 result = 1;
    	while (n--)result *= m;
    	return result;
    }
    //数字显示函数	  
    void OLED_ShowNum(u8 x, u8 y, u32 num, u8 len, u8 size)
    {
    	u8 t, temp;
    	u8 enshow = 0;
    	for (t = 0; t < len; t++)
    	{
    		temp = (num / mypow(10, len - t - 1)) % 10;
    		if (enshow == 0 && t < (len - 1))
    		{
    			if (temp == 0)
    			{
    				OLED_ShowChar(x + (size / 2)*t, y, ' ', size, 1);
    				continue;
    			}
    			else enshow = 1;
    		}
    		OLED_ShowChar(x + (size / 2)*t, y, temp + '0', size, 1);
    	}
    }
    //显示数字,高位是0,还是显示
    //x,y:起点坐标
    //num:数值(0~999999999);	 
    //len:长度(即要显示的位数)
    //size:字体大小
    //mode:
    //[7]:0,不填充;1,填充0.
    //[6:1]:保留
    //[0]:0,非叠加显示;1,叠加显示.
    void OLED_ShowxNum(u16 x, u16 y, u32 num, u8 len, u8 size, u8 mode)
    {
    	u8 t, temp;
    	u8 enshow = 0;
    	for (t = 0; t < len; t++)
    	{
    		temp = (num / OLED_Pow(10, len - t - 1)) % 10;
    		if (enshow == 0 && t < (len - 1))
    		{
    			if (temp == 0)
    			{
    				if (mode & 0X80)OLED_ShowChar(x + (size / 2)*t, y, '0', size, mode & 0X01);
    				else OLED_ShowChar(x + (size / 2)*t, y, ' ', size, mode & 0X01);
    				continue;
    			}
    			else enshow = 1;
    		}
    		OLED_ShowChar(x + (size / 2)*t, y, temp + '0', size, mode & 0X01);
    	}
    }
    //计算整数位数(为下面显示float型数据用)
    int DataNum(int num)
    {
    	int i;
    	if ((int)num == 0)i = 1;
    	else
    		for (i = 0; num != 0; i++)
    			num = num / 10;
    	return i;
    }
    //显示float型数据
    //num:输入的float型数据
    void OLED_ShowFloatNum(u16 x, u16 y, double num, u8 size, u8 mode)
    {
    	int a = DataNum((int)num);
    	if ((int)num == 0)                                             //如果输入的float型数据整数部分是0,则加1(便于调用ShowxNum函数)
    	{
    		OLED_ShowxNum(x + size / 2, y, 10000 * (num + 1), 5, size, mode);        //显示电压的小数部分,如果是3.0011的话,这里显示30011
    		OLED_ShowxNum(x, y, (int)num, DataNum((int)num), size, mode);    //显示电压的整数部分,如果是3.0011的话,这里显示3
    		OLED_ShowChar(x + a * size / 2, y, '.', size, mode);                 //显示小数点
    	}
    	else                                                         //如果输入的float型数据整数部分是不是0,则正常显示
    	{
    		if (num - (int)num == 0)                                        //如果是正整数
    		{
    			OLED_ShowxNum(x, y, (int)num, DataNum((int)num), size, mode);      //显示整数
    			OLED_ShowChar(x + a * size / 2, y, '.', size, mode);                  //显示小数点
    			OLED_ShowChar(x + (a + 1)*size / 2, y, '0', size, mode);              //显示小数点后一个0
    		}
    		else                                                        //如果不是正整数
    		{
    			OLED_ShowxNum(x + size / 2, y, 10000 * num, DataNum((int)num) + 4, size, mode);    //显示电压的小数部分,如果是3.0011的话,这里显示30011
    			OLED_ShowChar(x + a * size / 2, y, '.', size, mode);                           //显示小数点
    			OLED_ShowxNum(x, y, (int)num, DataNum((int)num), size, mode);//显示电压的整数部分,如果是3.0011的话,这里显示3
    		}
    	}
    }
    
    //初始化SSD1306					    
    void OLED_Init(void)
    {
    	GPIO_InitTypeDef  GPIO_InitStructure;
    
    	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA | RCC_AHB1Periph_GPIOB | RCC_AHB1Periph_GPIOC | RCC_AHB1Periph_GPIOD | RCC_AHB1Periph_GPIOE | RCC_AHB1Periph_GPIOG, ENABLE);//IO口时钟使能
    
    	  //GPIO初始化设置
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
    	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
    	GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
    	GPIO_Init(GPIOB, &GPIO_InitStructure);//
    
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_11;
    	GPIO_Init(GPIOC, &GPIO_InitStructure);
    
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
    	GPIO_Init(GPIOD, &GPIO_InitStructure);
    
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_5;
    	GPIO_Init(GPIOE, &GPIO_InitStructure);
    
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;
    	GPIO_Init(GPIOG, &GPIO_InitStructure);
    
    	OLED_WR = 1;
    	OLED_RD = 1;
    
    	OLED_CS = 1;
    	OLED_RS = 1;
    
    	OLED_RST = 0;
    	delay_ms(100);
    	OLED_RST = 1;
    
    	//以下为SSD1306设置初始化代码,基本不用改动				  
    	OLED_WR_Byte(0xAE, OLED_CMD); //关闭显示
    	OLED_WR_Byte(0xD5, OLED_CMD); //设置时钟分频因子,振荡频率
    	OLED_WR_Byte(80, OLED_CMD);   //[3:0],分频因子;[7:4],振荡频率
    	OLED_WR_Byte(0xA8, OLED_CMD); //设置驱动路数
    	OLED_WR_Byte(0X3F, OLED_CMD); //默认0X3F(1/64) 
    	OLED_WR_Byte(0xD3, OLED_CMD); //设置显示偏移
    	OLED_WR_Byte(0X00, OLED_CMD); //默认为0
    
    	OLED_WR_Byte(0x40, OLED_CMD); //设置显示开始行 [5:0],行数.
    
    	OLED_WR_Byte(0x8D, OLED_CMD); //电荷泵设置
    	OLED_WR_Byte(0x14, OLED_CMD); //bit2 开启/关闭
    	OLED_WR_Byte(0x20, OLED_CMD); //设置内存地址模式
    	OLED_WR_Byte(0x02, OLED_CMD); //[1:0],00列地址模式;01行地址模式;10,页地址模式;默认10;
    	OLED_WR_Byte(0xA1, OLED_CMD); //段重定义设置,bit0:0,0->0;1,0->127;
    	OLED_WR_Byte(0xC0, OLED_CMD); //设置COM扫描方向;bit3:0,普通模式;1,重定义模式 COM[N-1]->COM0;N:驱动路数
    	OLED_WR_Byte(0xDA, OLED_CMD); //设置COM口硬件引脚配置
    	OLED_WR_Byte(0x12, OLED_CMD); //[5:4]配置
    
    	OLED_WR_Byte(0x81, OLED_CMD); //对比度设置
    	OLED_WR_Byte(0xEF, OLED_CMD); //1~255;默认0X7F (亮度设置,越大越亮)
    	OLED_WR_Byte(0xD9, OLED_CMD); //设置预充电周期
    	OLED_WR_Byte(0xf1, OLED_CMD); //[3:0],PHASE 1;[7:4],PHASE 2;
    	OLED_WR_Byte(0xDB, OLED_CMD); //设置VCOMH 电压倍率
    	OLED_WR_Byte(0x30, OLED_CMD); //[6:4] 000,0.65*vcc;001,0.77*vcc;011,0.83*vcc;
    
    	OLED_WR_Byte(0xA4, OLED_CMD); //全局显示开启;bit0:1,开启;0,关闭;(白屏/黑屏)
    	OLED_WR_Byte(0xA6, OLED_CMD); //设置显示方式;bit0:1,反相显示;0,正常显示	    						   
    	OLED_WR_Byte(0xAF, OLED_CMD); //开启显示	 
    	OLED_Clear();//OLED清屏
    }
    /*******************OLED.h代码*************************/
    //-----------------OLED端口定义----------------  					   
    #define OLED_CS 	PBout(7)
    #define OLED_RST    PGout(15)	
    #define OLED_RS 	PDout(6)
    #define OLED_WR 	PAout(4)		  
    #define OLED_RD 	PDout(7)
    
    #define OLED_CMD  	0		//写命令
    #define OLED_DATA 	1		//写数据
    /*******************main()函数代码*************************/
    int main(void)
    {
    	OLED_Init();				//初始化OLED
    	while (1)
    	{
    		OLED_ShowChar(100, 30, 'K', 16, 1);//显示字符
    		OLED_ShowString(64, 52, "CODE:", 12);//显示字符串
    		OLED_ShowFloatNum(10, 30, 0.1213, 16, 1);//显示小数
    		OLED_ShowChinese(26, 10, 3, 1);//显示汉字
    		OLED_Refresh_Gram(2);//更新显示到OLED,此句非常重要,将数据写到SSD1306的GRAM区域
    		delay_ms(1000);
    	}
    }
    
    

    三.IIC驱动方式

    一些驱动方式与显示代码均与上面一致,因此这里只介绍IIC写数据/命令驱动代码,也即重写OLED_WR_Byte(u8 dat,u8 cmd)函数,其他均不变!

    //向SSD1306写入一个字节
    //dat:要写入的数据/命令
    //cmd:数据/命令标志 0,表示命令;1,表示数据
    void WriteCmd(u8 command)    //写命令函数
    {
        IIC_Start();
        IIC_Send_Byte(0x78);//OLED地址
        IIC_Wait_Ack(t);
        IIC_Send_Byte(0x00);//写命令寄存器地址
        IIC_Wait_Ack();
        IIC_Send_Byte(command);
        IIC_Wait_Ack();
        IIC_Stop();
    }
    void WriteData(u8 data)      //写数据函数
    {
        IIC_Start();
        IIC_Send_Byte(0x78);//OLED地址
        IIC_Wait_Ack();
        IIC_Send_Byte(0x40);//写数据寄存器地址
        IIC_Wait_Ack();
        IIC_Send_Byte(data);
        IIC_Wait_Ack();
        IIC_Stop();
    }
    void OLED_WR_Byte(u8 dat,u8 cmd)  //为了直接替换上面,做一个封装函数
    {	 
    	if(cmd)
    	  WriteData(dat);
    	else
    	  WriteCmd(dat);
    } 
    

    这里采用的是模拟IIC通信方式,在main函数中要先配置IIC初始化

    四.SPI驱动方式

    SPI模式使用的信号线有如下几条:
    CS:OLED 片选信号。
    RST(RES):硬复位 OLED。
    DC:命令/数据标志(0,读写命令;1,读写数据)。
    SCLK:串行时钟线。在 4 线串行模式下,D0 信号线作为串行时钟线 SCLK。
    SDIN:串行数据线。在 4 线串行模式下,D1 信号线作为串行数据线 SDIN。
    在 4 线串行模式下,只能往模块写数据而不能读数据。
    在 4 线 SPI 模式下,每个数据长度均为 8 位,在 SCLK 的上升沿,数据从 SDIN 移入到
    SSD1306,并且是高位在前的。DC 线还是用作命令/数据的标志线。在 4 线 SPI 模式下,写操作的时序如图 17.1.6 所示:
    在这里插入图片描述
    一些驱动方式与显示代码均与上面一致,因此这里只介绍SPI写数据/命令驱动代码,也即重写OLED_WR_Byte(u8 dat,u8 cmd)函数,其他均不变!

    //向SSD1306写入一个字节
    //dat:要写入的数据/命令
    //cmd:数据/命令标志 0,表示命令;1,表示数据
    
    //STM32F407采用硬件SPI发送数据(也可采用模拟SPI方式发送数据,在后续的博客中会有介绍)
    void OLED_Write_Byte(uint8_t dat)
    {  
        SPI1_ReadWriteByte(dat);      //调用硬件SPI写数据函数
    }
    //写数据
    void OLED_Write_Data(uint8_t dat)
    {
        CS=0;
        DC=1;
        OLED_Write_Byte(dat);
    }
    //写命令
    void OLED_Write_Cmd(uint8_t cmd)
    { 
        CS=0;
        DC=0;
        OLED_Write_Byte(cmd);
    }
    //为了直接替换上面,做一个封装函数
    void OLED_WR_Byte(u8 dat,u8 cmd)
    {	 
    	if(cmd)
    	  OLED_Write_Data(dat);
    	else
    		OLED_Write_Cmd(dat);
    } 
    

    小结:OLED的驱动方式非常简单,应用起来也非常的方便,分辨率也较高,作为平时辅助开发的小工具也是极好的。在上文中对于各种字符的显示均给出了驱动程序,可以非常方便的调用,另外对于字符取模也有很多可用的小软件,大家可自行应用。
    至此,OLED显示屏的几种驱动方式均已介绍完,也预示着第一篇博客的完结。作为刚入行半年的嵌入式小白,小心翼翼的编辑每一行文字。但由于技术水平有限,难免会有诸多错误之处,还希望得到诸位大神的批评指教,也希望借此平台可以和大家畅所欲言,共同进步!

    展开全文
  • 我们先了解下驱动如何实现电容屏的多点触摸,其实很简单,主要需要触摸屏IC FT5406 能够捕获多点数据,这点电容屏基本多能支持到捕获2点以上,而FT5406 可以捕获5个触摸点,编写驱动时,只要去获取这几个点的数据,...

    ***************************************************************************************************************************

    作者:EasyWave                                                                                 时间:2013.02.06

    类别:Linux 内核驱动源码分析                                                      声明:转载,请保留链接

    注意:如有错误,欢迎指正。这些是我学习的日志文章......

    ***************************************************************************************************************************

    一:FT5X06电容触摸IC简介

    FT5x06系列ICs是单芯片电容式触摸屏控制器IC,带有一个内置的8位微控制器单元(MCU)。采用互电容的方法,在配合的相互的电容式触摸面板,它支持真正的多点触摸功能。FT5x06具有用户友好的输入的功能,这可以应用在许多便携式设备,例如蜂窝式电话,移动互联网设备,上网本和笔记本个人电脑。FT5x06系列IC包括FT5206/FT5306/FT5406。具体的功能如下图所示:

    uid-29728680-id-5748727.html

    二:硬件接口设计

    从FT5X06的datasheet中,我们可以看到,FT5X06既可以工作的SPI的接口方式,也可以工作在I2C的接口方式,不管工作在SPI,还是工作在I2C,从硬件的接口设计上来说,这下面的几个控制口,都是需要要接的。如下图所示:

    uid-29728680-id-5748727.html

    1):INT引脚,这个脚是一个中端信号,它用来通知HOST端FT5X06已经准备好,可以进行读操作了。

    2):WAKE引脚:这个功能主要的作用是将FT5X06从睡眠状态转换到工作状态。

    3):/RST引脚:FT5X06的芯片复位信号。

    如何来设计硬件接口呢,这个我们可以从FT5X06的datasheet看出来,首先我们来看下FT5X06的上电时序,如下图所示:

    uid-29728680-id-5748727.html

    FT5X06的上电时序

    uid-29728680-id-5748727.html

    FT5X06的RESET时序

    uid-29728680-id-5748727.html

    FT5X06的wakeup时序

    各自的最小的时间限制如下所所示:

    uid-29728680-id-5748727.html

    因此,从上面的图片和表格中,我们可以看出,在poweron中,必须确保在上电后,wakeup的电平为高电平,至于INT信号,只要确保无INT信号时,这个INT为高即可,这个可以从poweron的时序可以看出,它是一个低电平的动作,这个是驱动中来做的事情了。

    接下来就是确定I2C的从地址,如下图所示:[以下引用自网络]

    uid-29728680-id-5748727.html

    从地址高位必须为:3,低位必须根据i2ccon设定的值来确定。

    uid-29728680-id-5748727.html

    根据FT5406数据手册上的指令,我们先了解下驱动如何实现电容屏的多点触摸,其实很简单,主要需要触摸屏IC FT5406 能够捕获多点数据,这点电容屏基本多能支持到捕获2点以上,而FT5406 可以捕获5个触摸点,编写驱动时,只要去获取这几个点的数据,然后上报就可以了。如下图所示:[以下引用自网络]

    uid-29728680-id-5748727.html

    uid-29728680-id-5748727.html

    02H :捕获的触摸点个数              03H- 1EH :对应每个点的x,y坐标数值。

    三:Linux驱动源码

    I2C的驱动需要根据具体的ARM芯片,一般来说,IC原厂,一般会将在linux的bsp中都会有I2C的驱动,这个部分不需要我们去写的,我们只需要将FT5X06和BSP包中的I2C驱动匹配起来就好了。而整个FT5X06也是这样做的。在linu内核中关于i2c的一般会有这个函数:i2c_board_info()i2c_board_info用于构建信息表来列出存在的I2C设备。这一信息用于增长新型I2C驱动的驱动模型树。对于主板,它使用i2c_register_board_info()来静态创建。对于子板,利用已知的适配器使用i2c_new_device()动态创建。

    structi2c_board_info {

    chartype[I2C_NAME_SIZE];

    unsigned shortflags;

    unsigned shortaddr;

    void*platform_data;

    structdev_archdata *archdata;

    intirq;

    };

    staticstructi2c_board_info i2c_devs0[] __initdata = {

    #ifdef CONFIG_TOUCHSCREEN_CDTLCD

    {

    I2C_BOARD_INFO("ft5x0x_ts", 0x3x),

    .irq = IRQ_EINT1,

    },

    #endif

    };

    最后在内核初始化的部分调用int i2c_register_board_info(int busnum, struct i2c_board_info const *info, unsigned len);函数即可。如下详细的解释:

    @busnum: 指定这些设备属于哪个总线

    @info: I2C设备描述符向量

    @len: 向量中描述符的数量;为了预留特定的总线号,可以是0。

    i2c_register_board_info(0, i2c_devs0, ARRAY_SIZE(i2c_devs0));

    下面贴出部分代码:[下面的代码是下的Linux驱动,如果要修改到通用的Linux内核中,需要修改代码的]

    staticint

    ft5x0x_ts_probe(structi2c_client *client,conststructi2c_device_id *id)

    {

    structft5x0x_ts_data *ft5x0x_ts;

    structinput_dev *input_dev;

    interr = 0;

    unsigned charuc_reg_value;

    #if CFG_SUPPORT_TOUCH_KEY

    inti;

    #endif

    printk("[FTS] ft5x0x_ts_probe, driver version is %s.\n", CFG_FTS_CTP_DRIVER_VERSION);

    if(!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {

    err = -ENODEV;

    gotoexit_check_functionality_failed;

    }

    ft5x0x_ts = kzalloc(sizeof(structft5x0x_ts_data), GFP_KERNEL);

    //ft5x0x_ts = kmalloc(sizeof(struct ft5x0x_ts_data), GFP_KERNEL);

    if(!ft5x0x_ts) {

    err = -ENOMEM;

    gotoexit_alloc_data_failed;

    }

    //memset(ft5x0x_ts, 0, sizeof(struct ft5x0x_ts_data));

    this_client = client;

    i2c_set_clientdata(client, ft5x0x_ts);

    mutex_init(&ft5x0x_ts->device_mode_mutex);

    INIT_WORK(&ft5x0x_ts->pen_event_work, ft5x0x_ts_pen_irq_work);

    ft5x0x_ts->ts_workqueue = create_singlethread_workqueue(dev_name(&client->dev));

    if(!ft5x0x_ts->ts_workqueue) {

    err = -ESRCH;

    gotoexit_create_singlethread;

    }

    err = request_irq(IRQ_EINT(6), ft5x0x_ts_interrupt, IRQF_TRIGGER_FALLING, "ft5x0x_ts", ft5x0x_ts);

    if(err

    dev_err(&client->dev, "ft5x0x_probe: request irq failed\n");

    gotoexit_irq_request_failed;

    }

    disable_irq(IRQ_EINT(6));

    input_dev = input_allocate_device();

    if(!input_dev) {

    err = -ENOMEM;

    dev_err(&client->dev, "failed to allocate input device\n");

    gotoexit_input_dev_alloc_failed;

    }

    ft5x0x_ts->input_dev = input_dev;

    set_bit(ABS_MT_TOUCH_MAJOR, input_dev->absbit);

    set_bit(ABS_MT_POSITION_X, input_dev->absbit);

    set_bit(ABS_MT_POSITION_Y, input_dev->absbit);

    set_bit(ABS_MT_WIDTH_MAJOR, input_dev->absbit);

    input_set_abs_params(input_dev,

    ABS_MT_POSITION_X, 0, SCREEN_MAX_X, 0, 0);

    input_set_abs_params(input_dev,

    ABS_MT_POSITION_Y, 0, SCREEN_MAX_Y, 0, 0);

    input_set_abs_params(input_dev,

    ABS_MT_TOUCH_MAJOR, 0, PRESS_MAX, 0, 0);

    input_set_abs_params(input_dev,

    ABS_MT_WIDTH_MAJOR, 0, 200, 0, 0);

    input_set_abs_params(input_dev,

    ABS_MT_TRACKING_ID, 0, 5, 0, 0);

    set_bit(EV_KEY, input_dev->evbit);

    set_bit(EV_ABS, input_dev->evbit);

    #if CFG_SUPPORT_TOUCH_KEY

    //setup key code area

    set_bit(EV_SYN, input_dev->evbit);

    set_bit(BTN_TOUCH, input_dev->keybit);

    input_dev->keycode = tsp_keycodes;

    for(i = 0; i

    {

    input_set_capability(input_dev, EV_KEY, ((int*)input_dev->keycode)[i]);

    tsp_keystatus[i] = KEY_RELEASE;

    }

    #endif

    input_dev->name      = FT5X0X_NAME;      //dev_name(&client->dev)

    err = input_register_device(input_dev);

    if(err) {

    dev_err(&client->dev,

    "ft5x0x_ts_probe: failed to register input device: %s\n",

    dev_name(&client->dev));

    gotoexit_input_register_device_failed;

    }

    #ifdef CONFIG_HAS_EARLYSUSPEND

    printk("==register_early_suspend =\n");

    ft5x0x_ts->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1;

    ft5x0x_ts->early_suspend.suspend = ft5x0x_ts_suspend;

    ft5x0x_ts->early_suspend.resume  = ft5x0x_ts_resume;

    register_early_suspend(&ft5x0x_ts->early_suspend);

    #endif

    msleep(150);  //make sure CTP already finish startup process

    //get some register information

    uc_reg_value = ft5x0x_read_fw_ver();

    printk("[FTS] Firmware version = 0x%x\n", uc_reg_value);

    ft5x0x_read_reg(FT5X0X_REG_PERIODACTIVE, &uc_reg_value);

    printk("[FTS] report rate is %dHz.\n", uc_reg_value * 10);

    ft5x0x_read_reg(FT5X0X_REG_THGROUP, &uc_reg_value);

    printk("[FTS] touch threshold is %d.\n", uc_reg_value * 4);

    #if CFG_SUPPORT_AUTO_UPG

    fts_ctpm_auto_upg();

    #endif

    #if CFG_SUPPORT_UPDATE_PROJECT_SETTING

    fts_ctpm_update_project_setting();

    #endif

    enable_irq(IRQ_EINT(6));

    //create sysfs

    err = sysfs_create_group(&client->dev.kobj, &ft5x0x_attribute_group);

    if(0 != err)

    {

    dev_err(&client->dev, "%s() - ERROR: sysfs_create_group() failed: %d\n", __FUNCTION__, err);

    sysfs_remove_group(&client->dev.kobj, &ft5x0x_attribute_group);

    }

    else

    {

    printk("ft5x0x:%s() - sysfs_create_group() succeeded.\n", __FUNCTION__);

    }

    printk("[FTS] ==probe over =\n");

    return0;

    exit_input_register_device_failed:

    input_free_device(input_dev);

    exit_input_dev_alloc_failed:

    //  free_irq(client->irq, ft5x0x_ts);

    free_irq(IRQ_EINT(6), ft5x0x_ts);

    exit_irq_request_failed:

    //exit_platform_data_null:

    cancel_work_sync(&ft5x0x_ts->pen_event_work);

    destroy_workqueue(ft5x0x_ts->ts_workqueue);

    exit_create_singlethread:

    printk("==singlethread error =\n");

    i2c_set_clientdata(client, NULL);

    kfree(ft5x0x_ts);

    exit_alloc_data_failed:

    exit_check_functionality_failed:

    returnerr;

    }

    staticint__devexit ft5x0x_ts_remove(structi2c_client *client)

    {

    structft5x0x_ts_data *ft5x0x_ts;

    printk("==ft5x0x_ts_remove=\n");

    ft5x0x_ts = i2c_get_clientdata(client);

    unregister_early_suspend(&ft5x0x_ts->early_suspend);

    //  free_irq(client->irq, ft5x0x_ts);

    mutex_destroy(&ft5x0x_ts->device_mode_mutex);

    free_irq(IRQ_EINT(6), ft5x0x_ts);

    input_unregister_device(ft5x0x_ts->input_dev);

    kfree(ft5x0x_ts);

    cancel_work_sync(&ft5x0x_ts->pen_event_work);

    destroy_workqueue(ft5x0x_ts->ts_workqueue);

    i2c_set_clientdata(client, NULL);

    del_timer(&test_timer);

    return0;

    }

    staticconststructi2c_device_id ft5x0x_ts_id[] = {

    { FT5X0X_NAME, 0x3x },{ }

    };

    MODULE_DEVICE_TABLE(i2c, ft5x0x_ts_id);

    staticstructi2c_driver ft5x0x_ts_driver = {

    .probe      = ft5x0x_ts_probe,

    .remove     = __devexit_p(ft5x0x_ts_remove),

    .id_table   = ft5x0x_ts_id,

    .driver = {

    .name   = FT5X0X_NAME,

    .owner  = THIS_MODULE,

    },

    };

    staticint__init ft5x0x_ts_init(void)

    {

    intret;

    printk("==ft5x0x_ts_init==\n");

    ret = i2c_add_driver(&ft5x0x_ts_driver);

    printk("ret=%d\n",ret);

    returnret;

    }

    staticvoid__exit ft5x0x_ts_exit(void)

    {

    printk("==ft5x0x_ts_exit==\n");

    i2c_del_driver(&ft5x0x_ts_driver);

    }

    module_init(ft5x0x_ts_init);

    module_exit(ft5x0x_ts_exit);

    MODULE_AUTHOR("");

    MODULE_DESCRIPTION("FocalTech ft5x0x TouchScreen driver");

    MODULE_LICENSE("GPL");

    save_snippets.png

    移植到通用的Linux的内核中,就不写出来了,自己去研究吧。

    展开全文
  • 2_触摸屏驱动程序之编写驱动 (100ask.net) 触摸屏使用过程: 触摸屏某点被按下,产生INT_TC中断; 在中断处理程序中,打开定时器 定时器时间到,启动ADC转换,得到x和y坐标; ADC结束,产生ADC中断; 、在ADC...
     
    

    触摸屏使用过程:

    1. 触摸屏某点被按下,产生INT_TC中断;
    2. 在中断处理程序中,打开定时器
    3. 定时器时间到,启动ADC转换,得到x和y坐标;
    4. ADC结束,产生ADC中断;
    5. 、在ADC中断处理函数里,上报(input_event),启
    6. 抬起,松开屏幕

    1、编写基本框架(s3c_ts.c)

    #include <linux/errno.h>
    #include <linux/kernel.h>
    #include <linux/module.h>
    #include <linux/slab.h>
    #include <linux/input.h>
    #include <linux/init.h>
    #include <linux/serio.h>
    #include <linux/delay.h>
    #include <linux/platform_device.h>
    #include <linux/clk.h>
    #include <asm/io.h>
    #include <asm/irq.h>
    
    static struct input_dev *s3c_ts_dev;
    static int s3c_ts_init(void)
    {
    	/* 1. 分配一个input_dev结构体 */
    	s3c_ts_dev = input_allocate_device();
    
    	/* 2. 设置 */
    	/* 2.1 能产生哪类事件 */
    	set_bit(EV_KEY, s3c_ts_dev->evbit);
    	set_bit(EV_ABS, s3c_ts_dev->evbit);
    
    	/* 2.2 能产生这类事件里的哪些事件 */
    	set_bit(BTN_TOUCH, s3c_ts_dev->keybit);
    
    	input_set_abs_params(s3c_ts_dev, ABS_X, 0, 0x3FF, 0, 0);
    	input_set_abs_params(s3c_ts_dev, ABS_Y, 0, 0x3FF, 0, 0);
    	input_set_abs_params(s3c_ts_dev, ABS_PRESSURE, 0, 1, 0, 0);
    
    
    	/* 3. 注册 */
    	input_register_device(s3c_ts_dev);
    
    	/* 4. 硬件相关的操作 */
    	/* 4.1 使能时钟(CLKCON[15]) */
    	/* 4.2 设置S3C2440的ADC/TS寄存器 */
    	return 0;
    }
    
    static void s3c_ts_exit(void)
    {
    	input_unregister_device(s3c_ts_dev);
    	input_free_device(s3c_ts_dev);
    }
    
    module_init(s3c_ts_init);
    module_exit(s3c_ts_exit);
    MODULE_LICENSE("GPL");
    

    2、分析内核自带触摸屏驱动

    先打开JZ2440开发板原理图,查看触摸屏的四根引脚(TSYM、TSYP、TSXM、TSXP)分别接在ADC的——AIN4、5、6、7上面。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AnJd88Vm-1627315066794)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210724163001.png)]

    通过S3C2440手册搜索发现,这些引脚不需要配置什么寄存器就可以使用。

    看一下内核自带的触摸屏驱动(drivers/input/touchscreen/s3c2410_ts.c),找到probe函数,看做了什么事情,

    ts.clock = clk_get(dev, "adc");
    if (IS_ERR(ts.clock)) {
    	dev_err(dev, "cannot get adc clock source\n");
    return -ENOENT;
    }
    
    clk_enable(ts.clock);
    

    这里有个使能时钟的函数:

    Tips:在内核启动的时候,为了省电,会把一些不相关的模块给关掉。

    怎么关?就是通过设置CLKCON寄存器或者是(Clock Gating Control Register),我们在要用任何模块之前必须把对应的位置1(打开模块时钟)。

    内核中,就是通过clk_getclk_enable使能模块时钟的。

    然后,再看芯片手册上的ADC和触摸屏接口那一章:

    	The 10-bit CMOS ADC (Analog to Digital Converter) is a recycling type device with 8-channel analog inputs. It converts the analog input signal into 10-bit binary digital codes at a maximum conversion rate of 500KSPS with 2.5MHz A/D converter clock. A/D converter operates with on-chip sample-and-hold function and power down mode is supported. 
    	Touch Screen Interface can control/select pads (XP, XM, YP, YM) of the Touch Screen for X, Y position conversion. Touch Screen Interface contains Touch Screen Pads control logic and ADC interface logic with an interrupt generation logic.
    

    ​ 这里说的就是这里面有个10位的mos ADC转换器,有8路信道。在ADC工作频率为2.5MHz时,最大转换频率是500KSPS,同时,因为Power Supply Voltage: 3.3V (最大输入电压是3.3v ),所以如果ADC的输入电压是3.3V的话,输出就是10个1(0x3ff),如果是0V的话,输出就是0。每个刻度就是3.3v/10位 10位的话就是1024 就是3.3V/1024 最小刻度是3mv

    然后是我们的ADC转换时间

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eY78GCUy-1627315066797)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210724170349.png)]

    如果PCLK是50MHZ,而ADC最大工作频率是2.5MZ,所以要设置分频系数,把这个频率给降低下来

    然后下面就是它提供的例子 the prescaler value is 49, ADC的工作频率就是A/D converter freq. = 50MHz/(49+1) = 1MHz .

    转换时间就需要Conversion time = 1/(1MHz / 5cycles) = 1/200kHz = 5us (1MHZ的5个周期 就是5us )

    • 再看下接口模式
    1. 正常的转换模式(Normal Conversion Mode):正常转换模式就是一般的ADC操作,比如说你想测量某个电压。

    2. 分离的xy坐标转换模式(Separate X/Y Position Conversion Mode) :这种模式分为两个部分。一种是测量X坐标,一种是测量Y坐标。

    进入X或Y坐标模式需要采取的措施是:

    ​ 1.设置0x69到TSCONn寄存器

    ​ 2.通过设置TSADCCONn开始转换

    ​ 3.X坐标转换结束后能被中断给通知

    ​ 4.从TSDATXn读出坐标转换数据

    1. 自动(连续)的XY转换模式(Auto (Sequential) X/Y Position Conversion Mode):当你进入这个模式之后,它会自动的帮你即转换x坐标也转换Y坐标

    2. 等待中断模式,就是等待按下产生中断模式:若想在我们按下触摸屏后让它产生中断,就要进入该模式。当触摸笔按下的时候,触摸屏会产生INT_PENn这个中断。

      • 怎么进入这个模式呢?设置rADCTSC=0xd3 就可以了。

    3、硬件相关代码编写

    3.1 使能时钟

    //函数体外定义全局变量clk
    struct clk* clk;
    
    /* 4.1 使能时钟(CLKCON[15]) */
    clk = clk_get(NULL, "adc");
    clk_enable(clk);
    

    3.2 设置S3C2440的ADC控制寄存器

    //函数体外定义寄存器结构体
    struct s3c_ts_regs {
    	unsigned long adccon;
    	unsigned long adctsc;
    	unsigned long adcdly;
    	unsigned long adcdat0;
    	unsigned long adcdat1;
    	unsigned long adcupdn;
    };
    
    //仅在本文件内使用的静态寄存器指针变量
    static volatile struct s3c_ts_regs *s3c_ts_regs;
    
    //在初始化函数内对寄存器结构体进行地址映射
    s3c_ts_regs = ioremap(0x58000000, sizeof(struct s3c_ts_regs));
    

    3.3 设置ADC控制寄存器

    • ADCCON

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ctM2H8cE-1627315066801)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210725154619.png)]

    PRESCEN(bit[14]) : =1 A/D converter prescaler enable

    PRSCVL(bit[13:6]): =49 A/D converter prescaler value,最大值为2.5MHz,在此我们取1MHz,所以ADCCLK=PCLK/(49+1)=50MHz/(49+1)=1MHz

    SEL_MUX(bit[5:3]):模拟信号输入信道选择,若为普通的AD转换,则可在此选择AIN0-3,我们先不设

    STDBM(bit[2]):省电模式选择位,默认为0,我们用的是非省电模式,所以在此可不用设置

    READ_START(bit[1]):通过读操作自动启动AD转换,我们在此不选该功能

    ENABLE_START(bit[0]): 通过置1手动开启AD转换(转换完成后自动清零),先设为0

    s3c_ts_regs->adccon = (1<<14)|(49<<6);
    

    3.4 编写触摸中断处理函数(pen_down_up_irq)并注册中断(INT_TC)

    当触摸屏被按下时,AD转换进入等待中断模式(通过设置ADCTSC=0xd3)

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2KqNOrul-1627315066804)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210725165524.png)]

    等待中断模式等效电路

    当触摸屏没有被按下时,由于上拉电阻的原因,Y_ADC处于高电平状态;当被按下时,Y_ADC的电压由于联通到y轴接地而变为低电平,此低电平就是中断的触发信号,使之产生pen down事件。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zYCIWAa7-1627315066809)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210725202306.png)]

    Tips

    左边的图为读取x坐标时的等效电路图:测X_ADC时,S1(XP_SEN Enable)、S2(YP_SEN Disable)、S3(XM_SEN Enable),S4(YM_SEN Disable)、S5(PULL_UP Disable);

    测Y_ADC时,S1(XP_SEN Disable)、S2(YP_SEN Enable)、S3(XM_SEN Disable断开,S4(YM_SEN Enable)、S5(PULL_UP Disable)接通;

    //调用该函数进入等待触摸屏按下模式
    static void enter_wait_pen_down_mode(void)
    {
    	s3c_ts_regs->adctsc = 0xd3;
    }
    
    //调用该函数进入等待触摸屏松开模式
    static void enter_wait_pen_up_mode(void)
    {
    	s3c_ts_regs->adctsc = 0x1d3;
    }
    
    //触摸屏中断处理函数V1.0
    static irqreturn_t pen_down_up_irq(int irq, void *dev_id)
    {
    	if (s3c_ts_regs->adcdat0 & (1<<15))	//Stylus up 松开
    	{
    		printk("pen up\n");
    		enter_wait_pen_down_mode();	//送开的话,就进入等待下次按下的模式
    	}
    	else	//Stylus down 按下
    	{		
            printk("pen down\n");
    		enter_wait_pen_up_mode();	//按下的话,就进入等待松开的模式	
    	}
        
    	return IRQ_HANDLED;
    }
    

    在初始化函数中,注册中断处理函数:

    request_irq(IRQ_TC, pen_down_up_irq, IRQF_SAMPLE_RANDOM, "ts_pen", NULL);
    

    3.5 编写退出函数

    static void s3c_ts_exit(void)
    {
    	free_irq(IRQ_TC, NULL);
    	iounmap(s3c_ts_regs);
    	input_unregister_device(s3c_ts_dev);
    	input_free_device(s3c_ts_dev);
    }
    

    4、测试

    4.1 编译无触摸屏驱动的内核映像文件

    //虚拟机(上位机)命令行下
    cd linux3.4.2
    make menuconfig	//去掉原来的LCD驱动程序
    make uImage
    

    依次进入:

    • Device Drivers——>
      • Input device support——>
        • Touchscreens——>
          • < > S3C2410/S3C2440 touchscreens

    4.2 使用新内核启动并加载自己的触摸屏驱动

    //Uboot命令行
    tftp 30000000 uImage 或者 nfs 30000000 虚拟机IP:网络文件系统目录/uImage
    bootm 30000000
    
    //新内核启动后的命令行下
    insmod s3c_ts.ko
    

    按下/松开触摸屏进行测试

    //新内核启动后的命令行下
    pen down
    pen up
    #
    #
    pen down
    pen up
    

    5、拓展——按下时启动ADC,显示坐标

    修改触摸屏中断处理函数,当触摸屏被按下时,进入TC中断处理函数,自动测量模式并启动ADC转换;

    AD转换完成后产生ADC中断,并在中断处理函数中完成相应任务(例如显示x、y点坐标)后,进入等待触摸屏松开模式。

    5.1 修改触摸屏(TC)中断处理函数

    //触摸屏中断处理函数V2.0
    static irqreturn_t pen_down_up_irq(int irq, void *dev_id)
    {
    	if (s3c_ts_regs->adcdat0 & (1<<15))	//Stylus up 松开
    	{
    		printk("pen up\n");			//触摸屏被松开
    		enter_wait_pen_down_mode();	//松开的话,就进入等待下次按下的模式
    	}
    	else	//Stylus down 按下
    	{		
            //printk("pen down\n");
    		//enter_wait_pen_up_mode();	
            enter_measure_xy_mode();	//按下的话,就进入测量的模式
    		start_adc();				//启动ADC转换
            enter_wait_pen_up_mode();	//进入等待触摸屏被松开
    	}
        
    	return IRQ_HANDLED;
    }
    

    5.2 编辑进入自动测量模式函数

    static void enter_measure_xy_mode(void)
    {
    	s3c_ts_regs->adctsc = (1<<3)|(1<<2);	//断开上拉电阻,打开AUTO_PST
    }
    

    5.3 编辑启动ADC函数

    static void start_adc(void)
    {
    	s3c_ts_regs->adccon |= (1<<0);
    }
    

    5.4 编辑ADC中断处理函数

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vq6HXUS1-1627315066810)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210725203338.png)]

    //在初始化文件中编写ADC中断处理函数
    static irqreturn_t adc_irq(int irq, void *dev_id)
    {
       static int cnt = 0;
       printk("adc_irq cnt = %d, x = %d, y = %d\n", ++cnt, adcdat0 & 0x3ff, adcdat1 & 0x3ff);
       return IRQ_HANDLED;
    }
    

    5.5 注册ADC中断处理函数

    //在初始化函数中对ADC中断进行注册
    request_irq(IRQ_ADC, adc_irq, IRQF_SAMPLE_RANDOM, "adc", NULL);
    

    5.6 修改退出函数

    static void s3c_ts_exit(void)
    {
    	free_irq(IRQ_TC, NULL);
        free_irq(IRQ_ADC, NULL);
    	iounmap(s3c_ts_regs);
    	input_unregister_device(s3c_ts_dev);
    	input_free_device(s3c_ts_dev);
    }
    

    5.7 测试

    修改完成后,重新编译生成s3c_tc.ko驱动文件,下载安装后测试:

    rmmod s3c_tc	//卸载旧驱动
    insmod s3c_tc.ko	//安装新驱动
    
    //碰触触摸屏
    #
    #
    adc_irq cnt = 1, x = 330, y = 667
    pen_up
    adc_irq cnt = 2, x = 239, y = 739
    pen_up
    adc_irq cnt = 3, x = 215, y = 779
    #
    #
    

    测试发现:当我们滑动触摸屏时,其输出不能连续显示。且输出的坐标也不是太精确!

    6、优化——支持滑动操作、坐标更精确

    6.1 使电压稳定后再进行转换——设置ADC延时

    //修改初始化函数,添加ADCDLY延时
    static int s3c_ts_init(void)
    {
    	struct clk* clk;
    	
    	/* 1. 分配一个input_dev结构体 */
    	s3c_ts_dev = input_allocate_device();
    
    	/* 2. 设置 */
    	/* 2.1 能产生哪类事件 */
    	set_bit(EV_KEY, s3c_ts_dev->evbit);
    	set_bit(EV_ABS, s3c_ts_dev->evbit);
    
    	/* 2.2 能产生这类事件里的哪些事件 */
    	set_bit(BTN_TOUCH, s3c_ts_dev->keybit);
    
    	input_set_abs_params(s3c_ts_dev, ABS_X, 0, 0x3FF, 0, 0);
    	input_set_abs_params(s3c_ts_dev, ABS_Y, 0, 0x3FF, 0, 0);
    	input_set_abs_params(s3c_ts_dev, ABS_PRESSURE, 0, 1, 0, 0);
    
    
    	/* 3. 注册 */
    	input_register_device(s3c_ts_dev);
    
    	/* 4. 硬件相关的操作 */
    	/* 4.1 使能时钟(CLKCON[15]) */
    	clk = clk_get(NULL, "adc");
    	clk_enable(clk);
    	
    	/* 4.2 设置S3C2440的ADC/TS寄存器 */
    	s3c_ts_regs = ioremap(0x58000000, sizeof(struct s3c_ts_regs));
    
    	/* bit[14]  : 1-A/D converter prescaler enable
    	 * bit[13:6]: A/D converter prescaler value,
    	 *            49, ADCCLK=PCLK/(49+1)=50MHz/(49+1)=1MHz
    	 * bit[0]: A/D conversion starts by enable. 先设为0
    	 */
    	s3c_ts_regs->adccon = (1<<14)|(49<<6);
    
    	request_irq(IRQ_TC, pen_down_up_irq, IRQF_SAMPLE_RANDOM, "ts_pen", NULL);
    	request_irq(IRQ_ADC, adc_irq, IRQF_SAMPLE_RANDOM, "adc", NULL);
    
    	/* 优化措施1: 
    	 * 设置ADCDLY为最大值, 这使得电压稳定后再发出IRQ_TC中断
    	 */
    	s3c_ts_regs->adcdly = 0xffff;
    
    	enter_wait_pen_down_mode();
    	
    	return 0;
    }
    

    6.2 多次ADC转换,求平均值

    从按下至松开的这段期间,进行多次AD转换,并求其平均数。

    static irqreturn_t adc_irq(int irq, void *dev_id)
    {
    	static int cnt = 0;
    	static int x[4], y[4];
    	int adcdat0, adcdat1;
    	
    	/* 优化措施2: 如果ADC完成时, 发现触摸笔已经松开, 则丢弃此次结果 */
    	adcdat0 = s3c_ts_regs->adcdat0;
    	adcdat1 = s3c_ts_regs->adcdat1;
    
    	if (s3c_ts_regs->adcdat0 & (1<<15))	/* 已经松开 */
    	{
            cnt = 0;
    		enter_wait_pen_down_mode();
    	}
    	else
    	{
    		/* 优化措施3: 多次测量求平均值 */
    		x[cnt] = adcdat0 & 0x3ff;
    		y[cnt] = adcdat1 & 0x3ff;
    		++cnt;
    		if (cnt == 4)	//如果测量次数已经到了4次,则打印出平均值,并等待触摸屏被松开
    		{
    			printk("x = %d, y = %d\n", (x[0]+x[1]+x[2]+x[3])/4, (y[0]+y[1]+y[2]+y[3])/4);
                cnt = 0;
                enter_wait_pen_up_mode();				
    		}
    		else	//如果测量次数不够4次,则再度启动adc转换
    		{
    			enter_measure_xy_mode();
    			start_adc();
    		}		
    	}
    	
    	return IRQ_HANDLED;
    }
    

    6.3 添加软件过滤功能

    6.3.1 编写软件过滤函数

    把四次测量的值放入数组中,从头至尾方向,依次使前两项的平均值和第3项的值进行比较,若绝对值在允许误差范围内,则表示过滤成功,返回1,否则失败,返回0

    static int s3c_filter_ts(int x[], int y[])
    {
    #define ERR_LIMIT 10
    
    	int avr_x, avr_y;
    	int det_x, det_y;
    
    	avr_x = (x[0] + x[1])/2;
    	avr_y = (y[0] + y[1])/2;
    
    	det_x = (x[2] > avr_x) ? (x[2] - avr_x) : (avr_x - x[2]);
    	det_y = (y[2] > avr_y) ? (y[2] - avr_y) : (avr_y - y[2]);
    
    	if ((det_x > ERR_LIMIT) || (det_y > ERR_LIMIT))
    		return 0;
    
    	avr_x = (x[1] + x[2])/2;
    	avr_y = (y[1] + y[2])/2;
    
    	det_x = (x[3] > avr_x) ? (x[3] - avr_x) : (avr_x - x[3]);
    	det_y = (y[3] > avr_y) ? (y[3] - avr_y) : (avr_y - y[3]);
    
    	if ((det_x > ERR_LIMIT) || (det_y > ERR_LIMIT))
    		return 0;
    	
    	return 1;
    }
    

    6.3.2 修改adc中断处理函数

    static irqreturn_t adc_irq(int irq, void *dev_id)
    {
    	static int cnt = 0;
    	static int x[4], y[4];
    	int adcdat0, adcdat1;
    	
    	
    	/* 优化措施2: 如果ADC完成时, 发现触摸笔已经松开, 则丢弃此次结果 */
    	adcdat0 = s3c_ts_regs->adcdat0;
    	adcdat1 = s3c_ts_regs->adcdat1;
    
    	if (s3c_ts_regs->adcdat0 & (1<<15))
    	{
    		/* 已经松开 */
    		cnt = 0;
    		enter_wait_pen_down_mode();
    	}
    	else
    	{
    		/* 优化措施3: 多次测量求平均值 */
    		x[cnt] = adcdat0 & 0x3ff;
    		y[cnt] = adcdat1 & 0x3ff;
    		++cnt;
    		if (cnt == 4)
    		{
    			/* 优化措施4: 软件过滤 */
    			if (s3c_filter_ts(x, y))
    			{			
    				printk("x = %d, y = %d\n", (x[0]+x[1]+x[2]+x[3])/4, (y[0]+y[1]+y[2]+y[3])/4);
    			}
    			cnt = 0;
    			enter_wait_pen_up_mode();
    		}
    		else
    		{
    			enter_measure_xy_mode();
    			start_adc();
    		}		
    	}
    	
    	return IRQ_HANDLED;
    }
    

    6.4 支持滑动操作

    6.4.1 定义一个定时器

    static struct timer_list ts_timer;
    

    6.4.2 修改初始化函数,添加定时器

    static int s3c_ts_init(void)
    {
    	struct clk* clk;
    	
    	/* 1. 分配一个input_dev结构体 */
    	s3c_ts_dev = input_allocate_device();
    
    	/* 2. 设置 */
    	/* 2.1 能产生哪类事件 */
    	set_bit(EV_KEY, s3c_ts_dev->evbit);
    	set_bit(EV_ABS, s3c_ts_dev->evbit);
    
    	/* 2.2 能产生这类事件里的哪些事件 */
    	set_bit(BTN_TOUCH, s3c_ts_dev->keybit);
    
    	input_set_abs_params(s3c_ts_dev, ABS_X, 0, 0x3FF, 0, 0);
    	input_set_abs_params(s3c_ts_dev, ABS_Y, 0, 0x3FF, 0, 0);
    	input_set_abs_params(s3c_ts_dev, ABS_PRESSURE, 0, 1, 0, 0);
    
    
    	/* 3. 注册 */
    	input_register_device(s3c_ts_dev);
    
    	/* 4. 硬件相关的操作 */
    	/* 4.1 使能时钟(CLKCON[15]) */
    	clk = clk_get(NULL, "adc");
    	clk_enable(clk);
    	
    	/* 4.2 设置S3C2440的ADC/TS寄存器 */
    	s3c_ts_regs = ioremap(0x58000000, sizeof(struct s3c_ts_regs));
    
    	/* bit[14]  : 1-A/D converter prescaler enable
    	 * bit[13:6]: A/D converter prescaler value,
    	 *            49, ADCCLK=PCLK/(49+1)=50MHz/(49+1)=1MHz
    	 * bit[0]: A/D conversion starts by enable. 先设为0
    	 */
    	s3c_ts_regs->adccon = (1<<14)|(49<<6);
    
    	request_irq(IRQ_TC, pen_down_up_irq, IRQF_SAMPLE_RANDOM, "ts_pen", NULL);
    	request_irq(IRQ_ADC, adc_irq, IRQF_SAMPLE_RANDOM, "adc", NULL);
    
    	/* 优化措施1: 
    	 * 设置ADCDLY为最大值, 这使得电压稳定后再发出IRQ_TC中断
    	 */
    	s3c_ts_regs->adcdly = 0xffff;
    
    	/* 优化措施5: 使用定时器处理长按,滑动的情况
    	 * 
    	 */
    	init_timer(&ts_timer);
    	ts_timer.function = s3c_ts_timer_function;
    	add_timer(&ts_timer);	//在出口函数中要删除它
    
    	enter_wait_pen_down_mode();
    	
    	return 0;
    }
    

    6.4.3 修该ADC中断处理函数,添加启动定时器处理长按、滑动的情况

    添加的代码,见优化措施5:

    static irqreturn_t adc_irq(int irq, void *dev_id)
    {
    	static int cnt = 0;
    	static int x[4], y[4];
    	int adcdat0, adcdat1;
    	
    	
    	/* 优化措施2: 如果ADC完成时, 发现触摸笔已经松开, 则丢弃此次结果 */
    	adcdat0 = s3c_ts_regs->adcdat0;
    	adcdat1 = s3c_ts_regs->adcdat1;
    
    	if (s3c_ts_regs->adcdat0 & (1<<15))
    	{
    		/* 已经松开 */
    		cnt = 0;
    		input_report_abs(s3c_ts_dev, ABS_PRESSURE, 0);
    		input_report_key(s3c_ts_dev, BTN_TOUCH, 0);
    		input_sync(s3c_ts_dev);
    		enter_wait_pen_down_mode();
    	}
    	else
    	{
    		// printk("adc_irq cnt = %d, x = %d, y = %d\n", ++cnt, adcdat0 & 0x3ff, adcdat1 & 0x3ff);
    		/* 优化措施3: 多次测量求平均值 */
    		x[cnt] = adcdat0 & 0x3ff;
    		y[cnt] = adcdat1 & 0x3ff;
    		++cnt;
    		if (cnt == 4)
    		{
    			/* 优化措施4: 软件过滤 */
    			if (s3c_filter_ts(x, y))
    			{			
    				printk("x = %d, y = %d\n", (x[0]+x[1]+x[2]+x[3])/4, (y[0]+y[1]+y[2]+y[3])/4);
    			}
    			cnt = 0;
    			enter_wait_pen_up_mode();
    
    			/* 优化措施5:启动定时器处理长按/滑动的情况 */
    			mod_timer(&ts_timer, jiffies + HZ/100);	//添加一个10ms的定时器,时间到了就进入定时器处理函数
    		}
    		else
    		{
    			enter_measure_xy_mode();
    			start_adc();
    		}		
    	}
    	
    	return IRQ_HANDLED;
    }
    

    6.4.4 编辑定时器处理函数

    当10ms的定时器已经到了的话,

    static void s3c_ts_timer_function(unsigned long data)
    {
    	if (s3c_ts_regs->adcdat0 & (1<<15))	/* 如果已经松开,则等待下次按下 */
    	{
    		enter_wait_pen_down_mode();
    	}
    	else
    	{
    		/* 测量X/Y坐标 */
    		enter_measure_xy_mode();
    		start_adc();
    	}
    }
    

    6.4.5 修改出口函数

    static void s3c_ts_exit(void)
    {
    	free_irq(IRQ_TC, NULL);
    	free_irq(IRQ_ADC, NULL);
    	iounmap(s3c_ts_regs);
    	input_unregister_device(s3c_ts_dev);
    	input_free_device(s3c_ts_dev);
    	del_timer(&ts_timer);		//删除定时器
    }
    

    6.4.6 编译测试

    重新编译安装驱动后,使用触摸屏滑动或长按,观察输出数据:坐标可以连续输出!

    7、将printk函数替换成上报事件,得到完整触摸屏驱动

    7.1 修改adc中断处理函数

    注释掉printk函数,添加上报事件语句:

    static irqreturn_t adc_irq(int irq, void *dev_id)
    {
    	static int cnt = 0;
    	static int x[4], y[4];
    	int adcdat0, adcdat1;
    	
    	
    	/* 优化措施2: 如果ADC完成时, 发现触摸笔已经松开, 则丢弃此次结果 */
    	adcdat0 = s3c_ts_regs->adcdat0;
    	adcdat1 = s3c_ts_regs->adcdat1;
    
    	if (s3c_ts_regs->adcdat0 & (1<<15))
    	{
    		/* 已经松开 */
    		cnt = 0;
    		input_report_abs(s3c_ts_dev, ABS_PRESSURE, 0);	//压力为0
    		input_report_key(s3c_ts_dev, BTN_TOUCH, 0);		//无触摸屏按键类事件
    		input_sync(s3c_ts_dev);
    		enter_wait_pen_down_mode();
    	}
    	else
    	{
    		// printk("adc_irq cnt = %d, x = %d, y = %d\n", ++cnt, adcdat0 & 0x3ff, adcdat1 & 0x3ff);
    		/* 优化措施3: 多次测量求平均值 */
    		x[cnt] = adcdat0 & 0x3ff;
    		y[cnt] = adcdat1 & 0x3ff;
    		++cnt;
    		if (cnt == 4)
    		{
    			/* 优化措施4: 软件过滤 */
    			if (s3c_filter_ts(x, y))
    			{			
    				//printk("x = %d, y = %d\n", (x[0]+x[1]+x[2]+x[3])/4, (y[0]+y[1]+y[2]+y[3])/4);
    				input_report_abs(s3c_ts_dev, ABS_X, (x[0]+x[1]+x[2]+x[3])/4);
    				input_report_abs(s3c_ts_dev, ABS_Y, (y[0]+y[1]+y[2]+y[3])/4);
                    //此处仅需表示松开或按下两种压力状态,若是绘图板,则会有多个压力值
    				input_report_abs(s3c_ts_dev, ABS_PRESSURE, 1);
    				input_report_key(s3c_ts_dev, BTN_TOUCH, 1);		//触摸屏按键类事件
    				input_sync(s3c_ts_dev);
    			}
    			cnt = 0;
    			enter_wait_pen_up_mode();
    
    			/* 启动定时器处理长按/滑动的情况 */
    			mod_timer(&ts_timer, jiffies + HZ/100);
    		}
    		else
    		{
    			enter_measure_xy_mode();
    			start_adc();
    		}		
    	}
    	
    	return IRQ_HANDLED;
    }
    

    7.2 修改TC中断处理函数

    static irqreturn_t pen_down_up_irq(int irq, void *dev_id)
    {
    	if (s3c_ts_regs->adcdat0 & (1<<15))
    	{
    		//printk("pen up\n");
    		input_report_abs(s3c_ts_dev, ABS_PRESSURE, 0);
    		input_report_key(s3c_ts_dev, BTN_TOUCH, 0);
    		input_sync(s3c_ts_dev);
    		enter_wait_pen_down_mode();
    	}
    	else
    	{
    		//测量xy坐标
    		enter_measure_xy_mode();
    		start_adc();
    	}
    	return IRQ_HANDLED;
    }
    

    7.3 编译测试

    7.3.1 使用hexdump命令测试

    rmmod s3c_tc
    ls /dev/event*	//安装新驱动前查看原来有哪些
    ls:/dev/event*:No such file or directory
    insmod s3c_tc.ko
    ls /dev/event*	//安装完新驱动后,查看新增加的是哪个event?
    /dev/event0
    
    hexdump /dev/event0
    

    输出如下:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MtcvUt1t-1627315066811)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210725225531.png)]

    其输出结果的前4行分别对应adc_irq处理函数中的4个上报事件:

    input_report_abs(s3c_ts_dev, ABS_X, (x[0]+x[1]+x[2]+x[3])/4);
    input_report_abs(s3c_ts_dev, ABS_Y, (y[0]+y[1]+y[2]+y[3])/4);
    input_report_abs(s3c_ts_dev, ABS_PRESSURE, 1);				input_report_key(s3c_ts_dev, BTN_TOUCH, 1);	
    

    前面的秒和微妙代表后面事件发生的时间。

    第一行代表上报事件input_report_abs(s3c_ts_dev, ABS_X, (x[0]+x[1]+x[2]+x[3])/4);

    type:代表EV_ABS宏的取值为3,即绝对位移;

    code:代表ABS_X宏的取值为0,即x方向;

    value:4个字节表示x坐标值。

    第4行代表上报事件input_report_key(s3c_ts_dev, BTN_TOUCH, 1);

    type:代表EV_ABS宏的取值为1,即按键;

    code:代表ABS_X宏的取值为014a,即BTN_TOUCH;

    value:4个字节表示按键值1。

    7.3.2 使用tslib测试

    • 安装tslib
    tar xzf tslib-1.4.tar.gz
    
    cd tslib
    
    ./autogen.sh
    提示错误:./autogen.sh: 4: autoreconf: not found
    解决方法:sudo apt-get install autoconf automake libtool
    
    mkdir tmp
    
    echo "ac_cv_func_malloc_0_nonnull=yes" >arm-linux.cache
    
    ./configure --host=arm-linux --cache-file=arm-linux.cache --prefix=$(pwd)/tmp
    
    make
    
    make install
    
    • 复制tmp目录下的所有文件到开发板的根目录中
    cp tmp  /nfs_root/first_fs/ts_dir -rfd
    cd /mnt/ts_dir
    cp * / -rfd
    
    • 安装驱动程序
    # insmod cfbcopyarea.ko
    # insmod cfbfillrect.ko
    # insmod cfbimgblt.ko
    # insmod s3c_lcd.ko	//也可以使用内核自带的LCD驱动
    					//如果发生段错误,则重新编译lcd驱动后,再加载
    # ls /dev/ev*
    ls:/dev/event*:No such file or directory
    # insmod s3c_ts.ko
    # ls /dev/ev*
    /dev/event0			//这是触摸屏设备
    #
    # ls /dev/fb0		//这是LCD设备
    					
    
    • 测试

      • 修改/etc/ts.conf第一行(去掉#号和第一个空格)
      # module_raw input
      改为:
      module_raw input
      
      • 添加环境变量
      # export TSLIB_TSDEVICE=/dev/event0		//指定tc触摸屏设备
      # export TSLIB_CALIBFILE=/etc/pointercal	//校验文件
      # export TSLIB_CONFFILE=/etc/ts.conf		//配置文件
      # export TSLIB_PLUGINDIR=/lib/ts			//插件
      # export TSLIB_CONSOLEDEVICE=none
      # export TSLIB_FBDEVICE=/dev/fb0			//指定lcd显示屏
      
      # ts_calibrate							//开始校验
      错误提示:selected device is not a touchscreen I understand
      

    image-20210726233245064

    • 解决办法:看到这个错误的提示,我们应该想到tslib源码里面肯定有这个错误提示的条件,这样就让我们有了思路,我们可以去找到这个文件看看他错误判断的条件是啥?

      • 查找“selected device is not a touchscreen I understand”出处:
      cd tslib/plugins
      grep -nr "selected device is not a touchscreen I understand"
      匹配到二进制文件 .libs/input.so
      匹配到二进制文件 .libs/input-raw.o
      input-raw.c:61:		fprintf(stderr, "selected device is not a touchscreen I understand\n");
      

      可以看出,该错误提示语句出自input-raw.c之手!

      • 打开input-raw.c文件,找出相关段落
       47 static int check_fd(struct tslib_input *i)
       48 {
       49     struct tsdev *ts = i->module.dev;
       50     int version;
       51     u_int32_t bit;
       52     u_int64_t absbit;
       53 
       54     if (! ((ioctl(ts->fd, EVIOCGVERSION, &version) >= 0) &&
       55         (version == EV_VERSION) &&
       56         (ioctl(ts->fd, EVIOCGBIT(0, sizeof(bit) * 8), &bit) >= 0) &&
       57         (bit & (1 << EV_ABS)) &&
       58         (ioctl(ts->fd, EVIOCGBIT(EV_ABS, sizeof(absbit) * 8), &absbit) >= 0) &&
       59         (absbit & (1 << ABS_X)) &&
       60         (absbit & (1 << ABS_Y)) && (absbit & (1 << ABS_PRESSURE)))) {
       61         fprintf(stderr, "selected device is not a touchscreen I understand\n");
       62         return -1;
       63     }
       64 
       65     if (bit & (1 << EV_SYN))
       66         i->using_syn = 1;
       67 
       68     return 0;
       69 }
      

      tslib通过EVIOCGVERSION来获取驱动的版本号,然后再通过EVIOCGBIT来判断设备是否为触摸屏,最后获取触摸屏的X轴(ABS_X),Y轴(ABS_Y),以及压力(ABS_PRESSURE)。

      其中只要有一项内容不正确,tslib都会认为该设备不是触摸屏,而打印出“selecteddevice is not a touchscreen I understand”错误。

      EVIOCGBIT ioctl处理 4个参数 ( ioctl(fd, EVIOCGBIT(ev_type, max_bytes), bitfield))。 ev_type是返回的 type feature( 0是个特殊 case,表示返回设备支持的所有的type features)。 max_bytes表示返回的最大字节数。 bitfield域是指向保存结果的内存指针。 return value表示保存结果的实际字节数,如果调用失败,则返回负值。

      这里用bit和EV_ABS相与来判断是不是EV_ABS事件,判断是这个事件后再判断这个事件是否又X,Y轴和压力值。
      image-20210726233040736

      • 核查内核(linux-3.4.2/include/linux/input.h)与编译tslib的编译器(/usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include/linux/input.h)中的EV_VERSION版本号是否一致。

      内核中的定义为: #define EV_VERSION 0x010001

      编译器中的定义: #define EV_VERSION 0x010000

      两者不等,所以只要将内核中的版本号改为0x010000再重新编译内核,或者将编译器中的改为0x010001,然后再重新编译tslib库也行。

      **另外:**若在触摸屏驱动程序中没有同时上报坐标值和压力值也会引起类似的错误提醒。因为tslib同时判断的几个条件必须同时满足,方才不会报错。所以不管你的应用程序需不需要测试压力值,在你的触摸屏驱动程序中都要上报压力值。即:

      input_report_abs(s3c_ts_dev, ABS_X, x);
      input_report_abs(s3c_ts_dev, ABS_Y, y);
      input_report_abs(s3c_ts_dev, ABS_PRESSURE, 1);
      input_report_key(s3c_ts_dev, BTN_TOUCH, 1);
      input_sync(s3c_ts_dev);
      

      还有一点需要提醒的是,在设置tslib环境变量的时候,一定要正确设置触摸屏(例如:event0)和显示屏(例如:fb0)的文件名,特别是当内核中安装了多个event事件的时候。

      • 测试继续
      # ts_calibrate	
      xres = 480, yres = 272					//屏幕出现十字线,用于点击校验
      Took 3 samples...
      Top left : X = 713 Y = 806
      。。。
      Center : X = 477 Y = 524
      ...
      Calibration constant:16972928 -18997 -162 ...
      
      # ls /etc/pointercal
       /etc/pointercal
       
      # ts_test			//十字架会随着你的手指在触摸屏上移动。
       215.787878:	124	234	1
       215.782448:	102	219	0
       。。。
       
      # ts_			//按下tab命令补全键,显示所有可执行测试命令
       ts_harvest		ts_print_raw//打印触摸点原始坐标
       ts_calibrate	ts_print//打印触摸点像素坐标
       ts_test
      
    展开全文
  • 描述由于智能手机的发展和大屏幕的兴起,触摸屏已经得到了广泛的应用。触摸屏分为两种:电阻触屏 俗称“软屏”;电容触屏俗称“硬屏”。电阻触摸屏的屏体部分是一块多层复合薄膜,由一层玻璃或有机玻璃作为基层,...

    描述

    由于智能手机的发展和大屏幕的兴起,触摸屏已经得到了广泛的应用。触摸屏分为两种:电阻触屏 俗称“软屏”;电容触屏俗称“硬屏”。

    电阻触摸屏的屏体部分是一块多层复合薄膜,由一层玻璃或有机玻璃作为基层,表面涂有一层透明的阻性材料组成的导电层(ITO膜),上面再盖有一层外表面经过硬化处理、光滑防刮的塑料层。它的内表面也涂有一层ITO,在两层导电层之间有许多细小(小于千分之一英寸)的透明隔离点把它们隔开。当手指接触屏幕时,两层 ITO发生接触,电阻发生变化,控制器根据检测到的电阻变化来计算接触点的坐标,再依照这个坐标来进行相应的操作,因此这种技术必须是要施力到屏幕上,才能获得触摸效果。

    f5c0a41fc5916cfa27182ef0fb84b6c7.png

    所有的电阻式触摸屏都采用分压器原理来产生代表X坐标和Y坐标的电压。

    分压器是通过将两个电阻进行串联来实现的。

    上面的电阻(R1)连接正参考电压(VREF),

    下面的电阻(R2)接地。

    两个电阻连接点处的电压测量值与下面那个电阻的阻值成正比。

    所以电阻屏的定位是通过AD采样获取电压,来确定点击位置的。

    电容式触摸屏利用人体的电流感应进行工作,其触摸屏由一块四层复合玻璃屏构成。当手指触摸在触摸屏上时,由于人体电场、用户和触摸屏表面形成以一个耦合电容,对于高频电流来说,电容是直接导体,于是手指从接触点吸走一个很小的电流。这个电流分别从触摸屏四角上的电极中流出,并且流经这四个电极的电流与手指到四角的距离成正比,控制器通过对这四个电流比例的精确计算,得出触摸点的位置信息。

    两种屏幕都有其优缺点。电阻屏价格低廉,精度较高。电容屏外层可以使用玻璃,抗损性好,不容易出现误操作,可以实现多点触控。

    这里驱动的是驱动IC为XPT2046的4线电阻式触摸屏,触摸屏的控制芯片很多,包括ADS7843、ADS7846、TSC2046、AK4182等。这几款芯片的驱动程序基本上都是一样的,而且封装也有一样的。

    XPT2046是一款4导线制触摸屏控制器,内含12位分辨率125Khz转换速率逐步逼近型A/D转换器。XPT2046支持从1.5V~5.25V的低电压I/O接口。XPT2046能通过执行两次A/D转换查出被按下的屏幕位置,还可以测量出加在屏幕上的压力大小。内部自带2.5V参考电压可以作为辅助输入,温度测量和电池检测模式之用。XPT2046片内还集成一个温度传感器。

    这里使用STM32驱动XPT2046和LCD实现一个触摸检测和字符显示的功能,当检测到特定位置” Click“按下后,显示一个hello world 。.

    XPT2046驱动: (LCD的驱动参见:STM32 驱动 TFT LCD):

    xpt2046.cxpt2046.h

    触摸屏的触摸检测是通过连接XPT2046的引脚,映射为外部中断触发引脚,在有屏幕触摸时,触发外部中断,所以需要配置一个外部中断函数:

    stm32f10x_it.c

    #include“stm32f10x_it.h”#include“xpt2046.h”//中断,检测到PEN脚的一个下降沿。//置位Pen_Point.Key_Sta为按下状态//中断线0线上的中断检测voidEXTI1_IRQHandler(void){Pen_Point.Key_Sta=Key_Down;//按键按下EXTI-》PR=1《《1;//清除中断标志位使用库函数会出现问题,原因不详//EXTI_ClearITPendingBit(EXTI_Line1);//这两句清除中断顺序不能颠倒//EXTI_ClearFlag(EXTI_Line1);//清除LINE1上的中断标志位}

    在mian()函数中调用相关函数:

    intmain(void){//STM32初始化RCC_Configuration();NVIC_Configuration();USART_Configuration();SPI_Configuration();EXTI_Configuration();GPIO_Configuration();LCD_Init();Touch_Init();POINT_COLOR=BLACK;//设置字体颜色Delay(100000);LCD_ShowString(180,0,“Click”);//显示“Click”字符按钮while(1){if(Pen_Point.Key_Sta==Key_Down)//触摸屏被按下{Pen_Int_Set(0);//关闭中断do{Convert_Pos();Pen_Point.Key_Sta=Key_Up;if(Pen_Point.X0》180&&Pen_Point.Y0《16)//判定按下的是否是“Click”字符按钮区域{LCD_Clear(WHITE);LCD_ShowString(3050,“HelloDog.。”);}}while(TOUCH_PEN==0);//如果PEN一直有效,则一直执行Pen_Int_Set(1);//开启中断}}}

    打开APP阅读更多精彩内容

    点击阅读全文

    展开全文
  • 一、电容式触摸屏检测原理 基本原理是利用充电时间检测电容大小,从而通过检测出电容值的变化来获知触摸信号。电容屏的最上层是玻璃(不会像电阻屏那样形变),核心层部分也是由ITO材料构成的,这些导电材料在屏幕里...
  • 电阻触摸屏驱动

    2020-01-09 20:56:54
    如下图所示,该开发板使用的是4线触摸屏,方框标记的引脚是专门用来接收模拟输入信号。  引脚说明:  YM: (Y Minus)触摸屏的Y坐标的负线,也可以用Y -表示  YP : (Y Power)触摸屏的Y坐标的正线, 也可以用Y+表示  ...
  • 关于 触摸屏驱动,其实也是input输入子系统,只不过是一升级版罢了。 我所说的都是基于韦老大所说,再加上自己的理解。这里关于输入子系统再做一些补充: —————–入子系统体系————— 设备事件层: ...
  • 6.Linux-触摸屏驱动

    2018-12-27 22:53:02
    1.先来回忆之前第12节分析的输入子系统 其中输入子系统层次如下图所示,   其中事件处理层的函数都是通过input_register_handler()函数... ... 所以最终如下图所示: ...右边的驱动事件处理,内核是已经写好了的,所以我...
  • ​ 电容多点触摸屏驱动由以下种类型的驱动组成: IIC 设备驱动,电容触摸 IC 基本都是 IIC 接口。 通过中断引脚 (INT) 向内核上报触摸信息,坐标的上报在中断服务函数中完成。 触摸屏的坐标信息、屏幕按下...
  • FSMC驱动TFT显示屏(和驱动触摸屏

    千次阅读 2019-05-28 16:04:50
    触摸屏,其驱动芯片为FT6336 一、显示屏 首先驱动显示屏,买到屏幕之后问卖家拿到驱动程序、原理图和技术文档。 这个屏幕有七种驱动方式: 表一 TFT驱动方式 MIPI-DBI Operating Mode ...
  • linux 触摸屏驱动介绍

    2021-01-11 12:24:22
    本节的触摸屏驱动也是使用之前的输入子系统 1.先来回忆之前第12节分析的输入子系统 其中输入子系统层次如下图所示, 其中事件处理层的函数都是通过input_register_handler()函数注册到input_handler_list链表中 搜索...
  • 这里主要说触摸屏的工作原理,以及对上述驱动程序代码的简单分析。分析中参考了网上的很多资料。感谢原作者的无私奉献,因为涉及多篇,在此就没有注出原作的链接。本文分为三部分,第一部分讲叙硬件知识,包括触摸...
  • 本帖最后由 QIANLILI 于 2018-4-14 10:35 编辑TFT液晶驱动建议最好用低压的单片机如果要用C51的最好用STC8F系列。宽电压。目前性价比也不错。另外,楼主贴出的只是液晶的初始化程序。 你还缺完整显示代码。 ...
  • 触摸屏驱动编写

    2020-03-31 22:21:50
    触摸屏要使用到输入子系统 分析之前的输入子系统 输入子系统详解 用linux里面的 input.c 用两边的probe 匹配系统里的软件控制和驱动 写出框架 我们要写的就是 input_dev 开始代码编写和分析 分配一input_dev...
  • LINUX驱动触摸屏

    2020-04-23 00:50:14
    从技术原理来区别触摸屏,可将触摸屏分为5种类:矢量压力传感技术触摸屏,电阻技术触摸屏,电容技术触摸屏,红外线技术触摸屏,表面声波技术触摸屏,每一种触摸屏都有其各自的优缺点,要了解哪种触摸屏是适用于哪种场合,...
  • 触摸屏驱动 a.先来了解下基本知识 1.ft5x6触摸屏驱动通过I2C接口与CPU进行连接,服务于I2C总线,同时触摸屏属于又是输入设备,因此又隶属于输入子系统。 2.ft5x06通过I2C接口连接CPU,直接将数字信号放入内部寄存器...
  • 我们先了解下驱动如何实现电容屏的多点触摸,其实很简单,主要需要触摸屏IC FT5406 能够捕获多点数据,这点电容屏基本多能支持到捕获2点以上,而FT5406 可以捕获5个触摸点,编写驱动时,只要去获取这几个点的数据,...
  • 1、简介 (a). 基本概念 多点触摸(multitouch,MT)屏的IC芯片一般都是通过I2C协议...Linux驱动:电阻屏驱动分析 除了参考以上种驱动机制,linux内核已经提供关于多点触摸协议的说明文档:Documentation/input/multi
  • 多点电容触摸驱动框架前面小节已经详细的讲解了 linux 下多点触摸屏驱动原理,本小节我们来梳理一下 linux 下多点电容触摸驱动的编写框架和步骤。首先确定驱动需要用到哪些知识点,哪些框架?根据前面的分析,我们...
  • Linux触摸屏驱动

    2021-05-10 16:28:36
    hander_list链表中,搜索input_register_handler注册函数,就可以看到事件处理层里的函数:右边的驱动事件处理,内核是已经写好了的,所以触摸屏只需要写具体的驱动设备,然后内核会与触摸屏驱动tsdev.c自动连接。...
  • 在读者学习本章以及后续章节...因此如果想要写出通用的触摸屏驱动,需要使用输入子系统完成。 考虑到我是用的并不是之前的TINY4412,在此给出下文所分析的文件: https://files.cnblogs.com/files/Lioker/15_ts.z...
  • 电容触摸屏驱动分析

    千次阅读 2013-08-23 08:19:37
    转载出处:android 电容屏(三):驱动调试之驱动程序分析篇(转) 一、总体架构 1、IIC部分,初始化gt8105的数据和传回主控制的坐标位置信息就是通过IIC这条线传输的;...二、电容触摸屏的主要参数(面试的
  • stm32 驱动 触摸屏

    千次阅读 2016-08-30 21:36:28
    由于智能手机的发展和大屏幕的兴起,触摸屏已经得到了广泛的应用。触摸屏分为两种:电阻触屏 俗称“软屏”;电容触屏俗称“硬屏”。    电阻触摸屏的屏体部分是一块多层复合薄膜,由一层玻璃或有机玻璃作为基层,...
  • AM335x(TQ335x)学习笔记——触摸屏驱动编写

    万次阅读 热门讨论 2014-11-26 22:50:44
    由于种种原因,TQ335x的触摸屏驱动是以模块方式提供的,且Linux官方内核中也没有带该触摸屏的驱动源码,单纯的配置DTS是无法完成TQ335x的触摸驱动移植工作的,因此,本文参考内核中原有的pixcir_i2c_ts驱动编写TQ335...
  • 其实基于开发板对触摸屏驱动进行移植,需要做的工作并不是太多,因为大部分工作开发板的供应商已经提供好了,但是用起来总是不尽人意,所以需要进一步优化,废话少说,步入正题。 首先看一下硬件电路,如下图所示:...
  • armlinux学习笔记--触摸屏驱动程序分析//*******************************************************//*2007.6.26//*******************************************************Linux 下的触摸屏驱动程序主要都在/...
  • DIY基于RTD2662的LVDS屏幕驱动

    千次阅读 2020-05-25 10:27:17
    在闲鱼上收了块车载导航的显示,8寸+电容触控,一开始用标准的LVDS驱动板去驱动发现驱动不了,网上查了好久也没有查到号资料,只能找同一品牌的类似屏幕,终于在百度文库里面找到一针脚定义类似的,跳线转接...
  • 开发环境: 开发板:JZ2440V3 ...2_触摸屏驱动程序之编写驱动 (100ask.net) 触摸屏使用过程: 触摸屏某点被按下,产生INT_TC中断; 在中断处理程序中,打开定时器 定时器时间到,启动ADC转...
  • 2.1主要功能 目前使用非16个连续IO口模拟8080时序,通过8080协议驱动2.8寸TFT屏幕, 这个驱动移植真的费劲,网上的资料都是连续的16个引脚,直接按位操作即可,这里将资料开放出来,供大家以后参考。 目前实现的...
  • iTOP-4412开发板LCD的屏幕驱动

    千次阅读 2018-04-09 15:33:05
    大家好今天我们来讲一下 iTOP-4412 开发板 LCD 的屏幕驱动, iTOP-4412 开发板支持 4.3 寸, 7 寸, 9.7寸的 lcd 显示。其中 4.3 寸是用的 cpu 直接出来的 RGB 信号,7 寸和 9.7 寸是...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 4,462
精华内容 1,784
关键字:

8080屏幕驱动是几个引脚