DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
mpr121.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file mpr121.cpp
6 * @brief NXP MPR121 capacitive-touch codec - implementation. See mpr121.h.
7 *
8 * The bring-up register values are the NXP AN3944 / reference defaults (rising/falling/touched
9 * filter constants, CONFIG1 0x10, CONFIG2 0x20), with per-electrode touch/release thresholds
10 * and an electrode-config (ECR) that enables the electrodes with baseline tracking.
11 */
12
14#include "ServerConfig.h"
15
16#if DETWS_ENABLE_MPR121
17
18#include <string.h>
19
20uint16_t mpr121_touched(uint8_t status_lo, uint8_t status_hi)
21{
22 return (uint16_t)(((uint16_t)status_lo | ((uint16_t)status_hi << 8)) & 0x0FFF);
23}
24
25bool mpr121_is_touched(uint16_t mask, uint8_t e)
26{
27 return e < MPR121_ELECTRODES && (mask & (uint16_t)(1u << e)) != 0;
28}
29
30bool mpr121_proximity(uint8_t status_hi)
31{
32 return (status_hi & 0x10) != 0; // status bit 12
33}
34
35bool mpr121_overcurrent(uint8_t status_hi)
36{
37 return (status_hi & 0x80) != 0; // status bit 15
38}
39
40uint16_t mpr121_word10(uint8_t lsb, uint8_t msb)
41{
42 return (uint16_t)(((uint16_t)lsb | ((uint16_t)msb << 8)) & 0x03FF);
43}
44
45size_t mpr121_build_init(uint8_t *buf, size_t cap, uint8_t n, uint8_t touch_thr, uint8_t release_thr)
46{
47 if (!buf || n == 0 || n > MPR121_ELECTRODES)
48 return 0;
49 // Reset, ECR-stop, then the rising / falling / touched baseline-filter defaults.
50 static const uint8_t fixed[] = {
51 0x80, 0x63, // soft reset
52 0x5E, 0x00, // ECR = stop (config only allowed while stopped)
53 0x2B, 0x01, 0x2C, 0x01, // MHDR, NHDR
54 0x2D, 0x0E, 0x2E, 0x00, // NCLR, FDLR
55 0x2F, 0x01, 0x30, 0x05, // MHDF, NHDF
56 0x31, 0x01, 0x32, 0x00, // NCLF, FDLF
57 0x33, 0x00, 0x34, 0x00, // NHDT, NCLT
58 0x35, 0x00, // FDLT
59 };
60 size_t need = sizeof(fixed) + (size_t)n * 4 + 8;
61 if (cap < need)
62 return 0;
63 size_t i = sizeof(fixed);
64 memcpy(buf, fixed, sizeof(fixed));
65 for (uint8_t e = 0; e < n; e++)
66 {
67 buf[i++] = (uint8_t)(0x41 + 2 * e); // touch threshold reg
68 buf[i++] = touch_thr;
69 buf[i++] = (uint8_t)(0x42 + 2 * e); // release threshold reg
70 buf[i++] = release_thr;
71 }
72 buf[i++] = 0x5B;
73 buf[i++] = 0x00; // debounce
74 buf[i++] = 0x5C;
75 buf[i++] = 0x10; // CONFIG1
76 buf[i++] = 0x5D;
77 buf[i++] = 0x20; // CONFIG2
78 buf[i++] = 0x5E;
79 buf[i++] = (uint8_t)(0x80 | n); // ECR: CL=baseline tracking, ELE_EN=n (written last)
80 return i;
81}
82
83// ---------------------------------------------------------------------------
84// I2C binding
85// ---------------------------------------------------------------------------
86
87#if defined(ARDUINO)
88
89#include "services/i2c.h"
90#include <Arduino.h>
91#include <Wire.h>
92
93namespace
94{
95// All MPR121 I2C-binding state, owned by one instance (internal linkage): the device address,
96// so it is one named owner, unreachable from any other translation unit.
97struct Mpr121Ctx
98{
99 uint8_t addr = DETWS_MPR121_I2C_ADDR;
100};
101Mpr121Ctx s_mpr;
102
103bool wr(uint8_t reg, uint8_t val)
104{
105 Wire.beginTransmission(s_mpr.addr);
106 Wire.write(reg);
107 Wire.write(val);
108 return Wire.endTransmission() == 0;
109}
110
111bool rd(uint8_t reg, uint8_t *out, uint8_t n)
112{
113 Wire.beginTransmission(s_mpr.addr);
114 Wire.write(reg);
115 if (Wire.endTransmission(false) != 0)
116 return false;
117 if (Wire.requestFrom((int)s_mpr.addr, (int)n) != (int)n)
118 return false;
119 for (uint8_t i = 0; i < n; i++)
120 out[i] = (uint8_t)Wire.read();
121 return true;
122}
123} // namespace
124
125bool mpr121_begin(uint8_t addr)
126{
127 s_mpr.addr = addr ? addr : (uint8_t)DETWS_MPR121_I2C_ADDR;
129 uint8_t seq[MPR121_INIT_MAX];
132 if (n == 0)
133 return false;
134 if (!wr(seq[0], seq[1])) // soft reset first; then let the chip settle
135 return false;
136 delay(1);
137 for (size_t i = 2; i + 1 < n; i += 2)
138 if (!wr(seq[i], seq[i + 1]))
139 return false;
140 return true;
141}
142
143uint16_t mpr121_read_touched()
144{
145 uint8_t s[2] = {0, 0};
146 if (!rd(0x00, s, 2))
147 return 0;
148 return mpr121_touched(s[0], s[1]);
149}
150
151uint16_t mpr121_read_filtered(uint8_t e)
152{
153 if (e >= MPR121_ELECTRODES)
154 return 0;
155 uint8_t d[2] = {0, 0};
156 if (!rd((uint8_t)(0x04 + 2 * e), d, 2))
157 return 0;
158 return mpr121_word10(d[0], d[1]);
159}
160
161#else // host build: no I2C. The decode + init-sequence builder above are host-tested.
162
163bool mpr121_begin(uint8_t)
164{
165 return false;
166}
167uint16_t mpr121_read_touched()
168{
169 return 0;
170}
171uint16_t mpr121_read_filtered(uint8_t)
172{
173 return 0;
174}
175
176#endif // ARDUINO
177
178#endif // DETWS_ENABLE_MPR121
User-facing configuration for DeterministicESPAsyncWebServer.
#define DETWS_MPR121_I2C_ADDR
I2C address of the MPR121 (0x5A default; 0x5B/0x5C/0x5D via the ADDR pin).
#define DETWS_MPR121_RELEASE_THRESHOLD
MPR121 per-electrode release threshold (delta counts; should be below the touch threshold).
#define DETWS_MPR121_TOUCH_THRESHOLD
MPR121 per-electrode touch threshold (delta counts from baseline; NXP AN3944 suggests ~4....
The one owner of the shared I2C bus bring-up for the peripheral drivers.
void detws_i2c_begin()
Bring up the shared I2C bus on DETWS_I2C_SDA_PIN / DETWS_I2C_SCL_PIN (-1 = default).
Definition i2c.h:33
NXP MPR121 12-channel capacitive-touch controller codec (DETWS_ENABLE_MPR121).
#define MPR121_INIT_MAX
Largest init sequence in bytes: (17 fixed + 2*12 threshold) pairs * 2 bytes.
Definition mpr121.h:36
bool mpr121_overcurrent(uint8_t status_hi)
True if the over-current flag (status bit 15) is set (wiring fault / short).
uint16_t mpr121_read_filtered(uint8_t e)
Read electrode e's 10-bit filtered capacitance value.
uint16_t mpr121_read_touched()
Read the current 12-electrode touch bitmask (0 if the device is absent).
size_t mpr121_build_init(uint8_t *buf, size_t cap, uint8_t n_electrodes, uint8_t touch_thr, uint8_t release_thr)
Build the MPR121 bring-up sequence as consecutive (register, value) byte pairs.
bool mpr121_begin(uint8_t addr)
Reset + configure the MPR121 at addr over I2C.
bool mpr121_proximity(uint8_t status_hi)
True if the proximity electrode (status bit 12) is active.
bool mpr121_is_touched(uint16_t mask, uint8_t e)
True if electrode e (0..11) is touched in a mask from mpr121_touched.
uint16_t mpr121_word10(uint8_t lsb, uint8_t msb)
Combine a little-endian LSB/MSB register pair into a 10-bit value (filtered/baseline).
uint16_t mpr121_touched(uint8_t status_lo, uint8_t status_hi)
Decode the 12-electrode touch bitmask from the two status registers (0x00 low, 0x01 high)....
#define MPR121_ELECTRODES
Sense electrodes on the MPR121 (ELE0..ELE11).
Definition mpr121.h:33