Skip to content

Commit 09563ca

Browse files
committed
feat: count pulses instead of just edges
This gives a couple notable benefits: * It handles debouncing on both edges of the pulse. * It allows the user to configure a different pulse width, which could be used for a fixture lid switch where the length of a "valid" test is known as a means to reject invalid pulses. For example, if a test takes at least 30 seconds, the minimum pulse width could be set to that, allowing for an operator closing a fixture and reopening it due to a setup issue or something.
1 parent 20e5685 commit 09563ca

File tree

3 files changed

+76
-36
lines changed

3 files changed

+76
-36
lines changed

CMakeLists.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,19 @@ target_link_libraries("${PROJECT_NAME}"
2626

2727
target_compile_features("${PROJECT_NAME}" PUBLIC cxx_std_20)
2828

29+
if (DEFINED MIN_PULSE_WIDTH_US)
30+
if (MIN_PULSE_WIDTH_US GREATER_EQUAL 10)
31+
target_compile_definitions("${PROJECT_NAME}" PRIVATE
32+
"-DCONFIG_MIN_PULSE_WIDTH_US=${MIN_PULSE_WIDTH_US}"
33+
)
34+
else()
35+
message(FATAL_ERROR
36+
"Invalid minimum pulse width: ${MIN_PULSE_WIDTH_US}\n"
37+
"MIN_PULSE_WIDTH_US must be a number >= 10."
38+
)
39+
endif()
40+
endif()
41+
2942
option(USB_SERIAL "Use USB serial instead of UART" OFF)
3043

3144
# add -DUSB_SERIAL=ON when configuring to use USB (9600 baud) instead of UART

README.md

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ This project uses a Raspberry Pi Pico to identify and store various information
44
about a system/fixture, settable and retrievable over UART or USB. Additionally,
55
writing can be disabled by jumping GPIO pins 14 and 15 together (pins 19 and 20
66
on the board), and GPIO 13 (pin 17 on the board) can be switched to GND and used
7-
as a falling edge counter, such as for a fixture lid switch.
7+
as a pulse counter, such as for a fixture lid switch. This pin is active low and
8+
counts any low pulse longer than a minimum time period (configurable by with a
9+
CMake variable).
810

911
This project makes use of a Pi Pico and a MIKROE EEPROM 3 Click. The I2C lines
1012
for the EEPROM should be connected to GPIOs 16 (SDA) and 17 (SCL) (pins 21 and
@@ -17,7 +19,10 @@ read the Pico's unique 64-bit identifier as a hex string, but of course this is
1719
read-only. The idea is that the Pico's serial number can always be used to
1820
uniquely identify any device, as no two Picos have the same serial.
1921

20-
The EEPROM specifies up to 1,000,000 writes per flash cell (per 4-byte word).
22+
The EEPROM specifies up to 1,000,000 writes per flash cell (per 4-byte word). In
23+
theory, this means we can count up to 1 million pulses reliably, as no wear
24+
leveling is implemented at the moment. This could be added in the future to
25+
trivially multiply the maximum count.
2126

2227
| Field | Access | Description |
2328
|---|---|---|
@@ -32,7 +37,7 @@ The EEPROM specifies up to 1,000,000 writes per flash cell (per 4-byte word).
3237
| `USER3` | Read-write | General-purpose field 3 |
3338
| `USER4` | Read-write | General-purpose field 4 |
3439
| `SERIAL` | Read-only | Pico's unique 64-bit serial number |
35-
| `EDGECOUNT?` | Read-only | Falling edge count on GP13 (board pin 17) |
40+
| `PULSECOUNT?` | Read-only | Low pulse count on GP13 (board pin 17) |
3641

3742
Note that each of the above fields has a maximum length of 63. Each field is 64
3843
bytes, but is null-terminated. Values longer than 63 bytes will be truncated.
@@ -65,7 +70,7 @@ There are some additional commands:
6570
|---|---|
6671
| `CLEAR` | Clear all writable fields |
6772
| `CHECK?` | Check that the data stored in EEPROM matches the stored checksum, then return either `OK` or `ERR` |
68-
| `RESETCOUNT` | Reset the edge count to 0 |
73+
| `RESETCOUNT` | Reset the pulse count to 0 |
6974

7075
## Build Requirements
7176

@@ -91,6 +96,10 @@ This step may take quite some time, as it will download the Pi Pico SDK. If you
9196
wish to use USB for serial communications instead of UART, add `-DUSB_SERIAL=ON`
9297
to the above command.
9398

99+
The default minimum pulse width is 100ms. To override this time limit, pass
100+
`-DMIN_PULSE_WIDTH_US=500000` to get 500ms (you can use any whole number of
101+
microseconds greater than or equal to 10, however).
102+
94103
After the configuration step is done, you can build the project like so:
95104

96105
```

src/main.cpp

Lines changed: 50 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,18 @@
3535

3636
#define I2C_INST i2c0
3737

38+
// How long a low pulse must be to be counted.
39+
#ifdef CONFIG_MIN_PULSE_WIDTH_US
40+
#define MIN_PULSE_WIDTH_US (int64_t(CONFIG_MIN_PULSE_WIDTH_US))
41+
#else
42+
#define MIN_PULSE_WIDTH_US (100'000ULL)
43+
#endif // CONFIG_MIN_PULSE_WIDTH_US
44+
45+
static_assert(MIN_PULSE_WIDTH_US >= 10ULL,
46+
"Minimum pulse width must be at least 10us!");
47+
3848
static constexpr std::uint32_t kDeviceInfoAddr{0x0};
39-
static constexpr std::uint32_t kEdgeCountAddr{0x800};
49+
static constexpr std::uint32_t kPulseCountAddr{0x800};
4050

4151
// Board ID (this is set only once and stored here).
4252
static char board_id[PICO_UNIQUE_BOARD_ID_SIZE_BYTES * 2 + 1];
@@ -47,9 +57,8 @@ static DeviceInfoBlock data;
4757
// EEPROM peripheral.
4858
static AT24CM02 eeprom{I2C_INST, true};
4959

50-
static volatile std::uint32_t edgecount;
51-
static std::uint32_t last_edgecount;
52-
static volatile ::absolute_time_t last_edge_time;
60+
static volatile std::uint32_t pulsecount;
61+
static std::uint32_t last_pulsecount;
5362

5463
[[noreturn]] static void Panic() noexcept {
5564
while (1) {
@@ -82,20 +91,20 @@ static void ValidateDeviceInfo() noexcept {
8291
}
8392
}
8493

85-
static void StoreEdgeCount(std::uint32_t ec) noexcept {
86-
if (!eeprom.Write(kEdgeCountAddr, reinterpret_cast<const std::uint8_t*>(&ec),
87-
sizeof(ec))) {
94+
static void StorePulseCount(std::uint32_t pc) noexcept {
95+
if (!eeprom.Write(kPulseCountAddr, reinterpret_cast<const std::uint8_t*>(&pc),
96+
sizeof(pc))) {
8897
Panic();
8998
}
9099
}
91100

92-
static void LoadEdgeCount() noexcept {
93-
std::uint32_t ec;
94-
if (!eeprom.Read(kEdgeCountAddr, reinterpret_cast<std::uint8_t*>(&ec),
95-
sizeof(ec))) {
101+
static void LoadPulseCount() noexcept {
102+
std::uint32_t pc;
103+
if (!eeprom.Read(kPulseCountAddr, reinterpret_cast<std::uint8_t*>(&pc),
104+
sizeof(pc))) {
96105
Panic();
97106
}
98-
edgecount = ec;
107+
pulsecount = pc;
99108
}
100109

101110
[[nodiscard]] static bool WriteLockEnabled() noexcept {
@@ -147,8 +156,8 @@ static void HandleSerialMessage(std::string_view message) noexcept {
147156
} else {
148157
std::printf("ERR\n");
149158
}
150-
} else if (header == "EDGECOUNT"sv) {
151-
std::printf("%u\n", edgecount);
159+
} else if (header == "PULSECOUNT"sv) {
160+
std::printf("%u\n", pulsecount);
152161
}
153162
}
154163
} else {
@@ -159,9 +168,9 @@ static void HandleSerialMessage(std::string_view message) noexcept {
159168
StoreDeviceInfo();
160169
}
161170
} else if (header == "RESETCOUNT"sv) {
162-
StoreEdgeCount(0);
163-
edgecount = 0;
164-
last_edgecount = 0;
171+
StorePulseCount(0);
172+
pulsecount = 0;
173+
last_pulsecount = 0;
165174
}
166175
}
167176
}
@@ -203,28 +212,37 @@ int main(void) {
203212
LoadDeviceInfo();
204213
ValidateDeviceInfo();
205214

206-
LoadEdgeCount();
215+
LoadPulseCount();
207216
// check for blank EEPROM
208-
if (edgecount == 0xFFFFFFFF) {
209-
edgecount = 0;
210-
StoreEdgeCount(0);
217+
if (pulsecount == 0xFFFFFFFF) {
218+
pulsecount = 0;
219+
StorePulseCount(0);
211220
}
212221

213222
// Get the board ID (we only need to do this once)
214223
::pico_get_unique_board_id_string(board_id, sizeof(board_id));
215224

216-
last_edgecount = edgecount;
217-
last_edge_time = ::get_absolute_time();
225+
last_pulsecount = pulsecount;
226+
static volatile ::absolute_time_t falling_edge_time = ::get_absolute_time();
227+
static volatile bool pulse_started = false;
218228

219-
// Falling edges increase the edge count.
229+
// When we see a falling edge, store the time that happened. When we see
230+
// a rising edge, see how long the pulse was, and if it was long enough,
231+
// increment the edge count. Else, we wait for the next falling edge.
220232
::gpio_set_irq_enabled_with_callback(
221-
PIN_SWITCH, GPIO_IRQ_EDGE_FALL, true, [](::uint, std::uint32_t) {
233+
PIN_SWITCH, GPIO_IRQ_EDGE_FALL | GPIO_IRQ_EDGE_RISE, true,
234+
[](::uint, std::uint32_t event_mask) {
222235
const auto now = ::get_absolute_time();
223-
const auto diff = ::absolute_time_diff_us(last_edge_time, now);
224-
// 50ms debounce
225-
if (diff >= 50'000) {
226-
last_edge_time = now;
227-
edgecount = edgecount + 1;
236+
if (!pulse_started && (event_mask & GPIO_IRQ_EDGE_FALL)) {
237+
pulse_started = true;
238+
falling_edge_time = now;
239+
} else if (pulse_started && (event_mask & GPIO_IRQ_EDGE_RISE)) {
240+
const auto diff = ::absolute_time_diff_us(falling_edge_time, now);
241+
// Look for low pulses wide enough to be counted.
242+
if (diff >= MIN_PULSE_WIDTH_US) {
243+
pulse_started = false;
244+
pulsecount = pulsecount + 1;
245+
}
228246
}
229247
});
230248

@@ -234,8 +252,8 @@ int main(void) {
234252
while (1) {
235253
c = ::getchar_timeout_us(10);
236254
if (c == PICO_ERROR_TIMEOUT) {
237-
if (edgecount != last_edgecount) {
238-
StoreEdgeCount(last_edgecount = edgecount);
255+
if (pulsecount != last_pulsecount) {
256+
StorePulseCount(last_pulsecount = pulsecount);
239257
}
240258
continue;
241259
}

0 commit comments

Comments
 (0)