Go Down

Topic: Serial transmission through SERCOM registers timing (Read 2045 times) previous topic - next topic

AH17

I attempted to write a version of Serial.print() by using the SERCOM5 registers and timed it through port manipulation.

Code: [Select]


      void serialPrint(short data) {
        int hunds = 0;
        int tens = 0;
        int ones = 0;
          hunds = (data/100);
         tens = ((data - hunds*100)/10);
          ones = (data-hunds*100-tens*10);
          printint(hunds+48);
          printint(tens+48);
        //  digitalWrite(expPin2,HIGH);
          printint(ones+48);
        //    digitalWrite(expPin2,LOW);
          printint('\r');
          printint(10);
       }

      void serialSetup() {
         SERCOM5->USART.CTRLA.bit.MODE = SERCOM_USART_CTRLA_MODE_USART_INT_CLK;
         SERCOM5->USART.CTRLB.bit.CHSIZE = UART_CHAR_SIZE_5_BITS;
         SERCOM5->USART.CTRLA.bit.TXPO = UART_TX_PAD_0;
         SERCOM5->USART.CTRLA.bit.SAMPR = SAMPLE_RATE_x3;
         SERCOM5->USART.CTRLA.bit.ENABLE = 1;
         SERCOM5->USART.INTENCLR.bit.RXC = 1;
         SERCOM5->USART.INTENCLR.bit.RXS = 1;
         SERCOM5->USART.INTENCLR.bit.RXBRK = 1;
         SERCOM5->USART.CTRLB.bit.RXEN = 0;
      }

void printint(short data) {
       REG_PORT_OUTSET0 = PORT_PA07;     // Set port pin PA07 HIGH
       SERCOM5->USART.DATA.bit.DATA = data;
       REG_PORT_OUTCLR0 = PORT_PA07;     // Set port pin PA07  LOW
       SERCOM5->USART.CTRLB.bit.TXEN = 1;
       REG_PORT_OUTSET0 = PORT_PA06;     // Set port pin PA06 HIGH
       while (SERCOM5->USART.INTFLAG.bit.DRE != SERCOM_USART_INTFLAG_DRE){}
       REG_PORT_OUTCLR0 = PORT_PA06;     // Set port pin PA06 LOW
       SERCOM5->USART.INTFLAG.bit.DRE = SERCOM_USART_INTFLAG_DRE;
       Sercom flushUART();
      }



I attached a screenshot of the scope.  The top signal indicates when the new serialPrint function is called, and the other two are shown in the code above.  I'm wondering why the first call of printint is always significantly shorter than the following four (.4 us vs 3.64 us), and if there is a way to change that.  I feel like I could just be missing setting some registers(s), but I'm sure there could be other causes as well.  I'm probably doing unnecessary things as well because I wanted to experiment and see if they changed anything.  Thanks!
     

Sulimarco

I guess this is due to the behavior of the DRE bit that you poll in the following line:

Code: [Select]
while (SERCOM5->USART.INTFLAG.bit.DRE != SERCOM_USART_INTFLAG_DRE){}

Anyway if you need help you may have some chance only if you post the full sketch.

Marco

AH17

Sorry, I was hoping that would be enough.  Here's the full thing.  I've been messing around with a lot of things though, so I'm sure I have variables I'm not using anymore and unnecessary steps, I was planning on cleaning it up when the serial print function works. 

Code: [Select]

#include "Arduino.h"
#include "Uart.h"
#include "SERCOM.h"
#include "HardwareSerial.h"
#include "RingBuffer.h"
#include "sercom.h"
#include "variant.h"

#include "sam.h"

static __inline__ void ADCsync() __attribute__((always_inline, unused));
static void   ADCsync() {
  while (ADC->STATUS.bit.SYNCBUSY == 1);
}
static __inline__ void syncADC() __attribute__((always_inline, unused));
static void syncGCLK() {
  while (GCLK->STATUS.bit.SYNCBUSY == 1); //Just wait till the ADC is free
}
uint32_t anaPin = A1; // ADC channel
uint32_t val = 0;
uint32_t dac = 80;   // This is the dac value

int upChirpTime = 20000;  // in microseconds
int downChirpTime = 20000;  // in microseconds
int sampleRate = 48000;  // in hertz, NOTE: decrease to 48000 if upChirpTime+downChirpTime > 30000
const int syncPin = 12;
const int testPin = 11;
const int expPin = 10;
int count = 0;
int inputPin = A1;
int expPin2 = 9;
int expPin3 = 8;

volatile int sIndex;  // tracks points in array
int upChirpSampleCount = sampleRate * upChirpTime * 1e-6; // number of samples in the up chirp ramp
int downChirpSampleCount = sampleRate * downChirpTime * 1e-6; // number of samples in the down chirp ramp
int sampleCount = upChirpSampleCount + downChirpSampleCount;  // total number of samples
int *rampSamples; // array to store ramp points
int *syncSamples; // array to store sync points
short *recSamples; // array to store sync points
int sendCheck = 0;
int numSamps = 0;
int countPulses = 1;
int printCount = 0;
int loopCount = 0;
int sendEn = 0;
int j;
int starts = 0;
int ends = 0;
int vals = 0;

void setup() {
  Serial.begin(1843200);
  pinMode(syncPin, OUTPUT);
  pinMode(testPin, OUTPUT);
  pinMode(expPin, OUTPUT);
  REG_PORT_DIRSET0 = PORT_PA07;
REG_PORT_DIRSET0 = PORT_PA06;
  analogWriteResolution(10);
  analogReadResolution(12);
  Serial.println();
  Serial.println("ready to begin");
  rampSamples = (int *) malloc(sampleCount * sizeof(int)); // allocate the buffer where the ramp samples are stored
  syncSamples = (int *) malloc(sampleCount * sizeof(int)); // allocate the buffer where the sync samples are stored
  genRamp(sampleCount, upChirpSampleCount, downChirpSampleCount); // calls function to calculate ramp values and store into memory
  recSamples = (short *) malloc(sampleCount * sizeof(int)); // allocate the buffer to store the received samples

  Serial.println(upChirpSampleCount);
  analogRead(inputPin);
  selAnalog(inputPin);
  fastADCsetup();
  serialSetup();
}

void loop() {
  tcConfigure(sampleRate); // setup the timer counter based off of the sample rate
  while (sendEn == 1) {
  __enable_irq();
    printCount++;
  }
  while (sIndex < upChirpSampleCount)
  {
      __disable_irq();
  }
     tcStartCounter();
  tcDisable();
  tcReset();
  count++;
}

void genRamp(int sCount, int sCountUp, int sCountDown) {
  for (int i = 0; i < sCount; i++) { // loop to calculate the ramp based on sample count
    if (i < sCountUp) {
      rampSamples[i] = (int)(1023.0 / sCountUp * i); // for 10-bit DAC
      syncSamples[i] = 1;
    }
    else {
      rampSamples[i] = (int)(-1023.0 / sCountDown * (i - sCountUp) + 1023); // for 10-bit DAC
      syncSamples[i] = 0;
    }
  }
}

void tcConfigure(int sampleRate)
{
  GCLK->CLKCTRL.reg = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCM_TC4_TC5)) ;
  while (GCLK->STATUS.bit.SYNCBUSY);
  tcReset(); //reset TC5
  TC5->COUNT16.CTRLA.reg |= TC_CTRLA_MODE_COUNT16;
  TC5->COUNT16.CTRLA.reg |= TC_CTRLA_WAVEGEN_MFRQ;
  TC5->COUNT16.CTRLA.reg |= TC_CTRLA_PRESCALER_DIV1 | TC_CTRLA_ENABLE;
  TC5->COUNT16.CC[0].reg = (uint16_t) (SystemCoreClock / (sampleRate/1) - 1);
  while (tcIsSyncing());  // wait until TC5 is done syncing
  NVIC_DisableIRQ(TC5_IRQn);
  NVIC_ClearPendingIRQ(TC5_IRQn);
  NVIC_SetPriority(TC5_IRQn, 0);
  NVIC_EnableIRQ(TC5_IRQn);
  TC5->COUNT16.INTENSET.bit.MC0 = 1;
  while (tcIsSyncing());  // wait until TC5 is done syncing
}

bool tcIsSyncing()
{
  return TC5->COUNT16.STATUS.reg & TC_STATUS_SYNCBUSY;
}

void tcStartCounter()
{
TC5->COUNT16.CTRLA.reg |= TC_CTRLA_ENABLE;  // set the CTRLA register
  while (tcIsSyncing());  // wait until snyc'd
}

void tcReset()
{
  TC5->COUNT16.CTRLA.reg = TC_CTRLA_SWRST;
  while (tcIsSyncing());
  while (TC5->COUNT16.CTRLA.bit.SWRST);
}

void tcDisable()
{
  TC5->COUNT16.CTRLA.reg &= ~TC_CTRLA_ENABLE;
}

void TC5_Handler (void)
{
  analogWrite(A0, rampSamples[sIndex]);
  digitalWrite(syncPin, syncSamples[sIndex]);
  loopCount++;
  TC5->COUNT16.INTFLAG.bit.MC0 = 1;
  if (sIndex  == 0) {
    countPulses++;
    numSamps = 0;
    int f = 0;
      while (f<upChirpSampleCount) {
    digitalWrite(expPin,HIGH);
      serialPrint(f);
     digitalWrite(expPin,LOW);
     f++;
    }
  }
   sIndex++; 
 if (sIndex == upChirpSampleCount+downChirpSampleCount) {
    sIndex = 0;
    printCount = 0;
  }
  RingBuffer buff;
  if ((sIndex < upChirpSampleCount) ) {
    digitalWrite(testPin,HIGH);
    recSamples[sIndex] = (short)anaRead();
    digitalWrite(testPin,LOW);
    sendEn = 0;
  }
  else {
    sendEn = 1;
  }
}

uint32_t selAnalog(uint32_t ulPin){      // Selects the analog input channel in INPCTRL
ADCsync();
ADC->INPUTCTRL.bit.MUXPOS = g_APinDescription[ulPin].ulADCChannelNumber; // Selection for the positive ADC input
}

uint32_t fastADCsetup() {
  ADCsync();
  ADCsync();
  ADCsync();
  ADC->AVGCTRL.reg = 0x00 ;       //Single conversion no averaging
  ADCsync();
  ADC->SAMPCTRL.reg = 0x00;       //Minimal sample length is 1/2 CLK_ADC cycle
  syncGCLK();
  GCLK->CLKCTRL.reg = 0x431E; //enable GGCLK for ADC, CLKGEN3 = 8 MHz oscillator
  syncGCLK();
  ADCsync();
  ADC->CTRLB.reg =  0x000     ; // Prescale 4, 12 bit resolution, single conversion
  ADCsync();
  ADC->CTRLA.bit.ENABLE = 0x01; 
}

void serialPrint(short data) {
  int hunds = 0;
  int tens = 0;
  int ones = 0;
    hunds = (data/100);
   tens = ((data - hunds*100)/10);
    ones = (data-hunds*100-tens*10);
    printint(hunds+48);
    printint(tens+48);
    printint(ones+48);
    printint('\r');
    printint(10);
 }


      void printint(short data) {
           REG_PORT_OUTSET0 = PORT_PA07;     // Set port pin PA07 to 1 / HIGH
       SERCOM5->USART.DATA.bit.DATA = data;
           REG_PORT_OUTCLR0 = PORT_PA07;     // Set port pin PA07 to LOW
       SERCOM5->USART.CTRLB.bit.TXEN = 1;
           REG_PORT_OUTSET0 = PORT_PA06;     // Set port pin PA06 to 1 / HIGH
      while (SERCOM5->USART.INTFLAG.bit.DRE != SERCOM_USART_INTFLAG_DRE){}
           REG_PORT_OUTCLR0 = PORT_PA06;     // Set port pin PA06 to LOW
      SERCOM5->USART.INTFLAG.bit.DRE = SERCOM_USART_INTFLAG_DRE;
        Sercom flushUART();
      }
     
      void serialSetup() {
          SERCOM5->USART.CTRLA.bit.MODE = SERCOM_USART_CTRLA_MODE_USART_INT_CLK;
        SERCOM5->USART.CTRLB.bit.CHSIZE = UART_CHAR_SIZE_5_BITS;
          SERCOM5->USART.CTRLA.bit.TXPO = UART_TX_PAD_0;
        SERCOM5->USART.CTRLA.bit.SAMPR = SAMPLE_RATE_x3;
         SERCOM5->USART.CTRLA.bit.ENABLE = 1;
         SERCOM5->USART.INTENCLR.bit.RXC = 1;
         SERCOM5->USART.INTENCLR.bit.RXS = 1;
         SERCOM5->USART.INTENCLR.bit.RXBRK = 1;
         SERCOM5->USART.CTRLB.bit.RXEN = 0;
      }

uint32_t anaRead() {
  ADC->INTFLAG.bit.RESRDY = 1;              // Data ready flag cleared
  ADC->SWTRIG.bit.START = 1;                // Start ADC conversion
  while ( ADC->INTFLAG.bit.RESRDY == 0 );   // Wait till conversion done
  ADCsync();
  uint32_t valueRead = ADC->RESULT.reg;     // read the result
  ADCsync();
   return valueRead;
}


Sulimarco

Hi AH17,

thanks for having posted your code.

What you see is the normal behavior of the USART and specifically of the DRE flag.
The DRE flag signals when the DATA register is empty, and then when is possible to write a new character in it.

As soon as a character is written in DATA it is immediatly transferred into the TX shift register if the USART is not already trasmitting sometingh.
So when you write the first character the DRE flag is set almost immediatly, but, when you write the others, the USART has first to complete the previous character shift out and then it will set the DRE flag.

See USART block diagram attachment (from the datasheet)

If you like to see all your test pulses with the same length you can change your printint function with the following code

Code: [Select]
      void printint(short data)
      {
        REG_PORT_OUTSET0 = PORT_PA07;             // Set port pin PA07 to 1 / HIGH
        SERCOM5->USART.DATA.bit.DATA = data;
        REG_PORT_OUTCLR0 = PORT_PA07;             // Set port pin PA07 to LOW
               
        REG_PORT_OUTSET0 = PORT_PA06;             // Set port pin PA06 to 1 / HIGH
        while(!SERCOM5->USART.INTFLAG.bit.TXC){}  // wait until all bits are shifted out       
        REG_PORT_OUTCLR0 = PORT_PA06;             // Set port pin PA06 to LOW       
      }



but nothing changes in the real behaviour, and it is also a little bit slower.


I didn'd dig into your code, I just had a look at the serial part, but I have seen many strange things.


I just point out the following:

1) There is no need to reinitializie SERCOM5 as it has already been initialized by Serial.begin

2) Anyway take into account that CTRLA and CTRLB register are enable-protected, i.e. they can only be changed when the USART module is disabled, so your initialization has almost no use.

3) There is no need to enable TX every time you send a character: it is sufficent to do this one time (and this is already done in Serial.begin.

4) The USART can trasmit a character made by 5 to 9 bit (depending on configuration), but no more, so thake care of what you are trying to send.



Marco
 















AH17

Hi Marco,

Thank you very much for your response!  Your description of the DRE flag makes a lot of sense, I appreciate it.  Based on that, do you know of a way to write a faster serial print?  I would also like to ensure the characters send when I write them to the DR, so checking the TXC bit is useful.  I'm also confused by the BAUD register value that I found through Atmel Studio.  Of course you set a baud rate in Serial.begin, but the documentation on the baud register and the different modes confuses me, and I'm not sure where the value in the register is coming from. 


The initialization and Serial.begin aren't meant to both still be there, they're from when I was testing out other things and I just haven't cleaned it up yet because I figured it didn't hurt (I could be wrong).  About 4, I believe I set it to use 5 bits per character, and will not need more than that for now.  Thanks for the information and suggestions though! 

david_prentice

Your baudrate is determined by what the other side will accept.  e.g. 9600,  ... 115200.

The USART will be able to transmit at that particular rate without any gaps.

In practice your ARM will be able to handle all the USART comms via interrupts.    And you can get on with the rest of your program at the same time.

David.

Sulimarco

Hi,


Quote
Posted by david_prentice  - Today at 06:58 am
 The USART will be able to transmit at that particular rate without any gaps.
I agree with David and this already happens in your code, so you already run as fast as possible.


Quote
Posted by AH17  - Jul 20, 2016, 09:15 pm
  I'm also confused by the BAUD register value that I found through Atmel Studio.  Of course you set a baud rate in Serial.begin, but the documentation on the baud register and the different modes confuses me, and I'm not sure where the value in the register is coming from. 
I agree that setting the baud rate through direct registers access is not very simple, but I don't understand if you don't trust what Serial.begin does or if don't want to use it, for personal reasons, or if you just want to learn about this issue.

Quote
Posted by AH17  - Jul 20, 2016, 09:15 pm
About 4, I believe I set it to use 5 bits per character, and will not need more than that for now.  Thanks for the information and suggestions though!
If you really want to use 5 bit per character, remember that you have first to disable the USART module before setting this option.
Remember also that 5 bits per character allows you to transmit the numbers from 0x00 to 0x1F (from 0 to 31 in decimal) and no more.


Marco
 
 

AH17

@david_prentice: Did you put what looks like the upper limit of baudrate as 115200 because of Arduino's serial monitor, or was that not supposed to be an upper limit?  I'm using Putty instead of Arduino's serial monitor, so I think the baudrate is able to exceed that, but I could easily be wrong. 


@Marco:  Sorry that was confusing, I would really just like to learn about the issue and the registers use.  And to disable the USART, do you just mean writing a 0 to the enable bit in the CTRLA register or something else?  I briefly tried manipulating the enable bit to disable it, but the program got stuck in that line of code in the Atmel Studio debugger. 

Thank you both for your responses!

david_prentice

I wrote 115200 because it is a common baudrate.    If you are using a real RS232 the driver chips have speed limits e.g. slew rate means 230400 is probably as fast as you can go.

If you are hard wiring two UARTs you can probably manage baudrates in MHz.

If you said what you actually want to do,    there might be better ways to do it.

Any real world comms link might have noise or errors.    You need some form of protocol and recovery.   It is not much good if you transmit MBytes at high speed but a single bit error destroys the whole caboodle.

David.

Sulimarco

Quote
Posted by AH17  - Jul 22, 2016, 02:34 pm
@Marco:  Sorry that was confusing, I would really just like to learn about the issue and the registers use.
Don't worry, I appreciate that you are experimenting to increase your knowledges.

Quote
Posted by AH17  - Jul 22, 2016, 02:34 pm And to disable the USART, do you just mean writing a 0 to the enable bit in the CTRLA register or something else?
Yes I mean that, as stated in the 26.6.3.1. Initialization paragraph of the datasheet.

Quote
Posted by AH17  - Jul 22, 2016, 02:34 pm I briefly tried manipulating the enable bit to disable it, but the program got stuck in that line of code in the Atmel Studio debugger.
This is because SERCOM5 is used by the EDBG chip (and then by the debugger) to communicate with the microprocessor.
I think it is better to use another SERCOM block to experiment, If you want to debug it.

Marco

 



AH17

David and Marco, thank you so much again!

 I am trying to transmit ~1000 integers in less than 20ms, preferably even less time if possible.  I just used this code to estimate the time it'd take to send 1000 integers with this baud rate, and found it to be 32266 us, or ~32 ms.  I realize this isn't the most accurate way to test the time it takes, but I'm assuming it's close enough to see that Serial.print isn't fast enough as is, correct me if I'm wrong.  I also used a digital oscilloscope to verify this.

Code: [Select]
int count = 0;
int outPin = 9;
int startTime = 0;
int endTime = 0;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(1843200);
  pinMode(outPin, OUTPUT);
  startTime = micros();
}

void loop() {
  // put your main code here, to run repeatedly:

  if (count < 1000) {
    digitalWrite(outPin,HIGH);
    Serial.println(count);
    digitalWrite(outPin,LOW);
  }
  else if (count == 1000) {
    endTime = micros();
    Serial.println(endTime-startTime);
  }
  else {

    while(1);
  }
  count++;
}


I've experimented with SPI a little bit also using the digital oscilloscope I have, but am having issues with the logging option on the scope, so if there is a way to do this through a UART I'd rather do that. 

david_prentice

#11
Jul 26, 2016, 08:18 am Last Edit: Jul 26, 2016, 08:30 pm by david_prentice
Think about it.    A signed long is any number between -2147483648 and +2147483647.
This up to 12 characters with the newline.    Smaller numbers are shorter strings e.g. "123"

Send 1000 of these strings at 115200 baud will take 1041ms.

On an AVR an int is a int16_t i.e. -32768 ... +32767 or up to 7 characters in the string with the newline.

You can of course send the 4 raw bytes that make up an int32_t or the 2 raw bytes that make up an int16_t.
In which case you probably don't want to send a newline.     The output is not human-readable.   The other end must be able to understand raw bytes.

David.

Sulimarco

If you want to check what comes out from the USART put one probe of your scope on pin 37 of the microprocessor: that is the USART tx and it is connected to a resistor very close to it.

Marco

Go Up