ADC to DMA with PSoC 3

Tip / Sign in to post questions, reply, level up, and achieve exciting badges. Know more

cross mob
RuPi_283656
Level 4
Level 4
10 sign-ins First solution authored 25 replies posted

Hello. I've been beating my head trying to make this work.  PSoC 3 processor, ADC to DMA to memory.  Everything seems set up right but the DMA result memory location never gets the value.  If I read the ADC directly I get a good value, so the ADC is working.  The ADC interrupt is firing because I can breakpoint it there, and I can see that the ADC_convDone flag is set.

I've gone round and round with the documentation and think everything is set up right.  But the DMA screens in Creator 4.4 do not exactly align with the documentation.

The one thing I cannot find is exactly how to set up the TD for my scenario.  I have the ADC converting continuously, and the TD is set up as: Endian=off, trq=off, nrq=off, length=1, source ADC_DEC_SAMP_PTR, Inc=off, dest=memory value,  Auto Next=off, and Next Td=End.

I have tried Auto Next=on and Next Td=0, but that does not change anything and I don't think it is right anyway.

So here are my setup values:

/* Defines for AzDma */
#define AzDma_BYTES_PER_BURST 1
#define AzDma_REQUEST_PER_BURST 1
#define AzDma_SRC_BASE (CYDEV_PERIPH_BASE)
#define AzDma_DST_BASE (CYDEV_SRAM_BASE)

/* Variable declarations for AzDma */
/* Move these variable declarations to the top of the function */
uint8 AzDma_Chan;
uint8 AzDma_TD[1];

/* DMA Configuration for AzDma */
AzDma_Chan = AzDma_DmaInitialize (AzDma_BYTES_PER_BURST, AzDma_REQUEST_PER_BURST,
HI16 (AzDma_SRC_BASE), HI16 (AzDma_DST_BASE));
AzDma_TD[0] = CyDmaTdAllocate ();
CyDmaTdSetConfiguration (AzDma_TD[0], 1, CY_DMA_DISABLE_TD, 0);
CyDmaTdSetAddress (AzDma_TD[0], LO16 ((uint32)ADC_DEC_SAMP_PTR), LO16 ((uint32)AdRes.Az));
CyDmaChSetInitialTd (AzDma_Chan, AzDma_TD[0]);
CyDmaChEnable (AzDma_Chan, 1);

What I want is for the memory location to get overwritten with each conversion - for now.

Thanks for any insight, Russ

0 Likes
1 Solution

Russ,


However, I now realize that I need to switch a mux in my interrupt routine.  I have three channels being muxed to the ADC, and with each interrupt I switch to the next channel.  I don't know if I can do that using DMA?  If not, I guess I'd be better to stay with the interrupt, although I might find a different way to change channels.


Theoretically ... yes.

The AMux analog switch control is found in the peripheral register set.  So theoretically DMA can access it.

However, this is where it gets complicated.

If you use either of these AMux components that look like this:

Len_CONSULTRON_0-1671367182035.png or Len_CONSULTRON_1-1671367453822.png

 

The API calls handle turning ON and OFF the appropriate analog switches.  That is why it works using the switching in an ISR.

It is possible to use DMA but depending on what inputs you use it could get complicated since it might take multiple AMux registers to provide the switching needed.

Therefore using this type of AMux, although much more HW resource minimized, is not advisable.

If you use a Control Register with  either of these types of AMux components, then it's much more DMA friendly.

Len_CONSULTRON_2-1671367651759.png  or  Len_CONSULTRON_3-1671367685869.png

In this case, the HW to control the AMux is connected to the Control Register.  Then all you have to do is DMA the correct value to the single Control Register value register to switch the AMux.

Len
"Engineering is an Art. The Art of Compromise."

View solution in original post

0 Likes
16 Replies
Len_CONSULTRON
Level 9
Level 9
Beta tester 500 solutions authored 1000 replies posted

Russ,

I found one major issue with your code fragment that sets up the DMA.  See issue in RED.

CyDmaTdSetConfiguration(DMA_1_TD[0], 1u, DMA_INVALID_TD, DMA_1__TD_TERMOUT_EN);

DMA_INVALID_TD is a possible return response for the CyDmaTdSetConfiguration function.

I suggest you use DMA__TD_TERMOUT_EN instead.

 

For future reference if you are using the CY8CKIT-030, you can load an example called "ADC_DMA
_VDAC Example Project".  This is a PSoC3 example on how to use and setup the DMA with an ADC.

Len
"Engineering is an Art. The Art of Compromise."
0 Likes

Thanks for the reply Len.  I did run the example and it works.  But, the DMA in the example does not write to  memory, it writes to the input of a DAC.  I changed define for the DMA_1_DST_BASE to CYDEV_SRAM_BASE, and the address line to write the result to a global integer variable, and that variable is never changed.  There must be some other trick to getting DMA to write to memory, but I cannot find it as yet.

Thanks, Russ

0 Likes

Russ,

I've used DMA to dump from ADC to SRAM on many projects (using a PSoC5LP) with expected success.

I don't see why this would be different on a PSoC3.

Len
"Engineering is an Art. The Art of Compromise."
0 Likes
lock attach
Attachments are accessible only for community members.

Russ,

I believe I have an answer to your issue.

The major issue as to why you are always getting the same value from the ADC into the SRAM is because you are NOT placing the address of the SRAM into the DMA initialization.

See the code line in RED.

CyDmaTdSetAddress (AzDma_TD[0], LO16 ((uint32)ADC_DEC_SAMP_PTR), LO16 ((uint32)&AdRes.Az));

You left out the pointer symbol to the SRAM variable.

Make the change shown in RED and you should get new values.

I attached a very quick PSoC3 project that uses a WaveDAC8 to generate a sine wave.  The ADC samples the sine wave and dumps to the AdRes.Az variable.  The value of the AdRes.Az is then loaded into port 0.

It works.

Len
"Engineering is an Art. The Art of Compromise."
0 Likes

Thanks again Len.  You were right about needing the memory pointer.  I was fooled by the cast to a uint32 rather than a pointer type.  So I fixed that and now it works fine for single byte transfers.  But I need to use 12 bit resolution so I need 2 byte transfers.  When I set that up, only one byte is getting transferred, the high byte.  So if the proper value is 0x0F38, the result I get is 0x0F00.  In a breakpoint I can change the low byte to some value other than 0, but in subsequent DMA transfers that low byte never changes.  Here is my DMA setup:

/* Defines for AzDma */
#define AzDma_BYTES_PER_BURST 2
#define AzDma_REQUEST_PER_BURST 1
#define AzDma_SRC_BASE (CYDEV_PERIPH_BASE)
#define AzDma_DST_BASE (CYDEV_SRAM_BASE)

/* Variable declarations for AzDma */
/* Move these variable declarations to the top of the function */
uint8 AzDma_Chan;
uint8 AzDma_TD[1];

/* DMA Configuration for AzDma */
 AzDma_Chan = AzDma_DmaInitialize(AzDma_BYTES_PER_BURST,       AzDma_REQUEST_PER_BURST,
HI16(AzDma_SRC_BASE), HI16(AzDma_DST_BASE));
AzDma_TD[0] = CyDmaTdAllocate();
CyDmaTdSetConfiguration(AzDma_TD[0], 2, AzDma_TD[0], CY_DMA_TD_AUTO_EXEC_NEXT);
CyDmaTdSetAddress(AzDma_TD[0], LO16((uint32)ADC_DEC_SAMP_PTR), LO16((uint32)&AdRes.Az));
CyDmaChSetInitialTd(AzDma_Chan, AzDma_TD[0]);
CyDmaChEnable(AzDma_Chan, 1);

The Az variable in AdRes is uint16 also.

By the way, I did all this several years ago with a PSoC5, and had little trouble with it.  It seems the PSoC3 is a very different animal!

Thanks for any more ideas,  Russ

0 Likes

Russ,

I see you've made the size adjustments:

#define AzDma_BYTES_PER_BURST 2

CyDmaTdSetConfiguration(AzDma_TD[0], 2, AzDma_TD[0], CY_DMA_TD_AUTO_EXEC_NEXT);

Functionally the only difference between the PSoC5 and the PSoC3 is that the PsoC5 is little-endian, and the PSoC3 is BIG-endian.

Len
"Engineering is an Art. The Art of Compromise."
0 Likes
lock attach
Attachments are accessible only for community members.

Russ,

I changed my example code to:

  • ADC resolution => 12-bits
  • AzDma => 2 byte transfers.
  • AdRes.Az => 2 bytes.

When I run this code in debug mode and watch the AdRes.Az variable I see both bytes changing.

Note: My AzDma initialization code is not the same as yours.

Attached in the updated project.

Len
"Engineering is an Art. The Art of Compromise."
0 Likes
lock attach
Attachments are accessible only for community members.

Russ,

Just for "S&G" I attached a second version of my project.

This version replaces the isr that loads the data acquired in the AdRes.Az variable into the 8-bit data port and changes it to a DMA transfer instead.

With this new code change, once the CPU configures all the components and DMA, the CPU does NOTHING but sitting there "tiddling its thumbs".  All the work is occurring in HW aided by the DMA HW.

Enjoy!

Len
"Engineering is an Art. The Art of Compromise."
0 Likes
lock attach
Attachments are accessible only for community members.

Hi again Len. Thanks for your patience!  Attached is a modified version of your project.  The only difference is that there is also a VDAC8 in the design, not connected.  If you run it you should see that it works the same way as your original.  But disconnect the WaveDac and connect the AzDac (and change the start command in main), and you should see that now only the high byte of the result location gets set.  I surely cannot see why!

Thanks, Russ

0 Likes

Russ,

I've downloaded the modified version of my original project.

I've quickly reviewed your changes.  Yes the VDAC8 is there and not even connected or started.  Other than that, the code looks untouched.   When I ran the original project it was based on, I did get both bytes changing.  (The higher address byte changes just not that often)  This is mostly due to the WaveDAC8 has 0V to 1.024V variation and the ADC is set to 0V to 2.048V range. Therefore the ADC results only show up as 0x0000 to 0x07FF at most.

The upper byte is the MSB of the 12-bit result.  Therefore the upper byte will "slowly" change between 0x00 to 0x07 compared to the lower byte (LSB).

I'm booked at work today but should have some report to you tomorrow.

Experiment Suggestion:  For a quick test, change the ADC input range to 0V to 1.024V.

Although this range might exceed the WaveDAC8 input at times, this should guarantee more MSB changes.

Len
"Engineering is an Art. The Art of Compromise."
0 Likes

Russ,

I have tested your modified version of my original project.

I'm getting both bytes (MSB and LSB) changing as expected.

Here are some debug watch windows of the SRAM variable loaded from the ADC to AdRes.Az.

Len_CONSULTRON_0-1671151559694.png

Len_CONSULTRON_1-1671151623285.pngLen_CONSULTRON_2-1671151649078.png

So I don't know what you're getting.

 

Len
"Engineering is an Art. The Art of Compromise."
0 Likes

Len, well I don't understand it. When I disconnect the Wave8 Dac and connect the VDac8, then I only see the high byte changing, no matter what value I send to the Dac.

I have bypassed the issue for now, by intercepting the ADC interrupt with a callback.  The callback simply reads the ADC value and puts it in AdRes.Az.  That works fine, except of course, that it requires the processor to do the work instead of taking the load off the processor like the DMA would do.

But I need to get on with this project and so the interrupt will have to do, at least for now.

Thanks for your help Len,

Regards, Russ

0 Likes

Russ,

I'm confused.  I used the WaveDAC8 as a stimulus to the ADC input.

In your design what is the input to the ADC?

Len
"Engineering is an Art. The Art of Compromise."
0 Likes

Russ,

I might have some more clues here.

The HW for the peripherals on the PSoC5LP and the PSoC3 are basically identical. 

This includes the addressing of all the peripherals (including the ADC).  The addressing of > 8-bit datapaths is in little-endian format.

The PSoC5LP using the ARM processor is little-endian also.  Therefore a 16-bit DMA transfer from the ADC data register (DEC_OUTAMP) can be loaded without alteration.

The PSoC3 using the 8051 CPU is BIG-endian.  Therefore a 16-bit DMA transfer from the ADC data register (DEC_OUTAMP) needs the bytes to be ENDIAN swapped before it can be used by the CPU.

This might the issue you are seeing.  When you changed the DMA transfer to an ISR-based transfer, the ADC_GetResult16() routine knows the CPU is BIG-endian and makes the swap in SW.  This is probably why this method is working.

To "fix" this issue try this change in your AzDMA initialization code (Note the change in RED😞

CyDmaTdSetConfiguration(AzDma_TD[0], 2, AzDma_TD[0], CY_DMA_TD_SWAP_EN| AzDma__TD_TERMOUT_EN);

When I make this change I get the following results for AdRes.Az in the debugger watch.

Len_CONSULTRON_0-1671196827052.pngLen_CONSULTRON_1-1671196853065.pngLen_CONSULTRON_2-1671196879411.png

Now the MSB is on the right-handed side of the variable value.

As pointed out above, if the results of any peripheral greater than 8-bits is to be used by the PSoC3 CPU, it needs to be endian corrected.  This automatically occurs in the Infineon-supplied API calls.

However, if the > 8-bit data needs to be DMA transferred for CPU use, you need to make sure the proper ENDIAN flag is set in DMA initialization.  This is not automatically done for you.

In general use this as a guide for > 8-bit data:

PERIPHERAL === endian-swap ==> CPU ==== endian-swap ==> PERIPHERAL

A Special Note:

If you need to DMA transfer a > 8-bit data value between peripherals then you should not need to endian correct.  This is because all the peripheral HW are little-endian formatted.

PERIPHERAL === no endian-swap ==> PERIPHERAL

Len
"Engineering is an Art. The Art of Compromise."
0 Likes

Len, thanks for that explanation.  I had tried endian swap before with no improvements, but that might have been before you had me fix the address pointer...

However, I now realize that I need to switch a mux in my interrupt routine.  I have three channels being muxed to the ADC, and with each interrupt I switch to the next channel.  I don't know if I can do that using DMA?  If not, I guess I'd be better to stay with the interrupt, although I might find a different way to change channels.

Thanks, Russ

0 Likes

Russ,


However, I now realize that I need to switch a mux in my interrupt routine.  I have three channels being muxed to the ADC, and with each interrupt I switch to the next channel.  I don't know if I can do that using DMA?  If not, I guess I'd be better to stay with the interrupt, although I might find a different way to change channels.


Theoretically ... yes.

The AMux analog switch control is found in the peripheral register set.  So theoretically DMA can access it.

However, this is where it gets complicated.

If you use either of these AMux components that look like this:

Len_CONSULTRON_0-1671367182035.png or Len_CONSULTRON_1-1671367453822.png

 

The API calls handle turning ON and OFF the appropriate analog switches.  That is why it works using the switching in an ISR.

It is possible to use DMA but depending on what inputs you use it could get complicated since it might take multiple AMux registers to provide the switching needed.

Therefore using this type of AMux, although much more HW resource minimized, is not advisable.

If you use a Control Register with  either of these types of AMux components, then it's much more DMA friendly.

Len_CONSULTRON_2-1671367651759.png  or  Len_CONSULTRON_3-1671367685869.png

In this case, the HW to control the AMux is connected to the Control Register.  Then all you have to do is DMA the correct value to the single Control Register value register to switch the AMux.

Len
"Engineering is an Art. The Art of Compromise."
0 Likes