From d8bdaf72038ea17cd72e382faa3a071f841e220e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Sun, 4 Mar 2018 00:01:28 +0100 Subject: [PATCH] simple pwm --- gex.mk | 3 +- platform/platform.c | 2 + units/simple_pwm/_pwmdim_api.c | 64 +++++++++++ units/simple_pwm/_pwmdim_init.c | 170 ++++++++++++++++++++++++++++ units/simple_pwm/_pwmdim_internal.h | 65 +++++++++++ units/simple_pwm/_pwmdim_settings.c | 89 +++++++++++++++ units/simple_pwm/unit_pwmdim.c | 69 +++++++++++ units/simple_pwm/unit_pwmdim.h | 16 +++ utils/ini_parser.h | 3 +- 9 files changed, 478 insertions(+), 3 deletions(-) create mode 100644 units/simple_pwm/_pwmdim_api.c create mode 100644 units/simple_pwm/_pwmdim_init.c create mode 100644 units/simple_pwm/_pwmdim_internal.h create mode 100644 units/simple_pwm/_pwmdim_settings.c create mode 100644 units/simple_pwm/unit_pwmdim.c create mode 100644 units/simple_pwm/unit_pwmdim.h diff --git a/gex.mk b/gex.mk index feb7cbb..f0732f9 100644 --- a/gex.mk +++ b/gex.mk @@ -17,6 +17,7 @@ GEX_SRC_DIR = \ User/units/sipo \ User/units/fcap \ User/units/touch \ + User/units/simple_pwm \ User/TinyFrame \ User/CWPack \ User/tasks @@ -92,7 +93,7 @@ GEX_CDEFS = $(GEX_CDEFS_BASE) \ -DASSERT_FILENAMES=1 \ -DDEBUG_VFS=0 \ -DDEBUG_FLASH_WRITE=0 \ - -DVERBOSE_HARDFAULT=0 \ + -DVERBOSE_HARDFAULT=1 \ -DUSE_STACK_MONITOR=1 \ -DUSE_DEBUG_UART=1 \ -DDEBUG_MALLOC=0 \ diff --git a/platform/platform.c b/platform/platform.c index 678cd34..7c7e242 100644 --- a/platform/platform.c +++ b/platform/platform.c @@ -20,6 +20,7 @@ #include "units/sipo/unit_sipo.h" #include "units/fcap/unit_fcap.h" #include "units/touch/unit_touch.h" +#include "units/simple_pwm/unit_pwmdim.h" #include "hw_utils.h" void plat_init_resources(void) @@ -92,6 +93,7 @@ void plat_init_resources(void) ureg_add_type(&UNIT_SIPO); ureg_add_type(&UNIT_FCAP); ureg_add_type(&UNIT_TOUCH); + ureg_add_type(&UNIT_PWMDIM); // Free all present resources { diff --git a/units/simple_pwm/_pwmdim_api.c b/units/simple_pwm/_pwmdim_api.c new file mode 100644 index 0000000..ef5c920 --- /dev/null +++ b/units/simple_pwm/_pwmdim_api.c @@ -0,0 +1,64 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#include "platform.h" +#include "unit_base.h" +#include "unit_pwmdim.h" + +#define PWMDIM_INTERNAL +#include "_pwmdim_internal.h" + +error_t UPWMDIM_SetFreq(Unit *unit, uint32_t freq) +{ + struct priv *priv = unit->data; + + uint16_t presc; + uint32_t count; + float real_freq; + if (!hw_solve_timer(PLAT_APB1_HZ, freq, true, &presc, &count, &real_freq)) { + dbg("Failed to resolve timer params."); + return E_BAD_VALUE; + } + LL_TIM_SetPrescaler(priv->TIMx, (uint32_t) (presc - 1)); + LL_TIM_SetAutoReload(priv->TIMx, count - 1); + + // we must re-calculate duty cycles because they are absolute related to the ARR which we just changed + UPWMDIM_SetDuty(unit, 0, priv->duty1); + UPWMDIM_SetDuty(unit, 1, priv->duty2); + UPWMDIM_SetDuty(unit, 2, priv->duty3); + UPWMDIM_SetDuty(unit, 3, priv->duty4); + +// LL_TIM_GenerateEvent_UPDATE(priv->TIMx); // - this appears to cause jumpiness + priv->freq = freq; + + return E_SUCCESS; +} + +error_t UPWMDIM_SetDuty(Unit *unit, uint8_t ch, uint16_t duty1000) +{ + struct priv *priv = unit->data; + + uint32_t cnt = (LL_TIM_GetAutoReload(priv->TIMx) + 1)*duty1000 / 1000; + + if (ch == 0) { + priv->duty1 = duty1000; + LL_TIM_OC_SetCompareCH1(priv->TIMx, cnt); + } + else if (ch == 1) { + priv->duty2 = duty1000; + LL_TIM_OC_SetCompareCH2(priv->TIMx, cnt); + } + else if (ch == 2) { + priv->duty3 = duty1000; + LL_TIM_OC_SetCompareCH3(priv->TIMx, cnt); + } + else if (ch == 3) { + priv->duty4 = duty1000; + LL_TIM_OC_SetCompareCH4(priv->TIMx, cnt); + } else { + return E_BAD_VALUE; + } + + return E_SUCCESS; +} diff --git a/units/simple_pwm/_pwmdim_init.c b/units/simple_pwm/_pwmdim_init.c new file mode 100644 index 0000000..739b1fe --- /dev/null +++ b/units/simple_pwm/_pwmdim_init.c @@ -0,0 +1,170 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#include "platform.h" +#include "unit_base.h" + +#define PWMDIM_INTERNAL +#include "_pwmdim_internal.h" + +/** Allocate data structure and set defaults */ +error_t UPWMDIM_preInit(Unit *unit) +{ + struct priv *priv = unit->data = calloc_ck(1, sizeof(struct priv)); + if (priv == NULL) return E_OUT_OF_MEM; + + priv->cfg.freq = 1000; + priv->cfg.ch1_choice = 1; + priv->cfg.ch2_choice = 0; + priv->cfg.ch3_choice = 0; + priv->cfg.ch4_choice = 0; + + priv->duty1 = 500; + priv->duty2 = 500; + priv->duty3 = 500; + priv->duty4 = 500; + + return E_SUCCESS; +} + +/** Finalize unit set-up */ +error_t UPWMDIM_init(Unit *unit) +{ + bool suc = true; + struct priv *priv = unit->data; + + TRY(rsc_claim(unit, R_TIM3)); + priv->TIMx = TIM3; + hw_periph_clock_enable(priv->TIMx); + + // copy the default frequency + priv->freq = priv->cfg.freq; + + const Resource ch1_pins[] = { R_PA6, R_PB4, R_PC6 }; + const uint32_t ch1_af[] = { LL_GPIO_AF_1, LL_GPIO_AF_1, LL_GPIO_AF_0 }; + + const Resource ch2_pins[] = { R_PA7, R_PB5, R_PC7 }; + const uint32_t ch2_af[] = { LL_GPIO_AF_1, LL_GPIO_AF_1, LL_GPIO_AF_0 }; + + const Resource ch3_pins[] = { R_PB0, R_PC8 }; + const uint32_t ch3_af[] = { LL_GPIO_AF_1, LL_GPIO_AF_0 }; + + const Resource ch4_pins[] = { R_PB1, R_PC9 }; + const uint32_t ch4_af[] = { LL_GPIO_AF_1, LL_GPIO_AF_0 }; + + Resource r[4] = {}; + uint32_t af[4] = {}; + + // --- resolve pins and AFs --- + if (priv->cfg.ch1_choice > 0) { + if (priv->cfg.ch1_choice > 3) return E_BAD_CONFIG; + r[0] = ch1_pins[priv->cfg.ch1_choice - 1]; + af[0] = ch1_af[priv->cfg.ch1_choice - 1]; + TRY(rsc_claim(unit, r[0])); + } + + if (priv->cfg.ch2_choice > 0) { + if (priv->cfg.ch2_choice > 3) return E_BAD_CONFIG; + r[1] = ch2_pins[priv->cfg.ch2_choice - 1]; + af[1] = ch2_af[priv->cfg.ch2_choice - 1]; + TRY(rsc_claim(unit, r[1])); + } + + if (priv->cfg.ch3_choice > 0) { + if (priv->cfg.ch3_choice > 2) return E_BAD_CONFIG; + r[2] = ch3_pins[priv->cfg.ch3_choice - 1]; + af[2] = ch3_af[priv->cfg.ch3_choice - 1]; + TRY(rsc_claim(unit, r[2])); + } + + if (priv->cfg.ch4_choice > 0) { + if (priv->cfg.ch4_choice > 2) return E_BAD_CONFIG; + r[3] = ch4_pins[priv->cfg.ch4_choice - 1]; + af[3] = ch4_af[priv->cfg.ch4_choice - 1]; + TRY(rsc_claim(unit, r[3])); + } + + // --- configure AF + timer --- + LL_TIM_DeInit(priv->TIMx); // force a reset + + uint16_t presc; + uint32_t count; + float real_freq; + if (!hw_solve_timer(PLAT_APB1_HZ, priv->freq, true, &presc, &count, &real_freq)) { + dbg("Failed to resolve timer params."); + return E_BAD_VALUE; + } + LL_TIM_SetPrescaler(priv->TIMx, (uint32_t) (presc - 1)); + LL_TIM_SetAutoReload(priv->TIMx, count - 1); + LL_TIM_EnableARRPreload(priv->TIMx); + + dbg("Presc %d, cnt %d", (int)presc, (int)count); + + // TODO this can probably be turned into a loop over an array of structs + + + if (priv->cfg.ch1_choice > 0) { + TRY(hw_configure_gpiorsc_af(r[0], af[0])); + + LL_TIM_OC_EnablePreload(priv->TIMx, LL_TIM_CHANNEL_CH1); + LL_TIM_OC_SetMode(priv->TIMx, LL_TIM_CHANNEL_CH1, LL_TIM_OCMODE_PWM1); + LL_TIM_OC_SetCompareCH1(priv->TIMx, count/2); + LL_TIM_CC_EnablePreload(priv->TIMx); + LL_TIM_CC_EnableChannel(priv->TIMx, LL_TIM_CHANNEL_CH1); + } + + if (priv->cfg.ch2_choice > 0) { + TRY(hw_configure_gpiorsc_af(r[1], af[1])); + + LL_TIM_OC_EnablePreload(priv->TIMx, LL_TIM_CHANNEL_CH2); + LL_TIM_OC_SetMode(priv->TIMx, LL_TIM_CHANNEL_CH2, LL_TIM_OCMODE_PWM1); + LL_TIM_OC_SetCompareCH2(priv->TIMx, count/2); + LL_TIM_CC_EnableChannel(priv->TIMx, LL_TIM_CHANNEL_CH2); + } + + if (priv->cfg.ch3_choice > 0) { + TRY(hw_configure_gpiorsc_af(r[2], af[2])); + + LL_TIM_OC_EnablePreload(priv->TIMx, LL_TIM_CHANNEL_CH3); + LL_TIM_OC_SetMode(priv->TIMx, LL_TIM_CHANNEL_CH3, LL_TIM_OCMODE_PWM1); + LL_TIM_OC_SetCompareCH3(priv->TIMx, count/2); + LL_TIM_CC_EnableChannel(priv->TIMx, LL_TIM_CHANNEL_CH3); + } + + if (priv->cfg.ch4_choice > 0) { + TRY(hw_configure_gpiorsc_af(r[3], af[3])); + + LL_TIM_OC_EnablePreload(priv->TIMx, LL_TIM_CHANNEL_CH4); + LL_TIM_OC_SetMode(priv->TIMx, LL_TIM_CHANNEL_CH4, LL_TIM_OCMODE_PWM1); + LL_TIM_OC_SetCompareCH4(priv->TIMx, count/2); + LL_TIM_CC_EnableChannel(priv->TIMx, LL_TIM_CHANNEL_CH4); + } + + LL_TIM_GenerateEvent_UPDATE(priv->TIMx); + LL_TIM_EnableAllOutputs(priv->TIMx); + + // postpone this for later - when user uses the start command. + // prevents beeping right after restart if used for audio. +// LL_TIM_EnableCounter(priv->TIMx); + + return E_SUCCESS; +} + + +/** Tear down the unit */ +void UPWMDIM_deInit(Unit *unit) +{ + struct priv *priv = unit->data; + + // de-init peripherals + if (unit->status == E_SUCCESS ) { + LL_TIM_DeInit(priv->TIMx); + } + + // Release all resources, deinit pins + rsc_teardown(unit); + + // Free memory + free_ck(unit->data); +} diff --git a/units/simple_pwm/_pwmdim_internal.h b/units/simple_pwm/_pwmdim_internal.h new file mode 100644 index 0000000..f456fe8 --- /dev/null +++ b/units/simple_pwm/_pwmdim_internal.h @@ -0,0 +1,65 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#ifndef GEX_F072_PWMDIM_INTERNAL_H +#define GEX_F072_PWMDIM_INTERNAL_H + +#ifndef PWMDIM_INTERNAL +#error bad include! +#endif + +#include "unit_base.h" + +/** Private data structure */ +struct priv { + // settings + struct { + uint32_t freq; + uint8_t ch1_choice; + uint8_t ch2_choice; + uint8_t ch3_choice; + uint8_t ch4_choice; + } cfg; + + // internal state + uint32_t freq; + uint16_t duty1; + uint16_t duty2; + uint16_t duty3; + uint16_t duty4; + + TIM_TypeDef *TIMx; +}; + +/** Allocate data structure and set defaults */ +error_t UPWMDIM_preInit(Unit *unit); + +/** Load from a binary buffer stored in Flash */ +void UPWMDIM_loadBinary(Unit *unit, PayloadParser *pp); + +/** Write to a binary buffer for storing in Flash */ +void UPWMDIM_writeBinary(Unit *unit, PayloadBuilder *pb); + +// ------------------------------------------------------------------------ + +/** Parse a key-value pair from the INI file */ +error_t UPWMDIM_loadIni(Unit *unit, const char *key, const char *value); + +/** Generate INI file section for the unit */ +void UPWMDIM_writeIni(Unit *unit, IniWriter *iw); + +// ------------------------------------------------------------------------ + +/** Finalize unit set-up */ +error_t UPWMDIM_init(Unit *unit); + +/** Tear down the unit */ +void UPWMDIM_deInit(Unit *unit); + + +error_t UPWMDIM_SetFreq(Unit *unit, uint32_t freq); + +error_t UPWMDIM_SetDuty(Unit *unit, uint8_t ch, uint16_t duty1000); + +#endif //GEX_F072_PWMDIM_INTERNAL_H diff --git a/units/simple_pwm/_pwmdim_settings.c b/units/simple_pwm/_pwmdim_settings.c new file mode 100644 index 0000000..7de7567 --- /dev/null +++ b/units/simple_pwm/_pwmdim_settings.c @@ -0,0 +1,89 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#include "platform.h" +#include "unit_base.h" + +#define PWMDIM_INTERNAL +#include "_pwmdim_internal.h" + +/** Load from a binary buffer stored in Flash */ +void UPWMDIM_loadBinary(Unit *unit, PayloadParser *pp) +{ + struct priv *priv = unit->data; + + uint8_t version = pp_u8(pp); + (void)version; + + priv->cfg.freq = pp_u32(pp); + priv->cfg.ch1_choice = pp_u8(pp); + priv->cfg.ch2_choice = pp_u8(pp); + priv->cfg.ch3_choice = pp_u8(pp); + priv->cfg.ch4_choice = pp_u8(pp); +} + +/** Write to a binary buffer for storing in Flash */ +void UPWMDIM_writeBinary(Unit *unit, PayloadBuilder *pb) +{ + struct priv *priv = unit->data; + + pb_u8(pb, 0); // version + + pb_u32(pb, priv->cfg.freq); + pb_u8(pb, priv->cfg.ch1_choice); + pb_u8(pb, priv->cfg.ch2_choice); + pb_u8(pb, priv->cfg.ch3_choice); + pb_u8(pb, priv->cfg.ch4_choice); +} + +// ------------------------------------------------------------------------ + +/** Parse a key-value pair from the INI file */ +error_t UPWMDIM_loadIni(Unit *unit, const char *key, const char *value) +{ + bool suc = true; + struct priv *priv = unit->data; + + if (streq(key, "frequency")) { + priv->cfg.freq = cfg_u32_parse(value, &suc); + } + else if (streq(key, "ch1_pin")) { + priv->cfg.ch1_choice = cfg_u8_parse(value, &suc); + } + else if (streq(key, "ch2_pin")) { + priv->cfg.ch2_choice = cfg_u8_parse(value, &suc); + } + else if (streq(key, "ch3_pin")) { + priv->cfg.ch3_choice = cfg_u8_parse(value, &suc); + } + else if (streq(key, "ch4_pin")) { + priv->cfg.ch4_choice = cfg_u8_parse(value, &suc); + } + else { + return E_BAD_KEY; + } + + if (!suc) return E_BAD_VALUE; + return E_SUCCESS; +} + +/** Generate INI file section for the unit */ +void UPWMDIM_writeIni(Unit *unit, IniWriter *iw) +{ + struct priv *priv = unit->data; + + iw_comment(iw, "Default pulse frequency (Hz)"); + iw_entry_d(iw, "frequency", priv->cfg.freq); + + iw_comment(iw, "Pin mapping - 0=disabled"); + iw_comment(iw, "Channel1 - 1:PA6, 2:PB4, 3:PC6"); + iw_entry_d(iw, "ch1_pin", priv->cfg.ch1_choice); + iw_comment(iw, "Channel2 - 1:PA7, 2:PB5, 3:PC7"); + iw_entry_d(iw, "ch2_pin", priv->cfg.ch2_choice); + iw_comment(iw, "Channel3 - 1:PB0, 2:PC8"); + iw_entry_d(iw, "ch3_pin", priv->cfg.ch3_choice); + iw_comment(iw, "Channel4 - 1:PB1, 2:PC9"); + iw_entry_d(iw, "ch4_pin", priv->cfg.ch4_choice); +} + diff --git a/units/simple_pwm/unit_pwmdim.c b/units/simple_pwm/unit_pwmdim.c new file mode 100644 index 0000000..dac34e7 --- /dev/null +++ b/units/simple_pwm/unit_pwmdim.c @@ -0,0 +1,69 @@ +// +// Created by MightyPork on 2017/11/25. +// + +#include "unit_base.h" +#include "unit_pwmdim.h" + +#define PWMDIM_INTERNAL +#include "_pwmdim_internal.h" + +// ------------------------------------------------------------------------ + +enum PwmSimpleCmd_ { + CMD_SET_FREQUENCY = 0, + CMD_SET_DUTY = 1, + CMD_STOP = 2, + CMD_START = 3, +}; + +/** Handle a request message */ +static error_t UPWMDIM_handleRequest(Unit *unit, TF_ID frame_id, uint8_t command, PayloadParser *pp) +{ + struct priv *priv = unit->data; + + switch (command) { + case CMD_SET_FREQUENCY: + TRY(UPWMDIM_SetFreq(unit, pp_u32(pp))); + return E_SUCCESS; + + case CMD_SET_DUTY: + for (; pp_length(pp) > 0;) { + uint8_t ch = pp_u8(pp); + uint16_t duty = pp_u16(pp); + TRY(UPWMDIM_SetDuty(unit, ch, duty)); + } + return E_SUCCESS; + + case CMD_STOP: + LL_TIM_DisableCounter(priv->TIMx); + LL_TIM_SetCounter(priv->TIMx, 0); + return E_SUCCESS; + + case CMD_START: + LL_TIM_EnableCounter(priv->TIMx); + return E_SUCCESS; + + default: + return E_UNKNOWN_COMMAND; + } +} + +// ------------------------------------------------------------------------ + +/** Simple PWM dimming output */ +const UnitDriver UNIT_PWMDIM = { + .name = "PWMDIM", + .description = "Simple PWM output", + // Settings + .preInit = UPWMDIM_preInit, + .cfgLoadBinary = UPWMDIM_loadBinary, + .cfgWriteBinary = UPWMDIM_writeBinary, + .cfgLoadIni = UPWMDIM_loadIni, + .cfgWriteIni = UPWMDIM_writeIni, + // Init + .init = UPWMDIM_init, + .deInit = UPWMDIM_deInit, + // Function + .handleRequest = UPWMDIM_handleRequest, +}; diff --git a/units/simple_pwm/unit_pwmdim.h b/units/simple_pwm/unit_pwmdim.h new file mode 100644 index 0000000..220c430 --- /dev/null +++ b/units/simple_pwm/unit_pwmdim.h @@ -0,0 +1,16 @@ +// +// Created by MightyPork on 2017/11/25. +// +// Digital input unit; single or multiple pin read access on one port (A-F) +// + +#ifndef U_PWMDIM_H +#define U_PWMDIM_H + +#include "unit.h" + +extern const UnitDriver UNIT_PWMDIM; + +// UU_ prototypes + +#endif //U_PWMDIM_H diff --git a/utils/ini_parser.h b/utils/ini_parser.h index 4e817ee..6503e4d 100644 --- a/utils/ini_parser.h +++ b/utils/ini_parser.h @@ -1,6 +1,5 @@ // -// INI file parser with a FSM generated by Ragel. This was originally written for ESPTerm -// Used to extract sections, keys and values from user-provided settings file +// INI file parser. Used to extract sections, keys and values from user-provided settings file // #ifndef INIPARSE_STREAM_H