├── .gitignore ├── .gitattributes ├── Makefile ├── pi2c.h ├── cmd.h ├── rpi_pin.h ├── pi2c.c ├── main.c ├── README.md ├── cli.h ├── si4703.h ├── cli.c ├── rpi_pin.c ├── rds.h ├── cio.c ├── rds.c ├── si4703.c └── cmd.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | rdspi -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set default behaviour, in case users don't have core.autocrlf set. 2 | # https://help.github.com/articles/dealing-with-line-endings 3 | * text eol=lf 4 | 5 | # Explicitly declare text files we want to always be normalized and converted 6 | # to native line endings on checkout. 7 | *.c text 8 | *.h text 9 | *.conf text 10 | *.md text 11 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CXX = g++ 2 | CFLAGS = -Wall -Wextra -Werror 3 | CFLAGS += -g 4 | #CFLAGS += -O3 5 | #CFLAGS += -std=gnu99 6 | LIBS = 7 | 8 | CORE = rdspi 9 | OBJS = cmd.o main.o pi2c.o rpi_pin.o si4703.o rds.o cio.o cli.o 10 | #SRC = cmd.c main.c pi2c.c rpi_pin.c si4703.c rds.c cio.c cli.c 11 | #HFILES = Makefile pi2c.h rpi_pin.h si4703.h rds.h cmd.h cli.h 12 | 13 | all: $(CORE) 14 | 15 | $(CORE): $(OBJS) Makefile 16 | $(CXX) $(CFLAGS) -o $(CORE) $(OBJS) $(LIBS) 17 | 18 | clean: 19 | rm -f $(CORE) 20 | rm -f *.o 21 | 22 | #%.o: %.c $(HFILES) 23 | %.o: %.c 24 | $(CXX) -c $(CFLAGS) $< -o $@ 25 | 26 | 27 | -------------------------------------------------------------------------------- /pi2c.h: -------------------------------------------------------------------------------- 1 | /* Basic I2C wrapper for Raspberry Pi. 2 | Copyright (c) 2014 Andrey Chilikin (https://github.com/achilikin) 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 2 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | */ 17 | 18 | /** 19 | Basic I2C wrapper for Raspberry Pi. 20 | 21 | Make sure that RPi i2c driver is enabled, check following files: 22 | /etc/modprobe.d/raspi-blacklist.conf 23 | #blacklist i2c-bcm2708 24 | /etc/modules 25 | i2c-dev 26 | /etc/modprobe.d/i2c.conf 27 | options i2c_bcm2708 baudrate=400000 28 | */ 29 | 30 | #ifndef __PI2C_H__ 31 | #define __PI2C_H__ 32 | 33 | #include 34 | 35 | #ifdef __cplusplus 36 | extern "C" { 37 | #endif 38 | 39 | #define PI2C_BUS0 0 /*< P5 header I2C bus */ 40 | #define PI2C_BUS1 1 /*< P1 header I2C bus */ 41 | #define PI2C_BUS PI2C_BUS1 // default bus 42 | 43 | int pi2c_open(uint8_t bus); /*< open I2C bus */ 44 | int pi2c_close(uint8_t bus); /*< close I2C bus */ 45 | int pi2c_select(uint8_t bus, uint8_t slave); /*< select I2C slave */ 46 | int pi2c_read(uint8_t bus, uint8_t *data, uint32_t len); 47 | int pi2c_write(uint8_t bus, const uint8_t *data, uint32_t len); 48 | #ifdef __cplusplus 49 | } 50 | #endif 51 | 52 | #endif 53 | -------------------------------------------------------------------------------- /cmd.h: -------------------------------------------------------------------------------- 1 | /* CLI commands hadlers for Si4703 based RDS scanner 2 | Copyright (c) 2014 Andrey Chilikin (https://github.com/achilikin) 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 2 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | */ 17 | 18 | #ifndef __SI4703_CMD_H__ 19 | #define __SI4703_CMD_H__ 20 | 21 | #include 22 | 23 | #ifdef __cplusplus 24 | extern "C" { 25 | #if 0 // dummy bracket for VAssistX 26 | } 27 | #endif 28 | #endif 29 | 30 | #define UNUSED(x) x __attribute__((unused)) 31 | 32 | typedef int (*cmd_handler)(int fd, char *arg); 33 | 34 | typedef struct cmd { 35 | const char *name; 36 | const char *help; 37 | cmd_handler cmd; 38 | } cmd_t; 39 | 40 | int cmd_dump(int fd, char *arg); 41 | int cmd_reset(int fd, char *arg); 42 | int cmd_power(int fd, char *arg); 43 | int cmd_scan(int fd, char *arg); 44 | int cmd_spectrum(int fd, char *arg); 45 | int cmd_spacing(int fd, char *arg); 46 | int cmd_seek(int fd, char *arg); 47 | int cmd_tune(int fd, char *arg); 48 | int cmd_monitor(int fd, char *arg); 49 | int cmd_volume(int fd, char *arg); 50 | int cmd_set(int fd, char *arg); 51 | 52 | int cmd_arg(char *cmd, const char *str, char **arg); 53 | int cmd_is(char *str, const char *is); 54 | 55 | extern int stop; 56 | int is_stop(int *stop); 57 | 58 | #ifdef __cplusplus 59 | } 60 | #endif 61 | #endif 62 | -------------------------------------------------------------------------------- /rpi_pin.h: -------------------------------------------------------------------------------- 1 | /* Raspberry Pi pin access wrapper 2 | Copyright (c) 2014 Andrey Chilikin (https://github.com/achilikin) 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 2 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | */ 17 | 18 | #ifndef __RPI_IRQ_H__ 19 | #define __RPI_IRQ_H__ 20 | 21 | #include 22 | #include 23 | 24 | #ifdef __cplusplus 25 | extern "C" { 26 | #endif 27 | 28 | #define RPI_REV1 1 29 | #define RPI_REV2 2 30 | 31 | enum PIN_DIRECTION { RPI_INPUT, RPI_OUTPUT }; 32 | enum PIN_EDGE_MODE { EDGE_NONE = 0, EDGE_RISING, EDGE_FALLING, EDGE_BOTH }; 33 | 34 | // revision 1 - old, 2 - new, including P5 pins 35 | // must be called before any other rpi_pin_* functions if revision is 1 36 | int rpi_pin_init(int pi_revision); 37 | 38 | // export gpio pin, must be called before other get/set functions 39 | int rpi_pin_export(uint8_t pin, enum PIN_DIRECTION dir); 40 | int rpi_pin_set_dir(uint8_t pin, enum PIN_DIRECTION dir); 41 | int rpi_pin_get(uint8_t pin); // read input value 42 | int rpi_pin_set(uint8_t pin, uint8_t value); // set output value 0-1 43 | int rpi_pin_unexport(uint8_t pin); 44 | 45 | // functions for pin change of value edge detection support using poll() 46 | // returns pin's file descriptor 47 | int rpi_pin_fd(uint8_t pin); 48 | 49 | // enables POLLPRI event on edge detection, pin must be in INPUT mode 50 | int rpi_pin_poll_enable(uint8_t pin, enum PIN_EDGE_MODE mode); 51 | 52 | // clears pending polls and returns current value 53 | static inline int rpi_pin_poll_clear(int fd) 54 | { 55 | char val; 56 | 57 | lseek(fd, 0, SEEK_SET); 58 | read(fd, &val, 1); 59 | 60 | return val - '0'; 61 | } 62 | 63 | #define rpi_delay_ms(x) usleep((x)*1000u) 64 | 65 | #ifdef __cplusplus 66 | } 67 | #endif 68 | 69 | #endif 70 | -------------------------------------------------------------------------------- /pi2c.c: -------------------------------------------------------------------------------- 1 | /* Basic I2C wrapper for Raspberry Pi. 2 | Copyright (c) 2014 Andrey Chilikin (https://github.com/achilikin) 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 2 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | */ 17 | 18 | /* 19 | Basic I2C wrapper for Raspberry Pi. 20 | 21 | Make sure that RPi i2c driver is enabled, check following files: 22 | /etc/modprobe.d/raspi-blacklist.conf 23 | #blacklist i2c-bcm2708 24 | /etc/modules 25 | i2c-dev 26 | /etc/modprobe.d/i2c.conf 27 | options i2c_bcm2708 baudrate=400000 28 | */ 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #include "pi2c.h" 36 | 37 | /* i2c bus file descriptors */ 38 | static int i2c_bus[2] = { -1, -1 }; 39 | 40 | /* open I2C bus if not opened yet and store file descriptor */ 41 | int pi2c_open(uint8_t bus) 42 | { 43 | char bus_name[64]; 44 | 45 | if (bus > PI2C_BUS1) 46 | return -1; 47 | 48 | // already opened? 49 | if (i2c_bus[bus] >= 0) 50 | return 0; 51 | 52 | // open i2c bus and store file descriptor 53 | sprintf(bus_name, "/dev/i2c-%u", bus); 54 | 55 | if ((i2c_bus[bus] = open(bus_name, O_RDWR)) < 0) 56 | return -1; 57 | 58 | return 0; 59 | } 60 | 61 | /* close I2C bus file descriptor */ 62 | int pi2c_close(uint8_t bus) 63 | { 64 | if (bus > PI2C_BUS1) 65 | return -1; 66 | 67 | if (i2c_bus[bus] >= 0) 68 | close(i2c_bus[bus]); 69 | i2c_bus[bus] = -1; 70 | 71 | return 0; 72 | } 73 | 74 | /* select I2C device for pi2c_write() calls */ 75 | int pi2c_select(uint8_t bus, uint8_t slave) 76 | { 77 | if ((bus > PI2C_BUS1) || (i2c_bus[bus] < 0)) 78 | return -1; 79 | 80 | return ioctl(i2c_bus[bus], I2C_SLAVE, slave); 81 | } 82 | 83 | /* write to I2C device selected by pi2c_select() */ 84 | int pi2c_write(uint8_t bus, const uint8_t *data, uint32_t len) 85 | { 86 | if ((bus > PI2C_BUS1) || (i2c_bus[bus] < 0)) 87 | return -1; 88 | 89 | if (write(i2c_bus[bus], data, len) != (ssize_t)len) 90 | return -1; 91 | 92 | return 0; 93 | } 94 | 95 | /* read I2C device selected by pi2c_select() */ 96 | int pi2c_read(uint8_t bus, uint8_t *data, uint32_t len) 97 | { 98 | if ((bus > PI2C_BUS1) || (i2c_bus[bus] < 0)) 99 | return -1; 100 | 101 | if (read(i2c_bus[bus], data, len) != (ssize_t)len) 102 | return -1; 103 | 104 | return 0; 105 | } 106 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | /* Si4703 based RDS scanner 2 | Copyright (c) 2015 Andrey Chilikin (https://github.com/achilikin) 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 2 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include "cmd.h" 24 | #include "cli.h" 25 | #include "pi2c.h" 26 | #include "rpi_pin.h" 27 | #include "si4703.h" 28 | 29 | cmd_t commands[] = { 30 | { "reset", "reset radio module", cmd_reset }, 31 | { "power", "power up|down", cmd_power }, 32 | { "dump", "dump registers map", cmd_dump }, 33 | { "spacing", "spacing 50|100|200 kHz", cmd_spacing }, 34 | { "scan", "scan [mode 1-5]", cmd_scan }, 35 | { "spectrum", "spectrum [rssi limit]", cmd_spectrum }, 36 | { "seek", "seek up|down", cmd_seek }, 37 | { "tune", "tune [freq]", cmd_tune }, 38 | { "volume", "volume [0-30]", cmd_volume }, 39 | { "rds", "rds [on|off|verbose] gt [0,...,15] [time sec (0 - no timeout)] [log]", cmd_monitor }, 40 | { "set", "set register value", cmd_set }, 41 | { NULL, NULL, NULL } 42 | }; 43 | 44 | int stop = 0; 45 | console_io_t cli; 46 | int stdio_cli_handler(console_io_t *cli, void *ptr); 47 | 48 | int is_stop(int *pstop) 49 | { 50 | int stop = cli.getch(&cli); 51 | if (pstop) { 52 | if (*pstop) 53 | stop = *pstop; 54 | else 55 | *pstop = stop; 56 | } 57 | return stop; 58 | } 59 | 60 | int main(int argc, char **argv) 61 | { 62 | char *arg = NULL; 63 | char argbuf[BUFSIZ]; 64 | int cmd_mode = 0, verbose = 1; 65 | stdio_mode(STDIO_MODE_CANON); 66 | 67 | if (argc == 2) { 68 | if (cmd_is(argv[1], "cmd")) { 69 | cmd_mode = 1; 70 | cli.prompt = '>'; 71 | } 72 | if (cmd_is(argv[1], "help")) 73 | argc = 1; 74 | } 75 | 76 | if (argc == 1) { 77 | printf("Supported commands:\n"); 78 | printf(" cmd: run in interactive command mode\n"); 79 | for(uint32_t i = 0; commands[i].name != NULL; i++) { 80 | printf(" %s: %s\n", commands[i].name, commands[i].help); 81 | } 82 | return 0; 83 | } 84 | 85 | if (!cmd_mode && argc > 2 && cmd_is(argv[argc-1], "--silent")) { 86 | cli.prompt = '\0'; 87 | verbose = 0; 88 | argc--; 89 | } 90 | 91 | if (!verbose) 92 | cli.ofd = open("/dev/null", O_WRONLY); 93 | stdio_init(&cli, stdio_cli_handler); 94 | stdio_mode(STDIO_MODE_RAW); 95 | 96 | rpi_pin_init(RPI_REV2); 97 | rpi_pin_export(SI_RESET, RPI_INPUT); 98 | pi2c_open(PI2C_BUS); 99 | pi2c_select(PI2C_BUS, SI4703_ADDR); 100 | 101 | if (cmd_mode) { 102 | while(!stop) 103 | cli.interact(&cli, NULL); 104 | goto restore; 105 | } 106 | 107 | argbuf[0] = '\0'; 108 | if (argc > 2) { 109 | for(int i = 2; i < argc; i++) { 110 | if (i > 2) 111 | strcat(argbuf, " "); 112 | strcat(argbuf, argv[i]); 113 | } 114 | arg = argbuf; 115 | } 116 | 117 | for(uint32_t i = 0; commands[i].name != NULL; i++) { 118 | if (cmd_is(argv[1], commands[i].name)) { 119 | if (commands[i].cmd(cli.ofd, arg) != 0) 120 | dprintf(cli.ofd, "Communication error\n"); 121 | break; 122 | } 123 | } 124 | 125 | restore: 126 | rpi_pin_unexport(SI_RESET); 127 | pi2c_close(PI2C_BUS); 128 | stdio_mode(STDIO_MODE_CANON); 129 | 130 | return 0; 131 | } 132 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | RdSpi 2 | ===== 3 | 4 | Si4703 based RDS scanner for Raspberry Pi using Si4703S-B16 breakout board PL102RT-S V1.5 5 | 6 | ![PL102RT-S V1.5](http://1.bp.blogspot.com/-j91XFG7MIac/U4t7Ey8lmWI/AAAAAAAAATA/-J92Tfr5Ej0/s1600/pl102rt-s-v15.png) 7 | 8 | Installation 9 | ------------ 10 | 11 | Connect PL102RT-S V1.5 breakout to Rpi: 12 | 13 | | RPi | PL102RT-S | 14 | | -------- |----------:| 15 | | SDA | SDIO | 16 | | SCL | SCLK | 17 | | #23 (GP4)| RST | 18 | | GND | GND | 19 | |3.3V | Vd | 20 | 21 | To make communication more stable connect RST pin to a pullup resistor. 22 | 23 | If in addition to RDS scanning you want to listen to radio audio then connect 24 | whatever amplifier you have to LOUT/ROUT. [Adafruit MAX98306](http://www.adafruit.com/products/987) works just fine. 25 | 26 | Make RdSpi. It accepts one command at a time: 27 | 28 | * **_cmd_** - run in interactive command mode 29 | * **_reset_** - resets and powers up Si4703, dumps register map while resetting 30 | * **_power on|down_** - powers Si4703 up or down 31 | * **_dump_** - dumps Si4703 register 32 | * **_spacing kHz_** - sets 200, 100, or 50 kHz spacing 33 | * **_scan (mode)_** - scans for radio stations, mode can be specified 1-5, see [AN230](http://www.silabs.com/Support%20Documents/TechnicalDocs/AN230.pdf), Table 23. Summary of Seek Settings 34 | * **_spectrum_** - scans full FM band and prints RSSI 35 | * **_seek up|down_** - seeks to the next/prev station 36 | * **_tune freq_** - tunes to specified FM frequency, for example `rdspi tune 9500` or `rdspi tune 95.00` or `rdspi tune 95.` to tune to 95.00 MHz 37 | * **_rds on|off|verbose_** - sets RDS mode, on/off for RDSPRF, verbose for RDSM 38 | * **_rds [gt G] [time T] [log]_** - scan for RDS messages. Use to _gt_ specify RDS Group Type to scan for, for example 0 for basic tuning and switching information. Use _time_ to specify timeout T in seconds. T = 0 turns off timeout. Use _log_ to scroll output instead on using one-liners. 39 | * **_rds_** - scan for complete RDS PS and Radiotext messages with default 15 seconds timeout 40 | * **_volume 0-30_** - set audio volume, 0 to mute 41 | * **_set register=value_** - set specified register 42 | 43 | It is better to start with `reset` :) Note that `reset` requires `sudo` to write to reset pin, other commands can be used without `sudo`. 44 | 45 | ``` 46 | $sudo rdspi reset 47 | $rdspi tune 95.00 48 | $rdspi volume 10 49 | $rdspi set volext=1 50 | $rdspi dump 51 | ``` 52 | 53 | To disable output use ```--silent``` as the last command line parameter: 54 | ``` 55 | $sudo rdspi reset --silent 56 | $rdspi tune 9500 --silent 57 | ``` 58 | 59 | Screenshots 60 | ----------- 61 | 62 | * [rdspi dump](http://3.bp.blogspot.com/-OXuzT8qIl9Y/U4uHJIeWVyI/AAAAAAAAATQ/cm2Y-9AsPI0/s1600/dump.png) 63 | * [rdspi scan](http://4.bp.blogspot.com/-w3Rr9ScBuhA/U4uHeOIE43I/AAAAAAAAATY/xRO8Dcd-KSw/s1600/scan.png) 64 | * [rdspi spectrum](http://1.bp.blogspot.com/-7OW7MaMvY_M/U4uHlLo7ZaI/AAAAAAAAATg/le6EdWwt_OI/s1600/spectrum.png) 65 | * [rdspi rds log](http://1.bp.blogspot.com/-Lwb6mZEmLF4/U4uH0sbmRTI/AAAAAAAAATo/-339yycuW_E/s1600/rds.png) 66 | 67 | Links 68 | ----- 69 | 70 | * [Si4702/03 FM Radio Receiver ICs](http://www.silabs.com/products/audio/fmreceivers/pages/si470203.aspx) 71 | * [Si4703S-C19 Data Sheet](https://www.sparkfun.com/datasheets/BreakoutBoards/Si4702-03-C19-1.pdf) 72 | * Si4703S-B16 Data Sheet: *Not on SiLabs site anymore, [google](https://www.google.com/search?q=Si4703+B16) for it* 73 | * Si4700/01/02/03 Firmware Change List, AN281: *Not on SiLabs site anymore, [google](https://www.google.com/search?q=Si4700%2F01%2F02%2F03+Firmware+Change+List%2C+AN281) for it* 74 | * [Using RDS/RBDS with the Si4701/03](http://www.silabs.com/Support%20Documents/TechnicalDocs/AN243.pdf) 75 | * [Si4700/01/02/03 Programming Guide](http://www.silabs.com/Support%20Documents/TechnicalDocs/AN230.pdf) 76 | * RBDS Standard: ftp://ftp.rds.org.uk/pub/acrobat/rbds1998.pdf 77 | -------------------------------------------------------------------------------- /cli.h: -------------------------------------------------------------------------------- 1 | /* BSD License 2 | Copyright (c) 2015 Andrey Chilikin https://github.com/achilikin 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | * Redistributions of source code must retain the above copyright notice, 6 | this list of conditions and the following disclaimer. 7 | * Redistributions in binary form must reproduce the above copyright notice, 8 | this list of conditions and the following disclaimer in the documentation 9 | and/or other materials provided with the distribution. 10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 11 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 12 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 13 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS 14 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 15 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 16 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 17 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 18 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 19 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 20 | POSSIBILITY OF SUCH DAMAGE. 21 | */ 22 | #ifndef CONSOLE_IO_H 23 | #define CONSOLE_IO_H 24 | 25 | #include 26 | 27 | #ifdef __cplusplus 28 | extern "C" { 29 | #if 0 // to trick VisualAssist 30 | } 31 | #endif 32 | #endif 33 | 34 | struct console_io_s; 35 | 36 | #define CLI_EOK 0 // success 37 | #define CLI_EARG -1 // invalid argument 38 | #define CLI_ENOTSUP -2 // command not supported 39 | #define CLI_ENODEV -3 // device communication error 40 | 41 | typedef int (cgetch_t)(struct console_io_s *cio); 42 | typedef int (cputch_t)(struct console_io_s *cio, int ch); 43 | typedef int (cputs_t)(struct console_io_s *cio, const char *str); 44 | 45 | // command line processing, returns CLI_E* 46 | typedef int (cproc_t)(struct console_io_s *cio, void *ptr); 47 | 48 | struct console_io_s 49 | { 50 | int ifd; // input file descriptor 51 | int ofd; // output file descriptor 52 | char prompt; 53 | void *data; 54 | 55 | cgetch_t *getch; 56 | cputch_t *putch; 57 | cputs_t *puts; 58 | cproc_t *interact; 59 | cproc_t *proc; 60 | }; 61 | 62 | typedef struct console_io_s console_io_t; 63 | 64 | #define CMD_LEN 0x7F 65 | #define CLI_DEBUG 0x8000 66 | 67 | typedef struct cmd_line_s 68 | { 69 | uint16_t flags; 70 | uint16_t cursor; 71 | uint16_t esc; 72 | uint16_t idx; 73 | char cmd[CMD_LEN + 1]; 74 | char hist[CMD_LEN + 1]; 75 | } cmd_line_t; 76 | 77 | // non-character flag 78 | #define KEY_EXTRA 0x8000 79 | 80 | #define ARROW_UP 0x8001 81 | #define ARROW_DOWN 0x8002 82 | #define ARROW_RIGHT 0x8003 83 | #define ARROW_LEFT 0x8004 84 | 85 | #define KEY_HOME 0x8005 86 | #define KEY_INS 0x8006 87 | #define KEY_DEL 0x8007 88 | #define KEY_END 0x8008 89 | #define KEY_PGUP 0x8009 90 | #define KEY_PGDN 0x800A 91 | 92 | #define KEY_F1 0x8011 93 | #define KEY_F2 0x8012 94 | #define KEY_F3 0x8013 95 | #define KEY_F4 0x8014 96 | #define KEY_F5 0x8015 97 | #define KEY_F6 0x8017 98 | #define KEY_F7 0x8018 99 | #define KEY_F8 0x8019 100 | #define KEY_F9 0x8020 101 | #define KEY_F10 0x8021 102 | #define KEY_F11 0x8023 103 | #define KEY_F12 0x8024 104 | 105 | // Terminal ESC codes 106 | static const char clrs[] = { 27, '[', '2', 'J', '\0' }; // clear screen 107 | static const char cdwn[] = { 27, '[', 'J', '\0' }; // clear down 108 | static const char csol[] = { 27, '[', '1', 'K', '\0' }; // clear to Start OL 109 | static const char ceol[] = { 27, '[', '0', 'K', '\0' }; // clear to EOL 110 | static const char scur[] = { 27, '[', 's', '\0' }; // save cursor 111 | static const char rcur[] = { 27, '[', 'u', '\0' }; // restore cursor 112 | static const char gotop[] = { 27, '[', '1', ';', '1', 'H','\0' }; // top left 113 | static const char civis[] = { 27, '[', '?', '2', '5', 'l','\0' }; // invisible cursor 114 | 115 | enum stdio_mode_t { STDIO_MODE_CANON = 0, STDIO_MODE_RAW }; 116 | 117 | void stdio_mode(enum stdio_mode_t mode); 118 | void stdio_init(console_io_t *cli, cproc_t *cli_handler); 119 | 120 | #ifdef __cplusplus 121 | } 122 | #endif 123 | 124 | #endif 125 | -------------------------------------------------------------------------------- /si4703.h: -------------------------------------------------------------------------------- 1 | /* Si4703 based RDS scanner 2 | Copyright (c) 2014 Andrey Chilikin (https://github.com/achilikin) 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 2 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | */ 17 | 18 | /* 19 | Si4702/03 FM Radio Receiver ICs: 20 | http://www.silabs.com/products/audio/fmreceivers/pages/si470203.aspx 21 | 22 | Si4703S-C19 Data Sheet: 23 | https://www.sparkfun.com/datasheets/BreakoutBoards/Si4702-03-C19-1.pdf 24 | 25 | Si4703S-B16 Data Sheet: 26 | Not on SiLabs site anymore, google for it 27 | 28 | Si4700/01/02/03 Firmware Change List, AN281: 29 | Not on SiLabs site anymore, google for it 30 | 31 | Using RDS/RBDS with the Si4701/03: 32 | http://www.silabs.com/Support%20Documents/TechnicalDocs/AN243.pdf 33 | 34 | Si4700/01/02/03 Programming Guide: 35 | http://www.silabs.com/Support%20Documents/TechnicalDocs/AN230.pdf 36 | 37 | RBDS Standard: 38 | ftp://ftp.rds.org.uk/pub/acrobat/rbds1998.pdf 39 | */ 40 | 41 | #ifndef __SI4703_B16_H__ 42 | #define __SI4703_B16_H__ 43 | 44 | #include 45 | 46 | #ifdef __cplusplus 47 | extern "C" { 48 | #if 0 // dummy bracket for VAssistX 49 | } 50 | #endif 51 | #endif 52 | 53 | #define _BM(bit) (1 << ((uint16_t)bit)) // convert bit number to bit mask 54 | 55 | #define SI4703_ADDR 0x10 56 | #define SI_RESET 23 57 | 58 | // Define the register names 59 | #define DEVICEID 0x00 60 | #define CHIPID 0x01 61 | #define POWERCFG 0x02 62 | #define DSMUTE _BM(15) 63 | #define DMUTE _BM(14) 64 | #define MONO _BM(13) 65 | #define RDSM _BM(11) 66 | #define SKMODE _BM(10) 67 | #define SEEKUP _BM(9) 68 | #define SEEK _BM(8) 69 | #define PWR_DISABLE _BM(6) 70 | #define PWR_ENABLE _BM(0) 71 | // seek direction 72 | #define SEEK_DOWN 0 73 | #define SEEK_UP 1 74 | 75 | #define CHANNEL 0x03 76 | #define TUNE _BM(15) 77 | #define CHAN 0x003FF 78 | 79 | #define SYSCONF1 0x04 80 | #define RDSIEN _BM(15) 81 | #define STCIEN _BM(14) 82 | #define RDS _BM(12) 83 | #define DE _BM(11) 84 | #define AGCD _BM(10) 85 | #define BLNDADJ 0x00C0 86 | #define GPIO3 0x0030 87 | #define GPIO2 0x000C 88 | #define GPIO1 0x0003 89 | 90 | #define SYSCONF2 0x05 91 | #define SEEKTH 0xFF00 92 | #define BAND 0x00C0 93 | #define SPACING 0x0030 94 | #define VOLUME 0x000F 95 | #define BAND0 0x0000 // 87.5 - 107 MHz (Europe, USA) 96 | #define BAND1 0x0040 // 76-108 MHz (Japan wide band) 97 | #define BAND2 0x0080 // 76-90 MHz (Japan) 98 | #define SPACE50 0x0020 // 50 kHz spacing 99 | #define SPACE100 0x0010 // 100 kHz (Europe, Japan) 100 | #define SPACE200 0x0000 // 200 kHz (USA, Australia) 101 | 102 | #define SYSCONF3 0x06 103 | #define SMUTER 0xC000 104 | #define SMUTEA 0x3000 105 | #define RDSPRF _BM(9) 106 | #define VOLEXT _BM(8) 107 | #define SKSNR 0x00F0 108 | #define SKCNT 0x000F 109 | 110 | #define TEST1 0x07 111 | #define XOSCEN _BM(15) 112 | #define AHIZEN _BM(14) 113 | 114 | #define TEST2 0x08 115 | #define BOOTCONF 0x09 116 | 117 | #define STATUSRSSI 0x0A 118 | #define RDSR _BM(15) 119 | #define STC _BM(14) 120 | #define SFBL _BM(13) 121 | #define AFCRL _BM(12) 122 | #define RDSS _BM(11) 123 | #define BLERA 0x0600 124 | #define STEREO _BM(8) 125 | #define RSSI 0x00FF 126 | 127 | #define READCHAN 0x0B 128 | #define BLERB 0xC000 129 | #define BLERC 0x3000 130 | #define BLERD 0x0C00 131 | #define RCHAN 0x03FF 132 | 133 | #define RDSA 0x0C 134 | #define RDSB 0x0D 135 | #define RDSC 0x0E 136 | #define RDSD 0x0F 137 | 138 | int si_read_regs(uint16_t *regs); 139 | int si_update(uint16_t *regs); 140 | void si_dump(int fd, uint16_t *regs, const char *title, uint16_t span); 141 | void si_power(uint16_t *regs, uint16_t mode); 142 | void si_set_volume(uint16_t *regs, int volume); 143 | 144 | int si_get_freq(uint16_t *regs); 145 | int si_seek(uint16_t *regs, int dir); 146 | void si_set_channel(uint16_t *regs, int chan); 147 | void si_tune(uint16_t *regs, int freq); 148 | 149 | int si_set_state(uint16_t *regs, const char *name, uint16_t val); 150 | void si_set_rdsprf(uint16_t *regs, int set); 151 | 152 | extern int si_band[3][2]; 153 | extern int si_space[3]; 154 | 155 | #ifdef __cplusplus 156 | } 157 | #endif 158 | #endif 159 | -------------------------------------------------------------------------------- /cli.c: -------------------------------------------------------------------------------- 1 | /* BSD License 2 | Copyright (c) 2015 Andrey Chilikin https://github.com/achilikin 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | * Redistributions of source code must retain the above copyright notice, 6 | this list of conditions and the following disclaimer. 7 | * Redistributions in binary form must reproduce the above copyright notice, 8 | this list of conditions and the following disclaimer in the documentation 9 | and/or other materials provided with the distribution. 10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 11 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 12 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 13 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS 14 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 15 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 16 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 17 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 18 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 19 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 20 | POSSIBILITY OF SUCH DAMAGE. 21 | */ 22 | 23 | #include 24 | #include 25 | 26 | #include "cli.h" 27 | #include "cmd.h" 28 | 29 | static const char *shelp = 30 | "general commands\n" 31 | " exit|quit|q\n" 32 | " cli debug on|off\n" 33 | " cls: clear screen\n" 34 | "si4703 commands\n" 35 | ""; 36 | 37 | static int cli_proc(console_io_t *cli, char *arg, void *ptr); 38 | static int cls_proc(console_io_t *cli, char *arg, void *ptr); 39 | static int exit_proc(console_io_t *cli, char *arg, void *ptr); 40 | static int help_proc(console_io_t *cli, char *arg, void *ptr); 41 | 42 | static int reset_proc(console_io_t *cli, char *arg, void *ptr); 43 | static int power_proc(console_io_t *cli, char *arg, void *ptr); 44 | static int dump_proc(console_io_t *cli, char *arg, void *ptr); 45 | static int spacing_proc(console_io_t *cli, char *arg, void *ptr); 46 | static int scan_proc(console_io_t *cli, char *arg, void *ptr); 47 | static int spectrum_proc(console_io_t *cli, char *arg, void *ptr); 48 | static int tune_proc(console_io_t *cli, char *arg, void *ptr); 49 | static int seek_proc(console_io_t *cli, char *arg, void *ptr); 50 | static int volume_proc(console_io_t *cli, char *arg, void *ptr); 51 | static int set_proc(console_io_t *cli, char *arg, void *ptr); 52 | static int rds_proc(console_io_t *cli, char *arg, void *ptr); 53 | 54 | typedef int (cmd_proc)(console_io_t *cli, char *arg, void *ptr); 55 | static struct command_s { 56 | const char *cmd; 57 | cmd_proc *proc; 58 | } clis[] = { 59 | { "cli", cli_proc }, 60 | { "cls", cls_proc }, 61 | { "help", help_proc }, 62 | { "exit", exit_proc }, 63 | { "quit", exit_proc }, 64 | { "q", exit_proc }, 65 | { "reset", reset_proc }, 66 | { "power", power_proc }, 67 | { "dump", dump_proc }, 68 | { "spacing", spacing_proc }, 69 | { "scan", scan_proc }, 70 | { "spectrum", spectrum_proc }, 71 | { "tune", tune_proc }, 72 | { "seek", seek_proc }, 73 | { "volume", volume_proc }, 74 | { "set", set_proc }, 75 | { "rds", rds_proc }, 76 | { NULL, NULL } 77 | }; 78 | extern cmd_t commands[]; 79 | 80 | int stdio_cli_handler(console_io_t *cli, void *ptr) 81 | { 82 | char *arg; 83 | cmd_line_t *cl = (cmd_line_t *)cli->data; 84 | 85 | for(unsigned i = 0; clis[i].proc != NULL; i++) { 86 | if (cmd_arg(cl->cmd, clis[i].cmd, &arg)) 87 | return clis[i].proc(cli, arg, ptr); 88 | } 89 | 90 | return CLI_ENOTSUP; 91 | } 92 | 93 | int help_proc(console_io_t *cli, UNUSED(char *arg), UNUSED(void *ptr)) 94 | { 95 | cli->puts(cli, shelp); 96 | for(uint32_t i = 0; commands[i].name != NULL; i++) { 97 | dprintf(cli->ofd, " %s: %s\n", commands[i].name, commands[i].help); 98 | } 99 | return CLI_EOK; 100 | } 101 | 102 | int exit_proc(UNUSED(console_io_t *cli), UNUSED(char *arg), UNUSED(void *ptr)) 103 | { 104 | stop = 1; 105 | return CLI_EOK; 106 | } 107 | 108 | int cls_proc(console_io_t *cli, UNUSED(char *arg), UNUSED(void *ptr)) 109 | { 110 | cli->puts(cli, clrs); 111 | return CLI_EOK; 112 | } 113 | 114 | int cli_proc(console_io_t *cli, char *arg, UNUSED(void *ptr)) 115 | { 116 | cmd_line_t *cl = (cmd_line_t *)cli->data; 117 | 118 | if (cmd_arg(arg, "debug", &arg)) { 119 | if (cmd_is(arg, "on")) 120 | cl->flags |= CLI_DEBUG; 121 | else if (cmd_is(arg, "off")) 122 | cl->flags &= ~CLI_DEBUG; 123 | else 124 | return CLI_EARG; 125 | } 126 | return CLI_EOK; 127 | } 128 | 129 | int reset_proc(console_io_t *cli, char *arg, UNUSED(void *ptr)) 130 | { 131 | return cmd_reset(cli->ofd, arg); 132 | } 133 | 134 | int power_proc(console_io_t *cli, char *arg, UNUSED(void *ptr)) 135 | { 136 | return cmd_power(cli->ofd, arg); 137 | } 138 | 139 | int dump_proc(console_io_t *cli, char *arg, UNUSED(void *ptr)) 140 | { 141 | return cmd_dump(cli->ofd, arg); 142 | } 143 | 144 | int spacing_proc(console_io_t *cli, char *arg, UNUSED(void *ptr)) 145 | { 146 | return cmd_spacing(cli->ofd, arg); 147 | } 148 | 149 | int scan_proc(console_io_t *cli, char *arg, UNUSED(void *ptr)) 150 | { 151 | return cmd_scan(cli->ofd, arg); 152 | } 153 | 154 | int spectrum_proc(console_io_t *cli, char *arg, UNUSED(void *ptr)) 155 | { 156 | return cmd_spectrum(cli->ofd, arg); 157 | } 158 | 159 | int tune_proc(console_io_t *cli, char *arg, UNUSED(void *ptr)) 160 | { 161 | return cmd_tune(cli->ofd, arg); 162 | } 163 | 164 | int seek_proc(console_io_t *cli, char *arg, UNUSED(void *ptr)) 165 | { 166 | return cmd_seek(cli->ofd, arg); 167 | } 168 | 169 | int volume_proc(console_io_t *cli, char *arg, UNUSED(void *ptr)) 170 | { 171 | return cmd_volume(cli->ofd, arg); 172 | } 173 | 174 | int set_proc(console_io_t *cli, char *arg, UNUSED(void *ptr)) 175 | { 176 | return cmd_set(cli->ofd, arg); 177 | } 178 | 179 | int rds_proc(console_io_t *cli, char *arg, UNUSED(void *ptr)) 180 | { 181 | return cmd_monitor(cli->ofd, arg); 182 | } 183 | -------------------------------------------------------------------------------- /rpi_pin.c: -------------------------------------------------------------------------------- 1 | /* Raspberry Pi pin access wrapper 2 | Copyright (c) 2014 Andrey Chilikin (https://github.com/achilikin) 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 2 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include "rpi_pin.h" 24 | 25 | #if _DEBUG 26 | #define _IFDEB(x) do { x; } while(0) 27 | #else 28 | #define _IFDEB(x) 29 | #endif 30 | 31 | static const char *irq_mode[] = { "none\n", "rising\n", "falling\n", "both\n" }; 32 | 33 | static uint8_t valid_pins_r1[64] = 34 | { 35 | // pin index 36 | // 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 37 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 38 | 1,1,0,0,1,0,0,1,1,1,1,1,0,0,1,1,0,1,1,0,0,1,1,1,1,1,0,0,0,0,0,0 39 | }; 40 | 41 | static uint8_t valid_pins_r2[64] = 42 | { 43 | // pin index 44 | // 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 45 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 46 | 0,0,1,1,1,0,0,1,1,1,1,1,0,0,1,1,0,1,1,0,0,0,1,1,1,1,0,1,1,1,1,1 47 | }; 48 | 49 | static uint8_t *pvalid_pins = valid_pins_r2; 50 | 51 | static int pin_fds[64]; 52 | 53 | #define FPIN_EXPORTED 0x0001 54 | #define FPIN_DIR_INPUT 0x0020 55 | 56 | static int pin_flags[64]; 57 | 58 | int rpi_pin_init(int pi_revision) 59 | { 60 | if (pi_revision == 1) 61 | pvalid_pins = valid_pins_r1; 62 | 63 | return 0; 64 | } 65 | 66 | int rpi_pin_export(uint8_t pin, enum PIN_DIRECTION dir) 67 | { 68 | FILE *fd; 69 | char file[128]; 70 | 71 | if ((pin > 63) || !pvalid_pins[pin]) { 72 | _IFDEB(fprintf(stderr, "%s: invalid pin #%u\n", __func__, pin)); 73 | return -1; 74 | } 75 | 76 | if ((fd = fopen ("/sys/class/gpio/export", "w")) == NULL) { 77 | _IFDEB(fprintf(stderr, "%s: unable to export pin #%u\n", __func__, pin)); 78 | return -1; 79 | } 80 | fprintf(fd, "%d\n", pin); 81 | fclose(fd); 82 | 83 | sprintf(file, "/sys/class/gpio/gpio%d/direction", pin); 84 | if ((fd = fopen (file, "w")) == NULL) { 85 | _IFDEB(fprintf(stderr, "%s: unable to set direction for pin #%u\n", __func__, pin)); 86 | return -1; 87 | } 88 | 89 | pin_flags[pin] |= FPIN_EXPORTED; 90 | if (dir == RPI_INPUT) 91 | pin_flags[pin] |= FPIN_DIR_INPUT; 92 | fprintf(fd, (dir == RPI_INPUT) ? "in\n" : "out\n"); 93 | fclose(fd); 94 | 95 | sprintf(file, "/sys/class/gpio/gpio%d/value", pin); 96 | pin_fds[pin] = open(file, O_RDWR); 97 | 98 | return 0; 99 | } 100 | 101 | int rpi_pin_set_dir(uint8_t pin, enum PIN_DIRECTION dir) 102 | { 103 | FILE *fd; 104 | char file[128]; 105 | 106 | if ((pin > 63) || !pvalid_pins[pin]) 107 | return -1; 108 | 109 | if (!(pin_flags[pin] & FPIN_EXPORTED)) 110 | return -1; 111 | 112 | sprintf(file, "/sys/class/gpio/gpio%d/direction", pin); 113 | if ((fd = fopen (file, "w")) == NULL) { 114 | _IFDEB(fprintf(stderr, "%s: unable to set direction for pin #%u\n", __func__, pin)); 115 | return -1; 116 | } 117 | 118 | if (dir == RPI_INPUT) { 119 | pin_flags[pin] |= FPIN_DIR_INPUT; 120 | fprintf(fd, "in\n"); 121 | } 122 | else { 123 | pin_flags[pin] &= ~FPIN_DIR_INPUT; 124 | fprintf(fd, "out\n"); 125 | } 126 | fclose(fd); 127 | 128 | return 0; 129 | } 130 | 131 | // read input value: 132 | // 0 - low 133 | // 1 - high 134 | // -1 - error 135 | int rpi_pin_get(uint8_t pin) 136 | { 137 | // this will check pin validity as well 138 | int fd = rpi_pin_fd(pin); 139 | 140 | if (fd < 0) 141 | return -1; 142 | 143 | if (!(pin_flags[pin] & FPIN_DIR_INPUT)) 144 | return -1; 145 | 146 | return rpi_pin_poll_clear(fd); 147 | } 148 | 149 | // set output value 150 | int rpi_pin_set(uint8_t pin, uint8_t value) 151 | { 152 | // this will check pin validity as well 153 | int fd = rpi_pin_fd(pin); 154 | 155 | if (fd < 0) 156 | return -1; 157 | 158 | if (pin_flags[pin] & FPIN_DIR_INPUT) 159 | return -1; 160 | 161 | lseek(fd, 0, SEEK_SET); 162 | return (write(fd, value ? "1" : "0", 1) == 1) ? 0 : -1; 163 | } 164 | 165 | int rpi_pin_unexport(uint8_t pin) 166 | { 167 | FILE *fd; 168 | 169 | if ((pin > 63) || !pvalid_pins[pin]) 170 | return -1; 171 | 172 | if (!(pin_flags[pin] & FPIN_EXPORTED)) 173 | return 0; 174 | 175 | if ((fd = fopen ("/sys/class/gpio/unexport", "w")) == NULL) { 176 | _IFDEB(fprintf(stderr, "%s: unable to unexport pin #%u\n", __func__, pin)); 177 | return -1; 178 | } 179 | fprintf(fd, "%d\n", pin); 180 | fclose(fd); 181 | 182 | pin_flags[pin] = 0; 183 | close(pin_fds[pin]); 184 | pin_fds[pin] = -1; 185 | 186 | return 0; 187 | } 188 | 189 | // functions for pin change of value edge detection support using poll() 190 | 191 | // returns pin's file descriptor 192 | int rpi_pin_fd(uint8_t pin) 193 | { 194 | if ((pin > 63) || !pvalid_pins[pin]) 195 | return -1; 196 | 197 | if (pin_flags[pin] & FPIN_EXPORTED) 198 | return pin_fds[pin]; 199 | 200 | return -1; 201 | } 202 | 203 | // enables POLLPRI on edge detection, pin must be in INPUT mode 204 | // returns file descriptor to be used with poll() or -1 as error 205 | int rpi_pin_poll_enable(uint8_t pin, enum PIN_EDGE_MODE mode) 206 | { 207 | FILE *fd; 208 | char file[128]; 209 | 210 | if ((pin > 63) || !pvalid_pins[pin]) 211 | return -1; 212 | 213 | if (!(pin_flags[pin] & FPIN_EXPORTED)) 214 | rpi_pin_export(pin, RPI_INPUT); 215 | 216 | if (pin_fds[pin] < 0) 217 | return -1; 218 | 219 | sprintf(file, "/sys/class/gpio/gpio%d/edge", pin); 220 | if ((fd = fopen (file, "w")) == NULL) { 221 | _IFDEB(fprintf(stderr, "%s: setting edge detection failed for pin #%u: %s\n", __func__, pin)); 222 | return -1; 223 | } 224 | 225 | fputs(irq_mode[mode], fd); 226 | fclose(fd); 227 | 228 | rpi_pin_poll_clear(pin_fds[pin]); 229 | 230 | return pin_fds[pin]; 231 | } 232 | 233 | -------------------------------------------------------------------------------- /rds.h: -------------------------------------------------------------------------------- 1 | /* Some of RBDS standard defines 2 | ftp://ftp.rds.org.uk/pub/acrobat/rbds1998.pdf 3 | 4 | Copyright (c) 2014 Andrey Chilikin (https://github.com/achilikin) 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | 20 | #ifndef __RDS_CONSTANTS_H__ 21 | #define __RDS_CONSTANTS_H__ 22 | 23 | #include 24 | 25 | #ifdef __cplusplus 26 | extern "C" { 27 | #if 0 // dummy bracket for VAssistX 28 | } 29 | #endif 30 | #endif 31 | 32 | // RBDS Standard: 33 | // ftp://ftp.rds.org.uk/pub/acrobat/rbds1998.pdf 34 | 35 | // RBDS Standard, Annex D 36 | // Program Identification: Country Code, Area Coverage, Reference Number 37 | #define RDS_PI(CC,AC,RN) ((((uint16_t)RN) & 0xFF) | \ 38 | ((((uint16_t)AC) & 0x0F) << 8) | \ 39 | ((((uint16_t)CC) & 0x0F) << 12)) 40 | 41 | // Some European Broadcasting Area Country Codes 42 | // It is better not to use CC of your country 43 | // to avoid collision with any local radio stations 44 | #define RDS_GERMANY 0x0D 45 | #define RDS_IRELAND 0x02 46 | #define RDS_RUSSIA 0x07 47 | #define RDS_UK 0x0C 48 | 49 | // Coverage-Area Codes 50 | #define CAC_LOCAL 0 // Local, single transmitter 51 | #define CAC_INTER 1 // International 52 | #define CAC_NATIONAL 2 // National 53 | #define CAC_SUPRA 3 // Supra-Regional 54 | #define CAC_REGION 4 // Regional + Region code 0-11 55 | 56 | #define RDS_GT(GT,VER) ((uint16_t)(((((uint8_t)GT)&0xF)<<1) | (((uint8_t)VER)&0x1)) << 11) 57 | #define RDS_TP 0x4000 // Traffic Program ID, do not use with MMR-70 58 | #define RDS_PTY(PTY) ((((uint16_t)PTY) & 0x1F) << 5) 59 | #define RDS_GET_GT(reg) ((uint16_t)(reg) & 0xF800) 60 | 61 | #define RDS_VER 0x0800 // A/B version 62 | #define RDS_TA 0x0010 // Traffic Announcement 63 | #define RDS_MS 0x0008 // Music/Speech (group 0A/0B) 64 | #define RDS_DI 0x0004 // Decoder-Identification control code (group 0A/0B) 65 | #define RDS_AB 0x0010 // Text A/B reset flag 66 | 67 | #define RDS_PSIM 0x0003 // Program Service name index mask 68 | #define RDS_RTIM 0x000F // Radiotext index mask 69 | #define RDS_PTYM (0x1F<<5) 70 | 71 | // Some of RBDS groups parsed so far 72 | #define RDS_GT_00A RDS_GT(0,0) 73 | #define RDS_GT_01A RDS_GT(1,0) 74 | #define RDS_GT_02A RDS_GT(2,0) 75 | #define RDS_GT_03A RDS_GT(3,0) 76 | #define RDS_GT_04A RDS_GT(4,0) 77 | #define RDS_GT_05A RDS_GT(5,0) 78 | #define RDS_GT_08A RDS_GT(8,0) 79 | #define RDS_GT_10A RDS_GT(10,0) 80 | #define RDS_GT_14A RDS_GT(14,0) 81 | 82 | const char *rds_gt_name(uint8_t group, uint8_t version); 83 | 84 | // Some of PTY codes, for full list see RBDS Standard, Annex F 85 | // DO NOT USE 30 or 31!!! 86 | #define PTY_NONE 0 87 | #define PTY_NEWS 1 88 | #define PTY_INFORM 2 89 | #define PTY_SPORT 3 90 | #define PTY_TALK 4 91 | #define PTY_ROCK 5 92 | #define PTY_COUNTRY 10 93 | #define PTY_JAZZ 14 94 | #define PTY_CLASSIC 15 95 | #define PTY_WEATHER 29 96 | 97 | #define RDS_A 0 98 | #define RDS_B 1 99 | #define RDS_C 2 100 | #define RDS_D 3 101 | 102 | typedef struct rds_hdr_s 103 | { 104 | uint16_t rds[4]; 105 | uint8_t gt; // group type 106 | uint8_t ver; // version 107 | uint8_t tp; // Traffic Program 108 | uint8_t pty; 109 | } rds_hdr_t; 110 | 111 | typedef struct rds_gt00a_s 112 | { 113 | rds_hdr_t hdr; 114 | uint8_t valid; 115 | uint8_t ta; 116 | uint8_t ms; 117 | uint8_t di; 118 | uint8_t ci; 119 | char ps[9]; 120 | uint8_t naf; 121 | uint8_t af[25]; 122 | } rds_gt00a_t; 123 | 124 | typedef struct rds_gt01a_s 125 | { 126 | rds_hdr_t hdr; 127 | uint8_t rpc; 128 | uint8_t la; 129 | uint8_t vc; 130 | uint16_t slc; 131 | uint16_t pinc; 132 | } rds_gt01a_t; 133 | 134 | typedef struct rds_gt02a_s 135 | { 136 | rds_hdr_t hdr; 137 | uint16_t valid; // valid segments 138 | uint8_t ab; 139 | uint8_t si; 140 | char rt[65]; 141 | } rds_gt02a_t; 142 | 143 | typedef struct rds_gt03a_s 144 | { 145 | rds_hdr_t hdr; 146 | uint8_t agtc; // Application Group Type Code 147 | uint8_t ver; // 0 - A, 1 - B 148 | uint16_t msg; 149 | uint16_t aid; // Application ID 150 | 151 | uint8_t vc; 152 | uint8_t ltn; 153 | uint8_t afi, m, i, n, r, u; 154 | uint8_t g, sid, ta, tw, td; 155 | } rds_gt03a_t; 156 | 157 | typedef struct rds_gt04a_s 158 | { 159 | rds_hdr_t hdr; 160 | uint8_t hour; 161 | uint8_t minute; 162 | uint8_t tz_hour; 163 | uint8_t tz_half; 164 | uint8_t ts_sign; 165 | uint8_t day; 166 | uint8_t month; 167 | uint8_t week; 168 | uint16_t year; 169 | } rds_gt04a_t; 170 | 171 | typedef struct rds_gt05a_s 172 | { 173 | rds_hdr_t hdr; 174 | uint32_t channel; 175 | uint16_t tds[32][2]; 176 | } rds_gt05a_t; 177 | 178 | typedef struct rds_gt08a_s 179 | { 180 | rds_hdr_t hdr; 181 | uint8_t x4; 182 | uint8_t vc; 183 | uint8_t x3; 184 | uint8_t x2; 185 | char spn[9]; // service provider name 186 | uint16_t y; 187 | uint8_t d; 188 | uint8_t dir; 189 | uint8_t ext; 190 | uint16_t eve; 191 | uint16_t loc; 192 | } rds_gt08a_t; 193 | 194 | typedef struct rds_gt10a_s 195 | { 196 | rds_hdr_t hdr; 197 | uint8_t ab; 198 | uint8_t ci; 199 | char ps[9]; 200 | } rds_gt10a_t; 201 | 202 | typedef struct rds_gt14a_s 203 | { 204 | rds_hdr_t hdr; 205 | uint8_t tp_on; 206 | uint8_t variant; 207 | uint16_t info; 208 | uint16_t pi_on; 209 | 210 | uint16_t avc; 211 | char ps[9]; 212 | uint8_t af[2]; 213 | uint16_t mf[5]; 214 | uint16_t li; 215 | uint16_t pty; 216 | uint16_t pin; 217 | 218 | } rds_gt14a_t; 219 | 220 | int rds_parse_gt00a(uint16_t *prds, rds_gt00a_t *pgt); 221 | int rds_parse_gt01a(uint16_t *prds, rds_gt01a_t *pgt); 222 | int rds_parse_gt02a(uint16_t *prds, rds_gt02a_t *pgt); 223 | int rds_parse_gt03a(uint16_t *prds, rds_gt03a_t *pgt); 224 | int rds_parse_gt04a(uint16_t *prds, rds_gt04a_t *pgt); 225 | int rds_parse_gt05a(uint16_t *prds, rds_gt05a_t *pgt); 226 | int rds_parse_gt08a(uint16_t *prds, rds_gt08a_t *pgt); 227 | int rds_parse_gt10a(uint16_t *prds, rds_gt10a_t *pgt); 228 | int rds_parse_gt14a(uint16_t *prds, rds_gt14a_t *pgt); 229 | 230 | #ifdef __cplusplus 231 | } 232 | #endif 233 | #endif 234 | -------------------------------------------------------------------------------- /cio.c: -------------------------------------------------------------------------------- 1 | /* BSD License 2 | Copyright (c) 2015 Andrey Chilikin https://github.com/achilikin 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | * Redistributions of source code must retain the above copyright notice, 6 | this list of conditions and the following disclaimer. 7 | * Redistributions in binary form must reproduce the above copyright notice, 8 | this list of conditions and the following disclaimer in the documentation 9 | and/or other materials provided with the distribution. 10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 11 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 12 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 13 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS 14 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 15 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 16 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 17 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 18 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 19 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 20 | POSSIBILITY OF SUCH DAMAGE. 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include "cli.h" 30 | #include "cmd.h" 31 | 32 | void stdio_mode(enum stdio_mode_t mode) 33 | { 34 | struct termios term_attr; 35 | 36 | tcgetattr(STDIN_FILENO, &term_attr); 37 | if (mode == STDIO_MODE_RAW) 38 | term_attr.c_lflag &= ~(ECHO | ICANON); 39 | else 40 | term_attr.c_lflag |= (ECHO | ICANON); 41 | term_attr.c_cc[VMIN] = (mode == STDIO_MODE_RAW) ? 0 : 1; 42 | tcsetattr(STDIN_FILENO, TCSANOW, &term_attr); 43 | 44 | return; 45 | } 46 | 47 | static int stdin_getch(int fd) 48 | { 49 | struct pollfd polls; 50 | polls.fd = fd; 51 | polls.events = POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI; 52 | polls.revents = 0; 53 | 54 | if (poll(&polls, 1, 0) < 0) 55 | return -1; 56 | 57 | int ch = 0; 58 | if (polls.revents) 59 | read(fd, &ch, 1); 60 | 61 | return ch; 62 | } 63 | 64 | // Escape sequence states 65 | #define ESC_CHAR 0 66 | #define ESC_BRACKET 1 67 | #define ESC_BRCHAR 2 68 | #define ESC_TILDA 3 69 | #define ESC_FUNC 4 70 | #define ESC_CRLF 5 71 | 72 | static int stdio_getch(console_io_t *cli) 73 | { 74 | cmd_line_t *cl = (cmd_line_t *)cli->data; 75 | 76 | int ch = stdin_getch(cli->ifd); 77 | if (ch <= 0) 78 | return ch; 79 | 80 | if (cl->flags & CLI_DEBUG) { 81 | dprintf(cli->ofd, "['"); 82 | if (ch < ' ') 83 | dprintf(cli->ofd, "%X", ch); 84 | else 85 | dprintf(cli->ofd, "%c", ch); 86 | dprintf(cli->ofd, "':%u]", ch); 87 | } 88 | 89 | if (ch == 127) 90 | return '\b'; 91 | 92 | // ESC sequence state machine 93 | if (ch == 27) { 94 | cl->esc = ESC_BRACKET; 95 | return 0; 96 | } 97 | if (cl->esc == ESC_BRACKET) { 98 | if ((ch == '[') || (ch == 'O')) { 99 | cl->esc = ESC_BRCHAR; 100 | return 0; 101 | } 102 | } 103 | if (cl->esc == ESC_BRCHAR) { 104 | if (ch >= 'A' && ch <= 'D') { 105 | ch = KEY_EXTRA | (ch - 'A' + 1); 106 | cl->esc = ESC_CHAR; 107 | goto fret; 108 | } 109 | if (ch == 'F') { 110 | ch = KEY_END; 111 | cl->esc = ESC_CHAR; 112 | goto fret; 113 | } 114 | if (ch == 'H') { 115 | ch = KEY_HOME; 116 | cl->esc = ESC_CHAR; 117 | goto fret; 118 | } 119 | 120 | if ((ch >= '1') && (ch <= '6')) { 121 | cl->esc = ESC_TILDA; 122 | cl->idx = ch - '0'; 123 | return 0; 124 | } 125 | goto fret; 126 | } 127 | if (cl->esc == ESC_TILDA) { 128 | if (ch == '~') { 129 | ch = KEY_EXTRA | (cl->idx + 4); 130 | cl->esc = ESC_CHAR; 131 | goto fret; 132 | } 133 | if ((ch >= '0') && (ch <= '9')) { 134 | cl->idx <<= 4; 135 | cl->idx |= ch - '0'; 136 | cl->esc = ESC_FUNC; 137 | return 0; 138 | } 139 | cl->esc = ESC_CHAR; 140 | return 0; 141 | } 142 | 143 | if (cl->esc == ESC_FUNC) { 144 | if (ch == '~') { 145 | cl->esc = ESC_CHAR; 146 | ch = cl->idx | KEY_EXTRA; 147 | goto fret; 148 | } 149 | cl->esc = ESC_CHAR; 150 | return 0; 151 | } 152 | 153 | // convert CR to LF 154 | if (ch == '\r') { 155 | cl->esc = ESC_CRLF; 156 | return '\n'; 157 | } 158 | // do not return LF if it is part of CR+LF combination 159 | if (ch == '\n') { 160 | if (cl->esc == ESC_CRLF) { 161 | cl->esc = ESC_CHAR; 162 | return 0; 163 | } 164 | } 165 | cl->esc = ESC_CHAR; 166 | fret: 167 | if ((cl->flags & CLI_DEBUG) && (ch & KEY_EXTRA)) 168 | dprintf(cli->ofd, "<%04X>", ch); 169 | 170 | return ch; 171 | } 172 | 173 | static int stdio_putch(console_io_t *cli, int ch) 174 | { 175 | write(cli->ofd, &ch, 1); 176 | return 0; 177 | } 178 | 179 | static int stdio_puts(console_io_t *cli, const char *str) 180 | { 181 | dprintf(cli->ofd, "%s", str); 182 | return 0; 183 | } 184 | 185 | static int stdio_interact(console_io_t *cli, void *ptr) 186 | { 187 | uint16_t ch; 188 | 189 | if ((ch = cli->getch(cli)) <= 0) 190 | return ch; 191 | 192 | cmd_line_t *cl = (cmd_line_t *)cli->data; 193 | 194 | if (ch & KEY_EXTRA) { 195 | if (ch == ARROW_UP && (cl->cursor == 0)) { 196 | // execute last successful command 197 | for(cl->cursor = 0; ; cl->cursor++) { 198 | cl->cmd[cl->cursor] = cl->hist[cl->cursor]; 199 | if (cl->cmd[cl->cursor] == '\0') 200 | break; 201 | } 202 | cli->puts(cli, cl->cmd); 203 | } 204 | return 1; 205 | } 206 | 207 | if (ch == '\n') { 208 | cli->putch(cli, ch); 209 | if (*cl->cmd) { 210 | int8_t ret = cli->proc(cli, ptr); 211 | memcpy(cl->hist, cl->cmd, CMD_LEN); 212 | if (ret == CLI_EARG) 213 | cli->puts(cli, "Invalid argument\n"); 214 | else if (ret == CLI_ENOTSUP) 215 | cli->puts(cli, "Unknown command\n"); 216 | else if (ret == CLI_ENODEV) 217 | cli->puts(cli, "Device error\n"); 218 | } 219 | for(uint8_t i = 0; i < cl->cursor; i++) 220 | cl->cmd[i] = '\0'; 221 | cl->cursor = 0; 222 | if (!stop && cli->prompt) { 223 | cli->putch(cli, cli->prompt); 224 | cli->putch(cli, ' '); 225 | } 226 | return 1; 227 | } 228 | 229 | // backspace processing 230 | if (ch == '\b') { 231 | if (cl->cursor) { 232 | cl->cursor--; 233 | cl->cmd[cl->cursor] = '\0'; 234 | cli->putch(cli,'\b'); 235 | cli->putch(cli, ' '); 236 | cli->putch(cli, '\b'); 237 | } 238 | } 239 | 240 | // skip control or damaged bytes 241 | if (ch < ' ') 242 | return 0; 243 | 244 | // echo 245 | cli->putch(cli, ch); 246 | 247 | cl->cmd[cl->cursor++] = (uint8_t)ch; 248 | cl->cursor &= CMD_LEN; 249 | // clean up in case of overflow (command too long) 250 | if (!cl->cursor) { 251 | for(unsigned i = 0; i <= CMD_LEN; i++) 252 | cl->cmd[i] = '\0'; 253 | } 254 | 255 | return 1; 256 | } 257 | 258 | void stdio_init(console_io_t *cli, cproc_t *cli_handler) 259 | { 260 | cli->data = malloc(sizeof(cmd_line_t)); 261 | memset(cli->data, 0, sizeof(cmd_line_t)); 262 | cli->ifd = STDIN_FILENO; 263 | if (!cli->ofd) 264 | cli->ofd = STDOUT_FILENO; 265 | cli->getch = stdio_getch; 266 | cli->putch = stdio_putch; 267 | cli->puts = stdio_puts; 268 | cli->interact = stdio_interact; 269 | cli->proc = cli_handler; 270 | 271 | if (cli->prompt) { 272 | stdio_putch(cli, cli->prompt); 273 | stdio_putch(cli, ' '); 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /rds.c: -------------------------------------------------------------------------------- 1 | /* Some of RBDS standard functions 2 | ftp://ftp.rds.org.uk/pub/acrobat/rbds1998.pdf 3 | 4 | Copyright (c) 2014 Andrey Chilikin (https://github.com/achilikin) 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | #include 20 | #include 21 | 22 | #include "rds.h" 23 | 24 | #ifndef _BM 25 | #define _BM(bit) (1 << ((uint16_t)bit)) // convert bit number to bit mask 26 | #endif 27 | 28 | #define _BM(bit) (1 << ((uint16_t)bit)) // convert bit number to bit mask 29 | const char *rds_gt_00 = "Basic tuning and switching information"; 30 | const char *rds_gt_01 = "Program-item number and slow labeling codes"; 31 | const char *rds_gt_02 = "Radiotext"; 32 | const char *rds_gt_03A = "Applications Identification for Open Data"; 33 | const char *rds_gt_03B = "Open data application"; 34 | const char *rds_gt_04A = "Clock-time and date"; 35 | const char *rds_gt_04B = "Open data application"; 36 | const char *rds_gt_05 = "Transparent data channels or ODA"; 37 | const char *rds_gt_06 = "In house applications or ODA"; 38 | const char *rds_gt_07A = "Radio paging or ODA"; 39 | const char *rds_gt_07B = "Open data application"; 40 | const char *rds_gt_08 = "Traffic Message Channel or ODA"; 41 | const char *rds_gt_09 = "Emergency warning systems or ODA"; 42 | const char *rds_gt_10A = "Program Type Name"; 43 | const char *rds_gt_10B = "Open data application"; 44 | const char *rds_gt_11 = "Open data application"; 45 | const char *rds_gt_12 = "Open data application"; 46 | const char *rds_gt_13A = "Enhanced Radio paging or ODA"; 47 | const char *rds_gt_13B = "Open data application"; 48 | const char *rds_gt_14 = "Enhanced Other Networks information"; 49 | const char *rds_gt_15A = "Fast basic tuning and switching information (phased out)"; 50 | const char *rds_gt_15B = "Fast tuning and switching information"; 51 | 52 | static const char *rds_gt_names[] = { 53 | rds_gt_00, rds_gt_00, 54 | rds_gt_01, rds_gt_01, 55 | rds_gt_02, rds_gt_02, 56 | rds_gt_03A, rds_gt_03B, 57 | rds_gt_04A, rds_gt_04B, 58 | rds_gt_05, rds_gt_05, 59 | rds_gt_06, rds_gt_06, 60 | rds_gt_07A, rds_gt_07B, 61 | rds_gt_08, rds_gt_08, 62 | rds_gt_09, rds_gt_09, 63 | rds_gt_10A, rds_gt_10B, 64 | rds_gt_11, rds_gt_11, 65 | rds_gt_12, rds_gt_12, 66 | rds_gt_13A, rds_gt_13B, 67 | rds_gt_14, rds_gt_14, 68 | rds_gt_15A, rds_gt_15B 69 | }; 70 | 71 | const char *rds_gt_name(uint8_t group, uint8_t version) 72 | { 73 | int idx = (group & 0x0F)*2 + (version & 0x01); 74 | return rds_gt_names[idx]; 75 | } 76 | 77 | static char *rds_swap16(void *ptr) 78 | { 79 | char *p8 = (char *)ptr; 80 | char tmp = p8[0]; 81 | p8[0] = p8[1]; 82 | p8[1] = tmp; 83 | 84 | return (char *)ptr; 85 | } 86 | 87 | static int rds_add_af(rds_gt00a_t *dst, uint8_t *paf) 88 | { 89 | if (paf[1] == 250) // skip LF/MF for now 90 | return 0; 91 | 92 | for(int n = 0; n < 2; n++) { 93 | if (paf[n] >= 225 && paf[n] <= 249) { // number of af 94 | dst->naf = paf[n] - 224; 95 | memset(dst->af, 0, sizeof(dst->af)); 96 | continue; 97 | } 98 | if (!paf[n] || paf[n] > 204) 99 | continue; 100 | for(int i = 0; i < 25; i++) { 101 | if (dst->af[i] == 0) 102 | dst->af[i] = paf[n]; 103 | if (dst->af[i] == paf[n]) 104 | break; 105 | } 106 | } 107 | return 0; 108 | } 109 | 110 | int rds_parse_gt00a(uint16_t *prds, rds_gt00a_t *pgt) 111 | { 112 | uint8_t ci = (prds[RDS_B] & 0x03); 113 | pgt->ta = !!(prds[RDS_B] & RDS_TA); // Traffic Announcement 114 | pgt->ms = (prds[RDS_B] & RDS_MS) ? 'M' : 'S'; // Music/Speech 115 | 116 | // Decoder control bit 117 | if (prds[RDS_B] & RDS_DI) 118 | pgt->di |= _BM(3-ci); 119 | else 120 | pgt->di &= ~_BM(3-ci); 121 | 122 | pgt->ci = ci; 123 | pgt->valid |= 1 << ci; 124 | 125 | if (!(prds[RDS_B] & RDS_VER)) { 126 | uint8_t *paf = (uint8_t *)rds_swap16(&prds[RDS_C]); 127 | rds_add_af(pgt, paf); 128 | } 129 | 130 | ci <<= 1; 131 | if (pgt->ps[0] == '\0') { 132 | memset(pgt->ps, ' ', 8); 133 | pgt->ps[8] = '\0'; 134 | } 135 | 136 | char *pchar = rds_swap16(&prds[RDS_D]); 137 | for(int i = 0; i < 2; i++) { 138 | if (isalnum(pchar[i]) || pchar[i] == ' ') 139 | pgt->ps[ci+i] = pchar[i]; 140 | } 141 | 142 | return 0; 143 | } 144 | 145 | int rds_parse_gt01a(uint16_t *prds, rds_gt01a_t *pgt) 146 | { 147 | pgt->rpc = prds[RDS_B] & 0x1F; 148 | pgt->la = !!(prds[RDS_C] & 0x8000); 149 | pgt->vc = (prds[RDS_C] >> 12) & 0x07; 150 | pgt->slc = prds[RDS_C]& 0x0FFF; 151 | pgt->pinc = prds[RDS_D]& 0x0FFF; 152 | return 0; 153 | } 154 | 155 | int rds_parse_gt02a(uint16_t *prds, rds_gt02a_t *pgt) 156 | { 157 | uint8_t ab = !!(prds[RDS_B] & RDS_AB); 158 | uint8_t si = (prds[RDS_B] & 0x0F); 159 | 160 | if (pgt->rt[0] == '\0' || pgt->ab != ab) { 161 | memset(pgt->rt, ' ', 64); 162 | pgt->rt[64] = '\0'; 163 | } 164 | 165 | pgt->ab = ab; 166 | pgt->si = si; 167 | pgt->valid |= 1 << si; 168 | si *= 4; 169 | 170 | char *pchar = rds_swap16(&prds[RDS_C]); 171 | rds_swap16(&prds[RDS_D]); 172 | 173 | for (int i = 0; i < 4; i++) { 174 | uint8_t ch = pchar[i]; 175 | if (isalnum(ch) || ch == ' ') { 176 | if (ch == '\r') { 177 | pgt->valid = 0xFFFF; 178 | ch = '^'; 179 | } 180 | pgt->rt[si + i] = ch; 181 | } 182 | } 183 | 184 | return 0; 185 | } 186 | 187 | int rds_parse_gt03a(uint16_t *prds, rds_gt03a_t *pgt) 188 | { 189 | pgt->agtc = (prds[RDS_B] >> 1) & 0x0F; 190 | pgt->ver = prds[RDS_B] & 0x01; 191 | uint16_t msg = prds[RDS_C]; 192 | pgt->msg = msg; 193 | pgt->aid = prds[RDS_D]; 194 | 195 | pgt->vc = msg >> 14; 196 | if (pgt->vc == 0) { 197 | pgt->ltn = (msg >> 6) & 0x3F; 198 | pgt->afi = !!(msg & 0x20); 199 | pgt->m = !!(msg & 0x10); 200 | pgt->i = !!(msg & 0x08); 201 | pgt->n = !!(msg & 0x04); 202 | pgt->r = !!(msg & 0x02); 203 | pgt->u = !!(msg & 0x01); 204 | } 205 | else { 206 | pgt->g = (msg >> 12) & 0x03; 207 | pgt->sid = (msg >> 6) & 0x3F; 208 | pgt->ta = (msg >> 4) & 0x03; 209 | pgt->tw = (msg >> 2) & 0x03; 210 | pgt->td = (msg >> 0) & 0x03; 211 | } 212 | return 0; 213 | } 214 | 215 | int rds_parse_gt04a(uint16_t *prds, rds_gt04a_t *pgt) 216 | { 217 | uint8_t hour = (prds[RDS_D] >> 12) & 0x0F; 218 | hour |= (prds[RDS_C] & 0x01) << 4; 219 | if (hour > 23) 220 | return -1; 221 | uint8_t minute = (prds[RDS_D] >> 6) & 0x3F; 222 | if (minute > 59) 223 | return -1; 224 | int8_t tz = prds[RDS_D] & 0x0F; 225 | 226 | uint32_t date = (prds[RDS_C] >> 1); 227 | date |= (prds[RDS_B] & 0x03) << 15; 228 | int y = (int)(((double)date - 15078.2)/365.25); 229 | int m = (int)((((double)date - 14956.1) - (int)(y*365.25))/30.6001); 230 | int d = date - 14956 - (int)(y*365.25) - (int)(m*30.60001); 231 | int k = (m == 14 || m == 15) ? 1 : 0; 232 | y += k; 233 | m = m - 1 - k*12; 234 | 235 | pgt->hour = hour; 236 | pgt->minute = minute; 237 | pgt->tz_hour = tz >> 1; 238 | pgt->tz_half = tz &0x01; 239 | pgt->ts_sign = prds[RDS_D] & _BM(5); 240 | pgt->day = d; 241 | pgt->month = m; 242 | pgt->year = y + 1900; 243 | 244 | return 0; 245 | } 246 | 247 | int rds_parse_gt05a(uint16_t *prds, rds_gt05a_t *pgt) 248 | { 249 | uint8_t channel = prds[RDS_B] & 0x001F; 250 | pgt->channel |= 1u << channel; 251 | pgt->tds[channel][0] = prds[RDS_C]; 252 | pgt->tds[channel][1] = prds[RDS_D]; 253 | 254 | return 0; 255 | } 256 | 257 | int rds_parse_gt08a(uint16_t *prds, rds_gt08a_t *pgt) 258 | { 259 | if (pgt->spn[0] == '\0') { 260 | memset(pgt->spn, ' ', 8); 261 | pgt->spn[8] = '\0'; 262 | } 263 | 264 | pgt->x4 = !!(prds[RDS_B] & 0x10); 265 | pgt->vc = (prds[RDS_B] & 0x0F); 266 | 267 | pgt->x3 = !!(prds[RDS_B] & 0x08); 268 | pgt->x2 = !!(prds[RDS_B] & 0x04); 269 | 270 | pgt->y = prds[RDS_C]; 271 | pgt->d = !!(prds[RDS_C] & 0x8000); 272 | pgt->dir = !!(prds[RDS_C] & 0x4000); 273 | pgt->ext = (prds[RDS_C] >> 11) & 0x07; 274 | pgt->eve = prds[RDS_C] & 0x7FF; 275 | 276 | pgt->loc = prds[RDS_D]; 277 | 278 | return 0; 279 | } 280 | 281 | int rds_parse_gt10a(uint16_t *prds, rds_gt10a_t *pgt) 282 | { 283 | uint8_t ab = !!(prds[RDS_B] & RDS_AB); 284 | uint8_t ci = (prds[RDS_B] & 0x01); 285 | 286 | if (pgt->ps[0] == '\0' || pgt->ab != ab) { 287 | memset(pgt->ps, ' ', 8); 288 | pgt->ps[8] = '\0'; 289 | } 290 | pgt->ci = ci; 291 | pgt->ab = ab; 292 | 293 | ci *= 4; 294 | char *pchar = rds_swap16(&prds[RDS_C]); 295 | rds_swap16(&prds[RDS_D]); 296 | for(int i = 0; i < 4; i++) { 297 | if (isalnum(pchar[i]) || pchar[i] == ' ') 298 | pgt->ps[ci+i] = pchar[i]; 299 | } 300 | return 0; 301 | } 302 | 303 | int rds_parse_gt14a(uint16_t *prds, rds_gt14a_t *pgt) 304 | { 305 | char *pchar = rds_swap16(&prds[RDS_C]); 306 | 307 | if (pgt->pi_on != prds[RDS_D]) { 308 | rds_hdr_t hdr; 309 | memcpy(&hdr, &pgt->hdr, sizeof(hdr)); 310 | memset(pgt, 0, sizeof(rds_gt14a_t)); 311 | memset(pgt->ps, ' ', 8); 312 | memcpy(&pgt->hdr, &hdr, sizeof(hdr)); 313 | } 314 | 315 | pgt->tp_on = !!(prds[RDS_B] & 0x10); 316 | pgt->variant = prds[RDS_B] & 0x0F; 317 | pgt->info = prds[RDS_C]; 318 | pgt->pi_on = prds[RDS_D]; 319 | 320 | pgt->avc |= 1 << pgt->variant; 321 | 322 | if (pgt->variant < 4) { 323 | for(int i = 0; i <2; i++) { 324 | if (isalnum(pchar[i]) || pchar[i] == ' ') 325 | pgt->ps[pgt->variant*2+i] = pchar[i]; 326 | } 327 | return 0; 328 | } 329 | if (pgt->variant == 4) { 330 | pgt->af[0] = pchar[1]; 331 | pgt->af[1] = pchar[0]; 332 | return 0; 333 | } 334 | if (pgt->variant < 10) { 335 | pgt->mf[pgt->variant - 5] = prds[RDS_C]; 336 | return 0; 337 | } 338 | if (pgt->variant == 12) { 339 | pgt->li = prds[RDS_C]; 340 | return 0; 341 | } 342 | if (pgt->variant == 13) { 343 | pgt->pty = prds[RDS_C]; 344 | return 0; 345 | } 346 | if (pgt->variant == 14) { 347 | pgt->pin = prds[RDS_C]; 348 | return 0; 349 | } 350 | return 0; 351 | } 352 | -------------------------------------------------------------------------------- /si4703.c: -------------------------------------------------------------------------------- 1 | /* Si4703 based RDS scanner 2 | Copyright (c) 2014 Andrey Chilikin (https://github.com/achilikin) 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 2 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | */ 17 | 18 | /* 19 | Si4702/03 FM Radio Receiver ICs: 20 | http://www.silabs.com/products/audio/fmreceivers/pages/si470203.aspx 21 | 22 | Si4703S-C19 Data Sheet: 23 | https://www.sparkfun.com/datasheets/BreakoutBoards/Si4702-03-C19-1.pdf 24 | 25 | Si4703S-B16 Data Sheet: 26 | Not on SiLabs site anymore, google for it 27 | 28 | Si4700/01/02/03 Firmware Change List, AN281: 29 | Not on SiLabs site anymore, google for it 30 | 31 | Using RDS/RBDS with the Si4701/03: 32 | http://www.silabs.com/Support%20Documents/TechnicalDocs/AN243.pdf 33 | 34 | Si4700/01/02/03 Programming Guide: 35 | http://www.silabs.com/Support%20Documents/TechnicalDocs/AN230.pdf 36 | 37 | RBDS Standard: 38 | ftp://ftp.rds.org.uk/pub/acrobat/rbds1998.pdf 39 | */ 40 | #include 41 | #include 42 | #include 43 | 44 | #include "rds.h" 45 | #include "pi2c.h" 46 | #include "si4703.h" 47 | #include "rpi_pin.h" 48 | 49 | struct si4703_state { 50 | const char *name; 51 | uint8_t reg; 52 | uint8_t offset; 53 | uint16_t mask; 54 | }si_state[] = { 55 | { "DSMUTE", POWERCFG, 15, DSMUTE }, 56 | { "DMUTE", POWERCFG, 14, DMUTE }, 57 | { "MONO", POWERCFG, 13, MONO }, 58 | { "RDSM", POWERCFG, 11, RDSM }, 59 | { "SKMODE", POWERCFG, 10, SKMODE }, 60 | { "SEEKUP", POWERCFG, 9, SEEKUP }, 61 | { "SEEK", POWERCFG, 8, SEEK }, 62 | { "DISABLE", POWERCFG, 6, PWR_DISABLE }, 63 | { "ENABLE", POWERCFG, 0, PWR_ENABLE }, // do not write 0 to this register 64 | 65 | { "TUNE", CHANNEL, 15, TUNE }, 66 | { "CHAN", CHANNEL, 0, CHAN }, 67 | 68 | { "RDSIEN", SYSCONF1, 15, RDSIEN }, 69 | { "SRCIEN", SYSCONF1, 14, STCIEN }, 70 | { "RDS", SYSCONF1, 12, RDS }, 71 | { "DE", SYSCONF1, 11, DE }, 72 | { "AGCD", SYSCONF1, 10, AGCD }, 73 | { "BLNDADJ", SYSCONF1, 7, BLNDADJ }, 74 | { "GPIO3", SYSCONF1, 4, GPIO3 }, 75 | { "GPIO2", SYSCONF1, 2, GPIO2 }, 76 | { "GPIO1", SYSCONF1, 0, GPIO1 }, 77 | 78 | { "SEEKTH", SYSCONF2, 8, SEEKTH }, 79 | { "BAND", SYSCONF2, 6, BAND }, 80 | { "SPACE", SYSCONF2, 4, SPACING }, 81 | { "VOLUME", SYSCONF2, 0, VOLUME }, 82 | 83 | { "SMUTER", SYSCONF3, 14, SMUTER }, 84 | { "SMUTEA", SYSCONF3, 12, SMUTEA }, 85 | { "RDSPRF", SYSCONF3, 9, RDSPRF }, 86 | { "VOLEXT", SYSCONF3, 8, VOLEXT }, 87 | { "SKSNR", SYSCONF3, 4, SKSNR }, 88 | { "SKCNT", SYSCONF3, 0, SKCNT }, 89 | 90 | { "XOSCEN", TEST1, 15, XOSCEN }, 91 | { "AHIZEN", TEST1, 14, AHIZEN }, 92 | 93 | { "RDSR", STATUSRSSI, 15, RDSR }, 94 | { "STC", STATUSRSSI, 14, STC }, 95 | { "SFBL", STATUSRSSI, 13, SFBL }, 96 | { "AFCRL", STATUSRSSI, 12, AFCRL }, 97 | { "RDSS", STATUSRSSI, 11, RDSS }, 98 | { "BLERA", STATUSRSSI, 9, BLERA }, 99 | { "ST", STATUSRSSI, 8, STEREO }, 100 | { "RSSI", STATUSRSSI, 0, RSSI }, 101 | 102 | { "BLERB", READCHAN, 14, BLERB }, 103 | { "BLERC", READCHAN, 12, BLERC }, 104 | { "BLERD", READCHAN, 10, BLERD }, 105 | { "READCHAN", READCHAN, 0, RCHAN }, 106 | }; 107 | 108 | int si_band[3][2] = { {8750, 10800}, {7600, 10800}, {7600, 9000}}; 109 | int si_space[3] = { 20, 10, 5 }; 110 | 111 | inline int cmd_is(const char *str, const char *is) 112 | { 113 | return strcmp(str, is) == 0; 114 | } 115 | 116 | int si_set_state(uint16_t *regs, const char *name, uint16_t val) 117 | { 118 | uint16_t current; 119 | int nstates = sizeof(si_state)/sizeof(si_state[0]); 120 | for(int i = 0; i < nstates; i++) { 121 | if (cmd_is(si_state[i].name, name)) { 122 | current = regs[si_state[i].reg] & si_state[i].mask; 123 | current >>= si_state[i].offset; 124 | val <<= si_state[i].offset; 125 | val &= si_state[i].mask; 126 | regs[si_state[i].reg] &= ~si_state[i].mask; 127 | regs[si_state[i].reg] |= val; 128 | return current; 129 | } 130 | } 131 | return -1; 132 | } 133 | 134 | int si_read_regs(uint16_t *regs) 135 | { 136 | uint8_t buf[32]; 137 | 138 | if (pi2c_read(PI2C_BUS, buf, 32) < 0) 139 | return -1; 140 | 141 | // Si4703 sends back registers as 10, 11, 12, 13, 14, 15, 0, ... 142 | // so we need to shuffle our buffer a bit 143 | for(int i = 0, x = 10; x != 9; x++, i += 2) { 144 | x &= 0x0F; 145 | regs[x] = buf[i] << 8; 146 | regs[x] |= buf[i+1]; 147 | } 148 | return 0; 149 | } 150 | 151 | static inline int bit_set(uint16_t reg, uint16_t bit) 152 | { 153 | int ret = 0; 154 | if (reg & bit) 155 | ret = 1; 156 | return ret; 157 | } 158 | 159 | static int _get_freq(uint16_t *regs, uint16_t reg) 160 | { 161 | int band = (regs[SYSCONF2] >> 6) & 0x03; 162 | int space = (regs[SYSCONF2] >> 4) & 0x03; 163 | int nchan = reg & 0x03FF; 164 | 165 | int freq = nchan*si_space[space] + si_band[band][0]; 166 | return freq; 167 | } 168 | 169 | static const char *si_parse_reg(uint16_t *regs, uint8_t reg) 170 | { 171 | static char buf[256]; 172 | uint16_t bits = regs[reg]; 173 | 174 | if (reg == CHIPID) { 175 | sprintf(buf, ": REV %X DEV %s FIRMWARE %d", (bits >> 10) & 0x3F, 176 | ((bits >> 6) & 0x0F) == 9 ? "Si4703" : "Si4702", bits & 0x3F); 177 | return buf; 178 | } 179 | if (reg == POWERCFG) { 180 | sprintf(buf, ": DSMUTE %d DMUTE %d MONO %d RDSM %d SKMODE %d SEEKUP %d SEEK %d DISABLE %d ENABLE %d", 181 | bit_set(bits, DSMUTE), bit_set(bits, DMUTE), bit_set(bits, MONO), bit_set(bits, RDSM), 182 | bit_set(bits, SKMODE), bit_set(bits, SEEKUP), bit_set(bits, SEEK), 183 | bit_set(bits, PWR_DISABLE), bit_set(bits, PWR_ENABLE)); 184 | return buf; 185 | } 186 | if (reg == CHANNEL) { 187 | int freq = _get_freq(regs, regs[CHANNEL]); 188 | sprintf(buf, ": TUNE %d CHAN %d (%d.%02dMHz)", bit_set(bits, TUNE), bits & 0x3FF, freq/100, freq%100); 189 | return buf; 190 | } 191 | if (reg == SYSCONF1) { 192 | sprintf(buf, ": RDSIEN %d STCIEN %d RDS %d DE %d AGCD %d BLNDADJ %d GPIO3 %d GPIO2 %d GPIO %d", 193 | bit_set(bits, RDSIEN), bit_set(bits, STCIEN), bit_set(bits, RDS), 194 | bit_set(bits, DE), bit_set(bits, AGCD), (bits >> 6) & 0x03, 195 | (bits >> 4) & 0x03, (bits >> 2) & 0x03, bits & 0x3); 196 | return buf; 197 | } 198 | if (reg == SYSCONF2) { 199 | sprintf(buf, ": SEEKTH %d BAND %d SPACE %d VOLUME %d", 200 | (bits >> 8) & 0xFF, (bits >> 6) & 0x03, (bits >> 4) & 0x03, bits & 0x0F); 201 | return buf; 202 | } 203 | if (reg == SYSCONF3) { 204 | sprintf(buf, ": SMUTER %d SMUTEA %d RDSPRF %d VOLEXT %d SKSNR %d SKCNT %d", (bits >> 14) & 0x03, 205 | (bits >> 12) & 0x03, bit_set(bits, RDSPRF), bit_set(bits, VOLEXT), (bits >> 4) & 0x0F, bits & 0x0F); 206 | return buf; 207 | } 208 | if (reg == TEST1) { 209 | sprintf(buf, ": XOSCEN %d AHIZEN %d", bit_set(bits, XOSCEN), bit_set(bits, AHIZEN)); 210 | return buf; 211 | } 212 | if (reg == STATUSRSSI) { 213 | sprintf(buf, ": RDSR %d STC %d SF/BL %d AFCRL %d RDSS %d BLERA %d ST %d RSSI %d", 214 | bit_set(bits, RDSR), bit_set(bits, STC), bit_set(bits, SFBL), bit_set(bits, AFCRL), 215 | bit_set(bits, RDSS), (bits >> 9) & 0x03, (bits >> 8) & 0x01, bits & 0xFF); 216 | return buf; 217 | } 218 | if (reg == READCHAN) { 219 | int freq = _get_freq(regs, regs[READCHAN]); 220 | sprintf(buf, ": BLERB %d BLERC %d BLERD %d READCHAN %d (%d.%02dMHz)", 221 | (bits >> 14) & 0x03, (bits >> 12) & 0x03, (bits >> 10) & 0x03, bits & 0x3FF, freq/100, freq%100); 222 | return buf; 223 | } 224 | return ""; 225 | } 226 | 227 | void si_dump(int fd, uint16_t *regs, const char *title, uint16_t span) 228 | { 229 | if (title) 230 | dprintf(fd, "%s", title); 231 | uint8_t start = (span >> 8) & 0xFF; 232 | uint8_t num = span & 0xFF; 233 | if (start > 15) 234 | return; 235 | if ((start + num) > 16) 236 | return; 237 | for(uint8_t i = 0; i < num; i++) 238 | dprintf(fd, "%X %04X%s\n", i, regs[start + i], si_parse_reg(regs, start + i)); 239 | } 240 | 241 | int si_update(uint16_t *regs) 242 | { 243 | int i = 0, ret = 0; 244 | uint8_t buf[32]; 245 | 246 | // write automatically begins at register 0x02 247 | for(int reg = 2 ; reg < 8 ; reg++) { 248 | buf[i++] = regs[reg] >> 8; 249 | buf[i++] = regs[reg] & 0x00FF; 250 | } 251 | 252 | ret = pi2c_write(PI2C_BUS, buf, i); 253 | return ret; 254 | } 255 | 256 | void si_set_channel(uint16_t *regs, int chan) 257 | { 258 | int band = (regs[SYSCONF2] >> 6) & 0x03; 259 | int space = (regs[SYSCONF2] >> 4) & 0x03; 260 | int nchan = (si_band[band][1] - si_band[band][0])/si_space[space]; 261 | 262 | if (chan > nchan) chan = nchan; 263 | 264 | regs[CHANNEL] &= 0xFC00; 265 | regs[CHANNEL] |= chan; 266 | regs[CHANNEL] |= TUNE; 267 | si_update(regs); 268 | 269 | rpi_delay_ms(10); 270 | 271 | // poll to see if STC is set 272 | int i = 0; 273 | while(i++ < 100) { 274 | si_read_regs(regs); 275 | if (regs[STATUSRSSI] & STC) break; 276 | rpi_delay_ms(10); 277 | } 278 | 279 | regs[CHANNEL] &= ~TUNE; 280 | si_update(regs); 281 | 282 | // wait for the si4703 to clear the STC as well 283 | i = 0; 284 | while(i++ < 100) { 285 | si_read_regs(regs); 286 | if (!(regs[STATUSRSSI] & STC)) break; 287 | rpi_delay_ms(10); 288 | } 289 | } 290 | 291 | // freq: 9500 for 95.00 MHz 292 | void si_tune(uint16_t *regs, int freq) 293 | { 294 | si_read_regs(regs); 295 | 296 | int band = (regs[SYSCONF2] >> 6) & 0x03; 297 | int space = (regs[SYSCONF2] >> 4) & 0x03; 298 | 299 | if (freq < si_band[band][0]) freq = si_band[band][0]; 300 | if (freq > si_band[band][1]) freq = si_band[band][1]; 301 | 302 | int nchan = (freq - si_band[band][0])/si_space[space]; 303 | 304 | si_set_channel(regs, nchan); 305 | } 306 | 307 | void si_set_rdsprf(uint16_t *regs, int set) 308 | { 309 | if (set) { 310 | regs[SYSCONF1] |= RDS; 311 | regs[SYSCONF3] |= RDSPRF; 312 | } 313 | else { 314 | regs[SYSCONF1] &= ~RDS; 315 | regs[SYSCONF3] &= ~RDSPRF; 316 | } 317 | } 318 | 319 | void si_set_volume(uint16_t *regs, int volume) 320 | { 321 | if (volume < 0) volume = 0; 322 | if (volume > 30) volume = 30; 323 | 324 | if (volume > 15) { 325 | regs[SYSCONF3] |= VOLEXT; 326 | volume -= 15; 327 | } 328 | else 329 | regs[SYSCONF3] &= ~VOLEXT; 330 | 331 | if (volume) 332 | regs[POWERCFG] |= DSMUTE | DMUTE; // unmute 333 | else 334 | regs[POWERCFG] &= ~(DSMUTE | DMUTE); // mute 335 | 336 | regs[SYSCONF2] &= ~VOLUME; // clear volume bits 337 | regs[SYSCONF2] |= volume; // set new volume 338 | si_update(regs); 339 | } 340 | 341 | int si_get_freq(uint16_t *regs) 342 | { 343 | return _get_freq(regs, regs[READCHAN]); 344 | } 345 | 346 | int si_seek(uint16_t *regs, int dir) 347 | { 348 | si_read_regs(regs); 349 | 350 | int channel = regs[READCHAN] & RCHAN; // current channel 351 | if(dir == SEEK_DOWN) 352 | regs[POWERCFG] &= ~SEEKUP; //Seek down is the default upon reset 353 | else 354 | regs[POWERCFG] |= SEEKUP; //Set the bit to seek up 355 | 356 | regs[POWERCFG] |= SEEK; //Start seek 357 | si_update(regs); //Seeking will now start 358 | 359 | //Poll to see if STC is set 360 | int i = 0; 361 | while(i++ < 500) { 362 | si_read_regs(regs); 363 | if((regs[STATUSRSSI] & STC) != 0) break; //Tuning complete! 364 | rpi_delay_ms(10); 365 | } 366 | 367 | si_read_regs(regs); 368 | int valueSFBL = regs[STATUSRSSI] & SFBL; //Store the value of SFBL 369 | regs[POWERCFG] &= ~SEEK; //Clear the seek bit after seek has completed 370 | si_update(regs); 371 | 372 | //Wait for the si4703 to clear the STC as well 373 | i = 0; 374 | while(i++ < 500) { 375 | si_read_regs(regs); 376 | if( (regs[STATUSRSSI] & STC) == 0) break; //Tuning complete! 377 | rpi_delay_ms(10); 378 | } 379 | 380 | if (channel == (regs[READCHAN] & RCHAN)) 381 | return 0; 382 | if (valueSFBL) //The bit was set indicating we hit a band limit or failed to find a station 383 | return 0; 384 | return si_get_freq(regs); 385 | } 386 | 387 | void si_power(uint16_t *regs, uint16_t mode) 388 | { 389 | if (mode == PWR_ENABLE) { 390 | // set only ENABLE bit, leaving device muted 391 | regs[POWERCFG] = PWR_ENABLE; 392 | // by default we allow wrap by not setting SKMODE 393 | // regs[POWERCFG] |= SKMODE; 394 | regs[SYSCONF1] |= RDS; // enable RDS 395 | 396 | // set mono/stereo blend adjust to default 0 or 31-49 RSSI 397 | regs[SYSCONF1] &= ~BLNDADJ; 398 | // set different BLDADJ if needed 399 | // x00=31-49, x40=37-55, x80=19-37,xC0=25-43 RSSI 400 | // regs[SYSCONF1] |= 0x0080; 401 | // enable RDS High-Performance Mode 402 | regs[SYSCONF3] |= RDSPRF; 403 | regs[SYSCONF1] |= DE; // set 50us De-Emphasis for Europe, skip for USA 404 | // select general Europe/USA 87.5 - 108 MHz band 405 | regs[SYSCONF2] = BAND0 | SPACE100; // 100kHz channel spacing for Europe 406 | 407 | // apply recommended seek settings for "most stations" 408 | regs[SYSCONF2] &= ~SEEKTH; 409 | regs[SYSCONF2] |= 0x0C00; // SEEKTH 12 410 | regs[SYSCONF3] &= 0xFF00; 411 | regs[SYSCONF3] |= 0x004F; // SKSNR 4, SKCNT 15 412 | } 413 | else { 414 | // power down condition 415 | regs[POWERCFG] = PWR_DISABLE | PWR_ENABLE; 416 | } 417 | 418 | si_update(regs); 419 | // recommended powerup time 420 | rpi_delay_ms(110); 421 | } 422 | -------------------------------------------------------------------------------- /cmd.c: -------------------------------------------------------------------------------- 1 | /* CLI commands handlers for Si4703 based RDS scanner 2 | Copyright (c) 2014 Andrey Chilikin (https://github.com/achilikin) 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 2 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include "cmd.h" 24 | #include "cli.h" 25 | #include "rds.h" 26 | #include "pi2c.h" 27 | #include "si4703.h" 28 | #include "rpi_pin.h" 29 | 30 | #define RSSI_LIMIT 35 31 | #define DEFAULT_STATION 9500 // Local station with the good signal strength 32 | #define DEFAULT_RDS_SCAN_TIMEOUT 15000 // Default RDS scan timeout in milliseconds 33 | 34 | inline const char *is_on(uint16_t mask) 35 | { 36 | if (mask) return "ON"; 37 | return "OFF"; 38 | } 39 | 40 | int cmd_arg(char *cmd, const char *str, char **arg) 41 | { 42 | if (!cmd) 43 | return 0; 44 | while(*cmd && *cmd <= ' ') cmd++; 45 | char *end; 46 | size_t len = strlen(str); 47 | if (strncmp(cmd, str, len) == 0) { 48 | end = cmd + len; 49 | if (*end > ' ') 50 | return 0; 51 | if (arg) { 52 | for(; *end <= ' ' && *end != '\0'; end ++); 53 | *arg = end; 54 | } 55 | return 1; 56 | } 57 | return 0; 58 | } 59 | 60 | int cmd_is(char *str, const char *is) 61 | { 62 | if (!str || !is) 63 | return 0; 64 | return cmd_arg(str, is, NULL); 65 | } 66 | 67 | int cmd_reset(int fd, UNUSED(char *arg)) 68 | { 69 | uint16_t si_regs[16]; 70 | si_read_regs(si_regs); 71 | 72 | rpi_pin_set_dir(SI_RESET, RPI_OUTPUT); 73 | rpi_pin_set(SI_RESET, 0); 74 | rpi_delay_ms(10); 75 | rpi_pin_set_dir(SI_RESET, RPI_INPUT); 76 | rpi_delay_ms(1); 77 | 78 | if (si_read_regs(si_regs) != 0) { 79 | dprintf(fd, "Unable to read Si4703!\n"); 80 | return CLI_ENODEV; 81 | } 82 | si_dump(fd, si_regs, "Reset Map:\n", 16); 83 | 84 | // enable the oscillator 85 | si_regs[TEST1] |= XOSCEN; 86 | si_update(si_regs); 87 | rpi_delay_ms(500); // recommended delay 88 | si_read_regs(si_regs); 89 | si_dump(fd, si_regs, "\nOscillator enabled:\n", 16); 90 | // the only way to reliable start the device is to powerdown and powerup 91 | // just powering up does not work for me after cold start 92 | uint8_t powerdown[2] = { 0, PWR_DISABLE | PWR_ENABLE }; 93 | pi2c_write(PI2C_BUS, powerdown, 2); 94 | rpi_delay_ms(110); 95 | 96 | cmd_power(fd, const_cast("up")); 97 | // tune to the local station with known signal strength 98 | si_tune(si_regs, DEFAULT_STATION); 99 | // si_tune() does not update registers 100 | si_update(si_regs); 101 | rpi_delay_ms(10); 102 | si_read_regs(si_regs); 103 | si_dump(fd, si_regs, "\nTuned\n", 16); 104 | return 0; 105 | } 106 | 107 | int cmd_power(int fd, char *arg) 108 | { 109 | uint16_t si_regs[16]; 110 | if (si_read_regs(si_regs) != 0) 111 | return CLI_ENODEV; 112 | 113 | if (arg && *arg) { 114 | if (cmd_is(arg, "up")) { 115 | si_power(si_regs, PWR_ENABLE); 116 | si_dump(fd, si_regs, "\nPowerup:\n", 16); 117 | return 0; 118 | } 119 | if (cmd_is(arg, "down")) { 120 | si_power(si_regs, PWR_DISABLE); 121 | si_dump(fd, si_regs, "\nPowerdown:\n", 16); 122 | return 0; 123 | } 124 | } 125 | 126 | si_dump(fd, si_regs, "\nPower:\n", 16); 127 | return 0; 128 | } 129 | 130 | int cmd_dump(int fd, char *arg __attribute__((unused))) 131 | { 132 | uint16_t si_regs[16]; 133 | if (si_read_regs(si_regs) == 0) { 134 | si_dump(fd, si_regs, "Registers map:\n", 16); 135 | return 0; 136 | } 137 | return CLI_ENODEV; 138 | } 139 | 140 | static int get_ps_si(char *ps_name, uint16_t *regs, int timeout) 141 | { 142 | int dt = 0; 143 | rds_gt00a_t rd; 144 | memset(&rd, 0, sizeof(rd)); 145 | 146 | while(dt < timeout) { 147 | if (rd.valid == 0x0F) 148 | break; 149 | si_read_regs(regs); 150 | if (regs[STATUSRSSI] & RDSR) { 151 | // basic tuning and switching information 152 | if (RDS_GET_GT(regs[RDSB]) == RDS_GT_00A) { 153 | memcpy(rd.hdr.rds, ®s[RDSA], sizeof(rd.hdr.rds)); 154 | rds_parse_gt00a(rd.hdr.rds, &rd); 155 | } 156 | rpi_delay_ms(40); // Wait for the RDS bit to clear 157 | dt += 40; 158 | } 159 | else { 160 | rpi_delay_ms(30); 161 | dt += 30; 162 | } 163 | } 164 | strcpy(ps_name, rd.ps); 165 | if (rd.valid == 0x0F) 166 | return rd.hdr.rds[0]; 167 | return -1; 168 | } 169 | 170 | int cmd_scan(int fd, char *arg) 171 | { 172 | uint8_t mode = 0; 173 | int nstations = 0; 174 | int freq, seek = 0; 175 | uint16_t si_regs[16]; 176 | 177 | if (si_read_regs(si_regs) != 0) 178 | return CLI_ENODEV; 179 | 180 | si_regs[POWERCFG] |= SKMODE; // stop seeking at the upper or lower band limit 181 | 182 | // mode as recommended in AN230, Table 23. Summary of Seek Settings 183 | if (arg && *arg) { 184 | mode = atoi(arg); 185 | if (mode > 5) mode = 5; 186 | } 187 | 188 | si_set_channel(si_regs, 0); 189 | dprintf(fd, "scanning, press any key to terminate...\n"); 190 | 191 | if (mode > 0) { 192 | si_regs[SYSCONF2] &= 0x00FF; 193 | si_regs[SYSCONF3] &= 0xFF00; 194 | uint16_t conf2 = 0; 195 | uint16_t conf3 = 0; 196 | 197 | if (mode == 1) { 198 | conf2 = 0x1900; 199 | conf3 = 0x0000; 200 | } 201 | if (mode == 2) { 202 | conf2 = 0x1900; 203 | conf3 = 0x0048; 204 | } 205 | if (mode == 3) { 206 | conf2 = 0x0C00; 207 | conf3 = 0x0048; 208 | } 209 | if (mode == 4) { 210 | conf2 = 0x0C00; 211 | conf3 = 0x007F; 212 | } 213 | if (mode == 5) { 214 | conf2 = 0x0000; 215 | conf3 = 0x004F; 216 | } 217 | si_regs[SYSCONF2] |= conf2; 218 | si_regs[SYSCONF3] |= conf3; 219 | 220 | si_update(si_regs); 221 | } 222 | 223 | int stop = 0; 224 | while(!is_stop(&stop)) { 225 | freq = si_seek(si_regs, SEEK_UP); 226 | if (freq == 0) 227 | break; 228 | seek = freq; 229 | nstations++; 230 | uint8_t rssi = si_regs[STATUSRSSI] & RSSI; 231 | dprintf(fd, "%5d ", freq); 232 | for(int si = 0; si < rssi; si++) 233 | dprintf(fd, "-"); 234 | dprintf(fd, " %d", rssi); 235 | 236 | int dt = 0; 237 | if (rssi > RSSI_LIMIT) { 238 | while(dt < 10000) { 239 | si_read_regs(si_regs); 240 | if (si_regs[STATUSRSSI] & STEREO) break; 241 | if (is_stop(&stop)) break; 242 | rpi_delay_ms(10); 243 | dt += 10; 244 | } 245 | } 246 | uint16_t st = si_regs[STATUSRSSI] & STEREO; 247 | 248 | if (st) { 249 | dprintf(fd, " ST"); 250 | if (rssi > RSSI_LIMIT) { 251 | int pi; 252 | char ps_name[16]; 253 | if ((pi = get_ps_si(ps_name, si_regs, 5000)) != -1) 254 | dprintf(fd, " %04X '%s'", pi, ps_name); 255 | } 256 | } 257 | dprintf(fd, "\n"); 258 | } 259 | 260 | si_regs[POWERCFG] &= ~SKMODE; // restore wrap mode 261 | si_tune(si_regs, seek); 262 | 263 | dprintf(fd, "%d stations found\n", nstations); 264 | return 0; 265 | } 266 | 267 | int cmd_spectrum(int fd, char *arg) 268 | { 269 | uint16_t si_regs[16]; 270 | uint8_t rssi_limit = RSSI_LIMIT; 271 | 272 | if (si_read_regs(si_regs) != 0) 273 | return CLI_ENODEV; 274 | int band = (si_regs[SYSCONF2] >> 6) & 0x03; 275 | int space = (si_regs[SYSCONF2] >> 4) & 0x03; 276 | int nchan = (si_band[band][1] - si_band[band][0]) / si_space[space]; 277 | 278 | if (arg && *arg) 279 | rssi_limit = (uint8_t)atoi(arg); 280 | 281 | dprintf(fd, "scanning, press any key to terminate...\n"); 282 | 283 | int stop = 0; 284 | for (int i = 0; i <= nchan && !stop; i++, is_stop(&stop)) { 285 | si_regs[CHANNEL] &= ~CHAN; 286 | si_regs[CHANNEL] |= i; 287 | si_regs[CHANNEL] |= TUNE; 288 | si_update(si_regs); 289 | rpi_delay_ms(10); 290 | 291 | while(1) { 292 | si_read_regs(si_regs); 293 | if (si_regs[STATUSRSSI] & STC) break; 294 | if (is_stop(&stop)) break; 295 | rpi_delay_ms(10); 296 | } 297 | si_regs[CHANNEL] &= ~TUNE; 298 | si_update(si_regs); 299 | while(i) { 300 | si_read_regs(si_regs); 301 | if(!(si_regs[STATUSRSSI] & STC)) break; 302 | if (is_stop(&stop)) break; 303 | rpi_delay_ms(10); 304 | } 305 | 306 | uint8_t rssi = si_regs[STATUSRSSI] & 0xFF; 307 | dprintf(fd, "%5d ", si_band[band][0] + i*si_space[space]); 308 | for(int si = 0; si < rssi; si++) 309 | dprintf(fd, "-"); 310 | dprintf(fd, " %d", rssi); 311 | 312 | int dt = 0; 313 | if (rssi > rssi_limit) { 314 | while(dt < 3000) { 315 | si_read_regs(si_regs); 316 | if (si_regs[STATUSRSSI] & STEREO) break; 317 | if (is_stop(&stop)) break; 318 | rpi_delay_ms(10); 319 | dt += 10; 320 | } 321 | } 322 | uint16_t st = si_regs[STATUSRSSI] & STEREO; 323 | 324 | if (st) { 325 | dprintf(fd, " ST"); 326 | if (rssi > rssi_limit) { 327 | int pi; 328 | char ps_name[16]; 329 | if ((pi = get_ps_si(ps_name, si_regs, 5000)) != -1) 330 | dprintf(fd, " %04X '%s'", pi, ps_name); 331 | } 332 | } 333 | dprintf(fd, "\n"); 334 | } 335 | return 0; 336 | } 337 | 338 | int cmd_seek(int fd, char *arg) 339 | { 340 | int dir = SEEK_UP; 341 | uint16_t si_regs[16]; 342 | 343 | if (cmd_is(arg, "up")) 344 | dir = SEEK_UP; 345 | else if (cmd_is(arg, "down")) 346 | dir = SEEK_DOWN; 347 | else { 348 | dprintf(fd, "wrong seeking direction\n"); 349 | return -1; 350 | } 351 | 352 | dprintf(fd, "seeking %s\n", arg); 353 | 354 | if (si_read_regs(si_regs) != 0) 355 | return CLI_ENODEV; 356 | int freq = si_seek(si_regs, dir); 357 | if (freq == 0) { 358 | dprintf(fd, "seek failed\n"); 359 | return -1; 360 | } 361 | dprintf(fd, "tuned to %u\n", freq); 362 | return 0; 363 | } 364 | 365 | int cmd_tune(int fd, char *arg) 366 | { 367 | unsigned freq = DEFAULT_STATION; 368 | uint16_t si_regs[16]; 369 | 370 | if (arg && *arg) { 371 | freq = strtol(arg, &arg, 10); 372 | if (*arg == '.') { 373 | unsigned decimal = strtol(arg + 1, NULL, 10); 374 | if (decimal > 100) 375 | decimal = 0; 376 | freq = freq * 100 + decimal; 377 | } 378 | } 379 | 380 | if (si_read_regs(si_regs) != 0) 381 | return CLI_ENODEV; 382 | si_tune(si_regs, freq); 383 | si_read_regs(si_regs); 384 | freq = si_get_freq(si_regs); 385 | if (freq) { 386 | dprintf(fd, "Tuned to %d.%02dMHz\n", freq/100, freq%100); 387 | si_dump(fd, si_regs, "Register map:\n", 16); 388 | return 0; 389 | } 390 | return -1; 391 | } 392 | 393 | int cmd_spacing(int fd, char *arg) 394 | { 395 | uint16_t spacing = 0; 396 | uint16_t si_regs[16]; 397 | if (si_read_regs(si_regs) != 0) 398 | return CLI_ENODEV; 399 | 400 | if (arg && *arg) { // do we have an extra parameter? 401 | spacing = atoi(arg); 402 | if (spacing == 200) spacing = 0; 403 | if (spacing == 100) spacing = 1; 404 | if (spacing == 50) spacing = 2; 405 | if (spacing > 2) { 406 | dprintf(fd, "Invalid spacing, use 200, 100, 50 kHz\n"); 407 | return -1; 408 | } 409 | si_regs[SYSCONF2] &= ~SPACING; 410 | si_regs[SYSCONF2] |= spacing << 4; 411 | si_update(si_regs); 412 | return 0; 413 | } 414 | 415 | spacing = (si_regs[SYSCONF2] & SPACING) >> 4; 416 | dprintf(fd, "spacing %d\n", si_space[spacing]); 417 | return 0; 418 | } 419 | 420 | // VT100 ESC codes for monitor mode 421 | static const char clr_all[] = { 27, '[', '2', 'J', '\0' }; // clear screen 422 | static const char clr_eol[] = { 27, '[', '0', 'K', '\0' }; // clear to EOL 423 | static const char go_top[] = { 27, '[', '1', ';', '1', 'H','\0' }; // go top left 424 | static const char cur_vis[] = { 27, '[', '?', '2', '5', 'h','\0' }; // show cursor 425 | static const char cur_hid[] = { 27, '[', '?', '2', '5', 'l','\0' }; // hide cursor 426 | static const char txt_nor[] = { 27, '[', '0', 'm', '\0' }; // normal text 427 | static const char txt_rev[] = { 27, '[', '7', 'm', '\0' }; // reverse text 428 | 429 | static void print_rds_hdr(int fd, rds_hdr_t *phdr) 430 | { 431 | dprintf(fd, "%04X %04X %04X %04X ", phdr->rds[0], phdr->rds[1], phdr->rds[2], phdr->rds[3]); 432 | dprintf(fd, "| GT %02d%c PTY %2d TP %d | ", phdr->gt, 'A' + phdr->ver, phdr->pty, phdr->tp); 433 | } 434 | 435 | /* default printer for 'rds log' command */ 436 | static void print_rds(int fd, rds_hdr_t *phdr, int log) 437 | { 438 | print_rds_hdr(fd, phdr); 439 | dprintf(fd, "%02X %04X %04X", phdr->rds[1] & 0x1F, phdr->rds[2], phdr->rds[3]); 440 | dprintf(fd, "%s\n", log ? "" : clr_eol); 441 | } 442 | 443 | static void cmd_monitor_si(int fd, uint16_t *regs, uint16_t pr_mask, uint32_t timeout, int log) 444 | { 445 | rds_gt00a_t rd0; 446 | rds_gt01a_t rd1; 447 | rds_gt02a_t rd2; 448 | rds_gt03a_t rd3; 449 | rds_gt04a_t rd4; 450 | rds_gt05a_t rd5; 451 | rds_gt08a_t rd8; 452 | rds_gt10a_t rd10; 453 | rds_gt14a_t rd14; 454 | rds_hdr_t rds[16]; 455 | 456 | memset(&rd0, 0, sizeof(rd0)); 457 | memset(&rd1, 0, sizeof(rd1)); 458 | memset(&rd2, 0, sizeof(rd2)); 459 | memset(&rd3, 0, sizeof(rd3)); 460 | memset(&rd5, 0, sizeof(rd5)); 461 | memset(&rd8, 0, sizeof(rd8)); 462 | memset(&rd10, 0, sizeof(rd10)); 463 | memset(&rd14, 0, sizeof(rd14)); 464 | 465 | uint16_t gt_mask = 0; // mask of groups detected 466 | uint16_t gta_mask = 0; // mask of A groups detected 467 | uint16_t gtb_mask = 0; // mask of B groups detected 468 | uint16_t rt_mask = 0; // mask of radiotext segments processed 469 | uint16_t ps_mask = 0; 470 | uint32_t endTime = 0; 471 | 472 | if (!log) { 473 | dprintf(fd, "%s%s%s", clr_all, go_top, cur_hid); 474 | dprintf(fd, "monitoring RDS, press any key to terminate...%s%s\n", clr_eol, txt_nor); 475 | } 476 | 477 | while(!is_stop(NULL)) { 478 | if (timeout && (rt_mask == 0xFFFF) && (ps_mask == 0x0F)) 479 | break; 480 | si_read_regs(regs); 481 | if (regs[STATUSRSSI] & RDSR) { 482 | rds_hdr_t hdr; 483 | uint16_t gtv = RDS_GET_GT(regs[RDSB]); 484 | uint8_t ver = (regs[RDSB] >> 11) & 0x01; 485 | uint8_t gt = (regs[RDSB] >> 12) & 0x0F; 486 | hdr.gt = gt; 487 | hdr.ver = ver; 488 | hdr.tp = (regs[RDSB] >> 10) & 0x01; 489 | hdr.pty = (regs[RDSB] >> 5) & 0x1F; 490 | memcpy(hdr.rds, ®s[RDSA], sizeof(hdr.rds)); 491 | memcpy(&rds[gt], &hdr, sizeof(hdr)); 492 | 493 | gt_mask |= _BM(gt); 494 | if (!ver) 495 | gta_mask |= _BM(gt); 496 | else 497 | gtb_mask |= _BM(gt); 498 | 499 | if (!log) { 500 | dprintf(fd, "%s%s", go_top, txt_rev); 501 | print_rds_hdr(fd, &hdr); 502 | dprintf(fd, "monitoring RDS, press any key to terminate...%s%s\n", clr_eol, txt_nor); 503 | } 504 | 505 | // 0A: basic tuning and switching information 506 | if (gtv == RDS_GT_00A) { 507 | memcpy(&rd0.hdr, &hdr, sizeof(hdr)); 508 | rds_parse_gt00a(®s[RDSA], &rd0); 509 | } 510 | // 1A: Program Item Number and slow labeling codes 511 | if (gtv == RDS_GT_01A) { 512 | memcpy(&rd1.hdr, &hdr, sizeof(hdr)); 513 | rds_parse_gt01a(®s[RDSA], &rd1); 514 | } 515 | // 2A: Radiotext 516 | if (gtv == RDS_GT_02A) { 517 | memcpy(&rd2.hdr, &hdr, sizeof(hdr)); 518 | rds_parse_gt02a(®s[RDSA], &rd2); 519 | } 520 | // 3A: AID for ODA 521 | if (gtv == RDS_GT_03A) { 522 | memcpy(&rd3.hdr, &hdr, sizeof(hdr)); 523 | rds_parse_gt03a(®s[RDSA], &rd3); 524 | } 525 | // 4A: Clock-time and date 526 | if (gtv == RDS_GT_04A) { 527 | memcpy(&rd4.hdr, &hdr, sizeof(hdr)); 528 | rds_parse_gt04a(®s[RDSA], &rd4); 529 | } 530 | // 5A: Transparent data channels or ODA 531 | if (gtv == RDS_GT_05A) { 532 | memcpy(&rd5.hdr, &hdr, sizeof(hdr)); 533 | rds_parse_gt05a(®s[RDSA], &rd5); 534 | } 535 | // 8A: Traffic Message Channel 536 | if (gtv == RDS_GT_08A) { 537 | memcpy(&rd8.hdr, &hdr, sizeof(hdr)); 538 | rds_parse_gt08a(®s[RDSA], &rd8); 539 | } 540 | // 10A: Program Type Name 541 | if (gtv == RDS_GT_10A) { 542 | memcpy(&rd10.hdr, &hdr, sizeof(hdr)); 543 | rds_parse_gt10a(®s[RDSA], &rd10); 544 | } 545 | // 14A: Enhanced Other Networks information 546 | if (gtv == RDS_GT_14A) { 547 | memcpy(&rd14.hdr, &hdr, sizeof(hdr)); 548 | rds_parse_gt14a(®s[RDSA], &rd14); 549 | } 550 | 551 | rpi_delay_ms(40); // Wait for the RDS bit to clear, from AN230 552 | endTime += 40; 553 | 554 | uint16_t mask = pr_mask & gta_mask; 555 | if (log) 556 | mask = pr_mask & _BM(gt); 557 | 558 | if (mask & _BM(0)) { 559 | ps_mask = rd0.valid; 560 | print_rds_hdr(fd, &rd0.hdr); 561 | dprintf(fd, "TA %d MS %c DI %X Ci %d PS '%s' AF %d %d (%d): ", 562 | rd0.ta, rd0.ms, rd0.di, rd0.ci, rd0.ps, regs[RDSC] &0xFF, regs[RDSC] >> 8, rd0.naf); 563 | for(int i = 0; rd0.af[i]; i++) 564 | dprintf(fd, "%d ", 8750 + rd0.af[i]*10); 565 | dprintf(fd, "%s\n", log ? "" : clr_eol); 566 | } 567 | 568 | if (mask & _BM(1)) { 569 | print_rds_hdr(fd, &rd1.hdr); 570 | dprintf(fd, "RPC %d LA %d VC %d SLC %03X ", 571 | rd1.rpc, rd1.la, rd1.vc, rd1.slc); 572 | if (rd1.pinc) 573 | dprintf(fd, " %02d %02d:%02d", rd1.pinc >> 11, 574 | (rd1.pinc >> 6) & 0x1F, rd1.pinc & 0x3F); 575 | dprintf(fd, "%s\n", log ? "" : clr_eol); 576 | } 577 | 578 | if (mask & _BM(2)) { 579 | rt_mask = rd2.valid; 580 | print_rds_hdr(fd, &rd2.hdr); 581 | dprintf(fd, "AB %c Si %2d ", 'A' + rd2.ab, rd2.si); 582 | dprintf(fd, "RT '%s'", rd2.rt); 583 | dprintf(fd, "%s\n", log ? "" : clr_eol); 584 | } 585 | 586 | if (mask & _BM(3)) { 587 | print_rds_hdr(fd, &rd3.hdr); 588 | dprintf(fd, "AGTC %d%c Msg %04X AID %04X VC %d ", 589 | rd3.agtc, rd3.ver + 'A', rd3.msg, rd3.aid, rd3.vc); 590 | if (rd3.vc == 0) { 591 | dprintf(fd, "LTN %d ", rd3.ltn); 592 | if (rd3.afi) dprintf(fd, "AFI "); 593 | if (rd3.m) dprintf(fd, "M "); 594 | if (rd3.i) dprintf(fd, "I "); 595 | if (rd3.n) dprintf(fd, "N "); 596 | if (rd3.r) dprintf(fd, "R "); 597 | if (rd3.u) dprintf(fd, "U "); 598 | } 599 | else { 600 | dprintf(fd, "SID %d ", rd3.sid); 601 | if (rd3.m) 602 | dprintf(fd, "G %d Ta %d Tw %d Td %d", rd3.g, rd3.ta, rd3.tw, rd3.td); 603 | } 604 | dprintf(fd, "%s\n", log ? "" : clr_eol); 605 | } 606 | 607 | if (mask & _BM(4)) { 608 | print_rds_hdr(fd, &rd4.hdr); 609 | dprintf(fd, "%d/%02d/%02d %02d:%02d", 610 | rd4.year, rd4.month, rd4.day, rd4.hour, rd4.minute); 611 | if (rd4.tz_hour == 0 && rd4.tz_half == 0) 612 | dprintf(fd, " UTC"); 613 | else 614 | dprintf(fd, " TZ%c%d.%d", rd4.ts_sign ? '-' : '+', 615 | rd4.tz_hour, rd4.tz_half); 616 | dprintf(fd, "%s\n", log ? "" : clr_eol); 617 | } 618 | 619 | if (mask & _BM(5)) { 620 | print_rds_hdr(fd, &rd5.hdr); 621 | for (uint8_t i = 0; i < 32; i++) { 622 | if (rd5.channel & (1u << i)) 623 | dprintf(fd, "TDS[%u] %04X %04X ", 624 | i, rd5.tds[i][0], rd5.tds[i][1]); 625 | } 626 | dprintf(fd, "%s\n", log ? "" : clr_eol); 627 | } 628 | 629 | if (mask & _BM(6)) 630 | print_rds(fd, &rds[6], log); 631 | 632 | if (mask & _BM(7)) 633 | print_rds(fd, &rds[7], log); 634 | 635 | if (mask & _BM(8)) { 636 | // check if 8A is Alert-C 637 | print_rds_hdr(fd, &rd8.hdr); 638 | if (rd3.agtc == 8 && rd3.ver == 0 && rd3.aid == 0xCD46) { 639 | dprintf(fd, "S%d G%d CI%d ", rd8.x4, rd8.x3, rd8.x2); 640 | if (rd8.x3) 641 | dprintf(fd, "D%d DIR%d Ext %d Eve %d Loc %04X", 642 | rd8.d, rd8.dir, rd8.ext, rd8.eve, rd8.loc); 643 | else 644 | dprintf(fd, "Y %04X Loc %04X", rd8.y, rd8.loc); 645 | } 646 | else 647 | dprintf(fd, "X4 %d VC %d", rd8.x4, rd8.vc); 648 | dprintf(fd, "%s\n", log ? "" : clr_eol); 649 | } 650 | 651 | if (mask & _BM(9)) 652 | print_rds(fd, &rds[9], log); 653 | 654 | if (mask & _BM(10)) { 655 | print_rds_hdr(fd, &rd10.hdr); 656 | dprintf(fd, "AB %c Ci %d PTYN '%s'", 'A' + rd10.ab, rd10.ci, rd10.ps); 657 | dprintf(fd, "%s\n", log ? "" : clr_eol); 658 | } 659 | 660 | if (mask & _BM(11)) 661 | print_rds(fd, &rds[11], log); 662 | 663 | if (mask & _BM(12)) 664 | print_rds(fd, &rds[12], log); 665 | 666 | if (mask & _BM(13)) 667 | print_rds(fd, &rds[13], log); 668 | 669 | if (mask & _BM(14)) { 670 | print_rds_hdr(fd, &rd14.hdr); 671 | dprintf(fd, "TP %d VC %2d ", rd14.tp_on, rd14.variant); 672 | dprintf(fd, "I %04X ", rd14.info); 673 | dprintf(fd, "PI %04X ", rd14.pi_on); 674 | dprintf(fd, "PS '%s' ", rd14.ps); 675 | if (rd14.avc & _BM(13)) 676 | dprintf(fd, "PTY %2d TA %d ", rd14.pty >> 11, rd14.pty & 0x01); 677 | if (rd14.avc & _BM(14)) 678 | dprintf(fd, "PIN %04X", rd14.pin); 679 | dprintf(fd, "%s\n", log ? "" : clr_eol); 680 | } 681 | 682 | if (mask & _BM(15)) 683 | print_rds(fd, &rds[15], log); 684 | } 685 | else { 686 | rpi_delay_ms(30); 687 | endTime += 30; 688 | } 689 | 690 | if (timeout && (endTime >= timeout)) 691 | break; 692 | } 693 | 694 | int freq = si_get_freq(regs); 695 | dprintf(fd, "\nScanned %d.%02d ", freq/100, freq%100); 696 | if (rd0.valid == 0x0F) 697 | dprintf(fd, "'%s' ", rd0.ps); 698 | dprintf(fd, "for %d ms\n", endTime); 699 | if (rd2.valid) 700 | dprintf(fd, "Radiotext: '%s'\n", rd2.rt); 701 | if (!gt_mask) 702 | dprintf(fd, "no RDS detected\n"); 703 | else { 704 | dprintf(fd, "Active groups %04X:\n", gt_mask); 705 | for(int i = 0; i < 16; i++) { 706 | if (gta_mask & (1 << i)) 707 | dprintf(fd, " %02dA %s\n", i, rds_gt_name(i, 0)); 708 | if (gtb_mask & (1 << i)) 709 | dprintf(fd, " %02dB %s \n", i, rds_gt_name(i, 1)); 710 | } 711 | dprintf(fd, "\n"); 712 | } 713 | if (!log) 714 | dprintf(fd, "%s", cur_vis); 715 | } 716 | 717 | int cmd_monitor(int fd, char *arg) 718 | { 719 | int log = 0; 720 | uint16_t si_regs[16]; 721 | uint16_t gtmask = 0xFFFF; 722 | uint32_t timeout = DEFAULT_RDS_SCAN_TIMEOUT; 723 | 724 | si_read_regs(si_regs); 725 | 726 | if (cmd_is(arg, "on")) { 727 | si_set_rdsprf(si_regs, 1); 728 | si_update(si_regs); 729 | si_read_regs(si_regs); 730 | dprintf(fd, "RDSPRF set to %s\n", is_on(si_regs[SYSCONF3] & RDSPRF)); 731 | si_dump(fd, si_regs, "\nRegister map\n", 16); 732 | return 0; 733 | } 734 | if (cmd_is(arg, "off")) { 735 | si_set_rdsprf(si_regs, 0); 736 | si_update(si_regs); 737 | si_read_regs(si_regs); 738 | dprintf(fd, "RDSPRF set to %s\n", is_on(si_regs[SYSCONF3] & RDSPRF)); 739 | si_dump(fd, si_regs, "\nRegister map\n", 16); 740 | return 0; 741 | } 742 | if (cmd_is(arg, "verbose")) { 743 | si_read_regs(si_regs); 744 | si_regs[POWERCFG] ^= RDSM; 745 | si_update(si_regs); 746 | si_read_regs(si_regs); 747 | dprintf(fd, "RDSM set to %s\n", is_on(si_regs[POWERCFG] & RDSM)); 748 | si_dump(fd, si_regs, "\nRegister map\n", 16); 749 | return 0; 750 | } 751 | 752 | char *val; 753 | if (cmd_arg(arg, "gt", &val)) { 754 | gtmask = 0; 755 | while(*val && isdigit(*val)) { 756 | uint16_t gt = strtoul(val, &val, 10); 757 | gtmask |= (1 << gt) & 0xFFFF; 758 | while(*val && (*val == ',' || *val <= ' ')) 759 | val++; 760 | } 761 | arg = val; 762 | } 763 | 764 | if (cmd_arg(arg, "time", &val)) { 765 | timeout = strtoul(val, &val, 10)*1000; // argument - timeout in seconds 766 | arg = val; 767 | } 768 | 769 | if (cmd_is(arg, "log")) 770 | log = 1; 771 | 772 | cmd_monitor_si(fd, si_regs, gtmask, timeout, log); 773 | return 0; 774 | } 775 | 776 | int cmd_volume(int fd, char *arg) 777 | { 778 | uint8_t volume; 779 | uint16_t si_regs[16]; 780 | si_read_regs(si_regs); 781 | 782 | volume = si_regs[SYSCONF2] & VOLUME; 783 | if (arg && *arg) { 784 | volume = atoi(arg); 785 | si_set_volume(si_regs, volume); 786 | return 0; 787 | } 788 | 789 | dprintf(fd, "volume %d\n", volume); 790 | return 0; 791 | } 792 | 793 | int cmd_set(int fd, char *arg) 794 | { 795 | uint16_t val; 796 | const char *pval; 797 | char buf[32]; 798 | 799 | if (arg == NULL || *arg == '\0') 800 | return -1; 801 | pval = arg; 802 | int i; 803 | for(i = 0; i < 31; i++) { 804 | if (*pval == '=') 805 | break; 806 | buf[i] = toupper(*pval++); 807 | } 808 | buf[i] = '\0'; 809 | if (*pval != '=') 810 | return -1; 811 | 812 | val = (uint16_t)atoi(++pval); 813 | 814 | uint16_t regs[16]; 815 | si_read_regs(regs); 816 | if (si_set_state(regs, buf, val) != -1) { 817 | si_update(regs); 818 | si_dump(fd, regs, arg, 16); 819 | return 0; 820 | } 821 | return -1; 822 | } 823 | --------------------------------------------------------------------------------