Home
JAQForum Ver 24.01
Log In or Join  
Active Topics
Local Time 17:05 11 May 2025 Privacy Policy
Jump to

Notice. New forum software under development. It's going to miss a few functions and look a bit ugly for a while, but I'm working on it full time now as the old forum was too unstable. Couple days, all good. If you notice any issues, please contact me.

Forum Index : Microcontroller and PC projects : [MicroMite]AD9850 Controller Code V14

Author Message
G8JCF

Guru

Joined: 15/05/2014
Location: United Kingdom
Posts: 676
Posted: 03:06pm 24 Jul 2014
Copy link to clipboard 
Print this post

Hi

Here is the latest version of the AD9850 Controller code.


The changes from previous versions are
1) V14 is much faster than previous versions
o Uses sextets of BCD digits instead of triplets
o Only 1 Hz tuning steps now use extended precision arithmetic, 10Hz
and above tuning steps use native MMBasic single arithmetic
o More in-lining and flattening out of loops
o LCD update is asynchronous to the DDS update based on a timer

2) Tested with 128 PPR AVAGO optical encoders.

3) Uses I2C LCD - if U have a conventional LCD, then rem out the LCDI2C statements
and replace with native MMBasic LCD statements - syntax is very similar. I2C LCDs
are very much slower to update than 4 bit parallel LCDs hence the extra need
for the Settick based method to decouple LCD updates from DDS updates,
ie DDS updates must be done in realtime, but the eye can only discern LCD
changes at about 10 times per second, and also cheap LCDs have very slow
response times.

4) The Step Change Switch now provides Short and Long push functionality. With a
Long push, the tuning step is changed AND the digits to the RHS are all
zeroed. With a Short push, the tuning step is just changed. This Long/Short
push mechanism is provide by using INTB, ie interrupt on Both edges and timing
the duration of the switch closure. 500mS or more is a Long push.
See ISRStepChange

5) I have used different pins than previously for the Encoder, & Push Switch
in order to maximise the number of ADC and INT ports left available for other
purposes.

6) Implemented Gear Ratio so that if one has a very high PPR encoder then one
could gear down its pulse rate to something more uMite friendly. I didn't
find that with my 128 PPR encoders that I needed to gear down - see ISRREInt


Any questions, suggestions, bugs, problems etc, please get back to me.

I hope this code proves useful to those people experimenting with the eBay AD9850 module.

73

Peter


'AD9850 VFO Controller V14 - Fri 25 July 2014
'
'Peter Carnegie - GM8JCF - 2014

'eBay AD9850 Modules use 125,000,000 Hz Clocks
'AD9850 Fout= (? Phase × CLKIN)/2^32 where Fout=32 bit tuning word
'TuningWord= Fout * 2^32 / CLKIN = Fout * (2^32/125,000,000)
'2^32/125,000,000 = 34.359738368 - VB6 Double Precision
'Using Integer only arithmetic, multiply by 1,000,000
'DivConst= 34359738
'We then divide the result of TuningWord=DivConst * Fout by 1,000,000
'
'
'
'V11 - Include tuning steps sizes 10,000, 100,000, 1,000,000 Hz
' - Implement Native SINGLE precision for step-sizes of 100Hz or greater
' else use BCD library

' - Optimised DDS write SUBS
' - Changed ISR's to SUB/END SUB
' - Added Sub StepChange to respond to Push Switch
' - Updates to LCD are now done only every 100mS, see SETTICK 100, DispFreq,1
'
'Known Problems
' - If the Step size is set greater than the current frequency and the encoder is
' rotated ANT-Clockwise, "strange" operating frequency results, but rotating
' Clockwise so that the operating frequency is GT than step-size returns the
' op freq to correct values.
'
'TTD
' - Update the BCD arith routines to process quintets of digits at a time
' - Fix the Step size vs Op Frequency.

'V12 - Optimised the Increment of RawTWord
'TTD
' - Update the BCD arith routines to process quintets of digits at a time
' - Fix the Step size vs Op Frequency.

'V13 - Changed to sextet digit processing in BCD

'V14
' The changes from previous versions are
' 1) V14 is much faster than previous versions
' o Uses sextets of BCD digits instead of triplets
' o Only 1 Hz tuning steps now use extended precision arithmetic, 10Hz
' and above tuning steps use native MMBasic single arithmetic
' o More in-lining and flattening out of loops
' o LCD update is asynchronous to the DDS update based on a timer
'
' 2) Tested with 128 PPR AVAGO optical encoders.
'
' 3) Uses I2C LCD - if U have a conventional LCD, then rem out the LCDI2C statements
' and replace with native MMBasic LCD statements - syntax is very similar. I2C LCDs
' are very much slower to update than 4 bit parallel LCDs hence the extra need
' for the Settick based method to decouple LCD updates from DDS updates,
' ie DDS updates must be done in realtime, but the eye can only discern LCD
' changes at about 10 times per second, and also cheap LCDs have very slow
' response times.
'
' 4) The Step Change Switch now provides Short and Long push functionality. With a
' Long push, the tuning step is changed AND the digits to the RHS are all
' zeroed. With a Short push, the tuning step is just changed. This Long/Short
' push mechanism is provide by using INTB, ie interrupt on Both edges and timing
' the duration of the switch closure. 500mS or more is a Long push.
' See ISRStepChange
'
' 5) I have used different pins than previously for the Encoder, & Push Switch
' in order to maximise the number of ADC and INT ports left available for other
' purposes.
'
' 6) Implemented Gear Ratio so that if one has a very high PPR encoder then one
' could gear down its pulse rate to something more uMite friendly. I didn't
' find that with my 128 PPR encoders that I needed to gear down - see ISRREInt
'

'Define DDS Control pins
DIM WCLK:WCLK= 22 '16
DIM FQUD:FQUD= 21 '18
DIM SDATA:SData=15 '17

'Define Rotary Encoder Input pins - see page 22 uMite User Manual
DIM RA:RA=9 '2
DIM RB:RB=14 '3

'Define TuneStep change switch on Rotary Encoder
DIM SW:SW=10 '4


'High Speed for Initialisation
CPU 48

'Initialise the BCD Library
InitBCDLib

DIM DivConst(BCDSize)
'Load it up in BCD
LoadBCD DivConst(0),"34359738"

'2^32/125,000,000 as a SINGLE
'MMBasic will truncate it to 7 digits of precision
DIM DivConstSingle
DivConstSingle=34.359738368

'Cur Freq in BCD
DIM CurFreq(BCDSize)

'Set Starting Frequency in sextet BCD format
ClrBCD CurFreq(0)
LoadBCD CurFreq(0),"10000000"

'Current Frequency as a SINGLE
DIM CurrentFrequency
CurrentFrequency=10000000

'Initialise the LCD Display
'Characters per line
DispLen=16
'26=D7, 25=D6, 24=d5, 23=d4
'22=RS, 21=EN
'LCD INIT 26,25,24,23,22,21

'LCDI2C_Init I2CAddr,NibPos,RSBitNo,ENBitNo,BLBitNo
LCDI2C_Init &H27,1,0,2,3

'Turn On Backlightr
LCDI2C_BackLight(1)

'When Set to 1 will trigger a display of frequency
DIM DispRqst

'Set to every 100mS to signal that if a display refresh is needed
'now is the time to do it
DIM DispTick

'Rotary Encoder Gear Ratio
'Set the Gear ratio.
'With GearRatio=1, every encoder pulse will cause the main loop to run
'With GearRatio=2, every 2nd pulse will ....
'With GearRatio=3, every 3rd pulse will ....
'etc
'Hi PPR encoders, eg 128/256 and greater typically generate too many pulses per second for
'a low power MCU like the uMite to handle. Try setting GearRatio to 2,3 or 4
DIM GearRatio
GearRatio=1

'Used by RE ISR to count pulses and implerment Gear Ratio
DIM PulseCount

'Var set ON to signal StepChange request by user, by ISRStepChange
DIM StepChangeRqst

'Used by ISRStepChange to capture start time when switch was closed
DIM SWDownStart

'Set by ISRStepChange to the duration of the switch closed period
'U can use this to implement a short and long push
DIM SWPushDuration

'Variable incremented/decremented by the ISRREInt
'ISR for the Rotary Encoder
DIM Value,LastValue

'Raw Tuning Word, ie scaled by 1E6
DIM RawTWord(BCDSize)

'Tuning Word scaled back to real world units, ie divide by 1E6
DIM TWord(BCDSize)

'Index of which Step size is in force
DIM CurStepIndex

'Startup with 1Hz tuning steps
CurStepIndex=0

'Current Step in TWord units
DIM CurStepTW(BCDSize)

'Array of Possible Step Sizes in Hz
DIM StepHz(6)
StepHz(0)=1
StepHz(1)=10
StepHz(2)=100
StepHz(3)=1000
StepHz(4)=10000
StepHZ(5)=100000
StepHZ(6)=1000000

'What to Add/Subtract from the Scaled tuning Word
'for each Encoder Up/Down
DIM StepTWord$(6) Length 24

'These TuningWord sizes were calculated using Double precision arithmetic
'in VB6, using 2^32/125.0
'
StepTWord$(0)="34359738" '1Hz 34359738.368
StepTWord$(1)="343597384" '10Hz 343597383.68
StepTWord$(2)="3435973837" '100Hz 3435973836.8
StepTWord$(3)="34359738368" '1000Hz 34359738368.
StepTWord$(4)="343597383680" '10000Hz 343597383680
StepTWord$(5)="3435973836800" '100000Hz 3435973836800
StepTWord$(6)="34359738368000" '1000000Hz 34359738368000

'Load the CurStepTW with the startup Step in TW units
ClrBCD CurStepTW(0)
LoadBCD CurStepTW(0),StepTWord$(CurStepIndex)

'The Current Step in TW units in a SINGLE
DIM CurStepTWSingle
CurStepTWSingle=VAL(StepTWord$(CurStepIndex))/1E6

'Calculate Starting TuningWord
CopyBCD DivConst(0),BCDA(0)
CopyBCD CurFreq(0),BCDB(0)
'Calculate the Tuning Word, this one is * 1E6
MulAB2C

'Save the RawTWord, RawTWord = Actual TWord * 1E6
CopyBCD BCDC(0), RawTWord(0)

'SINGLE version of TWord
DIM TWordSingle
'Calculate the Tuning Word as a SINGLE
TWordSingle=CurrentFrequency * DivConstSingle

'Start with clean BCD Registers
ClrBCD(BCDA(0))
ClrBCD(BCDB(0))
ClrBCD(BCDC(0),BCDSize*2)

'Go do Main Loop
Main

END

'====================================================
'
'Loop for ever
'
SUB Main

'General purpose String used in Main Loop
LOCAL D$(1) length 24
LOCAL I,J,L,L1,Cy

'Var captures the size and direction of the rotary encoder
'change when rotary encoder has moved.
'Do it this way to improve responsiveness
LOCAL Change

D$(1)="GM8JCF"
LCDI2C 2, 5, D$(1)

'Initialise the I/O & DDS chip
InitDDS

'Configure the I/O pins for the Rotary Encoder
SETPIN RB, DIN
SETPIN RA, INTH, ISRREInt

'Configure the Pin for the Step Change request switch
SETPIN SW, INTB, ISRStepChange

'Set up the periodic LCD update timer
SETTICK 100,UpdateLCD,1

'Initialise the DDS chip to startup frequency
'Make the Actual DDS Tuning Word, ie scale down Raw by 1E6
'For BCD that just means removing the last 6 digits of RawTWord()
BCDA(0)=RawTWord(1):BCDA(1)=RawTWord(2)

'Convert BCD to UINT32
BCDA2Bin

'Send the Tuning Word to the DDS
SendDDSData

'Trigger a display of Frequency
DispRqst=1

'Display the Step Size
LCDI2C 2,1,STRING$(20," ")
LCDI2C 2,1,"Step="+STR$(StepHz(CurStepIndex))+" Hz"
PRINT "Tune Step="+STR$(StepHz(CurStepIndex)) 'DEBUG

'Save power
CPU 5

'Do for ever !
DO

Change=Value-LastValue
Value=LastValue

'Any movement ?
IF Change<>0 THEN

'We need all the speed we can get for the calculations
'CPU 48

L1=TIMER 'DEBUG

'If the Tune Step is 10Hz or greater then we can use the Native SINGLE
'arithmetic of MMBasic otherwise we must use ths much slower BCD arithmetic
'
IF StepHZ(CurStepIndex) >= 10 THEN
' L=TIMER 'DEBUG
IF Change<0 THEN
CurrentFrequency=CurrentFrequency-StepHz(CurStepIndex)
ELSE
CurrentFrequency=CurrentFrequency+StepHz(CurStepIndex)
ENDIF

'Calculate Tuning Word
TWordSingle=CurrentFrequency * DivConstSingle

' PRINT "Calc Time: ";TIMER-L 'DEBUG
' L=TIMER 'DEBUG

'Now we have to get it into the DDS Chip
SendDDSDataSingle TWordSingle

' PRINT "DDS Comms: ";TIMER-L 'DEBUG

ELSE

' L=TIMER 'DEBUG
'
'The Tune Step is less than 10Hz so we have to use extended precision arithmetic
'which is slower
'
'If encoder is turning ANTI-clockwise, Decrement
'If encoder is turning CLOCKwise, Increment
IF Change<0 THEN

J=CurFreq(0)+&HF4240-(StepHz(CurStepIndex))
BW=1-J\&HF4240
CurFreq(0)=J MOD &HF4240 '&HF4240=1,000,000
IF BW THEN
J=CurFreq(1)+&HF4240-BW
CurFreq(1)=J MOD &HF4240 '&HF4240=1,000,000
ENDIF

ELSE

J=CurFreq(0)+(StepHz(CurStepIndex))
Cy=J\&HF4240
CurFreq(0)=J MOD &HF4240
IF Cy THEN
J=CurFreq(1)+Cy
CurFreq(1)=J MOD &HF4240
ENDIF

ENDIF

'If ANTI-clockwise then decrement TuningWORD
'If CLOCKwise then incrementg TuningWORD
IF Change<0 THEN
'Decrement
J=RawTword(0)+&HF4240-CurStepTW(0)
Cy=1-(J\&HF4240)
RawTword(0)=J MOD &HF4240 '&HF4240 = 1,000,000

J=RawTword(1)+&HF4240-CurStepTW(1)-Cy
Cy=1-(J\&HF4240)
RawTword(1)=J MOD &HF4240 '&HF4240 = 1,000,000

J=RawTword(2)+&HF4240-CurStepTW(2)-Cy
RawTword(2)=J MOD &HF4240 '&HF4240 = 1,000,000

ELSE
'Increment
J=RawTword(0)+CurStepTW(0)
Cy=J\&HF4240
RawTword(0)=J MOD &HF4240

J=RawTword(1)+CurStepTW(1)+Cy
Cy=J\&HF4240
RawTword(1)=J MOD &HF4240

J=RawTword(2)+CurStepTW(2)+Cy
RawTword(2)=J MOD &HF4240

ENDIF

BCDC(0)=RawTword(0):BCDC(1)=RawTword(1):BCDC(2)=RawTword(2)

'Round to the nearest 1,000,000
'ie Add 500,000, then chop off the last 6 digits

J=BCDC(0)+&H7A120 '&H7A120=500,000
Cy=J\&HF4240
BCDC(0)=J MOD &HF4240
IF Cy THEN
J=BCDC(1)+Cy
Cy=J\&HF4240
BCDC(1)=J MOD &HF4240
IF Cy THEN
J=BCDC(1)+Cy
BCDC(1)=J MOD &HF4240
ENDIF
ENDIF

'And make the Actual DDS Tuning Word, ie scale down by 1E6
'For sextet BCD that just means dropping the last 6 digits
'The Tuning word in decimal must be less than 11 digits, 2^31
BCDA(0)=RawTWord(1):BCDA(1)=RawTWord(2)

'Convert BCD to UINT32
BCDA2Bin


' L=TIMER-L:PRINT "Calc Time: ";L 'DEBUG

' L=TIMER 'DEBUG
'Send the Tuning Word to the DDS
SendDDSData
' L=TIMER-L:PRINT "DDS Send Time:";L 'DEBUG
ENDIF

'Request a display of Frequency
DispRqst=1

'Reset change flag for next iteration
Change=0

L1=TIMER-L1:PRINT "Loop Time=";L1 'DEBUG

'Slowdown the CPU to save power
' CPU 5

ENDIF

'Is there a request to change the Tune Step ?
IF StepChangeRqst THEN
' PRINT "Main:SWDuration=";SWPushDuration 'DEBUG
IF SWPushDuration>500 THEN
StepChange(1) 'Specify Long Push
ELSE
StepChange(0) 'Specify Normal Push
ENDIF
StepChangeRqst=0
'Slowdown the CPU to save power
' CPU 5
ENDIF

'Has there been a Display Tick AND a Request to Display ?
IF (DispTick AND DispRqst) THEN
DispFreq
'Reset the Display Request & Display Tick flags
DispRqst=0
DispTick=0
'Slowdown the CPU to save power
' CPU 5
ENDIF


'Go check again
LOOP

END SUB

END

'=========================================================
'
'Interrupt Service Routines
'
'Here when the Rotary encoder moves
'
ISRREInt:
'There is work to do so ramp up the speed !
CPU 48
PulseCount=(PulseCount+1) MOD GearRatio
IF PulseCount<1 THEN

IF PIN(RB)=1 THEN
'Clockwise rotation
Value=Value+1
ELSE
'Anti-Clockwise rotation
Value=Value-1
ENDIF

ENDIF

IRETURN

'=========================================================
'
'Here on Step Switch click
'
ISRStepChange:
'There is work to do so ramp up the speed !
CPU 48
'On Switch close start timer
IF PIN(SW)=0 THEN
SWPushDuration=0
SWDownStart=TIMER
StepChangeRqst=0
ENDIF

'On Switch Hi, capture duration of push
IF PIN(SW)=1 THEN
SWPushDuration=TIMER-SWDownStart
' PRINT "ISRStepChange:SWDuration=";SWPushDuration 'DEBUG

'Set Step Change Requested
StepChangeRqst=1
' PRINT "ISRStepChange:StepChangeRqst=";StepChangeRqst 'DEBUG
ENDIF
IRETURN


'=========================================================
'
'Here on Settick 1
'
'
UpdateLCD:
'There is work to do so ramp up the speed !
CPU 48
DispTick=1
IRETURN


'=========================================================
'
'Change the Tuning Step
'
'If ZeroDig is set to 1, then digits to the RHS of the step will
'be zeroed.
'
SUB StepChange(ZeroDig)
PRINT "StepChange:PushTime=";ZeroDig 'DEBUG

LOCAL CurrentStepHz
LOCAL CurrentStepIndex
LOCAL Freq$(1) Length 12
LOCAL D$(1) Length 20
LOCAL M

'Capture the current step Index
CurrentStepIndex=CurStepIndex

'Bump the index
CurStepIndex=CurStepIndex+1

'Roll round CurStepIndex if greater than 6
CurStepIndex=CurStepIndex MOD 7

'Did we roll over to step 0 ?
'If Yes then we are going to use extended precision arithmetic
'so we have to load up the BCD variables
IF CurStepIndex=0 THEN

'Get the Current Frequency
' PRINT "StepChange:";STR$(CurrentFrequency) 'DEBUG

IF CurrentFrequency>999999 THEN

Freq$(1)=STR$(CurrentFrequency\1e6)+ STR$(CurrentFrequency-(CurrentFrequency\1e6)*1e6,6,0,"0")

'Fix up peculiar bug when CurrentFrequency is 9,999,999 Hz - only this single frequency
IF Freq$(1)="10-00001" THEN Freq$(1)="9999999"

ELSE

Freq$(1)=STR$(CurrentFrequency)

ENDIF

' PRINT "StepChange:Freq$(1):";Freq$(1) 'DEBUG

'Zero digits ?
IF ZeroDig THEN
'Zero the trailing digits depending on Step
FOR I=LEN(Freq$(1))-CurStepIndex TO LEN(Freq$(1))
POKE VAR Freq$(1),I,&H30
NEXT I
' PRINT "StepChange:Freq$(1):";Freq$(1) 'DEBUG
ENDIF

LoadBCD CurFreq(0),Freq$(1)

'Calculate the Tuning Word - this is the x 1E6 one
CopyBCD DivConst(0),BCDA(0)
CopyBCD CurFreq(0),BCDB(0)
'Calculate the Tuning Word, this one is * 1E6

MulAB2C

'Save the RawTWord in Sextet BCD format
RawTword(0)=BCDC(0):RawTword(1)=BCDC(1):RawTword(2)=BCDC(2): RawTWord(3)=0

'And Make sure CurFreq() is also in Sextet BCD format
LoadBCD CurFreq(0),Freq$(1)

ENDIF

'Was the last StepHz less than 10Hz and the new one greater than 9 Hz
IF (StepHz(CurrentStepIndex) < 10) AND (StepHz(CurStepIndex>9)) THEN
'Convert CurFreq() into CurrentFrequency
Freq$(1)=CurFreq2Asc$()

'Zero digits ?
IF ZeroDig THEN
'Zero the trailing digits depending on Step
FOR I=LEN(Freq$(1))-CurStepIndex TO LEN(Freq$(1))
POKE VAR Freq$(1),I+1,&H30
NEXT I
' PRINT "Conversion of Freq$(1):";Freq$(1) 'DEBUG
ENDIF
CurrentFrequency=VAL(Freq$(1))
ENDIF

'Is the Current Step in Hz >9
IF (StepHz(CurStepIndex) > 9) THEN

'If Zero digits then we zero the RHS digits in addition to changing the step
IF ZeroDig THEN
CurrentFrequency=(CurrentFrequency\(10^CurStepIndex))*(10^Cu rStepIndex)
ENDIF

' PRINT "StepChange:CurrentFrequency:";CurrentFrequency 'DEBUG

TWordSingle=CurrentFrequency * DivConstSingle
CurStepTWSingle=VAL(StepTWord$(CurStepIndex))/1E6

ENDIF

'Display the Step size
LCDI2C 2,1,STRING$(20," ")
D$(1)=STR$(StepHz(CurStepIndex))
IF MID$(D$(1),LEN(D$(1),1))="5" THEN
D$(1)=STR$(StepHz(CurStepIndex)/1e1)+"0"
ENDIF
LCDI2C 2,1,"Step="+D$(1)+" Hz"
PRINT "Tune Step="+STR$(StepHz(CurStepIndex)) 'DEBUG

'Request a display Update
DispRqst=1

END SUB


'=========================================================
'
'Returns a string representing the BCD Digits from CurFreq()
'eg if CurFreq(0)=123456, CurFreq(1)=789123 then
'CurFreq2ASC$ will return "789123123456"
'
'Arg Width is the number of digits per Numeric Var
'
FUNCTION CurFreq2Asc$(Width)

IF Width=0 THEN Width=6

LOCAL I,J,A$(1) Length BCDSize*Width,B$(1) Length BCDSize*Width

'Frequency is 8 digits long , eg 30,000,000
FOR I=1 TO 0 STEP -1

'Find First non Zero element
IF CurFreq(I)<>0 THEN

FOR J=I TO 0 STEP -1
B$(1)=B$(1)+STR$(CurFreq(J),Width,0,"0")
NEXT J

'Now Remove any leading Zeroes
FOR J=1 TO Width
IF PEEK(VAR B$(1),J)<>&H30 THEN
B$(1)=MID$(B$(1),J)
EXIT FOR
ENDIF

NEXT J

EXIT FOR

ENDIF

NEXT I

CurFreq2Asc$=B$(1)

END FUNCTION



'=========================================================
'
'Imitialise the DDS
'
SUB InitDDS

SETPIN FQUD, DOUT : PIN(FQUD)=0
SETPIN SData, DOUT : PIN(SData)=0
SETPIN WCLK, DOUT : PIN(WCLK)=0

END SUB


'=========================================================
'
'Tuning Word to be sent to the DDS must be in BCDC()
'Called by BCD based routines
'
SUB SendDDSData

PIN(FQUD)=&H0 'Make sure FQUD is Low
PIN(WCLK)=&H0 'Clock pin Low
PIN(Sdata)=&H0 'Data Pin Low

'Send the Tuning Word and COntrol bytes to the AD9850
'Inline for speed
Send8Bits BCDC(0)
Send8Bits BCDC(1)
Send8Bits BCDC(2)
Send8Bits BCDC(3)
Send8Bits 0

PULSE FQUD,0.05 'Latch the data into the DDS chip

END SUB


'=========================================================
'
'Send Tuning Word as SINGLE TWord to DDS
'
SUB SendDDSDataSingle(TWord)
LOCAL B

PIN(FQUD)=&H0 'Make sure FQUD is Low
PIN(WCLK)=&H0 'Clock pin Low
PIN(Sdata)=&H0 'Data Pin Low

'Make a copy because we're going to change the Arg
B=TWord

'Inline the 5 byte send for maximum speed
Send8Bits B AND &HFF:B=B\&H100
Send8Bits B AND &HFF:B=B\&H100
Send8Bits B AND &HFF:B=B\&H100
Send8Bits B
Send8Bits 0

PULSE FQUD,0.025 'Latch the data into the DDS chip

END SUB


'=========================================================
'
'Send Byte in B to DDS chip
'
SUB Send8Bits(B)

CPU 48

'Inline the 8 bit send for max speed
PIN(SData)=B AND &H1:PULSE WCLK,0.010
PIN(SData)=B AND &H2:PULSE WCLK,0.010
PIN(SData)=B AND &H4:PULSE WCLK,0.010
PIN(SData)=B AND &H8:PULSE WCLK,0.010
PIN(SData)=B AND &H10:PULSE WCLK,0.010
PIN(SData)=B AND &H20:PULSE WCLK,0.010
PIN(SData)=B AND &H40:PULSE WCLK,0.010
PIN(SData)=B AND &H80:PULSE WCLK,0.010

END SUB


'=========================================================
'
'Display Operating Frequency on LCD and Console
'
SUB DispFreq
LOCAL CurFreq$(1) Length DispLen,Pad$(1) Length DispLen
LOCAL L

'Are we using Single precision arithmetic ?
IF StepHZ(CurStepIndex) < 10 THEN
CurFreq$(1)=CurFreq2Asc$(6)
ELSE
IF CurrentFrequency >999999 THEN
'Now get the first |Digit
CurFreq$(1)=STR$(CurrentFrequency\1E6)
CurFreq$(1)=CurFreq$(1)+STR$((CurrentFrequency - (CurrentFrequency\1E6)* 1E6),6,0,"0")
ELSE
CurFreq$(1)=STR$(CurrentFrequency)
ENDIF
ENDIF

'Display Frequency on the console
PRINT "CurFreq$:";CurFreq$(1) 'DEBUG

'Display Frequency on the LCD on Line 1
Pad$(1)=STRING$((DispLen-LEN(CurFreq$(1)))\2," ")
CurFreq$(1)=Pad$(1)+CurFreq$(1)+Pad$(1)
LCDI2C 1,1,CurFreq$(1)

END SUB


'=========================================================
'
'Initialise the BCD Library
'MUST be called BEFORE calling any other BCD Lib routines
'Creates Global BCD Lib Vars, BCDA/B/C() and BCDSize
'NumDigits is Number of BCD digits, Default to 24
'BCDSize will return number of elements used to represent NumDigits, ie NumDigits\Width
'
SUB InitBCDLib(NumDigits)

DIM BCDWidth,BCDSize,BCDNumDigits

'Set precision default is 24 decimal digits
IF NumDigits<1 THEN
BCDNumDigits=24
ELSE
BCDNumDigits=NumDigits
ENDIF

IF BCDNumDigits MOD 6 <>0 THEN
ERROR "The Number of digits MUST be a multiple of 6"
ENDIF

'Number of Digits per Numeric Var
BCDWidth=6

'Number of Numeric vars per BCD register
'Each Numeric var holds BCDWidth digits
BCDSize=(BCDNumDigits\BCDWidth)

'NB BCDC is TWICE as long as the others to allow for Multiplication
DIM BCDA(BCDSize-1),BCDB(BCDSize-1),BCDC(BCDSize*2)

END SUB


'=========================================================
'BCDC()=BCDA() x BCDB()
'This algorithm works on Sextets of digits
'For optimum speed, make sure the shortest multiplier is in BCDB()
'
'MulAB2C reorganises data and then process in triplets, and then repacks the result
'
SUB MulAB2C

LOCAL Cy,I,J,K,L,M,N,Ele

'These are "shadow" BCD registers used for MUL & Div which are carried out in triplets
LOCAL _BCDA((BCDSize*2)-1),_BCDB((BCDSize*2)-1),_BCDC(BCDSize*4)

'Unpack sextets into _BCDA() and _BCDB
J=0
FOR I=0 TO BCDSize-1
_BCDA(J)=BCDA(I) MOD 1000
_BCDA(J+1)=BCDA(I) \ 1000

_BCDB(J)=BCDB(I) MOD 1000
_BCDB(J+1)=BCDB(I) \ 1000

J=J+2
NEXT I

'Array for the intermediate multiplication
LOCAL C1(BCDSize*4)

Ele=(BCDSize*2)-1

'Set default length
M=Ele
'Find length of BCDA()
FOR I=Ele TO 0 STEP -1
IF _BCDA(I)<>0 THEN
M=I
EXIT FOR
ENDIF
NEXT I

'Set default length
N=Ele
'Find Length of BCDB()
FOR I=Ele TO 0 STEP -1
IF _BCDB(I)<>0 THEN
N=I
EXIT FOR
ENDIF
NEXT I

'Now Do the Long Multiplication of the two numbers
L=0
K=0

FOR I=0 TO M+1
Cy=0
FOR J=0 TO N+1

C1(J)=(_BCDA(I)*_BCDB(J))+Cy

IF C1(J)>&H3E7 THEN '&H3E7=999
Cy=C1(J)\&H3E8 '&H3E8=1000
C1(J)=C1(J) MOD &H3E8
ELSE
Cy=0
ENDIF

NEXT J

'Add the partial result to the overall total
'Add each element of C() to D() result into D()
Cy = 0

FOR J=0 TO Ele
K=J+L
_BCDC(K)=_BCDC(K)+C1(J)+Cy
IF _BCDC(K)>&H3E7 THEN
Cy=_BCDC(K)\&H3E8
_BCDC(K)=_BCDC(K) MOD &H3E8
ELSE
Cy =0
ENDIF
NEXT J

L=L+1

NEXT I

'Repack the Result from _BCDC() into BCDC()
J=0
FOR I=0 TO (BCDSize*2)-1 STEP 2
BCDC(J)=_BCDC(I) + _BCDC(I+1)*&H3E8
J=J+1
NEXT I

END SUB


'=========================================================
'
'Clear BCD Register
'Dest is Element 0 of a BCD register, eg FREQ(0)
'NumDigits is Number of digits in the BCD register, is Optional
'
'The default Width is BCDSize, but can be specified otherwise
'eg when clearing BCDC() which is twice as long as BCDA/B()
'
'This SUB is OPTIMISED for 24 digits
'
SUB ClrBCD(Dest,Width)
LOCAL I

IF Width<1 THEN
POKE VAR Dest,0,0:POKE VAR Dest,1,0:POKE VAR Dest,2,0:POKE VAR Dest,3,0
POKE VAR Dest,4,0:POKE VAR Dest,5,0:POKE VAR Dest,6,0:POKE VAR Dest,7,0
POKE VAR Dest,8,0:POKE VAR Dest,9,0:POKE VAR Dest,10,0:POKE VAR Dest,11,0
POKE VAR Dest,12,0:POKE VAR Dest,13,0:POKE VAR Dest,14,0:POKE VAR Dest,15,0
ELSE
FOR I=0 TO (Width*4)-1
POKE VAR Dest,I,0
NEXT I
ENDIF

END SUB


'=========================================================
'
'Copy a BCD string into a BCD Register/Destination
'Dest is the first element of a BCD register, eg BCDA(0), CurFreq(0)
'A$ is a string of BCD digits, eg "1234567890"
'
SUB LoadBCD(Dest,A$)
LOCAL I,J,M
LOCAL B(BCDSize),A2$(BCDNumDigits)

'Pad to make sure string is a multiple of 6 digits
A2$(1)=LPad$(A$,BCDWidth)

J = 0
M=LEN(A2$(1))
FOR I = M-BCDWidth+1 TO 1 STEP -BCDWidth
B(J) = VAL(MID$(A2$(1), I, BCDWidth))
J = J + 1
NEXT I

FOR I=0 TO (BCDSize*4)-1
POKE VAR Dest,I,PEEK(VAR B(0),I)
NEXT I


END SUB

'=========================================================
'
'Prepend leading 0 to make sure
'string is a multiple of ML digits
'
'This SUB is OPTIMISED for 24 digits
'
FUNCTION LPad$(A$,ML)
LOCAL I,J,M
M=LEN(A$)

IF M MOD ML <> 0 THEN
FOR I=ML TO 24 STEP ML
IF M<I THEN
' Print "LPad$:A$";A$ 'DEBUG
LPad$=STRING$(I-M,"0")+A$
EXIT FOR
ENDIF
NEXT I
ELSE
LPad$=A$
ENDIF
END FUNCTION


'==============================================
'
'Convert BCDA() into an unsigned 32 bit integer
'in BCDC()
'Algorithm process Sextets of decimal digits
'The Number must be less than 2^32, and can be at most 10 digits
'in Length, ie 2 Numeric vars with sextet packing
'The result will at most be 4 Numeric vars
'
SUB BCDA2Bin

LOCAL I,L,Cy

BCDC(0)=0:BCDC(1)=0:BCDC(2)=0:BCDC(3)=0

'Each sextets of digits are worth 0-999999, ie 1,000,000's

L=&HF4240 '&HF4240=1,000,000
FOR I=1 TO 0 STEP -1

'Add Next Decimal Digit into the Binary Long word
BCDC(0)=BCDC(0)+(BCDA(I)*L)

'Has this caused an overflow
IF BCDC(0)>&HFF THEN
'Yes
'Now ripple the overflow across the other bytes
Cy=BCDC(0) AND &H7FFFFF00 '2147483392 '&H7FFFFF00
BCDC(0)=BCDC(0)AND &HFF '255 '&HFF
BCDC(1)=BCDC(1)+(Cy\&H100) '256)

'Has this caused an overflow
IF (BCDC(1)>&HFF) THEN
'Yes
'Now ripple the overflow across the other bytes
Cy=BCDC(1) AND &H7FFFFF00 '2147483392 '&H7FFFFF00
BCDC(1)=BCDC(1) AND &HFF '255 '&HFF
BCDC(2)=BCDC(2)+(Cy\&H100) '256)

'Has this caused an overflow
IF (BCDC(2)>&HFF) THEN
'Yes
'Now ripple the overflow across the other bytes
Cy=BCDC(2) AND &H7FFFFF00 '2147483392 '&H7FFFFF00
BCDC(2)=BCDC(2) AND &HFF '255 '&HFF

BCDC(3)=BCDC(3)+(Cy\&H100) '256)

ENDIF
ENDIF
ENDIF

L=1

NEXT I

'Was there an overflow
IF (BCDC(3)>&HFF) THEN
ERROR "OverFlow"
ENDIF

END SUB


'=========================================================== ==
'
' General purpose way to move BCD data between source and dest
' arrays. NOT as fast as doing a direct copy usin g a FOR loop.
'
' This SUB is OPTIMISED for 24 digits
'
SUB CopyBCD(Src,Dest)

POKE VAR Dest,0,PEEK(VAR Src,0):POKE VAR Dest,1,PEEK(VAR Src,1):POKE VAR Dest,2,PEEK(VAR Src,2):POKE VAR Dest,3,PEEK(VAR Src,3)
POKE VAR Dest,4,PEEK(VAR Src,4):POKE VAR Dest,5,PEEK(VAR Src,5):POKE VAR Dest,6,PEEK(VAR Src,6):POKE VAR Dest,7,PEEK(VAR Src,7)
POKE VAR Dest,8,PEEK(VAR Src,8):POKE VAR Dest,9,PEEK(VAR Src,9):POKE VAR Dest,10,PEEK(VAR Src,10):POKE VAR Dest,11,PEEK(VAR Src,11)
POKE VAR Dest,12,PEEK(VAR Src,12):POKE VAR Dest,13,PEEK(VAR Src,13):POKE VAR Dest,14,PEEK(VAR Src,14):POKE VAR Dest,15,PEEK(VAR Src,15)

END SUB


'----------------------------------------------------------- -------
'
' LCDI2CLib V2.1 2014-July-24 : Removed Unused functions for compactness
'
' Micromite to LCD using I2C module
'
' (c) Peter Carnegie 2014
'
' This code is a heavily modified version of I2CLCD.BAS by John Gerrard
'
' This LIB can use several different models of PCF8574 based I2C to LCD interfaces
' The most important difference between such modules is whether they use P0~P3 or
' P4~P7 for the 4 bit data nibble to the LCD. This LIB can ONLY deal with LO or HI nibbles. If the
' data bits are spread across the PCF8574 port bits in other ways, then the user will
' have to edit the code in SUB LCDI2C_DirectSend
'
' The most important call is LCDI2C_Init which configures the library appropriately.
' LCDI2C_Init I2CAddr,NibPos,RSBitNo,ENBitNo,BLBitNo
'
' Where I2CAddr is the I2C Address
' NibPos=0 use low 4 bits ie P0~P3, NibPos=1 use high 4 bits ie P4~P7
' RSBitNo is PCF8574 bit number for RS 0~7
' ENBitNo is PCF8574 bit number for EN 0~7
' BLBitNo is PCF8574 bit number for Backlight control 0~7
'
'
' Using the I2C -> LCD Interface from eBay priced about US$1.60
' http://www.ebay.co.uk/itm/291116790762?ssPageName=STRK:MEWNX :IT&_trksid=p3984.m1439.l2649
' http://www.youtube.com/watch?v=wmTWk4Rwfw0
'
' Uses PCF8574 - 8 bit I2C Port Expander
' I2CAddress is &H27 with unjumpered address pads
' PCF8574 port is wired as follows
' P0 - RS
' P1 - RW
' P2 - EN
' P3 - Backlight On/Off
' P4 - D4 LCD
' P5 - D5 LCD
' P6 - D6 LCD
' P7 - D7 LCD
'
' LCDI2C_Init I2CAddr,NibPos,RSBitNo,ENBitNo,BLBitNo
' LCDI2C_Init &H27,1,0,2,3

'
' Using BigMik board (see http://www.thebackshed.com/forum/forum_posts.asp?TID=6715&PN =1&TPN=2)
' Uses PCF8574 - 8 bit I2C Port Expander
' I2CAddress is &H20~&H27 depending on jumper settings
' PCF8574 port is wired as follows
' P0 - D4
' P1 - D5
' P2 - D6
' P3 - D7
' P4 - RS
' P5 - RW ?
' P6 - Backlight
' P7 - EN
'
' LCDI2C_Init I2CAddr,NibPos,RSBitNo,ENBitNo,BLBitNo
' LCDI2CInit &H2X,0,4,7,6
'
' Functions in the Library are modelled after the native MMBasic LCD commands
' to ease code conversion
'
' Sub LCDI2C(LineNum,CharPos,Text$) Display string Text$ on LineNum of the display starting at CharPos
' Sub LCDI2C_Clear() Clears the LCD
' Sub LCDI2C_Init I2CAddr,NibPos,RSBitNo,ENBitNo,BLBitNo Initialise the I2C and LCD
' Sub LCDI2C_Close() Close the I2C LCD

' SUB LCDI2C_Backlight(1|0) Turn LCD backlight ON|Off

' SUB LCDI2C_CMD(Byte) Send a COMMAND byte to the LCD, ie RS=0
' SUB LCDI2C_DATA(Byte) Send a DATA byte to the LCD, ie RS=1
'
' SUB LCDI2C_DirectSend(Byte) Send a BYTE to the LCD - used internally by the lib
' SUB LCDI2C_WireWrite(Byte) Write a byte onto the I2C bus to the LCD
'
'
' Uses 6 Global Variables
' LCDI2C_LCDBackLight, LCDI2C_I2CAddr, LCDI2C_RSDataMask,
' LCDI2C_EMask, LCDI2C_BackLight, LCDI2C_NibPos
'


'----------------------------------------------------------- -------
'
' Print String to LCD
'
'
SUB LCDI2C(LineNum,CharPos,Text$)
LOCAL I
IF LineNum=1 THEN I=(&H80 + CharPos-1)
IF LineNum=2 THEN I=(&HC0 + CharPos-1)
IF LineNum=3 THEN I=(&H94 + CharPos-1)
IF LineNum=4 THEN I=(&HD4 + CharPos-1)
LCDI2C_CMD(I)

FOR I=1 TO LEN(Text$)
LCDI2C_DATA(ASC(MID$(Text$,I,1)))
NEXT I

END SUB


'----------------------------------------------------------- -------
'
' INITIALIZE LCD
' Must be called before any LCDI2C operations
' I2CAddr is the I2C Address
' NibPos=0 use low 4 bits ie P0~P3, NibPos=1 use high 4 bits ie P4~P7
' RSBitNo is PCF8574 bit number for RS 0~7
' ENBitNo is PCF8574 bit number for EN 0~7
' BLBitNo is PCF8574 bit number for Backlight control 0~7
'
SUB LCDI2C_Init I2CAddr,NibPos,RSBitNo,ENBitNo,BLBitNo

'Backlight Control bit
DIM LCDI2C_BackLight
LCDI2C_BackLight=2^BLBitNo

'Current Backlight state - default to ON
DIM LCDI2C_LCDBackLight
LCDI2C_LCDBackLight=LCDI2C_BackLight

'I2C Bus Address
DIM LCDI2C_I2CAddr
LCDI2C_I2CAddr=I2CAddr

'RS Control bit
DIM LCDI2C_RSDataMask
LCDI2C_RSDataMask=2^RSBitNo

'EN Control Bit
DIM LCDI2C_EMask
LCDI2C_EMask=2^ENBitNo

'Save if P0~P3 or P4~P7 bits of PCF8574 port
DIM LCDI2C_NibPos
LCDI2C_NibPos=NiBPos

'Start I2C
I2C OPEN 400,100

'%0011---- %0011---- 8-bit / 8-bit
LCDI2C_CMD(&H33)
'%0011---- %0010---- 8-bit / 4-bit
LCDI2C_CMD(&H32)

' Byte commands - To configure the LCD

' Display Format
' 4bit mode, 2 lines, 5x7
'
' 001LNF00
' %00101000
LCDI2C_CMD(&H28)

' L : 0 = 4-bit Mode 1 = 8-bit Mode
' N : 0 = 1 Line 1 = 2 Lines
' F : 0 = 5x7 Pixels 1 = N/A


' Setup Display
' Display ON, Cursor On, Cursor Steady
'
' 00001DCB
' %00001110
LCDI2C_CMD(&H0C)

' D : 0 = Display Off 1 = Display On
' C : 0 = Cursor Off 1 = Cursor On
' B : 0 = Cursor Steady 1 = Cursor Flash


' Setup Cursor/Display
' Inc Cursor Cursor Move
'
' 000001IS
LCDI2C_CMD(&H06)
' I : 0 = Dec Cursor 1 = Inc Cursor
' S : 0 = Cursor Move 1 = Display Shift

LCDI2C_CMD(&H01)

'Turn Off LCDBacklight
LCDI2C_BackLight(0)

END SUB


'----------------------------------------------------------- -------
'
' Turn Backlight On | Off
' If State=0 then OFF Else ON
'
SUB LCDI2C_BackLight(State)
IF State<>0 THEN
LCDI2C_LCDBacklight=LCDI2C_Backlight
ELSE
LCDI2C_LCDBacklight=&H0
ENDIF
LCDI2C_WireWrite(0)
END SUB


'----------------------------------------------------------- -------
'
' Send Command Byte to LCD
'
'
SUB LCDI2C_CMD(Byte)

'Send Hi Nibble
LCDI2C_DirectSend(Byte AND &HF0)

'Send Low Nibble
LCDI2C_DirectSend((Byte AND &H0F) * 16)

END SUB


'----------------------------------------------------------- -------
'
' Send Data Byte to LCD
'
'
SUB LCDI2C_DATA(Byte)

'Send Hi Nibble
LCDI2C_DirectSend((Byte AND &HF0) OR LCDI2C_RSDataMask)

'Send Lo Nibble
LCDI2C_DirectSend(((Byte AND &H0F) * 16) OR LCDI2C_RSDataMask)

END SUB


'----------------------------------------------------------- -------
'
' Send Byte to LCD over I2C
' NB, we don't call LCDI2C_WireWrite in this SUB because that results in
' MUCH slower execution time than inlin'ing the I2C Write's
'
'
SUB LCDI2C_DirectSend(Byte)

LOCAL B,B1

'Use a copy so that we don't mess up caller's argument

'Are D4-D7 of LCD mapped to P0~P3 of PCD8574 ?
IF LCDI2C_NibPos=0 THEN
B1=Byte\16
ELSE
B1=Byte
ENDIF

'Take EN high, use var B 'cos it's quicker than doing the OR's in the I2C Write command itself
B=B1 OR LCDI2C_EMask OR LCDI2C_LCDBacklight
I2C WRITE LCDI2C_I2CAddr,0,1,B

'Take EN Low
I2C WRITE LCDI2C_I2CAddr,0,1,B1 OR LCDI2C_LCDBacklight

END SUB


'----------------------------------------------------------- -------
'
' Send Byte over I2C
'
'
SUB LCDI2C_WireWrite(Byte)
I2C WRITE LCDI2C_I2CAddr,0,1,Byte OR LCDI2C_LCDBacklight
END SUB

The only Konstant is Change
 
Print this page


To reply to this topic, you need to log in.

The Back Shed's forum code is written, and hosted, in Australia.
© JAQ Software 2025