|
|
View previous topic :: View next topic |
Author |
Message |
apakSeO
Joined: 07 Dec 2016 Posts: 60 Location: Northeast USA
|
setup_timer_2() postscale non-functional |
Posted: Thu Jan 12, 2017 4:58 pm |
|
|
Using: PIC18F46J50
MPLAB X
CCS v 5.065
Code below:
Code: |
// Below 4 lines must be in this order!!!
#include <18F46J50.h>
#fuses NOWDT,NOPROTECT,NODSBOR
#device PIC18F46J50 ICD=TRUE
#use delay(internal=8MHz, clock=48MHz) // Use internal 8MHz osc; setup CPU clock to 48MHz
// Tried experimenting with line below; not very functional
//#use pwm( CCP2,OUTPUT=PIN_A5,TIMER=2,FREQUENCY=5.86kHz,BITS=8,DUTY=65,PWM_ON)
//#use FIXED_IO( A_outputs=PIN_A7,PIN_A6,PIN_A5,PIN_A2 )
//#use FIXED_IO( C_outputs=PIN_C6,PIN_C2)
//#use FIXED_IO( D_outputs=PIN_D7,PIN_D6,PIN_D5,PIN_D4,PIN_D3,PIN_D2 )
//#use FIXED_IO( E_outputs=PIN_E2,PIN_E1,PIN_E0 )
//***** SET PERIPHERAL PIN SELECT FUNCTIONS
// Make pin RA5 a PWM capable pin output
#pin_select P2A = PIN_A5
void visOn( unsigned int8 duty )
{
delay_us(10); // Delay for settling
setup_ccp2(CCP_PWM); // Set ECCP2 module as PWM mode
// Clock period is 1/CLOCK_MHZ = 1/48e6 = 20.833ns
// Timer clock is 4*Fosc so each tick is 4*20.833ns = 83.33ns.
// Set timer2 to roll over @ 0xFF, so every 256 ticks * 83.33ns = 21.33us
// Prescaler set to 16, so 16 * 21.33us = 341us or 2.93kHz
// Now PWM from CPP2 is set at 2.93kHz with a post-scale of 1. Confirmed.
// Experimenting with other values of post scale does not work!!! Freq
// seems to be fixed at 2.93kHz and post scale value does nothing.
setup_timer_2(T2_DIV_BY_16,0xFF,2);
set_pwm2_duty(duty);
}
void main(void)
{
visOn(128); // Sets a duty cycle of 128/256 = 50%
while(1){
// Sit in da loop
}
return;
} |
The line setup_timer_2(T2_DIV_BY_16,0xFF,2) is functional except for the post-scale value. Prescale works and timer2 interrupt/rollover works great. However, the post-scale value appears to be non-functional. No matter if I set the post-scale value to 1,2,4,8, or 16 the PWM output frequency never changes from 2.93kHz.
set_pwm_duty() works flawlessly.
With a 48MHz clock, the lines should result in:
setup_timer_2(T2_DIV_BY_16,0xFF,1) --> 2.93kHz
setup_timer_2(T2_DIV_BY_16,0xFF,2) --> 1.47kHz
setup_timer_2(T2_DIV_BY_16,0xFF,4) --> 733Hz
setup_timer_2(T2_DIV_BY_16,0xFF,8) --> 366Hz
setup_timer_2(T2_DIV_BY_16,0xFF,16) --> 183Hz
However I am having no success in achieving these PWM frequencies; from what I read in the manuals ( both CCS and the specific PIC ) these options should be possible. |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Thu Jan 12, 2017 6:29 pm |
|
|
It's in the CCS manual:
Quote: | setup_timer_2( )
Postscale is a number 1-16 that determines how many timer overflows
before an interrupt: (1 means once, 2 means twice, an so on) |
In other words, the postscaler doesn't control the Timer2 frequency.
It controls the frequency of interrupts generated by Timer2.
Timer2 will normally generate an interrupt every time it overflows from
0xFF to 0x00. The postscaler value lets you change this. You could
have it interrupt once every 8 PWM cycles, for example.
PWM postscaler explanation:
http://www.ccsinfo.com/forum/viewtopic.php?t=29786&start=1
Sample code for PWM postscaler:
http://www.ccsinfo.com/forum/viewtopic.php?t=41473&start=3 |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19480
|
|
Posted: Fri Jan 13, 2017 2:44 am |
|
|
also _read the chip data sheet_.....
If you want lower frequency PWM, you can't do it with the hardware PWM. Can be generated quite easily using the CCP (since this has a 16bit counter).
The hardware PWM, is designed basically for speed. Given that for most PWM applications it is better to be clocking fast (provided the switchers can cope), and that high speed clocks are the one thing difficult to do in software or by other forms of hardware.
If you think about a 'PWM', you can't actually have a postscaler. Doing so, would get rid of the 'width' information in the PWM....
Since the PWM does it's comparisons directly to the selected timer, this would apply if you added a postscaler to the timer. |
|
|
apakSeO
Joined: 07 Dec 2016 Posts: 60 Location: Northeast USA
|
|
Posted: Fri Jan 13, 2017 10:06 am |
|
|
My error in thinking was that that the PWM frequency is governed by the timer2 interrupt frequency. It appears that the PWM frequency is governed only by the pre-scaler and the timerX roll-over from 0xFF to 0x00. |
|
|
apakSeO
Joined: 07 Dec 2016 Posts: 60 Location: Northeast USA
|
|
Posted: Fri Jan 13, 2017 10:11 am |
|
|
I've been trying for hours to get CCP1 to do a low frequency PWM with no success. Its difficult to wrap my head around what is going on here.
I tried looking at something similar in the example file ex_ccp1s.c:
Code: |
setup_ccp1(CCP_COMPARE_CLR_ON_MATCH); // Configure CCP1 in COMPARE mode
setup_timer_1(T1_INTERNAL); // Set up timer to instruction clk
while(TRUE)
{
while(input(PIN_B0)) ; // Wait for keypress
setup_ccp1(CCP_COMPARE_SET_ON_MATCH); // Configure CCP1 to set
set_compare_time(1,0); // C2 high now
set_timer1(0);
set_compare_time(1,500); // Set high time limit
// to 100 us
// limit is time/(clock/4)
// 500 = .0001*(20000000/4)
setup_ccp1(CCP_COMPARE_CLR_ON_MATCH); // Configure CCP1 in COMPARE
// mode and to pull pin C2
// low on a match with timer1
delay_ms(1000); // Debounce - Permit only one pulse/sec
}
|
But this solution does not jive with my program structure because in the while(1) loop CCP is constantly being toggled between CCP_COMPARE_SET_ON_MATCH and CCP_COMPARE_CLR_ON_MATCH and I don't want to be constantly doing this in the main while(1) loop.
Is there another way? My goal is to have a single function:
Code: |
void pwmOn( unsigned int8 duty, unsigned int16 freq)
{
} |
which takes the duty cycle and freq as an input and does the PWM on pin A5.
Are there any examples somewhere of using the CCP method of low-frequency PWM with variable duty cycle? |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19480
|
|
Posted: Fri Jan 13, 2017 10:36 am |
|
|
You do it in the CCP interrupt.
So just have a 'toggle' bit, and in the interrupt switch between set, and clear, and alternate the toggle. |
|
|
apakSeO
Joined: 07 Dec 2016 Posts: 60 Location: Northeast USA
|
|
Posted: Fri Jan 13, 2017 12:30 pm |
|
|
OK I tried following your lead and first want to make sure I can get a 50% duty cycle by toggling the bit in the ISR using the CCP mode. Code below:
Code: |
#include <18F46J50.h>
#fuses NOWDT,NOPROTECT,NODSBOR
#device PIC18F46J50 ICD=TRUE
#use delay(internal=8MHz, clock=48MHz) // Use internal 8MHz osc; setup CPU clock to 48MHz
void main(void)
{
setup_ccp1(CCP_COMPARE_INT); // CCP1 setup to interrupt on compare
setup_timer_1(T1_INTERNAL|T1_DIV_BY_1); // Timer1 ticks every 1/48e6 * 4 = 83.33ns
enable_interrupts(INT_CCP1);
enable_interrupts(GLOBAL);
set_compare_time(1,0xFFFF); // Using full timer1 count 0xFFFF should result in
// interrupt every 83.333ns * 65536 = 5.45ms
// or a period of 10.9ms ... OK WORKS
// Any other value does NOT work here,
// looks like compare is always 0xFFFF
// e.g. a value of 0x8000 for compare time
// should result in a period of 5.45ms
while(1)
{
// In main loop
}
}
#int_ccp1
void ccp1_isr()
{
output_toggle(PIN_A5);
}
|
Interrupt is generated, output on scope look good, and I end up with a 50% duty cycle. Great. Now I'm trying to change the value in set_compare_time(1, 0xFFFF) to something other than 0xFFFF but no matter what value I set for the compare time, the frequency of my output does not change. Tried values of 0x8000, 0x4000, 0x2000 ... feels like the set_compare_time() function is not modifying the compare time.
What am I missing here? |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
|
apakSeO
Joined: 07 Dec 2016 Posts: 60 Location: Northeast USA
|
|
Posted: Fri Jan 13, 2017 3:39 pm |
|
|
I checked out your sample code and got the main idea, but couldn't comprehend some aspects of it like the usage of !pulse_done_flag .... my application cannot sit and wait within main() for the pulse to be done.
So, here's what I came up with:
Code: | #include <18F46J50.h>
#fuses NOWDT,NOPROTECT,NODSBOR
#device PIC18F46J50 ICD=TRUE
#use delay(internal=8MHz, clock=48MHz) // Use internal 8MHz osc; setup CPU clock to 48MHz
#define PWM_PIN PIN_A5
#define PWM_PERIOD 5460L // 1/48e6 * 4 * 8 = 83.33ns per timer1 tick
// Timer1 divisor of 8 means 666.66ns per timer1 tick
// We want a pwm frequency of 275Hz or period of 3.64ms
// 3.64ms / 2 / 666.66ns = 2730 timer1 ticks
// Aiming for a PWM frequency of 275Hz
float duty = 50; // Default to 50% duty cycle
volatile float dutyDiv100;
volatile long high_time;
volatile float cMinusDuty;
volatile long low_time;
#int_ccp1
void ccp1_isr( void )
{
dutyDiv100 = duty/100;
high_time = dutyDiv100 * PWM_PERIOD;
cMinusDuty = (100 - duty)/100;
low_time = cMinusDuty * PWM_PERIOD;
if(!input_state(PWM_PIN)){ // If the pin is low,
output_high(PWM_PIN); // Set the pin high ...
set_compare_time(1, high_time); // .. and change timer to time the low state
}
else{
output_low(PWM_PIN); // Set the pin low...
set_compare_time(1, low_time); // .. change timer to now time the high state
}
set_timer1(0); // Reset the timer1 to start counting from zero
}
void main(void)
{
setup_timer_1(T1_INTERNAL|T1_DIV_BY_8); // Timer1 ticks every 1/48e-6 * 4 * 8 = 666.66ns
set_timer1(0); // Ensure timer starts at 0
setup_ccp1(CCP_COMPARE_INT); // CCP1 setup to interrupt on compare
output_low(PWM_PIN); // Ensure pin starts in OFF state
clear_interrupt(INT_CCP1);
enable_interrupts(INT_CCP1);
enable_interrupts(GLOBAL);
while(1)
{
for( int8 i = 1; i<100; i++) {
duty = i;
delay_ms(20);
}
for( int8 i = 100; i>0; i--) {
duty=i;
delay_ms(20);
}
}
}
|
The goal is to change a single variable 'duty' from within main() in order to vary the duty cycle of the constant frequency PWM output on A5.
I'm testing this observing the scope output as well as seeing a test LED brighten and dim continuously on my dev board.
Right now the code is mostly functional, but I don't like the smell of it because:
-- I had to define all those floats to deal with the math. Its ugly and probably wasteful. Is there a better way around this? Seems like set_compare_timer will not take anything other than a long for its argument.
-- I had to do the math from within ccp1_isr to constantly re-calculate the proper compare time. Doing math especially floating point within an ISR is probably a bad idea but I can't see a way around this.
-- The PWM frequency and duty cycle is off by few percent; I'm thinking ISR latency due to the floating point arithmetic?
Thanks for all your help so far. |
|
|
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|