PIC24FJ64GA002 のプログラム領域ROMをデータROMとして使う

1.はじめに

PIC24Fはデータ記憶用のROMがなく、データの保存にはプログラム領域のROMを用いる必要があるらしい。

とあることから、300程度のINTデータを保存しておきたくなったのだが、インターネット上でざっと私が調べた範囲では、多くても1ページ(3Byte×64)を使うもので、ページをまたがってデータを保存する方法が簡単に書かれたものはなかった。使い方を理解するのに相当の時間を費やしたので、ここにまとめておく。

 

ただしまだ完全とは言えず、データROMに使う領域を変更すると、一部使えなくなることがある(詳細は後述)。つまり、ここに書いてあることは、まったくの間違いという可能性もある。このプログラムを使う際はトライアルを厳重にする等、十分注意をお願いします。


なお、本調査にあたっては、以下のブログを参考にさせていただいた。
あらためてお礼を申し上げるとともに、リンクフリーとのことなので、リンクを張らせていただく。まず、こちらを読んだほうが理解が深まると思う。
    ハム三昧さん
    http://jr4pdp.blog.enjoy.jp/myblog/2019/05/pic24f64ga004ee-d37e.html

 

2.前提条件
 ・利用したPICは、PIC24FJ64GA002
  その他のPIC24でも使えると思う(未確認)。その場合はメモリマップを参照の上、利用する領域を適宜変更のこと。
 ・INT(16Bit)データを読み書きする。
 ・データの消去単位である1536Byteを対象とするが、1/3は使いにくいので使わないこととし、1024Bit(=Intで512個)のデータを読み書きする。
 ・コンパイラは、X16 V1.24。古いがご勘弁を。

3.使い方

3-1.事前準備
(1) ヘッダーファイルの準備
    p24FJ64GA002.h
をインクルードする。私の環境では、
    C:\Program Files (x86)\Microchip\xc16\v1.24\support\PIC24F\h
にあった。


(2)データROM領域の定義
コンフィギュレーションビット設定の前に
const __attribute__*1 unsigned int _flash_datas[96*8];
を追記する。

上記は、0x9C00~0xA1FFをデータROMにする例である。
このスタートアドレス(0x9C00)は、0x600 ごとに設定できる(0x9000,0x9600,0x9C00,0xA200・・・)。
*なぜか0x9600、0xA200に設定した場合は、半分の256個までしか使えなかった。
 理由は不明。間違っているのかもしれない と書いたのはこれが理由。

 

3-2.書き込み
da_tmp にあるデータを書き込みたい場合は以下とする。
複数ページに書き込む場合、要はoffsetを128づつインクリメントしていけばよい。

void Flash_Write() {
    unsigned int i=0;
    unsigned int j=0;
    
    unsigned int page = __builtin_tblpage(&_flash_datas);
    unsigned int offset = __builtin_tbloffset(&_flash_datas);

    // ページ消去
    TBLPAG = page;
    __builtin_tblwtl(offset, 0x0000); // 
    NVMCON = 0x4042;
    asm volatile ("disi #5");
    __builtin_write_NVM();          //erase 8line = 64*8
    while(NVMCONbits.WR);
 
    
    // メモリー書き込み
    for(j=0;j<8;j++) {
    NVMCON = 0x4001;
    TBLPAG = page;
  
    for (i=0; i<64; i++) {
        __builtin_tblwtl(offset + i*2, da_tmp[i+j*64]);  // *2 偶数
        __builtin_tblwth(offset + i*2, 0xFF);
       }
    asm volatile ("disi #5");
    __builtin_write_NVM();
    while(NVMCONbits.WR);
    
    offset=offset+128;
    
    }
}


3-3. 読み込み
da にデータを読み込む場合は以下。
こちらも、offsetを128づつインクリメントする。

void Flash_Read() {
 unsigned int i=0;
 unsigned int j=0;

    unsigned int page = __builtin_tblpage(&_flash_datas);
    unsigned int offset = __builtin_tbloffset(&_flash_datas);
      // フラッシュメモリー読み込み
        TBLPAG=page;
        
    for(j=0;j<8;j++){
        for(i=0;i<64;i++){
        da[i+j*64] = __builtin_tblrdl(offset + i*2);
        }
        
        offset=offset+128;
    }
}

 

*1:section(".Data_Memory"), space (prog), address (0x9c00)

I2C LCDディスプレイ ACM1602NI-FLW-FBW-M01 の使い方

メモ


・IDは0X50(1010000)+0なので、送信するIDは0xA0
・I2Cのクロックは100kHzまで
・コマンド送信は、ID(0xA0)、0x00、コマンド
・データ送信は、ID(0XA0)、0X80、データ
・コマンド、データ送信の間は5ms空ける
・Initは、以下コマンドを送信する(詳細は利用形態に合わせて変更方)
  0x01,0x38,0x0f,0x06
・表示前に、1行目なら0x80、2行目なら0xc0のコマンドを送る(パラレルと同じ)

RDA5807を使ったラジオ

 

0.まずはお約束

shiro46tan.hatenablog.com

1.回路

 


これで良しとした。
・PIC,74HC4511,7セグLEDを1枚に、RDA5807とパワーアンプを1枚に載せる。
・電源は5V、または単3×3(ニッケル水素電池の充電式)とする。
・私は赤の7セグLEDを利用した。暗いという方は抵抗を調整してください。

f:id:shiro46tan:20210507180409j:plain

回路図

 

f:id:shiro46tan:20210507180740j:plain

製作例
2.ソフト


・マニュアルによれば、RDA5807のIDは10H。1Bit左シフトして、20Hを送信となる。
レジスタへのデータ書き込み方法は、レジスタアドレスが自動INCされるので、それに合わせて順にデータを送る方法。この方法では、電源ON時に最後までデータを書き込めないことがあった。
・ネットにてレジスタアドレスとデータをペアで送る裏コマンドを発見(ID:11h。1Bit左シフトで22h)。これを用いる方法に変更した。
・それでも電源ON時はなぜか周波数が書き込めなかったので、電源ON時に限り周波数を2回書き込んでいる。個体ばらつきによっては、これでも電源ON時は動かないかもしれない。その場合はWait(プログラム中のdelay_ms(1000) )を大きくする。
・10ch分のプリセットは、関東地区用。周波数を変える場合は、freq[]の値を変える。
計算式は、(freq(kHz)-760000)/100。0chは別掲のTransmitterに合わせた。

 

3.動作確認

・電源を入れると、7セグLEDが約0.5秒間0を表示して消灯する。スピーカからは、ホワイトノイズまたは音声が聞こえる。
・SWを押すと、7セグLEDは順にカウントアップ、9の後は0になる。それに伴い受信局が変化すれば完成。
・電流は、5V、LED OFF、音量0で25mA前後。

 

4.プログラム


XC8 V1.34用。

 

/* File: RDA5087radio.c
*
* By Shiro46tan
* 2021/5/7
*/

// --PIC16F1503--
// SDA : PORTC(1) pin 9
// SCL : PORTC(0) pin10
// 4MHz

#include <xc.h>
#define _XTAL_FREQ 4000000

#define SW1 PORTAbits.RA0

#define LED0 PORTCbits.RC2 //Bit0
#define LED1 PORTAbits.RA2 //Bit1
#define LED2 PORTAbits.RA4 //Bit2
#define LED3 PORTCbits.RC3 //Bit3
#define LED4 PORTCbits.RC5 //LED ON/OFF
#define PL PORTAbits.RA5 //LED for check

//************* Config ***********************************
#pragma config FOSC = INTOSC, WDTE = OFF, PWRTE = OFF, MCLRE = OFF, CP = ON
// Watch Dog timer をOffにしないと、途中でリセットがかかる可能性あり
#pragma config BOREN = ON, CLKOUTEN = OFF
//    CLKOUTをRA4として使う
#pragma config WRT = OFF
#pragma config STVREN = ON, BORV = LO, LPBOR = OFF, LVP = OFF


//********************************************************

void i2cTxData(char);
void SendCMD(void);
void SleepIn(void);
void i2cStart(void);
void i2cStop(void);
void reset(void);
void send(unsigned char, unsigned char, unsigned char);


//初期設定

char VOLUME=0x08;

int freq[10]={0x3138,14500,15600,17000,0x7d0,0xdac,0xfa0,0x14b4,0x1964,0x21fc};
// 受信周波数 88.6 90.5 91.6 93 78 79.5 80 81.3 82.5 84.7
//
//
int RcvFreq=0x3138; //(受信周波数-76000KHz)/100 88.6MHz
char ch=0;

//////////// Main //////////////////////////////////
void main(void){

int i=0,j=0;

unsigned char num = 0;
OSCCON = 0b01101000; // 4MHz


__delay_ms(100);

 

ANSELA = 0x0;
ANSELC = 0x0; // PortCをデジタルI/Oにする
TRISC = 0b11000011; // PortC I/O設定 1がInput
TRISA = 0b11000011;
SSPCON1 = 0x28; // I2Cマスター
SSPSTAT = 0x00;
SSPADD = 0x24; // I2C周波数100KHz for 4MHz

WPUA=0b00000011; // pullup
OPTION_REGbits.nWPUEN=0; //

LED4=0;

__delay_ms(1000); //十分なWaitを取る

RcvFreq=0x3138;
reset();
__delay_ms(100);


SendCMD();

send(0x08,0x31,0x38);

INTCONbits.IOCIE=1; //IOC(状態変化割り込み)Enable
IOCAPbits.IOCAP0=1; //Port RA0(=SW1)ボタン割り込み

INTCONbits.GIE=1; //INTグローバルEnable

while(1){
SleepIn();
}

}

 

void i2cStart(void){
SEN = 1; // Start condition
while(SEN); // Start condition
}

void i2cStop(void){ //i2c STOP
SSP1IF = 0; // クリア
PEN = 1; // Stop condition
while(PEN); // Stop condition
}

void i2cTxData(char data){
SSP1IF = 0; //
SSPBUF = data; //
while(!SSP1IF); //
}

void send(unsigned char ch,unsigned char i,unsigned char j){
i2cStart();

i2cTxData(0x22); // スレーブアドレス 0010001+0

i2cTxData(ch); // channel

i2cTxData(i); // upper

i2cTxData(j); // lower

i2cStop();
}


void reset(void) {

send(0x02,0xa0,0x03);

}


void SendCMD(void){

//02H
send(0x02,0xd0,0x01);
// Mono+Bass Boost disable seekしない
// 32.768k Power On

//03H
send(0x03,0x00,0x04);
//Band=1

//04H
send(0x04,0x0a,0x00);
//De-Emphasis50us
//I2S,GPIO 使わない

//05H
send(0x05,0x88,0x80+VOLUME);
//
//Volume 0 - 15

//06H
send(0x06,0x00,0x00);
//I2Setc 使わない


//07H
send(0x07,0x40,0x03);
//0Bit=1にして、08Hに周波数を書き込む

//08H
send(0x08,RcvFreq>>8,0x00ff & RcvFreq);

//chの表示
LED4=1; //LED ON
LED0=ch&1;
LED1=(ch>>1)&1;
LED2=(ch>>2)&1;
LED3=(ch>>3)&1;
__delay_ms(1000); //1秒待つ

LED4=0; //LED OFF

}


void SleepIn(void){


IOCAPbits.IOCAP0=1; //RA0(=SW3)ボタン割り込みON

PL=0;
SLEEP();
}

 


void interrupt isr(void)
{


if(IOCAFbits.IOCAF0){ //RA0(=SW1)ボタンが押されたとき

while(SW1==0); //離すのを待つ
ch++;

if(ch>=10){ //ch=10になったら0にする
ch=0;
}

RcvFreq=freq[ch]; //受信周波数をfreq[ch]にする

SendCMD();
__delay_ms(100);

IOCAFbits.IOCAF0=0; //clear flag
}

}

QN8027使用 FM Transmitterの製作

0.お約束

念のため。確認ください。

 

shiro46tan.hatenablog.com

 

1.回路


以下で動作したので、これでよしとする。

f:id:shiro46tan:20210221121311j:plain

回路図

2.製作

・QN8027のピッチは0.5mmピッチなので、変換基盤を使う。
 変換基盤への半田付けは、フラックス塗でべた付け後、フラックスを吸わせた半田吸い取り線で余分な半田を除去した(トラ技式)。
・0.12uHのコイルは、エイヤでφ8のドリルに10回巻。
・LEDは動作チェック用。省略可。
・回路上にはICPSのコネクタを書いたが、現物では省略した。

写真:製作した基盤。一部1uFのチップコンを使ったため、すべての部品は見えない。

f:id:shiro46tan:20210221121404j:plain

電源は、本機の移動が不要なことから、スマホ用の5V電源を利用した。

 

3.ソフト


・周波数は88.6MHzとした。
 88.6=76+0.05*252 なので、レジスタ値は0xFCとする。
・無音後約58秒で電源を切れるようにした。
・I2C通信の前の100msウェイトは必須。無いと不安定になったり、データの書き込みができなかったりする。
・WatchDock TimerをOffにしないと、途中で突然リセットがかかることがある。

 

4.動作確認


・PIC16F1503にプログラムが正常に書き込まれていれば、LEDが0.5秒程度光った後消える
・5V電源を使った場合の電流は13mA、無音1分後(電波発射なし)の時で5mA程度。

 

5.注意


電波法に違反せぬよう利用のこと。

 

6.プログラム


XC8 V1.34用。

 

/*
* transmit.C
* By Shiro_46
* 2021.2
*/
// --PIC16F1503--
// SDA : PORTC(1) pin 9  I2Cで使う場合は入力にすること
// SCL : PORTC(0) pin10  I2Cで使う場合は入力にすること
// 他IOは、RA0~5、RC2~5
// 4MHz

#include <xc.h>

#define _XTAL_FREQ 4000000
#define PL PORTCbits.RC4 //チェック用LED


//************* Config ***********************************
#pragma config FOSC = INTOSC, WDTE = OFF, PWRTE = OFF, MCLRE = OFF, CP = ON
// Watch Dog timer をOffにしないと、途中でリセットがかかる可能性あり
#pragma config BOREN = ON, CLKOUTEN = OFF
#pragma config WRT = OFF
#pragma config STVREN = ON, BORV = LO, LPBOR = OFF, LVP = OFF

//********************************************************
void i2cStart(void); // Start Condition
void i2cStop(void); //Stop Condition
void i2cTxData(char); // i2c 8Bitデータ送信

void QN8027Send(unsigned char,unsigned char);
void Init(void);

 

//////////// Main //////////////////////////////////
void main(void){

OSCCON = 0b01101000; // CLOCK 4Mhz
ANSELA = 0x0; // PortAをすべてデジタルにする
ANSELC = 0x0; // PortCをすべてデジタルにする
TRISC = 0b11000011; // RC0,1が入力(2~5は出力)
TRISA = 0b00000000; // RAはすべて出力
SSPCON1 = 0x28; // I2Cマスターとする
SSPSTAT = 0x00; // I2Cバスをオープンにする
SSPADD = 0x09; // I2C Clock 100KHz for 4MHz

__delay_ms(100); //送信の前にWaitを入れること。入れないと不安定になる。

PL=0; //LED消灯

Init();


SLEEP();
}

 


void Init(void){

PL=1;
QN8027Send(0x00,0x80); //reset to default
QN8027Send(0x01,0xfc); //Freq=88.6MHz F=76+0.05*0xFC
QN8027Send(0x02,0x09); //PreEmphasis50 etc 0x09にすると、58秒無音でOffになる 0x39ならOnのまま
QN8027Send(0x04,0xd9); //RegVga Xtal=24MHz Input Gain 12dB Z=10k
QN8027Send(0x10,45); //TXPow Setting

 

QN8027Send(0x00,0x20); //Transmit On

__delay_ms(500);
PL=0;

}

// QN8027 のアドレスにデータを送る
// アドレス、データを渡す
//QN8027 のデバイスIDは0x2C
void QN8027Send(unsigned char reg,unsigned char data){

i2cStart(); //i2c Start
//QN8027のデバイスID 0X2C+PICからの送信(0)=0x58
i2cTxData(0x58); //
i2cTxData(reg); // レジスタ番号の送信
i2cTxData(data); // データの送信

i2cStop(); //i2c Stop
}

void i2cStart(void){
SEN = 1; // Start condition 送信
while(SEN); // Start condition 終了待ち
}

void i2cStop(void){ //i2c STOP
SSP1IF = 0; // 終わり
PEN = 1; // Stop condition 送信
while(PEN); // Stop condition 終了待ち
}

//-------- SSPBUFに1文字保存し送信終了を待つ -----------------
void i2cTxData(char data){
SSP1IF = 0; // 終了フラグクリア
SSPBUF = data; // データセット
while(!SSP1IF); // 送信終了待ち
}

 

PIC16F1503 のI2C通信

今回PICでデータ受信はしないので、データ送信のみできればよい。
やり方だけ書く。

1.準備


・SCL,SDAピンは入力モードに指定する
・SSPCON1のSSPMビットをセットする→マスタモードにするため

2.1バイト送信

1)~3)の手順でOK。


1) Start Conditionの送信
void i2cStart(void){
SEN = 1;
while(SEN);
}

2) 1byteデータ送信
void i2cTxData(char data){
SSP1IF = 0; // 終了フラグクリア
SSPBUF = data;
while(!SSP1IF); // 送信終了まで待つ
}

3)Stop Conditionの送信
void i2cStop(void){ //i2c STOP
SSP1IF = 0; // 終了フラグクリア
PEN = 1;
while(PEN);

TV音声トランスミッタ(とFMラジオ)の製作

0.はじめに

TV画面がどんどん大きくなっている。
画面が大きくなると、離れて見なければならない。
離れると、音声は大きくしないといけない。
近くを通るとうるさい。
だんだん耳も悪くなってきた。
ますますうるさい。


そこで、TV音声を耳元で聞くシステムを作ることにした。

1.構想

・どうせなら音の良いFMにしたい
・安定度の高いFMトランスミッタとラジオをアナログ回路で作るのは、私の腕では難しい
 →1チップのFMトランスミッタを使う
  DSPラジオを使う
  
・どうせなら通常のFM放送も聞けるようにしたい。
 ただし、TV音声は電源Onですぐ聞けるようにしたい
 →チューニングボリュームは無いほうがよい
  →I2Cを使ったラジオにして、

     電源On時には、TV音声が聞けるようにする
    チューニングボタンを押すと、通常のFM放送が聞けるようにする
・TV放送と一般放送を合わせて、10chとする(これだけあれば十分と考えた)
・あまり大きくしたくない
 →ラジオはモノラルにする
  ただし、トランスミッタは将来の気変わりに備えてステレオにしておく。
・どうせならAM放送も聞けるようにと思ったが、今回はあきらめる
  N〇Kラジオは卒業した
  民放AMはFMで聞ける
・電池で駆動できること

 

2.ICの選定

・I2Cのデータ送信は、PIC16F1503を使うことにした
  私がPICKITを持っている
  安い
  I2C通信が簡単にできる
・FMトランスミッタはQN8027、ラジオはRDA5807を使う
  安い
・オーディオパワーアンプはNJM2073にする→ノイズ大のためHT82V739に変更
  手持ちの関係で

・チャンネル表示用に、7セグLEDとデコーダ(74HC4511)を使う。
  デコーダを使うほうが、PICの出力を減らせる(実際は余ってますが)
  プログラムが楽になる
・電源は3.3Vが必要。→電池3本から、レギュレータで3.3Vを作る
 →レギュレータはS-812C33AY(手持ちの関係で)

PICを使った趣味の工作

 これまで細々と作りためてきた電子工作について、備忘録を兼ねてまとめることとしました。これから少しずつアップしていきます。同様の工作をしたいと思っている方の手助けになれば幸いです。

 

1.お約束

・本記事に関して、一切の責任は負いません。
 たまたま私の環境で動作しただけかもしれません。
 本記事の内容を実施したことによる不具合、不利益その他何が起きても私は関知しませんし、責任も負いません。

 

・公開はしますが著作権は放棄していません。利用は個人利用に限ります。商用利用は厳禁です。


・質問等をしていただいても、回答の義務は負いません。返事はないものと思ってください。


すなわち、本記事の内容を(間違いがあればそれも含めて)理解でき、自己責任で利用できる方が、個人利用に限り使って可 ということです。