H753_CPP_SPI_Resistive_Touch_Screen_Controller 1.0
STM32H753 SPI resistive touchscreen (Adafruit 333 overlay + 5767 TSC2046 controller) driver and tutorial
Loading...
Searching...
No Matches
TSC2046.cpp
1/*
2 * TSC2046.cpp
3 *
4 * Created on: Sep 15, 2026
5 * Author: johng
6 */
7
8#include "TSC2046.hpp"
9#include "STM32BusIORegister.hpp"
10#include <cmath>
11
12namespace {
13
14// Multiplexer channel-select addresses, single-ended reference mode
15// (temperature, VBAT, AUX -- everything except touch coordinates).
16constexpr uint8_t kAddrSerTemp0 = 0b000;
17constexpr uint8_t kAddrSerVBat = 0b010;
18constexpr uint8_t kAddrSerAux = 0b110;
19constexpr uint8_t kAddrSerTemp1 = 0b111;
20
21// Multiplexer channel-select addresses, differential reference mode
22// (touch coordinates only -- more accurate, not available for the others).
23constexpr uint8_t kAddrDfrYPos = 0b001;
24constexpr uint8_t kAddrDfrZ1Pos = 0b011;
25constexpr uint8_t kAddrDfrZ2Pos = 0b100;
26constexpr uint8_t kAddrDfrXPos = 0b101;
27
28constexpr float kInternalVRef = 2.5f;
29
41uint8_t buildControlByte(uint8_t channelSelect, bool singleEndedRef, bool enableInternalVRef, bool enableOrIdleADC) {
42 uint8_t word = 0x80; // START, always 1
43 word |= static_cast<uint8_t>((channelSelect & 0x07) << 4); // A2:A0
44 // bit3 (MODE) left at 0: always 12-bit conversions.
45 word |= singleEndedRef ? (1u << 2) : 0; // SER/DFR'
46 word |= enableInternalVRef ? (1u << 1) : 0; // PD1
47 word |= enableOrIdleADC ? (1u << 0) : 0; // PD0
48 return word;
49}
50
51} // namespace
52
53TSC2046::TSC2046(SPI_HandleTypeDef *hspi, GPIO_TypeDef *csPort, uint16_t csPin)
54 : _spiDevice(hspi, csPort, csPin), _xPlateResistance(400), _vref(-1.f),
55 _touchedThreshold(100000.f), _interruptsEnabled(true) {
56}
57
58void TSC2046::begin(uint32_t xPlateResistance) {
59 _xPlateResistance = xPlateResistance;
60 _spiDevice.begin();
61}
62
63void TSC2046::setVRef(float vref) {
64 _vref = vref;
65}
66
67void TSC2046::setTouchedThreshold(float touchedThresholdOhms) {
68 _touchedThreshold = touchedThresholdOhms;
69}
70
71float TSC2046::effectiveVRef() {
72 return (_vref < 0.f) ? kInternalVRef : _vref;
73}
74
75uint16_t TSC2046::extractTwelveBitValue(uint32_t registerValue) {
76 // The chip returns a 16-bit window: 1 leading null bit, then the 12-bit
77 // conversion result, then 3 trailing zero bits -- shift off the
78 // trailing padding and mask away the leading null bit.
79 return static_cast<uint16_t>((registerValue >> 3) & 0xFFF);
80}
81
82uint16_t TSC2046::readCoord(uint8_t channelSelect) {
83 bool enableInternalVRef;
84 bool enableOrIdleADC;
85
86 // NOTE (from the reference library): the datasheet says PD0=1 disables
87 // interrupts, but empirically PENIRQ' still responds unless *both* PD1
88 // and PD0 are high. Differential mode (used here) doesn't need VREF at
89 // all, so PD1 is free to double up as the "disable interrupts" bit.
90 if (_interruptsEnabled) {
91 enableInternalVRef = false;
92 enableOrIdleADC = false;
93 } else {
94 enableInternalVRef = true;
95 enableOrIdleADC = true;
96 }
97
98 uint8_t controlByte = buildControlByte(channelSelect, /*singleEndedRef=*/false, enableInternalVRef, enableOrIdleADC);
99
100 STM32BusIORegister controlReg(&_spiDevice, controlByte, /*width=*/2, /*addressWidth=*/1);
101 return extractTwelveBitValue(controlReg.read());
102}
103
104uint16_t TSC2046::readExtra(uint8_t channelSelect) {
105 bool enableInternalVRef = !(_vref > 0.f);
106 bool enableOrIdleADC = !_interruptsEnabled;
107
108 uint8_t controlByte = buildControlByte(channelSelect, /*singleEndedRef=*/true, enableInternalVRef, enableOrIdleADC);
109
110 STM32BusIORegister controlReg(&_spiDevice, controlByte, /*width=*/2, /*addressWidth=*/1);
111 return extractTwelveBitValue(controlReg.read());
112}
113
115 if (_interruptsEnabled) {
116 __disable_irq();
117 }
118
119 int16_t xResult = static_cast<int16_t>(readCoord(kAddrDfrXPos));
120 int16_t yResult = static_cast<int16_t>(readCoord(kAddrDfrYPos));
121 int16_t z1Result = static_cast<int16_t>(readCoord(kAddrDfrZ1Pos));
122 int16_t z2Result = static_cast<int16_t>(readCoord(kAddrDfrZ2Pos));
123
124 if (_interruptsEnabled) {
125 __enable_irq();
126 }
127
128 // R_TOUCH = R_X_PLATE * (X / 4096) * (Z2 / Z1 - 1)
129 float pressure = _xPlateResistance * (xResult / 4096.f) *
130 ((static_cast<float>(z2Result) / static_cast<float>(z1Result)) - 1.f);
131
132 TSC2046TouchPoint point;
133 point.x = xResult;
134 point.y = yResult;
135 point.pressure = pressure;
136 return point;
137}
138
140 TSC2046TouchPoint point = getPoint();
141 return std::isfinite(point.pressure) && point.pressure != 0.f && point.pressure < _touchedThreshold;
142}
143
144void TSC2046::enableInterrupts(bool enable) {
145 _interruptsEnabled = enable;
146 // Perform any read so the new PD bits actually reach the chip and take
147 // effect (matches the reference library -- the PD bits only reach the
148 // chip as a side effect of the next conversion command).
149 readCoord(0);
150}
151
153 return readTemperatureKelvin() - 273.f;
154}
155
157 return (9.f / 5.f) * readTemperatureC() + 32.f;
158}
159
160float TSC2046::readTemperatureKelvin() {
161 uint16_t temp0 = readExtra(kAddrSerTemp0);
162 uint16_t temp1 = readExtra(kAddrSerTemp1);
163
164 // V = (ADC_VALUE * VRef) / 4096; take the difference in millivolts, then
165 // apply the datasheet's simplified T = 2.573 K/mV formula.
166 float deltaMillivolts = ((static_cast<float>(temp1) - static_cast<float>(temp0)) * effectiveVRef() / 4096.f) * 1000.f;
167 return deltaMillivolts * 2.573f;
168}
169
171 constexpr float kVBatMultiplier = 4.f; // Datasheet: VBAT reading is divided by 4 internally.
172 uint16_t rawVBat = readExtra(kAddrSerVBat);
173 return (rawVBat * kVBatMultiplier * effectiveVRef()) / 4096.f;
174}
175
177 uint16_t rawVAux = readExtra(kAddrSerAux);
178 return (rawVAux * effectiveVRef()) / 4096.f;
179}
Represents one addressable, read-only, MSB-first register accessed over an STM32SPIDevice,...
void begin()
Deasserts chip-select (idle-high) so the bus starts in a known state. Call once before the first tran...
float readTemperatureF()
Same reading as readTemperatureC(), converted to degrees Fahrenheit.
Definition TSC2046.cpp:156
void setVRef(float vref)
Sets the voltage connected to the TSC2046's VRef pin, if any.
Definition TSC2046.cpp:63
TSC2046(SPI_HandleTypeDef *hspi, GPIO_TypeDef *csPort, uint16_t csPin)
Constructs the driver and its underlying STM32SPIDevice. Does not touch hardware – call begin() befor...
Definition TSC2046.cpp:53
float readAuxiliaryVoltage()
Definition TSC2046.cpp:176
float readBatteryVoltage()
Definition TSC2046.cpp:170
void enableInterrupts(bool enable)
Enables or disables the TSC2046's own touch interrupt (IRQ pin pulled low on touch)....
Definition TSC2046.cpp:144
TSC2046TouchPoint getPoint()
Reads the current touch position and pressure.
Definition TSC2046.cpp:114
bool isTouched()
Determines if the touchscreen is currently being touched, based on the pressure value from getPoint()...
Definition TSC2046.cpp:139
void setTouchedThreshold(float touchedThresholdOhms)
Sets the pressure threshold used by isTouched().
Definition TSC2046.cpp:67
float readTemperatureC()
Reads the TSC2046's on-die temperature sensor, in degrees Celsius.
Definition TSC2046.cpp:152
void begin(uint32_t xPlateResistance=400)
Initializes the driver. Call once before any other method.
Definition TSC2046.cpp:58
One touch reading: raw X/Y position and a pressure-proportional resistance value.
Definition TSC2046.hpp:32
int16_t x
Raw 12-bit X coordinate (0-4095). Meaningless if not touched.
Definition TSC2046.hpp:33