Created Wednesday 06 September 2023
Download example from this video (check attachments below!)
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"
- Open "Pinout & Configuration" tab → select "SYS"
- Select "Serial Wire" under "Debug" drop-down menu
- Now, microcontroller clocks are set for maximum performance (72 MHz max) and swdio is enabled for STLink
Configuring 16 GPIO outputs, Timer1 and DMA
- In "Pinout view" click on each pin from PB0 to PB15 and select "GPIO_Output"
- Open "Pinout & Configuration" tab → under "Timers" select "TIM1"
- Under "TIM1 Mode and Configuration" tab set "Clock Source" to "Internal Clock"
- Under "Configuration" tab select "Parameter Settings" tab
- Set "Prescaler" to 71 and "Counter Period" to 9. This will make timer fire at frequency 100 kHz (72MHz / 72 / 10 = 0.1 MHz)
- Set "Trigger Event Selection" to "Update Event"
- Open "DMA Settings" tab, press "Add", click on "Direction" and set it to "Memory To Peripheral", set "Mode" to "Circular"
- Generate the code
Writing LED animation using 16 emulated PWM channels
- #include for "string.h", "stdlib.h", "math.h"
- #define GPIO_BUFFER_SIZE 256
- three arrays uint16_t gpioBuffer[GPIO_BUFFER_SIZE]; uint16_t gpioBufferSafe[GPIO_BUFFER_SIZE]; uint16_t gpioBufferSafe2[GPIO_BUFFER_SIZE];
- #define LED_COUNT 16
- uint8_t currentLed=0; float currentLedFloat=0;
- write HAL_TIM_Base_Start_DMA under "USER CODE BEGIN 2", ctrl-click it to open source code
- copy-paste HAL_TIM_Base_Start_DMA to main.c and rename it to MY_HAL_TIM_Base_Start_DMA
- replace htim->hdma[TIM_DMA_ID_UPDATE]->XferCpltCallback = ... with myDmaCpltCallback;
- replace htim->hdma[TIM_DMA_ID_UPDATE]->XferHalfCpltCallback = .. with myDmaHalfCpltCallback;
- replace (uint32_t)&htim->Instance->ARR with (uint32_t)&GPIOB->ODR
- prepare two empty functions: void myDmaHalfCpltCallback( struct DMA_HandleTypeDef * hdma) and void myDmaCpltCallback( struct DMA_HandleTypeDef * hdma)
- write MY_HAL_TIM_Base_Start_DMA(&htim1, (uint32_t *)&gpioBuffer[0], GPIO_BUFFER_SIZE); under "USER CODE BEGIN 2"
- in while(1) loop, write an animation code
- memset((uint8_t *)&gpioBufferSafe,(char)0x00,sizeof(gpioBufferSafe)); - cleared first buffer
- for (int k=-8;k<LED_COUNT+8;k++) {} - for all LEDs + adjacent LEDs we will:
- uint8_t attenuation=1*abs(k-currentLed); - calculate attenuation depending on currentLed index
- uint16_t brightness=256>>attenuation; - brightness of adjacent LEDs
- int k2=k; - index variable to wrap negative and >=LED_COUNT values
- if (k2>=LED_COUNT) k2-=LED_COUNT;
- if (k2<0) k2+=LED_COUNT;
- PWM emulation code: for (int i=0;i<GPIO_BUFFER_SIZE;i++) { gpioBufferSafe[i] |= (i<brightness)<<k2; }
- inverting image: for (int i=0;i<GPIO_BUFFER_SIZE;i++) gpioBufferSafe[i]=~gpioBufferSafe[i];
- memcpy(&gpioBufferSafe2,&gpioBufferSafe,sizeof(gpioBuffer)); - copying FIRST buffer to SECOND buffer
- moving current LED: currentLedFloat+=0.075;
- currentLed=round(currentLedFloat);
- if (currentLed==LED_COUNT) currentLedFloat=0;
- under myDmaCpltCallback paste memcpy(&gpioBuffer,&gpioBufferSafe2,sizeof(gpioBuffer));
- Triple buffering works pretty well, you can try to make more effects!