1 /* I-Bus servo mixer for FlySky iA6B receiver */
4 #include <avr/interrupt.h>
6 #include <avr/pgmspace.h>
7 #include <util/atomic.h>
8 #include <util/delay.h>
10 #define N_IBUS_CHANNELS 18
11 #define IBUS_SERVO_FRAME_SIZE 32
12 #define IBUS_SERVO_FRAME_ID 0x40 // first byte after the length
13 #define N_PWM_CHANNELS 6
15 /* ---------- LEDs for debugging ---------- */
33 /* ----------------- Timer ----------------- */
35 typedef uint16_t time_t;
36 #define TICKS_IN_US (F_CPU/8000000UL)
38 static void timer_init(void)
40 TCCR1A = 0; // no PWM or WGM output
41 TCCR1B = _BV(CS11); // clk/8
43 DDRD |= _BV(PD2) | _BV(PD3) | _BV(PD4) | _BV(PD5);
47 static time_t inline get_time(void)
51 ATOMIC_BLOCK(ATOMIC_FORCEON) {
59 /* ----------------- USART ----------------- */
61 // I-Bus uses 115200n8
62 #define UART_BAUD 115200
63 #define UBRR_VAL ((F_CPU + 8UL * UART_BAUD) / (16UL*UART_BAUD) - 1)
65 volatile uint8_t serial_frame[IBUS_SERVO_FRAME_SIZE];
66 volatile uint8_t serial_frame_ready;
68 static volatile uint8_t serial_frame_pos;
70 void serial_init(void)
75 UCSR0B = _BV(RXEN0) | _BV(RXCIE0);
76 UCSR0C = _BV(UCSZ01)|_BV(UCSZ00);
78 serial_frame_ready = serial_frame_pos = 0;
81 static void serial_enable_rx(void)
83 UCSR0B &= ~(_BV(TXEN0) | _BV(TXCIE0));
84 UCSR0B |= _BV(RXEN0) | _BV(RXCIE0);
87 #define serial_rx_vect USART_RX_vect
89 #define serial_data UDR0
92 * USART receive interrupt
94 * In order to save the latency, we do only the necessary things here,
95 * and postpone parsing the frame to tne non-irq time in the main
96 * loop. Also, we expect the main loop to finish before the second
97 * byte of the next frame is read, so we do not do atomic reads on top
102 uint8_t val = serial_data;
104 // a shorthand - for now, we accept fixed-size packets only
105 if (serial_frame_pos == 0 && val != IBUS_SERVO_FRAME_SIZE)
108 if (serial_frame_pos == 1
109 && serial_frame[0] == IBUS_SERVO_FRAME_SIZE
110 && val != IBUS_SERVO_FRAME_ID)
113 serial_frame[serial_frame_pos++] = val;
115 if (serial_frame_pos == serial_frame[0])
116 serial_frame_ready = 1;
118 serial_frame_pos = 0;
121 /* ----------------- iBus ------------------ */
123 typedef int16_t servo_val_t;
124 servo_val_t ibus_channel[N_IBUS_CHANNELS];
125 #define N_IBUS_CHANNELS_DIRECT 14
127 * channel value ibus frame value
134 #define IBUS_SERVO_CENTER 1500
136 static void ibus_servo_frame(void)
138 uint16_t csum = 0xFFFF;
141 for (i = 0; i < serial_frame[0]-2; i++)
142 csum -= (uint16_t)serial_frame[i];
144 if ((serial_frame[serial_frame[0]-2] != (csum & 0xFF))
145 || (serial_frame[serial_frame[0]-1] != (csum >> 8))) {
150 for (i = 0; i < N_IBUS_CHANNELS_DIRECT; i++)
151 ibus_channel[i] = (uint16_t)serial_frame[2 + 2*i]
152 + (((uint16_t)serial_frame[3 + 2*i] & 0x000F) << 8)
154 // TODO channels 15-18
157 /* -------------- PWM output --------------- */
159 #define SERVO_PWM_CENTER (1500 * TICKS_IN_US)
161 time_t pwm_channels[N_PWM_CHANNELS];
162 volatile uint8_t pwm_busy;
163 uint8_t pwm_data_ready;
165 static uint8_t pwm_channel;
166 static time_t pwm_frame_start;
168 // TODO: move this into PROGMEM?
169 static const uint8_t pwm_channel_bit[] = {
178 #define PWM_CH_MASK (_BV(PD2)|_BV(PD3)|_BV(PD4)|_BV(PD5)|_BV(PD6)|_BV(PD7))
179 #define PWM_FRAME_LEN (20000 * TICKS_IN_US)
181 static void pwm_init(void)
185 for (i = 0; i < N_PWM_CHANNELS; i++)
188 pwm_channel = N_PWM_CHANNELS + 1;
189 pwm_data_ready = pwm_busy = 0;
191 TIMSK1 &= ~_BV(OCIE1A);
192 PORTD &= ~PWM_CH_MASK;
196 static void pwm_set(uint8_t channel, servo_val_t value)
198 time_t tm = SERVO_PWM_CENTER + (value * TICKS_IN_US);
200 pwm_channels[channel] = tm;
203 static void pwm_send_pulse()
205 uint8_t first_pulse = 0;
207 if (pwm_channel == N_PWM_CHANNELS + 1) {
213 if (pwm_channel == 0) {
218 // find a non-empty channel
219 while (!pwm_channels[pwm_channel] && pwm_channel < N_PWM_CHANNELS)
222 ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
224 if (pwm_channel < N_PWM_CHANNELS) {
225 PORTD |= pwm_channel_bit[pwm_channel];
226 OCR1A = now + pwm_channels[pwm_channel];
228 pwm_frame_start = now;
230 OCR1A = pwm_frame_start + PWM_FRAME_LEN;
233 TIMSK1 |= _BV(OCIE1A);
238 ISR(TIMER1_COMPA_vect) {
239 PORTD &= ~PWM_CH_MASK;
240 TIMSK1 &= ~_BV(OCIE1A);
246 /* -------- Custom mixing done here -------- */
248 static void do_mixes()
252 for (i = 0; i < N_PWM_CHANNELS; i++) {
253 pwm_set(i, ibus_channel[i]);
268 if (serial_frame_ready) {
269 serial_frame_ready = 0;