目次

NCO(Nemerically Controlled Oscillators)

 NCOは、固定周波数クロックから、任意の周波数を生成します。

 エアコンのモータの回転数を細かく制御したり
 ラジオのデジタルチューニングのような周波数
 を変えるために使われています。

 NCOは、位相アキュムレータを用意し固定周波数で
 アキュムレータに値を加えていき、飽和値に達する
 時間を制御します。



 オーバーフローする時間を、加算値で変更することで
 周波数を変えています。2進数の24ビットを、位相
 アキュムレータのビット数にして、加算値用レジスタ
 のビットサイズを20とすると、加算値を最大にして
 16回オーバーフローさせられます。

 24−20=4なので、2の4乗である16になります。

 加算値用レジスタのサイズを20ビットとすると設定
 できる最大値は、1048575で位相アキュムレータに16回
 加算すると、オーバーフローします。
 加算に16MHzのクロックを使うと、最高1MHzの周波数に
 対応します。

 位相アキュムレータを24ビット、加算用レジスタを20ビット
 周波数をmMHzとして生成できる周波数は、以下で計算します。




 オーバーフローさせて周波数を可変するので、上の式の分母に
 加算値用レジスタの設定値を代入すると、生成できる周波数に
 なります。


  16MHzを位相アキュムレータの加算のクロックに使うと
 100kHzを生成したいとき、次のように計算します。

    (2^24/N)x(1/16) us = 100x(10^3) us
      ↓
    (2^20/N) = (10^5)
      ↓
    (2^20)/(10^5)=N
      ↓
    10.48=N

 端数がでるので、11あるいは10を加算用レジスタに設定します。

 位相アキュムレータと加算用レジスタのビットサイズは固定で
 加算用クロックを2のN乗になるようにすれば、端数がなくなる
 ようにできます。

 CPLD、FPGAで実現することを考えていくことに。

 加算値を計算してCPLD/FPGAに渡せばよいので、マイコンで
 計算して3バイトずつ設定するようにします。



 加算値を設定するバスインタフェースは、以下。

  -- synchronizer
  process (nRESET,CLOCK)
  begin
    if ( nRESET = '0' ) then
      iTRG_SFT <= "000" ;
    elsif rising_edge(CLOCK) then
      iTRG_SFT <= iTRG_SFT(1 downto 0) & TRG ;
    end if ;
  end process ;
  iTRG <= '1' when ( iTRG_SFT = "011" ) else '0' ;

  -- bus interface
  process (nRESET,CLOCK)
  begin
    if ( nRESET = '0' ) then
      iREGX <= (others => '0') ;
    elsif rising_edge(CLOCK) then
      if ( iTRG = '1' ) then
        -- upper
        if ( iDsel = "10" ) then
          iREGX(20 downto 16) <= iDin(4 downto 0) ;
        -- upper
        elsif ( iDsel = "01" ) then
          iREGX(15 downto 8) <= iDin ;
        -- lower
        else
          iREGX( 7 downto 0) <= iDin ;
        end if;
      end if ;
    end if ;
  end process ;

 シンクロナイザで、トリガーをつかまえて
 指定バイトを更新します。

 位相アキュムレータは、24ビットの加算回路で実現。

  process (nRESET,CLOCK)
  begin
    if ( nRESET = '0' ) then
      iACC <= (others => '0') ;
      iREG <= (others => '0') ;
    elsif rising_edge(CLOCK) then
      if ( iACC(24) = '1' ) then
        iACC <= (others => '0') ;
        iREG <= iREGX ;
      else
        iACC <= iACC + ("0000" & iREG);
      end if ;
    end if ;
  end process ;
  iOVF <= iACC(24) ;

 位相アキュムレータのビットサイズを25としMSB(Most Significant Bit)を
 オーバーフローのビットとして取り出すと、指定周波数の矩形波になります。

 DUTY比が50%の矩形波が必要なら、オーバーフローのビットを
 利用してフリップフロップを動かします。

  process (iOVF)
  begin
    if rising_edge(iOVF) then
      iCNT50 <= not iCNT50 ;
    end if ;
  end process ;
  iOVF50 <= iCNT50 ;

 VHDLコードにまとめると、以下。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity nco is
  port (
    -- system
    nRESET : in  std_logic ;
    CLOCK  : in  std_logic ; -- 32MHz
    -- trigger
    TRG    : in  std_logic ;
    -- selec register number
    Dsel   : in  std_logic_vector(1 downto 0) ;
    -- data
    Din    : in  std_logic_vector(7 downto 0) ;
    -- monitor clock
    MOVF   : out std_logic --;
  ) ;
end nco;

architecture Behavioral of nco is
  -- synchronizer
  signal iTRG     : std_logic ;
  signal iTRG_SFT : std_logic_vector(2 downto 0) ;
  -- bus interface
  signal iDin  : std_logic_vector(7 downto 0) ;
  signal iDsel : std_logic_vector(1 downto 0) ;
  -- phase accumlater
  signal iACC : std_logic_vector(24 downto 0) ;
  signal iREG : std_logic_vector(20 downto 0) ;
  -- inrementer
  signal iREGX : std_logic_vector(20 downto 0) ;
  signal iOVF  : std_logic ;
begin
  -- input
  iDin  <= Din ;
  iDsel <= Dsel ;

  -- output
  MOVF <= iOVF ;

  -- synchronizer
  process (nRESET,CLOCK)
  begin
    if ( nRESET = '0' ) then
      iTRG_SFT <= "000" ;
    elsif rising_edge(CLOCK) then
      iTRG_SFT <= iTRG_SFT(1 downto 0) & TRG ;
    end if ;
  end process ;
  iTRG <= '1' when ( iTRG_SFT = "011" ) else '0' ;

  -- bus interface
  process (nRESET,CLOCK)
  begin
    if ( nRESET = '0' ) then
      iREGX <= (others => '0') ;
    elsif rising_edge(CLOCK) then
      if ( iTRG = '1' ) then
        -- upper
        if ( iDsel = "10" ) then
          iREGX(20 downto 16) <= iDin(4 downto 0) ;
        -- upper
        elsif ( iDsel = "01" ) then
          iREGX(15 downto 8) <= iDin ;
        -- lower
        else
          iREGX( 7 downto 0) <= iDin ;
        end if;
      end if ;
    end if ;
  end process ;

  -- adder
  process (nRESET,CLOCK)
  begin
    if ( nRESET = '0' ) then
      iACC <= (others => '0') ;
      iREG <= (others => '0') ;
    elsif rising_edge(CLOCK) then
      if ( iACC(24) = '1' ) then
        iACC <= (others => '0') ;
        iREG <= iREGX ;
      else
        iACC <= iACC + ("0000" & iREG);
      end if ;
    end if ;
  end process ;
  iOVF <= iACC(24) ;

end Behavioral;

 XilinxのCoolRunnerIIに回路を入れてみると、81マクロセル数
 となりました。

 UCFファイルは、以下。

# system
NET "CLOCK"  LOC = "P22" ;
NET "nRESET" LOC = "P99" ;

# BUS data 
NET "Din<0>"  LOC = "P6"  ;
NET "Din<1>"  LOC = "P7"  ;
NET "Din<2>"  LOC = "P8"  ;
NET "Din<3>"  LOC = "P9"  ;
NET "Din<4>"  LOC = "P10" ;
NET "Din<5>"  LOC = "P11" ;
NET "Din<6>"  LOC = "P12" ;
NET "Din<7>"  LOC = "P13" ;

# register selector
NET "Dsel<0>"  LOC = "P15" ;
NET "Dsel<1>"  LOC = "P16" ;

# latch trigger
NET "TRG"  LOC = "P14" ;

# NCO output
NET "MOVF"  LOC = "P95" ;

 位相アキュムレータの加算値を計算するのは
 マイコンに担当させるか、予めspread sheet
 (表計算ソフト)で求めて、ROMの中に入れる
 のがよいかも知れません。

 最近のFPGAは、内部にデジタルPLLをもち、100MHz程度の
 周波数クロックは、内部で生成できます。このクロックを
 位相アキュムレータの加算に使うことに。

 Tcl/Tkで、7100kHzから7199kHzを生成するための
 加算値を計算するスクリプトを考えます。
 位相アキュムレータの加算クロックに、100MHzを使います。

set fdiv [expr 100 * pow(10,3)]
set fmul [expr pow(2,24)]
#
for {set i 100} {$i < 200} {incr i} {
  # target frequency
  set fx [expr $i+7000]
  # convert frequency with kHz format
  set fxx [format "%d(kHz)" $fx]
  # calculate delta
  set n [expr ($fx * $fmul) / $fdiv ]
  # convert delta as integer
  set nn [format "%.0f" $n]
  # divide
  set q  [expr $nn / 256]
  set n2 [expr $nn % 256]
  set n1 [expr $q % 256]
  set n0 [expr $q / 256]
  # convert hexadecimal
  set sn0 [format "%02X" $n0]
  set sn1 [format "%02X" $n1]
  set sn2 [format "%02X" $n2]
  puts "$fxx -> $nn : $sn0 $sn1 $sn2"
}

 I/Oリダイレクトを利用して、ファイルに3バイト値
 保存してみると、以下。



 ファイル内容は、次のようになります。

7100(kHz) -> 1191182 : 12 2D 0E
7101(kHz) -> 1191350 : 12 2D B6
 :
7198(kHz) -> 1207624 : 12 6D 48
7199(kHz) -> 1207792 : 12 6D F0

 必要な3バイトはAWKを利用して取出せばよいでしょう。
 ROMに保存する場合は、アドレスが4飛びで変化する方が
 扱いやすいので、次のスクリプトで取り出します。

awk '{ print "00",$5,$6,$7 }' ttc.txt > ttcx.txt{enter}

 このスクリプトで4バイトごとに値を生成すると
 次のようになります。

00 12 2D 0E
00 12 2D B6
 :
00 12 6D 48
00 12 6D F0

 4バイトx200=800バイトになるので、FPGAに内蔵
 されているブロックRAMの利用で、充分格納可能
 です。

 ARMプロセッサを利用し、端末から周波数を設定すると
 加算値を求めて出力するプログラムを書きました。



 ソースコードは、以下。

#include <ADuC7026.h>

#define OFF 0
#define ON  OFF+1

/* data definitions */
typedef unsigned char  UBYTE ;
typedef   signed char  SBYTE ;
typedef unsigned short UWORD ;
typedef   signed short SWORD ;
typedef unsigned long  ULONG ;
typedef   signed long  SLONG ;

void  IRQ_Handler(void) __irq;
void  init_usr(void);

#define MASKFF 0xFF
#define MASK0F 0x0F
#define MASKF0 0xF0
#define MASK80 0x80
#define MASK40 0x40
#define MASK20 0x20
#define MASK10 0x10
#define MASK08 0x08
#define MASK04 0x04
#define MASK02 0x02
#define MASK01 0x01
#define MASK07 0x07

typedef union {
  struct {
    unsigned char B7:1;
    unsigned char B6:1;
    unsigned char B5:1;
    unsigned char B4:1;
    unsigned char B3:1;
    unsigned char B2:1;
    unsigned char B1:1;
    unsigned char B0:1;
  } BIT ;
  unsigned char DR ;
} FLAGSP ;

void  rs_putchar(UBYTE x);
void  crlf(void);
void  rs_puts(UBYTE *x);
UBYTE get_hex(UBYTE x);
UBYTE get_hex_2(UBYTE *x);

void  show_help(void);
void  show_info(void);
void  impress_data(UBYTE x);
void  impress_adr(UBYTE x);
void  impress_trigger(void);
void  send_data(void);
void  send_fixed_data(void);

/* RTOS */
#define TSK_ID_MAX 5

#define TSK_ID0 0
#define TSK_ID1 1
#define TSK_ID2 2
#define TSK_ID3 3

#define TTS_SUSPEND 0
#define TTS_WAIT    TTS_SUSPEND+1
#define TTS_READY   TTS_SUSPEND+2

typedef struct {
  void  (*tsk)(void);
  UWORD wcount ;
} TCBP ;

volatile TCBP tcbx[TSK_ID_MAX] ;
volatile TCBP pcur_tsk ;

volatile FLAGSP x_flags ;

#define WFLAG x_flags.BIT.B0
#define UFLAG x_flags.BIT.B1

volatile UWORD ready ;
volatile UWORD suspend ;
volatile UWORD waitq ;
volatile UBYTE run_tsk ;

void  init_os(void);
void  update_tsk_state(UBYTE tid,UBYTE sta);
void  cre_tsk(UBYTE tid,void (*tsk)(void));
void  sta_tsk(UBYTE tid,UBYTE sta);
void  rsm_tsk(UBYTE tid);
void  sus_tsk(UBYTE tid);
void  slp_tsk(void);
void  wai_tsk(UWORD x);
UBYTE is_tsk_ready(UBYTE tid);
void  timer_handler(void);

void  tsk0_proc(void);
void  tsk1_proc(void);
void  tsk2_proc(void);
void  tsk3_proc(void);

/* global variables */
volatile ULONG timtick ;
volatile UBYTE sbuf[8] ;
volatile UBYTE sindex ;
volatile UBYTE cmd ;

volatile UBYTE asc_hex[16] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};

volatile UBYTE dd[3] ;
volatile UBYTE xcnt3 ;

int main(void)
{
	/* initialize user */
	init_usr();
	/* initialize user */
	init_os();	
	/* show message */
	rs_puts("Hello"); crlf();
	/* endless loop */
	run_tsk = TSK_ID0 ;
	while(ON) {
		/* RTOS handling */
		pcur_tsk = tcbx[run_tsk] ;
		if ( is_tsk_ready( run_tsk ) ) { (*(pcur_tsk.tsk))(); }
		run_tsk++;
		if ( run_tsk == TSK_ID_MAX ) { run_tsk = TSK_ID0 ; }
		/* 10ms timer */
		if ( WFLAG == ON ) {
			/* clear */
			WFLAG = OFF ;
			/* call */
			timer_handler();
		}
	}
	/* dummy return */
	return (0);
}

void IRQ_Handler(void) __irq
{
  volatile UBYTE ch ;
  /* judge UART receive interruption */
  if ( (IRQSTA & UART_BIT) == UART_BIT ) {
    /* judge */
    if ( COMSTA0 & 1 ) {
      /* clear flag */
      ch = COMRX ;
      *(sbuf+sindex) = ch ;
      sindex++ ;      
      if ( ch == '\r' ) {
        sindex = 0 ;
        UFLAG = ON ;
      }
    }
  }
  /* judge timer3 interruption (1ms) */
  if ( (IRQSTA & WATCHDOG_TIMER_BIT) == WATCHDOG_TIMER_BIT ) {
    /* clear timer3 interrupt flag */
    T3CLRI = 0xff ;
    /* increment timer counter */
    timtick++ ;
    /* set flag */
	if ( (timtick & 15) == 10 ) { WFLAG = ON ; }
  }
}

void init_usr(void)
{
	/* select clock 10.44MHz 
       initialized in start up routine
	*/
	PLLKEY1 = 0xaa ;
	PLLCON  = 0x01 ;
	PLLKEY2 = 0x55 ;
	/* power control
       initialized in start up routine 
	*/
	/* clear flags */
	x_flags.DR = 0;
	/* clear counter */
	sindex  = 0 ;
	timtick = 0 ;
	/* initialize UART */
	{
		/* set baud rate 19200 bps CD = 2 */
		COMCON0 = 0x80 ; /* select COMDIV1 and COMDIV0 */
		COMDIV0 = 0x11 ;
		COMDIV1 = 0x00 ;
		/* set conditions */
		COMCON0 = 0x03 ; /* select COMRX and COMTX , 8bit data , 1 stop bit , no parity */
		/* enable interrupt */
		COMIEN0 = 0x01 ; /* ERBFI */
	}
	/* P0 */
	GP0DAT = 0xDF3C0000 ;
	/* P1 */
    GP1CON = 0x00000011 ; /* use UART */ 
    GP1DAT = 0xfe000000 ;
	/* P2 */    
    GP2DAT = 0x00000000 ; /* all bits inputs */
	/* P3 */
    GP3DAT = 0xff000000 ; /* all bits outputs */
	/* P4 */
    GP4DAT = 0xff000000 ; /* all bits outputs */
	/* initialize timer 3 (1s) */
	T3LD  = 33   ; /* (32.768kHz / 32) = about 1 kHz */
	T3CON = 0xc0 ; /* enable , cyclic , 1/1 */ 
	/* enable timer 3 interrupt and UART interrupt */
	IRQEN = WATCHDOG_TIMER_BIT | UART_BIT ;
}

/* UART putchar */
void  rs_putchar(UBYTE x)
{
	/* ? transmmit buffer empty */
	while( (COMSTA0 & 0x40) == 0 ) ;
	/* set value */
	COMTX = x ;
}

/* carriage return and line feed */
void  crlf(void)
{
	rs_putchar('\r');
	rs_putchar('\n');
}

/* UART puts */
void  rs_puts(UBYTE *x)
{
	while ( *x != '\0' ) {
		rs_putchar( *x ) ;
		x++ ;
	}
}

/* convert ASCII to number */
UBYTE get_hex(UBYTE x)
{
	UBYTE result ;
	/* default */
	result = 0 ;
	/* judge */
	if ( '0' <= x && x <= '9' ) { result = x - '0' ; }
	if ( 'A' <= x && x <= 'F' ) { result = x - 'A' + 10 ; }
	if ( 'a' <= x && x <= 'f' ) { result = x - 'a' + 10 ; }

	return result ;
}

UBYTE get_hex_2(UBYTE *x)
{
	UBYTE dh ;
	UBYTE dl ;
	/* separate & convert */
	dh = get_hex( *x );
	dl = get_hex( *(x+1) );
	/* concatenate */
	return( (dh << 4) | dl );
}

/* show help */
void  show_help(void)
{
	rs_puts("? help")              ; crlf();
	rs_puts("L load 3 byte")       ; crlf();
	rs_puts("F load fixed 3 byte") ; crlf();
	rs_puts("D set 3byte data")    ; crlf();
	rs_puts("d show 3byte data")   ; crlf();
}

void  show_info(void)
{
	UBYTE msg[7];
	UBYTE i ;
	/* default */
	*(msg+6) = '\0' ;
	/* loop */
	for ( i = 0 ; i < 3 ; i++ ) {
		*(msg+2*i) = asc_hex[(*(dd+0) >> 4) & MASK0F] ;
		*(msg+2*i+1) = asc_hex[ *(dd+0) & MASK0F] ;
	}
	/* show */
	rs_puts(msg);
	/* new line */
	crlf();
}

void  impress_data(UBYTE x)
{
	GP3DAT &= 0xff00ffff ;
	GP3DAT |= (x << 16);
}

void  impress_adr(UBYTE x)
{
	GP4DAT &= 0xfff8ffff ;
	GP4DAT |= (x << 16) ;
}

void  impress_trigger(void)
{
	GP4DAT |=  0x00040000 ;
	GP4DAT &= ~0x00040000 ;
}

void  send_data(void)
{
	UBYTE i;
	UBYTE j;
	/* loop */
	for ( i = 0 ; i < 3 ; i++ ) {
		/* set pointer */
		j = 2 - i ;
		/* impress */
		impress_data( *(dd+j) ) ;
		/* send register number */
		impress_adr(j) ;
		/* send trigger */
		impress_trigger() ;
	}
	/* impress */
	impress_data( 0 ) ;
}

void  send_fixed_data(void)
{
	UBYTE i;
	UBYTE j;
	/* loop */
	for ( i = 0 ; i < 3 ; i++ ) {
		/* set pointer */
		j = 2 - i ;
		/* impress */
		impress_data( 0xff ) ;
		/* send register number */
		impress_adr(j) ;
		/* send trigger */
		impress_trigger() ;
	}
	/* impress */
	impress_data( 0 ) ;
}

void init_os(void)
{
	/* clear RTOS values */
	ready = suspend = waitq = 0 ;
	/* TASK create */
	cre_tsk(TSK_ID0,tsk0_proc);
	cre_tsk(TSK_ID1,tsk1_proc);
	cre_tsk(TSK_ID2,tsk2_proc);
	cre_tsk(TSK_ID3,tsk3_proc);
	/* initialize task state */
	sta_tsk(TSK_ID0,TTS_READY);
	sta_tsk(TSK_ID1,TTS_SUSPEND);
	sta_tsk(TSK_ID2,TTS_SUSPEND);
	sta_tsk(TSK_ID3,TTS_READY);
	/* clear flag */
	WFLAG = OFF ;
}

#define MASKFFFF 0xffff

void  update_tsk_state(UBYTE tid,UBYTE sta)
{
	UWORD tmp ;
	UWORD tmpx ;
	/* get bit pattern */
	tmp  = (1 << tid);
	tmpx = tmp ^ MASKFFFF ;
	/* READY */
	if ( sta == TTS_READY ) {
		ready   |= tmp ;
		suspend &= tmpx;
		waitq   &= tmpx;
	}
	/* SUSPEND */
	if ( sta == TTS_SUSPEND ) {
		ready   &= tmpx;
		suspend |= tmp;
		waitq   &= tmpx;
	}
	/* WAIT */
	if ( sta == TTS_WAIT ) {
		ready   &= tmpx;
		suspend &= tmpx;
		waitq   |= tmp;
	}
}

void  cre_tsk(UBYTE tid,void (*tsk)(void))
{
	tcbx[tid].tsk    = tsk ;
	tcbx[tid].wcount = 0 ;
}

void  sta_tsk(UBYTE tid,UBYTE sta)
{
	update_tsk_state(tid,sta);
}

void  rsm_tsk(UBYTE tid)
{
	update_tsk_state(tid,TTS_READY);
}

void  sus_tsk(UBYTE tid)
{
	update_tsk_state(tid,TTS_SUSPEND);
}

void  slp_tsk(void)
{
	sus_tsk(run_tsk);
}

void  wai_tsk(UWORD x)
{
	update_tsk_state(run_tsk,TTS_WAIT);
	tcbx[run_tsk].wcount = x ;
}

UBYTE is_tsk_ready(UBYTE tid)
{
	return( ready & (1 << tid) ) ;
}

void  timer_handler(void)
{
	UBYTE i ;
	UWORD xtmp ;
	for ( i = 0 ; i < TSK_ID_MAX ; i++ ) {
		/* judge */
		if ( waitq & (1 << i) ) {
 			/* decrement */
			xtmp = tcbx[i].wcount ;
			xtmp-- ;
			tcbx[i].wcount = xtmp ;
			/* resume */
			if ( xtmp == 0 ) { rsm_tsk(i); }
		}
	}
}

/* SCI handling */
void  tsk0_proc(void)
{
	UBYTE i ;
	UBYTE j ;

	if ( UFLAG == ON ) {
		/* clear flag */
		UFLAG = OFF ;
		/* new line */
		crlf();
		/* judge */
		cmd = *(sbuf+0) ;
		/* help */
		if ( cmd == '?' ) { show_help(); }
		/* send data */
		if ( cmd == 'L' ) {
			rsm_tsk( TSK_ID1 );
		}
		/* store data */
		if ( cmd == 'D' ) {
			for ( i = 0 ; i < 3 ; i++ ) {
				j = 2 * i + 1 ;
				*(dd+i) = get_hex_2( sbuf+j ) ;
			}
		}
		/* show data */
		if ( cmd == 'd' ) { show_info(); }
	}
}

/* load */
void  tsk1_proc(void)
{
	/* send */
	send_data();
	/* exit */
	slp_tsk() ;
}

/* fixed value */
void  tsk2_proc(void)
{
	/* send */
	send_fixed_data();
	/* exit */
	slp_tsk() ;
}

/* monitor */
void  tsk3_proc(void)
{
	/* judge */
	GP4DAT &= ~(0x80 << 16);
	if ( xcnt3 & ON ) { GP4DAT |= (0x80 << 16); }
	/* increment */
	xcnt3++ ;
	/* delay 100ms */
	wai_tsk( 100 );
}

 ポート3は8ビットバス出力、ポート4をレジスタアドレスと
 トリガーを出力するようにします。

 ポート4のピンアサインは、以下。

P47 LED
P46 
P45 
P44 
P43 trigger
P42 
P41 register address 1
P40 register address 0

 UCFファイルで指定した、トリガーとレジスタアドレスの
 配置に合わせています。

 通信プロトコルは、以下としました。

通信速度    19200bps
データ長    8ビット
ストップビット 1ビット
パリティ    なし
フロー制御   なし

 操作すると、次のようになります。



 これで位相アキュムレータで利用する加算値を
 与えると、意図した周波数クロックが出てきます。


目次

inserted by FC2 system