print.c/h is a simple DMA based UART driver for debugging on Atmel SAM series microcontrollers. It includes functions for printing decimal, hexadecimal and strings. If you don't have an in-circuit debugger, using the UART for debugging is the next best thing and usually all you need.
This is not a traditional printf(). You have to call separate print functions for each output format you want to print. e.g. prints("My age is "); printi(78, '\n');
PF_BLOCK -> blocks until the buffer has enough room. PF_RETURN -> returns if there is no room on the buffer. PF_REMAINING -> puts just what it can fit on the buffer.
Function | Size | Description | 1st parameter | 2nd parameter |
setup_print_uart() | 58 | Sets up the UART/USART | ||
txbytes | 204 | Copies a string/data to the DMA buffer to be sent | Pointer to the string to print | length |
prints | Macro to txbytes() - puts in the length | Pointer to the string to print | ||
printi | 132 | prints a signed integer in base 10 | int32_t to print | append character |
printx | 148 | prints a unsigned integer in hexadecimal | uint32_t to print | append character |
Code | Output |
x=14; printi(x++, '\t'); printi(x++, '\n'); printx(x++, '\t'); printx(x++, '\n'); |
14 15 0x10 0x11 |
Micro | UART0 | UART1 | USART0 | USART1 | USART2 | USART3 |
Sam3x8e | ||||||
Sam3u1c | ||||||
Sam3n00a |
The main motivation for publishing this was to show the simplicity of the Atmel Sam's PDC (Peripheral DMA Controller) with a circular buffer. Each PDC enabled peripheral has its own PDC channel so there is minimal setup to use the PDC. There is one register (TPR) that points to the buffer in memory and another register (TCR) that holds the remaining count of bytes to transfer. When the UART is signalling for another data byte, if TCR does not equal zero, it moves the data from where TPR points to, to the UART tx data register. TCR is decremented and TPR is incremented. For example, if you wanted to transfer a string 10 bytes long, you would copy the string to the buffer, set TPR to point to the first byte in the buffer, set TCR to 10 and enable the PDC TX.
To get the PDC to work nicely with a circular buffer, we also need to use TNPR and TNCR (next pointer and next counter) register. TNPR and TNCR are not incremented or decremented like TPR and TCR are. When TCR is about to decrement to zero, the hardware copies TNPR to TPR and TNCR to TCR and sets TNCR to zero. This lets you "queue up" a second transfer.
Function txbytes() is used to add data to the circular buffer. There are four cases to consider when adding to the buffer....
if (!PF_UART->UART_TCR) { in = 0; PF_UART->UART_TPR = (uint32_t) tx_buff; }
The variable length is the length of the data to add to the buffer. At the very beginning of txbytes() the "HANDLE_FULL" behaviour makes sure length is never bigger than the remaining size of the buffer. Variable in is the buffer fill position and is a static uint8_t. The array tx_buff is the circular buffer and is 256 bytes so that in naturally wraps to the size of tx_buff. The code to add data to the buffer is the same for all four cases....
while (length--) tx_buff[in++] = data[i++];
Case 1 & 2
Variable i ends up being the same as the initial value of
length. Since length now equals zero, i is now
used as the reference for the length of data copied. In case #1, no
transfers are active (TCR will be 0). For case #1, TCR just equals
i. Since TCR equals zero to start, TCR+= i can be
used as it works nicely for case #2 as well. For case #2, if the
PDC had 7 more bytes to transfer when the transfer was disabled
(TCR = 7), three bytes are added (i=3), with TCR+= i,
TCR is now 10.
Case 3
remaining is defined to be the free space on the buffer,
front and back. What is meant by front and back....if a transfer is
not active and 56 bytes are copied to the buffer (TCR = 56), there
is room for 200 bytes on the back. Another example, if the
transfer is stopped after 10 bytes have been transferred (TCR now =
46), then there are 10 bytes free on the front.
remaining would equal 256 - 46 = 210. With the transfer
still disabled, if another 205 bytes are now copied to the buffer,
200 would be put on the back and the last 5 on the front. The PDC
must now transfer the 46+200 bytes on the back and also the 5 bytes
on the front. in would have wrapped and would now equal 5 as
well. TNCR needs to equal the size of the front. TNCR is set to
in. out is defined as (TPR - tx_buff) and works out
to be the index of tx_buff where the PDC is taking bytes off the
buffer. The size of back works out to be 256 - out.
So when in wraps around, TNCR = in and TCR = 256 - out, otherwise TCR+= i.....
if (in < out || !(remaining-i)) { //cases 3 & 4 REG_USART0_TCR = PF_TX_SIZE - out; REG_USART0_TNCR = in; } else //cases 1 & 2 REG_USART0_TCR+= i;
There is one problem...if in the above case 3 example the buffer was completely filled (210 bytes were copied instead of the 205) in would equal out (10) and the wrong code to set TCR would execute. !(remaining-i) is in the if statement to correct for this situation. When the buffer is completely full remaining-i equals zero, which triggers the right code to set TCR and TNCR. There is one catch....there is another time when remaining-i equals zero. If the buffer was completely filled in case #1, then out = in = zero. Fortunately because in equals zero, TNCR will get set to zero and because out also equals zero, TCR will get set to 256, so no harm done.
Case 4 is mostly the same as case 3, but TCR does not need to be set, only TNCR to the new value of in. Setting TCR anyway doesn't hurt anything and makes the code simpler.