ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
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 "protocore_config.h"
15#include "services/system/clock.h" // pcdelay
16
17#if PC_ENABLE_MPR121
18
19#include <string.h>
20
21#if defined(ARDUINO)
23#include <Arduino.h>
24#include <Wire.h>
25#endif
26uint16_t pc_mpr121_touched(uint8_t status_lo, uint8_t status_hi)
27{
28 return (uint16_t)(((uint16_t)status_lo | ((uint16_t)status_hi << 8)) & 0x0FFF);
29}
30
31bool pc_mpr121_is_touched(uint16_t mask, uint8_t e)
32{
33 return e < MPR121_ELECTRODES && (mask & (uint16_t)(1u << e)) != 0;
34}
35
36bool pc_mpr121_proximity(uint8_t status_hi)
37{
38 return (status_hi & 0x10) != 0; // status bit 12
39}
40
41bool pc_mpr121_overcurrent(uint8_t status_hi)
42{
43 return (status_hi & 0x80) != 0; // status bit 15
44}
45
46uint16_t pc_mpr121_word10(uint8_t lsb, uint8_t msb)
47{
48 return (uint16_t)(((uint16_t)lsb | ((uint16_t)msb << 8)) & 0x03FF);
49}
50
51size_t pc_mpr121_build_init(uint8_t *buf, size_t cap, uint8_t n, uint8_t touch_thr, uint8_t release_thr)
52{
53 if (!buf || n == 0 || n > MPR121_ELECTRODES)
54 {
55 return 0;
56 }
57 // Reset, ECR-stop, then the rising / falling / touched baseline-filter defaults.
58 static const uint8_t fixed[] = {
59 0x80, 0x63, // soft reset
60 0x5E, 0x00, // ECR = stop (config only allowed while stopped)
61 0x2B, 0x01, 0x2C, 0x01, // MHDR, NHDR
62 0x2D, 0x0E, 0x2E, 0x00, // NCLR, FDLR
63 0x2F, 0x01, 0x30, 0x05, // MHDF, NHDF
64 0x31, 0x01, 0x32, 0x00, // NCLF, FDLF
65 0x33, 0x00, 0x34, 0x00, // NHDT, NCLT
66 0x35, 0x00, // FDLT
67 };
68 size_t need = sizeof(fixed) + (size_t)n * 4 + 8;
69 if (cap < need)
70 {
71 return 0;
72 }
73 size_t i = sizeof(fixed);
74 memcpy(buf, fixed, sizeof(fixed));
75 for (uint8_t e = 0; e < n; e++)
76 {
77 buf[i++] = (uint8_t)(0x41 + 2 * e); // touch threshold reg
78 buf[i++] = touch_thr;
79 buf[i++] = (uint8_t)(0x42 + 2 * e); // release threshold reg
80 buf[i++] = release_thr;
81 }
82 buf[i++] = 0x5B;
83 buf[i++] = 0x00; // debounce
84 buf[i++] = 0x5C;
85 buf[i++] = 0x10; // CONFIG1
86 buf[i++] = 0x5D;
87 buf[i++] = 0x20; // CONFIG2
88 buf[i++] = 0x5E;
89 buf[i++] = (uint8_t)(0x80 | n); // ECR: CL=baseline tracking, ELE_EN=n (written last)
90 return i;
91}
92
93// ---------------------------------------------------------------------------
94// I2C binding
95// ---------------------------------------------------------------------------
96
97#if defined(ARDUINO)
98
99namespace
100{
101// All MPR121 I2C-binding state, owned by one instance (internal linkage): the device address,
102// so it is one named owner, unreachable from any other translation unit.
103struct Mpr121Ctx
104{
105 uint8_t addr = PC_MPR121_I2C_ADDR;
106};
107Mpr121Ctx s_mpr;
108
109bool wr(uint8_t reg, uint8_t val)
110{
111 Wire.beginTransmission(s_mpr.addr);
112 Wire.write(reg);
113 Wire.write(val);
114 return Wire.endTransmission() == 0;
115}
116
117bool rd(uint8_t reg, uint8_t *out, uint8_t n)
118{
119 Wire.beginTransmission(s_mpr.addr);
120 Wire.write(reg);
121 if (Wire.endTransmission(false) != 0)
122 {
123 return false;
124 }
125 if (Wire.requestFrom((int)s_mpr.addr, (int)n) != (int)n)
126 {
127 return false;
128 }
129 for (uint8_t i = 0; i < n; i++)
130 {
131 out[i] = (uint8_t)Wire.read();
132 }
133 return true;
134}
135} // namespace
136
137bool pc_mpr121_begin(uint8_t addr)
138{
139 s_mpr.addr = addr ? addr : (uint8_t)PC_MPR121_I2C_ADDR;
140 pc_i2c_begin();
141 uint8_t seq[MPR121_INIT_MAX];
144 if (n == 0)
145 {
146 return false;
147 }
148 if (!wr(seq[0], seq[1])) // soft reset first; then let the chip settle
149 {
150 return false;
151 }
152 pcdelay(1);
153 for (size_t i = 2; i + 1 < n; i += 2)
154 {
155 if (!wr(seq[i], seq[i + 1]))
156 {
157 return false;
158 }
159 }
160 return true;
161}
162
163uint16_t pc_mpr121_read_touched()
164{
165 uint8_t s[2] = {0, 0};
166 if (!rd(0x00, s, 2))
167 {
168 return 0;
169 }
170 return pc_mpr121_touched(s[0], s[1]);
171}
172
173uint16_t pc_mpr121_read_filtered(uint8_t e)
174{
175 if (e >= MPR121_ELECTRODES)
176 {
177 return 0;
178 }
179 uint8_t d[2] = {0, 0};
180 if (!rd((uint8_t)(0x04 + 2 * e), d, 2))
181 {
182 return 0;
183 }
184 return pc_mpr121_word10(d[0], d[1]);
185}
186
187#else // host build: no I2C. The decode + init-sequence builder above are host-tested.
188
189bool pc_mpr121_begin(uint8_t)
190{
191 return false;
192}
193uint16_t pc_mpr121_read_touched()
194{
195 return 0;
196}
197uint16_t pc_mpr121_read_filtered(uint8_t)
198{
199 return 0;
200}
201
202#endif // ARDUINO
203
204#endif // PC_ENABLE_MPR121
Pluggable monotonic clock for all library timing.
void pcdelay(uint32_t ms)
Block for at least ms milliseconds - the library's single delay primitive.
Definition clock.h:89
The one owner of the shared I2C bus bring-up for the peripheral drivers.
void pc_i2c_begin()
Bring up the shared I2C bus on PC_I2C_SDA_PIN / PC_I2C_SCL_PIN (-1 = default).
Definition i2c.h:33
NXP MPR121 12-channel capacitive-touch controller codec (PC_ENABLE_MPR121).
bool pc_mpr121_proximity(uint8_t status_hi)
True if the proximity electrode (status bit 12) is active.
#define MPR121_INIT_MAX
Largest init sequence in bytes: (17 fixed + 2*12 threshold) pairs * 2 bytes.
Definition mpr121.h:36
size_t pc_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 pc_mpr121_is_touched(uint16_t mask, uint8_t e)
True if electrode e (0..11) is touched in a mask from pc_mpr121_touched.
uint16_t pc_mpr121_read_filtered(uint8_t e)
Read electrode e's 10-bit filtered capacitance value.
bool pc_mpr121_overcurrent(uint8_t status_hi)
True if the over-current flag (status bit 15) is set (wiring fault / short).
uint16_t pc_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 pc_mpr121_read_touched()
Read the current 12-electrode touch bitmask (0 if the device is absent).
uint16_t pc_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)....
bool pc_mpr121_begin(uint8_t addr)
Reset + configure the MPR121 at addr over I2C.
#define MPR121_ELECTRODES
Sense electrodes on the MPR121 (ELE0..ELE11).
Definition mpr121.h:33
uint32_t mask(uint8_t width)
Mask of width low bits (width 32 handled without a 32-bit shift, which is UB).
Definition crc.h:61
User-facing configuration for ProtoCore.
#define PC_MPR121_TOUCH_THRESHOLD
MPR121 per-electrode touch threshold (delta counts from baseline; NXP AN3944 suggests ~4....
#define PC_MPR121_I2C_ADDR
I2C address of the MPR121 (0x5A default; 0x5B/0x5C/0x5D via the ADDR pin).
#define PC_MPR121_RELEASE_THRESHOLD
MPR121 per-electrode release threshold (delta counts; should be below the touch threshold).