feat: implement channel helpers

cel_crsf_channel_us_to_val() and cel_crsf_channel_val_to_us()
convert between microseconds (988-2012) and 11-bit values (172-1811)
with rounding. cel_crsf_channel_default() fills safe/disarmed values.
This commit is contained in:
2026-06-14 20:54:56 +02:00
parent a846b063f9
commit 8b181d0fcd
2 changed files with 87 additions and 16 deletions
+22 -16
View File
@@ -124,28 +124,34 @@ int16_t cel_crsf_channel_clamp(int16_t value) {
} }
int16_t cel_crsf_channel_us_to_val(uint16_t us) { int16_t cel_crsf_channel_us_to_val(uint16_t us) {
/* TODO: convert microseconds to 11-bit channel value. /* Clamp to valid range first to avoid underflow */
* Linear mapping: 988us -> 172, 2012us -> 1811. if (us < 988) us = 988;
* value = (us - 988) * (1811 - 172) / (2012 - 988) + 172 if (us > 2012) us = 2012;
* Clamp result to [CEL_CRSF_CH_MIN, CEL_CRSF_CH_MAX]. */ /* Round to nearest: add half the divisor before dividing */
(void)us; int16_t value = (int16_t)(
return CEL_CRSF_CH_MID; ((us - 988) * (CEL_CRSF_CH_MAX - CEL_CRSF_CH_MIN)
+ (2012 - 988) / 2) / (2012 - 988)
+ CEL_CRSF_CH_MIN);
return cel_crsf_channel_clamp(value);
} }
uint16_t cel_crsf_channel_val_to_us(int16_t value) { uint16_t cel_crsf_channel_val_to_us(int16_t value) {
/* TODO: convert 11-bit channel value to microseconds. value = cel_crsf_channel_clamp(value);
* Linear mapping: 172 -> 988us, 1811 -> 2012us. return (uint16_t)(
* us = (value - 172) * (2012 - 988) / (1811 - 172) + 988 */ ((value - CEL_CRSF_CH_MIN) * (2012 - 988)
(void)value; + (CEL_CRSF_CH_MAX - CEL_CRSF_CH_MIN) / 2)
return 1500; / (CEL_CRSF_CH_MAX - CEL_CRSF_CH_MIN) + 988);
} }
void cel_crsf_channel_default(int16_t channels[16]) { void cel_crsf_channel_default(int16_t channels[16]) {
/* TODO: fill with safe/disarmed values. memset(channels, 0, sizeof(int16_t) * 16);
* CH1-Roll, CH2-Pitch, CH4-Yaw = CEL_CRSF_CH_MID (centered) channels[0] = CEL_CRSF_CH_MID; /* roll */
* CH3-Throttle = CEL_CRSF_CH_MIN (zero) channels[1] = CEL_CRSF_CH_MID; /* pitch */
* CH5..CH16 (AUX) = CEL_CRSF_CH_MIN (off) */ channels[2] = CEL_CRSF_CH_MIN; /* throttle */
(void)channels; channels[3] = CEL_CRSF_CH_MID; /* yaw */
for (int i = 4; i < 16; i++) {
channels[i] = CEL_CRSF_CH_MIN;
}
} }
/* --------------------------------------------------------------------------- */ /* --------------------------------------------------------------------------- */
+65
View File
@@ -38,6 +38,60 @@ void test_channel_clamp_mid(void) {
TEST_ASSERT_EQUAL_INT16(CEL_CRSF_CH_MID, cel_crsf_channel_clamp(CEL_CRSF_CH_MID)); TEST_ASSERT_EQUAL_INT16(CEL_CRSF_CH_MID, cel_crsf_channel_clamp(CEL_CRSF_CH_MID));
} }
void test_channel_us_to_val_min(void) {
TEST_ASSERT_EQUAL_INT16(CEL_CRSF_CH_MIN, cel_crsf_channel_us_to_val(988));
}
void test_channel_us_to_val_mid(void) {
TEST_ASSERT_EQUAL_INT16(CEL_CRSF_CH_MID, cel_crsf_channel_us_to_val(1500));
}
void test_channel_us_to_val_max(void) {
TEST_ASSERT_EQUAL_INT16(CEL_CRSF_CH_MAX, cel_crsf_channel_us_to_val(2012));
}
void test_channel_us_to_val_below_min(void) {
TEST_ASSERT_EQUAL_INT16(CEL_CRSF_CH_MIN, cel_crsf_channel_us_to_val(0));
}
void test_channel_us_to_val_above_max(void) {
TEST_ASSERT_EQUAL_INT16(CEL_CRSF_CH_MAX, cel_crsf_channel_us_to_val(65535));
}
void test_channel_val_to_us_min(void) {
TEST_ASSERT_EQUAL_UINT16(988, cel_crsf_channel_val_to_us(CEL_CRSF_CH_MIN));
}
void test_channel_val_to_us_mid(void) {
TEST_ASSERT_EQUAL_UINT16(1500, cel_crsf_channel_val_to_us(CEL_CRSF_CH_MID));
}
void test_channel_val_to_us_max(void) {
TEST_ASSERT_EQUAL_UINT16(2012, cel_crsf_channel_val_to_us(CEL_CRSF_CH_MAX));
}
void test_channel_default_throttle_min(void) {
int16_t ch[16];
cel_crsf_channel_default(ch);
TEST_ASSERT_EQUAL_INT16(CEL_CRSF_CH_MIN, ch[2]); /* throttle */
}
void test_channel_default_centered(void) {
int16_t ch[16];
cel_crsf_channel_default(ch);
TEST_ASSERT_EQUAL_INT16(CEL_CRSF_CH_MID, ch[0]); /* roll */
TEST_ASSERT_EQUAL_INT16(CEL_CRSF_CH_MID, ch[1]); /* pitch */
TEST_ASSERT_EQUAL_INT16(CEL_CRSF_CH_MID, ch[3]); /* yaw */
}
void test_channel_default_aux_min(void) {
int16_t ch[16];
cel_crsf_channel_default(ch);
for (int i = 4; i < 16; i++) {
TEST_ASSERT_EQUAL_INT16(CEL_CRSF_CH_MIN, ch[i]);
}
}
/* Frame parse tests — ELRS format: [addr][length][type][payload][crc] */ /* Frame parse tests — ELRS format: [addr][length][type][payload][crc] */
/* Build a valid test frame with known CRC */ /* Build a valid test frame with known CRC */
@@ -123,6 +177,17 @@ int main(void) {
RUN_TEST(test_channel_clamp_min); RUN_TEST(test_channel_clamp_min);
RUN_TEST(test_channel_clamp_max); RUN_TEST(test_channel_clamp_max);
RUN_TEST(test_channel_clamp_mid); RUN_TEST(test_channel_clamp_mid);
RUN_TEST(test_channel_us_to_val_min);
RUN_TEST(test_channel_us_to_val_mid);
RUN_TEST(test_channel_us_to_val_max);
RUN_TEST(test_channel_us_to_val_below_min);
RUN_TEST(test_channel_us_to_val_above_max);
RUN_TEST(test_channel_val_to_us_min);
RUN_TEST(test_channel_val_to_us_mid);
RUN_TEST(test_channel_val_to_us_max);
RUN_TEST(test_channel_default_throttle_min);
RUN_TEST(test_channel_default_centered);
RUN_TEST(test_channel_default_aux_min);
RUN_TEST(test_parse_valid_frame); RUN_TEST(test_parse_valid_frame);
RUN_TEST(test_parse_null_frame); RUN_TEST(test_parse_null_frame);
RUN_TEST(test_parse_null_buf); RUN_TEST(test_parse_null_buf);