目次
前
次
NCO(Numerically Controlled Oscillator)
8ピンのPIC12F1501には、NCOモジュールが含まれています。
NCOは、累算器のオーバーフローを利用し矩形波の周波数を可変します。
ブロック図でみると、以下。
PIC12F1501に含まれる累算器は20ビット、オーバーフローを発生させる
ために利用する固定の加算値を16ビットレジスタに格納して使います。
矩形波の波形を50%のDUTY比にする場合、周波数はオーバーフローで
生成できる周波数の1/2となります。
NCOが生成する矩形波のDUTY比を50%にしないでよいなら、生成周波数が
出力周波数にできます。
PIC12F1501のNCOモジュールでは、DUTY比を50%にするブロックと
50%ではないブロックが含まれています。どちらを使うかは内部
レジスタで指定できます。
DUTY比が50%である矩形波を生成してみます。
ブロック図は、以下。
ブロック図から、50%のDUTY比をもった矩形波生成には
NxPFMビットを0に設定すればよいとわかります。
ブロック図を矩形波出力側から見ていくと、次のビットを
設定しなければならないと理解できます。
NCOモジュールから矩形波を出力するとき、反転して出力する
のか、そのまま出力するかの指定をNxPOLが担当。
NCOモジュールの矩形波出力をピンに接続するため、NxOEで指定。
実際に矩形波を出力するには、2カ所あるので、どちらにするか
を指定しなければなりません。
NCOは累算器のオーバーフローを利用するので、累算器に
指定した加算値を加えるトリガーが必要です。トリガー
にクロックを使うので、そのクロックソースをどれにする
かを選択します。
クロックソースは、以下。
- NCO1CLK
- LC1OUT
- Fosc
- HFINTOSC(16MHz)
NCO1CLKは、チップのRA5ピンから入力するクロック利用。
LC1OUTは、内部のCLCブロックから出力されたクロックを利用。
Foscは、内部のシステムクロックを利用。
内部にクロックジェネレータをもつとき、ジェネレータ関係
レジスタの設定値で、Foscは変わります。
HFINTOSCは、内部クロックジェネレータが生成する最高周波数
となり、PIC12F1501では16MHzです。
NCOモジュールを動かすために、初期化が必要なレジスタは以下。
割込みを利用しないときは、次のレジスタ類に値を設定。
- APFCON
- NCO1ACCU
- NCO1ACCH
- NCO1ACCL
- NCO1CLK
- NCO1CON
- NCO1INCH
- NCO1INCL
- TRISA
APFCONは、NCO1SELで生成した矩形波をRA5、RA1のどちらの
ピンに出力するのかを指定します。
NCO1INCH、NCO1INCLは、ダブルバッファ構造になっているため
生成する矩形波の周波数を任意のタイミングで変更可能。
NCO1CONのN1PFMビットを利用して、矩形波のDUTY比を50%と
します。
実験回路を次のようにして、動作確認してみました。
2個のプッシュスイッチで、NCO1INCの値を+100か−100します。
これで、矩形波の周波数を変更可能に。
COUTは、マイコンが動作していることを確認するため
タイマー割込みで、クロックを生成。
初期化処理は、次のようにまとめました。
#define NCOV 20970
void init_usr(void)
{
/* select 16MHz */
OSCCON = (0x0f << 3) | 0x03 ;
/* disable A/D converter */
ADCON0.ADON = OFF ;
ADCON2 = 0 ;
/* disable D/A converter */
DACCON0.DACEN = OFF ;
/* disable compare module */
CM1CON0.C1ON = OFF ;
CM1CON0.C1OE = OFF ;
/* I/O directions */
TRISA = 0x38 ; /* bit0,1,2 as output , others as input */
/* pull-up */
WPUA = 0x30 ;
/* initialize NCO */
{
/* initial displacement */
ncocnt = NCOV ;
send_displacement(ncocnt);
/* which pin */
APFCON.NCO1SEL = 0 ;
/* clear accumulator */
NCO1ACCU = 0x00 ;
NCO1ACCH = 0x00 ;
NCO1ACCL = 0x00 ;
/* select clock
output 1:1
input HFINTOSC (16 MHz)
*/
NCO1CLK = 0x00 ;
/* control */
NCO1CON = 0xf0 ;
}
/* initialize Timer 0 */
{
/*
16MHz/4 = 4MHz -> 4MHz/16 = 250kHz prescaler = 1:16
*/
OPTION_REG = 0x03 ;
/* 256 - 6 = 250 */
TMR0 = CNTBEGIN ;
/* enable timer0 overflow interrupt */
INTCON.TMR0IE = ON ;
}
/* enable general interrupt */
INTCON.GIE = ON ;
/* initialize variables */
hsft = 0 ;
dsft = 0 ;
timcnt = 0 ;
/* clear flags */
x_flags.DR = 0 ;
}
NCO1INCの値を変更するときに、専用関数を用意して
何をしているのかを把握しやすくしています。
typedef unsigned int UWORD ;
#define MASKFF 0xff
void send_displacement(UWORD x)
{
NCO1INCH = (x >> 8) & MASKFF ;
NCO1INCL = x & MASKFF ;
}
NCO1INCの値を増減するため、プッシュスイッチを使います。
シフトレジスタを利用して、変化を読み取りました。
if ( TFLAG == ON ) {
/* clear flag */
TFLAG = OFF ;
/* get switch state */
swstate = PORTA ^ MASKFF ;
/* shift */
hsft <<= 1 ;
dsft <<= 1 ;
/* mask */
hsft &= MASK07 ;
dsft &= MASK07 ;
/* update LSB */
if ( swstate & H_BIT ) { hsft |= ON ; }
if ( swstate & L_BIT ) { dsft |= ON ; }
/* judge */
if ( hsft == ON ) { HFLAG = ON ; }
if ( dsft == ON ) { DFLAG = ON ; }
}
値変更には、フラグを用意し専門の手続きが担当します。
/* increment 100 */
if ( HFLAG == ON ) {
/* clear flag */
HFLAG = OFF ;
/* update */
ncocnt += DISPX ;
/* upper limit */
if ( ncocnt > MAXDISPX ) { ncocnt = MAXDISPX ; }
/* update NCO module */
send_displacement(ncocnt);
}
/* increment 10 */
if ( DFLAG == ON ) {
/* clear flag */
DFLAG = OFF ;
/* update */
ncocnt -= DISPX ;
/* lower limit */
if ( ncocnt < MINDISPX ) { ncocnt = MINDISPX ; }
/* update NCO module */
send_displacement(ncocnt);
}
NCO1INCは16ビットレジスタなので、0から65535までの値を
設定できますが、上限、下限を用意して、指定した範囲で
動くようにリミッターを入れてあります。
シフトレジスタを動かすためのトリガーに、タイマー0の割込みを
利用します。割込みが発生時、RA2に1か0を出力してマイコンが
動作していることがわかるようにしておきます。
割込みハンドラは、以下。
void interrupt(void)
{
/* generate 1kHz = 1ms => 1Hz */
if ( INTCON.T0IF == ON ) {
/* clear flag */
INTCON.T0IF = OFF ;
/* initialize */
TMR0 = CNTBEGIN ;
/* increment */
timcnt++ ;
/* judge */
if ( timcnt == CNTMAX ) {
/* clear */
timcnt = 0 ;
/* set flag */
TFLAG = ON ;
}
/* monitor */
MFOUT = timcnt & ON ;
}
}
使う部材が揃ったので、全体をまとめます。
typedef unsigned char UBYTE ;
typedef unsigned int UWORD ;
typedef unsigned long ULONG ;
typedef union {
struct {
unsigned char B0:1;
unsigned char B1:1;
unsigned char B2:1;
unsigned char B3:1;
unsigned char B4:1;
unsigned char B5:1;
unsigned char B6:1;
unsigned char B7:1;
} BIT ;
unsigned char DR ;
} FLAGSP ;
volatile FLAGSP x_flags ;
#define NCOV 20970
#define MAXDISPX 65530
#define MINDISPX 10000
#define DISPX 100
#define MFOUT PORTA.F2
#define HFLAG x_flags.BIT.B0
#define DFLAG x_flags.BIT.B1
#define EFLAG x_flags.BIT.B2
#define TFLAG x_flags.BIT.B3
#define OFF 0
#define ON OFF+1
#define CNTBEGIN 6
#define CNTMAX 100
#define MASKFF 0xff
#define MASK07 0x07
#define H_BIT 0x20
#define L_BIT 0x10
volatile UBYTE timcnt ;
volatile UBYTE swstate ;
volatile UWORD ncocnt ;
volatile UBYTE hsft ;
volatile UBYTE dsft ;
/* function prototype */
void init_usr(void);
void send_displacement(UWORD x);
/* interrupt handler */
void interrupt(void)
{
/* generate 1kHz = 1ms => 1Hz */
if ( INTCON.T0IF == ON ) {
/* clear flag */
INTCON.T0IF = OFF ;
/* initialize */
TMR0 = CNTBEGIN ;
/* increment */
timcnt++ ;
/* judge */
if ( timcnt == CNTMAX ) {
/* clear */
timcnt = 0 ;
/* set flag */
TFLAG = ON ;
}
/* monitor */
MFOUT = timcnt & ON ;
}
}
void main(void)
{
/* initialize */
init_usr() ;
/* endless loop */
while ( ON ) {
/* debouncing */
if ( TFLAG == ON ) {
/* clear flag */
TFLAG = OFF ;
/* get switch state */
swstate = PORTA ^ MASKFF ;
/* shift */
hsft <<= 1 ;
dsft <<= 1 ;
/* mask */
hsft &= MASK07 ;
dsft &= MASK07 ;
/* update LSB */
if ( swstate & H_BIT ) { hsft |= ON ; }
if ( swstate & L_BIT ) { dsft |= ON ; }
/* judge */
if ( hsft == ON ) { HFLAG = ON ; }
if ( dsft == ON ) { DFLAG = ON ; }
}
/* increment 100 */
if ( HFLAG == ON ) {
/* clear flag */
HFLAG = OFF ;
/* update */
ncocnt += DISPX ;
/* upper limit */
if ( ncocnt > MAXDISPX ) { ncocnt = MAXDISPX ; }
/* update NCO module */
send_displacement(ncocnt);
}
/* increment 10 */
if ( DFLAG == ON ) {
/* clear flag */
DFLAG = OFF ;
/* update */
ncocnt -= DISPX ;
/* lower limit */
if ( ncocnt < MINDISPX ) { ncocnt = MINDISPX ; }
/* update NCO module */
send_displacement(ncocnt);
}
}
}
/* define function body */
void init_usr(void)
{
/* select 16MHz */
OSCCON = (0x0f << 3) | 0x03 ;
/* disable A/D converter */
ADCON0.ADON = OFF ;
ADCON2 = 0 ;
/* disable D/A converter */
DACCON0.DACEN = OFF ;
/* disable compare module */
CM1CON0.C1ON = OFF ;
CM1CON0.C1OE = OFF ;
/* I/O directions */
TRISA = 0x38 ; /* bit0,1,2 as output , others as input */
/* pull-up */
WPUA = 0x30 ;
/* initialize NCO */
{
/* initial displacement */
ncocnt = NCOV ;
send_displacement(ncocnt);
/* which pin */
APFCON.NCO1SEL = 0 ;
/* clear accumulator */
NCO1ACCU = 0x00 ;
NCO1ACCH = 0x00 ;
NCO1ACCL = 0x00 ;
/* select clock
output 1:1
input HFINTOSC (16 MHz)
*/
NCO1CLK = 0x00 ;
/* control */
NCO1CON = 0xf0 ;
}
/* initialize Timer 0 */
{
/*
16MHz/4 = 4MHz -> 4MHz/16 = 250kHz prescaler = 1:16
*/
OPTION_REG = 0x03 ;
/* 256 - 6 = 250 */
TMR0 = CNTBEGIN ;
/* enable timer0 overflow interrupt */
INTCON.TMR0IE = ON ;
}
/* enable general interrupt */
INTCON.GIE = ON ;
/* initialize variables */
hsft = 0 ;
dsft = 0 ;
timcnt = 0 ;
/* clear flags */
x_flags.DR = 0 ;
}
void send_displacement(UWORD x)
{
NCO1INCH = (x >> 8) & MASKFF ;
NCO1INCL = x & MASKFF ;
}
マルチメータの周波数カウンタで動作を確認してみました。
DUTY比が50%でない場合の実験を考えます。
ブロック図は、以下。
累算器のオーバーフローでSRラッチの出力をセットし
Ripple Counterの出力でSRラッチの出力をクリアする
ので、DUTY比は50%になりません。
タイミングチャートで、動作を見ていきます。
SRラッチをクリアするのは、Ripple Counterの出力なので
オーバーフローの周波数が、NCO出力矩形波の周波数と一致
します。
ブロック図からSRラッチのセット、クリアの優先順位は
セットにあります。Ripple Counterのクロックが有効に
なるのは、SRラッチのQ出力との論理積になっているので
Q出力が'H'のときに限定されているから。
Ripple Counterは、NCOxCLOCKを分周して生成するので
レジスタNCO1CLKの上位3ビットで、分周比を指定。
3ビットの値を、2のベキ乗の指数部に設定するので
矩形波のパルス幅を設定することと等価。
16MHzをNCO1CLOCKとした場合、どの程度になるかを計算すると以下。
- 0 16MHz = (1/16)us
- 1 8MHz = (1/8)us
- 2 4MHz = (1/4)us
- 3 2MHz = (1/1)us
- 4 1MHz = (1/1)us
- 5 500kHz = 2us
- 6 250kHz = 4us
- 7 125kHz = 8us
パルス幅から、分周比推定も可能と言えます。
50%DUTYとそうでない場合を選択できるようにするため
トグルスイッチを追加して実験してみます。
回路は、以下。
ファームウエアは、以下。
typedef unsigned char UBYTE ;
typedef unsigned int UWORD ;
typedef unsigned long ULONG ;
typedef union {
struct {
unsigned char B0:1;
unsigned char B1:1;
unsigned char B2:1;
unsigned char B3:1;
unsigned char B4:1;
unsigned char B5:1;
unsigned char B6:1;
unsigned char B7:1;
} BIT ;
unsigned char DR ;
} FLAGSP ;
volatile FLAGSP x_flags ;
#define NCOV 20970
#define MAXDISPX 65530
#define MINDISPX 10000
#define DISPX 100
#define MFOUT PORTA.F2
#define SD50 PORTA.F3
#define HFLAG x_flags.BIT.B0
#define DFLAG x_flags.BIT.B1
#define EFLAG x_flags.BIT.B2
#define TFLAG x_flags.BIT.B3
#define OFF 0
#define ON OFF+1
#define CNTBEGIN 6
#define CNTMAX 100
#define MASKFF 0xff
#define MASK07 0x07
#define H_BIT 0x20
#define L_BIT 0x10
volatile UBYTE timcnt ;
volatile UBYTE swstate ;
volatile UWORD ncocnt ;
volatile UBYTE hsft ;
volatile UBYTE dsft ;
/* function prototype */
void init_usr(void);
void send_displacement(UWORD x);
/* interrupt handler */
void interrupt(void)
{
/* generate 1kHz = 1ms => 1Hz */
if ( INTCON.T0IF == ON ) {
/* clear flag */
INTCON.T0IF = OFF ;
/* initialize */
TMR0 = CNTBEGIN ;
/* increment */
timcnt++ ;
/* judge */
if ( timcnt == CNTMAX ) {
/* clear */
timcnt = 0 ;
/* set flag */
TFLAG = ON ;
}
/* monitor */
MFOUT = timcnt & ON ;
}
}
void main(void)
{
/* initialize */
init_usr() ;
/* endless loop */
while ( ON ) {
/* debouncing */
if ( TFLAG == ON ) {
/* clear flag */
TFLAG = OFF ;
/* get switch state */
swstate = PORTA ^ MASKFF ;
/* shift */
hsft <<= 1 ;
dsft <<= 1 ;
/* mask */
hsft &= MASK07 ;
dsft &= MASK07 ;
/* update LSB */
if ( swstate & H_BIT ) { hsft |= ON ; }
if ( swstate & L_BIT ) { dsft |= ON ; }
/* judge */
if ( hsft == ON ) { HFLAG = ON ; }
if ( dsft == ON ) { DFLAG = ON ; }
}
/* increment 100 */
if ( HFLAG == ON ) {
/* clear flag */
HFLAG = OFF ;
/* update */
ncocnt += DISPX ;
/* upper limit */
if ( ncocnt > MAXDISPX ) { ncocnt = MAXDISPX ; }
/* update NCO module */
send_displacement(ncocnt);
}
/* increment 10 */
if ( DFLAG == ON ) {
/* clear flag */
DFLAG = OFF ;
/* update */
ncocnt -= DISPX ;
/* lower limit */
if ( ncocnt < MINDISPX ) { ncocnt = MINDISPX ; }
/* update NCO module */
send_displacement(ncocnt);
}
}
}
/* define function body */
void init_usr(void)
{
/* select 16MHz */
OSCCON = (0x0f << 3) | 0x03 ;
/* disable A/D converter */
ADCON0.ADON = OFF ;
ADCON2 = 0 ;
/* disable D/A converter */
DACCON0.DACEN = OFF ;
/* disable compare module */
CM1CON0.C1ON = OFF ;
CM1CON0.C1OE = OFF ;
/* I/O directions */
TRISA = 0x3c ; /* bit0,1 as output , others as input */
/* pull-up */
WPUA = 0x34 ;
/* initialize NCO */
{
/* initial displacement */
ncocnt = NCOV ;
send_displacement(ncocnt);
/* which pin */
APFCON.NCO1SEL = 0 ;
/* clear accumulator */
NCO1ACCU = 0x00 ;
NCO1ACCH = 0x00 ;
NCO1ACCL = 0x00 ;
/* select clock
output 1:1
input HFINTOSC (16 MHz)
ripple counter 1:4
*/
NCO1CLK = 0x40 ;
/* control */
NCO1CON = 0xf0 ;
if ( SD50 == ON ) { NCO1CON |= ON ; }
}
/* initialize Timer 0 */
{
/*
16MHz/4 = 4MHz -> 4MHz/16 = 250kHz prescaler = 1:16
*/
OPTION_REG = 0x03 ;
/* 256 - 6 = 250 */
TMR0 = CNTBEGIN ;
/* enable timer0 overflow interrupt */
INTCON.TMR0IE = ON ;
}
/* enable general interrupt */
INTCON.GIE = ON ;
/* initialize variables */
hsft = 0 ;
dsft = 0 ;
timcnt = 0 ;
/* clear flags */
x_flags.DR = 0 ;
}
void send_displacement(UWORD x)
{
NCO1INCH = (x >> 8) & MASKFF ;
NCO1INCL = x & MASKFF ;
}
次の基板でテストします。
(under construction)
目次
前
次