diff --git a/firmware/SOURCES b/firmware/SOURCES index e7d8820..eed089d 100644 --- a/firmware/SOURCES +++ b/firmware/SOURCES @@ -296,6 +296,7 @@ drivers/tuner/tea5767.c #endif /* (CONFIG_TUNER & TEA5767) */ #if (CONFIG_TUNER & SI4700) drivers/tuner/si4700.c +drivers/rds.c #endif /* (CONFIG_TUNER & SI4700) */ #if (CONFIG_TUNER & IPOD_REMOTE_TUNER) drivers/tuner/ipod_remote_tuner.c diff --git a/firmware/drivers/rds.c b/firmware/drivers/rds.c new file mode 100644 index 0000000..4e834c6 --- /dev/null +++ b/firmware/drivers/rds.c @@ -0,0 +1,170 @@ +#include +#include +#include +#include +#include "rds.h" + +/* programme identification */ +static uint16_t pi; +/* program service name */ +static char ps_data[9]; +static char ps_copy[9]; +static int ps_segment; +/* radio text */ +static char rt_data[65]; +static char rt_copy[65]; +static int rt_segment; +static int rt_abflag; + +/* resets the rds parser */ +void rds_reset(void) +{ + ps_copy[0] = '\0'; + rt_copy[0] = '\0'; + pi = 0; +} + +/* initialises the rds parser */ +void rds_init(void) +{ + rds_reset(); +} + +/* handles a group 0 packet, returns true if a complete message was received */ +static bool handle_group0(uint16_t data[4]) +{ + int segment, pos; + + segment = data[1] & 3; + + /* reset parsing if not in order */ + if (segment != ps_segment) { + ps_segment = 0; + if (segment != 0) { + return false; + } + } + + /* store data */ + pos = segment * 2; + ps_data[pos++] = (data[3] >> 8) & 0xFF; + ps_data[pos++] = (data[3] >> 0) & 0xFF; + if (++ps_segment == 4) { + ps_data[pos] = '\0'; + strcpy(ps_copy, ps_data); + return true; + } + return false; +} + +/* handles a radio text characters, returns true if end-of-line found */ +static bool handle_rt(int pos, char c) +{ + switch (c) { + case 0x0A: + /* line break hint */ + rt_data[pos] = ' '; + return false; + case 0x0D: + /* end of line */ + rt_data[pos] = '\0'; + return true; + default: + rt_data[pos] = c; + return false; + } +} + +/* handles a group 2 packet, returns true if a complete message was received */ +static bool handle_group2(uint16_t data[4]) +{ + int abflag, segment, version, pos; + bool done; + + /* reset parsing if not in order */ + abflag = (data[1] >> 4) & 1; + segment = data[1] & 0xF; + if ((abflag != rt_abflag) || (segment != rt_segment)) { + rt_abflag = abflag; + rt_segment = 0; + if (segment != 0) { + return false; + } + } + + /* store data */ + version = (data[1] >> 11) & 1; + done = false; + if (version == 0) { + pos = segment * 4; + done = done || handle_rt(pos++, (data[2] >> 8) & 0xFF); + done = done || handle_rt(pos++, (data[2] >> 0) & 0xFF); + done = done || handle_rt(pos++, (data[3] >> 8) & 0xFF); + done = done || handle_rt(pos++, (data[3] >> 0) & 0xFF); + } else { + pos = segment * 2; + done = done || handle_rt(pos++, (data[3] >> 8) & 0xFF); + done = done || handle_rt(pos++, (data[3] >> 0) & 0xFF); + } + if ((++rt_segment == 16) || done) { + rt_data[pos] = '\0'; + strcpy(rt_copy, rt_data); + return true; + } + + return false; +} + +/* processes one rds packet, return value indicates kind of message received */ +enum rds_event rds_process(uint16_t data[4]) +{ + int group; + + /* get programme identification */ + if (pi == 0) { + pi = data[0]; + } + + /* handle rds data based on group */ + group = (data[1] >> 11) & 0x1F; + switch (group) { + + case 0: /* group 0A: basic info */ + case 1: /* group 0B: basic info */ + if (handle_group0(data)) { + return RDS_EVENT_PS; + } + break; + + case 4: /* group 2A: radio text */ + case 5: /* group 2B: radio text */ + if (handle_group2(data)) { + return RDS_EVENT_RT; + } + break; + + default: + break; + } + + return RDS_EVENT_NONE; +} + +/* returns the programme identification code */ +uint16_t rds_get_pi(void) +{ + return pi; +} + +/* returns the most recent valid programme service name */ +char* rds_get_ps(void) +{ + return ps_copy; +} + +/* returns the most recent valid RadioText message */ +char* rds_get_rt(void) +{ + return rt_copy; +} + diff --git a/firmware/drivers/tuner/si4700.c b/firmware/drivers/tuner/si4700.c index 848d25e..f74cd8e 100644 --- a/firmware/drivers/tuner/si4700.c +++ b/firmware/drivers/tuner/si4700.c @@ -29,6 +29,9 @@ #include "tuner.h" /* tuner abstraction interface */ #include "fmradio.h" #include "fmradio_i2c.h" /* physical interface driver */ +#include "as3525.h" +#include "rds.h" +#include "thread.h" /* some models use the internal 32 kHz oscillator which needs special attention during initialisation, power-up and power-down. @@ -320,7 +323,67 @@ static void si4700_sleep(int snooze) SYSCONFIG2_SKEETHw(SEEK_THRESHOLD) | SYSCONFIG2_VOLUMEw(0xF), SYSCONFIG2_VOLUME | SYSCONFIG2_SEEKTH); + + /* enable RDS and RDS interrupt if supported (bit 9 of CHIPID) */ + if (cache[CHIPID] & (1 << 9)) { + si4700_write_set(SYSCONFIG1, SYSCONFIG1_RDS | SYSCONFIG1_RDSIEN); + si4700_write_masked(SYSCONFIG1, SYSCONFIG1_GPIO2_STC_RDS_I, + SYSCONFIG1_GPIO2); + } + } +} + +static void interrupt_setup(void) +{ + GPIOA_IE &= ~(1<<4); /* disable GPIO interrupt */ + GPIOA_DIR &= ~(1<<4); /* input */ + GPIOA_IS &= ~(1<<4); /* edge detect */ + GPIOA_IBE &= ~(1<<4); /* only one edge */ + GPIOA_IEV &= ~(1<<4); /* falling edge */ + GPIOA_IC = (1<<4); /* clear any pending interrupt */ + GPIOA_IE |= (1<<4); /* enable GPIO interrupt */ +} + +static struct semaphore rds_sema; +static int rds_event; +static uint32_t rds_stack[DEFAULT_STACK_SIZE/sizeof(uint32_t)]; + +void tuner_isr(void) +{ + /* read and clear the interrupt */ + if (GPIOA_MIS & (1<<4)) { + semaphore_release(&rds_sema); } + GPIOA_IC = (1<<4); +} + +static void rds_thread(void) +{ + rds_init(); + semaphore_init(&rds_sema, 1, 0); + interrupt_setup(); + while (true) { + semaphore_wait(&rds_sema, TIMEOUT_BLOCK); + si4700_read_reg(RDSD); + if (rds_process(&cache[RDSA])) { + rds_event = true; + } + } +} + +char* si4700_get_rds_info(int setting) +{ + char *text = NULL; + + switch(setting) { + case RADIO_RDS_NAME: + text = rds_get_ps(); + break; + case RADIO_RDS_TEXT: + text = rds_get_rt(); + break; + } + return text; } bool si4700_detect(void) @@ -355,6 +418,10 @@ void si4700_init(void) si4700_sleep(1); tuner_power(false); + + /* rds initialisation */ + create_thread(rds_thread, rds_stack, sizeof(rds_stack), 0, "rds" + IF_PRIO(, PRIORITY_SYSTEM) IF_COP(, CPU)); } } @@ -424,16 +491,19 @@ int si4700_set(int setting, int value) switch(setting) { case RADIO_SLEEP: + rds_reset(); if (value != 2) si4700_sleep(value); /* else actually it's 'pause' */ break; case RADIO_FREQUENCY: + rds_reset(); si4700_set_frequency(value); break; case RADIO_SCAN_FREQUENCY: + rds_reset(); si4700_set_frequency(value); return si4700_tuned(); @@ -488,6 +558,13 @@ int si4700_get(int setting) case RADIO_RSSI_MAX: val = RSSI_MAX; break; + + case RADIO_EVENT: + if (rds_event) { + val = 1; + rds_event = false; + } + break; } return val; diff --git a/firmware/export/config/sansaclipzip.h b/firmware/export/config/sansaclipzip.h index 4dc1226..0bf3f50 100644 --- a/firmware/export/config/sansaclipzip.h +++ b/firmware/export/config/sansaclipzip.h @@ -27,6 +27,8 @@ explicitly if different */ #define INPUT_SRC_CAPS (SRC_CAP_MIC | SRC_CAP_FMRADIO) +#define HAVE_RDS_CAP + /* define this if you have a bitmap LCD display */ #define HAVE_LCD_BITMAP /* define this if you have a colour LCD */ diff --git a/firmware/export/rds.h b/firmware/export/rds.h new file mode 100644 index 0000000..cc86739 --- /dev/null +++ b/firmware/export/rds.h @@ -0,0 +1,18 @@ +#include + +enum rds_event { + RDS_EVENT_NONE = 0, + RDS_EVENT_PS, + RDS_EVENT_RT +}; + +void rds_init(void); + +void rds_reset(void); +enum rds_event rds_process(uint16_t data[4]); + +uint16_t rds_get_pi(void); +char* rds_get_ps(void); +char* rds_get_rt(void); + + diff --git a/firmware/export/si4700.h b/firmware/export/si4700.h index 06a87c4..09d6fa1 100644 --- a/firmware/export/si4700.h +++ b/firmware/export/si4700.h @@ -40,9 +40,12 @@ int si4700_set(int setting, int value); int si4700_get(int setting); void si4700_dbg_info(struct si4700_dbg_info *nfo); +char* si4700_get_rds_info(int setting); + #ifndef CONFIG_TUNER_MULTI #define tuner_set si4700_set #define tuner_get si4700_get +#define tuner_get_rds_info si4700_get_rds_info #endif #endif /* _SI4700_H_ */ diff --git a/firmware/target/arm/as3525/system-as3525.c b/firmware/target/arm/as3525/system-as3525.c index 2a40724..a5c5367 100644 --- a/firmware/target/arm/as3525/system-as3525.c +++ b/firmware/target/arm/as3525/system-as3525.c @@ -155,6 +155,8 @@ void INT_GPIOA(void) void button_gpioa_isr(void); button_gpioa_isr(); #endif + void tuner_isr(void); + tuner_isr(); } void irq_handler(void)