PDA

View Full Version : NCO Calculator



mpgmike
- 19th November 2017, 23:13
8502

I'm building a project that requires calculating numerous frequencies with the Numerically Controlled Oscillator (NCO) function in Fixed Duty Cycle Mode. Realizing that it would take me way too much time punching numbers into a calculator, I developed a simple app that does all the math work for me. I'm relatively new to the whole PC programming thing (Visual Studio 2015), but it works. Unzip the attached file, double click the .exe, and viola. If you right-click the .exe and select "Create shortcut", then drag the shortcut to your desktop (or selected folder), it will open from there. You must open/install it first, though.

How it works;
- Select your Fosc, either by typing it in, or using the arrows up/down. It increments in 4 MHz chunks, so non-realistic Fosc settings are possible.
- Enter the desired frequency
- Click the "Calculate" button
- It displays the decimal NCO1INCx value
- It displays the hex NCO1INCx value
- It displays the frequency when the value is rounded down
- It displays the frequency when the value is rounded up
- The Clear button resets for the next entry.

It also tracks if the frequency you enter is too large for the 20-Bit NCO1INC register and gives you a pop-up warning. If you are targeting a relatively low frequency like 60 Hz, the calculated NCO value (@ 4 MHz) is 31.45725, the HEX value is 1F, the frequency generated by using the rounded up 32 (hex 20) is 61.0352 Hz, the frequency generated by using the (rounded down) 31 (hex 1F) is 59.1279 Hz. With this information:
- NCO1INCL = $1F
- NCO1INCH = $00
- NCO1INCU = $00

If you wanted a frequency of 11,780,000 and entered that in the "Enter Frequency" box, it would give you a pop-up that reads, "Frequency Too High for Fosc, Press Clear Button and Try Again". However, if you change Fosc to 32 (MHz), you get:
- NCO Decimal = 772013.34375
- NCO Hex = BC7AD
- Result Rounded Up = 11780010.0136
- Result Rounded Down = 11779994.7548

With that in mind, you would set your registers:
- NCO1INCL = $AD
- NCO1INCH = $C7
- NCO1INCU = $0B

Have fun.

mpgmike
- 19th November 2017, 23:44
I should also mention that your NCO registers must be:

NCO1CON = %10000000
NCO1CLK = 1

This puts it into Fixed Duty Cycle Mode with Fosc as your Clock Source.

Ioannis
- 20th November 2017, 07:46
Nice tool! Thanks for sharing.

Ioannis

mpgmike
- 27th November 2017, 01:44
8514

I'm a bit new to this whole PC programming thing. I spent some time with the NCO Calculator and realized it really needed a bit of refinement. Attached is a cleaner version. Old one still works, but the new one has a few filters that make it more user friendly. The new one expands the Fosc range to include more options.

mpgmike
- 5th December 2017, 06:18
Using Mr. E-Calc, I wanted to calculate values for a PWM frequency using LFINTOSC, which is 32 kHz. I entered 0.031 where it asks for Fosc (MHz) and wouldn't you know it, IT WORKED! Got me thinking about the NCO Calc. I had limited Fosc to 1 MHz on the low end. This updated Version 1.02 lowered the minimum to 1 kHz (0.001 in the Fosc box).

8527

Mike, K8LH
- 6th May 2018, 17:48
Hi mpgmike (and gang):

I like your NCO Calculator app'. Thank you.

My calculation results are slightly different from those produced by your NCO Calculator. Would you or another forum member have time to help me figure out the problem with my calculations, please?

I designed a ClockGen IC for a retro 65C02 project a couple years ago using an 8 pin 16F18313 (listing attached below). I used a 32-MHz clock and I used the NCO to generate a 1.8432 MHz clock for an ACIA chip on that project. My NCO calculations were;

Resolution = 32000000 / 2^20 / 2 = 15.2587890625-Hz
Phase_Inc = 1843200 / 15.2587890625 = 120795.9552

Rounding up the phase increment value to 120796 I get the following frequency output;

Freq_output = 120796 * 15.2587890625 = 1843200.68359375-Hz
Freq_error = 1843200.68359375 / 1843200 - 1 = ~0.00004%

Since my phase increment and output frequency values are slightly different than those of the NCO Calculator, I'm wondering if I messed up on my calculations.

Help appreciated. TIA...

Cheerful regards, Mike


;************************************************* *****************
; *
; Project: 65C02_Clock *
; File: 16F18313_Clock.asm *
; Author: Mike McLaren, K8LH *
; (C)2016: Micro Application Consultants *
; : All Rights Reserved *
; Date: 04-Apr-2016 *
; *
; 16F18313 Clock-Gen + Econo-Reset for 65C02 (8-MHz crystal) *
; produces a 1, 2, 4, or 8 MHz CPU clock and an ACIA clock. *
; *
; IDE: MPLABX v3.05 *
; Lang: MPASM v5.62 (absolute addressing mode) *
; *
;************************************************* *****************

#include <p16F18313.INC>
errorlevel -302,-311 ; suppress bank warnings
list st=off ; symbol table off
radix dec

;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
; config settings ~
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~

__CONFIG _CONFIG1, _FEXTOSC_HS & _FCMEN_OFF
;
; CLKOUTEN_OFF default
; CSWEN_ON default
;
__CONFIG _CONFIG2, _WDTE_OFF & _PPS1WAY_OFF
;
; MCLRE_ON default
; PWRTE_OFF default
; LPBOREN_OFF default
; BOREN_ON default
; BOREN_LOW default
; STVREN_ON default
; DEBUG_OFF default
;
__CONFIG _CONFIG3, _LVP_OFF
;
; WRT_OFF default
;
__CONFIG _CONFIG4, _CP_OFF & _CPD_OFF

;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
; variables ~
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
cblock 0x70 ; common RAM available any bank
delayhi ; DelayCy() subsystem variable
endc

;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
; constants ~
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
;
; assign clock and reset pins (RA0..RA2 inclusive).
;
PHI0_clk equ RA0 ; bit index for RA0 (0)
ACIA_clk equ RA1 ; bit index for RA1 (1)
RESB_out equ RA2 ; bit index for RA2 (2)
;
; associate to "Peripheral Pin Select" output registers.
;
PHI0_PPS equ RA0PPS+PHI0_clk ; equ RA0PPS
ACIA_PPS equ RA0PPS+ACIA_clk ; equ RA1PPS
RESB_PPS equ RA0PPS+RESB_out ; equ RA2PPS
;
; set 'CLKR_div' constant for desired 65C02 clock frequency.
;
; 6 -> 0.5-MHz (Fosc / 64)
; 5 -> 1.0-MHz (Fosc / 32)
; 4 -> 2.0-MHz (Fosc / 16)
; 3 -> 4.0-MHz (Fosc / 8)
; 2 -> 8.0-MHz (Fosc / 4)
; 1 -> 16.0-MHz (Fosc / 2)
;
CLKR_div equ 4 ; 2.0-MHz PHI0 CPU clock
;
; set 'NCO1_inc' constant for desired ACIA clock output.
;
; 2517 -> 38400-Hz ( 2400 * 16) @ 0.01659%
; 5033 -> 76800-Hz ( 4800 * 16) @ 0.00327%
; 10066 -> 153600-Hz ( 9600 * 16) @ 0.00327%
; 20133 -> 307200-Hz ( 19200 * 16) @ 0.00169%
; 40265 -> 614400-Hz ( 38400 * 16) @ 0.00079%
; 60398 -> 921600-Hz ( 57600 * 16) @ 0.00004%
; 120796 -> 1843200-Hz (115200 * 16) @ 0.00004%
; 241592 -> 3686400-Hz (230400 * 16) @ 0.00004%
;
NCO1_inc equ 120796 ; 1.8432-MHz (115200 * 16)

;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
; K8LH DelayCy() subsystem macro generates four instructions ~
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
radix dec
clock equ 32 ; 4, 8, 12, 16, 20 (MHz), etc.
usecs equ clock/4 ; cycles/microsecond multiplier
msecs equ usecs*1000 ; cycles/millisecond multiplier
dloop equ 5 ; loop size, 5 to ??? cycles
;
; -- loop -- -- delay range -- -- memory overhead ----------
; 5-cyc loop, 11..327690 cycles, 9 words (+4 each macro call)
; 6-cyc loop, 11..393226 cycles, 10 words (+4 each macro call)
; 7-cyc loop, 11..458762 cycles, 11 words (+4 each macro call)
; 8-cyc loop, 11..524298 cycles, 12 words (+4 each macro call)
; 9-cyc loop, 11..589834 cycles, 13 words (+4 each macro call)
;
DelayCy macro cycles ; range, see above
if (cycles<11)|(cycles>(dloop*65536+10))
error " DelayCy range error "
else
movlw high((cycles-11)/dloop)+1
movwf delayhi
movlw low ((cycles-11)/dloop)
call uLoop-((cycles-11)%dloop)
endif
endm

;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
; reset vector ~
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
org 0x0000
v_reset
bra setup ; |00

;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
; interrupt vector ~
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
org 0x0004
v_int
retfie ; |??

;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
; main setup ~
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~

setup
;
; turn off analog functions (all I/O will be digital).
;
banksel ANSELA ; bank 03 |03
clrf ANSELA ; analog off, digital I/O |03
;
; setup data direction for 'output' pins (default 'input').
;
banksel TRISA ; bank 01 |01
bcf TRISA,PHI0_clk ; set phi0 clock pin as output |01
bcf TRISA,ACIA_clk ; set acia clock pin as output |01
bcf TRISA,RESB_out ; set resb reset pin as output |01
;
; clear RESB pin output latch (hold the 65C02 in reset).
;
banksel LATA ; bank 02 |02
bcf LATA,RESB_out ; RESB_out = '0' |02
;
; setup Fosc for 32-MHz (external 8-MHz crystal and 4xPLL).
;
banksel OSCCON1 ; bank 18 |18
movlw b'00010000' ; -001---- NOSC[2:0], Ext 4xPLL |18
; ----0000 NDIV[3:0], Clk Div 1 |18
movwf OSCCON1 ; 8-MHz Xtal & 4xPLL -> 32-MHz |18
stable
btfss OSCCON3,ORDY ; OSC stable? yes, skip, else |18
bra stable ; loop (wait until OSC stable) |18
;
; assign PHI0_clk pin (RA0) and ACIA_clk pin (RA1) resources
; via 'Peripheral Pin Select'.
;
banksel PPSLOCK ; bank 28 |28
movlw 0x55 ; PPS unlock sequence |28
movwf PPSLOCK ; " |28
movlw 0xAA ; " |28
movwf PPSLOCK ; " |28
bcf PPSLOCK,PPSLOCKED ; " |28
banksel PHI0_PPS ; bank 29 |29
movlw b'00011110' ; assign CLKR (Ref Clock) output |29
movwf PHI0_PPS ; to the PHI0 clock pin (RA0) |29
movlw b'00011101' ; assign NCO module output to |29
movwf ACIA_PPS ; the ACIA clock pin (RA1) |29
banksel PPSLOCK ; bank 28 |28
bsf PPSLOCK,PPSLOCKED ; all done, lock it up |28
;
; setup CLKR (Reference Clock) module for PHI0 CPU Clock.
;
banksel CLKRCON ; bank 07 |07
movlw 0x10|CLKR_div ; 50% duty cycle & divider bits |07
movwf CLKRCON ; prep 1, 2, 4, or 8-MHz output |07
bsf CLKRCON,CLKREN ; enable Reference Clock output |07
;
; setup NCO to generate the ACIA clock.
;
banksel NCO1CON ; bank 08 |08
; bcf NCO1CON,N1PFM ; fixed duty cycle mode (default) |08
movlw b'00000001' ; N1CKS<1:0> = '01' = Fosc |08
movwf NCO1CLK ; set NCO clock source |08
clrf NCO1ACCL ; clear 20-bit phase accumulator |08
clrf NCO1ACCH ; " |08
clrf NCO1ACCU ; " |08
movlw upper(NCO1_inc) ; " |08
movwf NCO1INCU ; " |08
movlw high(NCO1_inc) ; " |08
movwf NCO1INCH ; " |08
movlw low(NCO1_inc) ; setup 20-bit phase increment |08
movwf NCO1INCL ; " |08
bsf NCO1CON,N1EN ; enable NCO module output |08
;
; complete the 65C02 reset cycle.
;
DelayCy(20*msecs) ; wait ~20-msecs |08
banksel LATA ; bank 02 |02
bsf LATA,RESB_out ; release 65C02 from reset |02

;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
; main loop ~
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~

loop
bra loop ; loop forever (until reset) |02

;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
; K8LH DelayCy() subsystem 16-bit 'uLoop' timing subroutine ~
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
a = dloop-1
while a > 0
nop ; (cycles-11)%dloop entry points |??
a -= 1
endw
uLoop addlw -1 ; subtract 'dloop' loop time |??
skpc ; borrow? no, skip, else |??
decfsz delayhi,F ; done? yes, skip, else |??
bra uLoop-dloop+5 ; do another loop |??
return ; |??

;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
end

mpgmike
- 9th May 2018, 00:04
In the NCO Calc I entered:

Fosc = 32 MHz
Frequency = 1,843,200 (didn't use commas in the NCO Calc, added for clarity here)

What I got in return was:

NCO Decimal = 120795.84
NCO HEX = 1D7DC
Result Rounded Up = 1,843,217.7002
Result Rounded Down = 1,843,202.4414

Our results aren't that much different. The project I developed the NCO Calc for is reading within < 0.02 Hz different than calculated with the NCO Calc. Not sure what you got.

Mike, K8LH
- 9th May 2018, 01:43
Hi mpgmike. Thanks for the reply.

Those are the same numbers I got from NCO Calculator. May I ask how you produced some of those numbers, if you have time, please? I'm concerned that I'm using the wrong formulas for the calculated output frequency and error.

What formula did you use to produce the 120795.84 phase increment value? My result was 120795.9552 which is ever so slightly different (formula in my previous post).

What formula do you use to produce "Result Rounded Up" and "Result Rounded Down" values? Are those supposed to be the frequency output when you round the phase increment value to integer values of 120795 or 120796? I'm concerned about my calculations as I came up with an output frequency of 1843200.68359375-Hz (~0.00004% off) using 120796 for the phase increment value.

If I'm using the wrong calculations I'd like to fix them and update the frequency error comments in my project.

Thank you for your time and kind consideration.

Cheerful regards, Mike, K8LH

mpgmike
- 9th May 2018, 02:16
The basic formula reduces down to:

NCO Value = (Frequency * 2,097,150) / Fosc

Building the NCO Calc in Visual Basic, the behind-the-scenes math was:

NcoDec = (Freq * 2.09715) / Fosc
NcoDecR = Math.Round(NcoDec, 3)
FreqDn = Math.Round(((NcoInt * Fosc) / 2.09715), 4)
FreqUp = Math.Round(((NcoInt1 * Fosc) / 2.09715), 4)

There's a bit of other code in there, but that's the math.

Mike, K8LH
- 9th May 2018, 03:52
I think I found the problem... 2^21 equals 2097152, not 2097150. When I plug the 2097152 value into your formulas the phase increment and output frequency results match my calculations.

Thank you so much for helping me verify my calculations are correct.

Cheerful regards, Mike, K8LH

Fosc = 32000000
Freq = 1843200

NCO Decimal = 120795.9552
NCO HEX = 1D7DC
Result Rounded Up = 1843200.68359375
Result Rounded Down = 1843185.4248046875
Resolution = 15.2587890625-Hz

Ioannis
- 9th May 2018, 06:53
I never used the NCO up to this moment. What is the purpose of this module?

Ioannis

Dave
- 9th May 2018, 11:33
Well Ioannis, Here is an excerpt form the data sheet:

The Numerically Controlled Oscillator (NCO1) module
is a timer that uses the overflow from the addition of an
increment value to divide the input frequency. The
advantage of the addition method over simple
counter-driven timer is that the output frequency
resolution does not vary with the divider value. The
NCO1 is most useful for applications that require
frequency accuracy and fine resolution at a fixed duty
cycle.

I have recently built and coded a closed loop PID control for a stepper motor with the NCO.

Ioannis
- 9th May 2018, 11:55
Thanks Dave for the info.

Ioannis

mpgmike
- 9th May 2018, 12:45
With NCO you can target frequencies with spectacular accuracy as opposed to PWM or other methods. Many of the newer PICs with NCO also have the option of using LFINTOSC and MFINTOSC with NCO for a slower-than-Fosc(/4) clock. My current project targets frequencies as slow as 2 Hz, with accuracy to 0.02 Hz, and as high as 3.1 MHz. NCO lets me get that accurate.

Mike, K8LH
- 11th May 2018, 00:41
Hi mpgmike:

At the risk of seeming like a troll, may I make a couple suggestions, just in case you ever revisit your NCO Calc program, please?

(1) Allow entry of NCO Frequencies such as 8388608-Hz without rounding.
(2) Correct the NCO bit width constant (2097152) in your calculations.
(3) Display the NCO frequency resolution.
(4) Display the output frequency error.

I whipped up a quick spreadsheet to highlight the calculation problems I'm experiencing. Your NCO Calc app' introduces errors by rounding the NCO frequency and by using the incorrect NCO bit width constant in your calculations.

Have fun. Best wishes. Cheerful regards, Mike, K8LH

8642

Ioannis
- 11th May 2018, 09:13
Isn't easier all these calculation done in a Excel file?

Ioannis

mpgmike
- 11th May 2018, 13:35
Thoughts I never thought. There are 2 modes for NCO, toggle (the one used) and a pulse-out, which yields 2X the frequency output of toggle mode. I had thought of possibly adding calculations for pulse-out mode.

Mike, K8LH
- 12th May 2018, 00:38
Isn't easier all these calculation done in a Excel file?

Ioannis

A spreadsheet is easier to make but I don't think it's easier to use. Mike's stand-alone app' is a seriously cool accomplishment. Personally, I'd rather use a small stand-alone app' instead of a spreadsheet.

Many years ago I built an SPBRG Calculator spreadsheet followed by an SPBRG Calculator app' written in JustBASIC. I much prefer using the app'.

Cheerful regards, Mike, K8LH

8644

richard
- 12th May 2018, 01:39
if only pbp was still being kept relevant, all these things and more are built into mplabx and easy to use

mpgmike
- 12th May 2018, 11:42
I forced myself to work with MPLABX over the past few days. I needed a combination of features only found in the PIC16F18426, which is too new for PBP3. I'm building the software in ASM using MPLABX. I like the Debug feature. As I'm not that familiar with ASM, the Debug showed me where I got stuck in an endless loop and helped me catch a few other problems in my code. It's taking me about 10X longer in ASM than it would in PBP3, but I'm learning a bunch through the experience.

Ioannis
- 12th May 2018, 16:59
You are a herο! Two times, for ASM and MPLABX!

I started using PIC with plain ASM coding but never go back again.

Ioannis

Mike, K8LH
- 26th January 2020, 03:00
Anyone know if mpgmike ever fixed the errors and updated his nco calculator app'?

mpgmike
- 26th January 2020, 15:28
No, I didn't.

Mike, K8LH
- 27th January 2020, 01:14
No, I didn't.If it's not stepping on your toes, would you mind if I created a stand-alone NCO Calculator app', much like my SPBRG Calculator app'? I just need an accurate tool people can use on a couple upcoming retro computer projects.

TIA... Cheerful regards, Mike, K8LH

mpgmike
- 27th January 2020, 15:04
Please do. I originally created it because I needed it for a project at the time, and decided to share it. I have no "inventor's syndrome" attachments, so if you want to contribute to the community, everyone benefits.