今天一天都被自闭在配置PB4的PWM+DMA模式,重映射真的很难受,尤其是应用到高频率0.847MHZ的场景下,不过,通过今天配置,让我对DMA了解了很多,不敢独享,故拿出来分享,欢迎大家纠错。
有几点需要注意:
1. PB4重映射到TIM3-CH1:下面三句必不可少,且顺序不能改变。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE); //使能GPIO外设和AFIO复用功能模块时钟
GPIO_PinRemapConfig(GPIO_Remap_SWJ_NoJTRST, ENABLE); //失能PB4——>JNTRST主要功能,这样子才能重映射到TIM3-CH1,注意顺序,先失能JNTRST再重映射
GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); //Timer3部分重映射 TIM3_CH1->PB4
2. PWM1模式和PWM2模式:
PWM模式1:在向上计数时,一旦TIMx_CNT < TIMx_CCR1时通道1为有效电平,否则为无效电平;在 向下计数时,一旦TIMx_CNT > TIMx_CCR1时通道1为无效电平,否则为有效电平。
PWM模式2:在向上计数时,一旦TIMx_CNT < TIMx_CCR1时通道1为无效电平,否则为有效电平;在向下计数时,一旦TIMx_CNT>TIMx_CCR1时通道1为有效电平,否则为无效电平
有效电平的设置为:TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //设置输出有效极性
3. 外设基地址别设置错了
比如这里我需要把TIM3-CH1作为外设,故外设基地址就设置为:
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&TIM3->CCR1); //DMA外设TIM3-CH1对应的基地址,“&”符号别忘记了
4. TIM-DMA中断设置
TIM_DMACmd(TIM3, TIM_DMA_Update, ENABLE); //使能定时器3的TIM_DMA_Update更新中断
这里一开始我并没有选择TIM_DMA_Update,而是选择的TIM_DMA_CC1,后面出来的PWM波形会出现异常现象,没有发PWM波时,会时而高电平时而低电平,很不稳定,故而选择TIM_DMA_Update,这里又可以牵扯出下面5和6两个需要注意点
5. TIM_DMACmd(TIM3, TIM_DMA_CC1, ENABLE);中间这个TIM_DMA_CC1如何选择?
TIM_DMA_CC1是和TIM的通道口对应的,如:PB4为TIM3-CH1,对应通道1,则为TIM_DMA_CC1,若为TIM3-CH2,则为TIM_DMA_CC2
6. TIM_DMACmd(TIM3, TIM_DMA_Update, ENABLE);这里我需要TIM_DMA_Update时,DMA通道口能不能随意设置?
答案:不能!
我一开始就是根据这个图将DMA通道口设置为通道6,而且只使能TIM_DMA_Update ,即:
TIM_DMACmd(TIM3, TIM_DMA_Update, ENABLE);
后面Debug发现,会卡在
while(!DMA_GetFlagStatus(DMA1_FLAG_TC3)); //等待传输完成
这句话里,找了好久好久的Bug,从网上也找了很久的解决办法,最后解决了问题,因为假如我设置为DMA的通道6,那么就应该用
TIM_DMACmd(TIM3, TIM_DMA_CC1, ENABLE);
而不能用TIM_DMA_Update,否则就会卡死。而如果必须要用TIM_DMA_Update,就应该将DMA通道口修改为通道3,由上表可知,只有通道三才有TIM3的“TIM_UP”这个功能,这才解决了问题
7. DMA_GetFlagStatus(DMA1_FLAG_TC3),这里的DMA1_FLAG_TC3该根据什么设置?
这个是根据你设的DMA通道口来设置的,比如我使用的是DMA通道3,那么就用DMA1_FLAG_TC3,类似于这种不知道各个参数的含义的,建议查看参考手册或者百度DMA配置参数解释。
最后,附上代码,仅供参考:
初始化配置函数:
void Timer3_PWM_DMA_Init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE); //使能GPIO外设和AFIO复用功能模块时钟
GPIO_PinRemapConfig(GPIO_Remap_SWJ_NoJTRST, ENABLE); //失能PB4——>JNTRST主要功能,这样子才能重映射到TIM3-CH1,注意顺序,先失能JNTRST再重映射
GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); //Timer3部分重映射 TIM3_CH1->PB4
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //使能定时器3时钟
TIM_TimeBaseStructure.TIM_Period = 85-1;
TIM_TimeBaseStructure.TIM_Prescaler = 0;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //PWM1模式
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //设置输出有效极性
TIM_OC1Init(TIM3, &TIM_OCInitStructure);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //使能DMA1时钟
DMA_DeInit(DMA1_Channel3); //使能DMA1的通道3
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&TIM3->CCR1); //DMA外设TIM3-CH1对应的基地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)LED_BYTE_Buffer; //内存基地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //设置传输方向:从内存到外设的传输方向
DMA_InitStructure.DMA_BufferSize = 24; //内存缓存器buffer的大小
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //设置传输数据时候外设地址是否递增,这里设置不递增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //设置传输数据时候内存地址是否递增,这里设置递增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //外设传输数据宽度设置,每次传输16位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //内存传输数据宽度设置,每次传输16位
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //工作在正常缓存模式
DMA_InitStructure.DMA_Priority = DMA_Priority_Low; //DM通道拥有低优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //失能内存到内存的传输方式,即非内存到内存传输
DMA_Init(DMA1_Channel3, &DMA_InitStructure); //初始化DMA1通道3
TIM_DMACmd(TIM3, TIM_DMA_Update, ENABLE); //使能定时器3的TIM_DMA_Update更新中断
}
发送函数里:
DMA_SetCurrDataCounter(DMA1_Channel3, buffersize); //先设置传输数据长度,再有下一步使能
DMA_Cmd(DMA1_Channel3, ENABLE); //打开DMA通道3
TIM_Cmd(TIM3, ENABLE); //打开TIM3
while(!DMA_GetFlagStatus(DMA1_FLAG_TC3)); //等待传输完成
TIM_Cmd(TIM3, DISABLE); //及时关闭
DMA_Cmd(DMA1_Channel3, DISABLE);
DMA_ClearFlag(DMA1_FLAG_TC3); //清除传输完成标志位