Creating a PWM-signal that works properly

Thread Starter

abcmann

Joined Apr 7, 2021
10
Hello guys,

I am currently working with a mircocontroller for the first time. My goal is to achieve a PWM-signal (1kHz). While trying to generate the signal I encountered the problem that my signal seems to be moving horizontal due to inconsistancy in time up to 100 µs. The goal was to guarantee accurate values up to 50µs. I inserted 2 example pictures in which you can see the problem:

F0000TEK.JPGF0001TEK.JPG

F0002TEK.JPGF0004TEK.JPG
The left and right pictures each show one PWM-signal. No settings were changed and if it would be viewed in Realtime you could see the signal jumping right and left by 100 µs. Although you can hardly spot any differenz from those pictures there defintely is one.


My take on generating a PWM-signal includes a counting loop which counts up and activates high until the value is reached to swap to low. When the absolut counting value which takes roughly 1ms (1kHz) the loop resets and starts all over again. I attached my code for the PWM-Generation. I tried to describe my variables as good as I can (I am no native english speaker), if anything remains unclear feel free to ask!

PWM-signal generation:
               ADC_Handler();
               Test_DEMOD2 = (DEMOD2_DATA>>8);      //fetches the measured values
               Test_DEMOD1 = (DEMOD1_DATA>>8);
               Counter=Counter+1;                   //Programmcounter
               SYST_Handler();                        //the described prewritten function

               if (((Test_DEMOD1 == 32)&(Test_DEMOD2 == 83))|((Test_DEMOD1 == 32)&(Test_DEMOD2 == 84)))    //genereates a pwm-signal while nothing is measured (signals failure)
               {
                   while (Inaktiv<22)
                   {
                       GPIO1_LOW();
                       Inaktiv=Inaktiv+1;
                   }
                   while (Inaktiv>=22)
                   {
                       GPIO1_HIGH();
                       Inaktiv=Inaktiv+1;
                       if (Inaktiv>=258)
                       {
                           Inaktiv=0;
                       }
                   }
               }

               DIF=Test_DEMOD1-Test_DEMOD2+120;                                         //shifting my numbers to positive values only
               if(DIF>=150)                                                             //prevents programm from stopping due to a too high value when initialising the measure
               {
                   DIF=149;
               }
               POS=DIF;             //POS and NEG are defined as float values in order to calculate a proper ratio
               NEG=150-DIF;
               VERH=POS/(POS+NEG);  //VERH stands for ratio
               if (((Test_DEMOD1 != 32)&(Test_DEMOD2 != 83)))
               {
                   TAN=12*VERH;            //the loop has to happen 12 times in order to achive a frequency of 1kHz
                   TAUS=12*(1-VERH);    //TAN is the Time while output his High and TAUS is the time when output is Low

                   while(aktiv<TAUS)
                   {
                       GPIO1_LOW();
                       aktiv=aktiv+1;
                   }
                   while(aktiv>=TAUS)
                   {
                       GPIO1_HIGH();
                       aktiv=aktiv+1;
                       if(aktiv>=12)
                       {
                           aktiv=0;
                       }
                   }
               }
   }
}

I think the problem revolves around the controller beeing overwhelmed by the amount of tasks he has to forfill. Additionaly the prewritten code provided uses a statemachine and interrupt functions which could also potentally cause delays in prozess time. Also there are prewritten functions called SYST_Config and SYST_Handler. The former one configures a reload count which could set a time in which the latter function is called. Sadly i wasn't quite able to understand how they are working since changing the reload count didn't result in any changes as how often SYST_Handler is beeing called.
The solution to my problem should revolve around utilazing a proper time to vary the High/Low timings instead of counting loops since these loops depend on the speed at which the core resolves its tasks and therfore is not really precise.
Any Help is highly appreciated!

Regards,Paul
 

Attachments

AlbertHall

Joined Jun 4, 2014
11,498
Processing time will affect the output pulses.
Using timer interruprs might alleviate the problem but the best way is to use a built in PWM generator.
What processor is this code for?
 

Thread Starter

abcmann

Joined Apr 7, 2021
10
Sadly there is no built in PWM generator as the support for it was cancelled...
This Code is for PGA970EVM the board has an ARM Cortex M0 Microcontroller.
 

BobTPH

Joined Jun 5, 2013
3,652
Delay loops will not work with interrupts going on. You need to use a timer interrupt which has higher priority than the other interrupts to schedule each transition with accurate timing.
Perhaps you are using the wrong uController. Most others have PWM hardware that is cycle accurate no matter what else is going on.

Bob
 

Thread Starter

abcmann

Joined Apr 7, 2021
10
According to TI the PWM signal can easily be integrated by programming the GPIO pins (what im trying to do). Regarding what the boards cost me so far I would like to achieve it using the PGA970EVM. There are different priorities going on as I saw in a post on E2E which prioritise the SYST_Handler function. But as mentioned i can't get it to work.
 

BobaMosfet

Joined Jul 1, 2009
1,848
Sadly there is no built in PWM generator as the support for it was cancelled...
This Code is for PGA970EVM the board has an ARM Cortex M0 Microcontroller.
First of all, without MCU support for PWM built in, you must use an interrupt. By definition, all tasks performed within the interrupt code MUST complete before the interrupt is called again, or you'll get iterative stacking until it crashes (because tasks never complete before next interrupt). I say this only so you are aware of it.

Using a counter yourself just doesn't work- your code is not priority. This is why you use interrupts. You're wanting 50uS accuracy, but you haven't stated how fast your processor can run at? I looked up your part #, and it either runs at 24MHz or uses an internal 8Mhz oscillator- which are you running it at?

Here is a crude yet conservative way to successfully operate within your interrupt time, without having to actually go to the effort of dissassembly and counting instruction cycles:

For example, a 20MHz processor takes 0.00000005 seconds for every clock cycle. How that actually translates (in terms of _any_ processor irregardless of architecture), is that 1 or more instructions can be executed in 1 or 2 clock cycles on average (guaranteed in the post prefetch, post pipeline era). so 50uS, divided by 10nS is your threshold: 1000 assembly instructions beyond your alotment (see next paragraph) can be executed before you begin to slip.

Since your resolution is 1/1000 of a second, that means the processor can execute at least 0.001 / 0.00000005 = 20,000 assembly instructions per clock cycle. for sake of argument, let's say it takes 7 assembly instructions (conservatively) to execute 1 C-language line of code, it means you can very roughly execute 2,800 lines of _efficient_ code, in that time period. Now, if you have more than 1 task executing at that level, divided that: 5 tasks: 2800 / 5 = 560 lines of code per task.

I say this, because it's important to get a 'feel' for how fast your processor actually is and what it can do _if you are smart and careful_ about how you code- AND you code _with_ the machine, not in spite of it.

Your processor is supposed to have 32 non-maskable interrupts- use one of these and keep your code tight/efficient, and I think you can achieve what you need.

IF you cannot, then add an RTC and use it to generate a 1kHz signal-- many real-time-clock chips have this ability very easily, and it can generate a signal into a pin on the ARM chip that acts as a pin-change interrupt.
 

MrChips

Joined Oct 2, 2009
24,141
From what I can see on the oscilloscope pictures, your signal is 3.3V and the trigger level is set to 3V.
That is a bit too close to the high end of the signal for my liking.
Try reducing the trigger level to about 2V.
 

Thread Starter

abcmann

Joined Apr 7, 2021
10
First of all, without MCU support for PWM built in, you must use an interrupt. By definition, all tasks performed within the interrupt code MUST complete before the interrupt is called again, or you'll get iterative stacking until it crashes (because tasks never complete before next interrupt). I say this only so you are aware of it.

Using a counter yourself just doesn't work- your code is not priority. This is why you use interrupts. You're wanting 50uS accuracy, but you haven't stated how fast your processor can run at? I looked up your part #, and it either runs at 24MHz or uses an internal 8Mhz oscillator- which are you running it at?

Here is a crude yet conservative way to successfully operate within your interrupt time, without having to actually go to the effort of dissassembly and counting instruction cycles:

For example, a 20MHz processor takes 0.00000005 seconds for every clock cycle. How that actually translates (in terms of _any_ processor irregardless of architecture), is that 1 or more instructions can be executed in 1 or 2 clock cycles on average (guaranteed in the post prefetch, post pipeline era). so 50uS, divided by 10nS is your threshold: 1000 assembly instructions beyond your alotment (see next paragraph) can be executed before you begin to slip.

Since your resolution is 1/1000 of a second, that means the processor can execute at least 0.001 / 0.00000005 = 20,000 assembly instructions per clock cycle. for sake of argument, let's say it takes 7 assembly instructions (conservatively) to execute 1 C-language line of code, it means you can very roughly execute 2,800 lines of _efficient_ code, in that time period. Now, if you have more than 1 task executing at that level, divided that: 5 tasks: 2800 / 5 = 560 lines of code per task.

I say this, because it's important to get a 'feel' for how fast your processor actually is and what it can do _if you are smart and careful_ about how you code- AND you code _with_ the machine, not in spite of it.

Your processor is supposed to have 32 non-maskable interrupts- use one of these and keep your code tight/efficient, and I think you can achieve what you need.

IF you cannot, then add an RTC and use it to generate a 1kHz signal-- many real-time-clock chips have this ability very easily, and it can generate a signal into a pin on the ARM chip that acts as a pin-change interrupt.
Thank you very much for such a qualified and fast answer!
I am using the the oscillator with 8MHz meaning I only have a total line of 1100 lines of code according to your calculation. I was told to keep its frequency as low as possible so your calculation really comes in handy. I will try my best using interruptfunctions maybe using those with the right priorisation will already solve the issue. Regarding the RTC i am kind of limited on spacing so Id like to avoid using additional elements.
 

Ian0

Joined Aug 7, 2020
3,186
Can you use the counter from the SYSTICK peripheral, instead of implementing your own counter?
Also, write at least some of it in assembler. I've noticed that using NXP's MCUXpresso, the complied C code for operating GPIO seem to take at least ten times as long as doing it in assembler.
A Cortex M0 has no built-in division instruction, so avoid doing any divisions in any code that needs to run quickly!
 

du00000001

Joined Nov 10, 2020
77
Provided there is at least an Output Compare unit available, you could implement the PWM with interrupts. Using OC instead of a real PWM unit would only impose a limitation on the smallest/largest duty cycle achievable (you cannot reach 0/100 % with OC easily), but the signal wouldn't be affected by interrupt latencies etc.
 

vanderghast

Joined Jun 14, 2018
55
If you are using a TI kit, you probably have an already made example under Ressource Explorer of CCS. It may be a little bit hard to spot, though. Check the path, here, near the top right of the picture, as example, for a MSP432. Not only you have a precise PWM, but you can change its duty cycle as it is running, with the push of one of the switch of the LaunchPad, in this example. Note also that the example is under T as in TIMER, not under P as PWM.Screenshot 2021-04-09 140120.png
 

Phil-S

Joined Dec 4, 2015
222
An Arduino board (Uno, Nano etc.) would have been a good choice to start with.
PWM is available on several output pins and there are many examples available either through the Arduino website or by searching the web.
If Nick Gammon (Gammon.au) has covered PWM on his website, it will be a thorough and well designed example.
 

Baker Steve

Joined Feb 21, 2016
8
First of all, without MCU support for PWM built in, you must use an interrupt. By definition, all tasks performed within the interrupt code MUST complete before the interrupt is called again, or you'll get iterative stacking until it crashes (because tasks never complete before next interrupt). I say this only so you are aware of it.
Boy oh boy this takes me back – to the 1980s in fact. One very costly controller on an HPIB bus driven by an HP minicomputer to control four even more costly radio receivers. Worked fine for the engineer in the US who developed the code for the controller, wouldn't work reliably for us (the customers) in the UK. Took six months to sort out, largely because the guy who wrote the code was in LA, he was blaming our application code, we were blaming his code and both of us suspected Hewlett Packard's code: the classic three-sided Mexican standoff.

In the end we pulled rank, shipped the guy over from LA, gave him carte blanche with Livingstone Instrument Hire, and he found the problem and fixed it in thirty minutes. It turned out that he'd developed and tested his controller code using an HP desktop, but when connected to our (then) new and superfast HP mini, it was being hit with interrupts faster than it could complete its interrupt service loop.

Apologies for inserting anecdote here, but it serves to underline BobaMosfet's point.
 
Top