432 lines
16 KiB
C++
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) );
|
|
}
|
|
|