Arduino Forum upgrade scheduled for Monday, October 20th, 11am-4pm (CEST). Sorry for the inconvenience!
Pages: [1]   Go Down
Author Topic: Can't get SPI master to go - what am I missing?  (Read 906 times)
0 Members and 1 Guest are viewing this topic.
Offline Offline
God Member
*****
Karma: 4
Posts: 813
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.)
Logged

Massachusetts, USA
Offline Offline
Tesla Member
***
Karma: 213
Posts: 9060
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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?
Logged

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

Offline Offline
God Member
*****
Karma: 4
Posts: 813
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.)
« Last Edit: December 31, 2011, 12:33:29 am by jwatte » Logged

Offline Offline
Full Member
***
Karma: 0
Posts: 167
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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

Massachusetts, USA
Offline Offline
Tesla Member
***
Karma: 213
Posts: 9060
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.

Logged

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

Offline Offline
God Member
*****
Karma: 4
Posts: 813
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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!
Logged

Global Moderator
Melbourne, Australia
Online Online
Brattain Member
*****
Karma: 535
Posts: 19768
Lua rocks!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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
Logged

http://gammon.com.au/electronics
Please post technical questions on the forum - not by personal message. Thanks!

Offline Offline
God Member
*****
Karma: 4
Posts: 813
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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:
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:
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:
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)))
    ;
  }
}
« Last Edit: January 01, 2012, 03:39:24 am by jwatte » Logged

Offline Offline
God Member
*****
Karma: 4
Posts: 813
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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 :-)
Logged

Pages: [1]   Go Up
Arduino Forum upgrade scheduled for Monday, October 20th, 11am-4pm (CEST). Sorry for the inconvenience!
Jump to: