How to set SPI clock to 76.9kHz by using the UNO R3?

Seriously? You have a device that requires that exact timing / frequency?

SPI is supposed to be bit-rate independent, what device do you have that needs such a specific rate?


Rob

it's a non-standard spi device(like spi, but not). I read the communication between the master device and slave device. The clock is 76.9kHz, 8 bits, idle high, rising edge sampling.

Have you got a link to the datasheet?

It won't be possible to be that specific with the SPI of the Arduino unless you use a different crystal frequency (and hence modified bootloader).

It may however be possible to do it using an Timer1 or Timer2 and compare/overflow interrupts. If you can post a datasheet I can give you a better example of how the timer could be used.

TCWORLD:
Have you got a link to the datasheet?

It won't be possible to be that specific with the SPI of the Arduino unless you use a different crystal frequency (and hence modified bootloader).

It may however be possible to do it using an Timer1 or Timer2 and compare/overflow interrupts. If you can post a datasheet I can give you a better example of how the timer could be used.

I am decoding a spi-like protocol. There are a master device and a slave device. Then I want use my UNO instead of the origin master device.

Waveform as follow picture.

As you can see the clock period is around 13 us. The corresponding clock frequency is 76.9kHz.

Is that possible to write my own clock generator that is based on the system clock and a clock counter? For example, count 100 system clock and set my_clk_generator to 1, then count another 100 system clock and set my_clk_generator to 0.

OK so you're not generating this you're at the mercy of an existing system.

Why not just hang your Arduino off the same line as a slave, that would allow you the sample one signal in hardware.

Alternatively run the CLK signal to an external interrupt input, then read the two signals in the ISR and reconstruct the data from that.

Either way you don't have to know or care about the frequency.


Rob

What I would suggest you do is to use the SPI module, set the divider to 128 (that will give you 125kHz), and try it.

In all likelyhood the slave device will work fine at that frequency. The design of the SPI devices should allow them to run at any speed the master sets (as long as they can keep up). I would think that the slave should manage 125kHz.

Then I want use my UNO instead of the origin master device.

Doh, I didn't read this bit.

Just because you are seeing 76.9kHz now that doesn't mean you have to generate the same, that frequency is probably generated by some bit banging from the current master device using who-knows-what processor, language and crystal.

If the slave device didn't work at 125kHz as TCWORLD said or some other slow rate I would eat my Arduino (the small one that is :)).


Rob

TCWORLD:
What I would suggest you do is to use the SPI module, set the divider to 128 (that will give you 125kHz), and try it.

In all likelyhood the slave device will work fine at that frequency. The design of the SPI devices should allow them to run at any speed the master sets (as long as they can keep up). I would think that the slave should manage 125kHz.

It seems that it doesn't work.

My Code

#include "Arduino.h"
//include the SPI library
#include <SPI.h>

const int CLKpin = 13; // set pin 13 as the CLK
const int MOSIpin = 11; // set pin 11 as the MOSI
const int MISOpin = 12; // set pin 12 as the MISO
const int SSpin = 10; // set pin 10 as the SS

// initialize setup
void setup(){
  pinMode (CLKpin, OUTPUT); // set CLKpin as an output
  pinMode (MOSIpin, OUTPUT); // set MOSIpin as an output
  pinMode (MISOpin, INPUT); // set MISOpin as an input
  pinMode (SSpin, OUTPUT); // set SSpin as an output
  SPI.begin(); // initialize SPI
  
  SPI.setBitOrder(MSBFIRST); 
  SPI.setDataMode(SPI_MODE3); 
  SPI.setClockDivider(SPI_CLOCK_DIV128); 
}



void loop() {
  byte COMMAND = 0x13;
  byte cmd_step = 0x02;
  
  SPI.transfer(COMMAND); // contract iris command
  delayMicroseconds(191);
  
  SPI.transfer(cmd_step);
  delayMicroseconds(16000);

It does indeed look like the slave is struggling, can you further reduce the speed?

Do you have a data sheet for the device?


Rob

Just as a quick test, try adding this to the start of the setup():

CLKPR = 0b10000000;
CLKPR = 0b00000001;

You will also have to half all of the delays for this test as the Arduino IDE wont know how to account for this change.

What it does is prescales the whole system clock from 16MHz down to 8MHz, which will mean you get a 62.5kHz SPI clock.

The other thing is what are the three signals in the first scope trace you posted? It doesn't quite look like SPI.

Graynomad:
It does indeed look like the slave is struggling, can you further reduce the speed?

Do you have a data sheet for the device?


Rob

I haven't the data sheet. I use the "SPI.setClockDivider(SPI_CLOCK_DIV128); " , which is the slowest spi clock in the SPI library.

I am decoding the protocol of Canon lens. There is no data sheet available. But there is a Canon services manual that was released by some guys.

The Canon services manual said as follows:
clock pulse 62.5kHz, 16us pulse
8 bit

My observation on oscilloscope:
clock pulse,13us pulse
8bit
clock idle high
rising edge sampling

Other non-official info (might be wrong):

  1. Motorola SPI; 8 bit serial; Such as the protocol used with the 68HC05 chip.

Oh, another option if you dont need the serial port is to use it in fake SPI mode. This allows you exact control over the baud rate.

Example (Note: This is untested, I based it on the code examples in the 328p datasheet):

void SPIInitialise() {
  // put your setup code here, to run once:
  pinMode(4,OUTPUT); //SCLK pin
  pinMode(0,INPUT); //MISO
  pinMode(1,OUTPUT); //MOSI
  pinMode(2,OUTPUT); //SS
  
  //Set UART as SPI.
  UBRR0 = 0;
  UCSR0C = (1<<UMSEL01)|(1<<UMSEL00)|(1<<UCPHA0)|(1<<UCPOL0);  //Set to MasterSPI (UMSEL0=1, UMSEL1=1), in Mode3 (CPHA=1, CPOL=1)
  UCSR0B = (1<<RXEN0)|(1<<TXEN0); //Enable TX and RX (TX = MOSI, RX = MISO)
  
  UBRR0 = 127; //SPI clock rate. UBRR0 = (FCPU/(2*BAUD)) -1;. So for 62.5KHz, UBRR0 = (16000000/(2*62500))-1 = 128 -1 = 127
}

byte SPITransfer(byte data) {
  
  /* Wait for empty transmit buffer */
  while ( !( UCSR0A & (1<<UDRE0)) );
  
  /* Put data into buffer, sends the data */
  UDR0 = data;
  
  /* Wait for data to be received */
  while ( !(UCSR0A & (1<<RXC0)) );
  
  /* Get and return received data from buffer */
  return UDR0;
}

void setup(){
  SPIInitialise();
}

void loop(){
  //Main loop
}

My bit-banged SPI used for uploading hex files:

// bit banged SPI pins
const byte MSPIM_SCK = 4;  // port D bit 4
const byte MSPIM_SS  = 5;  // port D bit 5
const byte BB_MISO   = 6;  // port D bit 6
const byte BB_MOSI   = 7;  // port D bit 7

// 8 MHz clock on this pin
const byte CLOCKOUT = 9;


// for fast port access (Atmega328)
#define BB_MISO_PORT PIND
#define BB_MOSI_PORT PORTD
#define BB_SCK_PORT PORTD
const byte BB_SCK_BIT = 4;
const byte BB_MISO_BIT = 6;
const byte BB_MOSI_BIT = 7;

// control speed of programming
const byte BB_DELAY_MICROSECONDS = 4;

...


// Bit Banged SPI transfer
byte BB_SPITransfer (byte c)
{       
  byte bit;
   
  for (bit = 0; bit < 8; bit++) 
    {
    // write MOSI on falling edge of previous clock
    if (c & 0x80)
        BB_MOSI_PORT |= _BV (BB_MOSI_BIT);
    else
        BB_MOSI_PORT &= ~_BV (BB_MOSI_BIT);
    c <<= 1;
 
    // read MISO
    c |= (BB_MISO_PORT & _BV (BB_MISO_BIT)) != 0;
 
   // clock high
    BB_SCK_PORT |= _BV (BB_SCK_BIT);
 
    // delay between rise and fall of clock
    delayMicroseconds (BB_DELAY_MICROSECONDS);
 
    // clock low
    BB_SCK_PORT &= ~_BV (BB_SCK_BIT);
    }
   
  return c;
  }  // end of BB_SPITransfer

By tweaking BB_DELAY_MICROSECONDS you should be able to get the frequency you want.

TCWORLD:
Oh, another option if you dont need the serial port is to use it in fake SPI mode. This allows you exact control over the baud rate.

Example (Note: This is untested, I based it on the code examples in the 328p datasheet):

void SPIInitialise() {

// put your setup code here, to run once:
 pinMode(4,OUTPUT); //SCLK pin
 pinMode(0,INPUT); //MISO
 pinMode(1,OUTPUT); //MOSI
 pinMode(2,OUTPUT); //SS
 
 //Set UART as SPI.
 UBRR0 = 0;
 UCSR0C = (1<<UMSEL01)|(1<<UMSEL00)|(1<<UCPHA0)|(1<<UCPOL0);  //Set to MasterSPI (UMSEL0=1, UMSEL1=1), in Mode3 (CPHA=1, CPOL=1)
 UCSR0B = (1<<RXEN0)|(1<<TXEN0); //Enable TX and RX (TX = MOSI, RX = MISO)
 
 UBRR0 = 127; //SPI clock rate. UBRR0 = (FCPU/(2BAUD)) -1;. So for 62.5KHz, UBRR0 = (16000000/(262500))-1 = 128 -1 = 127
}

byte SPITransfer(byte data) {
 
 /* Wait for empty transmit buffer /
 while ( !( UCSR0A & (1<<UDRE0)) );
 
 /
Put data into buffer, sends the data /
 UDR0 = data;
 
 /
Wait for data to be received /
 while ( !(UCSR0A & (1<<RXC0)) );
 
 /
Get and return received data from buffer */
 return UDR0;
}

void setup(){
 SPIInitialise();
}

void loop(){
 //Main loop
}

Thanks a lot. It seems that your code works. By the way, does your function "SPITransfer" read the MISO to the MCU? How can I store the return values to some variables?

Thanks. I am learning using Arduino.

saving is just a matter of reading the return variable.
The actual process depends on how the communication is set up. For example, if it is full duplex (master and slave send a byte at the same time, it would be this:

byte dataOut = ...;
byte dataIn = SPITransfer(dataOut);

Or if it was half duplex (master sends a byte, then produces 8 clock cycles for slave to send response) it would be:

byte dataOut = ...;
SPITransfer(dataOut);
byte dataIn = SPITransfer(0x00); //master sends out 0, but in doing so generates 8 clocks. Response is saved

Thanks.

Master sends the 1st command to Slave; at the same time, Slave responds to the last command that is before the 1st command; Then, Master sends the 2nd command to Slave; at the same time, Slave reponds to the 1st command.

CLK MOSI vs MISO
active cmd_0 00 ;(meanless null value)
active cmd_1 resp to cmd_0;
active cmd_2 resp to cmd_1;
active cmd_3 resp to cmd_2;

which method should I use?
I think it may be your first code.

BTW, I change another canon lens, which has 500kHz CLK pulse(2us period per pulse) and 40~70us period between two data transfer.

I changed UBRR0 = 15 (based on (16000000/(2*500,000))-1 =15) and all other delays in my code, which worked great for the first canon lens(62.5kHz, 140~200us period between two data transfer).

when I uploaded to UNO R3 that is connecting the new lens(compiling has passed), a error occurred: "not in sync; resp: 0xFF"

How can I solve this problem?

Thanks.

For n+1 commands, it would look like this:

SPITransfer(cmd_0);
cmd_0_response = SPITransfer(cmd_1);
cmd_1_response = SPITransfer(cmd_2);
cmd_2_response = SPITransfer(cmd_3);
...
cmd_(n-1)_response = SPITransfer(cmd_n);
cmd_n_response = SPITransfer(0x00);

As for the not in sync, try holding the reset button until just after it finishes compiling, and see if that helps.

TCWORLD:
As for the not in sync, try holding the reset button until just after it finishes compiling, and see if that helps.

I can upload code_1 to the mcu successfully without error message, then connect the lens to the mcu via an adapter. The mcu makes lens work properly.

If I upload code_2 with the lens that has already been connected to the mcu via an adapter, an error message will occur: "avrdude: stk500_getsync(): not in sync: resp=0x06". I found the code_2 haven't been upload to the mcu. The code runing in the mcu is still code_1.

The code_1 and code_2 are exactly the same except for the slightly different clock frequency, which should influence nothing.
If I upload code_2 to the mcu, then connect the lens, it works perperly as well. if I upload code_1 with the lens connected, an error message will occur.

I tried to hold pressing reset when upload, but it doesn't work.