├── .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 | 
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 |
--------------------------------------------------------------------------------