使用DMA方式获取多通道ADC数据的完整指南

[复制链接]
keer_zu 发表于 2025-8-27 01:14 | 显示全部楼层 |阅读模式

使用DMA方式获取多通道ADC数据的完整指南

当使用DMA方式采样多个ADC通道时,理解如何组织和访问数据至关重要。以下是详细的解释和代码示例。

数据存储方式

当使用DMA进行多通道ADC采样时,数据在内存中的存储方式取决于ADC的配置:

  1. 连续模式:ADC按顺序转换所有通道,DMA按顺序存储结果
  2. 扫描模式:ADC按预设顺序转换多个通道,DMA按相同顺序存储结果

对于N个通道的采样,DMA缓冲区中的数据排列通常为:

[通道0值, 通道1值, 通道2值, ..., 通道N-1值, 通道0值, 通道1值, ...]

配置步骤

1. 初始化ADC和DMA

首先,需要在CubeMX或代码中正确配置ADC和DMA:

// ADC初始化配置
hadc1.Instance = ADC1;
hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE;        // 启用扫描模式
hadc1.Init.ContinuousConvMode = ENABLE;           // 连续转换模式
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.NbrOfDiscConversion = 0;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 7;                   // 7个通道
hadc1.Init.DMAContinuousRequests = ENABLE;        // 启用DMA连续请求

// DMA初始化配置
hdma_adc1.Instance = DMA1_Channel1;
hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;
hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
hdma_adc1.Init.Mode = DMA_CIRCULAR;               // 循环模式
hdma_adc1.Init.Priority = DMA_PRIORITY_HIGH;

2. 配置ADC通道和规则序列

// 配置ADC通道和它们在规则序列中的顺序
ADC_ChannelConfTypeDef sConfig = {0};

// 通道0
sConfig.Channel = ADC_CHANNEL_0;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_28CYCLES;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);

// 通道1
sConfig.Channel = ADC_CHANNEL_1;
sConfig.Rank = ADC_REGULAR_RANK_2;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);

// 重复上述过程,配置所有7个通道...
// 通道6
sConfig.Channel = ADC_CHANNEL_6;
sConfig.Rank = ADC_REGULAR_RANK_7;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);

3. 定义数据缓冲区并启动DMA传输

#define ADC_CHANNELS 7
#define ADC_SAMPLES 100 // 每个通道采样100次

// 缓冲区大小 = 通道数 × 采样次数
uint16_t adc_buffer[ADC_CHANNELS * ADC_SAMPLES];

// 启动ADC DMA传输
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buffer, ADC_CHANNELS * ADC_SAMPLES);

访问特定通道的数据

DMA缓冲区中的数据按照转换顺序存储。要访问特定通道的数据,需要知道它在序列中的位置。

方法1:直接计算索引

// 获取第channel个通道的第sample次采样值
uint16_t get_channel_data(uint16_t* buffer, int channel, int sample) {
    // 计算索引:sample * 总通道数 + 通道序号
    return buffer[sample * ADC_CHANNELS + channel];
}

// 示例:获取通道3的第50次采样值
uint16_t value = get_channel_data(adc_buffer, 3, 50);

方法2:使用二维数组视角

虽然物理上是一维数组,但可以逻辑上视为二维数组:

// 将一维数组视为二维数组的实用函数
uint16_t get_adc_value(int channel, int sample) {
    return adc_buffer[sample * ADC_CHANNELS + channel];
}

// 打印所有通道的最新采样值
void print_latest_samples(void) {
    int latest_sample = ...; // 需要跟踪最新样本索引
  
    for (int ch = 0; ch < ADC_CHANNELS; ch++) {
        uint16_t value = get_adc_value(ch, latest_sample);
        float voltage = value * 3.3f / 4095.0f;
        printf("Channel %d: %d (%.2f V)\n", ch, value, voltage);
    }
}

方法3:使用结构体数组(更高级的技巧)

如果您希望数据组织更加直观,可以使用结构体:

// 定义包含所有通道数据的结构体
typedef struct {
    uint16_t ch0;
    uint16_t ch1;
    uint16_t ch2;
    uint16_t ch3;
    uint16_t ch4;
    uint16_t ch5;
    uint16_t ch6;
} adc_sample_t;

// 将DMA缓冲区重新解释为结构体数组
adc_sample_t* adc_samples = (adc_sample_t*)adc_buffer;

// 现在可以直接访问数据
uint16_t value = adc_samples[50].ch3; // 第50次采样中的通道3值

处理DMA传输完成中断

当使用循环DMA模式时,数据会不断被覆盖。您需要跟踪当前可用的数据位置:

volatile uint32_t dma_current_index = 0;
volatile uint32_t dma_last_index = 0;

// DMA传输完成回调函数
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) {
    // 获取当前DMA位置
    dma_current_index = ADC_SAMPLES * ADC_CHANNELS - __HAL_DMA_GET_COUNTER(hadc->DMA_Handle);
  
    // 处理从dma_last_index到dma_current_index之间的数据
    process_new_data(dma_last_index, dma_current_index);
  
    // 更新最后索引
    dma_last_index = dma_current_index;
  
    // 如果到达缓冲区末尾,回绕到开头
    if (dma_last_index >= ADC_SAMPLES * ADC_CHANNELS) {
        dma_last_index = 0;
    }
}

// 处理新数据的函数
void process_new_data(uint32_t start, uint32_t end) {
    for (uint32_t i = start; i < end; i += ADC_CHANNELS) {
        // 确保不越界
        if (i + ADC_CHANNELS > ADC_SAMPLES * ADC_CHANNELS) {
            break;
        }
      
        // 处理这一组采样(包含所有通道)
        for (int ch = 0; ch < ADC_CHANNELS; ch++) {
            uint16_t value = adc_buffer[i + ch];
            // 处理每个通道的值...
        }
    }
}

实用技巧和注意事项

  1. 缓冲区大小:确保缓冲区足够大,能容纳所有采样数据,避免数据被覆盖前未来得及处理。
  2. 数据对齐:如果使用结构体方法,确保结构体大小与DMA传输的数据对齐。
  3. ** volatile关键字**:在中断和主程序之间共享的变量(如索引)应使用volatile关键字。
  4. 超时处理:添加超时机制,防止因硬件故障导致程序卡死。
  5. 错误处理:检查ADC和DMA的错误标志,并实现相应的错误处理回调函数。
// ADC错误回调
void HAL_ADC_ErrorCallback(ADC_HandleTypeDef* hadc) {
    // 处理ADC错误
    printf("ADC error: 0x%08lX\n", hadc->ErrorCode);
}

// DMA错误回调
void HAL_DMA_ErrorCallback(DMA_HandleTypeDef* hdma) {
    // 处理DMA错误
    printf("DMA error\n");
}

通过以上方法,您可以有效地使用DMA方式采集多通道ADC数据,并轻松访问每个通道的采样值。这种方法大大提高了数据采集效率,减少了CPU开销。

您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:qq群:49734243 Email:zukeqiang@gmail.com

1481

主题

12924

帖子

55

粉丝
快速回复 在线客服 返回列表 返回顶部