[ Prev ] [ Index ] [ Next ]

16ch PWM LED animation

Created Wednesday 06 September 2023


https://youtu.be/C_n9Q8KzUEc


Download example from this video (check attachments below!)


Creating project, configuring MCU clock and enabling SWDIO

  1. Open STM32CubeIDE
  2. Select "File" → "New" → "STM32 Project"
  3. In "Commercial Part Number" field type STM32F103C8T6
  4. From the MCUs/MPUs List select STM32F103C8T6 (shipped in plastic trays) or STM32F103C8T6TR (shipped in tapes on reels)
  5. Click "Next >" button
  6. Enter Project Name, e.g. "How to use FFT on STM32F103C8T6" → Click "Finish" button
  7. In "Pinout & Configuration" tab, open "System Core" → "RCC"
  8. Set "High Speed Clock (HSE)" to "Crystal/Ceramic Resonator"
  9. Open "Clock Configuration" tab
  10. Check that "Input frequency" is equal to resonator on STM32F103C8T6 board (usually 8MHz)
  11. Set "PLL Source Mux" to HSE
  12. Set "System Clock Mux" to PLLCLK
  13. Set PLLMul to "X 9"
  14. Set APB1 Prescaler to "/ 2"
  15. Open "Pinout & Configuration" tab → select "SYS"
  16. Select "Serial Wire" under "Debug" drop-down menu
  17. Now, microcontroller clocks are set for maximum performance (72 MHz max) and swdio is enabled for STLink

Configuring 16 GPIO outputs, Timer1 and DMA

  1. In "Pinout view" click on each pin from PB0 to PB15 and select "GPIO_Output"
  2. Open "Pinout & Configuration" tab → under "Timers" select "TIM1"
  3. Under "TIM1 Mode and Configuration" tab set "Clock Source" to "Internal Clock"
  4. Under "Configuration" tab select "Parameter Settings" tab
  5. Set "Prescaler" to 71 and "Counter Period" to 9. This will make timer fire at frequency 100 kHz (72MHz / 72 / 10 = 0.1 MHz)
  6. Set "Trigger Event Selection" to "Update Event"
  7. Open "DMA Settings" tab, press "Add", click on "Direction" and set it to "Memory To Peripheral", set "Mode" to "Circular"
  8. Generate the code

Writing LED animation using 16 emulated PWM channels

  1. #include for "string.h", "stdlib.h", "math.h"
  2. #define GPIO_BUFFER_SIZE 256
  3. three arrays uint16_t gpioBuffer[GPIO_BUFFER_SIZE]; uint16_t gpioBufferSafe[GPIO_BUFFER_SIZE]; uint16_t gpioBufferSafe2[GPIO_BUFFER_SIZE];
  4. #define LED_COUNT 16
  5. uint8_t currentLed=0; float currentLedFloat=0;
  6. write HAL_TIM_Base_Start_DMA under "USER CODE BEGIN 2", ctrl-click it to open source code
  7. copy-paste HAL_TIM_Base_Start_DMA to main.c and rename it to MY_HAL_TIM_Base_Start_DMA
  8. replace htim->hdma[TIM_DMA_ID_UPDATE]->XferCpltCallback = ... with myDmaCpltCallback;
  9. replace htim->hdma[TIM_DMA_ID_UPDATE]->XferHalfCpltCallback = .. with myDmaHalfCpltCallback;
  10. replace (uint32_t)&htim->Instance->ARR with (uint32_t)&GPIOB->ODR
  11. prepare two empty functions: void myDmaHalfCpltCallback( struct DMA_HandleTypeDef * hdma) and void myDmaCpltCallback( struct DMA_HandleTypeDef * hdma)
  12. write MY_HAL_TIM_Base_Start_DMA(&htim1, (uint32_t *)&gpioBuffer[0], GPIO_BUFFER_SIZE); under "USER CODE BEGIN 2"
  13. in while(1) loop, write an animation code
  14. memset((uint8_t *)&gpioBufferSafe,(char)0x00,sizeof(gpioBufferSafe)); - cleared first buffer
  15. for (int k=-8;k<LED_COUNT+8;k++) {} - for all LEDs + adjacent LEDs we will:
  16. uint8_t attenuation=1*abs(k-currentLed); - calculate attenuation depending on currentLed index
  17. uint16_t brightness=256>>attenuation; - brightness of adjacent LEDs
  18. int k2=k; - index variable to wrap negative and >=LED_COUNT values
  19. if (k2>=LED_COUNT) k2-=LED_COUNT;
  20. if (k2<0) k2+=LED_COUNT;
  21. PWM emulation code: for (int i=0;i<GPIO_BUFFER_SIZE;i++) { gpioBufferSafe[i] |= (i<brightness)<<k2; }
  22. inverting image: for (int i=0;i<GPIO_BUFFER_SIZE;i++) gpioBufferSafe[i]=~gpioBufferSafe[i];
  23. memcpy(&gpioBufferSafe2,&gpioBufferSafe,sizeof(gpioBuffer)); - copying FIRST buffer to SECOND buffer
  24. moving current LED: currentLedFloat+=0.075;
  25. currentLed=round(currentLedFloat);
  26. if (currentLed==LED_COUNT) currentLedFloat=0;
  27. under myDmaCpltCallback paste memcpy(&gpioBuffer,&gpioBufferSafe2,sizeof(gpioBuffer));
  28. Triple buffering works pretty well, you can try to make more effects!