diff --git a/esp8266/Makefile b/esp8266/Makefile index 66337be035..b30bb2f95b 100644 --- a/esp8266/Makefile +++ b/esp8266/Makefile @@ -57,8 +57,10 @@ SRC_C = \ gccollect.c \ lexerstr32.c \ uart.c \ + esppwm.c \ modpyb.c \ modpybpin.c \ + modpybpwm.c \ modpybrtc.c \ modpybadc.c \ modpybi2c.c \ diff --git a/esp8266/esp8266.ld b/esp8266/esp8266.ld index 84301959ed..5dbc2ef8a7 100644 --- a/esp8266/esp8266.ld +++ b/esp8266/esp8266.ld @@ -131,6 +131,7 @@ SECTIONS *gchelper.o(.literal* .text*) *modpyb.o(.literal*, .text*) *modpybpin.o(.literal*, .text*) + *modpybpwm.o(.literal*, .text*) *modpybrtc.o(.literal*, .text*) *modpybadc.o(.literal*, .text*) *modpybspi.o(.literal*, .text*) diff --git a/esp8266/esppwm.c b/esp8266/esppwm.c new file mode 100644 index 0000000000..6c89caa4ba --- /dev/null +++ b/esp8266/esppwm.c @@ -0,0 +1,427 @@ +/****************************************************************************** + * Copyright 2013-2014 Espressif Systems (Wuxi) + * + * FileName: pwm.c + * + * Description: pwm driver + * + * Modification history: + * 2014/5/1, v1.0 create this file. + * 2016/3/2: Modifications by dpgeorge to suit MicroPython +*******************************************************************************/ +#include +#include + +#include "etshal.h" +#include "os_type.h" +#include "gpio.h" + +#include "esppwm.h" + +#include "py/mpprint.h" +#define PWM_DBG(...) +//#define PWM_DBG(...) mp_printf(&mp_plat_print, __VA_ARGS__) + +#define ICACHE_RAM_ATTR // __attribute__((section(".text"))) + +#define PWM_CHANNEL 8 +#define PWM_DEPTH 1023 +#define PWM_FREQ_MAX 1000 +#define PWM_1S 1000000 + +struct pwm_single_param { + uint16_t gpio_set; + uint16_t gpio_clear; + uint32_t h_time; +}; + +struct pwm_param { + uint32_t period; + uint16_t freq; + uint16_t duty[PWM_CHANNEL]; +}; + +STATIC const uint8_t pin_num[PWM_CHANNEL] = {0, 2, 4, 5, 12, 13, 14, 15}; + +STATIC struct pwm_single_param pwm_single_toggle[2][PWM_CHANNEL + 1]; +STATIC struct pwm_single_param *pwm_single; + +STATIC struct pwm_param pwm; + +STATIC int8_t pwm_out_io_num[PWM_CHANNEL] = {-1, -1, -1, -1, -1, -1, -1, -1}; + +STATIC uint8_t pwm_channel_toggle[2]; +STATIC uint8_t *pwm_channel; +STATIC uint8_t pwm_toggle = 1; +STATIC uint8_t pwm_timer_down = 1; +STATIC uint8_t pwm_current_channel = 0; +STATIC uint16_t pwm_gpio = 0; +STATIC uint8_t 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; + +STATIC void ICACHE_FLASH_ATTR +pwm_insert_sort(struct pwm_single_param pwm[], uint8 n) +{ + uint8 i; + + for (i = 1; i < n; i++) { + if (pwm[i].h_time < pwm[i - 1].h_time) { + int8 j = i - 1; + struct pwm_single_param tmp; + + memcpy(&tmp, &pwm[i], sizeof(struct pwm_single_param)); + memcpy(&pwm[i], &pwm[i - 1], sizeof(struct pwm_single_param)); + + while (tmp.h_time < pwm[j].h_time) { + memcpy(&pwm[j + 1], &pwm[j], sizeof(struct pwm_single_param)); + j--; + if (j < 0) { + break; + } + } + + memcpy(&pwm[j + 1], &tmp, sizeof(struct pwm_single_param)); + } + } +} + +STATIC 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 +pwm_start(void) +{ + uint8 i, j; + PWM_DBG("--Function pwm_start() is called\n"); + PWM_DBG("pwm_gpio:%x,pwm_channel_num:%d\n",pwm_gpio,pwm_channel_num); + 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 + + struct pwm_single_param *local_single = pwm_single_toggle[pwm_toggle ^ 0x01]; + uint8 *local_channel = &pwm_channel_toggle[pwm_toggle ^ 0x01]; + + // step 1: init PWM_CHANNEL+1 channels param + for (i = 0; i < pwm_channel_num; i++) { + uint32 us = pwm.period * pwm.duty[i] / PWM_DEPTH; + local_single[i].h_time = US_TO_RTC_TIMER_TICKS(us); + PWM_DBG("i:%d us:%d ht:%d\n",i,us,local_single[i].h_time); + local_single[i].gpio_set = 0; + local_single[i].gpio_clear = 1 << pin_num[pwm_out_io_num[i]]; + } + + local_single[pwm_channel_num].h_time = US_TO_RTC_TIMER_TICKS(pwm.period); + local_single[pwm_channel_num].gpio_set = pwm_gpio; + local_single[pwm_channel_num].gpio_clear = 0; + PWM_DBG("i:%d period:%d ht:%d\n",pwm_channel_num,pwm.period,local_single[pwm_channel_num].h_time); + // step 2: sort, small to big + pwm_insert_sort(local_single, pwm_channel_num + 1); + + *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 + for (i = pwm_channel_num; i > 0; i--) { + if (local_single[i].h_time == local_single[i - 1].h_time) { + local_single[i - 1].gpio_set |= local_single[i].gpio_set; + local_single[i - 1].gpio_clear |= local_single[i].gpio_clear; + + for (j = i + 1; j < *local_channel; j++) { + memcpy(&local_single[j - 1], &local_single[j], sizeof(struct pwm_single_param)); + } + + (*local_channel)--; + } + } + PWM_DBG("2channel:%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 4: cacl delt time + for (i = *local_channel - 1; i > 0; i--) { + local_single[i].h_time -= local_single[i - 1].h_time; + } + + // step 5: last channel needs to clean + local_single[*local_channel-1].gpio_clear = 0; + + // step 6: if first channel duty is 0, remove it + if (local_single[0].h_time == 0) { + local_single[*local_channel - 1].gpio_set &= ~local_single[0].gpio_clear; + local_single[*local_channel - 1].gpio_clear |= local_single[0].gpio_clear; + + for (i = 1; i < *local_channel; i++) { + memcpy(&local_single[i - 1], &local_single[i], sizeof(struct pwm_single_param)); + } + + (*local_channel)--; + } + + // if timer is down, need to set gpio and start timer + if (pwm_timer_down == 1) { + pwm_channel = local_channel; + pwm_single = local_single; + // 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; + } else { + pwm_toggle = 1; + } + + UNLOCK_PWM(critical); // leave critical + 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); +} + +/****************************************************************************** + * FunctionName : pwm_set_duty + * Description : set each channel's duty params + * Parameters : uint8 duty : 0 ~ PWM_DEPTH + * uint8 channel : channel index + * Returns : NONE +*******************************************************************************/ +void ICACHE_FLASH_ATTR +pwm_set_duty(uint16 duty, uint8 channel) +{ + uint8 i; + for(i=0;i= PWM_DEPTH) { + pwm.duty[channel] = PWM_DEPTH; + } else { + pwm.duty[channel] = duty; + } + UNLOCK_PWM(critical); // leave critical +} + +/****************************************************************************** + * FunctionName : pwm_set_freq + * Description : set pwm frequency + * Parameters : uint16 freq : 100hz typically + * Returns : NONE +*******************************************************************************/ +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) { + pwm.freq = 1; + } else { + pwm.freq = freq; + } + + pwm.period = PWM_1S / pwm.freq; + UNLOCK_PWM(critical); // leave critical +} + +/****************************************************************************** + * FunctionName : pwm_get_duty + * Description : get duty of each channel + * Parameters : uint8 channel : channel index + * Returns : NONE +*******************************************************************************/ +uint16 ICACHE_FLASH_ATTR +pwm_get_duty(uint8 channel) +{ + uint8 i; + for(i=0;i= (*pwm_channel - 1)) { // *pwm_channel may change outside + pwm_single = pwm_single_toggle[local_toggle]; + pwm_channel = &pwm_channel_toggle[local_toggle]; + + gpio_output_set(pwm_single[*pwm_channel - 1].gpio_set, + pwm_single[*pwm_channel - 1].gpio_clear, + pwm_gpio, + 0); + + pwm_current_channel = 0; + + if (*pwm_channel != 1) { + RTC_REG_WRITE(FRC1_LOAD_ADDRESS, pwm_single[pwm_current_channel].h_time); + } else { + pwm_timer_down = 1; + } + } else { + gpio_output_set(pwm_single[pwm_current_channel].gpio_set, + pwm_single[pwm_current_channel].gpio_clear, + pwm_gpio, 0); + + pwm_current_channel++; + RTC_REG_WRITE(FRC1_LOAD_ADDRESS, pwm_single[pwm_current_channel].h_time); + } +} + +/****************************************************************************** + * FunctionName : pwm_init + * Description : pwm gpio, params and timer initialization + * Parameters : uint16 freq : pwm freq param + * uint16 *duty : each channel's duty + * Returns : NONE +*******************************************************************************/ +void ICACHE_FLASH_ATTR +pwm_init(void) +{ + 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); + + for (i = 0; i < PWM_CHANNEL; i++) { + pwm_gpio = 0; + pwm.duty[i] = 0; + } + + pwm_set_freq(500, 0); + pwm_start(); + + ETS_FRC_TIMER1_INTR_ATTACH(pwm_tim1_intr_handler, NULL); + TM1_EDGE_INT_ENABLE(); + ETS_FRC1_INTR_ENABLE(); +} + +int ICACHE_FLASH_ATTR +pwm_add(uint8_t pin_id, uint32_t pin_mux, uint32_t pin_func){ + PWM_DBG("--Function pwm_add() is called. channel:%d\n", channel); + PWM_DBG("pwm_gpio:%x,pwm_channel_num:%d\n",pwm_gpio,pwm_channel_num); + 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.duty[0]:%d,[1]:%d,[2]:%d\n",pwm.duty[0],pwm.duty[1],pwm.duty[2]); + int channel = -1; + for (int i = 0; i < PWM_CHANNEL; ++i) { + if (pin_num[i] == pin_id) { + channel = i; + break; + } + } + if (channel == -1) { + return -1; + } + uint8 i; + for(i=0;i +#include + +void pwm_init(void); +void pwm_start(void); + +void pwm_set_duty(uint16_t duty, uint8_t channel); +uint16_t pwm_get_duty(uint8_t channel); +void pwm_set_freq(uint16_t freq, uint8_t channel); +uint16_t pwm_get_freq(uint8_t channel); +int pwm_add(uint8_t pin_id, uint32_t pin_mux, uint32_t pin_func); +bool pwm_delete(uint8_t channel); + +#endif diff --git a/esp8266/modmachine.c b/esp8266/modmachine.c index 41f246993c..817b5a2446 100644 --- a/esp8266/modmachine.c +++ b/esp8266/modmachine.c @@ -142,6 +142,7 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_Timer), MP_ROM_PTR(&esp_timer_type) }, { MP_ROM_QSTR(MP_QSTR_Pin), MP_ROM_PTR(&pyb_pin_type) }, + { MP_ROM_QSTR(MP_QSTR_PWM), MP_ROM_PTR(&pyb_pwm_type) }, { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&pyb_i2c_type) }, { MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&pyb_spi_type) }, }; diff --git a/esp8266/modpyb.h b/esp8266/modpyb.h index 7b8f887149..51f5b0a672 100644 --- a/esp8266/modpyb.h +++ b/esp8266/modpyb.h @@ -1,4 +1,5 @@ extern const mp_obj_type_t pyb_pin_type; +extern const mp_obj_type_t pyb_pwm_type; extern const mp_obj_type_t pyb_adc_type; extern const mp_obj_type_t pyb_rtc_type; extern const mp_obj_type_t pyb_i2c_type; diff --git a/esp8266/modpybpwm.c b/esp8266/modpybpwm.c new file mode 100644 index 0000000000..ccd5f56b25 --- /dev/null +++ b/esp8266/modpybpwm.c @@ -0,0 +1,172 @@ +/* + * This file is part of the Micro Python project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include + +#include "esppwm.h" + +#include "py/nlr.h" +#include "py/runtime.h" +#include "modpyb.h" + +typedef struct _pyb_pwm_obj_t { + mp_obj_base_t base; + pyb_pin_obj_t *pin; + uint8_t active; + uint8_t channel; +} pyb_pwm_obj_t; + +STATIC bool pwm_inited = false; + +/******************************************************************************/ +// MicroPython bindings for PWM + +STATIC void pyb_pwm_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + pyb_pwm_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "PWM(%u", self->pin->pin_id); + if (self->active) { + mp_printf(print, ", freq=%u, duty=%u", + pwm_get_freq(self->channel), pwm_get_duty(self->channel)); + } + mp_printf(print, ")"); +} + +STATIC void pyb_pwm_init_helper(pyb_pwm_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_freq, ARG_duty }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_freq, MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_duty, MP_ARG_INT, {.u_int = -1} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + int channel = pwm_add(self->pin->phys_port, self->pin->periph, self->pin->func); + if (channel == -1) { + nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_ValueError, + "PWM not supported on pin %d", self->pin->phys_port)); + } + + self->channel = channel; + self->active = 1; + if (args[ARG_freq].u_int != -1) { + pwm_set_freq(args[ARG_freq].u_int, self->channel); + } + if (args[ARG_duty].u_int != -1) { + pwm_set_duty(args[ARG_duty].u_int, self->channel); + } + + pwm_start(); +} + +STATIC mp_obj_t pyb_pwm_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true); + pyb_pin_obj_t *pin = mp_obj_get_pin_obj(args[0]); + + // create PWM object from the given pin + pyb_pwm_obj_t *self = m_new_obj(pyb_pwm_obj_t); + self->base.type = &pyb_pwm_type; + self->pin = pin; + self->active = 0; + self->channel = -1; + + // start the PWM subsystem if it's not already running + if (!pwm_inited) { + pwm_init(); + pwm_inited = true; + } + + // start the PWM running for this channel + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + pyb_pwm_init_helper(self, n_args - 1, args + 1, &kw_args); + + return MP_OBJ_FROM_PTR(self); +} + +STATIC mp_obj_t pyb_pwm_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { + pyb_pwm_init_helper(args[0], n_args - 1, args + 1, kw_args); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_KW(pyb_pwm_init_obj, 1, pyb_pwm_init); + +STATIC mp_obj_t pyb_pwm_deinit(mp_obj_t self_in) { + pyb_pwm_obj_t *self = MP_OBJ_TO_PTR(self_in); + pwm_delete(self->channel); + self->active = 0; + pwm_start(); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(pyb_pwm_deinit_obj, pyb_pwm_deinit); + +STATIC mp_obj_t pyb_pwm_freq(size_t n_args, const mp_obj_t *args) { + //pyb_pwm_obj_t *self = MP_OBJ_TO_PTR(args[0]); + if (n_args == 1) { + // get + return MP_OBJ_NEW_SMALL_INT(pwm_get_freq(0)); + } else { + // set + pwm_set_freq(mp_obj_get_int(args[1]), 0); + pwm_start(); + return mp_const_none; + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pyb_pwm_freq_obj, 1, 2, pyb_pwm_freq); + +STATIC mp_obj_t pyb_pwm_duty(size_t n_args, const mp_obj_t *args) { + pyb_pwm_obj_t *self = MP_OBJ_TO_PTR(args[0]); + if (!self->active) { + pwm_add(self->pin->phys_port, self->pin->periph, self->pin->func); + self->active = 1; + } + if (n_args == 1) { + // get + return MP_OBJ_NEW_SMALL_INT(pwm_get_duty(self->channel)); + } else { + // set + pwm_set_duty(mp_obj_get_int(args[1]), self->channel); + pwm_start(); + return mp_const_none; + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pyb_pwm_duty_obj, 1, 2, pyb_pwm_duty); + +STATIC const mp_rom_map_elem_t pyb_pwm_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&pyb_pwm_init_obj) }, + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&pyb_pwm_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR_freq), MP_ROM_PTR(&pyb_pwm_freq_obj) }, + { MP_ROM_QSTR(MP_QSTR_duty), MP_ROM_PTR(&pyb_pwm_duty_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(pyb_pwm_locals_dict, pyb_pwm_locals_dict_table); + +const mp_obj_type_t pyb_pwm_type = { + { &mp_type_type }, + .name = MP_QSTR_PWM, + .print = pyb_pwm_print, + .make_new = pyb_pwm_make_new, + .locals_dict = (mp_obj_dict_t*)&pyb_pwm_locals_dict, +}; diff --git a/esp8266/qstrdefsport.h b/esp8266/qstrdefsport.h index 5c1cbfd85c..77e66d85cc 100644 --- a/esp8266/qstrdefsport.h +++ b/esp8266/qstrdefsport.h @@ -133,6 +133,13 @@ Q(PULL_NONE) Q(PULL_UP) Q(PULL_DOWN) +// PWM class +Q(PWM) +Q(init) +Q(deinit) +Q(freq) +Q(duty) + // RTC Q(RTC) Q(datetime)