Background info for my question:
I am an amateur/hobby circuit designer (self taught), so apologies in advance. Also sorry for the long post. I have many skills, but brevity is not one of them.
I've designed a pinball lighting control circuit board. When I originally spec'ed out my requirements, and searched for components, the PSoC 1 rose to the top of my list. Many may think the PSoC 1 is not the best choice for this application (and they're probably right), but at this point I'm too heavily invested in time and money to do anything different.
I'm using the PSoC 1 with 56 GPIO, and configuring all 56 as LED outputs. I will say that my main goal is 100% achieved, I have a working solution that meets my original objectives of directly controlling all 56 outputs without matrixing, and minimal latency, and decent PWM for LED's (good enough for pinball, anyway, though a little less flicker would be a nice-to-have). Not only that, but my self-designed board is significantly cheaper than the commercial options that are available, so win-win even if my PSoC 1 choice was ill-conceived.
These same LED outputs are also optionally used to control a separate Power Driver board, allowing me to turn on/off pinball solenoids just like turning on/off an LED, except I don't use PWM for those signals, just on/off.
Keep in mind that for my following questions, each of the 56 outputs needs to be individually controllable for on/off and PWM duty cycle.
It was only after I designed my pinball lighting solution that someone asked if I could use it to control servos, and at first I thought sure, absolutley! But upon further inspection I don't think the PWM performance is high enough for accurate servo control, bringing me here.
Question 1: Hardware PWM vs. Software PWM?
I know the PSoC 1 can be configured with hardware PWM by using a digital block. But even with only 8-bit PWM, I have to dedicate a full digital block to PWM calculations. And best I can tell, I can only drive 1 GPIO individually with hardware PWM. I did see some options for using one PWM signal to drive multiple pins, but it seems like this needs to use shared PWM duty cycles, so this wouldn't allow me to individually control multiple GPIO using a single PWM digital block.
Am I correct? So a software PWM solution is the only option if I need 56 GPIO individually valued?
My chosen PSoC only has 4 digital blocks, so I quickly settled on software PWM, but I'm wondering if I misunderstand how best to leverage the PSoC 1's capabilities.
I would love it if there was a way to use a digital block to improve PWM performance for all 56 GPIO simultaneously, but while maintaining individual addressability.
Question 2: Software PWM Performance?
After significant code refinement, I have gotten my software PWM performance up to a staggering 8Hz (laugh), using 64 PWM levels (6-bit), while setting all 56 pins on each pass.
To provide a bit more detail: my software does 64 loops through the on/off settings for each PWM Period, and is able to complete 8 Period loops in 1 second (8*64 = 512 total loops per second). And it is doing this for all 56 GPIO, so technically it's processing the on/off state at a rate of around 28,672/GPIO Pins per second.
8Hz is pretty low. Like I wrote above, this is good enough for basic pinball lighting, and triggering solenoids on/off, but a bit slow for more advanced RGB lighting, and way too low for servos which commonly need around 50Hz. Also, servos need very fine pulse duration control often measured in fractions of a millisecond. My smallest duration right now is around 2ms, far too long to control servos. Even if my basic loop was magically running 6x faster at 50Hz, I don't think the duration control is fine enough for servos, and would require a hardware PWM implementation for adequate control.
So does the performance I'm achieving sound reasonable for a PSoC 1? Somehow I thought a 24MHz processor would be a little faster. It seems my code is taking 837 cycles per pin to process my code, which seems high to me.
The PSoC 1 Clocks and Global Resources documentation states that M8C assembly language instructions take between 4 and 15 cycles of CPUCLK to execute. I'm using C, not assembly, so I'm not sure if C has a performance overhead. For each GPIO, I use each GPIO's target PWM level (0-64) to query a 65x65 constant array of predetermined 6-bit PWM values, then use GetState to compare if the new On/Off state is different, and if it is different then I issue the LED_1_On or Off Pragma command. That's it, the code is super simple, so I didn't think it would take so many CPU clocks to look up a boolean value in a 2-dimensional array and see if it is different than the current On/Off state.
It also seems that if I scaled back my grand ambitions from 56 GPIO to just 1, at best this would only be 56x faster, essentially a 448 Hz software PWM solution for 1 GPIO.
Since I need 56 PWM LED outputs, I've never tested the digital block to see how fast that PWM solution performs, though I'd wager it's quite a bit faster. Still, 448 Hz for a single 6-bit software PWM coded GPIO seems pitiful.
Question 3: Best Practices for Accessing GPIO - Registers vs. Pragmas?
In my early test code, I was directly accessing the GPIO using the registers, like PRT4DR, thinking that would be fastest. But I've since changed to using Pragmas, like LED_01_Start, LED_01_On or LED_01_Off. From my tests, it seems performance is the same, and the Pragmas make for easier coding. Am I wrong?
Similarly, I was originally setting the GPIO ON or OFF on every pass, even if there was no change from the previous pass. I refined my code to only set the state if it had changed, and this seemed to boost performance. I started with an array of 56 booleans to track the On/Off states manually, but then started using the LED_01_GetState instead, and this Pragma seemed to have no adverse effect on performance. The only downside I discovered to using GetState is that it only reported correctly if I used the Pragmas for On and Off, otherwise it returned the wrong value if I set On/Off directly via registers.
Am I right that Pragmas have no performance impact, and possibly even memory benefits since I don't need to track and array of on/off states in my code?
Question 4: Can I Set All GPIO with 1 Command?
In my software PWM solution, I am stepping through each GPIO, one-by-one, to set their On/Off state (but only if it has changed). This seems really inefficient. In a worst case scenario of a 50% duty cycle, I'm flipping each GPIO's state with every pass, one-by-one.
It seems it would be more efficient if I could pass a single command with 7-byte value that represents the on/off state of all 56 GPIO.
I couldn't find any commands like this. Perhaps I'm overestimating the potential impact, as in my testing it seems that my code sets 1 GPIO at the same rate as all 56 GPIO, so perhaps toggling the GPIO state doesn't have much of a performance penalty. I guess this makes sense, as I would expect the PSoC to be highly optimized for setting the GPIO states. But if I could eliminate a loop through all 56 GPIO one-by-one, perhaps PWM frequency would improve due to code efficiency.
Question 5: Best Global Resource Settings?
Performance is my main concern for this device. Low latency is #1, followed by higher PWM frequency. For that reason, I've pretty much maxed out the Global Resource settings (or, at least I think I have maxed them out). But I understand that some of these settings consume more power without any benefit to my device, so minimizing wasted power drain seems wise if possible.
Below is what I've configured, and my reasoning. Does this look right?
Power Setting is set to 5.0v / 24MHz - I believe this is the fastest option.
CPU_Clock is SysClk/1 - I figure this is what is controlling my code speed, and that this is the fastest clock.
Sleep_Time is 1_Hz - I'm never "sleeping" in my code, it simply runs my simple loop forever as fast as possible, no delays, so I'm not sure if this has any impact at all for my purposes. Though perhaps I'm confused how this affects performance and it is slowing down my code execution by waking up the CPU every 1Hz!!!
VC1 is set to 16 - I don't see any performance difference vs. setting VC1 to 1
VC2 is set to 16 - I don't see any performance difference vs. setting VC2 to 1
VC3 is set to VC2/16 - I don't see any performance difference vs. setting to 1. The documentation indicates I can set this lower than VC1/2, going as low as 256. Should I change this to 256 for lower power drain? Diminishing returns?
SycClk Source is set to Internal - I'm using the built in IMO, not an external crystal solution
SysClk*2 Disable is set to No - I don't think the SYSCLK Doubler affects software PWM, as I don't think software code is able to take advantage of the special 48MHz clock timings that are available to the digital blocks, so I thought I should set this to Yes for more power savings. But every time I set it to Yes, the PSoC 1 failed to connect properly through USB. Perhaps that indicates a hardware issue, but leaving it set to No works just fine, even though this seems like a higher performance setting.
Analog Power is set to All Off - I originally was the default of SC On/Ref Low. All 56 GPIO are using the LED module configured to Active High. Since I'm using digital IO, my assessment is that these Analog Power settings don't apply to me, and it seems to behave the same with the All Off setting, which should have the least power drain.
Ref Mux is set to (Vdd/2)+/-BandGap - This too seems to be related to Analog Power for use in Analog Blocks, so I'm thinking this value has no impact at all for my design.
AGndBypass is set to Disable - This seems to be related to Analog Power's Ground, for use in Analog Blocks, so I think this has no impact on my design.
Op_Amp Bias is set to Low - Another analog setting that doesn't apply to my design?
A_Buff_Power is set to Low - And another analog setting that doesn't affect my design, right?
Trip Voltage [LVD] is set to 4.81V - The fact that my tests are operating without restart hiccups suggests that my USB 5V power delivery is working correctly, otherwise I would expect even minor voltage sag to cause restarts with this being the most aggressive setting. Side note, this is the 2nd revision of my design. My 1st design used an external power source shared with the LED's, and lighting more than a handful at once caused enough voltage sag to trigger a PSoC reset. I'm now using USB power for the PSoC, and separate power planes for my LED's, and I'm ecstatic that it's working well.
LVDThrottleBack is set to Disable - Combined with my take on the LVD behavior, I think this suggests that I am successfully running at 24MHz, and not scaling back to 12MHz due to low voltage.
Watchdog Enable is set to Disable - My understanding of the Watchdog is that it can check if the PSoC is hung, and restart it if it is non-responsive. Other than low software PWM performance, my board seems to be working flawlessly, even without the Watchdog enabled. I'm thinking that I might need to set this to Enabled for a production version of my design, to make it more reliable for end-users, but I'm not sure if that's the right way to think about this feature. But since I have a software loop that runs non-stop forever without sleep, I'm not sure that this even gives the Watchdog an opportunity to be effective.
Question 6 - What am I overlooking?
Sorry to be so greedy for input, but I thought it wise to ask the most general of questions - what am I completely overlooking?
All I need is for the PSoC 1 to individually address all 56 GPIO with a PWM signal, at the highest possible frequency, and with the most accurate duty cycle period duration.
Should I be using timers? Interrupts? Assembly instead of C?
I have a very refined loop that simply updates all GPIO as fast as possible with the current On/Off state based upon predefined PWM values. The code occasionally checks if the USB buffer has new PWM data to process (this is a tiny 56 byte record), a query that I've varied from once per PWM Period to 64 times per period with no discernable impact. I'm currently checking the USB buffers for data once every 2 PWM periods, approximately 256 checks/sec, or every 4ms. This is to keep latency low for my pinball control software.
Because it is simply running a loop as fast as possible, I didn't see a need for timers or interrupts, even though these would be typical in a PWM solution. I do understand that my timer-less approach leads to some performance variability, though it seems consistent enough as the processing load is essentially the same with every loop.
You made it to the end!
If you actually read everything above, you're awesome! Thank you! Hopefully you have some good advice to share back my way...
If I need to post code, let me know.
Solved! Go to Solution.
USB Data Transfer Bugs Fixed I worked around my USB data transfer issues, though I feel my solution is a bit of a hack and maybe not the preferred methodology.
The problem was that I needed to receive and reassemble 4 packets of 64 bytes into a 256 byte 2-dimensional array. I got really close by looping through bReadOutEP 4 times and loading the data into the array using offsets, but I was still getting data corruption, most likely caused by packets arriving out of order. The solution to this is probably using control bytes in the data to assist in re-assembly, but this wasn't a direction I was too eager to try.
Besides, I scoured the internet and this forum for hours looking for any real-world examples of this technique, and could find none.
My hack was that I realized that my PSoC 1 has 4 Endpoints, so I split my data array into 4 smaller 64 byte arrays, and send each on a separate Endpoint. Luckily I don't need any more Endpoints for anything else, as this solution is working perfectly.
With the data transmission bugs resolved, for the first time, I am seeing all 31 brightness levels on my LEDs! And my scope is showing perfectly formed and spaced duty cycles. Perfection!!!
And was I able to achieve that hoped for 210Hz refresh rate? Not exactly... I blew it away! For PWM Level 1 the scope sees a very constant 780 Hz!!! This climbs in 780Hz per level all the way to 11,700 Hz at PWM Levels 15 & 16, where it gradually decreases back to 780Hz at PWM Level 30, before going to solid ON (no Hz) at Level 31!!!
I don't know why these frequencies are even faster than my hardcoded tests, but I'm not complaining one bit.
That's still not good enough for servos, so I'm still using 4 digital blocks for hardware PWM, so 4 of the 56 LED's are running true PWM at 48MHz for servo control.
What's most amazing to me is that the 52 software PWM LED's now look identical to the hardware PWM's at all 6-bit brightness levels, and everything is perfectly flicker free!
Can you tell I'm quite excited?!?! I had hoped to achieve 60Hz minimums, and somehow I've achieved 13x better frequencies! And the latency from the host PC is the best I've observed yet, easily under 40ms and probably closer to 10ms.
Hopefully this novel of data I've posted here will help some other users in the future.
By pre-calculating on the host PC the On/Off states for all GPIO to simulate 5-bit PWM (32 brightness levels), and sending the new array of On/Off states over USB every time there is a change, you can get software PWM for LEDs that rivals hardware PWM on a PSoC 1, with minimum frequencies of 780Hz (if your PSoC isn't busy doing other things).
EDIT: To be clear, this approach is technically Pulse-Frequency Modulation (PFM), as the pulse width is fixed based upon the loop speed, and the frequency is adjusted by controlling the spacing to the next pulse.
This does require a constant connection to a host PC, so this won't be feasible for all applications. And 5-bit PFM is the limit for controlling all 56 GPIO of the top-end PSoC 1, as the state array for all 56 GPIO is 256 bytes, and there is not enough RAM to hold larger arrays for 8, 7 or even 6-bit PFM.
If you don't need 56 PWM outputs, scaling back the solution actually improves performance and PWM bit-depths. For example, for only 8 software PWM outputs (one full Port), you should see minimum frequencies 7x faster (5,480Hz), and you can use full 8-bit PFM as that array size is also 256 bytes.
If you don't have a host PC to calculate the On/Off states, then the PSoC 1 can at best achieve about 52Hz for 52 GPIO if it has to do all calculations itself, which increases to about 350Hz if you only need software PWM on 1 Port of 8 GPIOs. At 350Hz, you will get accurate LED dimming, nearly identical visually to hardware PWM.
Note that software PWM is not accurate enough for servo control, which requires precise frequencies and duty cycles that this approach can't replicate.