<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[WiFi Channels, Deep Sleep Glitches &amp; NB-IoT Workarounds | M5Stack Dev Diary]]></title><description><![CDATA[<h1>WiFi Channels, Deep Sleep Glitches &amp; NB-IoT Workarounds | M5Stack Dev Diary</h1>
<p dir="auto">Hey Makers! 👋</p>
<p dir="auto">We've been digging through some interesting edge cases that came up in the community recently—ranging from HID keyboard quirks on Tab5 to button debouncing on Cardputer, and even a gnarly UART issue with the NB-IoT2 Unit. If you've been scratching your head over any of these, here's what we found. Let's dive in! 🛠️</p>
<hr />
<h2>01 | Tab5: Parsing Special Keys in BLE HID Keyboards</h2>
<p dir="auto"><strong>Product</strong>: Tab5 (C145/K145)<br />
<strong>Category</strong>: Bluetooth / HID Protocol</p>
<p dir="auto"><strong>The Issue:</strong><br />
You've got a BLE keyboard connected to your Tab5, and basic alphanumeric keys work fine—but arrow keys and function keys (F1-F12)? Nothing. Radio silence.</p>
<p dir="auto"><strong>Why This Happens:</strong><br />
Standard HID keyboard reports use distinct key code ranges for special keys. Function keys live in <code>0x3A-0x45</code>, and arrow keys hang out in <code>0x4F-0x52</code>. If your parsing logic only handles the basic alphanumeric range, these guys get ignored.</p>
<p dir="auto"><strong>The Fix:</strong><br />
Add the special key codes and extend your HID report parser:</p>
<pre><code class="language-cpp">// Function keys (F1-F12)
#define HID_KEY_F1  0x3A
#define HID_KEY_F12 0x45

// Arrow keys
#define HID_KEY_ARROW_UP    0x52
#define HID_KEY_ARROW_DOWN  0x51
#define HID_KEY_ARROW_LEFT  0x50
#define HID_KEY_ARROW_RIGHT 0x4F
</code></pre>
<p dir="auto">Then, in your HID input handler:</p>
<pre><code class="language-cpp">void onInputReport(uint8_t reportId, uint16_t connHandle, uint8_t* data, uint16_t len) {
  if (len &lt; 8) return;
  
  uint8_t keyCodes[6] = {data[2], data[3], data[4], data[5], data[6], data[7]};
  
  for (int i = 0; i &lt; 6; i++) {
    if (keyCodes[i] == 0) continue;
    
    // Handle function keys (F1-F12)
    if (keyCodes[i] &gt;= HID_KEY_F1 &amp;&amp; keyCodes[i] &lt;= HID_KEY_F12) {
      int fKeyNum = keyCodes[i] - HID_KEY_F1 + 1;
      M5.Display.printf("[F%d]", fKeyNum);
    }
    // Handle arrow keys
    else {
      switch(keyCodes[i]) {
        case HID_KEY_ARROW_UP:    M5.Display.print("[UP]"); break;
        case HID_KEY_ARROW_DOWN:  M5.Display.print("[DOWN]"); break;
        case HID_KEY_ARROW_LEFT:  M5.Display.print("[LEFT]"); break;
        case HID_KEY_ARROW_RIGHT: M5.Display.print("[RIGHT]"); break;
      }
    }
  }
}
</code></pre>
<p dir="auto"><strong>Pro Tips:</strong></p>
<ul>
<li>HID key codes follow the USB HID Usage Tables spec—keyboard vendors stick to this religiously.</li>
<li>Arrow and function keys are <em>not</em> affected by modifier keys (Shift, Ctrl, etc.). No combo handling needed here.</li>
<li>Want to add Home/End/PageUp? They're in <code>0x4A-0x4E</code>. Same approach.</li>
<li>Media keys (volume, play/pause) live in a separate Consumer Control report (UUID <code>0x0C</code>). That's a whole different beast.</li>
</ul>
<hr />
<h2>02 | Cardputer-Adv: Debouncing the Dual Button Unit</h2>
<p dir="auto"><strong>Product</strong>: Cardputer-Adv (K132-Adv) / Dual Button Unit (U025)<br />
<strong>Category</strong>: Sensor / Signal Processing</p>
<p dir="auto"><strong>The Issue:</strong><br />
You're reading button states directly from GPIO, and it's chaos. One press registers as three. One release registers as five. You're going insane.</p>
<p dir="auto"><strong>Why This Happens:</strong><br />
Mechanical buttons are analog demons. When you press or release, the contacts bounce for 5-50ms, creating electrical noise that looks like multiple rapid presses to your microcontroller. Polling GPIO directly = you get all that noise.</p>
<p dir="auto"><strong>The Fix:</strong><br />
Use M5Unified's <code>Button_Class</code>. It handles debouncing in software, so you get clean, stable events.</p>
<p dir="auto"><strong>Step 1:</strong> Initialize your buttons with debounce thresholds.</p>
<pre><code class="language-cpp">#include &lt;M5Unified.h&gt;

#define BLUE_BTN_PIN 1  // G1 = Blue button
#define RED_BTN_PIN  2  // G2 = Red button

m5::Button_Class blueBtn;
m5::Button_Class redBtn;

void setup() {
  M5Cardputer.begin();
  
  // Set debounce time (20ms is a sweet spot for most buttons)
  blueBtn.setDebounceThresh(20);
  redBtn.setDebounceThresh(20);
  
  // Bind to GPIO (false = button is LOW when pressed)
  blueBtn.begin(BLUE_BTN_PIN, false);
  redBtn.begin(RED_BTN_PIN, false);
}
</code></pre>
<p dir="auto"><strong>Step 2:</strong> Check for stable events in your main loop.</p>
<pre><code class="language-cpp">void loop() {
  M5Cardputer.update();
  blueBtn.update();  // Internal debounce sampling
  redBtn.update();
  
  // Detect press (fires once)
  if (blueBtn.wasPressed()) {
    Serial.println("Blue Button Pressed");
  }
  
  // Detect release
  if (redBtn.wasReleased()) {
    Serial.println("Red Button Released");
  }
  
  // Detect click (full press + release)
  if (blueBtn.wasClicked()) {
    Serial.println("Blue Button Clicked");
  }
  
  delay(5);
}
</code></pre>
<p dir="auto"><strong>Available Debounced Events:</strong></p>
<ul>
<li><code>wasPressed()</code> — Fires once on press</li>
<li><code>wasReleased()</code> — Fires once on release</li>
<li><code>wasClicked()</code> — Fires once after a full press-and-release</li>
<li><code>isPressed()</code> — True while held down</li>
<li><code>wasHold()</code> — Fires after a long press (threshold configurable with <code>setHoldThresh(ms)</code>)</li>
</ul>
<p dir="auto"><strong>Pro Tips:</strong></p>
<ul>
<li>Keep debounce thresholds between 20-50ms. Too low = noise slips through. Too high = sluggish response.</li>
<li>The Dual Button Unit already has 10kΩ hardware pull-ups. Don't use <code>INPUT_PULLUP</code>.</li>
<li><code>Button_Class</code> uses continuous sampling to confirm state stability—way better than a simple <code>delay()</code>.</li>
<li>Default long-press threshold is 1000ms. Adjust with <code>setHoldThresh()</code> if needed.</li>
</ul>
<hr />
<h2>03 | StamPLC: Direct UART Control for NB-IoT2 Unit (SIM7028)</h2>
<p dir="auto"><strong>Product</strong>: StamPLC (K141) / NB-IoT2 Unit (U118)<br />
<strong>Category</strong>: Compilation Error / Library Compatibility</p>
<p dir="auto"><strong>The Issue:</strong><br />
You're using UIFlow 2.3.8 firmware on StamPLC, and the <code>NBIOT2Unit</code> class throws an <code>AttributeError</code> when you try to call <code>execute_at_command()</code> or <code>execute_at_command2()</code>. You can't even send a basic AT command to configure the APN.</p>
<p dir="auto"><strong>Why This Happens:</strong><br />
Early StamPLC firmware (&lt;2.4.0) shipped with a broken <code>NBIOT2Unit</code> driver. The method signatures were misaligned—the library expected some internal data structure instead of strings or tuples. This is fixed in later firmware, but there's no official update path yet for 2.3.8 users.</p>
<p dir="auto"><strong>The Workaround:</strong><br />
Bypass the buggy library entirely. Talk to the SIM7028 module directly over UART2 using Arduino's <code>HardwareSerial</code>.</p>
<p dir="auto"><strong>Step 1:</strong> Initialize UART2 with StamPLC's Port C pins.</p>
<pre><code class="language-cpp">#include &lt;M5StamPLC.h&gt;
#include &lt;HardwareSerial.h&gt;

// Port C mapping: RX=GPIO16, TX=GPIO17
HardwareSerial sim7028Serial(2);

void setup() {
  M5StamPLC.begin();
  Serial.begin(115200);  // Debug output
  
  sim7028Serial.begin(115200, SERIAL_8N1, 16, 17);  // Init UART2
  delay(2000);
}
</code></pre>
<p dir="auto"><strong>Step 2:</strong> Create an AT command helper.</p>
<pre><code class="language-cpp">String sendATCommand(String cmd, int timeout = 1000) {
  sim7028Serial.print(cmd + "\r\n");  // SIM7028 requires \r\n
  
  long start = millis();
  String response = "";
  
  while (millis() - start &lt; timeout) {
    if (sim7028Serial.available()) {
      response += sim7028Serial.readString();
    }
  }
  
  return response;
}
</code></pre>
<p dir="auto"><strong>Step 3:</strong> Test basic AT commands.</p>
<pre><code class="language-cpp">void loop() {
  // Basic comms test
  Serial.println(sendATCommand("AT"));  // Should return "OK"
  
  // Signal strength
  Serial.println(sendATCommand("AT+CSQ"));  // Returns +CSQ: &lt;rssi&gt;,&lt;ber&gt;
  
  // Network registration status
  Serial.println(sendATCommand("AT+CEREG?"));  // +CEREG: 0,1 = registered
  
  delay(5000);
}
</code></pre>
<p dir="auto"><strong>Step 4:</strong> Configure APN and activate network (full flow).</p>
<pre><code class="language-cpp">// Set APN (replace with your carrier's APN)
sendATCommand("AT+CGDCONT=1,\"IP\",\"telstra.m2m\"");
delay(1000);

// Activate PDP context
sendATCommand("AT+CGACT=1,1");
delay(2000);

// Attach to GPRS network
sendATCommand("AT+CGATT=1");
delay(5000);

// Verify IP address assignment
Serial.println(sendATCommand("AT+CGPADDR=1"));
</code></pre>
<p dir="auto"><strong>Pro Tips:</strong></p>
<ul>
<li>StamPLC's Port C uses UART2 with fixed pins: RX=16, TX=17. No remapping allowed.</li>
<li>SIM7028 <em>requires</em> <code>\r\n</code> line endings. Without them, commands are ignored.</li>
<li>Signal strength (CSQ) ranges from 0-31. (99 = unknown). You want ≥10 before trying to attach.</li>
<li>Network registration can take 5-30 seconds. <code>AT+CEREG?</code> returning <code>+CEREG: 0,1</code> means success.</li>
<li>This approach also works in UIFlow 2's MicroPython environment—just swap <code>HardwareSerial</code> for <code>machine.UART</code>.</li>
<li><strong>LED indicators:</strong> Blue LED blinking = searching for network. Solid = connected. Off = module unpowered or dead.</li>
</ul>
<hr />
<h2>💬 Discussion</h2>
<p dir="auto">Have you run into similar gotchas with HID parsing, button debouncing, or cellular modules? Drop a comment below or share your own workarounds! We'd love to hear how you're tackling these issues in the wild.</p>
<h2>📌 Resources</h2>
<ul>
<li>📚 <strong>Docs</strong>: <a href="https://docs.m5stack.com" target="_blank" rel="noopener noreferrer nofollow ugc">https://docs.m5stack.com</a></li>
<li>🗣️ <strong>Forum</strong>: <a href="https://community.m5stack.com">https://community.m5stack.com</a></li>
<li>🛒 <strong>Shop</strong>: <a href="https://shop.m5stack.com" target="_blank" rel="noopener noreferrer nofollow ugc">https://shop.m5stack.com</a></li>
</ul>
<hr />
<p dir="auto"><em>Note: This series shares common troubleshooting tips from the M5Stack engineering desk. All user data has been anonymized.</em></p>
]]></description><link>https://community.m5stack.com/topic/7923/wifi-channels-deep-sleep-glitches-nb-iot-workarounds-m5stack-dev-diary</link><generator>RSS for Node</generator><lastBuildDate>Wed, 29 Apr 2026 19:10:54 GMT</lastBuildDate><atom:link href="https://community.m5stack.com/topic/7923.rss" rel="self" type="application/rss+xml"/><pubDate>Mon, 01 Dec 2025 09:10:35 GMT</pubDate><ttl>60</ttl></channel></rss>