News:

Let's find out together what makes a PIC Tick!

Main Menu

Generate a 1 Khz sine wave with the Freqout

Started by Giuseppe, Aug 16, 2022, 10:07 PM

Previous topic - Next topic

Giuseppe

Hello
I wanted to generate a 1 Khz sine wave with the Freqout function of the Positron. I loaded the program below but the waveform that comes out is not the best. I put the 2 1K resistors + the 2 100nF capacitors as per Positron manual for hardware output, but the result was disappointing even putting the 10uF electrolyte capacitors and connecting a loudspeaker.
Do you have any ideas to propose?



Device = 12F1501
Xtal = 16

$define on_debug 0  '

$if _device = _12f1822
Config1 FOSC_INTOSC, WDTE_OFF, PWRTE_ON, MCLRE_OFF, CP_ON, CPD_ON, BOREN_ON, CLKOUTEN_OFF, IESO_OFF, FCMEN_OFF
Config2 WRT_OFF, PLLEN_OFF, STVREN_OFF, BORV_25, LVP_OFF
$endif

$if _device = _12f1501
Config1 FOSC_INTOSC, WDTE_OFF, PWRTE_ON, MCLRE_OFF, CP_ON, BOREN_ON, CLKOUTEN_OFF
Config2 WRT_OFF, STVREN_OFF, BORV_LO, LPBOR_OFF, LVP_OFF
$endif

'--------------------------------Set registro Option_reg ----------------------------
OPTION_REG =%00000111
OSCCON = %01111000   
WDTCON = %00010100   

'-------- Interrupt  --------------------------

INTCON = %00100000                       
'------------------------------------------------------------------------
LATA = %00000000    '
TRISA = %00111000   ' 
WPUA = %00111000    '
CM1CON0.7 = 0   '
$if _device = _12f1822
SRCON0.7 = 0    '
$endif


Symbol pul_up = PORTA.5
Symbol pul_down = PORTA.4
Symbol bf = PORTA.2

Do

FreqOut bf, 1000, 1000

DelayMS 1000

Loop
End





david

#1
You may want to look at some DDS options as these produce a good waveform even prior to any (modest) filtering.
I have some very old code that will need a bit of cleaning up first but you're welcome to have it.
To get started have a read of this-
http://www.g4jnt.com/pic_dds.pdf
I developed my own code prior to finding that item but he does explain things well. 
I used a 16 bit phase accumulator and decimated back to 8 bits after adding the phase increment and then outputting the value to PortB with a Dale R2R ladder network connected to it (my DAC).   

This is the working loop-
Sineloop:   
        acc=acc+incr            'add phase increment to accumulator
      PORTB=CRead8 Sine [acc>>8]     'decimate and look up value
      GoTo Sineloop                'repeat the loop
      
        Sine:      
CData As Byte  $80,$83,$86,$89,$8C,$8F,$92,$95,$98,$9C,$9F,$A2,$A5,$A8,$AB,$AE,_
               $B0,$B3,$B6,$B9,$BC,$BF,$C1,$C4,$C7,$C9,$CC,$CE,$D1,$D3,$D5,$D8,_
               $DA,$DC,$DE,$E0,$E2,$E4,$E6,$E8,$EA,$EC,$ED,$EF,$F0,$F2,$F3,$F5,_
                etc, etc

Cheers,
David

keytapper

I thought to use set of 16 value (or 32) to modulate the PWM. I think both MCU are capable to use, unless of difficulties to free the dedicated pins.
Ignorance comes with a cost

Stephen Moss

Quote from: Giuseppe on Aug 16, 2022, 10:07 PMI put the 2 1K resistors + the 2 100nF capacitors as per Positron manual for hardware output, but the result was disappointing
If my calculations are correct those values creates a 1.4KHz low pass filter, assuming those values are spot on. However, but with a possible 5% tolerance on the resistor and 20% on the capacitor that could be getting a little tight to your 1KHz signal, try changing the resistor to the next preferred value either side of 1K Ohms and see what difference that makes.
Although the associated peripherals should be off as default it might also be worth Writing to the APFCON register to move the peripherals away from pin RA2 and see if that also has any effect.  

Yasin

"PWM_WGen.bas" in compiler examples. This example will help.

david

The problem with PWM tone generation is that you have a sine wave (1.77V RMS) buried in 5V of ever changing PWM carrier.  This means you either need very good filtering or you need a lot of octaves between the wanted tone and the first PWM carrier fundamental.
Two series connected RC networks give a sloppy turnover point (below 1kHz) and a slow roll off.  I modelled the two stage filter and compared it to a 3rd order, 1.5dB ripple Chebyshev filter.  Even by 3kHz the active filter is just over 20dB better and by 10kHz is about 37dB better.  Chances are you may need to buffer the filtered output anyway so use the buffer as a decent filter.
If I can find how to attach an image I would share the results.....used to work in the past.

David

david

Comparison of filters.

top204

The Freqout and DTMFout commands are a throwback from the BASIC Stamp II unit, and were created for the compiler for backward compatability only when people were moving away from the Stamp and PBP and onto my compiler. They were created in the compiler about 21 years ago!

They do not use any peripherals on the device, so the crude PWM waveform used as a simple DAC is generated by toggling a pin in a tight loop. That is the reason they need a fast oscillator running the device, and 20 MHz or over is recommended.

For a decent sine wave generator, a DDS algorithm is a good method, as discaussed above, and below is a demo program I have just created, to show a simple sine wave generator using DDS. It uses the CCP1 peripheral operating as a fast PWM to act as a simple DAC:

'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' DDS sinewave generator with a PWM channel acting as a DAC.
' This demo is created for standard 18F devices that contain a CCP peripheral.
'
' Written for the Positron8 compiler by Les Johnson.
'
' A simple low pass filter circuit, as shown below, can be used to get a crude sine waveform output for testing.
'
'         |     2.7K
' PORTC.2 |----/\/\/\-----o-----> Output
'         |               |
'         |             __|__
'         |             _____ 100nF
'         |               |
'         |               |
'         |             -----
'                        ---  Gnd
'
    Device = 18F25K20                                       ' Tell the compiler what device it will compile for
    Declare Xtal = 64                                       ' Tell the compiler what frequency the device is operating at (in MHz)

'-----------------------------------------------------------------------------------------
' The main program starts here
'
Main:
    Setup()                                                 ' Setup the program
    DDS_SineWave(3000, 10000)                               ' Output a 3 KHz sinewave for approx 10 seconds

'-----------------------------------------------------------------------------------------
' Generate a sinewave signal from the CCP1 peripheral pin
' Input     : pFreq holds the frequency to generate
'           : pDuration holds the approximate time (in ms) for the waveform to be outputted
' Output    : None
' Notes     : DDS values calculated by:
'               cDivisor = (Device MIPS * 1000000) / 128
'               DDS value = (Frequency(in Hz) * 64) * (65536 / cDivisor)
'
Proc DDS_SineWave(pFreq As Dword, pDuration As Dword)
    Dim dAccum As Dword Access                              ' Accumulator for the DDS
    Symbol cDivisor = (((_xtal / 4) * 1000000) / 128)       ' Create a divisor based upon the device's operating frequency
    Symbol cMult = (65536.0 / cDivisor)                     ' Create a floating point multiplier constant
'
' Sine wave data
'
    Dim Sine_Table As Flash8 = {50, 54, 59, 64, 68, 73, 77, 81,
                                85, 88, 91, 93, 95, 97, 98, 99,
                                99, 99, 98, 97, 95, 93, 91, 88,
                                85, 81, 77, 73, 68, 64, 59, 54,
                                50, 45, 40, 35, 31, 26, 22, 18,
                                14, 11, 08, 06, 04, 02, 01, 00,
                                00, 00, 01, 02, 04, 06, 08, 11,
                                14, 18, 22, 26, 31, 35, 40, 45}

    dAccum = pFreq * 64                                     ' \
    pFreq = dAccum * cMult                                  ' / Calculate the value for the DDS, based upon the frequency required

    pDuration = pDuration * 124                             ' Calculate the duration in approx milliseconds
    PinOutput PORTC.2                                       ' Make the PWM peripheral's pin an output
    dAccum = 0                                              ' Reset the accumulator
    Do                                                      ' Create a loop
        Repeat : Until PIR1bits_TMR2IF = 1                  ' \ Synchronise to the start of the PWM cycle
        PIR1bits_TMR2IF = 0                                 ' /
        dAccum = dAccum + pFreq                             ' Accumulation of the frequency
        CCPR1L = CRead8 Sine_Table[dAccum.Byte2 & 0b00111111]' Divide output by 65536 and keep the first 6 bits
        Dec pDuration                                       ' Decrement the duration counter
        If pDuration = 0 Then                               ' Has the duration reached 0?
            PinInput PORTC.2                                ' Yes. So make the PWM peripheral's pin an input
            ExitProc                                        ' Exit the procedure
        EndIf
    Loop
EndProc

'-----------------------------------------------------------------------------------------
' Initialise the CCP1 peripheral as PWM
' Input     : None
' Output    : None
' Notes     : None
'
Proc PWM_Init()
    T2CON = %00000100  
    PR2 = 127           
    CCPR1L = 0         
    CCP1CON = %00001100                                     ' Enable the CCP1 peripheral as PWM
    PinInput PORTC.2                                        ' Make the PWM peripheral's pin an input
EndProc

'-----------------------------------------------------------------------------------------
' Setup the program
' Input     : None
' Output    : None
' Notes     : None
'
Proc Setup()
    PWM_Init()
EndProc

'-----------------------------------------------------------------------------------------
' Setup the config fuses for the 4xPLL on a PIC18F25K20 device using an external crystal
'
Config_Start
    FOSC = HSPLL        ' HS oscillator, PLL enabled and under software control
    Debug = Off         ' Background debugger disabled' RB6 and RB7 configured as general purpose I/O pins
    XINST = Off         ' Instruction set extension and Indexed Addressing mode disabled (Legacy mode)
    STVREN = Off        ' Reset on stack overflow/underflow disabled
    WDTEN = Off         ' WDT disabled (control is placed on SWDTEN bit)
    FCMEN = Off         ' Fail-Safe Clock Monitor disabled
    IESO = Off          ' Two-Speed Start-up disabled
    WDTPS = 128         ' Watchdog is 1:128
    BOREN = Off         ' Brown-out Reset disabled in hardware and software
    BORV = 18           ' VBOR set to 1.8 V nominal
    MCLRE = On          ' MCLR pin enabled, RE3 input pin disabled
    HFOFST = Off        ' The system clock is held Off until the HF-INTOSC is stable.
    LPT1OSC = Off       ' Timer1 operates in standard power mode
    PBADEN = Off        ' PORTB<4:0> pins are configured as digital I/O on Reset
    CCP2MX = PORTC      ' CCP2 input/output is multiplexed with RC1
    LVP = Off           ' Single-Supply ICSP disabled
    Cp0 = Off           ' Block 0 (000800-001FFF) not code-protected
    CP1 = Off           ' Block 1 (002000-003FFF) not code-protected
    CPB = Off           ' Boot block (000000-0007FF) not code-protected
    CPD = Off           ' Data EEPROM not code-protected
    WRT0 = Off          ' Block 0 (000800-001FFF) not write-protected
    WRT1 = Off          ' Block 1 (002000-003FFF) not write-protected
    WRTB = Off          ' Boot block (000000-0007FF) not write-protected
    WRTC = Off          ' Configuration registers (300000-3000FF) not write-protected
    WRTD = Off          ' Data EEPROM not write-protected
    EBTR0 = Off         ' Block 0 (000800-001FFF) not protected from table reads executed in other blocks
    EBTR1 = Off         ' Block 1 (002000-003FFF) not protected from table reads executed in other blocks
    EBTRB = Off         ' Boot block (000000-0007FF) not protected from table reads executed in other blocks
Config_End

The code can be adjusted for other devices. The only thing that should need changing is the PWM peripheral mechanism.

With a good filter, the sine waves are quite good, and the method can be updated to use a better resolution for the PWM acting as a DAC. I took the DDS mechanism used in the listing above from my DDS DTMF generator program, so the sine waves did not need to be perfect, but good enough for a DTMF decoder to work with.

Below is a screenshot of the program running in a simulator, because I do not own a frequency meter or an oscilloscope for photos. :-(

DDS Sinewave Generator.jpg

keytapper

Great, as usual  ;D
I think Giuseppe may look at the DAC, as well, for his 12F1501/12F1822. That would generate a 32 steps output.
Ignorance comes with a cost

top204

That's a good idea keytapper. The program could easily be adapted to the use the 5-bit DAC instead of the PWM.

david

Great to see the DDS code here.  Many thanks Les.
I would go the DAC way too but probably 8 bit which means an 8 bit table.
Dale/Bourns make some great little 10 pin SIP R-2R ladder networks which can often sit down one side of a PIC and make a very fast and simple DAC.   A company I once worked for dumped buckets of them but I only found out later and have just 2.  Highly recommended for experiments.
Be aware that as the frequency goes up the number of phase steps per cycle is reduced and this is where a good filter really helps.  At low frequencies an 8 bit DAC produces clean signals with very little filtering.

Cheers,
David

John Drew

Thanks for raising this thread Guiseppe. Good timing!
I am in the process of designing some telemetry for a solar syatem and I require to modulate tones on a TX to send ASCII to a receiver some 40km away, with perhaps a NE567 at the other end for decode back to TTL.
Les's solution could be the way to go using a 16bit chip. I'll give it a try and see what it looks like on a CRO and if it switches frequency quickly enough to be useful. About 1k of data (48 packets of 10-20 bytes) will be sent every 30 minutes. It might have to be as slow as 100baud. We'll see if the NE567 is up to it.
Some experiments coming up. Thanks everyone.
John

david

John,
The link I gave in post 1 is all about FSK for data transmission.   DDS is way faster than your loop filter time constant in a PLL system....

Cheers,
David

top204

#13
I couldn't find a link to the actual assembler code in that PDF david?

It would be interesting to see and, maybe, convert to Positron.

Ahhhh. The good old NE567 chip. I used that a few times in the past for morse decoders I wrote for the Sinclair ZX machines I had in the 1980s. Good times when everything was new to learn. :-)

John Lawton

I once designed a sinewave generator using a PIC12F1571 with, IIRC, 32kHz PWM and a 256 byte lookup table. Filtering wasn't a problem as the generated sinewave frequency was either 45 or 25Hz and it was very clean.

tumbleweed

Quote from: top204 on Aug 17, 2022, 02:09 PMI couldn't find a link to the actual assembler code in that PDF david?
There's a URL in the pdf file that has the asm source, along with some board files -
 http://www.g4jnt.com/PIC_DDS.zip

top204

Thanks tumbleweed.

I took a look at the assembler code and it is doing the same thing as the Positron listing above. However, it did give me an idea to use the larger data table for the sine wave and a hight frequency PWM operating at 8-bits, so the waveform is better.

I have created a post for it in the "Audio" section of the forum. Here:

DDS Sinewave Generator using PWM as an 8-bit DAC, on a PIC18F26K22 device

I placed it in the audio section rather than the PWM section, because it is using the PWM as an 8-bit DAC, and not as a PWM waveform. :-)





Giuseppe

To share with everyone my waveforms obtained with the freqout syntax.
Waveform with double rc filter 1K + 100nF as per manual:https://ibb.co/2n9Wxsy
Waveform with 2 10uF capacitors as per manual connected directly to the speaker:https://ibb.co/kBvczFs
As I had already mentioned I don't have decent waveforms I'll follow your advice with DDS and PWM let's see what I can get with the 12f1501. Thanks everyone for the answers

top204

#18
The manual's components for the FreqOut command, and the other commands that output signals, are only there for demonstration purposes, and for testing, because they will give a signal that can be heard. After the signal goes through a crude low pass filter, integrator, it will just drive a Piezo speaker, but not a standard speaker.

Also, the integrator's components need changing for each frequency that is generated.

I have just ran a test with the FreqOut command, on an enhanced 14-bit core device operating at 32 MHz in the simulator, and the sine wave is crude, but not too bad for software toggling pins in a loop while reading the sine data and performing checking as a crude DDS.

The test code listing is below:
'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' Demonstrate the FreqOut command working on a PIC12F1571 device.
' Written by Les Johnson for the Positron8 BASIC compiler.
'
    Device = 12F1571                                ' Tell the compiler what device to compile for
    Declare Xtal = 32                               ' Tell the compiler what frequency the device will be operating at (in MHz)

'----------------------------------------------------------------------------
' The main program starts here
'
Main:
    Do
        FreqOut PORTA.1, 1000, 1000                 ' Output a 1 KHz signal from pin PORTA.1
    Loop

Remember, the Freqout and DTMFout commands require a 20 MHz oscillator, or over for decent outputs, otherwise, the pins cannot be toggled fast enough while reading the Sin data etc...

For a decent analogue waveform to be outputted from the microcontroller, it will need to be DDS using a PWM as a DAC, or a true DAC chip. I think the device you are using has a built in 5-bit DAC, and all the above DDS program needs is a new sin table that has values for 5-bits in it.

A screenshot of the above program being simulated is shown below. It's not a bad sine wave for the way it is produced. i.e. Software only from a single pin.

Freqout at 1KHz.jpg


keytapper

Giuseppe, in one way or another you got a good horse to ride on  ;D
The components number are very minimal so just copy the great example  :P
Ignorance comes with a cost