--- 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> { // 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, Box> { 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> { 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> { // 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> { 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> { // 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> { 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> { 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> { 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> { 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> { 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)