View previous topic :: View next topic |
Author |
Message |
Diode
Joined: 27 Jul 2012 Posts: 35
|
Problems with sine wave generation and using SPI |
Posted: Sun May 19, 2013 4:54 am |
|
|
I try to generate a sine wave by the use of a DAC (MCP4921) and a PIC18F4455. I use the MCP4921.c file which is included by CCS. I'm new to SPI and the use of it so please forgive if I ask silly questions.
If I use the following code I have a sine wave of about 40Hz:
Code: | #include <stdlib.h>
#include "B:\Software\DA test\spi test.h"
#include <mcp4921.c>
#include <math.h>
//# USE SPI
int16 a,i,j;
int16 sin_table [181];
void fill_sin_table()
{
for (i = 0; i <=180; i++)
{
sin_table[i]=2046+floor(2046*sin((i*2) * PI /180));
}
}
void main()
{
//setup_spi(SPI_MASTER|SPI_L_TO_H|SPI_CLK_DIV_16);
fill_sin_table();
j=0;
init_dac();
setup_adc_ports(AN0);
setup_adc(ADC_CLOCK_DIV_64);
setup_wdt(WDT_OFF);
setup_timer_0(RTCC_INTERNAL);
setup_timer_1(T1_DISABLED);
delay_ms(100);
while(1)
{
j++;
if (j >= 180)
{
j = 0;
}
output_high(PIN_D1);
a = sin_table[j];
write_dac(a);
output_low(PIN_D1);
}
} |
I need a sine wave with a much higher frequency in the range of 30~50kHz. The above code doesn't use SPI, so I have changed it to this:
Code: | #include "B:\Ozon voeding\Software\DA test\spi test.h"
#include <stdlib.h>
#include <mcp4921.c>
#include <math.h>
#use SPI
int16 a,i,j;
int16 sin_table [181];
void fill_sin_table()
{
for (i = 0; i <=180; i++)
{
sin_table[i]=2046+floor(2046*sin((i*2) * PI /180));
}
}
void main()
{
setup_spi(SPI_MASTER|SPI_L_TO_H|SPI_CLK_DIV_16);
fill_sin_table();
j=0;
init_dac();
setup_adc_ports(AN0);
setup_adc(ADC_CLOCK_DIV_64);
setup_wdt(WDT_OFF);
setup_timer_0(RTCC_INTERNAL);
setup_timer_1(T1_DISABLED);
delay_ms(100);
while(1)
{
j++;
if (j >= 180)
{
j = 0;
}
output_high(PIN_D1);
a = sin_table[j];
write_dac(a);
output_low(PIN_D1);
}
}
|
This doesn't work, if I place the setup_spi in comment it does work again but on a low frequency.
As mentioned before I'm new to SPI so I'm not sure what is going wrong.
I have connected everything as follows:
Pin 2 of DAC to PIN B0 of PIC (!CS)
Pin 3 of DAC to PIN B1 of PIC (SCK)
Pin 4 of DAC to PIN B2 of PIC (SDI)
Pin 5 of DAC to PIN B4 of PIC (!LDAC)
I use compiler version 4.0.13
All help will be appreciated. |
|
|
temtronic
Joined: 01 Jul 2010 Posts: 9221 Location: Greensville,Ontario
|
|
Posted: Sun May 19, 2013 5:08 am |
|
|
First obvious comment..
WHAT 'clock' frequency is the PIC running at ?
I don't see any includes like 'PIC type','fuses' or 'use delay' similar to this...
//46k22 sequential turn signal code
//PIC type
#include <18f46k22.h>
//fuses configuration file
#include "46k22_fuses.h"
//definition of I/O pins for project
#include "46k22_cougar_pins.h"
//PIC clock speed
#use delay(clock=8000000)
...
I suggest you create a simple 'toggle LED at 1Hz' program and get it 'up and running' before you do your sinewave program.This will verify the PIC is running at the correct speed.You should always start with this program as a 'base' of KNOWN good code.
My thought is the PIC is running at some slow speed,maybe on the secondary failsafe oscillator(32KHz ?) though without further information I'm only guessing.
hth
jay |
|
|
Diode
Joined: 27 Jul 2012 Posts: 35
|
|
Posted: Sun May 19, 2013 5:14 am |
|
|
Hi Jay,
Thanks for your comment. Actually I had a blinking led in my code to check whether it is running or not, and it does (I had removed it to get rid of all things which are not important). I have toggled a pin with twice a delay of 10ms and I checked the signal with a oscilloscope and it is fine.
I have configured all by the PIC wizard so I have a separate config file, see the code below: Code: | #include <18F4455.h>
#device ICD=TRUE
#device adc=8
#FUSES NOWDT //No Watch Dog Timer
#FUSES HS //High speed Osc (> 4mhz for PCM/PCH) (>10mhz for PCD)
#FUSES NOPROTECT //Code not protected from reading
#FUSES NOBROWNOUT //No brownout reset
#FUSES NOPUT //No Power Up Timer
#FUSES NOCPD //No EE protection
#FUSES DEBUG //Debug mode for use with ICD
#FUSES NOLVP //No low voltage prgming, B3(PIC16) or B5(PIC18) used for I/O
#FUSES NOWRT //Program memory not write protected
#FUSES NOWRTD //Data EEPROM not write protected
#FUSES IESO //Internal External Switch Over mode enabled
#FUSES FCMEN //Fail-safe clock monitor enabled
#FUSES NOPBADEN //PORTB pins are configured as digital I/O on RESET
#FUSES NOWRTC //Configuration registers not write protected
#FUSES NOWRTB //Boot block not write protected
#FUSES NOEBTR //Memory not protected from table reads
#FUSES NOEBTRB //Boot block not protected from table reads
#FUSES NOCPB //No Boot Block code protection
#FUSES LPT1OSC //Timer1 configured for low-power operation
#FUSES MCLR //Master Clear pin enabled
#FUSES NOXINST //Extended set extension and Indexed Addressing mode disabled (Legacy mode)
#FUSES PLL1 //No PLL PreScaler
#use delay(clock=20000000,RESTART_WDT)
#use rs232(baud=9600,parity=N,xmit=PIN_C6,rcv=PIN_C7,bits=8)
|
|
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19499
|
|
Posted: Sun May 19, 2013 11:48 am |
|
|
Start with the speed you are running the SPI. Fosc/16. So you are sending one bit every 20000000/16 second. 1250000th second. Each byte then takes 8/1250000 second. 1/156250 second per byte. The table has 180 entries, and each takes 2 bytes to transfer, so it'll take just under 400th second to send. You need to start by upping the SPI clock to the highest frequency you can. Then you are going to need to reduce the number of samples in the sin table for the higher frequency outputs.
If you switch to using HSPLL, OSC/5 to feed the PLL, and CPU/2 from the PLL, you can run at 48MHz from the same crystal. Up the SPI to Fosc/4, and the frequency output will go up by over 8*. Reduce the number of entries in the sin table to 16, and the frequency possible goes up to 46KHz.
Best Wishes |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Sun May 19, 2013 12:08 pm |
|
|
Quote: |
This doesn't work, if I place the setup_spi in comment it does work again
but on a low frequency. As mentioned before I'm new to SPI so I'm not
sure what is going wrong. |
The CCS mcp4921.c driver is a software SPI driver. It works by toggling
i/o pins. It's inherently slow. You can't convert it to be a hardware SPI
driver just by adding a setup_spi() statement. You have to use a driver
that's written for hardware SPI. Example:
RF Developer has posted a driver for the MCP4822, which is similar to
the mcp4921:
http://www.ccsinfo.com/forum/viewtopic.php?t=46644&start=4
To convert it to the mcp4921, remove the function parameters for
DAC_SELECT and value_2. Just make DAC_SELECT into a #define
statement for the mcp4921 CS pin and put it above main(). Also, go
through the routine and delete all code associated with sending value_2
to the dac. Be careful, don't remove the LDAC stuff at the end.
Quote: | have connected everything as follows:
Pin 2 of DAC to PIN B0 of PIC (!CS)
Pin 3 of DAC to PIN B1 of PIC (SCK)
Pin 4 of DAC to PIN B2 of PIC (SDI)
Pin 5 of DAC to PIN B4 of PIC (!LDAC)
|
The line in bold is wrong for hardware SPI. The SDI pin of the dac
must be connected to the SDO pin of the 18F4455, which is PIN_C7.
Quote: | setup_spi(SPI_MASTER|SPI_L_TO_H|SPI_CLK_DIV_16); |
See the comments in this post about the correct SPI mode for the mcp4921:
http://www.ccsinfo.com/forum/viewtopic.php?t=39262&start=2 |
|
|
Diode
Joined: 27 Jul 2012 Posts: 35
|
|
Posted: Sun May 19, 2013 12:14 pm |
|
|
Thanks for your reply. The "problem" is in an earlier stage.
As soon as I uncomment the "setup_spi...." sentence my DAC doesn't work at all. Let's forget the sine wave generation at the moment.
First I need to get the SPI working, at the moment I want to write a fixed value to the DAC but it seems the SPI isn't working.
I have reconnected my DAC to the hardware SPI pins of the PIC:
Pin 2 of DAC to PIN B0 of PIC (!CS)
Pin 3 of DAC to PIN B1 of PIC (SCK)
Pin 4 of DAC to PIN C7 of PIC (SDI)
Pin 5 of DAC to GND |
|
|
Diode
Joined: 27 Jul 2012 Posts: 35
|
|
Posted: Sun May 19, 2013 12:36 pm |
|
|
Apparently I was typing my comments while PCM was writing one.
The mistake for the SDI pin is corrected, and I have reconnected B4 as well.
I skipped the complete sine wave code because at the moment this isn't the problem, my code look like this:
Code: |
#include "B:\Software\DA test\spi test.h"
#include <stdlib.h>
#include <mcp4921rev2.c>
#include <math.h>
#define SPI_MODE_0 (SPI_L_TO_H | SPI_XMIT_L_TO_H)
void main()
{
#include "B:\Ozon voeding\Software\DA test\spi test.h"
#include <stdlib.h>
#include <mcp4921rev2.c>
#include <math.h>
#define SPI_MODE_0 (SPI_L_TO_H | SPI_XMIT_L_TO_H)
void main()
{
setup_spi(SPI_MASTER|SPI_H_TO_L|SPI_XMIT_L_TO_H|SPI_CLK_DIV_4);
setup_adc_ports(AN0);
setup_adc(ADC_CLOCK_DIV_64);
setup_wdt(WDT_OFF);
setup_timer_0(RTCC_INTERNAL);
setup_timer_1(T1_DISABLED);
delay_ms(100);
while(1)
{
output_high(PIN_D1);
write_mcp4822(2048);
output_low(PIN_D1);
}
}
} |
I have changed the driver from RF developer but I'm not sure if I made the changes you suggested correctly:
Code: | /*
write_mcp4921rev2.c
This function writes two 12-bit words to a Microchip MCP4822 DAC and then
simultaneously latches the data to the outputs.
The two 16-bit words passed to the function are limited to 0xFFF to avoid any
rollovers.
*/
// Use with:
// 10MHz operation at 40MHz processor clock
// setup_spi(SPI_MASTER|SPI_H_TO_L|SPI_XMIT_L_TO_H|SPI_CLK_DIV_4);
// Typical Defines for the select pin and latch
//#define DAC_SELECT PIN_B0//C1
//#define latch_dac PIN_B4//C2
#define DAC_SELECT PIN_B0//C1
#define latch_dac PIN_B4//C2
void write_mcp4822(int16 value_1)
{
// Prepare the first value
if(value_1 > 0x0FFF) //Limit value of DAC A value with no rollover
{
value_1 = 0x0FFF;
}
// Set control bits
bit_clear(value_1, 15); // Write to DAC A
bit_clear(value_1, 13); // Output gain = 2
bit_set(value_1, 12); // Output enabled
//write the first value. The MCP4822 treats its two
// channels effectively as two DACS. We have to issue TWO
// SPI sequences. We cannot send one long one with both
// sets of data.
output_low(DAC_SELECT); //select DAC IC /CS
spi_write(make8(value_1,1)); //write upper byte
spi_write(make8(value_1,0)); //write lower byte
output_high(DAC_SELECT); //unselect DAC /CS
// Latch both values onto the analogue outputs
output_low(latch_dac);
// At 10MHz there is sufficient time between instructions to
// meet the MCP4822's timing requirements without any additional
// delays. The analogue output changed when we drove the latch
// low. All we need to do now is bring it high again.
// That was true at 10MHz, but not at 40MHz! So we have to add a few NOPs
// to ensure correct timing. 1 "wait state" gives 200ns at 40MHz, or 500ns at 10MHz
delay_cycles(1);
output_high(latch_dac);
} |
This seems to work but I would appreciate it if you could have a look at it.
Thanks a lot for the great (and fast) help. |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Sun May 19, 2013 12:46 pm |
|
|
1. You should rename the function to write_mcp4921().
2. You should take the SPI mode #define statements from this link,
http://www.ccsinfo.com/forum/viewtopic.php?t=39262&start=2
and put them above main(). Then use the #define value in your
setup_spi() statement. That way, the line becomes self-documenting.
You just look at it and you can see what SPI mode you're using.
This will also prevent typos, which you apparently have in your latest
posted code, whereby you inadvertently changed it to mode 3.
This is what I mean:
Code: | setup_spi(SPI_MASTER | SPI_MODE_0 | SPI_CLK_DIV_4); |
Also, for some reason your posted code has two main's in it. |
|
|
Diode
Joined: 27 Jul 2012 Posts: 35
|
|
Posted: Sun May 19, 2013 1:02 pm |
|
|
The two mains is a copy/paste mistake. I made a few mistake with it, you're right about that. I have changed in:
Code: |
#include "B:\Ozon voeding\Software\DA test\spi test.h"
#include <stdlib.h>
#include <mcp4921rev2.c>
#include <math.h>
#define SPI_MODE_0 (SPI_L_TO_H | SPI_XMIT_L_TO_H)
int16 i,j;
int16 sin_table [61];
void fill_sin_table()
{
for (i = 0; i <=60; i=i+1)
{
sin_table[i]=2046+floor(2046*sin((i*6) * PI /180));
}
}
void main()
{
fill_sin_table();
setup_spi(SPI_MASTER | SPI_MODE_0 | SPI_CLK_DIV_4);
//setup_adc_ports(AN0);
//setup_adc(ADC_CLOCK_DIV_64);
setup_wdt(WDT_OFF);
setup_timer_0(RTCC_INTERNAL);
setup_timer_1(T1_DISABLED);
delay_ms(100);
while(1)
{
j++;
if (j >= 60)
{
j = 0;
}
output_high(PIN_D1);
write_mcp4921(sin_table[j]);
output_low(PIN_D1);
}
} |
And Code: |
/*
write_mcp4921rev2.c
This function writes two 12-bit words to a Microchip MCP4822 DAC and then
simultaneously latches the data to the outputs.
The two 16-bit words passed to the function are limited to 0xFFF to avoid any
rollovers.
*/
// Use with:
// 10MHz operation at 40MHz processor clock
// setup_spi(SPI_MASTER|SPI_H_TO_L|SPI_XMIT_L_TO_H|SPI_CLK_DIV_4);
// Typical Defines for the select pin and latch
//#define DAC_SELECT PIN_B0//C1
//#define latch_dac PIN_B4//C2
#define DAC_SELECT PIN_B0//C1
#define latch_dac PIN_B4//C2
void write_mcp4921(int16 value_1)
{
// Prepare the first value
if(value_1 > 0x0FFF) //Limit value of DAC A value with no rollover
{
value_1 = 0x0FFF;
}
// Set control bits
bit_clear(value_1, 15); // Write to DAC A
bit_set(value_1, 13); // Output gain = 1 // clear bit makes gain 2
bit_set(value_1, 12); // Output enabled
//write the first value. The MCP4822 treats its two
// channels effectively as two DACS. We have to issue TWO
// SPI sequences. We cannot send one long one with both
// sets of data.
output_low(DAC_SELECT); //select DAC IC /CS
spi_write(make8(value_1,1)); //write upper byte
spi_write(make8(value_1,0)); //write lower byte
output_high(DAC_SELECT); //unselect DAC /CS
// Latch both values onto the analogue outputs
output_low(latch_dac);
// At 10MHz there is sufficient time between instructions to
// meet the MCP4822's timing requirements without any additional
// delays. The analogue output changed when we drove the latch
// low. All we need to do now is bring it high again.
// That was true at 10MHz, but not at 40MHz! So we have to add a few NOPs
// to ensure correct timing. 1 "wait state" gives 200ns at 40MHz, or 500ns at 10MHz
delay_cycles(1);
output_high(latch_dac);
} |
As you see I have added the sine functionality. It now generates a sine wave of 895Hz. Much better but not nearly high enough. It now is probably time to get a better look at the comments of Ttelmah. |
|
|
Diode
Joined: 27 Jul 2012 Posts: 35
|
|
Posted: Mon May 20, 2013 11:37 am |
|
|
Well, it does work now but the achieved frequency is not high enough. I have 12 entries in the sine table and have set the following fuses:
Code: |
#FUSES NOWDT //No Watch Dog Timer
#FUSES HSPLL //High speed Osc (> 4mhz for PCM/PCH) (>10mhz for PCD)
#FUSES CPUDIV2
#FUSES PLL2
#FUSES NOPROTECT //Code not protected from reading
#FUSES NOBROWNOUT //No brownout reset
#FUSES NOPUT //No Power Up Timer
#FUSES NOCPD //No EE protection
#FUSES DEBUG //Debug mode for use with ICD
#FUSES NOLVP //No low voltage prgming, B3(PIC16) or B5(PIC18) used for I/O
#FUSES NOWRT //Program memory not write protected
#FUSES NOWRTD //Data EEPROM not write protected
#FUSES IESO //Internal External Switch Over mode enabled
#FUSES FCMEN //Fail-safe clock monitor enabled
#FUSES NOPBADEN //PORTB pins are configured as digital I/O on RESET
#FUSES NOWRTC //Configuration registers not write protected
#FUSES NOWRTB //Boot block not write protected
#FUSES NOEBTR //Memory not protected from table reads
#FUSES NOEBTRB //Boot block not protected from table reads
#FUSES NOCPB //No Boot Block code protection
#FUSES LPT1OSC //Timer1 configured for low-power operation
#FUSES MCLR //Master Clear pin enabled
#FUSES NOXINST //Extended set extension and Indexed Addressing mode disabled (Legacy mode) |
The PLL and CPU fuses are according to the message of Ttelmah. The only thing I couldn't find is the osc/5 meaning/fuse?
The frequency which is generated is about 18kHz so that's still much lower as what I need.
I'm considering to change the setup and replace the DAC by a AD9833 programmable waveform generator.
The AD9833 can generate waveforms up to 12.5MHz so it must be able to do 30KHz easily. I also need a variable amplitude of the sine wave so I can feed the output of the AD9833 to a opamp with a digital potentiometer for gain control.
Does one of you know any alternatives? |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Mon May 20, 2013 11:45 am |
|
|
Quote: | I use compiler version 4.0.13 |
That's not a version number. Look at at the top of your .LST file, in your
project directory. The list file will be generated after a successful
compilation. Post your version number. Example of version numbers:
http://www.ccsinfo.com/devices.php?page=versioninfo |
|
|
Diode
Joined: 27 Jul 2012 Posts: 35
|
|
Posted: Mon May 20, 2013 11:47 am |
|
|
It is 4.013, sorry. |
|
|
ckielstra
Joined: 18 Mar 2004 Posts: 3680 Location: The Netherlands
|
|
Posted: Mon May 20, 2013 12:07 pm |
|
|
We definitely need a FAQ section in this forum as the same mistakes are being made by many newbies. These people can't be blamed for their errors but we can save everyone a lot of frustration by pointing at some common mistakes in advance.
FAQ 1 would be:
Don't use compiler versions 4.000 to 4.074. These early versions of the V4 compiler were for beta-testing only and had so many errors that you shouldn't use them. Around v4.075 the compiler started to work again but best to use v4.090 or higher (current v4.141 is great). |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Mon May 20, 2013 12:08 pm |
|
|
You want to use the PLL5 fuse for PLL with a 20 MHz crystal. Get rid of
the PLL2 fuse. And it is a buggy version. |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Mon May 20, 2013 12:28 pm |
|
|
Also, I'm not sure why you have CPUDIV2 in there. For 48 MHz
operation you should use CPUDIV1.
See the PLL example in this post (the 2nd program):
http://www.ccsinfo.com/forum/viewtopic.php?t=42223&start=1
Setup your #fuses and #use delay() very similar to that. |
|
|
|