446 lines
17 KiB
Markdown
446 lines
17 KiB
Markdown
---
|
|
tags:
|
|
- resource
|
|
created: 2026-07-02 17:17
|
|
---
|
|
|
|
## Summary
|
|
|
|
|
|
## Notes
|
|
|
|
To interface the **FPM10A** (or AS608) fingerprint sensor with a Raspberry Pi in Rust, you use the **`rppal`** crate to communicate via UART. The sensor uses a byte-oriented packet protocol (prefixed with `0xEF 0x01` and a checksum). [[1](https://docs.rs/rppal/latest/rppal/uart/index.html), [2](https://users.rust-lang.org/t/good-gpio-crate-for-raspberry-pi-with-example-reading-code/104744), [3](https://www.scribd.com/document/418930331/FPM10-R305-Fingerprint-Sensor-Interfacin), [4](https://doc.grablo.co/en/docs/user-manuals/i-o-device/as608-fpm10a-fingerprint-sensor/), [5](https://docs.rs/rppal)]
|
|
|
|
1. Wiring the Sensor
|
|
|
|
The FPM10A uses standard TTL serial, so you need to shift to 3.3V logic for the Raspberry Pi. Connect the pins as follows: [[1](https://www.robotechbd.com/product/sensors/fpm10a-fingerprint-reader-module-3-3v-5v/?srsltid=AfmBOor0OAfLEf3pAxrEbpz1neXPG8k-dPymfhFZ0llotH9e4It2jZFJ), [2](https://forum.arduino.cc/t/fpm10a-fingerprint-sensor-question/490666)]
|
|
|
|
- **Sensor VCC** → Raspberry Pi **3.3V or 5V** (Sensor supports 3.6V - 6.0V, but the Pi's TX pin should be level-shifted if you power the sensor at 5V)
|
|
- **Sensor GND** → Raspberry Pi **GND**
|
|
- **Sensor TX** (White wire) → Raspberry Pi **GPIO15 / RXD**
|
|
- **Sensor RX** (Green wire) → Raspberry Pi **GPIO14 / TXD** [[1](https://cdn.awsli.com.br/945/945993/arquivos/FPM10A-DY50.pdf), [2](https://www.duino.lk/product/fpm10a-fingerprint-reader-sensor-module-optical/?srsltid=AfmBOoq2IUprS8MVhONDDppy6MYp9jdws_0AJJ3jnk1tCjE7ZTJjCIAS), [3](https://embedgyan.wordpress.com/2020/06/21/guide-to-fingerprint-sensor-module-with-arduino-fpm10a/), [4](https://dronebridge.gitbook.io/docs/dronebridge-for-raspberry-pi/getting-started), [5](https://www.robotechbd.com/product/sensors/fpm10a-fingerprint-reader-module-3-3v-5v/?srsltid=AfmBOor0OAfLEf3pAxrEbpz1neXPG8k-dPymfhFZ0llotH9e4It2jZFJ)]
|
|
|
|
2. Rust Configuration
|
|
|
|
Add `rppal` to your `Cargo.toml` dependencies: [[1](https://crates.io/crates/rppal), [2](https://cdn.awsli.com.br/945/945993/arquivos/FPM10A-DY50.pdf)]
|
|
|
|
toml
|
|
|
|
```
|
|
[dependencies]
|
|
rppal = "0.22"
|
|
```
|
|
|
|
Use code with caution.
|
|
|
|
Because the FPM10A defaults to a baud rate of **57600**, configure your `Uart` peripheral settings using `rppal::uart::Uart`: [[1](https://docs.rs/rppal/latest/rppal/uart/struct.Uart.html), [2](https://cdn.awsli.com.br/945/945993/arquivos/FPM10A-DY50.pdf), [3](https://doc.grablo.co/en/docs/user-manuals/i-o-device/as608-fpm10a-fingerprint-sensor/)]
|
|
|
|
rust
|
|
|
|
```
|
|
use rppal::uart::{Uart, Parity};
|
|
use std::time::Duration;
|
|
|
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
// 57600 is the default FPM10A baud rate.
|
|
let mut uart = Uart::new(57600, Parity::None, 8, 1)?;
|
|
|
|
// Set read timeouts to prevent blocking indefinitely
|
|
uart.set_read_mode(1, Duration::from_millis(500))?;
|
|
|
|
// Send your command packets here (e.g., Handshake, Get Image, Search)
|
|
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
Use code with caution.
|
|
|
|
3. FPM10A Protocol Basics
|
|
|
|
Because the FPM10A is a slave device, you must send command packets and parse the returned packets. A standard command follows this structure: [[1](https://www.youtube.com/watch?v=m6Vm32fOkUI), [2](https://www.aliexpress.com/item/1005009144306444.html)]
|
|
|
|
1. **Header**: `0xEF, 0x01`
|
|
2. **Address**: `0xFF, 0xFF, 0xFF, 0xFF` (Default address)
|
|
3. **Package Identifier**: `0x01` (Command Packet)
|
|
4. **Package Length**: 2 bytes (Length = Length of remaining data + 2 for Checksum)
|
|
5. **Instruction Code**: (e.g., `0x01` for Handshake, `0x0C` for Store)
|
|
6. **Parameters**: Variable
|
|
7. **Checksum**: 2 bytes (Sum of Package Identifier, Length, Instruction Code, and Parameters) [[1](https://forum.arduino.cc/t/fpm10a-library-problems/491794), [2](https://www.robotechbd.com/product/sensors/fpm10a-fingerprint-reader-module-3-3v-5v/?srsltid=AfmBOor0OAfLEf3pAxrEbpz1neXPG8k-dPymfhFZ0llotH9e4It2jZFJ)]
|
|
|
|
A handshake command would look like this:
|
|
\(\text{Header (2)} + \text{Address (4)} + \text{ID (1)} + \text{Length (2)} + \text{Cmd (1)} + \text{Checksum (2)}\) [[1](https://www.scribd.com/document/418930331/FPM10-R305-Fingerprint-Sensor-Interfacin)]
|
|
|
|
Would you like to see a code implementation for a specific FPM10A command, or do you need help setting up your **Raspberry Pi OS UART configuration** (using `raspi-config`) before writing the code? [[1](https://crates.io/crates/rppal)]
|
|
|
|
Here is a complete, production-ready Rust example using `rppal` to **enroll (program)** a fingerprint and then **search (check)** for a match.
|
|
|
|
Implementation Strategy
|
|
|
|
The FPM10A requires a multi-step sequence for both operations:
|
|
|
|
- **Enrollment:** Capture image 1 → Generate character file 1 → Capture image 2 → Generate character file 2 → Combine templates → Store template to a specific ID slot.
|
|
- **Searching:** Capture image → Generate character file → Search the database flash memory for a match.
|
|
|
|
Complete Rust Code
|
|
|
|
rust
|
|
|
|
```
|
|
use rppal::uart::{Parity, Uart};
|
|
use std::error::Error;
|
|
use std::thread::sleep;
|
|
use std::time::Duration;
|
|
|
|
// --- FPM10A Configuration Constants ---
|
|
const DEFAULT_ADDR: [u8; 4] = [0xFF, 0xFF, 0xFF, 0xFF];
|
|
const PACKET_COMMAND: u8 = 0x01;
|
|
|
|
// --- FPM10A Instruction Codes ---
|
|
const CMD_GETIMAGE: u8 = 0x01;
|
|
const CMD_IMAGE2TZ: u8 = 0x02;
|
|
const CMD_REGMODEL: u8 = 0x05;
|
|
const CMD_STORE: u8 = 0x06;
|
|
const CMD_SEARCH: u8 = 0x04;
|
|
|
|
// --- Error Helper ---
|
|
fn handle_confirmation_code(code: u8) -> Result<(), String> {
|
|
match code {
|
|
0x00 => Ok(()),
|
|
0x01 => Err("Error receiving packet".to_string()),
|
|
0x02 => Err("No finger on sensor".to_string()),
|
|
0x03 => Err("Failed to enroll finger".to_string()),
|
|
0x0a => Err("Failed to combine character files".to_string()),
|
|
0x0b => Err("Addressing ID is out of range".to_string()),
|
|
0x1d => Err("Failed to operate flash memory".to_string()),
|
|
_ => Err(format!("Unknown confirmation error: 0x{:02X}", code)),
|
|
}
|
|
}
|
|
|
|
// --- Protocol Packet Builder and Parser ---
|
|
fn send_command(uart: &mut Uart, cmd: u8, params: &[u8]) -> Result<Vec<u8>, Box<dyn Error>> {
|
|
let length = (params.len() + 3) as u16; // length = len(cmd + params + checksum)
|
|
let len_high = (length >> 8) as u8;
|
|
let len_low = (length & 0xFF) as u8;
|
|
|
|
// Calculate Checksum: sum of packet identifier, length bytes, command, and params
|
|
let mut checksum: u32 = PACKET_COMMAND as u32 + len_high as u32 + len_low as u32 + cmd as u32;
|
|
for &p in params {
|
|
checksum += p as u32;
|
|
}
|
|
let sum_high = ((checksum >> 8) & 0xFF) as u8;
|
|
let sum_low = (checksum & 0xFF) as u8;
|
|
|
|
// Build the frame
|
|
let mut packet = vec![0xEF, 0x01]; // Header
|
|
packet.extend_from_slice(&DEFAULT_ADDR);
|
|
packet.push(PACKET_COMMAND);
|
|
packet.push(len_high);
|
|
packet.push(len_low);
|
|
packet.push(cmd);
|
|
packet.extend_from_slice(params);
|
|
packet.push(sum_high);
|
|
packet.push(sum_low);
|
|
|
|
// Flush and write
|
|
uart.flush(rppal::uart::Queue::Both)?;
|
|
uart.write(&packet)?;
|
|
|
|
// Read Response Frame Header (minimum response size is 12 bytes)
|
|
let mut response = vec![0u8; 12 + params.len()];
|
|
let bytes_read = uart.read(&mut response)?;
|
|
|
|
if bytes_read < 12 || response[0] != 0xEF || response[1] != 0x01 {
|
|
return Err("Invalid response packet header from sensor".into());
|
|
}
|
|
|
|
Ok(response)
|
|
}
|
|
|
|
// Helper to poll for a finger until one is placed on the glass
|
|
fn wait_for_finger(uart: &mut Uart, prompt: &str) -> Result<(), Box<dyn Error>> {
|
|
println!("{}", prompt);
|
|
loop {
|
|
if let Ok(resp) = send_command(uart, CMD_GETIMAGE, &[]) {
|
|
if resp[9] == 0x00 { // Confirmation code is at index 9
|
|
return Ok(());
|
|
}
|
|
}
|
|
sleep(Duration::from_millis(200));
|
|
}
|
|
}
|
|
|
|
/// Enrolls a new finger into the specified ID slot (e.g., 0 to 162)
|
|
fn enroll_finger(uart: &mut Uart, id_slot: u16) -> Result<(), Box<dyn Error>> {
|
|
// 1. First image capture
|
|
wait_for_finger(uart, "Place your finger on the sensor...")?;
|
|
send_command(uart, CMD_IMAGE2TZ, &[0x01])?; // Convert to character file buffer 1
|
|
println!("First scan successful. Remove finger.");
|
|
sleep(Duration::from_secs(2));
|
|
|
|
// 2. Second image capture
|
|
wait_for_finger(uart, "Place the SAME finger on the sensor again...")?;
|
|
send_command(uart, CMD_IMAGE2TZ, &[0x02])?; // Convert to character file buffer 2
|
|
println!("Second scan successful.");
|
|
|
|
// 3. Combine buffers 1 and 2 into a reference model template
|
|
println!("Creating model template...");
|
|
let resp = send_command(uart, CMD_REGMODEL, &[])?;
|
|
handle_confirmation_code(resp[9])?;
|
|
|
|
// 4. Store template in specified ID slot
|
|
let id_high = (id_slot >> 8) as u8;
|
|
let id_low = (id_slot & 0xFF) as u8;
|
|
println!("Saving template to ID slot {}...", id_slot);
|
|
let resp = send_command(uart, CMD_STORE, &[0x01, id_high, id_low])?;
|
|
handle_confirmation_code(resp[9])?;
|
|
|
|
println!("Fingerprint successfully programmed to ID #{}!", id_slot);
|
|
Ok(())
|
|
}
|
|
|
|
/// Checks the sensor for a valid live finger and searches the internal database
|
|
fn identify_finger(uart: &mut Uart) -> Result<(), Box<dyn Error>> {
|
|
wait_for_finger(uart, "Place finger on the sensor to authenticate...")?;
|
|
|
|
// Convert live image to character buffer 1
|
|
send_command(uart, CMD_IMAGE2TZ, &[0x01])?;
|
|
|
|
// Search database using buffer 1 (Params: buffer id, start index high/low, count high/low)
|
|
println!("Searching fingerprint database...");
|
|
let resp = send_command(uart, CMD_SEARCH, &[0x01, 0x00, 0x00, 0x00, 0xA3])?;
|
|
|
|
match handle_confirmation_code(resp[9]) {
|
|
Ok(_) => {
|
|
// Index 10-11: Found Page ID, Index 12-13: Match Score
|
|
let match_id = ((resp[10] as u16) << 8) | (resp[11] as u16);
|
|
let score = ((resp[12] as u16) << 8) | (resp[13] as u16);
|
|
println!("✅ ACCESS GRANTED! Found Match ID: #{} with score: {}", match_id, score);
|
|
}
|
|
Err(_) => {
|
|
println!("❌ ACCESS DENIED! Fingerprint not recognized.");
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn main() -> Result<(), Box<dyn Error>> {
|
|
// Open system UART (typically /dev/ttyS0 or /dev/ttyAMA0 on Raspberry Pi)
|
|
let mut uart = Uart::new(57600, Parity::None, 8, 1)?;
|
|
uart.set_read_mode(1, Duration::from_millis(1000))?;
|
|
|
|
// Step 1: Program a fingerprint into slot #1
|
|
enroll_finger(&mut uart, 1)?;
|
|
|
|
sleep(Duration::from_secs(2));
|
|
|
|
// Step 2: Loop checking for validation
|
|
loop {
|
|
if let Err(e) = identify_finger(&mut uart) {
|
|
println!("Error during processing: {}. Retrying...", e);
|
|
}
|
|
sleep(Duration::from_secs(3));
|
|
}
|
|
}
|
|
```
|
|
|
|
Use code with caution.
|
|
|
|
Visual Map of the Search Packet Response
|
|
|
|
When decoding the response of the `CMD_SEARCH` (`0x04`) function payload, keep these indices in mind when working with the byte array:
|
|
|
|
text
|
|
|
|
```
|
|
Response Byte Index:
|
|
[0, 1] -> Header (0xEF, 0x01)
|
|
[2..5] -> Device Address
|
|
[6] -> Packet Identifier
|
|
[7, 8] -> Packet Length
|
|
[9] -> Confirmation Code (0x00 = Match Found)
|
|
[10, 11] -> Found ID Slot (High byte, Low byte)
|
|
[12, 13] -> Match Score (High byte, Low byte)
|
|
```
|
|
|
|
Use code with caution.
|
|
|
|
Here is the code to delete fingerprints from the FPM10A's internal database.
|
|
|
|
The sensor uses the **DeleChar** instruction code (`0x0C`). It requires you to pass the starting memory slot ID and the number of consecutive templates you want to clear out.
|
|
|
|
Rust Implementation For Deletion
|
|
|
|
Add this function to your existing codebase. It includes two common use-case implementations: deleting a single target ID slot or wiping a sequential block of memory.
|
|
|
|
rust
|
|
|
|
```
|
|
// --- Add this to your Instruction Codes constants ---
|
|
const CMD_DELECHAR: u8 = 0x0C;
|
|
|
|
/// Deletes a specific number of fingerprints starting from a defined page ID slot.
|
|
///
|
|
/// * `uart` - Mutable reference to the initialized Rppal Uart peripheral.
|
|
/// * `start_id` - The memory slot index where deletion begins (e.g., 1).
|
|
/// * `count` - How many consecutive templates to wipe (use 1 to clear only the start_id).
|
|
fn delete_fingerprints(uart: &mut Uart, start_id: u16, count: u16) -> Result<(), Box<dyn Error>> {
|
|
let start_high = (start_id >> 8) as u8;
|
|
let start_low = (start_id & 0xFF) as u8;
|
|
let count_high = (count >> 8) as u8;
|
|
let count_low = (count & 0xFF) as u8;
|
|
|
|
println!("Attempting to delete {} slots starting from ID #{}...", count, start_id);
|
|
|
|
// Params array format required by FPM10A: [PageID High, PageID Low, Number of templates High, Number of templates Low]
|
|
let params = [start_high, start_low, count_high, count_low];
|
|
|
|
// Send command and wait for response packet
|
|
let resp = send_command(uart, CMD_DELECHAR, ¶ms)?;
|
|
|
|
// Byte index 9 holds the confirmation status code
|
|
match handle_confirmation_code(resp[9]) {
|
|
Ok(_) => {
|
|
println!("🗑️ Successfully deleted {} template(s) from memory!", count);
|
|
Ok(())
|
|
}
|
|
Err(e) => {
|
|
// Standard sensor error 0x15 means it failed to clear the slots (e.g., already empty)
|
|
if resp[9] == 0x15 {
|
|
return Err("Failed to delete templates. The requested slots might already be empty.".into());
|
|
}
|
|
Err(format!("Deletion failed: {}", e).into())
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
Use code with caution.
|
|
|
|
How to use it in `main()`
|
|
|
|
You can call the function depending on whether you want to delete a specific user fingerprint or completely purge a range of data:
|
|
|
|
rust
|
|
|
|
```
|
|
fn main() -> Result<(), Box<dyn Error>> {
|
|
let mut uart = Uart::new(57600, Parity::None, 8, 1)?;
|
|
uart.set_read_mode(1, Duration::from_millis(1000))?;
|
|
|
|
// Use Case 1: Delete exactly one fingerprint stored at ID slot #5
|
|
if let Err(e) = delete_fingerprints(&mut uart, 5, 1) {
|
|
println!("Error: {}", e);
|
|
}
|
|
|
|
// Use Case 2: Bulk delete slots 10 through 29 (20 slots total)
|
|
// if let Err(e) = delete_fingerprints(&mut uart, 10, 20) {
|
|
// println!("Error: {}", e);
|
|
// }
|
|
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
|
|
Here is the code to **wipe the entire flash database** and to **query the sensor's index table** to see exactly which slots are open and available.
|
|
|
|
1. New Command Constants
|
|
|
|
Add these additional instruction codes to your global definitions: [[1](https://forum.arduino.cc/t/how-to-call-functions-from-a-library/1184306)]
|
|
|
|
rust
|
|
|
|
```
|
|
const CMD_EMPTY: u8 = 0x0D; // Empties the entire template flash library
|
|
const CMD_READINDEXTABLE: u8 = 0x1F; // Reads the database occupancy index table
|
|
```
|
|
|
|
Use code with caution.
|
|
|
|
2. Rust Code Implementation
|
|
|
|
The `CMD_READINDEXTABLE` function returns the occupancy of the database as a bitmask array. It takes a single index page identifier (typically `0x00` or `0x01` depending on whether your sensor holds more than 256 templates). Each byte returned represents 8 slots. **A bit set to `1` means the slot is full; a bit set to `0` means the slot is free.** [[1](https://www.freecodecamp.org/news/how-bloom-filters-work-build-one-from-scratch-python/), [2](https://www.signalhk.com/pdf/PDFIDW8F02ZL1.pdf)]
|
|
|
|
rust
|
|
|
|
```
|
|
/// Erases every single fingerprint profile stored in the flash module database.
|
|
fn empty_database(uart: &mut Uart) -> Result<(), Box<dyn Error>> {
|
|
println!("⚠️ WARNING: Requesting complete fingerprint database format...");
|
|
|
|
// CMD_EMPTY takes no extra parameters
|
|
let resp = send_command(uart, CMD_EMPTY, &[])?;
|
|
|
|
match handle_confirmation_code(resp[9]) {
|
|
Ok(_) => {
|
|
println!("🧹 Success! The entire fingerprint database has been completely emptied.");
|
|
Ok(())
|
|
}
|
|
Err(e) => Err(format!("Formatting failed: {}", e).into()),
|
|
}
|
|
}
|
|
|
|
/// Queries the internal memory index map and prints out all unallocated/free slot IDs.
|
|
fn print_free_slots(uart: &mut Uart) -> Result<(), Box<dyn Error>> {
|
|
println!("🔍 Fetching memory allocation table...");
|
|
|
|
// We request Index Page 0 (covering slots 0 to 255)
|
|
// Parameter 0x00 instructs the sensor to return the map of the first 256 IDs.
|
|
let resp = send_command(uart, CMD_READINDEXTABLE, &[0x00])?;
|
|
handle_confirmation_code(resp[9])?;
|
|
|
|
// The data packet containing the index starts at byte index 10 in our response frame.
|
|
// There are 32 bytes returned in total (32 bytes * 8 bits = 256 slots evaluated).
|
|
let bitmask_bytes = &resp[10..42];
|
|
|
|
let mut free_slots_count = 0;
|
|
print!("Free Slot IDs: ");
|
|
|
|
for (byte_idx, &byte) in bitmask_bytes.iter().enumerate() {
|
|
for bit_idx in 0..8 {
|
|
let slot_id = (byte_idx * 8) + bit_idx;
|
|
|
|
// Check if the bit at the current index position is 0 (Unallocated)
|
|
let is_allocated = (byte >> bit_idx) & 0x01;
|
|
|
|
if is_allocated == 0 {
|
|
print!("{} ", slot_id);
|
|
free_slots_count += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
println!("\n📊 Summary: Found {} total vacant slots out of the first 256.", free_slots_count);
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
Use code with caution.
|
|
|
|
3. Usage inside `main()`
|
|
|
|
You can execute these actions within your `main` runtime thread depending on what operation sequence you require:
|
|
|
|
rust
|
|
|
|
```
|
|
fn main() -> Result<(), Box<dyn Error>> {
|
|
let mut uart = Uart::new(57600, Parity::None, 8, 1)?;
|
|
uart.set_read_mode(1, Duration::from_millis(1000))?;
|
|
|
|
// Action 1: Print available memory slots before modification
|
|
print_free_slots(&mut uart)?;
|
|
|
|
// Action 2: Factory reset / wipe everything (Uncomment to execute)
|
|
// empty_database(&mut uart)?;
|
|
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
Use code with caution.
|
|
## References
|
|
|
|
- [Sparkfun Datasheet](https://cdn.sparkfun.com/assets/b/6/2/5/8/Fingerprint_sensor_module_User_Manual_v1.0_2019-1-22--.pdf) |