I've made several posts in the PSOC Smarter Life Challenge, but never used the PSOC in my own designs. After I've seen the possibilities in the challenge, I came across a project at my work where I realized that PSOC could be a nice solution.
We're using some haptic feedback voice coils that need to be driven with a sine wave at a specific frequency. We're also using an EtherCAT based system that should control these motors. To make control easier, I wanted to make something where a digital input would come in, and a sine wave would come out if the input line was asserted for a channel. The other thing I wanted is to synchronize the outputs with the sine wave produced; the output should only be active for a multiple of one sine wave.
At first I wanted to use DMA for this, but the PSOC4 doesn't have that and the PSOC5 was too large for the design (and I wanted to play around with the PSOC4, to be honest).
After a day of tinkering, this was the result:
I'm using PWM1 to generate the PWM of the sine wave. The Digital comparator is contolling the 8 parallel D-flipflops whose outputs are AND-ed with the PWM signal. In my code, I'm writing the sine table sample number to 'DDS_count'. At the first sample, the comparators' output is changing state, and the DFFs latch the input values. The DDS_Sample_Clock determines how fast I'm going through my sine table. It generates an interrupt on which a new sample is written to the PWM block.
To get the software running, I only had to follow this post:Using Math Functions in PSoC Creator for PSoC5's GCC Compiler - Cypress to resolve an error with the linker. This post:Introduction to Interrupts for PSoC 4 Hardware Blocks - Cypress helped me to get going with the interrupts. Here's the code:
#include <project.h> #include <math.h> #define DDS_LENGTH 100 uint8_t dds_table[DDS_LENGTH] = {0,10,20,30,40,50,60,70,80,90}; CY_ISR(DDS_sample_clock_timer) { uint32 int_source; int_source = DDS_Sample_Clock_GetInterruptSourceMasked(); DDS_count_Control = DDS_count_Read() + 1; if(DDS_count_Read() == DDS_LENGTH) DDS_count_Control = 0; PWM_1_WriteCompare(dds_table[DDS_count_Read()]<<8); DDS_Sample_Clock_ClearInterrupt(int_source); } void CreateSineTable(void) { int i; for(i = 0 ; i < DDS_LENGTH ; i++) { dds_table[i] = 126*(1.0-cos(i*6.28/DDS_LENGTH)); } } int main() { CreateSineTable(); /* Place your initialization/startup code here (e.g. MyInst_Start()) */ PWM_1_Start(); //DDS_max_count_Control = DDS_LENGTH; DDS_count_Control = 0; DDS_Sample_Clock_Start(); DDS_Sample_Clock_SetInterruptMode(DDS_Sample_Clock_INTR_MASK_TC); DDS_sample_clock_timer_Start(); DDS_sample_clock_timer_StartEx(DDS_sample_clock_timer); CyGlobalIntEnable; /* Uncomment this line to enable global interrupts. */ for(;;) { /* Place your application code here. */ } }
That looks simple doesn't it? To enhance the resolution, I only need to increase DDS_LENGTH, and change the DDS_Sample_Clock period.
The only thing that I've encountered is that it sometimes would be nice to get access to one of the values in a building block in the CySch directly. For example, it would be nice to directly use the output of an ADC to the DDS_Sample_clock Period value to change the output frequency. I don't kinow whether/how that's possible