Created Tuesday 22 August 2023
Creating project, configuring MCU clock and enabling SWDIO
- Open STM32CubeIDE
- Select "File" → "New" → "STM32 Project"
- In "Commercial Part Number" field type STM32F103C8T6
- From the MCUs/MPUs List select STM32F103C8T6 (shipped in plastic trays) or STM32F103C8T6TR (shipped in tapes on reels)
- Click "Next >" button
- Enter Project Name, e.g. "How to use FFT on STM32F103C8T6" → Click "Finish" button
- In "Pinout & Configuration" tab, open "System Core" → "RCC"
- Set "High Speed Clock (HSE)" to "Crystal/Ceramic Resonator"
- Open "Clock Configuration" tab
- Check that "Input frequency" is equal to resonator on STM32F103C8T6 board (usually 8MHz)
- Set "PLL Source Mux" to HSE
- Set "System Clock Mux" to PLLCLK
- Set PLLMul to "X 9"
- Set APB1 Prescaler to "/ 2"
- Now, microcontroller clocks are set for maximum performance (72 MHz max)
Adding 8 ADC channels
- Go to "Pinout & Configuration" → "Analog" → "ADC1"
- Set checkboxes for IN0, IN1, IN2, IN3, IN4, IN5, IN6, IN7 (eight checkboxes for eight channels)
- Open "Clock Configuration" tab, set "ADC prescaler" to "/6", so ADC frequency becomes 12 MHz (maximum is 14MHz, but I want to keep HCLK at 72MHz)
- Go back to "Pinout & Configuration" → "Analog" → "ADC1"
- Open "DMA Settings", add "ADC1" DMA Request
- Set "Mode" to "Circular"
- Open "Parameter Settings" → "ADC_Settings", set "Continuous Conversion" to "Enabled"
- In "ADC_Regular_ConversionMode", set "Number Of Conversion" to 8
- Eight "Rank" slots appear, set "Channel" for each Rank from 0 to 7 ("Channel 0", "Channel 1", ... , "Channel 7")
- Set "Sampling Time" for each slot to "7.5 Cycles" for each Rank
- Note: Default "Sampling Time" is "1.5 Cycles", we using "7.5 Cycles" for illustrative purposes, so all frequencies will be whole numbers
- Note: Tconv=7.5+12.5=20, and ADC Clock of 12MHz / 20 = 600 [kHz]. Then single channel sampling frequency is 600 [kHz] / 8 = 75 [kHz]
Adding 4 PWM outputs
- Go back to "Pinout & Configuration" → "Timers" → "TIM1"
- Set "Clock Source" to "Internal Clock" (TIM1 is on APB2 which is 72MHz as shown on "Clock Configuration" tab)
- Set "Prescaler" to 1439
- Set "Counter Period" to 9
- This will give timer counting frequency of 72MHz / (1439+1) / (9+1) = 72000000 / 14400 = 5000 [Hz] = 5 [kHz]
- Set "Channel1", "Channel2", "Channel3" and "Channel4" to "PWM Generation CHxxx" (CH1, CH2, CH3 and CH4)
- Scroll down to "PWM Generation Channel 1" in TIM1 "Parameter Settings"
- Set "Pulse (16bits value) to 2, 4, 6 and 8 for Channel 1,2,3 and 4.
- From menu select "Project" → "Generate Code"
Adding Code
- In main.c, under "USER CODE BEGIN PV" add
- #define ADC_BUFFER_SIZE 60
- #define ADC_CHANNELS 8
- #define DMA_ADC_BUFFER_SIZE (ADC_CHANNELS*ADC_BUFFER_SIZE)
- __IO uint16_t dmaAdcBuffer[DMA_ADC_BUFFER_SIZE];
- __IO uint16_t dmaAdcBufferSafe[DMA_ADC_BUFFER_SIZE];
- eight arrays __IO uint16_t safeArray0 [ADC_BUFFER_SIZE]; with index 0 to 7
- uint8_t myState=3; // waiting state variable
- // myState = 0 - trigger, need to take ADC snapshot
- // myState = 1 - first half of ADC buffer saved
- // myState = 2 - second half of ADC buffer saved
- // myState = 3 - buffer is de-interlaced to 8 separate arrays,
- // waiting next "0" trigger written by Cube Monitor
- Under "USER CODE BEGIN 2" add
- HAL_ADC_Start_DMA(&hadc1, (uint32_t *) &dmaAdcBuffer, DMA_ADC_BUFFER_SIZE);
- HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_1); for channels 1 to 4
- HAL_TIM_Base_Start(&htim1);
- Under "USER CODE BEGIN 4"
- void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc)
- void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
- if (myState==0) {memmove( (void *)&dmaAdcBufferSafe[0], (void *)&dmaAdcBuffer[0], sizeof(dmaAdcBuffer)/2); myState=1; }
- if (myState==1) {memmove( (void *)&dmaAdcBufferSafe[DMA_ADC_BUFFER_SIZE/2],(void *)&dmaAdcBuffer[DMA_ADC_BUFFER_SIZE/2],sizeof(dmaAdcBuffer)/2);myState=2;}
- Under "USER CODE BEGIN WHILE"
- if (myState==2) {... myState=3;}
- for (int k=0; k<ADC_CHANNELS;k++)
- for (int i=0; i<ADC_BUFFER_SIZE;i++)
- switch (k) {}
- case 0: safeArray0[i]=dmaAdcBufferSafe[i*ADC_CHANNELS+k]; break; for arrays 0 to 7
- Compile program
Adding Cube Monitor nodes
- Open STM32CubeMonitor, find three stripes / hamburger in up-right corner and press it to open menu → Import
- "Import nodes" windows select "Local" → STM32CubeMonitor_BasicFlow.json → press Import button
- Delete "show notification", "myChart" and "Clear Graphs" nodes
- Three stripes / hamurger → Import → Clipboard tab → "select a file to import" button → find and select cubearrays10.json → Import button
- Three stripes / hamurger → Import → Clipboard tab → "select a file to import" button → find and select cubetrigger10.json→ Import button
- Connect variables node upper output to "CubeArrays v1.0" function node input
- Double-click on "myVariables", and add ELF file for STM32CubeIDE FFT project
- Use "Expand Variable List" checkbox and filter field to find all elements of safeArrayXXX (0 to 7), and add them using "Select All" button. Add single myState variable.
- Double-click "CubeArrays v1.0" function node, select "On Message" tab, scroll down to const arrayNames=["arrayName1","arrayName2",...
- Replace "arrayName1", "arrayName2" and so on with safeArray0, safeArray1, ... , safeArray7
- Open "Setup" tab and set "Outputs" to 8
- Double-click Chart #1, set "OR points" to 60, set "Y-axis" min to 0 and max to 4096
- Repeat for Chart #2 to Chart #8
- Double-click CubeTrigger function, set watchVariableName to myState
- Set watchVariableWaitValue to 3
- Set watchVariableSetValue to 0
- Press "Deploy" button
- Press "Dashboard" button, in opened window press "Start monitoring"
cubearrays10.json and cubetrigger10.json files can be found in in nodes10.zip archive here.