Serial Comm Ports for ever and ever… (Part 4)

Part 12345678

You will find thereafter an improved version of the previously described read and write routines. Instead of using a fixed length buffer for storing the data to be sent and another one for recording the incoming data, the readBytes function shall return a vector containing the incoming data.
In addition to the better look and feel of the code, it is now possible to pass one single byte or a vector of data without additional coding work. Unfortunately, the use of memory allocation within the readByte() function takes timmmmmmmmme 🙁
The changes are discussed next to the code:

In fact the write code is unchanged

/* 
Write bytes
*/
void writeBytes(byte *pdata, int nbrBytes, long baudRate, int nbrBits) {
  long interval = long(1000000 / baudRate); // in µs
  for (int j; j < nbrBytes; j++) {
    byte byteValue = pdata[j];
    long time_startOfByte = micros(); // Set reference time to half first bit interval
    // Send start bit 
    PORTB &= ~(1 << TxPin); // Set start bit (low level) on TxPin
    long time_endOfBit = time_startOfByte + interval;
    while (micros() < time_endOfBit);  // Wait until interval time has elapsed
    // Send data bits: LSB first, MSB last
    for (int i = 0; i < nbrBits; i++) {
      byte bitValue = ((byteValue >> i) & 0x01);
      if (bitValue) {
        PORTB |=  (1 << TxPin); 
      }
      else {
        PORTB &= ~(1 << TxPin); 
      }
      time_endOfBit = time_startOfByte + (interval * (i+2));
      while (micros() < time_endOfBit); // Wait until interval time has elapsed
    }
    // Send stop bit
    PORTB |=  (1 << TxPin); // Set stop bit (high level) on TxPin
    time_endOfBit = time_startOfByte + (interval * (nbrBits+2));
    while (micros() < time_endOfBit); // Wait until interval time has elapsed
  }
}

The read code has changed

/* 
Read incoming bytes
Returns a pointer to the data
*/
byte* readBytes(int nbrBytes, long baudRate, int nbrBits, int timeOut) {
  byte *ptrData = (byte*) malloc (sizeof(*ptrData) * nbrBytes); // Allocate memory space for the vector of data
  // default error code
  errorCode = ERR_NONE;
  int nbrReceivedBytes = 0;
  long interval = long(1000000 / baudRate); // in µs
  for (int j=0; j < nbrBytes; j++) {
    boolean elapsedTimeOut = false;
    byte byteValue = 0; 
    long abortTime = (millis() + timeOut);
    // Wait for the start bit (low level)
    while (PINB & (1 << RxPin)) { // Loop until level is high
      if (millis() > abortTime){ // Monitor timeout
        elapsedTimeOut = true;
        break;
      } 
    }
    long referenceTime = micros() + long(500000 / baudRate); // now + half interval
    if (elapsedTimeOut) { // 
      errorCode = ERR_TIMEOUT;
      return (NULL); 
    }
    else {
      // Read the incoming byte
      long nextSensingTime = 0;
      // Read bits
      for (int i=0; i < nbrBits; i++) {  
        // Set next sensing time
        nextSensingTime = (referenceTime + (interval * (i + 1)));
        // Wait for data bits
        while (micros() < nextSensingTime);
        byte bitValue = ((PINB >> RxPin) & 0x01);
        byteValue |= (bitValue << i);
       }
      // Wait for the stop bit
      nextSensingTime = referenceTime + interval * (nbrBits + 1);
      while (micros() < nextSensingTime);
      byte bitValue = ((PINB >> RxPin) & 0x01);
      if (bitValue){ // (High level)
        // Validate and record byte 
        nbrReceivedBytes++;
        ptrData[nbrReceivedBytes - 1] = byteValue;
      } 
      else {// (Low level)
        errorCode = ERR_FRAME;
        return (NULL); 
      }
    }
  } 
  return (ptrData);
}

What’s new here?

  • byte* readBytes(int nbrBytes, long baudRate, int nbrBits, int timeOut): readBytes returns a pointer value
  • byte *ptrData = (byte*) malloc (sizeof(*ptrData) * nbrBytes); : this one is interesting and it contains a couple of tricks. It means: create a pointer variable; the (byte*) instruction casts the malloc function in order to prevent any discrepencies, malloc allocates memory for nbrBytes times the size of one byte. sizeof(*ptrData) avoids any confusion in data types.
  • A NULL value is returned in case of failure.

Here are a couple of examples of use for these functions:

Using vectors of bytes

  byte *ptrDataOut = initializeData(26); // Fill in this vector with some data (Alphabet)
  int sentBytes = 6; // The the n first bytes
  writeBytes(ptrDataOut, sentBytes, baudRate, nbrBits); // Send Bytes
  // The remote device must send the echoed bytes with a little delay: about 20 ms
  byte *ptrDataIn = readBytes(sentBytes, baudRate, nbrBits, timeOut); // Get data echoed by external application
  listData(ptrDataIn, sentBytes); // List the content of the vector with received data
  free(ptrDataOut);  
  ptrDataOut = NULL;

Using single bytes

  byte value = 0x55; // Create a variable which contains a value
  byte *ptrDataOut = &value; // Create a pointer which points to the newly created variable
  writeBytes(ptrDataOut, 1, baudRate, nbrBits); // Send byte
  // The remote device must send the echoed bytes with a little delay: about 20 ms
byte result = *readBytes(1, baudRate, nbrBits, timeOut); // Get data echoed by external application by dereferencing the pointer
  free(ptrDataOut);  
  ptrDataOut = NULL;

Attention please: Look at the two last lines! Avoid memory problems with the free() command, and release the pointer in the clean way by nulling it.

And for completness, the two general purpose functions for initializing and listing vectors of data

/* 
Initialize the content of the vector of data 
The vector is filled with capitalized alphabet
*/
byte* initializeData(int nbrBytes){
  byte *ptrData = (byte*) malloc (sizeof(*ptrData) * nbrBytes); // Allocate memory space for the vector of data
  if (ptrData != NULL) { 
    for (int i=0; i < nbrBytes; i++) { // Record data in vector
      ptrData[i] = (i + 65);
    }
    return (ptrData);
  }
  else {
    Serial.println("Error while allocating memory");
    return (NULL);
 }
}

/*
List the content of the vector of data 
*/
void listData(byte *pdata, int nbrBytes){
   if (pdata != NULL) { // 
    for (int i=0; i < nbrBytes; i++) {
      Serial.print(pdata[i], BYTE);
      Serial.print(" ");
    }
    Serial.println();
  }
  else {
    Serial.println("Error while reading memory");
  }
}

Next post on same subject

Leave a Reply

You must be logged in to post a comment.