运用 DMA 功能实现高级定时器和 ADC 的同步触发采样

运用 DMA 功能实现高级定时器和 ADC 的同步触发采样_第1张图片

 前言

在做 BLDC 电机控制时,需要 ADC 的采样时刻和定时器产生的 PWM 波形相配合,才能获取准确的采样值, 本文介绍了 CW32F030 系列芯片通过运用 DMA 功能实现高级定时器和 ADC 的同步触发采样的功能。

PWM 输出实现

1.1 输出端口的配置

根据 GPIO 复用功能分配表(完整表格请参阅 CW32F030 用户手册中表 9-2 GPIO 复用功能分配表),选取期 望输出互补 PWM 波形的引脚,如本例中 PA8、PA9、PA10、PB13、PB14、PB15,如下表所示:

运用 DMA 功能实现高级定时器和 ADC 的同步触发采样_第2张图片

 PA8 和 PB13 组成一对互补输出通道 CH1,PA9 和 PB14 组成一对互补输出通道 CH2,PA10 和 PB15 组成一 对互补输出 CH3。

步骤如下:

1. 将相关的 GPIO 设置为输出;

2. 将 GPIO 配置为 ATIM 的比较输出复用功能。

代码如下:

void PWM_GPIOConfig(void)
{
/* PA8 PB13 CH1A CH1B
 PA9 PB14 CH2A CH2B
 PA10 PB15 CH3A CH3B
*/
GPIO_InitTypeDef GPIO_InitStruct;
__RCC_GPIOA_CLK_ENABLE();   // 使能 GPIO 的时钟,以便配置 GPIO 的寄存器
__RCC_GPIOB_CLK_ENABLE();
GPIO_InitStruct.IT = GPIO_IT_NONE;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pins = GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10;
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
GPIO_Init(CW_GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pins = GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_Init(CW_GPIOB, &GPIO_InitStruct);
PA08_AFx_ATIMCH1A();   // GPIO 端口功能复用
PA09_AFx_ATIMCH2A();
PA10_AFx_ATIMCH3A();
PB13_AFx_ATIMCH1B();
PB14_AFx_ATIMCH2B();
PB15_AFx_ATIMCH3B();
}

1.2 ATIM 的配置

ATIM 包含一个 16 位的计数器 / 定时器和 7 个比较单元。7 个比较单元中,有六个具有捕获功能,并且这 6 个捕获 / 比较单元可以成对使用,组成互补输出的功能。

本文以产生一个驱动 BLDC 电机所需的 20kHz 的三路互补输出的 PWM 波形为例,选取 ATIM 的时基信号为 PCLK。

本例中 PCLK 为 64MHz,并通过 ATIM 的预分频器进行 16 分频后,以 4MHz 频率进行计数。

为方便设定 ADC 的采样时间,ATIM 采用中央对齐模式计数,设置 ATIM 的自动重载寄存器(ARR)为 100, 则 ATIM 的将先从 0 累加至 99,再从 100 递减至 1,故计数周期为 2 倍的 ARR 寄存器的值,即 PWM 的频率 为 20kHz。

通过设置 ATIM 的控制寄存器(CR)的 COMP 位为 1,使得 PWM 以互补的方式输出,CH1A 和 CH1B 的脉宽 由通道 1比较 /捕获寄存器 A(CH1CCRA)决定,CH1B的输出脉宽不再由通道 1比较 /捕获寄存器 B(CH1CCRB) 决定,CH1CCRB 仍可用于设定 CH1B 比较匹配的值。CH2A 和 CH2B,CH3A 和 CH3B 与之类似。

在设置输出 PWM 互补输出时,可以对互补通道加入死区时间,由死区时间寄存器(DTR)控制。 ATIM 配置输出 3 对互补带死区的 PWM 波形,详细配置代码如下:

void ATIM_Config(void)
{
ATIM_InitTypeDef ATIM_InitStruct;
ATIM_OCInitTypeDef ATIM_OCInitStruct;
__RCC_ATIM_CLK_ENABLE();   // 开启 ATIM 的配置时钟,以便配置寄存器
ATIM_InitStruct.BufferState = DISABLE;   // 不使能缓存寄存器
ATIM_InitStruct.ClockSelect = ATIM_CLOCK_PCLK;   // 选择 PCLK 时钟计数
ATIM_InitStruct.CounterAlignedMode = ATIM_COUNT_MODE_CENTER_ALIGN; // 中心对齐
ATIM_InitStruct.CounterDirection = ATIM_COUNTING_UP;  // 向上计数;中心对齐方式下此项无效
ATIM_InitStruct.CounterOPMode = ATIM_OP_MODE_REPETITIVE;   // 连续运行模式
ATIM_InitStruct.OverFlowMask = ENABLE;   // 重复计数器上溢出屏蔽
ATIM_InitStruct.Prescaler = ATIM_Prescaler_DIV16;   // 16 分频 , 计数时钟 4MHz
ATIM_InitStruct.ReloadValue = 100;   // 从 0 加到 ARR-1, 再从 ARR 减到 1(20kHz)
ATIM_InitStruct.RepetitionCounter = 0;   // 重复周期 0
ATIM_InitStruct.UnderFlowMask = ENABLE;   // 重复计数下溢出屏蔽
ATIM_Init(&ATIM_InitStruct);   // 配置 ATIM 的基础配置
ATIM_OCInitStruct.BufferState = DISABLE;   // 比较输出缓冲不使能
ATIM_OCInitStruct.OCDMAState = DISABLE;   // 比较匹配时不触发 DMA 功能
ATIM_OCInitStruct.OCInterruptSelect = ATIM_OC_IT_NONE;   // 比较匹配时不产生中断
ATIM_OCInitStruct.OCInterruptState = DISABLE;   // 比较匹配时中断不使能
ATIM_OCInitStruct.OCMode = ATIM_OCMODE_PWM2;   // 比较输出采用 PWM2 模式
ATIM_OCInitStruct.OCPolarity = ATIM_OCPOLARITY_NONINVERT;   // 输出电平不反相
ATIM_OC1AInit(&ATIM_OCInitStruct);   // 配置 CH1、CH2、CH3 输出比较
ATIM_OC2AInit(&ATIM_OCInitStruct);
ATIM_OC3AInit(&ATIM_OCInitStruct);
ATIM_OC1BInit(&ATIM_OCInitStruct);
ATIM_OC2BInit(&ATIM_OCInitStruct);
ATIM_OC3BInit(&ATIM_OCInitStruct);
ATIM_SetCompare1A(ATIM_InitStruct.ReloadValue * 50/100);   // 通道 1 输出占空比 50% 的 PWM
ATIM_SetCompare2A(ATIM_InitStruct.ReloadValue * 25/100);   // 通道 2 输出占空比 75% 的 PWM
ATIM_SetCompare3A(ATIM_InitStruct.ReloadValue * 75/100);   // 通道 3 输出占空比 25% 的 PWM
ATIM_PWMOutputConfig(OCREFA_TYPE_SINGLE, OUTPUT_TYPE_COMP, 2);   // 互补输出,插入
死区,当前 4MHz 时钟,1us 死区,死区计算见用户手册
ATIM_CtrlPWMOutputs(ENABLE);   // 开启 PWM 输出
ATIM_Cmd(ENABLE);   // 开启 ATIM
}

产生的 PWM 波形如下:

运用 DMA 功能实现高级定时器和 ADC 的同步触发采样_第3张图片

1.3 ADC 的配置

在做 BLDC 电机控制时,需要通过 ADC 采集母线电压、母线电流、相电压、相电流、反相电动势等的变化, 因此需要 ADC 能对多个模拟量进行转换。CW32F030 的片上 ADC 具有 13 个外部输入通道,可以满足对采样 数量的需求。 

1.3.1 序列采样

当需求的采样通道小于等于 4 路时,可以通过 ADC 的序列采样模式实现,并且可以通过 ATIM 的通道 1~3 比 较 / 捕获寄存器 B 中任意一个寄存器设定 ADC 的采样时刻,这些操作都可以由硬件自动完成,减轻了 CPU 的工作量。

以采样 AIN0~AIN3 这 4 路输入为例,设定采样时刻为 ATIM 计数达到 ARR 时,其参考代码如下:

static void ADC_Config(void)
{
ADC_InitTypeDef ADC_InitStruct;
ADC_SerialChTypeDef ADC_SerialChStruct;
ATIM_OCInitTypeDef ATIM_OCInitStruct;
__RCC_ADC_CLK_ENABLE();
ADC_InitStruct.ADC_AccEn = ADC_AccDisable;   // ADC 累加功能不开启
ADC_InitStruct.ADC_Align = ADC_AlignRight;   // 采样结果右对齐,即结果存于 bit11~bit0
ADC_InitStruct.ADC_ClkDiv = ADC_Clk_Div4;   // ADC 的 采 样 时 钟 为 PCLK 的 4 分 频, 即
ADCCLK=16MHz
ADC_InitStruct.ADC_DMAEn = ADC_DmaDisable;   // DMA 触发 ADC 不使能
ADC_InitStruct.ADC_InBufEn = ADC_BufDisable;   // 高速采样,ADC 内部电压跟随器不使能
ADC_InitStruct.ADC_OpMode = ADC_SerialChScanMode;   // 序列扫描转换模式
ADC_InitStruct.ADC_SampleTime = ADC_SampTime5Clk;   // 设置为 5 个采样周期,须根据实际
情况调整
ADC_InitStruct.ADC_TsEn = ADC_TsDisable;   // 内部温度传感器禁止
ADC_InitStruct.ADC_VrefSel = ADC_Vref_VDDA;   // 采样参考电压选择为 VDDA
ADC_SerialChStruct.ADC_InitStruct = ADC_InitStruct;   // 序列采样的基本配置项
ADC_SerialChStruct.ADC_Sqr0Chmux = ADC_SqrCh0;   // 序列 0 配置为外部输入 0
ADC_SerialChStruct.ADC_Sqr1Chmux = ADC_SqrCh1;   // 序列 1 配置为外部输入 1
ADC_SerialChStruct.ADC_Sqr2Chmux = ADC_SqrCh2;   // 序列 2 配置为外部输入 2
ADC_SerialChStruct.ADC_Sqr3Chmux = ADC_SqrCh3;   // 序列 3 配置为外部输入 3
ADC_SerialChStruct.ADC_SqrEns = ADC_SqrEns03;   // 使能序列 0~3
ADC_SerialChScanModeCfg(&ADC_SerialChStruct);   // ADC 序列扫描模式初始化
ADC_ExtTrigCfg(ADC_TRIG_ATIM, ENABLE);   // 配置外部触发 ADC 的启动的触发源为 ATIM

/* 修改 ATIM 的部分配置,使能 ATIM 的 CH1B 通道发生比较匹配时,触发 ADC 开始转换 */
ATIM_ADCTriggerConfig(ATIM_ADC_TRIGGER_CH1B, ENABLE);   // 允许 ATIM 的 CH1B 在比较匹
配时触发 ADC 启动
// 修改 ATIM 的 CH1CR 寄存器,使用 CH1B 的在下计数比较匹配时触发 ADC,注:0~ARR-1 为上计数
方向
// ARR~1 为下计数方向
REGBITS_MODIFY(CW_ATIM->CH1CR, ATIM_CH1CR_CISB_Msk, ATIM_OC_IT_DOWN_COUNTER <<
ATIM_CH1CR_CISB_Pos);
ATIM_SetCompare1B(CW_ATIM->ARR);   // 设置 CH1B 比较匹配的值为 ARR
ADC_Enable();
}

上述方法完全由硬件实现,不需要 CPU 和中断的参与,执行效率非常高,不足的地方是采样通道限制为 4 路。

1.3.2 DMA 扩展采样

如果需要对超过 4 路的模拟量进行采样,则需要结合 DMA 的功能,以实现较少的 CPU 参与。其思路如下:

1. ADC 配置为单通道单次转换,完成转换后硬件触发 DMA;

2. DMA 的 CH1 用于将 ADC 的转换结果传输到 RAM 中,本例中将采样 6 个 ADC 通道,因此传输次数 CNT 为 6,源地址固定为 ADC 的 RESULT0 寄存器,目的地址需要递增;

3. DMA 的 CH2 用于更改 ADC 的采样通道,当 ADC 转换完成后,从 RAM 中取 ADC 的通道配置参数,自 动配置 ADC 的寄存器值,因此源地址为 RAM,地址递增,目的地址为 ADC 的通道控制寄存器;

4. DMA 的 CH3 用于再次启动 ADC,因为 ADC 配置为单次转换,当转换完成后,ADC 自动停止转换,所 以需要通过 DMA 向 ADC 的转换启动寄存器置位,以再次启动 ADC 转换;

5. DMA 的 CH1 传输完成后,ADC 的 6 路转换也完成了,并且转换结果也被传输到 RAM,可通过 CH1 的 传输完成中断,将 DMA 的参数重新配置,就实现了多路 ADC 的循环采样;

6. 通过 ATIM 的比较通道 4,去触发 DMA 的 CH4,向 ADC 的转换启动寄存器置位,启动 ADC。 其参考代码如下:

• ADC 的配置:

static void ADC_Config(void)
{
ADC_InitTypeDef ADC_InitStruct;
__RCC_ADC_CLK_ENABLE();
ADC_InitStruct.ADC_AccEn = ADC_AccDisable;   // ADC 累加功能不开启
ADC_InitStruct.ADC_Align = ADC_AlignRight;   // 采样结果右对齐,即结果存于 bit11~bit0
ADC_InitStruct.ADC_ClkDiv = ADC_Clk_Div4;   // ADC 的 采 样 时 钟 为 PCLK 的 4 分 频, 即
ADCCLK=16MHz
ADC_InitStruct.ADC_DMAEn = ADC_DmaEnable;   // ADC 转换完成触发 DMA
ADC_InitStruct.ADC_InBufEn = ADC_BufDisable;   // 高速采样,ADC 内部电压跟随器不使能
ADC_InitStruct.ADC_OpMode = ADC_SingleChOneMode;   // 单次单通道采样模式
ADC_InitStruct.ADC_SampleTime = ADC_SampTime5Clk;   // 设置为 5 个采样周期,须根据实际
情况调整
ADC_InitStruct.ADC_TsEn = ADC_TsDisable;   // 内部温度传感器禁止
ADC_InitStruct.ADC_VrefSel = ADC_Vref_VDDA;   // 采样参考电压选择为 VDDA
ADC_Init(&ADC_InitStruct);   // 初始化 ADC 的配置
CW_ADC->CR1_f.CHMUX = 0;   // 初始采样通道为 AIN0
ADC_Enable();   // 启用 ADC
}

ADC 配置为单次单通道采样,采样完成后可触发 DMA。

• DMA 的配置:

uint16_t ADC_ResultBuff[6] = {0};   // 用于存放 6 路采样结果
uint8_t ADC_CR1Array[5] = {0x81, 0x82, 0x83, 0x84, 0x85};   // 通过更改 ADC 的 CR1 寄存器实现 ADC 通
道自动切换
uint8_t ADC_Start = 0x01;   // ADC_START 寄存器的配置值
void DMA_Config(void)
{
// 使用 4 路 DMA 通道:CH1、CH2、CH3、 CH4
// CH1 将 ADC 单次单通道的采样结果传入 RAM(ADC_ResultBuff[6])
// CH2 将 ADC 的 CR1 寄存器的配置值从 RAM(ADC_CR1Array)传入寄存器
// CH3 将 ADC 的 START 寄存器的配置值从 RAM(ADC_Start) 传入寄存器
// CH1、CH2、CH3 由 ADC 硬件触发
// CH4 由 ATIM 硬件触发 , 启动 ADC
DMA_InitTypeDef DMA_InitStruct = {0};
__RCC_DMA_CLK_ENABLE();
DMA_InitStruct.DMA_DstAddress = (uint32_t)&ADC_ResultBuff[0];   // 目标地址
DMA_InitStruct.DMA_DstInc = DMA_DstAddress_Increase;   // 目标地址递增
DMA_InitStruct.DMA_Mode = DMA_MODE_BLOCK;   // BLOCK 传输模式
DMA_InitStruct.DMA_SrcAddress = (uint32_t)&CW_ADC->RESULT0;  // 源地址: ADC 的结果寄存器
DMA_InitStruct.DMA_SrcInc = DMA_SrcAddress_Fix;   // 源地址固定
DMA_InitStruct.DMA_TransferCnt = 0x6;   // DMA 传输次数
DMA_InitStruct.DMA_TransferWidth = DMA_TRANSFER_WIDTH_16BIT;   // 数据位宽 16bit
DMA_InitStruct.HardTrigSource = DMA_HardTrig_ADC_TRANSCOMPLETE;  // ADC 转换完成硬触发
DMA_InitStruct.TrigMode = DMA_HardTrig;   // 硬触发模式
DMA_Init(CW_DMACHANNEL1, &DMA_InitStruct);
DMA_Cmd(CW_DMACHANNEL1, ENABLE);
DMA_InitStruct.DMA_DstAddress = (uint32_t)&CW_ADC->CR1;    // 目标地址
DMA_InitStruct.DMA_DstInc = DMA_DstAddress_Fix;    // 目标地址固定
DMA_InitStruct.DMA_Mode = DMA_MODE_BLOCK;    // BLOCK 传输模式
DMA_InitStruct.DMA_SrcAddress = (uint32_t)&ADC_CR1Array[0];    // 源地址
DMA_InitStruct.DMA_SrcInc = DMA_SrcAddress_Increase;   // 源地址递增
DMA_InitStruct.DMA_TransferCnt = 0x5;    // DMA 传输次数
DMA_InitStruct.DMA_TransferWidth = DMA_TRANSFER_WIDTH_8BIT;    // 数据位宽 8bit
DMA_InitStruct.HardTrigSource = DMA_HardTrig_ADC_TRANSCOMPLETE;  // ADC 转换完成硬触发
DMA_InitStruct.TrigMode = DMA_HardTrig;    // 硬触发模式
DMA_Init(CW_DMACHANNEL2, &DMA_InitStruct);
DMA_Cmd(CW_DMACHANNEL2, ENABLE);
DMA_InitStruct.DMA_DstAddress = (uint32_t)&CW_ADC->START;   // 目标地址
DMA_InitStruct.DMA_DstInc = DMA_DstAddress_Fix;   // 目标地址固定
DMA_InitStruct.DMA_Mode = DMA_MODE_BLOCK;   // BLOCK 传输模式
DMA_InitStruct.DMA_SrcAddress = (uint32_t)&ADC_Start;   // 源地址
DMA_InitStruct.DMA_SrcInc = DMA_SrcAddress_Fix;    // 源地址固定
DMA_InitStruct.DMA_TransferCnt = 0x5;    // DMA 传输次数
DMA_InitStruct.DMA_TransferWidth = DMA_TRANSFER_WIDTH_8BIT;    // 数据位宽 8bit
DMA_InitStruct.HardTrigSource = DMA_HardTrig_ADC_TRANSCOMPLETE;  // ADC 转换完成硬触发
DMA_InitStruct.TrigMode = DMA_HardTrig;    // 硬触发模式
DMA_Init(CW_DMACHANNEL3, &DMA_InitStruct);
DMA_Cmd(CW_DMACHANNEL3, ENABLE);
DMA_InitStruct.DMA_DstAddress = (uint32_t)&CW_ADC->START;   // 目标地址
DMA_InitStruct.DMA_DstInc = DMA_DstAddress_Fix;    // 目标地址固定
DMA_InitStruct.DMA_Mode = DMA_MODE_BLOCK;   // BLOCK 传输模式
DMA_InitStruct.DMA_SrcAddress = (uint32_t)&ADC_Start;   // 源地址
DMA_InitStruct.DMA_SrcInc = DMA_SrcAddress_Fix;    // 源地址固定
DMA_InitStruct.DMA_TransferCnt = 0x1;    // DMA 传输次数
DMA_InitStruct.DMA_TransferWidth = DMA_TRANSFER_WIDTH_8BIT;    // 数据位宽 8bit
DMA_InitStruct.HardTrigSource = DMA_HardTrig_ATIM_CH1A2A3A4;   // ATIM 硬件触发
DMA_InitStruct.TrigMode = DMA_HardTrig;    // 硬触发模式
DMA_Init(CW_DMACHANNEL4, &DMA_InitStruct);
DMA_Cmd(CW_DMACHANNEL4, ENABLE);
DMA_ITConfig(CW_DMACHANNEL1, DMA_IT_TC, ENABLE);   // DMA 的 CH1 传输完成后触发中断
}

• ATIM 需要在之前的配置上增加通道 4 的设置,增加的代码如下:

{
// 配置 0C4 比较触发 DMA
CW_ATIM->MSCR_f.CCDS = 1;   // 允许 ATIM 打开 DMA 总开关
// 比较通道 4 在向下计数时,比较匹配时触发 DMA
ATIM_OC4Init(ENABLE, ATIM_OC_IT_DOWN_COUNTER, ENABLE, DISABLE, DISABLE);
ATIM_SetCompare4(ATIM_InitStruct.ReloadValue);   // 触发时刻
}

• DMA 的 CH1 通道传输完成后,将触发中断服务程序,其中断服务程序的代码如下:

void DMACH1_IRQHandlerCallBack(void)
{
CW_DMA->ICR_f.TC1 = 0x00;   // 清中断标志
CW_ATIM->ICR = 0x00;   // 清 ATIM 的中断标志
CW_ADC->ICR = 0x00;    // 清 ADC 的中断标志
CW_ADC->CR1 = 0x80;    // 恢复 ADC 的采样通道为 AIN0
//------------------------------------------------
// DMA.CH1 : 还原为初始设置,即将 ADC 的采样结果存储地址恢复到首位置
CW_DMACHANNEL1->CNT = 0x10006;    // 传 6 个
CW_DMACHANNEL1->SRCADDR = (uint32_t)(&CW_ADC->RESULT0);
CW_DMACHANNEL1->DSTADDR = (uint32_t)(&ADC_ResultBuff[0]);
CW_DMACHANNEL1->CSR_f.EN = 1;
//------------------------------------------------
// DMA.CH2 : 将下次采样的通道恢复到 AIN1,重置传输次数为 5
CW_DMACHANNEL2->CNT = 0x10005;    // 传 5 个
CW_DMACHANNEL2->SRCADDR = (uint32_t)(&ADC_CR1Array[0]);
CW_DMACHANNEL2->DSTADDR = (uint32_t)(&CW_ADC->CR1);
CW_DMACHANNEL2->CSR_f.EN = 1;
//------------------------------------------------
// DMA.CH3 : 重置传输次数为 5
CW_DMACHANNEL3->CNT = 0x10005;    // 传 5 个
CW_DMACHANNEL3->SRCADDR = (uint32_t)(&ADC_Start);
CW_DMACHANNEL3->DSTADDR = (uint32_t)(&CW_ADC->START);
CW_DMACHANNEL3->CSR_f.EN = 1;
//------------------------------------------------
// DMA.CH4 : 重置传输次数,等待 ATIM 比较匹配后再次触发 DMA 传输
CW_DMACHANNEL4->CNT = 0x10001;    // 传 1 个
CW_DMACHANNEL4->SRCADDR = (uint32_t)(&ADC_Start);
CW_DMACHANNEL4->DSTADDR = (uint32_t)(&CW_ADC->START);
CW_DMACHANNEL4->CSR_f.EN = 1;
}

这种方法可以实现多于 4个模拟通道的采样,采样结果自动保存在内存中,并且仅在最后一个通道采样完成后, 进入一次中断服务程序对 DMA 的配置进行复位,所以 CPU 的开销是比较小的,而且可以通过 ATIM 的比较 通道 4 灵活设置采样时机。

你可能感兴趣的:(单片机,嵌入式硬件)