Direct write to txdata register in Arduino Nano Every

NANO EVERY QUESTION (not NANO)

Dear colleagues, my first post here. Please bear with any ignorance or stupidity revealed.
I have ancient experience of writing assembler for various CPUs, including DSP Harvard types, but I struggle with compilers and what they get up to exactly interfacing to 'the metal'.
:slight_smile:

I want to transmit a single serial byte inside an interrupt, to emerge on pin 1 TX/D0. The interrupt is driven by a timer and is currently working to read the analogue result. The baud rate I have set is VERY MUCH faster than the repeat rate of the interrupt, so I am completely sure the TX buffer will be empty, therefore there should be no need for a test, just poke the byte into the register. (250k baud with ints repeating at 200µs intervals, a transmit byte lasts 44µs). The serial port is setup outside the ISR, in setup, with Serial1.begin(250000, SERIAL_8N2);

All the variables are volatile type. As I said, the ADCO read part is working properly.

Is USART.TXDATAL a valid register name in the Arduino IDE? If not, then am I missing an #include? Why does ADC0.RESRDY compile but USART1.TXDATAL does not? My IDE has 328 register emulation ON, but with native 4809 I still get the same message "Compilation error: expected primary-expression before 'DMXoutbuffer'"

Yes, I know that the in-built Serial function cannot normally work inside an Interrupt. Please don't mention the later lines where I commented that out, I know I need a different method there.

This is my ISR:

// Interrupt service routine for the ADC completion
ISR(ADC0_RESRDY_vect) {

  // Must read low first
  inttemp0 = ADC0.RESL;
  inttemp1 = (ADC0.RESH << 8);
  inttemp0 = inttemp0 + inttemp1;

  if (inttemp0 > peakval) {
    peakval = inttemp0;
 }
  

  // placeholder for timed DMX transmission interrupts
  if (DMXoutcounter <512)
  {
    USART1.TXDATAL = (byte DMXoutbuffer[DMXoutcounter]);
    DMXoutcounter ++;
  }
  if (DMXoutcounter == 512)
  {
    //serial.begin(57600);
    //serial.write(0);
    DMXoutcounter ++;
  }
  if (DMXoutcounter == 513)
  {
    //serial.begin(250000,SERIAL_8N2);
    //serial.write(0); // send start code
    DMXoutcounter = 0;
  }
}

Kind thanks in advance for any reply.
Adam Bennette

that looks wrong. Maybe should be:

USART1.TXDATAL = (byte) DMXoutbuffer[DMXoutcounter];

But the cast shouldn't be necessary. Hard to say without more of the code. (hint, hint)

Is USART.TXDATAL a valid register name in the Arduino IDE?

USART0.TXDATAL and USART1.RXDATAL should work without additional includes.

the following compiles without an errors:

volatile byte DMXoutbuffer[20];
volatile int DMXoutcounter;

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  USART1.TXDATAL = DMXoutbuffer[DMXoutcounter];
}
void loop() {}

consider

ISR (ADC0_RESRDY_vect)
{
    inttemp0  =  ADC0.RESL;
    inttemp0 |= (ADC0.RESH << 8);

    if (inttemp0 > peakval)
        peakval = inttemp0;

    USART1.TXDATAL =  DMXoutbuffer[DMXoutcounter];

    if (512 <= ++DMXoutcounter)
        DMXoutcounter = 0;
}

the above suggests that a 16-bit ADC value is transmitted within the ISR

  • where is the ADC conversion started? (at the end of the ISR)?
  • why is a value from DMXoutbuffer transmitted instead of the ADC value?
  • if the ADC value is 16-bits and presumably intended to be copied into the DMXoutbuffer[], why does it look like only a single byte is transmitted?
  • if multiple bytes need to be transmitted, how are the bytes identified?

Hi,
thank you for the replies, your comments gave me hope, so I tried again.

This compiles:

USART1.TXDATAL = DMXoutbuffer[DMXoutcounter];

This does not
USART1.TXDATAL = (byte DMXoutbuffer[DMXoutcounter]);
I suspect, but do not know, that the problem is casting DMXoutbuffer as byte when the DMXoutcounter is an int, so there are mixed data types in the expression. I am guessing the compiler does not like that.

Greg:
There are two separate things happening in the interrupt. The original purpose is to capture the ADC and do a peak detect. In the main loop this value is read asynchronously with: (pseudocode) cli(); copy peakval; clr peakval; sei();
Once I had that working I piggybacked my DMXtransmit into the same ISR for a free ride, since the refresh rate of the ISR is by ADC completion and occurs at a near-ideal rate for moderately spaced-out DMX.
The DMX transmit picks its data from a 512 bytes buffer and is completely separate from the Analogue function.

Since it was a royal pain to work out the register setup for the ADC in the '4809, here is my working code for that in case anyone is interested.
The '328 version I got from a forum and I'm ashamed to say I can't attribute it to the author. I took their work and worked through the 328 and 4809 docs in parallel to find the equivalents.


//NANO
//setup the AtoD converter to do auto conversion of A0 input every 100µs
//turn off the pull-up on the pin used for analog input
//DDRD =
// clear ADLAR in ADMUX (0x7C) to right-adjust the result
// ADCL will contain lower 8 bits, ADCH upper 2 (in last two bits)
//ADMUX &= B11011111;

// Set REFS1..0 in ADMUX (0x7C) to change reference voltage to the
// proper source (01)
//ADMUX |= B01000000;

//NANO EVERY
ADC0.CTRLC = ADC0.CTRLC | 0b00010000;
////////////////////////////////////////////////////////////////////////////

//NANO
// Clear MUX3..0 in ADMUX (0x7C) in preparation for setting the analog
// input
//ADMUX &= B11110000;

//NANO EVERY
ADC0.MUXPOS = ADC0.MUXPOS & 0b11100000;
ADC0.MUXPOS = ADC0.MUXPOS | 0b00000011; // ADC input 3 on pin 4

////////////////////////////////////////////////////////////////////////////

//NANO
// Set MUX3..0 in ADMUX (0x7C) to read from AD8 (Internal temp)
// Do not set above 15! You will overrun other parts of ADMUX. A full
// list of possible inputs is available in Table 24-4 of the ATMega328
// datasheet
//ADMUX |= 8;
// ADMUX |= B00001000; // Binary equivalent

// Set ADEN in ADCSRA (0x7A) to enable the ADC.
// Note, this instruction takes 12 ADC clocks to execute
//ADCSRA |= B10000000;

//NANO EVERY
ADC0.CTRLA = ADC0.CTRLA | 0b00000001;

/////////////////////////////////////////////////////////////////////////////

//NANO
// Set ADATE in ADCSRA (0x7A) to enable auto-triggering.
//ADCSRA |= B00100000;

//NANO EVERY
ADC0.CTRLA = ADC0.CTRLA | 0b00000010;

////////////////////////////////////////////////////////////////////////////

//NANO
// Clear ADTS2..0 in ADCSRB (0x7B) to set trigger mode to free running.
// This means that as soon as an ADC has finished, the next will be
// immediately started.
//ADCSRB &= B11111000;

// Set the Prescaler to 128 (16000KHz/128 = 125KHz)
// Above 200KHz 10-bit results are not reliable.
//ADCSRA |= B00000111;

//NANO EVERY
ADC0.CTRLC = ADC0.CTRLC | 0b00000110;

/////////////////////////////////////////////////////////////////////////

//NANO
// Set ADIE in ADCSRA (0x7A) to enable the ADC interrupt.
// Without this, the internal interrupt will not trigger.
//ADCSRA |= B00001000;

//NANO EVERY
ADC0.INTCTRL = ADC0.INTCTRL | 0b00000001;

// Enable global interrupts
// AVR macro included in <avr/interrupts.h>, which the Arduino IDE
// supplies by default.
sei();

////////////////////////////////////////////////////////////////////////////////

//NANO
// Kick off the first ADC
// Set ADSC in ADCSRA (0x7A) to start the ADC conversion
//ADCSRA |=B01000000;

//NANO EVERY
ADC0.COMMAND = ADC0.COMMAND | 0b00000001;
//

Thanks again for your help. Now I have to figure out how to change the baud rate inside the interrupt for sending the break.

what do you expect this to do? a cast would be

  USART1.TXDATAL = (byte) DMXoutbuffer [DMXoutcounter];

i'm curious.
what is your application?
why use an interrupt rather than a timer loop in main?
and why is bit-rate related to break?

I got the casting wrong.

My application is a DMX lighting controller (transmit only, no RDM).
I cannot afford time to wait on timers in the main loop, which has a lot of work to do to maintain a decent real-time refresh (I am aiming for 20Hz).
DMX512 is 250k baud, but is packetised with a break of minimum 92µs which is not directly transmissible by the UART - unless the baud rate is reduced the point where a value of 0x00, transmitted normally, has a duration of at least 92µs. The other way is to use a separate port pin to actively zero the tx output via an AND gate or suchlike = lost pin and additional external parts., hence the reduced baud rate idea (that is not my idea, I have seen this mentioned in several places).

at 8N1 for a 0x00 to last 92µs each bit must be 9.2µs ~ 108kbaud. at 8N2 it is ~119kbaud. It is allowed and sometimes preferable to extend the break time in the DMX512 spec, so I would send the break at 57600 baud 8N2 ~ 173µs. Once the break is sent, the baud rate of 250k is re-established to send the start code and next data block.

Thanks,
Adam

50 msec doesn't seem very tight

it's 512 channels at 20Hz.
~ 100µs between transmitted bytes. The main loop deals with UI and an OLED, not to mention a host of level calculations. In grown-up lighting controllers this is typically done with DSPs, ARMs or Pentiums. (but they have more than one DMX universe, each at 44Hz refresh, 44µs per byte.
Adam

i spent a decade+ programming DSPs and a few years working on ARM

do the transmitted bytes for each channel need to be separate or can then be a sequence of 512 byte + some delimiter (break?) sent every 50 msec?

interrupts consume cycles to store and then restore processor registers. avoiding an interrupt except for aperiodic activity will be more efficient.

at 8N1 for a 0x00 to last 92µs each bit must be 9.2µs ~ 108kbaud. at 8N2 it is ~119kbaud. It is allowed and sometimes preferable to extend the break time in the DMX512 spec, so I would send the break at 57600 baud 8N2 ~ 173µs.

That is incorrect. I was counting the stop bit as a low, but it is high. The number of stop bits is irrelevant for this purpose.

This is correct:
at 8Nx for a 0x00 to last 92µs each bit must be 10.22µs ~ 98kbaud. It is allowed and sometimes preferable to extend the break time in the DMX512 spec, so I would send the break at 57600 baud 8Nx ~ 156µs.

I take the point that ISRs are potentially greedy of cpu cycles if used at high repetition rates. Solutions to this are non-trivial as DMA tends to be Too Good at spitting out bytes with no idle gaps and many legacy DMX receivers have trouble with that. It is common in professional DMX transmitters to provide a user-variable pad time between bytes, i.e. extend the idle after the last stop bit of each byte, which cannot usually be implemented other than in a timed interrupt.

Adam

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.