Hello folks,
I was wondering if someone would be good enough to give me some general help on the following topic:
Using a 908JL16, 28 pin device, I'm attempting to use it to both generate and (later) decode the sub audible tone frequencies that range from 67 Hz to 250.3 Hz. If I can get the generation capability working, I believe that I can make the decoding portion work on my own.
I've read some of the posts here on using PWM to generate the tones in other processors. Using an XCEL spead sheet, I computed (and plotted) an accurate sine wave in a 256 byte table.
The tones (of a predetermined frequency) will be generated continuously when the M/P is in the transmit mode, and I would like to use Timer 1 for this purpose, and will be using a 9.8304 Mhz external oscillator.
Some questions:
1) Must I use the buffered mode when stepping through the SineTable?
2) How should the timers be set up / serviced?
3) What interruots are required?
4) How do I compute the 'time slices' as a function of the desired frequency (e.g. 100.0 Hz, 250.3, etc)?
Any code specific examples will be most appreciated.
I did try reading through AN-1777, but unfortunately got lost when trying to figure out the HC-08 code for dual chord tone synthesis.
Thanks, in advance, for any help.
Joe Burch
已解决! 转到解答。
Hello Joe,
I don't think what you are proposing is feasible, certainly not with the 908JL16 and a 2.5MHz bus frequency.
On the basis that you will need to resolve the frequency to 0.1Hz, the period resolution of a full cycle would need to be 1.6us at a frequency of 250Hz, the worst case scenario. At 67Hz the period resolution would increase to 22us.
Now the per step period resolution will depend on the number of steps per cycle. For the moment let's assume 64 steps. This would mean that the period resolution per step would become 25ns for a frequency of 250Hz, or a PWM clock of at least 40MHz. This is more than an order of magnitude higher than the 908JL16 can provide.
With 256 steps per cycle, the PWM clock would need to be 160MHz. This is obviously not the best method for generating a sine wave that requires good frequency resolution.
One method that can provide good frequency resolution, with more moderate clock rate, is the Direct Digital Synthesis (DDS) method. This would normally require a proper D/A converter for the tone output. I suggest that you Google this subject to find out more. In the more distant past, I actually used a special chip that provided audio tones by this method.
Regards,
Mac
Hello Joe,
I don't think what you are proposing is feasible, certainly not with the 908JL16 and a 2.5MHz bus frequency.
On the basis that you will need to resolve the frequency to 0.1Hz, the period resolution of a full cycle would need to be 1.6us at a frequency of 250Hz, the worst case scenario. At 67Hz the period resolution would increase to 22us.
Now the per step period resolution will depend on the number of steps per cycle. For the moment let's assume 64 steps. This would mean that the period resolution per step would become 25ns for a frequency of 250Hz, or a PWM clock of at least 40MHz. This is more than an order of magnitude higher than the 908JL16 can provide.
With 256 steps per cycle, the PWM clock would need to be 160MHz. This is obviously not the best method for generating a sine wave that requires good frequency resolution.
One method that can provide good frequency resolution, with more moderate clock rate, is the Direct Digital Synthesis (DDS) method. This would normally require a proper D/A converter for the tone output. I suggest that you Google this subject to find out more. In the more distant past, I actually used a special chip that provided audio tones by this method.
Regards,
Mac
Hello Joe,
I have had some further thoughts about this topic.
When I did the basic calculations, I was assuming that each step would be of fixed duration. However, the DDS method achieves the improved frequency resolution due to the fact that there may be small variations of step duration over each output cycle. Since these adjustments are spread over the whole cycle, there is little distortion to the output signal. Another characteristic of DDS is that, as the output frequency increases, the number of steps per cycle will gradually reduce, thus again moderating the clock frequency requirement. yet a further advantage of DDS is that the frequency adjustment parameter setting is directly proportional frequency, rather than output period, as for other synthesis methods.
As I said previously, DDS usually makes use of a D/A converter to achieve higher output frequencies. However, for sub-audible frequencies, digital-to-analog conversion using a PWM may work. Therefore it might be possible for you to implement DDS code within an MCU. I suspect that this code would need to be written in tight assembly code, and you also may require a somewhat higher bus frequency than the HC908 can easily provide.
Let's set some parameters for DDS operation.
I will assume that the main DDS register is 24 bits in length, and that the DDS clock is 16kHz, or thereabouts. To output a constant frequency, a fixed value N needs to be added to the main register for every DDS clock interval. With this arrangement, the frequency resolution would be Fclk / (2^24) ~ 0.001Hz. To achieve output frequencies beyond 250Hz, the fixed value will need to be a 16-bit value. The output frequency will be given by the expression N * Fclk / (2^24). Note that the waveform at the MS bit of the main register will be a square wave at the set frequency.
The upper bits of the main register would form the address for a sine look-up table, and the value from the look-up table would be applied to the PWM. Assuming 8-bit resolution for the PWM, the minimum bus clock (and PWM clock) would need to be Fclk * 256 ~ 4 MHz. However, this would mean that the execution time for each update would need to be completed in less than 250 cycles. I am not sure whether this is feasible. If a HCS08 device were to be used, a higher bus frequency would be available. An 8MHz bus frequency would allow up to 500 cycles for each update. However, if other functionality is required whilst the tone is output, the allowable execution cycles may require to be considerably reduced.
Now assuming an HCS08 device, I would suggest that the overflow interrupt for the TPM module used for the PWM output be used to trigger each update of the PWM register. The new PWM value should then come into effect on the next overflow.
Because of the symmetry of a sinusoid, it may be possible to reduce the size of the look-up table to one-half or one-quarter of the output cycle. But this will involve further manipulations of the data, and may push over the time limit.
Regards,
Mac
Hi MAC,
Thanks very much for both of your replies. I'll do some work on my spread sheet and code with the latest information.
This 'hobbyist project' (www.k3jls.net) enables ham radio operators to make use of older, high quality FM transceivers and requires the CTCSS capability. It currently works very well with a dedicated CTCSS chip (MX-COM 465P), but I was trying to make it simpler yet by emulating the CTCSS functions in the software. I'm reluctant to change the processor to a faster unit mainly because of the recoding time and surface mounting issues. The 908JL16 is nice because it is a 28 pin DIP, and has proven itself to be extremely reliable.
I came across AN-1222 last nite which describes an HC08 arithmetic frequency synthesis approach using an 8 bit DAC. My primary reason for wanting to use PMW is because - in my current design - I can't afford to free up an 8 bit port to dedicate to the DAC - the best that I can do is to reuse 4 bits.
I'll see if I can find some information on the web about the feasibility of arithmetic synthesis using a 4 bit DAC for frequencies below 250 Hz.
Thanks again for all of your help - it is much appreciated.
Joe Burch
Hello Joe,
joeburch wrote:I'm reluctant to change the processor to a faster unit mainly because of the recoding time and surface mounting issues. The 908JL16 is nice because it is a 28 pin DIP, and has proven itself to be extremely reliable.
You might also consider the 9S08SE8 device, if 8K of flash be sufficient. This is also available in a 28-pin DIP package. Apart from the reduced amount of flash, the other significant omissions appear to be no IIC module, and one less TPM channel than the older device. However, the TPM module for the 9S08 is easier to use for PWM mode, compared with the TIM module. Buffered PWM is basic to the TPM module.
At the assembly code level, the 9S08 is backward compatible with the HC908, so any re-coding should not be too much of an issue. However, the peripheral registers are not the same for the two device families, so the code for these would need to be reviewed.
Most importantly, the BDM interface is much easier to use for programming and debugging, compared with the MON08 interface, and its multiplicity of pin connections.
Regards,
Mac
Mac,
Thanks for the tip.
I took a look at this M/P. It looks like it might work if I were to really compress the code and eliminate some of the features. I'm currently at about 14K, much of which is required to support 100 user programmable scan channels with alpha tags.
Perhaps I'll just continue along with the external CTCSS device. The combination of the M/P and the CTCSS chip work exceptionally well. I guess the 'purist' in me (with spare time) was looking for a way to somehow combine both functions.
For what it's worth, the GE folks who have integrated CTCSS into their radios (starting back in the '80's) use a simple 2 bit DAC (two leads from the P/C, some resistors and a summing op-amp) to accomplish their version of CTCSS encoding. As a matter of fact, they use virtually the same circuit in a whole series of radios.
I wonder how they do it.
Regards,
Joe
Hello Joe,
If you already have done the sine table, you might give your original concept of using the PWM output a try. The code required to implement the DDS approach turns out to be rather more simple than I first thought. The execution time for the output update code is only 56 cycles. You should be able to get some results, even with a 2.5MHz bus frequency.
I would suggest setting up the TIM channel for unbuffered PWM, and also enable the interrupt for this channel. The interrupt will occur following each output compare event. The modulo register TMOD should be set to a value of $00FF. With a 2.4576 MHz bus frequency, the DDS clock and PWM frequency would be 9.6 kHz.
We actually need to pretend that DDSREG is a 20-bit register with respect to the position of the 8-bit output data. This should then require a FREQ setting of 109.2 per hertz output.
Within the TIM channel ISR framework, there would be the following experimental code.
; Add FREQ value to DDSREG LDA DDSREG+2 ;[3] ADD FREQ+1 ;[3] STA DDSREG+2 ;[3] LDA DDSREG+1 ;[3] ADC FREQ ;[3] STA DDSREG+1 ;[3] AND #$F0 ;[2] Mask upper nybble NSA ;[3] PSHA ;[2] LDA DDSREG ;[3] ADC #0 ;[2] STA DDSREG ;[3] AND #$0F ;[2] Mask lower nybble NSA ;[3] ORA 1,SP ;[4] Combine nybbles AIS #1 ;[2] Adjust stack pointer TAX ;[1] CLRH ;[1] LDA SINTAB,X ;[4] Fetch value from sine table CLR TCH0H ;[3] STA TCH0L ;[3] Update TIM channel duty
Both DDSREG (24 bit) and FREQ (16 bit) should reside within zero page to minimize the cycles required.
Your comment about the GE solution makes me wonder about the amount of harmonic distortion that can be tolerated. Towards the upper frequency limit of the range, the third and higher harmonics would be at audible frequencies.
Regards,
Mac
Hi Mac,
Thanks again for your time.
I'm going to try to code this thing completely myself, and will probably have some more questions.
For openers, how can I compute the exact single cycle time for the JL90816, using a 9.8304 Mhs crystal oscillator?
Is it the reciprocal of the bus speed 2.456 Mhz or about .4 us??
I need to know this to set up the 'timer ticks' on the interrupt.
Joe
Hello Joe,
A crystal frequency of 9.8304 MHz will produce a bus frequency of 2.4576 MHz (with a period of about 407ns). If you intend using the TIM module to output a PWM signal with 8-bit resolution, the TMOD setting would be 255 decimal, for an overflow period of 256 bus cycles (assuming prescale 1). The overflow rate will be 9600 Hz, and would be available as a periodic interrupt.
However, as previously mentioned, the TIM channel must utilize the output compare interrupt, rather than the overflow interrupt, to change the unbuffered PWM duty cycle without side effects.
Regards,
Mac
Hi Mac,
Thanks for your help on this.
I’ve been trying to follow the theme on AN-1771, with just one tone, with DREG’s generated for each of the ctcss frequencies (not shown here). I did this on a separete spreadsheet.
I followed most everything in the code that you provided except for the value to be assigned to FREQ. Could you plase elaborate on that?
The initpwm routine to generate the CTCSS tones is accessed when the radio is in the transmit mode (IRQ = 0). To that end, I have the IRQ interrupt pin set on an ‘edge only’ basis as IRQ will be 0 for the duration of the transmission.
The initpwm routine is accessed on transmissions (IRQ=0), and the initial settings for Timer 1, Channel 0 are placed here.
The ‘looper’ instruction at the end of this routine is designed to provide an exit mechanism when the PTT switch is released (IRQ=1). I would assume that the Timer interrupt would be generated while the program is in this ‘loop’.
The problem is that the timer won’t generate any interrupts. I know this because I changed the vector to point to a piece of code that would provide an LCD indication to that effect. I must have screwed something up in the code.
Could you please give it the once over and let me have the benefits of your thoughts / suggestions.
Thanks, in advance, for your help.
Joe Burch
; CTCSS TONE GENERATOR ROUTINES
; PWM Initialization Routine
initpwm
; index into ctcss_tbl to get the DREG value for this tone
cli ; so that the interrupt will work
ldhx #ctcss_tbl ; point to table
lda ctcss ; get current ctcss value
tax ; load index register
lda ,x ; get DREG value
sta ddsreg ; store to ddsreg
; now we have the first part solved.
clr track
; tim1 status and control register
; t1sc
; b7 - TOF - Timer Overflow = 1 = Output
; b6 - TOIE - Tim Ovfl Int Enable = Input = 1
; b5 - TSTOP - Timer Stop = Input = 1
; b4 - TRST
; b3 - Unused
; b2 - Prescaler Bit 2 - Input
; b1 - Prescaler Bit 1 - Input
; b0 - Prescaler Bit 0 - Input
; stop the timer TSTOP = 1
mov #$30,t1sc ; reset counter and prescaler = TRST = 1
ldhx #$00ff ; set pwm period
sthx t1modh
ldhx #$0080 ; load initial duty cycle
sta t1ch0l ; store it
; set up for unbuttered output compare
mov #$20,t1sc0
bset 1, t1sc0 ; set toggle on overflow bit
; activate the output compare
bset 0,t1sc ; set prescale = 1
bclr 5,t1sc ; clear the TSTOP bit
; tim1, channel 0 status and control register
; t1sc0
; b7 - CH0F
; b6 - CH10E
; b5 - MS0A = 1 for unbuffered operation
; b4 - MS0B = 0 for unbuffered operation
; b3 - ELS0B = 1 = set output on compare
; b2 - ELS0A = 1 = set output on compare
; b1 - TOV0 = 1 = toggle on overflow
looper ; b0 - CH0MAX = 0 (no steady state)
bih qtrans
bra looper
qtrans
bset 5,t1sc ; set the TSTOP bit
jmp dnxmit ; all done transmitting
; interrupt service request
_tovint ; tovi sets the new fsamp rate and calculates
; the new sin_table pointers for the next
bclr 7,t1sc ; clear the overflow bit
; Add FREQ value to DDSREG
' 'BIG MAC Code follows...."
lda ddsreg+2 ;
add freq+1 ;
sta ddsreg+2 ;
lda ddsreg+1 ;
adc freq ;
sta ddsreg+1 ;
and #$F0 ; Mask upper nybble
nsa ;
psha ;
lda ddsreg ;
adc #0 ;
sta ddsreg ;
and #$0F ; Mask lower nybble
nsa ;
ora 1,SP ; Combine nybbles
ais #1 ; Adjust stack pointer
tax ;
clrh
lda sin_tbl,x ; Fetch value from sine table
clr t1ch0h ;
sta t1ch0l ; Update TIM channel duty
; counting starts again
inc track ; set up for next interrupt.
bih qtrans ; stop if PTT is released
rti ; return
Hello Joe,
The constant value within the FREQ register determines the output frequency. The frequency may be calculated from the following formula:
Frequency = FREQ * Fclk / (2^N)
where Fclk = 9600 Hz, and N = 20
i.e. Frequency = FREQ * 9600 / 1048576
The FREQ value will need to be 16-bit size. On this basis, the CTCSS_TBL will require 16-bit entries, rather than 8-bit. I am assuming that this table is indexed so that the first entry refers to the lowest tone frequency, and each subsequent entry to progressively higher frequencies.
I can see a number of potential issues with your existing code.
If it is possible that you would require the TIM module overflow interrupt for other timing purposes, especially during receive mode, once started the TPM counter should not be stopped, at least not for extended periods. I would suggest that the initialisation of T1MOD and T1SC be done as part of the general reset initialisation. This would leave T1C0 and T1C0SC to be set up during INITPWM routine. It would also mean that PWM operation would be disabled at the completion of the transmission, by simply clearing T1C0SC.
Your setting for T1C0SC seems a little ambiguous. You will require unbuffered PWM mode, with clear output on compare for non-inverted PWM operation. When overflow occurs the output will become set, and when output compare occurs later in the cycle, the output will be cleared. To achieve this you might use the instruction MOV #$5A,T1C0SC. There is no need to separately control individual bits.
; PWM Initialization Routine initpwm: ldx CTCSS ; CTCSS tone index value lslx ; Adjust for word address clrh lda ctcss_tbl,x ; High byte sta freq lda ctcss_tbl+1,x; Low byte sta freq+1 mov #$08,ddsreg clr ddsreg+1 clr ddsreg+2 clr track bset 5,T1SC ; Temporarily stop TIM1 ldhx #0080 ; Initial PWM duty 50% sthx T1C0 mov #$5A,T1C0SC; Unbuffered PWM, clear on compare bclr 5,T1SC ; Re-enable timer rts
The PWM waveform will commence on the next TIM1 overflow. I note you had coding errors when you attempted to initialise T1CH0 with the following code:
ldhx #$0080 ; load initial duty cycle
sta t1c0l ; store it
Your naming of the ISR code suggest that you are implementing the TIM1 overflow interrupt. This is not correct, as previously mentioned. The PWM update should occur within the output compare ISR associated with CH0.
There are two major problems with the existing ISR code - you do not preserve the H-register setting when the ISR ends, and exiting via QTRANS at the completion of a transmission does not seem to use a RTI instruction, so the stack would become unbalanced - not a desirable situation. I might suggest that the following framework would be satisfactory.
; Output compare ISR_toc0int: bclr 7,T1C0SC ; Clear flag bil TOC01 ; Branch if PTT is active clr T1C0SC ; Disable PWM output rti TOC01: pshh ; Save current value ; Add FREQ value to DDSREG ... inc track pulh ; Restore previous value rti
Regards,
Mac
Hi Mac,
Three more items that I didn't mention:
1) the 'pulse' is a full 4.5 volts,
2) I know that the program is getting to toc01 because I put a print message there and it hits it consistently,
3) It looks like there is a brief pulse of much shorter duration before the pulse begins to ramp up and then ramp down.
I hope that this helps.
Thanks, again,
Joe Burch
Hello Joe,
There are two separate issues that initially may need to be separated - whether the phase accumulation process is operating correctly, and the PWM operation to produce a sine waveform. Of course you will need a low pass filter at the PWM output to see a sine wave on the oscilloscope.
To firstly test the operation of the DDS phase accumulator, temporarily transfer the state at 3,DDSREG to an output port pin. If operation is correct, the waveform at the pin will be a square wave at the required output frequency. You refer to a "print message" within the ISR code. This should be eliminated for further testing on the assumption it will consume too many cycles.
For an output frequency of 67.0Hz the FREQ value should be 7318, increasing to 27339 for a frequency of 250.3Hz. Are these the values that are being loaded into FREQ? Note that, if you are currently using the internal RC oscillator, in lieu of the crystal, the DDS clock rate will probably differ from the 9.6kHz on which the calculations were based.
Once this is producing the anticipated result, you can then move on to the operation of your sine lookup table, and any remaining PWM issues. I would suggest that the sine table should avoid using the values $00 and $FF to avoid possible timing issues between the overflow and output compare events. Perhaps the span of the table values might be 128 +/- 126, using an explicit zero reference state of 128. Alternatively, it would be possible to utilize an implicit zero reference by spanning the range 1 through to 254 ($01 to $FE).
I assume that you are now updating the PWM compare value within the correct ISR. An attempt to update within the overflow ISR would fail whenever the compare setting was less than the sum of the interrupt latency cycles, plus the number of cycles within the ISR code to T1C0 being written. An additional full PWM cycle would be added to the pulse width..
To eliminate a spurious pulse at the start of the PWM output may require a change of the initialisation value for DDSREG. Perhaps a value of zero rather than $080000. It may depend on the starting phase of your sine lookup table.
Regards,
Mac
Hi Mac,
Thanks for your reply and suggestions.
First of all, I found the problem with the 'ramping up and down'. One of the 3 bytes used in the DDSREG was being overwritten by another routine in the main program. I found this with the CodeWarrior simulation. So, I reassigned the variable and now I can see short duration 'pulses'. This particular program is close to 14K in length. I'll build a simple filter today to see the shape of the waveform.
Following your suggestion, I 'retooled' the sine table so that no 00 or FF values are in there. I plotted it on XCEL and it still looks like a sine wave. You had suggested that I make a quick mod to the code to see if the square wave generated was close to the selected frequency using another output port. Would you please provide a quick example of this code?
I did double check the generated values of 67 and 250.3 Hz and they are correct, both on the spread sheet and within the data table. I wish I had taken the time to learn 'C programming' as it would be a lot easier to do this in the assembler as compared with generating the data in a spread sheet and then manually entering it into the program. Ugh!
BTW, do you work for Freescale?
Joe
joeburch wrote:.. . I wish I had taken the time to learn 'C programming' as it would be a lot easier to do this in the assembler as compared with generating the data in a spread sheet and then manually entering it into the program. Ugh!
Hi Joe,
No, you are doing it the right way, almost. the MCU would be hard pressed to generate the sine wave in real time. A table is the best way to do what you are doing.
What you need is a better way to get the table into your program. I build tables in MathCad all of the time, and then "import" them into the program. You could easily do the same.
Have Excel export the table into a CSV file. You could then edit the file to put a "dc.w" in front of the data, and "include" it into your source file. If the assembler complains about lines being too long, you could break it up into smaller lines.
What I do is use a global replace to change every comma to a "<cr> dc.w ", so that each table entry is on a separate line. I typically automate this, using NotePad++ macros.
There are other ways to do this, as well. I had a colleague who wrote an Excel macro that directly generated include files for the assembler. I wish that I could do that with MathCad.
Hello,
I just did this yesterday.
Usually you end up with a column of data in an Excel spreadsheet.
The next part is the trick for the non Excel experts.
select the column of data you want.
copy it
in a new sheet in A1 use paste special with values only and transpose selected
now save this sheet as a csv file
open this file in your editor and you're 95% of the way there.
Hi Peg,
Thanks for the tip. I've been trying (with Mac's help) for the last 2 days to get the JL16 to output a PWM generated sine wave to replace the ctcss chip used in my radio conversions (www.k3jls.com).
I must be doing something really wrong but just can't seem to figure it out.
But, maybe things will work out tomorrow.
Thanks,
Joe
Hello Joe,
joeburch wrote:You had suggested that I make a quick mod to the code to see if the square wave generated was close to the selected frequency using another output port. Would you please provide a quick example of this code?I assumed use of PTB0 as test pin, but modify to suit your requirements. This code could be inserted into the ISR immediately after the TIM channel register is written.
BRSET 3,DDSREG,TOC02 ; Branch if phase output MSB is set BCLR 0,PTBD ; Clear test pin output BRA *+4 ; Skip next alwaysTOC02: BSET 0,PTBD ; Set test pin output
No, I have no association with Freescale. Once upon a tiime I did work for Motorola Communications (as it was then known) in Australia. I have been self employed for many years.
Regards,
Mac
Hi Mac,
There is a 100 micro second pulse on the output of Timer 1, Channel 0 that is constant no matter what the ctcss frequency. The same pulse is repeated about every 27 milli seconds, and this is virtually the same for every ctcss selection.
The test pin's (I used ptb 6) frequency changes, based upon the ctcss selection, but is nowhere near the frequency of a ctcss signal. For example, for a 250.3 ctcss frequency (the slowest repetition rate), is about 500 ms on, followed by 500 ms off. It's a square wave for sure, but a mighty slow one.
The 67.0 hz signal is on for 200 ms and then off for about 250 - it's hard to tell with the slow scope sweep.
I'll go over the code once again.
I was hoping that you worked for Freescale so that I could write a commendation for you.
Joe
Thanks again,
Joe
Hello Joe,
Your test results would seem to indicate that the TIM modulo setting is not correct. With a repetition interval of 27ms, this would closely correspond to the free running case with 65536 bus periods for each overflow. If this is the case, the square wave output will be 256 times slower than that anticipated. This would also account for a very small PWM pulse duty cycle.
If you pulled the modulo setting code from the CTCSS startup routine, did you then place it within the general reset initialisation processing?
Regards,
Mac