From 669543bbf2dc26ea41f0385c6ee0ced237239d53 Mon Sep 17 00:00:00 2001 From: philip Date: Wed, 17 Feb 2016 21:07:33 -0500 Subject: [PATCH] Hardware timer support and convert the PWM module to use it --- .gitignore | 1 + Makefile | 4 + app/driver/pwm.c | 198 +++++++++++++++++++--------------- app/include/driver/pwm.h | 2 +- app/modules/pwm.c | 4 +- app/platform/hw_timer.c | 183 +++++++++++++++++++++++++++++++ app/platform/hw_timer.h | 34 ++++++ app/platform/platform.c | 12 ++- app/platform/platform.h | 2 +- sdk-overrides/include/osapi.h | 2 + 10 files changed, 346 insertions(+), 96 deletions(-) create mode 100644 app/platform/hw_timer.c create mode 100644 app/platform/hw_timer.h diff --git a/.gitignore b/.gitignore index 57fca5e5..a7c13e91 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ sdk/ cache/ +user_config.h diff --git a/Makefile b/Makefile index 04045832..ec3a5f24 100644 --- a/Makefile +++ b/Makefile @@ -131,6 +131,10 @@ CCFLAGS += \ -mtext-section-literals # -Wall +ifneq ($(wildcard $(TOP_DIR)/user_config.h),) +INCLUDES := $(INCLUDES) -include "$(TOP_DIR)/user_config.h" +endif + CFLAGS = $(CCFLAGS) $(DEFINES) $(EXTRA_CCFLAGS) $(STD_CFLAGS) $(INCLUDES) DFLAGS = $(CCFLAGS) $(DDEFINES) $(EXTRA_CCFLAGS) $(STD_CFLAGS) $(INCLUDES) diff --git a/app/driver/pwm.c b/app/driver/pwm.c index 07469dd9..d49c8290 100644 --- a/app/driver/pwm.c +++ b/app/driver/pwm.c @@ -14,6 +14,7 @@ #include "os_type.h" #include "osapi.h" #include "gpio.h" +#include "hw_timer.h" #include "user_interface.h" #include "driver/pwm.h" @@ -21,6 +22,19 @@ // #define PWM_DBG os_printf #define PWM_DBG +// Enabling the next line will cause the interrupt handler to toggle +// this output pin during processing so that the timing is obvious +// +// #define PWM_DBG_PIN 13 // GPIO7 + +#ifdef PWM_DBG_PIN +#define PWM_DBG_PIN_HIGH() GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, 1 << PWM_DBG_PIN) +#define PWM_DBG_PIN_LOW() GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, 1 << PWM_DBG_PIN) +#else +#define PWM_DBG_PIN_HIGH() +#define PWM_DBG_PIN_LOW() +#endif + LOCAL struct pwm_single_param pwm_single_toggle[2][PWM_CHANNEL + 1]; LOCAL struct pwm_single_param *pwm_single; @@ -32,7 +46,13 @@ LOCAL int8 pwm_out_io_num[PWM_CHANNEL] = {-1, -1, -1, -1, -1, -1}; LOCAL uint8 pwm_channel_toggle[2]; LOCAL uint8 *pwm_channel; +// Toggle flips between 1 and 0 when we make updates so that the interrupt code +// cn switch cleanly between the two states. The cinterrupt handler uses either +// the pwm_single_toggle[0] or pwm_single_toggle[1] +// pwm_toggle indicates which state should be used on the *next* timer interrupt +// freq boundary. LOCAL uint8 pwm_toggle = 1; +LOCAL volatile uint8 pwm_current_toggle = 1; LOCAL uint8 pwm_timer_down = 1; LOCAL uint8 pwm_current_channel = 0; @@ -41,27 +61,8 @@ LOCAL uint16 pwm_gpio = 0; LOCAL uint8 pwm_channel_num = 0; -//XXX: 0xffffffff/(80000000/16)=35A -#define US_TO_RTC_TIMER_TICKS(t) \ - ((t) ? \ - (((t) > 0x35A) ? \ - (((t)>>2) * ((APB_CLK_FREQ>>4)/250000) + ((t)&0x3) * ((APB_CLK_FREQ>>4)/1000000)) : \ - (((t) *(APB_CLK_FREQ>>4)) / 1000000)) : \ - 0) - -//FRC1 -#define FRC1_ENABLE_TIMER BIT7 - -typedef enum { - DIVDED_BY_1 = 0, - DIVDED_BY_16 = 4, - DIVDED_BY_256 = 8, -} TIMER_PREDIVED_MODE; - -typedef enum { - TM_LEVEL_INT = 1, - TM_EDGE_INT = 0, -} TIMER_INT_MODE; +LOCAL void ICACHE_RAM_ATTR pwm_tim1_intr_handler(os_param_t p); +#define TIMER_OWNER ((os_param_t) 'P') LOCAL void ICACHE_FLASH_ATTR pwm_insert_sort(struct pwm_single_param pwm[], uint8 n) @@ -74,7 +75,6 @@ pwm_insert_sort(struct pwm_single_param pwm[], uint8 n) struct pwm_single_param tmp; os_memcpy(&tmp, &pwm[i], sizeof(struct pwm_single_param)); - os_memcpy(&pwm[i], &pwm[i - 1], sizeof(struct pwm_single_param)); while (tmp.h_time < pwm[j].h_time) { os_memcpy(&pwm[j + 1], &pwm[j], sizeof(struct pwm_single_param)); @@ -89,18 +89,8 @@ pwm_insert_sort(struct pwm_single_param pwm[], uint8 n) } } -LOCAL volatile uint8 critical = 0; - -#define LOCK_PWM(c) do { \ - while( (c)==1 ); \ - (c) = 1; \ -} while (0) - -#define UNLOCK_PWM(c) do { \ - (c) = 0; \ -} while (0) - -void ICACHE_FLASH_ATTR +// Returns FALSE if we cannot start +bool ICACHE_FLASH_ATTR pwm_start(void) { uint8 i, j; @@ -109,10 +99,19 @@ pwm_start(void) PWM_DBG("pwm_out_io_num[0]:%d,[1]:%d,[2]:%d\n",pwm_out_io_num[0],pwm_out_io_num[1],pwm_out_io_num[2]); PWM_DBG("pwm.period:%d,pwm.duty[0]:%d,[1]:%d,[2]:%d\n",pwm.period,pwm.duty[0],pwm.duty[1],pwm.duty[2]); - LOCK_PWM(critical); // enter critical + // First we need to make sure that the interrupt handler is running + // out of the same set of params as we expect + while (!pwm_timer_down && pwm_toggle != pwm_current_toggle) { + os_delay_us(100); + } + if (pwm_timer_down) { + pwm_toggle = pwm_current_toggle; + } - struct pwm_single_param *local_single = pwm_single_toggle[pwm_toggle ^ 0x01]; - uint8 *local_channel = &pwm_channel_toggle[pwm_toggle ^ 0x01]; + uint8_t new_toggle = pwm_toggle ^ 0x01; + + struct pwm_single_param *local_single = pwm_single_toggle[new_toggle]; + uint8 *local_channel = &pwm_channel_toggle[new_toggle]; // step 1: init PWM_CHANNEL+1 channels param for (i = 0; i < pwm_channel_num; i++) { @@ -132,9 +131,10 @@ pwm_start(void) *local_channel = pwm_channel_num + 1; PWM_DBG("1channel:%d,single[0]:%d,[1]:%d,[2]:%d,[3]:%d\n",*local_channel,local_single[0].h_time,local_single[1].h_time,local_single[2].h_time,local_single[3].h_time); - // step 3: combine same duty channels + // step 3: combine same duty channels (or nearly the same duty). If there is + // under 2 us between pwm outputs, then treat them as the same. for (i = pwm_channel_num; i > 0; i--) { - if (local_single[i].h_time == local_single[i - 1].h_time) { + if (local_single[i].h_time <= local_single[i - 1].h_time + US_TO_RTC_TIMER_TICKS(2)) { local_single[i - 1].gpio_set |= local_single[i].gpio_set; local_single[i - 1].gpio_clear |= local_single[i].gpio_clear; @@ -166,28 +166,43 @@ pwm_start(void) (*local_channel)--; } + // Make the new ones active + pwm_toggle = new_toggle; + // if timer is down, need to set gpio and start timer if (pwm_timer_down == 1) { pwm_channel = local_channel; pwm_single = local_single; + pwm_current_toggle = pwm_toggle; // start gpio_output_set(local_single[0].gpio_set, local_single[0].gpio_clear, pwm_gpio, 0); // yeah, if all channels' duty is 0 or 255, don't need to start timer, otherwise start... if (*local_channel != 1) { - pwm_timer_down = 0; - RTC_REG_WRITE(FRC1_LOAD_ADDRESS, local_single[0].h_time); - } - } - - if (pwm_toggle == 1) { - pwm_toggle = 0; + PWM_DBG("Need to setup timer\n"); + if (!platform_hw_timer_init(TIMER_OWNER, NMI_SOURCE, FALSE)) { + return FALSE; + } + pwm_timer_down = 0; + platform_hw_timer_set_func(TIMER_OWNER, pwm_tim1_intr_handler, 0); + platform_hw_timer_arm_ticks(TIMER_OWNER, local_single[0].h_time); + } else { + PWM_DBG("Timer left idle\n"); + platform_hw_timer_close(TIMER_OWNER); + } } else { - pwm_toggle = 1; + // ensure that all outputs are outputs + gpio_output_set(0, 0, pwm_gpio, 0); } - UNLOCK_PWM(critical); // leave critical +#ifdef PWM_DBG_PIN + // Enable as output + gpio_output_set(0, 0, 1 << PWM_DBG_PIN, 0); +#endif + PWM_DBG("3channel:%d,single[0]:%d,[1]:%d,[2]:%d,[3]:%d\n",*local_channel,local_single[0].h_time,local_single[1].h_time,local_single[2].h_time,local_single[3].h_time); + + return TRUE; } /****************************************************************************** @@ -210,7 +225,6 @@ pwm_set_duty(uint16 duty, uint8 channel) if(i==pwm_channel_num) // non found return; - LOCK_PWM(critical); // enter critical if (duty < 1) { pwm.duty[channel] = 0; } else if (duty >= PWM_DEPTH) { @@ -218,7 +232,6 @@ pwm_set_duty(uint16 duty, uint8 channel) } else { pwm.duty[channel] = duty; } - UNLOCK_PWM(critical); // leave critical } /****************************************************************************** @@ -230,7 +243,6 @@ pwm_set_duty(uint16 duty, uint8 channel) void ICACHE_FLASH_ATTR pwm_set_freq(uint16 freq, uint8 channel) { - LOCK_PWM(critical); // enter critical if (freq > PWM_FREQ_MAX) { pwm.freq = PWM_FREQ_MAX; } else if (freq < 1) { @@ -240,7 +252,6 @@ pwm_set_freq(uint16 freq, uint8 channel) } pwm.period = PWM_1S / pwm.freq; - UNLOCK_PWM(critical); // leave critical } /****************************************************************************** @@ -306,36 +317,57 @@ pwm_get_freq(uint8 channel) * Returns : NONE *******************************************************************************/ LOCAL void ICACHE_RAM_ATTR -pwm_tim1_intr_handler(void *p) +pwm_tim1_intr_handler(os_param_t p) { - (void)p; - uint8 local_toggle = pwm_toggle; // pwm_toggle may change outside - RTC_CLR_REG_MASK(FRC1_INT_ADDRESS, FRC1_INT_CLR_MASK); + (void)p; - if (pwm_current_channel >= (*pwm_channel - 1)) { // *pwm_channel may change outside - pwm_single = pwm_single_toggle[local_toggle]; - pwm_channel = &pwm_channel_toggle[local_toggle]; + PWM_DBG_PIN_HIGH(); - gpio_output_set(pwm_single[*pwm_channel - 1].gpio_set, - pwm_single[*pwm_channel - 1].gpio_clear, - pwm_gpio, - 0); + int offset = 0; - pwm_current_channel = 0; + while (1) { + if (pwm_current_channel >= (*pwm_channel - 1)) { + pwm_single = pwm_single_toggle[pwm_toggle]; + pwm_channel = &pwm_channel_toggle[pwm_toggle]; + pwm_current_toggle = pwm_toggle; - if (*pwm_channel != 1) { - RTC_REG_WRITE(FRC1_LOAD_ADDRESS, pwm_single[pwm_current_channel].h_time); - } else { - pwm_timer_down = 1; - } + gpio_output_set(pwm_single[*pwm_channel - 1].gpio_set, + pwm_single[*pwm_channel - 1].gpio_clear, + 0, + 0); + + pwm_current_channel = 0; + + if (*pwm_channel == 1) { + pwm_timer_down = 1; + break; + } } else { - gpio_output_set(pwm_single[pwm_current_channel].gpio_set, - pwm_single[pwm_current_channel].gpio_clear, - pwm_gpio, 0); + gpio_output_set(pwm_single[pwm_current_channel].gpio_set, + pwm_single[pwm_current_channel].gpio_clear, + 0, 0); - pwm_current_channel++; - RTC_REG_WRITE(FRC1_LOAD_ADDRESS, pwm_single[pwm_current_channel].h_time); + pwm_current_channel++; } + + int next_time = pwm_single[pwm_current_channel].h_time; + // Delay now holds the time (in ticks) since when the last timer expiry was + PWM_DBG_PIN_LOW(); + int delay = platform_hw_timer_get_delay_ticks(TIMER_OWNER) + 4 - offset; + + offset += next_time; + + next_time = next_time - delay; + + if (next_time > US_TO_RTC_TIMER_TICKS(4)) { + PWM_DBG_PIN_HIGH(); + platform_hw_timer_arm_ticks(TIMER_OWNER, next_time); + break; + } + PWM_DBG_PIN_HIGH(); + } + + PWM_DBG_PIN_LOW(); } /****************************************************************************** @@ -343,19 +375,13 @@ pwm_tim1_intr_handler(void *p) * Description : pwm gpio, params and timer initialization * Parameters : uint16 freq : pwm freq param * uint16 *duty : each channel's duty - * Returns : NONE + * Returns : void *******************************************************************************/ void ICACHE_FLASH_ATTR pwm_init(uint16 freq, uint16 *duty) { uint8 i; - RTC_REG_WRITE(FRC1_CTRL_ADDRESS, //FRC2_AUTO_RELOAD| - DIVDED_BY_16 - | FRC1_ENABLE_TIMER - | TM_EDGE_INT); - RTC_REG_WRITE(FRC1_LOAD_ADDRESS, 0); - // PIN_FUNC_SELECT(PWM_0_OUT_IO_MUX, PWM_0_OUT_IO_FUNC); // PIN_FUNC_SELECT(PWM_1_OUT_IO_MUX, PWM_1_OUT_IO_FUNC); // PIN_FUNC_SELECT(PWM_2_OUT_IO_MUX, PWM_2_OUT_IO_FUNC); @@ -373,10 +399,8 @@ pwm_init(uint16 freq, uint16 *duty) // pwm_set_freq_duty(freq, duty); pwm_start(); - - ETS_FRC_TIMER1_INTR_ATTACH(pwm_tim1_intr_handler, NULL); - TM1_EDGE_INT_ENABLE(); - ETS_FRC1_INTR_ENABLE(); + + PWM_DBG("pwm_init returning\n"); } bool ICACHE_FLASH_ATTR @@ -390,14 +414,12 @@ pwm_add(uint8 channel){ if(pwm_out_io_num[i]==channel) // already exist return true; if(pwm_out_io_num[i] == -1){ // empty exist - LOCK_PWM(critical); // enter critical pwm_out_io_num[i] = channel; pwm.duty[i] = 0; pwm_gpio |= (1 << pin_num[channel]); PIN_FUNC_SELECT(pin_mux[channel], pin_func[channel]); GPIO_REG_WRITE(GPIO_PIN_ADDR(GPIO_ID_PIN(pin_num[channel])), GPIO_REG_READ(GPIO_PIN_ADDR(GPIO_ID_PIN(pin_num[channel]))) & (~ GPIO_PIN_PAD_DRIVER_SET(GPIO_PAD_DRIVER_ENABLE))); //disable open drain; pwm_channel_num++; - UNLOCK_PWM(critical); // leave critical return true; } } @@ -413,7 +435,6 @@ pwm_delete(uint8 channel){ uint8 i,j; for(i=0;i 0x35A) ? \ + (((t)>>2) * ((APB_CLK_FREQ>>4)/250000) + ((t)&0x3) * ((APB_CLK_FREQ>>4)/1000000)) : \ + (((t) *(APB_CLK_FREQ>>4)) / 1000000)) : \ + 0) +#endif + +typedef enum { + FRC1_SOURCE = 0, + NMI_SOURCE = 1, +} FRC1_TIMER_SOURCE_TYPE; + +bool ICACHE_RAM_ATTR platform_hw_timer_arm_ticks(os_param_t owner, uint32_t ticks); + +bool ICACHE_RAM_ATTR platform_hw_timer_arm_us(os_param_t owner, uint32_t microseconds); + +bool platform_hw_timer_set_func(os_param_t owner, void (* user_hw_timer_cb_set)(os_param_t), os_param_t arg); + +bool platform_hw_timer_init(os_param_t owner, FRC1_TIMER_SOURCE_TYPE source_type, bool autoload); + +bool ICACHE_RAM_ATTR platform_hw_timer_close(os_param_t owner); + +uint32_t ICACHE_RAM_ATTR platform_hw_timer_get_delay_ticks(os_param_t owner); + +#endif + diff --git a/app/platform/platform.c b/app/platform/platform.c index 2c28bd6f..4215f5d8 100755 --- a/app/platform/platform.c +++ b/app/platform/platform.c @@ -402,7 +402,9 @@ uint32_t platform_pwm_setup( unsigned pin, uint32_t frequency, unsigned duty ) return 0; } clock = platform_pwm_get_clock( pin ); - pwm_start(); + if (!pwm_start()) { + return 0; + } return clock; } @@ -416,16 +418,18 @@ void platform_pwm_close( unsigned pin ) } } -void platform_pwm_start( unsigned pin ) +bool platform_pwm_start( unsigned pin ) { // NODE_DBG("Function platform_pwm_start() is called.\n"); if ( pin < NUM_PWM) { if(!pwm_exist(pin)) - return; + return FALSE; pwm_set_duty(DUTY(pwms_duty[pin]), pin); - pwm_start(); + return pwm_start(); } + + return FALSE; } void platform_pwm_stop( unsigned pin ) diff --git a/app/platform/platform.h b/app/platform/platform.h index 46e65e0f..d514b583 100644 --- a/app/platform/platform.h +++ b/app/platform/platform.h @@ -166,7 +166,7 @@ void platform_uart_alt( int set ); static inline int platform_pwm_exists( unsigned id ) { return ((id < NUM_PWM) && (id > 0)); } uint32_t platform_pwm_setup( unsigned id, uint32_t frequency, unsigned duty ); void platform_pwm_close( unsigned id ); -void platform_pwm_start( unsigned id ); +bool platform_pwm_start( unsigned id ); void platform_pwm_stop( unsigned id ); uint32_t platform_pwm_set_clock( unsigned id, uint32_t data ); uint32_t platform_pwm_get_clock( unsigned id ); diff --git a/sdk-overrides/include/osapi.h b/sdk-overrides/include/osapi.h index 0fe27242..2b98729e 100644 --- a/sdk-overrides/include/osapi.h +++ b/sdk-overrides/include/osapi.h @@ -8,6 +8,8 @@ int atoi(const char *nptr); int os_printf(const char *format, ...) __attribute__ ((format (printf, 1, 2))); int os_printf_plus(const char *format, ...) __attribute__ ((format (printf, 1, 2))); +void NmiTimSetFunc(void (*func)(void)); + #include_next "osapi.h" #endif