Чтение данных RFID-метки на Raspberry Pi Pico с embedded Rust
В этой главе мы выходим за рамки чтения UID и начинаем читать данные, которые хранятся на RFID-метке. Мы прочитаем все блоки первого сектора, то есть сектора 0.
Как уже говорилось, читать или записывать данные на RFID-метку напрямую нельзя. Прежде чем обращаться к блоку, нужно пройти аутентификацию с сектором, в котором находится этот блок. Когда аутентификация прошла успешно, считывателю разрешается читать или записывать блоки этого сектора.
Выберите способ вывода
В этой главе инструкции и код немного отличаются в зависимости от того, как вы выводите результаты. Выберите подходящую вкладку в зависимости от того, используете ли вы отладочный зонд или USB Serial.
В этой схеме для вывода данных блоков мы используем defmt через RTT.
Проект из шаблона
Начнём с создания нового проекта из шаблона.
cargo generate --git https://github.com/ImplFerris/pico2-template.git --tag v0.3.2
Когда появится запрос, введите имя проекта, например «read-blocks», и выберите «Embassy» в качестве HAL. Обязательно включите «defmt» для логирования.
Требуемые дополнительные крейты
Обновите Cargo.toml и добавьте необходимые крейты вместе с существующими зависимостями.
mfrc522 = "0.8.0"
embedded-hal-bus = "0.3.0"
heapless = { version = "0.9.2", features = ["defmt"] }
Дополнительные импорты
#![allow(unused)]
fn main() {
// Для SPI
use embassy_rp::spi::Spi;
use embassy_rp::{self as hal, spi};
use embassy_time::Delay;
use embedded_hal_bus::spi::ExclusiveDevice;
// Для вывода CS
use embassy_rp::gpio::{Level, Output};
// Драйвер для MFRC522
use mfrc522::{Mfrc522, comm::blocking::spi::SpiInterface};
// Для подготовки буфера с данными перед логированием
use core::fmt::Write;
use heapless::String;
}
Повторное использование существующей настройки
Оставшаяся инициализация SPI и RFID-считывателя такая же, как в предыдущей главе. Её можно повторно использовать здесь без изменений. В этой главе мы только добавляем несколько вспомогательных функций и изменяем главный цикл, чтобы читать блоки и выводить их содержимое.
Аутентификация
Большинство меток MIFARE Classic поставляются с ключом сектора по умолчанию. Самый распространённый ключ по умолчанию — шесть байтов 0xFF. Если аутентификация не проходит, возможно, стоит посмотреть документацию метки или попробовать другой ключ.
Для аутентификации нужны три вещи:
- UID метки, который мы уже получаем с помощью шагов REQA и select
- Абсолютный номер блока, для которого проводится аутентификация
- Ключ, которым выполняется аутентификация; в этом примере он задан прямо в коде
Вспомогательная функция для вывода байтов в виде hex-дампа
Эта функция преобразует сырые байты в шестнадцатеричный формат и выводит их.
#![allow(unused)]
fn main() {
fn print_hex(data: &[u8]) {
let mut buff: String<64> = String::new();
for &d in data.iter() {
write!(buff, "{:02x} ", d).expect("failed to write byte into buffer");
}
defmt::println!("{}", buff);
}
}
Чтение блока
Когда аутентификация с сектором прошла успешно, считывателю разрешается обращаться к блокам, принадлежащим этому сектору. С этого момента считыватель может отправлять команды чтения для этих блоков.
Каждый блок метки MIFARE Classic хранит ровно 16 байтов данных. Функция mf_read из крейта mfrc522 читает один блок за раз и при успехе возвращает эти 16 байтов. Если чтение не удалось, возвращается ошибка.
В этой главе мы сосредоточимся на секторе 0. В секторе 0 четыре блока. Аутентификация выполняется один раз для сектора, а затем все четыре блока читаются по порядку. Пока мы остаёмся в одном секторе, аутентификацию повторять не нужно.
#![allow(unused)]
fn main() {
fn read_sector<E, COMM>(
uid: &mfrc522::Uid,
sector: u8,
rfid: &mut Mfrc522<COMM, mfrc522::Initialized>,
) -> Result<(), &'static str>
where
COMM: mfrc522::comm::Interface<Error = E>,
{
const AUTH_KEY: [u8; 6] = [0xFF; 6];
let block_offset = sector * 4;
rfid.mf_authenticate(uid, block_offset, &AUTH_KEY)
.map_err(|_| "Auth failed")?;
for abs_block in block_offset..block_offset + 4 {
let data = rfid.mf_read(abs_block).map_err(|_| "Read failed")?;
print_hex(&data);
}
Ok(())
}
}
При обращении к памяти метки нужно указывать абсолютные номера блоков. Метка рассматривает все блоки как часть одной непрерывной последовательности, а не использует нумерацию внутри сектора. Например, сектор 0 использует блоки 0–3, сектор 1 — блоки 4–7, а сектор 2 — блоки 8–11.
Чтобы превратить номер сектора в правильный номер блока, вычисляем смещение. Поскольку в каждом секторе четыре блока, умножение номера сектора на 4 даёт первый блок этого сектора. Это значение сохраняется в block_offset и затем используется как начальная точка для чтения всех блоков выбранного сектора.
Для первого сектора, то есть сектора 0, вычисление простое: смещение равно 0 × 4, что даёт блок 0. Это значит, что сектор 0 начинается с блока 0 и включает блоки 0, 1, 2 и 3. Именно эти блоки мы читаем, когда проходим аутентификацию и обращаемся к первому сектору.
Главный цикл
Главный цикл работает похоже на то, что мы рассматривали в предыдущей главе. После выбора метки переходим к чтению её блоков. Когда данные блоков прочитаны, цикл отправляет команды HLTA и stop_crypto1, чтобы перевести карту в состояние HALT.
#![allow(unused)]
fn main() {
loop {
if let Ok(atqa) = rfid.reqa() {
if let Ok(uid) = rfid.select(&atqa) {
if let Err(e) = read_sector(&uid, 0, &mut rfid) {
defmt::error!("Error reading sector: {:?}", e);
}
let _ = rfid.hlta();
let _ = rfid.stop_crypto1();
Timer::after_millis(100).await;
}
}
Timer::after_millis(100).await;
}
}
Клонируйте существующий проект
Можно клонировать проект, который я создал, и перейти в папку read-blocks.
git clone https://github.com/ImplFerris/pico2-embassy-projects
cd pico2-embassy-projects/rfid/read-blocks/
Как запустить?
Используйте cargo embed, чтобы прошить и запустить программу: шаблон уже настроен на RTT и defmt, и вывод появляется в терминале после завершения прошивки.
cargo embed --release
Чтение данных блока
Поднесите RFID-метку к считывателю, и системная консоль покажет байты данных, прочитанные со всех блоков первого сектора (сектора 0).
Версия на rp-hal
Если вас интересует версия этого примера на rp-hal, её можно найти в репозитории ниже. Клонируйте репозиторий и перейдите в проект read-blocks:
git clone https://github.com/ImplFerris/pico2-rp-projects
cd pico2-rp-projects/rfid/read-blocks/