22п. A capacitive touchscreen on the FT6236/FT6206 over I2C: touches, gestures, the INT interrupt, rotation calibration for the ILI9341 and handling interface button presses
In section 22к a resistive sensor measured the finger: the XPT2046 gave ADC codes, they were converted into points by a three-touch calibration and cleaned of bounce — none of it is repeated here. In 22о the ILI9341 started working on the F407 at 320 × 240, and modules with this display often carry a different sensor — a capacitive one, with an FT6236 or FT6206 controller on I2C. Section 22а.6 gave the general view: the controller counts touches itself, returns ready coordinates, and the INT pin says they are there. Here is how it works in practice: the registers and the touch report, the event fields and the finger ID, the INT mode, the threshold and the report rate, the connection to the F407 next to the display, mapping a point for the screen rotation from 22о, a button handler with hold, swipe and pinch, and the choice between polling and the interrupt.
Sources: the ST ft6x06 driver V1.1.0 (STM32F4xx DFP 2.17.1, BSP Components) —
register addresses, event and gesture codes, the G_MODE values, the codes
in CHIP_ID: 0x11 — FT6206, 0xCD — FT6x36; no more than two touches; the
same file has the comments "no software reset" and "polling by default, no
INT generation". The bit fields (XH — the event in bits 7:6, YH — the ID
in bits 7:4) and reading both touches in one burst come from ft6x06 V2.0.0
(STM32H7xx DFP 3.1.1). From the ST board BSPs: on the STM32412G-Discovery
TS_I2C_ADDRESS is 0x70, reset is 10 ms low, INT has a pull-up, falling
edge, "active low", the bus runs at 100 kHz; on the STM32469I-Discovery
there are two addresses, 0x54 and 0x70, the code 0xD8 for the FT6336G and a
400 kHz bus. From the OpenMV ft5x06 module (MIT): address 0x38, reading all
points in one burst, a pause of 1 ms in reset and 39 ms after. RM0090 Rev 19:
receiving more than two bytes, p. 851; I2C_CCR, p. 870; I2C_TRISE,
p. 871; SYSCFG_EXTICR2, p. 291; EXTI4 is position 10 in the vector table,
p. 372; EXTI_FTSR, p. 385; EXTI_PR, p. 386. DS8626 Rev 9: PB10 is
I2C2_SCL, PB11 is I2C2_SDA, both FT, p. 52; AF4 — p. 63; PC4 and PC5 are
FT, p. 50; pin pull-up 30–50 kOhm, p. 115; fast mode I²C up to 400 kbit/s,
p. 121. DocID13587 Rev 17, table 40, p. 69: the SCL and SDA rise time in fast
mode is no longer than 300 ns, line capacitance up to 400 pF. The project
has no FT6236 document: what is in CIPHER, which units PERIODACTIVE and
TH_GROUP use, how INT behaves and what goes into the report on lift-up
follow the manufacturer's documentation and are not checked; in the model
they are assumptions. All numbers below come from a run of the driver
_verify/ctp22p/ft22p.c on a computer (gcc 12.2.0) with our own model of the
controller in Python 3.11; the firmware is built by armclang 6.24 for the
Cortex-M4.
What it is called in the documentation
Original Meaning projected capacitive (PCAP), mutual capacitance a projected capacitive sensor: an electrode grid under the glass TD_STATUS (touch data status) how many touches lie on the glass now event flag: press down, lift up, contact the event field: press, lift, contact touch ID the number under which the controller follows a finger from report to report G_MODE: interrupt polling / interrupt trigger what the INT pin does: nothing or "a report is ready" TH_GROUP (touch threshold) the threshold above which a signal counts as a finger PERIODACTIVE (report rate in active mode) the report rate while a finger is on the glass CIPHER, CHIP_ID chip codes: which FT6x we are talking to hit test checking which button a point fell into ghost touch a false touch caused by interference pinch (zoom in / zoom out) two fingers moving apart or together
22п.1 Capacitive versus resistive: what changes
The resistive panel from 22к is two layers the finger presses together; the controller is an ADC and the program does everything else: measures, calibrates, removes bounce. A capacitive one is an electrode grid under the glass with its own controller that scans the grid itself and returns a ready report: where the fingers are, how many there are and what happened to each. Hence the difference:
- Two fingers. According to the ST driver the FT6x06 sees up to two
touches (
FT6206_MAX_DETECTABLE_TOUCH= 2), each with its own ID. A pinch is possible; on a resistive panel it is not. - No point calibration. The controller returns coordinates in glass points, 12 bits per axis. What remains is how the touch glass is glued to the display and how the screen is rotated — four cases and two mirrors (22п.4), not a matrix from three touches.
- No pressure needed, the glass stays intact. A touch is a change of capacitance, so there is no pressing force (instead of the pressure of 22к there are the touch "weight" and "area") and no wear of the top layer.
- Gloves and water. A resistive panel does not care what presses it. A capacitive one does: a glove weakens the signal, a water drop creates one. What this leads to at different thresholds the model shows in 22п.6.
22п.2 Registers and the report: TD_STATUS, P1, P2, events and ID
The bus address is 0x38. In the ST board files TS_I2C_ADDRESS is 0x70 —
the same address already shifted by the read/write bit. For the
469I-Discovery ST tries two addresses, 0x54 and 0x70: on other modules the
address can differ.
| Address | Name | Contents |
|---|---|---|
0x00 |
DEV_MODE |
0 — working mode |
0x01 |
GEST_ID |
a gesture found by the controller itself |
0x02 |
TD_STATUS |
number of touches, bits 3:0 |
0x03…0x08 |
P1_XH, XL, YH, YL, WEIGHT, MISC |
the first touch |
0x09…0x0E |
P2_… |
the second — the same six bytes |
0x80 |
TH_GROUP |
touch threshold |
0x88 |
PERIODACTIVE |
report rate in active mode |
0xA3 |
CIPHER |
"chip selecting" |
0xA4 |
G_MODE |
0 — polling, 1 — interrupt |
0xA6 |
FIRMID |
firmware version |
0xA8 |
CHIP_ID |
0x11, 0xCD or 0xD8 |
In P1_XH bits 7:6 are the event: 0 — press, 1 — lift, 2 — contact (the
finger is lying), 3 — no event; bits 3:0 are the high bits of X. In P1_YH
bits 7:4 are the touch ID, 3:0 the high bits of Y. The ID is needed to tell
fingers apart: if the first finger leaves, the second may move from P2 to
P1, but its ID stays the same.
The chip codes are the least reliable place. ST reads CHIP_ID (0xA8) and
expects 0x11 for the FT6206 and 0xCD for the FT6x36; the 469I-Discovery also
has 0xD8 — according to the comment, the FT6336G. ST declares CIPHER (0xA3)
but gives no values. The driver accepts all three CHIP_ID codes and only
reads and keeps CIPHER and FIRMID. In the model CHIP_ID = 0xCD,
CIPHER = 0x36 — per the manufacturer's documentation, not checked.
Reading in one burst. The report is TD_STATUS and both touches: 13
bytes in a row from address 0x02. This is how ST V2.0.0 reads (12 bytes from
P1_XH) and OpenMV (from 0x01). At 400 kHz it takes 370 us of the bus, at
100 kHz 1480 us:
static void unpack(const uint8_t *b, ft_point_t *p)
{
p->ev = (uint8_t)(b[0] >> 6); /* XH [7:6] — событие */
p->x = (uint16_t)((b[0] & 0x0Fu) << 8 | b[1]);
p->id = (uint8_t)(b[2] >> 4); /* YH [7:4] — ID касания */
p->y = (uint16_t)((b[2] & 0x0Fu) << 8 | b[3]);
p->weight = b[4];
p->area = (uint8_t)(b[5] >> 4);
}
/* Всё сразу: TD_STATUS и оба касания — 13 байт с 0x02 одним чтением. */
int ft_read(ft_t *d, ft_report_t *r)
{
uint8_t b[FT_REPORT_BYTES];
TRY(rd(d, FT_TD_STATUS, b, FT_REPORT_BYTES));
r->n = (uint8_t)(b[0] & 0x0Fu);
if (r->n > FT_MAX_TOUCH)
r->n = 0; /* как у ST: больше двух — значит мусор */
unpack(b + 1, &r->p[0]);
unpack(b + 7, &r->p[1]);
return FT_OK;
}
The driver decodes both slots even if TD_STATUS is less than two: in the
model a finger that has just left arrives in the last report with the "lift"
event in the slot beyond TD_STATUS, and an empty slot is 0xFF bytes,
event 3. How the chip does it follows the manufacturer's documentation and is
not checked; the handler in 22п.5 does not rely on it and releases a finger
if its ID simply disappears from the report.
G_MODE, TH_GROUP, PERIODACTIVE. G_MODE = 1 — INT reports a new
report, 0 — polling; ST sets 0 by default with the comment "no INT
generation". In the model with G_MODE = 1 INT goes low on a new report and
stays low until TD_STATUS is read. TH_GROUP is the touch threshold,
PERIODACTIVE the report rate; ST does not say what units they use. In the
model the threshold is in units of its signal (a finger gives 100), and
PERIODACTIVE is reports per second: 60 — a report every 16.67 ms. These
are assumptions; on hardware the values have to be chosen from the document
for your chip.
22п.3 Connecting to the F407 and start-up
The ILI9341 from 22о took SPI1 (PA5–PA7) and PB0, PB1, PB6–PB8 — among them PB6 and PB8, where I2C1 of the F407 lives. So the touch goes to I2C2:
| Module pin | STM32F407 | Purpose |
|---|---|---|
| SCL, SDA | PB10, PB11 | I2C2, AF4, open drain, 400 kHz |
| INT | PC4 | input with pull-up, EXTI4 on the falling edge |
| RST | PC5 | output, 0 — reset |
| VCC, GND | 3.3 V, GND | power |
PB10 and PB11 are FT (DS8626, p. 52), AF4 — p. 63; PC4 and PC5 are FT,
p. 50. APB1 is 42 MHz: in fast mode with DUTY = 0 the low level is twice as
long as the high one (RM0090, p. 870), CCR = 42 / (3 · 0.4) = 35 — exactly
400 kHz. TRISE is the maximum rise time plus one (p. 871): 300 ns × 42 MHz
= 12.6, the integer part 12, plus one — 13. Receiving more than two bytes is
as on p. 851: the last three bytes through BTF. INT: in SYSCFG_EXTICR2
the EXTI4 field = port C (p. 291), EXTI_FTSR — falling edge (p. 385), the
EXTI4 handler — position 10 (p. 372).
Start-up is a reset through the RST pin (10 ms low, as at ST), a pause of
40 ms (OpenMV waits 39 ms), the codes, the settings and one read of
TD_STATUS:
int ft_init(ft_t *d, const ft_cfg_t *c)
{
uint8_t id[2];
d->xfers = d->bytes = 0;
if (d->addr == 0)
d->addr = FT_ADDR;
ft_port_rst(0);
ft_port_delay_ms(10);
ft_port_rst(1);
ft_port_delay_ms(40);
TRY(rd(d, FT_CHIP_ID, &d->chip_id, 1));
TRY(rd(d, FT_CIPHER, id, 1));
d->cipher = id[0];
TRY(rd(d, FT_FIRMID, &d->firm, 1));
if (d->chip_id != FT_ID_FT6206 && d->chip_id != FT_ID_FT6X36 && d->chip_id != FT_ID_FT6336G)
return FT_E_ID;
TRY(wr(d, FT_DEV_MODE, 0)); /* рабочий режим — так делает и OpenMV */
TRY(wr(d, FT_TH_GROUP, c->th_group));
TRY(wr(d, FT_PERIODACTIVE, c->period_active));
TRY(wr(d, FT_G_MODE, c->gmode));
return rd(d, FT_TD_STATUS, id, 1); /* прочитать — снять INT, если он уже прижат */
}
The FT6x06 has no software reset — ST says so directly in ft6x06_Reset, so
RST is better wired to a pin. On the bench start-up takes 8 transfers and 16
bytes: 690 us of the bus and 50.69 ms with the pauses. How long to really
wait after RST: ST holds it 10 ms low, OpenMV 1 ms and 39 ms after; the model
answers at once. With the address 0x70 instead of 0x38 start-up ends on the
very first transfer: the slave did not answer, code −1.
22п.4 Glass to screen: four rotations and mirrors
In the model the touch glass lies the same way as the ILI9341 memory at
MADCTL 0x00: the touch point (gx, gy) is column gx and row gy of the
memory. After that the screen rotation decides everything. In 22о the
display puts a screen point (x, y) into memory like this: MV swaps x and y,
then MX mirrors the column and MY the row. The touch needs the reverse:
remove the mirrors and swap the axes back. For the rotations from 22о:
| Rotation | MADCTL |
Screen | screen x | screen y |
|---|---|---|---|---|
| 0° | 08 | 240 × 320 | gx | gy |
| 90° | 68 | 320 × 240 | gy | 239 − gx |
| 180° | C8 | 240 × 320 | 239 − gx | 319 − gy |
| 270° | A8 | 320 × 240 | 319 − gy | gx |
void ft_to_screen(const ft_map_t *m, uint16_t px, uint16_t py, int16_t *x, int16_t *y)
{
int16_t gx = (int16_t)(m->swap ? py : px), gy = (int16_t)(m->swap ? px : py);
if (m->mir_x)
gx = (int16_t)(GLASS_W - 1 - gx);
if (m->mir_y)
gy = (int16_t)(GLASS_H - 1 - gy);
if (m->madctl & MAD_MX)
gx = (int16_t)(GLASS_W - 1 - gx);
if (m->madctl & MAD_MY)
gy = (int16_t)(GLASS_H - 1 - gy);
if (m->madctl & MAD_MV) {
*x = gy;
*y = gx;
} else {
*x = gx;
*y = gy;
}
}
The function takes the same MADCTL byte that went to the display — the
rotation changes in one place. The fields swap, mir_x, mir_y are for a
module where the touch is glued differently; ST does the same on the
412G-Discovery with the flags TS_SWAP_X, TS_SWAP_Y, TS_SWAP_XY and the
mapping 240 − 1 − x.
The table is checked in two ways. First on 1610 glass points (a grid with a
step of 7 points) for each rotation: the table formula passed through the
display write of 22о returns the same glass point — 0 mismatches, and
ft_to_screen in C gives the same screen point — also 0. Then by touches:
3 × 3 buttons on the screen, a tap in the middle of each.
| Rotation | MADCTL |
Correct | Rotation ignored | Touch mirrored in X | Mirror corrected |
|---|---|---|---|---|---|
| 0° | 08 | 9 of 9 | 9 of 9 | 3 of 9 | 9 of 9 |
| 90° | 68 | 9 of 9 | 0 of 9 | 3 of 9 | 9 of 9 |
| 180° | C8 | 9 of 9 | 1 of 9 | 3 of 9 | 9 of 9 |
| 270° | A8 | 9 of 9 | 0 of 9 | 3 of 9 | 9 of 9 |
With the rotation ignored, at 180° only the middle hits — the central button, at 90° and 270° nothing. A mirror in X keeps the middle column and swaps left and right.
22п.5 The interface handler: buttons, hold, debounce, swipe and pinch
The handler ui_feed gets a report with the points already in screen
coordinates and produces events: "pressed" with a button number, "click",
"hold", "cancel", a swipe in one of four directions, a pinch "together" or
"apart". It follows a finger by the ID from the report. Firmware settings:
hold — 500 ms, movement — 12 points, swipe — no shorter than 40 points and
no longer than 400 ms, pinch — the distance changed by 30 points.
Debounce by events. A false touch from interference lives one report: "press", then "lift". A real finger stays longer and in the second report comes with "contact". The handler accepts a touch when it sees "contact", not by the number of reads: when polling the same report can be read twice, and counting reads would accept the interference too.
k->seen = 1;
ft_to_screen(m, p->x, p->y, &k->x, &k->y);
k->t = now_ms;
if (!k->ok && (!c->confirm || p->ev == FT_EV_CONTACT))
n = accept(u, k, ev, n, max); /* антидребезг: ждать «контакт» — второй отчёт */
A button. The hit is tested on the first point of the touch. An accepted touch on a button gives "pressed"; if the finger left the button or moved more than 12 points — "cancel", this is no longer a click; if it lay still for 500 ms — "hold", and no click follows; on release — "click".
Swipe and pinch are computed from two samples — the first and the last, without the path history:
static int lift(ui_t *u, ui_touch_t *k, ui_ev_t *ev, int n, int max)
{
const ui_cfg_t *c = u->c;
if (k->ok && u->pinch) {
int32_t dd = u->d1 - u->d0;
if (dd >= c->pinch_min)
n = put(ev, n, max, UI_PINCH_OUT, -1, k); /* развели — увеличить */
else if (-dd >= c->pinch_min)
n = put(ev, n, max, UI_PINCH_IN, -1, k);
u->pinch = 0;
for (uint8_t i = 0; i < FT_MAX_TOUCH; i++)
u->k[i].solo = 0; /* второй палец доживает без жестов */
} else if (k->ok && k->solo) {
int16_t dx = (int16_t)(k->x - k->x0), dy = (int16_t)(k->y - k->y0);
if (k->btn >= 0 && !k->held)
n = put(ev, n, max, UI_CLICK, k->btn, k);
else if (k->btn < 0 && k->t - k->t0 <= c->swipe_ms && (absd(dx) >= c->swipe_min || absd(dy) >= c->swipe_min)) {
uint8_t t = absd(dx) >= absd(dy) ? (dx < 0 ? UI_SWIPE_L : UI_SWIPE_R) : (dy < 0 ? UI_SWIPE_U : UI_SWIPE_D);
n = put(ev, n, max, t, -1, k);
}
}
k->used = 0;
return n;
}
The second accepted finger cancels the button of the first one and starts a pinch: the distance between the fingers at the start and the last one, by an integer square root. Swipe and pinch are computed in screen coordinates, so "left" stays left at any rotation.
The controller has gestures of its own too — the GEST_ID register, codes at
ST: 0x10 up, 0x14 right, 0x18 down, 0x1C left, 0x48 and 0x49 — zoom. But they
are in glass coordinates: OpenMV reorders them for its board with the note
"rotated by 90 degrees". How to enable them follows the manufacturer's
documentation; in the model the register is always 0, and the gestures here
are our own.
The set in the model: 20 taps of 80…200 ms, 10 holds of 0.7…1 s, 16 swipes of 80…120 points in 120…300 ms, 10 times "pressed and slid off the button", 10 pinches (distance 60 ↔ 160 points in 300…500 ms). Portrait (08) and landscape (68) — 66 of 66 correct each; 1473 reads in 46.656 s, the bus busy 1.17 % of the time.
22п.6 Polling or the interrupt; gloves, water and interference
Delay and bus. 20 taps of 80…200 ms on buttons, 60 reports per second (16.67 ms), debounce on. The delay is from the touch to the "pressed" event:
| Method | Average delay | Worst | Reads per run | Bus during taps | Reads per second without touch |
|---|---|---|---|---|---|
| INT, 400 kHz | 24.68 ms | 33.39 ms | 168 | 0.73 % | 0 |
| INT, no debounce | 8.02 ms | 16.73 ms | 168 | 0.73 % | 0 |
| INT, 30 reports per second | 48.97 ms | 66.97 ms | 94 | 0.41 % | 0 |
| INT, 100 reports per second | 14.85 ms | 19.01 ms | 267 | 1.15 % | 0 |
| polling every 10 ms | 29.22 ms | 39.68 ms | 826 | 3.56 % | 96 |
| polling every 20 ms | 35.83 ms | 50.10 ms | 420 | 1.81 % | 49 |
| polling every 50 ms | 51.79 ms | 76.67 ms | 170 | 0.73 % | 19 |
| INT, 100 kHz | 27.06 ms | 34.68 ms | 168 | 2.90 % | 0 |
| polling every 10 ms, 100 kHz | 31.11 ms | 40.07 ms | 746 | 12.88 % | 87 |
All 20 clicks are in place in every row. With INT the bus works only when there is a report: no finger — not a single read. Polling every 10 ms reads 96 times per second even without a finger, and still does not catch up on the delay: the report is already there but has not been picked up yet. Debounce costs one report period — 24.68 ms versus 8.02; more frequent reports mean a smaller delay.
Reading the ST way. The ST driver V1.1.0 reads TD_STATUS, then for each
touch 4 bytes of X/Y and 3 bytes of "info" starting at Pn_WEIGHT. The third
info byte is already the next register, P2_XH (for the second touch —
0x0F), and ST takes the event exactly from it. In V2.0.0 the event comes from
its own XH. Repeated in ft_read_split, this read gave 0 clicks of 20 in
the model — for a single finger the event is always the "no event" of the
neighbouring empty slot — and 632 transfers instead of 168. On the bus one
touch read this way costs 512.5 us at 400 kHz versus 370, two — 825 us.
Strict event parsing. If a touch starts only on "press" and ends only on "lift", polling slower than the reports loses both. Taps of 60…120 ms:
| Parsing | INT | Polling every 50 ms |
|---|---|---|
| by ID and presence in the report | 20 of 20 | 20 of 20 |
| only by "press" and "lift" | 20 of 20 | 3 of 20 |
The same case is a microcontroller restart while a finger already lies on the glass: "contact" comes first, there is no "press". A finger on button 4 200 ms before the loop starts: parsing by ID gave "pressed" and "click" of button 4, the strict one gave nothing.
Gloves, water and interference. In the model a finger gives a signal of 100, a gloved finger 45, a water drop 50; the noise without interference is σ 6, with interference from the backlight PWM σ 20 (these are assumptions, not measurements). A false touch is noise above the threshold in one of 48 cells: it lives one report. 10 taps, 10 holds of 0.7…1 s, 10 gloved taps, a drop for 2 s and 10 s without touches:
Interference, TH_GROUP, debounce |
False touches in 10 s | False clicks | Taps | Holds | Gloved | Drop: events |
|---|---|---|---|---|---|---|
| none, 60, yes | 0 | 0 | 10 | 10 | 0 | 0 |
| PWM, 60, no | 34 | 18 | 0 | 0 | 0 | 25 |
| PWM, 60, yes | 33 | 0 | 9 | 4 | 2 | 5 |
| PWM, 90, yes | 0 | 0 | 6 | 0 | 0 | 0 |
| none, 40, yes | 0 | 0 | 10 | 10 | 3 | 7 |
| none, 30, yes | 0 | 0 | 10 | 10 | 10 | 1 |
| PWM, 40, yes | 406 | 0 | 1 | 4 | 0 | 6 |
Debounce by events removes all false clicks — 18 versus 0, — but not everything: with interference the finger sometimes disappears from the report for one scan, and a long hold breaks into pieces. A high threshold silences the interference and the real finger with it: at 90 taps are 6 of 10 and no holds at all. A glove is seen only with a low threshold — 30, and there a drop also gives a touch. The threshold is a compromise, and interference is better removed where it is born: route the backlight and the touch cable apart, raise the PWM frequency.
22п.7 Pitfalls
INT is held low, but there are no interrupts. In the model INT stays low
until TD_STATUS is read. If the first report came before EXTI was enabled —
a finger lay down during start-up — there will be no more falling edges: 10
taps, 0 reads, INT low until the end of the run. The firmware looks at the
level after enabling EXTI and after every read: if INT is already low, read
without waiting for an edge. Then the same 10 taps give 10 of 10.
for (;;) {
__disable_irq();
if (!int_flag && !ft_port_int_low()) { /* INT прижат, а спада не было — всё равно читать */
__WFI();
__enable_irq();
continue;
}
int_flag = 0;
__enable_irq();
if (ft_read(&T, &r) != FT_OK)
continue;
Address 0x70. In the ST board files the address is already shifted. Used as a 7-bit one it does not answer — start-up ends with code −1 on the first transfer.
400 kHz and pull-ups. The rise time in fast mode is up to 300 ns, line capacitance up to 400 pF (DocID13587, table 40). The rise to 0.7 of the supply through a pull-up R into a capacitance C is 0.8473 · R · C:
| Pull-up | 50 pF | 100 pF |
|---|---|---|
| 2.2 kOhm | 93 ns | 186 ns |
| 4.7 kOhm | 199 ns | 398 ns |
| 10 kOhm | 424 ns | 847 ns |
| 40 kOhm (the pin's own pull-up) | 1695 ns | 3389 ns |
The F407's own pull-up is 30–50 kOhm (DS8626, p. 115): no good for 400 kHz. Which resistors are on the module and whether there are any at all — see the module schematic, not checked here. If it does not fit — 100 kHz: the INT delay grew from 24.68 to 27.06 ms, and the bus is busy 4 times longer.
X and Y swapped, mirrors. A touch lands on neighbouring buttons or
"moves" the opposite way. Do not flip signs at random: take the display's
MADCTL (22п.4) and separately how the touch is glued (swap, mir_x,
mir_y). Check it with taps in the corners and the middle.
"Contact" without "press". After a microcontroller restart or when polling slower than the reports, the first thing seen is "contact". Strict parsing does not see such a touch; start a touch by a new ID with any event.
An event from someone else's register. The "ST V1.1.0 way" read takes the
event from P2_XH: with one finger it is always "no event". Read the report
in one burst and take the event from its own XH.
False touches from the backlight. The backlight PWM and a long cable give touches lasting one report. Use debounce by events rather than raising the threshold until gloves and holds are lost.
22п.8 Summary
- FT6236/FT6206 — up to two touches, address 0x38 (0x70 at ST, shifted); the report is 13 bytes from 0x02 in one read: 370 us at 400 kHz, 1480 us at 100 kHz.
XH[7:6] is the event: 0 — press, 1 — lift, 2 — contact;YH[7:4] is the ID;CHIP_IDat ST is 0x11, 0xCD or 0xD8.- Start-up: RST 10 ms low, a 40 ms pause, 8 transfers, 16 bytes, 690 us of
the bus, 50.69 ms in total; I2C2 on PB10/PB11,
CCR= 35,TRISE= 13. - Rotation: 08 — (gx, gy), 68 — (gy, 239 − gx), C8 — (239 − gx, 319 − gy), A8 — (319 − gy, gx); 0 mismatches on 1610 points of each rotation; with the rotation ignored at 90° — 0 of 9.
- Handler: debounce on "contact", hold 500 ms, swipe from 40 points within 400 ms, pinch from 30 points; 66 of 66 in both orientations.
- INT versus polling: delay 24.68 ms versus 29.22 ms (polling every 10 ms), without a touch 0 reads versus 96 per second.
- Pitfalls: INT held until read — look at the level; strict parsing when
polling every 50 ms — 3 of 20; the event from
P2_XHin ST V1.1.0 — 0 of 20; PWM interference — 18 false clicks without debounce and 0 with it.
Full sources
The bench host22p.c, the model model22p.py and the scenarios lie next to the check in _verify/ctp22p/.
ft22p.h — FT6x06 registers, report fields, port, driver, screen mapping and interface handler: types and prototypes.
/* ft22p.h — раздел 22п: ёмкостный тачскрин FT6236/FT6206 по I2C и обработчик кнопок, жестов и щипка.
* Адреса регистров, поля событий и коды — как в драйвере ST ft6x06 (STM32F4xx DFP 2.17.1, BSP Components,
* V1.1.0) и в ft6x06_reg.h (STM32H7xx DFP 3.1.1, V2.0.0); адрес 0x38 и чтение пачкой — как в модуле ft5x06
* OpenMV. Помечено «док.» — по документации производителя, не проверено (её в проекте нет).
* Шину, паузы, RST и INT даёт прошивка (fw22p.c — I2C2 STM32F407) или стенд (host22p.c — строки к model22p.py). */
#ifndef FT22P_H
#define FT22P_H
#include <stdint.h>
#define FT_ADDR 0x38u /* 7 бит; у ST — TS_I2C_ADDRESS 0x70, это тот же адрес, сдвинутый на 1 */
/* ── регистры ── */
#define FT_DEV_MODE 0x00u /* 0 — рабочий режим */
#define FT_GEST_ID 0x01u /* жест, найденный самим контроллером */
#define FT_TD_STATUS 0x02u /* число касаний: биты 3:0, 0…2 */
#define FT_P1_XH 0x03u /* событие [7:6], X [11:8] */
#define FT_P1_XL 0x04u
#define FT_P1_YH 0x05u /* ID касания [7:4], Y [11:8] */
#define FT_P1_YL 0x06u
#define FT_P1_WEIGHT 0x07u
#define FT_P1_MISC 0x08u /* площадь [7:4] */
#define FT_P2_XH 0x09u /* второе касание — те же шесть байт */
#define FT_TH_GROUP 0x80u /* порог касания */
#define FT_CTRL 0x86u /* 0 — всегда активен, 1 — засыпать без касаний */
#define FT_PERIODACTIVE 0x88u /* частота отчётов в активном режиме (единицы — док.) */
#define FT_LIB_VER_H 0xA1u
#define FT_CIPHER 0xA3u /* «Chip Selecting»: какой кристалл (значения — док.) */
#define FT_G_MODE 0xA4u /* 0 — опрос, 1 — прерывание по INT */
#define FT_FIRMID 0xA6u
#define FT_CHIP_ID 0xA8u /* ST ждёт 0x11 (FT6206), 0xCD (FT6x36); 0xD8 — FT6336G у 469I-Discovery */
#define FT_ID_FT6206 0x11u
#define FT_ID_FT6X36 0xCDu
#define FT_ID_FT6336G 0xD8u
#define FT_EV_DOWN 0u /* нажатие */
#define FT_EV_LIFT 1u /* отпускание */
#define FT_EV_CONTACT 2u /* контакт: палец лежит */
#define FT_EV_NONE 3u
#define FT_REPORT_BYTES 13u /* TD_STATUS и два касания по 6 байт: 0x02…0x0E одним чтением */
#define FT_MAX_TOUCH 2u
enum { FT_OK = 0, FT_E_NACK = -1, FT_E_ID = -2 };
/* ── шина, время, RST и INT: реализует прошивка или стенд ── */
int ft_port_write(uint8_t addr7, const uint8_t *buf, uint32_t n); /* 0 — ведомый подтвердил всё */
int ft_port_read(uint8_t addr7, uint8_t reg, uint8_t *buf, uint32_t n); /* адрес регистра, повторный старт, n байт */
void ft_port_delay_ms(uint32_t ms);
void ft_port_rst(int level); /* вывод RST: 0 — сброс */
int ft_port_int_low(void); /* 1 — INT прижат */
typedef struct {
uint8_t gmode; /* FT_G_MODE */
uint8_t th_group; /* FT_TH_GROUP */
uint8_t period_active; /* FT_PERIODACTIVE */
} ft_cfg_t;
typedef struct {
uint8_t ev, id; /* событие и ID касания */
uint16_t x, y; /* координаты стекла, 12 бит */
uint8_t weight, area;
} ft_point_t;
typedef struct {
uint8_t n; /* TD_STATUS: сколько касаний лежит */
ft_point_t p[FT_MAX_TOUCH]; /* оба места — даже если n меньше: там бывает «отпускание» */
} ft_report_t;
typedef struct {
uint8_t addr, chip_id, cipher, firm;
uint32_t xfers, bytes;
} ft_t;
int ft_init(ft_t *d, const ft_cfg_t *c);
int ft_read(ft_t *d, ft_report_t *r); /* одной пачкой 13 байт с 0x02 */
int ft_read_split(ft_t *d, ft_report_t *r); /* как драйвер ST: число, потом X/Y и сведения по касанию */
/* ── стекло → экран: монтаж тача и MADCTL дисплея (22о) ── */
#define MAD_MY 0x80u
#define MAD_MX 0x40u
#define MAD_MV 0x20u
#define GLASS_W 240
#define GLASS_H 320
typedef struct {
uint8_t madctl; /* тот же байт, что ушёл в ILI9341 (BGR и ML не важны) */
uint8_t swap, mir_x, mir_y; /* как тач приклеен к стеклу: обычно 0, 0, 0 */
} ft_map_t;
void ft_to_screen(const ft_map_t *m, uint16_t px, uint16_t py, int16_t *x, int16_t *y);
/* ── обработчик интерфейса ── */
typedef struct { int16_t x, y, w, h; } ui_rect_t;
enum { UI_NONE = 0, UI_DOWN, UI_CLICK, UI_HOLD, UI_CANCEL, UI_SWIPE_L, UI_SWIPE_R, UI_SWIPE_U, UI_SWIPE_D,
UI_PINCH_IN, UI_PINCH_OUT };
typedef struct {
const ui_rect_t *btn;
uint8_t nbtn;
uint8_t confirm; /* 1 — касание принято, когда пришёл «контакт» (антидребезг по событиям) */
uint8_t strict; /* 1 — начало только по «нажатию», конец только по «отпусканию» */
uint16_t hold_ms; /* удержание */
uint16_t slop; /* сдвиг, после которого кнопка уже не «держится» */
uint16_t swipe_min, swipe_ms; /* свайп: не короче и не дольше */
uint16_t pinch_min; /* щипок: на сколько точек изменилось расстояние */
} ui_cfg_t;
typedef struct {
uint8_t used, id, ok, seen, held, moved, solo;
int8_t btn; /* нажатая кнопка, −1 — нет */
int16_t x0, y0, x, y; /* первый и последний отсчёт */
uint32_t t0, t;
} ui_touch_t;
typedef struct { uint8_t type; int8_t btn; int16_t x, y; uint32_t t; } ui_ev_t;
typedef struct {
const ui_cfg_t *c;
ui_touch_t k[FT_MAX_TOUCH];
uint8_t pinch; /* оба пальца приняты */
int32_t d0, d1; /* расстояние в начале и последнее, точки */
} ui_t;
void ui_reset(ui_t *u, const ui_cfg_t *c);
int ui_feed(ui_t *u, const ft_report_t *r, const ft_map_t *m, uint32_t now_ms, ui_ev_t *ev, int max);
int ui_hit(const ui_cfg_t *c, int16_t x, int16_t y);
#endif
ft22p.c — the driver: reset, codes, INT mode, burst read and the ST-style read, glass to screen; the handler of buttons, hold, swipe and pinch — shared by the firmware and the bench.
/* ft22p.c — раздел 22п: драйвер FT6236/FT6206 (сброс, коды, режим INT, порог, чтение пачкой, стекло → экран)
* и обработчик интерфейса (кнопки, удержание, свайп, щипок). Общий для прошивки fw22p.c и стенда host22p.c. */
#include "ft22p.h"
#define TRY(x) do { int rc_ = (x); if (rc_ != FT_OK) return rc_; } while (0)
static int wr(ft_t *d, uint8_t reg, uint8_t v)
{
uint8_t b[2] = {reg, v};
d->xfers++;
d->bytes += 2u;
return ft_port_write(d->addr, b, 2) ? FT_E_NACK : FT_OK;
}
static int rd(ft_t *d, uint8_t reg, uint8_t *p, uint32_t n)
{
d->xfers++;
d->bytes += 1u + n;
return ft_port_read(d->addr, reg, p, n) ? FT_E_NACK : FT_OK;
}
/* Сброс выводом RST (10 мс в нуле — как BSP_TS_Reset у ST), пауза 40 мс (OpenMV ждёт 39 мс), коды, настройки. */
int ft_init(ft_t *d, const ft_cfg_t *c)
{
uint8_t id[2];
d->xfers = d->bytes = 0;
if (d->addr == 0)
d->addr = FT_ADDR;
ft_port_rst(0);
ft_port_delay_ms(10);
ft_port_rst(1);
ft_port_delay_ms(40);
TRY(rd(d, FT_CHIP_ID, &d->chip_id, 1));
TRY(rd(d, FT_CIPHER, id, 1));
d->cipher = id[0];
TRY(rd(d, FT_FIRMID, &d->firm, 1));
if (d->chip_id != FT_ID_FT6206 && d->chip_id != FT_ID_FT6X36 && d->chip_id != FT_ID_FT6336G)
return FT_E_ID;
TRY(wr(d, FT_DEV_MODE, 0)); /* рабочий режим — так делает и OpenMV */
TRY(wr(d, FT_TH_GROUP, c->th_group));
TRY(wr(d, FT_PERIODACTIVE, c->period_active));
TRY(wr(d, FT_G_MODE, c->gmode));
return rd(d, FT_TD_STATUS, id, 1); /* прочитать — снять INT, если он уже прижат */
}
static void unpack(const uint8_t *b, ft_point_t *p)
{
p->ev = (uint8_t)(b[0] >> 6); /* XH [7:6] — событие */
p->x = (uint16_t)((b[0] & 0x0Fu) << 8 | b[1]);
p->id = (uint8_t)(b[2] >> 4); /* YH [7:4] — ID касания */
p->y = (uint16_t)((b[2] & 0x0Fu) << 8 | b[3]);
p->weight = b[4];
p->area = (uint8_t)(b[5] >> 4);
}
/* Всё сразу: TD_STATUS и оба касания — 13 байт с 0x02 одним чтением. */
int ft_read(ft_t *d, ft_report_t *r)
{
uint8_t b[FT_REPORT_BYTES];
TRY(rd(d, FT_TD_STATUS, b, FT_REPORT_BYTES));
r->n = (uint8_t)(b[0] & 0x0Fu);
if (r->n > FT_MAX_TOUCH)
r->n = 0; /* как у ST: больше двух — значит мусор */
unpack(b + 1, &r->p[0]);
unpack(b + 7, &r->p[1]);
return FT_OK;
}
/* Как BSP ST V1.1.0: TD_STATUS, затем по каждому касанию X/Y — 4 байта с Pn_XH, «сведения» — 3 байта с
* Pn_WEIGHT, потом жест. Третий байт сведений — уже следующий регистр, Pn_XH соседа: ST берёт событие оттуда. */
int ft_read_split(ft_t *d, ft_report_t *r)
{
uint8_t n, b[4], w[3], g;
TRY(rd(d, FT_TD_STATUS, &n, 1));
r->n = (uint8_t)(n & 0x0Fu);
if (r->n > FT_MAX_TOUCH)
r->n = 0;
for (uint8_t i = 0; i < FT_MAX_TOUCH; i++) {
r->p[i].ev = FT_EV_NONE;
r->p[i].id = 15u;
}
for (uint8_t i = 0; i < r->n; i++) {
uint8_t base = (uint8_t)(FT_P1_XH + 6u * i);
TRY(rd(d, base, b, 4));
TRY(rd(d, (uint8_t)(base + 4u), w, 3));
r->p[i].x = (uint16_t)((b[0] & 0x0Fu) << 8 | b[1]);
r->p[i].id = (uint8_t)(b[2] >> 4);
r->p[i].y = (uint16_t)((b[2] & 0x0Fu) << 8 | b[3]);
r->p[i].weight = w[0];
r->p[i].area = (uint8_t)((w[1] & 0x40u) >> 4); /* маска ST: (0x04 << 4) >> 4 */
r->p[i].ev = (uint8_t)(w[2] >> 6); /* 0x09 или 0x0F, а не свой Pn_XH — как у ST */
}
return rd(d, FT_GEST_ID, &g, 1);
}
/* Стекло тача → точка экрана. Сначала монтаж (как тач приклеен), потом обратное к MADCTL ILI9341:
* дисплей кладёт точку (c, r) в память так — MV меняет c и r, затем MX зеркалит столбец, MY — строку. */
void ft_to_screen(const ft_map_t *m, uint16_t px, uint16_t py, int16_t *x, int16_t *y)
{
int16_t gx = (int16_t)(m->swap ? py : px), gy = (int16_t)(m->swap ? px : py);
if (m->mir_x)
gx = (int16_t)(GLASS_W - 1 - gx);
if (m->mir_y)
gy = (int16_t)(GLASS_H - 1 - gy);
if (m->madctl & MAD_MX)
gx = (int16_t)(GLASS_W - 1 - gx);
if (m->madctl & MAD_MY)
gy = (int16_t)(GLASS_H - 1 - gy);
if (m->madctl & MAD_MV) {
*x = gy;
*y = gx;
} else {
*x = gx;
*y = gy;
}
}
/* ── обработчик интерфейса ── */
void ui_reset(ui_t *u, const ui_cfg_t *c)
{
for (uint32_t i = 0; i < sizeof *u; i++)
((uint8_t *)u)[i] = 0;
u->c = c;
}
int ui_hit(const ui_cfg_t *c, int16_t x, int16_t y)
{
for (uint8_t i = 0; i < c->nbtn; i++) {
const ui_rect_t *b = &c->btn[i];
if (x >= b->x && x < b->x + b->w && y >= b->y && y < b->y + b->h)
return i;
}
return -1;
}
static int16_t absd(int32_t v)
{
return (int16_t)(v < 0 ? -v : v);
}
static int32_t dist(const ui_touch_t *a, const ui_touch_t *b)
{
int32_t dx = a->x - b->x, dy = a->y - b->y, s = dx * dx + dy * dy, r = 0;
while ((r + 1) * (r + 1) <= s) /* целый корень: расстояние в точках */
r++;
return r;
}
static int put(ui_ev_t *ev, int n, int max, uint8_t type, int8_t btn, const ui_touch_t *k)
{
if (n < max)
ev[n] = (ui_ev_t){type, btn, k->x, k->y, k->t};
return n + 1;
}
/* Палец ушёл: щипок, щелчок по кнопке или свайп по двум отсчётам — первому и последнему. */
static int lift(ui_t *u, ui_touch_t *k, ui_ev_t *ev, int n, int max)
{
const ui_cfg_t *c = u->c;
if (k->ok && u->pinch) {
int32_t dd = u->d1 - u->d0;
if (dd >= c->pinch_min)
n = put(ev, n, max, UI_PINCH_OUT, -1, k); /* развели — увеличить */
else if (-dd >= c->pinch_min)
n = put(ev, n, max, UI_PINCH_IN, -1, k);
u->pinch = 0;
for (uint8_t i = 0; i < FT_MAX_TOUCH; i++)
u->k[i].solo = 0; /* второй палец доживает без жестов */
} else if (k->ok && k->solo) {
int16_t dx = (int16_t)(k->x - k->x0), dy = (int16_t)(k->y - k->y0);
if (k->btn >= 0 && !k->held)
n = put(ev, n, max, UI_CLICK, k->btn, k);
else if (k->btn < 0 && k->t - k->t0 <= c->swipe_ms && (absd(dx) >= c->swipe_min || absd(dy) >= c->swipe_min)) {
uint8_t t = absd(dx) >= absd(dy) ? (dx < 0 ? UI_SWIPE_L : UI_SWIPE_R) : (dy < 0 ? UI_SWIPE_U : UI_SWIPE_D);
n = put(ev, n, max, t, -1, k);
}
}
k->used = 0;
return n;
}
/* Принять касание: одно — кнопка под пальцем, второе — начало щипка (кнопку отменить). */
static int accept(ui_t *u, ui_touch_t *k, ui_ev_t *ev, int n, int max)
{
ui_touch_t *o = &u->k[k == &u->k[0]];
k->ok = 1;
if (o->used && o->ok) {
if (o->btn >= 0)
n = put(ev, n, max, UI_CANCEL, o->btn, o);
o->btn = k->btn = -1;
u->pinch = o->solo; /* щипок, если первый палец ещё был «свой» */
k->solo = o->solo;
u->d0 = u->d1 = dist(k, o);
return n;
}
k->solo = 1;
k->btn = (int8_t)ui_hit(u->c, k->x0, k->y0);
if (k->btn >= 0)
n = put(ev, n, max, UI_DOWN, k->btn, k);
return n;
}
int ui_feed(ui_t *u, const ft_report_t *r, const ft_map_t *m, uint32_t now_ms, ui_ev_t *ev, int max)
{
const ui_cfg_t *c = u->c;
int n = 0;
for (uint8_t i = 0; i < FT_MAX_TOUCH; i++)
u->k[i].seen = 0;
for (uint8_t i = 0; i < FT_MAX_TOUCH; i++) {
const ft_point_t *p = &r->p[i];
ui_touch_t *k = 0;
if (p->ev == FT_EV_NONE || (i >= r->n && p->ev != FT_EV_LIFT))
continue; /* место пусто */
for (uint8_t j = 0; j < FT_MAX_TOUCH; j++)
if (u->k[j].used && u->k[j].id == p->id)
k = &u->k[j];
if (p->ev == FT_EV_LIFT) {
if (k) {
k->seen = 1;
n = lift(u, k, ev, n, max);
}
continue;
}
if (!k) {
if (c->strict && p->ev != FT_EV_DOWN)
continue; /* «контакт» без «нажатия» — строгий разбор его не видит */
for (uint8_t j = 0; j < FT_MAX_TOUCH && !k; j++)
if (!u->k[j].used)
k = &u->k[j];
if (!k)
continue;
*k = (ui_touch_t){1, p->id, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, now_ms, now_ms};
ft_to_screen(m, p->x, p->y, &k->x0, &k->y0);
}
k->seen = 1;
ft_to_screen(m, p->x, p->y, &k->x, &k->y);
k->t = now_ms;
if (!k->ok && (!c->confirm || p->ev == FT_EV_CONTACT))
n = accept(u, k, ev, n, max); /* антидребезг: ждать «контакт» — второй отчёт */
if (!k->ok)
continue;
if (absd(k->x - k->x0) > c->slop || absd(k->y - k->y0) > c->slop)
k->moved = 1;
if (k->btn >= 0) {
const ui_rect_t *b = &c->btn[k->btn];
if (k->moved || k->x < b->x || k->x >= b->x + b->w || k->y < b->y || k->y >= b->y + b->h) {
n = put(ev, n, max, UI_CANCEL, k->btn, k); /* повёл палец — это уже не щелчок, а свайп */
k->btn = -1;
} else if (!k->held && now_ms - k->t0 >= c->hold_ms) {
k->held = 1;
n = put(ev, n, max, UI_HOLD, k->btn, k);
}
}
}
if (u->pinch && u->k[0].used && u->k[1].used)
u->d1 = dist(&u->k[0], &u->k[1]);
if (!c->strict)
for (uint8_t j = 0; j < FT_MAX_TOUCH; j++)
if (u->k[j].used && !u->k[j].seen)
n = lift(u, &u->k[j], ev, n, max); /* ID пропал без «отпускания» — отчёт проскочил */
return n;
}
fw22p.c — STM32F407 firmware: I2C2 on PB10/PB11, INT on EXTI4, RST, a loop that checks the INT level.
/* fw22p.c — раздел 22п: тач FT6236 на STM32F407, 168 МГц, APB1 42 МГц. Дисплей — ILI9341 из 22о (SPI1, PA5–PA7,
* PB6–PB8, PB0, PB1), поэтому тач — на I2C2: PB10 — SCL, PB11 — SDA (AF4, открытый сток), 400 кГц.
* INT тача — PC4: вход с подтяжкой, EXTI4 по спаду. RST тача — PC5: выход. SysTick — 1 мс.
* Экран повёрнут в альбомную (MADCTL 0x68, поворот 1 в 22о); кнопки — сетка 3 × 3 по экрану 320 × 240.
* Тактирование до 168 МГц — как в разделе 6 (здесь не повторяется). */
#include "stm32f4xx.h"
#include "ft22p.h"
#define APB1_MHZ 42u
#define I2C_FAST_CCR (APB1_MHZ * 1000000u / (3u * 400000u)) /* F/S = 1, DUTY = 0: Tlow = 2 · Thigh (RM0090, с. 870) */
#define I2C_FAST_TRISE (APB1_MHZ * 300u / 1000u + 1u) /* фронт до 300 нс: 12,6 → 12 + 1 */
#define I2C_SPIN 20000u
#define INT_PIN 4u
#define RST_PIN 5u
_Static_assert(I2C_FAST_CCR == 35u, "CCR = 35: 42 МГц / (3 · 35) = 400 кГц");
_Static_assert(I2C_FAST_TRISE == 13u, "TRISE = 13");
_Static_assert(EXTI4_IRQn == 10, "EXTI4 — прерывание 10 (RM0090, с. 372)");
static const ft_cfg_t CFG = {.gmode = 1u, .th_group = 60u, .period_active = 60u};
static const ft_map_t MAP = {.madctl = 0x68u};
static const ui_rect_t BTN[9] = {
{6, 6, 94, 68}, {112, 6, 94, 68}, {218, 6, 94, 68},
{6, 86, 94, 68}, {112, 86, 94, 68}, {218, 86, 94, 68},
{6, 166, 94, 68}, {112, 166, 94, 68}, {218, 166, 94, 68},
};
static const ui_cfg_t UC = {BTN, 9u, 1u, 0u, 500u, 12u, 40u, 400u, 30u};
static ft_t T;
static ui_t U;
static volatile uint8_t int_flag;
static volatile uint32_t ms;
volatile uint8_t last_ev, last_btn; /* что увидел обработчик: для отладчика */
volatile uint32_t n_ev[11];
/* ── I2C2, ведущий, опросом; на каждом ожидании — предел ── */
static int wait1(uint32_t mask)
{
for (uint32_t n = 0; n < I2C_SPIN; n++) {
uint32_t sr = I2C2->SR1;
if (sr & I2C_SR1_AF) {
I2C2->SR1 = (uint16_t)~I2C_SR1_AF;
return 1;
}
if (sr & mask)
return 0;
}
return 2;
}
static void stop(void)
{
I2C2->CR1 |= I2C_CR1_STOP;
while (I2C2->CR1 & I2C_CR1_STOP) {
}
}
static int start(uint8_t a8)
{
I2C2->CR1 |= I2C_CR1_START;
if (wait1(I2C_SR1_SB))
return 1;
I2C2->DR = a8;
if (wait1(I2C_SR1_ADDR)) {
stop();
return 1;
}
return 0;
}
int ft_port_write(uint8_t addr7, const uint8_t *buf, uint32_t n)
{
if (start((uint8_t)(addr7 << 1)))
return 1;
(void)I2C2->SR1;
(void)I2C2->SR2;
for (uint32_t i = 0; i < n; i++) {
if (wait1(I2C_SR1_TXE)) {
stop();
return 1;
}
I2C2->DR = buf[i];
}
int e = n ? wait1(I2C_SR1_BTF) : 0;
stop();
return e;
}
/* Приём по RM0090: один байт — ACK снять до снятия ADDR; два — POS; больше двух — последние три через BTF (с. 851).
* Отчёт тача — 13 байт одним приёмом. */
int ft_port_read(uint8_t addr7, uint8_t reg, uint8_t *buf, uint32_t n)
{
if (start((uint8_t)(addr7 << 1)))
return 1;
(void)I2C2->SR1;
(void)I2C2->SR2;
if (wait1(I2C_SR1_TXE)) {
stop();
return 1;
}
I2C2->DR = reg;
if (wait1(I2C_SR1_BTF) || start((uint8_t)((addr7 << 1) | 1u))) {
stop();
return 1;
}
int e = 0;
if (n == 1) {
I2C2->CR1 &= ~I2C_CR1_ACK;
(void)I2C2->SR1;
(void)I2C2->SR2;
I2C2->CR1 |= I2C_CR1_STOP;
e = wait1(I2C_SR1_RXNE);
buf[0] = (uint8_t)I2C2->DR;
} else if (n == 2) {
I2C2->CR1 |= I2C_CR1_POS;
I2C2->CR1 &= ~I2C_CR1_ACK;
(void)I2C2->SR1;
(void)I2C2->SR2;
e = wait1(I2C_SR1_BTF);
I2C2->CR1 |= I2C_CR1_STOP;
buf[0] = (uint8_t)I2C2->DR;
buf[1] = (uint8_t)I2C2->DR;
} else {
(void)I2C2->SR1;
(void)I2C2->SR2;
uint32_t i = 0;
for (; i < n - 3u && !e; i++) {
e = wait1(I2C_SR1_RXNE);
buf[i] = (uint8_t)I2C2->DR;
}
e |= wait1(I2C_SR1_BTF);
I2C2->CR1 &= ~I2C_CR1_ACK;
buf[i++] = (uint8_t)I2C2->DR;
I2C2->CR1 |= I2C_CR1_STOP;
buf[i++] = (uint8_t)I2C2->DR;
e |= wait1(I2C_SR1_RXNE);
buf[i] = (uint8_t)I2C2->DR;
}
while (I2C2->CR1 & I2C_CR1_STOP) {
}
I2C2->CR1 = (I2C2->CR1 & ~I2C_CR1_POS) | I2C_CR1_ACK;
return e;
}
void ft_port_delay_ms(uint32_t d)
{
uint32_t t0 = ms;
while (ms - t0 < d) {
}
}
void ft_port_rst(int level)
{
GPIOC->BSRR = level ? 1u << RST_PIN : 1u << (RST_PIN + 16u);
}
int ft_port_int_low(void)
{
return !(GPIOC->IDR & (1u << INT_PIN));
}
void SysTick_Handler(void)
{
ms++;
}
void EXTI4_IRQHandler(void)
{
EXTI->PR = EXTI_PR_PR4;
int_flag = 1;
}
static void board_init(void)
{
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN | RCC_AHB1ENR_GPIOCEN;
RCC->APB1ENR |= RCC_APB1ENR_I2C2EN;
RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN;
SysTick_Config(168000u);
/* PB10, PB11 — AF4, открытый сток; подтяжки — на модуле (внутренние 30–50 кОм для 400 кГц слабы) */
GPIOB->MODER = (GPIOB->MODER & ~(0xFu << 20)) | 0xAu << 20;
GPIOB->OTYPER |= 3u << 10;
GPIOB->OSPEEDR |= 0xFu << 20;
GPIOB->AFR[1] = (GPIOB->AFR[1] & ~(0xFFu << 8)) | 0x44u << 8;
/* PC4 — вход с подтяжкой (INT — активный ноль), PC5 — выход RST, сразу в 0 */
GPIOC->BSRR = 1u << (RST_PIN + 16u);
GPIOC->MODER = (GPIOC->MODER & ~(0xFu << 8)) | 1u << 10;
GPIOC->PUPDR = (GPIOC->PUPDR & ~(3u << 8)) | 1u << 8;
I2C2->CR1 = I2C_CR1_SWRST;
I2C2->CR1 = 0;
I2C2->CR2 = APB1_MHZ;
I2C2->CCR = I2C_CCR_FS | I2C_FAST_CCR;
I2C2->TRISE = I2C_FAST_TRISE;
I2C2->CR1 = I2C_CR1_PE | I2C_CR1_ACK;
}
/* EXTI4 от порта C, по спаду. Включать после ft_init: тогда первый отчёт уже читаем по уровню, а не по спаду. */
static void int_on(void)
{
SYSCFG->EXTICR[1] = (SYSCFG->EXTICR[1] & ~SYSCFG_EXTICR2_EXTI4) | SYSCFG_EXTICR2_EXTI4_PC;
EXTI->FTSR |= EXTI_FTSR_TR4;
EXTI->PR = EXTI_PR_PR4;
EXTI->IMR |= EXTI_IMR_MR4;
NVIC_EnableIRQ(EXTI4_IRQn);
}
int main(void)
{
ft_report_t r;
ui_ev_t ev[8];
board_init();
if (ft_init(&T, &CFG) != FT_OK) {
last_ev = 0xFFu; /* нет ответа на 0x38 или чужой код в 0xA8 */
for (;;) {
}
}
ui_reset(&U, &UC);
int_on();
for (;;) {
__disable_irq();
if (!int_flag && !ft_port_int_low()) { /* INT прижат, а спада не было — всё равно читать */
__WFI();
__enable_irq();
continue;
}
int_flag = 0;
__enable_irq();
if (ft_read(&T, &r) != FT_OK)
continue;
int n = ui_feed(&U, &r, &MAP, ms, ev, 8);
for (int i = 0; i < n && i < 8; i++) {
last_ev = ev[i].type;
last_btn = (uint8_t)ev[i].btn;
n_ev[ev[i].type]++;
}
}
}
Next: section 43 "Hardware cryptography: CRC, RNG, HASH, CRYP".