ESPHome 2026.3.0
Loading...
Searching...
No Matches
rc522.cpp
Go to the documentation of this file.
1#include "rc522.h"
3#include "esphome/core/log.h"
4
5// Based on:
6// - https://github.com/miguelbalboa/rfid
7
8namespace esphome {
9namespace rc522 {
10
11static const uint8_t WAIT_I_RQ = 0x30; // RxIRq and IdleIRq
12
13static const char *const TAG = "rc522";
14
15// Max UID size for RFID tags (4, 7, or 10 bytes)
16static constexpr size_t RC522_MAX_UID_SIZE = 10;
17
18static const uint8_t RESET_COUNT = 5;
19
21 state_ = STATE_SETUP;
22 // Pull device out of power down / reset state.
23
24 // First set the resetPowerDownPin as digital input, to check the MFRC522 power down mode.
25 if (reset_pin_ != nullptr) {
27
28 if (!reset_pin_->digital_read()) { // The MFRC522 chip is in power down mode.
29 ESP_LOGV(TAG, "Power down mode detected. Hard resetting");
30 reset_pin_->pin_mode(gpio::FLAG_OUTPUT); // Now set the resetPowerDownPin as digital output.
31 reset_pin_->digital_write(false); // Make sure we have a clean LOW state.
32 delayMicroseconds(2); // 8.8.1 Reset timing requirements says about 100ns. Let us be generous: 2μsl
33 reset_pin_->digital_write(true); // Exit power down mode. This triggers a hard reset.
34 // Section 8.8.2 in the datasheet says the oscillator start-up time is the start up time of the crystal + 37,74μs.
35 // Let us be generous: 50ms.
37 return;
38 }
39 }
40
41 // Setup a soft reset
42 reset_count_ = RESET_COUNT;
44}
45
47 // Per original code, wait 50 ms
48 if (millis() - reset_timeout_ < 50)
49 return;
50
51 // Reset baud rates
52 ESP_LOGV(TAG, "Initialize");
53
56 // Reset ModWidthReg
58
59 // When communicating with a PICC we need a timeout if something goes wrong.
60 // f_timer = 13.56 MHz / (2*TPreScaler+1) where TPreScaler = [TPrescaler_Hi:TPrescaler_Lo].
61 // TPrescaler_Hi are the four low bits in TModeReg. TPrescaler_Lo is TPrescalerReg.
62 pcd_write_register(T_MODE_REG, 0x80); // TAuto=1; timer starts automatically at the end of the transmission in all
63 // communication modes at all speeds
64
65 // TPreScaler = TModeReg[3..0]:TPrescalerReg, ie 0x0A9 = 169 => f_timer=40kHz, ie a timer period of 25μs.
67 pcd_write_register(T_RELOAD_REG_H, 0x03); // Reload timer with 0x3E8 = 1000, ie 25ms before timeout.
69
70 // Default 0x00. Force a 100 % ASK modulation independent of the ModGsPReg register setting
72 pcd_write_register(MODE_REG, 0x3D); // Default 0x3F. Set the preset value for the CRC coprocessor for the CalcCRC
73 // command to 0x6363 (ISO 14443-3 part 6.2.4)
74
75 state_ = STATE_INIT;
76}
77
79 ESP_LOGCONFIG(TAG, "RC522:");
80 switch (this->error_code_) {
81 case NONE:
82 break;
83 case RESET_FAILED:
84 ESP_LOGE(TAG, "Reset command failed");
85 break;
86 }
87
88 LOG_PIN(" RESET Pin: ", this->reset_pin_);
89
90 LOG_UPDATE_INTERVAL(this);
91
92 for (auto *child : this->binary_sensors_) {
93 LOG_BINARY_SENSOR(" ", "Tag", child);
94 }
95}
96
98 if (state_ == STATE_INIT) {
100 pcd_clear_register_bit_mask_(COLL_REG, 0x80); // ValuesAfterColl=1 => Bits received after collision are cleared.
103 state_ = STATE_PICC_REQUEST_A;
104 } else {
105 ESP_LOGW(TAG, "Communication takes longer than update interval: %d", state_);
106 }
107}
108
110 // First check reset is needed
111 if (reset_count_ > 0) {
112 pcd_reset_();
113 return;
114 }
115 if (state_ == STATE_SETUP) {
116 initialize_();
117 return;
118 }
119
120 StatusCode status = STATUS_ERROR; // For lint passing. TODO: refactor this
121 if (awaiting_comm_) {
122 if (state_ == STATE_SELECT_SERIAL_DONE) {
123 status = await_crc_();
124 } else {
126 }
127
128 if (status == STATUS_WAITING) {
129 return;
130 }
131 awaiting_comm_ = false;
132 ESP_LOGV(TAG, "finished communication status: %d, state: %d", status, state_);
133 }
134
135 switch (state_) {
137 if (status == STATUS_TIMEOUT) { // no tag present
138 for (auto *obj : this->binary_sensors_)
139 obj->on_scan_end(); // reset the binary sensors
140 ESP_LOGV(TAG, "CMD_REQA -> TIMEOUT (no tag present) %d", status);
141 state_ = STATE_DONE;
142 } else if (status != STATUS_OK) {
143 ESP_LOGW(TAG, "CMD_REQA -> Not OK %d", status);
144 state_ = STATE_DONE;
145 } else if (back_length_ != 2) { // || *valid_bits_ != 0) { // ATQA must be exactly 16 bits.
146 ESP_LOGW(TAG, "CMD_REQA -> OK, but unexpected back_length_ of %d", back_length_);
147 state_ = STATE_DONE;
148 } else {
149 state_ = STATE_READ_SERIAL;
150 }
151 if (state_ == STATE_DONE) {
152 // Don't wait another loop cycle
154 }
155 break;
156 }
157 case STATE_READ_SERIAL: {
158 ESP_LOGV(TAG, "STATE_READ_SERIAL (%d)", status);
159 switch (uid_idx_) {
160 case 0:
162 break;
163 case 3:
165 break;
166 case 6:
168 break;
169 default:
170 ESP_LOGE(TAG, "uid_idx_ invalid, uid_idx_ = %d", uid_idx_);
171 state_ = STATE_DONE;
172 return;
173 }
174 buffer_[1] = 32;
176 state_ = STATE_SELECT_SERIAL;
177 break;
178 }
179 case STATE_SELECT_SERIAL: {
180 buffer_[1] = 0x70; // select
181 // todo: set CRC
182 buffer_[6] = buffer_[2] ^ buffer_[3] ^ buffer_[4] ^ buffer_[5];
185 break;
186 }
188 send_len_ = 6;
190 state_ = STATE_READ_SERIAL_DONE;
191 break;
192 }
194 if (status != STATUS_OK || back_length_ != 3) {
195 if (status == STATUS_TIMEOUT) {
196 ESP_LOGV(TAG, "STATE_READ_SERIAL_DONE -> TIMEOUT (no tag present) %d", status);
197 } else {
198 char hex_buf[format_hex_pretty_size(RC522_MAX_UID_SIZE)];
199 ESP_LOGW(TAG, "Unexpected response. Read status is %d. Read bytes: %d (%s)", status, back_length_,
201 }
202
203 state_ = STATE_DONE;
204 uid_idx_ = 0;
205
207 return;
208 }
209
210 // copy the uid
211 bool cascade = buffer_[2] == PICC_CMD_CT; // todo: should be determined based on select response (buffer[6])
212 for (uint8_t i = 2 + cascade; i < 6; i++)
214 ESP_LOGVV(TAG, "copied uid to idx %d last byte is 0x%x, cascade is %d", uid_idx_, uid_buffer_[uid_idx_ - 1],
215 cascade);
216
217 if (cascade) { // there is more bytes in the UID
218 state_ = STATE_READ_SERIAL;
219 return;
220 }
221
222 std::vector<uint8_t> rfid_uid(std::begin(uid_buffer_), std::begin(uid_buffer_) + uid_idx_);
223 uid_idx_ = 0;
224 // ESP_LOGD(TAG, "Processing '%s'", format_hex_pretty(rfid_uid, '-', false).c_str()); // NOLINT
226 state_ = STATE_INIT; // scan again on next update
227 bool report = true;
228
229 for (auto *tag : this->binary_sensors_) {
230 if (tag->process(rfid_uid)) {
231 report = false;
232 }
233 }
234
235 if (this->current_uid_ == rfid_uid) {
236 return;
237 }
238
239 this->current_uid_ = rfid_uid;
240
241 for (auto *trigger : this->triggers_ontag_)
242 trigger->process(rfid_uid);
243
244 if (report) {
245 char uid_buf[format_hex_pretty_size(RC522_MAX_UID_SIZE)];
246 ESP_LOGD(TAG, "Found new tag '%s'", format_hex_pretty_to(uid_buf, rfid_uid.data(), rfid_uid.size(), '-'));
247 }
248 break;
249 }
250 case STATE_DONE: {
251 if (!this->current_uid_.empty()) {
252#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
253 char uid_buf[format_hex_pretty_size(RC522_MAX_UID_SIZE)];
254 ESP_LOGV(TAG, "Tag '%s' removed",
255 format_hex_pretty_to(uid_buf, this->current_uid_.data(), this->current_uid_.size(), '-'));
256#endif
257 for (auto *trigger : this->triggers_ontagremoved_)
258 trigger->process(this->current_uid_);
259 }
260 this->current_uid_ = {};
261 state_ = STATE_INIT;
262 break;
263 }
264 default:
265 break;
266 }
267} // namespace rc522
268
273 // The datasheet does not mention how long the SoftRest command takes to complete.
274 // But the MFRC522 might have been in soft power-down mode (triggered by bit 4 of CommandReg)
275 // Section 8.8.2 in the datasheet says the oscillator start-up time is the start up time of the crystal + 37,74μs.
276 // Let us be generous: 50ms.
277
278 if (millis() - reset_timeout_ < 50)
279 return;
280
281 if (reset_count_ == RESET_COUNT) {
282 ESP_LOGI(TAG, "Soft reset");
283 // Issue the SoftReset command.
285 }
286
287 // Expect the PowerDown bit in CommandReg to be cleared (max 3x50ms)
288 if ((pcd_read_register(COMMAND_REG) & (1 << 4)) == 0) {
289 reset_count_ = 0;
290 ESP_LOGI(TAG, "Device online");
291 // Wait for initialize
293 return;
294 }
295
296 if (--reset_count_ == 0) {
297 ESP_LOGE(TAG, "Unable to reset");
298 this->error_code_ = RESET_FAILED;
299 mark_failed();
300 }
301}
302
308 uint8_t value = pcd_read_register(TX_CONTROL_REG);
309 if ((value & 0x03) != 0x03) {
310 pcd_write_register(TX_CONTROL_REG, value | 0x03);
311 }
312}
313
318 uint8_t value = pcd_read_register(TX_CONTROL_REG);
319 if ((value & 0x03) != 0x00) {
320 pcd_write_register(TX_CONTROL_REG, value & ~0x03);
321 }
322}
323
328 uint8_t mask
329) {
330 uint8_t tmp = pcd_read_register(reg);
331 pcd_write_register(reg, tmp | mask); // set bit mask
332}
333
338 uint8_t mask
339) {
340 uint8_t tmp = pcd_read_register(reg);
341 pcd_write_register(reg, tmp & (~mask)); // clear bit mask
342}
343
350void RC522::pcd_transceive_data_(uint8_t send_len) {
351#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
352 char hex_buf[format_hex_pretty_size(RC522_MAX_UID_SIZE)];
353 ESP_LOGV(TAG, "PCD TRANSCEIVE: RX: %s", format_hex_pretty_to(hex_buf, buffer_, send_len, '-'));
354#endif
355 delayMicroseconds(1000); // we need 1 ms delay between antenna on and those communication commands
356 send_len_ = send_len;
357 // Prepare values for BitFramingReg
358 // For REQA and WUPA we need the short frame format - transmit only 7 bits of the last (and only)
359 // uint8_t. TxLastBits = BitFramingReg[2..0]
360 uint8_t bit_framing = (buffer_[0] == PICC_CMD_REQA) ? 7 : 0;
361
362 pcd_write_register(COMMAND_REG, PCD_IDLE); // Stop any active command.
363 pcd_write_register(COM_IRQ_REG, 0x7F); // Clear all seven interrupt request bits
364 pcd_write_register(FIFO_LEVEL_REG, 0x80); // FlushBuffer = 1, FIFO initialization
365 pcd_write_register(FIFO_DATA_REG, send_len_, buffer_); // Write sendData to the FIFO
366 pcd_write_register(BIT_FRAMING_REG, bit_framing); // Bit adjustments
367 pcd_write_register(COMMAND_REG, PCD_TRANSCEIVE); // Execute the command
368 pcd_set_register_bit_mask_(BIT_FRAMING_REG, 0x80); // StartSend=1, transmission of data starts
369 awaiting_comm_ = true;
371}
372
374 if (millis() - awaiting_comm_time_ < 2) // wait at least 2 ms
375 return STATUS_WAITING;
376 uint8_t n = pcd_read_register(
377 COM_IRQ_REG); // ComIrqReg[7..0] bits are: Set1 TxIRq RxIRq IdleIRq HiAlertIRq LoAlertIRq ErrIRq TimerIRq
378 if (n & 0x01) { // Timer interrupt - nothing received in 25ms
379 back_length_ = 0;
380 error_counter_ = 0; // reset the error counter
381 return STATUS_TIMEOUT;
382 }
383 if (!(n & WAIT_I_RQ)) { // None of the interrupts that signal success has been set.
384 // Wait for the command to complete.
385 if (millis() - awaiting_comm_time_ < 40)
386 return STATUS_WAITING;
387 back_length_ = 0;
388 ESP_LOGW(TAG, "Communication with the MFRC522 might be down, reset in %d",
389 10 - error_counter_); // todo: trigger reset?
390 if (error_counter_++ >= 10) {
391 setup();
392 error_counter_ = 0; // reset the error counter
393 }
394
395 return STATUS_TIMEOUT;
396 }
397 // Stop now if any errors except collisions were detected.
398 uint8_t error_reg_value = pcd_read_register(
399 ERROR_REG); // ErrorReg[7..0] bits are: WrErr TempErr reserved BufferOvfl CollErr CRCErr ParityErr ProtocolErr
400 if (error_reg_value & 0x13) { // BufferOvfl ParityErr ProtocolErr
401 return STATUS_ERROR;
402 }
403 error_counter_ = 0; // reset the error counter
404
405 n = pcd_read_register(FIFO_LEVEL_REG); // Number of uint8_ts in the FIFO
406 if (n > sizeof(buffer_))
407 return STATUS_NO_ROOM;
408 if (n > sizeof(buffer_) - send_len_)
409 send_len_ = sizeof(buffer_) - n; // simply overwrite the sent values
410 back_length_ = n; // Number of uint8_ts returned
411 pcd_read_register(FIFO_DATA_REG, n, buffer_ + send_len_, rx_align_); // Get received data from FIFO
412 uint8_t valid_bits_local =
413 pcd_read_register(CONTROL_REG) & 0x07; // RxLastBits[2:0] indicates the number of valid bits in the last
414 // received uint8_t. If this value is 000b, the whole uint8_t is valid.
415
416 // Tell about collisions
417 if (error_reg_value & 0x08) { // CollErr
418 ESP_LOGW(TAG, "collision error, received %d bytes + %d bits (but anticollision not implemented)",
419 back_length_ - (valid_bits_local > 0), valid_bits_local);
420 return STATUS_COLLISION;
421 }
422 // Tell about collisions
423 if (valid_bits_local) {
424 ESP_LOGW(TAG, "only %d valid bits received, tag distance to high? Error code is 0x%x", valid_bits_local,
425 error_reg_value); // TODO: is this always due to collissions?
426 return STATUS_ERROR;
427 }
428#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
429 char hex_buf[format_hex_pretty_size(RC522_MAX_UID_SIZE)];
430 ESP_LOGV(TAG, "received %d bytes: %s", back_length_,
432#endif
433
434 return STATUS_OK;
435}
436
443void RC522::pcd_calculate_crc_(uint8_t *data,
444 uint8_t length
445) {
446 ESP_LOGVV(TAG, "pcd_calculate_crc_(..., %d, ...)", length);
447 pcd_write_register(COMMAND_REG, PCD_IDLE); // Stop any active command.
448 pcd_write_register(DIV_IRQ_REG, 0x04); // Clear the CRCIRq interrupt request bit
449 pcd_write_register(FIFO_LEVEL_REG, 0x80); // FlushBuffer = 1, FIFO initialization
450 pcd_write_register(FIFO_DATA_REG, length, data); // Write data to the FIFO
451 pcd_write_register(COMMAND_REG, PCD_CALC_CRC); // Start the calculation
452
453 awaiting_comm_ = true;
455}
456
458 if (millis() - awaiting_comm_time_ < 2) // wait at least 2 ms
459 return STATUS_WAITING;
460
461 // DivIrqReg[7..0] bits are: Set2 reserved reserved MfinActIRq reserved CRCIRq reserved reserved
462 uint8_t n = pcd_read_register(DIV_IRQ_REG);
463 if (n & 0x04) { // CRCIRq bit set - calculation done
464 pcd_write_register(COMMAND_REG, PCD_IDLE); // Stop calculating CRC for new content in the FIFO.
465 // Transfer the result from the registers to the result buffer
468
469 ESP_LOGVV(TAG, "pcd_calculate_crc_() STATUS_OK");
470 return STATUS_OK;
471 }
472 if (millis() - awaiting_comm_time_ < 89)
473 return STATUS_WAITING;
474
475 ESP_LOGD(TAG, "pcd_calculate_crc_() TIMEOUT");
476 // 89ms passed and nothing happened. Communication with the MFRC522 might be down.
477 return STATUS_TIMEOUT;
478}
479
480bool RC522BinarySensor::process(std::vector<uint8_t> &data) {
481 bool result = true;
482 if (data.size() != this->uid_.size()) {
483 result = false;
484 } else {
485 for (size_t i = 0; i < data.size(); i++) {
486 if (data[i] != this->uid_[i]) {
487 result = false;
488 break;
489 }
490 }
491 }
492 this->publish_state(result);
493 this->found_ = result;
494 return result;
495}
496void RC522Trigger::process(std::vector<uint8_t> &data) {
497 char uid_buf[format_hex_pretty_size(RC522_MAX_UID_SIZE)];
498 this->trigger(format_hex_pretty_to(uid_buf, data.data(), data.size(), '-'));
499}
500
501} // namespace rc522
502} // namespace esphome
uint8_t status
Definition bl0942.h:8
void mark_failed()
Mark this component as failed.
virtual void pin_mode(gpio::Flags flags)=0
virtual void digital_write(bool value)=0
virtual bool digital_read()=0
void trigger(const Ts &...x)
Definition automation.h:325
void publish_state(bool new_state)
Publish a new state to the front-end.
bool process(std::vector< uint8_t > &data)
Definition rc522.cpp:480
std::vector< uint8_t > uid_
Definition rc522.h:269
std::vector< RC522Trigger * > triggers_ontagremoved_
Definition rc522.h:246
void dump_config() override
Definition rc522.cpp:78
virtual uint8_t pcd_read_register(PcdRegister reg)=0
GPIOPin * reset_pin_
Definition rc522.h:241
enum esphome::rc522::RC522::RC522Error NONE
void pcd_transceive_data_(uint8_t send_len)
Transfers data to the MFRC522 FIFO, executes a command, waits for completion and transfers data back ...
Definition rc522.cpp:350
std::vector< RC522Trigger * > triggers_ontag_
Definition rc522.h:245
void pcd_reset_()
Performs a soft reset on the MFRC522 chip and waits for it to be ready again.
Definition rc522.cpp:272
uint32_t awaiting_comm_time_
Definition rc522.h:227
std::vector< RC522BinarySensor * > binary_sensors_
Definition rc522.h:244
void pcd_antenna_off_()
Turns the antenna off by disabling pins TX1 and TX2.
Definition rc522.cpp:317
uint8_t back_length_
In: Max number of uint8_ts to write to *backData. Out: The number of uint8_ts returned.
Definition rc522.h:234
void update() override
Definition rc522.cpp:97
StatusCode await_crc_()
Definition rc522.cpp:457
uint8_t reset_count_
Definition rc522.h:242
uint32_t reset_timeout_
Definition rc522.h:243
void setup() override
Definition rc522.cpp:20
uint8_t error_counter_
Definition rc522.h:237
StatusCode await_transceive_()
Definition rc522.cpp:373
void pcd_antenna_on_()
Turns the antenna on by enabling pins TX1 and TX2.
Definition rc522.cpp:307
void pcd_set_register_bit_mask_(PcdRegister reg, uint8_t mask)
Sets the bits given in mask in register reg.
Definition rc522.cpp:327
void pcd_calculate_crc_(uint8_t *data, uint8_t length)
Use the CRC coprocessor in the MFRC522 to calculate a CRC_A.
Definition rc522.cpp:443
std::vector< uint8_t > current_uid_
Definition rc522.h:247
void pcd_clear_register_bit_mask_(PcdRegister reg, uint8_t mask)
Clears the bits given in mask from register reg.
Definition rc522.cpp:337
uint8_t uid_buffer_[10]
Definition rc522.h:235
uint8_t buffer_[9]
buffer for communication, the first bits [0..back_idx-1] are for tx , [back_idx..back_idx+back_len] f...
Definition rc522.h:231
void loop() override
Definition rc522.cpp:109
virtual void pcd_write_register(PcdRegister reg, uint8_t value)=0
void process(std::vector< uint8_t > &data)
Definition rc522.cpp:496
@ FLAG_OUTPUT
Definition gpio.h:28
@ FLAG_INPUT
Definition gpio.h:27
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
const char * tag
Definition log.h:74
void IRAM_ATTR HOT delayMicroseconds(uint32_t us)
Definition core.cpp:30
char * format_hex_pretty_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator)
Format byte array as uppercase hex to buffer (base implementation).
Definition helpers.cpp:353
constexpr size_t format_hex_pretty_size(size_t byte_count)
Calculate buffer size needed for format_hex_pretty_to with separator: "XX:XX:...:XX\0".
Definition helpers.h:1200
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:26
uint16_t length
Definition tt21100.cpp:0