MCUdude / MegaCoreX

An Arduino hardware package for ATmega4809, ATmega4808, ATmega3209, ATmega3208, ATmega1609, ATmega1608, ATmega809 and ATmega808
GNU Lesser General Public License v2.1
239 stars 47 forks source link

Any way of using Half-Duplex Serial? #197

Open jonathanmlang opened 3 months ago

jonathanmlang commented 3 months ago

I cannot seem to find any way of implementing Half-Duplex serial. Is it supported? Can't find any mention of it anywhere in this library even though the atmega4809 datasheet mentions support for it. Ive come from the spence konde megatinycore and I can do:

Serial1.begin(9600, (SERIAL_8N1 | SERIAL_HALF_DUPLEX));

and that works great for attiny chips, but that does not work here and cannot find an alternative. Can you help?

MCUdude commented 3 months ago

It's currently not supported

jonathanmlang commented 3 months ago

How about in the softwareserial library?

MCUdude commented 3 months ago

Sorry, but the SoftwareSerial library doesn't support it either.

jonathanmlang commented 3 months ago

Ive managed to add support myself. It was trivial.

MCUdude commented 3 months ago

A PR is always welcome if you want to contribute to the project. Or if you aren't as good with the whole git thing, sharing the details here also helps.

jonathanmlang commented 3 months ago

At line 255 in UART.cpp I changed (*_hwserial_module).CTRLA |= USART_RXCIE_bm; to (*_hwserial_module).CTRLA |= (USART_RXCIE_bm | USART_LBME_bm);

Then commented out the pinmodes

// Set pin state for swapped UART pins pinMode(set->rx_pin, INPUT_PULLUP); //digitalWrite(set->tx_pin, HIGH); //pinMode(set->tx_pin, OUTPUT);

then when sending:

pinMode(PIN_PC4,1); Serial1.write(array, 24); delay(10); //give the serial chance to get sent while (Serial1.available()){byte d = Serial1.read();} // sent serial gets looped back, dump it into disposable variable pinMode(PIN_PC4,0); digitalWrite(PIN_PC4,0);

SpenceKonde commented 2 months ago

You need to set ODME as well to get half duplex in most applications, because usually you need open drain mode, otherwise the to devices may fight eachother over the state of the line, potentially damaging one of them. I think on mega0's you also need to make sure the PORTx.OUT bit corresponding to the data line being used by serial is cleared, otherwise it can try to drive the pin high even in open drain mode... similar to the bug with TWI (they overrode the direction, but forgot to override the output value)

Oh and wait, no, it's definitely not that simple, not at all, because in half duplex mode you'll receive every character you send. I went to a great deal of effort to blackhole the echo. That was a pain in the arse to do - I think I had write() turn off RXEN and turn on the TXC interrupt after clearing the TXC flag. And TXC int would... turn off TXC int, read from RXDATA until RXCIF is cleared and turn on RXCIE I think? Important to not clear TXC, because we need TXC set at all times that we have finished sending data. flush() needs to be able to know whether data is currently being sent. That data is not exposed. So it stores a flag in a variable the first time it is written to, and flush() doesn't wait at all if that's not set because if nothing was sent to the serial port, you know nothing is being sent right now. If data has been sent, flush waits for TXC flag to be set. So for flush to work you must clear TXC only in write(), and if you ever use the TXC interrupt (which as I said, you need to do if you don't want to hear yourself talk), instead of clearing TXC, it should turn itself off and the write should reenable it.

And of course like all the ISRs, TXC is written in asm so I can do unholy things that save flash and improve performance. And, I know I said

I may have gone a little overboard on the USART feature support.