mirror of https://github.com/Desuuuu/klipper.git
linux: Add support for pca9685 i2c pwm devices
Add support for controlling pca9685 PWM drivers using the standard Linux I2C interface. The pca9685 device is found on Replicape boards. Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
This commit is contained in:
parent
d851882278
commit
73a1c9d249
|
@ -3,6 +3,7 @@
|
|||
dirs-y += src/linux src/generic
|
||||
|
||||
src-y += linux/main.c linux/timer.c linux/console.c linux/watchdog.c
|
||||
src-y += linux/pca9685.c
|
||||
src-y += generic/crc16_ccitt.c generic/alloc.c
|
||||
|
||||
CFLAGS_klipper.elf += -lutil
|
||||
|
|
|
@ -25,7 +25,7 @@ static struct pollfd main_pfd[2];
|
|||
#define MP_TTY_IDX 1
|
||||
|
||||
// Report 'errno' in a message written to stderr
|
||||
static void
|
||||
void
|
||||
report_errno(char *where, int rc)
|
||||
{
|
||||
int e = errno;
|
||||
|
@ -37,7 +37,7 @@ report_errno(char *where, int rc)
|
|||
* Setup
|
||||
****************************************************************/
|
||||
|
||||
static int
|
||||
int
|
||||
set_non_blocking(int fd)
|
||||
{
|
||||
int flags = fcntl(fd, F_GETFL);
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
#include <time.h> // struct timespec
|
||||
|
||||
// console.c
|
||||
void report_errno(char *where, int rc);
|
||||
int set_non_blocking(int fd);
|
||||
int console_setup(char *name);
|
||||
void console_sleep(struct timespec ts);
|
||||
|
||||
|
|
|
@ -0,0 +1,205 @@
|
|||
// Communicating with a PCA9685 pwm device via linux i2c
|
||||
//
|
||||
// Copyright (C) 2017 Kevin O'Connor <kevin@koconnor.net>
|
||||
//
|
||||
// This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
|
||||
#include <fcntl.h> // open
|
||||
#include <linux/i2c-dev.h> // I2C_SLAVE
|
||||
#include <stdio.h> // snprintf
|
||||
#include <sys/ioctl.h> // ioctl
|
||||
#include <unistd.h> // write
|
||||
#include "basecmd.h" // oid_alloc
|
||||
#include "board/misc.h" // timer_from_us
|
||||
#include "command.h" // DECL_COMMAND
|
||||
#include "internal.h" // report_errno
|
||||
#include "sched.h" // DECL_SHUTDOWN
|
||||
|
||||
|
||||
/****************************************************************
|
||||
* Low-level i2c
|
||||
****************************************************************/
|
||||
|
||||
#define P9_MODE1 0x00
|
||||
#define P9_PRESCALE 0xfe
|
||||
#define CHANNEL_MAX 15
|
||||
#define CHANNEL_ALL 61
|
||||
#define VALUE_MAX 4096
|
||||
|
||||
#define OSC_MHZ 25
|
||||
#define CLOCK_MHZ timer_from_us(1)
|
||||
|
||||
struct i2c_s {
|
||||
uint8_t bus;
|
||||
uint8_t addr;
|
||||
uint32_t cycle_ticks;
|
||||
int fd;
|
||||
};
|
||||
static struct i2c_s devices[16];
|
||||
static int devices_count;
|
||||
|
||||
static void
|
||||
pca9685_write(int fd, uint8_t channel, uint16_t value)
|
||||
{
|
||||
uint8_t full_on = 0x00;
|
||||
if (value >= VALUE_MAX) {
|
||||
full_on = 0x10;
|
||||
value = 0x0000;
|
||||
} else if (!value) {
|
||||
value = 0x1000;
|
||||
}
|
||||
uint8_t msg[5] = { channel*4 + 0x06, 0x00, full_on, value, value >> 8 };
|
||||
int ret = write(fd, msg, sizeof(msg));
|
||||
if (ret < 0) {
|
||||
report_errno("write value i2c", ret);
|
||||
try_shutdown("Unable to update PCA9685 value");
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
open_i2c(uint8_t bus, uint8_t addr, uint32_t cycle_ticks)
|
||||
{
|
||||
// Find existing device (if already opened)
|
||||
int i;
|
||||
for (i=0; i<devices_count; i++)
|
||||
if (devices[i].bus == bus && devices[i].addr == addr) {
|
||||
if (cycle_ticks != devices[i].cycle_ticks)
|
||||
shutdown("All PCA9685 channels must have the same cycle_ticks");
|
||||
return devices[i].fd;
|
||||
}
|
||||
|
||||
// Setup new I2C device
|
||||
if (devices_count >= ARRAY_SIZE(devices))
|
||||
shutdown("Too many i2c devices");
|
||||
char fname[256];
|
||||
snprintf(fname, sizeof(fname), "/dev/i2c-%d", bus);
|
||||
int fd = open(fname, O_RDWR|O_CLOEXEC);
|
||||
if (fd < 0) {
|
||||
report_errno("open i2c", fd);
|
||||
shutdown("Unable to open i2c device");
|
||||
}
|
||||
int ret = ioctl(fd, I2C_SLAVE, addr);
|
||||
if (ret < 0) {
|
||||
report_errno("ioctl i2c", fd);
|
||||
shutdown("Unable to set address on i2c device");
|
||||
}
|
||||
ret = set_non_blocking(fd);
|
||||
if (ret < 0)
|
||||
shutdown("Unable to set non-blocking on i2c device");
|
||||
|
||||
// Init PCA9685
|
||||
const uint8_t sleep_msg[2] = { P9_MODE1, 0x31 };
|
||||
ret = write(fd, sleep_msg, sizeof(sleep_msg));
|
||||
if (ret < 0) {
|
||||
report_errno("write sleep i2c", ret);
|
||||
shutdown("Unable to sleep PCA9685");
|
||||
}
|
||||
uint32_t freq = DIV_ROUND_CLOSEST(OSC_MHZ*cycle_ticks, 4096*CLOCK_MHZ) - 1;
|
||||
freq = freq > 0xff ? 0xff : (freq < 0x03 ? 0x03 : freq);
|
||||
uint8_t freq_msg[2] = { P9_PRESCALE, freq };
|
||||
ret = write(fd, freq_msg, sizeof(freq_msg));
|
||||
if (ret < 0) {
|
||||
report_errno("write freq i2c", ret);
|
||||
shutdown("Unable to set freq on PCA9685");
|
||||
}
|
||||
const uint8_t wake_msg[2] = { P9_MODE1, 0x21 };
|
||||
ret = write(fd, wake_msg, sizeof(wake_msg));
|
||||
if (ret < 0) {
|
||||
report_errno("write unsleep i2c", ret);
|
||||
shutdown("Unable to wake PCA9685");
|
||||
}
|
||||
usleep(500);
|
||||
pca9685_write(fd, CHANNEL_ALL, 0);
|
||||
|
||||
devices[devices_count].bus = bus;
|
||||
devices[devices_count].addr = addr;
|
||||
devices[devices_count].cycle_ticks = cycle_ticks;
|
||||
devices[devices_count].fd = fd;
|
||||
return fd;
|
||||
}
|
||||
|
||||
void
|
||||
pca9685_shutdown(void)
|
||||
{
|
||||
int i;
|
||||
for (i=0; i<devices_count; i++)
|
||||
pca9685_write(devices[i].fd, CHANNEL_ALL, 0);
|
||||
}
|
||||
DECL_SHUTDOWN(pca9685_shutdown);
|
||||
|
||||
|
||||
/****************************************************************
|
||||
* Command interface
|
||||
****************************************************************/
|
||||
|
||||
struct i2cpwm_s {
|
||||
struct timer timer;
|
||||
int fd;
|
||||
uint8_t channel;
|
||||
uint16_t value;
|
||||
uint32_t max_duration;
|
||||
};
|
||||
|
||||
DECL_CONSTANT(PCA9685_MAX, VALUE_MAX);
|
||||
|
||||
static uint_fast8_t
|
||||
pca9685_end_event(struct timer *timer)
|
||||
{
|
||||
shutdown("Missed scheduling of next pca9685 event");
|
||||
}
|
||||
|
||||
static uint_fast8_t
|
||||
pca9685_event(struct timer *timer)
|
||||
{
|
||||
struct i2cpwm_s *p = container_of(timer, struct i2cpwm_s, timer);
|
||||
pca9685_write(p->fd, p->channel, p->value);
|
||||
if (!p->value || !p->max_duration)
|
||||
return SF_DONE;
|
||||
p->timer.waketime += p->max_duration;
|
||||
p->timer.func = pca9685_end_event;
|
||||
return SF_RESCHEDULE;
|
||||
}
|
||||
|
||||
void
|
||||
command_config_pca9685(uint32_t *args)
|
||||
{
|
||||
struct i2cpwm_s *p = oid_alloc(args[0], command_config_pca9685
|
||||
, sizeof(*p));
|
||||
uint8_t bus = args[1];
|
||||
uint8_t addr = args[2];
|
||||
p->channel = args[3];
|
||||
if (p->channel > CHANNEL_MAX)
|
||||
shutdown("Invalid pca9685 channel");
|
||||
p->max_duration = args[5];
|
||||
p->fd = open_i2c(bus, addr, args[4]);
|
||||
}
|
||||
DECL_COMMAND(command_config_pca9685, "config_pca9685 oid=%c bus=%c addr=%c"
|
||||
" channel=%c cycle_ticks=%u max_duration=%u");
|
||||
|
||||
void
|
||||
command_schedule_pca9685_out(uint32_t *args)
|
||||
{
|
||||
struct i2cpwm_s *p = oid_lookup(args[0], command_config_pca9685);
|
||||
sched_del_timer(&p->timer);
|
||||
p->timer.func = pca9685_event;
|
||||
p->timer.waketime = args[1];
|
||||
p->value = args[2];
|
||||
if (p->value > VALUE_MAX)
|
||||
shutdown("Invalid pca9685 value");
|
||||
sched_add_timer(&p->timer);
|
||||
}
|
||||
DECL_COMMAND(command_schedule_pca9685_out,
|
||||
"schedule_pca9685_out oid=%c clock=%u value=%hu");
|
||||
|
||||
void
|
||||
command_set_pca9685_out(uint32_t *args)
|
||||
{
|
||||
uint8_t bus = args[0], addr = args[1], channel = args[2];
|
||||
uint16_t value = args[4];
|
||||
if (channel > CHANNEL_MAX || value > VALUE_MAX)
|
||||
shutdown("Invalid pca9685 channel or value");
|
||||
int fd = open_i2c(bus, addr, args[3]);
|
||||
pca9685_write(fd, channel, value);
|
||||
}
|
||||
DECL_COMMAND(command_set_pca9685_out, "set_pca9685_out bus=%c addr=%c"
|
||||
" channel=%c cycle_ticks=%u value=%hu");
|
Loading…
Reference in New Issue