-
Notifications
You must be signed in to change notification settings - Fork 0
/
capsense.c
336 lines (291 loc) · 12.4 KB
/
capsense.c
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
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
/*******************************************************************************
* @file
* @brief Capacitive sense driver
*******************************************************************************
* # License
* <b>Copyright 2018 Silicon Laboratories Inc. www.silabs.com</b>
*******************************************************************************
*
* The licensor of this software is Silicon Laboratories Inc. Your use of this
* software is governed by the terms of Silicon Labs Master Software License
* Agreement (MSLA) available at
* www.silabs.com/about-us/legal/master-software-license-agreement. This
* software is distributed to you in Source Code format and is governed by the
* sections of the MSLA applicable to Source Code.
*
******************************************************************************/
#include "em_device.h"
#include "em_acmp.h"
#include "em_cmu.h"
#include "em_emu.h"
#include "capsense.h"
/*******************************************************************************
* @addtogroup kitdrv
* @{
******************************************************************************/
/*******************************************************************************
* @addtogroup CapSense
* @brief Capacitive sensing driver
*
* @details
* Capacitive sensing driver using TIMER and ACMP peripherals.
*
* @{
******************************************************************************/
/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */
/** The current channel we are sensing. */
static volatile uint8_t currentChannel;
/** Flag for measurement completion. */
static volatile bool measurementComplete;
#if defined(CAPSENSE_CH_IN_USE)
/******************************************************************************
* @brief
* A bit vector which represents the channels to iterate through
*
* @note
* This API is deprecated and new application should define
* CAPSENSE_CHANNELS instead of CAPSENSE_CH_IN_USE.
*
* @param ACMP_CHANNELS
* Vector of channels.
*****************************************************************************/
static const bool channelsInUse[ACMP_CHANNELS] = CAPSENSE_CH_IN_USE;
#elif defined(CAPSENSE_CHANNELS)
/******************************************************************************
* @brief
* An array of channels that the capsense driver should iterate through.
*
* @param CAPSENSE_CHANNELS
* Initializer list that contains all the channels that the application
* would like to use for capsense.
*
* @param ACMP_CHANNELS
* The number of channels in the array
*****************************************************************************/
static const ACMP_Channel_TypeDef channelList[ACMP_CHANNELS] = CAPSENSE_CHANNELS;
#endif
/******************************************************************************
* @brief The NUM_SLIDER_CHANNELS specifies how many of the ACMP_CHANNELS
* are used for a touch slider
*****************************************************************************/
#if !defined(NUM_SLIDER_CHANNELS)
#define NUM_SLIDER_CHANNELS 4
#endif
/******************************************************************************
* @brief This vector stores the latest read values from the ACMP
* @param ACMP_CHANNELS Vector of channels.
*****************************************************************************/
static volatile uint32_t channelValues[ACMP_CHANNELS] = { 0 };
/******************************************************************************
* @brief This stores the maximum values seen by a channel
* @param ACMP_CHANNELS Vector of channels.
*****************************************************************************/
static volatile uint32_t channelMaxValues[ACMP_CHANNELS] = { 0 };
/** @endcond */
/******************************************************************************
* @brief
* TIMER0 interrupt handler.
*
* @details
* When TIMER0 expires the number of pulses on TIMER1 is inserted into
* channelValues. If this values is bigger than what is recorded in
* channelMaxValues, channelMaxValues is updated.
* Finally, the next ACMP channel is selected.
*****************************************************************************/
void TIMER0_IRQHandler(void)
{
uint32_t count;
/* Stop timers */
TIMER0->CMD = TIMER_CMD_STOP;
TIMER1->CMD = TIMER_CMD_STOP;
/* Clear interrupt flag */
TIMER0->IFC = TIMER_IFC_OF;
/* Read out value of TIMER1 */
count = TIMER1->CNT;
/* Store value in channelValues */
channelValues[currentChannel] = count;
/* Update channelMaxValues */
if (count > channelMaxValues[currentChannel]) {
channelMaxValues[currentChannel] = count;
}
measurementComplete = true;
}
/******************************************************************************
* @brief Get the current channelValue for a channel
* @param channel The channel.
* @return The channelValue.
*****************************************************************************/
uint32_t CAPSENSE_getVal(uint8_t channel) {
return channelValues[channel];
}
/******************************************************************************
* @brief Get the current normalized channelValue for a channel
* @param channel The channel.
* @return The channel value in range (0-256).
*****************************************************************************/
uint32_t CAPSENSE_getNormalizedVal(uint8_t channel) {
uint32_t max = channelMaxValues[channel];
return (channelValues[channel] << 8) / max;
}
/******************************************************************************
* @brief Get the state of the Gecko Button
* @param channel The channel.
* @return true if the button is "pressed"
* false otherwise.
*****************************************************************************/
bool CAPSENSE_getPressed(uint8_t channel) {
uint32_t treshold;
/* Treshold is set to 12.5% below the maximum value */
/* This calculation is performed in two steps because channelMaxValues is
* volatile. */
treshold = channelMaxValues[channel];
treshold -= channelMaxValues[channel] >> 2;
if (channelValues[channel] < treshold) {
return true;
}
return false;
}
/******************************************************************************
* @brief Get the position of the slider
* @return The position of the slider if it can be determined,
* -1 otherwise.
*****************************************************************************/
int32_t CAPSENSE_getSliderPosition(void) {
int i;
int minPos = -1;
uint32_t minVal = 224; /* 0.875 * 256 */
/* Values used for interpolation. There is two more which represents the edges.
* This makes the interpolation code a bit cleaner as we do not have to make special
* cases for handling them */
uint32_t interpol[(NUM_SLIDER_CHANNELS + 2)];
for (i = 0; i < (NUM_SLIDER_CHANNELS + 2); i++) {
interpol[i] = 255;
}
/* The calculated slider position. */
int position;
/* Iterate through the slider bars and calculate the current value divided by
* the maximum value multiplied by 256.
* Note that there is an offset of 1 between channelValues and interpol.
* This is done to make interpolation easier.
*/
for (i = 1; i < (NUM_SLIDER_CHANNELS + 1); i++) {
/* interpol[i] will be in the range 0-256 depending on channelMax */
interpol[i] = channelValues[i - 1] << 8;
interpol[i] /= channelMaxValues[i - 1];
/* Find the minimum value and position */
if (interpol[i] < minVal) {
minVal = interpol[i];
minPos = i;
}
}
/* Check if the slider has not been touched */
if (minPos == -1) {
return -1;
}
/* Start position. Shift by 4 to get additional resolution. */
/* Because of the interpol trick earlier we have to substract one to offset that effect */
position = (minPos - 1) << 4;
/* Interpolate with pad to the left */
position -= ((256 - interpol[minPos - 1]) << 3)
/ (256 - interpol[minPos]);
/* Interpolate with pad to the right */
position += ((256 - interpol[minPos + 1]) << 3)
/ (256 - interpol[minPos]);
return position;
}
/******************************************************************************
* @brief
* Start a capsense measurement of a specific channel and waits for
* it to complete.
*****************************************************************************/
static void CAPSENSE_Measure(ACMP_Channel_TypeDef channel) {
/* Set up this channel in the ACMP. */
ACMP_CapsenseChannelSet(ACMP_CAPSENSE, channel);
/* Reset timers */
TIMER0->CNT = 0;
TIMER1->CNT = 0;
measurementComplete = false;
/* Start timers */
TIMER0->CMD = TIMER_CMD_START;
TIMER1->CMD = TIMER_CMD_START;
/* Wait for measurement to complete */
while ( measurementComplete == false ) {
EMU_EnterEM1();
}
}
/******************************************************************************
* @brief
* This function iterates through all the capsensors and reads and
* initiates a reading. Uses EM1 while waiting for the result from
* each sensor.
*****************************************************************************/
void CAPSENSE_Sense(void) {
/* Use the default STK capacative sensing setup and enable it */
ACMP_Enable(ACMP_CAPSENSE);
#if defined(CAPSENSE_CHANNELS)
/* Iterate through only the channels in the channelList */
for (currentChannel = 0; currentChannel < ACMP_CHANNELS; currentChannel++) {
CAPSENSE_Measure(channelList[currentChannel]);
}
#else
/* Iterate through all channels and check which channel is in use */
for (currentChannel = 0; currentChannel < ACMP_CHANNELS; currentChannel++) {
/* If this channel is not in use, skip to the next one */
if (!channelsInUse[currentChannel]) {
continue;
}
CAPSENSE_Measure((ACMP_Channel_TypeDef) currentChannel);
}
#endif
/* Disable ACMP while not sensing to reduce power consumption */
ACMP_Disable(ACMP_CAPSENSE);
}
/******************************************************************************
* @brief
* Initializes the capacitive sense system.
*
* @details
* Capacitive sensing uses two timers: TIMER0 and TIMER1 as well as ACMP.
* ACMP is set up in cap-sense (oscillator mode).
* TIMER1 counts the number of pulses generated by ACMP_CAPSENSE.
* When TIMER0 expires it generates an interrupt.
* The number of pulses counted by TIMER1 is then stored in channelValues
*****************************************************************************/
void CAPSENSE_Init(void) {
/* Use the default STK capacative sensing setup */
ACMP_CapsenseInit_TypeDef capsenseInit = ACMP_CAPSENSE_INIT_DEFAULT;
/* Enable TIMER0, TIMER1, ACMP_CAPSENSE and PRS clock */
CMU_ClockEnable(cmuClock_HFPER, true);
CMU_ClockEnable(cmuClock_TIMER0, true);
CMU_ClockEnable(cmuClock_TIMER1, true);
#if defined(ACMP_CAPSENSE_CMUCLOCK)
CMU_ClockEnable(ACMP_CAPSENSE_CMUCLOCK, true);
#else
CMU->HFPERCLKEN0 |= ACMP_CAPSENSE_CLKEN;
#endif
CMU_ClockEnable(cmuClock_PRS, true);
/* Initialize TIMER0 - Prescaler 2^9, top value 10, interrupt on overflow */
TIMER0->CTRL = TIMER_CTRL_PRESC_DIV512;
TIMER0->TOP = 10;
TIMER0->IEN = TIMER_IEN_OF;
TIMER0->CNT = 0;
/* Initialize TIMER1 - Prescaler 2^10, clock source CC1, top value 0xFFFF */
TIMER1->CTRL = TIMER_CTRL_PRESC_DIV1024 | TIMER_CTRL_CLKSEL_CC1;
TIMER1->TOP = 0xFFFF;
/*Set up TIMER1 CC1 to trigger on PRS channel 0 */
TIMER1->CC[1].CTRL = TIMER_CC_CTRL_MODE_INPUTCAPTURE /* Input capture */
| TIMER_CC_CTRL_PRSSEL_PRSCH0 /* PRS channel 0 */
| TIMER_CC_CTRL_INSEL_PRS /* PRS input selected */
| TIMER_CC_CTRL_ICEVCTRL_RISING /* PRS on rising edge */
| TIMER_CC_CTRL_ICEDGE_BOTH; /* PRS on rising edge */
/*Set up PRS channel 0 to trigger on ACMP1 output*/
PRS->CH[0].CTRL = PRS_CH_CTRL_EDSEL_POSEDGE /* Posedge triggers action */
| PRS_CH_CTRL_SOURCESEL_ACMP_CAPSENSE /* PRS source */
| PRS_CH_CTRL_SIGSEL_ACMPOUT_CAPSENSE; /* PRS source */
/* Set up ACMP1 in capsense mode */
ACMP_CapsenseInit(ACMP_CAPSENSE, &capsenseInit);
/* Enable TIMER0 interrupt */
NVIC_EnableIRQ(TIMER0_IRQn);
}
/** @} (end group CapSense) */
/** @} (end group kitdrv) */