Created
December 11, 2017 17:08
-
-
Save samehmohamed88/94f836f47e8bca30ac726c93c395467e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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) ); | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment