From d72516070644bbe7876ae002121913fc9e2b64ed Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 29 May 2018 20:59:17 -0400 Subject: [PATCH] mcp4451: Add initial support for programming the mcp4451 on lpc176x Add support for programming smoothieboard current. Signed-off-by: Kevin O'Connor --- config/example-extras.cfg | 29 +++++++++ config/generic-smoothieboard.cfg | 16 +++++ klippy/extras/mcp4451.py | 32 ++++++++++ src/lpc176x/Makefile | 2 +- src/lpc176x/i2c.c | 100 +++++++++++++++++++++++++++++++ 5 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 klippy/extras/mcp4451.py create mode 100644 src/lpc176x/i2c.c diff --git a/config/example-extras.cfg b/config/example-extras.cfg index 0ef6e63a..f6c9b86c 100644 --- a/config/example-extras.cfg +++ b/config/example-extras.cfg @@ -424,6 +424,35 @@ # default is to not scale the 'channel_x' parameters. +# Statically configured MCP4451 digipot connected via I2C bus (one may +# define any number of sections with an "mcp4451" prefix). +#[mcp4451 my_digipot] +#mcu: mcu +# The name of the micro-controller that the MCP4451 chip is +# connected to. The default is "mcu". +#i2c_address: +# The i2c address that the chip is using on the i2c bus. This +# parameter must be provided. +#wiper_0: +#wiper_1: +#wiper_2: +#wiper_3: +# The value to statically set the given MCP4451 "wiper" to. This is +# typically set to a number between 0.0 and 1.0 with 1.0 being the +# highest resistance and 0.0 being the lowest resistance. However, +# the range may be changed with the 'scale' parameter (see +# below). If a wiper is not specified then it is left unconfigured. +#scale: +# This parameter can be used to alter how the 'wiper_x' parameters +# are interpreted. If provided, then the 'wiper_x' parameters should +# be between 0.0 and 'scale'. This may be useful when the MCP4451 is +# used to set stepper voltage references. The 'scale' can be set to +# the equivalent stepper amperage if the MCP4451 were at its highest +# resistance, and then the 'wiper_x' parameters can be specified +# using the desired amperage value for the stepper. The default is +# to not scale the 'wiper_x' parameters. + + # Configure a TMC2130 stepper motor driver via SPI bus. To use this # feature, define a config section with a "tmc2130" prefix followed by # the name of the corresponding stepper config section (for example, diff --git a/config/generic-smoothieboard.cfg b/config/generic-smoothieboard.cfg index 0366e4f6..d52fff07 100644 --- a/config/generic-smoothieboard.cfg +++ b/config/generic-smoothieboard.cfg @@ -85,3 +85,19 @@ max_z_accel: 100 [static_digital_output leds] pins: P1.18, P1.19, P1.20, P1.21, P4.28 + +[mcp4451 stepper_digipot1] +i2c_address: 88 +# Scale the config so that values can be specified in amps. +scale: 2.25 +# wiper 0 is X (aka alpha), 1 is Y, 2 is Z, 3 is E0 +wiper_0: 1.0 +wiper_1: 1.0 +wiper_2: 1.0 +wiper_3: 1.0 + +[mcp4451 stepper_digipot2] +i2c_address: 90 +scale: 2.25 +# wiper 0 is E1 +wiper_0: 1.0 diff --git a/klippy/extras/mcp4451.py b/klippy/extras/mcp4451.py new file mode 100644 index 00000000..a7eda9aa --- /dev/null +++ b/klippy/extras/mcp4451.py @@ -0,0 +1,32 @@ +# MCP4451 digipot code +# +# Copyright (C) 2018 Kevin O'Connor +# +# This file may be distributed under the terms of the GNU GPLv3 license. +import mcu + +WiperRegisters = [0x00, 0x01, 0x06, 0x07] + +class mcp4451: + def __init__(self, config): + printer = config.get_printer() + self.mcu = mcu.get_printer_mcu(printer, config.get('mcu', 'mcu')) + self.i2c_addr = config.getint('i2c_address') + scale = config.getfloat('scale', 1., above=0.) + wipers = [None]*4 + for i in range(len(wipers)): + val = config.getfloat('wiper_%d' % (i,), None, + minval=0., maxval=scale) + if val is not None: + wipers[i] = int(val * 255. / scale + .5) + self.add_config_cmd(0x04, 0xff) + self.add_config_cmd(0x0a, 0xff) + for reg, val in zip(WiperRegisters, wipers): + if val is not None: + self.add_config_cmd(reg, val) + def add_config_cmd(self, reg, val): + self.mcu.add_config_cmd("i2c_send data=%02x%02x%02x" % ( + self.i2c_addr, (reg << 4) | ((val >> 8) & 0x03), val), is_init=True) + +def load_config_prefix(config): + return mcp4451(config) diff --git a/src/lpc176x/Makefile b/src/lpc176x/Makefile index a4e3a742..e6f9007c 100644 --- a/src/lpc176x/Makefile +++ b/src/lpc176x/Makefile @@ -13,7 +13,7 @@ CFLAGS_klipper.elf += -T $(OUT)LPC1768.ld CFLAGS_klipper.elf += --specs=nano.specs --specs=nosys.specs # Add source files -src-y += lpc176x/main.c lpc176x/timer.c lpc176x/gpio.c +src-y += lpc176x/main.c lpc176x/timer.c lpc176x/gpio.c lpc176x/i2c.c src-y += generic/crc16_ccitt.c generic/alloc.c src-y += generic/armcm_irq.c generic/timer_irq.c src-y += ../lib/lpc176x/device/system_LPC17xx.c diff --git a/src/lpc176x/i2c.c b/src/lpc176x/i2c.c new file mode 100644 index 00000000..76c5b738 --- /dev/null +++ b/src/lpc176x/i2c.c @@ -0,0 +1,100 @@ +// I2C functions on lpc176x +// +// Copyright (C) 2018 Kevin O'Connor +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include "LPC17xx.h" // LPC_I2C1 +#include "board/misc.h" // timer_is_before +#include "command.h" // DECL_COMMAND +#include "internal.h" // gpio_peripheral +#include "sched.h" // sched_shutdown + +// i2c connection status flags +enum { + IF_START = 1<<5, IF_STOP = 1<<4, IF_IRQ = 1<<3, IF_ACK = 1<<2, IF_ENA = 1<<6 +}; + +static void +i2c_init(void) +{ + static int have_run_init; + if (have_run_init) + return; + have_run_init = 1; + + // Init i2c bus 1 pins + gpio_peripheral(0, 0, 3, 0); + gpio_peripheral(0, 1, 3, 0); + + // Set 100Khz frequency + uint32_t PCLK = SystemCoreClock / 4, pulse = PCLK / (100000 * 2); + LPC_I2C1->I2SCLL = pulse; + LPC_I2C1->I2SCLH = pulse; + + // Enable interface + LPC_I2C1->I2CONCLR = IF_START | IF_IRQ | IF_ACK | IF_ENA; + LPC_I2C1->I2CONSET = IF_ENA; +} + +static void +i2c_wait(uint32_t bit, uint32_t timeout) +{ + for (;;) { + uint32_t flags = LPC_I2C1->I2CONSET; + if (flags & bit) + break; + if (!timer_is_before(timer_read_time(), timeout)) + shutdown("i2c timeout"); + } +} + +static void +i2c_start(uint32_t timeout) +{ + LPC_I2C1->I2CONCLR = IF_ACK | IF_IRQ | IF_START; + LPC_I2C1->I2CONSET = IF_ACK | IF_START; + i2c_wait(IF_IRQ, timeout); + uint32_t status = LPC_I2C1->I2STAT; + if (status != 0x10 && status != 0x08) + shutdown("Failed to send i2c start"); + LPC_I2C1->I2CONCLR = IF_START; +} + +static uint32_t +i2c_send_byte(uint8_t b, uint32_t timeout) +{ + LPC_I2C1->I2DAT = b; + LPC_I2C1->I2CONCLR = IF_IRQ; + i2c_wait(IF_IRQ, timeout); + return LPC_I2C1->I2STAT; +} + +static void +i2c_stop(uint32_t timeout) +{ + LPC_I2C1->I2CONSET = IF_STOP; + LPC_I2C1->I2CONCLR = IF_IRQ; + i2c_wait(IF_STOP, timeout); +} + +static void +i2c_send(uint8_t *data, int data_len) +{ + i2c_init(); + uint32_t timeout = timer_read_time() + timer_from_us(5000); + + i2c_start(timeout); + while (data_len--) + i2c_send_byte(*data++, timeout); + i2c_stop(timeout); +} + +// This provides just enough functionality to program an MCP4451 chip +void +command_i2c_send(uint32_t *args) +{ + uint8_t data_len = args[0], *data = (void*)(size_t)args[1]; + i2c_send(data, data_len); +} +DECL_COMMAND(command_i2c_send, "i2c_send data=%*s");