Serial Comm Ports for ever and ever… (Part 4)
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"); } }