Continue to Site

Welcome to EDAboard.com

Welcome to our site! EDAboard.com is an international Electronics Discussion Forum focused on EDA software, circuits, schematics, books, theory, papers, asic, pld, 8051, DSP, Network, RF, Analog Design, PCB, Service Manuals... and a whole lot more! To participate you need to register. Registration is free. Click here to register now.

12 Bit ADC with interfacing SPI in STM32

Status
Not open for further replies.

Alpaslan_Ersoz

Junior Member level 2
Joined
Aug 9, 2018
Messages
20
Helped
0
Reputation
0
Reaction score
0
Trophy points
1
Activity points
220
Hello,

I have an external 12 bit ADC (ADS7886) on a custom designed PCB with SMT32F767. This ADC interfaces with the microprocessor with SPI. Now my problem is that I couldn't read digitized values correctly. Maybe because of the incorrect driving of the ADC or setting of SPI. Based on the ADC datasheet, I should set ADC SCK as 20 MHz, but I couldn't do that with SPI. Because I can set only either 13.5 Mbits/s or 26 Mbits/s baudrate of the SPI. So how can I correctly drive ADC and read the digitized values?

Thank you

This main code:

Code:
void ADC_Conversion_of_Voltage_Transients()
{
					
		HAL_GPIO_WritePin(ADC_CS_GPIO_Port, ADC_CS_Pin, GPIO_PIN_RESET);
		HAL_SPI_Receive(&hspi4,ADC_Buf,2,100);
		HAL_GPIO_WritePin(ADC_CS_GPIO_Port, ADC_CS_Pin, GPIO_PIN_SET);
		Sample = (((uint16_t) ADC_Buf[1]) << 8 | ADC_Buf[0]);
		//Sample = 0x0 << 12 | ADC_Buf;
		volt = (float)(Sample * (5.0 / 4096.0)); // 
		testdata_in[i++]=volt;
		i %= 16;
	
}
This is SPI settings:

Code:
/* SPI4 init function */
static void MX_SPI4_Init(void)
{

  /* SPI4 parameter configuration*/
  hspi4.Instance = SPI4;
  hspi4.Init.Mode = SPI_MODE_MASTER;
  hspi4.Init.Direction = SPI_DIRECTION_2LINES;
  hspi4.Init.DataSize = SPI_DATASIZE_16BIT;
  hspi4.Init.CLKPolarity = SPI_POLARITY_HIGH;
  hspi4.Init.CLKPhase = SPI_PHASE_2EDGE;
  hspi4.Init.NSS = SPI_NSS_SOFT;
  hspi4.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;
  hspi4.Init.FirstBit = SPI_FIRSTBIT_MSB;
  hspi4.Init.TIMode = SPI_TIMODE_DISABLE;
  hspi4.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  hspi4.Init.CRCPolynomial = 7;
  hspi4.Init.CRCLength = SPI_CRC_LENGTH_DATASIZE;
  hspi4.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;
  if (HAL_SPI_Init(&hspi4) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

}

I have attached ADC SCK (Yellow figure), ADC CS and SDO (Green figure), ADC input signal (Biphasic yellow signal), and ADC output (Table results in debugger).
 

Attachments

  • test adc sck and sdo 10 7 19 0 0.png
    test adc sck and sdo 10 7 19 0 0.png
    24.6 KB · Views: 269
  • test adc output 10 7 19.PNG
    test adc output 10 7 19.PNG
    225 KB · Views: 227
  • test adc and sck 10 7 19 0.png
    test adc and sck 10 7 19 0.png
    21.9 KB · Views: 326
  • test adc input 10 7 19 0.png
    test adc input 10 7 19 0.png
    26.8 KB · Views: 218
Last edited by a moderator:

Hi,

I should set ADC SCK as 20 MHz, but I couldn't do that with SPI. Because I can set only either 13.5 Mbits/s or 26 Mbits/s baudrate of the SPI. So how can I correctly drive ADC and read the digitized values?
No, datasheet does not say you need to drive SCLK with 20MHz .... instead it says maximum SCLK frequency is 20MHz.
Thus 26MHz is out of specification.
But you may safely use 13.5MHz.

In your calculation you use "5V"....this means the ADC is supplied with 5V...thus the ADC's serial output signal is 5V, too. Is your STM input 5V compatible?

You use NSS_soft. This is not wrong, but why not hardware generated NSS? Mind: at least in hardware generated NSS mode the NSS signal is "open collector style" and needs external pull up resistor.

You use "SPI_PHASE_2EDGE", I'm not sure, but I'd use "SPI_PHASE_1EDGE" ... according ADC timing diagram.

I can't verify your scope picures, because it's not clear to me which picture shows which signals..
Give one picture only with clear signal description.

Show your schematic and PCB layout.

Klaus
 

No, darasheet does not say you need to drive SCLK with 20MHz .... instead it says maximum SCLK frequency is 20MHz.
Thus 26MHz is out of specification.
But you may safely use 13.5MHz.
Right! When I double check the datasheet, I revealed the SCLK maximum frequency. But TINA-TI simulation for ADC SPICE model, they run the ADC around 41.2ns clock period.

Is your STM input 5V compatible?
This is good point. Actually the STM has 3.3V supply voltage, so it may goes to saturation for more than 3.3V voltages. But the figure, whose name is test adc input 10 7 19 (you will see figure name at the bottom right of the figure when you click of it), is the ADC input analog signal and its amplitude maximum around 2.76V. So it is in the limit both microcontroller and ADC.

You use NSS_soft. This is not wrong, but why not hardware generated NSS? Mind: at least in hardware generated NSS mode the NSS signal is "open collector style" and needs external pull up resistor
Honestly, I didn't enable NSS pin of the SPI. Instead of it I use GPIO pin as reset and set function for CS of the ADC.

You use "SPI_PHASE_2EDGE", I'm not sure, but I'd use "SPI_PHASE_1EDGE" ... according ADC timing diagram.
Actually, in the datasheet, it says like "SDO goes to the 3-state condition", so I assume that it is mode 3 SPI, then I set as CPOL=1 and CPHA=1. But I will try your recommendation.

I can't verify your scope picures, because it's not clear to me which picture shows which signals..
Give one picture only with clear signal description.
Let's try to make a short list each of the figure name and its explanations. They may be more clear.

When I select one of the figure, you will see its name at bottom right of the figure.
- test adc input 10 7 19.png shows that ADC input analog signal with yellow biphasic square waves.
- test adc and sck 10 7 19.png shows that ADC SCK signal with yellow plot and ADC CS signal with green plot.
- test adc sck and sdo 10 7 19.png shows that ADC SCK signal with yellow plot and ADC SDO signal with green plot.
- test adc output 10 7 19.png shows that during debugging mode of the KEIL IDE, what the variables gets their values.

I have attached the schematic and PCB layout screenshots.

- ADC. png shows that ADC itself with its coupling capacitors.
- ADC MCU.png shows that ADC connection pins to the STM32.
- ADC layout.png shows that ADC itself layout.
- ADC layout MCU.png shows that ADC connections to the MCU.
ADC MCU.PNG
ADC layout.PNG
ADC.PNG
ADC layout MCU.PNG
 

Hi,

I'm confused with your informations... don't know what you refer to.
Please be more unambiguous.

But TINA-TI simulation for ADC SPICE model, they run the ADC around 41.2ns clock period.
I don't see any Tina simulation, nor any timing. But indeed it does not matter anyway.
--> you have to keep on datasheet specification " max 20MHz"

Picture "test adc input 10 7 19" is weird in either way.
The voltages make no sense, the timing makes no sense.
We don't have any knowledge about test conditions and where and how you tested it.

I expect your microcontroller input to be high impedance... for this condition the ADC datasheet says:
* HIGH output level is higher than VDD-0.2V, whis means 4.8V @ 5.0V_VCC
* LOW output level is lower than 0.4V
None of these level I can see on your scope picture.
My conclusion: Either there is something wrong with the measurement or test setup, or you operate the ADC beyond specification.

In either case the expected ADC output voltage is way higher than 3.3V .... thus again my question whether your microcontroller has 5V tolerant inputs.
If not: You need to rectify this problem first. (Decreasing ADC_VCC or reducing digital signal voltage to microcontroller input)
No need to go on unless this problem is solved.

Honestly, I didn't enable NSS pin of the SPI. Instead of it I use GPIO pin as reset and set function for CS of the ADC.
/CS controls the conversion. Falling edge samples the analog input voltage and starts the conversion.
It can not be left LOW...which I see on your scope pictures.
--> /CS = NSS needs to toggle!

"SDO goes to the 3-state condition", so I assume that it is mode 3 SPI, then I set as CPOL=1 and CPHA=1. But I will try your recommendation.
"3-state" is not related to SPI mode ... not to SPI at all.
It just says the output driver may have three states:
* HIGH
* LOW
* high impedance (floating)
"Going to 3-state condition" usually means " the output becomes high impedance"

Your scope pictures:
It has 4 channels. Please use one channel for one signal.
If you show digital communication, then you need to show all three signals (/CS, SCK, data) at once, because they relate to each other.

Schematic and PCB layout, are just too small snippets.
They give no information where the signals come from or go to...

Klaus
 

Thank you!

I am sorry for not sending clear messages. Now I changed to 3.3V of the ADC VCC. Then I capture the screenshot ADC SCK (yellow plot), ADC CS (blue plot), and ADC SDO (green plot).

test ADC CS, SCLK and SDO 0.png
 

Hi,

Looks good.
Correct voltage levels, clean timing.

The SCK frequency seems to be below 1MHz. You could go faster if you like.
Why do you send 32 bits? 16 should be enough. But it's no problem.

Klaus
 

Thank you!

I have modified my code a little bit. I also attached the results in Keil debug sessions.

uint8_t ADC_Buf[2];
uint16_t Sample;

HAL_GPIO_WritePin(ADC_CS_GPIO_Port, ADC_CS_Pin, GPIO_PIN_RESET);
HAL_SPI_Receive(&hspi4,ADC_Buf,1,100);
Sample = (((uint16_t) ADC_Buf[0]) << 8 | ADC_Buf[1]) & 0x0FFF;
volt = (float)(Sample * (5.0 / 4096.0)); //
testdata_in[i++]=volt;
i %= 16;
HAL_GPIO_WritePin(ADC_CS_GPIO_Port, ADC_CS_Pin, GPIO_PIN_SET);

watch window result.PNG


Now, how can I arrange digitized data period, acquisition time, and eventually plot it?

I want to plot like TINA TI simulation which is attached figure (the figure is result of my ADC simulation)

adc tina.PNG
 

Attachments

  • TINA TI Simulation.PNG
    TINA TI Simulation.PNG
    373.9 KB · Views: 471
Last edited:

Hi,

The TINA plot shows the operation of the "sample and hold" circuit inside the ADC.
You don't have access to the purple waveform, nor can you measure it, neither as analog signal, nor as digital signal.

The green line is called "ideal", but in my eyes it's the externally applied analog signal (as an example) to the ADC.

Klaus
 

Actually, when I check the output values in Keil IDE, I read values mostly 2.2V and 1.8V values. But the input signal has 1.3V low amplitude during 100us time session, 1.9V middle level (interphase delay) during 50us, and 2.55V during 100us. So how can I see all these values in Keil IDE debug session? How can I calculate sampling time period and digitized data acquisition time?

I have attached the input signal (yellow plot).

test adc input 10 7 19 0.png
 

Hi,

It's not clear to me what you want.

In either case according the diagram in post#5 your sampling period is much higher than 45us.
So if your input signal has a timing if 50us ... then you surely are beyond nyquist requirement.
Your digital conversion value is just a snapshot of a small piece of time.

Please mind: Nyquist talks about sine waveform..but your signal contains a lot of overtones.

Klaus
 

Hello,

According Nyquist theorem, I know that sampling frequency is at least two times bigger than analog signal. Practically, ten times bigger is selected. For example, in post #7, there is input analog signal and its frequency is almost 3kHz, so sampling frequency should be 30kHz. After that I don't know how can I modify SPI settings based on this calculation? The other thing is that how long should I wait to see all the outputs of the digital conversion? Because I just want to make sure my conversion perfectly works. If I can plot the digitized value, it will be great.

Thank you for your feedback and messages! I have learned many things in a short time with your posts.
 

Hi,

so sampling frequency should be 30kHz. After that I don't know how can I modify SPI settings based on this calculation?
SPI settings have nothing to do with sampling frequency.
It depends on how often you toggle /CS .... and read the conversion data.
But hopefully you don't try to use te run time of the main loop for sampling timing control.
--> You need at least an Interrupt with fixed, known timing. Or let the hardware (timer) directly start the conversion (without the need for an ISR)
If you are running low on processing power, then I recommend to use SPI in DMA mode.

The other thing is that how long should I wait to see all the outputs of the digital conversion?
What do you mean with "all the outputs"?
Number of samples? Number of bits?
For the bits outputted by the ADC: 4 leading zeros, 12 data bits = 16 bits. In doubt read the darasheet.

If I can plot the digitized value, it will be great.
What does this mean? Plotting a graph, or plotting the numbers?
Is there a plotter/printer connected to the STM32? If yes, what type and what interface?
Or display it on a monitor? Connected to the STM32 or via PC?

Klaus
 

Ok, I will try to control timer interrupt and share my results.

At the mean time, I mean with 'all outputs' that to observe the input analog signal amplitude values in digitized "volt" variable. You see that "testdata_in" array index is updated different values of "volt" variable. So, my concern is that why one of the index of the value in testdata_in array doesn't have 2.55V in certain amount of time?

I mean plotting "volt" variable with all possible values. STM Studio is one the in my mind. Do you have any other idea? I just want to show digitized values with sampling points.
 

Ok, I will try to control timer interrupt and share my results.

I have tried to do control sampling frequency with a timer in interrupt mode. But I couldn't figure out. My input signal has around 1.2kHz frequency, so I set the sampling frequency as 10kHz. But the clock cycle is not limited during the sampling time.

The code is in the stm32f7xx_it.c

Code:
/* USER CODE BEGIN 0 */

uint8_t Timer_Flag;

/* USER CODE END 0 */


void TIM4_IRQHandler(void)
{
  /* USER CODE BEGIN TIM4_IRQn 0 */

  /* USER CODE END TIM4_IRQn 0 */
  HAL_TIM_IRQHandler(&htim4);
  /* USER CODE BEGIN TIM4_IRQn 1 */
	HAL_GPIO_TogglePin(ADC_CS_GPIO_Port, ADC_CS_Pin);
	Timer_Flag=1;

  /* USER CODE END TIM4_IRQn 1 */
}


The code in main function is

Code:
void ADC_Conversion_of_Voltage_Transients()
{
		if(Timer_Flag)
		{
		HAL_SPI_Receive(&hspi4,ADC_Buf,16,100);
		Sample = (((uint16_t) ADC_Buf[1]) << 8 | ADC_Buf[0]) & 0x0FFF;
		volt = (float)(Sample * (5.0 / 4096.0)); //
		testdata_in[i++]=volt;
		i %= 16;
		Timer_Flag=0;
		}
}

The ADC CS (yellow plot) and ADC SCK (green plot) is attached.

test cs and sck 10 9 19 0.png
 

No useful SPI timing generated. Using timer interrupt to trigger the conversion is o.k., but the relation of CS and SPI_Receive isn't right.

Presently you toggle CS at 20 kHz and also set the timer flag at this rate. Instead you should set the flag at 10 kHz rate and operate CS together with read action.
 

Ok, I set timer in interrupt mode as 10kHz. But ADC SCK is not active only ADC CS is at low level. They are independent.

What is the wrong in my code?

Timer interrupt file code:

Code:
void TIM4_IRQHandler(void)
{
  /* USER CODE BEGIN TIM4_IRQn 0 */
	
  /* USER CODE END TIM4_IRQn 0 */
  HAL_TIM_IRQHandler(&htim4);
  /* USER CODE BEGIN TIM4_IRQn 1 */
	HAL_GPIO_TogglePin(ADC_CS_GPIO_Port, ADC_CS_Pin);
	Timer_Flag=1;	
	
	//volt = (float)(testdata_in * (5.0 / 4096.0)); //

  /* USER CODE END TIM4_IRQn 1 */
}


Main function:

Code:
void ADC_Conversion_of_Voltage_Transients()
{
			
		HAL_SPI_Receive(&hspi4,ADC_Buf,16,100);
		Sample = (((uint16_t) ADC_Buf[1]) << 8 | ADC_Buf[0]) & 0x0FFF;
		volt = (float)(Sample * (5.0 / 4096.0)); //
		testdata_in[i++]=volt;
		i %= 16;
		
}


Call function while loop:

Code:
if (Timer_Flag)
		{
			ADC_Conversion_of_Voltage_Transients();
			Timer_Flag=0;
		}

Here is ADC CS (yellow plot) which is controlled timer interrupt; ADC SCK (green plot which has 14MHz).

test sck and cs 10 9 19 0.png
 

Hi,

But ADC SCK is not active only ADC CS is at low level.
I see it differently: SCK is active all the time.

But I assume there is a misunderstanding.
When I said: " /CS needs to toggle"..
I did not mean it should toggle as 50% duty cycle.

More precisely I wanted to say: It must not stay continously LOW (as it did in your first scope pictures)
..because the ADC needs a "falling edge" to initiate a conversion....and "continously low" means there is no edge at all.

All is explained in the datasheet .... and btw is standard SPI.

If you want a 10kHz sampling rate, then you need a falling edge every 100us.
.. but for sure after the falling /CS edge you need to go on according interface specification:
At least 16 SCK cycles to perform the data transfer.
And after this you need to set /CS high.

Btw: it seems you have difficulties to understand the three SPI signals correctly. So -again- why don't you use the HAL feature to generate the /CS (=NSS) signal? It will do it correctly and you don't have to worry about it.

****
Additionally I recognized that you use HAL_receive_size = 16 .... which are 16 bytes, but you need 16 bit = 2 bytes (at least).
(I'm not familiar with HAL .... but this is how I understand the internet examples/tutorials).
Did you go through some tutorials? I recommend this. I did an internet search and found a lot of them...

Klaus
 

Have you changed the setup of the SPI module from the one shown in your first post?
Basically, the SPI module should NOT be generating the clock all of the time unless you are in 'Receive Only' mode or TI mode.
I think you need to understand some of the basics of SPI transfers (the following assumes the basic operation of the SPI module):
- the SPI master generates the clock and only when it initiates an exchange
- the 'Chip Select' pin (whether it is the NSS line or a GPIO line that you control) should be used to tell the slave to 'start listening' before the exchange begins, and then to 'stop listening' after the exchange completes
(You should always use the chip select line as this prevents spurious clock pulses from getting the SPI master and slave out of sync)
- an SPI exchange simultaneously has the master send data to the slave and the slave send data to the master
- the exchange occurs on the agreed edges of the clock pulse - this is why you must make sure that the setup of the master and the slave are the same with respect to how they interpret the clock
Therefore, to perform an exchange:
- the slave needs to load the value to be sent to the master before the exchange takes place
- the master (or your code) lowers the 'chip select' line (resets the slave to start listening)
- the master begins toggling the clock line and sending its value to the slave; at the same time it reads the value from the slave
- at the end of the exchange, if there are more values to exchange then the master and the slave needs to reload the new values and then above step is repeated
- when all values have been exchanged, the chip select line is raised and the job is done
You need to read the data sheet for the salve (the ADC in your case) and make sure you know what it is expecting from the master in terms of the operation of the chip select line, the number of clock pulses per value and the clock edges to be used to send and receive the value bits.
The ADC data sheet shows that the SPI transfers are very straight forward and the basic setup of the SPI module in the STM32F767xxx MCU will do the job well. You need to make sure that the number of bits (clock pulses) is set to 16, use NSS or a GPIO as you choose, you want CPHA and CPOL to either both be 0 or both be 1.
I also suggest that you forget all about the simplex, half duplex and other advanced modes (at least for now). While this will mean you need both the MOSI and MISO lines (at the start), it also means you have a chance to get the basics right and communication going with the ADC. (The ST SPI modules are very capable but, in my experience, they can be a real pain when you start using the mode advanced operating modes.)
Personally, I would get rid of all of the interrupt code and simply write an app that initialises the SPI module and in the main loop initiate an SPI exchange by writing a 0 (or some other dummy value), waiting for the RXNE flag to be set before reading the received value.
Once you get that working, then you can start introducing interrupts and using a timer to trigger the exchange etc. As a last step, if you really need to, look at the half duplex mode to free up the MISO pin.
When you have reliable reads form the ADC, then you can incorporate the code back into the main app to provide the rest of the functionality.
Susan
 

Thanks KlausST and Aussie Susan for great explanations!

I still try to find an applications about NSS enabled SPI because mostly YouTube videos and internet search don't have NSS function. They mostly prefer GPIO type CS pin. But I wonder how it works. So I will try to implement it.

The other recommendation from Susan, I try to implement it. SPI mode is full-duplex master.

Here is my code:

Code:
                HAL_GPIO_WritePin(ADC_CS_GPIO_Port, ADC_CS_Pin, GPIO_PIN_RESET);
		spidata[0]=0x00;
		spidata[1]=0x00;
		HAL_SPI_Transmit(&hspi4, spidata,2,100);
		HAL_SPI_Receive(&hspi4,ADC_Buf,16,100);
		Sample = (((uint16_t) ADC_Buf[1]) << 8 | ADC_Buf[0]) & 0x0FFF;
		volt = (float)(Sample * (5.0 / 4096.0)); //
		testdata_in[i++]=volt;
		i %= 16;
		HAL_GPIO_WritePin(ADC_CS_GPIO_Port, ADC_CS_Pin, GPIO_PIN_SET);


This is the result. Yellow is ADC CS and green is ADC SCK.

Test CS and SCK 10 10 19 0.png
 

You are still treating the SPI module like a UART (which can do independent transmits and receives).
Not tested but try something like:
Code:
spidata[0]=0x00;       // Set everything up  before you start the exchange
spidata[1]=0x00;
HAL_GPIO_WritePin(ADC_CS_GPIO_Port, ADC_CS_Pin, GPIO_PIN_RESET);
HAL_SPI_TransmitReceive(&hspi4, spidata,ADC_Buf,1,100);   // See below
HAL_GPIO_WritePin(ADC_CS_GPIO_Port, ADC_CS_Pin, GPIO_PIN_SET);
Sample = (((uint16_t) ADC_Buf[1]) << 8 | ADC_Buf[0]) & 0x0FFF;  // Do this after you have turned off the ADC
The size of the transfer is the number of bits per unit that you have told the SPI module to exchange. In the OP you set this to 16 bits so you only need to perform 1 exchange.
If you really want to you can save the 'spidata' array by using:
Code:
HAL_SPI_TransmitReceive(&hspi4, ADC_Buf,ADC_Buf,1,100);
What the function does (and you can see the library code if you look) is to read form the 'send' buffer, perform the exchange and then write to the 'receive' buffer. Therefore you can use the same buffer in this case as you don;t care what you send TO the ADC, only what comes back from it.
Susan
 

Status
Not open for further replies.

Similar threads

Part and Inventory Search

Welcome to EDABoard.com

Sponsor

Back
Top