最近在用N9H30的开发板学习LVGL。用于参加嵌入式GUI竞赛。我的这块开发板一直有触摸屏误触发的问题。控件少了还好,无所谓。控件一多就会误触发某些功能。这对于设计一款产品来说肯定是不太合适的。今天在测试键盘功能的时候,就发现一直在自己输入字符。于是就着此机会,初步研究了一下N9H30的触摸屏底层驱动。并优化解决了此问题。先看现象,如下两张图片,分别是闲置大概10分钟的结果,终端消息打印了几十条触摸屏触发信息,其中有8次触发在了键盘的‘r’键上面,输入框内自动输入了8个‘r’字符。在这次测试之前,还看到过误触发的‘i’键,’j’键等。误触发范围还挺广的。
为了方便后面的分析,这里先给出一张从网上截取的四线电阻触摸屏原理的简图,如下:
N9H30芯片内部的ADC,硬件支持四线电阻触摸屏或5线触摸板驱动。由于没有具体的用户手册,不太清楚ADC内部四线触摸屏驱动的具体原理。但推测,应该是把上图中的X-和Y-称为Z0和Z1。X+和Y+称为X和Y。分别把Y+接正电压,Y-接地,读取X+的AD值,把X+接正电压,X-接地,读取Y+的AD值。从而来计算X和Y两层介质的接触点坐标,即触摸点。
这里不对底层驱动做详细解析,只表述我最终分析的结果。对于触摸屏的底层驱动,开启了一个40ms的软件定时器,用于触发ADC采样。下面是ADC转换完成中断的回调函数。其中,nu_adc_touch_detect()函数应该是用来切换施压方向的。在enable的时候使能detect。在PenDownCallback()函数中,开启采样定时器。失能detect,即换方向采集。在AdcMenuStartCallback()回调函数中,通过Z0的AD值是否为0,来判断是否完成了一个检测周期,是则关闭定时器,再次使能detect,开启下一个检测周期。然后把ADC数据通过邮箱发出去,并释放信号量。
这里的优化是把判断Z0等于零的条件改成了小于等于某阈值,这个阈值可通过宏定义修改。我设置的是2,已经能够完全解决此问题了。这个参数过大,可能会影响有效探测的触摸屏范围。此优化,起因是探测的毕竟是模拟信号,就算直接接地,也很难保证不会有一些小毛刺干扰。加入一个小的阈值,可以有效解决Z0信号状态判断错误引起的误触发。
#define ADC_TOUCH_Z0_ACtiVE 2
static int32_t PenDownCallback(uint32_t status, uint32_t userData)
{
nu_adc_touch_detect(RT_FALSE);
rt_timer_start(g_sNuADC.psRtTouchMenuTimer);
return 0;
}
static int32_t AdcMenuStartCallback(uint32_t status, uint32_t userData)
{
nu_adc_t psNuAdc = (nu_adc_t)userData;
#if defined(BSP_USING_ADC_TOUCH)
static struct nu_adc_touch_data point;
static rt_bool_t bDrop = RT_FALSE;
static uint32_t u32LastZ0 = 0xffffu;
if (psNuAdc->psRtTouch != RT_NULL)
{
uint32_t value;
value = inpw(REG_ADC_XYDATA);
point.u32X = (value & 0x0ffful);
point.u32Y = ((value >> 16) & 0x0ffful);
value = inpw(REG_ADC_ZDATA);
point.u32Z0 = (value & 0x0ffful);
point.u32Z1 = ((value >> 16) & 0x0ffful);
/* Trigger next or not. */
if (point.u32Z0 <= ADC_TOUCH_Z0_ACTIVE)
{
/* Stop sampling procedure. */
rt_timer_stop(g_sNuADC.psRtTouchMenuTimer);
/* Re-start pendown detection */
nu_adc_touch_detect(RT_TRUE);
bDrop = RT_TRUE;
}
else
{
bDrop = RT_FALSE;
}
/* Notify upper layer. */
if ((!bDrop || (u32LastZ0 > ADC_TOUCH_Z0_ACTIVE)) && rt_mq_send(psNuAdc->m_pmqTouchXYZ, (const void *)&point, sizeof(struct nu_adc_touch_data)) == RT_EOK)
{
rt_hw_touch_isr(psNuAdc->psRtTouch);
}
u32LastZ0 = point.u32Z0;
}
else
#endif
{
rt_err_t result = rt_sem_release(psNuAdc->m_psSem);
RT_ASSERT(result == RT_EOK);
}
return 0;
}
如下是rtthread用于处理触摸事务的线程。这里等待信号量,并读取邮箱数据,获得触摸ADC值。转换成坐标值(nu_adc_touch_readpoint()函数中)。然后调用nu_touch_inputevent_cb()回调函数向后级驱动提交数据。原始驱动,这里来什么信号就提交什么信号。个人感觉对于这种类似按键的输入设备,还是加一个消抖的好。于是加入了touch_active_cnt触摸有效计数的变量。有效触发值也可以用宏定义修改。我用的也是2。响应速度和稳定度都让人很满意。
static void adc_touch_entry(void *parameter)
{
struct rt_touch_data touch_point;
rt_err_t result;
rt_device_t pdev;
int max_range;
int16_t touch_active_cnt=0;
BOOL touch_active_flag = FALSE;
adc_touch_sem = rt_sem_create("adc_touch_sem", 0, RT_IPC_FLAG_FIFO);
RT_ASSERT(adc_touch_sem != RT_NULL);
pdev = rt_device_find("adc_touch");
if (!pdev)
{
rt_kprintf("Not found
");
return ;
}
if (rt_memcmp((void *)&g_sCalMat, (void *)&g_sCalZero, sizeof(S_CALIBRATION_MATRIX)) != 0)
g_u32Calibrated = 1;
nu_adc_touch_readfile();
result = rt_device_open(pdev, RT_DEVICE_FLAG_INT_RX);
RT_ASSERT(result == RT_EOK);
result = rt_device_set_rx_indicate(pdev, adc_touch_rx_callback);
RT_ASSERT(result == RT_EOK);
max_range = BSP_LCD_WIDTH;
result = rt_device_control(pdev, RT_TOUCH_CTRL_SET_X_RANGE, (void *)&max_range);
RT_ASSERT(result == RT_EOK);
max_range = BSP_LCD_HEIGHT;
result = rt_device_control(pdev, RT_TOUCH_CTRL_SET_Y_RANGE, (void *)&max_range);
RT_ASSERT(result == RT_EOK);
result = rt_device_control(pdev, RT_TOUCH_CTRL_POWER_ON, RT_NULL);
RT_ASSERT(result == RT_EOK);
while (adc_touch_worker_run)
{
if (!g_u32Calibrated)
{
rt_kprintf("Start ADC touching calibration.
");
nu_touch_do_calibration(pdev);
rt_kprintf("Stop ADC touching calibration.
");
continue;
}
if (adc_request_point(pdev, &touch_point) == RT_EOK)
{
if (touch_point.event == RT_TOUCH_EVENT_DOWN
|| touch_point.event == RT_TOUCH_EVENT_UP
|| touch_point.event == RT_TOUCH_EVENT_MOVE)
{
if(touch_point.event == RT_TOUCH_EVENT_DOWN || touch_point.event == RT_TOUCH_EVENT_MOVE)
{
if(touch_active_cnt < ADC_TOUCH_FILTER_VAL)
{
touch_active_cnt++;
}
else {
touch_active_flag = TRUE;
}
}
else {
if(touch_active_cnt < ADC_TOUCH_FILTER_VAL)
{
rt_kprintf("err touch:%d
",touch_active_cnt);
}
touch_active_cnt = 0;
}
if(touch_active_flag)
{
nu_touch_inputevent_cb(touch_point.x_coordinate, touch_point.y_coordinate, touch_point.event);
rt_kprintf("x=%d y=%d event=%s%s%s
",
touch_point.x_coordinate,
touch_point.y_coordinate,
(touch_point.event == RT_TOUCH_EVENT_DOWN) ? "DOWN" : "",
(touch_point.event == RT_TOUCH_EVENT_UP) ? "UP" : "",
(touch_point.event == RT_TOUCH_EVENT_MOVE) ? "MOVE" : "");
}
if(touch_active_cnt == 0)
{
touch_active_flag = FALSE;
}
}
}
else {
if(touch_active_cnt)
{
rt_kprintf("err touch:%d
",touch_active_cnt);
}
touch_active_cnt = 0;
}
}
result = rt_device_control(pdev, RT_TOUCH_CTRL_POWER_OFF, RT_NULL);
RT_ASSERT(result == RT_EOK);
result = rt_device_close(pdev);
RT_ASSERT(result == RT_EOK);
}
整体改动不多,是一个小优化,有遇到相同问题的小伙伴可以自己把上面的逻辑加到代码中。
原作者:吉利咕噜2022
|