mirror of
https://github.com/lupyuen/lupyuen.github.io.git
synced 2025-01-13 10:18:33 +08:00
1203 lines
No EOL
73 KiB
HTML
1203 lines
No EOL
73 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<meta name="generator" content="rustdoc">
|
||
<title>NuttX RTOS for PinePhone: Touch Panel</title>
|
||
|
||
|
||
<!-- Begin scripts/articles/*-header.html: Article Header for Custom Markdown files processed by rustdoc, like chip8.md -->
|
||
<meta property="og:title"
|
||
content="NuttX RTOS for PinePhone: Touch Panel"
|
||
data-rh="true">
|
||
<meta property="og:description"
|
||
content="All about the Capacitive Touch Panel inside Pine64 PinePhone... And how we created the PinePhone Touch Panel Driver for Apache NuttX RTOS"
|
||
data-rh="true">
|
||
<meta name="description"
|
||
content="All about the Capacitive Touch Panel inside Pine64 PinePhone... And how we created the PinePhone Touch Panel Driver for Apache NuttX RTOS">
|
||
<meta property="og:image"
|
||
content="https://lupyuen.github.io/images/touch2-title.png">
|
||
<meta property="og:type"
|
||
content="article" data-rh="true">
|
||
<link rel="canonical" href="https://lupyuen.org/articles/touch2.html" />
|
||
<!-- End scripts/articles/*-header.html -->
|
||
<!-- Begin scripts/rustdoc-header.html: Header for Custom Markdown files processed by rustdoc, like chip8.md -->
|
||
<link rel="alternate" type="application/rss+xml" title="RSS Feed for lupyuen" href="/rss.xml" />
|
||
<link rel="stylesheet" type="text/css" href="../normalize.css">
|
||
<link rel="stylesheet" type="text/css" href="../rustdoc.css" id="mainThemeStyle">
|
||
<link rel="stylesheet" type="text/css" href="../dark.css">
|
||
<link rel="stylesheet" type="text/css" href="../light.css" id="themeStyle">
|
||
<link rel="stylesheet" type="text/css" href="../prism.css">
|
||
<script src="../storage.js"></script><noscript>
|
||
<link rel="stylesheet" href="../noscript.css"></noscript>
|
||
<link rel="shortcut icon" href="../favicon.ico">
|
||
<style type="text/css">
|
||
#crate-search {
|
||
background-image: url("../down-arrow.svg");
|
||
}
|
||
</style>
|
||
<!-- End scripts/rustdoc-header.html -->
|
||
|
||
|
||
</head>
|
||
<body class="rustdoc">
|
||
<!--[if lte IE 8]>
|
||
<div class="warning">
|
||
This old browser is unsupported and will most likely display funky
|
||
things.
|
||
</div>
|
||
<![endif]-->
|
||
|
||
|
||
<!-- Begin scripts/rustdoc-before.html: Pre-HTML for Custom Markdown files processed by rustdoc, like chip8.md -->
|
||
|
||
<!-- Begin Theme Picker -->
|
||
<div class="theme-picker" style="left: 0"><button id="theme-picker" aria-label="Pick another theme!"><img src="../brush.svg"
|
||
width="18" alt="Pick another theme!"></button>
|
||
<div id="theme-choices"></div>
|
||
</div>
|
||
<!-- Theme Picker -->
|
||
|
||
<!-- End scripts/rustdoc-before.html -->
|
||
|
||
|
||
<h1 class="title">NuttX RTOS for PinePhone: Touch Panel</h1>
|
||
<nav id="rustdoc"><ul>
|
||
<li><a href="#goodix-gt917s-touch-panel" title="Goodix GT917S Touch Panel">1 Goodix GT917S Touch Panel</a><ul></ul></li>
|
||
<li><a href="#read-the-product-id" title="Read the Product ID">2 Read the Product ID</a><ul></ul></li>
|
||
<li><a href="#poll-the-touch-panel" title="Poll the Touch Panel">3 Poll the Touch Panel</a><ul></ul></li>
|
||
<li><a href="#read-a-touch-point" title="Read a Touch Point">4 Read a Touch Point</a><ul></ul></li>
|
||
<li><a href="#interrupt-handler-for-touch-panel" title="Interrupt Handler for Touch Panel">5 Interrupt Handler for Touch Panel</a><ul></ul></li>
|
||
<li><a href="#nuttx-touch-panel-driver" title="NuttX Touch Panel Driver">6 NuttX Touch Panel Driver</a><ul></ul></li>
|
||
<li><a href="#lvgl-calls-our-driver" title="LVGL Calls Our Driver">7 LVGL Calls Our Driver</a><ul></ul></li>
|
||
<li><a href="#driver-limitations" title="Driver Limitations">8 Driver Limitations</a><ul></ul></li>
|
||
<li><a href="#whats-next" title="What’s Next">9 What’s Next</a><ul></ul></li>
|
||
<li><a href="#appendix-nuttx-touch-panel-driver-for-pinephone" title="Appendix: NuttX Touch Panel Driver for PinePhone">10 Appendix: NuttX Touch Panel Driver for PinePhone</a><ul>
|
||
<li><a href="#register-touch-panel-driver" title="Register Touch Panel Driver">10.1 Register Touch Panel Driver</a><ul></ul></li>
|
||
<li><a href="#open-the-touch-panel" title="Open the Touch Panel">10.2 Open the Touch Panel</a><ul></ul></li>
|
||
<li><a href="#read-a-touch-sample" title="Read a Touch Sample">10.3 Read a Touch Sample</a><ul></ul></li>
|
||
<li><a href="#interrupt-handler" title="Interrupt Handler">10.4 Interrupt Handler</a><ul></ul></li>
|
||
<li><a href="#setup-poll-for-touch-sample" title="Setup Poll for Touch Sample">10.5 Setup Poll for Touch Sample</a><ul></ul></li>
|
||
<li><a href="#close-the-touch-panel" title="Close the Touch Panel">10.6 Close the Touch Panel</a><ul></ul></li></ul></li>
|
||
<li><a href="#appendix-interrupt-handler-for-touch-panel" title="Appendix: Interrupt Handler for Touch Panel">11 Appendix: Interrupt Handler for Touch Panel</a><ul>
|
||
<li><a href="#attach-our-interrupt-handler" title="Attach our Interrupt Handler">11.1 Attach our Interrupt Handler</a><ul></ul></li>
|
||
<li><a href="#handle-interrupts-from-touch-panel" title="Handle Interrupts from Touch Panel">11.2 Handle Interrupts from Touch Panel</a><ul></ul></li>
|
||
<li><a href="#test-our-interrupt-handler" title="Test our Interrupt Handler">11.3 Test our Interrupt Handler</a><ul></ul></li>
|
||
<li><a href="#too-few-interrupts" title="Too Few Interrupts">11.4 Too Few Interrupts</a><ul></ul></li>
|
||
<li><a href="#too-many-interrupts" title="Too Many Interrupts">11.5 Too Many Interrupts</a><ul></ul></li>
|
||
<li><a href="#interrupt-priority" title="Interrupt Priority">11.6 Interrupt Priority</a><ul></ul></li></ul></li></ul></nav><p>📝 <em>12 Jan 2023</em></p>
|
||
<p><img src="https://lupyuen.github.io/images/touch2-title.png" alt="Apache NuttX RTOS reads the PinePhone Touch Panel" /></p>
|
||
<p>We’re porting <a href="https://lupyuen.github.io/articles/what"><strong>Apache NuttX RTOS</strong></a> (Real-Time Operating System) to <a href="https://wiki.pine64.org/index.php/PinePhone"><strong>Pine64 PinePhone</strong></a>…</p>
|
||
<ul>
|
||
<li><a href="https://lupyuen.github.io/articles/what"><strong>“NuttX RTOS for PinePhone: What is it?”</strong></a></li>
|
||
</ul>
|
||
<p>Now we can render <a href="https://lupyuen.github.io/articles/fb#lvgl-graphics-library"><strong>LVGL Graphical User Interfaces</strong></a>… But it won’t work yet with <strong>Touch Input</strong>!</p>
|
||
<p>Let’s talk about the <strong>Capacitive Touch Panel</strong> inside PinePhone…</p>
|
||
<ul>
|
||
<li>
|
||
<p>How it’s <strong>connected to PinePhone</strong></p>
|
||
<p>(Over I2C)</p>
|
||
</li>
|
||
<li>
|
||
<p>How we read <strong>Touch Points</strong></p>
|
||
<p>(Polling vs Interrupts)</p>
|
||
</li>
|
||
<li>
|
||
<p>How we created the <strong>Touch Panel Driver</strong> for NuttX</p>
|
||
<p>(Despite the missing docs)</p>
|
||
</li>
|
||
<li>
|
||
<p>And how we call the driver from <strong>LVGL Apps</strong></p>
|
||
<p><a href="https://www.youtube.com/shorts/APge9bTt-ho">(Watch the Demo on YouTube)</a></p>
|
||
</li>
|
||
</ul>
|
||
<p>We begin with the internals of the Touch Panel…</p>
|
||
<p><img src="https://lupyuen.github.io/images/touch2-schematic1.jpg" alt="Capacitive Touch Panel in PinePhone Schematic (Pages 9 and 11)" /></p>
|
||
<p><a href="https://files.pine64.org/doc/PinePhone/PinePhone%20v1.2b%20Released%20Schematic.pdf"><em>Capacitive Touch Panel in PinePhone Schematic (Pages 9 and 11)</em></a></p>
|
||
<h1 id="goodix-gt917s-touch-panel"><a class="doc-anchor" href="#goodix-gt917s-touch-panel">§</a>1 Goodix GT917S Touch Panel</h1>
|
||
<p>Inside PinePhone is the <strong>Goodix GT917S Capacitive Touch Panel</strong> (CTP) that talks over I2C.</p>
|
||
<p>According to the <a href="https://files.pine64.org/doc/PinePhone/PinePhone%20v1.2b%20Released%20Schematic.pdf"><strong>PinePhone Schematic</strong></a> Pages 9 and 11 (pic above)…</p>
|
||
<ul>
|
||
<li>
|
||
<p><strong>Touch Panel Interrupt</strong> (CTP-INT) is at <strong>PH4</strong></p>
|
||
<p>(Touch Panel fires an interrupt at PH4 when it’s touched)</p>
|
||
</li>
|
||
<li>
|
||
<p><strong>Touch Panel Reset</strong> (CTP-RST) is at <strong>PH11</strong></p>
|
||
<p>(We toggle PH11 to reset the Touch Panel)</p>
|
||
</li>
|
||
<li>
|
||
<p><strong>Touch Panel I2C</strong> (SCK / SDA) is at <strong>TWI0</strong></p>
|
||
<p>(That’s the port for Two Wire Interface, compatible with I2C)</p>
|
||
</li>
|
||
</ul>
|
||
<p><em>What are PH4 and PH11?</em></p>
|
||
<p>Just think of them as GPIOs on the Allwinner A64 SoC.</p>
|
||
<p>(Allwinner calls them PIOs)</p>
|
||
<p><em>Does it need special power?</em></p>
|
||
<p>Please remember to <strong>power up LDO (3.3V)</strong> through the Power Management Integrated Circuit…</p>
|
||
<ul>
|
||
<li><a href="https://lupyuen.github.io/articles/lcd#power-on-lcd-panel"><strong>“Power On LCD Panel”</strong></a></li>
|
||
</ul>
|
||
<p>PinePhone’s Touch Panel doesn’t seem to be the Power-Saving type like <a href="https://lupyuen.github.io/articles/touch#cst816s-touch-panel"><strong>PineTime’s CST816S</strong></a>.</p>
|
||
<p><em>How do we program the Touch Panel?</em></p>
|
||
<p>The datasheet doesn’t say much about programming the Touch Panel…</p>
|
||
<ul>
|
||
<li><a href="https://files.pine64.org/doc/datasheet/pinephone/GT917S-Datasheet.pdf"><strong>GT917S Datasheet</strong></a></li>
|
||
</ul>
|
||
<p>So we’ll create the driver by replicating the <strong>I2C Read / Write Operations</strong> from the official Android Driver <a href="https://github.com/goodix/gt9xx_driver_android/blob/master/gt9xx.c"><strong>gt9xx.c</strong></a>.</p>
|
||
<p>(Or the unofficial simpler driver <a href="https://github.com/DiveInEmbedded/GT911-Touch-driver/blob/main/Core/Src/GT911.c"><strong>GT911.c</strong></a>)</p>
|
||
<p><em>So PinePhone’s Touch Panel is actually undocumented?</em></p>
|
||
<p>Yeah it’s strangely common for Touch Panels to be undocumented.</p>
|
||
<p>(Just like PineTime’s <a href="https://lupyuen.github.io/articles/touch#cst816s-touch-panel"><strong>CST816S Touch Panel</strong></a>)</p>
|
||
<p>Let’s experiment with PinePhone’s Touch Panel to understand how it works…</p>
|
||
<p><a href="https://patents.google.com/patent/US7663607B2/en">(I think Touch Panels are poorly documented because of Apple’s patent on Multitouch)</a></p>
|
||
<p><img src="https://lupyuen.github.io/images/touch2-code2a.png" alt="Reading the Product ID from Touch Panel" /></p>
|
||
<h1 id="read-the-product-id"><a class="doc-anchor" href="#read-the-product-id">§</a>2 Read the Product ID</h1>
|
||
<p><em>What’s the simplest thing we can do with PinePhone’s Touch Panel?</em></p>
|
||
<p>Let’s read the <strong>Product ID</strong> from the Touch Panel.</p>
|
||
<p>We experimented with the Touch Panel (Bare Metal with NuttX) and discovered these <strong>I2C Settings</strong>…</p>
|
||
<ul>
|
||
<li>
|
||
<p><strong>I2C Address</strong> is <strong><code>0x5D</code></strong></p>
|
||
</li>
|
||
<li>
|
||
<p><strong>I2C Frequency</strong> is <strong>400 kHz</strong></p>
|
||
<p>(What’s the max?)</p>
|
||
</li>
|
||
<li>
|
||
<p><strong>I2C Register Addresses</strong> are 16-bit</p>
|
||
<p>(Send MSB before LSB, so we should swap the bytes)</p>
|
||
</li>
|
||
<li>
|
||
<p>Reading I2C Register <strong><code>0x8140</code></strong> (Product ID) will return the bytes…</p>
|
||
<div class="example-wrap"><pre class="language-text"><code>39 31 37 53</code></pre></div>
|
||
<p>Which is ASCII for “<strong><code>917S</code></strong>”</p>
|
||
<p>(Goodix GT917S Touch Panel)</p>
|
||
</li>
|
||
</ul>
|
||
<p>Based on the above settings, we wrote this <strong>Test Code</strong> that runs in the NuttX Kernel: <a href="https://github.com/lupyuen2/wip-nuttx/blob/c4991b1503387d57821d94a549425bcd8f268841/boards/arm64/a64/pinephone/src/pinephone_bringup.c#L316-L355">pinephone_bringup.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// Read Product ID from Touch Panel over I2C
|
||
static void touch_panel_read(
|
||
struct i2c_master_s *i2c // NuttX I2C Bus (Port TWI0)
|
||
) {
|
||
uint32_t freq = 400000; // I2C Frequency: 400 kHz
|
||
uint16_t addr = 0x5d; // Default I2C Address for Goodix GT917S
|
||
uint16_t reg = 0x8140; // Register Address: Read Product ID
|
||
|
||
// Swap the Register Address, MSB first
|
||
uint8_t regbuf[2] = {
|
||
reg >> 8, // First Byte: MSB
|
||
reg & 0xff // Second Byte: LSB
|
||
};
|
||
|
||
// Erase the Receive Buffer (4 bytes)
|
||
uint8_t buf[4];
|
||
memset(buf, 0xff, sizeof(buf));
|
||
|
||
// Compose the I2C Messages
|
||
struct i2c_msg_s msgv[2] = {
|
||
// Send the 16-bit Register Address (MSB first)
|
||
{
|
||
.frequency = freq,
|
||
.addr = addr,
|
||
.flags = 0,
|
||
.buffer = regbuf,
|
||
.length = sizeof(regbuf)
|
||
},
|
||
// Receive the Register Data (4 bytes)
|
||
{
|
||
.frequency = freq,
|
||
.addr = addr,
|
||
.flags = I2C_M_READ,
|
||
.buffer = buf,
|
||
.length = sizeof(buf)
|
||
}
|
||
};
|
||
|
||
// Execute the I2C Transfer
|
||
int ret = I2C_TRANSFER(i2c, msgv, 2);
|
||
DEBUGASSERT(ret == OK);
|
||
|
||
// Dump the Receive Buffer
|
||
infodumpbuffer("buf", buf, buflen);
|
||
// Shows "39 31 37 53" or "917S"
|
||
}</code></pre></div>
|
||
<p>This is what we see (with TWI0 Logging Enabled)…</p>
|
||
<p><img src="https://lupyuen.github.io/images/touch2-code3a.png" alt="Read Product ID from Touch Panel" /></p>
|
||
<p>Yep the I2C Response is correct…</p>
|
||
<div class="example-wrap"><pre class="language-text"><code>39 31 37 53</code></pre></div>
|
||
<p>Which is ASCII for “<strong><code>917S</code></strong>”!</p>
|
||
<p>(Goodix GT917S Touch Panel)</p>
|
||
<p><em>How’s the code above called by NuttX Kernel?</em></p>
|
||
<p>Read on to find out how we poll the Touch Panel and read the Product ID…</p>
|
||
<p><img src="https://lupyuen.github.io/images/touch2-code1a.png" alt="Polling the Touch Panel" /></p>
|
||
<h1 id="poll-the-touch-panel"><a class="doc-anchor" href="#poll-the-touch-panel">§</a>3 Poll the Touch Panel</h1>
|
||
<p><em>PinePhone’s Touch Panel will trigger interrupts right?</em></p>
|
||
<p>To detect Touch Events, we’ll need to <strong>handle the interrupts</strong> triggered by Touch Panel.</p>
|
||
<p>Based on our research, PinePhone’s <strong>Touch Panel Interrupt</strong> (CTP-INT) is connected at <strong>PH4</strong>.</p>
|
||
<p>But to simplify our first experiment, <strong>let’s poll PH4</strong>. (Instead of handling interrupts)</p>
|
||
<p><em>How do we poll PH4?</em></p>
|
||
<p>We read PH4 as a <strong>GPIO Input</strong>. When we touch the Touch Panel, PH4 goes from <strong>Low to High</strong>.</p>
|
||
<p>This is how we poll PH4: <a href="https://github.com/lupyuen2/wip-nuttx/blob/e249049370d21a988912f2fb95a21514863dfe8a/boards/arm64/a64/pinephone/src/pinephone_bringup.c#L283-L317">pinephone_bringup.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// Test Touch Panel Interrupt by Polling as GPIO Input.
|
||
// Touch Panel Interrupt (CTP-INT) is at PH4.
|
||
// We configure it for GPIO Input.
|
||
#define CTP_INT (PIO_INPUT | PIO_PORT_PIOH | PIO_PIN4)
|
||
|
||
// Poll for Touch Panel Interrupt (PH4) by reading as GPIO Input
|
||
void touch_panel_initialize(
|
||
struct i2c_master_s *i2c // NuttX I2C Bus (Port TWI0)
|
||
) {
|
||
|
||
// Configure the Touch Panel Interrupt for GPIO Input
|
||
int ret = a64_pio_config(CTP_INT);
|
||
DEBUGASSERT(ret == OK);
|
||
|
||
// Poll the Touch Panel Interrupt as GPIO Input
|
||
bool prev_val = false;
|
||
for (int i = 0; i < 6000; i++) { // Poll for 60 seconds
|
||
|
||
// Read the GPIO Input
|
||
bool val = a64_pio_read(CTP_INT);
|
||
|
||
// If value has changed...
|
||
if (val != prev_val) {
|
||
|
||
// Print the transition
|
||
if (val) { up_putc('+'); } // PH4 goes Low to High
|
||
else { up_putc('-'); } // PH4 goes High to Low
|
||
prev_val = val;
|
||
|
||
// If PH4 has just transitioned from Low to High...
|
||
if (val) {
|
||
|
||
// Read the Touch Panel over I2C
|
||
touch_panel_read(i2c);
|
||
}
|
||
}
|
||
|
||
// Wait a while
|
||
up_mdelay(10);
|
||
}
|
||
}</code></pre></div>
|
||
<p><a href="https://github.com/apache/nuttx/blob/master/arch/arm64/src/a64/a64_pio.c#L174-L344">(<strong>a64_pio_config</strong> configures PH4 as an Input Pin)</a></p>
|
||
<p><a href="https://github.com/apache/nuttx/blob/master/arch/arm64/src/a64/a64_pio.c#L390-L420">(<strong>a64_pio_read</strong> reads PH4 as an Input Pin)</a></p>
|
||
<p>The loop above watches for PH4 shifting from <strong>Low to High</strong>…</p>
|
||
<ul>
|
||
<li>
|
||
<p>When PH4 shifts from <strong>Low to High</strong>, we print “<strong><code>+</code></strong>”</p>
|
||
</li>
|
||
<li>
|
||
<p>When PH4 shifts from <strong>High to Low</strong>, we print “<strong><code>-</code></strong>”</p>
|
||
</li>
|
||
<li>
|
||
<p>After shifting from <strong>Low to High</strong>, we call <a href="https://lupyuen.github.io/articles/touch2#read-product-id"><strong>touch_panel_read</strong></a> to read the Touch Panel</p>
|
||
<p><a href="https://lupyuen.github.io/articles/touch2#read-product-id">(Which we’ve seen earlier)</a></p>
|
||
</li>
|
||
</ul>
|
||
<p>Thus our simple loop simulates an <strong>Interrupt Handler</strong>!</p>
|
||
<p><em>How do we open the I2C Port?</em></p>
|
||
<p>On NuttX, this is how we <strong>open the I2C Port</strong> and pass it to the above loop: <a href="https://github.com/lupyuen2/wip-nuttx/blob/e249049370d21a988912f2fb95a21514863dfe8a/boards/arm64/a64/pinephone/src/pinephone_bringup.c#L158-L170">pinephone_bringup.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// Open Allwinner A64 Port TWI0 for I2C
|
||
struct i2c_master_s *i2c =
|
||
a64_i2cbus_initialize(0); // 0 for TWI0
|
||
|
||
// Pass the I2C Port to the above loop
|
||
touch_panel_initialize(i2c);</code></pre></div>
|
||
<p>We insert this code at the end of the <a href="https://github.com/lupyuen2/wip-nuttx/blob/e249049370d21a988912f2fb95a21514863dfe8a/boards/arm64/a64/pinephone/src/pinephone_bringup.c#L57-L175"><strong>PinePhone Bringup Function</strong></a>, so that NuttX Kernel will run it at the end of startup.</p>
|
||
<p>(Yes it sounds hacky, but it’s a simple way to do Kernel Experiments)</p>
|
||
<p>Now that we can poll our Touch Panel, let’s read a Touch Point!</p>
|
||
<p><img src="https://lupyuen.github.io/images/touch2-code4a.png" alt="Reading a Touch Point" /></p>
|
||
<h1 id="read-a-touch-point"><a class="doc-anchor" href="#read-a-touch-point">§</a>4 Read a Touch Point</h1>
|
||
<p><em>When the Touch Panel is touched, how do we read the Touch Coordinates?</em></p>
|
||
<p>Based on the <a href="https://github.com/DiveInEmbedded/GT911-Touch-driver/blob/main/Core/Src/GT911.c"><strong>GT911 Reference Code</strong></a>, here are the steps to <strong>read a Touch Point</strong>…</p>
|
||
<ol>
|
||
<li>
|
||
<p>Read the <strong>Touch Panel Status</strong> (1 byte) at I2C Register <strong><code>0x814E</code></strong></p>
|
||
<p><strong>Status Code</strong> is <strong>Bit 7</strong> of Touch Panel Status</p>
|
||
<p><strong>Touched Points</strong> is <strong>Bits 0 to 3</strong> of Touch Panel Status</p>
|
||
</li>
|
||
<li>
|
||
<p>If <strong>Status Code</strong> is non-zero and <strong>Touched Points</strong> is 1 or more…</p>
|
||
<p>Read the <strong>Touch Coordinates</strong> (6 bytes) at I2C Register <strong><code>0x8150</code></strong></p>
|
||
<p><strong>First 2 Bytes</strong> (LSB First) are the <strong>X Coordinate</strong> (0 to 720)</p>
|
||
<p><strong>Next 2 Bytes</strong> (LSB First) are the <strong>Y Coordinate</strong> (0 to 1440)</p>
|
||
<p>(What’s in the 2 remaining bytes? Doesn’t seem to indicate Touch Up / Touch Down)</p>
|
||
</li>
|
||
<li>
|
||
<p>To acknowledge the Touch Point, set the <strong>Touch Panel Status</strong> to 0…</p>
|
||
<p>Write 0 to I2C Register <strong><code>0x814E</code></strong></p>
|
||
</li>
|
||
</ol>
|
||
<p>(This won’t support Multitouch, more about this later)</p>
|
||
<p>Here is our code: <a href="https://github.com/lupyuen2/wip-nuttx/blob/e249049370d21a988912f2fb95a21514863dfe8a/boards/arm64/a64/pinephone/src/pinephone_bringup.c#L338-L370">pinephone_bringup.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// I2C Registers for Touch Panel
|
||
#define GTP_READ_COORD_ADDR 0x814E // Touch Panel Status
|
||
#define GTP_POINT1 0x8150 // First Touch Point
|
||
|
||
// Read Touch Panel over I2C
|
||
static void touch_panel_read(
|
||
struct i2c_master_s *i2c // NuttX I2C Bus (Port TWI0)
|
||
) {
|
||
|
||
// Read the Touch Panel Status
|
||
uint8_t status[1];
|
||
touch_panel_i2c_read( // Read from I2C Touch Panel...
|
||
i2c, // NuttX I2C Bus (Port TWI0)
|
||
GTP_READ_COORD_ADDR, // I2C Register: 0x814E
|
||
status, // Receive Buffer
|
||
sizeof(status) // Buffer Size
|
||
);
|
||
// Receives "81"
|
||
|
||
// Decode the Status Code and the Touched Points
|
||
const uint8_t status_code = status[0] & 0x80; // Set to 0x80
|
||
const uint8_t touched_points = status[0] & 0x0f; // Set to 0x01
|
||
|
||
if (status_code != 0 && // If Status Code is OK and...
|
||
touched_points >= 1) { // Touched Points is 1 or more
|
||
|
||
// Read the First Touch Coordinates
|
||
uint8_t touch[6];
|
||
touch_panel_i2c_read( // Read from I2C Touch Panel...
|
||
i2c, // NuttX I2C Bus (Port TWI0)
|
||
GTP_POINT1, // I2C Register: 0x8150
|
||
touch, // Receive Buffer
|
||
sizeof(touch) // Buffer Size
|
||
);
|
||
// Receives "92 02 59 05 1b 00"
|
||
|
||
// Decode the Touch Coordinates
|
||
const uint16_t x = touch[0] + (touch[1] << 8);
|
||
const uint16_t y = touch[2] + (touch[3] << 8);
|
||
_info("touch x=%d, y=%d\n", x, y);
|
||
// Shows "touch x=658, y=1369"
|
||
}
|
||
|
||
// Set the Touch Panel Status to 0
|
||
touch_panel_set_status(i2c, 0);
|
||
}</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen2/wip-nuttx/blob/e249049370d21a988912f2fb95a21514863dfe8a/boards/arm64/a64/pinephone/src/pinephone_bringup.c#L372-L415">(<strong>touch_panel_i2c_read</strong> reads from the I2C Touch Panel)</a></p>
|
||
<p><a href="https://github.com/lupyuen2/wip-nuttx/blob/e249049370d21a988912f2fb95a21514863dfe8a/boards/arm64/a64/pinephone/src/pinephone_bringup.c#L417-L447">(<strong>touch_panel_set_status</strong> sets the I2C Touch Panel Status)</a></p>
|
||
<p>Let’s run the code…</p>
|
||
<p><img src="https://lupyuen.github.io/images/touch2-run1a.png" alt="Reading Touch Points with Polling" /></p>
|
||
<p>When we tap the screen, we see “<strong><code>-+</code></strong>” which means that PH4 has shifted from Low to High.</p>
|
||
<p>Followed by the reading of the <strong>Touch Panel Status</strong>…</p>
|
||
<div class="example-wrap"><pre class="language-text"><code>-+
|
||
twi_transfer: TWI0 count: 2
|
||
twi_wait: TWI0 Waiting...
|
||
twi_put_addr: TWI address 7bits+r/w = 0xba
|
||
twi_put_addr: TWI address 7bits+r/w = 0xbb
|
||
twi_wait: TWI0 Awakened with result: 0
|
||
0000 81 . </code></pre></div>
|
||
<p><a href="https://gist.github.com/lupyuen/b1ed009961c4202133879b760cb22833">(Source)</a></p>
|
||
<p>Touch Panel Status is <strong><code>0x81</code></strong>. Which means the status is OK and there’s <strong>One Touch Point</strong> detected.</p>
|
||
<p>Our code reads the <strong>Touch Coordinates</strong>…</p>
|
||
<div class="example-wrap"><pre class="language-text"><code>twi_transfer: TWI0 count: 2
|
||
twi_wait: TWI0 Waiting...
|
||
twi_put_addr: TWI address 7bits+r/w = 0xba
|
||
twi_put_addr: TWI address 7bits+r/w = 0xbb
|
||
twi_wait: TWI0 Awakened with result: 0
|
||
0000 92 02 59 05 1b 00 ..Y...
|
||
touch_panel_read: touch x=658, y=1369</code></pre></div>
|
||
<p><a href="https://gist.github.com/lupyuen/b1ed009961c4202133879b760cb22833">(Source)</a></p>
|
||
<p>This says that the Touch Point is at…</p>
|
||
<div class="example-wrap"><pre class="language-text"><code>x=658, y=1369</code></pre></div>
|
||
<p>Which is quite close to the Lower Right Corner. (Screen size is 720 x 1440)</p>
|
||
<p>Yep we can read the Touch Coordinates correctly, through polling! (But not so efficiently)</p>
|
||
<p>Let’s handle interrupts from the Touch Panel…</p>
|
||
<p><img src="https://lupyuen.github.io/images/touch2-code5a.png" alt="Attaching our Interrupt Handler" /></p>
|
||
<h1 id="interrupt-handler-for-touch-panel"><a class="doc-anchor" href="#interrupt-handler-for-touch-panel">§</a>5 Interrupt Handler for Touch Panel</h1>
|
||
<p><em>We’ve done polling with the Touch Panel…</em></p>
|
||
<p><em>Can we handle interrupts from the Touch Panel?</em></p>
|
||
<p>Earlier we’ve read the Touch Panel by polling… Which is easier but inefficient.</p>
|
||
<p>So we tried reading the Touch Panel with an <strong>Interrupt Handler</strong>…</p>
|
||
<ul>
|
||
<li><a href="https://lupyuen.github.io/articles/touch2#appendix-interrupt-handler-for-touch-panel"><strong>“Interrupt Handler for Touch Panel”</strong></a></li>
|
||
</ul>
|
||
<p>But there’s a problem: The Touch Panel only <strong>fires an interrupt once</strong>.</p>
|
||
<p>It won’t trigger interrupts correctly when we touch the screen.</p>
|
||
<p><em>Is this a showstopper for our Touch Panel Driver?</em></p>
|
||
<p>Not really, polling will work fine for now.</p>
|
||
<p>In a while we’ll run the <strong>LVGL Demo App</strong>, which uses polling. (Instead of interrupts)</p>
|
||
<p>Now we dive inside our NuttX Touch Panel Driver that will be called by NuttX Apps..</p>
|
||
<h1 id="nuttx-touch-panel-driver"><a class="doc-anchor" href="#nuttx-touch-panel-driver">§</a>6 NuttX Touch Panel Driver</h1>
|
||
<p><em>What’s inside our NuttX Touch Panel Driver for PinePhone?</em></p>
|
||
<p>We took the code from above and wrapped it inside our <strong>NuttX Touch Panel Driver</strong> for PinePhone…</p>
|
||
<ul>
|
||
<li><a href="https://github.com/apache/nuttx/blob/master/drivers/input/gt9xx.c"><strong>nuttx/drivers/input/gt9xx.c</strong></a></li>
|
||
</ul>
|
||
<p>NuttX Apps will access our driver at <strong>/dev/input0</strong>, which exposes the following <strong>File Operations</strong>: <a href="https://github.com/apache/nuttx/blob/master/drivers/input/gt9xx.c#L114-L132">gt9xx.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// File Operations supported by the Touch Panel
|
||
struct file_operations g_gt9xx_fileops = {
|
||
gt9xx_open, // Open the Touch Panel
|
||
gt9xx_close, // Close the Touch Panel
|
||
gt9xx_read, // Read a Touch Sample
|
||
gt9xx_poll // Setup Poll for Touch Sample</code></pre></div>
|
||
<p>NuttX Apps will call these Touch Panel Operations through the POSIX Standard Functions <strong><code>open()</code></strong>, <strong><code>close()</code></strong>, <strong><code>read()</code></strong> and <strong><code>poll()</code></strong>.</p>
|
||
<p>(Later we’ll see how LVGL Apps do this)</p>
|
||
<p><em>How do we start the Touch Panel Driver?</em></p>
|
||
<p>This is how we <strong>start the Touch Panel Driver</strong> when NuttX boots: <a href="https://github.com/apache/nuttx/blob/master/boards/arm64/a64/pinephone/src/pinephone_touch.c#L212-L245">pinephone_touch.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// Default I2C Address for Goodix GT917S
|
||
#define CTP_I2C_ADDR 0x5d
|
||
|
||
// Register the Touch Panel Driver
|
||
ret = gt9xx_register(
|
||
"/dev/input0", // Device Path
|
||
i2c, // I2C Bus
|
||
CTP_I2C_ADDR, // I2C Address of Touch Panel
|
||
&g_pinephone_gt9xx // Callbacks for PinePhone Operations
|
||
);
|
||
DEBUGASSERT(ret == OK);</code></pre></div>
|
||
<p><a href="https://lupyuen.github.io/articles/touch2#register-touch-panel-driver">(<strong>gt9xx_register</strong> comes from our Touch Panel Driver)</a></p>
|
||
<p><a href="https://github.com/apache/nuttx/blob/master/boards/arm64/a64/pinephone/src/pinephone_touch.c#L70-L79">(<strong>g_pinephone_gt9xx</strong> defines the Interrupt Callbacks)</a></p>
|
||
<p>The Touch Panel operations are explained in the Appendix…</p>
|
||
<ul>
|
||
<li>
|
||
<p><a href="https://lupyuen.github.io/articles/touch2#register-touch-panel-driver"><strong>“Register Touch Panel Driver”</strong></a></p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://lupyuen.github.io/articles/touch2#open-the-touch-panel"><strong>“Open the Touch Panel”</strong></a></p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://lupyuen.github.io/articles/touch2#read-a-touch-sample"><strong>“Read a Touch Sample”</strong></a></p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://lupyuen.github.io/articles/touch2#interrupt-handler"><strong>“Interrupt Handler”</strong></a></p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://lupyuen.github.io/articles/touch2#setup-poll-for-touch-sample"><strong>“Setup Poll for Touch Sample”</strong></a></p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://lupyuen.github.io/articles/touch2#close-the-touch-panel"><strong>“Close the Touch Panel”</strong></a></p>
|
||
</li>
|
||
</ul>
|
||
<p><em>The driver code looks familiar?</em></p>
|
||
<p>We borrowed the logic from the NuttX Driver for <a href="https://github.com/apache/nuttx/blob/master/drivers/input/cypress_mbr3108.c"><strong>Cypress MBR3108</strong></a>.</p>
|
||
<p>(Which is also an I2C Input Device)</p>
|
||
<p>Let’s test our Touch Panel Driver with a NuttX App…</p>
|
||
<p><img src="https://lupyuen.github.io/images/fb-lvgl3.jpg" alt="LVGL Demo App on PinePhone" /></p>
|
||
<h1 id="lvgl-calls-our-driver"><a class="doc-anchor" href="#lvgl-calls-our-driver">§</a>7 LVGL Calls Our Driver</h1>
|
||
<p><em>Have we tested our driver with NuttX Apps?</em></p>
|
||
<p>Our NuttX Touch Panel Driver works great with the <a href="https://github.com/lvgl/lvgl/tree/v8.3.3/demos/widgets"><strong>LVGL Demo App</strong></a>! (Pic above)</p>
|
||
<ul>
|
||
<li>
|
||
<p><a href="https://www.youtube.com/shorts/APge9bTt-ho"><strong>Watch the Demo on YouTube</strong></a></p>
|
||
<p><a href="https://gist.github.com/lupyuen/fc88153b915894dbdaefcb5a916232fe">(See the Debug Log)</a></p>
|
||
<p><a href="https://github.com/lupyuen/pinephone-nuttx/releases/tag/v11.0.1">(Download the Binaries)</a></p>
|
||
</li>
|
||
</ul>
|
||
<p>Here are the <strong>LVGL Settings</strong> for NuttX…</p>
|
||
<ol>
|
||
<li>
|
||
<p>Enable “<strong>Application Configuration</strong> > <strong>Graphics Support</strong> > <strong>Light and Versatile Graphics Library (LVGL)</strong>”</p>
|
||
</li>
|
||
<li>
|
||
<p>Enable “<strong>LVGL</strong> > <strong>Enable Framebuffer Port</strong>”</p>
|
||
</li>
|
||
<li>
|
||
<p>Enable “<strong>LVGL</strong> > <strong>Enable Touchpad Port</strong>”</p>
|
||
</li>
|
||
<li>
|
||
<p>Browse into “<strong>LVGL</strong> > <strong>LVGL Configuration</strong>”</p>
|
||
<ul>
|
||
<li>
|
||
<p>In “<strong>Color Settings</strong>”</p>
|
||
<p>Set <strong>Color Depth</strong> to “<strong>32: ARGB8888</strong>”</p>
|
||
</li>
|
||
<li>
|
||
<p>In “<strong>Memory settings</strong>”</p>
|
||
<p>Set <strong>Size of Memory</strong> to <strong>64</strong></p>
|
||
</li>
|
||
<li>
|
||
<p>In “<strong>HAL Settings</strong>”</p>
|
||
<p>Set <strong>Default Dots Per Inch</strong> to <strong>300</strong></p>
|
||
</li>
|
||
<li>
|
||
<p>In “<strong>Demos</strong>”</p>
|
||
<p>Enable “<strong>Show Some Widgets</strong>”</p>
|
||
</li>
|
||
</ul>
|
||
</li>
|
||
<li>
|
||
<p>Enable “<strong>Application Configuration</strong> > <strong>Examples</strong> > <strong>LVGL Demo</strong>”</p>
|
||
</li>
|
||
</ol>
|
||
<p>Also we need to set in <strong><code>.config</code></strong>…</p>
|
||
<div class="example-wrap"><pre class="language-text"><code>CONFIG_LV_TICK_CUSTOM=y
|
||
CONFIG_LV_TICK_CUSTOM_INCLUDE="port/lv_port_tick.h"</code></pre></div>
|
||
<p>Which is advised by <a href="https://github.com/apache/nuttx-apps/pull/1341#issuecomment-1375742962"><strong>FASTSHIFT</strong></a>…</p>
|
||
<blockquote>
|
||
<p>“The tick of LVGL should not be placed in the same thread as the rendering, because the execution time of <code>lv_timer_handler</code> is not deterministic, which will cause a large error in LVGL tick.”</p>
|
||
</blockquote>
|
||
<blockquote>
|
||
<p>“We should let LVGL use the system timestamp provided by <code>lv_port_tick</code>, just need to set two options (above)”</p>
|
||
</blockquote>
|
||
<p><a href="https://github.com/FASTSHIFT">(Thank you so much <strong>FASTSHIFT</strong>!)</a></p>
|
||
<p><em>How does LVGL call our Touch Panel Driver?</em></p>
|
||
<p>The LVGL App begins by <strong>opening our Touch Panel Driver</strong> at <strong>/dev/input0</strong>: <a href="https://github.com/apache/nuttx-apps/blob/master/graphics/lvgl/port/lv_port_touchpad.c#L134-L178">lv_port_touchpad.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// From lv_port_touchpad_init()...
|
||
// Open the Touch Panel Device
|
||
int fd = open(
|
||
"/dev/input0", // Path of Touch Panel Device
|
||
O_RDONLY | O_NONBLOCK // Read-Only Access
|
||
);</code></pre></div>
|
||
<p>The app runs an <strong>Event Loop</strong> that periodically reads a <strong>Touch Sample</strong> from our driver: <a href="https://github.com/apache/nuttx-apps/blob/master/graphics/lvgl/port/lv_port_touchpad.c#L56-L99">lv_port_touchpad.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// From touchpad_read()...
|
||
// Struct for Touch Sample
|
||
struct touch_sample_s sample;
|
||
|
||
// Read a Touch Sample from Touch Panel
|
||
read(
|
||
fd, // File Descriptor from `open("/dev/input0")`
|
||
&sample, // Touch Sample
|
||
sizeof(struct touch_sample_s) // Size of Touch Sample
|
||
);</code></pre></div>
|
||
<p>(More about the Event Loop in a while)</p>
|
||
<p>We’ll receive a Touch Sample that contains <strong>0 or 1 Touch Points</strong>. <a href="https://lupyuen.github.io/articles/touch2#read-a-touch-sample">(More about Touch Samples)</a></p>
|
||
<p>(The Read Operation above is <strong>Non-Blocking</strong>. It returns 0 Touch Points if the screen hasn’t been touched)</p>
|
||
<p>We extract the <strong>First Touch Point</strong> (inside the Touch Sample) and return it to LVGL: <a href="https://github.com/apache/nuttx-apps/blob/master/graphics/lvgl/port/lv_port_touchpad.c#L56-L99">lv_port_touchpad.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// From touchpad_read()...
|
||
// Get the First Touch Event from the Touch Sample
|
||
uint8_t touch_flags = sample.point[0].flags;
|
||
|
||
// If the Touch Event is Touch Down or Touch Move...
|
||
if (touch_flags & TOUCH_DOWN || touch_flags & TOUCH_MOVE) {
|
||
// Report it as LVGL Press
|
||
// with the Touch Coordinates
|
||
touchpad_obj->last_state = LV_INDEV_STATE_PR;
|
||
touchpad_obj->last_x = sample.point[0].x;
|
||
touchpad_obj->last_y = sample.point[0].y;
|
||
...
|
||
} else if (touch_flags & TOUCH_UP) {
|
||
// If the Touch Event is Touch Up,
|
||
// report it as LVGL Release with
|
||
// the previous Touch Coordinates
|
||
touchpad_obj->last_state = LV_INDEV_STATE_REL;
|
||
}</code></pre></div>
|
||
<p>And that’s how LVGL polls our driver to handle Touch Events!</p>
|
||
<p>(LVGL polling our driver is not so efficient, but it works!)</p>
|
||
<p><em>How to create our own LVGL Touchscreen App?</em></p>
|
||
<p>Inside our NuttX Project, look for the <strong>LVGL Demo Source Code</strong>…</p>
|
||
<ul>
|
||
<li><a href="https://github.com/lvgl/lvgl/blob/v8.3.3/demos/widgets/lv_demo_widgets.c#L97-L197">apps/graphics/lvgl/lvgl/ demos/widgets/lv_demo_widgets.c</a></li>
|
||
</ul>
|
||
<p>Modify the function <a href="https://github.com/lvgl/lvgl/blob/v8.3.3/demos/widgets/lv_demo_widgets.c#L97-L197"><strong>lv_demo_widgets</strong></a> to create our own <strong>LVGL Widgets</strong>…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// Create a Button, set the Width and Height
|
||
void lv_demo_widgets(void) {
|
||
lv_obj_t *btn = lv_btn_create(lv_scr_act());
|
||
lv_obj_set_height(btn, LV_SIZE_CONTENT);
|
||
lv_obj_set_width(btn, 120);
|
||
}</code></pre></div>
|
||
<p>For details, check out the <a href="https://docs.lvgl.io/master/widgets/index.html"><strong>LVGL Widget Docs</strong></a>.</p>
|
||
<p><em>Can we improve the rendering speed?</em></p>
|
||
<p>Yep we need to <a href="https://lupyuen.github.io/articles/fb#fix-missing-pixels"><strong>flush the CPU Cache</strong></a> to fix a rendering issue with the Allwinner A64 Display Engine.</p>
|
||
<p>This ought to improve the rendering speed and make LVGL more responsive.</p>
|
||
<p><a href="https://lupyuen.github.io/articles/fb#fix-missing-pixels">(More about this)</a></p>
|
||
<p><em>Where’s this LVGL Event Loop that periodically reads a Touch Sample from our driver?</em></p>
|
||
<p>The <strong>LVGL Event Loop</strong> comes from the Main Function of our LVGL Demo App: <a href="https://github.com/apache/nuttx-apps/blob/master/examples/lvgldemo/lvgldemo.c#L240-L249">lvgldemo.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// Loop forever handling LVGL Event...
|
||
while (1) {
|
||
|
||
// Execute LVGL Background Tasks
|
||
uint32_t idle = lv_timer_handler();
|
||
|
||
// Minimum sleep of 1ms
|
||
idle = idle ? idle : 1;
|
||
usleep(idle * 1000);
|
||
}</code></pre></div>
|
||
<p><a href="https://docs.lvgl.io/8.3/porting/project.html#initialization"><strong>lv_timer_handler</strong></a> will periodically execute <strong>LVGL Background Tasks</strong> to…</p>
|
||
<ul>
|
||
<li>
|
||
<p>Read <strong>Touch Samples</strong> (from our Touch Input Driver)</p>
|
||
</li>
|
||
<li>
|
||
<p>Update the <strong>LVGL Display</strong></p>
|
||
</li>
|
||
</ul>
|
||
<h1 id="driver-limitations"><a class="doc-anchor" href="#driver-limitations">§</a>8 Driver Limitations</h1>
|
||
<p><em>Is there anything missing in our NuttX Touch Panel Driver for PinePhone?</em></p>
|
||
<p>Yep our <strong>driver has limitations</strong>, since the Touch Panel Hardware is poorly documented…</p>
|
||
<ol>
|
||
<li>
|
||
<p>Our driver doesn’t support <strong>Multitouch and Swiping</strong>.</p>
|
||
<p>Someday we might fix this when we decipher the (undocumented) <a href="https://github.com/goodix/gt9xx_driver_android/blob/master/gt9xx.c"><strong>Official Android Driver</strong></a>.</p>
|
||
<p>(2,000 lines of code!)</p>
|
||
</li>
|
||
<li>
|
||
<p>But the <a href="https://lupyuen.github.io/articles/touch2#lvgl-calls-our-driver"><strong>LVGL Demo</strong></a> doesn’t support Multitouch either.</p>
|
||
<p>(So we might put on hold for now)</p>
|
||
</li>
|
||
<li>
|
||
<p>PinePhone’s Touch Panel seems to trigger <strong>too few interrupts</strong>. <a href="https://lupyuen.github.io/articles/touch2#too-few-interrupts">(See this)</a></p>
|
||
<p>Again we’ll have to decipher the (still undocumented) <a href="https://github.com/goodix/gt9xx_driver_android/blob/master/gt9xx.c"><strong>Official Android Driver</strong></a> to fix this.</p>
|
||
</li>
|
||
<li>
|
||
<p>Every <strong><code>read()</code></strong> forces an <strong>I2C Read AND Write</strong>.</p>
|
||
<p>This feels expensive. We should fix this with our Interrupt Handler.</p>
|
||
<p><a href="https://lupyuen.github.io/articles/touch2#too-few-interrupts">(After fixing our Interrupt Handler)</a></p>
|
||
</li>
|
||
<li>
|
||
<p>Note to Future Self: <strong><code>poll()</code></strong> won’t work correctly for awaiting Touch Points!</p>
|
||
<p>That’s because the Touch Panel won’t generate interrupts for every Touch Point. <a href="https://lupyuen.github.io/articles/touch2#too-few-interrupts">(See this)</a></p>
|
||
<p><a href="https://lupyuen.github.io/articles/touch2#setup-poll-for-touch-sample">(More about the Poll Setup)</a></p>
|
||
</li>
|
||
<li>
|
||
<p>The <a href="https://lupyuen.github.io/articles/touch2#lvgl-calls-our-driver"><strong>LVGL Demo</strong></a> doesn’t call <strong><code>poll()</code></strong>, it only calls non-blocking <strong><code>read()</code></strong>.</p>
|
||
<p>So we’re good for now.</p>
|
||
</li>
|
||
<li>
|
||
<p>As we add more features to our Touch Panel Driver, we should reuse the <strong>Touchscreen Upper Half Driver</strong>: <a href="https://github.com/apache/nuttx/blob/master/drivers/input/touchscreen_upper.c">touchscreen_upper.c</a></p>
|
||
</li>
|
||
</ol>
|
||
<h1 id="whats-next"><a class="doc-anchor" href="#whats-next">§</a>9 What’s Next</h1>
|
||
<p>PinePhone on NuttX will soon support LVGL Touchscreen Apps!</p>
|
||
<p>Now we need to tidy up the <a href="https://lupyuen.github.io/articles/touch2#lvgl-calls-our-driver"><strong>LVGL Demo App</strong></a> so that it’s more <strong>Touch-Friendly</strong> for a Phone Form Factor. We’ll talk more about this…</p>
|
||
<ul>
|
||
<li><a href="https://github.com/lupyuen/pinephone-nuttx#lvgl-settings-for-pinephone"><strong>“LVGL Settings for PinePhone”</strong></a></li>
|
||
</ul>
|
||
<p>(Pardon me while I learn to shoot a decent video of a Touchscreen App with a Mirrorless Camera)</p>
|
||
<p>Please check out the other articles on NuttX for PinePhone…</p>
|
||
<ul>
|
||
<li><a href="https://github.com/lupyuen/pinephone-nuttx"><strong>“Apache NuttX RTOS for PinePhone”</strong></a></li>
|
||
</ul>
|
||
<p>Many Thanks to my <a href="https://lupyuen.github.io/articles/sponsor"><strong>GitHub Sponsors</strong></a> for supporting my work! This article wouldn’t have been possible without your support.</p>
|
||
<ul>
|
||
<li>
|
||
<p><a href="https://lupyuen.github.io/articles/sponsor"><strong>Sponsor me a coffee</strong></a></p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://www.reddit.com/r/PINE64official/comments/10ae29o/nuttx_rtos_for_pinephone_touch_panel/"><strong>Discuss this article on Reddit</strong></a></p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://github.com/lupyuen/nuttx-ox64"><strong>My Current Project: “Apache NuttX RTOS for Ox64 BL808”</strong></a></p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://github.com/lupyuen/nuttx-star64"><strong>My Other Project: “NuttX for Star64 JH7110”</strong></a></p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://github.com/lupyuen/pinephone-nuttx"><strong>Older Project: “NuttX for PinePhone”</strong></a></p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://lupyuen.github.io"><strong>Check out my articles</strong></a></p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://lupyuen.github.io/rss.xml"><strong>RSS Feed</strong></a></p>
|
||
</li>
|
||
</ul>
|
||
<p><em>Got a question, comment or suggestion? Create an Issue or submit a Pull Request here…</em></p>
|
||
<p><a href="https://github.com/lupyuen/lupyuen.github.io/blob/master/src/touch2.md"><strong>lupyuen.github.io/src/touch2.md</strong></a></p>
|
||
<h1 id="appendix-nuttx-touch-panel-driver-for-pinephone"><a class="doc-anchor" href="#appendix-nuttx-touch-panel-driver-for-pinephone">§</a>10 Appendix: NuttX Touch Panel Driver for PinePhone</h1>
|
||
<p><em>What’s inside our NuttX Touch Panel Driver for PinePhone?</em></p>
|
||
<p>We took the code from above and wrapped it inside our <strong>NuttX Touch Panel Driver</strong> for PinePhone…</p>
|
||
<ul>
|
||
<li><a href="https://github.com/apache/nuttx/blob/master/drivers/input/gt9xx.c"><strong>nuttx/drivers/input/gt9xx.c</strong></a></li>
|
||
</ul>
|
||
<p>NuttX Apps will access our driver at <strong>/dev/input0</strong>, which exposes the following <strong>File Operations</strong>: <a href="https://github.com/apache/nuttx/blob/master/drivers/input/gt9xx.c#L114-L132">gt9xx.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// File Operations supported by the Touch Panel
|
||
struct file_operations g_gt9xx_fileops = {
|
||
gt9xx_open, // Open the Touch Panel
|
||
gt9xx_close, // Close the Touch Panel
|
||
gt9xx_read, // Read a Touch Sample
|
||
gt9xx_poll // Setup Poll for Touch Sample</code></pre></div>
|
||
<p>NuttX Apps will call these Touch Panel Operations through the POSIX Standard Functions <strong><code>open()</code></strong>, <strong><code>close()</code></strong>, <strong><code>read()</code></strong> and <strong><code>poll()</code></strong>.</p>
|
||
<p><em>How do we start the Touch Panel Driver?</em></p>
|
||
<p>This is how we <strong>start the Touch Panel Driver</strong> when NuttX boots: <a href="https://github.com/apache/nuttx/blob/master/boards/arm64/a64/pinephone/src/pinephone_touch.c#L212-L245">pinephone_bringup.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// Default I2C Address for Goodix GT917S
|
||
#define CTP_I2C_ADDR 0x5d
|
||
|
||
// Register the Touch Panel Driver
|
||
ret = gt9xx_register(
|
||
"/dev/input0", // Device Path
|
||
i2c, // I2C Bus
|
||
CTP_I2C_ADDR, // I2C Address of Touch Panel
|
||
&g_pinephone_gt9xx // Callbacks for PinePhone Operations
|
||
);
|
||
DEBUGASSERT(ret == OK);</code></pre></div>
|
||
<p><a href="https://lupyuen.github.io/articles/touch2#register-touch-panel-driver">(<strong>gt9xx_register</strong> comes from our Touch Panel Driver)</a></p>
|
||
<p><a href="https://github.com/apache/nuttx/blob/master/boards/arm64/a64/pinephone/src/pinephone_touch.c#L70-L79">(<strong>g_pinephone_gt9xx</strong> defines the Interrupt Callbacks)</a></p>
|
||
<p><em>The driver code looks familiar?</em></p>
|
||
<p>We borrowed the logic from the NuttX Driver for <a href="https://github.com/apache/nuttx/blob/master/drivers/input/cypress_mbr3108.c"><strong>Cypress MBR3108</strong></a>.</p>
|
||
<p>(Which is also an I2C Input Device)</p>
|
||
<p><strong>UPDATE:</strong> We should reuse the <strong>Touchscreen Upper Half Driver</strong>: <a href="https://github.com/apache/nuttx/blob/master/drivers/input/touchscreen_upper.c">touchscreen_upper.c</a></p>
|
||
<p>Let’s talk about the Touch Panel operations…</p>
|
||
<h2 id="register-touch-panel-driver"><a class="doc-anchor" href="#register-touch-panel-driver">§</a>10.1 Register Touch Panel Driver</h2>
|
||
<p>At startup, <a href="https://github.com/apache/nuttx/blob/master/boards/arm64/a64/pinephone/src/pinephone_bringup.c#L155-L172"><strong>pinephone_bringup</strong></a> registers our Touch Panel Driver at <strong>/dev/input0</strong> by calling…</p>
|
||
<ul>
|
||
<li>
|
||
<p><a href="https://github.com/apache/nuttx/blob/master/boards/arm64/a64/pinephone/src/pinephone_touch.c#L212-L245"><strong>pinephone_touch_panel_register</strong></a>, which calls…</p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://github.com/apache/nuttx/blob/master/drivers/input/gt9xx.c#L887-L956"><strong>gt9xx_register: Register Touch Panel Driver</strong></a></p>
|
||
</li>
|
||
</ul>
|
||
<p>Which will…</p>
|
||
<ol>
|
||
<li>
|
||
<p><strong>Initialise the Struct</strong> for Touch Panel</p>
|
||
</li>
|
||
<li>
|
||
<p><strong>Register the Touch Panel Driver</strong> with NuttX</p>
|
||
<p>(At <strong>/dev/input0</strong>)</p>
|
||
</li>
|
||
<li>
|
||
<p><strong>Attach the Interrupt Handler</strong> with NuttX</p>
|
||
<p><a href="https://github.com/apache/nuttx/blob/master/boards/arm64/a64/pinephone/src/pinephone_touch.c#L83-L128">(Implemented as <strong>pinephone_gt9xx_irq_attach</strong>)</a></p>
|
||
<p><a href="https://lupyuen.github.io/articles/touch2#attach-our-interrupt-handler">(As explained here)</a></p>
|
||
<p><a href="https://lupyuen.github.io/articles/touch2#interrupt-handler">(Interrupt Handler is <strong>gt9xx_isr_handler</strong>)</a></p>
|
||
</li>
|
||
<li>
|
||
<p><strong>Disable Interrupts</strong> from the Touch Panel</p>
|
||
<p>(We’ll enable interrupts when we open the Touch Panel)</p>
|
||
</li>
|
||
</ol>
|
||
<p>Now watch what happens when a NuttX App opens the Touch Panel…</p>
|
||
<h2 id="open-the-touch-panel"><a class="doc-anchor" href="#open-the-touch-panel">§</a>10.2 Open the Touch Panel</h2>
|
||
<p>When a NuttX App calls <strong><code>open()</code></strong> on <strong>/dev/input0</strong>, NuttX Kernel invokes this operation on our driver…</p>
|
||
<ul>
|
||
<li><a href="https://github.com/apache/nuttx/blob/master/drivers/input/gt9xx.c#L582-L669"><strong>gt9xx_open: Open the Touch Panel</strong></a></li>
|
||
</ul>
|
||
<p>Inside the <strong>Open Operation</strong> we…</p>
|
||
<ol>
|
||
<li>
|
||
<p><strong>Power On</strong> the Touch Panel</p>
|
||
<p><a href="https://github.com/apache/nuttx/blob/master/boards/arm64/a64/pinephone/src/pinephone_touch.c#L184-L208">(Implemented as <strong>pinephone_gt9xx_set_power</strong>)</a></p>
|
||
</li>
|
||
<li>
|
||
<p><strong>Probe the Touch Panel</strong> on the I2C Bus, to verify that it exists</p>
|
||
<p><a href="https://github.com/apache/nuttx/blob/master/drivers/input/gt9xx.c#L291-L328">(Implemented as <strong>gt9xx_probe_device</strong>)</a></p>
|
||
<p><a href="https://lupyuen.github.io/articles/touch2#read-the-product-id">(Which reads the <strong>Product ID</strong>)</a></p>
|
||
<p><a href="https://github.com/apache/nuttx/blob/master/drivers/input/gt9xx.c#L136-L213">(By calling <strong>gt9xx_i2c_read</strong>)</a></p>
|
||
</li>
|
||
<li>
|
||
<p><strong>Enable Interrupts</strong> from the Touch Panel</p>
|
||
<p><a href="https://github.com/apache/nuttx/blob/master/boards/arm64/a64/pinephone/src/pinephone_touch.c#L128-L184">(Implemented as <strong>pinephone_gt9xx_irq_enable</strong>)</a></p>
|
||
<p><a href="https://lupyuen.github.io/articles/touch2#attach-our-interrupt-handler">(As explained here)</a></p>
|
||
</li>
|
||
</ol>
|
||
<p>The <a href="https://github.com/apache/nuttx/blob/master/drivers/input/gt9xx.c#L582-L669"><strong>Actual Flow</strong></a> looks more complicated because we do <strong>Reference Counting</strong>.</p>
|
||
<p>(We do the above steps only on the first call to <strong><code>open()</code></strong>)</p>
|
||
<p>Let’s read some touch data…</p>
|
||
<h2 id="read-a-touch-sample"><a class="doc-anchor" href="#read-a-touch-sample">§</a>10.3 Read a Touch Sample</h2>
|
||
<p><em>What’s a Touch Sample?</em></p>
|
||
<p>When a NuttX App reads data from our Touch Panel, the app passes a <strong>Touch Sample Struct</strong>…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// Struct for Touch Sample
|
||
struct touch_sample_s sample;
|
||
|
||
// Read a Touch Sample from Touch Panel
|
||
read(
|
||
fd, // File Descriptor from `open("/dev/input0")`
|
||
&sample, // Touch Sample
|
||
sizeof(struct touch_sample_s) // Size of Touch Sample
|
||
);</code></pre></div>
|
||
<p><a href="https://github.com/apache/nuttx-apps/blob/master/graphics/lvgl/port/lv_port_touchpad.c#L60-L70">(Source)</a></p>
|
||
<p>A Touch Sample contains <strong>One Touch Point</strong> (by default): <a href="https://github.com/apache/nuttx/blob/master/include/nuttx/input/touchscreen.h#L129-L149">touchscreen.h</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// Touch Sample Struct
|
||
struct touch_sample_s {
|
||
int npoints; // Number of Touch Points in point[]
|
||
struct touch_point_s point[1]; // Touch Points of length npoints
|
||
};</code></pre></div>
|
||
<p>A <strong>Touch Point</strong> contains the X and Y Coordinates, also indicates whether it’s Touch Up or Touch Down: <a href="https://github.com/apache/nuttx/blob/master/include/nuttx/input/touchscreen.h#L112-L129">touchscreen.h</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// Touch Point Struct
|
||
struct touch_point_s {
|
||
uint8_t id; // Identifies the finger touched (Multitouch)
|
||
uint8_t flags; // Touch Up or Touch Down
|
||
int16_t x; // X Coordinate of the Touch Point
|
||
int16_t y; // Y Coordinate of the Touch Point
|
||
...</code></pre></div>
|
||
<p>When the app calls <strong><code>read()</code></strong>, NuttX Kernel calls our driver at…</p>
|
||
<ul>
|
||
<li><a href="https://github.com/apache/nuttx/blob/master/drivers/input/gt9xx.c#L451-L582"><strong>gt9xx_read: Read a Touch Sample</strong></a></li>
|
||
</ul>
|
||
<p>Which does this…</p>
|
||
<ol>
|
||
<li>
|
||
<p>If the <strong>Last Result</strong> was <strong>Touch Down</strong>…</p>
|
||
<p>We return the Last Touch Point, now changed to <strong>Touch Up</strong>.</p>
|
||
<p><a href="https://lupyuen.github.io/articles/touch2#lvgl-calls-our-driver">(We simulate the Touch Up because our LVGL Demo expects it)</a></p>
|
||
</li>
|
||
<li>
|
||
<p>If the <strong>Last Result</strong> was <strong>NOT Touch Down</strong>…</p>
|
||
<p>We clear the Interrupt Pending Flag, <strong>read the Touch Point</strong> from the Touch Panel and return it.</p>
|
||
<p><a href="https://github.com/apache/nuttx/blob/master/drivers/input/gt9xx.c#L362-L451">(Implemented as <strong>gt9xx_read_touch_data</strong>)</a></p>
|
||
<p><a href="https://lupyuen.github.io/articles/touch2#read-a-touch-point">(As explained here)</a></p>
|
||
<p><a href="https://github.com/apache/nuttx/blob/master/drivers/input/gt9xx.c#L328-L362">(Which calls <strong>gt9xx_set_status</strong> to set the status)</a></p>
|
||
<p><a href="https://github.com/apache/nuttx/blob/master/drivers/input/gt9xx.c#L213-L291">(Which calls <strong>gt9xx_i2c_write</strong> to write over I2C)</a></p>
|
||
<p>We ignore <strong>Duplicate Touch Points</strong>.</p>
|
||
<p><a href="https://gist.github.com/lupyuen/52bd626001f94e279c736979e074aac9">(Otherwise we’ll see duplicates like this)</a></p>
|
||
<p><a href="https://www.youtube.com/shorts/APge9bTt-ho">(Also fixes the duplicate keypresses at the end of this video)</a></p>
|
||
</li>
|
||
</ol>
|
||
<p>Since our driver doesn’t support Multitouch, the Read Operation will return <strong>either 0 or 1 Touch Points</strong>.</p>
|
||
<p><em>Why the Duplicate Touch Points?</em></p>
|
||
<p>Right now we ignore <strong>Duplicate Touch Points</strong>, because we saw the Touch Panel generating duplicate points. <a href="https://gist.github.com/lupyuen/52bd626001f94e279c736979e074aac9">(See this)</a></p>
|
||
<p><a href="https://github.com/lupyuen2/wip-nuttx-apps/blob/touch2/graphics/lvgl/port/lv_port_touchpad.c#L83-L99">(We added the logs here)</a></p>
|
||
<p>The Touch Panel seems to be producing <strong>Touch Up Events</strong>… Even though the 6-byte Touch Data looks identical for the Touch Down and Touch Up Events. <a href="https://gist.github.com/lupyuen/fc88153b915894dbdaefcb5a916232fe">(See this)</a></p>
|
||
<p>Eventually we’ll have to decode the Touch Up Events. And then remove our <a href="https://lupyuen.github.io/articles/touch2#read-a-touch-sample"><strong>Simulated Touch Up Event</strong></a>.</p>
|
||
<p>Let’s talk about the Interrupt Pending Flag…</p>
|
||
<h2 id="interrupt-handler"><a class="doc-anchor" href="#interrupt-handler">§</a>10.4 Interrupt Handler</h2>
|
||
<p>This is our <strong>Interrupt Handler</strong> for Touch Panel Interrupts…</p>
|
||
<ul>
|
||
<li><a href="https://github.com/apache/nuttx/blob/master/drivers/input/gt9xx.c#L842-L883"><strong>gt9xx_isr_handler: Interrupt Handler</strong></a></li>
|
||
</ul>
|
||
<p>Inside the Interrupt Handler we…</p>
|
||
<ol>
|
||
<li>
|
||
<p>Set the <strong>Interrupt Pending Flag</strong></p>
|
||
<p>(Which is protected by a NuttX Critical Section)</p>
|
||
</li>
|
||
<li>
|
||
<p>Notify the <strong>Poll Waiters</strong> (Background Threads)</p>
|
||
<p><a href="https://lupyuen.github.io/articles/touch2#handle-interrupts-from-touch-panel">(As explained here)</a></p>
|
||
</li>
|
||
</ol>
|
||
<p>Now we talk about the Poll Waiters…</p>
|
||
<h2 id="setup-poll-for-touch-sample"><a class="doc-anchor" href="#setup-poll-for-touch-sample">§</a>10.5 Setup Poll for Touch Sample</h2>
|
||
<p>A NuttX App calls <strong><code>poll()</code></strong> to set up (or tear down) a <strong>Poll for Touch Sample</strong>.</p>
|
||
<p>This enables the app to suspend itself and <strong>block until a Touch Panel Interrupt</strong> has been triggered. (And there’s a Touch Point available)</p>
|
||
<p>When an app calls <strong><code>poll()</code></strong>, the NuttX Kernel calls our driver at…</p>
|
||
<ul>
|
||
<li><a href="https://github.com/apache/nuttx/blob/master/drivers/input/gt9xx.c#L735-L842"><strong>gt9xx_poll: Setup Poll for Touch Sample</strong></a></li>
|
||
</ul>
|
||
<p><strong>For Poll Setup:</strong></p>
|
||
<ol>
|
||
<li>
|
||
<p>We find an Available Slot for the <strong>Poll Waiter</strong></p>
|
||
<p><a href="https://github.com/apache/nuttx/blob/master/drivers/input/gt9xx.c#L72-L99">(Poll Waiter Slots are defined in <strong>gt9xx_dev_s</strong>)</a></p>
|
||
<p><a href="https://github.com/apache/nuttx/blob/master/drivers/input/Kconfig#L501-L522">(<strong>INPUT_GT9XX_NPOLLWAITERS</strong> is the max number of slots, set to 1)</a></p>
|
||
</li>
|
||
<li>
|
||
<p>We <strong>bind the Poll Struct</strong> and this Slot</p>
|
||
</li>
|
||
<li>
|
||
<p>If <strong>Interrupt Pending</strong> is set, we notify the Poll Waiters</p>
|
||
</li>
|
||
</ol>
|
||
<p><strong>For Poll Teardown</strong>: We unbind the Poll Setup</p>
|
||
<h2 id="close-the-touch-panel"><a class="doc-anchor" href="#close-the-touch-panel">§</a>10.6 Close the Touch Panel</h2>
|
||
<p>When a NuttX App calls <strong><code>close()</code></strong> on <strong>/dev/input0</strong>, NuttX Kernel invokes this operation on our driver…</p>
|
||
<ul>
|
||
<li><a href="https://github.com/apache/nuttx/blob/master/drivers/input/gt9xx.c#L669-L735"><strong>gt9xx_close: Close the Touch Panel</strong></a></li>
|
||
</ul>
|
||
<p>Inside the <strong>Close Operation</strong> we…</p>
|
||
<ol>
|
||
<li>
|
||
<p><strong>Disable Interrupts</strong> from the Touch Panel</p>
|
||
<p><a href="https://github.com/apache/nuttx/blob/master/boards/arm64/a64/pinephone/src/pinephone_touch.c#L128-L184">(Implemented as <strong>pinephone_gt9xx_irq_enable</strong>)</a></p>
|
||
</li>
|
||
<li>
|
||
<p><strong>Power Off</strong> the Touch Panel</p>
|
||
<p><a href="https://github.com/apache/nuttx/blob/master/boards/arm64/a64/pinephone/src/pinephone_touch.c#L184-L208">(Implemented as <strong>pinephone_gt9xx_set_power</strong>)</a></p>
|
||
</li>
|
||
</ol>
|
||
<p>We do this only if the <strong>Reference Count</strong> decrements to 0.</p>
|
||
<p>(Which indicates the final <strong><code>close()</code></strong> for our driver)</p>
|
||
<p><img src="https://lupyuen.github.io/images/touch2-code5a.png" alt="Attaching our Interrupt Handler" /></p>
|
||
<h1 id="appendix-interrupt-handler-for-touch-panel"><a class="doc-anchor" href="#appendix-interrupt-handler-for-touch-panel">§</a>11 Appendix: Interrupt Handler for Touch Panel</h1>
|
||
<p>Earlier we’ve read the Touch Panel by polling… Which is easier but inefficient.</p>
|
||
<ul>
|
||
<li>
|
||
<p><a href="https://lupyuen.github.io/articles/touch2#poll-the-touch-panel"><strong>“Poll the Touch Panel”</strong></a></p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://lupyuen.github.io/articles/touch2#read-a-touch-point"><strong>“Read a Touch Point”</strong></a></p>
|
||
</li>
|
||
</ul>
|
||
<p>So we tried reading the Touch Panel with an <strong>Interrupt Handler</strong>… But there’s a problem: The Touch Panel only <strong>fires an interrupt once</strong>!</p>
|
||
<p>It won’t trigger interrupts correctly when we touch the screen.</p>
|
||
<p><em>Is this a showstopper for our Touch Panel Driver?</em></p>
|
||
<p>Not really, polling will work fine for now.</p>
|
||
<p>Earlier we saw that the <a href="https://lupyuen.github.io/articles/touch2#lvgl-calls-our-driver"><strong>LVGL Demo App</strong></a> runs OK because it uses polling. (Instead of interrupts)</p>
|
||
<p>This section talks about our experiments with Touch Panel Interrupts…</p>
|
||
<h2 id="attach-our-interrupt-handler"><a class="doc-anchor" href="#attach-our-interrupt-handler">§</a>11.1 Attach our Interrupt Handler</h2>
|
||
<p>Earlier we said that PinePhone’s Touch Panel fires an <strong>interrupt at PH4</strong> when it’s touched…</p>
|
||
<ul>
|
||
<li><a href="https://lupyuen.github.io/articles/touch2#goodix-gt917s-touch-panel"><strong>“Goodix GT917S Touch Panel”</strong></a></li>
|
||
</ul>
|
||
<p>This is how we attach our <strong>Interrupt Handler</strong> to PH4 in NuttX: <a href="https://github.com/lupyuen2/wip-nuttx/blob/touch2/boards/arm64/a64/pinephone/src/pinephone_bringup.c#L289-L329">pinephone_bringup.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// Touch Panel Interrupt (CTP-INT) is at PH4
|
||
#define CTP_INT ( \
|
||
PIO_EINT | \ /* PIO External Interrupt */
|
||
PIO_PORT_PIOH | \ /* PIO Port H */
|
||
PIO_PIN4 \ /* PIO Pin 4 */
|
||
)
|
||
|
||
// Register the Interrupt Handler for Touch Panel
|
||
void touch_panel_initialize(void) {
|
||
|
||
// Attach the PIO Interrupt Handler for Port PH
|
||
int ret = irq_attach( // Attach a NuttX Interrupt Handler...
|
||
A64_IRQ_PH_EINT, // Interrupt Number for Port PH: 53
|
||
touch_panel_interrupt, // Interrupt Handler
|
||
NULL // Argument for Interrupt Handler
|
||
);
|
||
DEBUGASSERT(ret == OK);
|
||
|
||
// Set Interrupt Priority in Generic Interrupt Controller v2
|
||
arm64_gic_irq_set_priority(
|
||
A64_IRQ_PH_EINT, // Interrupt Number for Port PH: 53
|
||
2, // Interrupt Priority
|
||
IRQ_TYPE_EDGE // Trigger on Low-High Transition
|
||
);
|
||
|
||
// Enable the PIO Interrupt for Port PH.
|
||
// A64_IRQ_PH_EINT is 53.
|
||
up_enable_irq(A64_IRQ_PH_EINT);
|
||
|
||
// Configure the Touch Panel Interrupt for Pin PH4
|
||
ret = a64_pio_config(CTP_INT);
|
||
DEBUGASSERT(ret == OK);
|
||
|
||
// Enable the Touch Panel Interrupt for Pin PH4
|
||
ret = a64_pio_irqenable(CTP_INT);
|
||
DEBUGASSERT(ret == OK);
|
||
}</code></pre></div>
|
||
<p><a href="https://github.com/apache/nuttx/blob/master/arch/arm64/src/common/arm64_gicv2.c#L1275-L1325">(<strong>arm64_gic_irq_set_priority</strong> configures the Generic Interrupt Controller)</a></p>
|
||
<p><a href="https://github.com/apache/nuttx/blob/master/arch/arm64/src/a64/a64_pio.c#L174-L344">(<strong>a64_pio_config</strong> configures PH4 as an Interrupt Pin)</a></p>
|
||
<p><a href="https://github.com/apache/nuttx/blob/master/arch/arm64/src/a64/a64_pio.c#L420-L440">(<strong>a64_pio_irqenable</strong> enables interrupts on Pin PH4)</a></p>
|
||
<p><em>Why call both up_enable_irq and a64_pio_irqenable?</em></p>
|
||
<p>Allwinner A64 does Two-Tier Interrupts, by Port and Pin…</p>
|
||
<ul>
|
||
<li>
|
||
<p>First we enable interrupts for <strong>Port PH</strong></p>
|
||
<p>(By calling <strong>up_enable_irq</strong>)</p>
|
||
</li>
|
||
<li>
|
||
<p>Then we enable interrupts for <strong>Pin PH4</strong></p>
|
||
<p><a href="https://github.com/apache/nuttx/blob/master/arch/arm64/src/a64/a64_pio.c#L420-L440">(By calling <strong>a64_pio_irqenable</strong>)</a></p>
|
||
</li>
|
||
</ul>
|
||
<p>Which means that our Interrupt Handler will be shared by <strong>all Pins on Port PH</strong>.</p>
|
||
<p>(When we enable them in future)</p>
|
||
<p><em>What’s touch_panel_interrupt?</em></p>
|
||
<p><strong>touch_panel_interrupt</strong> is our Interrupt Handler. Let’s do a simple one…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// Interrupt Handler for Touch Panel
|
||
static int touch_panel_interrupt(int irq, void *context, void *arg) {
|
||
|
||
// Print something when interrupt is triggered
|
||
up_putc('.');
|
||
return OK;
|
||
}</code></pre></div>
|
||
<p>This Interrupt Handler simply prints “<strong><code>.</code></strong>” whenever the Touch Panel triggers an interrupt.</p>
|
||
<p>But our Interrupt Handler won’t actually read the Touch Coordinates.</p>
|
||
<p>(Because Interrupt Handlers can’t make I2C calls)</p>
|
||
<p>We’ll fix this in the next section.</p>
|
||
<p><em>It’s OK to call up_putc in an Interrupt Handler?</em></p>
|
||
<p>Yep it’s perfectly OK, because <a href="https://github.com/apache/nuttx/blob/master/arch/arm64/src/a64/a64_serial.c#L619-L649"><strong>up_putc</strong></a> simply writes to the UART Output Register.</p>
|
||
<p>(It won’t trigger another interrupt)</p>
|
||
<p><img src="https://lupyuen.github.io/images/touch2-code6a.png" alt="Handling Interrupts from Touch Panel" /></p>
|
||
<h2 id="handle-interrupts-from-touch-panel"><a class="doc-anchor" href="#handle-interrupts-from-touch-panel">§</a>11.2 Handle Interrupts from Touch Panel</h2>
|
||
<p>Here’s the actual Interrupt Handler in our Touch Panel Driver: <a href="https://github.com/apache/nuttx/blob/master/drivers/input/gt9xx.c#L842-L883">gt9xx.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// Interrupt Handler for Touch Panel
|
||
static int gt9xx_isr_handler(int irq, FAR void *context, FAR void *arg) {
|
||
|
||
// For Testing: Print something when interrupt is triggered
|
||
up_putc('.');
|
||
|
||
// Get the Touch Panel Device
|
||
FAR struct gt9xx_dev_s *priv = (FAR struct gt9xx_dev_s *)arg;
|
||
|
||
// Begin Critical Section
|
||
flags = enter_critical_section();
|
||
|
||
// Set the Interrupt Pending Flag
|
||
priv->int_pending = true;
|
||
|
||
// End Critical Section
|
||
leave_critical_section(flags);
|
||
|
||
// Notify the Poll Waiters
|
||
poll_notify( // Notify these File Descriptors...
|
||
priv->fds, // File Descriptors to notify
|
||
1, // Max 1 File Descriptor supported
|
||
POLLIN // Poll Event to be notified
|
||
);
|
||
return 0;
|
||
}</code></pre></div>
|
||
<p><a href="https://github.com/apache/nuttx/blob/master/drivers/input/gt9xx.c#L72-L99">(<strong>gt9xx_dev_s</strong> is the Touch Panel Device)</a></p>
|
||
<p>Our Interrupt Handler won’t actually read the Touch Coordinates. (Because Interrupt Handlers can’t make I2C calls)</p>
|
||
<p>Instead our Interrupt Handler sets the <strong>Interrupt Pending Flag</strong> and <strong>notifies the Background Thread</strong> (via Poll Waiters) that there’s a Touch Event waiting to be processed.</p>
|
||
<p>The Background Thread calls <strong><code>poll()</code></strong>, suspends itself and <strong>waits for the notification</strong> before processing the Touch Event over I2C.</p>
|
||
<p><a href="https://github.com/apache/nuttx/blob/master/drivers/input/gt9xx.c#L735-L842">(Thanks to <strong>gt9xx_poll</strong>)</a></p>
|
||
<p>Let’s test our new and improved Interrupt Handler…</p>
|
||
<p><img src="https://lupyuen.github.io/images/touch2-run3a.png" alt="Testing our Interrupt Handler" /></p>
|
||
<h2 id="test-our-interrupt-handler"><a class="doc-anchor" href="#test-our-interrupt-handler">§</a>11.3 Test our Interrupt Handler</h2>
|
||
<p><em>How do we test our Interrupt Handler?</em></p>
|
||
<p>We could start a Background Thread that will be notified when the screen is touched…</p>
|
||
<p>Or we can run a simple loop that checks whether the <strong>Interrupt Pending Flag is set</strong> by our Interrupt Handler.</p>
|
||
<p>Let’s test the simple way: <a href="https://github.com/lupyuen2/wip-nuttx/blob/c3eccc67d879806a015ae592205e641dcffa7d09/boards/arm64/a64/pinephone/src/pinephone_bringup.c#L293-L309">pinephone_bringup.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// Poll for Touch Panel Interrupt
|
||
for (int i = 0; i < 6000; i++) { // Poll for 60 seconds
|
||
|
||
// If Touch Panel Interrupt has been triggered...
|
||
if (priv->int_pending) {
|
||
|
||
// Read the Touch Panel over I2C
|
||
touch_panel_read(i2c_dev);
|
||
|
||
// Reset the Interrupt Pending Flag
|
||
priv->int_pending = false;
|
||
}
|
||
|
||
// Wait a while
|
||
up_mdelay(10); // 10 milliseconds
|
||
}</code></pre></div>
|
||
<p>(This loop works only when Interrupt Trigger is set to <strong>IRQ_TYPE_LEVEL</strong>. See the next section)</p>
|
||
<p>Note that we call <a href="https://lupyuen.github.io/articles/touch2#read-a-touch-point"><strong>touch_panel_read</strong></a> to read the Touch Coordinates. (After the Touch Interrupt has been triggered)</p>
|
||
<p>And it works! (Pic above)</p>
|
||
<div class="example-wrap"><pre class="language-text"><code>0000 81 .
|
||
0000 19 01 e6 02 2a 00 ....*.
|
||
touch_panel_read: touch x=281, y=742
|
||
|
||
0000 81 .
|
||
0000 81 02 33 00 25 00 ..3.%.
|
||
touch_panel_read: touch x=641, y=51
|
||
|
||
0000 81 .
|
||
0000 0f 00 72 05 14 00 ..r...
|
||
touch_panel_read: touch x=15, y=1394</code></pre></div>
|
||
<p><a href="https://gist.github.com/lupyuen/91a37a4b54f75f7386374a30821dc1b2">(Source)</a></p>
|
||
<p>The log shows that we’ve read the Touch Panel Status <strong><code>0x81</code></strong>, followed by the Touch Coordinates. Yep we’ve tested our Interrupt Handler successfully!</p>
|
||
<p>But there’s a problem…</p>
|
||
<h2 id="too-few-interrupts"><a class="doc-anchor" href="#too-few-interrupts">§</a>11.4 Too Few Interrupts</h2>
|
||
<p><em>So Touch Panel Interrupts are working OK?</em></p>
|
||
<p>There’s a problem: The Touch Panel only <strong>fires an interrupt once</strong>!</p>
|
||
<p>It won’t trigger interrupts correctly when we touch the screen.</p>
|
||
<p><em>How do we know this?</em></p>
|
||
<p>The Debug Log shows that “<strong><code>.</code></strong>” is printed only once… Which means our Interrupt Handler is only triggered once!</p>
|
||
<div class="example-wrap"><pre class="language-text"><code>gt9xx_probe_device (0x40b1ba18):
|
||
0000 39 31 37 53 917S
|
||
pinephone_gt9xx_irq_enable: enable=1
|
||
gt9xx_set_status: status=0
|
||
gt9xx_i2c_write: reg=0x814e, val=0
|
||
touchpad /dev/input0 open success
|
||
touchpad_init
|
||
.
|
||
Before: disp_size=2
|
||
After: disp_size=1
|
||
gt9xx_read: buflen=32
|
||
gt9xx_read_touch_data:
|
||
gt9xx_i2c_read: reg=0x814e, buflen=1
|
||
gt9xx_i2c_read (0x40b1bab0):
|
||
0000 80</code></pre></div>
|
||
<p><a href="https://gist.github.com/lupyuen/3b406c58ea275a3dfadc4c4dff50f1a7">(See the Debug Log)</a></p>
|
||
<p>We might need to study the <a href="https://lupyuen.github.io/articles/interrupt#generic-interrupt-controller"><strong>Generic Interrupt Controller</strong></a> to learn how it handles such interrupts.</p>
|
||
<p><em>Is this a showstopper for our Touch Panel Driver?</em></p>
|
||
<p>Not really, polling will work fine for now.</p>
|
||
<p>Earlier we saw that the <a href="https://lupyuen.github.io/articles/touch2#lvgl-calls-our-driver"><strong>LVGL Demo App</strong></a> runs OK because it uses polling. (Instead of interrupts)</p>
|
||
<p>But let’s consider an alternative setup (with too many interrupts)…</p>
|
||
<p><img src="https://lupyuen.github.io/images/touch2-run2a.png" alt="Touch Panel triggers our Interrupt Handler Non-Stop" /></p>
|
||
<h2 id="too-many-interrupts"><a class="doc-anchor" href="#too-many-interrupts">§</a>11.5 Too Many Interrupts</h2>
|
||
<p><em>Why did we set the Interrupt Trigger to IRQ_TYPE_EDGE?</em></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// Set Interrupt Priority in Generic Interrupt Controller v2
|
||
arm64_gic_irq_set_priority(
|
||
A64_IRQ_PH_EINT, // Interrupt Number for Port PH: 53
|
||
0, // Interrupt Priority
|
||
IRQ_TYPE_EDGE // Trigger on Low-High Transition
|
||
);</code></pre></div>
|
||
<p><a href="https://github.com/apache/nuttx/blob/master/boards/arm64/a64/pinephone/src/pinephone_touch.c#L83-L128">(Source)</a></p>
|
||
<p><strong>IRQ_TYPE_EDGE</strong> means that the interrupt is triggered on <strong>Low-High Transitions</strong>.</p>
|
||
<p>When we tested this, the Touch Panel triggers an interrupt <strong>only once</strong>.</p>
|
||
<p>(We’re not sure why it doesn’t trigger further interrupts)</p>
|
||
<p>Let’s try out <strong>IRQ_TYPE_LEVEL</strong>, which triggers an interrupt by <strong>Low-High Level</strong>…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// Set Interrupt Priority in Generic Interrupt Controller v2
|
||
arm64_gic_irq_set_priority(
|
||
A64_IRQ_PH_EINT, // Interrupt Number for Port PH: 53
|
||
2, // Interrupt Priority
|
||
IRQ_TYPE_LEVEL // Trigger by Level
|
||
);</code></pre></div>
|
||
<p><em>What happens when we run it?</em></p>
|
||
<p>When we run the code, it generates a <strong>never-ending stream</strong> of “<strong><code>.</code></strong>” characters…</p>
|
||
<p><strong>Without us touching</strong> the screen! (Pic above)</p>
|
||
<p><em>Is this a bad thing?</em></p>
|
||
<p>Yes it’s terrible! This means that the Touch Panel fires Touch Input Interrupts continuously…</p>
|
||
<p><strong>NuttX will be overwhelmed</strong> handling Touch Input Interrupts 100% of the time. No time for other tasks!</p>
|
||
<p><em>What if we limit the interrupts?</em></p>
|
||
<p>Previously we tried <strong>throttling the interrupts</strong> from the Touch Panel. We <strong>disable the Touch Panel Interrupt</strong> if we’re still waiting for it to be processed: <a href="https://github.com/lupyuen2/wip-nuttx/blob/d535cee56c5a362db04ad0f69a13d4c16a47936d/drivers/input/gt9xx.c#L856-L870">gt9xx.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// Interrupt Handler for Touch Panel, with Throttling and Forwarding
|
||
static int gt9xx_isr_handler(int irq, FAR void *context, FAR void *arg) {
|
||
|
||
// Print "." when Interrupt Handler is triggered
|
||
up_putc('.');
|
||
|
||
// Get the Touch Panel Device
|
||
FAR struct gt9xx_dev_s *priv = (FAR struct gt9xx_dev_s *)arg;
|
||
|
||
// If the Touch Panel Interrupt has not been processed...
|
||
if (priv->int_pending) {
|
||
|
||
// Disable the Touch Panel Interrupt
|
||
priv->board->irq_enable(priv->board, false);
|
||
}
|
||
|
||
// Omitted: Set the Interrupt Pending Flag
|
||
// and notify the Poll Waiters</code></pre></div>
|
||
<p>It seems to work… But it doesn’t look right to throttle interrupts in an Interrupt Handler.</p>
|
||
<p><em>Is it OK to throttle interrupts?</em></p>
|
||
<p>Between calls to <strong><code>read()</code></strong>, our driver might <strong>fail to detect</strong> some Touch Input Events.</p>
|
||
<p>This happens because we throttle the Touch Panel Interrupts, and we re-enable them only when <strong><code>read()</code></strong> is called. <a href="https://github.com/lupyuen2/wip-nuttx/blob/d535cee56c5a362db04ad0f69a13d4c16a47936d/drivers/input/gt9xx.c#L495-L500">(Like this)</a></p>
|
||
<p>Interrupts that fire before <strong><code>read()</code></strong> will likely get ignored.</p>
|
||
<p><em>Maybe we didn’t set the Touch Panel Status correctly? Causing the Excessive Interrupts?</em></p>
|
||
<p>We checked that the <strong>Touch Panel Status</strong> was correctly set to 0 after every interrupt. Yet we’re still receiving Excessive Interrupts. <a href="https://gist.github.com/lupyuen/726110f8d24416584fe232330ffb1683">(See this)</a></p>
|
||
<p><a href="https://gist.github.com/lupyuen/726110f8d24416584fe232330ffb1683">(Why does Status <code>0x81</code> change to <code>0x80</code>, instead of 0?)</a></p>
|
||
<p>Thus we need to stick with <strong>IRQ_TYPE_EDGE</strong> and figure out how to trigger interrupts correctly on Touch Input.</p>
|
||
<p><a href="https://lupyuen.github.io/articles/interrupt#generic-interrupt-controller">(Perhaps by studying the <strong>Generic Interrupt Controller</strong>)</a></p>
|
||
<h2 id="interrupt-priority"><a class="doc-anchor" href="#interrupt-priority">§</a>11.6 Interrupt Priority</h2>
|
||
<p><em>Why did we set Interrupt Priority to 2?</em></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// Set Interrupt Priority in Generic Interrupt Controller v2
|
||
arm64_gic_irq_set_priority(
|
||
A64_IRQ_PH_EINT, // Interrupt Number for Port PH: 53
|
||
2, // Interrupt Priority
|
||
IRQ_TYPE_EDGE // Trigger on Low-High Transition
|
||
);</code></pre></div>
|
||
<p><a href="https://github.com/apache/nuttx/blob/master/boards/arm64/a64/pinephone/src/pinephone_touch.c#L83-L128">(Source)</a></p>
|
||
<p>We set the Interrupt Priority to 2 for <strong>Legacy Reasons</strong>…</p>
|
||
<p>The code below comes from the Early Days of NuttX Arm64: <a href="https://github.com/apache/nuttx/blob/master/arch/arm64/src/qemu/qemu_serial.c#L585-L630">arch/arm64/src/qemu/qemu_serial.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// Attach Interrupt Handler for Arm64 QEMU UART
|
||
static int qemu_pl011_attach(struct uart_dev_s *dev) {
|
||
...
|
||
// Set Interrupt Priority for Arm64 QEMU UART
|
||
arm64_gic_irq_set_priority(
|
||
sport->irq_num, // Interrupt Number for UART
|
||
IRQ_TYPE_LEVEL, // Interrupt Priority is 2 ???
|
||
0 // Interrupt Trigger by Level ???
|
||
);</code></pre></div>
|
||
<p>There seems to be a <strong>mix-up in the arguments</strong>: Interrupt Priority vs Interrupt Trigger.</p>
|
||
<p><strong>IRQ_TYPE_LEVEL</strong> is 2, so the code above sets the <strong>Interrupt Priority to 2</strong>, with the Default Interrupt Trigger (by Level).</p>
|
||
<p>That’s why we configured our Touch Panel Interrupt for <strong>Priority 2</strong>, to be consistent with the existing calls.</p>
|
||
<p>Someday we should fix all existing calls to <strong>arm64_gic_irq_set_priority</strong>…</p>
|
||
<ul>
|
||
<li>
|
||
<p><a href="https://github.com/apache/nuttx/blob/master/arch/arm64/src/qemu/qemu_serial.c#L585-L630"><strong>qemu_pl011_attach</strong></a></p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://github.com/apache/nuttx/blob/master/arch/arm64/src/a64/a64_serial.c#L204-L253"><strong>a64_uart_attach</strong></a></p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://github.com/apache/nuttx/blob/master/arch/arm64/src/a64/a64_twi.c#L1808-L1859"><strong>twi_hw_initialize</strong></a></p>
|
||
</li>
|
||
</ul>
|
||
<p>To this…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // Set Interrupt Priority with Correct Arguments
|
||
arm64_gic_irq_set_priority(
|
||
irq_num, // Interrupt Number
|
||
0, // Interrupt Priority
|
||
IRQ_TYPE_LEVEL // Interrupt Trigger by Level
|
||
);</code></pre></div>
|
||
<p><a href="https://github.com/apache/nuttx/blob/master/boards/arm64/a64/pinephone/src/pinephone_touch.c#L83-L128">(<strong>UPDATE</strong>: Interrupt Priority is now 0 for the Touch Panel)</a></p>
|
||
|
||
|
||
<!-- Begin scripts/rustdoc-after.html: Post-HTML for Custom Markdown files processed by rustdoc, like chip8.md -->
|
||
|
||
<!-- Begin Theme Picker and Prism Theme -->
|
||
<script src="../theme.js"></script>
|
||
<script src="../prism.js"></script>
|
||
<!-- Theme Picker and Prism Theme -->
|
||
|
||
<!-- End scripts/rustdoc-after.html -->
|
||
|
||
|
||
</body>
|
||
</html> |