Direct Memory Access (or DMA), is a microcontroller feature, which allows peripheral interfaces to directly access memory, completely bypassing the CPU. The CPU only needs to setup the transfer of data. The rest of the process is done without it.
DMA is a commonly used in graphic, network, and sound cards. More importantly, it is used in multi-core processors, which is essentially the usage of DMA in the PICpilot.
One DMA cycle follows this procedure:
In the PICpilot, the only peripheral that uses DMA is the SPI interface, although many other ones are supported.
When a DMA transfer occurs, first a peripheral makes a request to the DMA controller (based on a predefined channel number). The request causes the DMA controller to read or write to a preconfigured peripheral address. Once this is completed, the DMA controller writes the data to the DPSRAM location (this is RAM dedicated to the DMA controller) or reads from it. The direction in which this transfer occurs depends on the register value corresponding to the predefined settings of the DMA controller.
In the PICpilot, the Direct Memory Access module is constantly active through the SPI1 module. This means that the data transmission is continuous between both chips. Essentially, when this process is continuous it provides a way to share global variables between two physical microcontrollers simultaneously. This is how path data gets transmitted to the attitude manager. The path data is made global between the two chips.
It should be noted that DMA is also used between the path manager chip and the GPS.
On the PIC microcontroller, the DMA must only be initialized. The DMA controller continues operation automatically without direct processor input. The code to initialize the interface is below.
In the code
In order to transfer data using DMA, both chips must have been initialized. In the PICpilot, only the DMA interface is initialized for data transfer between the GPS and the Path Management Chip (PM Chip), as well as the PM Chip and the Attitude Management Chip (AM Chip). Both of these connections are enabled using SPI (see previous section).
The GPS continuously sends data as soon as it is plugged in. It sends data in the following data format:
typedef struct _GPSData { long double latitude; //8 Bytes long double longitude; //8 Bytes float time; //4 Bytes float speed; int altitude; int heading; char satellites; //1 Byte char positionFix; } GPSData;
In order to properly configure this connection the following code is used:
GPSData gpsData __attribute__((space(dma))); /* * */ void __attribute__((__interrupt__, no_auto_psv)) _DMA2Interrupt(void){ newGPSDataAvailable = 1; IFS1bits.DMA2IF = 0;// Clear the DMA0 Interrupt Flag } void init_DMA2(){ IFS1bits.DMA2IF = 0; IEC1bits.DMA2IE = 1; DMA2CONbits.AMODE = 0b00; //Register Indirect Mode DMA2CONbits.DIR = 0; //Transfer from SPI to DSPRAM DMA2CONbits.MODE = 0b00; //Transfer continuously DMA2CONbits.SIZE = 1; //Transfer bytes (8 bits) DMA2STA = __builtin_dmaoffset(&gpsData); //Primary Transfer Buffer DMA2PAD = (volatile unsigned int) &SPI2BUF; //Peripheral Address DMA2CNT = sizeof(GPSData) - 1; //+1 for checksum //DMA Transfer Count Length DMA2REQ = 0b0100001; //IRQ code for SPI2 DMA2CONbits.CHEN = 1; //Enable the channel }
Note how a block of memory ("GPSData") is reserved for the incoming data from the GPS unit. The register values are configured as follows:
Register | Value | Function |
---|---|---|
DMA2CONbits.AMODE | 0 | This enables Register Indirect with Post Increment Mode. This enables data to be stored in chunks one after another (incremented locations). |
DMA2CONbits.DIR | 0 | This indicates the direction that data travels on the bus. In this case, the data is always incoming. Therefore, it is copied from the SPI interface to the DSPRAM. |
DMA2CONbits.MODE | 0 | This indicates if the transfer occurs a single time, or continuously, and whether a ping pong buffer should be used. It is currently enabled for continuous usage without a ping pong buffer. |
DMA2CONbits.SIZE | 1 | Indicates if each DMA transfer is 8 bits or 16 bits. In this case, it is 8 bits. |
DMA2STA | &gpsData with an added DMA offset | This indicates where the primary buffer is located. This memory block was initialized at the beginning of the above code snippet, where "space(dma)" is called. |
DMA2PAD | &SPI2BUF | This stores the referenced location from where the data is obtained. (This DMA channel will use the SPI2 buffer to get the data). |
DMA2CNT | Number of bytes in GPSData - 1 | This is a counter variable, which indicates the number of transfers that need to be completed per DMA request. |
DMA2REQ | 0b0100001 | This is the IRQ (Interrupt Request) code for the SPI2 interface. This allows the peripheral to send an interrupt request to the DMA controller instead of the CPU. |
DMA2CONbits.CHEN | 1 | Enables the DMA channel. |
On the other hand, the setup between the PM chip and the AM chip is slightly different. These two chips communicate with each other simultaneously through the SPI1 interface. The code controlling the initialization found in InterchipDMA.c/.h.
The data being sent to the PM chip from the AM chip is in the form of:
typedef struct _AMData { WaypointWrapper waypoint; float pathGain; float orbitGain; float calibrationHeight; char command; char checksum; } AMData;
Vice-versa, the data being sent to the AM chip from the PM chip is in the form of:
typedef struct _PMData { float time; //4 Bytes - hhmmss.ssss long double latitude; //8 Bytes - ddd.mmmmmm long double longitude; //8 Bytes - ddd.mmmmmm float speed; //KM/H float altitude; int sp_Altitude; // Meters int heading; //Degrees int sp_Heading; //Degrees char satellites; //1 Byte char positionFix; //0 = No GPS, 1 = GPS fix, 2 = DGSP Fix char targetWaypoint; char batteryLevel; } PMData;
The initialization process is extremely similar for both chips (PM and AM). Each chip requires a DMA channel to read the incoming data, as well as to write the outgoing data. As a result, both the AM chip and the PM chip have the same setup with a few different variables names:
void __attribute__((__interrupt__, no_auto_psv)) _DMA0Interrupt(void){ #if !PATH_MANAGER if (!transmitInitialized){ transmitInitialized = 1; DMA1REQbits.FORCE = 1; while (DMA1REQbits.FORCE == 1); } #endif newDataAvailable = 1; IFS0bits.DMA0IF = 0;// Clear the DMA0 Interrupt Flag } void __attribute__((__interrupt__, no_auto_psv)) _DMA1Interrupt(void){ IFS0bits.DMA1IF = 0;// Clear the DMA0 Interrupt Flag } void init_DMA0(){ IFS0bits.DMA0IF = 0; IEC0bits.DMA0IE = 1; IPC1bits.DMA0IP = 7; //Highest Priority DMACS0 = 0; //Clear any IO error flags DMA0CONbits.DIR = 0; //Transfer from SPI to DSPRAM DMA0CONbits.AMODE = 0b00; //With post increment mode DMA0CONbits.MODE = 0b00; //Transfer continuously DMA0CONbits.SIZE = 0; //Transfer words (16 bits) #if PATH_MANAGER DMA0STA = __builtin_dmaoffset(&amData); //Primary Transfer Buffer #else DMA0STA = __builtin_dmaoffset(&pmData); //Primary Transfer Buffer #endif DMA0PAD = (volatile unsigned int) &SPI1BUF; //Peripheral Address DMA0CNT = PATH_MANAGER?(sizeof(AMData)/2 + sizeof(AMData) % 2 - 1):(sizeof(PMData)/2 + sizeof(PMData) % 2 - 1); //+1 for checksum //DMA Transfer Count Length DMA0REQ = 0x000A;//0b0100001; //IRQ code for SPI1 DMA0CONbits.CHEN = 1; //Enable the channel } void init_DMA1(){ IFS0bits.DMA1IF = 0; IEC0bits.DMA1IE = 1; IPC3bits.DMA1IP = 7; DMACS1 = 0; //Clear any IO error flags DMA1CONbits.DIR = 1; //Transfer from DSPRAM to SPI DMA1CONbits.AMODE = 0b00; //Without post increment mode DMA1CONbits.MODE = 0b00; //Transfer continuously, ping ponging between buffers DMA1CONbits.SIZE = 0; //Transfer words (16 bits) #if PATH_MANAGER DMA1STA = __builtin_dmaoffset(&pmData); //Primary Transfer Buffer #else DMA1STA = __builtin_dmaoffset(&amData); //Primary Transfer Buffer #endif DMA1PAD = (volatile unsigned int) &SPI1BUF; //Peripheral Address DMA1CNT = PATH_MANAGER?(sizeof(PMData)/2 + sizeof(PMData) % 2 - 1):(sizeof(AMData)/2 + sizeof(AMData) % 2 - 1); //+1 for checksum //DMA Transfer Count Length DMA1REQ = 0x000A;//0b0100001; //IRQ code for SPI1 DMA1CONbits.CHEN = 1; //Enable the channel }
The above code is very similar to the first example with the GPS. The differences include the direction of data transfer, the buffer variables, the IRQ codes, as well as the 16 bit mode interfacing.
Also, in order to initialize transfer, the SPI master must send the first packet. This is evident in the DMA0 interrupt routine:
#if !PATH\_MANAGER if (!transmitInitialized){ transmitInitialized = 1; DMA1REQbits.FORCE = 1; while (DMA1REQbits.FORCE == 1); } #endif
A DMA request is forced by setting the DMA1REQbits.FORCE bit to 1. The following sets of data do not need to be forced, they happen automatically (since continuous mode was enabled).
Add Comment