lupyuen.org/articles/touch2.html

1203 lines
No EOL
73 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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="Whats Next">9 Whats 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>Were 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 wont work yet with <strong>Touch Input</strong>!</p>
<p>Lets talk about the <strong>Capacitive Touch Panel</strong> inside PinePhone…</p>
<ul>
<li>
<p>How its <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 its 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>(Thats 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>PinePhones Touch Panel doesnt seem to be the Power-Saving type like <a href="https://lupyuen.github.io/articles/touch#cst816s-touch-panel"><strong>PineTimes CST816S</strong></a>.</p>
<p><em>How do we program the Touch Panel?</em></p>
<p>The datasheet doesnt 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 well 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 PinePhones Touch Panel is actually undocumented?</em></p>
<p>Yeah its strangely common for Touch Panels to be undocumented.</p>
<p>(Just like PineTimes <a href="https://lupyuen.github.io/articles/touch#cst816s-touch-panel"><strong>CST816S Touch Panel</strong></a>)</p>
<p>Lets experiment with PinePhones 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 Apples 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>Whats the simplest thing we can do with PinePhones Touch Panel?</em></p>
<p>Lets 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>(Whats 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 &gt;&gt; 8, // First Byte: MSB
reg &amp; 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(&quot;buf&quot;, buf, buflen);
// Shows &quot;39 31 37 53&quot; or &quot;917S&quot;
}</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>Hows 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>PinePhones Touch Panel will trigger interrupts right?</em></p>
<p>To detect Touch Events, well need to <strong>handle the interrupts</strong> triggered by Touch Panel.</p>
<p>Based on our research, PinePhones <strong>Touch Panel Interrupt</strong> (CTP-INT) is connected at <strong>PH4</strong>.</p>
<p>But to simplify our first experiment, <strong>lets 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 &lt; 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(&#39;+&#39;); } // PH4 goes Low to High
else { up_putc(&#39;-&#39;); } // 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 weve 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 its a simple way to do Kernel Experiments)</p>
<p>Now that we can poll our Touch Panel, lets 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>(Whats in the 2 remaining bytes? Doesnt 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 wont 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 &quot;81&quot;
// Decode the Status Code and the Touched Points
const uint8_t status_code = status[0] &amp; 0x80; // Set to 0x80
const uint8_t touched_points = status[0] &amp; 0x0f; // Set to 0x01
if (status_code != 0 &amp;&amp; // If Status Code is OK and...
touched_points &gt;= 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 &quot;92 02 59 05 1b 00&quot;
// Decode the Touch Coordinates
const uint16_t x = touch[0] + (touch[1] &lt;&lt; 8);
const uint16_t y = touch[2] + (touch[3] &lt;&lt; 8);
_info(&quot;touch x=%d, y=%d\n&quot;, x, y);
// Shows &quot;touch x=658, y=1369&quot;
}
// 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>Lets 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 theres <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>Lets 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>Weve done polling with the Touch Panel…</em></p>
<p><em>Can we handle interrupts from the Touch Panel?</em></p>
<p>Earlier weve 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 theres a problem: The Touch Panel only <strong>fires an interrupt once</strong>.</p>
<p>It wont 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 well 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>Whats 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 well 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(
&quot;/dev/input0&quot;, // Device Path
i2c, // I2C Bus
CTP_I2C_ADDR, // I2C Address of Touch Panel
&amp;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>Lets 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> &gt; <strong>Graphics Support</strong> &gt; <strong>Light and Versatile Graphics Library (LVGL)</strong></p>
</li>
<li>
<p>Enable “<strong>LVGL</strong> &gt; <strong>Enable Framebuffer Port</strong></p>
</li>
<li>
<p>Enable “<strong>LVGL</strong> &gt; <strong>Enable Touchpad Port</strong></p>
</li>
<li>
<p>Browse into “<strong>LVGL</strong> &gt; <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> &gt; <strong>Examples</strong> &gt; <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=&quot;port/lv_port_tick.h&quot;</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(
&quot;/dev/input0&quot;, // 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(&quot;/dev/input0&quot;)`
&amp;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>Well 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 hasnt 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 &amp; TOUCH_DOWN || touch_flags &amp; TOUCH_MOVE) {
// Report it as LVGL Press
// with the Touch Coordinates
touchpad_obj-&gt;last_state = LV_INDEV_STATE_PR;
touchpad_obj-&gt;last_x = sample.point[0].x;
touchpad_obj-&gt;last_y = sample.point[0].y;
...
} else if (touch_flags &amp; TOUCH_UP) {
// If the Touch Event is Touch Up,
// report it as LVGL Release with
// the previous Touch Coordinates
touchpad_obj-&gt;last_state = LV_INDEV_STATE_REL;
}</code></pre></div>
<p>And thats 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>Wheres 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 doesnt 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> doesnt support Multitouch either.</p>
<p>(So we might put on hold for now)</p>
</li>
<li>
<p>PinePhones 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 well 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> wont work correctly for awaiting Touch Points!</p>
<p>Thats because the Touch Panel wont 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> doesnt call <strong><code>poll()</code></strong>, it only calls non-blocking <strong><code>read()</code></strong>.</p>
<p>So were 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 Whats 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 its more <strong>Touch-Friendly</strong> for a Phone Form Factor. Well 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 wouldnt 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>Whats 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(
&quot;/dev/input0&quot;, // Device Path
i2c, // I2C Bus
CTP_I2C_ADDR, // I2C Address of Touch Panel
&amp;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>Lets 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>(Well 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>Lets 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>Whats 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(&quot;/dev/input0&quot;)`
&amp;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 its 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 well 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 doesnt 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 well 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>Lets 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 theres 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 weve 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 theres a problem: The Touch Panel only <strong>fires an interrupt once</strong>!</p>
<p>It wont 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 PinePhones Touch Panel fires an <strong>interrupt at PH4</strong> when its 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>Whats touch_panel_interrupt?</em></p>
<p><strong>touch_panel_interrupt</strong> is our Interrupt Handler. Lets 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(&#39;.&#39;);
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 wont actually read the Touch Coordinates.</p>
<p>(Because Interrupt Handlers cant make I2C calls)</p>
<p>Well fix this in the next section.</p>
<p><em>Its OK to call up_putc in an Interrupt Handler?</em></p>
<p>Yep its 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 wont 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>Heres 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(&#39;.&#39;);
// 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-&gt;int_pending = true;
// End Critical Section
leave_critical_section(flags);
// Notify the Poll Waiters
poll_notify( // Notify these File Descriptors...
priv-&gt;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 wont actually read the Touch Coordinates. (Because Interrupt Handlers cant 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 theres 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>Lets 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>Lets 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 &lt; 6000; i++) { // Poll for 60 seconds
// If Touch Panel Interrupt has been triggered...
if (priv-&gt;int_pending) {
// Read the Touch Panel over I2C
touch_panel_read(i2c_dev);
// Reset the Interrupt Pending Flag
priv-&gt;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 weve read the Touch Panel Status <strong><code>0x81</code></strong>, followed by the Touch Coordinates. Yep weve tested our Interrupt Handler successfully!</p>
<p>But theres 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>Theres a problem: The Touch Panel only <strong>fires an interrupt once</strong>!</p>
<p>It wont 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 lets 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>(Were not sure why it doesnt trigger further interrupts)</p>
<p>Lets 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 its 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 were 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 &quot;.&quot; when Interrupt Handler is triggered
up_putc(&#39;.&#39;);
// 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-&gt;int_pending) {
// Disable the Touch Panel Interrupt
priv-&gt;board-&gt;irq_enable(priv-&gt;board, false);
}
// Omitted: Set the Interrupt Pending Flag
// and notify the Poll Waiters</code></pre></div>
<p>It seems to work… But it doesnt 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 didnt 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 were 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-&gt;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>Thats 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>