Go Down

Topic: Can't get SPI master to go - what am I missing? (Read 910 times) previous topic - next topic

jwatte

I'm trying to use SPI to generate a custom serial signal. AFAICT, I'm setting up all the registers as per the datasheet for the atmega328, but the device stubbornly refuses to generate a clock on CLK, nor any signal on MOSI. I have set CLK, MOSI and Was to outs, and puooes WS high. I have also turned on SPI in the power control register.

My code is kind-of big, but the functions setup_spit and blast_codes in this project are the interesting ones:
https://github.com/jwatte/arduino-chargetimer/blob/master/ircontrol/ircontrol.ino

I've verified on a scope that there is no output (all low) and also, I get to the point where I wait for the transmit flag to go high, and it never does (after 2 seconds, the watchdog resets the board.)

johnwasser

You don't seem to be using any Arduino libraries.  Are you using Arduino hardware?  Perhaps you should try the folks at AVRfreaks (http://www.avrfreaks.net/) who have more experience with using the raw AVR hardware registers.

You're really trying to modulate a 38 KHz carrier with a 125 KHz signal?
Send Bitcoin tips to: 1L3CTDoTgrXNA5WyF77uWqt4gUdye9mezN
Send Litecoin tips to : LVtpaq6JgJAZwvnVq3ftVeHafWkcpmuR1e

jwatte

#2
Dec 31, 2011, 06:31 am Last Edit: Dec 31, 2011, 06:33 am by jwatte Reason: 1
Thanks for your answer!

The only SPI library I found was the SDCard library, and it isn't packaged to be separately usable.
However, when I compared my code to that, it seemed the same.
I still like the Arduino bootloader/programmer setup - not ready to go bare-back by default just yet :-)
Also I use millis, pinMode, etc...

And yes, I modulate a PWM output with the SPI output. This lets me get a precise carrier (frequency and duty cycle) and precise-ish modulation timing (at 8 us quantization). Note that I send large swaths of 0s and 1s in a row most ot the time. One byte is 64 us, which is a little over two cycles at 38 Mhz (at about 26 us each.)

extent

A master SPI library is bundled with the arduino IDE, it's also documented in the online documentation.  Search for "SPI"

johnwasser

Instead of using the SPI output for timing you might be better served by using one of the timers.  You could probably even use a single timer for both carrier frequency and modulation if the carrier frequency is a multiple of the modulation pulse width.

Send Bitcoin tips to: 1L3CTDoTgrXNA5WyF77uWqt4gUdye9mezN
Send Litecoin tips to : LVtpaq6JgJAZwvnVq3ftVeHafWkcpmuR1e

jwatte

I need a timer for the carrier. I can't keep resetting that timer because that will screw with the pulse shape because of interrupt jitter.
I have considered simply bit banging a pin for the modulator. The interrupt jitter may delay any change by up to 30us though, which is worse precision than I'd like. My end goal is to scale this up to 433 Mhz carriers ("B&O type")
The Arduino library sets up all three timers for 490 Hz PWM, and uses the first one for housekeeping. I'm using the third timer for the carrier. Perhaps the second timer is available; is there a good place where I can check whether any parts are using that timer for something else?

However, SPI should work. I'm going to look at it some more today and see what I can learn. Thanks for your support!

Nick Gammon

I'm not sure you are using SPI for its intended purpose here. Without reading your code in great detail, SPI generates clock pulses only when you need to transfer data. It isn't a "free running" clock generator. So the period between calls to output data will be jitter, if you like.

You don't need to "find" libraries, they are all inbuilt.

More info here:

http://gammon.com.au/spi

jwatte

#7
Jan 01, 2012, 09:29 am Last Edit: Jan 01, 2012, 09:39 am by jwatte Reason: 1
I'm aware that it only generates the clock when it has data to send. I actually don't care about the clock (other than as a side effect to see that the SPI interface is running) -- I care about the high/low signal generated based on the data I stuff into the SPI data output register.

Here's my setup code snipped from the bigger file linked above:

Code: [Select]

void setup() {
 wdt_reset();
 wdt_enable(WDTO_2S);

 pinMode(PIN_MODULATOR, OUTPUT);  //  PB3, OC2A, MOSI  --  modulator
 digitalWrite(PIN_MODULATOR, LOW);
 pinMode(PIN_SPI_CK, OUTPUT);  //  PB5, SCK, Arduino status LED (unfortunate)
 digitalWrite(PIN_SPI_CK, HIGH);
 pinMode(PIN_STATUS, OUTPUT);   //  PB5, SCK, application status LED
 //  flash indicator while booting
 digitalWrite(PIN_STATUS, HIGH);
 pinMode(PIN_SPI_SS, OUTPUT);  //  Configure as output to make sure SPI runs
 digitalWrite(PIN_SPI_SS, HIGH);

 pinMode(PIN_CARRIER, OUTPUT);   //  PD3, OC2B  --  carrier
 digitalWrite(PIN_CARRIER, LOW);

 digitalWrite(PIN_SPI_CK, LOW);  //  and then it turns into a clock
 digitalWrite(PIN_STATUS, LOW);
 
 //  set up SPI clock
 setup_spi();
}

void setup_spi() {
 //  enable SPI
 PRR = PRR | (1 << PRSPI);
 //  turn off fast mode if it's on
 SPSR = 0;
 //  divide down to 125 kHz
 SPCR = (1 << SPE) | (1 << MSTR) | (1 << CPHA) | (1 << SPR1) | (1 << SPR0);
}


Here's the write-data code:

Code: [Select]

void blast_samples() {
 //  make MOSI modulate based on samples
 unsigned char mval = 0;
 unsigned short ticks = 0;
 //  write SPI just to get started
 SPDR = 0x00;
 unsigned char i = 0;
 while ((i != nSamples) || (ticks != 0)) {
   //  Spend time while the data is shifted out generating the next data byte.
   //  This must run faster than 64 microseconds!
   //  (does it? my guess is this takes about 30 us... but I should measure!)
   //  The theory is that I can take an interrupt while doing this, but then
   //  defer interrupts while waiting for the next byte to go out, to get
   //  tight timing.
   unsigned char nuByte = 0;
   for (unsigned char j = 0; j != 8; ++j) {
     if (ticks == 0) {
       if (i != nSamples) {
         ticks = samples[i];
         i += 1;
         if (ticks & 0x8000U) {
           mval = 0xff;
           ticks = ticks & 0x7fffU;
         }
         else {
           mval = 0;
         }
       }
       else {
         ticks = 8 - j;
         mval = 0;
       }
     }
     nuByte = nuByte | (bitvals[j] & mval);
     ticks -= 1;
   }
   //  disable interrupts -- I know I won't call this with interrupts disabled
   wdt_reset();
   cli();
   //  wait for MOSI to complete
   while (!(SPSR & (1 << SPIF))) {
     // do nothing, with interrupts off!
   }
   //  write SPI
   SPDR = nuByte;
   //  enable interrupts -- I know I won't call this with interrupts disabled
   sei();
 }
 ok(0xfbu); // debug output
 //  wait for pulses to clear
 while (!(SPSR & (1 << SPIF))) {
   // do nothing, until complete!
 }
 ok(0xfeu); // debug output
 digitalWrite(11, LOW);  //  turn off modulator for sure
}


If I call this with nSamples == 0, it should wiggle the clock for 8 cycles and put low signal (0s) on MOSI. Then SPSR should get the SPIF bit set.
However, the clock never starts wiggling, and the program gets hung on the loop waiting for SPSR/SPIF to go high, and then the watchdog resets it.
Thus, I'm doing *something* wrong.


Btw: The reason I didn't find the SPI library was that I did a "grep -r" for SPDR in all .c/.cpp files, and it didn't come up. The reason for that is that the module defines its own constants, and only uses the AVR constants in a couple of inline functions in the .h file...
Which begs the question: Why does the SDCard library not use the SPI library, but instead bit-bangs itself?

Also, here's the Atmel sample code for SPI, which looks similar to mine. Maybe someone else can help me spot-the-difference?

Code: [Select]

void SPI_MasterInit(void)
{
  /* Set MOSI and SCK output, all others input */
  DDR_SPI = (1<<DD_MOSI)|(1<<DD_SCK);
  /* Enable SPI, Master, set clock rate fck/16 */
  SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR0);
}

void SPI_MasterTransmit(char cData)
{
  /* Start transmission */
  SPDR = cData;
  /* Wait for transmission complete */
  while(!(SPSR & (1<<SPIF)))
    ;
  }
}


jwatte

My fallback is probably to take an interrupt each time timer 2 compare B goes off (the pulse goes low), and then count the number of pulses, and only output whole pulses. That's a little bit dangerous, though, because with interrupt jitter, I may mis-count the number of pulses. A 30 us interrupt jitter (as I've measured from the Arduino library) can be two full pulses at 60 kHz carrier! And I may take multiple of those during a single "high" modulator interval. That's what's so sweet about this SPI set-up -- as long as the worst-case interrupt plus the time to calculate the next byte is less than the duration of one byte on SPI (64 us) there will be zero jitter! (well, about 5 instructions for the test loop and then the write -- I can live with that :-)

Oh, and: Happy new year :-)

Go Up