Welcome! Log In Create A New Profile

Advanced

Fan etc on external i2c-controller

Posted by maeckes 
Fan etc on external i2c-controller
November 01, 2018 03:53PM
Hi there,

my Board (GT2560) does not support controle of more then one fan.
So I would like to add I/Os by using an i2c-expander.

I know, that I have to modify the GT2560-Board to reach the i2c-Bus - but how can I teach Marlin to use the new I/Os for the fans?

Thank you in advance for any help!

Best regards
Maeckes
Re: Fan etc on external i2c-controller
November 01, 2018 10:34PM
NB most I/O expanders only provide digital output, IE on or off, for a fan you really want PWM so you can control the speed.

First you have to identify the chip used on your PWM capable IO expander, then find an arduino example of how to use that chip, finally you integrate that into the marlin firmware.

If no one has made it work on arduino, then you have to find the datasheet for your chip, read, it, understand it, let it take over you life, and write code to use it.

"16 Channel 12-bit PWM/Servo Driver-I2C interface PCA9685 " should work... and your still going to have to add mosfets
Adafruit has libraries [learn.adafruit.com]

you just need to modify the M106/M107 commands as needed

Edited 1 time(s). Last edit at 11/01/2018 10:45PM by Dust.
Re: Fan etc on external i2c-controller
November 02, 2018 02:54AM
Hi,

this is exactly the chip which I ordered and I also took a look in the example sheets.
And you are right - I also ordered some Mosfets to controle the Fans.

Ok, that is the electronical part and I think I know how to use the experimentaly i2c-Gcode-function.
But Iam Looking for another Function:

Marlin offers the option to switch on/off a fan, if the extruders temperature is above X degrees.
This is exactly a feature I need.

Here a part of the code:
// @section extruder

/**
* Extruder cooling fans
*
* Extruder auto fans automatically turn on when their extruders'
* temperatures go above EXTRUDER_AUTO_FAN_TEMPERATURE

So .... how do I set a Pin of the Servo-driver to the specific fan (into Marlin), to switch it on if extruders temperature is above 50 degrees?

Thank you in advance for your help!

Edited 1 time(s). Last edit at 11/02/2018 02:55AM by maeckes.
Re: Fan etc on external i2c-controller
November 02, 2018 03:46AM
you cant use a pin, well not directly.

you have to change the code that normally uses a pin to use the i2c device

as a hack, set the pin to something unique -100, -101 etc

then in the code you can program if the pin is < 100 it should use i2c device... same trick they use for thermocouple vs thermistor

but I would get m106/107 working first, as the auto fans are much more complicated
Re: Fan etc on external i2c-controller
December 01, 2018 04:09PM
Hi there

I just prepared the 12-bit port expander which you also named before (PCA9685 Chipset).
Now I would like to try if it works.

Like written in the manual, the board should have the address 0x40 (which is 64 in decimalcode).

As far as I know, I have to send an M260 command, to switch an output (as written here).

So the code is something like
M260 A64
M260 B*
M260 S1

Does anyone know, what I have to send instead of * ?

Thank you in advance!

Edited 3 time(s). Last edit at 12/01/2018 04:11PM by maeckes.
Re: Fan etc on external i2c-controller
December 01, 2018 08:29PM
Its not quite that simple.. it not a byte, but multiple bytes

using M260 you have to manipulate the individual registers inside the chip

The following is a example from a pic processor, but it goes into details on all the registers and and eg
See [openlabpro.com]

so this should work for led 0,%50on/%50off (I have not tested it)

;init the chip
M260 A64
M260 B00 ;Control register set to address 00h
M260 B33 ;(0x21) Mode 1 configured to with AI=1 and ALLCALL=1
M260 S1

;set led0 50on/50off
M260 A64
M260 B06 ;Control register set to address LED0_ON_L
M260 B00 ;Writing 8 LSB bits of ON count
M260 B08 ;Writing 4 MSB bits of ON count
M260 B00 ;Writing 8 LSB bits of OFF count
M260 B08 ;Writing 4 MSB bits of ON count
M260 S1
Re: Fan etc on external i2c-controller
December 02, 2018 05:56AM
Thank you very much for your help!

What I did:
- connected the port expander (adafruit servo driver) with my GT2560 Board
- uploaded the example-code for adafruit servo-driver on it
-> Output works, there is a +5V signal on the PWM-Pin of Output #0

Then I uploaded the marlin code with
#define EXPERIMENTAL_I2CBUS

Next I used the upper code you posted and sent it line by line in the terminal.

But with Marlin I don't get an output on PWM-Pin #0 ....

Do you habe any further idea?

Edited 1 time(s). Last edit at 12/02/2018 05:57AM by maeckes.
Re: Fan etc on external i2c-controller
December 02, 2018 07:03AM
ok looks like more gcode needed, try this

;reset
M260 A64
M260 B00 ;Control register set to address 00h
M260 B128 ;(0x80) Mode 1 configured to with restart=1
M260 S1

;set PWM freq
M260 A64
M260 B254 ;Control register set to address FEh
M260 B5
M260 S1

;init the chip
M260 A64
M260 B00 ;Control register set to address 00h
M260 B33 ;(0x21) Mode 1 configured to with AI=1 and ALLCALL=1
M260 S1

;set led0 50on/50off
M260 A64
M260 B06 ;Control register set to address LED0_ON_L
M260 B00 ;Writing 8 LSB bits of ON count
M260 B08 ;Writing 4 MSB bits of ON count
M260 B00 ;Writing 8 LSB bits of OFF count
M260 B08 ;Writing 4 MSB bits of ON count
M260 S1

Edited 1 time(s). Last edit at 12/02/2018 07:07AM by Dust.
Re: Fan etc on external i2c-controller
December 02, 2018 07:43AM
First - thank you very much for your support!

I just tried it several times but unfortunately not successful... sad smiley


Do you have an idea why...?
Re: Fan etc on external i2c-controller
December 02, 2018 08:49AM
not really, I mostly copied the guts of what adafruit servo driver does...

Perhaps EXPERIMENTAL_I2CBUS is not functional...

If I get a chance ill see if I can have a play on real hardware
Re: Fan etc on external i2c-controller
December 02, 2018 11:06AM
ok.. ive set up a test environment

made a simple sketch, this works fine

#include

// PCA9685 I2C address is 0x40(64)
#define Addr 0x40

int mode = 0;
void setup()
{
  // Initialise I2C communication as MASTER
  Wire.begin();

  // Start I2C Transmission
  Wire.beginTransmission(Addr);
  // Select MODE1 register
  Wire.write(0x00);
  // Response to LED all-call I2C address
  Wire.write(0x01);
  // Stop I2C Transmission
  Wire.endTransmission();
  delay(5);

// Start I2C Transmission
    Wire.beginTransmission(Addr);
    // Select ALL_LED_ON_L register
    Wire.write(0x06);
    // ALL_LED_ON lower byte
    Wire.write(0x00);
    // Stop I2C Transmission
    Wire.endTransmission();
    
    // Start I2C Transmission
    Wire.beginTransmission(Addr);
    // Select ALL_LED_ON_H register
    Wire.write(0x07);
    // ALL_LED_ON higher byte
    Wire.write(0x00);
    
    // Stop I2C Transmission
    Wire.endTransmission();
    
    
    // Start I2C Transmission
    Wire.beginTransmission(Addr);
    // Select ALL_LED_OFF_L register
    Wire.write(0x08);
    // ALL_LED_OFF lower byte
    Wire.write(0xFF);
    
    // Stop I2C Transmission
    Wire.endTransmission();
    
    // Start I2C Transmission
    Wire.beginTransmission(Addr);
    // Select ALL_LED_OFF_H register
    Wire.write(0x09);
    // ALL_LED_OFF higher byte
    Wire.write(0x0F);
    // Stop I2C Transmission
    Wire.endTransmission();
}

void loop()
{
 
}


the exact same thing using gcode, also works.
M260 A64
M260 B0
M260 B1
M260 S1

M260 A64
M260 B6
M260 B0
M260 S1

M260 A64
M260 B7
M260 B0
M260 S1

M260 A64
M260 B8
M260 B255
M260 S1

M260 A64
M260 B9
M260 B15
M260 S1

Edited 1 time(s). Last edit at 12/02/2018 11:14AM by Dust.
Re: Fan etc on external i2c-controller
December 02, 2018 12:42PM
WORKS!!!
Thank you very much!!

But at the moment I don't understand it completely...

Iam working with this datasheet

Example:
M260 A64 ;i2c-address in decimal
M260 B9 ;choose "LED0_OFF_H" in the Register definitions (chapter 7.3 / page 10)
M260 B15 ; what does this mean? Something in Table 7 / page 21??
M260 S1; send to bus

Perhapse you can show me what I would have to change in the whole code if I want to switch it off?
And what do I have to change to choose another channel? Like LED1 ?

THANKS A LOT!
Re: Fan etc on external i2c-controller
December 02, 2018 07:30PM
This is initialization, only needs to be done once

M260 A64
M260 B0
M260 B1
M260 S1

this turns on the led0 and sets the PWM 0/4095 (0 On/4095 Off, this is reverse of what is expected, but works)
M260 A64
M260 B6  ;LED_0_ON_L register address see registers 6 - 69 . Each Led has 4 registers a ON_L, ON_H, OFF_L and OFF_H
M260 B0
M260 S1

M260 A64
M260 B7 ;LED_0_ON_H  
M260 B0
M260 S1

M260 A64
M260 B8 ;LED_0_OFF_L
M260 B255
M260 S1

M260 A64
M260 B9 ;LED_0_OFF_H
M260 B15
M260 S1

so to turn it off you would send
M260 A64
M260 B6  ;LED_0_ON_L register address see registers 6 - 69 . Each Led has 4 registers a ON_L, ON_H, OFF_L and OFF_H
M260 B255
M260 S1

M260 A64
M260 B7 ;LED_0_ON_H  
M260 B15
M260 S1

M260 A64
M260 B8 ;LED_0_OFF_L
M260 B0
M260 S1

M260 A64
M260 B9 ;LED_0_OFF_H
M260 B0
M260 S1

and to turn on say led13

M260 A64
M260 B58  ;LED_13_ON_L  (led * 4 +6, led is in range  at 0..15) So we want led 13, 13*4+6 = 58
M260 B0
M260 S1

M260 A64
M260 B59 ;LED_13_ON_H (+1 from  LED_13_ON_L)
M260 B0
M260 S1

M260 A64
M260 B60 ;LED_13_OFF_L (+2 from  LED_13_ON_L)
M260 B255
M260 S1

M260 A64
M260 B61 ;LED_13_OFF_H (+3 from  LED_13_ON_L)
M260 B15
M260 S1

Each led takes two values On time and Off time, these can have values from 0..4095
If you have ever looked at how computers store numbers you know that a 8 bit number can only be 0..255, so to store 0..4096 you need two bytes, one is called L for low byte and the other H for high byte.

Calculating L and H values

Say you want to set 75% on,25%off (always has to add up to 100 %)
4095 * 0.75 = 3071, off value (remember its reversed, must be a whole number)
4095 - 3071 = 1024, on value

Easiest way to split into L and H is to convert it to Hexadecimal
3071 = BFF hex, you split this into two B and FF. B is the High value and FF is the Low value, then just convert it back to decimal, B = 11 and FF is 255
So you end up with H11 and L255
1024 = 400 hex so you end up with H4 and L0

full on is 4095 = FFFhex, F is high, FF is low, F = 15 and FF = 255, so you end up with Low = 255, High = 15

Edited 4 time(s). Last edit at 12/02/2018 09:05PM by Dust.
Re: Fan etc on external i2c-controller
December 09, 2018 04:33AM
Hi,

thank you very much for your detailed informations!

That works just perfect.

Now I would like to use it in Marlin. There is already a function called "Extruder Auto Cooling Fan" which I would like to use.

If Iam right, I have to define the pin like
#define E0_AUTO_FAN_PIN 100

So now I have to connect the "Pin 100" with the upper code you posted, to let Marlin switch the Extruder cooling Fan on and off.
And this has to be an if-else-combination to use the whole "switch-on" or "switch-off"-Code ... right?

But where should I integrate the custom code so that it doesn't slow down the printer?

best regards
Re: Fan etc on external i2c-controller
December 13, 2018 11:58PM
v0.5 (first steps)

adds virtual ports 100-115 (maps to i2c ports 0-15) to m42 gcode

eg m42 p100 s255

new file pca9685.h add to main marlin directory
/**
 * Driver for the Philips PCA9685 LED driver.
 * Written by Peter Ellens Dec 2018
   Based on PCA9632 Driver by Robert Mendon Feb 2017.
 */

#ifndef __PCA9685_H__
#define __PCA9685_H__

/* Register addresses */
#define PCA9685_MODE1_REG           (byte)0x00
#define PCA9685_MODE2_REG           (byte)0x01
#define PCA9685_SUBADR1_REG         (byte)0x02
#define PCA9685_SUBADR2_REG         (byte)0x03
#define PCA9685_SUBADR3_REG         (byte)0x04
#define PCA9685_ALLCALL_REG         (byte)0x05
#define PCA9685_LED0_REG            (byte)0x06 // Start of LEDx regs, 4B per reg, 2B on phase, 2B off phase, little-endian
#define PCA9685_PRESCALE_REG        (byte)0xFE
#define PCA9685_ALLLED_REG          (byte)0xFA

// Mode1 register pin layout
#define PCA9685_MODE_RESTART        (byte)0x80
#define PCA9685_MODE_EXTCLK         (byte)0x40
#define PCA9685_MODE_AUTOINC        (byte)0x20
#define PCA9685_MODE_SLEEP          (byte)0x10
#define PCA9685_MODE_SUBADR1        (byte)0x08
#define PCA9685_MODE_SUBADR2        (byte)0x04
#define PCA9685_MODE_SUBADR3        (byte)0x02
#define PCA9685_MODE_ALLCALL        (byte)0x01

// Mode2 register pin layout
#define PCA9685_MODE_INVRT          (byte)0x10
#define PCA9685_MODE_OCH            (byte)0x08
#define PCA9685_MODE_TOTEM_POLE     (byte)0x04
#define PCA9685_MODE_OUTNE1         (byte)0x02
#define PCA9685_MODE_OUTNE0         (byte)0x01

#define PCA9685_PWM_FULL            (uint16_t)0x01000 // Special value for full on/full off LEDx modes

void pca9685_set_port_PWM8(const uint8_t port, uint16_t pwm);
void pca9685_set_port_PWM16(const uint8_t port, uint16_t pwm);

#endif // __PCA9685_H__

new file pca9685.cpp add to main marlin directory
/**
 * Driver for the Philips PCA9685 LED driver.
 * Written by Peter Ellens Dec 2018
   Based on PCA9632 Driver by Robert Mendon Feb 2017.
 */

#include "MarlinConfig.h"

#ifdef PCA9685

#include "pca9685.h"
#include 

#define PCA9685_MODE1_VALUE   PCA9685_MODE_ALLCALL|PCA9685_MODE_AUTOINC
#define PCA9685_MODE2_VALUE   PCA9685_MODE_TOTEM_POLE


#define PCA9685_ADDRESS             (byte)0x40

byte PCA9685_init = 0;

static void PCA9685_WriteRegister(const byte addr, const byte regadd, const byte value) {
  Wire.beginTransmission(addr);
  Wire.write(regadd);
  Wire.write(value);
  Wire.endTransmission();
}

static void PCA9685_WriteAllRegisters(const byte addr, const byte regadd, const byte value1, const byte value2, const byte value3, const byte value4) {
  Wire.beginTransmission(addr);
  Wire.write(regadd);
  Wire.write(value1);
  Wire.write(value2);
  Wire.write(value3);
  Wire.write(value3);
  Wire.endTransmission();
}
void pca9685_set_port_PWM16(const uint8_t port, uint16_t pwm) {
  byte on_l=0;
  byte on_h=0;
  byte off_l=0;
  byte off_h=0;


  Wire.begin();
  if (!PCA9685_init) {

    PCA9685_init = 1;
    PCA9685_WriteRegister(PCA9685_ADDRESS,PCA9685_MODE1_REG, PCA9685_MODE1_VALUE);
    PCA9685_WriteRegister(PCA9685_ADDRESS,PCA9685_MODE2_REG, PCA9685_MODE2_VALUE);
  }

  if (pwm > PCA9685_PWM_FULL ) pwm = PCA9685_PWM_FULL;

  if (pwm == PCA9685_PWM_FULL ) on_h = (PCA9685_PWM_FULL&0x1F00)>>8;
  else if (pwm == 0) off_h = (PCA9685_PWM_FULL&0x1F00)>>8;
  else {
    off_h = (pwm&0xF00)>>8;
    off_l = pwm&0xFF;
  }

  PCA9685_WriteAllRegisters(PCA9685_ADDRESS,PCA9685_LED0_REG+(4*port), on_h, on_l, off_h, off_l);
}

void pca9685_set_port_PWM8(const uint8_t port, uint16_t pwm) {
  if (pwm >= 255 ) pwm = 256;
  pca9685_set_port_PWM16(port, pwm<<4);
}

#endif // PCA9685

add this to Configuration.h
// Support for PCA9685 PWM driver
#define PCA9685

add this near the top of Marlin_main.cpp after #include "parser.h"
#ifdef PCA9685
#include "pca9685.h"
#endif

Next find the M42 section of code in Marlin_main.cpp
inline void gcode_M42() {
  if (!parser.seenval('S')) return;
  const byte pin_status = parser.value_byte();

  const pin_t pin_number = parser.byteval('P', LED_PIN);
  if (pin_number < 0) return;

  if (!parser.boolval('I') && pin_is_protected(pin_number)) return protected_pin_err();

  pinMode(pin_number, OUTPUT);
  digitalWrite(pin_number, pin_status);
  analogWrite(pin_number, pin_status);


and change it to
inline void gcode_M42() {
  if (!parser.seenval('S')) return;
  const byte pin_status = parser.value_byte();

  const pin_t pin_number = parser.byteval('P', LED_PIN);
  if (pin_number < 0) return;

  if (!parser.boolval('I') && pin_is_protected(pin_number)) return protected_pin_err();

  #ifdef PCA9685
  if (pin_number < 100) {
  #endif

  pinMode(pin_number, OUTPUT);
  digitalWrite(pin_number, pin_status);
  analogWrite(pin_number, pin_status);

  #ifdef PCA9685
  }
  else {
    pca9685_set_port_PWM8(pin_number-100, pin_status);
  }
  #endif


no idea how controller does the auto fan yet.... but this makes it somewhat useful.

Edited 3 time(s). Last edit at 12/14/2018 12:50AM by Dust.
Re: Fan etc on external i2c-controller
April 30, 2020 12:30PM
I know this is a very old topic but did you manage to get this working on autofan?

I have a similar setup which I'm trying to get working [reprap.org]
Re: Fan etc on external i2c-controller
April 30, 2020 01:25PM
No, I moved on to other things.. (bug hunting in marlin)
Re: Fan etc on external i2c-controller
April 30, 2020 02:47PM
Fair enough, I will keep bashing away.

Thanks
Re: Fan etc on external i2c-controller
December 28, 2021 11:02PM
updated to marlin 2.0

diff --git a/Marlin/Configuration.h b/Marlin/Configuration.h
index c50af25e63..3316181893 100644
--- a/Marlin/Configuration.h
+++ b/Marlin/Configuration.h
@@ -2861,6 +2861,14 @@
 // Support for PCA9533 PWM LED driver
 //#define PCA9533
 
+// Support for PCA9685 PWM driver
+#define PCA9685
+#if ENABLED(PCA9685)
+  #define PCA9685_ADDRESS (byte)0x40 // I2C address
+  #define PCA9685_FREQUENCY      200 // PWM frequency 1-3500Hz , 200 is default
+  #define PCA9685_M43_START_PIN  100 // pins over this number are mapped onto PCA9685
+#endif
+
 /**
  * RGB LED / LED Strip Control
  *

diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h
index 6e44f009ed..05e092cc14 100644
--- a/Marlin/Configuration_adv.h
+++ b/Marlin/Configuration_adv.h
@@ -4181,7 +4181,7 @@
 //
 // M42 - Set pin states
 //
-//#define DIRECT_PIN_CONTROL
+#define DIRECT_PIN_CONTROL
 
 //
 // M43 - display pin status, toggle pins, watch pins, watch endstops & toggle LED, test servo probe

diff --git a/Marlin/src/feature/pca9685.cpp b/Marlin/src/feature/pca9685.cpp
new file mode 100644
index 0000000000..e6e8b68d2f
--- /dev/null
+++ b/Marlin/src/feature/pca9685.cpp
@@ -0,0 +1,161 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2021 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <[www.gnu.org];.
+ *
+ */
+
+/**
+ * Driver for the Philips PCA9685 PWM driver.
+ */
+
+#include "../inc/MarlinConfig.h"
+
+#if ENABLED(PCA9685)
+
+#include "pca9685.h"
+#include <Wire.h>
+
+#define PCA9685_MODE1_VALUE         PCA9685_MODE_ALLCALL|PCA9685_MODE_AUTOINC
+#define PCA9685_MODE2_VALUE         PCA9685_MODE_TOTEM_POLE
+#ifndef PCA9685_ADDRESS
+  #define PCA9685_ADDRESS           (byte)0x40
+#endif
+
+/* Register addresses */
+#define PCA9685_MODE1_REG           (byte)0x00
+#define PCA9685_MODE2_REG           (byte)0x01
+#define PCA9685_SUBADR1_REG         (byte)0x02
+#define PCA9685_SUBADR2_REG         (byte)0x03
+#define PCA9685_SUBADR3_REG         (byte)0x04
+#define PCA9685_ALLCALL_REG         (byte)0x05
+#define PCA9685_LED0_REG            (byte)0x06 // Start of PWMx regs, 4B per reg, 2B on phase, 2B off phase, little-endian
+#define PCA9685_PRESCALE_REG        (byte)0xFE
+#define PCA9685_ALLLED_REG          (byte)0xFA
+
+// Mode1 register pin layout
+#define PCA9685_MODE_RESTART        (byte)0x80
+#define PCA9685_MODE_EXTCLK         (byte)0x40
+#define PCA9685_MODE_AUTOINC        (byte)0x20
+#define PCA9685_MODE_SLEEP          (byte)0x10
+#define PCA9685_MODE_SUBADR1        (byte)0x08
+#define PCA9685_MODE_SUBADR2        (byte)0x04
+#define PCA9685_MODE_SUBADR3        (byte)0x02
+#define PCA9685_MODE_ALLCALL        (byte)0x01
+
+// Mode2 register pin layout
+#define PCA9685_MODE_INVRT          (byte)0x10
+#define PCA9685_MODE_OCH            (byte)0x08
+#define PCA9685_MODE_TOTEM_POLE     (byte)0x04
+#define PCA9685_MODE_OUTNE1         (byte)0x02
+#define PCA9685_MODE_OUTNE0         (byte)0x01
+
+#define PCA9685_PWM_FULL            (uint16_t)0x01000 // Special value for full on/full off LEDx modes
+
+#define FREQUENCY_OSCILLATOR         25000000  // Int. osc. frequency in datasheet
+#define PCA9685_PRESCALE_MIN                3  // minimum prescale value
+#define PCA9685_PRESCALE_MAX              255  // maximum prescale value
+
+byte PCA9685_init = 0;
+
+static void PCA9685_WriteRegister(const byte addr, const byte regadd, const byte value) {
+  Wire.beginTransmission(addr);
+  Wire.write(regadd);
+  Wire.write(value);
+  Wire.endTransmission();
+}
+
+static uint8_t PCA9685_ReadRegister(const byte addr, const byte regadd) {
+  Wire.beginTransmission(addr);
+  Wire.write(regadd);
+  Wire.requestFrom((uint8_t)addr,(uint8_t)1);
+  uint8_t value = Wire.read();
+  Wire.endTransmission();
+  return value;
+}
+
+static void PCA9685_WriteAllRegisters(const byte addr, const byte regadd, const byte value1, const byte value2, const byte value3, const byte value4) {
+  Wire.beginTransmission(addr);
+  Wire.write(regadd);
+  Wire.write(value1);
+  Wire.write(value2);
+  Wire.write(value3);
+  Wire.write(value3);
+  Wire.endTransmission();
+}
+
+/*!
+ *  @brief  Sets the PWM frequency for the entire chip, up to ~1.6 KHz
+ *  @param  freq Floating point frequency that we will attempt to match
+ */
+void PCA9685_setPWMFreq(float freq) {
+  // Range output modulation frequency is dependant on oscillator
+  if (freq < 1)
+    freq = 1;
+  if (freq > 3500)
+    freq = 3500; // Datasheet limit is 3052=50MHz/(4*4096)
+
+  float prescaleval = ((FREQUENCY_OSCILLATOR / (freq * 4096.0)) + 0.5) - 1;
+  if (prescaleval  PCA9685_PRESCALE_MIN)
+    prescaleval = PCA9685_PRESCALE_MIN;
+  if (prescaleval > PCA9685_PRESCALE_MAX)
+    prescaleval = PCA9685_PRESCALE_MAX;
+  uint8_t prescale = (uint8_t)prescaleval;
+
+  uint8_t oldmode = PCA9685_ReadRegister(PCA9685_ADDRESS, PCA9685_MODE1_REG);
+  uint8_t newmode = (oldmode & ~PCA9685_MODE_RESTART) | PCA9685_MODE_SLEEP; // sleep
+  PCA9685_WriteRegister(PCA9685_ADDRESS,PCA9685_MODE1_REG, newmode);
+  PCA9685_WriteRegister(PCA9685_ADDRESS,PCA9685_PRESCALE_REG, prescale);
+  PCA9685_WriteRegister(PCA9685_ADDRESS,PCA9685_MODE1_REG, oldmode);
+  safe_delay(5);
+  // This sets the MODE1 register to turn on auto increment.
+  PCA9685_WriteRegister(PCA9685_ADDRESS,PCA9685_MODE1_REG, oldmode | PCA9685_MODE_RESTART | PCA9685_MODE_AUTOINC);
+}
+
+void pca9685_set_port_PWM16(const uint8_t port, uint16_t pwm) {
+  byte on_l=0;
+  byte on_h=0;
+  byte off_l=0;
+  byte off_h=0;
+
+  Wire.begin();
+  if (!PCA9685_init) {
+    PCA9685_init = 1;
+    PCA9685_WriteRegister(PCA9685_ADDRESS,PCA9685_MODE1_REG, PCA9685_MODE1_VALUE);
+    PCA9685_WriteRegister(PCA9685_ADDRESS,PCA9685_MODE2_REG, PCA9685_MODE2_VALUE);
+    PCA9685_setPWMFreq(PCA9685_FREQUENCY);
+  }
+
+  if (pwm > PCA9685_PWM_FULL ) pwm = PCA9685_PWM_FULL;
+
+  if (pwm == PCA9685_PWM_FULL ) on_h = (PCA9685_PWM_FULL&0x1F00)>>8;
+  else if (pwm == 0) off_h = (PCA9685_PWM_FULL&0x1F00)>>8;
+  else {
+    off_h = (pwm&0xF00)>>8;
+    off_l = pwm&0xFF;
+  }
+
+  PCA9685_WriteAllRegisters(PCA9685_ADDRESS,PCA9685_LED0_REG+(4*port), on_h, on_l, off_h, off_l);
+}
+
+void pca9685_set_port_PWM8(const uint8_t port, uint16_t pwm) {
+  if (pwm >= 255 ) pwm = 256;
+  pca9685_set_port_PWM16(port, pwm<<4);
+}
+
+#endif // PCA9685

diff --git a/Marlin/src/feature/pca9685.h b/Marlin/src/feature/pca9685.h
new file mode 100644
index 0000000000..8815265ff5
--- /dev/null
+++ b/Marlin/src/feature/pca9685.h
@@ -0,0 +1,29 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2021 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <[www.gnu.org];.
+ *
+ */
+#pragma once
+
+/**
+ * Driver for the Philips PCA9685 PWM driver.
+ */
+
+void pca9685_set_port_PWM8(const uint8_t port, uint16_t pwm);
+void pca9685_set_port_PWM16(const uint8_t port, uint16_t pwm);

diff --git a/Marlin/src/gcode/control/M42.cpp b/Marlin/src/gcode/control/M42.cpp
index eead971a01..0c6a7aee12 100644
--- a/Marlin/src/gcode/control/M42.cpp
+++ b/Marlin/src/gcode/control/M42.cpp
@@ -27,6 +27,10 @@
 #include "../gcode.h"
 #include "../../MarlinCore.h" // for pin_is_protected
 
+#if ENABLED(PCA9685)
+  #include "../../feature/pca9685.h"
+#endif
+
 #if HAS_FAN
   #include "../../module/temperature.h"
 #endif
@@ -118,18 +122,30 @@ void GcodeSuite::M42() {
     return;
   }
 
-  // An OUTPUT_OPEN_DRAIN should not be changed to normal OUTPUT (STM32)
-  // Use M42 Px M1/5 S0/1 to set the output type and then set value
-  #ifndef OUTPUT_OPEN_DRAIN
-    pinMode(pin, OUTPUT);
+  #if ENABLED(PCA9685)
+    if (pin_index < PCA9685_M43_START_PIN) {
   #endif
-  extDigitalWrite(pin, pin_status);
 
-  #ifdef ARDUINO_ARCH_STM32
-    // A simple I/O will be set to 0 by analogWrite()
-    if (pin_status <= 1 && !PWM_PIN(pin)) return;
+    // An OUTPUT_OPEN_DRAIN should not be changed to normal OUTPUT (STM32)
+    // Use M42 Px M1/5 S0/1 to set the output type and then set value
+    #ifndef OUTPUT_OPEN_DRAIN
+      pinMode(pin, OUTPUT);
+    #endif
+    extDigitalWrite(pin, pin_status);
+
+    #ifdef ARDUINO_ARCH_STM32
+      // A simple I/O will be set to 0 by analogWrite()
+      if (pin_status <= 1 && !PWM_PIN(pin)) return;
+    #endif
+    analogWrite(pin, pin_status);
+
+  #if ENABLED(PCA9685)
+    }
+    else if (pin_index < PCA9685_M43_START_PIN+16) {
+      pca9685_set_port_PWM8(pin_index-PCA9685_M43_START_PIN, pin_status);
+    }
+    else { SERIAL_ECHO_START(); SERIAL_ECHOLNPGM(" M42 pin ",pin_index," (PCA9685) out of range."); }
   #endif
-  analogWrite(pin, pin_status);
 }
 
 #endif // DIRECT_PIN_CONTROL

diff --git a/ini/features.ini b/ini/features.ini
index f54b645f85..0ad554c597 100644
--- a/ini/features.ini
+++ b/ini/features.ini
@@ -124,6 +124,7 @@ BLINKM                                 = src_filter=<src/feature/leds/blinkm.cpp>
 HAS_COLOR_LEDS                         = src_filter=+<src/feature/leds/leds.cpp> +<src/gcode/feature/leds/M150.cpp>
 PCA9533                                = src_filter=+<src/feature/leds/pca9533.cpp>
 PCA9632                                = src_filter=+<src/feature/leds/pca9632.cpp>
+PCA9685                                = src_filter=+<src/feature/pca9685.cpp>
 PRINTER_EVENT_LEDS                     = src_filter=+<src/feature/leds/printer_event_leds.cpp>
 TEMP_STAT_LEDS                         = src_filter=+<src/feature/leds/tempstat.cpp>
 MAX7219_DEBUG                          = src_filter=+<src/feature/max7219.cpp> +<src/gcode/feature/leds/M7219.cpp>

Edited 3 time(s). Last edit at 12/28/2021 11:08PM by Dust.
Attachments:
open | download - out.diff (11 KB)
Sorry, only registered users may post in this forum.

Click here to login