/** \file \brief Manage heaters, including PID and PWM, ARM specific part. */ #if defined TEACUP_C_INCLUDE && defined __ARM_STM32F411__ #include "cmsis-stm32f4xx.h" #include "pinio.h" #include "sersendf.h" #include "debug.h" /** Test configuration. */ #ifdef EECONFIG #error EEPROM handling (EECONFIG) not yet supported on ARM. #endif #ifdef BANG_BANG #error BANG_BANG not supported on ARM. You may set PWM frequency of one \ or all heater(s) to zero, which gives similar, better behaviour. #endif /** \def PWM_SCALE G-code standard (if such a thing exists at all) gives a heater setting range between 0 (off) and 255 (full on), so we let the PWM timers count up to 255. Doing so allows to set the prescaler for these frequencies (all on a 96 MHz CPU clock): prescaler frequency prescaler frequency 0 367.5 kHz 4 75.3 kHz 1 188.2 kHz 5 62.7 kHz 2 125.5 kHz ... ... 3 94.1 kHz 65535 5.74 Hz As one can see, frequency steps are rather coarse on the high end and become finer grained the lower it gets. If this range is generally too high for your purposes, you can set PWM_SCALE to multiples of 255 to lower the range. Doubling it to 510 moves the frequency range to 2.9 Hz...183.8 kHz, quadrupling it to 1020 moves the range to 1.5 Hz...91.9 kHz and so on. The highest allowed number is 65535. That said, code below calculates the best prescaler value for a configured frequency, so you should bother about PWM_SCALE only of you need frequencies below 6 Hz. */ #define PWM_SCALE 1020 // some helper macros #define _EXPANDER(pre, val, post) pre ## val ## post #define EXPANDER(pre, val, post) _EXPANDER(pre, val, post) /** \struct heater_definition_t Holds pinout data to allow changing PWM output after initialisation. Port, pin, PWM channel if used. After inititalisation we can no longer do the #include "config_wrapper.h" trick. */ typedef struct { union { /// Pointer to the capture compare register which changes PWM duty. __IO uint32_t* ccr; /// Pointer to the port for non-PWM pins. __IO uint32_t* bsrr; }; uint16_t masked_pin; uint16_t max_value; ///< max value for the heater, for PWM in percent * 256 pwm_type_t pwm_type; ///< saves the pwm-type: NO_PWM, SOFTWARE_PWM, HARDWARE_PWM uint8_t invert; ///< Wether the heater pin signal needs to be inverted. } heater_definition_t; // When pwm >= 2 it's hardware pwm, if the pin has hardware pwm. // When pwm == 1 it's software pwm. // pwm == 0 is no pwm at all. // Use this macro only in DEFINE_HEATER_ACTUAL-macros. #define PWM_TYPE(pwm, pin) (((pwm) >= HARDWARE_PWM_START) ? ((pin ## _TIMER) ? HARDWARE_PWM : SOFTWARE_PWM) : (pwm)) #undef DEFINE_HEATER_ACTUAL #define DEFINE_HEATER_ACTUAL(name, pin, invert, pwm, max_value) \ { \ { (PWM_TYPE(pwm, pin) == HARDWARE_PWM) ? \ &(pin ## _TIMER-> EXPANDER(CCR, pin ## _CHANNEL,)) : \ &(pin ## _PORT->BSRR) }, \ MASK(pin ## _PIN), \ (PWM_TYPE(pwm, pin) != SOFTWARE_PWM) ? \ ((max_value * 64 + 12) / 25) : \ (uint16_t)(255UL * 100 / max_value), \ PWM_TYPE(pwm, pin), \ invert ? 1 : 0 \ }, static const heater_definition_t heaters[NUM_HEATERS] = { #include "config_wrapper.h" }; #undef DEFINE_HEATER_ACTUAL // We test any heater if we need software-pwm #define DEFINE_HEATER_ACTUAL(name, pin, invert, pwm, ...) \ | (PWM_TYPE(pwm, pin) == SOFTWARE_PWM) static const uint8_t software_pwm_needed = 0 #include "config_wrapper.h" ; #undef DEFINE_HEATER_ACTUAL /** Initialise heater subsystem. Initialise PWM timers, etc. Inspired by heater-arm_lpc11xx.c (pwm.c in LPC1343CodeBase): https://github.com/microbuilder/LPC1343CodeBase Note that PWM is inversed, pins start at Low by chip design. When the pin's counter is reached, they're set to High. Reaching the timer reset counter is programmed to reset everything and start over. Thus, having both counter matches similar gives a low duty, having the pin counter match zero gives full on. For simplicity we reset all timer counters always on Match 3 and always at PWM_SCALE (255 per default), so setting a pin match to PWM_SCALE / 2 gives 50% duty, setting it to PWM_SCALE gives full off. This choice disallows using a pin connected to a Match 3, working around this would make code much more complicated (and still not allow to use more than 3 pins per timer). On ARM we can define a PWM frequency pretty fine grained, so we take the 'pwm' value of DEFINE_HEATER() not only wether to use PWM at all, but also to define the PWM frequency. Float values are allowed. If there's more than one pin on a timer, they share the same PWM frequency; the frequency choosen is the one of the pin defined last. */ void heater_init() { /** Pins on the STM32F411RE are usable as following, N are negated pin (active low) some pins are commented out (-) because they are shared. You can change this in arduino_stm32f4xx. But take care! You could pwm two pins simultanious or disable other important functions (serial connection). PWM5 = TIM5 = Stepper timer. pin timer/channel alt func for PWM other uses PIOA_0 PWM2/1, 5/1 01, 02 AD0 PIOA_1 PWM2/2, 5/2 01, 02 MOSI4, AD1 - PIOA_2 PWM2/3, 5/3, 9/1 01, 02, 03 TX2, AD2, UART! - PIOA_3 PWM2/4, 5/4, 9/2 01, 02, 03 RX2, AD3, UART! - PIOA_5 PWM2/1 01 LED1, SCK1, AD5 - PIOA_6 PWM3/1 02 MISO1, AD6 - PIOA_7 PWM1/1N, 3/2 01, 02 MOSI1, AD7 PIOA_8 PWM1/1 01 SCL1 PIOA_9 PWM1/2 01 TX1 PIOA_10 PWM1/3 01 MOSI5, RX1 PIOA_11 PWM1/4 01 TX6, MISO4 - PIOA_15 PWM2/1 01 NSS1, TX1 PIOB_0 PWM1/2N, 3/3 01, 02 SCK5, CK5, AD8 PIOB_1 PWM1/3N, 3/4 01, 02 NSS4, WS5, AD9 - PIOB_3 PWM2/2 01 SDA2, SCK3 PIOB_4 PWM3/1 02 SDA3, MISO3 PIOB_5 PWM3/2 02 MOSI3 PIOB_6 PWM4/1 02 SCL1, TX1 PIOB_7 PWM4/2 02 SDA1, RX1 PIOB_8 PWM4/3, 10/1 02, 03 SCL1, MOSI5 PIOB_9 PWM4/4, 11/1 02, 03 SDA1, NSS2 PIOB_10 PWM2/3 01 SCL3 - PIOB_13 PWM1/1N 01 SCK2 - PIOB_14 PWM1/2N 01 MISO2 - PIOB_15 PWM1/3N 01 MOSI2 - PIOC_6 PWM3/1 02 MCK2, TX6 - PIOC_7 PWM3/2 02 SCK2, RX6 - PIOC_8 PWM3/3 02 SDA3 - PIOC_9 PWM3/4 02 SDA3 */ // Auto-generate pin setup. #undef DEFINE_HEATER_ACTUAL #define DEFINE_HEATER_ACTUAL(name, pin, invert, pwm, ...) \ if (PWM_TYPE(pwm, pin) == HARDWARE_PWM) { \ uint32_t freq; \ if (pin ## _TIMER == TIM1) { \ RCC->APB2ENR |= RCC_APB2ENR_TIM1EN; } /* turn on TIM1 */ \ else if (pin ## _TIMER == TIM2) { \ RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; } /* turn on TIM2 */ \ else if (pin ## _TIMER == TIM3) { \ RCC->APB1ENR |= RCC_APB1ENR_TIM3EN; } /* turn on TIM3 */ \ else if (pin ## _TIMER == TIM4) { \ RCC->APB1ENR |= RCC_APB1ENR_TIM4EN; } /* turn on TIM4 */ \ /* TIM5 is for stepper, TIM9, TIM10 and TIM11 are not used */ \ SET_MODE(pin, 0x2); /* pin mode to AF */ \ SET_AFR(pin, pin ## _AF); \ SET_OSPEED(pin, 0x3); /* high speed */ \ PULL_OFF(pin); /* no pullup/-down */ \ pin ## _TIMER->CR1 |= TIM_CR1_ARPE; /* auto-reload preload */ \ pin ## _TIMER->ARR = PWM_SCALE - 1; /* reset on auto reload at 254 */ \ /* PWM_SCALE - 1, so CCR = 255 is full off. */ \ pin ## _TIMER-> EXPANDER(CCR, pin ## _CHANNEL,) = 0; /* start off */ \ freq = F_CPU / PWM_SCALE / (pwm ? pwm : 1); /* Figure PWM freq. */ \ if (freq > 65535) \ freq = 65535; \ if (freq < 1) \ freq = 1; \ pin ## _TIMER->PSC = freq - 1; /* 1kHz */ \ if (pin ## _CHANNEL <= 2) \ pin ## _TIMER->CCMR1 |= 0x68UL << (8 * (pin ## _CHANNEL && 2)); \ else \ pin ## _TIMER->CCMR2 |= 0x68UL << (8 * (pin ## _CHANNEL && 4)); \ \ pin ## _TIMER->CCER |= EXPANDER(TIM_CCER_CC, pin ## _CHANNEL, E); \ /* output enable */ \ if (pin ## _INVERT ^ invert) \ pin ## _TIMER->CCER |= EXPANDER(TIM_CCER_CC, pin ## _CHANNEL, P); \ else \ pin ## _TIMER->CCER &= ~(EXPANDER(TIM_CCER_CC, pin ## _CHANNEL, P)); \ \ /* invert the signal for negated timers*/ \ /* also with a XOR for inverted heaters */ \ pin ## _TIMER->EGR |= TIM_EGR_UG; /* update generation */ \ pin ## _TIMER->CR1 |= TIM_CR1_CEN; /* enable counters */ \ } \ else { \ SET_OUTPUT(pin); \ WRITE(pin, invert ? 1 : 0); \ } #include "config_wrapper.h" #undef DEFINE_HEATER_ACTUAL pid_init(); } /** Set PWM output. \param index The heater we're setting the output for. \param value The PWM value to write, range 0 (off) to 255 (full on). This function is called by M106 or, if a temp sensor is connected to the heater, every few milliseconds by its PID handler. Using M106 on an output with a sensor changes its setting only for a short moment. */ void do_heater(heater_t index, uint8_t value) { if (index < NUM_HEATERS) { if (heaters[index].pwm_type == HARDWARE_PWM) { // Remember, we scale, and duty cycle is inverted. *heaters[index].ccr = (uint32_t)((heaters[index].max_value * value) * (PWM_SCALE / 255) / 256); if (DEBUG_PID && (debug_flags & DEBUG_PID)) sersendf_P(PSTR("PWM %su = %lu\n"), index, *heaters[index].ccr); } else { *(heaters[index].bsrr) = heaters[index].masked_pin << ((value >= HEATER_THRESHOLD && ! heaters[index].invert) ? 0 : 16); } if (value) power_on(); } } #endif /* defined TEACUP_C_INCLUDE && defined __ARM_STM32F411__ */