ラジオの製作(IC制御)その4

M6951はどうだ

前回までで、Si4730、BK1088とことごとくAM受信できず。
alasixosaka.hatenablog.com
3度目の正直とばかり、M6951を購入した。
結論から言うと、今回はなんとかAMの受信に成功しました。
今度は失敗できないぞということで、ちょっとコストはかかるが、M6951モジュールを使ったラジオキットを購入。
www.aitendo.com
モジュール自体は、ピンソケットで取り付ける形なので、モジュール単体でも検証も可能だし、キットだと安心というのが購入の理由でした。
しかし、キットの方はうまく動かない。液晶は一応表示されるが、なんか周波数が変だし、音も全くならず、ノイズすら聞こえない。
コントロールに使っているチップが、韓国製のMC96F6432Qとかいう馴染みのないチップでデバッグもできないし、半田付けに関してはしっかりチェックしたがブリッジや半田付け不良の部分は見当たらないので、動かない理由がわからない。せっかくのキットだったが、あきらめてモジュールで動かすことにした。
まずは、モジュールにPIC16F1938を接続し、局は決め打ちでFM大阪を受信するプログラムを走らせた。すると感度はやや低いもののちゃんと受信する。
次に、AMアンテナを繋いで、ABCを受信するようにプログラムを変えてみた。しかし、ノイズのみで局は受信しない。
Si4730の例もあるので、ちょっとずれていたりしたら聞こえないのかなと思い、I2Cの液晶を繋いで、Seekするプログラムを作ってみた。
すると、NHKの第2放送だけガンガン受信する。アンテナの向きを変えてみると、NHKの第1放送も受信するようになった。しかし、民放は依然として全く受信できない。
この時のアンテナは、バーの長さが8cmのやや大きめのものを使った。
www.aitendo.com
それに、バーアンテナホルダを使ってブレッドボードに接続した。
www.aitendo.com
物は試しにアンテナをキットについていたのに繋ぎ変えると、感度が上がった。
形状からしてBT670CGとかいうやつのようだ。
www.aitendo.com
バーの長さが5cmとやや小ぶりなアンテナだが、こっちのほうが感度が良いとは。意外な結果だった。
曲がりなりにも、AMを受信できたので、窓際に持って行って試してみた。すると、感度が格段に良くなり、ABC、MBSOBCの民放各局も受信できるようになった。
それにしても、NHKはさすがですね。民放が受信できない状況でもちゃんと聞こえる。特に第2放送はアンテナなしでもかすかに聞こえるくらい。これからはテストはまずNHK第2放送が聞こえるかどうかから始めることにしよう。
回路図はこんな感じでテストしました。液晶表示器はI2C制御のAQM1602というのを使っています。KiCadのライブラリに同じのが無かったので、回路図上では適当なのを代わりに使っています。

f:id:alasixOsaka:20200801134928j:plain
M6951のテスト回路

プログラムリストは下記の通りです。
とりあえず、動けばいいやということで作ったので、結構適当です(特に表示の部分が)。一応動作説明をしておくと、SW1を押すと、UP方向にSeekします。M6951の場合、Seekして周波数の上限まで行くと、自動的に下限値からSeekを継続します。局を捕まえると、Seekがストップして、1行目がTunedという表示になり、2行目に周波数を表示します。KHzとかMHzとか単位を表示しない数字だけのそっけない表示ですが。
SW2を押すと、AMとFMを切り替えます。こちらも、切り替えたときにAMとかFMとか表示されますが、Seekするとクリアされてしまいます。
関数の部分とかは、参考にさせてもらったサイトをほぼそのまま使っています。

/*
 * File:   main.c
 * Author: hideaki3
 *
 * Created on 2020/07/24, 21:11
 */

// PIC16F1938 Configuration Bit Settings

    // 'C' source line config statements

    // CONFIG1
    #pragma config FOSC = INTOSC    // Oscillator Selection (INTOSC oscillator: I/O function on CLKIN pin)
    #pragma config WDTE = OFF       // Watchdog Timer Enable (WDT disabled)
    #pragma config PWRTE = OFF      // Power-up Timer Enable (PWRT disabled)
    #pragma config MCLRE = ON      // MCLR Pin Function Select (MCLR/VPP pin function is digital input)
    #pragma config CP = OFF         // Flash Program Memory Code Protection (Program memory code protection is disabled)
    #pragma config CPD = OFF        // Data Memory Code Protection (Data memory code protection is disabled)
    #pragma config BOREN = OFF      // Brown-out Reset Enable (Brown-out Reset disabled)
    #pragma config CLKOUTEN = OFF   // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)
    #pragma config IESO = OFF       // Internal/External Switchover (Internal/External Switchover mode is disabled)
    #pragma config FCMEN = OFF      // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is disabled)

    // CONFIG2
    #pragma config WRT = OFF        // Flash Memory Self-Write Protection (Write protection off)
    #pragma config VCAPEN = OFF     // Voltage Regulator Capacitor Enable (All VCAP pin functionality is disabled)
    #pragma config PLLEN = OFF// PLL Enable (4x PLL disabled)
    #pragma config STVREN = ON      // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will cause a Reset)
    #pragma config BORV = LO        // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
    #pragma config LVP = ON         // Low-Voltage Programming Enable (Low-voltage programming enabled)

    // #pragma config statements should precede project file includes.
    // Use project enums instead of #define for ON and OFF.

    #include <xc.h>
    #include <pic16f1938.h>
    #include <stdint.h>
    #include <stdio.h>
    //#define _XTAL_FREQ 32000000
    #define _XTAL_FREQ 4000000
 
    //#define LCD_ADD 0x7C
    // LCDモジュール
    #define LCD_I2C_ADDRESS 0x7c  // LCDモジュールのI2Cアドレス
    #define DEV_DSP  0x20  // DSPのI2Cアドレス

    #define  vol         22                       // 音量   0 - 31
    #define  inFreq     (8510 - 7600) / 10        // FM 大阪  11
    //
// 関数プロトタイプ宣言
//
 
// LCDモジュール表示制御関数
void lcdInitialize(void);               // LCD初期化
void lcdClearDisplay(void);             // ディスプレイ全消去
void lcdSendCommandData(uint8_t);           // コマンド送信
void lcdSendCharacterData(uint8_t);              // 1文字表示
void lcdLocateCursor(uint8_t,uint8_t);  // カーソル位置指定
 
// LCDモジュールI2Cプロトコル関数
void lcdI2CProtocol(uint8_t, uint8_t, uint8_t);
 
// I2Cプロトコル各信号の生成関数
void    i2cProtocolStart(void);        // スタートビット生成
void    i2cProtocolStop(void);         // ストップビット生成
void    i2cProtocolSendData(uint8_t);  // 1バイトデータ送信

void dsp_init(void);
uint16_t dsp_read_freq(void);

unsigned char dsp_read_freq1(void);
unsigned char dsp_read_freq2(void);


void dsp_search(char);
void dsp_tune(uint16_t);
void dsp_band(void);


    char moji[] = "Hello, PIC World!";
    char moji2[] = "Wak-tech";
    char count =0;
    uint16_t freq;
    uint16_t freqA;
    char band = 1;    //0:AM 1:FM
    
    
    
    void PICinit(){
      OSCCON = 0b01101000;    //4MHz
      ANSELA = 0b00000000;
      ANSELB = 0b00000000;
      TRISA  = 0b00000110;   //RA1,RA2をINPUT
      TRISB  = 0b00000000;
      TRISC  = 0b00011000;
      PORTA  = 0b00000000;    //2進数で書いた場合
      PORTB  = 0x00;          //16進数で書いた場合
    }
    

    int main(void){
      PICinit();      //PICを初期化
      //
    // I2C通信設定
    //
    // SMP Standard Speed; CKE disabled; 
      SSPSTAT = 0x80;
    // SSPEN enabled; CKP Idle:Low, Active:High; SSPM FOSC/4_SSPxADD_I2C; 
      SSPCON1 = 0x28;
    // SBCDE disabled; BOEN disabled; SCIE disabled; PCIE disabled; DHEN disabled; SDAHT 300ns; AHEN disabled; 
      SSPCON3 = 0x00;
    // Baud Rate Generator = 100kHz 
      SSPADD = 0x09;
      
      // LCD初期化
      lcdInitialize();
      
      lcdClearDisplay(); //画面をクリア
      __delay_ms(20);
      lcdSendCommandData(0x02); //ホームへカーソル移動
      __delay_ms(2); // LCD側の処理待ち
      lcdSendCommandData(0x02);   //ホームへカーソル移動
      printf(moji);
      dsp_init();
      __delay_ms(200);
      
      freq = 802;
      freqA = 666;
      
      dsp_tune(freq);
      __delay_ms(200);
      
      int radio = (dsp_read_freq())&0x1FFF;  //戻り値の下位13bitがチャンネル
      //int radio2 = dsp_read_freq2();
      
      freq = radio/4+300;                    //周波数に変換
      lcdLocateCursor(0,1);
      printf("%d",freq);
      //printf("%d", radio);
      //lcdLocateCursor(10,1);
      //printf("%d", radio2);
      while(1){
          
          //lcdLocateCursor(0,1); //2列目へ移動
          __delay_ms(200);
          if (PORTAbits.RA1 == 0){         //スイッチが押されていたら
              __delay_ms(10);              
              while(PORTAbits.RA1 == 0){}  //チャタリング防止
              dsp_search(1);               //up方向にseek
              __delay_ms(10);
          }
          if (PORTAbits.RA2 == 0){
              __delay_ms(10);
              while(PORTAbits.RA2 == 0){}
              dsp_band();
              __delay_ms(10);
          }
          
      }
      return 0;
    }
    
//
// LCDモジュールに制御コードまたはデータを送信
//
void lcdI2CProtocol(uint8_t address, uint8_t control_code, uint8_t data) {
     
    i2cProtocolStart();                 // スタートコンディション
    i2cProtocolSendData(address);       // アドレス送信
    i2cProtocolSendData(control_code);  // 制御コード送信 (動作設定=0x00/文字表示=0x40)
    i2cProtocolSendData(data);          // データ送信
    i2cProtocolStop();                  // ストップコンディション
 
    return;
}    
    
//
// 表示文字データ送信
//   0x40の後にデータを送信
void lcdSendCharacterData(uint8_t data){
 
    // 表示文字のデータを送信する場合の制御コードは0x40
    lcdI2CProtocol(LCD_I2C_ADDRESS, 0x40, data);
     
    // ウエイト
    //   文字表示の場合はウエイトは必要なくても動作しているが
    //   表示されない場合は1ms程度のウエイトを入れる
    // __delay_ms(1);
 
    return;
}
 
//
// コマンド送信
//   0x00の後にコマンドを送信
//
void lcdSendCommandData(uint8_t command){
 
    // コマンドを送信する場合の制御コードは0x00
    lcdI2CProtocol(LCD_I2C_ADDRESS, 0x00, command);
 
    // ウエイト
    //   データシートではウエイト時間は26.3us以上になっているが、
    //   それより長くしないと初期化できないケースがあるため1msのウエイトを入れる
    __delay_ms(1);
     
    return;
}
    
//
// ディスプレイ消去
//
void lcdClearDisplay(void){
     
    lcdSendCommandData(0x01);
     
    return;
}    
    
//
// カーソル位置移動
//    引数は水平方向右側プラスのX軸、垂直方向下側プラスのY軸で、それぞれ0から開始
//    左上の座標が(x=0, y=0)
//
void lcdLocateCursor(uint8_t position_x, uint8_t position_y){
     
    lcdSendCommandData( 0x80 + 0x40 * position_y + position_x );
     
    return;
}    
    
//
// printf関数の文字出力部分
//
void putch(char character) {
 
    lcdSendCharacterData(character);
     
    return;
}    
    
//
// LCDモジュール初期化
//
void lcdInitialize(void){
 
    // 初期化コマンド送信
    lcdSendCommandData(0x38); // 2行モードに設定
    lcdSendCommandData(0x39); // 拡張コマンド選択
    lcdSendCommandData(0x14); // 内部クロック周波数設定
    lcdSendCommandData(0x70); // コントラスト設定(C3:C0 = 0b0000に設定)
    lcdSendCommandData(0x56); // 電源電圧が3.3VなのでBooster=ON、コントラスト設定(C5:C4 = 0b10に設定)
    lcdSendCommandData(0x6c); // オペアンプのゲイン設定
     
    // モジュール内電源安定化のための時間待ち
    __delay_ms(200);
     
    // 初期化コマンド続き
    lcdSendCommandData(0x38); // 通常コマンド選択
    lcdSendCommandData(0x01); // ディスプレイ表示内容クリア
    lcdSendCommandData(0x0c); // ディスプレイ表示
     
    return;
}    
    
    
//
// I2Cプロトコル制御関数
//
 
// スタートコンディション生成
void i2cProtocolStart() {
     
    // SSP1CON2レジスタのSENビットを1に設定すると
    // スタートコンディションが生成される
    // 発行が完了するとSSP1IFが1になるのでwhile文で待つ
    SSPIF = 0;
    SSPCON2bits.SEN = 1;
    while (SSPIF == 0) {}
    SSPIF = 0;
     
    return;
}
 
// ストップコンディション生成
void i2cProtocolStop() {
 
    // SSP1CON2レジスタのPENビットを1に設定すると
    // ストップコンディションが生成される
    // 発行が完了するとSSP1IFが1になるのでwhile文で待つ
    SSPIF = 0;
    SSPCON2bits.PEN = 1;
    while (SSPIF == 0) {}
    SSPIF = 0;
 
    return;
}
 
// 1バイトデータ送信
void i2cProtocolSendData(uint8_t data) {
 
    // SSP1BUFに送信したいデータをセットすると、そのデータが送信される
    // 発行が完了するとSSP1IFが1になるのでwhile文で待つ
    SSPIF = 0;
    SSPBUF = data;
    while (SSPIF == 0) {}
    SSPIF = 0;
     
    return;
}    

// リピートスタートコンディション生成
void i2cProtocolRepeatStart() {
     
    SSPIF = 0;
    SSPCON2bits.RSEN = 1;
    while (SSPIF == 0) {}
    SSPIF = 0;
 
    return;
}

// 1バイトデータ受信
uint8_t i2cProtocolReceiveData() {
     
    SSPIF = 0;
    SSPCON2bits.RCEN = 1;
    while (SSPIF == 0) {}
    SSPIF = 0;
 
    return SSPBUF;
}

// Ack送信
void i2cProtocolSendAck() {
     
    // ACKDTにACKをセット(負論理なので0を設定)
    SSPCON2bits.ACKDT = 0;
 
    // NACK信号生成
    SSPCON2bits.ACKEN = 1;
    while (SSPCON2bits.ACKEN) {}
 
    return;
}
 
// Nack送信
void i2cProtocolSendNack() {
     
    // ACKDTにNACKをセット(負論理なので1を設定)
    SSPCON2bits.ACKDT = 1;
 
    // NACK信号生成
    SSPCON2bits.ACKEN = 1;
    while (SSPCON2bits.ACKEN) {}
 
    return;
}
  
void dsp_init(void){
    i2cProtocolStart();
    i2cProtocolSendData(DEV_DSP);
    i2cProtocolSendData(0x01);                     //レジスタ1-9まで書き込み   
    i2cProtocolSendData(0b00010001);        //AM522-1620/9khz FM 76-108MHz
    i2cProtocolSendData(0b01101000);        //AM 3kHz  FM 85.1MHz
    i2cProtocolSendData(0b10011100);        //FM 85.1MHz
    i2cProtocolSendData(0x39);              //76MHz start
    i2cProtocolSendData(0x61);              //108MHz stop
    i2cProtocolSendData(0b10100000);        //vol 40 max63 
    i2cProtocolSendData(0b00100010);        //de 50us FM space 50kHz
    i2cProtocolSendData(0b01011000);        //default
    i2cProtocolSendData(0b00001111);        //default
    i2cProtocolStop();
    __delay_ms(10);
    i2cProtocolStart();
    i2cProtocolSendData(DEV_DSP);
    i2cProtocolSendData(0x00);                     //レジスタ0に書き込み
    i2cProtocolSendData(0b11000000);        //power on FM tunebit off seekdown mute off
    i2cProtocolStop();
    //__delay_ms(10);
    i2cProtocolStart();
    i2cProtocolSendData(DEV_DSP);
    i2cProtocolSendData(0x00);                     //レジスタ0に書き込み
    i2cProtocolSendData(0b11100000);        //power on FM tunebit off seekdown mute off
    i2cProtocolStop();
    i2cProtocolStart();
    i2cProtocolSendData(0x00);
    i2cProtocolSendData(0b11000000);        // tunebit on
    i2cProtocolStop();
}

uint16_t dsp_read_freq(){
    
    uint16_t data1,data2;
    uint16_t freq;
    
    i2cProtocolStart();
    i2cProtocolSendData(DEV_DSP);      //writeでi2cアドレスを送信
    i2cProtocolSendData(0x14);         //レジスタアドレス0x14
    i2cProtocolStop();                 //一旦stop
    i2cProtocolRepeatStart();          //読み込みをするのでrepeatstart
    i2cProtocolSendData(DEV_DSP+1);    //readでi2cアドレスを送信
    data1 = i2cProtocolReceiveData();  //レジスタ14をdata1に
    i2cProtocolSendAck();              //続けて読むのでAckを返す
    data2 = i2cProtocolReceiveData();  //レジスタ15をdata2に
    i2cProtocolSendNack();             //読み込み終了でNackを返す
    i2cProtocolStop();
    freq = ((data1)<<8)+data2;        //stcを含めた16bitデータを返す
    return freq;
}

unsigned char dsp_read_freq1(){
    
    unsigned char data;
    
    
    i2cProtocolStart();
    i2cProtocolSendData(DEV_DSP);
    i2cProtocolSendData(0x14);
    i2cProtocolStop();
    i2cProtocolRepeatStart();
    i2cProtocolSendData(DEV_DSP+1);
    data = i2cProtocolReceiveData();
    i2cProtocolSendNack();
    i2cProtocolStop();
    return data;
}

unsigned char dsp_read_freq2(){
    
    unsigned char data;
    
    
    i2cProtocolStart();
    i2cProtocolSendData(DEV_DSP);
    i2cProtocolSendData(0x15);
    i2cProtocolStop();
    i2cProtocolRepeatStart();
    i2cProtocolSendData(DEV_DSP+1);
    data = i2cProtocolReceiveData();
    i2cProtocolSendNack();
    i2cProtocolStop();
    return data;
}

void dsp_tune(uint16_t fr){
    uint16_t chan;
    unsigned char data1, data2;
    unsigned char tune1, tune2;
    switch (band){
        case 0:              //AM
            chan = fr / 3;
            tune1 = 0b10000000;
            tune2 = 0b10100000;
            break;
        case 1:              //FM
            chan = (fr-300)*4;
            tune1 = 0b11000000;
            tune2 = 0b11100000;
            break;
    }
    data1 = (chan >> 8)+0x60;
    data2 = chan & 0x00ff;
    
    i2cProtocolStart();
    i2cProtocolSendData(DEV_DSP);
    i2cProtocolSendData(0x02);
    i2cProtocolSendData(data1);
    i2cProtocolSendData(data2);
    i2cProtocolStop();
    
    i2cProtocolStart();
    i2cProtocolSendData(DEV_DSP);
    i2cProtocolSendData(0x00);
    i2cProtocolSendData(tune1);        //power on FM tunebit off seekdown mute off
    i2cProtocolStop();
    
    i2cProtocolStart();
    i2cProtocolSendData(DEV_DSP);
    i2cProtocolSendData(0x00);
    i2cProtocolSendData(tune2);        //power on FM tunebit off seekdown mute off
    i2cProtocolStop();
    
    i2cProtocolStart();
    i2cProtocolSendData(DEV_DSP);
    i2cProtocolSendData(0x00);
    i2cProtocolSendData(tune1);        //power on FM tunebit off seekdown mute off
    i2cProtocolStop();
}

void dsp_search(char ud){
    uint16_t stc = 0;               //stcの初期値
    unsigned char reg0;             //レジスタ0の初期値
    switch (band){
        case 0:                     //AM
            reg0 = 0b10000000;
            break;
        case 1:                     //FM
            reg0 = 0b11000000;
            break;
    }
    i2cProtocolStart();
    i2cProtocolSendData(DEV_DSP);
    i2cProtocolSendData(0x00);
    i2cProtocolSendData(reg0);     //レジスタ0のbit4:seekを立ち下げ
    i2cProtocolStop();
    i2cProtocolStart();
    i2cProtocolSendData(DEV_DSP);
    i2cProtocolSendData(0x00);
    if(ud==0){
        i2cProtocolSendData(reg0 | 0x10);   //bit3:"0"seek down bit4立ち上げ
    }else{
        i2cProtocolSendData(reg0 | 0x18);   //bit3:"1"seek up bit4立ち上げ
    }
    i2cProtocolStop();
    lcdClearDisplay();
    while(stc==0){
        uint16_t rad = dsp_read_freq();
        stc = rad & 0x4000;                //stcは14bit "1"なら終了
        switch (band){
            case 0:
                freq = (rad & 0x1FFF)*3;
                break;
            case 1:
                freq = (rad & 0x1FFF)/4+300;               //下位13bitが周波数
                
                break;
        }
        
        lcdLocateCursor(0,1);              //カーソル2行目の左端
        printf("%d",freq);                 //周波数表示
        //printf("%x",rad);
        __delay_ms(20);
    }
    lcdLocateCursor(0,0);
    printf("tuned");
}

void dsp_band(){
    uint16_t rad;
    switch (band) {
        lcdClearDisplay();
        lcdLocateCursor(0,0);
        case 0:        //AM->FM
            band = 1;
            i2cProtocolStart();
            i2cProtocolSendData(DEV_DSP);
            i2cProtocolSendData(0x00);
            i2cProtocolSendData(0b11000000);
            i2cProtocolStop();
            printf("FM");
            dsp_tune(freq);
            rad = dsp_read_freq();
            freq = (rad & 0x1FFF)/4+300; 
            break;
            
        case 1:        //FM->AM
            band = 0;
            i2cProtocolStart();
            i2cProtocolSendData(DEV_DSP);
            i2cProtocolSendData(0x00);
            i2cProtocolSendData(0b10000000);
            i2cProtocolStop();
            printf("AM");
            dsp_tune(freqA);
            rad = dsp_read_freq();
            freq = (rad & 0x1FFF)*3;
            break;
    } 
    lcdLocateCursor(0,1);              //カーソル2行目の左端
    printf("%d",freq);                 //周波数表示
}

参考にしたサイト
コピペで超簡単!PICマイコンでI2C接続のLCD(AQM1602)を使う【PIC16F1938】 | Wak-tech
PICマイコン入門-実践│ツール・ラボ
災害用M6951ポケットラジオ: バブル・バ・ブル
実験テーマ107
DSPラジオモジュール「M6951」を使ったラジオ番組自動録音装置の製作