2010年8月26日木曜日

FMステレオチューナ: 操作表示部のAVRソース

前回に続き、操作表示部のAVRソースを掲載します。現状では、初期の機能を実装できておらず、プリセットボタンを押すと、所定の放送局に切り替わるだけでしかありません。はっきり言って未完成。本来なら公表できるシロモノではありませんが、ここは趣味のブログですので・・・。ちなみに初めてのAVRプログラムなので、ネット上のサンプルを断片的にコピペしてます。今、よく見たらvolatileの使い方を勘違いしていますね。なお、ロータリーエンコーダ周りはChaNさんのWeb資料を使わせて頂いています。



#include
#include

void sample_sw(void);
void LED_disp(void);
void led7seg(uint8_t num);
uint32_t bin2bcd(uint16_t n);


volatile uint32_t rdbuf = 0;
volatile uint8_t sw_stat, sw_stat2;
volatile uint8_t sw_stat_prev, sw_stat2_prev;
volatile uint8_t updated = 1;        // 周波数設定更新 0:何もしない 1:表示更新
volatile uint8_t preset_ch = 1;        // プリセットチャンネル
volatile uint8_t preset_ch_memory[16] = {28,50,57,75,125,18,35,47,65,0,0,0,0,0,0,0};    // プリセットチャンネルメモリ
volatile uint8_t preset_enable = 1;    // 0: チューニングモード、1: プリセットモード
volatile uint8_t frequency = 0;    // 周波数 0: 76.0MHz, 141: 90.0MHz
volatile uint16_t freq_value[141] = {    // DDS周波数設定値 0-141; 76.0-90.0MHz
        0x07E3,0x083C,0x0895,0x08EE,0x0947,0x099F,0x09F8,0x0A51,0x0AAA,0x0B03,
        0x0B5C,0x0BB5,0x0C0E,0x0C67,0x0CBF,0x0D18,0x0D71,0x0DCA,0x0E23,0x0E7C,
        0x0ED5,0x0F2E,0x0F87,0x0FDF,0x1038,0x1091,0x10EA,0x1143,0x119C,0x11F5,
        0x124E,0x12A7,0x12FF,0x1358,0x13B1,0x140A,0x1463,0x14BC,0x1515,0x156E,
        0x15C7,0x161F,0x1678,0x16D1,0x172A,0x1783,0x17DC,0x1835,0x188E,0x18E7,
        0x193F,0x1998,0x19F1,0x1A4A,0x1AA3,0x1AFC,0x1B55,0x1BAE,0x1C07,0x1C5F,
        0x1CB8,0x1D11,0x1D6A,0x1DC3,0x1E1C,0x1E75,0x1ECE,0x1F27,0x1F7F,0x1FD8,
        0x2031,0x208A,0x20E3,0x213C,0x2195,0x21EE,0x2247,0x229F,0x22F8,0x2351,
        0x23AA,0x2403,0x245C,0x24B5,0x250E,0x2567,0x25BF,0x2618,0x2671,0x26CA,
        0x2723,0x277C,0x27D5,0x282E,0x2887,0x28DF,0x2938,0x2991,0x29EA,0x2A43,
        0x2A9C,0x2AF5,0x2B4E,0x2BA7,0x2BFF,0x2C58,0x2CB1,0x2D0A,0x2D63,0x2DBC,
        0x2E15,0x2E6E,0x2EC7,0x2F1F,0x2F78,0x2FD1,0x302A,0x3083,0x30DC,0x3135,
        0x318E,0x31E7,0x323F,0x3298,0x32F1,0x334A,0x33A3,0x33FC,0x3455,0x34AE,
        0x3507,0x355F,0x35B8,0x3611,0x366A,0x36C3,0x371C,0x3775,0x37CE,0x3827,
        0x387F
        };

volatile struct {    // ロータリエンコーダ 軸位置構造体
    long position;   // 軸位置
    int moved;       // 移動フラグ
} Encoder;



void port_init(void)
{
    //     ポート設定 0:入力, 1:出力
    DDRA = 0b11111111;
    DDRB = 0b00000000;
    DDRC = 0b11111111;
    DDRD = 0b01000000;
    DDRE = 0b01110000;
    DDRF = 0b11111100;
    DDRG = 0b00100;

    //    出力ポート初期化
    PORTA = 0b00000000;
    PORTC = 0b00000000;
    PORTD = 0b00000000;
    PORTE = 0b00000000;
    PORTF = 0b00000000;
    PORTG = 0b00000;

    //    入力ポート、1:プルアップ
    PORTB = 0b11110010;
    PORTD = 0b10000000;
    PORTE = 0b00000011;
    PORTF = 0b00000011;
    PORTG = 0b11011;
}


void timer1_init(uint8_t t)
{
    // CTC動作

    TCNT1 = 0;    //    タイマ1の初期値設定
    OCR1A = t;    //    タイマ/カウンタ1比較レジスタA

    // 15.11.1 タイマ/カウンタ1制御レジスタA (初期値は0x00なので必要ない)
    // WGM10=0, WGM11=0
    TCCR1A = 0b00000000;

    // 15.11.2 タイマ/カウンタ1制御レジスタB
    //         || ++--- WGM13:WGM12    01  波形生成種別(4bitの上位2bit) CTC top=OCR1A
    //         || ||+++
    TCCR1B = 0b00001101;    //  タイマ/カウンタ1制御レジスタ CS12:CS11:CS10 101 1024分周

    // 15.11.8 タイマ/カウンタ1割り込みマスクレジスタ
    TIMSK = 0b00010000;
}


void init_devices(void)
{
    cli();
    port_init();
    timer1_init(2);    // 割込間隔 clk/1024, 上限255
}


ISR (TIMER1_COMPA_vect)
//C:\WinAVR-20090313\avr\include\avr\iom64
{

    sample_sw();        // Switch読み込み
    change_detect();    // Switch状態変化検出
    sample_encoder();    // ロータリエンコーダ読み込み
    freq_set_update();    // Switchの指定により周波数設定更新

    if (updated == 1) {
        LED_disp();            // 7セグLED表示
    }

    if_mb();
    updated = 0;

}



void sample_sw(void)
{

    static uint8_t sw_prev, sw_prev2;
    uint8_t a, a2;

    a = ~PIND & 0b00111111;    // ビットマスク
    a2 = ~PINE & 0b00000100;
    //    PD0: Tuning/Preset
    //    PD5: CH1, PD4: CH2, PD3: CH3, PD2: CH4, PD1: CH5
    //    PE2: Store to Memory

    if (a == sw_prev) {
        sw_stat = a;    // 前回値と同じなら有効として格納
    }
        sw_prev = a;

    if (a2 == sw_prev2) {
        sw_stat2 = a2;
    }
        sw_prev2 = a2;

}


void change_detect(void)
{

    uint8_t a=0, a2=0, n=0;

    a = sw_stat;

    // PD0~PD5が押されているか確認
    for (n = 0; n < 6; n++) {
        a2 += a & 0x01;
        a >>= 1;
    }

    // スイッチが1つだけ、あるいは押されていない場合のみ処理継続
    if (a2 == 0) {
        sw_stat_prev = a;    // 前値として保存
    } else if (a2 == 1) {

        if (sw_stat_prev == 0) {    // 新たにボタンが押されたとき
            updated = 1;    // 状態変化フラグ
        }
        sw_stat_prev = a;    // 前値として保存
    }
}



void freq_set_update(void)
{

    static uint8_t sw_prev;    // Preset/Tuningスイッチ切替確認用


    // プッシュボタンの処理
    if (updated == 1) {
        if (sw_stat == 0x20) {    // PD5: CH1
            preset_enable = 1;
            preset_ch = 0;
            frequency = preset_ch_memory[preset_ch];
        }

        if (sw_stat == 0x10) {    // PD4: CH2
            preset_enable = 1;
            preset_ch = 1;
            frequency = preset_ch_memory[preset_ch];
        }

        if (sw_stat == 0x08) {    // PD3: CH3
            preset_enable = 1;
            preset_ch = 2;
            frequency = preset_ch_memory[preset_ch];
        }

        if (sw_stat == 0x04) {    // PD2: CH4
            preset_enable = 1;
            preset_ch = 3;
            frequency = preset_ch_memory[preset_ch];
        }

        if (sw_stat == 0x02) {    // PD1: CH5
            preset_enable = 1;
            preset_ch = 4;
            frequency = preset_ch_memory[preset_ch];
        }


        if ((sw_stat == 0x01) && (sw_prev == 0)){    // PD0: TUNING/PRESET SELECT、前値と現在値が異なる場合
            if (preset_enable ==1) {
                preset_enable = 0;
            } else {
                preset_enable = 1;
                frequency = preset_ch_memory[preset_ch];
            }
        }
    }


    // ロータリエンコーダの処理
    if (Encoder.moved == 1) {

        if (preset_enable == 1) {

            if ((Encoder.position == 1) && (preset_ch < 15)) {
                preset_ch = preset_ch + 1;
                frequency = preset_ch_memory[preset_ch];
            }

            if ((Encoder.position == -1) && (preset_ch > 0)) {
                preset_ch = preset_ch - 1;
                frequency = preset_ch_memory[preset_ch];
            }

        } else {

            if ((Encoder.position == 1) && (frequency < 140)) {
                frequency = frequency + 1;
            }

            if ((Encoder.position == -1) && (frequency > 0)) {
                frequency = frequency - 1;
            }
        }

        updated = 1;
        Encoder.moved = 0;
    }
}


void if_mb(void)
{
    // PE6: SCLKout, PE5: SDout, PE4: ENout, PE3:RDin

    uint16_t n;
    uint32_t sdbuf;

    // 周波数設定読み込み
    sdbuf = freq_value[frequency];


    PORTE |= _BV(4);    // EN->1 : PE4

//    for (n = 0; n <= 15; n = n + 1) {
    for (n = 0; n <= 31; n = n + 1) {
        // SDout
//        if(sdbuf & _BV(15 - n)){
        if(sdbuf & _BV(31 - n)){
            PORTE |= _BV(5);
        }else{
            PORTE &= ~_BV(5);
        }

        // SCLKout
        PORTE |= _BV(6);    // SCLKout->1 : PE6
        PORTE &= ~_BV(6);    // SCLKout->0 : PE6

        // RDin
//        if(PINE & _BV(3)){
        if(bit_is_set(PINE, 3)){
//            rdbuf |= _BV(15 - n);
            rdbuf |= _BV(31 - n);
        }else{
//            rdbuf &= ~_BV(15 - n);
            rdbuf &= ~_BV(31 - n);
        }
    }

    PORTE &= ~_BV(4);    // EN->0 : PE4
}


void LED_disp(void)
{

    uint32_t a;

    a = bin2bcd(preset_ch + 1);

    // チューニングモードの時は非表示
    // preset_enable 0: チューニングモード、1: プリセットモード
    if(preset_enable == 0){        // ゼロサプレス
        a = 0x00FF;
    }

    // メモリチャンネル表示設定 x1桁
    PORTF &= ~_BV(5);    // LE->0 : PF5
    led7seg(a);
    PORTG |= _BV(2);    // DP->1: PG2
    PORTF |= _BV(5);    // LE->1 : PF5

    a = a >> 4;

    // メモリチャンネル表示設定 x10桁
    if(a == 0){        // ゼロサプレス
        a = 0x000F;
    }

    PORTF &= ~_BV(6);    // LE->0 : PF6
    led7seg(a);
    PORTG |= _BV(2);    // DP->1: PG2
    PORTF |= _BV(6);    // LE->1 : PF6


    a = bin2bcd(frequency + 760);


    // 周波数表示設定 0.1MHz桁
    PORTF &= ~_BV(3);    // LE->0 : PF3
    led7seg(a);
    PORTG |= _BV(2);    // DP->1: PG2
    PORTF |= _BV(3);    // LE->1 : PF3

    a = a >> 4;

    // 周波数表示設定 1MHz桁
    PORTD &= ~_BV(6);    // LE->0 : PD6
    led7seg(a);
    PORTG &= ~_BV(2);    // DP->0: PG2
    PORTD |= _BV(6);    // LE->1 : PD6

    a = a >> 4;

    // 周波数表示設定 10MHz桁
    PORTF &= ~_BV(4);    // LE->0 : PF4
    led7seg(a);
    PORTG |= _BV(2);    // DP->1: PG2
    PORTF |= _BV(4);    // LE->1 : PF4

}



void led7seg(uint8_t num)       // 7セグ表示データ設定
{           
    if(num & _BV(0)){        // x1: PF2
        PORTF |= _BV(2);
    }else{
        PORTF &= ~_BV(2);
    }

    if(num & _BV(1)){        // x2: PA6
        PORTA |= _BV(6);
    }else{
        PORTA &= ~_BV(6);
    }

    if(num & _BV(2)){        // x4: PA7
        PORTA |= _BV(7);
    }else{
        PORTA &= ~_BV(7);
    }

    if(num & _BV(3)){        // x8: PC7
        PORTC |= _BV(7);
    }else{
        PORTC &= ~_BV(7);
    }
}




void sample_encoder(void)
{
    // PB0 : CHA
    // PE7 : CHB

    static uint8_t reinput, rencoder, rencoder_prev;
    static const int dir[] = { 0,1,-1,0,-1,0,0,1,1,0,0,-1,0,-1,1,0 }; // 回転方向テーブル
    static int i;                           // インデックス
    int n;

    reinput = (PINB & 0x01) + ((PINE >> 6) & 0x02);

    if (reinput == rencoder_prev) {
        rencoder = reinput;    // 前回値と同じなら有効として格納
    }

    rencoder_prev = reinput;

    i = (i << 2) + (rencoder & 3);   // 前回値と今回値でインデックスとする
//    i = (i << 2) + (PINB & 0x01) + ((PINE >> 6) & 0x02);   // 前回値と今回値でインデックスとする
    n = dir[i & 15];                    // 変化パターンから動きを得る
    if (n == 0) {                            // 動きがあったら位置更新
        Encoder.moved = 0;
    } else {
        Encoder.position = n;
        Encoder.moved = 1;
    }
}




void wait(unsigned int time)       // 100us wait ルーチン。 クロック設定のこと。
{                                // http://www.geocities.jp/kuman2600/c_memo.html
    time*=1;                    // timeにF_CPU(MHz)をかける。重要!!
    unsigned char lpcnt;

    __asm__ __volatile__("\n"
    "Entry%=: \n\t"
    "ldi %0,27\n"                // 計算値は25。24の方が実測値は良い。
    "Loop%=: \n\t"                // 上の行で 24 のときは100us、249にすれば1msのルーチンになる。
    "nop\n\t"
    "dec %0\n\t"
    "brne Loop%=\n\t"
    "sbiw %1,1\n\t"
    "brne Entry%=\n\t"
    :"=&a"(lpcnt)
    :"w"(time)
    );

  return;
}



uint32_t bin2bcd(uint16_t n)
{

//    n-> 16bit
//    http://oshiete1.goo.ne.jp/qa1110530.html

    uint32_t bcd = 0;
    uint8_t base = 0;

    while (n > 0){
        bcd |= (n%10) << base;
        base += 4;
        n /= 10;
    }
    return bcd;
}


int main(void)
{
    init_devices();
    frequency = preset_ch_memory[preset_ch];

    //    割込 初期設定
    SREG |= _BV(7);    // 全割込許可

    sei();    // 割り込み許可

    while(1);    // 無限ループ
}

0 件のコメント:

コメントを投稿