Completion of serial transmission - strange behaviour

I have come across something weird that I can find no explanation for.

I am developing an application using Serial1 in a Mega to transmit a string of characters to a slave device. The slave accepts 2400 baud, 2 stop bit, no parity, and replies over the same line using a strange semi PWM format - pulse high for < 400 us = LOW; high > 400 us = HIGH. The message string is prescribed as 6 bytes, the first is always 0xFF, there are 4 following bytes, and a checksum/address combination - 6 bytes in all. Each bit is a total 1.1 ms, and there is a start bit of around 1.5 ms. In some cases, there will be no slave response so the system has to accommodate this.

My concept is to use interrupts as much as possible so the Arduino can do processing while the communications is happening. On the completion of the transmission, I want to start a timeout check using one of the 16-bit timers. Initially I set this up for 15 ms, but found that the timer was not correctly synchronised to the end of the transmission.

Code is as follows:

int i=0, phase = 0;
byte message[] = { 0xFF, 0xFE, 0xFE, 0xFE, 0xFE, 0x00 };
byte item, receivedValue;
int receivedBitCount;
int timeOut = 0xA23A;
int messageLength = sizeof(message);
boolean messageSent, transmitFlag, receiveFlag, nextState;
boolean int3Flag, timedOutFlag, readComplete, dataValid;
volatile long intTime;
long pulseStart, pulseWidth;
void setup() 
{
  pinMode(18, INPUT);
  pinMode(17, OUTPUT);
  Serial.begin(38400);
  messageSent = LOW;
  transmitFlag = LOW;
  receiveFlag = LOW;
  item = 0;
  Serial.println(messageLength);
  Serial.print(UCSR1A, BIN),Serial.print(" ");Serial.print(UCSR1B, BIN);Serial.print(" ");Serial.println(UCSR1C, BIN);
}

void loop() 
{
  switch (phase)
  {
    case 0:
    {
 // Update message details and start sending process
      Serial.println(calcCheckSum(message));
      message[messageLength - 1] = calcCheckSum(message) | (item & 0x0F);
      Serial1Start();
      transmitFlag = HIGH;
      phase = 1;
      break;
    }
    case 1:
    {
 // Write byte to buffer - if done, move on, else go round again
      Serial.print(i); Serial.print(" = ");
      Serial.print(message[i], HEX);Serial.println();
      UDR1 = message[i++];
      if (i > messageLength - 1)
      {
        i = 0;
        phase = 3;
      }
      else phase = 2;
      break;
    }
    case 2:
    {
 // Wait for buffer to be emptied - then write next one
      if (UCSR1A & (1 << UDRE1))
      {
        phase = 1;
      }
      break;
    }
    case 3:
 // Message completed - wait for shift register to be emptied.
    {
      if (UCSR1A & (1 << TXC1))
      {
        phase = 4;
        digitalWrite(17, HIGH);
        transmitFlag = LOW;
      }
      break;
    }
    case 4:
    {
 // Message sent out - disable transmit.  Also increment item ptr.      
     UCSR1A = B00000000 | (1 << TXC1);     // Clear TXC1 flag
     UCSR1B = B00000000;                   // Disable transmitter function
     item++;
     if (item > 3) item = 0;
 // Prepare to receive.  Start timer and initialise values.
//     digitalWrite(17, HIGH);
     receivedBitCount = 0;
     receivedValue = 0;
     dataValid = false;
     readComplete = LOW;
     startTimer5();
     Serial.print("  TCCR5A = ");Serial.print(TCCR5A, BIN);Serial.print("  TCCR5B = ");Serial.print(TCCR5B, BIN);
     Serial.print("  TCCR5C = ");Serial.print(TCCR5C, BIN);Serial.print("  TIMSK5 = ");Serial.print(TIMSK5, BIN);
     Serial.print("  TIFR5 = ");Serial.println(TIFR5, BIN);
     phase = 5;
     break;
    }
    case 5:
    {
      while (!timedOutFlag)
      {
        Serial.println(TCNT5, HEX);
      }
      if(timedOutFlag)
      {
        digitalWrite(17, LOW);
        readComplete = true;
        phase = 0;
        timedOutFlag = false;
      }
      phase = 6;
     break;
   }
   case 6:
   {
 // Response completed. Disable interrupts.    
    stopTimer5();
    stopInt3();
    if (!readComplete) Serial.println(" No response.");
//    Serial.println(receivedValue, BIN);
    phase = 7;
    break;
   }
   case 7:
   {
    delay(10);
    phase = 0;
    break;
   }
  }
// Serial.println(phase);Serial.println(timedOutFlag);
  if (timedOutFlag)
  {
    digitalWrite(17, LOW);
    receivedValue = 0;
    if (!readComplete)
    {
      Serial.println(" No response.");
      readComplete = true;
    }
    timedOutFlag = false;
//    stopTimer5();
    readComplete = true;
    phase = 0;
  }
}

ISR(TIMER5_OVF_vect)
{
  timedOutFlag = HIGH;
  TCNT5 = timeOut; 
}

void startTimer5()
{
  TCCR5A = B00000000;     // Normal mode of operaton
  TCCR5B = B00000010;     // Prescale = 1/8 - tick is 2 MHz = 0.5 us
  TCCR5C = B00000000;
  TCNT5 = timeOut;          // 0xFFFF - 0x8ACF = 0x7530 = 30,000 = 15 ms
  TIMSK5 = 1 << TOIE5;    // Enable timer overflow interrupt
  timedOutFlag = LOW;
}

void stopTimer5()
{
  TIMSK5 = 0;             // Disable timer 5 overflow interrupt
  TCCR5B = B00000000;     // Stop timer
}


void Serial1Start()
{
  UBRR1H = 0x01;
  UBRR1L = 0xA0;
  UCSR1A = B00000000;
  UCSR1B = B00001000;
  UCSR1C = B00001110;
}

byte calcCheckSum(byte data[])
{
//  int dataLength = sizeof(data);
  int CS = 0;
  for (int i = 1; i < 5; i++)
  {
    CS = CS + data[i];
  }
  CS = CS + (CS >> 8);
  CS = CS + (CS << 4);
  CS = CS & 0xF0;
  return CS;
}

I'm using a switch statement to handle the various phases. The area of concern is case 4, where I am detecting the sending of the last bit in the transmitted message and then starting Timer 5.

With the timer set to give a 15 ms delay, it was starting after the 4th bit of the 6-bit message instead of waiting till the complete message has been sent. By experimenting, I found that adding an extra 0x00 byte to the end of the message, it was sent and the timer started at the end of the stream as expected. I also altered the timer preset to give a 12 ms timeout, and again it worked as expected.

In the attached photos, the top trace is the traffic on the communications port, and the bottom shows the operation of the timer - HIGH when the timer is running.

I have absolutely no idea what is causing this, and any input from the group will be welcome!


Delay set to 15 ms - showing overall communication stream and timer active period.


Same - detail of change from serial transmission to custom format reception.


As above but with delay set to 12 ms

Please display your image(s) in your post so we can see it(them) without downloading it(them). See this Simple Image Guide

You don't seem to have posted the complete program. I see a call to a function named Serial1Start() but there is no code for the function.

And it's not clear to me what part of the program is causing you problem?

...R

@Robin2 -
Images added to main text as requested ...

There is a lot more code that has been included in the main body that has been stripped out for trouble-shooting - it's fairly long and not relevant to this problem. I have added Serial1Start() and calcCheckSum() to the code extract.

I'm not sure where the problem lies - that is the issue. The objective is to complete a transmission using Serial1 TX pin using standard serial format (2400 baud, 2 stop bit, no parity) then turn the pin around to receive the special PWM-like format. As some of the replies may not have any incoming data I'm using Timer5 to generate an interrupt that will halt the receive routine. I am starting the timer using TXC1 which should indicate that the last bit of the last byte of the transmission stream has gone out.

What is actually happening is that, with a 15 ms delay (Timer5 preset 0x8ACF0), the timer is starting at the end of the 4th byte of the transmission, with 2 more bytes being sent after TXC1 has apparently been triggered. If I change the timer preset to 12 ms (preset 0xA23A) it operates as expected. The change to the timer setting is the only change made between the two sets of images.

The relevant code is on cases 3, 4, and 5 of the switch function - case 3 detects TXC1 and sets the indication on pin 17 (bottom trace). Case 4 stops the transmission and starts the timer, while case 5 is a loop until the timer times out. The transmission of a byte is done in case 3, which should not be entered after TXC1 has been triggered until the next transmission is started.

bmd1103:
There is a lot more code that has been included in the main body that has been stripped out for trouble-shooting - it's fairly long and not relevant to this problem. I have added Serial1Start() and calcCheckSum() to the code extract.

Can you post a complete working program that is just sufficient to illustrate the problem?

The problem with posting parts of a program is that the problem is often in the part you are not looking at - which may be the reason why you have not found it.

If you have code that updates the Atmega registers please include a comment to say what each line does so we don't have to trawl through the Atmega datasheet.

And PLEASE do NOT make significant changes to earlier Posts as the the Thread makes no sense when read from top to bottom. Put new material in a new Reply. (I am not referring to the pictures - for which, thank you)

...R

Edit: deleted. I got my bits mixed up.

MorganS:
This doesn't clear TXC1. It left-shifts 1 by the VALUE of TXC1.

From the Atmega328 datasheet

The TXCn Flag bit is automatically cleared when a transmit complete interrupt is executed, or it can be cleared by writing
a one to its bit location.

...R

Further testing ...

I changed the value of the timer preset to see if that had any effect - the program fails for values of the preset between 0x8A6F and 0x8D9F. I have not tested much outside this range. I have also tried changing to Timer 3 with no effect on the problem.

As the program is working with a delay of 12 ms - preset of 0xA23A - I'm moving on with other areas. The complete code, with diagnostic Serial.prints stripped out, is:

int i=0, phase = 0;
byte message[] = { 0xFF, 0xFC, 0xFE, 0xFE, 0xFE, 0x00 };
byte item, receivedValue;
int receivedBitCount;
int timeOut = 0xA23A;
int messageLength = sizeof(message);
boolean nextState;
boolean int3Flag, timedOutFlag, readComplete, dataValid;
volatile long intTime;
long pulseStart, pulseWidth;
void setup() 
{
  pinMode(18, INPUT);
  pinMode(17, OUTPUT);
  Serial.begin(38400);
  item = 0;
}

void loop() 
{
  switch (phase)
  {
    case 0:
    {
 // Update message details and start sending process
      message[messageLength - 1] = calcCheckSum(message) | (item & 0x0F);
      Serial1Start();
      phase = 1;
      break;
    }
    case 1:
    {
 // Write byte to buffer - if done, move on, else go round again
      UDR1 = message[i++];
      if (i > messageLength - 1)
      {
        i = 0;
        phase = 3;
      }
      else phase = 2;
      break;
    }
    case 2:
    {
 // Wait for buffer to be emptied - then write next one
      if (UCSR1A & (1 << UDRE1))
      {
        phase = 1;
      }
      break;
    }
    case 3:
 // Message completed - wait for shift register to be emptied.
    {
      if (UCSR1A & (1 << TXC1))
      {
        UCSR1A = B00000000 | (1 << TXC1);     // Clear TXC1 flag
        UCSR1B = B00000000;                   // Disable transmitter function
        item++;
        if (item > 3) item = 0;
 // Prepare to receive.  Start timer and initialise values.
        digitalWrite(17, HIGH);
        receivedBitCount = 0;
        receivedValue = 0;
        dataValid = false;
        readComplete = LOW;
        startTimer3();
        nextState = HIGH;
        setUpInt3(HIGH);
        phase = 5;
      }
      break;
    }
    case 5:
    {
      while (!(timedOutFlag))   // | readComplete))
      {
        digitalWrite(17, (!digitalRead(17)));
        if (int3Flag & nextState)
        {
          nextState = false;
          int3Flag = false;
          setUpInt3(nextState);
          pulseStart = intTime;
        }
 // On falling edge, calculate interval and determine bit 
 // Shift received byte 1 place to right and set/clear MS bit.
        if (int3Flag & !nextState)
        {
          int3Flag = false;
          nextState = true;
          setUpInt3(nextState);
          pulseWidth = intTime - pulseStart;
          if (pulseWidth > 400)
          {
            receivedValue = (receivedValue >> 1) | B10000000;
          }
          else
          {
            receivedValue = (receivedValue >> 1);
          }
 // Check no of bits received and terminate if done.      
          receivedBitCount++;
          if (receivedBitCount > 8)
          {
            readComplete = true;
            dataValid = true;
          }
        }     // if (int3Flag & !nextState)
      }     // while (!(timedOutFlag | )readComplete))
      
      if(timedOutFlag)
      {
        digitalWrite(17, LOW);
        readComplete = true;
        phase = 0;
        timedOutFlag = false;
      }
      phase = 6;
     break;
   }   // case 5:
   case 6:
   {
 // Response completed. Disable interrupts.    
    stopTimer3();
    stopInt3();
    if (!readComplete) Serial.println(" No response.");
    Serial.println(receivedValue, HEX);
    phase = 7;
    break;
   }
   case 7:
   {
    delay(10);
    phase = 0;
    break;
   }
  }
// Serial.println(phase);Serial.println(timedOutFlag);
  if (timedOutFlag)
  {
    digitalWrite(17, LOW);
    receivedValue = 0;
    if (!readComplete)
    {
      Serial.println(" No response.");
      readComplete = true;
    }
    timedOutFlag = false;
    readComplete = true;
    phase = 0;
  }
}

ISR(INT3_vect)
{
  intTime = micros();
  int3Flag = true;
}

void setUpInt3(boolean b)
// b = HIGH for a rising edge; LOW for a falling edge.
{
  EIMSK = EIMSK & B11110111;      // disable interrupt 
  EIFR = EIFR | B00001000;        // Clear Int3Flag ( should be done when ISR executed)
  int3Flag = LOW;   
  if (b) 
  {
    EICRA = EICRA | B11000000;    // Look for rising edge
  }
  else
  {
    EICRA = EICRA & B10111111;    // Look for falling edge
  }
  EIMSK = EIMSK | B00001000;      // Re-enable interrupt
}

void stopInt3()
{
  EIMSK = EIMSK & B11110111;      // disable interrupt 
}

ISR(TIMER3_OVF_vect)
{
  timedOutFlag = HIGH;
  TCNT3 = timeOut; 
}

void startTimer3()
{
  TCCR3A = B00000000;     // Normal mode of operaton
  TCCR3B = B00000010;     // Prescale = 1/8 - tick is 2 MHz = 0.5 us
  TCCR3C = B00000000;
  TCNT3 = timeOut;        // 0xFFFF - 0x8ACF = 0x7530 = 30,000 = 15 ms
  TIMSK3 = 1 << TOIE3;    // Enable timer overflow interrupt
  timedOutFlag = LOW;
}
void stopTimer3()
{
  TIMSK3 = 0;             // Disable timer 3 overflow interrupt
  TCCR3B = B00000000;     // Stop timer
}

void Serial1Start()
{
  UBRR1H = 0x01;          // Baud rate 2400
  UBRR1L = 0xA0;
  UCSR1A = B01000000;     // Reset TXC1
  UCSR1B = B00001000;     // Enable TX function
  UCSR1C = B00001110;     // 8 data bits: 2 stop bits
}

byte calcCheckSum(byte data[])
{
//  int dataLength = sizeof(data);
  int CS = 0;
  for (int i = 1; i < 5; i++)
  {
    CS = CS + data[i];
  }
  CS = CS + (CS >> 8);
  CS = CS + (CS << 4);
  CS = CS & 0xF0;
  return CS;
}

I hope the details around registers are sufficient. The problem occurs if the value of timeOut is changed to 0x8ACF, or anything between 0x8A6F and 0x8D9E.

My only idea as to what is happening is that there is some sort of unexpected interaction between the Serial1 function and timer functions.

The code in Reply #6 is easier to follow.

However I don't really understand the data you are trying to interpret. Can you post a drawing (rather than a 'scope trace) that represents a typical message and how your code interprets it. In particular I don't understand the role of the 12 millisecs,so please deal with that in the diagram.

As a separate issue, why are you not simply using Serial1.write() to send the data - it would seem to be much simpler.

...R

I've attached a document showing the two types of data I want to handle. However, this doesn't set out how they are sequenced in practice. From the 'scope pictures which show a transmission and the associated response, there is virtually no time delay between the completion of the transmitted command in standard 8N2 serial format, and the commencement of the 1.5-1.9 ms "start bit" for the slave response. However, each command sequence is capable of handling 4 attached slaves, and commands are sent in cycles of 4, aimed at each potential slave in turn.

If no slave is connected to the chain in that position, there is simply no response, so I have to detect that case, which is where the timeout comes in. The incoming response will take 8.8 ms for the data stream and 1 - 2 ms for the "start" bit, so 12 ms is about the minimum timeout I need to get a complete response.

The sequence I am using is based on a state transition approach. The first set of states deal with the command transmission. Once the command has been completely sent (I am using the TXC flag to signal this), the system moves on to the receive function, where it looks for a low-high transition on the pin previously used as TX1. When this is detected, the time is grabbed and the INT function changed to look for the next high-low transition. This then allows the pulse width to be calculated and the response bit set or cleared as appropriate.

I have got working code for this, and with the 12 ms delay I can manage to decipher the response strings. However, I am still baffled as to why the TXC flag is apparently triggered early if the timeout is within a fairly restricted range of values.

Meccano_SmartModuleProtocols_2015.pdf (425 KB)

From a quick look at that PDF it seems to use a system that is essentially identical to regular serial transmission with a bit interval of 417µsec which is probably 2400 baud.

Have you tried treating the data as a 2400baud serial stream?

If I was trying to detect that data I would use an interrupt to detect the start bit and then sample the following bits at 417µs intervals. I posted code that works like that in Yet Another Software Serial

It should be straightforward to check if a byte is not received within some time limit.

...R

There are two parts to the data stream

The host computer transmits a standard 2400 8N2 serial byte, then looks for a reply from the slave. The slave response is in the form of a pwm signal, with start bit of about 2 ms and bit time of 1.1 ms. A "0" is indicated by a pulse width of < 400 us, and a "1" of > 400 (in practice, times are about 300 and 800 us).

A host computer port can be connected to up to 4 slaves. These are effectively polled in turn using the last nibble of the 6-byte command stream as an address. Polling will rotate through all 4 possible slave addresses. If a slave is not present, this is detected because the signal line remains high rather than being pulled low. To cover this case, I am using a timer to indicate that the expected period has expired and the response is effectively "null".

I need to send a serial stream of 6 bytes, then disable the serial transmitter and turn the pin around to receive the slave response. I am using pin 18 on the Mega for communication, acting as TXD1 on transmit and INT3 on receive. The TXC1 flag in transmit mode indicates that the command stream has been transmitted completely, disables the transmitter, starts Timer3, and enables INT3. If there is no activity on INT3 before Timer3 times out, a "null" response is recorded.

Generating a command and interpreting the response is not the issue I intended to raise in my original post. As I said there, if I set my timer to 15 ms, the timeout starts in the middle of the process of transmitting the command stream - if it is reduced to 12 ms, it works as expected. My question is:

What is the mechanism that apparently allows the timer setting to affect the operation of the USART?

bmd1103:
There are two parts to the data stream

I was reading the section of the PDF headed "VI. The Data Stream from the MeccaBrain". If that is not the correct part, then which part is?

Life would have been a lot simpler if you posted that PDF in your Original Post.

...R

My project involves both types of communication. I am using pin 18 on an Arduino Mega in its TXD1 configuration to send a serial string as per section VI. When a transmission is completed, I am then receiving data sent from a slave device to the "Brain" as detailed in "Section VII. The Data Stream to the MeccaBrain", as detailed on page 10, using pin 18 in its INT3 configuration. As I have said, I am successfully doing this, and can send a command string from an Arduino and receive the appropriate response from slave devices.

I'm triggering the changeover process from send to receive using TXC1 to indicate that the transmitted stream has been completely sent. This simultaneously starts a timeout using Timer3 to check that a response is received within a suitable time, and sets up INT3 to handle the received stream. The process works as required except for a limited range of values in the preset I'm using for Timer3, when TXC1 is apparently triggered in the middle of transmitting the command stream. The only way I can actually detect this is through observing that the timer starts mid-command instead of at the end.

I do not need any assistance with either communication format. I am sure I am not the only one who has wanted to do something similar with an Arduino, but can't find any references to this situation. There is quite possibly a very simple explanation and I am simply trying to find out why the timer setting apparently affects the operation of the UART.

bmd1103:
My project involves both types of communication. I am using pin 18 on an Arduino Mega in its TXD1 configuration to send a serial string as per section VI. When a transmission is completed, I am then receiving data sent from a slave device to the "Brain" as detailed in "Section VII. The Data Stream to the MeccaBrain", as detailed on page 10

No wonder I am confused. Do you mean that you are pretending the Mega is the MeccaBrain?

I will try to remember to have another look at the PDF tomorrow.

...R

The Meccabrain is somewhat limited although it can be driven from a tablet via Bluetooth. Some of the peripherals are interesting and I'm looking into how they can be used. The published document mentions servos and "Smart LEDs" but the MAX robot also has "Smart motors" and an ultrasonic sensor using the same protocols.

I'm also working on a signal sniffer that can read the traffic on one of the lines which will hopefully allow me to work out how an Arduino can make use of these devices. Again, I've got the communications sorted apart from a minor timing issue.

bmd1103:
The Meccabrain is somewhat limited although it can be driven from a tablet via Bluetooth. Some of the peripherals are interesting and I'm looking into how they can be used. The published document mentions servos and "Smart LEDs" but the MAX robot also has "Smart motors" and an ultrasonic sensor using the same protocols.

I'm also working on a signal sniffer that can read the traffic on one of the lines which will hopefully allow me to work out how an Arduino can make use of these devices. Again, I've got the communications sorted apart from a minor timing issue.

I have not had time to re-study the PDF yet but I am commenting on this because it does not answer the question in my Reply #13, and a clear answer will make my study of the PDF much more effective.

...R

My objective is to use an Arduino to monitor and control the Meccano peripherals. This will allow them to be used in a much wider range of applications than the very limited ones for the kits they are supplied with.

bmd1103:
My objective is to use an Arduino to monitor and control the Meccano peripherals. This will allow them to be used in a much wider range of applications than the very limited ones for the kits they are supplied with.

I sorta figured that out a while back.

But you have still not answered the question in Reply #13 - perhaps in my confusion I have not asked the right question.

Can you post a diagram that illustrates the relationship between the Mega and the various Meccano items and the data flows between them. It would also help if you indicate which sections of the PDF relate to each of the Meccano items in the diagram.

I am very confused, but I suspect I could help if you can clarify stuff.

...R

I can't definitively produce a diagram showing how the various parts connect as I am trying to find out what potential the Meccano modules have for use with Arduino systems, and that is still a work in progress. The diagram shows the official connections to the MAX system, and what I hope to be able to do with the Arduino. As shown in the protocol document, each channel on the Meccabrain can handle up to 4 modules in positions 1 to 4, daisy-chained so that 1 is the module connected directly to the Meccabrain, 2 is connected to 1, etc. The Meccabrain unit described in the Protocol Description is an older unit with 8 channels whereas the one I am dealing with has only 4, as shown in the diagram.

Each channel uses the signal formats specified in Sections VI and VII as shown in the diagram - commands from the Meccabrain to the modules use the standard serial protocol described in Section VI while the responses from the modules to the Meccabrain use the "PWM" format described in Section VII. The Meccabrain also provides power to the modules and this places a limit on the number of modules that can be connected to each channel. Since the modules reply one at a time in turn, the existing format is very inefficient - commands are issued about every 45 ms, but each module replies once in 180 ms. If no module is connected, the others in that channel still have to wait for the full cycle.

I intend to replicate this structure, but in a more flexible way, so that any unconnected modules can be skipped, and that the full channel capacity can be used if possible.

I honestly don't know what other information I can give you - since this is an exploratory exercise intended to find out the capabilities of the system, I can't say definitely that I want to achieve a specific objective. Thanks for your time and assistance.

Your diagram is a big help - it seems like you do want the Mega to take the place of the Meccabrain - which was my question in Reply #13

However the 2 sides of your drawing are not equivalent. On the Meccabrain side there are familiar things (servo, motor. LED, sensor) all of which are regularly used with Arduinos but on the Arduno side you have Modules. I don't understand why the two sides are different.

If the ultimate purpose of the Meccabrain system is to control servos and motors then why not ditch the whole system and just use the Arduino to control those things directly - cut out the middle-man. My guess is that the Meccabrain was designed for those unfortunate folks who don't have Arduinos :slight_smile:

...R