PWM help - Page 1
Forum section: electronics / beginners

author: sunflower
author_rank: Newbie
timestamp: December 17, 2020, 06:38:04 pm
content: Hi, I am trying to do a project with PIC in Proteus, but I got stuck. I need to "read on PWM": to do edge to edge timing while monitoring duty cycle, changing the edge to read the interval, and then, under 40% and above 60% to show two different digits.ex: 45% = 1; 55%=0 => it's okI was told it's like one whire communication DHT11, DHT12, but I never did anything else like this before.Until now I barely did and understood this part but I don't know if it's a good start.(I generated a variable pwm signal using internal adc and a potentiometer, but I think I need to use a pulse generator)#define _XTAL_FREQ 20000000#define TMR2PRESCALE 4#include <xc.h>long PWM_freq = 5000;PWM_Initialize(){PR2 = (_XTAL_FREQ/(PWM_freq*4*TMR2PRESCALE)) - 1;CCP1M3 = 1; CCP1M2 = 1;T2CKPS0 = 1;T2CKPS1 = 0; TMR2ON = 1;TRISC2 = 0;}PWM_Duty(unsigned int duty){if(duty<1023){duty = ((float)duty/1023)*(_XTAL_FREQ/(PWM_freq*TMR2PRESCALE));CCP1X = duty & 1;CCP1Y = duty & 2;CCPR1L = duty>>2;}}void ADC_Initialize(){ADCON0 = 0b01000001;ADCON1 = 0b11000000;}unsigned int ADC_Read(unsigned char channel){ADCON0 &= 0x11000101;ADCON0 |= channel<<3;__delay_ms(2);GO_nDONE = 1;while(GO_nDONE);return ((ADRESH<< 8 )+ADRESL);}void main(){int adc_value;TRISC = 0x00; //PORTC as outputTRISA = 0xFF; //PORTA as inputTRISD = 0x00;ADC_Initialize();PWM_Initialize();do{adc_value = ADC_Read(4);PWM_Duty(adc_value);__delay_ms(50);}while(1);}I can't find any examples for what I have to do, just some theory but it doesn't help. Any help/advice is useful, thank you.

author: opampsmoker
author_rank: Regular Contributor
timestamp: December 17, 2020, 11:09:50 pm
content: In order to detect the duty cycle, i would RC filter it and see what voltage you get.....then you can work out the duty cycle from that voltage. Eg, if the pulse height is 5V, and your filtered PWM is giving 2.5V...then the duty cycle must have been 50%

author: sunflower
author_rank: Newbie
timestamp: December 18, 2020, 08:36:41 am
content: Yes, thank you, that's what I wanted to do but I stopped because I am not sure if it's what I needed.It is the right start? I think I need to have different duty cycles but a fixed period of time: 10ms , and to do front to front timing.(I am not sure how to tho)ex: at 45%=1 logic55%=0 logicbut under 40% and above 60% to show a 2 different digit code

author: MIS42N
author_rank: Frequent Contributor
timestamp: December 18, 2020, 11:55:44 am
content: I don't understand what you are trying to do, but PWM is not too hard.I think the PIC you are using has a 10 bit PWM timed by an 8 bit timer. The 8 bit timer resets when its value hits a predetermined limit (I think your PR2)The length of time taken between resets is the period. You probably understand that.The number of bits generated by the PWM in the period is PR2 * 4. The PWM clocks out 4 bits for every increment of the timer. The first n bits are 1 bits, the remainder are 0 bits where n is the value loaded into the PWM register (CCP1?)The duty cycle is what percentage of the bits are 1 bits. As an example, set PR2 to 134 and load 317 into the PWM as the duty value. The PWM will output 134 * 4 bits = 536 bits of which the first 317 are 1 bits.So the duty cycle is 317/536 * 100 (to get percentage) = about 59%I don't understand "read on PWM" and "edge to edge". I hope this is helpful.

author: Ian.M
author_rank: Super Contributor
timestamp: December 18, 2020, 12:34:25 pm
content: To read the PWM duty cycle directly, you need to 'timestamp' each edge. The PIC16F877A CCP modules support edge triggered capture events, but unfortunately you must choose between capturing rising or falling edges as one CCP module cant be configured for both simultaneously. (See datasheet section8.1 Capture Mode)There are two alternatives here: if the PWM signal's duty cycle never goes too close to 0% or 100% and it isn't too fast, you can flip the capture mode edge in the capture ISR, otherwise you'll need to use both CCP modules, connecting both CCP1 and CCP2 pins to the signal, so one can capture the rising edges and the other the falling edges.Capture mode uses Timer 1 for a timebase. To keep the maths simple set its prescaler for a rollover period longer than the period of the slowest PWM you must handle, but not any longer than that. The capture result(s) will be 16 bit timestamps which are 'snapshots' of Timer 1 at each capture event, and you must store them in a variable before the next matching edge or they will be overwritten. You need three edge times to determine the duty cycle so you have to preserve the previous result for either the rising or falling edge.If you use 16 bit unsigned arithmetic, you can ignore timer rollover and simply subtract the timestamp of an earlier edge within (or on the boundary) of the same PWM cycle to get the time difference. When you have the on time and the period you can simply calculate the duty cycle, though as float division is particularly CPU intensive on 8 bit PICs, you'd be well advised to do that part and your display update in the main loop, with 'frozen' copies of the current on time and period, so your calculation doesn't get confused if the capture ISRs update them before its calculated.N.B. 0% and 100% PWM are special cases - DC levels - which wont trigger capture events. You need to detect them by writing a Timer 1 ISR that checks if there's been any capture events since it last ran. i.e. it should check a flag set by the CCP ISR(s) and if NOT set, update the on time with either 0 (0%) or the last period (100%) according to the level on the CCP pin, then finally clear the flag.

author: MIS42N
author_rank: Frequent Contributor
timestamp: December 18, 2020, 02:02:58 pm
content: Aha - you don't want to generate a PWM but to measure it. Ian M's explanation is a good one.You say "edge to edge timing while monitoring duty cycle, changing the edge to read the interval" - that implies that whoever set the problem expects that it can be done by a single compare module, changing the detection edge between captures.

author: sunflower
author_rank: Newbie
timestamp: December 18, 2020, 08:19:05 pm
content: Thank you for your help, but I'm still not sure how to start. I changed the circuit, I don't know if it's what I need, I mean I think I need variable duty cycle so I can choose under 40% and above 60%, no?I was told to use a pulse generator but I get constant signal.I know my time period is 10ms and that duty cycle = pulse width/time period*100 so I quess I'll have DC=PW*100/0.01So in the pulse generator settings I choose the period and the pulse width the way I want?I heard working with PWM is easy but I never did anything like this before

author: Ian.M
author_rank: Super Contributor
timestamp: December 18, 2020, 08:51:57 pm
content: Set 'Pulsed (High) Voltage' to 5 as you want logic level compatibility with your 5V PIC. Your other settings look reasonable and should give you a 100Hz 50% PWM signal source.To test the different cases you would need to either change the pulse generator duty cycle percentage and run the sim again, or if you want it to change while its running you'd need a variable duty cycle pulse generator. One way to get there would be to XOR the outputs of two 50% duty cycle pulse generators similar to this one with fractionally different periods. I suggest using 20ms and 20.1ms which will give you an approx. 10ms period (100Hz) PWM increasing from 0% to 100% duty cycle over 2 seconds then decreasing back to 0% over the next 2 seconds, repeating indefinitely.

author: rstofer
author_rank: Super Contributor
timestamp: December 18, 2020, 09:46:58 pm
content: What you want to Google for is "pulse width measurement with PIC". You didn't say which PIC you are using so I can't pick a particular reply or video. Yes, there even videos on measuring pulse width.You seem to be saying that your input is always 100 Hz. If that is so, all you need to do is start the timer on the leading edge, stop it on the falling edge and then do the math.If you don't know the frequency, measure the time between two successive rising edges (the total period) and then the time between a rising edge and falling edge (the pulse width). Do the math and you have duty cycle.Your application isn't unique, it will be all over Google. Or even a Microchip App Note:http://ww1.microchip.com/downloads/en/appnotes/01473a.pdf

author: sunflower
author_rank: Newbie
timestamp: December 19, 2020, 01:17:50 pm
content: I can use whatever PIC I want, I choose PIC16F877A because I saw a lot of projects with it (and I learn better with examples). I found something like thishttps://microcontrollerslab.com/pulse-width-measurement-pic-microcontroller/, I think it's what I need, and thishttp://nexus.eu5.org/projects/var_pwm/var_pwm.htmlbut I am not sure.

author: Ian.M
author_rank: Super Contributor
timestamp: December 19, 2020, 01:53:00 pm
content: 'Classic' (Midrange 14 bit core) PICs like the PIC16F877A are architecturally limited and nasty to work with. The PIC16F877A is a very early example of this product range and if you insist on using 'Classic' Midrange parts you'd be well advised to start with the top-end PIC in the range, the PIC16F887, which maxes out available RAM and program memory and has significantly better peripherals.However you'd probably be better off choosing a PIC16F1xxx(x) (Enhanced Midrange 14 bit core) or PIC18 ('High End' 16 bit core) device with hardware support for direct duty cycle measurement e.g. multiple hardware gated 16 bit timers.

author: MIS42N
author_rank: Frequent Contributor
timestamp: December 20, 2020, 02:01:57 am
content: No wonder you are having trouble. I looked athttps://microcontrollerslab.com/pulse-width-measurement-pic-microcontroller/, it is not good code and the 'documentation' is misleading. I should say up front that I'm not familiar with C language but I do know a bit about PICs (I program in assembler). So when I read:TRISC.B2 = 1; // CCP1 pin - RC5 set to inputTRISC.B1 = 1; // CCP1 pin - RC5 set to inputI think one of these is actually CCP2, not CCP1. So I wouldn't rely on the documentation.I hope you realise that although the two examples look similar, one is to create a PWM signal and the other is to measure a PWM signal. It can't be done in a single processor, you need two - one to create, the other to measure. I was fooled by your first diagram thinking it was a creator.Now to why I don't like the measuring code. It uses the two compare modes using CCP1 to capture rising edge and CCP2 to capture falling edge. That's OK. But the whole idea of capture mode is it transfers the value of TMR1 at the time of capture to the CCPRxH:CCPRxL pair. The example code doesn't do this, it starts and stops TMR1 to measure the pulse. That is a bad way of doing it. There is a delay between the pulse changing and the code executing, so TMR1 gets started late and it is stopped late. And you don't know if those two delays are the same.The correct way to do it is to allow TMR1 to free run - start it and just let it reset every 65536 cycles. When there is an interrupt, look at where the interrupt comes from (CCP1 or CCP2) and use the appropriate register pair CCPRxH:CCPRxL to see what the value of TMR1 was at the time of the pulse level changing. You need to store 3 16 bit values - two consecutive CCPR1x values (for illustration, call them PR1A and PR1B) and the CCPR2x value between PR1A and PR1B (PR2X). You can calculate the period using UNSIGNED values PR1B - PR1A (calculations have to be unsigned because TMR1 might roll over between the two measurements) giving the unsigned period in terms of TMR1 counts. The high period is PR2X - PR1A. If you just want the duty cycle as a fraction then (PR2X-PR1A)/(PR1B-PR1A).This is the simplest way but it assumes that the pulse duration is less than the time it takes for TMR1 to go from 0 to 65535. A general purpose program would start TMR1 with the smallest prescale and have an interrupt routine for TMR1. If TMR1 rolls over before capturing PR1A then increase the prescale and start again. The same thing applies when capturing PR1B - you can allow 1 roll over of TMR1 and test that the second reading is less than the first, otherwise increase the prescale. If the duty cycle is 0% or 100% eventually you will get to the maximum prescale and give up. A more complex but more flexible method is to extend the value of TMR1 by keeping a counter in software incremented on each TMR1 overflow, then create the captured PR1A PR1B PR2X from the software counter as the high value and the hardware value as the low value. This is more complex but can be more accurate because it always uses TMR1 running at full speed. And it still needs limit test so the code eventually realises the signal is 0% or 100%.

author: rstofer
author_rank: Super Contributor
timestamp: December 20, 2020, 02:54:22 am
content: Quote from: sunflower on December 19, 2020, 01:17:50 pmIf you can do it with whichever PIC you want, can you do it with something other than a PIC?An Arduino comes to mind since it already has pulsewidth measuring functions in the library:https://create.arduino.cc/projecthub/ambhatt/frequency-and-duty-cycle-measurement-using-arduino-1e4896

author: sunflower
author_rank: Newbie
timestamp: December 20, 2020, 07:51:45 am
content: Thank you for the help, and no, I was told that arduino is too easy to work with...even though I am a beginner in this area.

author: MIS42N
author_rank: Frequent Contributor
timestamp: December 20, 2020, 09:38:34 am
content: Quote from: sunflower on December 20, 2020, 07:51:45 amI think you will be OK with the PIC16F877A. Did you download the documentation? If not, go tohttps://www.microchip.com/wwwproducts/en/PIC16F877Aand get it.I looked again at what you wrote in your first post - the diagram is labeled capture.png but the code and the circuit are a PWM generator, not a capture. You said you had this working. Note in that diagram CCP1 is used as a PWM output. The second diagram in your post #6 is for capturing, and in that case CCP1 and CCP2 are inputs. How to use them is explained in the datasheet in a section called CAPTURE/COMPARE/PWM MODULES. You need a generator and a capturer, with CCP1 of the generator sending to CCP1/2 of the capturer. Or you could use something else as the generator.Does this make sense to you? I think you should try and write the capture program, post it here for comment.

author: rstofer
author_rank: Super Contributor
timestamp: December 20, 2020, 03:26:29 pm
content: Maybe something like this can help:https://www.mikrocontroller.net/attachment/347018/Pulse_Width.pdf