summaryrefslogtreecommitdiff
path: root/apps
diff options
context:
space:
mode:
Diffstat (limited to 'apps')
-rw-r--r--apps/plugins/SOURCES5
-rw-r--r--apps/plugins/alpine_cdc.c1199
2 files changed, 1204 insertions, 0 deletions
diff --git a/apps/plugins/SOURCES b/apps/plugins/SOURCES
index 775e20e9ee..1948cf663e 100644
--- a/apps/plugins/SOURCES
+++ b/apps/plugins/SOURCES
@@ -54,3 +54,8 @@ euroconverter.c
54jackpot.c 54jackpot.c
55nim.c 55nim.c
56#endif /* #ifdef HAVE_LCD_CHARCELLS */ 56#endif /* #ifdef HAVE_LCD_CHARCELLS */
57
58#ifndef HAVE_MMC
59/* not for Ondio, has no remote control pin */
60alpine_cdc.c
61#endif
diff --git a/apps/plugins/alpine_cdc.c b/apps/plugins/alpine_cdc.c
new file mode 100644
index 0000000000..16ebf3950f
--- /dev/null
+++ b/apps/plugins/alpine_cdc.c
@@ -0,0 +1,1199 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 *
11 * Copyright (C) 2003 Jörg Hohensohn
12 *
13 * Alpine CD changer Project
14 * This is a feasibility study for Archos emulating an Alpine M-Bus CD changer.
15 *
16 * Currently realized as a plugin, which can't control the mp3 playback.
17 * It "just" follows all radio commands and answers accordingly.
18 * The debug version shows a dump of the M-Bus communication on screen.
19 *
20 * Usage: Start plugin, it will stay in the background and do the emulation.
21 * You need to make an adapter with an 8-pin DIN plug for the radio at one end
22 * and a 4-ring 3.5 mm plug for the Archos headphone jack at the other.
23 * The Archos remote pin connects to the M-Bus, audio as usual.
24 *
25 * All files in this archive are subject to the GNU General Public License.
26 * See the file COPYING in the source tree root for full license agreement.
27 *
28 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
29 * KIND, either express or implied.
30 *
31 ****************************************************************************/
32
33/* Only build for (correct) target */
34#if !defined(SIMULATOR) && CONFIG_CPU==SH7034 && !defined(HAVE_MMC)
35
36#ifdef HAVE_LCD_CHARCELLS /* player model */
37#define LINES 2
38#define COLUMNS 11
39#else /* recorder models */
40#define LINES 8
41#define COLUMNS 32 /* can't really tell for proportional font */
42#endif
43
44/****************** imports ******************/
45
46#include "plugin.h"
47#include "sh7034.h"
48#include "system.h"
49
50/****************** constants ******************/
51
52/* measured bit time on the M-Bus is 3.075 ms = 325.2 Hz */
53#define MBUS_BAUDRATE 3252 /* make it 10 * bittime */
54#define MBUS_STEP_FREQ (MBUS_BAUDRATE/2) /* 5 steps per bit */
55#define MBUS_BIT_FREQ (MBUS_BAUDRATE/10) /* the true bit frequency again */
56
57#define MBUS_MAX_SIZE 16 /* maximum length of an M-Bus packet, incl. checksum */
58#define MBUS_RCV_QUEUESIZE 4 /* how many packets can be queued by receiver */
59
60#define ERI1 (*((volatile unsigned long*)0x090001A0)) /* RX error */
61#define RXI1 (*((volatile unsigned long*)0x090001A4)) /* RX */
62
63#define PB10 0x0400
64
65/* receive status */
66#define RX_BUSY 0 /* reception in progress */
67#define RX_RECEIVED 1 /* valid data available */
68#define RX_FRAMING 2 /* frame error */
69#define RX_OVERRUN 3 /* receiver overrun */
70#define RX_PARITY 4 /* parity error */
71#define RX_SYMBOL 5 /* invalid bit timing */
72#define RX_OVERFLOW 6 /* buffer full */
73#define RX_OVERLAP 7 /* receive interrupt while transmitting */
74
75/* timer operation mode */
76#define TM_OFF 0 /* not in use */
77#define TM_TRANSMIT 1 /* periodic timer to transmit */
78#define TM_RX_TIMEOUT 2 /* single shot for receive timeout */
79
80/* emulation play state */
81#define EMU_IDLE 0
82#define EMU_PREPARING 1
83#define EMU_STOPPED 2
84#define EMU_PAUSED 3
85#define EMU_PLAYING 4
86#define EMU_SPINUP 5
87#define EMU_FF 6
88#define EMU_FR 7
89
90
91/****************** prototypes ******************/
92
93void timer_init(unsigned hz, unsigned to); /* setup static timer registers and values */
94void timer_set_mode(int mode); /* define for what the timer should be used right now */
95void timer4_isr(void); /* IMIA4 ISR */
96
97void transmit_isr(void); /* 2nd level ISR for M-Bus transmission */
98
99void uart_init(unsigned baudrate); /* UART setup */
100void uart_rx_isr(void) __attribute__((interrupt_handler)); /* RXI1 ISR */
101void uart_err_isr(void) __attribute__((interrupt_handler)); /* ERI1 ISR */
102void receive_timeout_isr(void); /* 2nd level ISR for receiver timeout */
103
104void mbus_init(void); /* prepare the M-Bus layer */
105int mbus_send(unsigned char* p_msg, int digits); /* packet send */
106int mbus_receive(unsigned char* p_msg, unsigned bufsize, int timeout); /* packet receive */
107
108unsigned char calc_checksum(unsigned char* p_msg, int digits); /* make M-Bus checksum */
109bool bit_test(unsigned char* buf, unsigned bit); /* test one bit of M-Bus packet */
110void bit_set(unsigned char* buf, unsigned bit, bool val); /* set/clear one bit of M-Bus packet */
111
112void print_scroll(char* string); /* implements a scrolling screen */
113void dump_packet(char* dest, int dst_size, char* src, int n); /* convert packet to ASCII */
114
115void emu_init(void); /* init changer emulation */
116void emu_process_packet(unsigned char* mbus_msg, int msg_size); /* feed a received radio packet */
117void emu_tick(void); /* for regular actions of the emulator */
118
119int get_playtime(void); /* return the current track time in seconds */
120int get_tracklength(void); /* return the total length of the current track */
121void set_track(int selected);
122int get_track(void); /* return the track number */
123void set_play(void); /* start or resume playback */
124void set_pause(void); /* pause playback */
125void set_stop(void); /* stop playback */
126void set_position(int seconds); /* seek */
127void get_playmsg(void); /* update the play message with Rockbox info */
128void get_diskmsg(void); /* update the disk status message with Rockbox info */
129
130void sound_neutral(void); /* set to everything flat and 0 dB volume */
131void sound_normal(void); /* return to user settings */
132
133void thread(void); /* the thread running it all */
134int main(void* parameter); /* main loop */
135enum plugin_status plugin_start(struct plugin_api* api, void* parameter); /* entry */
136
137
138/****************** data types ******************/
139
140/* one entry in the receive queue */
141typedef struct
142{
143 unsigned char buf[MBUS_MAX_SIZE]; /* message buffer */
144 unsigned size; /* length of data in the buffer */
145 unsigned error; /* error code from reception */
146} t_rcv_queue_entry;
147
148
149/****************** globals ******************/
150
151
152/* information owned by the timer transmit ISR */
153struct
154{
155 unsigned char send_buf[MBUS_MAX_SIZE]; /* M-Bus message */
156 unsigned send_size; /* current length of data in the buffer */
157 unsigned index; /* index for which byte to send */
158 unsigned byte; /* which byte to send */
159 unsigned bitmask; /* which bit to send */
160 unsigned step; /* where in the pulse are we */
161 bool bit; /* currently sent bit */
162 bool collision; /* set if a collision happened */
163 bool busy; /* flag if in transmission */
164} gSendIRQ;
165
166
167/* information owned by the UART receive ISR */
168struct
169{
170 t_rcv_queue_entry queue[MBUS_RCV_QUEUESIZE]; /* M-Bus message queue */
171 unsigned buf_read; /* readout maintained by the user application */
172 unsigned buf_write; /* writing maintained by ISR */
173 bool overflow; /* indicate queue overflow */
174 unsigned byte; /* currently assembled byte */
175 unsigned bit; /* which bit to receive */
176} gRcvIRQ;
177
178
179/* information owned by the timer */
180struct
181{
182 unsigned mode; /* off, transmit, receive timout */
183 unsigned transmit; /* value for transmit */
184 unsigned timeout; /* value for receive timeout */
185} gTimer;
186
187
188/* information owned by the changer emulation */
189struct
190{
191 unsigned char playmsg[15]; /* current play state msg */
192 unsigned char changemsg[11]; /* changing message */
193 unsigned char diskmsg[12]; /* disk status message */
194 long poll_interval; /* call the emu each n ticks */
195 int time; /* seconds within the song */
196 int set_state; /* the desired state to change into */
197} gEmu;
198
199
200/* communication to the worker thread */
201struct
202{
203 int id; /* ID of the thread */
204 bool foreground; /* set as long as we're owning the UI */
205 bool exiting; /* signal to the thread that we want to exit */
206 bool ended; /* response from the thread, that is has exited */
207} gTread;
208
209static struct plugin_api* rb; /* here is the global API struct pointer */
210
211
212/****************** implementation ******************/
213
214
215/* setup static timer registers and values */
216void timer_init(unsigned hz, unsigned to)
217{
218 rb->memset(&gTimer, 0, sizeof(gTimer));
219
220 gTimer.transmit = FREQ / hz; /* time for bit transitions */
221 gTimer.timeout = FREQ / to; /* time for receive timeout */
222}
223
224
225/* define for what the timer should be used right now */
226void timer_set_mode(int mode)
227{
228 TCNT4 = 0; /* start counting at 0 */
229 gTimer.mode = mode; /* store the mode */
230
231 if (mode == TM_RX_TIMEOUT)
232 {
233 rb->plugin_register_timer(gTimer.timeout, 11, timer4_isr);
234 }
235 else if (mode == TM_TRANSMIT)
236 {
237 rb->plugin_register_timer(gTimer.transmit, 14, timer4_isr);
238 }
239 else
240 {
241 rb->plugin_unregister_timer();
242 }
243}
244
245
246void timer4_isr(void) /* IMIA4 */
247{
248 switch (gTimer.mode)
249 { /* distribute the interrupt */
250 case TM_TRANSMIT:
251 transmit_isr();
252 break;
253 case TM_RX_TIMEOUT:
254 receive_timeout_isr();
255 rb->plugin_unregister_timer(); /* single shot */
256 break;
257 default:
258 timer_set_mode(TM_OFF); /* spurious interrupt */
259 } /* switch */
260}
261
262
263/* About Alpine M-Bus
264 * ------------------
265 *
266 * The protocol uses a single wire in half duplex mode.
267 * A bit like I2C, this wire is either pulled low or left floating high.
268 * Bit time is ~3 ms, a "zero" is coded as ~0.6 ms low, a "one" as ~1.8 ms low.
269 * Nice to view in a 0.6 ms grid:
270 *
271 * 0 0.6 1.2 1.8 2.4 3.0
272 * | | | | | |
273 * __ ___________________
274 * \____/ \ "zero" bit
275 * __ _________
276 * \______________/ \ "one" bit
277 *
278 * So I send out the data in a timer interrupt spawned to 0.6 ms.
279 * In phases where the line is floating high, I can check for collisions.
280 * (happens if the other side driving it low, too.)
281 *
282 * Data is transmitted in multiples of 4 bit, to ease BCD representation.
283 */
284
285
286/* 2nd level ISR for M-Bus transmission */
287void transmit_isr(void)
288{
289 bool exit = false;
290
291 TSR4 &= ~0x01; /* clear the interrupt */
292
293 switch(gSendIRQ.step++)
294 {
295 case 0:
296 and_b(~0x04, &PBDRH); /* low (read-modify-write access may have changed it while it was input) */
297 or_b(0x04, &PBIORH); /* drive low (output) */
298 break;
299 case 1: /* 0.6 ms */
300 if (!gSendIRQ.bit) /* sending "zero"? */
301 and_b(~0x04, &PBIORH); /* float (input) */
302 break;
303 case 2: /* 1.2 ms */
304 if (!gSendIRQ.bit && ((PBDR & PB10) == 0))
305 gSendIRQ.collision = true;
306 break;
307 case 3: /* 1.8 ms */
308 if (gSendIRQ.bit) /* sending "one"? */
309 and_b(~0x04, &PBIORH); /* float (input) */
310 else if ((PBDR & PB10) == 0)
311 gSendIRQ.collision = true;
312 break;
313 case 4: /* 2.4 ms */
314 if ((PBDR & PB10) == 0)
315 gSendIRQ.collision = true;
316
317 /* prepare next round */
318 gSendIRQ.step = 0;
319 gSendIRQ.bitmask >>= 1;
320 if (gSendIRQ.bitmask)
321 { /* new bit */
322 gSendIRQ.bit = (gSendIRQ.byte & gSendIRQ.bitmask) != 0;
323 }
324 else
325 { /* new byte */
326 if (++gSendIRQ.index < gSendIRQ.send_size)
327 {
328 gSendIRQ.bitmask = 0x08;
329 gSendIRQ.byte = gSendIRQ.send_buf[gSendIRQ.index];
330 gSendIRQ.bit = (gSendIRQ.byte & gSendIRQ.bitmask) != 0;
331 }
332 else
333 exit = true; /* done */
334 }
335 break;
336 }
337
338 if (exit || gSendIRQ.collision)
339 { /* stop transmission */
340 or_b(0x20, PBCR1_ADDR+1); /* RxD1 again for PB10 */
341 timer_set_mode(TM_OFF); /* stop the timer */
342 gSendIRQ.busy = false; /* do this last, to avoid race conditions */
343 }
344}
345
346
347/* For receiving, I use the "normal" serial RX feature of the CPU,
348 * so we can receive within an interrupt, no line polling necessary.
349 * Luckily, the M-Bus bit always starts with a falling edge and ends with a high,
350 * this matches with the start bit and the stop bit of a serial transmission.
351 * The baudrate is set such that the M-Bus bit time (ca. 3ms) matches
352 * the serial reception time of one byte, so we receive one byte per
353 * M-Bus bit.
354 * Start bit, 8 data bits and stop bit (total=10) nicely fall into the 5
355 * phases like above:
356 *
357 * 0 0.6 1.2 1.8 2.4 3.0 ms
358 * | | | | | | time
359 * __ _______________________________
360 * \_______/ \ "zero" bit
361 * __ _______________
362 * \_______________________/ \ "one" bit
363 *
364 * | | | | | | | | | | | serial sampling interval
365 * Start 0 1 2 3 4 5 6 7 Stop bit (LSB first!)
366 *
367 * By looking at the bit pattern in the serial byte we can distinguish
368 * the short low from the longer low, tell "zero" and "one" apart.
369 * So we receive 0xFE for a "zero", 0xE0 for a "one".
370 * It may be necessary to treat the bits next to transitions as don't care,
371 * in case the timing is not so accurate.
372 * Bits are always sent "back-to-back", so I detect the packet end by timeout.
373 */
374
375
376void uart_init(unsigned baudrate)
377{
378 RXI1 = (unsigned long)uart_rx_isr; /* install ISR */
379 ERI1 = (unsigned long)uart_err_isr; /* install ISR */
380
381 SCR1 = 0x00; /* disable everything; select async mode with SCK pin as I/O */
382 SMR1 = 0x00; /* async, 8N1, NoMultiProc, sysclock/1 */
383 BRR1 = ((FREQ/(32*baudrate))-1);
384
385 IPRE = (IPRE & ~0xf000) | 0xc000; /* interrupt on level 12 */
386
387 rb->sleep(1); /* hardware needs to settle for at least one bit interval */
388
389 and_b(~(SCI_RDRF | SCI_ORER | SCI_FER | SCI_PER), &SSR1); /* clear any receiver flag */
390 or_b(SCI_RE | SCI_RIE , &SCR1); /* enable the receiver with interrupt */
391}
392
393
394void uart_rx_isr(void) /* RXI1 */
395{
396 unsigned char data;
397 t_rcv_queue_entry* p_entry = &gRcvIRQ.queue[gRcvIRQ.buf_write]; /* short cut */
398
399 data = RDR1; /* get data */
400
401 and_b(~SCI_RDRF, &SSR1); /* clear data received flag */
402
403 if (gTimer.mode == TM_TRANSMIT)
404 p_entry->error = RX_OVERLAP; /* oops, we're also transmitting, stop */
405 else
406 timer_set_mode(TM_RX_TIMEOUT); /* (re)spawn timeout */
407
408 if (p_entry->error != RX_BUSY)
409 return;
410
411 if ((data & ~0x00) == 0xFE) /* 01111111 in line order (reverse) */
412 { /* "zero" received */
413 gRcvIRQ.byte <<= 1;
414 }
415 else if ((data & ~0x00) == 0xE0) /* 00000111 in line order (reverse) */
416 { /* "one" received */
417 gRcvIRQ.byte = gRcvIRQ.byte << 1 | 0x01;
418 }
419 else
420 { /* unrecognized pulse */
421 p_entry->error = RX_SYMBOL;
422 }
423
424 if (p_entry->error == RX_BUSY)
425 {
426 if (++gRcvIRQ.bit >= 4)
427 { /* byte completed */
428 if (p_entry->size >= sizeof(p_entry->buf))
429 {
430 p_entry->error = RX_OVERFLOW; /* buffer full */
431 }
432 else
433 {
434 p_entry->buf[p_entry->size] = gRcvIRQ.byte;
435 gRcvIRQ.byte = 0;
436 gRcvIRQ.bit = 0;
437 p_entry->size++;
438 }
439 }
440 }
441}
442
443
444void uart_err_isr(void) /* ERI1 */
445{
446 t_rcv_queue_entry* p_entry = &gRcvIRQ.queue[gRcvIRQ.buf_write]; /* short cut */
447
448 if (p_entry->error == RX_BUSY)
449 { /* terminate reception in case of error */
450 if (SSR1 & SCI_FER)
451 p_entry->error = RX_FRAMING;
452 else if (SSR1 & SCI_ORER)
453 p_entry->error = RX_OVERRUN;
454 else if (SSR1 & SCI_PER)
455 p_entry->error = RX_PARITY;
456 }
457
458 /* clear any receiver flag */
459 and_b(~(SCI_RDRF | SCI_ORER | SCI_FER | SCI_PER), &SSR1);
460}
461
462
463/* 2nd level ISR for receiver timeout, this finalizes reception */
464void receive_timeout_isr(void)
465{
466 t_rcv_queue_entry* p_entry = &gRcvIRQ.queue[gRcvIRQ.buf_write]; /* short cut */
467
468 timer_set_mode(TM_OFF); /* single shot */
469
470 if (p_entry->error == RX_BUSY) /* everthing OK so far? */
471 p_entry->error = RX_RECEIVED; /* end with valid data */
472
473 /* move to next queue entry */
474 gRcvIRQ.buf_write++;
475 if (gRcvIRQ.buf_write >= MBUS_RCV_QUEUESIZE)
476 gRcvIRQ.buf_write = 0;
477 p_entry = &gRcvIRQ.queue[gRcvIRQ.buf_write];
478
479 if (gRcvIRQ.buf_write == gRcvIRQ.buf_read)
480 { /* queue overflow */
481 gRcvIRQ.overflow = true;
482 /* what can I do? Continueing overwrites the oldest. */
483 }
484
485 gRcvIRQ.byte = 0;
486 gRcvIRQ.bit = 0;
487 p_entry->size = 0;
488 p_entry->error = RX_BUSY; /* enable receive on new entry */
489}
490
491
492/* generate the checksum */
493unsigned char calc_checksum(unsigned char* p_msg, int digits)
494{
495 int chk = 0;
496 int i;
497
498 for (i=0; i<digits; i++)
499 {
500 chk ^= p_msg[i];
501 }
502 chk = (chk+1) % 16;
503
504 return chk;
505}
506
507
508/****************** high-level M-Bus functions ******************/
509
510void mbus_init(void)
511{
512 /* init the send object */
513 rb->memset(&gSendIRQ, 0, sizeof(gSendIRQ));
514 timer_init(MBUS_STEP_FREQ, (MBUS_BIT_FREQ*10)/15); /* setup frequency and timeout (1.5 bit) */
515
516 /* init receiver */
517 rb->memset(&gRcvIRQ, 0, sizeof(gRcvIRQ));
518 uart_init(MBUS_BAUDRATE);
519}
520
521
522/* send out a number of BCD digits (one per byte) with M-Bus protocol */
523int mbus_send(unsigned char* p_msg, int digits)
524{
525 /* wait for previous transmit/receive to end */
526 while(gTimer.mode != TM_OFF) /* wait for "free line" */
527 rb->sleep(1);
528
529 /* fill in our part */
530 rb->memcpy(gSendIRQ.send_buf, p_msg, digits);
531
532 /* add checksum */
533 gSendIRQ.send_buf[digits] = calc_checksum(p_msg, digits);
534 digits++;
535
536 /* debug dump, to be removed */
537 if (gTread.foreground)
538 {
539 char buf[MBUS_MAX_SIZE+1];
540 dump_packet(buf, sizeof(buf), gSendIRQ.send_buf, digits);
541 /*print_scroll(buf); */
542 }
543
544 gSendIRQ.send_size = digits;
545
546 /* prepare everything so the ISR can start right away */
547 gSendIRQ.index = 0;
548 gSendIRQ.byte = gSendIRQ.send_buf[0];
549 gSendIRQ.bitmask = 0x08;
550 gSendIRQ.step = 0;
551 gSendIRQ.bit = (gSendIRQ.byte & gSendIRQ.bitmask) != 0;
552 gSendIRQ.collision = false;
553 gSendIRQ.busy = true;
554
555 /* last chance to wait for a new detected receive to end */
556 while(gTimer.mode != TM_OFF) /* wait for "free line" */
557 rb->sleep(1);
558
559 and_b(~0x30, PBCR1_ADDR+1); /* GPIO for PB10 */
560 timer_set_mode(TM_TRANSMIT); /* run */
561
562 /* make the call blocking until sent out */
563 rb->sleep(digits*4*HZ/MBUS_BIT_FREQ); /* should take this long */
564
565 while(gSendIRQ.busy) /* poll in case it lasts longer */
566 rb->sleep(1); /* (should not happen) */
567
568 /* debug output, to be removed */
569 if (gTread.foreground)
570 {
571 if (gSendIRQ.collision)
572 print_scroll("collision");
573 }
574
575 return gSendIRQ.collision;
576}
577
578
579/* returns the size of message copy, 0 if timed out, negative on error */
580int mbus_receive(unsigned char* p_msg, unsigned bufsize, int timeout)
581{
582 int retval = 0;
583
584 do
585 {
586 if (gRcvIRQ.buf_read != gRcvIRQ.buf_write)
587 { /* something in the queue */
588 t_rcv_queue_entry* p_entry = &gRcvIRQ.queue[gRcvIRQ.buf_read]; /* short cut */
589
590 if (p_entry->error == RX_RECEIVED)
591 { /* seems valid */
592 rb->memcpy(p_msg, p_entry->buf, MIN(p_entry->size, bufsize));
593 retval = p_entry->size; /* return message size */
594 }
595 else
596 { /* an error occured */
597 retval = - p_entry->error; /* return negative number */
598 }
599
600 /* next queue readout position */
601 gRcvIRQ.buf_read++;
602 if (gRcvIRQ.buf_read >= MBUS_RCV_QUEUESIZE)
603 gRcvIRQ.buf_read = 0;
604
605 return retval; /* exit */
606 }
607
608 if (timeout != 0 || gTimer.mode != TM_OFF) /* also carry on if reception in progress */
609 {
610 if (timeout != -1 && timeout != 0) /* if not infinite or expired */
611 timeout--;
612
613 rb->sleep(1); /* wait a while */
614 }
615
616 } while (timeout != 0 || gTimer.mode != TM_OFF);
617
618 return 0; /* timeout */
619}
620
621
622/****************** MMI helper fuctions ******************/
623
624
625void print_scroll(char* string)
626{
627 static char screen[LINES][COLUMNS+1]; /* visible strings */
628 static unsigned pos = 0; /* next print position */
629 static unsigned screentop = 0; /* for scrolling */
630
631 if (!gTread.foreground)
632 return; /* just to protect careless callers */
633
634 if (pos >= LINES)
635 { /* need to scroll first */
636 int i;
637 rb->lcd_clear_display();
638 screentop++;
639 for (i=0; i<LINES-1; i++)
640 rb->lcd_puts(0, i, screen[(i+screentop) % LINES]);
641
642 pos = LINES-1;
643 }
644
645 /* no strncpy avail. */
646 rb->snprintf(screen[(pos+screentop) % LINES], sizeof(screen[0]), "%s", string);
647
648 rb->lcd_puts(0, pos, screen[(pos+screentop) % LINES]);
649#ifndef HAVE_LCD_CHARCELLS
650 rb->lcd_update();
651#endif
652 pos++;
653}
654
655
656void dump_packet(char* dest, int dst_size, char* src, int n)
657{
658 int i;
659 int len = MIN(dst_size-1, n);
660
661 for (i=0; i<len; i++)
662 { /* convert to hex digits */
663 dest[i] = src[i] < 10 ? '0' + src[i] : 'A' + src[i] - 10;
664 }
665 dest[i] = '\0'; /* zero terminate string */
666}
667
668
669/****************** CD changer emulation ******************/
670
671bool bit_test(unsigned char* buf, unsigned bit)
672{
673 return (buf[bit/4] & (0x01 << bit%4)) != 0;
674}
675
676
677void bit_set(unsigned char* buf, unsigned bit, bool val)
678{
679 if (val)
680 buf[bit/4] |= (0x01 << bit%4);
681 else
682 buf[bit/4] &= ~(0x01 << bit%4);
683}
684
685
686void emu_init(void)
687{
688 rb->memset(&gEmu, 0, sizeof(gEmu));
689
690 gEmu.poll_interval = HZ;
691
692 /* init the play message to 990000000000000 */
693 gEmu.playmsg[0] = gEmu.playmsg[1] = 0x9;
694
695 /* init the changing message to 9B900000001 */
696 gEmu.changemsg[0] = gEmu.changemsg[2] = 0x9;
697 gEmu.changemsg[1] = 0xB;
698 gEmu.changemsg[10] = 0x1;
699
700 /* init the disk status message to 9C1019999990 */
701 rb->memset(&gEmu.diskmsg, 0x9, sizeof(gEmu.diskmsg));
702 gEmu.diskmsg[1] = 0xC;
703 gEmu.diskmsg[2] = gEmu.diskmsg[4] = 0x1;
704 gEmu.diskmsg[3] = gEmu.diskmsg[11] = 0x0;
705}
706
707/* feed a radio command into the emulator */
708void emu_process_packet(unsigned char* mbus_msg, int msg_size)
709{
710 bool playmsg_dirty = false;
711 bool diskmsg_dirty = false;
712
713 if (msg_size == 2 && mbus_msg[0] == 1 && mbus_msg[1] == 8)
714 { /* 18: ping */
715 mbus_send("\x09\x08", 2); /* 98: ping OK */
716 }
717 else if (msg_size == 5 && mbus_msg[0] == 1 && mbus_msg[1] == 1 && mbus_msg[2] == 1)
718 { /* set play state */
719 if (bit_test(mbus_msg, 16))
720 {
721 if (gEmu.set_state == EMU_FF || gEmu.set_state == EMU_FR) /* was seeking? */
722 { /* seek to final position */
723 set_position(gEmu.time);
724 }
725 else if (gEmu.set_state != EMU_PLAYING || gEmu.set_state != EMU_PAUSED)
726 { /* was not playing yet, better send disk message */
727 diskmsg_dirty = true;
728 }
729 set_play();
730 gEmu.set_state = EMU_PLAYING;
731 playmsg_dirty = true;
732 }
733
734 if (bit_test(mbus_msg, 17))
735 {
736 gEmu.set_state = EMU_PAUSED;
737 playmsg_dirty = true;
738 set_pause();
739 }
740
741 if (bit_test(mbus_msg, 14))
742 {
743 gEmu.set_state = EMU_STOPPED;
744 playmsg_dirty = true;
745 set_stop();
746 }
747
748 if (bit_test(mbus_msg, 18))
749 {
750 gEmu.set_state = EMU_FF;
751 playmsg_dirty = true;
752 set_pause();
753 }
754
755 if (bit_test(mbus_msg, 19))
756 {
757 gEmu.set_state = EMU_FR;
758 playmsg_dirty = true;
759 set_pause();
760 }
761
762 if (bit_test(mbus_msg, 12)) /* scan stop */
763 {
764 bit_set(gEmu.playmsg, 51, false);
765 playmsg_dirty = true;
766 }
767
768 if (gEmu.set_state == EMU_FF || gEmu.set_state == EMU_FR)
769 gEmu.poll_interval = HZ/4; /* faster refresh */
770 else
771 gEmu.poll_interval = HZ;
772 }
773 else if (msg_size == 8 && mbus_msg[0] == 1 && mbus_msg[1] == 1 && mbus_msg[2] == 4)
774 { /* set program mode */
775 gEmu.playmsg[11] = mbus_msg[3]; /* copy repeat, random, intro */
776 gEmu.playmsg[12] = mbus_msg[4]; /* ToDo */
777 playmsg_dirty = true;
778 }
779 else if (msg_size ==8 && mbus_msg[0] == 1 && mbus_msg[1] == 1 && mbus_msg[2] == 3)
780 { /* changing */
781 gEmu.time = 0; /* reset playtime */
782 playmsg_dirty = true;
783 if (mbus_msg[3] == 0)
784 { /* changing track */
785 if (mbus_msg[4] == 0xA && mbus_msg[5] == 0x3)
786 { /* next random */
787 gEmu.playmsg[3] = rb->rand() % 10; /* ToDo */
788 gEmu.playmsg[4] = rb->rand() % 10;
789 }
790 else if (mbus_msg[4] == 0xB && mbus_msg[5] == 0x3)
791 { /* previous random */
792 gEmu.playmsg[3] = rb->rand() % 10; /* ToDo */
793 gEmu.playmsg[4] = rb->rand() % 10;
794 }
795 else
796 { /* normal track select */
797 set_track(mbus_msg[4]*10 + mbus_msg[5]);
798 }
799 }
800 else
801 { /* changing disk */
802 diskmsg_dirty = true;
803 gEmu.changemsg[3] = mbus_msg[3]; /* copy disk */
804 gEmu.diskmsg[2] = mbus_msg[3];
805 gEmu.changemsg[7] = gEmu.playmsg[11]; /* copy flags from status */
806 gEmu.changemsg[8] = gEmu.playmsg[12];
807 /*gEmu.playmsg[3] = 0; */ /* reset to track 1 */
808 /*gEmu.playmsg[4] = 1; */
809 mbus_send(gEmu.changemsg, sizeof(gEmu.changemsg));
810 }
811 }
812 else
813 { /* if in doubt, send Ack */
814 mbus_send("\x09\x0F\x00\x00\x00\x00\x00", 7);
815 }
816
817 if (playmsg_dirty)
818 {
819 rb->yield(); /* give the mpeg thread a chance to process */
820 get_playmsg(); /* force update */
821 mbus_send(gEmu.playmsg, sizeof(gEmu.playmsg));
822 }
823
824 if (diskmsg_dirty)
825 {
826 get_diskmsg(); /* force update */
827 mbus_send(gEmu.diskmsg, sizeof(gEmu.diskmsg));
828 }
829}
830
831
832/* called each second in case the emulator has something to do */
833void emu_tick(void)
834{
835 get_playmsg(); /* force update */
836 if (bit_test(gEmu.playmsg, 56)) /* play bit */
837 {
838 unsigned remain; /* helper as we walk down the digits */
839
840 switch(gEmu.set_state)
841 {
842 case EMU_FF:
843 gEmu.time += 10;
844 case EMU_FR:
845 gEmu.time -= 5;
846
847 if (gEmu.time < 0)
848 gEmu.time = 0;
849 else if (gEmu.time > get_tracklength())
850 gEmu.time = get_tracklength();
851
852 /* convert value to MM:SS */
853 remain = (unsigned)gEmu.time;
854 gEmu.playmsg[7] = remain / (10*60);
855 remain -= gEmu.playmsg[7] * (10*60);
856 gEmu.playmsg[8] = remain / 60;
857 remain -= gEmu.playmsg[8] * 60;
858 gEmu.playmsg[9] = remain / 10;
859 remain -= gEmu.playmsg[9] * 10;
860 gEmu.playmsg[10] = remain;
861 }
862
863 mbus_send(gEmu.playmsg, sizeof(gEmu.playmsg));
864 }
865}
866
867
868/****************** communication with Rockbox playback ******************/
869
870
871/* update the play message with Rockbox info */
872void get_playmsg(void)
873{
874 int track, time;
875
876 if (gEmu.set_state != EMU_FF && gEmu.set_state != EMU_FR)
877 {
878 switch(rb->mpeg_status())
879 {
880 case MPEG_STATUS_PLAY:
881 print_scroll("MpegStat Play");
882 if (gEmu.set_state == EMU_FF || gEmu.set_state == EMU_FR)
883 gEmu.playmsg[2] = gEmu.set_state; /* set FF/FR */
884 else
885 gEmu.playmsg[2] = EMU_PLAYING; /* set normal play */
886
887 bit_set(gEmu.playmsg, 56, true); /* set play */
888 bit_set(gEmu.playmsg, 57, false); /* clear pause */
889 bit_set(gEmu.playmsg, 59, false); /* clear stop */
890 break;
891
892 case MPEG_STATUS_PLAY | MPEG_STATUS_PAUSE:
893 print_scroll("MpegStat Pause");
894 gEmu.playmsg[2] = EMU_PAUSED;
895 bit_set(gEmu.playmsg, 56, false); /* clear play */
896 bit_set(gEmu.playmsg, 57, true); /* set pause */
897 bit_set(gEmu.playmsg, 59, false); /* clear stop */
898 break;
899
900 default:
901 print_scroll("MpegStat 0");
902 gEmu.playmsg[2] = EMU_STOPPED;
903 bit_set(gEmu.playmsg, 56, false); /* clear play */
904 bit_set(gEmu.playmsg, 57, false); /* clear pause */
905 bit_set(gEmu.playmsg, 59, true); /* set stop */
906 break;
907 }
908
909 /* convert value to MM:SS */
910 time = get_playtime();
911 gEmu.time = time; /* copy it */
912 gEmu.playmsg[7] = time / (10*60);
913 time -= gEmu.playmsg[7] * (10*60);
914 gEmu.playmsg[8] = time / 60;
915 time -= gEmu.playmsg[8] * 60;
916 gEmu.playmsg[9] = time / 10;
917 time -= gEmu.playmsg[9] * 10;
918 gEmu.playmsg[10] = time;
919 }
920 else /* FF/FR */
921 {
922 gEmu.playmsg[2] = gEmu.set_state; /* in FF/FR, report that instead */
923 }
924
925 track = get_track();
926 gEmu.playmsg[3] = track / 10;
927 gEmu.playmsg[4] = track % 10;
928}
929
930/* update the disk status message with Rockbox info */
931void get_diskmsg(void)
932{
933 int tracks = rb->playlist_amount();
934 if (tracks > 99)
935 tracks = 99;
936 gEmu.diskmsg[5] = tracks / 10;
937 gEmu.diskmsg[6] = tracks % 10;
938}
939
940/* return the current track time in seconds */
941int get_playtime(void)
942{
943 struct mp3entry* p_mp3entry;
944
945 p_mp3entry = rb->mpeg_current_track();
946 if (p_mp3entry == NULL)
947 return 0;
948
949 return p_mp3entry->elapsed / 1000;
950}
951
952/* return the total length of the current track */
953int get_tracklength(void)
954{
955 struct mp3entry* p_mp3entry;
956
957 p_mp3entry = rb->mpeg_current_track();
958 if (p_mp3entry == NULL)
959 return 0;
960
961 return p_mp3entry->length / 1000;
962}
963
964/* change to a new track */
965void set_track(int selected)
966{
967 if (selected > get_track())
968 {
969 print_scroll("mpeg_next");
970 rb->mpeg_next();
971 }
972 else if (selected < get_track())
973 {
974 print_scroll("mpeg_prev");
975 rb->mpeg_prev();
976 }
977}
978
979/* return the track number */
980int get_track(void)
981{
982 struct mp3entry* p_mp3entry;
983
984 p_mp3entry = rb->mpeg_current_track();
985 if (p_mp3entry == NULL)
986 return 0;
987
988 return p_mp3entry->index + 1; /* track numbers start with 1 */
989}
990
991/* start or resume playback */
992void set_play(void)
993{
994 if (rb->mpeg_status() == MPEG_STATUS_PLAY)
995 return;
996
997 if (rb->mpeg_status() == (MPEG_STATUS_PLAY | MPEG_STATUS_PAUSE))
998 {
999 print_scroll("mpeg_resume");
1000 rb->mpeg_resume();
1001 }
1002 else
1003 {
1004 print_scroll("mpeg_play(0)");
1005 rb->mpeg_play(0);
1006 }
1007}
1008
1009/* pause playback */
1010void set_pause(void)
1011{
1012 if (rb->mpeg_status() == MPEG_STATUS_PLAY)
1013 {
1014 print_scroll("mpeg_pause");
1015 rb->mpeg_pause();
1016 }
1017}
1018
1019/* stop playback */
1020void set_stop(void)
1021{
1022 if (rb->mpeg_status() & MPEG_STATUS_PLAY)
1023 {
1024 print_scroll("mpeg_stop");
1025 rb->mpeg_stop();
1026 }
1027}
1028
1029/* seek */
1030void set_position(int seconds)
1031{
1032 if (rb->mpeg_status() & MPEG_STATUS_PLAY)
1033 {
1034 print_scroll("mpeg_ff_rewind");
1035 rb->mpeg_ff_rewind(seconds * 1000);
1036 }
1037}
1038
1039/****************** main thread + helper ******************/
1040
1041/* set to everything flat and 0 dB volume */
1042void sound_neutral(void)
1043{ /* neutral sound settings */
1044 rb->mpeg_sound_set(SOUND_BASS, 0);
1045 rb->mpeg_sound_set(SOUND_TREBLE, 0);
1046 rb->mpeg_sound_set(SOUND_BALANCE, 0);
1047 rb->mpeg_sound_set(SOUND_VOLUME, 92); /* 0 dB */
1048 rb->mpeg_sound_set(SOUND_LOUDNESS, 0);
1049 rb->mpeg_sound_set(SOUND_SUPERBASS, 0);
1050 rb->mpeg_sound_set(SOUND_AVC, 0);
1051}
1052
1053/* return to user settings */
1054void sound_normal(void)
1055{ /* restore sound settings */
1056 rb->mpeg_sound_set(SOUND_BASS, rb->global_settings->bass);
1057 rb->mpeg_sound_set(SOUND_TREBLE, rb->global_settings->treble);
1058 rb->mpeg_sound_set(SOUND_BALANCE, rb->global_settings->balance);
1059 rb->mpeg_sound_set(SOUND_VOLUME, rb->global_settings->volume);
1060 rb->mpeg_sound_set(SOUND_LOUDNESS, rb->global_settings->loudness);
1061 rb->mpeg_sound_set(SOUND_SUPERBASS, rb->global_settings->superbass);
1062 rb->mpeg_sound_set(SOUND_AVC, rb->global_settings->avc);
1063}
1064
1065/* the thread running it all */
1066void thread(void)
1067{
1068 int msg_size;
1069 unsigned char mbus_msg[MBUS_MAX_SIZE];
1070 char buf[32];
1071 bool connected = false;
1072 long last_tick = *rb->current_tick; /* for 1 sec tick */
1073
1074 do
1075 {
1076 msg_size = mbus_receive(mbus_msg, sizeof(mbus_msg), 1);
1077 if (msg_size > 0)
1078 { /* received something */
1079 if(gTread.foreground)
1080 {
1081 dump_packet(buf, sizeof(buf), mbus_msg, msg_size);
1082 /*print_scroll(buf); */
1083 }
1084 if (msg_size > 2 && mbus_msg[0] == 1
1085 && mbus_msg[msg_size-1] == calc_checksum(mbus_msg, msg_size-1))
1086 { /* sanity and checksum OK */
1087 if (!connected)
1088 { /* with the first received packet: */
1089 sound_neutral(); /* set to flat and 0dB volume */
1090 connected = true;
1091 }
1092 emu_process_packet(mbus_msg, msg_size-1); /* pass without chksum */
1093 }
1094 else if(gTread.foreground)
1095 { /* not OK */
1096 print_scroll("bad packet");
1097 }
1098 }
1099 else if (msg_size < 0 && gTread.foreground)
1100 { /* error */
1101 rb->snprintf(buf, sizeof(buf), "rcv error %d", msg_size);
1102 print_scroll(buf);
1103 }
1104
1105 if (*rb->current_tick - last_tick >= gEmu.poll_interval)
1106 { /* call the emulation regulary */
1107 emu_tick();
1108 last_tick += gEmu.poll_interval;
1109 }
1110
1111 } while (!gTread.exiting);
1112
1113 gTread.ended = true; /* acknowledge the exit */
1114 rb->remove_thread(gTread.id); /* commit suicide */
1115 rb->yield(); /* pass control to other threads, we won't return */
1116}
1117
1118/* callback to end the TSR plugin, called before a new one gets loaded */
1119void exit_tsr(void)
1120{
1121 gTread.exiting = true; /* tell the thread to end */
1122 while (!gTread.ended) /* wait until it did */
1123 rb->yield();
1124
1125 uart_init(BAUDRATE); /* return to standard baudrate */
1126 IPRE = (IPRE & ~0xF000); /* UART interrupt off */
1127 timer_set_mode(TM_OFF); /* timer interrupt off */
1128
1129 sound_normal(); /* restore sound settings */
1130}
1131
1132/****************** main ******************/
1133
1134
1135int main(void* parameter)
1136{
1137 (void)parameter;
1138 /*int button; */
1139 int stacksize;
1140 void* stack;
1141
1142 mbus_init(); /* init the M-Bus layer */
1143 emu_init(); /* init emulator */
1144
1145 rb->splash(HZ/5, true, "Alpine CDC"); /* be quick on autostart */
1146
1147#ifdef DEBUG
1148 print_scroll("Alpine M-Bus Test");
1149 print_scroll("any key to TSR");
1150#endif
1151
1152 /* init the worker thread */
1153 stack = rb->plugin_get_buffer(&stacksize); /* use the rest as stack */
1154 stack = (void*)(((unsigned int)stack + 100) & ~3); /* a bit away, 32 bit align */
1155 stacksize = (stacksize - 100) & ~3;
1156 if (stacksize < DEFAULT_STACK_SIZE)
1157 {
1158 rb->splash(HZ*2, true, "Out of memory");
1159 return -1;
1160 }
1161
1162 rb->memset(&gTread, 0, sizeof(gTread));
1163 gTread.foreground = true;
1164 gTread.id = rb->create_thread(thread, stack, stacksize, "CDC");
1165
1166#ifdef DEBUG
1167 do
1168 {
1169 button = rb->button_get(true);
1170 } while (button & BUTTON_REL));
1171#endif
1172
1173 gTread.foreground = false; /* we're in the background now */
1174 rb->plugin_tsr(exit_tsr); /* stay resident */
1175
1176#ifdef DEBUG
1177 return rb->default_event_handler(button);
1178#else
1179 return 0;
1180#endif
1181}
1182
1183
1184/***************** Plugin Entry Point *****************/
1185
1186
1187enum plugin_status plugin_start(struct plugin_api* api, void* parameter)
1188{
1189 /* this macro should be called as the first thing you do in the plugin.
1190 it test that the api version and model the plugin was compiled for
1191 matches the machine it is running on */
1192 TEST_PLUGIN_API(api);
1193 rb = api; /* copy to global api pointer */
1194
1195 /* now go ahead and have fun! */
1196 return (main(parameter)==0) ? PLUGIN_OK : PLUGIN_ERROR;
1197}
1198
1199#endif /* #ifndef SIMULATOR, etc. */