SPI interface with STM32F407  - Page 1
Forum section: electronics / general

author: BlogRahul
author_rank: Regular Contributor
timestamp: September 09, 2023, 08:13:20 pm
content: Hello everyone,I am trying to interface an MCP3008 ADC with an STM32 microcontroller via the SPI protocol.Connection between the MCP3008 to the STM32F407:MCP3008 VDD to 3.3V on the STM32F407.MCP3008 VREF to 3.3V.MCP3008 AGND to ground (GND).MCP3008 DGND to ground (GND).MCP3008 CLK to an SPI clock pin on the STM32F407 (e.g., SPI1_CLK, PA5).MCP3008 DOUT to an SPI data input pin on the STM32F407 (e.g., SPI1_MISO, PA6).MCP3008 DIN to an SPI data output pin on the STM32F407 (e.g., SPI1_MOSI, PA7).MCP3008 CS/SHDN to a GPIO pin on the STM32F407 (PA4).MCP3008 CH0- to the analog signal.Code :I've written code to configure and communicate with the MCP3008 via SPI.The code reads data from the MCP3008 channel 0 and sends it to the UARTCode:#include<stdio.h>#include<string.h>#include "main.h"#include "usb_host.h"SPI_HandleTypeDef hspi1;UART_HandleTypeDef huart2;void SystemClock_Config(void);static void MX_GPIO_Init(void);static void MX_SPI1_Init(void);static void MX_USART2_UART_Init(void);void MX_USB_HOST_Process(void);// Function to read ADC data from MCP3008uint16_t MCP3008_ReadChannel(uint8_t channel);// Custom _write function for printf redirectionint _write(int file, char *ptr, int len){int i;for (i = 0; i < len; i++){HAL_UART_Transmit(&huart2, (uint8_t *)&ptr, 1, HAL_MAX_DELAY);}return len;}int main(void){HAL_Init();/* Configure the system clock */SystemClock_Config();/* Initialize all configured peripherals */MX_GPIO_Init();MX_SPI1_Init();MX_USART2_UART_Init();MX_USB_HOST_Init();uint16_t adcValue = 0;char txBuffer;while (1){MX_USB_HOST_Process();// Read ADC value from MCP3008 on channel 0 (we can change the channel)adcValue = MCP3008_ReadChannel(0);// Format ADC value into a stringsprintf(txBuffer, "ADC Value: %u\r\n", adcValue);// Transmit the formatted string over UARTHAL_UART_Transmit(&huart2, (uint8_t *)txBuffer, strlen(txBuffer), HAL_MAX_DELAY);// Delay before the next conversionHAL_Delay(1000); // Delay for 1 second}}void SystemClock_Config(void){RCC_OscInitTypeDef RCC_OscInitStruct = {0};RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};/** Configure the main internal regulator output voltage*/__HAL_RCC_PWR_CLK_ENABLE();__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);/** Initializes the RCC Oscillators according to the specified parameters* in the RCC_OscInitTypeDef structure.*/RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;RCC_OscInitStruct.HSEState = RCC_HSE_ON;RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;RCC_OscInitStruct.PLL.PLLM = 8;RCC_OscInitStruct.PLL.PLLN = 336;RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;RCC_OscInitStruct.PLL.PLLQ = 7;if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK){Error_Handler();}/** Initializes the CPU, AHB and APB buses clocks*/RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK){Error_Handler();}}/*** @brief SPI1 Initialization Function* @param None* @retval None*/static void MX_SPI1_Init(void){/* USER CODE BEGIN SPI1_Init 0 *//* USER CODE END SPI1_Init 0 *//* USER CODE BEGIN SPI1_Init 1 *//* USER CODE END SPI1_Init 1 *//* SPI1 parameter configuration*/hspi1.Instance = SPI1;hspi1.Init.Mode = SPI_MODE_MASTER;hspi1.Init.Direction = SPI_DIRECTION_2LINES;hspi1.Init.DataSize = SPI_DATASIZE_8BIT;hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;hspi1.Init.NSS = SPI_NSS_SOFT;hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;hspi1.Init.TIMode = SPI_TIMODE_DISABLE;hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;hspi1.Init.CRCPolynomial = 10;if (HAL_SPI_Init(&hspi1) != HAL_OK){Error_Handler();}/* USER CODE BEGIN SPI1_Init 2 *//* USER CODE END SPI1_Init 2 */}/*** @brief USART2 Initialization Function* @param None* @retval None*/static void MX_USART2_UART_Init(void){/* USER CODE BEGIN USART2_Init 0 *//* USER CODE END USART2_Init 0 *//* USER CODE BEGIN USART2_Init 1 *//* USER CODE END USART2_Init 1 */huart2.Instance = USART2;huart2.Init.BaudRate = 115200;huart2.Init.WordLength = UART_WORDLENGTH_8B;huart2.Init.StopBits = UART_STOPBITS_1;huart2.Init.Parity = UART_PARITY_NONE;huart2.Init.Mode = UART_MODE_TX_RX;huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;huart2.Init.OverSampling = UART_OVERSAMPLING_16;if (HAL_UART_Init(&huart2) != HAL_OK){Error_Handler();}/* USER CODE BEGIN USART2_Init 2 *//* USER CODE END USART2_Init 2 */}/*** @brief GPIO Initialization Function* @param None* @retval None*/static void MX_GPIO_Init(void){GPIO_InitTypeDef GPIO_InitStruct = {0};/* USER CODE BEGIN MX_GPIO_Init_1 *//* USER CODE END MX_GPIO_Init_1 *//* GPIO Ports Clock Enable */__HAL_RCC_GPIOE_CLK_ENABLE();__HAL_RCC_GPIOC_CLK_ENABLE();__HAL_RCC_GPIOH_CLK_ENABLE();__HAL_RCC_GPIOA_CLK_ENABLE();__HAL_RCC_GPIOB_CLK_ENABLE();__HAL_RCC_GPIOD_CLK_ENABLE();/*Configure GPIO pin Output Level */HAL_GPIO_WritePin(CS_I2C_SPI_GPIO_Port, CS_I2C_SPI_Pin, GPIO_PIN_RESET);/*Configure GPIO pin Output Level */HAL_GPIO_WritePin(OTG_FS_PowerSwitchOn_GPIO_Port, OTG_FS_PowerSwitchOn_Pin, GPIO_PIN_SET);/*Configure GPIO pin Output Level */HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);/*Configure GPIO pin Output Level */HAL_GPIO_WritePin(GPIOD, LD4_Pin|LD3_Pin|LD5_Pin|LD6_Pin|Audio_RST_Pin, GPIO_PIN_RESET);/*Configure GPIO pin : CS_I2C_SPI_Pin */GPIO_InitStruct.Pin = CS_I2C_SPI_Pin;GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;HAL_GPIO_Init(CS_I2C_SPI_GPIO_Port, &GPIO_InitStruct);/*Configure GPIO pin : OTG_FS_PowerSwitchOn_Pin */GPIO_InitStruct.Pin = OTG_FS_PowerSwitchOn_Pin;GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;HAL_GPIO_Init(OTG_FS_PowerSwitchOn_GPIO_Port, &GPIO_InitStruct);/*Configure GPIO pin : PDM_OUT_Pin */GPIO_InitStruct.Pin = PDM_OUT_Pin;GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;GPIO_InitStruct.Alternate = GPIO_AF5_SPI2;HAL_GPIO_Init(PDM_OUT_GPIO_Port, &GPIO_InitStruct);/*Configure GPIO pin : B1_Pin */GPIO_InitStruct.Pin = B1_Pin;GPIO_InitStruct.Mode = GPIO_MODE_EVT_RISING;GPIO_InitStruct.Pull = GPIO_NOPULL;HAL_GPIO_Init(B1_GPIO_Port, &GPIO_InitStruct);/*Configure GPIO pin : PA4 */GPIO_InitStruct.Pin = GPIO_PIN_4;GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);/*Configure GPIO pin : BOOT1_Pin */GPIO_InitStruct.Pin = BOOT1_Pin;GPIO_InitStruct.Mode = GPIO_MODE_INPUT;GPIO_InitStruct.Pull = GPIO_NOPULL;HAL_GPIO_Init(BOOT1_GPIO_Port, &GPIO_InitStruct);/*Configure GPIO pin : CLK_IN_Pin */GPIO_InitStruct.Pin = CLK_IN_Pin;GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;GPIO_InitStruct.Alternate = GPIO_AF5_SPI2;HAL_GPIO_Init(CLK_IN_GPIO_Port, &GPIO_InitStruct);/*Configure GPIO pins : LD4_Pin LD3_Pin LD5_Pin LD6_PinAudio_RST_Pin */GPIO_InitStruct.Pin = LD4_Pin|LD3_Pin|LD5_Pin|LD6_Pin|Audio_RST_Pin;GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);/*Configure GPIO pins : I2S3_MCK_Pin I2S3_SCK_Pin I2S3_SD_Pin */GPIO_InitStruct.Pin = I2S3_MCK_Pin|I2S3_SCK_Pin|I2S3_SD_Pin;GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;GPIO_InitStruct.Alternate = GPIO_AF6_SPI3;HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);/*Configure GPIO pin : OTG_FS_OverCurrent_Pin */GPIO_InitStruct.Pin = OTG_FS_OverCurrent_Pin;GPIO_InitStruct.Mode = GPIO_MODE_INPUT;GPIO_InitStruct.Pull = GPIO_NOPULL;HAL_GPIO_Init(OTG_FS_OverCurrent_GPIO_Port, &GPIO_InitStruct);/*Configure GPIO pin : MEMS_INT2_Pin */GPIO_InitStruct.Pin = MEMS_INT2_Pin;GPIO_InitStruct.Mode = GPIO_MODE_EVT_RISING;GPIO_InitStruct.Pull = GPIO_NOPULL;HAL_GPIO_Init(MEMS_INT2_GPIO_Port, &GPIO_InitStruct);}// Add the following function to read ADC data from MCP3008uint16_t MCP3008_ReadChannel(uint8_t channel){uint8_t txData;uint8_t rxData;// Create the command to request data from MCP3008txData = 0x01; // Start bittxData = (channel << 4) | 0x80; // Single-ended mode, channel selectiontxData = 0x00; // Dummy data to clock in results// Lower the CS/SHDN pin to activate MCP3008HAL_GPIO_WritePin(CS_I2C_SPI_GPIO_Port, CS_I2C_SPI_Pin, GPIO_PIN_RESET);// Transfer data over SPIHAL_SPI_TransmitReceive(&hspi1, txData, rxData, sizeof(txData), HAL_MAX_DELAY);// Raise the CS/SHDN pin to deactivate MCP3008HAL_GPIO_WritePin(CS_I2C_SPI_GPIO_Port, CS_I2C_SPI_Pin, GPIO_PIN_SET);// Extract and return the ADC value from the received datauint16_t adcValue = ((rxData & 0x03) << 8) | rxData;return adcValue;}void Error_Handler(void){/* USER CODE BEGIN Error_Handler_Debug *//* User can add his own implementation to report the HAL error return state */__disable_irq();while (1){}}#ifdef USE_FULL_ASSERT/*** @brief Reports the name of the source file and the source line number* where the assert_param error has occurred.* @param file: pointer to the source file name* @param line: assert_param error line source number* @retval None*/void assert_failed(uint8_t *file, uint32_t line){/* USER CODE BEGIN 6 *//* User can add his own implementation to report the file name and line number,ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) *//* USER CODE END 6 */}#endif /* USE_FULL_ASSERT */Issue Description:When I run the code, I constantly receive an ADC value of 1023, which is the maximum value.Even when I adjust the potentiometer to change the input voltage, the ADC value remains stuck at 1023.Troubleshooting Attempts:I've checked my hardware connections, including the SPI wiring, power supply, and ground connections. Everything seems fine.I've reviewed the SPI configuration settings in my code and ensured they match the MCP3008 requirements.I verified that I'm reading from channel 0 of the MCP3008 and made sure the potentiometer is connected to the correct channel.Request for Assistance:I can't seem to identify the root cause of this issue. I suspect there might be a problem with the SPI communication or the MCP3008 configuration. I would greatly appreciate any suggestions, or tips from the community to help me troubleshoot and resolve this problem.

author: langwadt
author_rank: Super Contributor
timestamp: September 09, 2023, 08:26:04 pm
content: with a quick look at the datasheet I thinkCode:txData = 0x01; // Start bittxData = (channel << 4) | 0x80; // Single-ended mode, channel selectiontxData = 0x00; // Dummy data to clock in resultsshould be something likeCode:txData = 0x80 | 0x40 | (channel << 3); // Start bit,Single-ended mode, channel selectiontxData = 0x00; // Dummy data to clock in resultstxData = 0x00; // Dummy data to clock in results

author: BlogRahul
author_rank: Regular Contributor
timestamp: September 09, 2023, 08:53:44 pm
content: Quote from: langwadt on September 09, 2023, 08:26:04 pmI made a changes and now I consistently getting an ADC value of 287. I am not know why this is happening ?

author: peter-h
author_rank: Super Contributor
timestamp: September 10, 2023, 08:32:58 am
content: Have you enabled the master clock to the SPI(s)?I had only a quick look at the DS but have used the MCP3550 extensively. Check stuff like- implementing Tsample wait correctly (the MCP3550 is about 60ms and the way it indicates conversion complete is messy when using SPI, so I just wait for 80ms)- the right SPI data/clock phase (4 combinations IIRC, and more than one works when per DS it should not)- bit order and byte order- respect minimum CS timings (code you find on github if often dud because somebody hacked it on a 16MHz AVR or whatever and never checked timings with a scope) and use a proper delay function (in asm) to achieve minimum delays in tens of ns resolution

author: DavidAlfa
author_rank: Super Contributor
timestamp: September 10, 2023, 08:42:01 am
content: It's HAL, should be done automatically.It would not receive anything otherwise, no flag would be set...

author: dietert1
author_rank: Super Contributor
timestamp: September 10, 2023, 09:15:49 am
content: One could look at the SPI interface signals using a scope to check what is really happening. And check it against datasheet Fig. 6-1.Many things can be wrong, like SPI clock frequency or polarity, active edge configuration, bit order, number of bits, chip select. Or some other setup problem, like MCU pin or clock configuration. The UART also needs to be tested before you can trust it ("Hello world.")Regards, Dieter

author: peter-h
author_rank: Super Contributor
timestamp: September 10, 2023, 11:24:27 am
content: IMHO any SPI stuff needsscope verification against the DS, otherwise it can be working marginally.I have a lot of notes from my project where various combinations of clock/data/phase etc "work" but probably marginally.

author: BlogRahul
author_rank: Regular Contributor
timestamp: September 11, 2023, 02:47:38 pm
content: I don't have scope to check signals. How can we troubleshoot this issue without specialized equipment ?

author: DavidAlfa
author_rank: Super Contributor
timestamp: September 11, 2023, 02:59:40 pm
content: From the datasheet! Waveforms at pages 21 and 22.Quote from:https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/ProductDocuments/DataSheets/MCP3004-MCP3008-Data-Sheet-DS20001295.pdfWhich matches STM32 CPOL / CPHA = 0 and CPOL / CPHA = 1:This is SPI mode 0 (CPOL / CPHA = 0), so it's OK:Code:hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;HSE xtal is 8MHz, right?This seems to run the 407 core at 168MHz, APB1 at 42MHz and APB2 at 84MHz.Code:RCC_OscInitStruct.HSEState = RCC_HSE_ON;RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;RCC_OscInitStruct.PLL.PLLM = 8;RCC_OscInitStruct.PLL.PLLN = 336;RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;RCC_OscInitStruct.PLL.PLLQ = 7;...RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;But your SPI prescaler is set to 2! SPI1 runs on APB2, so you're running SPI at 42MHz!Code:hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;It seems you forgot to read the MCP3008 SPI frequency limits in the datasheet:Code:VDD fSCLK5V 3.6MHz2.7V 1.35MHzCheck the SPI clock in CubeMX config.Set it to the highest, 256, (328KHz), it's a good starting value.Don't get obsessed with the speed, you may increase the frequency later, when it's already working.64 will give you the Fmax at 2.7VDD (1.31MHz).32 does 2.62MHz (OK if VDD=5V).16 will be too much in any case (5.6MHz)When talking about VDD, I refer to the MCP3008 VDD, not the STM32's.

author: peter-h
author_rank: Super Contributor
timestamp: September 11, 2023, 03:35:58 pm
content: QuoteYou better not be developing a serious commercial product then

author: DavidAlfa
author_rank: Super Contributor
timestamp: September 11, 2023, 03:42:59 pm
content: Quote from: peter-h on September 11, 2023, 03:35:58 pmHe never said so?.He's clearly a beginner, at least in digital design (Otherwise no experienced engineer would miss the AC parameters in the datasheet), just trying to interface the MCP3008, might be for training, DIY, whatever!

author: dietert1
author_rank: Super Contributor
timestamp: September 11, 2023, 04:46:43 pm
content: Better get a scope as soon as possible. My first one (a Hameg 203) came after working six weeks in my home towns greens - during school holidays. I still remember how bad it was. It didn't even have a trigger but supported some kind of sync method.Nowadays 100 bucks buys you a reasonable scope. Or borrow one.Regards, Dieter

author: CountChocula
author_rank: Unknown
timestamp: September 11, 2023, 05:08:07 pm
content: Did you verify that the potentiometer is actually changing the voltage on your ADC's input pin (you can do that with a multimeter, no need for a scope)?