nichtgedacht
User
Programmieren mit CubeMX
Programmieren mit CubeMX
Hi
Ich möchte mal zeigen wie einfach man zur Initialisierung eines Peripheriebausteins mit CubeMX kommt.
Ziel: Pulse für WS2812 LEDs erzeugen lassen und dabei die MCU möglichst wenig belasten, also die DMA benutzen.
Erst mal sehen welche Pins für den Zweck noch frei sind und welche man vielleicht besser für Anderes aufhebt.
Zunächst wird in der Pinout Ansicht der Pin PB0 als TIM3_CH3 festgepinnt. Das Pinning kann später wieder gelöst werden.
Unter Peripherals wird TIM3 aufgeklappt und bei Channel3 "PWM Generation CH3" ausgewählt.
Dann zur Configuration Ansicht wechseln. Hier werden für alle Features Buttons gezeigt.
Wir klicken auf TIM3.
Im Tab "Parameter Settings" wird als einzige Änderung bei "Counter Period" 89 eingetragen.
Erklärung: Wir wollen 800 kHz Impulse, der Timer wird mit 72 MHz geclockt, das Auto Reload Register (ARR) aka "Counter Period"
wird mit Teiler - 1 geladen, kein Vorteiler wegen besserer Auflösung. Rechnung: 72 * 10^6 / 800 * 10^3 = 90 also 89 ins ARR
Im Tab "DMA Settings" wird ein DMA Kanal hinzugefügt und konfiguriert. Der Request soll jeweils vom abgelaufen Puls von TIM3_CH3 kommen.
Transportrichtung ist "Memory To Peripheral" und Priorität "high". Im Speicher sollen die Werte byteweise angeordnet sein, die Peripherie
hat Word lange Register. Im Speicher soll der Pointer inkrementiert werden in der Peripherie nicht. Die Werte aus dem Speicher sollen im Kreis herum
geladen werden.
Im Tab "GPIO Settings" wird der einzige für TIM3 zuständige Pin PB0 auf Speed "high" eingestellt und GPIO Mode auf "Alternate Function Opne Drain"
Erklärung: Die WS2812 LEDs werden mit 5V betrieben. Der STM32 kann von sich aus nur 3,3V an seinen Ausgängen liefern (Betriebsspannung).
Da die Pins aber 5V tolerant sind soll ein externer Pullup Widerstand verwendet werden.
Nachdem alles eingerichtet ist kann unter Project -> "Generate Code" neuer Code im vorhandenen Projekt erzeugt werden.
Ergebnis:
tim.c
dma.c
tim.h und dma.h mit den Prototypen und Variablen für die handles werden natürlich auch erzeugt resp. modifiziert.
Wie man sieht, wurde der bereits vorhandenen Code für Timer2 sauber ergänzt und der Code für die erstmalig benutzte
DMA komplett neu erzeugt.
In main.c oder sonstwo brauch man dann nur noch sowas ähnliches:
Nachdem HAL_TIM_PWM_Start_DMA einmal aufgerufen wurde braucht man nur noch die LED Farben nach belieben ändern und danach die Bits der RGB Werte mit led_trans_vals()
in Counter Werte für das Pulslängen Register (CCR) umzuwandeln. Die DMA liefert sie fortlaufend zum passenden Zeitpunkt an das Register. Die Werte für die 1 und 0 Pulse
ergeben sich wie folgt: 0 Puls 400 nS, 1 Puls 800 nS ==> 72 * 10^6 * 400 * 10^-9 = 30 ...
Natürlich ist das noch suboptimal und es gibt speichersparende Lösungen. Z.B. kann man nur 2 Werte in aCCValue_Buffer[] aufbewahren und den ersten
Wert interruptgesteuert in der Callback Function "XferHalfCpltCallback" ersetzen und den zweiten Wert in "XferCpltCallback".
Allerdings muss man dann für weniger Speicherverbrauch mit mehr verteilter Rechenzeit bezahlen. So hat man das Rechnen konzentriert an einer Stelle.
Gruß
Dieter
Programmieren mit CubeMX
Hi
Ich möchte mal zeigen wie einfach man zur Initialisierung eines Peripheriebausteins mit CubeMX kommt.
Ziel: Pulse für WS2812 LEDs erzeugen lassen und dabei die MCU möglichst wenig belasten, also die DMA benutzen.
Erst mal sehen welche Pins für den Zweck noch frei sind und welche man vielleicht besser für Anderes aufhebt.
Zunächst wird in der Pinout Ansicht der Pin PB0 als TIM3_CH3 festgepinnt. Das Pinning kann später wieder gelöst werden.
Unter Peripherals wird TIM3 aufgeklappt und bei Channel3 "PWM Generation CH3" ausgewählt.
Dann zur Configuration Ansicht wechseln. Hier werden für alle Features Buttons gezeigt.
Wir klicken auf TIM3.
Im Tab "Parameter Settings" wird als einzige Änderung bei "Counter Period" 89 eingetragen.
Erklärung: Wir wollen 800 kHz Impulse, der Timer wird mit 72 MHz geclockt, das Auto Reload Register (ARR) aka "Counter Period"
wird mit Teiler - 1 geladen, kein Vorteiler wegen besserer Auflösung. Rechnung: 72 * 10^6 / 800 * 10^3 = 90 also 89 ins ARR
Im Tab "DMA Settings" wird ein DMA Kanal hinzugefügt und konfiguriert. Der Request soll jeweils vom abgelaufen Puls von TIM3_CH3 kommen.
Transportrichtung ist "Memory To Peripheral" und Priorität "high". Im Speicher sollen die Werte byteweise angeordnet sein, die Peripherie
hat Word lange Register. Im Speicher soll der Pointer inkrementiert werden in der Peripherie nicht. Die Werte aus dem Speicher sollen im Kreis herum
geladen werden.
Im Tab "GPIO Settings" wird der einzige für TIM3 zuständige Pin PB0 auf Speed "high" eingestellt und GPIO Mode auf "Alternate Function Opne Drain"
Erklärung: Die WS2812 LEDs werden mit 5V betrieben. Der STM32 kann von sich aus nur 3,3V an seinen Ausgängen liefern (Betriebsspannung).
Da die Pins aber 5V tolerant sind soll ein externer Pullup Widerstand verwendet werden.
Nachdem alles eingerichtet ist kann unter Project -> "Generate Code" neuer Code im vorhandenen Projekt erzeugt werden.
Ergebnis:
tim.c
Code:
#include "tim.h"
#include "dma.h"
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
TIM_HandleTypeDef htim2;
TIM_HandleTypeDef htim3;
DMA_HandleTypeDef hdma_tim3_ch3;
/* TIM2 init function */
void MX_TIM2_Init(void)
{
TIM_MasterConfigTypeDef sMasterConfig;
TIM_OC_InitTypeDef sConfigOC;
htim2.Instance = TIM2;
htim2.Init.Prescaler = 17;
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 19999;
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
if (HAL_TIM_PWM_Init(&htim2) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 0;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_2) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_3) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_4) != HAL_OK)
{
Error_Handler();
}
HAL_TIM_MspPostInit(&htim2);
}
/* TIM3 init function */
void MX_TIM3_Init(void)
{
TIM_MasterConfigTypeDef sMasterConfig;
TIM_OC_InitTypeDef sConfigOC;
htim3.Instance = TIM3;
htim3.Init.Prescaler = 0;
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 89;
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
if (HAL_TIM_PWM_Init(&htim3) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 0;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
if (HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_3) != HAL_OK)
{
Error_Handler();
}
HAL_TIM_MspPostInit(&htim3);
}
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef* tim_pwmHandle)
{
if(tim_pwmHandle->Instance==TIM2)
{
/* USER CODE BEGIN TIM2_MspInit 0 */
/* USER CODE END TIM2_MspInit 0 */
/* Peripheral clock enable */
__HAL_RCC_TIM2_CLK_ENABLE();
/* Peripheral interrupt init */
HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIM2_IRQn);
/* USER CODE BEGIN TIM2_MspInit 1 */
/* USER CODE END TIM2_MspInit 1 */
}
else if(tim_pwmHandle->Instance==TIM3)
{
/* USER CODE BEGIN TIM3_MspInit 0 */
/* USER CODE END TIM3_MspInit 0 */
/* Peripheral clock enable */
__HAL_RCC_TIM3_CLK_ENABLE();
/* Peripheral DMA init*/
hdma_tim3_ch3.Instance = DMA1_Channel2;
hdma_tim3_ch3.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_tim3_ch3.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_tim3_ch3.Init.MemInc = DMA_MINC_ENABLE;
hdma_tim3_ch3.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
hdma_tim3_ch3.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_tim3_ch3.Init.Mode = DMA_CIRCULAR;
hdma_tim3_ch3.Init.Priority = DMA_PRIORITY_HIGH;
if (HAL_DMA_Init(&hdma_tim3_ch3) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(tim_pwmHandle,hdma[TIM_DMA_ID_CC3],hdma_tim3_ch3);
/* USER CODE BEGIN TIM3_MspInit 1 */
/* USER CODE END TIM3_MspInit 1 */
}
}
void HAL_TIM_MspPostInit(TIM_HandleTypeDef* timHandle)
{
GPIO_InitTypeDef GPIO_InitStruct;
if(timHandle->Instance==TIM2)
{
/* USER CODE BEGIN TIM2_MspPostInit 0 */
/* USER CODE END TIM2_MspPostInit 0 */
/**TIM2 GPIO Configuration
PA0-WKUP ------> TIM2_CH1
PA1 ------> TIM2_CH2
PA2 ------> TIM2_CH3
PA3 ------> TIM2_CH4
*/
GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* USER CODE BEGIN TIM2_MspPostInit 1 */
/* USER CODE END TIM2_MspPostInit 1 */
}
else if(timHandle->Instance==TIM3)
{
/* USER CODE BEGIN TIM3_MspPostInit 0 */
/* USER CODE END TIM3_MspPostInit 0 */
/**TIM3 GPIO Configuration
PB0 ------> TIM3_CH3
*/
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* USER CODE BEGIN TIM3_MspPostInit 1 */
/* USER CODE END TIM3_MspPostInit 1 */
}
}
void HAL_TIM_PWM_MspDeInit(TIM_HandleTypeDef* tim_pwmHandle)
{
if(tim_pwmHandle->Instance==TIM2)
{
/* USER CODE BEGIN TIM2_MspDeInit 0 */
/* USER CODE END TIM2_MspDeInit 0 */
/* Peripheral clock disable */
__HAL_RCC_TIM2_CLK_DISABLE();
/* Peripheral interrupt Deinit*/
HAL_NVIC_DisableIRQ(TIM2_IRQn);
/* USER CODE BEGIN TIM2_MspDeInit 1 */
/* USER CODE END TIM2_MspDeInit 1 */
}
else if(tim_pwmHandle->Instance==TIM3)
{
/* USER CODE BEGIN TIM3_MspDeInit 0 */
/* USER CODE END TIM3_MspDeInit 0 */
/* Peripheral clock disable */
__HAL_RCC_TIM3_CLK_DISABLE();
/* Peripheral DMA DeInit*/
HAL_DMA_DeInit(tim_pwmHandle->hdma[TIM_DMA_ID_CC3]);
/* USER CODE BEGIN TIM3_MspDeInit 1 */
/* USER CODE END TIM3_MspDeInit 1 */
}
}
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/**
* @}
*/
/**
* @}
*/
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
dma.c
Code:
#include "dma.h"
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/*----------------------------------------------------------------------------*/
/* Configure DMA */
/*----------------------------------------------------------------------------*/
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/**
* Enable DMA controller clock
*/
void MX_DMA_Init(void)
{
/* DMA controller clock enable */
__HAL_RCC_DMA1_CLK_ENABLE();
/* DMA interrupt init */
/* DMA1_Channel2_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA1_Channel2_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA1_Channel2_IRQn);
}
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/**
* @}
*/
/**
* @}
*/
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
tim.h und dma.h mit den Prototypen und Variablen für die handles werden natürlich auch erzeugt resp. modifiziert.
Wie man sieht, wurde der bereits vorhandenen Code für Timer2 sauber ergänzt und der Code für die erstmalig benutzte
DMA komplett neu erzeugt.
In main.c oder sonstwo brauch man dann nur noch sowas ähnliches:
Code:
#define NR_LEDS 12
enum { G, R, B };
void led_trans_vals(void);
uint8_t aCCValue_Buffer[392]; // max 16 LEDs (24 * 12 + 8)
uint8_t led_val[16][3];
led_val[0][G] = 0;
led_val[0][R] = 64;
led_val[0][B] = 0;
...
led_val[NR_LEDS - 1][G] = 0;
led_val[NR_LEDS - 1][R] = 64;
led_val[NR_LEDS - 1][B] = 0;
led_trans_vals();
HAL_TIM_PWM_Start_DMA(&htim3, TIM_CHANNEL_3, (uint32_t *) aCCValue_Buffer, NR_LEDS * 24 + 8);
void led_trans_vals(void)
{
uint8_t i, j;
uint16_t k;
k = 0;
for (i = 0; i < NR_LEDS; i++) // each LED
{
for (j = 0; j < 3; j++) // 3 colors
{
aCCValue_Buffer[k++] = led_val[i][j] & 1 << 7 ? 60 : 30; // each bit
aCCValue_Buffer[k++] = led_val[i][j] & 1 << 6 ? 60 : 30;
aCCValue_Buffer[k++] = led_val[i][j] & 1 << 5 ? 60 : 30;
aCCValue_Buffer[k++] = led_val[i][j] & 1 << 4 ? 60 : 30;
aCCValue_Buffer[k++] = led_val[i][j] & 1 << 3 ? 60 : 30;
aCCValue_Buffer[k++] = led_val[i][j] & 1 << 2 ? 60 : 30;
aCCValue_Buffer[k++] = led_val[i][j] & 1 << 1 ? 60 : 30;
aCCValue_Buffer[k++] = led_val[i][j] & 1 ? 60 : 30;
}
}
for (i = 0; i < 8; i++)
{
aCCValue_Buffer[k++] = 0; // pulse pause
}
}
Nachdem HAL_TIM_PWM_Start_DMA einmal aufgerufen wurde braucht man nur noch die LED Farben nach belieben ändern und danach die Bits der RGB Werte mit led_trans_vals()
in Counter Werte für das Pulslängen Register (CCR) umzuwandeln. Die DMA liefert sie fortlaufend zum passenden Zeitpunkt an das Register. Die Werte für die 1 und 0 Pulse
ergeben sich wie folgt: 0 Puls 400 nS, 1 Puls 800 nS ==> 72 * 10^6 * 400 * 10^-9 = 30 ...
Natürlich ist das noch suboptimal und es gibt speichersparende Lösungen. Z.B. kann man nur 2 Werte in aCCValue_Buffer[] aufbewahren und den ersten
Wert interruptgesteuert in der Callback Function "XferHalfCpltCallback" ersetzen und den zweiten Wert in "XferCpltCallback".
Allerdings muss man dann für weniger Speicherverbrauch mit mehr verteilter Rechenzeit bezahlen. So hat man das Rechnen konzentriert an einer Stelle.
Gruß
Dieter