目次

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は、チップのRA5ピンから入力するクロック利用。

 LC1OUTは、内部のCLCブロックから出力されたクロックを利用。

 Foscは、内部のシステムクロックを利用。
 内部にクロックジェネレータをもつとき、ジェネレータ関係
 レジスタの設定値で、Foscは変わります。

 HFINTOSCは、内部クロックジェネレータが生成する最高周波数
 となり、PIC12F1501では16MHzです。

 NCOモジュールを動かすために、初期化が必要なレジスタは以下。



 割込みを利用しないときは、次のレジスタ類に値を設定。

 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とした場合、どの程度になるかを計算すると以下。

 パルス幅から、分周比推定も可能と言えます。

 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)

目次

inserted by FC2 system