DMA and division

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.
greg79
Level 4
Level 4
25 replies posted 25 sign-ins 10 replies posted

My apologies for the half baked project, I am trying to wrap my head around using interrupts to request a DMA transfer. The goal: 1. clock triggered DMA transfer (1 byte per request) from LUT to SRAM -> 2. division of the transferred byte with a constant value -> 3. place the result of division on DMA2 and write byte in DAC's internal register.

How do I trigger the second DMA once the division is done? How do I trigger the division when the first DMA is done?

Thank you for all the help in advacne.

lutsramdac.PNG

 

0 Likes
1 Solution

greq,

Here's a working version with a slight caveat:

Fixes:

Keep the DMA_2 at a higher priority than DMA_1.

Change the TopDesign to the following:

Len_CONSULTRON_2-1630162926523.png

Notice the isr_1 is now connected to the second half of the 1KHz squarewave (inverted phase)  Therefore the ISR is executed with a 500us delay after the DMA_1 occurs.  Since DMA_2 is executed before DMA_1 then it will use the scaled LUT data from the previous DMA_1 transfer.

You are probably asking: Why issue the isr_1 with a delay of 500us?

The issue is we can't use isr_1 on the NRQ of DMA_1 as I stated on an earlier post.  If the NRQ occurred with each byte transfer this would be better because the NRQ only occurs AFTER the transfer.  But since we are using the SRC address incrementing on the LUT table, the NRQ will not happen until the entire table is transferred one byte at a time.

We issue isr_1 with a delay to hopefully make sure that DMA_1 finishes.  The chances of DMA_1 is done in 500us is extremely likely.

Here's a scope pic with the new sinewave scaled to /2 which is what you're looking for.

Len_CONSULTRON_3-1630163811459.png

I had a question:  isr_1 is currently a /2 operation.  Will it always be /2 or adjustable?

If it is always a /2, a HW state machine can be created to perform a /2 (HW shift right) and it totally only needs the CPU for initialization and isr_1 can be eliminated.

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

View solution in original post

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

greg,

The simplest way to load the VDAC with the new data is once you completed the division in isr_1, just push it into the VDAC_1 with the CPU.  In this case, you don't need DMA_2.  The downside is that loading the VDAC may not be totally synchronized to DMA_1. 

ISRs are prone to variable latencies due to servicing other priority isrs or disabled ints. 

A way to better synchronize  to load the VDAC is to use the exact same clock source driving DMA_1 to drive DMA_2.  This virtually guarantees synchronization with one clock delay.   Note:  Setup DMA_2 to be a higher priority.  It will load the VDAC with the initial value you assign, then DMA_1 (then isr_1) will be processed afterward.  The new dividend should be a available for DMA_2 on the next clock.  This way, you have 1/1KHz = 1ms to service the isr before needing it for DMA_2.

Len_CONSULTRON_0-1630154176571.png

Here are some notes about your DMA initialization implementation.

Issue #1

I'm assuming that your project intent is to generate a "sine" wave continuously.  If true, I see some issues with your initialization routines.

CyDmaTdSetConfiguration(DMA_1_TD[0], 8, CY_DMA_DISABLE_TD, CY_DMA_TD_TERMIN_EN | DMA_1__TD_TERMOUT_EN | CY_DMA_TD_INC_SRC_ADR | CY_DMA_TD_INC_DST_ADR);

DMA_1_TD[0] will transfer 8 bytes (using 8 Clock_1 cycles) and then terminate.  Therefore the DMA channel will also terminate and need to be restarted by CPU.

Also, you are incrementing the DST memory which in this case is "dividend".  This variable is an "int" which on this compiler should be 32-bits (4-Bytes).  DMA_1_TD[0] will move 8 bytes of the LUT to 8bytes of dividend of which dividend is only 4 bytes which means it will corrupt RAM after dividend.

Here is a change to your code line that should fix both the problems I mentioned:

CyDmaTdSetConfiguration(DMA_1_TD[0], sizeof(sineWaveLUT), DMA_1_TD[0], DMA_1__TD_TERMOUT_EN | CY_DMA_TD_INC_SRC_ADR);

In this, the TD will be active for sizeof(sineWaveLUT) [the size of the LUT table].  Once all these bytes are transferred, it will reload DMA_1_TD[0] to repeat the process for continuous wave.

Additionally I eliminated incrementing the DST address.  This now only loads the dividend without incrementing.

I also eliminated the TERMIN line.  It is not needed since you weren't planning on a HW termination of a DMA.

Note:  I recommend changing dividend to uint8_t.   This will match the LUT table data type.

Issue #2

I didn't notice the isr_1 routine in your code.

Here's my recommendation:

CY_ISR(LUT_2_VDAC_isr)
{
... Your code here ...
}

int main(void)
{
  CyGlobalIntEnable; /* Enable global interrupts. */

/* Place your initialization/startup code here (e.g. MyInst_Start()) */
  Clock_1_Start();
  VDAC8_1_Start();
  isr_1_StartEx(&LUT_2_VDAC_isr);
  configDMA1();

... rest of your code ...
}

This will keep your special isr_1 code in main.c and force a call to it with isr_1_StartEx().

Issue #3

Similar to Issue #1 you are only transferring 8 bytes and the TD and channel terminate and need to be restarted with the CPU.

Additionally, since you are incrementing the SRC address, it assumes "dividend" is a variable with 8 bytes.  However at most dividend is only 4 bytes.

I've made a change to your code line:

CyDmaTdSetConfiguration(DMA_2_TD[0], 1, DMA_2_TD[0], DMA_2__TD_TERMOUT_EN);

Note that DMA_2_TD[0] doesn't terminate it just cycles back on itself.   Also the transfer size is 1 byte.  This matches the preferred size of "dividend" which is uint8_t.

I also eliminated the incrementing of the SRC address.  In both the VDAC data register and dividend, it is a byte-only transfer.

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

Thank you, Len! The code still doesn't seem to work. You said: 

Note:  Setup DMA_2 to be a higher priority.  It will load the VDAC with the initial value you assign, then DMA_1 (then isr_1) will be processed afterward.  The new dividend should be a available for DMA_2 on the next clock.  This way, you have 1/1KHz = 1ms to service the isr before needing it for DMA_2.

How do I load an initial value to the VDAC? I have uploaded the latest, adjusted version of the project (not working unfortunately).

0 Likes
lock attach
Attachments are accessible only for community members.
greg79
Level 4
Level 4
25 replies posted 25 sign-ins 10 replies posted

I have managed to add initial value to the DAC with VDAC8_SetValue() and I can see the sine wave on my scope. However, the division on the samples does not seem to take place. I tried with different divisors and the amplitude of the sine wave does not change. What am I my missing? Is it possible that the second DMA transfer happens before the division? Thank you for the help, Len!

 

#include "project.h"

/* Defines for DMA_1 */
#define DMA_1_BYTES_PER_BURST 1
#define DMA_1_REQUEST_PER_BURST 1
#define DMA_1_SRC_BASE (sineWaveLUT)
#define DMA_1_DST_BASE (CYDEV_SRAM_BASE)

/* Defines for DMA_2 */
#define DMA_2_BYTES_PER_BURST 1
#define DMA_2_REQUEST_PER_BURST 1
#define DMA_2_SRC_BASE (CYDEV_SRAM_BASE)
#define DMA_2_DST_BASE (CYDEV_PERIPH_BASE)

void configDMA1(void);
void configDMA2(void);

uint8_t dividend;
float divisor = 12.0;

uint8_t sineWaveLUT[100] = {0x80,0x88,0x8f,0x97,0x9f,0xa7,0xae,0xb6,
0xbd,0xc4,0xca,0xd1,0xd7,0xdc,0xe2,0xe7,
0xeb,0xef,0xf3,0xf6,0xf9,0xfb,0xfd,0xfe,
0xff,0xff,0xff,0xfe,0xfd,0xfb,0xf9,0xf6,
0xf3,0xef,0xeb,0xe7,0xe2,0xdc,0xd7,0xd1,
0xca,0xc4,0xbd,0xb6,0xae,0xa7,0x9f,0x97,
0x8f,0x88,0x80,0x77,0x70,0x68,0x60,0x58,
0x51,0x49,0x42,0x3b,0x35,0x2e,0x28,0x23,
0x1d,0x18,0x14,0x10,0xc,0x9,0x6,0x4,
0x2,0x1,0x0,0x0,0x0,0x1,0x2,0x4,
0x6,0x9,0xc,0x10,0x14,0x18,0x1d,0x23,
0x28,0x2e,0x35,0x3b,0x42,0x49,0x51,0x58,
0x60,0x68,0x70,0x77};

CY_ISR(LUT_2_VDAC_isr)
{
dividend = dividend/divisor;
}

int main(void)
{
CyGlobalIntEnable; /* Enable global interrupts. */

/* Place your initialization/startup code here (e.g. MyInst_Start()) */
Clock_1_Start();
VDAC8_1_Start();
VDAC8_1_SetValue(0);
isr_1_StartEx(&LUT_2_VDAC_isr);
configDMA1();
configDMA2();

for(;;)
{
/* Place your application code here. */
}
}

void configDMA1(){
/* Variable declarations for DMA_1 */
/* Move these variable declarations to the top of the function */
uint8 DMA_1_Chan;
uint8 DMA_1_TD[1];

/* DMA Configuration for DMA_1 */
DMA_1_Chan = DMA_1_DmaInitialize(DMA_1_BYTES_PER_BURST, DMA_1_REQUEST_PER_BURST,
HI16(DMA_1_SRC_BASE), HI16(DMA_1_DST_BASE));
DMA_1_TD[0] = CyDmaTdAllocate();
CyDmaTdSetConfiguration(DMA_1_TD[0], sizeof(sineWaveLUT), DMA_1_TD[0], DMA_1__TD_TERMOUT_EN | CY_DMA_TD_INC_SRC_ADR);
CyDmaTdSetAddress(DMA_1_TD[0], LO16((uint32)sineWaveLUT), LO16((uint32)dividend));
CyDmaChSetInitialTd(DMA_1_Chan, DMA_1_TD[0]);
CyDmaChEnable(DMA_1_Chan, 1);

};

void configDMA2(){
/* Variable declarations for DMA_2 */
/* Move these variable declarations to the top of the function */
uint8 DMA_2_Chan;
uint8 DMA_2_TD[1];

/* DMA Configuration for DMA_2 */
DMA_2_Chan = DMA_2_DmaInitialize(DMA_2_BYTES_PER_BURST, DMA_2_REQUEST_PER_BURST,
HI16(DMA_2_SRC_BASE), HI16(DMA_2_DST_BASE));
DMA_2_TD[0] = CyDmaTdAllocate();
CyDmaTdSetConfiguration(DMA_2_TD[0], 1, DMA_2_TD[0], DMA_2__TD_TERMOUT_EN);
CyDmaTdSetAddress(DMA_2_TD[0], LO16((uint32)dividend), LO16((uint32)VDAC8_1_Data_PTR));
CyDmaChSetInitialTd(DMA_2_Chan, DMA_2_TD[0]);
CyDmaChEnable(DMA_2_Chan, 1);
};

 

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

greq,

I made two very simple fixes and I was able to get the following scope plot.

Len_CONSULTRON_0-1630159820019.png

Fix #1

In both the DMA initializations to have to reference the "dividend" pointer not the value.

Therefore in DMA_1 init:

CyDmaTdSetAddress(DMA_1_TD[0], LO16((uint32)sineWaveLUT), LO16((uint32)&dividend));

in DMA_2_init:

CyDmaTdSetAddress(DMA_2_TD[0], LO16((uint32)&dividend), LO16((uint32)VDAC8_1_Data_PTR));

 

Fix #2

You didn't call the DMA_2 initialization in main().

Therefore add after the DMA_1 init:

    configDMA2();

 

New issue:

There appears a 'glitch' in the sinewave.   It is a dis-continuality of the sine function.  However I looked at you LUT and couldn't find where this might be.

There we are close.  One more thing to find and fix.

 

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

greg,

If I have the project running and I select "Debug\Attach to Running Target..."  with "Halt target on attach" selected, the 'glitch' goes away and the sinewave is ideal.

I know the reason for the issue.

The isr_1 ONLY happens when NRQ is asserted when DMA_1_TD[0] is complete.  This means that the isr_1 occurs when All the LUT table data is moved.  Therefore all but one LUT table data is at full scale.  The one at the very end is a /2.  Hence the 'glitch'.

I'm working on a fix.  It's a bit tricky.

The problem is basically your original question: How to properly sequence a DMA to RAM, perform a division with the CPU and DMA the result to the VDAC.

The issue is that the DMA operations are very dependable and predictable but the CPU operation (isr_1) is not as dependable.

Question:  isr_1 is currently a /2 operation.  Will it always be /2 or adjustable?

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

greq,

Here's a working version with a slight caveat:

Fixes:

Keep the DMA_2 at a higher priority than DMA_1.

Change the TopDesign to the following:

Len_CONSULTRON_2-1630162926523.png

Notice the isr_1 is now connected to the second half of the 1KHz squarewave (inverted phase)  Therefore the ISR is executed with a 500us delay after the DMA_1 occurs.  Since DMA_2 is executed before DMA_1 then it will use the scaled LUT data from the previous DMA_1 transfer.

You are probably asking: Why issue the isr_1 with a delay of 500us?

The issue is we can't use isr_1 on the NRQ of DMA_1 as I stated on an earlier post.  If the NRQ occurred with each byte transfer this would be better because the NRQ only occurs AFTER the transfer.  But since we are using the SRC address incrementing on the LUT table, the NRQ will not happen until the entire table is transferred one byte at a time.

We issue isr_1 with a delay to hopefully make sure that DMA_1 finishes.  The chances of DMA_1 is done in 500us is extremely likely.

Here's a scope pic with the new sinewave scaled to /2 which is what you're looking for.

Len_CONSULTRON_3-1630163811459.png

I had a question:  isr_1 is currently a /2 operation.  Will it always be /2 or adjustable?

If it is always a /2, a HW state machine can be created to perform a /2 (HW shift right) and it totally only needs the CPU for initialization and isr_1 can be eliminated.

Len
"Engineering is an Art. The Art of Compromise."
0 Likes
greg79
Level 4
Level 4
25 replies posted 25 sign-ins 10 replies posted

I have followed every recommendation and the division never seems to take place, the output signal is still between 0V and 4V. What am I missing?

scope.PNG

Here is the current code, I wonder if there is something about the DMA1 config that stops the the interrupt. 

 

#include "project.h"

    /* Defines for DMA_1 */
#define DMA_1_BYTES_PER_BURST 1
#define DMA_1_REQUEST_PER_BURST 1
#define DMA_1_SRC_BASE (sineWaveLUT)
#define DMA_1_DST_BASE (CYDEV_SRAM_BASE)

/* Defines for DMA_2 */
#define DMA_2_BYTES_PER_BURST 1
#define DMA_2_REQUEST_PER_BURST 1
#define DMA_2_SRC_BASE (CYDEV_SRAM_BASE)
#define DMA_2_DST_BASE (CYDEV_PERIPH_BASE)

void configDMA1(void);
void configDMA2(void);

uint8_t dividend;
float divisor = 2.0;

uint8_t sineWaveLUT[100] = {0x80,0x88,0x8f,0x97,0x9f,0xa7,0xae,0xb6,
0xbd,0xc4,0xca,0xd1,0xd7,0xdc,0xe2,0xe7,
0xeb,0xef,0xf3,0xf6,0xf9,0xfb,0xfd,0xfe,
0xff,0xff,0xff,0xfe,0xfd,0xfb,0xf9,0xf6,
0xf3,0xef,0xeb,0xe7,0xe2,0xdc,0xd7,0xd1,
0xca,0xc4,0xbd,0xb6,0xae,0xa7,0x9f,0x97,
0x8f,0x88,0x80,0x77,0x70,0x68,0x60,0x58,
0x51,0x49,0x42,0x3b,0x35,0x2e,0x28,0x23,
0x1d,0x18,0x14,0x10,0xc,0x9,0x6,0x4,
0x2,0x1,0x0,0x0,0x0,0x1,0x2,0x4,
0x6,0x9,0xc,0x10,0x14,0x18,0x1d,0x23,
0x28,0x2e,0x35,0x3b,0x42,0x49,0x51,0x58,
0x60,0x68,0x70,0x77};

CY_ISR(LUT_2_VDAC_isr)
{
    dividend = dividend/divisor;
}

int main(void)
{
    CyGlobalIntEnable; /* Enable global interrupts. */

    /* Place your initialization/startup code here (e.g. MyInst_Start()) */
    Clock_1_Start();
    VDAC8_1_Start();
    VDAC8_1_SetValue(0);
    isr_1_StartEx(&LUT_2_VDAC_isr);
    configDMA1();
    configDMA2();
    
    for(;;)
    {
        /* Place your application code here. */
    }
}

void configDMA1(){
/* Variable declarations for DMA_1 */
/* Move these variable declarations to the top of the function */
uint8 DMA_1_Chan;
uint8 DMA_1_TD[1];

/* DMA Configuration for DMA_1 */
DMA_1_Chan = DMA_1_DmaInitialize(DMA_1_BYTES_PER_BURST, DMA_1_REQUEST_PER_BURST, 
    HI16(DMA_1_SRC_BASE), HI16(DMA_1_DST_BASE));
DMA_1_TD[0] = CyDmaTdAllocate();
CyDmaTdSetConfiguration(DMA_1_TD[0], sizeof(sineWaveLUT), DMA_1_TD[0], DMA_1__TD_TERMOUT_EN | CY_DMA_TD_INC_SRC_ADR);
CyDmaTdSetAddress(DMA_1_TD[0], LO16((uint32)sineWaveLUT), LO16((uint32)&dividend));
CyDmaChSetInitialTd(DMA_1_Chan, DMA_1_TD[0]);
CyDmaChEnable(DMA_1_Chan, 1);

};

void configDMA2(){
/* Variable declarations for DMA_2 */
/* Move these variable declarations to the top of the function */
uint8 DMA_2_Chan;
uint8 DMA_2_TD[1];

/* DMA Configuration for DMA_2 */
DMA_2_Chan = DMA_2_DmaInitialize(DMA_2_BYTES_PER_BURST, DMA_2_REQUEST_PER_BURST, 
    HI16(DMA_2_SRC_BASE), HI16(DMA_2_DST_BASE));
DMA_2_TD[0] = CyDmaTdAllocate();
CyDmaTdSetConfiguration(DMA_2_TD[0], 1, DMA_2_TD[0], DMA_2__TD_TERMOUT_EN);
CyDmaTdSetAddress(DMA_2_TD[0], LO16((uint32)&dividend), LO16((uint32)VDAC8_1_Data_PTR));
CyDmaChSetInitialTd(DMA_2_Chan, DMA_2_TD[0]);
CyDmaChEnable(DMA_2_Chan, 1);
};
0 Likes
lock attach
Attachments are accessible only for community members.
greg79
Level 4
Level 4
25 replies posted 25 sign-ins 10 replies posted

Amazing explanation, thank you and it's working! The output is 0V-2V as expected. I forgot the NRQ of DMA only happens when the entire LUT is read.

I want to be able to change the divisor and apply other operations too. The idea is to mix signals from different LUTs and adjust their amplitude before mixing. At the moment I just want to be able to  attenuate the signal and I presume changing the divisor value will do the job. 

The final, working project is attached.

0 Likes

greg,

I'm glad you resolved the issues.

As I said in the explanation that inverting the Clock_1 and starting isr_1 on the opposite edge of the clock that the DMAs are using means that you have only 1/2 the period of Clock_1 (500us in this case) to process the data.

This was the easiest implementation and most straightforward in code.

If you need more time than 1/2 of Clock_1 period there is a way to maximizing the time if you're interested.

It is much more complicated and requires knowledge of sparsely documented DMA features to know when the data has been transferred by DMA_1.

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

greg,

Mod #1

I've modified your final project to allow you nearly the full Clock_1 period to process isr_1.

It uses the DMA techniques I learned to determine where the DMA is currently processing. 

I have commented the code to try to explain what's going on.  It might still have "holes" in the explanation.

I recommend examining the changes in the project.

I've also made some other modifications that should improve the performance.

Mod #2

I placed an Opamp following the VDAC_1.  VDACs (or also IDACs) are medium impedance resources.  Capacitance or additional resistance loads will usually lower the output voltage (due to the medium source resistance of about 500 to 1000 ohms.)

Placing an Opamp at high power can provide up to 10mA out of most GPIO pins.  If you want more source/sink current, you need to change the GPIO to either P0.0, P0.1, P3.6 or P3.7.  These pins can source/sink up to 25mA.

When I used your pin assignment (P1.6) without the Opamp, my output was reduced to 1.81V peak-to-peak.   Using the Opamp before P1.6, my output P2P was 2.09V

Mod #3

I created a new var "uint8_t dividend_scaled".   I found that if I stop isr_1 (disable for example or use the debugger), the output will go to full-scale because the LUT table processing is not a active.

By using a new var, I process the LUT table data transferred when isr_1 is active but when not active the output flat-lines.

Len
"Engineering is an Art. The Art of Compromise."
0 Likes
greg79
Level 4
Level 4
25 replies posted 25 sign-ins 10 replies posted

Thank you, Len! Fantastic!

0 Likes
greg79
Level 4
Level 4
25 replies posted 25 sign-ins 10 replies posted

I just checked the code and all the changes and the comments make sense, I appreciate your time and effort. It is incredible how much I learn from posting here! 

I started wondering if using the DFB for math would help or would that be an overkill? I need to generate 8 signals, half of those are mixed from other signals:  a lot of signal processing such as attenuation and mixing so there will be a lot of math. I wonder how can I time and sync these math operations to the DMA transfer? 

To give you an idea what I want: 

  1. DMA transfer sample A from LUT then attenuate it for mixing (division) and save the result in a buffer/array
  2. DMA transfer sample B from LUT then attenuate it for mixing (division) and save the result in a buffer/array
  3. mix (add) sample A and B from buffer and write (DMA transfer sample) the result into the DAC's register
  4. repeat this with the other 7 signals

It is paramount that the divisions and the addition are not skipped and are executed fast before the clock-triggered DMA brings in the next samples. The DFB (or state machine?) would be great for this but then I have this nagging feeling that all of this and more was successfully implemented in the 80's with 8-bit processors (8051 and Z80) in Japanese and US polyphonic MIDI synthesizers. Anyway, I will keep experimenting and see how fast the CPU can handle the signal processing. 

Untitled drawing (3).jpg

 

0 Likes

greg,

I've never used the DFB.  Therefore my recommendation to use it as the high-speed math function would need to be validated by others more familiar with it.

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

I'm making it too complicated. I don't think using DMA to transfer one 8-bit byte is necessary, I can just do the memory-to-memory transfer via the software, then do the math. I just don't know how to do the whole process (mem-to-mem transfer, division, addition, transfer to DAC) synced to a clock. Maybe an interrupt tied to a clock? I will investigate, prototype and share. 

About the DFB: unnecessary in this context nevertheless it is the most fascinating component of PSOC. Paul Phillips's DFBUtility is highly recommended, the samples and the visualization helps a lot. 

0 Likes

Greg,

I don't understand a necessity to divide the lookup table values. Wouldn't be enough to multiply 8-bit LUT1 x LUT2 and take the highest byte from the 16-bit product? 

0 Likes

So to mix the two samples one can just add them in 16 bit and take the highest byte? This is great, thanks! The two signals must be attenuated before mixing. In synthesizer terms, say I want to modulate the filter with a gentle LFO of 10% amount (1/10 amplitude) and an envelope of 50%. The two signals are then added. 

0 Likes

Greg,

Are you looking for something like that? Note that only multiplication and bit shift were used:

Wave Env Mod_01.png

 

0 Likes

Exactly!!! I have been trying to prototype a simpler way without the here.

0 Likes

greg,

I assume from previous posts you are looking for 8-note polyphonic operation.  If so, what is the top sine frequency to support?

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

The synth is an analog IC and is monophonic. However it requires 8 control voltage signals so in essence, it is 8 voice polyphonic (even though the control voltage is not in audio range). I have posted a project in an other post where I'm trying to achieve the task without DMAs here (with no success yet): 

https://community.cypress.com/t5/PSoC-5-3-1-MCU/LUT-to-DAC-transfer-without-DMA/m-p/286952#M45514

0 Likes

greg,

https://community.cypress.com/t5/PSoC-5-3-1-MCU/LUT-to-DAC-transfer-without-DMA/m-p/286952#M45514

has been solved.

Question: What is the top sine frequency to support?

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

greg79,

The AS3394 synth chip seems to require static control voltages or at least slow-domain, like  pich control DC voltage, envelope profile, amplitude modulation (vibrato), etc. It already has a built-in VCO to develop the pitch carrier wave, and uses other analog inputs to modulate that VCO output.

AS3394 synth.png

On the other hand, PSoC5 is capable of digital wave synthesis like shown below (w/o using AS3394). But it wasn't the goal - was it?

Wave Env Mod_01.png

Can you please explain which way you want to proceed?

0 Likes

The goal is to generate the 8 control voltages at this point, exactly the same way as it was used before (albeit with 8-bit CPUs): one DAC and a demux with holding capacitors on the outputs. Just like the image, DAC -> demux -> voice chip.

seq.jpg

0 Likes
lock attach
Attachments are accessible only for community members.
greg79
Level 4
Level 4
25 replies posted 25 sign-ins 10 replies posted

At last, mixing two signals from two LUTs (see attached project). It is rather inelegant and the two signals are not in phase due to the different sample size (100 and 255). Nevertheless it works and verified it with the scope. I can even change the time/frequency of the signals by changing the LUT increment value. Once again, it is not elegant or efficient but clearly demonstrates that two signals can be mixed with arbitrary amplitudes. Look ma, no DMA!

The next step is two create two streams of signals mixed from four and demuxed to two analog pins. Thank you for all the help!

scope.PNG

 

#include "project.h"

uint8_t sineSampleSize = 100;
uint8_t sawSampleSize = 255;
uint8_t tableIndexSaw;
uint8_t tableIndexSine;
float amplitudeIndex = 0.5;
uint8_t sampleIncrement = 1; // changing the icrement value skips samples thus changing the playback speed aka frequency

uint8_t sineWaveLUT[100] = {0x80,0x88,0x8f,0x97,0x9f,0xa7,0xae,0xb6,
0xbd,0xc4,0xca,0xd1,0xd7,0xdc,0xe2,0xe7,
0xeb,0xef,0xf3,0xf6,0xf9,0xfb,0xfd,0xfe,
0xff,0xff,0xff,0xfe,0xfd,0xfb,0xf9,0xf6,
0xf3,0xef,0xeb,0xe7,0xe2,0xdc,0xd7,0xd1,
0xca,0xc4,0xbd,0xb6,0xae,0xa7,0x9f,0x97,
0x8f,0x88,0x80,0x77,0x70,0x68,0x60,0x58,
0x51,0x49,0x42,0x3b,0x35,0x2e,0x28,0x23,
0x1d,0x18,0x14,0x10,0xc,0x9,0x6,0x4,
0x2,0x1,0x0,0x0,0x0,0x1,0x2,0x4,
0x6,0x9,0xc,0x10,0x14,0x18,0x1d,0x23,
0x28,0x2e,0x35,0x3b,0x42,0x49,0x51,0x58,
0x60,0x68,0x70,0x77};

uint8_t sawWaveLUT[255] = {
0x00,0x01, 
0x02,0x03,0x04,0x05,0x06, 
0x07,0x08,0x09,0x0A,0x0B, 
0x0C,0x0D,0x0E,0x0F,0x10, 
0x11,0x12,0x13,0x14,0x15, 
0x16,0x17,0x18,0x19,0x1A, 
0x1B,0x1C,0x1D,0x1E,0x1F, 
0x20,0x21,0x22,0x23,0x24, 
0x25,0x26,0x27,0x28,0x29, 
0x2A,0x2B,0x2C,0x2D,0x2E, 
0x2F,0x30,0x31,0x32,0x33, 
0x34,0x35,0x36,0x37,0x38, 
0x39,0x3A,0x3B,0x3C,0x3D, 
0x3E,0x3F,0x40,0x41,0x42, 
0x43,0x44,0x45,0x46,0x47, 
0x48,0x49,0x4A,0x4B,0x4C, 
0x4D,0x4E,0x4F,0x50,0x51, 
0x52,0x53,0x54,0x55,0x56, 
0x57,0x58,0x59,0x5A,0x5B, 
0x5C,0x5D,0x5E,0x5F,0x60, 
0x61,0x62,0x63,0x64,0x65, 
0x66,0x67,0x68,0x69,0x6A, 
0x6B,0x6C,0x6D,0x6E,0x6F, 
0x70,0x71,0x72,0x73,0x74, 
0x75,0x76,0x77,0x78,0x79, 
0x7A,0x7B,0x7C,0x7D,0x7E, 
0x7F,0x80,0x81,0x82,0x83, 
0x84,0x85,0x86,0x87,0x88, 
0x89,0x8A,0x8B,0x8C,0x8D, 
0x8E,0x8F,0x90,0x91,0x92, 
0x93,0x94,0x95,0x96,0x97, 
0x98,0x99,0x9A,0x9B,0x9C, 
0x9D,0x9E,0x9F,0xA0,0xA1, 
0xA2,0xA3,0xA4,0xA5,0xA6, 
0xA7,0xA8,0xA9,0xAA,0xAB, 
0xAC,0xAD,0xAE,0xAF,0xB0, 
0xB1,0xB2,0xB3,0xB4,0xB5, 
0xB6,0xB7,0xB8,0xB9,0xBA, 
0xBB,0xBC,0xBD,0xBE,0xBF, 
0xC0,0xC1,0xC2,0xC3,0xC4, 
0xC5,0xC6,0xC7,0xC8,0xC9, 
0xCA,0xCB,0xCC,0xCD,0xCE, 
0xCF,0xD0,0xD1,0xD2,0xD3, 
0xD4,0xD5,0xD6,0xD7,0xD8, 
0xD9,0xDA,0xDB,0xDC,0xDD, 
0xDE,0xDF,0xE0,0xE1,0xE2, 
0xE3,0xE4,0xE5,0xE6,0xE7, 
0xE8,0xE9,0xEA,0xEB,0xEC, 
0xED,0xEE,0xEF,0xF0,0xF1, 
0xF2,0xF3,0xF4,0xF5,0xF6, 
0xF7,0xF8,0xF9,0xFA,0xFB, 
0xFC,0xFD,0xFE
};

CY_ISR(clock_interrupt){
   
    int sampleSaw = sawWaveLUT[tableIndexSaw];
    //VDAC8_SetValue(sample*amplitudeIndex); // attenuate sample and write it in the DAC's internal register
    if(tableIndexSaw == sawSampleSize ){        // if the last sample of the LUT is reached, start again from first sample (index 0)
        tableIndexSaw=0;
    } else {
    tableIndexSaw = tableIndexSaw + sampleIncrement;  // if last sample is not reached, increase the index
    }
    
    int sampleSine = sineWaveLUT[tableIndexSine];
    if(tableIndexSine == sineSampleSize ){        // if the last sample of the LUT is reached, start again from first sample (index 0)
        tableIndexSine=0;
    } else {
    tableIndexSine = tableIndexSine + sampleIncrement;  // if last sample is not reached, increase the index
    }
    int mixSample = sampleSaw*0.3 + sampleSine*0.5;
    VDAC8_SetValue(mixSample);
}

int main(void)
{
    VDAC8_Start();
    Opamp_1_Start();
    clock_interrupt_StartEx(clock_interrupt);
    CyGlobalIntEnable; /* Enable global interrupts. */
    
    for(;;)
    {
        /* Place your application code here. */
    }
}

 

 

Thank you for all the help, I am finally getting there!

0 Likes
lock attach
Attachments are accessible only for community members.

greg,

For S&Gs, I created a 8-note polyphonic project.  It can play up to 8 notes simultaneously.  

I won't get into the theory here and hope you will analyze the project I attached.

It short:

  • Each note has a settable frequency. (Frequency = 0 is OFF)
  • If the note is ON, It uses the DMA to send the LUT to SRAM.
  • The SRAM value is scaled (/2) (via isr)
  • The VDAC is updated at 200KHz.
  • The VDAC isr accumulates all note scaled values and averages them.
  • The project uses a UART @ 115.2Kbps 8N1 to signal which notes to turn ON or OFF.
    • '1' - note 1 ON at 110 Hz (A2)
    • '!' - note 1 OFF.
    • '2' - note 2 ON at 138.591 Hz (C#3)
    • '@' - note 2 OFF
    • '3' - note 3 ON at 164.814 Hz (E3)
    • '#' - note 3 OFF.
    • '4' - note 4 ON at 220.0 Hz (A3)
    • '$' - note 4 OFF
    • ... I hope you can see the pattern here ...
    • 'O' - ALL notes OFF.

If all notes are turned on you should get A major chord across 2 1/2 octaves.

There is API calls where you can enter your own note frequencies and will try to scale them as best as possible.

The UART can be replaced with switches if you want.

The code may not be elegant (or bug free).  However it is a proof of concept.

Len
"Engineering is an Art. The Art of Compromise."
0 Likes
greg79
Level 4
Level 4
25 replies posted 25 sign-ins 10 replies posted

Marvelous, thank you!

0 Likes