关于STM32F4xx使用DMA+TIM3_PWM调试灯带WS2812过程记录
1、叙述
(1)叙述
最近调试灯带2812,尝试使用PA6引脚调试,以前没怎么用过,通过不断找资料学习,成功点亮灯带,但是这其中还是踩了不少坑,这个过程,以及过程中遇到的问题值得记录下来,引以为鉴。
(2)环境
MCU:STM32F407ZGT6 开发工具:STM32CubeIDE 1.7.0 HAL库版本:stm32cube_fw_f4_v1262
2、参考文档
本文主要参考以下文档,写的言简意赅,并且有相关配置,这里就不重复了。 DMA+PWM形式: https://blog.csdn.net/qq_17351161/article/details/107285588#comments_12806183 DMA+PWM形式(双缓冲模式): https://blog.csdn.net/qq_17351161/article/details/107346568
自我实现
实现代码
通过学习上述文字内容,自己用软件STM32CubeIDE重新,在F1和F4上都实现了一边,这里给实现的四个代码,需要的自行下载,都是STM32CubeIDE版本,共四种。 STM32F1上DMA+PWM形式(TIM2 ): https://download.csdn.net/download/qq_22146161/54793933 STM32F1上DMA+PWM形式(TIM2 TIM3双缓冲): https://download.csdn.net/download/qq_22146161/54794408 STM32F4上DMA+PWM形式(TIM3 ch1 ch2)PA6 PA7: https://download.csdn.net/download/qq_22146161/58667099 STM32F4上DMA+PWM形式(TIM3 h1 ch2双缓冲)PA6 PA7: https://download.csdn.net/download/qq_22146161/54824864
代码细节
以上述代码第三为例,也就是“STM32F4上DMA+PWM形式(TIM3 ch1 ch2)PA6 PA7”为例,代码包括三个主要部分 初始配置 代码在软件 STM32CubeIDE 配置完成后一部分会在文件“stm32f4xx_hal_msp.c”这部分包括PWM配置和端口引脚配置。 其中>>void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef* htim_pwm); 和函数>>void HAL_TIM_MspPostInit(TIM_HandleTypeDef* htim);非常重要
#include "main.h"
extern DMA_HandleTypeDef hdma_tim3_ch2;
extern DMA_HandleTypeDef hdma_tim3_ch1_trig;
void HAL_TIM_MspPostInit(TIM_HandleTypeDef *htim);
void HAL_MspInit(void)
{
__HAL_RCC_SYSCFG_CLK_ENABLE();
__HAL_RCC_PWR_CLK_ENABLE();
}
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef* htim_pwm)
{
if(htim_pwm->Instance==TIM3)
{
__HAL_RCC_TIM3_CLK_ENABLE();
hdma_tim3_ch2.Instance = DMA1_Stream5;
hdma_tim3_ch2.Init.Channel = DMA_CHANNEL_5;
hdma_tim3_ch2.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_tim3_ch2.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_tim3_ch2.Init.MemInc = DMA_MINC_ENABLE;
hdma_tim3_ch2.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
hdma_tim3_ch2.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
hdma_tim3_ch2.Init.Mode = DMA_CIRCULAR;
hdma_tim3_ch2.Init.Priority = DMA_PRIORITY_VERY_HIGH;
hdma_tim3_ch2.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
if (HAL_DMA_Init(&hdma_tim3_ch2) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(htim_pwm,hdma[TIM_DMA_ID_CC2],hdma_tim3_ch2);
hdma_tim3_ch1_trig.Instance = DMA1_Stream4;
hdma_tim3_ch1_trig.Init.Channel = DMA_CHANNEL_5;
hdma_tim3_ch1_trig.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_tim3_ch1_trig.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_tim3_ch1_trig.Init.MemInc = DMA_MINC_ENABLE;
hdma_tim3_ch1_trig.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
hdma_tim3_ch1_trig.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
hdma_tim3_ch1_trig.Init.Mode = DMA_CIRCULAR;
hdma_tim3_ch1_trig.Init.Priority = DMA_PRIORITY_VERY_HIGH;
hdma_tim3_ch1_trig.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
if (HAL_DMA_Init(&hdma_tim3_ch1_trig) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(htim_pwm,hdma[TIM_DMA_ID_CC1],hdma_tim3_ch1_trig);
__HAL_LINKDMA(htim_pwm,hdma[TIM_DMA_ID_TRIGGER],hdma_tim3_ch1_trig);
}
}
void HAL_TIM_MspPostInit(TIM_HandleTypeDef* htim)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(htim->Instance==TIM3)
{
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF2_TIM3;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
}
void HAL_TIM_PWM_MspDeInit(TIM_HandleTypeDef* htim_pwm)
{
if(htim_pwm->Instance==TIM3)
{
__HAL_RCC_TIM3_CLK_DISABLE();
HAL_DMA_DeInit(htim_pwm->hdma[TIM_DMA_ID_CC2]);
HAL_DMA_DeInit(htim_pwm->hdma[TIM_DMA_ID_CC1]);
HAL_DMA_DeInit(htim_pwm->hdma[TIM_DMA_ID_TRIGGER]);
}
}
功能代码实现 功能部分代码主要参考博客文章里,另外这里定时器初始化也需要注意
#include "main.h"
TIM_HandleTypeDef htim3;
DMA_HandleTypeDef hdma_tim3_ch2;
DMA_HandleTypeDef hdma_tim3_ch1_trig;
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_TIM3_Init(void);
#define ONE_PULSE (59)
#define ZERO_PULSE (29)
#define RESET_PULSE (48)
#define LED_NUMS (4)
#define LED_DATA_LEN (24)
#define WS2812_DATA_LEN (LED_NUMS*LED_DATA_LEN)
uint16_t static RGB_buffur[RESET_PULSE + WS2812_DATA_LEN] = { 0 };
void ws2812_set_RGB(uint8_t R, uint8_t G, uint8_t B, uint16_t num)
{
uint16_t* p = (RGB_buffur + RESET_PULSE) + (num * LED_DATA_LEN);
for (uint16_t i = 0;i < 8;i++)
{
p[i] = (G << i) & (0x80)?ONE_PULSE:ZERO_PULSE;
p[i + 8] = (R << i) & (0x80)?ONE_PULSE:ZERO_PULSE;
p[i + 16] = (B << i) & (0x80)?ONE_PULSE:ZERO_PULSE;
}
}
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
{
__HAL_TIM_SetCompare(htim, TIM_CHANNEL_1,0);
HAL_TIM_PWM_Stop_DMA(htim,TIM_CHANNEL_1);
__HAL_TIM_SetCompare(htim, TIM_CHANNEL_2,0);
HAL_TIM_PWM_Stop_DMA(htim,TIM_CHANNEL_2);
}
void ws2812_example(void)
{
ws2812_set_RGB(0x22, 0x22, 0x22, 0);
ws2812_set_RGB(0x22, 0x00, 0x00, 1);
ws2812_set_RGB(0x00, 0x22, 0x00, 2);
ws2812_set_RGB(0x00, 0x00, 0x22, 3);
HAL_TIM_PWM_Start_DMA(&htim3,TIM_CHANNEL_1,(uint32_t *)RGB_buffur,176);
HAL_TIM_PWM_Start_DMA(&htim3,TIM_CHANNEL_2,(uint32_t *)RGB_buffur,176);
HAL_Delay(500);
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_TIM3_Init();
while (1)
{
ws2812_example();
}
}
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
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 = 4;
RCC_OscInitStruct.PLL.PLLN = 168;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = 4;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
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_DIV2;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
static void MX_TIM3_Init(void)
{
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_OC_InitTypeDef sConfigOC = {0};
htim3.Instance = TIM3;
htim3.Init.Prescaler = 0;
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 105;
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 = 74;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
if (HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_2) != HAL_OK)
{
Error_Handler();
}
HAL_TIM_MspPostInit(&htim3);
}
static void MX_DMA_Init(void)
{
__HAL_RCC_DMA1_CLK_ENABLE();
HAL_NVIC_SetPriority(DMA1_Stream4_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA1_Stream4_IRQn);
HAL_NVIC_SetPriority(DMA1_Stream5_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA1_Stream5_IRQn);
}
static void MX_GPIO_Init(void)
{
__HAL_RCC_GPIOH_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
}
void Error_Handler(void)
{
__disable_irq();
while (1)
{
}
}
#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t *file, uint32_t line)
{
}
#endif
3、验证
如下图,是灯带效果图
还有示波器抓到的波形图
4、在F4上遇到的问题
如果细心的小伙伴们可能也注意到了 F4上没有使用TIM2,多次尝试到发文为止,还未解决,并挂在网上悬赏,链接如下,描述很清楚了。 https://ask.csdn.net/questions/7589282
5、调试细节-问题记录
(1)初始化顺序
在主函数内初始化顺序不要改变 在主函数内初始化顺序不要改变 在主函数内初始化顺序不要改变 重要的事情说三遍,在尝试移植的过程中,发现波形突然不受控制了。 DMA无法输出正常波形。 后来发现移植时候,虽然代码像下边那样些,但是实际上,在别处定时器3先初始化了,移植时很难找到这个毛病。 推导出代码顺序非常重要,有时候发现明明好好的代码在移植时,或者突然出问题,可能需要检查顺序。
MX_GPIO_Init();
MX_DMA_Init();
MX_TIM3_Init();
(2)上下拉问题
在F4中,PA6和PA7默认上下是不同的,PA6默认下拉,PA7默认上拉 可以在代码 GPIO_InitStruct.Pull = GPIO_PULLDOWN;更改为下拉,或者每个引脚单独设置。
void HAL_TIM_MspPostInit(TIM_HandleTypeDef* htim)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(htim->Instance==TIM3)
{
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLDOWN;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF2_TIM3;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
(3)验证2-后期逻辑仪抓取波形
后来上逻辑分析仪,那么可以通过抓取波形来验证输入代码对不对了,功能代码如下图,注意ws2812_set_RGB函数后面设置的数组,理论上逻辑仪出来的也应该对应,即ws2812这个灯珠以1.25us为一个周期,1/3方波认为是0,2/3方波认为是1。
这里一共4个灯那么,如下图是抓出来的,可以看到数据是一致的,另外上下两个波形差了点位置,说明CPU代码执行先后是时间有间隔的。
6、总结
这次确实也学到不少东西,希望我的前车之鉴,对你们有参考价值,也是自我记录与总结。
|