PiCar/rpiPWM1.cpp

432 lines
16 KiB
C++

#include "rpiPWM1.h"
//Need to do this to be able to access these constants
//Private constants
const int rpiPWM1::BCM2708_PERI_BASE;
const int rpiPWM1::PWM_BASE;/* PWM controller */
const int rpiPWM1::CLOCK_BASE; /* Clock controller */
const int rpiPWM1::GPIO_BASE; /* GPIO controller */
const int rpiPWM1::PWM_CTL ;
const int rpiPWM1::PWM_RNG1;
const int rpiPWM1::PWM_DAT1;
const int rpiPWM1::PWMCLK_CNTL;
const int rpiPWM1::PWMCLK_DIV;
const int rpiPWM1::BLOCK_SIZE;
//Public constants
const int rpiPWM1::PWMMODE;
const int rpiPWM1::MSMODE;
const int rpiPWM1::ERRFREQ;
const int rpiPWM1::ERRCOUNT;
const int rpiPWM1::ERRDUTY;
const int rpiPWM1::ERRMODE;
/***********************************************************************
* rpiPWM1::rpiPWM1()
* This is the Default constructor. First, it mmaps the registers in
* Physical memory responsible for configuring GPIO, PWM and the PWM clock.
* It then sets the frequency to 1KHz, PWM resolution to 256, duty
* cycle to 50% & pwm mode to 'PWMMODE'
* It then calls configPWM1Pin() to configure GPIO18 to ALT5 to allow it to
* output PWM1 waveforms.
* Finally configPWM1() is called to configure the PWM1 peripheral
***********************************************************************/
rpiPWM1::rpiPWM1()
{
this->clk = mapRegAddr(CLOCK_BASE);// map PWM clock registers into memory
this->pwm = mapRegAddr(PWM_BASE); //map PWM registers into memory
this->gpio = mapRegAddr(GPIO_BASE);// map GPIO registers into memory
this->frequency = 1000.0; // set frequency
this->counts = 256; //set PWM resolution
this->dutyCycle = 50.0; //set duty cycle
this->mode = PWMMODE; // set pwm mode
configPWM1Pin(); //configure GPIO18 to ALT15 (PWM output)
configPWM1(); // configure PWM1
}
/***********************************************************************
* rpiPWM1::rpiPWM1(double Hz, unsigned int cnts, double duty, unsigned int m)
* This is the overloaded constructor. First, it mmaps the registers in
* Physical memory responsible for configuring GPIO, PWM and the PWM clock.
* It then sets the frequency, PWM resolution, duty cycle & pwm mode as
* per the parameters provided.
*
* It then calls configPWM1Pin() to configure GPIO18 to ALT5 to allow it to
* output PWM1 waveforms.
* Finally configPWM1() is called to configure the PWM1 peripheral
* Parameters: - Hz (double) - Frequency
* - cnts (unsigned int) - PWM resolution (counts)
* - duty (double) - Duty Cycle as a percentage
* - m (int) - PWM mode (can be either 1 for PWMMODE (rpiPWM1::PWMMODE)
* or 2 for MSMODE (rpiPWM1::MSMODE)
***********************************************************************/
rpiPWM1::rpiPWM1(double Hz, unsigned int cnts, double duty, int m)
{
this->clk = mapRegAddr(CLOCK_BASE);
this->gpio = mapRegAddr(GPIO_BASE);
this->pwm = mapRegAddr(PWM_BASE);
if( (cnts < 0) || (cnts > UINT_MAX) ) {
printf("counts value must be between 0-%d\n",UINT_MAX);
exit(1);
}
if ((Hz < 1e-5) || (Hz > 19200000.0f)){
printf("frequency value must be between 0-19200000\n");
exit(1);
}
if( (duty < 1e-5) || (duty> 99.99999) ) {
printf("dutyCycle value must be between 0-99.99999\n");
exit(1);
}
if( (m != PWMMODE) && (m != MSMODE) ) {
printf("mode must be either PWMMODE(1) or MSMODE(2)\n");
exit(1);
}
this->frequency = Hz;
this->counts = cnts;
this->dutyCycle = duty;
this->mode = m;
configPWM1Pin();
configPWM1();
}
/***********************************************************************
* rpiPWM1::~rpiPWM1()
* Destructor - Puts all Peripheral registers in their original (reset state)
* and then unmaps the portions of memory containing to register addresses
* for the PWM clock, PWM and GPIO peripherals
***********************************************************************/
rpiPWM1::~rpiPWM1(){
//lets put the PWM peripheral registers in their original state
*(pwm + PWM_CTL) = 0;
*(pwm + PWM_RNG1) = 0x20;
*(pwm + PWM_DAT1) = 0;
// unmap the memory block containing PWM registers
if(munmap((void*)pwm, BLOCK_SIZE) < 0){
perror("munmap (pwm) failed");
exit(1);
}
//lets put the PWM Clock peripheral registers in their original state
//kill PWM clock
*(clk + PWMCLK_CNTL) = 0x5A000000 | (1 << 5);
usleep(10);
// wait until busy flag is set
while ( (*(clk + PWMCLK_CNTL)) & 0x00000080){}
//reset divisor
*(clk + PWMCLK_DIV) = 0x5A000000;
usleep(10);
// source=osc and enable clock
*(clk + PWMCLK_CNTL) = 0x5A000011;
// unmap the memory block containing PWM Clock registers
if(munmap((void*)clk, BLOCK_SIZE) < 0){
perror("munmap (clk) failed");
exit(1);
}
//lets put the GPIO peripheral registers in their original state
//first put it in input mode (default)
//taken from #define INP_GPIO(g) *(gpio+((g)/10)) &= ~(7<<(((g)%10)*3))
*(gpio+1) &= ~(7 << 24);
//then munmap
if(munmap((void*)gpio, BLOCK_SIZE) < 0){
perror("munmap (gpio) failed");
exit(1);
}
}
/***********************************************************************
* unsigned int rpiPWM1::setFrequency(const double &hz)
* This function sets the PWM frequency and then reinitializes the PWM1
* peripheral to update the frequency. The function performs a check to
* ensure that the PWM frequency is between 0 & 19.2MHz
* Parameters: hz (double) - Frequency in Hz
* Return Value: 0 if successful or rpiPWM1::ERRFREQ if frequency
* parameter is invalid
***********************************************************************/
unsigned int rpiPWM1::setFrequency(const double &hz){
unsigned int retVal = 0;
if (hz < 1e-5 || hz > 19200000.0f){ // make sure that Frequency is valid
retVal = ERRFREQ; //if not return error code
}
else{
this->frequency = hz;
configPWM1();
}
return retVal; // return 0 for success.....
}
/***********************************************************************
* unsigned int rpiPWM1::setCounts(const int &cnts)
* This function sets the PWM resolution and then reinitializes the PWM1
* peripheral to update the PWM resolution (counts). The function performs a check to
* ensure that the PWM resolution is between 0 & UINT_MAX (its a 32-bit register)
* Parameters: cnts (unsigned int) - counts
* Return Value: 0 if successful or rpiPWM1::ERRCOUNT if count value is invalid
***********************************************************************/
unsigned int rpiPWM1::setCounts(const unsigned int &cnts){
unsigned int retVal = 0;
if( (cnts < 0) || (cnts > UINT_MAX) ) {
retVal = ERRCOUNT;
}
else{
this->counts = cnts;
configPWM1();
}
return retVal;
}
/***********************************************************************
* unsigned int rpiPWM1::setDutyCycle(const double &duty)
* This function sets the PWM DutyCycle while the PWM peripheral is running.
* The function performs a check to ensure that the PWM Duty Cycle is between
* 0 & 99.99999 %
* Parameters: duty (double) - Duty Cycle in %
* Return Value: 0 if successful or rpiPWM1::ERRDUTY if Duty cycle is invalid
****************************************************************************/
unsigned int rpiPWM1::setDutyCycle(const double &duty){
unsigned int bitCount = 0;
unsigned int retVal = 0;
if( (duty < 1e-5) || (duty > 99.99999) ) {
retVal = ERRDUTY;
}
else {
this->dutyCycle = duty;
bitCount = (int) ((this->dutyCycle/100.0) * this->counts);
*(pwm + PWM_DAT1) = bitCount;
}
return retVal;
}
/***********************************************************************
* unsigned int rpiPWM1::setDutyCycleForce(const double &duty, unsigned int &m)
* This function firsts stops the PWM1 peripheral, sets the PWM DutyCycle
* and the PWM mode and then re-enables the PWM1 peripheral in the new mode
* The function performs a check to ensure that the PWM Duty Cycle is between
* 0 & 99.99999 % and that an appropriate mode is selected.
*
* Parameters: duty (double) - Duty Cycle in %
* m (int) - pwm mode (rpiPWM1::PWMMODE or rpiPWM1::MSMODE)
* Return Value: 0 if successful or rpiPWM1::ERRDUTY if Duty cycle is invalid
*******************************************************************************/
unsigned int rpiPWM1::setDutyCycleForce(const double &duty, const int &m){
int retVal = 0;
if( (m != PWMMODE) && (m != MSMODE) ) {
retVal = ERRMODE;
}
else if( (duty < 1e-5) || (duty > 99.99999) ) {
retVal = ERRDUTY;
}
else{
this->mode = m;
this->dutyCycle = duty;
// disable PWM & start from a clean slate
*(pwm + PWM_CTL) = 0;
// needs some time until the PWM module gets disabled, without the delay the PWM module crashs
usleep(10);
// set the number of counts that constitute a period
*(pwm + PWM_RNG1) = this->counts;
//set duty cycle
*(pwm + PWM_DAT1) = (int) ((this->dutyCycle/100.0) * this->counts);
// start PWM1 in
if(this->mode == PWMMODE) //PWM mode
*(pwm + PWM_CTL) |= (1 << 0);
else // M/S Mode
*(pwm + PWM_CTL) |= ( (1 << 7) | (1 << 0) );
}
return retVal;
}
/***********************************************************************
* unsigned int rpiPWM1::setDutyCycleCount(unsigned int &dutyCycleCnts )
* This function sets the PWM DutyCycle as a function of PWM resolution,
* while the PWM peripheral is running. The function performs a check to
* ensure that the PWM Duty Cycle count value is between 0 and count
* Parameters: dutyCycleCnts (unsigned int) - Duty Cycle in counts
* Return Value:0 if successful or rpiPWM1::ERRDUTY if Duty cycle is invalid
***********************************************************************/
unsigned int rpiPWM1::setDutyCycleCount(const unsigned int &dutyCycleCnts ){
unsigned int retVal = 0;
if( (dutyCycleCnts < 0) || ( dutyCycleCnts > this->counts )) {
retVal = ERRDUTY;
}
else{
this->dutyCycle = ((dutyCycleCnts * 1.0)/ this->counts) * 100.0;
*(pwm + PWM_DAT1) = dutyCycleCnts;
}
return retVal;
}
/***********************************************************************
* unsigned int rpiPWM1::setMode(unsigned int &m)
* This function sets the PWM mode. The function performs a check to
* ensure that a valid PWM mode is requested.
* Parameters: m (int) - pwm mode (rpiPWM1::PWMMODE or rpiPWM1::MSMODE)
* Return Value: 0 if successful or rpiPWM1::ERRMODE if MODE is invalid
********************************************************************************/
unsigned int rpiPWM1::setMode(const int &m){
unsigned int retVal = 0;
if( (m != PWMMODE) && (m != MSMODE) ) {
retVal = ERRMODE;
}
else{
this->mode = m;
setDutyCycleForce(this->dutyCycle, this->mode);
}
return retVal;
}
/***********************************************************************
*These are a bunch of 'getter' functions that enable the user
* to access the PWM frequency, resolution, duty cycle and mode as well
* as the PWM clock divisor value.
*********************************************************************/
double rpiPWM1::getFrequency() const{ return this->frequency;}
int rpiPWM1::getCounts() const { return this->counts;}
int rpiPWM1::getDivisor() const {return this->divisor;}
double rpiPWM1::getDutyCycle() const {return this->dutyCycle;}
int rpiPWM1::getMode() const{return this->mode;}
/***********************************************************************
* volatile unsigned *rpiPWM1::mapRegAddr(unsigned long baseAddr)
* This function maps a block (4KB) of physical memory into the memory of
* the calling process. It enables a user space process to access registers
* in physical memory directly without having to interact with in kernel side
* code i.e. device drivers
* Parameter - baseAddr (unsigned long) - this is the base address of a 4KB
* block of physical memory that will be mapped into the user
* space process memory.
* Return Value - mapped pointer in process memory
***********************************************************************/
volatile unsigned *rpiPWM1::mapRegAddr(unsigned long baseAddr){
int mem_fd = 0;
void *regAddrMap = MAP_FAILED;
/* open /dev/mem.....need to run program as root i.e. use sudo or su */
if (!mem_fd) {
if ((mem_fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0) {
perror("can't open /dev/mem");
exit (1);
}
}
/* mmap IO */
regAddrMap = mmap(
NULL, //Any adddress in our space will do
BLOCK_SIZE, //Map length
PROT_READ|PROT_WRITE|PROT_EXEC,// Enable reading & writting to mapped memory
MAP_SHARED|MAP_LOCKED, //Shared with other processes
mem_fd, //File to map
baseAddr //Offset to base address
);
if (regAddrMap == MAP_FAILED) {
perror("mmap error");
close(mem_fd);
exit (1);
}
if(close(mem_fd) < 0){ //No need to keep mem_fd open after mmap
//i.e. we can close /dev/mem
perror("couldn't close /dev/mem file descriptor");
exit(1);
}
return (volatile unsigned *)regAddrMap;
}
/***********************************************************************
* void rpiPWM1::configPWM1Pin()
* This function is responsible for putting GPIO18 in PWM mode by setting
* its alternate function to ALT5. It firsts make the GPIO an input
* (default state) and then switches to alternate function 5 (PWM mode)
***********************************************************************/
void rpiPWM1::configPWM1Pin()
{
/*GPIO 18 in ALT5 mode for PWM0 */
// Let's first set pin 18 to input
//taken from #define INP_GPIO(g) *(gpio+((g)/10)) &= ~(7<<(((g)%10)*3))
*(gpio+1) &= ~(7 << 24);
//then set it to ALT5 function PWM0
//taken from #define SET_GPIO_ALT(g,a) *(gpio+(((g)/10))) |= (((a)<=3?(a)+4:(a)==4?3:2)<<(((g)%10)*3))
*(gpio+1) |= (2<<24);
}
/***********************************************************************
* void rpiPWM1::configPWM1()
* This function configures the PWM1 peripheral.
* - It stops the PWM clock
* - Calculates an appropriate divisor value based on PWM Freq and resolution
* - Writes this divisor value to the PWM clock
* - Enables the PWM clock
* - Disables the PWM peripheral
* - Writes the PWM resolution and Duty Cycle to the appropriate registers
* - Enables the PWM peripheral in the requested mode
* *********************************************************************/
void rpiPWM1::configPWM1(){
double period;
double countDuration;
// stop clock and waiting for busy flag doesn't work, so kill clock
*(clk + PWMCLK_CNTL) = 0x5A000000 | (1 << 5);
usleep(10);
// wait until busy flag is set
while ( (*(clk + PWMCLK_CNTL)) & 0x00000080){}
//calculate divisor value for PWM1 clock...base frequency is 19.2MHz
period = 1.0/this->frequency;
countDuration = period/(counts*1.0f);
this->divisor = (int)(19200000.0f / (1.0/countDuration));
if( this->divisor < 0 || this->divisor > 4095 ) {
printf("divisor value must be between 0-4095\n");
exit(-1);
}
//set divisor
*(clk + PWMCLK_DIV) = 0x5A000000 | (this->divisor << 12);
// source=osc and enable clock
*(clk + PWMCLK_CNTL) = 0x5A000011;
// disable PWM & start from a clean slate
*(pwm + PWM_CTL) = 0;
// needs some time until the PWM module gets disabled, without the delay the PWM module crashs
usleep(10);
// set the number of counts that constitute a period with 0 for 20 milliseconds = 320 bits
*(pwm + PWM_RNG1) = this->counts; usleep(10);
//set duty cycle
*(pwm + PWM_DAT1) = (int) ((this->dutyCycle/100.0) * this->counts); usleep(10);
// start PWM1 in
if(this->mode == PWMMODE) //PWM mode
*(pwm + PWM_CTL) |= (1 << 0);
else // M/S Mode
*(pwm + PWM_CTL) |= ( (1 << 7) | (1 << 0) );
}