SPI - Clock does not seem to be working fine

Hello, I am trying to use the SPI clock to generate a clock signal to a microphone. The clock must be between 1-3 MHz so I have created the following code:

void setup() {
  // Put initialization like pinMode and begin functions here.
  SPI.setClockSpeed(3, MHZ);
  SPI.begin();
  delay(5000);
}

// loop() runs over and over again, as quickly as it can execute.
void loop() {
  SPI.transfer(0xffff);
}

the problem is, when I connect an oscilloscope it show that the clock is always at 1, and only have slight peaks at 0.

Can anyone give me some help

Thanks

First, if you are using default AUTOMATIC system mode, loop() will only run aproximately every 1ms, since all the cloud tasks take place between iterations of loop() taking that long.
Next, I’d not abuse SPI for that, since the clock won’t have a 50% duty cycle.
You could use a timed interrupt for that via SparkIntervalTimer library.
Or you might consider using a PWM, but I haven’t checked the max. rate for that.

1 Like

From the references it seems that the maximum frequency for a PWM is 230kHz

Is there any way of doing this easily??

Thanks

I couldn't find an explicit statement about max PWM frequency, only here

https://docs.particle.io/reference/firmware/photon/#analogwrite-pwm-

With a resolution set to 2 bit you may get the max frequency, but depending on pin that might be more or less.

And after rethinking your 1~3MHz SparkIntervalTimer will be out too, due to it's max rate of 3~5µs between each trigger.

So you might be back at SPI :blush:
Try SYSTEM_THREAD(ENABLED) to decouple the cloud thread from loop() and go for longer running DMA based SPI.transfer() with a calkback retriggering the next transfer.

But the more professional solution would be to use a 3MHz quarz oszillator.

1 Like

Hi,

Sorry, could you please provide more information, it is just use SYSTEM_THREAD(ENABLED)?? Or do I have to do anything else?

Thanks

I’d just do something like this

(not tested tho’)

SYSTEM_THREAD(ENABLED)
//SYSTEM_MODE(SEMI_AUTOMATIC)   // optional to start without WiFi

const int spiBytes = 128;

void startTransmission() {
  SPI.transfer(NULL, NULL, spiBytes, startTransmission);
}

void setup() {
  SPI.setClockSpeed(3, MHZ);
  SPI.begin();
  startTransmission();
}

void loop() {
}

But the non-symmetric duty cycle will still be an issue.
To overcome that you could go for 6MHz and use an external flip-flop.

Having said this, @rickkas7 or @peekay123 may have some more fitting suggestions since they have already been tapping into to hardware timers of the µC and provide a less hacky solution.

Actually, I recommend using an external oscillator to get the correct frequency and duty cycle. As @ScruffR has it, the ISR call will be recursive and may cause a stack overflow. If not, it will cause a delay before and after each 128 byte block due to the interrupt latency and the DMA setup times. The resulting clock will be inconsistent.

@peekay123, I dare to contradict.
SPI.transfer(void*, void*, count, callback ) is an async function. It will start the DMA transfer an leave the function before the next callback call happens :wink:
But the non-consistent timing may be an issue, depending on the actual needs of the microphone used.

And I had suggested the ext. crystal too

@ScruffR, glad you corrected me! However, the ISR and DMA setup latency issues still apply :wink:

1 Like

Yup, that was added in the subsequent edit :sunglasses:

1 Like

It seems like it ought to be possible, but I’m not really sure. I’m traveling right now and while I have a lot of things with me, I didn’t bring an oscilloscope or a logic analyzer LOL. I’ll try to look into this next week if an answer isn’t found before then.

I never leave without my (want to be) “oszilloscope” (Dimension 98 * 60 * 14.5 (mm)) :joy:

1 Like

I was able to do it with a hardware timer in PWM mode using the STM32F205 bare metal. I’m not positive this is a great idea, but it does appear to work. I got a nice 2.857 MHz signal output on D3.

#include "Particle.h"

void TIM3_PWMConfig(void);

void setup() {
	Serial.begin(9600);

	TIM3_PWMConfig();
}

void loop() {

}

// Example from:
// STM32F2xx Standard Library
// http://www.st.com/content/ccc/resource/technical/document/user_manual/59/2d/ab/ad/f8/29/49/d6/DM00023896.pdf/files/DM00023896.pdf/jcr:content/translations/en.DM00023896.pdf

void TIM3_PWMConfig(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	TIM_OCInitTypeDef  TIM_OCInitStructure;

	// Using TIM3_CH1 on Photon Pin D3, STM32F205 pin PB4
	//
	// If you want to use a different pin, see the data sheet to see what timer it's
	// connected to and what STM32F205 pin it uses:
	// https://docs.particle.io/datasheets/photon-datasheet/#pin-and-button-definition

	/* TIM3 IO configuration *************************************/
	/* Enable GPIOC clock */
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);

	/* Connect TIM3 pin (PB4) to AF2 */
	GPIO_PinAFConfig(GPIOB, GPIO_PinSource4, GPIO_AF_TIM3);

	/* Configure TIM3 CH2 pin (PB5) as alternate function */
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 ;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ;
	GPIO_Init(GPIOB, &GPIO_InitStructure);

	/* TIM3 configuration *****************************************
	 *
	 TIM3 is configured to generate PWM signal on CH1 with a
     frequency of 3 MHz and 50% duty cycle.
     TIM3 input clock (TIM3CLK) is equal to:
	   - PCLK1 if PCLK1 prescaler is 1
	   - 2 x PCLK1, otherwise
	 This example sets TIM_Prescaler to 0 and assumes that
	 	 HCLK = 120 MHz and PCLK1 = 30 MHz
	 	 => TIM3CLK = 2 x PCLK1 = 60 MHz

	 TIM3 signal frequency = TIM3CLK / ((Prescaler + 1) * Period)

	 Setting the Prescaler to 0, the Period = TIM3CLK / 3 MHz
		60 MHz / 3 MHz = 20

	20 = 2.857 MHz, 0.35 uS period
	19 = 3.030 MHz, 0.34 uS period

	 **************************************************************/
	/* Enable TIM3 clock */
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

	/* Time base configuration */
	TIM_TimeBaseStructure.TIM_Period = 20;
	TIM_TimeBaseStructure.TIM_Prescaler = 0;
	TIM_TimeBaseStructure.TIM_ClockDivision = 0;
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);

	/* Configure CH1 in PWM1 Mode */
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
	TIM_OCInitStructure.TIM_Pulse = 10;
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
	TIM_OC1Init(TIM3, &TIM_OCInitStructure);

	TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);
	TIM_ARRPreloadConfig(TIM3, ENABLE);
	/* Enable TIM3 counter */
	TIM_Cmd(TIM3, ENABLE);
}

2 Likes

Thanks @rickkas7!

How would you go about connecting to D6 (PA15)?

I tried the below without any luck
GPIO_PinAFConfig(GPIOA, GPIO_PinSource15, GPIO_AF_TIM3);

My overall goal is to increase the SPI clock frequency to ~5MHz-6MHz as I connected up an MCP3008 ADC that I would like to slightly overclock. Is that possible using an external clock?

I’m pretty sure that SPI cannot be externally clocked. The SPI clock can only be set to the STM32 peripheral clock divided by 2, 4, 8, 16, 32, 64, 128 or 256.

The peripheral clock is 60 MHz for SPI and 30 MHz for SPI1, I believe.

4 Likes

Makes sense, thank you!

@Garrett are you still looking to sue a mic on a particle ?

If so let’s talk,I been looking into a PDM type mic.

Did this ever work out? I am interested in interfacing a PDM mic to the Xeon.

It looks like the nRF52840 chip can support it and AdaFruit has added Arduino support for their boards: https://github.com/adafruit/Adafruit_nRF52_Arduino/blob/2c5d06993f2464437fb54e20b91b8c1593c5dfd6/libraries/PDM/examples/PDMSerialPlotter/PDMSerialPlotter.ino