Raspberry Pi to Arduino Serial Communication

Good Day, I am trying to send packetized binary data from an mp3 file which is located on my Raspberry Pi to my Arduino using serial UART connection. I have 3 cables connected from the pi to arduino (Tx, Rx and GND). Everything is going well except the pi is not able to send the packets to my arduino. I have tried simple code to send hex data and it works well so its not the connection. Please do have a look and any help is appreciated!

Pi Code:

import serial
import time
from pydub import AudioSegment
import os

# Constants
PACKET_SIZE = 128
SERIAL_PORT = '/dev/ttyAMA0'
BAUD_RATE = 9600
PACKET_DELAY = 0.5

# Read the MP3 file
def read_mp3_file(filename):
    audio = AudioSegment.from_mp3(filename)
    return audio.raw_data, audio.frame_rate

# Packetize the audio data
def packetize_data(audio_data):
    packetized_data = []
    num_packets = (len(audio_data) + PACKET_SIZE - 1) // PACKET_SIZE
    for i in range(num_packets):
        packet_payload = audio_data[i * PACKET_SIZE: (i + 1) * PACKET_SIZE]
        packet = packet_payload
        packetized_data.append(packet)
        print(f"Packet {i}: Payload={packet_payload}")  # Display the generated packet
    return packetized_data

# Calculate the checksum of a packet
def calculate_checksum(packet):
    checksum = 0
    for byte in packet:
        checksum ^= byte
    return checksum

# Transmit packets to Arduino
def transmit_packets(packets):
    try:
        ser = serial.Serial(SERIAL_PORT, BAUD_RATE)
        print("Transmitting packets...")
        for i, packet in enumerate(packets):
            sequence_number = i.to_bytes(1, byteorder='big')  # Convert the sequence number to bytes
            header = b'\x00'  # Placeholder for the header
            payload = packet
            checksum = calculate_checksum(sequence_number + header + payload)  # Calculate the checksum
            serialized_packet = sequence_number + header + payload + bytes([checksum])
            ser.write(serialized_packet)
            time.sleep(PACKET_DELAY)
            # Wait for acknowledgement
            while True:
                if ser.readable() and ser.in_waiting >= 1:
                    received_sequence_number = ord(ser.read())
                    if received_sequence_number == sequence_number[0]:
                        print(f"Acknowledged packet: Sequence={received_sequence_number}")
                        break  # Acknowledgement received, move to the next packet
                    else:
                        print(f"Incorrect acknowledgement received: Sequence={received_sequence_number}")
                else:
                    print(f"No acknowledgement received for packet: Sequence={i}")
                # Retry sending the packet
                ser.write(serialized_packet)
                time.sleep(PACKET_DELAY)
        ser.close()
        print("Transmission complete")
    except serial.SerialException as e:
        print(f"Serial communication error: {str(e)}")
    except Exception as e:
        print(f"An error occurred during transmission: {str(e)}")

# Example usage
if __name__ == '__main__':
    # Read the MP3 file
    filename = '/home/sit57/test.mp3'
    if not os.path.exists(filename):
        print(f"File '{filename}' does not exist")
        exit(1)
    data, sample_rate = read_mp3_file(filename)

    # Packetize the audio data
    packets = packetize_data(data)

    # Print the number of packets
    print("Number of packets to transmit:", len(packets))

    # Transmit packets serially to Arduino
    transmit_packets(packets)

Arduino Code:

#include <Arduino.h>

#define PACKET_SIZE 128

void setup() {
  Serial.begin(9600);
  while (!Serial) {
    ; // Wait for Serial port to be ready
  }
}

void loop() {
  byte expected_sequence_number = 0;

  // Wait for packet from Raspberry Pi
  while (Serial.available() < PACKET_SIZE) {
    // Wait until enough bytes are available
  }

  byte received_sequence_number = Serial.read();
  byte header = Serial.read();
  byte payload[PACKET_SIZE - 2];
  for (int i = 0; i < PACKET_SIZE - 2; i++) {
    payload[i] = Serial.read();
  }
  byte checksum = Serial.read();

  // Verify the checksum
  byte calculated_checksum = calculate_checksum(received_sequence_number, header, payload, checksum);
  if (calculated_checksum == checksum) {
    // Print the received packet
    Serial.print("Received packet: Sequence=");
    Serial.print(received_sequence_number);
    Serial.print(", Header=");
    Serial.print(header);
    Serial.print(", Payload=");
    for (int i = 0; i < PACKET_SIZE - 2; i++) {
      Serial.print(payload[i]);
    }
    Serial.println();

    // Send acknowledgement
    Serial.write(received_sequence_number);
    expected_sequence_number++; // Move to the next expected sequence number
  } else {
    // Resend acknowledgement for the previous packet
    Serial.write(expected_sequence_number - 1);
  }
}

// Calculate the checksum of a packet
byte calculate_checksum(byte sequence_number, byte header, byte* payload, byte received_checksum) {
  byte checksum = 0;
  checksum ^= sequence_number;
  checksum ^= header;
  for (int i = 0; i < PACKET_SIZE - 2; i++) {
    checksum ^= payload[i];
  }
  checksum ^= received_checksum;  // Include the received checksum in the calculation
  return checksum;
}

What Arduino do you use?
The size of buffer of the Uno serial is 64 bytes only, so if you would wait until the board received 128 chars without processing it - you definitely will lost some part of the data.

You must process incoming bytes immediately, and not wait until the message arrives in full.

I’m using the UNO R3 with Raspberry Pi 3 Model B. Correct me if i’m wrong, I’ll have to change my packet size to 64 bytes and process them immediately?

All data is represented in binary no matter the binary represents ASCII characters, strings, integers, floats.....

1 Like

Will always be true, for reasons stated, so this will wait forever.
The default buffer length can be changed to a larger number, search the message threads for that. Alternatively, I'd suggest a packet size like 32, or 48, well within the serial buffer size, to allow for more to accumulate while you're grabbing the first set. Where are you putting the data? Not much memory in an Uno.

Thank you for the suggestion. I was planning to store it in the UNO but I’ll have to look for alternatives to store my data

suggest you switch to a ESP32 which has more memory and onboard WiFi
you could transfer your .mp3 file from the RPi to ESP32 over WiFi using a TCP/IP protocol - possibly FTP? how large is the file?
what do you wish to do with the file when received?

Changing the packet size or buffer length is not a method to solve an issue. imagine that you need to send 10 megabytes of data - what will you do then? Looking for a board with 100 MB memory? :slight_smile:

The correct approach is always to process the data in parts. Received 10 bytes - processed, then another 10 bytes and so on. This algorithm always works and does not depend on the serial buffer size.

1 Like

The problem of the buffer size is already pointed out. You can however easily work around that. You should read the data as it arrives, add it to payload and count the number of received bytes. Once that is 128, you can continue processing the data. A small finite state machine should do the trick.

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.