1313#include " sampling.h"
1414#include " pump_dac.h"
1515#include " max3185x.h"
16+ #include " auxout.h"
17+ #include " pwmout.h"
18+
19+ void SendMsIoBoxFormat (Configuration* configuration);
20+
21+ IoHandler msIoBoxTxHandler (CanIoProtocol::MsIoBox, 20 , SendMsIoBoxFormat);
1622
1723// MS IoBox protocol
1824
19- #define MSIOBOX_TX_PERIOD_MS 10
20- #define MSIOBOX_BASE_ID 0x200
21- #define MSIOBOX_OFFSSET 0x20
25+ #define MS_IOBOX_BASE_ID 0x200
26+ #define MS_IOBOX_OFFSSET 0x20
27+ #define MS_IOBOX_DEVICE_ID (n ) (MS_IOBOX_BASE_ID + (MS_IOBOX_OFFSSET )*(n))
28+
29+ #define MS_IOBOX_PING 0x00
30+ #define MS_IOBOX_CONFIG 0x01
31+ #define MS_IOBOX_SET_PWM (n ) (0x02 + ((n) & 0x03 ))
32+ #define MS_IOBOX_LAST_IN 0x05
33+
34+ /* Packets from device to MS3 */
35+ #define MS_IOBOX_WHOAMI 0x08
36+ #define MS_IOBOX_ADC14 0x09
37+ #define MS_IOBOX_ADC57 0x0A
2238
2339namespace msiobox
2440{
2541
26- // RX SYN: ID = BASE_ID + 0
27- // TX ACK: ID = BASE_ID + 8
42+ struct pwm_settings {
43+ beuint16_t on;
44+ beuint16_t off;
45+ } __attribute__ ((packed));
2846
29- // Settings: ID = BASE_ID + 1
30- struct IoBoxSettings
31- {
32- uint8_t OutputMode; // 0 = On/Off, 1 = PWM
33- uint8_t : 8 ; // Reserved
34- uint8_t TachConfig; // Bitfield
35- uint8_t : 8 ; // Reserved
36- uint8_t AdcBroadcastInterval; // ms
37- uint8_t TachBroadcastInterval; // ms
38- uint8_t : 16 ; // Reserved
47+ /* Base + 0x00 */
48+ /* "Are you there?" packet with zero payload */
49+
50+ /* Base + 0x01 */
51+ struct cfg {
52+ uint8_t pwm_mask; // 0 - On/Off, 1 - PWM
53+ uint8_t pad0;
54+ uint8_t tachin_mask;
55+ uint8_t pad1;
56+ uint8_t adc_broadcast_interval; // mS
57+ uint8_t tach_broadcast_interval; // mS
58+ uint8_t pad2[2 ];
3959} __attribute__((packed));
4060
41- static_assert (sizeof (IoBoxSettings) == 8 );
61+ /* Base + 0x02, 0x03, 0x04 */
62+ struct pwm {
63+ pwm_settings ch[2 ];
64+ } __attribute__ ((packed));
4265
43- // Output:
44- // ID = BASE_ID + 2 : Channel 1, 2
45- // ID = BASE_ID + 3 : Channel 3, 4
46- // ID = BASE_ID + 4 : Channel 5, 6
47- struct IoBoxOutputSettings
48- {
49- struct {
50- uint16_t OnPeriod;
51- uint16_t OffPeriod;
52- } __attribute__((packed)) Channels[2 ];
66+ static_assert (sizeof (pwm) == 8 );
67+
68+ /* Base + 0x05 */
69+ struct pwm_last {
70+ pwm_settings ch[1 ];
71+ uint8_t out_state;
72+ } __attribute__ ((packed));
73+
74+ static_assert (sizeof (pwm_last) == 5 );
75+
76+ /* Base + 0x08 */
77+ struct whoami {
78+ uint8_t version;
79+ uint8_t pad[3 ];
80+ beuint16_t pwm_period; // PWM clock periods in 0.01 uS
81+ beuint16_t tachin_period; // Tach-in clock periods in 0.01 uS
5382} __attribute__((packed));
5483
55- static_assert (sizeof (IoBoxOutputSettings ) == 8 );
84+ static_assert (sizeof (whoami ) == 8 );
5685
57- // Output:
58- // ID = BASE_ID + 5 : Channel 7, Output enables
59- struct IoBoxOutputState
60- {
61- struct {
62- uint16_t OnPeriod;
63- uint16_t OffPeriod;
64- } __attribute__((packed)) Channels[1 ];
65- uint8_t OutputEnable; // Bitfield
66- uint8_t : 24 ; // Reserved
86+ /* Base + 0x09 */
87+ struct adc14 {
88+ beuint16_t adc[4 ];
6789} __attribute__((packed));
6890
69- static_assert (sizeof (IoBoxOutputState) == 8 );
91+ static_assert (sizeof (adc14) == 8 );
92+
93+ /* Base + 0x0A */
94+ struct adc57 {
95+ uint8_t inputs;
96+ uint8_t pad;
97+ beuint16_t adc[3 ];
98+ } __attribute__((packed));
99+
100+ static_assert (sizeof (adc57) == 8 );
70101
71102} // namespace msiobox
72103
104+ static bool configured = false ;
105+ static uint8_t pwm_mask = 0x00 ;
106+ static uint8_t tachin_mask = 0x00 ;
107+ static uint8_t tach_broadcast_interval = 20 ;
108+
109+ /*
110+ * TODO: validate
111+ * Scale value to 0 .. 1023
112+ * MegaSquirt IOBox has 0..5V ADC input range
113+ */
114+ static uint16_t CanIoBoxGetAdc (size_t index)
115+ {
116+ /* Total 7 ADC inputs, mapping:
117+ * 1 - AUX left
118+ * 2 - AUX right
119+ * 3 - AUX out left voltage
120+ * 4 - AUX out right voltage
121+ * 5 - WBO supply voltage
122+ * 6 - Left sensor heater supply
123+ * 7 - Right sensor heater supply */
124+ switch (index) {
125+ case 1 : // AUX Left
126+ case 2 : // AUX Right
127+ {
128+ #if (AUX_INPUT_CHANNELS > 0)
129+ if (index > AUX_INPUT_CHANNELS ) {
130+ return 0 ;
131+ }
132+ float voltage = GetAuxInputVoltage (index - 1 );
133+ if (voltage < 0 ) voltage = 0 ;
134+ if (voltage > 5 ) voltage = 5 ;
135+ return (uint16_t )(voltage / 5.0 * 1024.0 );
136+ #else
137+ return 0 ;
138+ #endif
139+ }
140+ case 3 : // AUX out left voltage
141+ case 4 : // AUX out right voltage
142+ return 0 ;
143+ case 5 : // WBO supply voltage
144+ return 0 ;
145+ case 6 :
146+ case 7 :
147+ {
148+ const auto & sampler = GetSampler (index - 6 );
149+ /* TODO: clamp */
150+ return sampler.GetInternalHeaterVoltage () / 25.5 * 1024 ;
151+ }
152+ default :
153+ return 0.0 ;
154+ }
155+ }
156+
73157void SendMsIoBoxFormat (Configuration* configuration)
74158{
75- (void )configuration;
159+ // if (!configuration->ioExpanderConfig.enable_tx)
160+ // return;
161+
162+ if (!configured)
163+ return ;
164+
165+ const uint32_t base = MS_IOBOX_DEVICE_ID (configuration->ioExpanderConfig .Offset );
166+
167+ if (1 ) // Create a scope to ensure frame is sent early
168+ {
169+ CanTxTyped<msiobox::adc14> frame1 (base + MS_IOBOX_ADC14 , false );
170+
171+ for (size_t i = 0 ; i < 4 ; i++) {
172+ frame1->adc [i] = CanIoBoxGetAdc (i);
173+ }
174+ }
175+
176+ if (1 ) // Create a scope to ensure frame is sent early
177+ {
178+ CanTxTyped<msiobox::adc57> frame2 (base + MS_IOBOX_ADC57 , false );
179+
180+ for (size_t i = 0 ; i < 3 ; i++) {
181+ frame2.get ().adc [i] = CanIoBoxGetAdc (4 + i);
182+ }
183+ }
76184}
77185
78- void HandleMsIoBoxCanMessage (const CANRxFrame* msg , Configuration* configuration, struct CanStatusData * statusData )
186+ void ProcessMsIoBoxCanMessage (const CANRxFrame* fr , Configuration* cfg )
79187{
80- (void )msg;
81- (void )configuration;
82- (void )statusData;
188+ if (!(cfg->ioExpanderConfig .Protocol == CanIoProtocol::MsIoBox)) {
189+ return ;
190+ }
191+ // For now only support standard IDs
192+ if (fr->IDE != CAN_IDE_STD ) {
193+ return ;
194+ }
195+
196+ const uint32_t base = MS_IOBOX_DEVICE_ID (cfg->ioExpanderConfig .Offset );
197+ uint32_t frame_id = fr->SID ;
198+
199+ if ((frame_id < base) || (frame_id > base + MS_IOBOX_LAST_IN ))
200+ return ;
201+
202+ if ((frame_id == base + MS_IOBOX_PING ) && (fr->DLC == 0 ))
203+ {
204+ CanTxTyped<msiobox::whoami> frame (base + MS_IOBOX_WHOAMI , false );
205+
206+ frame->version = 1 ;
207+
208+ // PWM clock periods in 0.01 uS, equal to clock freq / 100 * 1000
209+ frame->pwm_period = 5000 ;
210+ // Tach-in clock periods in 0.01 uS, equal to clock freq / 100 * 1000
211+ frame->tachin_period = 66 ;
212+
213+ return ;
214+ }
215+
216+ if ((frame_id == base + MS_IOBOX_CONFIG ) && (fr->DLC == sizeof (msiobox::cfg)))
217+ {
218+ const msiobox::cfg *iobox_config = reinterpret_cast <const msiobox::cfg *>(fr->data8 );
219+
220+ // can0 201 [8] 00 00 00 00 14 14 00 00
221+ pwm_mask = iobox_config->pwm_mask ;
222+ tachin_mask = iobox_config->tachin_mask ;
223+ tach_broadcast_interval = iobox_config->tach_broadcast_interval ;
224+
225+ msIoBoxTxHandler.m_intervalMs = iobox_config->adc_broadcast_interval ;
226+
227+ configured = true ;
228+
229+ return ;
230+ }
231+
232+ if ((frame_id >= base + MS_IOBOX_SET_PWM (0 )) &&
233+ (frame_id <= base + MS_IOBOX_SET_PWM (2 )) &&
234+ (fr->DLC == sizeof (msiobox::pwm)))
235+ {
236+ const msiobox::pwm *pwm = reinterpret_cast <const msiobox::pwm *>(fr->data8 );
237+
238+ /* Two first channels are mapped to analog outputs */
239+ if (frame_id == base + MS_IOBOX_SET_PWM (0 )) {
240+ for (size_t n = 0 ; n < AFR_CHANNELS ; n++) {
241+ // if allowed to control DAC output over CAN
242+ if (cfg->auxOutputSource [n] != AuxOutputMode::IOExpander) {
243+ continue ;
244+ }
245+
246+ uint32_t period = pwm->ch [n].off + pwm->ch [n].on ;
247+ float duty = (period == 0 ) ? 0 : (float )pwm->ch [n].on / period;
248+ SetAuxDac (n, 5.0 * duty);
249+ }
250+ }
251+ #if (PWM_OUTPUT_CHANNELS > 0)
252+ /* Second set of PWM channels */
253+ else if (frame_id == base + MS_IOBOX_SET_PWM (1 )) {
254+ for (size_t n = 0 ; n < PWM_OUTPUT_CHANNELS ; n++) {
255+ uint32_t period = pwm->ch [n].off + pwm->ch [n].on ;
256+ float duty = (period == 0 ) ? 0 : (float )pwm->ch [n].on / period;
257+ SetAuxPwmDuty (n, duty);
258+ }
259+ }
260+ #endif
261+
262+ /* TODO: PWM periods */
263+ return ;
264+ }
265+
266+ if ((frame_id == base + MS_IOBOX_SET_PWM (3 )) && (fr->DLC == sizeof (msiobox::pwm_last)))
267+ {
268+ const msiobox::pwm_last *pwm = reinterpret_cast <const msiobox::pwm_last *>(fr->data8 );
269+
270+ /* TODO: PWM periods and outputs */
271+ (void )pwm;
272+
273+ return ;
274+ }
83275}
0 commit comments