Sam7 Peripheral DMA Controller (PDC)
Having gotten the AIC working I thought I'd take a look at the DMA controller.
I was thinking about using the AIC for interrupt driven serial I/O but it seems for serial input the PDC can do what I want and I don't really need to interrupt drive it.
The PDC doesn't have a control register space of its own. Instead all the peripherals which have PDC support include PDC register at an offset of hex 100 from the peripheral base address.
If for example I'm interested in DMA for a USART with a base address of FFFC0000 then the Receive Pointer Register for this USART will be at FFFC0100
These are the PDC register offsets etc.... Offset Register Register Name Read/Write Reset 0x100 Receive Pointer Register PERIPH(1)_RPR Read/Write 0x0 0x104 Receive Counter Register PERIPH_RCR Read/Write 0x0 0x108 Transmit Pointer Register PERIPH_TPR Read/Write 0x0 0x10C Transmit Counter Register PERIPH_TCR Read/Write 0x0 0x110 Receive Next Pointer Register PERIPH_RNPR Read/Write 0x0 0x114 Receive Next Counter Register PERIPH_RNCR Read/Write 0x0 0x118 Transmit Next Pointer Register PERIPH_TNPR Read/Write 0x0 0x11C Transmit Next Counter Register PERIPH_TNCR Read/Write 0x0 0x120 PDC Transfer Control Register PERIPH_PTCR Write-only - 0x124 PDC Transfer Status Register PERIPH_PTSR Read-only 0x0
I've defined these as constants in the include file PDC.FTH - I renamed PERIPH(1)_RPR to PERIPH_RPR.
In emforth the base address of the system USART is kept in a variable called *USART.
Which USART is used depends on the particular SAM7 board I am running and is usually figured out by the kernel at boot time.
By using *USART to access the PDC my code should be work on different boards without recompilation.
Kernel hack.To allow the lowest level serial input routines to be redirected the variable *TIMEDSIN was added to emforth Ver1.78.
This normally points to the rountine "TSIN" which is a serial byte input routine with an optional timeout.
This is called by several serial input words including SIN.
I needed to be able to point this to my new buffered version of TSIN.
The test code worked and adds buffered serial input to emforth. The buffer is 256 bytes but could easily be changed for more or less.
The test code
SP! HEX 4000 GETMEM DROP INCLUDE ..\include\PDC.FTH // *USART HOLDS THE BASE FOR THE SYSTEM USART VARIABLE SERINBUF 100 VARALLOT // serial input buffer VARIABLE SERININDEX // input buff pointer RUNTIME
For starters the PDC register address offsets are included and the buffer and pointer declared.
: BUFFTSIN // TIMED SIN TO- NUM=TIME, -1 = FOREVER / TO -- CHAR/FALSE BEGIN SERINBUF SERINPTR @ + *USART @ PERIPH_RPR + @ - // Is there a new char. IF DROP SERINBUF SERININDEX @ + C@ 1 SERININDEX +! // yes, get it and inc pointer SERININDEX @ FF AND // Do we need to wrap the pointer ? 0= IF // yes, wrap it SERINBUF *USART @ PERIPH_RNPR + ! // Set up serial in next ptr 100 *USART @ PERIPH_RNCR + ! // Set up serial in next cntr 0 SERININDEX ! // wrap pointer to zero THEN EXIT THEN // Return char - just exit DUP 0< IF DROP -1 THEN // If count negative force it to -1. 1- DUP -1 = UNTIL ;
The serial input routine checks our buffer index plus buffer address against the PDC serial in pointer to determine if a new byte is present. If so it fetches it and adjusts the index.
If the index has reached past the end of the buffer it is reset and the NEXT pointer and counter are rewritten. The code is a little awkward because it can also loop around a set number of times as a crude timeout or loop forever. This works fine for the non-interrupted kernel but would most likely be changed to use a timer in any serious applications.
The rewriting of the NEXT registers could be done using interrupts but it easy to do it this way.
: SETUPPDC SERINBUF *USART @ PERIPH_RPR + ! // Set up ser in ptr 100 *USART @ PERIPH_RCR + ! // Set up ser in cntr SERINBUF *USART @ PERIPH_RNPR + ! // Set up ser in next ptr 100 *USART @ PERIPH_RNCR + ! // Set up ser in next cntr 0 SERININDEX ! // set the pointer to zero 1 *USART @ PERIPH_PTCR + ! // enable dma for recieve [ ' BUFFTSIN LITERAL ] *TIMEDSIN ! // point *TIMEDSIN to our new version of TSIN ; SETUPPDC
The setup code simply writes the correct values into the pointer and counter registers, zeroes the index, enabled the transfer then redirects *TIMEDSIN to point to my new input code.
All in all it was a lot easier than I expected.