/** \file \brief Manage heaters, including PID and PWM, ARM specific part. For test cases see the intro comment in heater.c. */ #if defined TEACUP_C_INCLUDE && defined __ARM_LPC1114__ #include "cmsis-lpc11xx.h" #include #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 48 MHz CPU clock): prescaler frequency prescaler frequency 0 188.2 kHz 4 37.6 kHz 1 91.1 kHz 5 31.4 kHz 2 62.7 kHz ... ... 3 47.0 kHz 65535 2.87 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 1.4 Hz...91.1 kHz, quadrupling it to 1020 moves the range to 0.7 Hz...46.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 3 Hz. */ #define PWM_SCALE 1020 /** \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 match register which changes PWM duty. __IO uint32_t* match; /// Pointer to the port for non-PWM pins. __IO uint32_t* masked_port; }; 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; #define PWM_TYPE(pwm, pin) (((pwm) >= HARDWARE_PWM) ? ((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->MR[pin ## _MATCH]) : \ &(pin ## _PORT->MASKED_ACCESS[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 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 LPC1114 are usable as following, capture pins not listed: pin timer/match func for PWM other uses PIO0_1 CT32B0_MAT2 0x2 --- PIO0_8 CT16B0_MAT0 0x2 MISO0 PIO0_9 CT16B0_MAT1 0x2 MOSI0 PIO0_10 CT16B0_MAT2 0x3 SCK0 (also on PIO0_6) PIO0_11 CT32B0_MAT3 0x3 AD0, Step timer PIO1_1 CT32B1_MAT0 0x3 AD2 PIO1_2 CT32B1_MAT1 0x3 AD3 PIO1_3 CT32B1_MAT2 0x3 AD4 PIO1_4 CT32B1_MAT3 0x2 AD5, PWM reset PIO1_6 CT32B0_MAT0 0x2 RXD, Step timer PIO1_7 CT32B0_MAT1 0x2 TXD, Step timer PIO1_9 CT16B1_MAT0 0x1 --- */ // 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 == LPC_TMR16B0) { \ LPC_SYSCON->SYSAHBCLKCTRL |= (1 << 7); /* Turn on CT16B0. */ \ } \ else if (pin ## _TIMER == LPC_TMR16B1) { \ LPC_SYSCON->SYSAHBCLKCTRL |= (1 << 8); /* Turn on CT16B1. */ \ } \ else if (pin ## _TIMER == LPC_TMR32B1) { \ LPC_SYSCON->SYSAHBCLKCTRL |= (1 << 10); /* Turn on CT32B1. */ \ } \ \ LPC_IOCON->pin ## _CMSIS = pin ## _PWM; /* Connect to timer. */ \ /*pin ## _TIMER->IR = 0; ( = reset value) No interrupts. */ \ pin ## _TIMER->TCR = (1 << 0); /* Enable counter. */ \ freq = F_CPU / PWM_SCALE / (pwm ? pwm : 1); /* Figure PWM freq. */ \ if (freq > 65535) \ freq = 65535; \ if (freq < 1) \ freq = 1; \ pin ## _TIMER->PR = freq - 1; /* Prescaler to freq. */ \ pin ## _TIMER->MCR = (1 << 10); /* Reset on Match 3. */ \ /* PWM_SCALE - 1, so match = 255 is full off. */ \ pin ## _TIMER->MR[3] = PWM_SCALE - 1; /* Match 3 at 254. */ \ pin ## _TIMER->MR[pin ## _MATCH] = /* Match pin = duty. */ \ invert ? 0 : PWM_SCALE; \ /*pin ## _TIMER->CCR = 0; ( = reset value) No pin capture. */ \ pin ## _TIMER->EMR |= ((1 << pin ## _MATCH) /* Connect to pin. */ \ | (0x03 << ((pin ## _MATCH * 2) + 4))); /* Toggle pin on match.*/ \ /*pin ## _TIMER->CTCR = 0; ( = reset value) Timer mode. */ \ pin ## _TIMER->PWMC |= ((1 << 3) /* 3 to PWM mode. */ \ | (1 << pin ## _MATCH)); /* Pin to PWM mode. */ \ } \ 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) { uint32_t pwm_value; // Remember, we scale, and the timer inverts already. pwm_value = (uint32_t)((heaters[index].max_value * value) * (PWM_SCALE / 255) / 256); if ( ! heaters[index].invert) pwm_value = PWM_SCALE - pwm_value; *heaters[index].match = pwm_value; if (DEBUG_PID && (debug_flags & DEBUG_PID)) sersendf_P(PSTR("PWM %su = %lu\n"), index, *heaters[index].match); } else { *(heaters[index].masked_port) = ((value >= HEATER_THRESHOLD && ! heaters[index].invert) || (value < HEATER_THRESHOLD && heaters[index].invert)) ? 0xFFFF : 0x0000; } if (value) power_on(); } } #endif /* defined TEACUP_C_INCLUDE && defined __ARM_LPC1114__ */