PSoC6 DMA to GPIO Port is unreliable at high speeds

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

cross mob
lock attach
Attachments are accessible only for community members.
travisjayday
Level 1
Level 1
5 replies posted 5 sign-ins First reply posted

Hi there,

My problem is I need to drive a VGA monitor with a PSoC6. My solution is to use DMA transfers from an image buffer to a GPIO Port. The GPIO Port is connected to an external high speed DAC to convert the digital 8 bit signal into an analog signal for the monitor. 

I've achieved this reasonably well with my PSoC5 LP, PSoC Creator, and the UDB Control Register. The PSoC5 LP would DMA transfer byte-by-byte to the control register block, and this block would be connected in the schematic editor to a bunch of GPIO pins. The pins would update their state at a fixed frequency and according to the value of the incoming DMA byte. This worked very fast and without problems.

However, I've now switched to the PSoC6 MCU, specifically I have the CY8CPROTO-062-4343W (2MB Flash). This board is not supported in PSoC Creator as far as I can tell (I have no idea why, but it is what it is). Since I'm now using Modus Toolbox, I can't create the UDB Control Register I did with the PSOC5. Instead, I looked at the TRM manual and found out you can directly write to the memory mapped GPIO Ports and update their state that way.

I figured out this is how to get the address of the port:

 

uint32_t* port9 = (uint32_t*) Cy_GPIO_PortToAddr(9);

 

and you can write an arbitrary byte to it like:

 

*port9 = 0xff; 

 

After figuring this out I was relieved, because I thought I could now just do the same thing: set-up a continuous byte-to-word DMA transfer from my source buffer to this peripheral address. I looked at DMA examples for PSoC6 and managed to get it working. Here are some relevant snippets:

 

// Configure descriptor source. Buffer_a = framebuffer to read from.
Cy_DMAC_Descriptor_SetSrcAddress(&RDMA_Descriptor_0, (uint32_t *) buffer_a);
Cy_DMAC_Descriptor_SetDstAddress(&RDMA_Descriptor_0, (uint32_t *) port9);

// Standard Channel Config
Cy_DMAC_Channel_SetDescriptor(RDMA_HW, RDMA_CHANNEL, &RDMA_Descriptor_0);
Cy_DMAC_Channel_SetInterruptMask(RDMA_HW, RDMA_CHANNEL, CY_DMAC_INTR_MASK);
Cy_DMAC_Channel_Enable(RDMA_HW, RDMA_CHANNEL);

// Start DMA listen for signal
Cy_DMAC_Enable(RDMA_HW);

 

RDMA is what I named my DMAC channel 0. See the attached image for the exact configuration.

I was very happy because it seemed to work fine. But under closer inspection I realized a huge problem. The generated signal was not consistent.

The source buffer looks like this: All zeros with three consecutive 1's. It gets initialized at the start of main, and never edited again.

 

for (int i = 0; i < 640; i++) buffer_a[i] = 0x00;
buffer_a[200] = 0xff;
buffer_a[201] = 0xff;
buffer_a[202] = 0xff;

 

 Thus, on my oscilloscope I'm expecting to see a bunch of zeros followed by a 3 pixel bump.

Like: ______---_______ (see correct_square.jpg)

However, I'm not getting this always. Sometimes, almost every other time, the bump is messed up. Sometimes there's a pixel missing

Like: _______-_-_______ or _______--_______  (See corrupt_square.jpg and half_square.jpg)

And other times there's just a weird, very short dip.

Like: _______-.-________ (See corrupt_square2.jpg).

 

Also note that I'm running the peripheral clock at 100Mhz. If I divide it by 4 (run it at 25Mhz), I don't have these problems but anything >25Mhz creates this inconsistent behavior. Dividing it by 4 is not an option for me though, as that GPIO update speed is way too slow (the PSOC5 even could clock at 74Mhz without problems).

I'm at a loss of what to do. Is this a limitation of the PSoC6? Surely not? Is there a better / fast / more reliable way to update GPIO pins as quickly as possible instead of writing directly to the Register Port Address? Could I somehow use UDB like I did with the PSoC5?

What can I do?

Any advice would be greatly appreciated. It's very hard to find resources on this topic IMO, so I'm hoping someone can shed some light onto this 🙂

Thank you!

0 Likes
1 Solution
Ekta_N
Moderator
Moderator
Moderator
750 replies posted First like given 250 solutions authored

Hello @travisjayday 

Could you please let me know how is the DMA triggered everytime?

Regarding the DMA transfer timing:

1. Clock inaccuracy:  The PSoC 62 IMO has an accuracy of +/- 2 percent. If you are observing a timing error within that range then it possible that this error is due to the clock inaccuracy.

2. Bus arbitration: Yes, as mentioned by you you can increase the priority of the bus by writing '0' (highest priority) to the  PRIO bits(bits 8 and 9, refer to the PSOC 62 register TRM page 396 for more details) of the PROT_SMPU_MSx_CTL register. To know exactly which PROT_SMPU_MSx_CTL register to write you can refer to the psoc6_02_config.h file (in case of PSoC 62) that will be present in the .....project_workspace\mtb_shared\mtb-pdl-cat1\release-v2.2.0\devices\COMPONENT_CAT1A\include directory

Ekta_0-1620046558331.png

 

As mentioned in the above image to increase the bus priority for the Data wire 0 you need to write to  PROT_SMPU_MS2_CTL, similarly for DMA Controller you need to write to  PROT_SMPU_MS4_CTL register.

Thanks and Regards

Ekta

View solution in original post

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

travis,

...
However, I've now switched to the PSoC6 MCU, specifically I have the CY8CPROTO-062-4343W (2MB Flash). This board is not supported in PSoC Creator as far as I can tell (I have no idea why, but it is what it is). Since I'm now using Modus Toolbox, I can't create the UDB Control Register I did with the PSOC5. Instead, I looked at the TRM manual and found out you can directly write to the memory mapped GPIO Ports and update their state that way.
...

In general, ModusToolbox is the direction that Cypress/Infineon wants to go to in the future.  However, this tool currently has no method to configure (and program) USBs.  Additionally many of the PSoC6s do not have UDBs.

The PSoC6s that have UDBs are currently supported in Creator.

The CY8CPROTO-062-4343W has no UDBs.  Therefore there is no attempt to allow this IC to be supported in Creator.

(Note:  The Cy8CPROTO-063-BLE has UDBs and is supported in Creator)

Your post is very descriptive but may lack enough detail to have others accurately reproduce your results.

Are you willing to share your project with the forum?

Len
"Engineering is an Art. The Art of Compromise."
0 Likes
travisjayday
Level 1
Level 1
5 replies posted 5 sign-ins First reply posted

Hi Len,

Thank you for your quick reply. And thank you for clarifying the UDB topic, that makes sense! (I didn't realize my board didn't even have UDBs!).

In preparing a minimally reproducible example to share with you, I cut so much code (including interrupt handling) that I actually got a solid waveform. After, rebuilding the initial code line by line, I found out that my ISR was the culprit. If I write to the GPIO register in the ISR, the inconsistent output behavior arises for some reason. Not writing to the GPIO register within the ISR solves makes the output wave solid. So somehow writing to the GPIO within the ISR caused the trouble!

 

Very strange.

 

Thanks for your help. I don't have a satisfying answer but at least I can move forward now.

 

Best,

 

Travis

 

Thank you!

 

Travis

0 Likes

travis,

I'm glad you got mostly an answer.

I'm confused though.  I thought you were ONLY updating the GPIO register by DMA. 

If you write to the GPIO with DMA and ISR, it is basically asynchronous to each other.  No guarantee as to the results.

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

Hmm yes you're right. The idea is to auto-disable the DMA channel after an X transfer so to  then have the ISR re-enable the DMA channel after a short delay. The delay between DMA transfers is equal to the VGA blanking interval. The reason I wrote to the Port in the ISR was to ensure the output is low when entering the blanking interval. But I think you're right that might have been the issue. I have a hunch I didn't have the timings properly setup, so the ISR happened at irregular intervals with respect to the DMA transfers.

 

I got it working now though. The next issue (which is not as major ) is that there seems to be some variability in DMA transfer timing. This is causing some horizontal jitter (see attached image). I don't think there's much I can do about it but it'd be great to hear the opinion of an experienced PSoC user.

 

Just to clarify the situation:

- A single X DMA transfer starts on the far left of the screen.

- The VGA monitor progresses from left to right, the GPIO pin gets updated and it enables and disables the pixels on the screen.

 

The problem:

- The X DMA transfers start synchronously on the far left of the screen.

- As they progress, they go out of sync. The further right they go on the display, the more out of sync they get.

My guess is that this is caused by either:

- Clock inaccuracy: Could it be that each clock cycle may have a slightly different length?

- Bus arbitration: The TRM mentions a DMA transfer may be interrupted by other bus masters. Could this be happening at irregular intervals causing the transfer inaccuracies?

I've set the DMAC priority to 0 but I think this only has an effect within the DMAC Block. The manual I'm reading (https://www.cypress.com/file/510651/download) briefly mentions to tweak PROT_SMPU_MSx_CTL[PRIO] to change the arbitration scheme. I can't find any reference on how to use this, could you maybe clarify or share some references on how to make the DMAC controller the highest priority master on the bus?

 

Thank you for your help!

 

Travis

0 Likes

travis,

I'm still digesting your last post.

Idea:  Why don't you add the blanking as a DMA TD into the same DMA chain as the data TD.   This should keep it in sync.

Len
"Engineering is an Art. The Art of Compromise."
0 Likes
Ekta_N
Moderator
Moderator
Moderator
750 replies posted First like given 250 solutions authored

Hello @travisjayday 

Could you please let me know how is the DMA triggered everytime?

Regarding the DMA transfer timing:

1. Clock inaccuracy:  The PSoC 62 IMO has an accuracy of +/- 2 percent. If you are observing a timing error within that range then it possible that this error is due to the clock inaccuracy.

2. Bus arbitration: Yes, as mentioned by you you can increase the priority of the bus by writing '0' (highest priority) to the  PRIO bits(bits 8 and 9, refer to the PSOC 62 register TRM page 396 for more details) of the PROT_SMPU_MSx_CTL register. To know exactly which PROT_SMPU_MSx_CTL register to write you can refer to the psoc6_02_config.h file (in case of PSoC 62) that will be present in the .....project_workspace\mtb_shared\mtb-pdl-cat1\release-v2.2.0\devices\COMPONENT_CAT1A\include directory

Ekta_0-1620046558331.png

 

As mentioned in the above image to increase the bus priority for the Data wire 0 you need to write to  PROT_SMPU_MS2_CTL, similarly for DMA Controller you need to write to  PROT_SMPU_MS4_CTL register.

Thanks and Regards

Ekta

0 Likes
travisjayday
Level 1
Level 1
5 replies posted 5 sign-ins First reply posted

Hi Ekta,

Thank you for your reply! The DMA is triggered by a TCPWM PWM Compare Event.

I've tried Len's idea with using larger TDs but that did not work well because I think small inaccuracies every now and then compounded and I couldn't get a steady signal. Besides, I realized that the datawire DMA transfers are capped at 65kB which is smaller than my framebuffer, so that wouldn't be a path worth perusing in my case.

Being more meticulous with the timers, I've gotten fairly good results that are within the clock inaccuracy range that Ekta mentioned. Also, thank you for pointing out the bus master defines in psoc6_02_config.h file! I could not find any information on that, so that's very helpful.

The last issue I'm facing is synchronizing two DMA transfers.

I need to start the DW0 Channel 0 and DW1 Channel 1 transfers at the exact same time. I've hooked them up to two independent timers (TCPWM1.0 and TCPWM1.12) that fire their compare signal on the same clock-cycle (measured on oscilloscope). However,

- From the falling edge of the trigger signal to the rising edge of the DW1 channel's first transferred byte, 460ns pass.

- From the falling edge of the trigger signal to the rising edge of the DW0 channel's first transferred byte 480ns pass.

So there's like a 20ns offset between DW0 and DW1 transfers. At 150MHz, that's around 3000 cycles, so something's delaying DW0. I don't have enough knowledge of how the PSoc6 DW blocks handle signals so I don't know why this would be. A guess is maybe that DW0 tries to write to the MMIO Port but DW1 is currently controlling the bus, so DW0 has to wait for DW1 to finish, and only then can start. Is this reasonable? If this were the case, setting DW1's bus priority in PROT_SMPU_MS3_CTL to something lower than PROT_SMPU_MS2_CTL, should make DW0 be ahead of DW1 right?

I've tried writing to the PROT_SMPU_MSX_CTL registers to no avail. The values don't change and are just zero to begin with which is strange because the TRM mentions default, non-zero values. Sorry if this is a silly question but how are you supposed to write to these registers? This is how I'm doing it:

    uint32_t* MS2_CTL = (uint32_t*) 0x40240008;			// DW0
    uint32_t* MS3_CTL = (uint32_t*) 0x4024000C;			// DW1
    uint32_t* MS14_CTL = (uint32_t*) 0x40240038;		// CM4 CPU
    printf("Current MS14/CPU Value: %x\n",*MS14_CTL);
    printf("Current MS3/DW1 Value: %x\n", *MS3_CTL);
    printf("Current MS2/DW0 Value: %x\n", *MS2_CTL);
    *MS14_CTL = (*MS14_CTL) | 0x0300;
    *MS3_CTL  = (*MS3_CTL)  | 0x0300;
    *MS2_CTL  = (*MS2_CTL)  & 0xfcff;
    printf("---------\n");
    printf("Current MS14/CPU Value: %x\n",*MS14_CTL);
    printf("Current MS3/DW1 Value: %x\n", *MS3_CTL);
    printf("Current MS2/DW0 Value: %x\n", *MS2_CTL);

and this is the corresponding output:

Current MS14/CPU Value: 0
Current MS3/DW1 Value: 0
Current MS2/DW0 Value: 0
---------
Current MS14/CPU Value: 0
Current MS3/DW1 Value: 0
Current MS2/DW0 Value: 0


 

Thank you again for your help!

This forum is great 🙂

0 Likes
Ekta_N
Moderator
Moderator
Moderator
750 replies posted First like given 250 solutions authored

Hello @travisjayday 

Sorry for the delay in reply.

In order to write to a register you can use the following APIs:

#define CYREG_PROT_SMPU_MS2_CTL  0x40230008 (make sure you are reading the correct register, this register corresponds to the PSoC 6 2M part)

uint32_t reg_data = CY_GET_REG32(CYREG_PROT_SMPU_MS2_CTL);
reg_data = reg_data & 0xFFFFFCFF;
CY_SET_REG32(CYREG_PROT_SMPU_MS2_CTL, reg_data);
uint32_t value = CY_GET_REG32(CYREG_PROT_SMPU_MS2_CTL);
Cy_SysLib_Delay(100);

On reading this register the default value that I observed was 3. reg_data & 0xFFFFFCFF should change the value to 0, thus increasing the priority.

Thanks and Regards

Ekta

travisjayday
Level 1
Level 1
5 replies posted 5 sign-ins First reply posted

Hi @Ekta_N ,

thank you for your explanation. I realized that I was using the wrong register addresses. After I used the right addresses, I could control the priority of the DMA wires successfully.  However, I noticed that if an SPI transaction is happening,  it takes precedence over the DMA. Whenever an SPI transaction occurs, DMA transfers from SRAM to peripheral MMIO are slightly delayed. Do you know why this would be? Is it possible that the SPI controller takes full control of the bus during a transaction? Is the interaction between SPI and databus documented somewhere?

 

Thank you again, you've been a fantastic help 🙂

 

Travis

0 Likes