0

I am writing EPROM dumper with Arduino UNO and utility program in C and I am using Ubuntu 20.04. The Arduino sketch works fine, but I am having few problems with the utility program. The first problem is that the utility hangs (like in some kind of infinite loop) if I don't open the serial monitor in Arduino IDE previously, or any other serial communication app (like puTTY). The second problem I have is that the utility also hangs when reading the last bytes which Arduino sends - inspecting the dumped file in hex editor, the last 4-6 bytes are missing. By printing string messages in the function serial_port_read_block_file, I think that the program hangs up in the read function.

Thank you in advance and sorry if I understood something wrong configuring the serial communication.

EDIT: Adding switch-case statement for setting the baud rate solved the first problem. The second problem is that I forgot that byte with the value of '\n' can be received, and I was just discarding this byte, so at the end I was missing few bytes. Also, I should have checked in the serial_port_read_block_file function if n>0, so I don't access address beyond the scope of the block array (with the statement buffer_cur_pos[-1], if n=0 I accessed invalid address).

Conclusion: As @Majenko suggested, I should implement some protocol that will wrap up the data transfer, to distinguish the data from the start/stop conditions. I also changed the code below with the fixes which made the program working (both, in the Arduino sketch and the utility program).

The Arduino sketch:

//74HC595 pins
const int shiftData = 10;
const int shiftClock = 11;
const int shiftLatch = 12;

//ROM data pins (successive)
const int romD0 = 2;

//pgm constants and variables
static const uint16_t BLOCK_SIZE = 256;
uint16_t nBytes = 0;
boolean readMode = false;

void sendAddress(uint16_t addr) {
  //first, send the high byte of the address
  shiftOut(shiftData, shiftClock, MSBFIRST, addr >> 8);
  //then, send the low byte of the address
  shiftOut(shiftData, shiftClock, MSBFIRST, addr);
  //pulse the shiftLatch pin, to output the address
  digitalWrite(shiftLatch, LOW);
  digitalWrite(shiftLatch, HIGH);
  digitalWrite(shiftLatch, LOW);
}

uint8_t readByte(uint16_t addr) {
  uint8_t data = 0;
  //send the address
  sendAddress(addr);
  for (int i = 7; i >= 0; i--)
    data = (data << 1) | digitalRead(romD0 + i);
  return data;
}

void sendBytes(uint16_t bytes) {
  //send the data in blocks with size BLOCK_SIZE
  const uint16_t BLOCKS = bytes / BLOCK_SIZE; //number of whole blocks
  const uint16_t REMAINDER = bytes % BLOCK_SIZE; //the remaining bytes that don't form a block

  uint8_t data[BLOCK_SIZE]; //buffer for block of bytes to be sent

  //send the whole blocks
  for (uint16_t block = 0; block < BLOCKS; block++) {
    for (uint16_t offset = 0; offset < BLOCK_SIZE; offset++) {
      data[offset] = readByte(block * BLOCK_SIZE + offset);
    }
    Serial.write(data, BLOCK_SIZE);
    delay(100);
  }

  //send the remaining bytes
  for (uint16_t offset = 0; offset < REMAINDER; offset++) {
    data[offset] = readByte(BLOCKS * BLOCK_SIZE + offset);
  }
  Serial.write(data, REMAINDER);
  Serial.write("\r\n"); //end of transmission
  delay(100);
}

void setup() {
  pinMode(shiftData, OUTPUT);
  pinMode(shiftClock, OUTPUT);
  pinMode(shiftLatch, OUTPUT);

  for (int i = 0; i < 8; i++)
    pinMode(romD0 + i, INPUT);

  Serial.begin(57600);

  //wait for serial port to connect
  while (!Serial) {}
}

void loop() {
  while (!Serial.available()) {} //wait until data is available
  char c = Serial.read();
  delay(100);
  switch (c) {
    case 'S': //send ready command
      Serial.write("> ");
    break;
    case 'R':
      readMode = true;
      while ((c = Serial.read()) != '\n') {
        nBytes = nBytes * 10 + (c - '0');
      }
      delay(100);
    break;
    default:
    break;
  }

  if (readMode) {
    sendBytes(nBytes);
  }

  //reset for next loop
  readMode = false;
  nBytes = 0;
  delay(100);
}

The utility program:

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <termios.h>

static const uint16_t BLOCK_SIZE = 256;
static const uint32_t BAUD_RATE = 57600;
    
void help(void) {
    printf(
            "rdump - ROM dumper\n"
            "usage: rdump [OPTIONS]\n"
            "Options:\n"
            "-p <serial port>       specify serial port of the Arduino\n"
            "-f <filename>          specify output filename\n"
            "-s <num bytes>         specify the number of bytes to read\n"
            "-h                     print this help file\n"
    );
}

//opening and initializing serial communication
int serial_port_init(const char *port, uint32_t baud) {

    //open serial port for read/write, save its file descriptor in fd
    int fd = open(port, O_RDWR | O_NOCTTY | O_SYNC);
    
    if(fd == -1) {
        fprintf(stderr, "serial_port_init: unable to open %s\n", port);
        return -1;
    }
    
    struct termios options; //serial port options
    
    //get the initial serial port options
    if(tcgetattr(fd, &options) < 0) {
        fprintf(stderr, "serial_port_init: unable to get terminal options\n");
        return -1;
    }
    
    //set the baud rate
    cfsetispeed(&options, (speed_t)baud);
    cfsetospeed(&options, (speed_t)baud);
    
    options.c_cflag |= (CLOCAL | CREAD); //enable the receiver and block control lines
    
    //set the character size to 8 bits, no parity bit and 1 stop bit (8N1 configuration)
    options.c_cflag &= ~PARENB; //no parity
    options.c_cflag &= ~CSTOPB; //1 stop bit
    options.c_cflag &= ~CSIZE; //bit mask for character size
    options.c_cflag |= CS8; //8 bit character size
    //disable hardware flow control
    options.c_cflag &= ~CRTSCTS;
    //configure for non-canonical mode
    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHONL | ISIG); //disable cannonical mode, echo, erasure, new-line echo,  INTR, QUIT and SUSP
    options.c_iflag &= ~(IXON | IXOFF | IXANY); //disable software flow control
    options.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL); //disable any special handling of received bytes
    options.c_oflag &= ~OPOST; // prevent special interpretation of output bytes
    options.c_oflag &= ~ONLCR; // prevent conversion of newline to carriage return/line feed
    
    //read returns after 0.5 second timeout
    options.c_cc[VMIN] = 0;
    options.c_cc[VTIME] = 5;
    
    serial_port_flush(fd); //flush the serial port
    
    //set the new options for the serial port
    if(tcsetattr(fd, TCSANOW, &options) < 0) {
        fprintf(stderr, "serial_port_init: unable to set terminal options\n");
        return -1;
    }
    
    return fd;
}

//closing the serial communication
void serial_port_close(int fd) {
    close(fd);
}

void serial_port_flush(int fd) {
    sleep(2);
    tcflush(fd, TCIOFLUSH);
}

//check if serial port is ready
int serial_port_ready(int fd) {
    uint8_t c[1];
    uint8_t prev = 0;
    ssize_t n = read(fd, c, 1);
    int done = 0;
    if(n <= 0) return 0;
    prev = c[0];
    do {
        //printf("%02X", (uint8_t)prev);
        n = read(fd, c, 1);
        if(n <= 0) return 0;
        done = (prev == '>') && (c[0] == ' ');
        prev = c[0];
    } while(!done);
    return 1;
}

//writing to the serial port
ssize_t serial_port_write(int fd, const char *data) {
    size_t data_length = strlen(data);
    ssize_t n = write(fd, data, data_length);
    if(n != data_length) {
        fprintf(stderr, "serial_port_write: unable to write the data\n");
        return -1;
    }
    return n;
}

//reading block of bytes from the serial port into file
int32_t serial_port_read_block_file(int fd, uint8_t *block, uint16_t block_size, FILE **file) {
    uint8_t *block_cur_pos = block;
    memset(block, 0, block_size);
    
    ssize_t n;
    uint16_t total = 0;
    int done = 0;
    int try = 0;
    static const int TRIES = 5;
    //printf("serial_port_read_block_file: started reading block.\n");
    do {
        n = read(fd, block_cur_pos, block_size - (block_cur_pos - block));
        if(n == -1) {
            fprintf(stderr, "serial_port_read_block_file: unable to read the data\n");
            return -1;
        }
        block_cur_pos += n;
        total += n;
        //print_byte_array(block, block_cur_pos - block + 1);
        if(n > 0) {
            done = (block_cur_pos[-1] == '\n' && block_cur_pos[-2] == '\r') || (total == block_size);
        } else {
            try++;
            if(try >= TRIES) done = 1;
        }
    } while(!done);
    //printf("serial_port_read_block_file: done reading the required block. Total read: %u bytes\n", total);
    if(total == 0) return 0;
    if(block_cur_pos[-1] == '\n' && block_cur_pos[-2] == '\r') {
        total -= 2;
        fwrite(block, sizeof(uint8_t), total, *file);
    } else {
        fwrite(block, sizeof(uint8_t), total, *file);
    }
    fflush(*file);
    //printf("serial_port_read_block_file: done writing the bytes to the file.\n");
    return total;
}

//reading (bytes) bytes from the serial port into file
int32_t serial_port_read_file(int fd, FILE **file, uint16_t bytes) {
    uint8_t *block = (uint8_t*)malloc(BLOCK_SIZE * sizeof(uint8_t));
    uint16_t total = 0;
    ssize_t n;
    do {
        n = serial_port_read_block_file(fd, block, BLOCK_SIZE, file);
        if(n == -1) {
            return -1;
        }
        total += n;
    } while(total < bytes);
    free(block);
    //printf("Done reading.......\n");
    return total;
}

int main(int argc, char *argv[]) {
    char *port_name = NULL;
    char *file_name = NULL;
    uint16_t bytes;
    //parsing command line arguments
    opterr = 0;
    char c;
    while((c = getopt(argc, argv, "p:f:s:h")) != -1) {
        switch(c) {
            case 'p':
                port_name = strdup(optarg);
            break;
            case 'f':
                file_name = strdup(optarg);
            break;
            case 's':
                bytes = atoi(optarg);
            break;
            case 'h':
                help();
                return 0;
            break;
            default:
                fprintf(stderr, 
                        "rdump: invalid option: %c\n"
                        "Try 'rdump -h' for more information.\n", 
                        optopt);
                return 0;
            break;
        }
    }
    
    //opening serial port communication with the arduino
    int port = serial_port_init(port_name, BAUD_RATE);
    if(port < 0) {
        //fprintf(stderr, "rdump: unable to open port %s\n", port_name);
        free(port_name);
        free(file_name);
        return -1;
    }
    
    printf("rdump: serial communication to %s opened successfully\n", port_name);
    sleep(3); //wait for serial port to be ready

    //send command for testing if the serial port is ready
    char cmd[20];
    cmd[0] = 'S';
    cmd[1] = '\0';
    if(serial_port_write(port, cmd) < 0) {
        serial_port_close(port);
        return -3;
    }
    //check if serial port is ready
    if(!serial_port_ready(port)) {
        fprintf(stderr, "rdump: serial port is not ready.\n");
        serial_port_close(port);
        free(port_name);
        free(file_name);
        return -2;
    }
    printf("rdump: serial port is ready\n");
    //send command for reading n bytes
    sprintf(cmd, "R%d\n", bytes);
    if(serial_port_write(port, cmd) < 0) {
        serial_port_close(port);
        free(port_name);
        free(file_name);
        return -3;
    }
    //receive the requested data
    FILE *rom = fopen(file_name, "wb");
    fsync(fileno(rom));
    if(rom == NULL) {
        fprintf(stderr, "rdump: unable to create file %s\n", file_name);
        serial_port_close(port);
        free(port_name);
        free(file_name);
        return -4;
    }
    printf("rdump: file %s created successfully.\n", file_name);
    printf("Dumping ROM to file %s...\n", file_name);
    //uint8_t buffer[10000];
    //int n = serial_port_read_block_file(port, buffer, 10000, rom);
    //int n = serial_port_read(port, buffer, 10000);
    //fwrite(buffer, sizeof(uint8_t), n, rom);
    ssize_t dumped_bytes = serial_port_read_file(port, &rom, bytes);
    if(dumped_bytes < 0) {
        return -5;
    }
    if(dumped_bytes != bytes) {
        fprintf(stderr, "Dumped %zd of %u bytes\n", dumped_bytes, bytes);
    } else {
        printf("Done!\n");
    }
    //close the file, serial port and free the memory
    fclose(rom);
    serial_port_close(port);
    free(port_name);
    free(file_name);
    //printf("Exiting...\n");
    return 0;
}
  • For the baud rate on Linux you must use B57600 not 57600. – Majenko Jul 24 at 15:09
  • For your data transfer you should implement some form of protocol that wraps your data transfer and is distinguishable from your data transfer. One example would be to transfer the data as ASCII HEX with transfer start and stop markers that don't use 0-9A-F. One good example would be to implement Intel HEX format (the same format Arduino sketches are uploaded in) since that is a standard for EEPROM programmers to work with. – Majenko Jul 24 at 15:13
  • I've used github.com/Ghostbird/Arduino_EEPROM_RW in the past. Which is somewhat similar to what you are trying to do, as far as I can see. – Gerben Jul 24 at 16:13
  • @Majenko Thank you very much for your information. I thought that conversion from int to baud rate could be done just by casting the int to speed_t. Also I will try to implement protocol which you suggested. – Bojan Jul 24 at 22:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.