2018-06-13 19:08:26 -05:00
/**
* Marlin 3 D Printer Firmware
2020-02-03 08:00:57 -06:00
* Copyright ( c ) 2020 MarlinFirmware [ https : //github.com/MarlinFirmware/Marlin]
2018-06-13 19:08:26 -05:00
* Copyright ( c ) 2016 Bob Cousins bobcousins42 @ googlemail . com
*
* 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 < http : //www.gnu.org/licenses/>.
*/
2018-11-04 02:25:55 -06:00
# pragma once
2017-06-17 18:36:10 -05:00
2019-05-02 00:45:50 -05:00
# include "../shared/Marduino.h"
2018-08-14 03:28:52 -05:00
# include "../shared/HAL_SPI.h"
2019-09-02 19:49:58 -05:00
# include "fastio.h"
# include "watchdog.h"
# include "math.h"
2017-06-17 18:36:10 -05:00
2017-11-05 08:49:38 -06:00
# ifdef USBCON
2019-11-12 22:16:54 -06:00
# include <HardwareSerial.h>
2017-11-05 08:49:38 -06:00
# else
2019-07-08 23:42:29 -05:00
# define HardwareSerial_h // Hack to prevent HardwareSerial.h header inclusion
2017-11-05 08:49:38 -06:00
# include "MarlinSerial.h"
# endif
2019-07-08 23:42:29 -05:00
# include <stdint.h>
2019-05-02 00:45:50 -05:00
# include <util/delay.h>
# include <avr/eeprom.h>
# include <avr/pgmspace.h>
# include <avr/interrupt.h>
# include <avr/io.h>
2019-10-24 14:57:20 -05:00
# ifndef pgm_read_ptr
// Compatibility for avr-libc 1.8.0-4.1 included with Ubuntu for
// Windows Subsystem for Linux on Windows 10 as of 10/18/2019
2019-10-27 15:29:37 -05:00
# define pgm_read_ptr_far(address_long) (void*)__ELPM_word((uint32_t)(address_long))
2019-10-24 14:57:20 -05:00
# define pgm_read_ptr_near(address_short) (void*)__LPM_word((uint16_t)(address_short))
# define pgm_read_ptr(address_short) pgm_read_ptr_near(address_short)
# endif
2019-07-09 22:30:06 -05:00
// ------------------------
2017-06-17 18:36:10 -05:00
// Defines
2019-07-09 22:30:06 -05:00
// ------------------------
2017-06-17 18:36:10 -05:00
2020-05-05 17:55:35 -05:00
// AVR PROGMEM extension for sprintf_P
# define S_FMT "%S"
2017-06-17 18:36:10 -05:00
# ifndef CRITICAL_SECTION_START
2020-02-11 01:13:02 -06:00
# define CRITICAL_SECTION_START() unsigned char _sreg = SREG; cli()
# define CRITICAL_SECTION_END() SREG = _sreg
2017-06-17 18:36:10 -05:00
# endif
2018-06-01 19:02:22 -05:00
# define ISRS_ENABLED() TEST(SREG, SREG_I)
# define ENABLE_ISRS() sei()
# define DISABLE_ISRS() cli()
2017-06-17 18:36:10 -05:00
2019-07-09 22:30:06 -05:00
// ------------------------
2017-06-17 18:36:10 -05:00
// Types
2019-07-09 22:30:06 -05:00
// ------------------------
2017-06-17 18:36:10 -05:00
2017-11-05 19:31:07 -06:00
typedef uint16_t hal_timer_t ;
2017-06-17 18:36:10 -05:00
# define HAL_TIMER_TYPE_MAX 0xFFFF
2017-10-26 13:37:26 -05:00
typedef int8_t pin_t ;
2019-06-26 00:40:29 -05:00
# define SHARED_SERVOS HAS_SERVOS
2017-06-17 18:36:10 -05:00
# define HAL_SERVO_LIB Servo
2019-07-09 22:30:06 -05:00
// ------------------------
2017-06-17 18:36:10 -05:00
// Public Variables
2019-07-09 22:30:06 -05:00
// ------------------------
2017-06-17 18:36:10 -05:00
//extern uint8_t MCUSR;
2018-10-03 00:47:27 -05:00
// Serial ports
2017-11-05 08:49:38 -06:00
# ifdef USBCON
# if ENABLED(BLUETOOTH)
# define MYSERIAL0 bluetoothSerial
# else
# define MYSERIAL0 Serial
# endif
2018-10-03 00:47:27 -05:00
# define NUM_SERIAL 1
2017-11-05 08:49:38 -06:00
# else
2018-10-03 00:47:27 -05:00
# if !WITHIN(SERIAL_PORT, -1, 3)
2020-01-02 17:59:06 -06:00
# error "SERIAL_PORT must be from -1 to 3. Please update your configuration."
2018-10-03 00:47:27 -05:00
# endif
# define MYSERIAL0 customizedSerial1
# ifdef SERIAL_PORT_2
# if !WITHIN(SERIAL_PORT_2, -1, 3)
2020-01-02 17:59:06 -06:00
# error "SERIAL_PORT_2 must be from -1 to 3. Please update your configuration."
2018-10-03 00:47:27 -05:00
# elif SERIAL_PORT_2 == SERIAL_PORT
2020-01-02 17:59:06 -06:00
# error "SERIAL_PORT_2 must be different than SERIAL_PORT. Please update your configuration."
2018-10-03 00:47:27 -05:00
# endif
# define MYSERIAL1 customizedSerial2
2020-01-02 17:59:06 -06:00
# define NUM_SERIAL 2
2018-10-03 00:47:27 -05:00
# else
# define NUM_SERIAL 1
# endif
2017-11-05 08:49:38 -06:00
# endif
2020-01-03 21:00:44 -06:00
# ifdef DGUS_SERIAL_PORT
# if !WITHIN(DGUS_SERIAL_PORT, -1, 3)
# error "DGUS_SERIAL_PORT must be from -1 to 3. Please update your configuration."
# elif DGUS_SERIAL_PORT == SERIAL_PORT
# error "DGUS_SERIAL_PORT must be different than SERIAL_PORT. Please update your configuration."
# elif defined(SERIAL_PORT_2) && DGUS_SERIAL_PORT == SERIAL_PORT_2
# error "DGUS_SERIAL_PORT must be different than SERIAL_PORT_2. Please update your configuration."
# endif
# define DGUS_SERIAL internalDgusSerial
# define DGUS_SERIAL_GET_TX_BUFFER_FREE DGUS_SERIAL.get_tx_buffer_free
# endif
2019-07-09 22:30:06 -05:00
// ------------------------
2017-06-17 18:36:10 -05:00
// Public functions
2019-07-09 22:30:06 -05:00
// ------------------------
2017-06-17 18:36:10 -05:00
2019-09-16 20:31:08 -05:00
void HAL_init ( ) ;
2019-06-27 16:29:17 -05:00
2019-09-16 20:31:08 -05:00
//void cli();
2017-06-17 18:36:10 -05:00
2017-09-27 04:57:33 -05:00
//void _delay_ms(const int delay);
2017-06-17 18:36:10 -05:00
2019-09-16 20:31:08 -05:00
inline void HAL_clear_reset_source ( ) { MCUSR = 0 ; }
inline uint8_t HAL_get_reset_source ( ) { return MCUSR ; }
2017-06-17 18:36:10 -05:00
2019-08-06 04:46:30 -05:00
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wunused-function"
2017-06-17 18:36:10 -05:00
extern " C " {
2019-09-16 20:31:08 -05:00
int freeMemory ( ) ;
2017-06-17 18:36:10 -05:00
}
2019-08-06 04:46:30 -05:00
# pragma GCC diagnostic pop
2017-06-17 18:36:10 -05:00
// timers
2017-10-07 13:34:25 -05:00
# define HAL_TIMER_RATE ((F_CPU) / 8) // i.e., 2MHz or 2.5MHz
2017-12-09 19:57:25 -06:00
2018-05-13 16:48:02 -05:00
# define STEP_TIMER_NUM 1
# define TEMP_TIMER_NUM 0
2018-06-03 01:43:00 -05:00
# define PULSE_TIMER_NUM STEP_TIMER_NUM
2017-12-09 19:57:25 -06:00
2018-05-13 16:48:02 -05:00
# define TEMP_TIMER_FREQUENCY ((F_CPU) / 64.0 / 256.0)
2018-06-11 23:04:26 -05:00
# define STEPPER_TIMER_RATE HAL_TIMER_RATE
# define STEPPER_TIMER_PRESCALE 8
# define STEPPER_TIMER_TICKS_PER_US ((STEPPER_TIMER_RATE) / 1000000) // Cannot be of type double
2017-12-09 19:57:25 -06:00
2018-06-11 23:04:26 -05:00
# define PULSE_TIMER_RATE STEPPER_TIMER_RATE // frequency of pulse timer
# define PULSE_TIMER_PRESCALE STEPPER_TIMER_PRESCALE
# define PULSE_TIMER_TICKS_PER_US STEPPER_TIMER_TICKS_PER_US
2017-12-09 19:59:12 -06:00
2017-06-17 18:36:10 -05:00
# define ENABLE_STEPPER_DRIVER_INTERRUPT() SBI(TIMSK1, OCIE1A)
# define DISABLE_STEPPER_DRIVER_INTERRUPT() CBI(TIMSK1, OCIE1A)
2018-04-23 22:05:07 -05:00
# define STEPPER_ISR_ENABLED() TEST(TIMSK1, OCIE1A)
2017-06-17 18:36:10 -05:00
2018-04-23 22:05:07 -05:00
# define ENABLE_TEMPERATURE_INTERRUPT() SBI(TIMSK0, OCIE0B)
# define DISABLE_TEMPERATURE_INTERRUPT() CBI(TIMSK0, OCIE0B)
# define TEMPERATURE_ISR_ENABLED() TEST(TIMSK0, OCIE0B)
2017-06-17 18:36:10 -05:00
2019-09-30 21:44:07 -05:00
FORCE_INLINE void HAL_timer_start ( const uint8_t timer_num , const uint32_t ) {
2018-06-03 01:43:00 -05:00
switch ( timer_num ) {
case STEP_TIMER_NUM :
// waveform generation = 0100 = CTC
SET_WGM ( 1 , CTC_OCRnA ) ;
// output mode = 00 (disconnected)
SET_COMA ( 1 , NORMAL ) ;
// Set the timer pre-scaler
// Generally we use a divider of 8, resulting in a 2MHz timer
// frequency on a 16MHz MCU. If you are going to change this, be
// sure to regenerate speed_lookuptable.h with
// create_speed_lookuptable.py
SET_CS ( 1 , PRESCALER_8 ) ; // CS 2 = 1/8 prescaler
// Init Stepper ISR to 122 Hz for quick starting
// (F_CPU) / (STEPPER_TIMER_PRESCALE) / frequency
OCR1A = 0x4000 ;
TCNT1 = 0 ;
break ;
case TEMP_TIMER_NUM :
// Use timer0 for temperature measurement
// Interleave temperature interrupt with millies interrupt
OCR0B = 128 ;
break ;
}
}
2017-06-17 18:36:10 -05:00
2018-06-11 23:04:26 -05:00
# define TIMER_OCR_1 OCR1A
# define TIMER_COUNTER_1 TCNT1
# define TIMER_OCR_0 OCR0A
# define TIMER_COUNTER_0 TCNT0
2019-03-16 23:43:06 -05:00
# define _CAT(a,V...) a##V
2018-02-10 20:42:00 -06:00
# define HAL_timer_set_compare(timer, compare) (_CAT(TIMER_OCR_, timer) = compare)
# define HAL_timer_get_compare(timer) _CAT(TIMER_OCR_, timer)
# define HAL_timer_get_count(timer) _CAT(TIMER_COUNTER_, timer)
2017-06-17 18:36:10 -05:00
2018-04-23 22:05:07 -05:00
/**
* On AVR there is no hardware prioritization and preemption of
* interrupts , so this emulates it . The UART has first priority
* ( otherwise , characters will be lost due to UART overflow ) .
* Then : Stepper , Endstops , Temperature , and - finally - all others .
*/
2018-05-13 14:46:08 -05:00
# define HAL_timer_isr_prologue(TIMER_NUM)
# define HAL_timer_isr_epilogue(TIMER_NUM)
/* 18 cycles maximum latency */
2019-03-10 20:43:59 -05:00
# define HAL_STEP_TIMER_ISR() \
2019-09-16 20:31:08 -05:00
extern " C " void TIMER1_COMPA_vect ( ) __attribute__ ( ( signal , naked , used , externally_visible ) ) ; \
extern " C " void TIMER1_COMPA_vect_bottom ( ) asm ( " TIMER1_COMPA_vect_bottom " ) __attribute__ ( ( used , externally_visible , noinline ) ) ; \
void TIMER1_COMPA_vect ( ) { \
2018-05-13 14:46:08 -05:00
__asm__ __volatile__ ( \
A ( " push r16 " ) /* 2 Save R16 */ \
A ( " in r16, __SREG__ " ) /* 1 Get SREG */ \
A ( " push r16 " ) /* 2 Save SREG into stack */ \
A ( " lds r16, %[timsk0] " ) /* 2 Load into R0 the Temperature timer Interrupt mask register */ \
A ( " push r16 " ) /* 2 Save TIMSK0 into the stack */ \
A ( " andi r16,~%[msk0] " ) /* 1 Disable the temperature ISR */ \
A ( " sts %[timsk0], r16 " ) /* 2 And set the new value */ \
A ( " lds r16, %[timsk1] " ) /* 2 Load into R0 the stepper timer Interrupt mask register [TIMSK1] */ \
A ( " andi r16,~%[msk1] " ) /* 1 Disable the stepper ISR */ \
A ( " sts %[timsk1], r16 " ) /* 2 And set the new value */ \
A ( " push r16 " ) /* 2 Save TIMSK1 into stack */ \
A ( " in r16, 0x3B " ) /* 1 Get RAMPZ register */ \
A ( " push r16 " ) /* 2 Save RAMPZ into stack */ \
A ( " in r16, 0x3C " ) /* 1 Get EIND register */ \
A ( " push r0 " ) /* C runtime can modify all the following registers without restoring them */ \
A ( " push r1 " ) \
A ( " push r18 " ) \
A ( " push r19 " ) \
A ( " push r20 " ) \
A ( " push r21 " ) \
A ( " push r22 " ) \
A ( " push r23 " ) \
A ( " push r24 " ) \
A ( " push r25 " ) \
A ( " push r26 " ) \
A ( " push r27 " ) \
A ( " push r30 " ) \
A ( " push r31 " ) \
A ( " clr r1 " ) /* C runtime expects this register to be 0 */ \
A ( " call TIMER1_COMPA_vect_bottom " ) /* Call the bottom handler - No inlining allowed, otherwise registers used are not saved */ \
A ( " pop r31 " ) \
A ( " pop r30 " ) \
A ( " pop r27 " ) \
A ( " pop r26 " ) \
A ( " pop r25 " ) \
A ( " pop r24 " ) \
A ( " pop r23 " ) \
A ( " pop r22 " ) \
A ( " pop r21 " ) \
A ( " pop r20 " ) \
A ( " pop r19 " ) \
A ( " pop r18 " ) \
A ( " pop r1 " ) \
A ( " pop r0 " ) \
A ( " out 0x3C, r16 " ) /* 1 Restore EIND register */ \
A ( " pop r16 " ) /* 2 Get the original RAMPZ register value */ \
A ( " out 0x3B, r16 " ) /* 1 Restore RAMPZ register to its original value */ \
A ( " pop r16 " ) /* 2 Get the original TIMSK1 value but with stepper ISR disabled */ \
A ( " ori r16,%[msk1] " ) /* 1 Reenable the stepper ISR */ \
A ( " cli " ) /* 1 Disable global interrupts - Reenabling Stepper ISR can reenter amd temperature can reenter, and we want that, if it happens, after this ISR has ended */ \
A ( " sts %[timsk1], r16 " ) /* 2 And restore the old value - This reenables the stepper ISR */ \
A ( " pop r16 " ) /* 2 Get the temperature timer Interrupt mask register [TIMSK0] */ \
A ( " sts %[timsk0], r16 " ) /* 2 And restore the old value - This reenables the temperature ISR */ \
A ( " pop r16 " ) /* 2 Get the old SREG value */ \
A ( " out __SREG__, r16 " ) /* 1 And restore the SREG value */ \
A ( " pop r16 " ) /* 2 Restore R16 value */ \
A ( " reti " ) /* 4 Return from interrupt */ \
: \
: [ timsk0 ] " i " ( ( uint16_t ) & TIMSK0 ) , \
[ timsk1 ] " i " ( ( uint16_t ) & TIMSK1 ) , \
[ msk0 ] " M " ( ( uint8_t ) ( 1 < < OCIE0B ) ) , \
[ msk1 ] " M " ( ( uint8_t ) ( 1 < < OCIE1A ) ) \
: \
) ; \
} \
2019-09-16 20:31:08 -05:00
void TIMER1_COMPA_vect_bottom ( )
2018-05-13 14:46:08 -05:00
/* 14 cycles maximum latency */
2019-03-10 20:43:59 -05:00
# define HAL_TEMP_TIMER_ISR() \
2019-09-16 20:31:08 -05:00
extern " C " void TIMER0_COMPB_vect ( ) __attribute__ ( ( signal , naked , used , externally_visible ) ) ; \
extern " C " void TIMER0_COMPB_vect_bottom ( ) asm ( " TIMER0_COMPB_vect_bottom " ) __attribute__ ( ( used , externally_visible , noinline ) ) ; \
void TIMER0_COMPB_vect ( ) { \
2018-05-13 14:46:08 -05:00
__asm__ __volatile__ ( \
A ( " push r16 " ) /* 2 Save R16 */ \
A ( " in r16, __SREG__ " ) /* 1 Get SREG */ \
A ( " push r16 " ) /* 2 Save SREG into stack */ \
A ( " lds r16, %[timsk0] " ) /* 2 Load into R0 the Temperature timer Interrupt mask register */ \
A ( " andi r16,~%[msk0] " ) /* 1 Disable the temperature ISR */ \
A ( " sts %[timsk0], r16 " ) /* 2 And set the new value */ \
A ( " sei " ) /* 1 Enable global interrupts - It is safe, as the temperature ISR is disabled, so we cannot reenter it */ \
A ( " push r16 " ) /* 2 Save TIMSK0 into stack */ \
A ( " in r16, 0x3B " ) /* 1 Get RAMPZ register */ \
A ( " push r16 " ) /* 2 Save RAMPZ into stack */ \
A ( " in r16, 0x3C " ) /* 1 Get EIND register */ \
A ( " push r0 " ) /* C runtime can modify all the following registers without restoring them */ \
A ( " push r1 " ) \
A ( " push r18 " ) \
A ( " push r19 " ) \
A ( " push r20 " ) \
A ( " push r21 " ) \
A ( " push r22 " ) \
A ( " push r23 " ) \
A ( " push r24 " ) \
A ( " push r25 " ) \
A ( " push r26 " ) \
A ( " push r27 " ) \
A ( " push r30 " ) \
A ( " push r31 " ) \
A ( " clr r1 " ) /* C runtime expects this register to be 0 */ \
A ( " call TIMER0_COMPB_vect_bottom " ) /* Call the bottom handler - No inlining allowed, otherwise registers used are not saved */ \
A ( " pop r31 " ) \
A ( " pop r30 " ) \
A ( " pop r27 " ) \
A ( " pop r26 " ) \
A ( " pop r25 " ) \
A ( " pop r24 " ) \
A ( " pop r23 " ) \
A ( " pop r22 " ) \
A ( " pop r21 " ) \
A ( " pop r20 " ) \
A ( " pop r19 " ) \
A ( " pop r18 " ) \
A ( " pop r1 " ) \
A ( " pop r0 " ) \
A ( " out 0x3C, r16 " ) /* 1 Restore EIND register */ \
A ( " pop r16 " ) /* 2 Get the original RAMPZ register value */ \
A ( " out 0x3B, r16 " ) /* 1 Restore RAMPZ register to its original value */ \
A ( " pop r16 " ) /* 2 Get the original TIMSK0 value but with temperature ISR disabled */ \
A ( " ori r16,%[msk0] " ) /* 1 Enable temperature ISR */ \
2018-06-01 19:02:22 -05:00
A ( " cli " ) /* 1 Disable global interrupts - We must do this, as we will reenable the temperature ISR, and we don't want to reenter this handler until the current one is done */ \
2018-05-13 14:46:08 -05:00
A ( " sts %[timsk0], r16 " ) /* 2 And restore the old value */ \
A ( " pop r16 " ) /* 2 Get the old SREG */ \
A ( " out __SREG__, r16 " ) /* 1 And restore the SREG value */ \
A ( " pop r16 " ) /* 2 Restore R16 */ \
A ( " reti " ) /* 4 Return from interrupt */ \
: \
: [ timsk0 ] " i " ( ( uint16_t ) & TIMSK0 ) , \
[ msk0 ] " M " ( ( uint8_t ) ( 1 < < OCIE0B ) ) \
: \
) ; \
} \
2019-09-16 20:31:08 -05:00
void TIMER0_COMPB_vect_bottom ( )
2017-06-17 18:36:10 -05:00
// ADC
# ifdef DIDR2
2020-01-17 02:39:22 -06:00
# define HAL_ANALOG_SELECT(ind) do{ if (ind < 8) SBI(DIDR0, ind); else SBI(DIDR2, ind & 0x07); }while(0)
2017-06-17 18:36:10 -05:00
# else
2020-01-17 02:39:22 -06:00
# define HAL_ANALOG_SELECT(ind) SBI(DIDR0, ind);
2017-06-17 18:36:10 -05:00
# endif
2019-09-16 20:31:08 -05:00
inline void HAL_adc_init ( ) {
2017-06-17 18:36:10 -05:00
ADCSRA = _BV ( ADEN ) | _BV ( ADSC ) | _BV ( ADIF ) | 0x07 ;
DIDR0 = 0 ;
# ifdef DIDR2
DIDR2 = 0 ;
# endif
}
2020-01-17 02:39:22 -06:00
# define SET_ADMUX_ADCSRA(ch) ADMUX = _BV(REFS0) | (ch & 0x07); SBI(ADCSRA, ADSC)
2017-06-17 18:36:10 -05:00
# ifdef MUX5
2020-01-17 02:39:22 -06:00
# define HAL_START_ADC(ch) if (ch > 7) ADCSRB = _BV(MUX5); else ADCSRB = 0; SET_ADMUX_ADCSRA(ch)
2017-06-17 18:36:10 -05:00
# else
2020-01-17 02:39:22 -06:00
# define HAL_START_ADC(ch) ADCSRB = 0; SET_ADMUX_ADCSRA(ch)
2017-06-17 18:36:10 -05:00
# endif
2019-11-06 17:49:17 -06:00
# define HAL_ADC_RESOLUTION 10
2018-07-26 03:59:19 -05:00
# define HAL_READ_ADC() ADC
# define HAL_ADC_READY() !TEST(ADCSRA, ADSC)
2017-06-17 18:36:10 -05:00
2017-10-26 13:37:26 -05:00
# define GET_PIN_MAP_PIN(index) index
# define GET_PIN_MAP_INDEX(pin) pin
# define PARSED_PIN_INDEX(code, dval) parser.intval(code, dval)
# define HAL_SENSITIVE_PINS 0, 1
2018-09-27 18:02:50 -05:00
# ifdef __AVR_AT90USB1286__
# define JTAG_DISABLE() do{ MCUCR = 0x80; MCUCR = 0x80; }while(0)
# endif
2018-07-01 15:20:28 -05:00
// AVR compatibility
# define strtof strtod
2019-03-26 01:03:23 -05:00
2020-04-02 19:31:08 -05:00
# define HAL_CAN_SET_PWM_FREQ // This HAL supports PWM Frequency adjustment
2019-03-26 01:03:23 -05:00
/**
* set_pwm_frequency
* Sets the frequency of the timer corresponding to the provided pin
* as close as possible to the provided desired frequency . Internally
* calculates the required waveform generation mode , prescaler and
* resolution values required and sets the timer registers accordingly .
* NOTE that the frequency is applied to all pins on the timer ( Ex OC3A , OC3B and OC3B )
* NOTE that there are limitations , particularly if using TIMER2 . ( see Configuration_adv . h - > FAST FAN PWM Settings )
*/
void set_pwm_frequency ( const pin_t pin , int f_desired ) ;
/**
* set_pwm_duty
* Sets the PWM duty cycle of the provided pin to the provided value
* Optionally allows inverting the duty cycle [ default = false ]
* Optionally allows changing the maximum size of the provided value to enable finer PWM duty control [ default = 255 ]
*/
void set_pwm_duty ( const pin_t pin , const uint16_t v , const uint16_t v_size = 255 , const bool invert = false ) ;