Skip to content

Commit 19bbcb3

Browse files
authored
Add support for PWM receiver output (ExpressLRS#998)
* Switch ESP8266 timer to use timer0 * Add rx_config_pwm_t to config * Add target DIY_2400_RX_PWMP * Fix modelmatch setting to 0 every web submit * Fix build errors in config * Fix CRSF build error on TXes * Spelling ouput * Ghost ATTO does not have a TIM7 * Add inverted output flag * Better sync with the waveform generator * Failsafe in 1 second instead of the disconnect timeout * CRSF_RCVR_NO_SERIAL * Delay servo init until channel is valid from the TX * Update external to target define * Extra _ in device name :( * Add DIY_900_RX_PWMP * Save the Reg domain until is is removed in ExpressLRS#1001 * Split PWMP 900/2400 into separate files * Use esp8282-8285 variant for all 82xx targets The platform was using the generic variant which prevented us from being able to use GPIO9/GPIO10 with the waveform generator.
1 parent 2cc6fd4 commit 19bbcb3

20 files changed

Lines changed: 459 additions & 23 deletions

File tree

src/boards/esp8285-8285.json

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"comment": "esp8285 variant for ExpressLRS that uses variant=esp8285 instead of variant=generic",
3+
"build": {
4+
"arduino": {
5+
"ldscript": "eagle.flash.1m256.ld"
6+
},
7+
"core": "esp8266",
8+
"extra_flags": "-DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_ESP01",
9+
"f_cpu": "80000000L",
10+
"f_flash": "40000000L",
11+
"flash_mode": "dout",
12+
"mcu": "esp8266",
13+
"variant": "esp8285"
14+
},
15+
"connectivity": [
16+
"wifi"
17+
],
18+
"frameworks": [
19+
"arduino",
20+
"esp8266-rtos-sdk",
21+
"esp8266-nonos-sdk"
22+
],
23+
"name": "Generic ESP8285 Module",
24+
"upload": {
25+
"maximum_ram_size": 81920,
26+
"maximum_size": 1048576,
27+
"require_upload_port": true,
28+
"resetmethod": "ck",
29+
"speed": 115200
30+
},
31+
"url": "http://www.esp8266.com/wiki/doku.php?id=esp8266-module-family",
32+
"vendor": "Espressif"
33+
}

src/html/main.css

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1164,4 +1164,13 @@ th {
11641164
.info-bg {
11651165
background: #88cef7;
11661166
}
1167-
1167+
/* Custon code for ExpressLRS PWM Output table */
1168+
.pwmtbl th {
1169+
text-align: center;
1170+
font-weight: bold;
1171+
}
1172+
.pwmtbl td {
1173+
text-align: center;
1174+
padding-left: 2px;
1175+
padding-right: 2px;
1176+
}

src/html/rx_index.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,19 @@ <h2>Model Match:</h2>
7373
</fieldset>
7474
</div>
7575

76+
<div align="left" id="pwm_container" style="display:none;">
77+
<fieldset>
78+
Set PWM output and failsafe positions. Note: Failsafe values are absolute and do not use the "invert" flag.
79+
<br><br>
80+
<legend>
81+
<h2>PWM Output</h2>
82+
</legend>
83+
<form action="/pwm" id="pwm" method="POST">
84+
</form>
85+
<br><br>
86+
</fieldset>
87+
</div>
88+
7689
<div align="left" id="apmode" style="display:none;">
7790
<fieldset>
7891
Here you can join a network and it will be saved as your &quot;home&quot; network.

src/html/scan.js

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,68 @@ function _(el) {
55
return document.getElementById(el);
66
}
77

8+
function getPwmFormData()
9+
{
10+
let ch = 0;
11+
let inField;
12+
let outData = [];
13+
while (inField = _(`pwm_${ch}_ch`))
14+
{
15+
let inChannel = inField.value;
16+
let invert = _(`pwm_${ch}_inv`).checked ? 1 : 0;
17+
let failsafeField = _(`pwm_${ch}_fs`);
18+
let failsafe = failsafeField.value;
19+
if (failsafe > 2011) failsafe = 2011;
20+
if (failsafe < 988) failsafe = 988;
21+
failsafeField.value = failsafe;
22+
23+
let raw = (invert << 14) | (inChannel << 10) | (failsafe - 988);
24+
//console.log(`PWM ${ch} input=${inChannel} fs=${failsafe} inv=${invert} raw=${raw}`);
25+
outData.push(raw);
26+
++ch;
27+
}
28+
29+
let outForm = new FormData();
30+
outForm.append('pwm', outData.join(','));
31+
return outForm;
32+
}
33+
34+
function updatePwmSettings(arPwm)
35+
{
36+
if (arPwm === undefined)
37+
return;
38+
// arPwm is an array of raw integers [49664,50688,51200]. 10 bits of failsafe position, 4 bits of input channel, 1 bit invert
39+
let htmlFields = ['<table class="pwmtbl"><tr><th>Output</th><th>Input</th><th>Invert?</th><th>Failsafe</th></tr>'];
40+
arPwm.forEach((item, index) => {
41+
let failsafe = (item & 1023) + 988; // 10 bits
42+
let ch = (item >> 10) & 15; // 4 bits
43+
let inv = (item >> 14) & 1;
44+
htmlFields.push(`<tr><th>${index+1}</th><td><select id="pwm_${index}_ch">
45+
<option value="0"${(ch===0) ? ' selected' : ''}>ch1</option>
46+
<option value="1"${(ch===1) ? ' selected' : ''}>ch2</option>
47+
<option value="2"${(ch===2) ? ' selected' : ''}>ch3</option>
48+
<option value="3"${(ch===3) ? ' selected' : ''}>ch4</option>
49+
<option value="4"${(ch===4) ? ' selected' : ''}>ch5 (AUX1)</option>
50+
<option value="5"${(ch===5) ? ' selected' : ''}>ch6 (AUX2)</option>
51+
<option value="6"${(ch===6) ? ' selected' : ''}>ch7 (AUX3)</option>
52+
<option value="7"${(ch===7) ? ' selected' : ''}>ch8 (AUX4)</option>
53+
<option value="8"${(ch===8) ? ' selected' : ''}>ch9 (AUX5)</option>
54+
<option value="9"${(ch===9) ? ' selected' : ''}>ch10 (AUX6)</option>
55+
<option value="10"${(ch===10) ? ' selected' : ''}>ch11 (AUX7)</option>
56+
<option value="11"${(ch===11) ? ' selected' : ''}>ch12 (AUX8)</option>
57+
</select></td><td><input type="checkbox" id="pwm_${index}_inv"${(inv) ? ' checked' : ''}></td>
58+
<td><input id="pwm_${index}_fs" value="${failsafe}" size="4"/></td></tr>`);
59+
});
60+
htmlFields.push('<tr><td colspan="4"><input type="submit" value="Set PWM Output"></td></tr></table>');
61+
62+
let grp = document.createElement('DIV');
63+
grp.setAttribute('class', 'group');
64+
grp.innerHTML = htmlFields.join('');
65+
66+
_('pwm').appendChild(grp);
67+
_('pwm_container').style.display = 'block';
68+
}
69+
870
function get_mode() {
971
var json_url = 'mode.json';
1072
xmlhttp = new XMLHttpRequest();
@@ -24,6 +86,7 @@ function get_mode() {
2486
scanTimer = setInterval(get_networks, 2000);
2587
}
2688
_('modelid').value = data.modelid;
89+
updatePwmSettings(data.pwm);
2790
}
2891
};
2992
xmlhttp.open("POST", json_url, true);
@@ -271,8 +334,10 @@ _('sethome').addEventListener('submit', callback("Set Home Network", "An error o
271334
_('connect').addEventListener('click', callback("Connect to Home Network", "An error occurred connecting to the Home network", "/connect", null));
272335
_('access').addEventListener('click', callback("Access Point", "An error occurred starting the Access Point", "/access", null));
273336
_('forget').addEventListener('click', callback("Forget Home Network", "An error occurred forgetting the home network", "/forget", null));
337+
_('pwm').addEventListener('submit', callback('Set PWM Output', 'Unknown error', '/pwm', getPwmFormData));
274338
if (_('modelmatch') != undefined) {
275-
_('modelmatch').addEventListener('submit', callback("Set Model Match", "An error occurred updating the model match number", "/model", null));
339+
_('modelmatch').addEventListener('submit', callback("Set Model Match", "An error occurred updating the model match number", "/model",
340+
() => { return new FormData(_('modelmatch')); }));
276341
}
277342

278343
//=========================================================
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#define DEVICE_NAME "DIY2400 PWMP"
2+
3+
#define CRSF_RCVR_NO_SERIAL
4+
5+
// GPIO pin definitions
6+
// same as TARGET_RX_ESP8266_SX1280 except with no serial/button and PWM outputs
7+
#define GPIO_PIN_NSS 15
8+
#define GPIO_PIN_BUSY 5
9+
#define GPIO_PIN_DIO1 4
10+
#define GPIO_PIN_MOSI 13
11+
#define GPIO_PIN_MISO 12
12+
#define GPIO_PIN_SCK 14
13+
#define GPIO_PIN_RST 2
14+
//#define GPIO_PIN_RCSIGNAL_RX -1 // does not use UART
15+
//#define GPIO_PIN_RCSIGNAL_TX -1
16+
#define GPIO_PIN_LED_RED 16 // LED_RED on RX
17+
#if defined(DEBUG_LOG)
18+
#define GPIO_PIN_PWM_OUTPUTS {0, 9, 10}
19+
#else
20+
#define GPIO_PIN_PWM_OUTPUTS {0, 1, 3, 9, 10}
21+
#endif
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#define DEVICE_NAME "DIY900 PWMP"
2+
3+
#define CRSF_RCVR_NO_SERIAL
4+
5+
// GPIO pin definitions
6+
// same as DIY_900_RX_ESP8285_SX127x except with no serial/button and PWM outputs
7+
#define GPIO_PIN_NSS 15
8+
#define GPIO_PIN_DIO0 4
9+
//#define GPIO_PIN_DIO1 5 // unused
10+
#define GPIO_PIN_MOSI 13
11+
#define GPIO_PIN_MISO 12
12+
#define GPIO_PIN_SCK 14
13+
#define GPIO_PIN_RST 2
14+
//#define GPIO_PIN_RCSIGNAL_RX -1 // does not use UART
15+
//#define GPIO_PIN_RCSIGNAL_TX -1
16+
#define GPIO_PIN_LED_RED 16
17+
#if defined(DEBUG_LOG)
18+
#define GPIO_PIN_PWM_OUTPUTS {0, 9, 10}
19+
#else
20+
#define GPIO_PIN_PWM_OUTPUTS {0, 1, 3, 9, 10}
21+
#endif

src/lib/CONFIG/config.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,11 @@ RxConfig::SetDefaults()
450450
SetModelId(0xFF);
451451
SetSSID("");
452452
SetPassword("");
453+
#if defined(GPIO_PIN_PWM_OUTPUTS)
454+
for (unsigned int ch=0; ch<PWM_MAX_CHANNELS; ++ch)
455+
SetPwmChannel(ch, 512, ch, false);
456+
SetPwmChannel(2, 0, 2, false); // ch2 is throttle, failsafe it to 988
457+
#endif
453458
Commit();
454459
}
455460

@@ -476,4 +481,37 @@ RxConfig::SetPassword(const char *password)
476481
m_modified = true;
477482
}
478483

484+
#if defined(GPIO_PIN_PWM_OUTPUTS)
485+
void
486+
RxConfig::SetPwmChannel(uint8_t ch, uint16_t failsafe, uint8_t inputCh, bool inverted)
487+
{
488+
if (ch > PWM_MAX_CHANNELS)
489+
return;
490+
491+
rx_config_pwm_t *pwm = &m_config.pwmChannels[ch];
492+
if (pwm->val.failsafe == failsafe && pwm->val.inputChannel == inputCh
493+
&& pwm->val.inverted == inverted)
494+
return;
495+
496+
pwm->val.failsafe = failsafe;
497+
pwm->val.inputChannel = inputCh;
498+
pwm->val.inverted = inverted;
499+
m_modified = true;
500+
}
501+
502+
void
503+
RxConfig::SetPwmChannelRaw(uint8_t ch, uint16_t raw)
504+
{
505+
if (ch > PWM_MAX_CHANNELS)
506+
return;
507+
508+
rx_config_pwm_t *pwm = &m_config.pwmChannels[ch];
509+
if (pwm->raw == raw)
510+
return;
511+
512+
pwm->raw = raw;
513+
m_modified = true;
514+
}
515+
#endif
516+
479517
#endif

src/lib/CONFIG/config.h

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
#define RX_CONFIG_MAGIC (0b10 << 30)
1414

1515
#define TX_CONFIG_VERSION 4
16-
#define RX_CONFIG_VERSION 3
16+
#define RX_CONFIG_VERSION 4
1717
#define UID_LEN 6
1818

1919
#if defined(TARGET_TX)
@@ -101,6 +101,18 @@ extern TxConfig config;
101101
///////////////////////////////////////////////////
102102

103103
#if defined(TARGET_RX)
104+
constexpr uint8_t PWM_MAX_CHANNELS = 8;
105+
106+
typedef union {
107+
struct {
108+
uint16_t failsafe:10; // us output during failsafe +988 (e.g. 512 here would be 1500us)
109+
uint8_t inputChannel:4; // 0-based input channel
110+
uint8_t inverted:1; // invert channel output
111+
uint8_t unused:1;
112+
} val;
113+
uint16_t raw;
114+
} rx_config_pwm_t;
115+
104116
typedef struct {
105117
uint32_t version;
106118
bool isBound;
@@ -109,6 +121,7 @@ typedef struct {
109121
uint8_t modelId;
110122
char ssid[33];
111123
char password[33];
124+
rx_config_pwm_t pwmChannels[PWM_MAX_CHANNELS];
112125
} rx_config_t;
113126

114127
class RxConfig
@@ -131,6 +144,9 @@ class RxConfig
131144
bool IsModified() const { return m_modified; }
132145
const char* GetSSID() const { return m_config.ssid; }
133146
const char* GetPassword() const { return m_config.password; }
147+
#if defined(GPIO_PIN_PWM_OUTPUTS)
148+
const rx_config_pwm_t *GetPwmChannel(uint8_t ch) { return &m_config.pwmChannels[ch]; }
149+
#endif
134150

135151
// Setters
136152
void SetIsBound(bool isBound);
@@ -141,6 +157,10 @@ class RxConfig
141157
void SetStorageProvider(ELRS_EEPROM *eeprom);
142158
void SetSSID(const char *ssid);
143159
void SetPassword(const char *password);
160+
#if defined(GPIO_PIN_PWM_OUTPUTS)
161+
void SetPwmChannel(uint8_t ch, uint16_t failsafe, uint8_t inputCh, bool inverted);
162+
void SetPwmChannelRaw(uint8_t ch, uint16_t raw);
163+
#endif
144164

145165
private:
146166
rx_config_t m_config;

src/lib/CRSF/CRSF.cpp

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -830,6 +830,7 @@ void ICACHE_RAM_ATTR CRSF::ESP32uartTask(void *pvParameters)
830830
#elif CRSF_RX_MODULE // !CRSF_TX_MODULE
831831
bool CRSF::RXhandleUARTout()
832832
{
833+
#if !defined(CRSF_RCVR_NO_SERIAL)
833834
uint8_t peekVal = SerialOutFIFO.peek(); // check if we have data in the output FIFO that needs to be written
834835
if (peekVal > 0)
835836
{
@@ -844,11 +845,13 @@ bool CRSF::RXhandleUARTout()
844845
return true;
845846
}
846847
}
848+
#endif // CRSF_RCVR_NO_SERIAL
847849
return false;
848850
}
849851

850852
void ICACHE_RAM_ATTR CRSF::sendLinkStatisticsToFC()
851853
{
854+
#if !defined(CRSF_RCVR_NO_SERIAL)
852855
constexpr uint8_t outBuffer[4] = {
853856
LinkStatisticsFrameLength + 4,
854857
CRSF_ADDRESS_FLIGHT_CONTROLLER,
@@ -866,10 +869,12 @@ void ICACHE_RAM_ATTR CRSF::sendLinkStatisticsToFC()
866869

867870
//this->_dev->write(outBuffer, LinkStatisticsFrameLength + 4);
868871
#endif
872+
#endif // CRSF_RCVR_NO_SERIAL
869873
}
870874

871875
void ICACHE_RAM_ATTR CRSF::sendRCFrameToFC()
872876
{
877+
#if !defined(CRSF_RCVR_NO_SERIAL)
873878
constexpr uint8_t outBuffer[] = {
874879
// No need for length prefix as we aren't using the FIFO
875880
CRSF_ADDRESS_FLIGHT_CONTROLLER,
@@ -887,18 +892,48 @@ void ICACHE_RAM_ATTR CRSF::sendRCFrameToFC()
887892
this->_dev->write((byte *)&PackedRCdataOut, RCframeLength);
888893
this->_dev->write(crc);
889894
#endif
895+
#endif // CRSF_RCVR_NO_SERIAL
890896
}
891897

892898
void ICACHE_RAM_ATTR CRSF::sendMSPFrameToFC(uint8_t* data)
893899
{
900+
#if !defined(CRSF_RCVR_NO_SERIAL)
894901
const uint8_t totalBufferLen = CRSF_FRAME_SIZE(data[1]);
895902
if (totalBufferLen <= CRSF_FRAME_SIZE_MAX)
896903
{
897904
data[0] = CRSF_ADDRESS_FLIGHT_CONTROLLER;
898905
this->_dev->write(data, totalBufferLen);
899906
}
907+
#endif // CRSF_RCVR_NO_SERIAL
900908
}
901-
#endif // CRSF_TX_MODULE
909+
910+
/**
911+
* @brief Get encoded channel position from PackedRCdataOut
912+
* @param ch: zero-based channel number
913+
* @return CRSF-encoded channel position, or 0 if invalid channel
914+
**/
915+
uint16_t CRSF::GetChannelOutput(uint8_t ch)
916+
{
917+
switch (ch)
918+
{
919+
case 0: return PackedRCdataOut.ch0;
920+
case 1: return PackedRCdataOut.ch1;
921+
case 2: return PackedRCdataOut.ch2;
922+
case 3: return PackedRCdataOut.ch3;
923+
case 4: return PackedRCdataOut.ch4;
924+
case 5: return PackedRCdataOut.ch5;
925+
case 6: return PackedRCdataOut.ch6;
926+
case 7: return PackedRCdataOut.ch7;
927+
case 8: return PackedRCdataOut.ch8;
928+
case 9: return PackedRCdataOut.ch9;
929+
case 10: return PackedRCdataOut.ch10;
930+
case 11: return PackedRCdataOut.ch11;
931+
default:
932+
return 0;
933+
}
934+
}
935+
936+
#endif // CRSF_RX_MODULE
902937

903938
void CRSF::GetDeviceInformation(uint8_t *frame, uint8_t fieldCount)
904939
{

0 commit comments

Comments
 (0)