diff --git a/docs/LED pin PWM.md b/docs/LED pin PWM.md deleted file mode 100644 index 8ed96a357e2..00000000000 --- a/docs/LED pin PWM.md +++ /dev/null @@ -1,96 +0,0 @@ -# LED pin PWM - -Normally LED pin is used to drive WS2812 led strip. LED pin is held low, and every 10ms or 20ms a set of pulses is sent to change color of the 32 LEDs: - -![alt text](/docs/assets/images/ws2811_packets.png "ws2811 packets") -![alt text](/docs/assets/images/ws2811_data.png "ws2811 data") - -As alternative function, it is possible to generate PWM signal with specified duty ratio on the LED pin. - -Feature can be used to drive external devices such as a VTX power switch. Setting the PWM duty cycle to 100% or 0% can -provide an extra PINIO pin. It is also used to simulate [OSD joystick](OSD%20Joystick.md) to control cameras. - -PWM frequency is fixed to 24kHz with duty ratio between 0 and 100%: - -![alt text](/docs/assets/images/led_pin_pwm.png "led pin pwm") - -Note that the LED feature needs to be enabled when using the PIN in this mode (feature LED_STRIP). - -There are four modes of operation: -- low -- high -- shared_low -- shared_high - -Mode is configured using ```led_pin_pwm_mode``` setting: ```LOW```, ```HIGH```, ```SHARED_LOW```, ```SHARED_HIGH``` - -*Note that in any mode, there will be ~2 seconds LOW pulse on boot.* - -## LOW -LED Pin is initialized to output low level by default and can be used to generate PWM signal. - -ws2812 strip can not be controlled. - -## HIGH -LED Pin is initialized to output high level by default and can be used to generate PWM signal. - -ws2812 strip can not be controlled. - -## SHARED_LOW (default) -LED Pin is used to drive WS2812 strip. Pauses between pulses are low: - -![alt text](/docs/assets/images/ws2811_packets.png "ws2811 packets") - -It is possible to generate PWM signal with duty ratio >0...100%. - -While PWM signal is generated, ws2811 strip is not updated. - -When PWM generation is disabled, LED pin is used to drive ws2812 strip. - -Total ws2812 pulses duration is ~1ms with ~9ms pauses. Thus connected device should ignore PWM signal with duty ratio < ~10%. - -## SHARED_HIGH -LED Pin is used to drive WS2812 strip. Pauses between pulses are high. ws2812 pulses are prefixed with 50us low 'reset' pulse: - -![alt text](/docs/assets/images/ws2811_packets_high.png "ws2811 packets_high") -![alt text](/docs/assets/images/ws2811_data_high.png "ws2811 data_high") - - It is possible to generate PWM signal with duty ratio 0...<100%. - - While PWM signal is generated, ws2811 strip is not updated. - - When PWM generation is disabled, LED pin is used to drive ws2812 strip. Total ws2812 pulses duration is ~1ms with ~9ms pauses. Thus connected device should ignore PWM signal with duty ratio > ~90%. - - After sending ws2812 protocol pulses for 32 LEDS, we held line high for 9ms, then send 50us low 'reset' pulse. Datasheet for ws2812 protocol does not describe behavior for long high pulse, but in practice it works the same as 'reset' pulse. To be safe, we also send correct low 'reset' pulse before starting next LEDs update sequence. - - This mode is used to simulate OSD joystick. It is Ok that effectively voltage level is held >90% while driving LEDs, because OSD joystick keypress voltages are below 90%. - - See [OSD Joystick](OSD%20Joystick.md) for more information. - -# Generating PWM signal with programming framework - -See "LED Pin PWM" operation in [Programming Framework](Programming%20Framework.md) - - -# Generating PWM signal from CLI - -```ledpinpwm ``` - value = 0...100 - enable PWM generation with specified duty cycle - -```ledpinpwm``` - disable PWM generation ( disable to allow ws2812 LEDs updates in shared modes ) - - -# Example of driving LED - -It is possible to drive single color LED with brightness control. Current consumption should not be greater then 1-2ma, thus LED can be used for indication only. - -![alt text](/docs/assets/images/ledpinpwmled.png "led pin pwm led") - -# Example of driving powerfull white LED - -To drive power LED with brightness control, Mosfet should be used: - -![alt text](/docs/assets/images/ledpinpwmpowerled.png "led pin pwm power_led") - -# Programming tab example for using the LED pin as a PINIO, such as for turning a VTX or camera on and off -![screenshot of programming tab using led as pinio](/docs/assets/images/led-as-pinio.png) - diff --git a/docs/OSD Joystick.md b/docs/OSD Joystick.md index 9e0f677e455..9c62eb850d3 100644 --- a/docs/OSD Joystick.md +++ b/docs/OSD Joystick.md @@ -1,8 +1,8 @@ # OSD joystick -LED pin can be used to emulate 5key OSD joystick for OSD camera pin, while still driving ws2812 LEDs (shared functionality). +A PINIO channel can be used to emulate a 5-key OSD joystick for OSD camera control. -See [LED pin PWM](LED%20pin%20PWM.md) for more details. +See [PINIO PWM](PINIO%20PWM.md) for more details. Note that for cameras which support RuncamDevice protocol, there is alternative functionality using serial communication: [Runcam device](Runcam%20device.md) @@ -22,17 +22,17 @@ To simulate 5key joystick, it is sufficient to generate correct voltage on camer # Enabling OSD Joystick emulation -```set led_pin_pwm_mode=shared_high``` - ```set osd_joystick_enabled=on``` -Also enable "Multi-color RGB LED Strip support" in Configuration tab. +```set osd_joystick_pinio_channel=``` + +Where `` is the PINIO channel (0-3) connected to the camera OSD pin. # Connection diagram -We use LED pin PWM functionality with RC filter to generate voltage: +We use PINIO PWM with an RC filter to generate voltage: -![alt text](/docs/assets/images/ledpinpwmfilter.png "led pin pwm filter") +![alt text](/docs/assets/images/ledpinpwmfilter.png "PINIO PWM filter") # Example PCB layout (SMD components) @@ -48,7 +48,7 @@ If default voltages does not work with your camera model, then you have to measu 2. Measure voltages on OSD pin while each key is pressed. 3. Connect camera to FC throught RC filter as shown on schematix above. 4. Enable OSD Joystick emulation (see "Enabling OSD Joystick emulation" above) -4. Use cli command ```led_pin_pwm ```, value = 0...100 to find out PWM values for each voltage. +4. Use CLI command `piniopwm `, value = 0...100 to find out PWM values for each voltage. 5. Specify PWM values in configuration and save: ```set osd_joystick_down=0``` @@ -87,7 +87,7 @@ There are 3 RC Boxes which can be used in armed and unarmed state: - Camera 2 - Up - Camera 3 - Down -Other keys can be emulated using Programming framework ( see [LED pin PWM](LED%20pin%20PWM.md) for more details ). +Other keys can be emulated using the Programming framework (see [PINIO PWM](PINIO%20PWM.md) for more details). # Behavior on boot diff --git a/docs/PINIO PWM.md b/docs/PINIO PWM.md new file mode 100644 index 00000000000..fbc9b80a335 --- /dev/null +++ b/docs/PINIO PWM.md @@ -0,0 +1,71 @@ +# PINIO PWM + +INAV provides two mechanisms for generating output signals on GPIO pins: + +1. **PINIO channels (0-3)** — Any PWM-capable timer output defined as `PINIOx_PIN` in the target. Supports full 0-100% duty cycle PWM at 24 kHz. +2. **LED strip idle level (channel 4)** — The WS2812 LED strip pin can be switched between idle-LOW and idle-HIGH between LED update bursts. Binary on/off only. + +## PINIO PWM channels + +PINIO channels are configured per-target in `target.h` using `PINIO1_PIN` through `PINIO4_PIN`. When a PINIO pin has a timer, it is automatically configured as a 24 kHz PWM output. + +PWM duty cycle can be controlled via: +- **CLI:** `piniopwm [channel] ` (duty = 0-100) +- **Programming framework:** Operation 52, Operand A = channel (0-3), Operand B = duty (0-100) + +Setting duty to 0 stops PWM generation (pin goes LOW, or HIGH if `PINIO_FLAGS_INVERTED` is set in target.h). + +Feature can be used to drive external devices such as a VTX power switch. Setting the PWM duty cycle to 100% or 0% effectively provides a digital on/off output. It is also used to simulate [OSD joystick](OSD%20Joystick.md) to control cameras. + +PWM frequency is fixed to 24kHz with duty ratio between 0 and 100%: + +![alt text](/docs/assets/images/led_pin_pwm.png "PINIO PWM signal") + +## LED strip idle level (channel 4) + +When the LED strip feature is enabled, the WS2812 pin sends data bursts (~1 ms) every 10-20 ms. Between bursts, the pin idles at a configurable level. + +The LED strip idle level is accessible as channel `4` (the next channel after PINIO hardware channels 0-3): + +- **CLI:** `piniopwm 4 ` — value > 0 sets idle HIGH, 0 sets idle LOW +- **Programming framework:** Operation 52, Operand A = 4, Operand B = value (>0 = HIGH, 0 = LOW) + +This can be used to drive a MOSFET or similar device connected to the LED pin, toggled by the programming framework based on flight mode, RC channel, GPS state, etc. + +*Note: there will be a ~2 second LOW pulse on the LED pin during boot.* + +### LED strip idle level timing + +Normally LED pin is held low between WS2812 updates: + +![alt text](/docs/assets/images/ws2811_packets.png "ws2811 packets") +![alt text](/docs/assets/images/ws2811_data.png "ws2811 data") + +When idle is set HIGH, the pin is held high between updates. Total ws2812 pulse duration is ~1ms with ~9ms pauses. Connected devices should be tolerant of these brief transients. + +# Generating PWM/output signals with programming framework + +See operation 52 "PINIO PWM" in [Programming Framework](Programming%20Framework.md) + +# Generating PWM/output signals from CLI + +`piniopwm [channel] ` — channel = 0-4, duty = 0-100 + +- One argument: sets duty on channel 0 (backward compatible) +- Two arguments: first is channel, second is duty +- No arguments: stops PWM on channel 0 + +# Example of driving LED + +It is possible to drive single color LED with brightness control. Current consumption should not be greater then 1-2ma, thus LED can be used for indication only. + +![alt text](/docs/assets/images/ledpinpwmled.png "PINIO PWM LED") + +# Example of driving powerful white LED + +To drive power LED with brightness control, a MOSFET should be used: + +![alt text](/docs/assets/images/ledpinpwmpowerled.png "PINIO PWM power LED") + +# Programming tab example for using a PINIO channel to switch a VTX or camera on and off +![screenshot of programming tab using PINIO](/docs/assets/images/led-as-pinio.png) diff --git a/docs/Programming Framework.md b/docs/Programming Framework.md index 4233f78487d..9a374a07e6a 100644 --- a/docs/Programming Framework.md +++ b/docs/Programming Framework.md @@ -114,7 +114,7 @@ for complete documentation on using JavaScript to program your flight controller | 49 | Timer | A simple on - off timer. `true` for the duration of `Operand A` [ms]. Then `false` for the duration of `Operand B` [ms]. | | 50 | Delta | This returns `true` when the value of `Operand A` has changed by the value of `Operand B` or greater within 100ms. ( \|ΔA\| >= B ) | | 51 | Approx Equals (A ~ B) | `true` if `Operand B` is within 1% of `Operand A`. | -| 52 | LED Pin PWM | Value `Operand A` from [`0` : `100`] PWM / PINIO generation on LED Pin. See [LED pin PWM](LED%20pin%20PWM.md). Any other value stops PWM generation (stop to allow ws2812 LEDs updates in shared modes). | +| 52 | PINIO PWM | `Operand A` = channel (0-3 for PINIO hardware, 4 for LED strip idle level). `Operand B` = duty cycle (0-100). Channels 0-3 support full PWM; channel 4 is binary (>0 = HIGH). See [PINIO PWM](PINIO%20PWM.md). | | 53 | Disable GPS Sensor Fix | Disables the GNSS sensor fix. For testing GNSS failure. | | 54 | Mag calibration | Trigger a magnetometer calibration. | | 55 | Set Gimbal Sensitivity | Scales `Operand A` from [`-16` : `15`] diff --git a/src/main/drivers/light_ws2811strip.c b/src/main/drivers/light_ws2811strip.c index fe5f405d032..cc052fcd872 100644 --- a/src/main/drivers/light_ws2811strip.c +++ b/src/main/drivers/light_ws2811strip.c @@ -43,26 +43,17 @@ #include "drivers/timer.h" #include "drivers/light_ws2811strip.h" -#include "config/parameter_group_ids.h" -#include "fc/settings.h" #include "fc/runtime_config.h" #define WS2811_PERIOD (WS2811_TIMER_HZ / WS2811_CARRIER_HZ) #define WS2811_BIT_COMPARE_1 ((WS2811_PERIOD * 2) / 3) #define WS2811_BIT_COMPARE_0 (WS2811_PERIOD / 3) -PG_REGISTER_WITH_RESET_TEMPLATE(ledPinConfig_t, ledPinConfig, PG_LEDPIN_CONFIG, 0); - -PG_RESET_TEMPLATE(ledPinConfig_t, ledPinConfig, - .led_pin_pwm_mode = SETTING_LED_PIN_PWM_MODE_DEFAULT -); - static DMA_RAM timerDMASafeType_t ledStripDMABuffer[WS2811_DMA_BUFFER_SIZE]; static IO_t ws2811IO = IO_NONE; static TCH_t * ws2811TCH = NULL; static bool ws2811Initialised = false; -static bool pwmMode = false; static hsvColor_t ledColorBuffer[WS2811_LED_STRIP_LENGTH]; @@ -112,14 +103,6 @@ bool ledConfigureDMA(void) { return timerPWMConfigChannelDMA(ws2811TCH, ledStripDMABuffer, sizeof(ledStripDMABuffer[0]), WS2811_DMA_BUFFER_SIZE); } -void ledConfigurePWM(void) { - timerConfigBase(ws2811TCH, 100, WS2811_TIMER_HZ ); - timerPWMConfigChannel(ws2811TCH, 0); - timerPWMStart(ws2811TCH); - timerEnable(ws2811TCH); - pwmMode = true; -} - void ws2811LedStripInit(void) { const timerHardware_t * timHw = timerGetByTag(IO_TAG(WS2811_PIN), TIM_USE_ANY); @@ -141,28 +124,17 @@ void ws2811LedStripInit(void) IOInit(ws2811IO, OWNER_LED_STRIP, RESOURCE_OUTPUT, 0); IOConfigGPIOAF(ws2811IO, IOCFG_AF_PP_FAST, timHw->alternateFunction); - if (ledPinConfig()->led_pin_pwm_mode == LED_PIN_PWM_MODE_LOW) { - ledConfigurePWM(); - *timerCCR(ws2811TCH) = 0; - } else if (ledPinConfig()->led_pin_pwm_mode == LED_PIN_PWM_MODE_HIGH) { - ledConfigurePWM(); - *timerCCR(ws2811TCH) = 100; - } else { - if (!ledConfigureDMA()) { - // If DMA failed - abort - ws2811Initialised = false; - return; - } - - // Zero out DMA buffer - memset(&ledStripDMABuffer, 0, sizeof(ledStripDMABuffer)); - if ( ledPinConfig()->led_pin_pwm_mode == LED_PIN_PWM_MODE_SHARED_HIGH ) { - ledStripDMABuffer[WS2811_DMA_BUFFER_SIZE-1] = 255; - } - ws2811Initialised = true; - - ws2811UpdateStrip(); + if (!ledConfigureDMA()) { + // If DMA failed - abort + ws2811Initialised = false; + return; } + + // Zero out DMA buffer — LED pin idles LOW between WS2812 bursts + memset(&ledStripDMABuffer, 0, sizeof(ledStripDMABuffer)); + ws2811Initialised = true; + + ws2811UpdateStrip(); } bool isWS2811LedStripReady(void) @@ -191,7 +163,7 @@ void ws2811UpdateStrip(void) static rgbColor24bpp_t *rgb24; // don't wait - risk of infinite block, just get an update next time round - if (pwmMode || timerPWMDMAInProgress(ws2811TCH)) { + if (timerPWMDMAInProgress(ws2811TCH)) { return; } @@ -216,40 +188,9 @@ void ws2811UpdateStrip(void) timerPWMStartDMA(ws2811TCH); } -//value -void ledPinStartPWM(uint16_t value) { - if (ws2811TCH == NULL) { - return; - } - - if ( !pwmMode ) { - timerPWMStopDMA(ws2811TCH); - //FIXME: implement method to release DMA - ws2811TCH->dma->owner = OWNER_FREE; - - ledConfigurePWM(); - } - *timerCCR(ws2811TCH) = value; -} - -void ledPinStopPWM(void) { - if (ws2811TCH == NULL || !pwmMode ) { - return; - } - - if ( ledPinConfig()->led_pin_pwm_mode == LED_PIN_PWM_MODE_HIGH ) { - *timerCCR(ws2811TCH) = 100; - return; - } else if ( ledPinConfig()->led_pin_pwm_mode == LED_PIN_PWM_MODE_LOW ) { - *timerCCR(ws2811TCH) = 0; - return; - } - pwmMode = false; - - if (!ledConfigureDMA()) { - ws2811Initialised = false; - } +void ws2811SetIdleHigh(bool high) +{ + ledStripDMABuffer[WS2811_DMA_BUFFER_SIZE - 1] = high ? 255 : 0; } - #endif diff --git a/src/main/drivers/light_ws2811strip.h b/src/main/drivers/light_ws2811strip.h index 94c36445ec7..d0edcc276ea 100644 --- a/src/main/drivers/light_ws2811strip.h +++ b/src/main/drivers/light_ws2811strip.h @@ -16,8 +16,10 @@ */ #pragma once + +#include + #include "common/color.h" -#include "config/parameter_group.h" #define WS2811_LED_STRIP_LENGTH 128 #define WS2811_BITS_PER_LED 24 @@ -30,27 +32,8 @@ #define WS2811_TIMER_HZ 2400000 #define WS2811_CARRIER_HZ 800000 -typedef enum { - LED_PIN_PWM_MODE_SHARED_LOW = 0, - LED_PIN_PWM_MODE_SHARED_HIGH = 1, - LED_PIN_PWM_MODE_LOW = 2, - LED_PIN_PWM_MODE_HIGH = 3 -} led_pin_pwm_mode_e; - -typedef struct ledPinConfig_s { - uint8_t led_pin_pwm_mode; //led_pin_pwm_mode_e -} ledPinConfig_t; - -PG_DECLARE(ledPinConfig_t, ledPinConfig); - void ws2811LedStripInit(void); -void ws2811LedStripHardwareInit(void); -void ws2811LedStripDMAEnable(void); -bool ws2811LedStripDMAInProgress(void); - -//value 0...100 -void ledPinStartPWM(uint16_t value); -void ledPinStopPWM(void); +void ws2811SetIdleHigh(bool high); void ws2811UpdateStrip(void); diff --git a/src/main/drivers/pinio.c b/src/main/drivers/pinio.c index 3e89e5fccca..5b2619fcb43 100644 --- a/src/main/drivers/pinio.c +++ b/src/main/drivers/pinio.c @@ -65,6 +65,7 @@ const int pinioHardwareCount = ARRAYLEN(pinioHardware); /*** Runtime configuration ***/ typedef struct pinioRuntime_s { IO_t io; + TCH_t *tch; // Non-NULL when pin is configured in PWM mode bool inverted; bool state; } pinioRuntime_t; @@ -84,6 +85,32 @@ void pinioInit(void) continue; } + // If the pin has a timer and is unclaimed, configure it as a PWM output. + // pwmMotorAndServoInit() runs before pinioInit(), so claimed motor/servo pins + // are already owned and the OWNER_FREE check correctly skips them. + const timerHardware_t *timHw = timerGetByTag(pinioHardware[i].ioTag, TIM_USE_ANY); + if (timHw && IOGetOwner(io) == OWNER_FREE) { + TCH_t *tch = timerGetTCH(timHw); + if (tch) { + IOInit(io, OWNER_PINIO, RESOURCE_OUTPUT, RESOURCE_INDEX(i)); + IOConfigGPIOAF(io, IOCFG_AF_PP, timHw->alternateFunction); + // period=100 means CCR value is directly the duty percentage (0–100); + // 2.4 MHz / 100 = 24 kHz PWM, above audible range + timerConfigBase(tch, 100, 2400000); + timerPWMConfigChannel(tch, 0); + timerPWMStart(tch); + timerEnable(tch); + pinioRuntime[i].tch = tch; + pinioRuntime[i].io = io; + pinioRuntime[i].inverted = (pinioHardware[i].flags & PINIO_FLAGS_INVERTED) != 0; + pinioRuntime[i].state = false; + // Start in the "off" state: HIGH if inverted, LOW if normal + *timerCCR(tch) = pinioRuntime[i].inverted ? 100 : 0; + continue; + } + } + + // GPIO fallback: no timer available or pin already claimed IOInit(io, OWNER_PINIO, RESOURCE_OUTPUT, RESOURCE_INDEX(i)); IOConfigGPIO(io, pinioHardware[i].ioMode); @@ -102,13 +129,38 @@ void pinioInit(void) void pinioSet(int index, bool newState) { + if (index < 0 || index >= pinioHardwareCount) { + return; + } + if (!pinioRuntime[index].io) { return; } if (newState != pinioRuntime[index].state) { - IOWrite(pinioRuntime[index].io, newState ^ pinioRuntime[index].inverted); + if (pinioRuntime[index].tch) { + *timerCCR(pinioRuntime[index].tch) = (newState ^ pinioRuntime[index].inverted) ? 100 : 0; + } else { + IOWrite(pinioRuntime[index].io, newState ^ pinioRuntime[index].inverted); + } pinioRuntime[index].state = newState; } } + +void pinioSetDuty(int index, uint8_t duty) +{ + if (index < 0 || index >= pinioHardwareCount) { + return; + } + + if (!pinioRuntime[index].tch) { + return; + } + + // Clamp to valid range and apply inversion + if (duty > 100) { + duty = 100; + } + *timerCCR(pinioRuntime[index].tch) = pinioRuntime[index].inverted ? (100 - duty) : duty; +} #endif diff --git a/src/main/drivers/pinio.h b/src/main/drivers/pinio.h index a1de21c12de..2b15c9633b2 100644 --- a/src/main/drivers/pinio.h +++ b/src/main/drivers/pinio.h @@ -24,6 +24,7 @@ #include #include "drivers/io_types.h" +#include "drivers/timer.h" #define PINIO_COUNT 4 #define PINIO_FLAGS_INVERTED 0x80 @@ -39,3 +40,4 @@ extern const int pinioHardwareCount; void pinioInit(void); void pinioSet(int index, bool newState); +void pinioSetDuty(int index, uint8_t duty); diff --git a/src/main/drivers/pwm_mapping.c b/src/main/drivers/pwm_mapping.c index 2d01127a504..4ef3d5fea6d 100644 --- a/src/main/drivers/pwm_mapping.c +++ b/src/main/drivers/pwm_mapping.c @@ -232,6 +232,10 @@ static void timerHardwareOverride(timerHardware_t * timer) { timer->usageFlags &= ~(TIM_USE_MOTOR|TIM_USE_SERVO); timer->usageFlags |= TIM_USE_LED; break; + case OUTPUT_MODE_PINIO: + // Clear motor/servo/LED flags so pinioInit() can claim this timer + timer->usageFlags &= ~(TIM_USE_MOTOR|TIM_USE_SERVO|TIM_USE_LED); + break; } } diff --git a/src/main/drivers/pwm_mapping.h b/src/main/drivers/pwm_mapping.h index 3f08d9b500a..c860166bc74 100644 --- a/src/main/drivers/pwm_mapping.h +++ b/src/main/drivers/pwm_mapping.h @@ -55,7 +55,8 @@ typedef enum { typedef enum { PIN_LABEL_NONE = 0, - PIN_LABEL_LED + PIN_LABEL_LED, + PIN_LABEL_PINIO_BASE = 2 // values 2..5 = USER1..USER4 (add channel index 0-3) } pinLabel_e; typedef enum { diff --git a/src/main/fc/cli.c b/src/main/fc/cli.c index 6c60f08c6ed..087fc5f23e4 100644 --- a/src/main/fc/cli.c +++ b/src/main/fc/cli.c @@ -58,6 +58,8 @@ bool cliMode = false; #include "drivers/flash.h" #include "drivers/io.h" #include "drivers/io_impl.h" +#include "drivers/light_ws2811strip.h" +#include "drivers/pinio.h" #include "drivers/osd_symbols.h" #include "drivers/persistent.h" #include "drivers/sdcard/sdcard.h" @@ -68,8 +70,6 @@ bool cliMode = false; #include "drivers/time.h" #include "drivers/usb_msc.h" #include "drivers/vtx_common.h" -#include "drivers/light_ws2811strip.h" - #include "fc/fc_core.h" #include "fc/cli.h" #include "fc/config.h" @@ -170,6 +170,7 @@ static const char * outputModeNames[] = { "MOTORS", "SERVOS", "LED", + "PINIO", NULL }; @@ -2165,21 +2166,55 @@ static void cliModeColor(char *cmdline) } } -static void cliLedPinPWM(char *cmdline) + +#endif // USE_LED_STRIP + +#ifdef USE_PINIO +static void cliPinioPwm(char *cmdline) { - int i; + int channel = 0; + int duty; if (isEmpty(cmdline)) { - ledPinStopPWM(); - cliPrintLine("PWM stopped"); + pinioSetDuty(0, 0); + cliPrintLine("PWM stopped on channel 0"); + return; + } + + // Find second argument (space-separated) + char *dutyStr = strchr(cmdline, ' '); + if (dutyStr) { + // Two args: channel duty + channel = fastA2I(cmdline); + dutyStr++; + duty = fastA2I(dutyStr); } else { - i = fastA2I(cmdline); - ledPinStartPWM(i); - cliPrintLinef("PWM started: %d%%",i); + // One arg: duty on channel 0 + duty = fastA2I(cmdline); + } + + if (channel < 0 || channel > PINIO_COUNT) { + cliPrintLinef("Error: channel must be 0-%d", PINIO_COUNT); + return; + } + if (duty < 0 || duty > 100) { + cliPrintLine("Error: duty must be 0-100"); + return; + } + +#ifdef USE_LED_STRIP + if (channel == PINIO_COUNT) { + ws2811SetIdleHigh(duty > 0); + cliPrintLinef("LED idle %s", duty > 0 ? "HIGH" : "LOW"); + return; } -} #endif + pinioSetDuty(channel, (uint8_t)duty); + cliPrintLinef("PWM ch %d: %d%%", channel, duty); +} +#endif // USE_PINIO + static void cliDelay(char* cmdLine) { int ms = 0; if (isEmpty(cmdLine)) { @@ -3188,6 +3223,8 @@ static void cliTimerOutputMode(char *cmdline) mode = OUTPUT_MODE_SERVOS; } else if(!sl_strcasecmp("LED", tok)) { mode = OUTPUT_MODE_LED; + } else if(!sl_strcasecmp("PINIO", tok)) { + mode = OUTPUT_MODE_PINIO; } else { cliShowParseError(); return; @@ -4880,7 +4917,9 @@ const clicmd_t cmdTable[] = { CLI_COMMAND_DEF("help", NULL, NULL, cliHelp), #ifdef USE_LED_STRIP CLI_COMMAND_DEF("led", "configure leds", NULL, cliLed), - CLI_COMMAND_DEF("ledpinpwm", "start/stop PWM on LED pin, 0..100 duty ratio", "[]\r\n", cliLedPinPWM), +#endif +#ifdef USE_PINIO + CLI_COMMAND_DEF("piniopwm", "set PINIO PWM duty cycle", "[] \r\n", cliPinioPwm), #endif CLI_COMMAND_DEF("map", "configure rc channel order", "[]", cliMap), CLI_COMMAND_DEF("memory", "view memory usage", NULL, cliMemory), @@ -4941,7 +4980,7 @@ const clicmd_t cmdTable[] = { #ifdef USE_OSD CLI_COMMAND_DEF("osd_layout", "get or set the layout of OSD items", "[ [ [ []]]]", cliOsdLayout), #endif - CLI_COMMAND_DEF("timer_output_mode", "get or set the outputmode for a given timer.", "[ []]", cliTimerOutputMode), + CLI_COMMAND_DEF("timer_output_mode", "get or set the outputmode for a given timer.", "[ []]", cliTimerOutputMode), }; static void cliHelp(char *cmdline) diff --git a/src/main/fc/fc_msp.c b/src/main/fc/fc_msp.c index 45379d1caa4..4b97d6df8f8 100644 --- a/src/main/fc/fc_msp.c +++ b/src/main/fc/fc_msp.c @@ -55,6 +55,9 @@ #include "drivers/osd.h" #include "drivers/osd_symbols.h" #include "drivers/pwm_mapping.h" +#ifdef USE_PINIO +#include "drivers/pinio.h" +#endif #include "drivers/sdcard/sdcard.h" #include "drivers/serial.h" #include "drivers/system.h" @@ -1681,12 +1684,28 @@ static bool mspFcProcessOutCommand(uint16_t cmdMSP, sbuf_t *dst, mspPostProcessF sbufWriteU8(dst, timer2id(timerHardware[i].tim)); #endif sbufWriteU32(dst, timerHardware[i].usageFlags); - #if defined(SITL_BUILD) || !defined(WS2811_PIN) + #if defined(SITL_BUILD) sbufWriteU8(dst, 0); #else - // Extra label to help identify repurposed PINs. - // Eventually, we can try to add more labels for PPM pins, etc. - sbufWriteU8(dst, timerHardware[i].tag == led_tag ? PIN_LABEL_LED : PIN_LABEL_NONE); + { + uint8_t specialLabel = PIN_LABEL_NONE; + #if defined(WS2811_PIN) + if (timerHardware[i].tag == led_tag) { + specialLabel = PIN_LABEL_LED; + } + #endif + #ifdef USE_PINIO + if (specialLabel == PIN_LABEL_NONE) { + for (int j = 0; j < pinioHardwareCount; j++) { + if (timerHardware[i].tag == pinioHardware[j].ioTag) { + specialLabel = PIN_LABEL_PINIO_BASE + j; + break; + } + } + } + #endif + sbufWriteU8(dst, specialLabel); + } #endif } } diff --git a/src/main/fc/fc_tasks.c b/src/main/fc/fc_tasks.c index 4ca125d5c6a..5cfb7dec86d 100755 --- a/src/main/fc/fc_tasks.c +++ b/src/main/fc/fc_tasks.c @@ -420,7 +420,7 @@ void fcTasksInit(void) #endif #endif #ifdef USE_RCDEVICE -#ifdef USE_LED_STRIP +#ifdef USE_PINIO setTaskEnabled(TASK_RCDEVICE, rcdeviceIsEnabled() || osdJoystickEnabled()); #else setTaskEnabled(TASK_RCDEVICE, rcdeviceIsEnabled()); diff --git a/src/main/fc/settings.yaml b/src/main/fc/settings.yaml index 1e1932531e5..bdcfa5a348e 100644 --- a/src/main/fc/settings.yaml +++ b/src/main/fc/settings.yaml @@ -189,9 +189,6 @@ tables: - name: nav_mc_althold_throttle values: ["STICK", "MID_STICK", "HOVER"] enum: navMcAltHoldThrottle_e - - name: led_pin_pwm_mode - values: ["SHARED_LOW", "SHARED_HIGH", "LOW", "HIGH"] - enum: led_pin_pwm_mode_e - name: gyro_filter_mode values: ["OFF", "STATIC", "DYNAMIC", "ADAPTIVE"] enum: gyroFilterType_e @@ -4264,27 +4261,22 @@ groups: field: attnFilterCutoff max: 100 - - name: PG_LEDPIN_CONFIG - type: ledPinConfig_t - headers: ["drivers/light_ws2811strip.h"] - members: - - name: led_pin_pwm_mode - condition: USE_LED_STRIP - description: "PWM mode of LED pin." - default_value: "SHARED_LOW" - field: led_pin_pwm_mode - table: led_pin_pwm_mode - - name: PG_OSD_JOYSTICK_CONFIG type: osdJoystickConfig_t headers: ["io/osd_joystick.h"] - condition: USE_RCDEVICE && USE_LED_STRIP + condition: USE_RCDEVICE && USE_PINIO members: - name: osd_joystick_enabled description: "Enable OSD Joystick emulation" default_value: OFF field: osd_joystick_enabled type: bool + - name: osd_joystick_pinio_channel + description: "PINIO channel index (0-3) for the camera OSD control pin" + default_value: 0 + field: pinio_channel + min: 0 + max: 3 - name: osd_joystick_down description: "PWM value for DOWN key" default_value: 0 diff --git a/src/main/flight/mixer.h b/src/main/flight/mixer.h index 12688bd2c09..6c4370d4176 100644 --- a/src/main/flight/mixer.h +++ b/src/main/flight/mixer.h @@ -48,7 +48,8 @@ typedef enum { OUTPUT_MODE_AUTO = 0, OUTPUT_MODE_MOTORS, OUTPUT_MODE_SERVOS, - OUTPUT_MODE_LED + OUTPUT_MODE_LED, + OUTPUT_MODE_PINIO } outputMode_e; typedef struct motorAxisCorrectionLimits_s { diff --git a/src/main/io/osd_joystick.c b/src/main/io/osd_joystick.c index c1d9dee5a5a..def03c66590 100644 --- a/src/main/io/osd_joystick.c +++ b/src/main/io/osd_joystick.c @@ -17,7 +17,7 @@ #include "fc/runtime_config.h" #include "drivers/time.h" -#include "drivers/light_ws2811strip.h" +#include "drivers/pinio.h" #include "io/serial.h" #include "io/rcdevice.h" @@ -25,13 +25,14 @@ #include "osd_joystick.h" #ifdef USE_RCDEVICE -#ifdef USE_LED_STRIP +#ifdef USE_PINIO -PG_REGISTER_WITH_RESET_TEMPLATE(osdJoystickConfig_t, osdJoystickConfig, PG_OSD_JOYSTICK_CONFIG, 0); +PG_REGISTER_WITH_RESET_TEMPLATE(osdJoystickConfig_t, osdJoystickConfig, PG_OSD_JOYSTICK_CONFIG, 1); PG_RESET_TEMPLATE(osdJoystickConfig_t, osdJoystickConfig, .osd_joystick_enabled = SETTING_OSD_JOYSTICK_ENABLED_DEFAULT, + .pinio_channel = SETTING_OSD_JOYSTICK_PINIO_CHANNEL_DEFAULT, .osd_joystick_down = SETTING_OSD_JOYSTICK_DOWN_DEFAULT, .osd_joystick_up = SETTING_OSD_JOYSTICK_UP_DEFAULT, .osd_joystick_left = SETTING_OSD_JOYSTICK_LEFT_DEFAULT, @@ -45,28 +46,29 @@ bool osdJoystickEnabled(void) { void osdJoystickSimulate5KeyButtonPress(uint8_t operation) { + const int ch = osdJoystickConfig()->pinio_channel; switch (operation) { case RCDEVICE_CAM_KEY_ENTER: - ledPinStartPWM( osdJoystickConfig()->osd_joystick_enter ); + pinioSetDuty(ch, osdJoystickConfig()->osd_joystick_enter); break; case RCDEVICE_CAM_KEY_LEFT: - ledPinStartPWM( osdJoystickConfig()->osd_joystick_left ); + pinioSetDuty(ch, osdJoystickConfig()->osd_joystick_left); break; case RCDEVICE_CAM_KEY_UP: - ledPinStartPWM( osdJoystickConfig()->osd_joystick_up ); + pinioSetDuty(ch, osdJoystickConfig()->osd_joystick_up); break; case RCDEVICE_CAM_KEY_RIGHT: - ledPinStartPWM( osdJoystickConfig()->osd_joystick_right ); + pinioSetDuty(ch, osdJoystickConfig()->osd_joystick_right); break; case RCDEVICE_CAM_KEY_DOWN: - ledPinStartPWM( osdJoystickConfig()->osd_joystick_down ); + pinioSetDuty(ch, osdJoystickConfig()->osd_joystick_down); break; } } void osdJoystickSimulate5KeyButtonRelease(void) { - ledPinStopPWM(); + pinioSetDuty(osdJoystickConfig()->pinio_channel, 0); } diff --git a/src/main/io/osd_joystick.h b/src/main/io/osd_joystick.h index 574b8e3b776..15f52134692 100644 --- a/src/main/io/osd_joystick.h +++ b/src/main/io/osd_joystick.h @@ -2,11 +2,12 @@ #include "config/parameter_group.h" -#ifdef USE_RCDEVICE -#ifdef USE_LED_STRIP +#ifdef USE_RCDEVICE +#ifdef USE_PINIO typedef struct osdJoystickConfig_s { bool osd_joystick_enabled; + uint8_t pinio_channel; // PINIO index for the cam-control output pin uint8_t osd_joystick_down; uint8_t osd_joystick_up; uint8_t osd_joystick_left; diff --git a/src/main/io/rcdevice_cam.c b/src/main/io/rcdevice_cam.c index 0edbc8bf979..1388ca170ee 100644 --- a/src/main/io/rcdevice_cam.c +++ b/src/main/io/rcdevice_cam.c @@ -49,7 +49,7 @@ bool waitingDeviceResponse = false; static bool isFeatureSupported(uint8_t feature) { #ifndef UNIT_TEST -#ifdef USE_LED_STRIP +#ifdef USE_PINIO if (!rcdeviceIsEnabled() && osdJoystickEnabled() ) { return true; } @@ -117,7 +117,7 @@ static void rcdeviceCameraControlProcess(void) switchStates[switchIndex].isActivated = true; } #ifndef UNIT_TEST -#ifdef USE_LED_STRIP +#ifdef USE_PINIO else if ((behavior1 != RCDEVICE_PROTOCOL_CAM_CTRL_UNKNOWN_CAMERA_OPERATION) && osdJoystickEnabled()) { switch (behavior1) { case RCDEVICE_PROTOCOL_CAM_CTRL_SIMULATE_WIFI_BTN: @@ -137,7 +137,7 @@ static void rcdeviceCameraControlProcess(void) UNUSED(behavior1); } else { #ifndef UNIT_TEST -#ifdef USE_LED_STRIP +#ifdef USE_PINIO if (osdJoystickEnabled() && switchStates[switchIndex].isActivated) { osdJoystickSimulate5KeyButtonRelease(); } @@ -275,7 +275,7 @@ static void rcdevice5KeySimulationProcess(timeUs_t currentTimeUs) waitingDeviceResponse = true; } #ifndef UNIT_TEST -#ifdef USE_LED_STRIP +#ifdef USE_PINIO else if (osdJoystickEnabled()) { osdJoystickSimulate5KeyButtonRelease(); isButtonPressed = false; @@ -320,7 +320,7 @@ static void rcdevice5KeySimulationProcess(timeUs_t currentTimeUs) waitingDeviceResponse = true; } #ifndef UNIT_TEST -#ifdef USE_LED_STRIP +#ifdef USE_PINIO else if (osdJoystickEnabled()) { if ( key == RCDEVICE_CAM_KEY_CONNECTION_OPEN ) { rcdeviceInMenu = true; diff --git a/src/main/programming/logic_condition.c b/src/main/programming/logic_condition.c index edba68b8e94..5336a54ad12 100644 --- a/src/main/programming/logic_condition.c +++ b/src/main/programming/logic_condition.c @@ -59,6 +59,7 @@ #include "io/vtx.h" #include "drivers/vtx_common.h" #include "drivers/light_ws2811strip.h" +#include "drivers/pinio.h" PG_REGISTER_ARRAY_WITH_RESET_FN(logicCondition_t, MAX_LOGIC_CONDITIONS, logicConditions, PG_LOGIC_CONDITIONS, 4); @@ -506,15 +507,19 @@ static int logicConditionCompute( } break; +#ifdef USE_PINIO + case LOGIC_CONDITION_PINIO_PWM: + // operandA = channel, operandB = duty cycle (0-100) + // Channels 0..PINIO_COUNT-1 = hardware PINIO (PWM capable) + // Channel PINIO_COUNT = LED strip idle level (binary: >0 = HIGH) #ifdef USE_LED_STRIP - case LOGIC_CONDITION_LED_PIN_PWM: - - if (operandA >=0 && operandA <= 100) { - ledPinStartPWM((uint8_t)operandA); - } else { - ledPinStopPWM(); + if (operandA == PINIO_COUNT) { + ws2811SetIdleHigh(operandB > 0); + return operandB; } - return operandA; +#endif + pinioSetDuty(operandA, (uint8_t)constrain(operandB, 0, 100)); + return operandB; break; #endif #ifdef USE_GPS_FIX_ESTIMATION diff --git a/src/main/programming/logic_condition.h b/src/main/programming/logic_condition.h index 74ea96c98ee..859096aeee5 100644 --- a/src/main/programming/logic_condition.h +++ b/src/main/programming/logic_condition.h @@ -81,7 +81,7 @@ typedef enum { LOGIC_CONDITION_TIMER = 49, LOGIC_CONDITION_DELTA = 50, LOGIC_CONDITION_APPROX_EQUAL = 51, - LOGIC_CONDITION_LED_PIN_PWM = 52, + LOGIC_CONDITION_PINIO_PWM = 52, LOGIC_CONDITION_DISABLE_GPS_FIX = 53, LOGIC_CONDITION_RESET_MAG_CALIBRATION = 54, LOGIC_CONDITION_SET_GIMBAL_SENSITIVITY = 55,