diff --git a/code/espurna/config/general.h b/code/espurna/config/general.h index 90d571f7..c35b52b2 100644 --- a/code/espurna/config/general.h +++ b/code/espurna/config/general.h @@ -498,9 +498,17 @@ PROGMEM const char* const custom_reset_string[] = { #endif #ifndef LIGHT_MAX_PWM -#define LIGHT_MAX_PWM 4095 // Maximum PWM value + +#if LIGHT_PROVIDER == LIGHT_PROVIDER_MY9192 +#define LIGHT_MAX_PWM 256 +#endif + +#if LIGHT_PROVIDER == LIGHT_PROVIDER_DIMMER +#define LIGHT_MAX_PWM 1000 #endif +#endif // LIGHT_MAX_PWM + #ifndef LIGHT_LIMIT_PWM #define LIGHT_LIMIT_PWM LIGHT_MAX_PWM // Limit PWM to this value (prevent 100% power) #endif diff --git a/code/espurna/config/hardware.h b/code/espurna/config/hardware.h index 5892a1b0..07431853 100644 --- a/code/espurna/config/hardware.h +++ b/code/espurna/config/hardware.h @@ -465,6 +465,7 @@ #define LED1_PIN_INVERSE 1 // Channels + #define LIGHT_CHANNELS 1 #define LIGHT_CH1_PIN 12 #define LIGHT_CH1_INVERSE 0 @@ -516,9 +517,9 @@ #define LED1_PIN_INVERSE 1 // Channels + #define LIGHT_CHANNELS 2 #define LIGHT_CH1_PIN 12 // Cold white #define LIGHT_CH2_PIN 14 // Warm white - #define LIGHT_CH1_INVERSE 0 #define LIGHT_CH2_INVERSE 0 @@ -689,11 +690,11 @@ #define LED1_PIN_INVERSE 1 // Channels + #define LIGHT_CHANNELS 4 #define LIGHT_CH1_PIN 14 // RED #define LIGHT_CH2_PIN 5 // GREEN #define LIGHT_CH3_PIN 12 // BLUE #define LIGHT_CH4_PIN 13 // WHITE - #define LIGHT_CH1_INVERSE 0 #define LIGHT_CH2_INVERSE 0 #define LIGHT_CH3_INVERSE 0 @@ -713,11 +714,11 @@ #define LED1_PIN_INVERSE 1 // Channels + #define LIGHT_CHANNELS 4 #define LIGHT_CH1_PIN 5 // RED #define LIGHT_CH2_PIN 12 // GREEN #define LIGHT_CH3_PIN 13 // BLUE #define LIGHT_CH4_PIN 15 // WHITE - #define LIGHT_CH1_INVERSE 0 #define LIGHT_CH2_INVERSE 0 #define LIGHT_CH3_INVERSE 0 @@ -815,12 +816,12 @@ #define LED1_PIN_INVERSE 1 // Channels + #define LIGHT_CHANNELS 5 #define LIGHT_CH1_PIN 15 // RED #define LIGHT_CH2_PIN 13 // GREEN #define LIGHT_CH3_PIN 12 // BLUE #define LIGHT_CH4_PIN 14 // WHITE1 #define LIGHT_CH5_PIN 4 // WHITE2 - #define LIGHT_CH1_INVERSE 0 #define LIGHT_CH2_INVERSE 0 #define LIGHT_CH3_INVERSE 0 @@ -837,11 +838,11 @@ #define DUMMY_RELAY_COUNT 1 // Channels + #define LIGHT_CHANNELS 4 #define LIGHT_CH1_PIN 12 // RED #define LIGHT_CH2_PIN 14 // GREEN #define LIGHT_CH3_PIN 13 // BLUE #define LIGHT_CH4_PIN 15 // WHITE - #define LIGHT_CH1_INVERSE 0 #define LIGHT_CH2_INVERSE 0 #define LIGHT_CH3_INVERSE 0 @@ -1074,9 +1075,9 @@ #define LED1_PIN_INVERSE 1 // Channels + #define LIGHT_CHANNELS 2 #define LIGHT_CH1_PIN 0 #define LIGHT_CH2_PIN 2 - #define LIGHT_CH1_INVERSE 0 #define LIGHT_CH2_INVERSE 0 @@ -1094,12 +1095,12 @@ #define DUMMY_RELAY_COUNT 1 // Channels + #define LIGHT_CHANNELS 5 #define LIGHT_CH1_PIN 12 // RED #define LIGHT_CH2_PIN 14 // GREEN #define LIGHT_CH3_PIN 13 // BLUE #define LIGHT_CH4_PIN 15 // WHITE1 #define LIGHT_CH5_PIN 5 // WHITE2 - #define LIGHT_CH1_INVERSE 0 #define LIGHT_CH2_INVERSE 0 #define LIGHT_CH3_INVERSE 0 @@ -1144,11 +1145,11 @@ #define DUMMY_RELAY_COUNT 1 // Channels + #define LIGHT_CHANNELS 4 #define LIGHT_CH1_PIN 13 // RED #define LIGHT_CH2_PIN 12 // GREEN #define LIGHT_CH3_PIN 14 // BLUE #define LIGHT_CH4_PIN 2 // WHITE - #define LIGHT_CH1_INVERSE 0 #define LIGHT_CH2_INVERSE 0 #define LIGHT_CH3_INVERSE 0 diff --git a/code/espurna/light.ino b/code/espurna/light.ino index 6e9817ab..3c92c6fb 100644 --- a/code/espurna/light.ino +++ b/code/espurna/light.ino @@ -13,6 +13,13 @@ Copyright (C) 2016-2017 by Xose Pérez #include #include +#if LIGHT_PROVIDER == LIGHT_PROVIDER_DIMMER +#define PWM_CHANNEL_NUM_MAX LIGHT_CHANNELS +extern "C" { + #include "pwm.h" +} +#endif + Ticker colorTicker; typedef struct { unsigned char pin; @@ -29,25 +36,26 @@ unsigned int _brightness = LIGHT_MAX_BRIGHTNESS; my9291 * _my9291; #endif -// Gamma Correction lookup table for gamma=2.8 and 12 bit (4095) full scale +// Gamma Correction lookup table (8 bit) // TODO: move to PROGMEM -const unsigned short gamma_table[LIGHT_MAX_VALUE+1] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, - 2, 2, 2, 3, 3, 4, 4, 5, 5, 6, 7, 8, 8, 9, 10, 11, - 12, 13, 15, 16, 17, 18, 20, 21, 23, 25, 26, 28, 30, 32, 34, 36, - 38, 40, 43, 45, 48, 50, 53, 56, 59, 62, 65, 68, 71, 75, 78, 82, - 85, 89, 93, 97, 101, 105, 110, 114, 119, 123, 128, 133, 138, 143, 149, 154, - 159, 165, 171, 177, 183, 189, 195, 202, 208, 215, 222, 229, 236, 243, 250, 258, - 266, 273, 281, 290, 298, 306, 315, 324, 332, 341, 351, 360, 369, 379, 389, 399, - 409, 419, 430, 440, 451, 462, 473, 485, 496, 508, 520, 532, 544, 556, 569, 582, - 594, 608, 621, 634, 648, 662, 676, 690, 704, 719, 734, 749, 764, 779, 795, 811, - 827, 843, 859, 876, 893, 910, 927, 944, 962, 980, 998,1016,1034,1053,1072,1091, -1110,1130,1150,1170,1190,1210,1231,1252,1273,1294,1316,1338,1360,1382,1404,1427, -1450,1473,1497,1520,1544,1568,1593,1617,1642,1667,1693,1718,1744,1770,1797,1823, -1850,1877,1905,1932,1960,1988,2017,2045,2074,2103,2133,2162,2192,2223,2253,2284, -2315,2346,2378,2410,2442,2474,2507,2540,2573,2606,2640,2674,2708,2743,2778,2813, -2849,2884,2920,2957,2993,3030,3067,3105,3143,3181,3219,3258,3297,3336,3376,3416, -3456,3496,3537,3578,3619,3661,3703,3745,3788,3831,3874,3918,3962,4006,4050,4095 }; +const unsigned char gamma_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, + 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 11, 11, 11, + 12, 12, 13, 13, 14, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, + 19, 20, 20, 21, 22, 22, 23, 23, 24, 25, 25, 26, 26, 27, 28, 28, + 29, 30, 30, 31, 32, 33, 33, 34, 35, 35, 36, 37, 38, 39, 39, 40, + 41, 42, 43, 43, 44, 45, 46, 47, 48, 49, 50, 50, 51, 52, 53, 54, + 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 71, + 72, 73, 74, 75, 76, 77, 78, 80, 81, 82, 83, 84, 86, 87, 88, 89, + 91, 92, 93, 94, 96, 97, 98, 100, 101, 102, 104, 105, 106, 108, 109, 110, + 112, 113, 115, 116, 118, 119, 121, 122, 123, 125, 126, 128, 130, 131, 133, 134, + 136, 137, 139, 140, 142, 144, 145, 147, 149, 150, 152, 154, 155, 157, 159, 160, + 162, 164, 166, 167, 169, 171, 173, 175, 176, 178, 180, 182, 184, 186, 187, 189, + 191, 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, + 223, 225, 227, 229, 231, 233, 235, 238, 240, 242, 244, 246, 248, 251, 253, 255 +}; // ----------------------------------------------------------------------------- // UTILS @@ -197,9 +205,10 @@ void _fromMireds(unsigned long mireds) { unsigned int _toPWM(unsigned long value, bool bright, bool gamma, bool reverse) { value = constrain(value, 0, LIGHT_MAX_VALUE); if (bright) value *= ((float) _brightness / LIGHT_MAX_BRIGHTNESS); - unsigned int pwm = gamma ? gamma_table[value] : map(value, 0, LIGHT_MAX_VALUE, 0, LIGHT_LIMIT_PWM); - if (reverse) pwm = LIGHT_LIMIT_PWM - pwm; - return pwm; + if (gamma) value = gamma_table[value]; + if (LIGHT_MAX_VALUE != LIGHT_LIMIT_PWM) value = map(value, 0, LIGHT_MAX_VALUE, 0, LIGHT_LIMIT_PWM); + if (reverse) value = LIGHT_LIMIT_PWM - value; + return value; } // Returns a PWM valule for the given channel ID @@ -252,13 +261,11 @@ void _lightProviderUpdate() { if (_lightState) { - float ratio = (float) LIGHT_MAX_VALUE / LIGHT_MAX_PWM; - - unsigned int red = _toPWM(0) * ratio; - unsigned int green = _toPWM(1) * ratio; - unsigned int blue = _toPWM(2) * ratio; - unsigned int white = _toPWM(3) * ratio; - unsigned int warm = _toPWM(4) * ratio; + unsigned int red = _toPWM(0); + unsigned int green = _toPWM(1); + unsigned int blue = _toPWM(2); + unsigned int white = _toPWM(3) + unsigned int warm = _toPWM(4); _my9291->setColor((my9291_color_t) { red, green, blue, white, warm }); _my9291->setState(true); @@ -274,8 +281,9 @@ void _lightProviderUpdate() { #if LIGHT_PROVIDER == LIGHT_PROVIDER_DIMMER for (unsigned int i=0; i < _channels.size(); i++) { - analogWrite(_channels[i].pin, _toPWM(i)); + pwm_set_duty(_toPWM(i), i); } + pwm_start(); #endif @@ -571,6 +579,30 @@ void _lightAPISetup() { } +#if LIGHT_PROVIDER == LIGHT_PROVIDER_DIMMER + +unsigned long getIOMux(unsigned long gpio) { + unsigned long muxes[16] = { + PERIPHS_IO_MUX_GPIO0_U, PERIPHS_IO_MUX_U0TXD_U, PERIPHS_IO_MUX_GPIO2_U, PERIPHS_IO_MUX_U0RXD_U, + PERIPHS_IO_MUX_GPIO4_U, PERIPHS_IO_MUX_GPIO5_U, PERIPHS_IO_MUX_SD_CLK_U, PERIPHS_IO_MUX_SD_DATA0_U, + PERIPHS_IO_MUX_SD_DATA1_U, PERIPHS_IO_MUX_SD_DATA2_U, PERIPHS_IO_MUX_SD_DATA3_U, PERIPHS_IO_MUX_SD_CMD_U, + PERIPHS_IO_MUX_MTDI_U, PERIPHS_IO_MUX_MTCK_U, PERIPHS_IO_MUX_MTMS_U, PERIPHS_IO_MUX_MTDO_U + }; + return muxes[gpio]; +} + +unsigned long getIOFunc(unsigned long gpio) { + unsigned long funcs[16] = { + FUNC_GPIO0, FUNC_GPIO1, FUNC_GPIO2, FUNC_GPIO3, + FUNC_GPIO4, FUNC_GPIO5, FUNC_GPIO6, FUNC_GPIO7, + FUNC_GPIO8, FUNC_GPIO9, FUNC_GPIO10, FUNC_GPIO11, + FUNC_GPIO12, FUNC_GPIO13, FUNC_GPIO14, FUNC_GPIO15 + }; + return funcs[gpio]; +} + +#endif + void lightSetup() { #ifdef LIGHT_ENABLE_PIN @@ -608,11 +640,17 @@ void lightSetup() { _channels.push_back((channel_t) {LIGHT_CH5_PIN, LIGHT_CH5_INVERSE, 0}); #endif - analogWriteRange(LIGHT_MAX_PWM+1); - analogWriteFreq(LIGHT_PWM_FREQUENCY); + uint32 pwm_duty_init[PWM_CHANNEL_NUM_MAX]; + uint32 io_info[PWM_CHANNEL_NUM_MAX][3]; for (unsigned int i=0; i < _channels.size(); i++) { + pwm_duty_init[i] = 0; + io_info[i][0] = getIOMux(_channels[i].pin); + io_info[i][1] = getIOFunc(_channels[i].pin); + io_info[i][2] = _channels[i].pin; pinMode(_channels[i].pin, OUTPUT); } + pwm_init(LIGHT_MAX_PWM, pwm_duty_init, PWM_CHANNEL_NUM_MAX, io_info); + pwm_start(); #endif diff --git a/code/espurna/pwm.c b/code/espurna/pwm.c new file mode 100644 index 00000000..5b5f306b --- /dev/null +++ b/code/espurna/pwm.c @@ -0,0 +1,448 @@ +/* + * Copyright (C) 2016 Stefan Brüns + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* Set the following three defines to your needs */ + +#ifndef SDK_PWM_PERIOD_COMPAT_MODE + #define SDK_PWM_PERIOD_COMPAT_MODE 0 +#endif +#ifndef PWM_MAX_CHANNELS + #define PWM_MAX_CHANNELS 8 +#endif +#define PWM_DEBUG 0 +#define PWM_USE_NMI 0 + +/* no user servicable parts beyond this point */ + +#define PWM_MAX_TICKS 0x7fffff +#if SDK_PWM_PERIOD_COMPAT_MODE +#define PWM_PERIOD_TO_TICKS(x) (x * 0.2) +#define PWM_DUTY_TO_TICKS(x) (x * 5) +#define PWM_MAX_DUTY (PWM_MAX_TICKS * 0.2) +#define PWM_MAX_PERIOD (PWM_MAX_TICKS * 5) +#else +#define PWM_PERIOD_TO_TICKS(x) (x) +#define PWM_DUTY_TO_TICKS(x) (x) +#define PWM_MAX_DUTY PWM_MAX_TICKS +#define PWM_MAX_PERIOD PWM_MAX_TICKS +#endif + +#include +#include +#include +#include + +// from SDK hw_timer.c +#define TIMER1_DIVIDE_BY_16 0x0004 +#define TIMER1_ENABLE_TIMER 0x0080 + +struct pwm_phase { + uint32_t ticks; ///< delay until next phase, in 200ns units + uint16_t on_mask; ///< GPIO mask to switch on + uint16_t off_mask; ///< GPIO mask to switch off +}; + +/* Three sets of PWM phases, the active one, the one used + * starting with the next cycle, and the one updated + * by pwm_start. After the update pwm_next_set + * is set to the last updated set. pwm_current_set is set to + * pwm_next_set from the interrupt routine during the first + * pwm phase + */ +typedef struct pwm_phase (pwm_phase_array)[PWM_MAX_CHANNELS + 2]; +static pwm_phase_array pwm_phases[3]; +static struct { + struct pwm_phase* next_set; + struct pwm_phase* current_set; + uint8_t current_phase; +} pwm_state; + +static uint32_t pwm_period; +static uint32_t pwm_period_ticks; +static uint32_t pwm_duty[PWM_MAX_CHANNELS]; +static uint16_t gpio_mask[PWM_MAX_CHANNELS]; +static uint8_t pwm_channels; + +// 3-tuples of MUX_REGISTER, MUX_VALUE and GPIO number +typedef uint32_t (pin_info_type)[3]; + +struct gpio_regs { + uint32_t out; /* 0x60000300 */ + uint32_t out_w1ts; /* 0x60000304 */ + uint32_t out_w1tc; /* 0x60000308 */ + uint32_t enable; /* 0x6000030C */ + uint32_t enable_w1ts; /* 0x60000310 */ + uint32_t enable_w1tc; /* 0x60000314 */ + uint32_t in; /* 0x60000318 */ + uint32_t status; /* 0x6000031C */ + uint32_t status_w1ts; /* 0x60000320 */ + uint32_t status_w1tc; /* 0x60000324 */ +}; +static struct gpio_regs* gpio = (struct gpio_regs*)(0x60000300); + +struct timer_regs { + uint32_t frc1_load; /* 0x60000600 */ + uint32_t frc1_count; /* 0x60000604 */ + uint32_t frc1_ctrl; /* 0x60000608 */ + uint32_t frc1_int; /* 0x6000060C */ + uint8_t pad[16]; + uint32_t frc2_load; /* 0x60000620 */ + uint32_t frc2_count; /* 0x60000624 */ + uint32_t frc2_ctrl; /* 0x60000628 */ + uint32_t frc2_int; /* 0x6000062C */ + uint32_t frc2_alarm; /* 0x60000630 */ +}; +static struct timer_regs* timer = (struct timer_regs*)(0x60000600); + +static void ICACHE_RAM_ATTR +pwm_intr_handler(void) +{ + if ((pwm_state.current_set[pwm_state.current_phase].off_mask == 0) && + (pwm_state.current_set[pwm_state.current_phase].on_mask == 0)) { + pwm_state.current_set = pwm_state.next_set; + pwm_state.current_phase = 0; + } + + do { + // force write to GPIO registers on each loop + asm volatile ("" : : : "memory"); + + gpio->out_w1ts = (uint32_t)(pwm_state.current_set[pwm_state.current_phase].on_mask); + gpio->out_w1tc = (uint32_t)(pwm_state.current_set[pwm_state.current_phase].off_mask); + + uint32_t ticks = pwm_state.current_set[pwm_state.current_phase].ticks; + + pwm_state.current_phase++; + + if (ticks) { + if (ticks >= 16) { + // constant interrupt overhead + ticks -= 9; + timer->frc1_int &= ~FRC1_INT_CLR_MASK; + WRITE_PERI_REG(&timer->frc1_load, ticks); + return; + } + + ticks *= 4; + do { + ticks -= 1; + // stop compiler from optimizing delay loop to noop + asm volatile ("" : : : "memory"); + } while (ticks > 0); + } + + } while (1); +} + +/** + * period: initial period (base unit 1us OR 200ns) + * duty: array of initial duty values, may be NULL, may be freed after pwm_init + * pwm_channel_num: number of channels to use + * pin_info_list: array of pin_info + */ +void ICACHE_FLASH_ATTR +pwm_init(uint32_t period, uint32_t *duty, uint32_t pwm_channel_num, + uint32_t (*pin_info_list)[3]) +{ + int i, j, n; + + pwm_channels = pwm_channel_num; + if (pwm_channels > PWM_MAX_CHANNELS) + pwm_channels = PWM_MAX_CHANNELS; + + for (i = 0; i < 3; i++) { + for (j = 0; j < (PWM_MAX_CHANNELS + 2); j++) { + pwm_phases[i][j].ticks = 0; + pwm_phases[i][j].on_mask = 0; + pwm_phases[i][j].off_mask = 0; + } + } + pwm_state.current_set = pwm_state.next_set = 0; + pwm_state.current_phase = 0; + + uint32_t all = 0; + // PIN info: MUX-Register, Mux-Setting, PIN-Nr + for (n = 0; n < pwm_channels; n++) { + pin_info_type* pin_info = &pin_info_list[n]; + PIN_FUNC_SELECT((*pin_info)[0], (*pin_info)[1]); + gpio_mask[n] = 1 << (*pin_info)[2]; + all |= 1 << (*pin_info)[2]; + if (duty) + pwm_set_duty(duty[n], n); + } + GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, all); + GPIO_REG_WRITE(GPIO_ENABLE_W1TS_ADDRESS, all); + + pwm_set_period(period); + +#if PWM_USE_NMI + ETS_FRC_TIMER1_NMI_INTR_ATTACH(pwm_intr_handler); +#else + ETS_FRC_TIMER1_INTR_ATTACH(pwm_intr_handler, NULL); +#endif + TM1_EDGE_INT_ENABLE(); + + timer->frc1_int &= ~FRC1_INT_CLR_MASK; + timer->frc1_ctrl = 0; + + pwm_start(); +} + +__attribute__ ((noinline)) +static uint8_t ICACHE_FLASH_ATTR +_pwm_phases_prep(struct pwm_phase* pwm) +{ + uint8_t n, phases; + + uint16_t off_mask = 0; + for (n = 0; n < pwm_channels + 2; n++) { + pwm[n].ticks = 0; + pwm[n].on_mask = 0; + pwm[n].off_mask = 0; + } + phases = 1; + for (n = 0; n < pwm_channels; n++) { + uint32_t ticks = PWM_DUTY_TO_TICKS(pwm_duty[n]); + if (ticks == 0) { + pwm[0].off_mask |= gpio_mask[n]; + } else if (ticks >= pwm_period_ticks) { + pwm[0].on_mask |= gpio_mask[n]; + } else { + if (ticks < (pwm_period_ticks/2)) { + pwm[phases].ticks = ticks; + pwm[0].on_mask |= gpio_mask[n]; + pwm[phases].off_mask = gpio_mask[n]; + } else { + pwm[phases].ticks = pwm_period_ticks - ticks; + pwm[phases].on_mask = gpio_mask[n]; + pwm[0].off_mask |= gpio_mask[n]; + } + phases++; + } + } + pwm[phases].ticks = pwm_period_ticks; + + // bubble sort, lowest to hightest duty + n = 2; + while (n < phases) { + if (pwm[n].ticks < pwm[n - 1].ticks) { + struct pwm_phase t = pwm[n]; + pwm[n] = pwm[n - 1]; + pwm[n - 1] = t; + if (n > 2) + n--; + } else { + n++; + } + } + +#if PWM_DEBUG + int t = 0; + for (t = 0; t <= phases; t++) { + ets_printf("%d @%d: %04x %04x\n", t, pwm[t].ticks, pwm[t].on_mask, pwm[t].off_mask); + } +#endif + + // shift left to align right edge; + uint8_t l = 0, r = 1; + while (r <= phases) { + uint32_t diff = pwm[r].ticks - pwm[l].ticks; + if (diff && (diff <= 16)) { + uint16_t mask = pwm[r].on_mask | pwm[r].off_mask; + pwm[l].off_mask ^= pwm[r].off_mask; + pwm[l].on_mask ^= pwm[r].on_mask; + pwm[0].off_mask ^= pwm[r].on_mask; + pwm[0].on_mask ^= pwm[r].off_mask; + pwm[r].ticks = pwm_period_ticks - diff; + pwm[r].on_mask ^= mask; + pwm[r].off_mask ^= mask; + } else { + l = r; + } + r++; + } + +#if PWM_DEBUG + for (t = 0; t <= phases; t++) { + ets_printf("%d @%d: %04x %04x\n", t, pwm[t].ticks, pwm[t].on_mask, pwm[t].off_mask); + } +#endif + + // sort again + n = 2; + while (n <= phases) { + if (pwm[n].ticks < pwm[n - 1].ticks) { + struct pwm_phase t = pwm[n]; + pwm[n] = pwm[n - 1]; + pwm[n - 1] = t; + if (n > 2) + n--; + } else { + n++; + } + } + + // merge same duty + l = 0, r = 1; + while (r <= phases) { + if (pwm[r].ticks == pwm[l].ticks) { + pwm[l].off_mask |= pwm[r].off_mask; + pwm[l].on_mask |= pwm[r].on_mask; + pwm[r].on_mask = 0; + pwm[r].off_mask = 0; + } else { + l++; + if (l != r) { + struct pwm_phase t = pwm[l]; + pwm[l] = pwm[r]; + pwm[r] = t; + } + } + r++; + } + phases = l; + +#if PWM_DEBUG + for (t = 0; t <= phases; t++) { + ets_printf("%d @%d: %04x %04x\n", t, pwm[t].ticks, pwm[t].on_mask, pwm[t].off_mask); + } +#endif + + // transform absolute end time to phase durations + for (n = 0; n < phases; n++) { + pwm[n].ticks = + pwm[n + 1].ticks - pwm[n].ticks; + // subtract common overhead + pwm[n].ticks--; + } + pwm[phases].ticks = 0; + + // do a cyclic shift if last phase is short + if (pwm[phases - 1].ticks < 16) { + for (n = 0; n < phases - 1; n++) { + struct pwm_phase t = pwm[n]; + pwm[n] = pwm[n + 1]; + pwm[n + 1] = t; + } + } + +#if PWM_DEBUG + for (t = 0; t <= phases; t++) { + ets_printf("%d +%d: %04x %04x\n", t, pwm[t].ticks, pwm[t].on_mask, pwm[t].off_mask); + } + ets_printf("\n"); +#endif + + return phases; +} + +void ICACHE_FLASH_ATTR +pwm_start(void) +{ + pwm_phase_array* pwm = &pwm_phases[0]; + + if ((*pwm == pwm_state.next_set) || + (*pwm == pwm_state.current_set)) + pwm++; + if ((*pwm == pwm_state.next_set) || + (*pwm == pwm_state.current_set)) + pwm++; + + uint8_t phases = _pwm_phases_prep(*pwm); + + // all with 0% / 100% duty - stop timer + if (phases == 1) { + if (pwm_state.next_set) { +#if PWM_DEBUG + ets_printf("PWM stop\n"); +#endif + timer->frc1_ctrl = 0; + ETS_FRC1_INTR_DISABLE(); + } + pwm_state.next_set = NULL; + + GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, (*pwm)[0].on_mask); + GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, (*pwm)[0].off_mask); + + return; + } + + // start if not running + if (!pwm_state.next_set) { +#if PWM_DEBUG + ets_printf("PWM start\n"); +#endif + pwm_state.current_set = pwm_state.next_set = *pwm; + pwm_state.current_phase = phases - 1; + ETS_FRC1_INTR_ENABLE(); + RTC_REG_WRITE(FRC1_LOAD_ADDRESS, 0); + timer->frc1_ctrl = TIMER1_DIVIDE_BY_16 | TIMER1_ENABLE_TIMER; + return; + } + + pwm_state.next_set = *pwm; +} + +void ICACHE_FLASH_ATTR +pwm_set_duty(uint32_t duty, uint8_t channel) +{ + if (channel > PWM_MAX_CHANNELS) + return; + + if (duty > PWM_MAX_DUTY) + duty = PWM_MAX_DUTY; + + pwm_duty[channel] = duty; +} + +uint32_t ICACHE_FLASH_ATTR +pwm_get_duty(uint8_t channel) +{ + if (channel > PWM_MAX_CHANNELS) + return 0; + return pwm_duty[channel]; +} + +void ICACHE_FLASH_ATTR +pwm_set_period(uint32_t period) +{ + pwm_period = period; + + if (pwm_period > PWM_MAX_PERIOD) + pwm_period = PWM_MAX_PERIOD; + + pwm_period_ticks = PWM_PERIOD_TO_TICKS(period); +} + +uint32_t ICACHE_FLASH_ATTR +pwm_get_period(void) +{ + return pwm_period; +} + +uint32_t ICACHE_FLASH_ATTR +get_pwm_version(void) +{ + return 1; +} + +void ICACHE_FLASH_ATTR +set_pwm_debug_en(uint8_t print_en) +{ + (void) print_en; +} diff --git a/code/espurna/pwm.h b/code/espurna/pwm.h new file mode 100755 index 00000000..bf5605fb --- /dev/null +++ b/code/espurna/pwm.h @@ -0,0 +1,33 @@ +#ifndef __PWM_H__ +#define __PWM_H__ + +/*pwm.h: function and macro definition of PWM API , driver level */ +/*user_light.h: user interface for light API, user level*/ +/*user_light_adj: API for color changing and lighting effects, user level*/ + + + /*NOTE!! : DO NOT CHANGE THIS FILE*/ + + /*SUPPORT UP TO 8 PWM CHANNEL*/ +//#define PWM_CHANNEL_NUM_MAX 8 + +struct pwm_param { + uint32 period; + uint32 freq; + uint32 duty[PWM_CHANNEL_NUM_MAX]; //PWM_CHANNEL<=8 +}; + + +/* pwm_init should be called only once, for now */ +void pwm_init(uint32 period, uint32 *duty,uint32 pwm_channel_num,uint32 (*pin_info_list)[3]); +void pwm_start(void); + +void pwm_set_duty(uint32 duty, uint8 channel); +uint32 pwm_get_duty(uint8 channel); +void pwm_set_period(uint32 period); +uint32 pwm_get_period(void); + +uint32 get_pwm_version(void); +void set_pwm_debug_en(uint8 print_en); + +#endif