Skip to content

Commit 6847744

Browse files
authored
Merge pull request #1 from BloomyControls/eeprom
2.0.0: use I2C EEPROM and add a pulse counter
2 parents d06d010 + 3f5fe3c commit 6847744

File tree

7 files changed

+787
-287
lines changed

7 files changed

+787
-287
lines changed

CMakeLists.txt

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,37 @@ set(PICO_SDK_FETCH_FROM_GIT_TAG 2.1.1)
77

88
include(pico_sdk_import.cmake)
99

10-
project("pico-ident" VERSION "1.3.0")
10+
project("pico-ident" VERSION "2.0.0")
1111

1212
pico_sdk_init()
1313

14-
add_executable(pico-ident src/main.c)
14+
add_executable(pico-ident
15+
src/AT24CM02.cpp
16+
src/main.cpp
17+
)
1518

1619
target_link_libraries("${PROJECT_NAME}"
1720
pico_stdlib
1821
pico_unique_id
1922
hardware_flash
20-
hardware_gpio)
23+
hardware_gpio
24+
hardware_i2c
25+
)
26+
27+
target_compile_features("${PROJECT_NAME}" PUBLIC cxx_std_20)
28+
29+
if (DEFINED MIN_PULSE_WIDTH_US)
30+
if (MIN_PULSE_WIDTH_US GREATER_EQUAL 50000)
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 >= 50000."
38+
)
39+
endif()
40+
endif()
2141

2242
option(USB_SERIAL "Use USB serial instead of UART" OFF)
2343

README.md

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,25 @@
33
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
6-
on the board).
6+
on the board), and GPIO 13 (pin 17 on the board) can be switched to GND and used
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).
710

8-
**Pre-built UF2 files ready to flash onto the Pico for both USB and UART are
9-
available [here](https://github.com/BloomyControls/pico-ident/releases).**
11+
This project makes use of a Pi Pico and a MIKROE EEPROM 3 Click. The I2C lines
12+
for the EEPROM should be connected to GPIOs 16 (SDA) and 17 (SCL) (pins 21 and
13+
22 on the board, respectively).
14+
15+
**Pre-build UF2 binaries are available for USB or UART configurations
16+
[here][releases].**
17+
18+
> [!IMPORTANT]
19+
> This is the new version with pulse counting using the AT24CM02 I2C EEPROM
20+
> chip. For the old version without pulse counting which uses only the Pico's
21+
> on-board flash, use [version 1.3.0][1.3.0].
22+
23+
[releases]: https://github.com/BloomyControls/pico-ident/releases
24+
[1.3.0]: https://github.com/BloomyControls/pico-ident/releases/tag/v1.3.0
1025

1126
## Information Fields
1227

@@ -15,12 +30,12 @@ read the Pico's unique 64-bit identifier as a hex string, but of course this is
1530
read-only. The idea is that the Pico's serial number can always be used to
1631
uniquely identify any device, as no two Picos have the same serial.
1732

18-
It's worth noting that each sector (4096 bytes) of flash has a guaranteed
19-
minimum of 100,000 program-erase cycles according to the manufacturer. Each
20-
write to one of these fields will incur one erase and one program. This
21-
effectively limits us to 100,000 writes to any of these fields. Of course, the
22-
intended use of this device is to be set once and then write-locked for the rest
23-
of its lifespan, so that's likely not a huge concern here.
33+
The EEPROM specifies up to 1,000,000 writes per flash cell (per 4-byte word).
34+
Using a simple wear-leveling scheme using 16 words, the theoretical minimum
35+
number of pulses that can be counted without resetting is about 16 million. This
36+
is expected to be incredibly high for most applications, but by changing
37+
a constant, this number can be trivially increased for more demanding
38+
applications.
2439

2540
| Field | Access | Description |
2641
|---|---|---|
@@ -35,6 +50,7 @@ of its lifespan, so that's likely not a huge concern here.
3550
| `USER3` | Read-write | General-purpose field 3 |
3651
| `USER4` | Read-write | General-purpose field 4 |
3752
| `SERIAL` | Read-only | Pico's unique 64-bit serial number |
53+
| `PULSECOUNT?` | Read-only | Low pulse count on GP13 (board pin 17) |
3854

3955
Note that each of the above fields has a maximum length of 63. Each field is 64
4056
bytes, but is null-terminated. Values longer than 63 bytes will be truncated.
@@ -61,12 +77,13 @@ to query the manufacturer:
6177
MFG?\r
6278
```
6379

64-
There are two additional commands:
80+
There are some additional commands:
6581

6682
| Command | Description |
6783
|---|---|
6884
| `CLEAR` | Clear all writable fields |
69-
| `CHECK?` | Check that the data stored in flash matches the stored checksum, then return either `OK` or `ERR` |
85+
| `CHECK?` | Check that the data stored in EEPROM matches the stored checksum, then return either `OK` or `ERR` |
86+
| `RESETCOUNT` | Reset the pulse count to 0 |
7087

7188
## Build Requirements
7289

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

112+
The default minimum pulse width is 100ms. To override this time limit, pass
113+
`-DMIN_PULSE_WIDTH_US=500000` to get 500ms (you can use any whole number of
114+
microseconds greater than or equal to 50,000).
115+
95116
After the configuration step is done, you can build the project like so:
96117

97118
```

src/AT24CM02.cpp

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* AT24CM02 Driver Code
3+
*
4+
* Copyright (c) 2025, Bloomy Controls, Inc. All rights reserved.
5+
* Use of this source code is governed by a BSD-3-clause license that can be
6+
* found in the LICENSE file or at https://opensource.org/license/BSD-3-Clause
7+
*/
8+
9+
#include "AT24CM02.h"
10+
11+
#include <algorithm>
12+
13+
constexpr static std::uint32_t kAddrMask{(1U << 18U) - 1U};
14+
15+
AT24CM02::AT24CM02(::i2c_inst_t* i2c, bool addr_pin) noexcept
16+
: i2c_{i2c}, addr_pin_(addr_pin ? 1U : 0U) {}
17+
18+
bool AT24CM02::Write(std::uint32_t addr, const std::uint8_t* buf,
19+
std::size_t len) const noexcept {
20+
if (!i2c_ || !buf || len == 0 || (addr & ~kAddrMask)) {
21+
return false;
22+
}
23+
24+
if (addr + len >= kPages * kBytesPerPage) {
25+
return false;
26+
}
27+
28+
std::size_t page_remain = kBytesPerPage - (addr % kBytesPerPage);
29+
30+
if (len < page_remain) {
31+
page_remain = len;
32+
}
33+
34+
while (len > 0) {
35+
const std::uint8_t i2c_addr =
36+
(0xA0 | (addr_pin_ << 3U) | (addr >> 15U)) >> 1U;
37+
std::uint8_t addr_bytes[2];
38+
39+
addr_bytes[0] = (addr & 0x0FF00) >> 8;
40+
addr_bytes[1] = addr & 0x000FF;
41+
42+
if (::i2c_write_burst_blocking(i2c_, i2c_addr, addr_bytes,
43+
sizeof(addr_bytes)) == PICO_ERROR_GENERIC) {
44+
return false;
45+
}
46+
47+
if (::i2c_write_blocking(i2c_, i2c_addr, buf, page_remain, false) ==
48+
PICO_ERROR_GENERIC) {
49+
return false;
50+
}
51+
52+
// this could be replaced with ACK polling if we hate waiting for the max
53+
// write time
54+
::sleep_ms(10);
55+
56+
addr += page_remain;
57+
buf += page_remain;
58+
len -= page_remain;
59+
60+
page_remain = std::min(len, kBytesPerPage);
61+
}
62+
63+
return true;
64+
}
65+
66+
bool AT24CM02::Read(std::uint32_t addr, std::uint8_t* buf,
67+
std::size_t len) const noexcept {
68+
if (!i2c_ || !buf || len == 0 || (addr & ~kAddrMask)) {
69+
return false;
70+
}
71+
72+
if (addr + len >= kPages * kBytesPerPage) {
73+
return false;
74+
}
75+
76+
const std::uint8_t i2c_addr =
77+
(0xA0 | (addr_pin_ << 3U) | (addr >> 15U)) >> 1U;
78+
std::uint8_t addr_bytes[2];
79+
80+
addr_bytes[0] = (addr & 0x0FF00) >> 8;
81+
addr_bytes[1] = addr & 0x000FF;
82+
83+
if (::i2c_write_blocking(i2c_, i2c_addr, addr_bytes, sizeof(addr_bytes),
84+
true) == PICO_ERROR_GENERIC) {
85+
return false;
86+
}
87+
88+
if (::i2c_read_blocking(i2c_, i2c_addr, buf, len, false) ==
89+
PICO_ERROR_GENERIC) {
90+
return false;
91+
}
92+
93+
return true;
94+
}

src/AT24CM02.h

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* AT24CM02 I2C Driver
3+
*
4+
* Copyright (c) 2025, Bloomy Controls, Inc. All rights reserved.
5+
* Use of this source code is governed by a BSD-3-clause license that can be
6+
* found in the LICENSE file or at https://opensource.org/license/BSD-3-Clause
7+
*/
8+
9+
#ifndef PICO_IDENT_SRC_AT24CM02_H
10+
#define PICO_IDENT_SRC_AT24CM02_H
11+
12+
#include <cstddef>
13+
#include <cstdint>
14+
15+
#include "hardware/i2c.h"
16+
17+
/**
18+
* @brief Driver for AT24CM02 I2C EEPROM chip.
19+
*/
20+
class AT24CM02 {
21+
public:
22+
/// Total number of pages in flash.
23+
static constexpr std::size_t kPages{1024U};
24+
/// Number of bytes per page.
25+
static constexpr std::size_t kBytesPerPage{256U};
26+
27+
/**
28+
* @brief Constructor.
29+
*
30+
* @param[in] i2c I2C instance to use
31+
* @param[in] addr_pin state of the A2 pin on the EEPROM chip (high or low)
32+
*/
33+
explicit AT24CM02(::i2c_inst_t* i2c, bool addr_pin) noexcept;
34+
35+
explicit AT24CM02(std::nullptr_t, bool) = delete;
36+
37+
/**
38+
* @brief Write data to the EEPROM. Does not need to be page-aligned, and may
39+
* be longer than a page.
40+
*
41+
* @param[in] addr 18-bit memory address to write to
42+
* @param[in] buf data to write to memory
43+
* @param[in] len length of data in bytes
44+
*
45+
* @return Whether the write was successful.
46+
*/
47+
bool Write(std::uint32_t addr, const std::uint8_t* buf,
48+
std::size_t len) const noexcept;
49+
50+
/**
51+
* @brief Read data from the EEPROM. Does not need to be page-aligned, and may
52+
* be longer than a page.
53+
*
54+
* @param[in] addr 18-bit memory address to write to
55+
* @param[out] buf buffer to write data into
56+
* @param[in] len number of bytes to read
57+
*
58+
* @return Whether the read was successful.
59+
*/
60+
bool Read(std::uint32_t addr, std::uint8_t* buf,
61+
std::size_t len) const noexcept;
62+
63+
private:
64+
/// State of the address pin on the chip.
65+
const std::uint8_t addr_pin_;
66+
/// I2C instance.
67+
::i2c_inst_t* i2c_;
68+
};
69+
70+
#endif /* PICO_IDENT_SRC_AT24CM02_H */

0 commit comments

Comments
 (0)