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ビット
パリティ なし
フロー制御 なし
操作すると、次のようになります。
これで位相アキュムレータで利用する加算値を
与えると、意図した周波数クロックが出てきます。
目次
前
次