Private
Public Access
2
0

rpi: Adding PWM support

The Pi doesn't have userspace level PWM support, but you can mmap the
raw registers and configure it that way, which is what this patch is
doing. Tested with Raspberry Pi 3 B. I am able to set the duty cycle,
change the frequency, and set the pulse width. Signals verified with
logic analyzer. Note that the only accessible pwm is on GPIO18, which is
also used by the audio subsystem.

Signed-off-by: Nick Crast <nicholas.crast@anaren.com>
Signed-off-by: Brendan Le Foll <brendan.le.foll@intel.com>
This commit is contained in:
nick
2017-09-07 14:15:58 -04:00
committed by Brendan Le Foll
parent 615e0cd83a
commit 8356c2f03d

View File

@@ -23,14 +23,15 @@
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <dirent.h>
#include <mraa/common.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <sys/mman.h>
#include <mraa/common.h>
#include "common.h"
#include "arm/raspberry_pi.h"
#include "common.h"
#define PLATFORM_NAME_RASPBERRY_PI_B_REV_1 "Raspberry Pi Model B Rev 1"
#define PLATFORM_NAME_RASPBERRY_PI_A_REV_2 "Raspberry Pi Model A Rev 2"
@@ -52,22 +53,251 @@
#define PLATFORM_RASPBERRY_PI3_B 9
#define MMAP_PATH "/dev/mem"
#define BCM2835_PERI_BASE 0x20000000
#define BCM2835_GPIO_BASE (BCM2835_PERI_BASE + 0x200000)
#define BCM2836_PERI_BASE 0x3f000000
#define BCM2836_GPIO_BASE (BCM2836_PERI_BASE + 0x200000)
#define BCM2835_BLOCK_SIZE (4 * 1024)
#define BCM2836_BLOCK_SIZE (4 * 1024)
#define BCM2837_PERI_BASE (0x3F000000)
#define BCM2837_BLOCK_SIZE (4 * 1024)
#define BCM283X_GPSET0 0x001c
#define BCM283X_GPCLR0 0x0028
#define BCM2835_GPLEV0 0x0034
#define MAX_SIZE 64
// MMAP
#define GPIO_OFFSET (0x200000)
#define PWM_OFFSET (0x20C000)
#define CLOCK_OFFSET (0x101000)
// PWM
#define PWM_BASE_FREQ (19200000)
// Base register addresses
#define PWM_CTL (0)
#define PWM_RNG1 (4)
#define PWM_DAT1 (5)
#define PWMCLK_CNTL (40)
#define PWMCLK_DIV (41)
// Register addresses offsets divided by 4 (register addresses are word (32-bit) aligned
#define PWM_RNG (4095)
#define DEFAULT_PERIOD_US 500
#define MIN_PERIOD_US 400
#define MAX_PERIOD_US 500000
static volatile unsigned* clk_reg = NULL;
static volatile unsigned* gpio_reg = NULL;
static volatile unsigned* pwm_reg = NULL;
// GPIO MMAP
static uint8_t* mmap_reg = NULL;
static int mmap_fd = 0;
static int mmap_size;
static unsigned int mmap_count = 0;
static int platform_detected = 0;
static uint32_t peripheral_base = BCM2835_PERI_BASE;
static uint32_t block_size = BCM2835_BLOCK_SIZE;
static bool pwm_enabled = false;
/**
* Calculate the PWM clk divisor given a desired period
*/
static inline uint32_t
calc_divisor(uint32_t desired_period_us)
{
float desired_freq = 1.0 / (desired_period_us / 1000000.0);
float divisor = PWM_BASE_FREQ / (desired_freq * PWM_RNG);
return (uint32_t)(divisor + .5); // Round to nearest integer
}
/**
* Memory map an arbitrary address
*/
static volatile unsigned*
mmap_reg_addr(unsigned long base_addr)
{
int mem_fd = 0;
void* reg_addr_map = 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");
return NULL;
}
}
/* mmap IO */
reg_addr_map =
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
base_addr // Offset to base address
);
if (reg_addr_map == MAP_FAILED) {
perror("mmap error");
close(mem_fd);
return NULL;
}
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");
return NULL;
}
return (volatile unsigned*) reg_addr_map;
}
/**
* Memory map PWM registers
*/
static int8_t
mmap_regs()
{
// Already mapped!
if (clk_reg || gpio_reg || pwm_reg) {
return;
}
clk_reg = mmap_reg_addr(peripheral_base + CLOCK_OFFSET);
gpio_reg = mmap_reg_addr(peripheral_base + GPIO_OFFSET);
pwm_reg = mmap_reg_addr(peripheral_base + PWM_OFFSET);
// If all 3 weren't mapped correctly
if (!(clk_reg && gpio_reg && pwm_reg)) {
return -1;
}
return 0;
}
static void
pwm_stop()
{
// stop clock and waiting for busy flag doesn't work, so kill clock
*(clk_reg + PWMCLK_CNTL) = 0x5A000000 | (1 << 5);
usleep(10);
// wait until busy flag is set
while ((*(clk_reg + PWMCLK_CNTL)) & 0x00000080) {
}
*(pwm_reg + PWM_CTL) = 0;
usleep(10);
}
static void
pwm_start()
{
// source=osc and enable clock
*(clk_reg + PWMCLK_CNTL) = 0x5A000011;
// start PWM1 in mark space mode
*(pwm_reg + PWM_CTL) |= ((1 << 7) | (1 << 0));
usleep(10);
}
static void
config_pwm1(uint32_t divisor)
{
/*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_reg + 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_reg + 1) |= (2 << 24);
pwm_stop();
// set divisor
*(clk_reg + PWMCLK_DIV) = 0x5A000000 | (divisor << 12);
// disable PWM & start from a clean slate
*(pwm_reg + 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_reg + PWM_RNG1) = PWM_RNG;
usleep(10);
// If the user has pwm set to enabled
if (pwm_enabled) {
pwm_start();
}
}
// This shouldn't be a float, since it's called in mraa_pwm_read_duty, which
// seems to return the duty cycle in us
float
mraa_raspberry_pi_duty_cycle_read_replace(mraa_pwm_context dev)
{
uint32_t duty_counts = *(pwm_reg + PWM_DAT1);
return (((float) duty_counts) / PWM_RNG) * dev->period;
}
mraa_result_t
mraa_raspberry_pi_pwm_period_us_replace(mraa_pwm_context dev, int period)
{
config_pwm1(calc_divisor(period / 1000));
dev->period = period;
return MRAA_SUCCESS;
}
// Used in pwm.c, duty is actually just the raw counts
// Shouldn't be a float
mraa_result_t
mraa_raspberry_pi_pwm_write_duty_replace(mraa_pwm_context dev, float duty)
{
// We are given duty as a percentage of the period
// Convert it to be a percentage of the range
float duty_pct = duty / dev->period;
*(pwm_reg + PWM_DAT1) = (uint32_t)(duty_pct * PWM_RNG);
return MRAA_SUCCESS;
}
mraa_result_t
mraa_raspberry_pi_pwm_enable_replace(mraa_pwm_context dev, int enable)
{
if (enable == 1) {
pwm_enabled = true;
pwm_start();
} else {
pwm_enabled = false;
pwm_stop();
}
return MRAA_SUCCESS;
}
mraa_result_t
mraa_raspberry_pi_pwm_initraw_replace(mraa_pwm_context dev, int pin)
{
if (pin != 0) {
return MRAA_ERROR_INVALID_PARAMETER;
}
if (mmap_regs() != 0) {
return MRAA_ERROR_UNSPECIFIED;
}
config_pwm1(DEFAULT_PERIOD_US);
*(pwm_reg + PWM_DAT1) = 0;
dev->period = DEFAULT_PERIOD_US;
return MRAA_SUCCESS;
}
mraa_result_t
mraa_raspberry_pi_spi_init_pre(int index)
@@ -188,13 +418,8 @@ mraa_raspberry_pi_mmap_setup(mraa_gpio_context dev, mraa_boolean_t en)
return MRAA_ERROR_INVALID_HANDLE;
}
if (platform_detected == PLATFORM_RASPBERRY_PI2_B_REV_1) {
mmap_reg = (uint8_t*) mmap(NULL, BCM2836_BLOCK_SIZE, PROT_READ | PROT_WRITE,
MAP_FILE | MAP_SHARED, mmap_fd, BCM2836_GPIO_BASE);
} else {
mmap_reg = (uint8_t*) mmap(NULL, BCM2835_BLOCK_SIZE, PROT_READ | PROT_WRITE,
MAP_FILE | MAP_SHARED, mmap_fd, BCM2835_GPIO_BASE);
}
mmap_reg = (uint8_t*) mmap(NULL, block_size, PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED,
mmap_fd, peripheral_base + GPIO_OFFSET);
if (mmap_reg == MAP_FAILED) {
syslog(LOG_ERR, "raspberry mmap: failed to mmap");
mmap_reg = NULL;
@@ -271,11 +496,15 @@ mraa_raspberry_pi()
b->platform_name = PLATFORM_NAME_RASPBERRY_PI2_B_REV_1;
platform_detected = PLATFORM_RASPBERRY_PI2_B_REV_1;
b->phy_pin_count = MRAA_RASPBERRY_PI2_B_REV_1_PINCOUNT;
peripheral_base = BCM2836_PERI_BASE;
block_size = BCM2836_BLOCK_SIZE;
} else if (strstr(line, "a02082") || strstr(line, "a020a0") ||
strstr(line, "a22082") || strstr(line, "a32082")) {
b->platform_name = PLATFORM_NAME_RASPBERRY_PI3_B;
platform_detected = PLATFORM_RASPBERRY_PI3_B;
b->phy_pin_count = MRAA_RASPBERRY_PI3_B_PINCOUNT;
peripheral_base = BCM2837_PERI_BASE;
block_size = BCM2837_BLOCK_SIZE;
} else {
b->platform_name = PLATFORM_NAME_RASPBERRY_PI_B_REV_1;
platform_detected = PLATFORM_RASPBERRY_PI_B_REV_1;
@@ -293,7 +522,7 @@ mraa_raspberry_pi()
if (!tweakedCpuinfo) {
// See Documentation/devicetree/bindings/arm/bcm/brcm,bcm2835.txt
// for the values
const char *compatible_path = "/proc/device-tree/compatible";
const char* compatible_path = "/proc/device-tree/compatible";
if (mraa_file_contains(compatible_path, "raspberrypi,model-b")) {
b->platform_name = PLATFORM_NAME_RASPBERRY_PI_B_REV_1;
platform_detected = PLATFORM_RASPBERRY_PI_B_REV_1;
@@ -340,9 +569,9 @@ mraa_raspberry_pi()
b->aio_count = 0;
b->adc_raw = 0;
b->adc_supported = 0;
b->pwm_default_period = 500;
b->pwm_max_period = 2147483;
b->pwm_min_period = 1;
b->pwm_default_period = DEFAULT_PERIOD_US;
b->pwm_max_period = MAX_PERIOD_US;
b->pwm_min_period = MIN_PERIOD_US;
if (b->phy_pin_count == 0) {
free(b);
@@ -392,6 +621,11 @@ mraa_raspberry_pi()
b->adv_func->i2c_init_pre = &mraa_raspberry_pi_i2c_init_pre;
b->adv_func->gpio_mmap_setup = &mraa_raspberry_pi_mmap_setup;
b->adv_func->spi_frequency_replace = &mraa_raspberry_pi_spi_frequency_replace;
b->adv_func->pwm_init_raw_replace = &mraa_raspberry_pi_pwm_initraw_replace;
b->adv_func->pwm_write_replace = &mraa_raspberry_pi_pwm_write_duty_replace;
b->adv_func->pwm_period_replace = &mraa_raspberry_pi_pwm_period_us_replace;
b->adv_func->pwm_read_replace = &mraa_raspberry_pi_duty_cycle_read_replace;
b->adv_func->pwm_enable_replace = &mraa_raspberry_pi_pwm_enable_replace;
strncpy(b->pins[0].name, "INVALID", MRAA_PIN_NAME_SIZE);
b->pins[0].capabilities = (mraa_pincapabilities_t){ 0, 0, 0, 0, 0, 0, 0, 0 };
@@ -450,9 +684,10 @@ mraa_raspberry_pi()
b->pins[11].gpio.mux_total = 0;
strncpy(b->pins[12].name, "GPIO18", MRAA_PIN_NAME_SIZE);
b->pins[12].capabilities = (mraa_pincapabilities_t){ 1, 1, 0, 0, 0, 0, 0, 0 };
b->pins[12].capabilities = (mraa_pincapabilities_t){ 1, 1, 1, 0, 0, 0, 0, 0 };
b->pins[12].gpio.pinmap = pin_base + 18;
b->pins[12].gpio.mux_total = 0;
b->pins[12].pwm.pinmap = 0;
if (platform_detected == PLATFORM_RASPBERRY_PI_B_REV_1) {
strncpy(b->pins[13].name, "GPIO21", MRAA_PIN_NAME_SIZE);
@@ -593,8 +828,7 @@ mraa_raspberry_pi()
if ((platform_detected == PLATFORM_RASPBERRY_PI_A_PLUS_REV_1) ||
(platform_detected == PLATFORM_RASPBERRY_PI_B_PLUS_REV_1) ||
(platform_detected == PLATFORM_RASPBERRY_PI2_B_REV_1) ||
(platform_detected == PLATFORM_RASPBERRY_PI3_B) ||
(platform_detected == PLATFORM_RASPBERRY_PI_ZERO)) {
(platform_detected == PLATFORM_RASPBERRY_PI3_B) || (platform_detected == PLATFORM_RASPBERRY_PI_ZERO)) {
strncpy(b->pins[27].name, "ID_SD", MRAA_PIN_NAME_SIZE);
b->pins[27].capabilities = (mraa_pincapabilities_t){ 1, 0, 0, 0, 0, 0, 0, 0 };