I'm trying to figure out how exactly does serial transmission work (ATmega328), so I started reading source code. Specifically, I'm most interested in Serial.write(). So to begin, this is Serial write source:
size_t HardwareSerial::write(uint8_t c)
{
int i = (_tx_buffer->head + 1) % SERIAL_BUFFER_SIZE;
// If the output buffer is full, there's nothing for it other than to
// wait for the interrupt handler to empty it a bit
// ???: return 0 here instead?
while (i == _tx_buffer->tail)
;
_tx_buffer->buffer[_tx_buffer->head] = c;
_tx_buffer->head = i;
sbi(*_ucsrb, _udrie);
// clear the TXC bit -- "can be cleared by writing a one to its bit location"
transmitting = true;
sbi(*_ucsra, TXC0);
return 1;
}
What am I assuming: Serial.write doesn't mess with registers directly, it actually stores received data (byte) into transmission buffer. I am not so good at pointers, but I am assuming that received byte's address is now head. After the pointer stuff, TXC0 bit gets written in UCSR0A register.
This is what datasheet says about TXC0 bit:
TXCn: USART Transmit Complete
This flag bit is set when the entire frame in the Transmit Shift Register has been shifted out and there are no new
data currently present in the transmit buffer (UDRn). 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. The TXCn Flag can generate
a Transmit Complete interrupt (see description of the TXCIEn bit).
Pretty confused here. Anyways, what does happen after calling Serial.write? It should be some interrupt function but I can't figure out which one (looking in HardwareSerial.cpp).
The hardware serial library has changed a lot over the years and is now fully buffered and interrupt driven on both sending and receiving of the serial data. Other then just knowledge one really doesn't have to understand how it works to utilized the Serial.xxx() functions supplied for the user's use.
If you just want to learn how it all works you will have to wait for another's reply as my favorite programming language is solder.
retrolefty:
The hardware serial library has changed a lot over the years and is now fully buffered and interrupt driven on both sending and receiving of the serial data. Other then just knowledge one really doesn't have to understand how it works to utilized the Serial.xxx() functions supplied for the user's use.
If you just want to learn how it all works you will have to wait for another's reply as my favorite programming language is solder.
I'm asking because I'm writing my code in pure AVR C, and while I understand the reasons behind making serial receiving interrrupt driven, I don't understand how/why is transmission is interrupt driven. That is, what I want to know precisely is when does Arduino remove data from TX buffer and sends it.
retrolefty:
The hardware serial library has changed a lot over the years and is now fully buffered and interrupt driven on both sending and receiving of the serial data. Other then just knowledge one really doesn't have to understand how it works to utilized the Serial.xxx() functions supplied for the user's use.
If you just want to learn how it all works you will have to wait for another's reply as my favorite programming language is solder.
I'm asking because I'm writing my code in pure AVR C, and while I understand the reasons behind making serial receiving interrrupt driven, I don't understand how/why is transmission is interrupt driven. That is, what I want to know precisely is when does Arduino remove data from TX buffer and sends it.
Well I write my sketches in 'pure' gcc C/C++ using the many supplied arduino functions and libraries the platform provides.
The advantage of having the transmit utilize interrupts is that then serial write becomes a 'non-blocking' function as long as the transmit buffer isn't filled up, (32 or 64 bytes, I don't recall at the moment) and if it does become full it reverts and becomes a 'blocking function' until space is available in to the output buffer. This can be a big deal in some sketches depending on how many tasks the sketch is trying to manage.
retrolefty:
Well I write my sketches in 'pure' gcc C/C++ using the many supplied arduino functions and libraries the platform provides.
What I meant was that I don't use any Arduino libraries.
retrolefty:
The advantage of having the transmit utilize interrupts is that then serial write becomes a 'non-blocking' function as long as the transmit buffer isn't filled up, (32 or 64 bytes, I don't recall at the moment) and if it does become full it reverts and becomes a 'blocking function' until space is available in to the output buffer.
Uhm, you make it sound like nothing is transmitted until buffer is full. What if I want to send one byte only?
retrolefty:
Well I write my sketches in 'pure' gcc C/C++ using the many supplied arduino functions and libraries the platform provides.
What I meant was that I don't use any Arduino libraries.
retrolefty:
The advantage of having the transmit utilize interrupts is that then serial write becomes a 'non-blocking' function as long as the transmit buffer isn't filled up, (32 or 64 bytes, I don't recall at the moment) and if it does become full it reverts and becomes a 'blocking function' until space is available in to the output buffer.
Uhm, you make it sound like nothing is transmitted until buffer is full. What if I want to send one byte only?
No, data is sent as soon as it's written in the transmit buffer, but the function returns immediately in that case. If the buffer is full the function as to wait for at least one character to leave the buffer and free up space. You would have to review the code in the transmitter ISR routine to see the relationship between the serial write function and the code that does the actual writing to the uart hardware registers.
retrolefty:
No, data is sent as soon as it's written in the transmit buffer, but the function returns immediately in that case. If the buffer is full the function as to wait for at least one character to leave the buffer and free up space. You would have to review the code in the transmitter ISR routine to see the relationship between the serial write function and the code that does the actual writing to the uart hardware registers.
I fail to see the point in using buffer then, that is, if your code sends only 1-byte data and not text or anything larger than 1 byte.
fail to see the point in using buffer then, that is, if your code sends only 1-byte data and not text or anything larger than 1 byte.
Because serial transmission is slow.
If you had to block every time you wrote a character tot he TX register, waiting for it to be shifted out, your software would waste a lot of processor time unproductively.
retrolefty:
No, data is sent as soon as it's written in the transmit buffer, but the function returns immediately in that case. If the buffer is full the function as to wait for at least one character to leave the buffer and free up space. You would have to review the code in the transmitter ISR routine to see the relationship between the serial write function and the code that does the actual writing to the uart hardware registers.
I fail to see the point in using buffer then, that is, if your code sends only 1-byte data and not text or anything larger than 1 byte.
Probably no advantage if one is only sending one character at a frequency slower then the baud rate that the uart is set up at but that is not always the case with most applications. Sending say 20 characters at a slow baudrate would cause the program to spend some time waiting for the uart's data buffer to free up for each additional character. Having fully buffered and interrupt driven serial comm channel is a really big deal for many arduino sketches as it can save a lot of wasted time waiting for the hardware to catch up with the program flow. Because you don't have such a need yet does not lesson the advantages. It all operates very transparently to the user's program.
If you need to look over code that did not use buffered and interrupt driven serial transmission perhaps you could look over some of the very old arduino IDE releases before they upgraded the serial library code. Not sure how many versions back you would have to go however.
I think I figured which interrupt gets called on transmission:
ISR(USART_UDRE_vect)
But, according to this site, that interrupt is executed when data register is empty. Uhm, why would you go into interrupt when data register is empty? Isn't that most of the time (empty)?
that interrupt is executed when data register is empty. Uhm, why would you go into interrupt when data register is empty
When it becomes empty.
So that you can put something into it, so that it is no longer empty.
Oh! I see. Okay, just for the kicks I tried to c/p Serial.write code into my AVR C code, but for some reason it doesn't work.
Here's the relevant part:
#define TX_BUFFER_SIZE 64
//define serial baud rate
#define BAUD_RATE 38400
//baud prescale calculation
#define BAUD_PRESCALE (((F_CPU / (BAUD_RATE * 16UL))) - 1)
struct ring_buffer {
uint8_t buffer[TX_BUFFER_SIZE];
volatile uint16_t head;
volatile uint16_t tail;
};
ring_buffer *_rx_buffer;
ring_buffer *_tx_buffer;
ring_buffer rx_buffer = { { 0 }, 0, 0 };
ring_buffer tx_buffer = { { 0 }, 0, 0 };
void SerialWrite(uint8_t dataToWrite) {
uint16_t i = (_tx_buffer->head + 1) % TX_BUFFER_SIZE;
// If the output buffer is full, there's nothing for it other than to
// wait for the interrupt handler to empty it a bit
// ???: return 0 here instead?
while (i == _tx_buffer->tail)
;
_tx_buffer->buffer[_tx_buffer->head] = dataToWrite;
_tx_buffer->head = i;
UCSR0B |= (1<<UDRIE0);
// clear the TXC bit -- "can be cleared by writing a one to its bit location"
UCSR0A |= (1<<TXC0);
}
ISR(USART_UDRE_vect) {
if (tx_buffer.head == tx_buffer.tail) {
// Buffer empty, so disable interrupts
UCSR0B &= 0b11011111;
}
else {
// There is more data in the output buffer. Send the next byte
uint8_t c = tx_buffer.buffer[tx_buffer.tail];
tx_buffer.tail = (tx_buffer.tail + 1) % TX_BUFFER_SIZE;
UDR0 = c;
}
}
void SerialWrite2(uint8_t dataToWrite) {
//do nothing until UDR is ready for more data to be written to it
while ((UCSR0A & (1 << UDRE0)) == 0) {};
//send out the uint8_t value in the variable dataToWrite
UDR0 = dataToWrite;
}
void setUART() {
//set baud rate
UBRR0H = (BAUD_PRESCALE >> 8);
UBRR0L = BAUD_PRESCALE;
//enable receiver and transmitter
UCSR0B = (1<<RXEN0)|(1<<TXEN0);
//Frame format: 8data, No parity, 1stop bit
UCSR0C = (3<<UCSZ00);
}
And this is the main loop:
while (1) {
//change column each 2ms
if ((getMillis() - columnSwitchTimer) > 500) {
SerialWrite(128);
columnSwitchTimer = getMillis();
}
}
Nothing wrong with timers, when I try SerialWrite2(128) it does send 128 every half second. SerialWrite doesn't work. What did I miss?