Home STM32 STM32 HAL How to Interface R307 Fingerprint Module with STM32 (UART Communication Tutorial)

Interface R307 Fingerprint Module with STM32 for Biometric Authentication

The R307 fingerprint sensor is a widely used biometric module for secure authentication in embedded systems. Today we will learn how to interface the R307 with STM32 using UART communication. This setup allows the microcontroller to enroll, store, and verify fingerprints with high accuracy. Combining STM32 with the R307 sensor makes it possible to build practical applications like smart door locks, attendance systems, and identity verification devices, where speed, security, and reliability are essential.

In this tutorial, we will learn how to interface the R307s Fingerprint Sensor Module with an STM32 microcontroller. The sensor communicates through a serial interface, so we will make use of the UART peripheral of STM32 for this purpose.

We will go through the complete process of enrolling a fingerprint into the module and then demonstrate how to verify the fingerprint against the stored data.

To better understand the steps, I’ve also created a detailed walkthrough video. You can follow along with the explanations below while watching the video here:

R307 Fingerprint Module Overview

The R307 Fingerprint Sensor Module is a widely used biometric module designed for easy integration with microcontrollers and embedded systems. It provides a reliable way to capture, store, and match fingerprints for authentication purposes. The module uses UART communication, making it simple to connect with controllers like STM32, Arduino, or ESP32.

R307 Fingerprint Sensor

Key Features

  • UART communication interface for easy integration with microcontrollers
  • Supports both 1:1 (verification) and 1:N (identification) matching modes
  • Onboard storage for fingerprint templates, reducing the need for external memory
  • Compact design with built-in image processing and matching algorithms

Technical Specifications

  • Operating Voltage: 3.6V to 6.0V (typically 3.3V or 5V compatible)
  • Working Current: ~50 mA (typical), with low idle current
  • Resolution: 500 dpi for accurate fingerprint capture
  • Response Time: Less than 1 second for fingerprint recognition
  • Template Capacity: Can store up to 1000 fingerprints (depending on the version)

Common Applications

  • Attendance systems for schools, offices, and organizations
  • Access control in secure areas, smart locks, and door entry systems
  • Embedded IoT security for devices requiring biometric authentication

Hardware Requirement

I am going to use the STM32F446 Dev board from WeAct Studio along with the R307s Fingerprint Sensor Module for this project. Below are the Hardware required for the project.

Wiring R307 Fingerprint Module with STM32

Before we begin programming, we first need to establish the hardware connections between the R307 Fingerprint Sensor and the STM32 microcontroller. Since the R307 communicates using UART protocol, we will connect its TX and RX pins to the corresponding UART pins on STM32. Additionally, we will power the module with a 5V supply and use LED indicators to show the fingerprint status.

R307 connection with STM32

The wiring connections are as follows:

R307 to STM32 Connections

  • Pin 1 (VCC)5V on STM32 (Red wire)
  • Pin 2 (GND)GND on STM32 (Black wire)
  • Pin 3 (TX – Data Output)PA1 (UART4_RX) on STM32 (Yellow wire)
  • Pin 4 (RX – Data Input)PA0 (UART4_TX) on STM32 (Green wire)
  • Pin 5 (Touch/Finger Detect) → Not connected
  • Pin 6 (Wakeup/LED Control) → Not connected

Additional Components

  • Red LED connected to pin PC1 to indicate finger mismatch
  • Green LED connected to pin PC0 to indicate finger match
  • Both LEDs are connected with current-limiting resistors

FTDI Module

It is used to view the user interactive logs on the serial console. Since we only need to send the data from STM32 to FTDI, only the pin PA10 (UART1_TX) is connected to the RX pin of the module.

STM32CubeMX Configuration

Let’s use STM32CubeMX to configure the UART peripheral for the fingerprint sensor and set up GPIO pins for the LEDs.

UART4 (R307) Configuration

Below is the image showing the UART4 configuration for R307 fingerprint module.

STM32 UART4 Configuration

The UART4 is configured in the Asynchronous mode. The Baud rate should be set to 57600 Bits/s with 8 Bits of word length, No parity and 1 Stop bit. This is the requirement for the fingerprint sensor to work.

The pins PA0 and PA1 are configured as the UART4_TX and UART4_RX pins respectively.These pins are connected to R307 RX and TX pins as mentioned in the wiring diagram.

UART1 (FTDI) Configuration

As I mentioned earlier, the user interactive logs will be sent through UART1 to the FT232, which will then be displayed on the serial console. The image below shows the UART1 configuration.

STM32 UART4 Configuration

The UART1 is configured in the Asynchronous mode. The Baud rate is set to 115200 Bits/s with 8 Bits of word length, No parity and 1 Stop bit. We can configure this however we want, but this is an optimal configuration.

The pins PA9 and PA10 are configured as the UART1_TX and UART1_RX pins respectively. Since we only need to transmit data to the FTDI, PA9 (UART1_TX) is connected to FTDI RX as mentioned in the wiring diagram.

LED Configuration

We will use the LEDs to light up when the finger is matched with the database or it does not match. The image below shows the pin configuration for the LEDs.

STM32 LED Pin configuration

As mentioned in the wiring diagram the RED LED is connected to pin PC1 and GREEN LED is connected to pin PC0. Both the pins are configured as GPIO Output, which we will control in the code itself.

Writing Code to Communicate with R307

Before writing the main code, we will first learn the command structure, how to send a command, and how to handle the response.

Understanding the Command and Response Packets

The image below shows a general command/data/response packet used by the R307.

R307 Data command format packet

The description for the packet is shown in the table below.

FieldSize (Bytes)Description
Header2Fixed value 0xEF01 indicating start of packet
Address4Module address (default: 0xFFFFFFFF)
Package ID1Identifies type of packet:
0x01 → Command
0x02 → Data
0x07 → Acknowledge
0x08 → End of data
Package Length2Number of bytes in Package Content + 2 (checksum itself is excluded)
Package ContentN (varies)Actual instruction, parameters, or returned data
Checksum2Sum of Package ID + Length + Content

For example, if we want to send the command to verify the password, the command and response packets will be as shown in the images below.

R307 command packet

The command packet for verifying module’s password is as follows:

  • Header (2 bytes): Always 0xEF01, start of packet.
  • Module Address (4 bytes): Default is 0xFFFFFFFF.
  • Package Identifier (1 byte): 0x01 → Command packet.
  • Package Length (2 bytes): 1B Instruction + 4B Password + 2B Checksum.
  • Instruction Code (1 byte): 0x13 → Verify Password command.
  • Password (4 bytes): The password we send for verification (default: 0x00000000).
  • Checksum (2 bytes): Sum of Package Identifier + Length + Content.

Example: EF01 FFFFFFFF 01 0007 13 00000000 XXXX

The Response packet sent by the R307 for this command is as follows:

  • Header (2 bytes): 0xEF01.
  • Module Address (4 bytes): Same as sent.
  • Package Identifier (1 byte): 0x07 → Acknowledge packet.
  • Package Length (2 bytes): 1B Confirmation Code + 2B Checksum.
  • Confirmation Code (1 byte): Shows the result.
    • 00H → Correct password
    • 01H → Error receiving command package
    • 13H → Wrong password
  • Checksum (2 bytes): Same rule as command.

Example: EF01 FFFFFFFF 07 0003 00 XXXX (means password is correct).

How to build the command to be sent

We need to build the command before transmitting it over the UART. We will write a common function to build all types of commands. This function R307_BuildCommand creates a valid command packet that can be sent to the R307 fingerprint sensor.

static uint16_t R307_BuildCommand(uint8_t *out_buf, uint8_t instruction,
                                  const uint8_t *params, uint16_t params_len)
{
    uint16_t idx = 0;

    // Header + address
    out_buf[idx++] = 0xEF;
    out_buf[idx++] = 0x01;
    out_buf[idx++] = 0xFF;
    out_buf[idx++] = 0xFF;
    out_buf[idx++] = 0xFF;
    out_buf[idx++] = 0xFF;

    // Packet identifier: command packet
    out_buf[idx++] = 0x01;

    // Length = Instruction(1) + params_len + checksum(2)
    uint16_t length_field = 1 + params_len + 2;
    out_buf[idx++] = (length_field >> 8) & 0xFF;
    out_buf[idx++] = length_field & 0xFF;

    // Instruction
    out_buf[idx++] = instruction;

    // Params
    if (params_len && params != NULL) {
        memcpy(&out_buf[idx], params, params_len);
        idx += params_len;
    }

    // Compute checksum: sum of bytes from packet identifier (index 6) to last data byte
    uint16_t sum = 0;
    for (uint16_t i = 6; i < idx; ++i) sum += out_buf[i];

    // Append checksum (2 bytes)
    out_buf[idx++] = (sum >> 8) & 0xFF;
    out_buf[idx++] = sum & 0xFF;

    return idx;
}

Here’s what happens step by step:

  1. Start with header and address
    It always begins with:
    • EF01 → fixed start code
    • FFFFFFFF → default sensor address
  2. Add packet identifier
    0x01 means this is a command packet.
  3. Add length field
    Length = Instruction byte (1) + Parameters length + Checksum (2).
    This tells the sensor how many bytes are coming after this field.
  4. Add instruction
    This is the command code (like “enroll finger” or “delete template”).
  5. Add parameters (if any)
    If extra data is needed for the command, copy it here.
  6. Compute checksum
    Add up all bytes starting from packet identifier (0x01) up to the last parameter.
  7. Append checksum (2 bytes)
    The checksum ensures data integrity, so the sensor knows if the command was corrupted.
  8. Return total length
    Finally, the function returns the number of bytes written in out_buf.

How to Send Command and Check Response

Below is function to send the command to the device and check the response sent by the device.

static HAL_StatusTypeDef R307_SendCommand(uint8_t *cmd, uint16_t cmd_len,
                                   uint8_t *response, uint16_t response_max_len)
{
    HAL_StatusTypeDef status;

    // Transmit full command
    status = HAL_UART_Transmit(&R307_UART, cmd, cmd_len, 500);
    if (status != HAL_OK) {
    	DB_LOG("UART transmit failed");
        return status;
    }

    // Receive header (9 bytes): EF01 + Addr(4) + PID + LEN_H + LEN_L
    status = HAL_UART_Receive(&R307_UART, response, 9, 1000);
    if (status != HAL_OK) {
    	DB_LOG("UART receive failed (header)");
        return status;
    }

    // Get length field (this length already includes checksum)
    uint16_t payload_len = ((uint16_t)response[7] << 8) | response[8];
    uint16_t total_len = 9 + payload_len; // header + payload_len (payload includes checksum)

    if (total_len > response_max_len) {
    	DB_LOG("Response too large (%d bytes, buffer max %d)", total_len, response_max_len);
        // Drain remaining bytes if needed? For now return error
        return HAL_ERROR;
    }

    // Read rest of packet (payload_len bytes)
    if (payload_len > 0) {
        status = HAL_UART_Receive(&R307_UART, &response[9], payload_len, 1000);
        if (status != HAL_OK) {
        	DB_LOG("UART receive failed (payload)");
            return status;
        }
    }

    return HAL_OK;
}

This function R307_SendCommand is used to send a command to the R307 fingerprint sensor and then receive its reply over UART.

Here’s what happens step by step:

  1. Send the command
    It first sends the full command (given in cmd with length cmd_len) through UART.
  2. Receive the header (first 9 bytes of reply)
    The fingerprint sensor always replies with a packet. The first 9 bytes of this reply are always the same (lengthwise):
    • EF01 → start codeAddr(4) → sensor addressPID → packet identifierLEN_H + LEN_L → total length of the rest of the packet
  3. Check how long the rest of the packet is
    From the header, it extracts the payload length (number of bytes to follow, including checksum).
  4. Receive the payload
    If payload length > 0, it reads the remaining bytes of the packet.
  5. Return success
    If everything went fine, it returns HAL_OK.

Fingerprint Enrollment Process

We will write a separate function to Enroll the fingerprint to the sensor.

HAL_StatusTypeDef R307_Enroll(uint16_t page_id)
{
    FP_LOG("\r\n=== ENROLL FINGERPRINT ===");
    FP_LOG("Target ID = %d", page_id);

    // 1st capture
    FP_LOG("Place your finger...");
    if (WaitForFingerPlacement(20000) != HAL_OK) {
    	return HAL_TIMEOUT;
    }

    FP_LOG("Capturing finger...");
    while (R307_CaptureFinger() != HAL_OK) {
        HAL_Delay(200); // wait and retry
    }
    if (R307_Image2Tz(1) != HAL_OK) {
        FP_LOG("Failed to convert first image");
        return HAL_ERROR;
    }
    FP_LOG("First image captured and converted");

    // Wait until finger is removed (with timeout)
    FP_LOG("Please remove your finger...");

    if (WaitForFingerRemoval(10000) != HAL_OK) {
        return HAL_TIMEOUT;
    }

    // 2nd capture
    FP_LOG("Place the same finger again...");
    if (WaitForFingerPlacement(20000) != HAL_OK) {
    	return HAL_TIMEOUT;
    }

    FP_LOG("Capturing finger again...");
    while (R307_CaptureFinger() != HAL_OK) {
        HAL_Delay(200); // wait and retry
    }

    if (R307_Image2Tz(2) != HAL_OK) {
        FP_LOG("Failed to convert second image");
        return HAL_ERROR;
    }
    FP_LOG("Second image captured and converted");

    FP_LOG("Please remove your finger...");

       if (WaitForFingerRemoval(10000) != HAL_OK) {
           return HAL_TIMEOUT;
       }

    // Merge buffers into template
    if (R307_GenerateTemplate() != HAL_OK) {
        FP_LOG("Failed to generate template (merge)");
        return HAL_ERROR;
    }
    FP_LOG("Template merged");

    // Store model at page_id (store from buffer 1)
    if (R307_StoreTemplate(1, page_id) != HAL_OK) {
        FP_LOG("Failed to store template");
        return HAL_ERROR;
    }

    FP_LOG("Enroll successful. Stored at ID=%d", page_id);
    return HAL_OK;
}

The function asks the user to place and remove the same finger twice, converts both images into data, merges them into a fingerprint template, and then stores it in the sensor’s database at the given ID.

Here’s what happens step by step:

  1. Start enrollment
    It prints messages to the log, showing the target ID where the fingerprint will be stored.
  2. First capture
    • Waits until the finger is detected.
    • Captures the fingerprint image.
    • Converts the image into data and saves it into buffer 1.
  3. Ask to remove finger
    Waits until the finger is lifted (with timeout).
  4. Second capture
    • Waits until the finger is detected.
    • Captures the image again.
    • Converts it into data and saves it into buffer 2.
  5. Ask to remove finger again
    Waits until the finger is lifted.
  6. Generate template
    • Combines the two captured images (buffer 1 and buffer 2).
    • Creates a final fingerprint template.
  7. Store template
    • Saves the generated template into sensor memory at the given page_id.
    • If this works, enrollment is complete.
  8. Success
    Prints confirmation that the fingerprint has been stored successfully.

Fingerprint Verification & Matching

Here we will check the finger against all stored IDs to find a match. Below is the function to verify the finger.

HAL_StatusTypeDef R307_Verify(uint16_t *out_page_id, uint16_t *out_score)
{
	HAL_StatusTypeDef status;

    FP_LOG("\r\n=== VERIFY FINGERPRINT ===");
    FP_LOG("Place your finger...");
    if (WaitForFingerPlacement(20000) != HAL_OK) {
    	return HAL_TIMEOUT;
    }

    FP_LOG("Capturing finger...");
    while (R307_CaptureFinger() != HAL_OK) {
        HAL_Delay(200); // wait and retry
    }
    if (R307_Image2Tz(1) != HAL_OK) return HAL_ERROR;

    // Search entire library; change range if you have different capacity
    uint16_t page_id = 0, score = 0;
    FP_LOG("Searching Database...");
    if (R307_SearchDatabase(&page_id, &score, 0x0000, 0x03E8) == HAL_OK) {
        FP_LOG("Match found: ID=%d Score=%d", page_id, score);
        if (out_page_id) *out_page_id = page_id;
        if (out_score)   *out_score   = score;
        status = HAL_OK;
    }
    else {
    	status = HAL_ERROR;
    }

    FP_LOG("Please remove your finger...");

       if (WaitForFingerRemoval(10000) != HAL_OK) {
           status = HAL_TIMEOUT;
       }
    return status;
}

This function captures a finger, searches it in the stored database, and tells whether a match is found along with its ID and score.

Here’s what happens step by step:

  1. Start verification
    Shows a log message and asks the user to place their finger.
  2. Capture fingerprint
    • Waits until finger is detected.
    • Captures the fingerprint image.
    • Converts it into data (stored in buffer 1).
  3. Search database
    • Compares the captured fingerprint with all stored templates (from ID 0x0000 to 0x03E8, i.e., up to 1000 IDs).
    • If a match is found → returns the page_id (location in database) and score (how close the match is).
    • If no match → returns error.
  4. Remove finger
    Waits until the user lifts the finger before finishing.
  5. Return result
    • Returns HAL_OK if match found.
    • Returns HAL_ERROR if no match.
    • Returns HAL_TIMEOUT if user didn’t place/remove finger in time.

The main() function

Inside the main function, we will clear memory and enrolls all fingerprints on the first run. After that, it will keep running in a loop, verifying any finger placed and showing green for success or red for failure.

int main(void)
{
	.....
	.....

  if (R307_VerifyPassword(0x00000000) != HAL_OK) {
      FP_LOG("Password verify failed!");
      Error_Handler();
  }

  if (HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR0) != 0x9876){
	  FP_LOG("Clearing All Fingerprints");
	  if (R307_ClearAllFingerprints() != HAL_OK){
		  FP_LOG("Failed to clear All Fingerprints");
		  Error_Handler();
	  }
	  FP_LOG("All Fingerprints Removed");
	  for (int i=1; i<=NUM_FINGERS; i++)
	  {
		  HAL_StatusTypeDef status;
		  int numRetries = 3;

		  do
		  {
			  FP_LOG("\n\nPlace your finger to enroll at ID %d", i);
			  status = R307_Enroll(i);
		  }
		  while ((status != HAL_OK) && (numRetries-- > 0));
		  if (status != HAL_OK)
			  FP_LOG("Enrollment failed for ID %d after 3 attempts", i);
	  }
	  HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR0, 0x9876);
  }

  while (1)
  {
	  uint16_t matched_id, score;
	  FP_LOG("\n\nPlace finger to verify...");
	  HAL_StatusTypeDef status = R307_Verify(&matched_id, &score);
	  if (status == HAL_ERROR) {
	      FP_LOG("No match found.");
	      HAL_GPIO_WritePin(RED_PORT, RED_PIN, 1);
	  }
	  else if (status == HAL_OK) {
		  HAL_GPIO_WritePin(GREEN_PORT, GREEN_PIN, 1);
	  }
	  HAL_Delay(2000);
	  HAL_GPIO_WritePin(GREEN_PORT, GREEN_PIN, 0);
	  HAL_GPIO_WritePin(RED_PORT, RED_PIN, 0);
  }
}

Here’s what happens step by step:

  1. Verify sensor password
    • Sends a default password (0x00000000) to check communication with the fingerprint sensor.
    • If it fails you can send command to set the password for the sensor and then verify the password again.
  2. Check if enrollment is needed
    • Reads a backup register in RTC (RTC_BKP_DR0).
    • If it’s not set to 0x9876, it means enrollment hasn’t been done before.
  3. Clear all old fingerprints
    • Deletes all stored templates from sensor memory.
  4. Enroll new fingerprints
    • For each ID from 1 to NUM_FINGERS, it asks the user to place a finger.
    • Calls R307_Enroll(i) to capture and store that finger at ID i.
    • If enrollment fails, it retries up to 3 times.
    • Once all are done, it writes 0x9876 into RTC backup so next reset it won’t re-enroll.
  5. Loop forever (verification mode)
    • Waits for a finger to be placed.
    • Runs R307_Verify to check if it matches any stored ID.
    • If match found, turns Green LED ON.
    • If no match, turns Red LED ON.
    • Waits 2 seconds, then turns LEDs off.

Result

Below is the short video showing the working of the above code.

As you can see in the video, I enrolled the right thumb and right index finger first. Then while checking the Green Light turned on when the correct finger is placed on the sensor. Whereas the Red light turned on when the left thumb or left index finger is placed.

The logs on the console guides user about what to do. It asks user to place the finger on the sensor or remove the finger from it.

In conclusion, the R307 fingerprint module combined with STM32 microcontrollers provides a simple yet powerful solution for biometric authentication in embedded projects. With UART communication, it becomes easy to enroll, store, and verify fingerprints directly from the microcontroller. This makes the setup ideal for building secure door locks, attendance systems, and identity verification devices. While the R307 offers reliable onboard processing, the STM32 adds flexibility and control, enabling developers to create customized solutions. Together, they deliver a cost-effective, accurate, and efficient way to integrate biometric security into real-world applications.

PROJECT DOWNLOAD

Info

You can help with the development by DONATING Below.
To download the project, click the DOWNLOAD button.

Project FAQs

Subscribe
Notify of

0 Comments
Newest
Oldest Most Voted
Inline Feedbacks
View all comments