STM32使用LL库ADC+DMA配置过程中遇到的一些问题

STM32使用LL库ADC+DMA配置过程中遇到的一些问题

使用LL库配置ADC+DMA,使用了AIN3,AIN5,两个ADC通道不连续,使用扫描模式,连续转换,使用DMA通道1,使用了CUBE_MX配置,这个不难。
ADC配置界面
ADC配置界面1
DMA配置界面
DMA配置界面
接下来是代码:
ADC初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
void MX_ADC1_Init(void)
{
  LL_ADC_REG_InitTypeDef ADC_REG_InitStruct = {0};
  LL_ADC_InitTypeDef ADC_InitStruct = {0};

  LL_GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* Peripheral clock enable */
  LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_ADC);
 
  LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOA);
  /**ADC1 GPIO Configuration  
  PA3   ------> ADC1_IN3
  */
  GPIO_InitStruct.Pin = ADC_IN3_Pin;
  GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;
  GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
  LL_GPIO_Init(SHORT_IN_GPIO_Port, &GPIO_InitStruct);
    /*
    PA5 -----> ADC1_IN5
    */
    GPIO_InitStruct.Pin = ADC_IN5_Pin;
    GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
    LL_GPIO_Init(SHORT_IN_GPIO_Port, &GPIO_InitStruct);
   
    /* ADC1 DMA Init */
 
  /* ADC1 Init */
  LL_DMA_SetPeriphRequest(DMA1, LL_DMA_CHANNEL_1, LL_DMAMUX_REQ_ADC1);

  LL_DMA_SetDataTransferDirection(DMA1, LL_DMA_CHANNEL_1, LL_DMA_DIRECTION_PERIPH_TO_MEMORY);

  LL_DMA_SetChannelPriorityLevel(DMA1, LL_DMA_CHANNEL_1, LL_DMA_PRIORITY_LOW);

  LL_DMA_SetMode(DMA1, LL_DMA_CHANNEL_1, LL_DMA_MODE_CIRCULAR);

  LL_DMA_SetPeriphIncMode(DMA1, LL_DMA_CHANNEL_1, LL_DMA_PERIPH_NOINCREMENT);

  LL_DMA_SetMemoryIncMode(DMA1, LL_DMA_CHANNEL_1, LL_DMA_MEMORY_INCREMENT);

  LL_DMA_SetPeriphSize(DMA1, LL_DMA_CHANNEL_1, LL_DMA_PDATAALIGN_WORD);

  LL_DMA_SetMemorySize(DMA1, LL_DMA_CHANNEL_1, LL_DMA_MDATAALIGN_WORD);
   
  LL_ADC_EnableInternalRegulator(ADC1);
     /** Configure Regular Channel
  */
  //若要编译器优化代码,此处是关键
  LL_ADC_REG_SetSequencerChAdd(ADC1, LL_ADC_CHANNEL_3 | LL_ADC_CHANNEL_5);
  /** Configure Regular Channel
  */
//这里要特别注意,如果使用了代码优化,就只使用上面的语句就可以
//  LL_ADC_REG_SetSequencerChAdd(ADC1, LL_ADC_CHANNEL_5);
   
  /** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion)
  */
  ADC_InitStruct.Clock = LL_ADC_CLOCK_SYNC_PCLK_DIV2;
  ADC_InitStruct.Resolution = LL_ADC_RESOLUTION_10B;
  ADC_InitStruct.DataAlignment = LL_ADC_DATA_ALIGN_RIGHT;
  ADC_InitStruct.LowPowerMode = LL_ADC_LP_MODE_NONE;
  LL_ADC_Init(ADC1, &ADC_InitStruct);
 
    ADC_REG_InitStruct.TriggerSource = LL_ADC_REG_TRIG_SOFTWARE;
  ADC_REG_InitStruct.SequencerDiscont = LL_ADC_REG_SEQ_DISCONT_DISABLE;
  ADC_REG_InitStruct.ContinuousMode = LL_ADC_REG_CONV_CONTINUOUS;
  ADC_REG_InitStruct.DMATransfer = LL_ADC_REG_DMA_TRANSFER_UNLIMITED;
  ADC_REG_InitStruct.Overrun = LL_ADC_REG_OVR_DATA_OVERWRITTEN;
  LL_ADC_REG_Init(ADC1, &ADC_REG_InitStruct);
   
  LL_ADC_SetOverSamplingScope(ADC1, LL_ADC_OVS_DISABLE);
  LL_ADC_SetTriggerFrequencyMode(ADC1, LL_ADC_CLOCK_FREQ_MODE_HIGH);
  LL_ADC_REG_SetSequencerConfigurable(ADC1, LL_ADC_REG_SEQ_FIXED);
  LL_ADC_SetSamplingTimeCommonChannels(ADC1, LL_ADC_SAMPLINGTIME_COMMON_1, LL_ADC_SAMPLINGTIME_39CYCLES_5);
  LL_ADC_DisableIT_EOC(ADC1);
  LL_ADC_DisableIT_EOS(ADC1);
  LL_ADC_REG_SetSequencerScanDirection(ADC1, LL_ADC_REG_SEQ_SCAN_DIR_FORWARD);
}

DMA初始化

1
2
3
4
5
6
7
8
9
10
void MX_DMA_Init(void)
{
  /* Init with LL driver */
  /* DMA controller clock enable */
  LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_DMA1);
  /* DMA interrupt init DMA 照常省略掉中断 */
  /* DMA1_Channel1_IRQn interrupt configuration */
//  NVIC_SetPriority(DMA1_Channel1_IRQn, 0);
//  NVIC_EnableIRQ(DMA1_Channel1_IRQn);
}

main函数中使能ADC和DMA

1
2
3
4
5
6
7
8
9
10
11
/* Run the ADC calibration  使能ADC */
    LL_ADC_StartCalibration(ADC1);
    while( LL_ADC_IsCalibrationOnGoing(ADC1));
    LL_ADC_Enable(ADC1);
    LL_ADC_REG_StartConversion(ADC1);
    LL_mDelay(2); //延时2ms,避免DMA读取数据通道不对
    //DMA使能 这个是关键,不能放到adc校准前面否则读出来的数据会不对
    LL_DMA_SetDataLength(DMA1,LL_DMA_CHANNEL_1,2);
    LL_DMA_SetPeriphAddress(DMA1,LL_DMA_CHANNEL_1,LL_ADC_DMA_GetRegAddr(ADC1,LL_ADC_DMA_REG_REGULAR_DATA));
    LL_DMA_SetMemoryAddress(DMA1,LL_DMA_CHANNEL_1,(uint32_t)&AD_DMA);
    LL_DMA_EnableChannel(DMA1,LL_DMA_CHANNEL_1);

这里讲优化编译时出现的问题,如果使用如下2条语句来添加通道的话,经过keil -o1级编译优化后,则只会得到通道3的数据,可以在debug时打开ADC外设来查看ADC寄存器初始化了哪几个通道,这里我怀疑是后面初始化通道5的语句被keil优化了。

1
2
LL_ADC_REG_SetSequencerChAdd(ADC1, LL_ADC_CHANNEL_3);
LL_ADC_REG_SetSequencerChAdd(ADC1, LL_ADC_CHANNEL_5);

解决这个问题的办法就是使用下面这个语句:

1
LL_ADC_REG_SetSequencerChAdd(ADC1, LL_ADC_CHANNEL_3 | LL_ADC_CHANNEL_5);

此语句与上面2条语句等效,经过测试是可以完美解决上面的问题。

这里还遇到另外一个问题就是可能会出现DMA采集数据和ADC通道对应不上的问题,之前在另一份代码中3 ADC通道+DMA通过先使能ADC再使能DMA可以解决这个问题,但是在这里问题没有得到解决,原因目前不知,在网上的结论可能是ADC校准时会导致DMA传输数据,但是我在使能ADC之前已经先校准ADC,还是会出现这个问题,后来我在使能DMA之前延时2ms,发现问题得到解决,所以我认为出现这个问题可能是因为校准ADC时产生的数据还没有被ADC正式运转时采样的数据覆盖,这时候启动DMA,DMA立马去ADC那里查询到有数据,然后传输过来,这就导致了通道错乱,如果延时一下,ADC采样的数据就会覆盖掉校准时的数据,这时候DMA去传输ADC的数据才是正确的。具体的请看mai函数中使能ADC和DMA那段。至于ADC使能后要延时多久,这个个人认为是延时的这个时间至少要大于ADC的采样时间,也就是让ADC有足够的时间去刷新数据寄存器。