-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathboard-pyportal.go
257 lines (224 loc) · 6.08 KB
/
board-pyportal.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
//go:build pyportal
package board
import (
"machine"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/ili9341"
"tinygo.org/x/drivers/pixel"
"tinygo.org/x/drivers/touch/resistive"
)
const (
Name = "pyportal"
)
var (
Power = dummyBattery{state: NoBattery}
Sensors = baseSensors{} // TODO: light, temperature
Display = mainDisplay{}
Buttons = noButtons{}
)
type mainDisplay struct{}
var display *ili9341.Device
func (d mainDisplay) Configure() Displayer[pixel.RGB565BE] {
// Initialize backlight and disable at startup.
backlight := machine.TFT_BACKLIGHT
backlight.Configure(machine.PinConfig{Mode: machine.PinOutput})
backlight.Low()
// Enable and configure display.
display = ili9341.NewParallel(
machine.LCD_DATA0,
machine.TFT_WR,
machine.TFT_DC,
machine.TFT_CS,
machine.TFT_RESET,
machine.TFT_RD,
)
display.Configure(ili9341.Config{
Rotation: ili9341.Rotation270,
})
// Enable the TE ("tearing effect") pin to read vblank status.
te := machine.TFT_TE
te.Configure(machine.PinConfig{Mode: machine.PinInput})
display.EnableTEOutput(true)
return display
}
func (d mainDisplay) MaxBrightness() int {
return 1
}
func (d mainDisplay) SetBrightness(level int) {
machine.TFT_BACKLIGHT.Set(level > 0)
}
func (d mainDisplay) WaitForVBlank(defaultInterval time.Duration) {
// Wait until the display has finished updating.
// TODO: wait for a pin interrupt instead of blocking.
for machine.TFT_TE.Get() == true {
}
for machine.TFT_TE.Get() == false {
}
}
func (d mainDisplay) PPI() int {
return 166 // appears to be the same size/resolution as the Gopher Badge and the MCH2022 badge
}
// Configure the resistive touch input on this display.
func (d mainDisplay) ConfigureTouch() TouchInput {
machine.InitADC()
resistiveTouch.Configure(&resistive.FourWireConfig{
YP: machine.TOUCH_YD,
YM: machine.TOUCH_YU,
XP: machine.TOUCH_XR,
XM: machine.TOUCH_XL,
})
return touchInput{}
}
var resistiveTouch resistive.FourWire
var touchPoints [1]TouchPoint
type touchInput struct{}
var touchID uint32
// State associated with the touch input.
var (
medianFilterX, medianFilterY medianFilter
iirFilterX, iirFilterY iirFilter
lastPosX, lastPosY int
)
func (input touchInput) ReadTouch() []TouchPoint {
// Values calibrated on the PyPortal I have. Other boards might have
// slightly different values.
// TODO: make this configurable?
const (
xmin = 54000
xmax = 16000
ymin = 48000
ymax = 22000
)
point := resistiveTouch.ReadTouchPoint()
if point.Z > 8192 {
medianFilterX.add(point.X)
medianFilterY.add(point.Y)
var posX, posY int
if touchPoints[0].ID == 0 {
// First touch on the touch screen.
touchID++
touchPoints[0].ID = touchID
for i := 0; i < 4; i++ {
// Initialize the median filter at this point with some more
// samples, so that the entire median filter is filled.
point := resistiveTouch.ReadTouchPoint()
medianFilterX.add(point.X)
medianFilterY.add(point.Y)
}
// Reset the IIR filter, and use the position as-is.
iirFilterX.add(medianFilterX.value(), true)
iirFilterY.add(medianFilterY.value(), true)
posX = iirFilterX.value()
posY = iirFilterY.value()
} else {
// New touch value while we were touching before.
// Add the value to the IIR filter.
iirFilterX.add(medianFilterX.value(), false)
iirFilterY.add(medianFilterY.value(), false)
// Use some hysteresis to avoid moving the point when it didn't
// actually move.
posX = lastPosX
posY = lastPosY
const diff = 400 // arbitrary value that appears to work well
if iirFilterX.value() > lastPosX+diff {
posX = iirFilterX.value() - diff
}
if iirFilterX.value() < lastPosX-diff {
posX = iirFilterX.value() + diff
}
if iirFilterY.value() > lastPosY+diff {
posY = iirFilterY.value() - diff
}
if iirFilterY.value() < lastPosY-diff {
posY = iirFilterY.value() + diff
}
}
lastPosX = posX
lastPosY = posY
x := int16(clamp(posX, ymin, ymax, 0, 239))
y := int16(clamp(posY, xmin, xmax, 0, 319))
if display != nil {
// Adjust for screen rotation.
switch display.Rotation() {
case drivers.Rotation90:
x, y = y, 239-x
case drivers.Rotation180:
x = 239 - x
y = 319 - y
case drivers.Rotation270:
x, y = 319-y, x
}
}
touchPoints[0].Y = y
touchPoints[0].X = x
return touchPoints[:1]
} else {
touchPoints[0].ID = 0
}
return nil
}
// Map and clamp an input value to an output range.
func clamp(value, lowIn, highIn, lowOut, highOut int) int {
rangeIn := highIn - lowIn
rangeOut := highOut - lowOut
valueOut := (value - lowIn) * rangeOut / rangeIn
if valueOut > highOut {
valueOut = highOut
}
if valueOut < lowOut {
valueOut = lowOut
}
return valueOut
}
// Touch screen filtering has been implemented using the description in this
// article:
// https://dlbeer.co.nz/articles/tsf.html
// It works a lot better than the rather naive algorithm I implemented before.
type medianFilter [5]int
func (f *medianFilter) add(n int) {
// Shift the value into the array.
f[0] = f[1]
f[1] = f[2]
f[2] = f[3]
f[3] = f[4]
f[4] = n
}
func (f *medianFilter) value() int {
// Optimal sorting algorithm.
// It is based on the sorting algorithm described here:
// https://bertdobbelaere.github.io/sorting_networks.html
sorted := *f
compareSwap := func(a, b *int) {
if *a > *b {
*b, *a = *a, *b
}
}
compareSwap(&sorted[1], &sorted[4])
compareSwap(&sorted[0], &sorted[3])
compareSwap(&sorted[1], &sorted[3])
compareSwap(&sorted[0], &sorted[2])
compareSwap(&sorted[2], &sorted[4])
compareSwap(&sorted[0], &sorted[1])
compareSwap(&sorted[1], &sorted[2])
compareSwap(&sorted[3], &sorted[4])
compareSwap(&sorted[2], &sorted[3])
// Return the median value.
return sorted[2]
}
// Infinite impulse response filter, to smooth the input values somewhat.
type iirFilter struct {
state int
}
func (f *iirFilter) add(x int, reset bool) {
if reset {
f.state = x
}
// For every update, the new value is half of x and half of the old value,
// added together:
// f.state = f.state*0.5 + x*0.5
f.state = (f.state + x + 1) / 2
}
func (f *iirFilter) value() int {
return f.state
}