Continue to Site

Welcome to our site!

Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

  • Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

PIC18F45K20 6 channel software PWM

Status
Not open for further replies.

Aleks

Member
Hi guys, can someone give me some hints on how to implement a 6 channel software PWM for driving 6 servo motors on a PIC18F45K20. That said the period of every PWM will be 20mS and the tON time will wary from 600uS to 2.4mS.

I can not change the microcontroller so I must implement a software PWM.

Thanks in advance,
Aleks
 
What resolution would you prepared to compromise with? A 7 bit resolution? If so you could easily create a period of up to 2KHz PWM for 6 channels...

A timer to control the frequency and 6 variables to control the 6 outputs... If you set a timer reload of 128 and keep track of the output states in 6 variables..
 
As it's only 6 channels, there only has to be 6 pulses every 20ms. This means you can just use a single timer (16bit preferred) ISR to turn the pins on and off, one at a time.
 
What resolution would you prepared to compromise with? A 7 bit resolution?
Why compromise? Just use a 16-bit timer clocked by the instruction cycle clock for both inter-pulse gaps, and for the pulse themselves.
 
I suppose... But its only servo motors... I doubt he'll need 16 bit resolution.... I would keep the timer activity as small as needed..
There still only needs to be a single interrupt on each pulse edge, so that's not any more timer activity. The pic 16-bit timer won't ever give 16-resolution for the 2.4ms pulse.
 
You could try using CCP module "special event" mode with 4, 8, or 16 MHz clock and prescaler 1, 2, or 4 (respectively) for 1-usec pulse width resolution...

Regards, Mike

Code:
static unsigned char n = 0;
static unsigned int width[] = { 1500, 1500, 1500, 1500,
                                1500, 1500, 1500, 1500,
                                20000 };
static unsigned char servo = 0b00000001;
Code:
void interrupt()
{
 /****************************************************************
  *  K8LH Crazy-8 Hi-Rez Soft 8-channel (PORTB) Servo Algorithm  *
  *                                                              *
  *  1 usec steps, 600-2400 usecs, prescale 1,2,4 (4,8,16 MHz)   *
  ****************************************************************/
  if (pir1.CCP1IF == 1)        // if "special event" interrupt
  { pir1.CCP1IF = 0;           // clear the interrupt flag
    portb = servo;             // output new Servo pulse
    ccpr1 = width[n++];        // setup next CCP1 "match" value

    width[8] -= ccpr1;         // adjust end-of-cycle off time
    servo <<= 1;               // and prep for next channel

    if(n == 9)                 // if end of a 20-msec period
    { n = 0; servo = 1;        // reset "n" and "servo"
      width[8] = 20000;        // reset the 20.0-msec period
    }
  }
}
 
Last edited:
You could try using CCP module "special event" mode with 4, 8, or 16 MHz clock and prescaler 1, 2, or 4 (respectively) for 1-usec pulse width resolution...

Regards, Mike

Hey Mike,

This is what I have so far :

Code:
/*****************************************************************
 *  K8LH Crazy-8 'Soft' 8-channel Servo Controller variables     *
 *****************************************************************/

unsigned char k = 0;

unsigned int Pulse[] = { 1500, 1500, 1500, 1500,
                                1500, 1500, 1500, 1500,
                                20000 };
unsigned char Servo = 1;

void interrupt ()
{
 /****************************************************************
  *  K8LH Crazy-8 Hi-Rez Soft 8-channel (PORTB) Servo Algorithm  *
  ****************************************************************/
  if (PIR1.CCP1IF == 1) {     // if "special event trigger"
    PIR1.CCP1IF = 0;        // clear CCP1 interrupt flag
    LATD = Servo;               // output new Servo pulse and
    CCPR1 = Pulse[k++];         // setup next CCP1 "match" value

    Pulse[8] -= CCPR1;          // adjust end-of-cycle off time
    Servo <<= 1;                // and prep for next channel

    if (k == 9) {               // if end of a 20-msec cycle
      k = 0;                    // reset array index
      Pulse[8] = 20000;         // reset the 20.0-msec period
      Servo++;                  // reset Servo shadow to Servo 1
    }
  }
}
/*****************************************************************
 *  MAIN                                                         *
 *****************************************************************/

void main (void)
{


  // setup INTOSC for 8-MHz operation
  OSCCON.IRCF2 = 1;
  OSCCON.IRCF1 = 1;
  OSCCON.IRCF0 = 0;
  //TRISA = 0;
  LATD = 0;                     // all PORT B servo outputs '0'
  TRISD = 0;                    // set PORT B all outputs
  ADCON1 = 15;                  // all digital I/O (no analog)

  // setup TMR1 and CCP1 module "special event trigger" interrupts
  CCPR1 = 1000;                 // first "compare" interrupt
  T1CON = 0b00010000;           // pre 2, post 1, 1 usec 'ticks'
  CCP1CON = 0b00001011;         // "special event trigger" mode

  PIR1 = 0;                     // clear peripheral int flags
  PIR2 = 0;                     //
  PIE1 = 0;                     // clear peripheral int enables
  PIE2 = 0;                     //

  PIE1.CCP1IE = 1;          // enable CCP1 interrupts
  INTCON.PEIE = 1;          // enable peripheral interrupts
  INTCON.GIE = 1;           // enable global interrupts
  T1CON.TMR1ON = 1;         // turn on Timer 1
  
  while(1){}
  
  

}

The problem is that on PORTD, all of the pins have pulses of 212uS width and their frequency is 546 Hz, what might be the problem that I'm not getting the 50 Hz period of the pulses? And also, I can't change any of the pin duration times, regardless of the previous problem.

Regards,
Aleks
 
Last edited:
Hi Aleks,

Sounds like only the CCPR1L register is being written and not the full 16 bit CCPR1H:L register pair. You should check this out in the MPLAB Simulator with the Watch window. Various C compilers handle the 16 bit register pairs differently. If your compiler doesn't provide you with a way to write the full 16 bit CCPR1 register pair, you'll have to write the high and low bytes individually (yuch!).

Please let us know how it works out...

Cheerful regards, Mike
 
Hi Aleks,

Sounds like only the CCPR1L register is being written and not the full 16 bit CCPR1H:L register pair. You should check this out in the MPLAB Simulator with the Watch window. Various C compilers handle the 16 bit register pairs differently. If your compiler doesn't provide you with a way to write the full 16 bit CCPR1 register pair, you'll have to write the high and low bytes individually (yuch!).

Please let us know how it works out...

Cheerful regards, Mike

AHA! Thanks for the info Mike, I will try the MPLAB simulator as soon as I figure out how to use it (probably in a few hours) and I'll tell the results. Anyhow, I'm using Mikroelektronika's microC compiler if that's of any hint to you about the 16 bit pairs. Here is a link to the generated assembly by the compiler, can you tell by looking at it if the 16bit register pairs are handled properly ? - **broken link removed**

Regards,
Aleks
 
-- Nevermind this, I just saw that PEIE is enabled --

@Mike

Could the problem also be that the priority interrupts are disabled by default (IPEN = 0) and the PEIE is not set by default, which will turn on all of the low priority interrupts, including Timer1 ?
 
Last edited:
... can you tell by looking at it if the 16bit register pairs are handled properly ?

That code shows that the compiler is not treating 'ccpr1' as a 16-bit register pair. I can't help you with your particular compiler. Sorry!
 
Am I missing something?? The PWM modules are 10 bit maximum... 8 bits are in the CCPR1L and the other two are in the CCPCON ... The transfer to the CCPR1H is automatic...

Yes, you're missing something. Take another read. It's a software driver using CCP module "special event" mode using 16-bit TMR1 and CCPR1 register sets... Please pay attention (grin)...
 
@Mike

Could the problem also be that the priority interrupts are disabled by default (IPEN = 0) and the PEIE is not set by default, which will turn on all of the low priority interrupts, including Timer1 ?

The example code was for 16F devices and I use BoostC. You'll have to figure out the differences for your compiler and device. Maybe someone will jump in and lend you a hand.

Best wishes, Mike
 
Last edited:
That code shows that the compiler is not treating 'ccpr1' as a 16-bit register pair. I can't help you with your particular compiler. Sorry!

Hey Mike, thanks for the prompt response. How will the shift operands for writing to the both low and high bytes of CCPR1 affect the total execution of the algorithm ? Will it loose on resolution ?

The compiler microC is a pretty shitty one, I don't think that there is any way to make it *like* 16bit register pairs (this is the main reason I'm more keen to AVR-s, we have gcc-avr and avr-libc there :) )

Regards,
Aleks
 
The CCPRxL/H are contiguous and LSB-first, so you should be fine using:
Code:
*(unsigned int*)&CCPR1 = value;

or simply redefine the register:
Code:
#define CCPR1W (*(unsigned int*)&CCPR1)
which will allow you to refer to CCPR1W for read or write.

There's probably some way specific to the compiler you're using too..
 
Last edited:
The CCPRxL/H are contiguous and LSB-first, so you should be fine using:
Code:
*(unsigned short*)&CCPR1 = value;

or simply redefine the register:
Code:
#define CCPR1W (*(unsigned short*)&CCPR1)
which will allow you to refer to CCPR1W for read or write.

There's probably some way specific to the compiler you're using too..

I think "unsigned short" in mikroC is same as "unsigned char".. so it is only 1 byte long. If this is true, the code should be:

Code:
*(unsigned int*)&CCPR1 = value;
or
Code:
#define CCPR1W (*(unsigned int*)&CCPR1)
which will allow you to refer to CCPR1W for read or write.
 
Last edited:

I've been involved in few debates about "ASM vs. C" and I've always defended the C language. But, you really have to know the language and the tools, especially your compiler, when writing software for microcontrollers. Non-standard C, like the above, is not unusual in the world of microcontrollers. ASM knowledge is very useful, if not essential, for one to be able to write good C programs for embedded systems. But the thing is, when you write ASM, you know what you get. When you write C, you better make sure what you get. Sorry for off-topic.
 
Last edited:
Status
Not open for further replies.

Latest threads

New Articles From Microcontroller Tips

Back
Top