用DMA来自动控制PWM的输出(音频输出,交直流转换)

一、前提分析

举例:一首歌所包含的音阶有高有低,而按照某种编曲的顺序排列也就对应了不同的频率(五线谱:1234567 对应的音阶各不相同)所以频率可以理解为它的源头。频率的来源又可由PWM来控制故而一首歌所包含的频率序列很长,若每次都交给cpu来做不能做到节省资源,故引出如下技术方案。

二、 DMA和PWM之间如何相关联

控制PWM输出一般采用TIM的OC(定时器的输出比较),初始化完成后一般通过改变TIMx->CCRx寄存器的值来改变占空比,而DMA搬运方向中有存储器到外设这种,因此外设的地址可设为TIMx->CCRx的地址,存储器的地址根据 一、前提分析 中的思想初始化一个含有频率序列的数组,存储器的基地址即为此数组。

cpp 复制代码
U32 SRC_Buffer [256];
//这个频率序列的初始化就需要根据相应场景来进行,这里重在编程思想的记录
void DMA_Configuration( void )
{
  DMA_InitTypeDef DMA_InitStructure;
  
  RCC_AHBPeriphClockCmd( RCC_AHBPeriph_DMA1, ENABLE );  // dma1时钟使能
    
  DMA_DeInit( DMA1_Channel5 );   // DMA复位
  DMA_StructInit( &DMA_InitStructure );   // DMA缺省的参数
    
  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) &TIM1->CCR3; //TIM1的通道3
  DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t) SRC_Buffer;  //内存地址
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //dma传输方向,单向
  DMA_InitStructure.DMA_BufferSize = sizeof( SRC_Buffer )/4; //此处根据实际情况调整
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //设置DMA的外设递增模式,一个外设
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;  //设置DMA的内存递增模式,
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //外设数据字长
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;   //内存数据字长
  //循环模式开启,Buffer写满后,自动回到初始地址开始传输
  DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;  //设置DMA的传输模式
  DMA_InitStructure.DMA_Priority = DMA_Priority_High; //设置DMA的优先级别
  DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;  //设置DMA的2个memory中的变量互相访问
  DMA_Init( DMA1_Channel5, &DMA_InitStructure );
  
  DMA_ClearFlag( DMA1_IT_TC5 );
  DMA_ITConfig( DMA1_Channel5, DMA_IT_TC, ENABLE );
  
  DMA_Cmd( DMA1_Channel5, ENABLE );
}

三、如何控制每次DMA传输之间的间隔以拟合音轨或正弦波

这里可以将DMA与定时器绑定从而控制PWM流,触发条件为定时器计数溢出,由于定时器计数溢出事件与DMA功能绑定,故间隔取决于定时器的频率和预装载值。

cpp 复制代码
void Tim1_Configuration( void )
{
  INT16U TIM_Prescaler, TIM_Period;
  INT32U utemp;
  TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
  TIM_OCInitTypeDef TIM_OCInitStructure;
  
  RCC_APB2PeriphClockCmd( RCC_APB2Periph_TIM1, ENABLE );
  
  TIM_DeInit( TIM1 );

  
  TIM_Prescaler = xxx;//不同芯片及不同场景此处取值不同,故此处由测试得出
 
  
  
  TIM_Period = xxx;               // 不同芯片及不同场景此处取值不同,故此处由测试得出
    
  TIM_TimeBaseStructure.TIM_Period = TIM_Period - 1;
  TIM_TimeBaseStructure.TIM_Prescaler = TIM_Prescaler - 1;
  TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
  TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;               
  TIM_TimeBaseInit( TIM1, &TIM_TimeBaseStructure );
  TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
  TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; // 使能输出比较状态
  TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Disable; // 失能输出比较N状态
  TIM_OCInitStructure.TIM_Pulse = 72;
  TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
  TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_Low;          
  TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset;
  TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Reset;
  TIM_OC3Init( TIM1, &TIM_OCInitStructure );
  TIM_OC3PreloadConfig( TIM1, TIM_OCPreload_Enable );   // 使能TIMx在CCR3上的预装载寄存器
    
 
  TIM_DMACmd( TIM1, TIM_DMA_Update, ENABLE );        //这里是最重要的一步


  TIM_Cmd( TIM1, ENABLE );      // 定时器开始运行
    
  // 这条语句必须要有!!!
  TIM_CtrlPWMOutputs( TIM1, ENABLE ); 
}

最后说明,上述代码中虽开启了很多中断,但实际未参与功能的实现,留作预留接口便于更深层次的逻辑扩展。

相关推荐
MARIN_shen22 分钟前
Marin说PCB之POC电路layout设计仿真案例---06
网络·单片机·嵌入式硬件·硬件工程·pcb工艺
荒古前23 分钟前
龟兔赛跑 PTA
c语言·算法
Asa31938 分钟前
STM32-按键扫描配置
stm32·单片机·嵌入式硬件
Eric.Lee202143 分钟前
moviepy将图片序列制作成视频并加载字幕 - python 实现
开发语言·python·音视频·moviepy·字幕视频合成·图像制作为视频
却道天凉_好个秋1 小时前
音视频学习(二十四):hls协议
音视频·hls
南城花随雪。1 小时前
单片机:实现驱动超声波(附带源码)
单片机·嵌入式硬件
嵌入式科普1 小时前
十三、从0开始卷出一个新项目之瑞萨RZN2L串口DMA接收不定长
c语言·stm32·瑞萨·e2studio·rzn2l
小林熬夜学编程3 小时前
【Linux网络编程】第十四弹---构建功能丰富的HTTP服务器:从状态码处理到服务函数扩展
linux·运维·服务器·c语言·网络·c++·http
Jackey_Song_Odd3 小时前
C语言 单向链表反转问题
c语言·数据结构·算法·链表
A懿轩A4 小时前
C/C++ 数据结构与算法【数组】 数组详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·考研·算法·数组