Radio Data System の パケットのコードを読む

備忘録。
RDS どころか C・C++ も初心者なので、間違いなどがあったら、指摘くださると嬉しいです。

## Radio Data System とは？
Radio Data System (以下、RDS) とは EU製のラジオの規格。

https://ja.wikipedia.org/wiki/Radio_Data_System

仕様書は、

http://www.interactive-radio-system.com/docs/EN50067_RDS_Standard.pdf

などにあります。
ここでは、この RDS のパケットの解析について、最初は分からなかったので、まとめます。今回の RDS のコードは、Automotive Grade Linux (以下、AGL) という コネクテッドカーに活用するオープンプラットフォームを開発する OSSのプロジェクトのコードから拝借しました。

https://jidounten-lab.com/u_agl-connected-matome

## AGL Radio 
レポジトリは、gitlab にあります。

https://gitlab.com/automotivegradelinux/apps/agl-service-radio/-/tree/master

今回の RDS は、tef665x というハードウェア上で i2c 通信で動くものになっています。

https://www.nxp.com/products/audio-and-radio/analog-digital-radio-and-audio/dsp-based-radio-tuner-one-chip:TEF665X

デバイスは違いますが、i2c 通信は、

https://www.si-linux.co.jp/catwiki/index.php?I2Cデバイスの扱い方

などが分かりやすいと思います。

実際の tef665x のコードは

https://gitlab.com/automotivegradelinux/apps/agl-service-radio/-/blob/master/src/radio_impl_tef665x.c

にあります。
長いコードですが、ここでは主に パケットを解析している部分を読んでみます。

簡単にコードの流れを追うと、

```c: radio_impl_tef665x.c の 1593行目
static int i2c_init(const char *i2c, int state, uint32_t *i2c_file_desc)
{
    int fd = 0, t;

	if(state == _open)
	{
		fd = open(i2c, O_RDWR);

		if(fd < 0)
		{
			_debug("could not open %s", i2c);
			return fd;
		}

		t = ioctl(fd, I2C_SLAVE, I2C_ADDRESS);
		if (t < 0)
		{
			_debug("could not set up slave ", 0);
			return t;
		}
		*i2c_file_desc = fd;
	}
	else
	{
		close(*i2c_file_desc);
	}

	return 0;
}
```

で、i2c 通信の初期化を行い、`fd` つまり 引数の `i2c_file_desc` で 通信を行っていきます。その一例が、下のようなものになります。

```c: radio_impl_tef665x.c の 225行目
static int tef665x_get_cmd(int i2c_file_desc, TEF665x_MODULE module, uint8_t cmd, uint8_t *receive, int len)
{
	uint8_t temp;
	uint8_t buf[3];
	int ret;

	buf[0]= module;		//module,	FM/AM/APP
	buf[1]= cmd;		//cmd,		1,2,10,...
	buf[2]= 1;	        //index, 	always 1

	write(i2c_file_desc, buf, 3);

	ret = read(i2c_file_desc, receive, len);
	temp = (ret == len) ? 1 : 0;
	_debug("return value", temp);
	if(temp==0)
		fprintf(stderr, "Error Number: %d: %s",errno,strerror(errno));
	return temp;
}
```

`i2c_file_desc` に write で書き込んで、それを read で読み出しています。
他にもあるんですが、今回は 本題のパケット部分を読んでみましょう。

パケット解析部分は、

https://gitlab.com/automotivegradelinux/apps/agl-service-radio/-/blob/master/src/radio_impl_tef665x.c#L1054

からの関数にあります。
少し長いのですが、簡単に説明します。また、細かい仕様は

http://www.interactive-radio-system.com/docs/EN50067_RDS_Standard.pdf

の 15ページ目以降を参考にしました。

## RDS のパケット部分を読んでみる

RDS のパケットは、以下のようなフォーマットで出来ています。

![スクリーンショット 2023-02-11 13.33.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92647/d72f6441-c297-4b7b-e76b-ea548a1c89c9.png)

PI Code というのは通信を識別するコードで、次の CheckWord + offset A はコードではハードウェアが吸収していて出てこないので飛ばして、その次の Group type code が パケットの種類を示していて重要な要素です。
この Group type code は `0A・0B・1A・1B ... 15A・15B` までがあって、それぞれで例えば、時刻の情報 や Alternative Frequency (周波数が弱くなった時に代替の周波数に切り替える仕様・以下 AF)の情報 や ラジオのテキスト情報 の受信の役割が割り当てられています。

では、まず `0A・0B` から見てみましょう。
仕様の絵は、以下の通りになります。
![スクリーンショット 2023-02-11 13.45.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92647/0cebcf91-88fd-fbfd-3af9-fc632e431f42.png)
コードは下のようになります。

```c: radio_impl_tef665x.c の 1120行目
			//Group Type 0A
			if (GType==0)
			{
				Extract_Alt_Freqs(raw_data,Rds_STU);
			}

			//Group Type 0B
			else
			{
				Rds_STU->PICode=Convert8bto16b(&raw_data[6]);
			} 
```

0A なら AF の情報を、0B なら PI Code を取得しているのが分かると思います。

次に、1A（コードでは 1Bは省略されている） を読んでみましょう。
![スクリーンショット 2023-02-11 13.50.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92647/22e8950b-7348-4eac-1713-20a7c776e27b.png)
コードは
```c:
		//Group Type 1A
		if(GTypeVer == 0)
		{
			if(Error_D == 0)
			{
				Rds_STU->Day  = raw_data[8] >> 3;
				Rds_STU->Hour = raw_data[8] >> 3;
				Rds_STU->Min  = ((raw_data[8] & 7) << 2) | (raw_data[9] >> 6) ;
			}
		}
```
になっていて、時刻の情報を取得しています。Show Labelling codes は飛ばして、Program item number code だけの情報ですが、分かりやすいですね。

このような要領で読んでいけば、残りの 2A・2B・4A・4B も読みやすいと思います（少し 0A〜1Aより長いですが）。