
PSoC3 + Si4735でデジタルAM/FMラジオ
投稿日 2014/05/13
Silicon Laboratoriesのデジタル・ラジオIC Si4735搭載モジュールを入手したので、さっそくデジタル・ラジオを作ってみました。

写真1:PSoC3 + Si4735 AM/FMラジオ FM J-WAVE 81.3MHz受信中 音量は47
Si4735はマイコンからI2Cインターフェースで制御します。
今回マイコンにはCypressのPSoC3を使用しました。CQ出版社から発行された「シリーズ最強! PSoCボード+デバッグボード」付録のボードです。CPUはCY8C3866LTI-030です。
写真のように、以前I2C LCD, ボタン類、ロータリエンコーダのテストで試用したベースを使い、そこにSi4735をI2Cでつなぎました。
シールバッテリ簡易充電回路

写真2:使用したPSoCコンポーネント
PSoC3のコンポーネントは以下を使用しました。
[]内はコンポーネント名とピン番号です。
I2C MASTER [I2C]
I2C SCL用のDigital OUT Pin [SCL_1 P12-4]
I2C SDA用のDigital OUT Pin [SDA_1 P12-5]
SI4735リセット用Digital OUTPUT Pin [RST P2-1]
REDボタン用Digital INPUT Pin(AM/FM切り替え) [RED_BTN P12-3]
BLUEボタン用Digital INPUT Pin(選局-前) [BLUE_BTN P12-2]
YELLOWボタン用Digital INPUT Pin(選局-次) [YELLOW_BTN P2-7]
ロータリエンコーダAチャネル用Digital INPUT Pin [A_Ch P12-6]
ロータリエンコーダBチャネル用Digital INPUT Pin [B_Ch P12-7]
チャタリング防止用クロック源 [Clock_1]
クロック割り込み用Interupt [isr_Clock_1ms]
選局と音量値を記憶するためのEEPROM [EEPROM]
写真1にある、緑と白のボタンは未使用です。
I2C LCDは、3.3V I2C LCD ACM1602NI です。
I2Cインターフェースの信号線2本 SCLとSDAをつないでおきます。
SCLとSDAのプルアップ抵抗はSi4735モジュール搭載のものは使わず、2.2KΩを外付けとしました。
ロータリエンコーダは音量調整に使用します。
A_Ch, B_Chをつなぎ、ピン・コンポーネントでPull UPしておきます。
チャタリングがあるので、プログラムの割り込み処理ルーチンで見ています。
イヤホンのGND, L, Rの信号線をソケットにつないでおきます。
イヤホンはインピーダンスの関係で、マッチング範囲にないと音が歪みますので、ヘッドホンや、アンプ付きスピーカなどを試してみる必要があります。今回はCREATIVEのT12 Wirelessを有線で使用しました。きれいに鳴っています。
電源はマイコン、Si4735モジュール、LCDともに3.3Vですので便利です。
今回はUSBの書き込み器から供給しています。
アンテナは、FMアンテナに適当なワイヤをつなぎました。
AMのアンテナは何もつないでいませんので少し感度が弱いのと、混信があります。
混信はNHK第一に第二がすこしバックで聞こえます。これの対策はバーアンテナなどの同調回路を外付けしないとなんともなりません。へたにアンテナだけ長いのを付けると悪化します。
電源とグランド間に適当にパスコンを配置しておきます。
ハードの接続はこれで終了です。
次に、プログラミングですが、スペックをすべて理解するのは大変ですので、ネットの情報を参考にしました。
とくにArduinoのライブラリが参考になるのですが、ごちゃごちゃしてますので、自分なりに実験して必要な部分のみを、自分の好みでインプリメントし後述のコードに収まりました。
このラジオの仕様
AM/FMボタン切り替え
選局はプログラムにあらかじめ登録してあるものをボタンで切り替え
音量は0から63を1ステップで調整
電源を切っても、以前の選局と音量を記憶
オーディオ出力はアナログ 一般のヘッドホンやアンプ付きスピーカが使えます
Si4735は、最低限以下の設定を行えば、取り合えず放送を聴くことができます。
①GPIOをピン(RST)使用し、Si4735のRESET(RS)ピンをLOWからHIGHにしてRESETを解除する。
このときI2Cモードで制御することが認識されます。
②AMまたはFMラジオとしてパワーオンを行う。パワーオンと言いますが、これは電源を投入しているのではなく、パワーオン・コマンドです。これでSi4735がAMまたはFMラジオとして起動されます。
③一旦ミュート(オーディオ出力オフ)しておき、受信周波数と音量ボリュームを設定してからミュートを解除(オーディオ出力オン)します。
これらの設定で一応受信可能となります。
RESETピン(負論理)をLOW(true)からHIGHにしたときの立ち上がりエッジで、GPIO1ピンがHIGH、GPIO2ピンがLOWであれば、I2Cインターフェース(2-Wire)モードと認識されます。GPIO1はVccにプルアップ、GPIO2はGNDにプルダウンされているので、解放状態ですでにHIGH, LOWになっています。
今回は実験なので、受信周波数は数局をボタンで切り替えるようにしましたが、範囲を指定して自動選局させることもできます。
また今回はやっていませんが、オーディオ出力をデジタルモードにすることもできます。
LCDには、現在のAM/FMの別、局名、周波数、音量値を表示します。
受信は快適です。十分実用範囲のラジオが完成します。勿論FMはステレオです。
こんな小型のチップに受信機機能が収まっているとは、信じられないくらいです。
プログラミングにおいては、各所にディレイ(CyDelay(n))を適当な値で挿入しておかないと、うまく動かないことがありますので、少しカットアンドトライが必要でした。(もっと適値があるかも知れませんが)
問題は、電源オン後や、AMとFMを切り替える時にパワーオンコマンドを実行しますが、そのときバチッと音がします。気になりますが対処方法がわからないのでそのままにしてあります。
開発環境: Cypress Semiconductor PSoC Creater 3.0
ソース・コード
雑なコーディングですが、機能的には実用範囲と思います。
ラジオ局の周波数は、関東地方用で私の好みですので、地域とお好みによって番組表などを見て変更する必要があります。
ブログの仕様により連続した半角スペースが抜けている箇所がありますので、文字表示等では調整が必要です。
#include <device.h>
#include <stdio.h>
#define LCD_SLAVE_ADDRESS 0x50 //Slave Address Right Justified
#define Ri4735_SLAVE_ADDRESS 0x11
#define ON 1
#define OFF 0
#define FM 0
#define AM 1
CY_ISR_PROTO(Clock_1ms_Interrupt);
int8 mode;
int8 vol;
int vol_1;
float FM_freq;
uint16 AM_freq;
int8 AM_Station_No;
int8 FM_Station_No;
int8 EEPROM_data[16];
uint8 int_cnt = 0;
uint8 now = 0;
uint8 prev = 0;
int8 change = 0;
char string[20];
struct AM_Radio_Table{
char name[14];
int freq;
};
struct AM_Radio_Table AM_Radio_Stations[10] = {
{"NHK 1 ", 594},
{"NHK 2 ", 693},
{"AFN ", 810},
{"TBS ", 954},
{"BUNKA ", 1134},
{"NIPPON ", 1242},
{"", 0},
{"", 0},
{"", 0},
{"", 0}
};
struct FM_Radio_Table{
char name[14];
float freq;
};
struct FM_Radio_Table FM_Radio_Stations[13] = {
{"Inter FM ", 76.1},
{"OPEN UNIV. ", 78.8},
{"NACK 5 ", 79.5},
{"TOKYO FM ", 80.0},
{"J-WAVE ", 81.3},
{"NHK FM GUNMA ", 81.6},
{"NHK FM TOKYO ", 82.5},
{"YOKOHAMA FM ", 84.7},
{"GUNMA FM ", 86.3},
{"", 0},
{"", 0},
{"", 0},
{"", 0}
};
CY_ISR(Clock_1ms_Interrupt){
if(int_cnt == 3) { //make 3ms
int_cnt = 0;
now = (A_Ch_Read() << 1) + B_Ch_Read();
if(prev != now) {
if (((prev << 1) + now) & 0x02) {
if(now == 0x03){ //right, lock position
vol++;
if(vol > 63) vol = 63;
change = 1;
}
} else {
if(now == 0x03){ //left, lock position
vol--;
if(vol < 0) vol = 0;
change = -1;
}
}
prev = now;
}
} else
int_cnt++;
}
void I2C_LCD_WriteCommand(char8 command){
I2C_MasterSendStart(LCD_SLAVE_ADDRESS, 0);
CyDelay(1);
I2C_MasterWriteByte(0x00);
CyDelay(1);
I2C_MasterWriteByte(command);
CyDelay(1);
I2C_MasterSendStop();
CyDelay(1);
}
void I2C_LCD_WriteChar(char8 databyte){
I2C_MasterSendStart(LCD_SLAVE_ADDRESS, 0);
CyDelay(1);
I2C_MasterWriteByte(0x80);
CyDelay(1);
I2C_MasterWriteByte(databyte);
CyDelay(1);
I2C_MasterSendStop();
CyDelay(1);
}
void I2C_LCD_SetPosition(uint8 col, uint8 row){
uint8 pos;
row != 0? (pos = 0xC0 + col) : (pos = 0x80 + col);
I2C_LCD_WriteCommand(pos);
CyDelay(1);
}
void I2C_LCD_WriteString(uint8 col, uint8 row, uint8 *str){
I2C_LCD_SetPosition(col, row);
I2C_MasterSendStart(LCD_SLAVE_ADDRESS, 0);
while(*str){
I2C_MasterWriteByte(0x80);
CyDelay(1);
I2C_MasterWriteByte(*str++);
CyDelay(1);
}
I2C_MasterSendStop();
CyDelay(1);
}
void I2C_LCD_SetCursor(uint8 col, uint8 row){
I2C_LCD_SetPosition(col, row);
I2C_LCD_WriteCommand(0x0E);
}
void I2C_LCD_CursorShiftRight(uint8 cols){
uint8 i;
for(i = 0; i < cols; i++){
I2C_LCD_WriteCommand(0x14);
CyDelay(1);
}
}
void I2C_LCD_CursorShiftLeft(uint8 cols){
uint8 i;
for(i = 0; i < cols; i++){
I2C_LCD_WriteCommand(0x10);
CyDelay(1);
}
}
void I2C_LCD_SetBlink(uint8 col, uint8 row){
I2C_LCD_SetPosition(col, row);
I2C_LCD_WriteCommand(0x0D);
}
void I2C_LCD_ClearAll(void){
I2C_LCD_WriteCommand(0x01);
}
void I2C_LCD_ClearLine1(void){
I2C_LCD_WriteString(0, 0, " " );
}
void I2C_LCD_ClearLine2(void){
I2C_LCD_WriteString(0, 1, " " );
}
void I2C_LCD_Init(void){
I2C_LCD_WriteCommand(0x01);
CyDelay(3);
I2C_LCD_WriteCommand(0x38);
CyDelay(1);
I2C_LCD_WriteCommand(0x0c);
CyDelay(1);
I2C_LCD_WriteCommand(0x06);
CyDelay(1);
}
void Si4735_Powerdown(void){
I2C_MasterSendStart(Ri4735_SLAVE_ADDRESS, 0);
CyDelay(1);
I2C_MasterWriteByte(0x11);
CyDelay(1);
I2C_MasterSendStop();
CyDelay(50);
}
void Si4735_FM_poweron(void) {
I2C_MasterSendStart(Ri4735_SLAVE_ADDRESS, 0);
CyDelay(1);
I2C_MasterWriteByte(0x01);
CyDelay(1);
I2C_MasterWriteByte(0x10);
CyDelay(1);
I2C_MasterWriteByte(0x05);
CyDelay(1);
I2C_MasterSendStop();
CyDelay(50);
}
void Si4735_AM_poweron(void) {
I2C_MasterSendStart(Ri4735_SLAVE_ADDRESS, 0);
CyDelay(1);
I2C_MasterWriteByte(0x01);
CyDelay(1);
I2C_MasterWriteByte(0x11);
CyDelay(1);
I2C_MasterWriteByte(0x05);
CyDelay(1);
I2C_MasterSendStop();
CyDelay(50);
}
void Si4735_FM_Setfreq(int freq) {
char hbyte, lbyte;
hbyte = (freq >> 8) & 0x00FF;
lbyte = freq & 0x00FF;
I2C_MasterSendStart(Ri4735_SLAVE_ADDRESS, 0);
CyDelay(1);
I2C_MasterWriteByte(0x20);
CyDelay(1);
I2C_MasterWriteByte(0x00);
CyDelay(1);
I2C_MasterWriteByte(hbyte);
CyDelay(1);
I2C_MasterWriteByte(lbyte);
CyDelay(1);
I2C_MasterSendStop();
CyDelay(10);
}
void Si4735_AM_Setfreq(int freq) {
char hbyte, lbyte;
hbyte = (freq >> 8) & 0x00FF;
lbyte = freq & 0x00FF;
I2C_MasterSendStart(Ri4735_SLAVE_ADDRESS, 0);
CyDelay(1);
I2C_MasterWriteByte(0x40);
CyDelay(1);
I2C_MasterWriteByte(0x00);
CyDelay(1);
I2C_MasterWriteByte(hbyte);
CyDelay(1);
I2C_MasterWriteByte(lbyte);
CyDelay(1);
I2C_MasterSendStop();
CyDelay(10);
}
void Si4735_SetProperty(unsigned char arg1, unsigned char arg2, unsigned char arg3, unsigned char arg4) {
I2C_MasterSendStart(Ri4735_SLAVE_ADDRESS, 0);
CyDelay(1);
I2C_MasterWriteByte(0x12);
CyDelay(1);
I2C_MasterWriteByte(0x00);
CyDelay(1);
I2C_MasterWriteByte(arg1);
CyDelay(1);
I2C_MasterWriteByte(arg2);
CyDelay(1);
I2C_MasterWriteByte(arg3);
CyDelay(1);
I2C_MasterWriteByte(arg4);
CyDelay(1);
I2C_MasterSendStop();
CyDelay(10);
}
void Si4735_mute(unsigned char mute_mode){
if(mute_mode == ON)
Si4735_SetProperty(0x40, 0x01, 0x00, 0x03);
else
Si4735_SetProperty(0x40, 0x01, 0x00, 0x00);
}
void Si4735_setVolume(int8 vol_level){
Si4735_SetProperty(0x40, 0x00, 0x00, vol_level);
CyDelay(50);
}
void AM_Display(void){
sprintf(string, "AM %s", AM_Radio_Stations[AM_Station_No].name);
I2C_LCD_WriteString(0, 0, string);
sprintf(string, "%4dKHz Vol %2d", AM_Radio_Stations[AM_Station_No].freq, (int)vol);
I2C_LCD_WriteString(0, 1, string);
}
void FM_Display(void){
sprintf(string, "FM %s", FM_Radio_Stations[FM_Station_No].name);
I2C_LCD_WriteString(0, 0, string);
sprintf(string, "%4.1fMHz Vol %2d", FM_Radio_Stations[FM_Station_No].freq, (int)vol);
I2C_LCD_WriteString(0, 1, string);
}
void AM_Mode_Setting(void){
Si4735_AM_poweron();
CyDelay(200);
Si4735_mute(ON);
AM_freq = AM_Radio_Stations[AM_Station_No].freq;
Si4735_AM_Setfreq(AM_freq);
Si4735_setVolume(vol);
Si4735_mute(OFF);
AM_Display();
}
void FM_Mode_Setting(void){
Si4735_FM_poweron();
CyDelay(200);
Si4735_mute(ON);
FM_freq = FM_Radio_Stations[FM_Station_No].freq;
Si4735_FM_Setfreq((uint16)(FM_freq * 100));
Si4735_setVolume(vol);
Si4735_mute(OFF);
FM_Display();
}
void Save_Data(void){
EEPROM_data[0] = mode;
EEPROM_data[1] = AM_Station_No;
EEPROM_data[2] = FM_Station_No;
EEPROM_data[3] = vol;
CYGlobalIntDisable;
EEPROM_Write(EEPROM_data, 0);
CYGlobalIntEnable
}
void Restore_Data(void){
int i;
for(i = 0; i < 4; i++){
EEPROM_data[i] = CY_GET_REG8(CYDEV_EE_BASE + i);
}
mode = EEPROM_data[0];
AM_Station_No = EEPROM_data[1];
FM_Station_No = EEPROM_data[2];
vol = EEPROM_data[3];
}
int main(){
change = 0;
CyGlobalIntEnable;
isr_Clock_1ms_StartEx(Clock_1ms_Interrupt);
EEPROM_Start();
I2C_Start();
I2C_LCD_Init();
Restore_Data();
I2C_LCD_WriteString(0, 0, " Si4735 Radio " );
I2C_LCD_WriteString(0, 1, " by JF1VRR " );
CyDelay(1000);
I2C_LCD_ClearAll();
RST_Write(1); //Reset Si4735 (I2C mode)
CyDelay(10);
if(mode == AM) {
AM_Mode_Setting();
} else {
FM_Mode_Setting();
}
while(1){
if(change == 1 || change == -1){
vol_1 = vol;
sprintf(string, "%2d", vol_1);
I2C_LCD_WriteString(12, 1, string);
Si4735_setVolume(vol);
Save_Data();
change = 0;
}
if(RED_BTN_Read() == 0){
if(mode == FM){
mode = AM;
Si4735_Powerdown();
AM_Mode_Setting();
} else {
mode = FM;
Si4735_Powerdown();
FM_Mode_Setting();
}
Save_Data();
}
if(BLUE_BTN_Read() == 0){
if(mode == AM){
AM_Station_No--;
if(AM_Station_No < 0) AM_Station_No = 0;
AM_Mode_Setting();
} else {
FM_Station_No--;
if(FM_Station_No < 0) FM_Station_No = 0;
FM_Mode_Setting();
}
Save_Data();
}
if(YELLOW_BTN_Read() == 0){
if(mode == AM){
AM_Station_No++;
if(AM_Station_No > 5) AM_Station_No = 5;
AM_Mode_Setting();
} else {
FM_Station_No++;
if(FM_Station_No > 8) FM_Station_No = 8;
FM_Mode_Setting();
}
Save_Data();
}
}
}
参考:
PSoC3 I2C LCDをつないでみる
PSoC3 ロータリ・エンコーダをつなぐ
(JF1VRR)