Compare commits
14 Commits
0f62418d93
...
feature/es
| Author | SHA1 | Date | |
|---|---|---|---|
| 3e20b79de7 | |||
| 40d2880fdc | |||
| 4ccfc5128b | |||
| 5cc2bd0b1d | |||
| 284553f3f8 | |||
| e25971af89 | |||
| 5796b28e1a | |||
| 12a8710a2f | |||
| 468d2cba74 | |||
| d2d5d7dc4b | |||
| f1aac6611d | |||
| 733b05eaeb | |||
| 715d50c255 | |||
| 883fff95dd |
1
.gitignore
vendored
1
.gitignore
vendored
@ -291,3 +291,4 @@ dkms.conf
|
||||
|
||||
.vscode/settings.json
|
||||
sdkconfig.defaults
|
||||
.clangd
|
||||
|
||||
35
README.md
35
README.md
@ -7,7 +7,7 @@ Professional LED controller firmware for ESP32. Designed for model aircraft with
|
||||
### Hardware Support
|
||||
- **ESP32 DevKitC** and **ESP32-C3 MINI** Development Board
|
||||
- Dual WS2812B LED strip support (configurable GPIOs)
|
||||
- PWM signal input for RC control
|
||||
- PWM or SBUS signal input for RC control
|
||||
- Real-time LED animation at 60 FPS
|
||||
|
||||
### Animation Modes
|
||||
@ -35,7 +35,7 @@ led-controller-firmware/
|
||||
│ ├── control.c/h # initialization
|
||||
│ ├── config.c/h # NVS
|
||||
│ ├── led.c/h # WS2812B control (RMT driver)
|
||||
│ ├── rcsignal.c/h # PWM signal reading
|
||||
│ ├── rcsignal.c/h # PWM/SBUS signal reading
|
||||
│ ├── localbtn.c/h # Local btn reading
|
||||
│ └── animation.c/h # LED animation patterns
|
||||
├── CMakeLists.txt
|
||||
@ -80,7 +80,7 @@ ESP32 Pin -> Component
|
||||
----------- ----------
|
||||
GPIO XX -> WS2812B Strip A Data
|
||||
GPIO XX -> WS2812B Strip B Data
|
||||
GPIO XX -> RC PWM Signal
|
||||
GPIO XX -> RC PWM/SBUS Signal
|
||||
GPIO XX -> Local button Signal
|
||||
GND -> Common Ground
|
||||
5V -> LED Strip Power (if current < 500mA)
|
||||
@ -93,11 +93,21 @@ GND -> Common Ground
|
||||
- Add 100-500µF capacitor near strips
|
||||
- Add 330Ω resistor on data line
|
||||
|
||||
### PWM Signal
|
||||
|
||||
### PWM Signal Mode
|
||||
- Standard RC PWM: 1000-2000µs pulse width
|
||||
- 1500µs threshold for mode switching
|
||||
- Rising edge >1500µs after <1500µs triggers next mode
|
||||
|
||||
|
||||
### SBUS Mode (FrSky X4R-SB)
|
||||
```
|
||||
X4R-SB SBUS/CH4 → ESP32 UART1 RX Pin (configured GPIO)
|
||||
```
|
||||
|
||||
**Note**: The FrSky X4R-SB outputs inverted SBUS. The module automatically handles this inversion.
|
||||
|
||||
|
||||
## Development
|
||||
|
||||
### Adding New Animations
|
||||
@ -118,6 +128,23 @@ idf.py monitor
|
||||
# Exit monitor: Ctrl+]
|
||||
```
|
||||
|
||||
#### Debug Output (SBUS Mode Only)
|
||||
|
||||
```c
|
||||
// Print all 16 channels to console
|
||||
rcsignal_debug_print_channels();
|
||||
```
|
||||
|
||||
Example output:
|
||||
```
|
||||
I (12345) RCSIGNAL: SBUS Channels:
|
||||
I (12345) RCSIGNAL: CH1: 992 CH2: 992 CH3: 172 CH4: 1811
|
||||
I (12345) RCSIGNAL: CH5: 992 CH6: 992 CH7: 992 CH8: 992
|
||||
I (12345) RCSIGNAL: CH9: 992 CH10: 992 CH11: 992 CH12: 992
|
||||
I (12345) RCSIGNAL: CH13: 992 CH14: 992 CH15: 992 CH16: 992
|
||||
I (12345) RCSIGNAL: Trigger channel (CH4): 1811
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
See [LICENSE](LICENSE)
|
||||
|
||||
233
main/animation.c
233
main/animation.c
@ -42,12 +42,6 @@ static int16_t beatsin16(uint8_t bpm, int16_t min_val, int16_t max_val)
|
||||
return result;
|
||||
}
|
||||
|
||||
// Beat calculation helper (beatsin8 variant)
|
||||
static uint8_t beatsin8(uint8_t bpm, uint8_t min_val, uint8_t max_val)
|
||||
{
|
||||
return (uint8_t)beatsin16(bpm, min_val, max_val);
|
||||
}
|
||||
|
||||
// Random helper
|
||||
static uint8_t random8(void)
|
||||
{
|
||||
@ -102,17 +96,21 @@ static void anim_rainbow(void)
|
||||
// Rainbow generator
|
||||
uint16_t num_leds_a = led_get_num_leds_a();
|
||||
uint16_t num_leds_b = led_get_num_leds_b();
|
||||
uint16_t num_leds = num_leds_a + num_leds_b;
|
||||
|
||||
for (uint16_t i = 0; i < num_leds_a; i++)
|
||||
for (uint16_t i = 0; i < num_leds; i++)
|
||||
{
|
||||
hsv_t hsv = {(uint8_t)(global_hue + (i * 7)), 255, 255};
|
||||
led_set_pixel_a(i, led_hsv_to_rgb(hsv));
|
||||
}
|
||||
rgb_t color = led_hsv_to_rgb(hsv);
|
||||
|
||||
for (uint16_t i = 0; i < num_leds_b; i++)
|
||||
{
|
||||
hsv_t hsv = {(uint8_t)(global_hue + (i * 7)), 255, 255};
|
||||
led_set_pixel_b(i, led_hsv_to_rgb(hsv));
|
||||
if (i < num_leds_a)
|
||||
{
|
||||
led_set_pixel_a(num_leds_a - i - 1, color);
|
||||
}
|
||||
else
|
||||
{
|
||||
led_set_pixel_b(i - num_leds_a, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -138,7 +136,7 @@ static void add_glitter(uint8_t chance_of_glitter)
|
||||
static void anim_rainbow_glitter(void)
|
||||
{
|
||||
anim_rainbow();
|
||||
add_glitter(80);
|
||||
add_glitter(255);
|
||||
}
|
||||
|
||||
static void anim_confetti(void)
|
||||
@ -149,16 +147,16 @@ static void anim_confetti(void)
|
||||
uint16_t num_leds = led_get_num_leds_a() + led_get_num_leds_b();
|
||||
uint16_t pos = random16(num_leds);
|
||||
|
||||
hsv_t hsv = {(uint8_t)(global_hue + random8()), 200, 255};
|
||||
hsv_t hsv = {(uint8_t)(global_hue + random8()), 255, 255};
|
||||
rgb_t color = led_hsv_to_rgb(hsv);
|
||||
|
||||
if (pos < led_get_num_leds_a())
|
||||
{
|
||||
led_add_pixel_a(pos, color);
|
||||
led_set_pixel_a(led_get_num_leds_a() - pos - 1, color);
|
||||
}
|
||||
else
|
||||
{
|
||||
led_add_pixel_b(pos - led_get_num_leds_a(), color);
|
||||
led_set_pixel_b(pos - led_get_num_leds_a(), color);
|
||||
}
|
||||
}
|
||||
|
||||
@ -183,40 +181,14 @@ static void anim_sinelon(void)
|
||||
}
|
||||
}
|
||||
|
||||
static void anim_bpm(void)
|
||||
{
|
||||
// Colored stripes pulsing at 33 BPM
|
||||
uint8_t bpm = 33;
|
||||
uint8_t beat = beatsin8(bpm, 64, 255);
|
||||
|
||||
uint16_t num_leds_a = led_get_num_leds_a();
|
||||
uint16_t num_leds_b = led_get_num_leds_b();
|
||||
|
||||
// PartyColors palette simulation
|
||||
const uint8_t palette_colors[] = {
|
||||
170, 240, 90, 150, 210, 30, 180, 0,
|
||||
210, 255, 150, 240, 255, 60, 255, 120};
|
||||
|
||||
for (uint16_t i = 0; i < num_leds_a; i++)
|
||||
{
|
||||
uint8_t color_index = (global_hue + (i * 2)) & 0x0F;
|
||||
uint8_t brightness = beat - global_hue + (i * 10);
|
||||
hsv_t hsv = {palette_colors[color_index], 255, brightness};
|
||||
led_set_pixel_a(i, led_hsv_to_rgb(hsv));
|
||||
}
|
||||
|
||||
for (uint16_t i = 0; i < num_leds_b; i++)
|
||||
{
|
||||
uint8_t color_index = (global_hue + ((i + num_leds_a) * 2)) & 0x0F;
|
||||
uint8_t brightness = beat - global_hue + ((i + num_leds_a) * 10);
|
||||
hsv_t hsv = {palette_colors[color_index], 255, brightness};
|
||||
led_set_pixel_b(i, led_hsv_to_rgb(hsv));
|
||||
}
|
||||
}
|
||||
|
||||
static void anim_navigation(void)
|
||||
{
|
||||
// Navigation lights: left red, right green
|
||||
// Aviation navigation lights with strobe overlay:
|
||||
// - Red: Port (left) wingtip - steady
|
||||
// - Green: Starboard (right) wingtip - steady
|
||||
// - White strobe: Overlays outer nav lights with bright flashes
|
||||
|
||||
static uint8_t strobe_counter = 0;
|
||||
led_clear_all();
|
||||
|
||||
uint16_t num_leds_a = led_get_num_leds_a();
|
||||
@ -224,22 +196,35 @@ static void anim_navigation(void)
|
||||
|
||||
rgb_t red = {255, 0, 0};
|
||||
rgb_t green = {0, 255, 0};
|
||||
rgb_t white = {255, 255, 255};
|
||||
rgb_t black = {0, 0, 0};
|
||||
|
||||
// Side red (last 3 LEDs of strip A)
|
||||
// Anti-collision strobe pattern: Double flash at ~1 Hz
|
||||
// Flash duration: 3 frames (~50ms) for high-intensity effect
|
||||
bool first_flash = (strobe_counter < 3);
|
||||
bool second_flash = (strobe_counter >= 7 && strobe_counter < 10);
|
||||
bool strobe_active = (first_flash || second_flash);
|
||||
|
||||
// Port (left) - Red navigation light OR white strobe (outer 3 LEDs of strip A)
|
||||
if (num_leds_a >= 3)
|
||||
{
|
||||
led_set_pixel_a(num_leds_a - 1, red);
|
||||
rgb_t color_a = strobe_active ? white : black;
|
||||
led_set_pixel_a(num_leds_a - 1, color_a);
|
||||
led_set_pixel_a(num_leds_a - 2, red);
|
||||
led_set_pixel_a(num_leds_a - 3, red);
|
||||
}
|
||||
|
||||
// Side green (last 3 LEDs of strip B)
|
||||
// Starboard (right) - Green navigation light OR white strobe (outer 3 LEDs of strip B)
|
||||
if (num_leds_b >= 3)
|
||||
{
|
||||
led_set_pixel_b(num_leds_b - 1, green);
|
||||
rgb_t color_b = strobe_active ? white : black;
|
||||
led_set_pixel_b(num_leds_b - 1, color_b);
|
||||
led_set_pixel_b(num_leds_b - 2, green);
|
||||
led_set_pixel_b(num_leds_b - 3, green);
|
||||
}
|
||||
|
||||
// Strobe cycle: 90 frames = 1.5 second at 60 FPS
|
||||
strobe_counter = (strobe_counter + 1) % 90;
|
||||
}
|
||||
|
||||
static void anim_chase(void)
|
||||
@ -247,25 +232,45 @@ static void anim_chase(void)
|
||||
// Red dot sweeping with trailing dots
|
||||
led_clear_all();
|
||||
|
||||
uint16_t num_leds = led_get_num_leds_a() + led_get_num_leds_b();
|
||||
int16_t pos = beatsin16(40, 0, num_leds - 1);
|
||||
uint16_t num_leds_a = led_get_num_leds_a();
|
||||
uint16_t num_leds_b = led_get_num_leds_b();
|
||||
uint16_t total_leds = num_leds_a + num_leds_b;
|
||||
|
||||
// Get oscillating position across both strips
|
||||
int16_t center_pos = beatsin16(40, 0, total_leds - 1);
|
||||
|
||||
rgb_t red = {255, 0, 0};
|
||||
|
||||
// Set main dot and trailing dots
|
||||
for (int offset = -2; offset <= 2; offset++)
|
||||
// Draw center dot with dimmed trailing dots (3 dots total: center ±1)
|
||||
for (int8_t offset = -1; offset <= 1; offset++)
|
||||
{
|
||||
int16_t led_pos = pos + offset;
|
||||
if (led_pos >= 0 && led_pos < num_leds)
|
||||
int16_t led_pos = center_pos + offset;
|
||||
|
||||
// Skip if position is out of bounds
|
||||
if (led_pos < 0 || led_pos >= total_leds)
|
||||
continue;
|
||||
|
||||
// Calculate brightness based on distance from center
|
||||
uint8_t brightness = (offset == 0) ? 255 : 32; // Center: full, trailing: 12%
|
||||
|
||||
// Create dimmed color
|
||||
rgb_t dimmed_red = {
|
||||
(red.r * brightness) / 255,
|
||||
(red.g * brightness) / 255,
|
||||
(red.b * brightness) / 255};
|
||||
|
||||
// Map virtual position to physical LED
|
||||
if (led_pos < num_leds_a)
|
||||
{
|
||||
if (led_pos < led_get_num_leds_a())
|
||||
{
|
||||
led_set_pixel_a(led_pos, red);
|
||||
}
|
||||
else
|
||||
{
|
||||
led_set_pixel_b(led_pos - led_get_num_leds_a(), red);
|
||||
}
|
||||
// Strip A (mirrored: position 0 maps to last LED)
|
||||
uint16_t strip_a_index = num_leds_a - led_pos - 1;
|
||||
led_set_pixel_a(strip_a_index, dimmed_red);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Strip B (direct mapping)
|
||||
uint16_t strip_b_index = led_pos - num_leds_a;
|
||||
led_set_pixel_b(strip_b_index, dimmed_red);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -275,26 +280,46 @@ static void anim_chase_rgb(void)
|
||||
// RGB cycling dot sweeping with trailing dots
|
||||
led_clear_all();
|
||||
|
||||
uint16_t num_leds = led_get_num_leds_a() + led_get_num_leds_b();
|
||||
int16_t pos = beatsin16(40, 0, num_leds - 1);
|
||||
uint16_t num_leds_a = led_get_num_leds_a();
|
||||
uint16_t num_leds_b = led_get_num_leds_b();
|
||||
uint16_t total_leds = num_leds_a + num_leds_b;
|
||||
|
||||
// Get oscillating position across both strips
|
||||
int16_t center_pos = beatsin16(40, 0, total_leds - 1);
|
||||
|
||||
hsv_t hsv = {global_hue, 255, 192};
|
||||
rgb_t color = led_hsv_to_rgb(hsv);
|
||||
|
||||
// Set main dot and trailing dots
|
||||
for (int offset = -2; offset <= 2; offset++)
|
||||
// Draw center dot with dimmed trailing dots (3 dots total: center ±1)
|
||||
for (int8_t offset = -1; offset <= 1; offset++)
|
||||
{
|
||||
int16_t led_pos = pos + offset;
|
||||
if (led_pos >= 0 && led_pos < num_leds)
|
||||
int16_t led_pos = center_pos + offset;
|
||||
|
||||
// Skip if position is out of bounds
|
||||
if (led_pos < 0 || led_pos >= total_leds)
|
||||
continue;
|
||||
|
||||
// Calculate brightness based on distance from center
|
||||
uint8_t brightness = (offset == 0) ? 255 : 32; // Center: full, trailing: 12%
|
||||
|
||||
// Create dimmed color
|
||||
rgb_t dimmed_color = {
|
||||
(color.r * brightness) / 255,
|
||||
(color.g * brightness) / 255,
|
||||
(color.b * brightness) / 255};
|
||||
|
||||
// Map virtual position to physical LED
|
||||
if (led_pos < num_leds_a)
|
||||
{
|
||||
if (led_pos < led_get_num_leds_a())
|
||||
{
|
||||
led_add_pixel_a(led_pos, color);
|
||||
}
|
||||
else
|
||||
{
|
||||
led_add_pixel_b(led_pos - led_get_num_leds_a(), color);
|
||||
}
|
||||
// Strip A (mirrored: position 0 maps to last LED)
|
||||
uint16_t strip_a_index = num_leds_a - led_pos - 1;
|
||||
led_set_pixel_a(strip_a_index, dimmed_color);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Strip B (direct mapping)
|
||||
uint16_t strip_b_index = led_pos - num_leds_a;
|
||||
led_set_pixel_b(strip_b_index, dimmed_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -305,18 +330,26 @@ static void anim_random(void)
|
||||
uint16_t num_leds = led_get_num_leds_a() + led_get_num_leds_b();
|
||||
uint16_t random_pos = random16(num_leds);
|
||||
|
||||
// Randomly clear all (rare event)
|
||||
if (random_pos == num_leds - 1 && random8() > 200)
|
||||
{
|
||||
led_clear_all();
|
||||
return;
|
||||
}
|
||||
|
||||
// Set random LED to random color
|
||||
rgb_t random_color = {
|
||||
random8(),
|
||||
random8(),
|
||||
random8()};
|
||||
0,
|
||||
0,
|
||||
0};
|
||||
|
||||
// Set random LED to random basis color
|
||||
switch (random16(3))
|
||||
{
|
||||
case 0:
|
||||
random_color.r = 255;
|
||||
break;
|
||||
case 1:
|
||||
random_color.g = 255;
|
||||
break;
|
||||
case 2:
|
||||
random_color.b = 255;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (random_pos < led_get_num_leds_a())
|
||||
{
|
||||
@ -379,31 +412,28 @@ void animation_update(void)
|
||||
anim_white();
|
||||
break;
|
||||
case ANIM_RAINBOW:
|
||||
// anim_rainbow();
|
||||
anim_rainbow();
|
||||
break;
|
||||
case ANIM_RAINBOW_GLITTER:
|
||||
// anim_rainbow_glitter();
|
||||
anim_rainbow_glitter();
|
||||
break;
|
||||
case ANIM_CONFETTI:
|
||||
// anim_confetti();
|
||||
anim_confetti();
|
||||
break;
|
||||
case ANIM_SINELON:
|
||||
anim_sinelon();
|
||||
break;
|
||||
case ANIM_BPM:
|
||||
// anim_bpm();
|
||||
break;
|
||||
case ANIM_NAVIGATION:
|
||||
anim_navigation();
|
||||
break;
|
||||
case ANIM_CHASE:
|
||||
// anim_chase();
|
||||
anim_chase();
|
||||
break;
|
||||
case ANIM_CHASE_RGB:
|
||||
// anim_chase_rgb();
|
||||
anim_chase_rgb();
|
||||
break;
|
||||
case ANIM_RANDOM:
|
||||
// anim_random();
|
||||
anim_random();
|
||||
break;
|
||||
default:
|
||||
anim_black();
|
||||
@ -425,7 +455,6 @@ const char *animation_get_mode_name(animation_mode_t mode)
|
||||
"Rainbow with Glitter",
|
||||
"Confetti",
|
||||
"Sinelon",
|
||||
"33BPM",
|
||||
"Navigation",
|
||||
"Chase",
|
||||
"Chase RGB",
|
||||
|
||||
@ -23,11 +23,10 @@ typedef enum {
|
||||
ANIM_RAINBOW_GLITTER = 6, // Rainbow with glitter
|
||||
ANIM_CONFETTI = 7, // Random colored speckles
|
||||
ANIM_SINELON = 8, // Colored dot sweeping (RGB cycling)
|
||||
ANIM_BPM = 9, // Colored stripes @ 33 BPM
|
||||
ANIM_NAVIGATION = 10, // Navigation lights (red left, green right)
|
||||
ANIM_CHASE = 11, // Red dot sweeping
|
||||
ANIM_CHASE_RGB = 12, // RGB cycling dot sweeping
|
||||
ANIM_RANDOM = 13, // Random mode
|
||||
ANIM_NAVIGATION = 9, // Navigation lights (red left, green right)
|
||||
ANIM_CHASE = 10, // Red dot sweeping
|
||||
ANIM_CHASE_RGB = 11, // RGB cycling dot sweeping
|
||||
ANIM_RANDOM = 12, // Random mode
|
||||
ANIM_MODE_COUNT
|
||||
} animation_mode_t;
|
||||
|
||||
|
||||
@ -22,12 +22,25 @@ static const char *TAG = "CONFIG";
|
||||
|
||||
#define HARDCODED_CONFIG
|
||||
#ifdef HARDCODED_CONFIG
|
||||
#define HARDCODED_CONFIG_LED_STRIP_A_PIN 12U
|
||||
#define HARDCODED_CONFIG_LED_STRIP_B_PIN 14U
|
||||
#define HARDCODED_CONFIG_LED_STRIP_A_COUNT 7U
|
||||
#define HARDCODED_CONFIG_LED_STRIP_B_COUNT 7U
|
||||
#define HARDCODED_CONFIG_PWM_PIN 13U
|
||||
#define HARDCODED_CONFIG_LOCALBTN_PIN GPIO_NUM_0
|
||||
#define HARDCODED_CONFIG_LED_STRIP_A_PIN 2U
|
||||
#define HARDCODED_CONFIG_LED_STRIP_B_PIN 3U
|
||||
#define HARDCODED_CONFIG_LED_STRIP_A_COUNT 9U
|
||||
#define HARDCODED_CONFIG_LED_STRIP_B_COUNT 9U
|
||||
#define HARDCODED_CONFIG_RC_SIGNAL_PIN 1U
|
||||
#define HARDCODED_CONFIG_USE_SBUS_MODE true
|
||||
#define HARDCODED_CONFIG_SBUS_TRIGGER_CHANNEL 3U // Channel 4
|
||||
#define HARDCODED_CONFIG_SBUS_THRESHOLD_LOW 800U
|
||||
#define HARDCODED_CONFIG_SBUS_THRESHOLD_HIGH 1100U
|
||||
#define HARDCODED_CONFIG_SBUS_INVERTED true // Set inverted RX for FrSky receivers (they output inverted SBUS)
|
||||
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
#define HARDCODED_CONFIG_LOCALBTN_PIN 9
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32)
|
||||
#define HARDCODED_CONFIG_LOCALBTN_PIN 0
|
||||
#else
|
||||
#error "Unsupported target: BOOT button GPIO not defined"
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
// Global state
|
||||
@ -36,8 +49,14 @@ static config_t current_config = {
|
||||
.led_pin_strip_b = -1,
|
||||
.led_count_strip_a = -1,
|
||||
.led_count_strip_b = -1,
|
||||
.pwm_pin = -1,
|
||||
.localBtn_pin = -1};
|
||||
.rc_signal_pin = -1,
|
||||
.localBtn_pin = -1,
|
||||
.use_sbus_mode = false,
|
||||
.sbus_trigger_channel = 3,
|
||||
.sbus_threshold_low = 800,
|
||||
.sbus_threshold_high = 1100,
|
||||
.sbus_inverted = false,
|
||||
};
|
||||
|
||||
static void calculate_config_hash(const config_t *cfg, uint8_t *out_hash);
|
||||
|
||||
@ -74,13 +93,19 @@ static esp_err_t load_config_from_nvs(void)
|
||||
// We found a valid config
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
ESP_LOGI(TAG, "Loaded config from NVS");
|
||||
ESP_LOGI(TAG, " Strip A: GPIO%d", current_config.led_pin_strip_a);
|
||||
ESP_LOGI(TAG, " Strip B: GPIO%d", current_config.led_pin_strip_b);
|
||||
ESP_LOGI(TAG, " Strip A LED count: %d", current_config.led_count_strip_a);
|
||||
ESP_LOGI(TAG, " Strip B LED count: %d", current_config.led_count_strip_b);
|
||||
ESP_LOGI(TAG, " PWM Pin: GPIO%d", current_config.pwm_pin);
|
||||
ESP_LOGI(TAG, " RC Signal Pin: GPIO%d", current_config.rc_signal_pin);
|
||||
ESP_LOGI(TAG, " RC Signal Mode: %s", current_config.use_sbus_mode ? "SBUS" : "PWM");
|
||||
if (current_config.use_sbus_mode)
|
||||
{
|
||||
ESP_LOGI(TAG, " SBUS Trigger Channel: %d", current_config.sbus_trigger_channel + 1);
|
||||
ESP_LOGI(TAG, " SBUS Thresholds: %d / %d", current_config.sbus_threshold_low, current_config.sbus_threshold_high);
|
||||
}
|
||||
ESP_LOGI(TAG, " Local btn Pin: GPIO%d", current_config.localBtn_pin);
|
||||
|
||||
return ESP_OK;
|
||||
@ -123,8 +148,12 @@ esp_err_t config_reset_config(void)
|
||||
current_config.led_pin_strip_b = -1;
|
||||
current_config.led_count_strip_a = -1;
|
||||
current_config.led_count_strip_b = -1;
|
||||
current_config.pwm_pin = -1;
|
||||
current_config.rc_signal_pin = -1;
|
||||
current_config.localBtn_pin = -1;
|
||||
current_config.use_sbus_mode = false;
|
||||
current_config.sbus_trigger_channel = 3;
|
||||
current_config.sbus_threshold_low = 800;
|
||||
current_config.sbus_threshold_high = 1100;
|
||||
|
||||
return save_config_to_nvs();
|
||||
}
|
||||
@ -135,8 +164,12 @@ void config_get_config(config_t *const cnf)
|
||||
cnf->led_pin_strip_b = current_config.led_pin_strip_b;
|
||||
cnf->led_count_strip_a = current_config.led_count_strip_a;
|
||||
cnf->led_count_strip_b = current_config.led_count_strip_b;
|
||||
cnf->pwm_pin = current_config.pwm_pin;
|
||||
cnf->rc_signal_pin = current_config.rc_signal_pin;
|
||||
cnf->localBtn_pin = current_config.localBtn_pin;
|
||||
cnf->use_sbus_mode = current_config.use_sbus_mode;
|
||||
cnf->sbus_trigger_channel = current_config.sbus_trigger_channel;
|
||||
cnf->sbus_threshold_low = current_config.sbus_threshold_low;
|
||||
cnf->sbus_threshold_high = current_config.sbus_threshold_high;
|
||||
}
|
||||
|
||||
esp_err_t config_init(void)
|
||||
@ -160,8 +193,13 @@ esp_err_t config_init(void)
|
||||
current_config.led_pin_strip_b = HARDCODED_CONFIG_LED_STRIP_B_PIN;
|
||||
current_config.led_count_strip_a = HARDCODED_CONFIG_LED_STRIP_A_COUNT;
|
||||
current_config.led_count_strip_b = HARDCODED_CONFIG_LED_STRIP_B_COUNT;
|
||||
current_config.pwm_pin = HARDCODED_CONFIG_PWM_PIN;
|
||||
current_config.rc_signal_pin = HARDCODED_CONFIG_RC_SIGNAL_PIN;
|
||||
current_config.localBtn_pin = HARDCODED_CONFIG_LOCALBTN_PIN;
|
||||
current_config.use_sbus_mode = HARDCODED_CONFIG_USE_SBUS_MODE;
|
||||
current_config.sbus_trigger_channel = HARDCODED_CONFIG_SBUS_TRIGGER_CHANNEL;
|
||||
current_config.sbus_threshold_low = HARDCODED_CONFIG_SBUS_THRESHOLD_LOW;
|
||||
current_config.sbus_threshold_high = HARDCODED_CONFIG_SBUS_THRESHOLD_HIGH;
|
||||
current_config.sbus_inverted = HARDCODED_CONFIG_SBUS_INVERTED;
|
||||
|
||||
save_config_to_nvs();
|
||||
#endif
|
||||
|
||||
@ -17,12 +17,20 @@
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
int8_t led_pin_strip_a; // GPIO pin for LED strip A (-1 = not configured)
|
||||
int8_t led_pin_strip_b; // GPIO pin for LED strip B (-1 = not configured)
|
||||
int8_t led_count_strip_a; // LED count for LED strip A (-1 = not configured)
|
||||
int8_t led_count_strip_b; // LED count for LED strip B (-1 = not configured)
|
||||
int8_t pwm_pin; // GPIO pin for PWM input (-1 = not configured)
|
||||
int8_t localBtn_pin; // GPIO pin for local btn input (-1 = not configured)
|
||||
int8_t led_pin_strip_a; // GPIO pin for LED strip A (-1 = not configured)
|
||||
int8_t led_pin_strip_b; // GPIO pin for LED strip B (-1 = not configured)
|
||||
int8_t led_count_strip_a; // LED count for LED strip A (-1 = not configured)
|
||||
int8_t led_count_strip_b; // LED count for LED strip B (-1 = not configured)
|
||||
int8_t rc_signal_pin; // GPIO pin for RC signal input (-1 = not configured)
|
||||
int8_t localBtn_pin; // GPIO pin for local btn input (-1 = not configured)
|
||||
|
||||
// RC Signal mode settings
|
||||
bool use_sbus_mode; // true = SBUS mode, false = PWM mode
|
||||
uint8_t sbus_trigger_channel; // SBUS channel for mode trigger (0-15, typically 3 for CH4)
|
||||
uint16_t sbus_threshold_low; // SBUS low threshold (default 800)
|
||||
uint16_t sbus_threshold_high; // SBUS high threshold (default 1100)
|
||||
bool sbus_inverted; // true = SBUS in inverted mode (like FrSky), false = normal mode
|
||||
|
||||
uint8_t hash[CONFIG_HASH_LEN]; // SHA256 Hash of config
|
||||
} config_t;
|
||||
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
#include "driver/gpio.h"
|
||||
|
||||
static const char *TAG = "CONTROL";
|
||||
static uint8_t current_animation_mode = 0;
|
||||
@ -64,8 +65,11 @@ esp_err_t control_init(void)
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Start ISR service
|
||||
ESP_ERROR_CHECK(gpio_install_isr_service(0));
|
||||
|
||||
// Initialize RC signal
|
||||
ret = rcsignal_init(current_config.pwm_pin);
|
||||
ret = rcsignal_init(¤t_config);
|
||||
if (ret != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "RC signal init failed: %s", esp_err_to_name(ret));
|
||||
|
||||
@ -194,7 +194,13 @@ static esp_err_t init_strip(led_strip_t *strip, int8_t pin, uint16_t num_leds)
|
||||
rmt_tx_channel_config_t tx_chan_config = {
|
||||
.clk_src = RMT_CLK_SRC_DEFAULT,
|
||||
.gpio_num = pin,
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
.mem_block_symbols = 48,
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32)
|
||||
.mem_block_symbols = 64,
|
||||
#else
|
||||
#error "Unsupported target: rmt block symbols undefined"
|
||||
#endif
|
||||
.resolution_hz = 80000000, // 80MHz
|
||||
.trans_queue_depth = 4,
|
||||
};
|
||||
|
||||
322
main/rcsignal.c
322
main/rcsignal.c
@ -1,11 +1,13 @@
|
||||
/**
|
||||
* @file rcsignal.c
|
||||
* @brief RC PWM signal reading implementation using edge capture
|
||||
* @brief RC PWM/SBUS signal reading implementation with runtime mode selection
|
||||
*/
|
||||
|
||||
#include "rcsignal.h"
|
||||
#include "config.h"
|
||||
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/uart.h"
|
||||
#include "esp_timer.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
@ -15,15 +17,41 @@
|
||||
|
||||
static const char *TAG = "RCSIGNAL";
|
||||
|
||||
// SBUS protocol constants
|
||||
#define SBUS_FRAME_SIZE 25
|
||||
#define SBUS_HEADER 0x0F
|
||||
#define SBUS_FOOTER 0x00
|
||||
#define SBUS_FOOTER2 0x04 // Alternative footer
|
||||
#define SBUS_BAUDRATE 100000
|
||||
#define SBUS_CH_MIN 172
|
||||
#define SBUS_CH_CENTER 992
|
||||
#define SBUS_CH_MAX 1811
|
||||
#define UART_NUM UART_NUM_1
|
||||
#define UART_BUF_SIZE 256
|
||||
|
||||
// PWM mode constants
|
||||
#define PULSE_THRESHOLD_US 1500
|
||||
#define SIGNAL_TIMEOUT_MS 100
|
||||
static struct
|
||||
{
|
||||
int8_t gpio_pin;
|
||||
bool use_sbus_mode; // Runtime mode selection
|
||||
uint8_t sbus_trigger_channel; // SBUS trigger channel
|
||||
uint16_t sbus_threshold_low; // SBUS low threshold
|
||||
uint16_t sbus_threshold_high; // SBUS high threshold
|
||||
|
||||
// SBUS state
|
||||
volatile uint16_t channels[SBUS_NUM_CHANNELS];
|
||||
volatile int64_t last_frame_time;
|
||||
uint8_t rx_buffer[SBUS_FRAME_SIZE];
|
||||
|
||||
// PWM state
|
||||
volatile uint32_t pulse_width_us;
|
||||
volatile int64_t last_edge_time;
|
||||
volatile int64_t pulse_start_time;
|
||||
volatile bool last_level;
|
||||
|
||||
// Common state
|
||||
volatile bool signal_active;
|
||||
volatile bool pull_detected;
|
||||
uint8_t current_mode;
|
||||
@ -32,6 +60,13 @@ static struct
|
||||
TaskHandle_t monitor_task;
|
||||
} rcsignal = {
|
||||
.gpio_pin = -1,
|
||||
.use_sbus_mode = false,
|
||||
.sbus_trigger_channel = 3,
|
||||
.sbus_threshold_low = 800,
|
||||
.sbus_threshold_high = 1100,
|
||||
.channels = {0},
|
||||
.last_frame_time = 0,
|
||||
.rx_buffer = {0},
|
||||
.pulse_width_us = 0,
|
||||
.last_edge_time = 0,
|
||||
.pulse_start_time = 0,
|
||||
@ -44,6 +79,7 @@ static struct
|
||||
.monitor_task = NULL,
|
||||
};
|
||||
|
||||
// PWM Mode: GPIO ISR handler
|
||||
static void IRAM_ATTR gpio_isr_handler(void *arg)
|
||||
{
|
||||
int64_t now = esp_timer_get_time();
|
||||
@ -68,81 +104,214 @@ static void IRAM_ATTR gpio_isr_handler(void *arg)
|
||||
rcsignal.last_level = level;
|
||||
}
|
||||
|
||||
// SBUS Mode: Parse SBUS frame
|
||||
static bool parse_sbus_frame(const uint8_t *frame, uint16_t *channels)
|
||||
{
|
||||
// Check header and footer
|
||||
if (frame[0] != SBUS_HEADER || (frame[24] != SBUS_FOOTER && frame[24] != SBUS_FOOTER2))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// SBUS uses 11 bits per channel, packed into bytes
|
||||
channels[0] = ((frame[1] | frame[2] << 8) & 0x07FF);
|
||||
channels[1] = ((frame[2] >> 3 | frame[3] << 5) & 0x07FF);
|
||||
channels[2] = ((frame[3] >> 6 | frame[4] << 2 | frame[5] << 10) & 0x07FF);
|
||||
channels[3] = ((frame[5] >> 1 | frame[6] << 7) & 0x07FF);
|
||||
channels[4] = ((frame[6] >> 4 | frame[7] << 4) & 0x07FF);
|
||||
channels[5] = ((frame[7] >> 7 | frame[8] << 1 | frame[9] << 9) & 0x07FF);
|
||||
channels[6] = ((frame[9] >> 2 | frame[10] << 6) & 0x07FF);
|
||||
channels[7] = ((frame[10] >> 5 | frame[11] << 3) & 0x07FF);
|
||||
channels[8] = ((frame[12] | frame[13] << 8) & 0x07FF);
|
||||
channels[9] = ((frame[13] >> 3 | frame[14] << 5) & 0x07FF);
|
||||
channels[10] = ((frame[14] >> 6 | frame[15] << 2 | frame[16] << 10) & 0x07FF);
|
||||
channels[11] = ((frame[16] >> 1 | frame[17] << 7) & 0x07FF);
|
||||
channels[12] = ((frame[17] >> 4 | frame[18] << 4) & 0x07FF);
|
||||
channels[13] = ((frame[18] >> 7 | frame[19] << 1 | frame[20] << 9) & 0x07FF);
|
||||
channels[14] = ((frame[20] >> 2 | frame[21] << 6) & 0x07FF);
|
||||
channels[15] = ((frame[21] >> 5 | frame[22] << 3) & 0x07FF);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void monitor_task(void *arg)
|
||||
{
|
||||
uint32_t last_pulse_width = 0;
|
||||
|
||||
while (1)
|
||||
if (rcsignal.use_sbus_mode)
|
||||
{
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
|
||||
// Check for signal timeout
|
||||
int64_t now = esp_timer_get_time();
|
||||
if (rcsignal.signal_active && (now - rcsignal.last_edge_time) > (SIGNAL_TIMEOUT_MS * 1000))
|
||||
// SBUS mode
|
||||
while (1)
|
||||
{
|
||||
rcsignal.signal_active = false;
|
||||
rcsignal.pulse_width_us = 0;
|
||||
}
|
||||
// Read SBUS data from UART
|
||||
int len = uart_read_bytes(UART_NUM, rcsignal.rx_buffer, SBUS_FRAME_SIZE, pdMS_TO_TICKS(20));
|
||||
|
||||
// Detect mode change (rising edge on PWM signal > 1500us)
|
||||
if (rcsignal.pulse_width_us != last_pulse_width)
|
||||
{
|
||||
last_pulse_width = rcsignal.pulse_width_us;
|
||||
|
||||
if (rcsignal.pulse_width_us < PULSE_THRESHOLD_US)
|
||||
if (len == SBUS_FRAME_SIZE)
|
||||
{
|
||||
rcsignal.pull_detected = true;
|
||||
uint16_t temp_channels[SBUS_NUM_CHANNELS];
|
||||
|
||||
if (parse_sbus_frame(rcsignal.rx_buffer, temp_channels))
|
||||
{
|
||||
// Copy parsed channels
|
||||
for (int i = 0; i < SBUS_NUM_CHANNELS; i++)
|
||||
{
|
||||
rcsignal.channels[i] = temp_channels[i];
|
||||
}
|
||||
|
||||
rcsignal.last_frame_time = esp_timer_get_time();
|
||||
rcsignal.signal_active = true;
|
||||
|
||||
// Check trigger channel for mode change
|
||||
uint16_t ch_value = rcsignal.channels[rcsignal.sbus_trigger_channel];
|
||||
|
||||
// Detect pull low
|
||||
if (ch_value < rcsignal.sbus_threshold_low)
|
||||
{
|
||||
rcsignal.pull_detected = true;
|
||||
}
|
||||
|
||||
// Detect rising edge (pull high after low)
|
||||
if (ch_value > rcsignal.sbus_threshold_high && rcsignal.pull_detected)
|
||||
{
|
||||
rcsignal.pull_detected = false;
|
||||
|
||||
if (rcsignal.callback)
|
||||
{
|
||||
rcsignal.callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rcsignal.pulse_width_us > PULSE_THRESHOLD_US && rcsignal.pull_detected)
|
||||
// Check for signal timeout
|
||||
int64_t now = esp_timer_get_time();
|
||||
if (rcsignal.signal_active && (now - rcsignal.last_frame_time) > (SIGNAL_TIMEOUT_MS * 1000))
|
||||
{
|
||||
// Mode change detected
|
||||
rcsignal.pull_detected = false;
|
||||
rcsignal.signal_active = false;
|
||||
memset((void *)rcsignal.channels, 0, sizeof(rcsignal.channels));
|
||||
}
|
||||
|
||||
if (rcsignal.callback)
|
||||
vTaskDelay(pdMS_TO_TICKS(5));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// PWM mode
|
||||
uint32_t last_pulse_width = 0;
|
||||
|
||||
while (1)
|
||||
{
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
|
||||
// Check for signal timeout
|
||||
int64_t now = esp_timer_get_time();
|
||||
if (rcsignal.signal_active && (now - rcsignal.last_edge_time) > (SIGNAL_TIMEOUT_MS * 1000))
|
||||
{
|
||||
rcsignal.signal_active = false;
|
||||
rcsignal.pulse_width_us = 0;
|
||||
}
|
||||
|
||||
// Detect mode change (rising edge on PWM signal > 1500us)
|
||||
if (rcsignal.pulse_width_us != last_pulse_width)
|
||||
{
|
||||
last_pulse_width = rcsignal.pulse_width_us;
|
||||
|
||||
if (rcsignal.pulse_width_us < PULSE_THRESHOLD_US)
|
||||
{
|
||||
rcsignal.callback();
|
||||
rcsignal.pull_detected = true;
|
||||
}
|
||||
|
||||
if (rcsignal.pulse_width_us > PULSE_THRESHOLD_US && rcsignal.pull_detected)
|
||||
{
|
||||
// Mode change detected
|
||||
rcsignal.pull_detected = false;
|
||||
|
||||
if (rcsignal.callback)
|
||||
{
|
||||
rcsignal.callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t rcsignal_init(int8_t pin)
|
||||
esp_err_t rcsignal_init(const config_t *config)
|
||||
{
|
||||
if (pin < 0)
|
||||
if (!config || config->rc_signal_pin < 0)
|
||||
{
|
||||
ESP_LOGI(TAG, "RC signal disabled (no pin configured)");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
rcsignal.gpio_pin = pin;
|
||||
// Store configuration
|
||||
rcsignal.gpio_pin = config->rc_signal_pin;
|
||||
rcsignal.use_sbus_mode = config->use_sbus_mode;
|
||||
rcsignal.sbus_trigger_channel = config->sbus_trigger_channel;
|
||||
rcsignal.sbus_threshold_low = config->sbus_threshold_low;
|
||||
rcsignal.sbus_threshold_high = config->sbus_threshold_high;
|
||||
|
||||
// Configure GPIO
|
||||
gpio_config_t io_conf = {
|
||||
.pin_bit_mask = (1ULL << pin),
|
||||
.mode = GPIO_MODE_INPUT,
|
||||
.pull_up_en = GPIO_PULLUP_ENABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_ANYEDGE,
|
||||
};
|
||||
ESP_ERROR_CHECK(gpio_config(&io_conf));
|
||||
if (rcsignal.use_sbus_mode)
|
||||
{
|
||||
// SBUS Mode: Configure UART with inverted RX
|
||||
ESP_LOGI(TAG, "Initializing SBUS mode on GPIO%d", rcsignal.gpio_pin);
|
||||
ESP_LOGI(TAG, " Trigger channel: CH%d", rcsignal.sbus_trigger_channel + 1);
|
||||
ESP_LOGI(TAG, " Thresholds: %d / %d", rcsignal.sbus_threshold_low, rcsignal.sbus_threshold_high);
|
||||
|
||||
// Install ISR service
|
||||
ESP_ERROR_CHECK(gpio_install_isr_service(0));
|
||||
ESP_ERROR_CHECK(gpio_isr_handler_add(pin, gpio_isr_handler, NULL));
|
||||
uart_config_t uart_config = {
|
||||
.baud_rate = SBUS_BAUDRATE,
|
||||
.data_bits = UART_DATA_8_BITS,
|
||||
.parity = UART_PARITY_EVEN,
|
||||
.stop_bits = UART_STOP_BITS_2,
|
||||
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
|
||||
.source_clk = UART_SCLK_APB,
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(uart_param_config(UART_NUM, &uart_config));
|
||||
ESP_ERROR_CHECK(uart_set_pin(UART_NUM, UART_PIN_NO_CHANGE, rcsignal.gpio_pin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
|
||||
ESP_ERROR_CHECK(uart_driver_install(UART_NUM, UART_BUF_SIZE * 2, 0, 0, NULL, 0));
|
||||
|
||||
if (config->sbus_inverted)
|
||||
{
|
||||
// Set inverted RX for FrSky receivers (they output inverted SBUS)
|
||||
ESP_ERROR_CHECK(uart_set_line_inverse(UART_NUM, UART_SIGNAL_RXD_INV));
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "SBUS UART configured with inverted RX");
|
||||
}
|
||||
else
|
||||
{
|
||||
// PWM Mode: Configure GPIO with interrupts
|
||||
ESP_LOGI(TAG, "Initializing PWM mode on GPIO%d", rcsignal.gpio_pin);
|
||||
|
||||
gpio_config_t io_conf = {
|
||||
.pin_bit_mask = (1ULL << rcsignal.gpio_pin),
|
||||
.mode = GPIO_MODE_INPUT,
|
||||
.pull_up_en = GPIO_PULLUP_ENABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_ANYEDGE,
|
||||
};
|
||||
ESP_ERROR_CHECK(gpio_config(&io_conf));
|
||||
|
||||
// Add ISR handler (ISR service must be installed by caller)
|
||||
ESP_ERROR_CHECK(gpio_isr_handler_add(rcsignal.gpio_pin, gpio_isr_handler, NULL));
|
||||
}
|
||||
|
||||
// Create monitor task
|
||||
BaseType_t ret = xTaskCreate(monitor_task, "rcsignal_monitor", 2048, NULL, 5, &rcsignal.monitor_task);
|
||||
if (ret != pdPASS)
|
||||
{
|
||||
gpio_isr_handler_remove(pin);
|
||||
gpio_uninstall_isr_service();
|
||||
if (rcsignal.use_sbus_mode)
|
||||
{
|
||||
uart_driver_delete(UART_NUM);
|
||||
}
|
||||
else
|
||||
{
|
||||
gpio_isr_handler_remove(rcsignal.gpio_pin);
|
||||
}
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
rcsignal.initialized = true;
|
||||
ESP_LOGI(TAG, "RC signal initialized on GPIO%d", pin);
|
||||
ESP_LOGI(TAG, "RC signal initialized successfully");
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
@ -158,9 +327,16 @@ void rcsignal_deinit(void)
|
||||
rcsignal.monitor_task = NULL;
|
||||
}
|
||||
|
||||
if (rcsignal.gpio_pin >= 0)
|
||||
if (rcsignal.use_sbus_mode)
|
||||
{
|
||||
gpio_isr_handler_remove(rcsignal.gpio_pin);
|
||||
uart_driver_delete(UART_NUM);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (rcsignal.gpio_pin >= 0)
|
||||
{
|
||||
gpio_isr_handler_remove(rcsignal.gpio_pin);
|
||||
}
|
||||
}
|
||||
|
||||
rcsignal.initialized = false;
|
||||
@ -173,7 +349,21 @@ void rcsignal_register_callback(rcsignal_mode_change_callback_t callback)
|
||||
|
||||
uint32_t rcsignal_get_pulse_width(void)
|
||||
{
|
||||
return rcsignal.pulse_width_us;
|
||||
if (rcsignal.use_sbus_mode)
|
||||
{
|
||||
// In SBUS mode, return trigger channel value mapped to microseconds
|
||||
// SBUS: 172-1811 -> PWM: ~1000-2000us
|
||||
if (rcsignal.signal_active)
|
||||
{
|
||||
uint16_t ch_val = rcsignal.channels[rcsignal.sbus_trigger_channel];
|
||||
return 1000 + ((ch_val - SBUS_CH_MIN) * 1000) / (SBUS_CH_MAX - SBUS_CH_MIN);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return rcsignal.pulse_width_us;
|
||||
}
|
||||
}
|
||||
|
||||
bool rcsignal_is_active(void)
|
||||
@ -185,3 +375,45 @@ uint8_t rcsignal_get_current_mode(void)
|
||||
{
|
||||
return rcsignal.current_mode;
|
||||
}
|
||||
|
||||
uint16_t rcsignal_get_sbus_channel(uint8_t channel)
|
||||
{
|
||||
if (!rcsignal.use_sbus_mode || channel >= SBUS_NUM_CHANNELS)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return rcsignal.channels[channel];
|
||||
}
|
||||
|
||||
void rcsignal_debug_print_channels(void)
|
||||
{
|
||||
if (!rcsignal.use_sbus_mode)
|
||||
{
|
||||
ESP_LOGW(TAG, "Not in SBUS mode");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!rcsignal.signal_active)
|
||||
{
|
||||
ESP_LOGW(TAG, "No SBUS signal active");
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "SBUS Channels:");
|
||||
ESP_LOGI(TAG, " CH1: %4d CH2: %4d CH3: %4d CH4: %4d",
|
||||
rcsignal.channels[0], rcsignal.channels[1],
|
||||
rcsignal.channels[2], rcsignal.channels[3]);
|
||||
ESP_LOGI(TAG, " CH5: %4d CH6: %4d CH7: %4d CH8: %4d",
|
||||
rcsignal.channels[4], rcsignal.channels[5],
|
||||
rcsignal.channels[6], rcsignal.channels[7]);
|
||||
ESP_LOGI(TAG, " CH9: %4d CH10: %4d CH11: %4d CH12: %4d",
|
||||
rcsignal.channels[8], rcsignal.channels[9],
|
||||
rcsignal.channels[10], rcsignal.channels[11]);
|
||||
ESP_LOGI(TAG, " CH13: %4d CH14: %4d CH15: %4d CH16: %4d",
|
||||
rcsignal.channels[12], rcsignal.channels[13],
|
||||
rcsignal.channels[14], rcsignal.channels[15]);
|
||||
|
||||
// Highlight the trigger channel
|
||||
ESP_LOGI(TAG, "Trigger channel (CH%d): %d", rcsignal.sbus_trigger_channel + 1,
|
||||
rcsignal.channels[rcsignal.sbus_trigger_channel]);
|
||||
}
|
||||
|
||||
@ -1,16 +1,20 @@
|
||||
/**
|
||||
* @file rcsignal.h
|
||||
* @brief RC PWM signal reading and parsing module
|
||||
* @brief RC PWM/SBUS signal reading and parsing module
|
||||
*/
|
||||
|
||||
#ifndef RCSIGNAL_H
|
||||
#define RCSIGNAL_H
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "config.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
// SBUS protocol constants (always defined for use in both modes)
|
||||
#define SBUS_NUM_CHANNELS 16 // SBUS supports 16 proportional channels
|
||||
|
||||
/**
|
||||
* @brief Callback function type for mode changes
|
||||
*/
|
||||
@ -18,10 +22,10 @@ typedef void (*rcsignal_mode_change_callback_t)();
|
||||
|
||||
/**
|
||||
* @brief Initialize RC signal reading
|
||||
* @param pin GPIO pin for PWM input (-1 to disable)
|
||||
* @param config Pointer to configuration structure
|
||||
* @return ESP_OK on success
|
||||
*/
|
||||
esp_err_t rcsignal_init(int8_t pin);
|
||||
esp_err_t rcsignal_init(const config_t *config);
|
||||
|
||||
/**
|
||||
* @brief Deinitialize RC signal reading
|
||||
@ -52,4 +56,16 @@ bool rcsignal_is_active(void);
|
||||
*/
|
||||
uint8_t rcsignal_get_current_mode(void);
|
||||
|
||||
/**
|
||||
* @brief Get SBUS channel value (only valid in SBUS mode)
|
||||
* @param channel Channel index (0-15)
|
||||
* @return Channel value (172-1811) or 0 if invalid/not in SBUS mode
|
||||
*/
|
||||
uint16_t rcsignal_get_sbus_channel(uint8_t channel);
|
||||
|
||||
/**
|
||||
* @brief Debug function to print all SBUS channels (only valid in SBUS mode)
|
||||
*/
|
||||
void rcsignal_debug_print_channels(void);
|
||||
|
||||
#endif // RCSIGNAL_H
|
||||
|
||||
Reference in New Issue
Block a user