Embedded 使用带DMA的STM32L4 ADC读取成像传感器

Embedded 使用带DMA的STM32L4 ADC读取成像传感器,embedded,stm32,dma,adc,Embedded,Stm32,Dma,Adc,我读了很多关于如何进行多通道ADC读数的书。我的第一次成功是通过民意测验转化。然后我设法用DMA切换到ADC,但我被我的新目标卡住了。我想读取图像传感器的模拟输出。我不会用完整的循环来打扰您,但基本上最重要的是列的循环: 设置像素列的8位地址(带BSRR的8个GPIO引脚) 在读取模拟输出之前等待40ns(由于计算造成的时间延迟,如有必要,添加一些NOP) 读取两个输出(同时读取两个像素)并使用它们进行一些计算 使用通道选择和轮询进行转换非常慢,我希望每秒有几帧(越多越好)。 我试着在扫描模式下

我读了很多关于如何进行多通道ADC读数的书。我的第一次成功是通过民意测验转化。然后我设法用DMA切换到ADC,但我被我的新目标卡住了。我想读取图像传感器的模拟输出。我不会用完整的循环来打扰您,但基本上最重要的是列的循环:

  • 设置像素列的8位地址(带BSRR的8个GPIO引脚)
  • 在读取模拟输出之前等待40ns(由于计算造成的时间延迟,如有必要,添加一些NOP)
  • 读取两个输出(同时读取两个像素)并使用它们进行一些计算
  • 使用通道选择和轮询进行转换非常慢,我希望每秒有几帧(越多越好)。 我试着在扫描模式下进行,2个通道,contconv禁用,DMA设置为正常模式。我仍然看到由于每次ADC启动都会有很大的开销。 最好的办法是使用ADC+DMA读取整行320个值,然后停止ADC,进行一些计算并继续下一行。我想一个大问题是我试图通过Truestudio在调试模式下检查它,因此任何关于如何以最佳方式进行的建议都将受到赞赏。首先,一些澄清问题:

  • 尽管我有2个通道,但为DMA设置ADCbuffer[320]并写入HAL_ADC_Start_DMA(&hadc1,ADCbuffer,320)是否正确?我希望ADC在contconv模式下循环2个通道的序列,这样DMA将填充缓冲区。我的想法是检查列号,一旦到达最后一列,停止ADC。 如果这是不可行的,我将坚持ADCbuffer[2]和两个DMA读数
  • 当我尝试用带有2个通道和循环DMA的contconv ADC调试这个案例时,我注意到了一些问题(其中一些问题可能是由于调试器模式引起的)。顺便说一下,系统时钟为100MHz(我的处理器的闪存为4W),ADC的工作频率为80MHz:

  • 不管我选择了什么集成时间,两个HAL_ADC_ConvHalfCpltCallback之间的时间保持在大约150个刻度。即使在640.5个周期的情况下,我也会得到200个滴答声。我觉得很奇怪,但这可能是由于调试模式
  • 虽然HAL_ADC_ConvHalfCpltCallback是定期调用的,我可以为下一个像素插入总线更改,但HAL_ADC_ConvCpltCallback不是随机调用的,也很少随机调用。你知道为什么会这样吗
  • 考虑到时序问题,我很好奇是否有一种方法可以将ADC与DMA、禁用contconv、正常模式DMA一起使用,但要使用一些快速ADC启动(不是HAL)或ADC启动中断。因此,我可以确保读取2个值,向传感器发送命令以读取下一个像素等,但开销较小。 我认为我的代码目前设置正确,但我会发布它,以防有人发现问题并修复一些问题。我使用的是STM32L4R5ZI:

    static void MX_ADC1_Init(void)
    {
    
    
      ADC_ChannelConfTypeDef sConfig = {0};
    
      hadc1.Instance = ADC1;
      hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1;
      hadc1.Init.Resolution = ADC_RESOLUTION_12B;
      hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
      hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE;
      hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV;
      hadc1.Init.LowPowerAutoWait = DISABLE;
      hadc1.Init.ContinuousConvMode = ENABLE;
      hadc1.Init.NbrOfConversion = 2;
      hadc1.Init.NbrOfDiscConversion = 0;
      hadc1.Init.DiscontinuousConvMode = DISABLE;
      hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
      hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
      hadc1.Init.DMAContinuousRequests = ENABLE; 
      hadc1.Init.Overrun = ADC_OVR_DATA_PRESERVED;
      hadc1.Init.OversamplingMode = DISABLE;
      if (HAL_ADC_Init(&hadc1) != HAL_OK)
      {
        Error_Handler();
      }
    
      sConfig.Channel = ADC_CHANNEL_1;
      sConfig.Rank = 1;
      sConfig.SamplingTime = ADC_SAMPLETIME_12CYCLES_5; 
      sConfig.SingleDiff = ADC_SINGLE_ENDED;
      sConfig.OffsetNumber = ADC_OFFSET_NONE;
      sConfig.Offset = 0;
      if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
      {
        Error_Handler();
      }
    
      sConfig.Channel = ADC_CHANNEL_2;
      sConfig.Rank = 2;
      if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
      {
        Error_Handler();
      }
      HAL_NVIC_SetPriority(ADC1_IRQn, 0, 0);
      HAL_NVIC_EnableIRQ(ADC1_IRQn);
    }
    
    
    static void MX_DMA_Init(void)
    {
    
      /* DMA controller clock enable */
      __HAL_RCC_DMAMUX1_CLK_ENABLE();
      __HAL_RCC_DMA2_CLK_ENABLE();
      __DMA2_CLK_ENABLE();
    
      /* DMA interrupt init */
      hdma_adc1.Instance = DMA2_Channel3;
      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_WORD;
      hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
      hdma_adc1.Init.Mode = DMA_CIRCULAR;
      hdma_adc1.Init.Priority = DMA_PRIORITY_HIGH;
      //HAL_DMA_DeInit(&hdma_adc1);
      HAL_DMA_Init(&hdma_adc1);
      __HAL_LINKDMA(&hadc1, DMA_Handle, hdma_adc1);
    
      DMAMUX1_Channel9->CCR = 0x5;
    
    
      /* DMA interrupt init */
      /* DMA1_Channel1_IRQn interrupt configuration */
      HAL_NVIC_SetPriority(DMA2_Channel3_IRQn, 0, 0);
      HAL_NVIC_EnableIRQ(DMA2_Channel3_IRQn);
      /* DMAMUX1_OVR_IRQn interrupt configuration */
      HAL_NVIC_SetPriority(DMAMUX1_OVR_IRQn, 0, 0);
      HAL_NVIC_EnableIRQ(DMAMUX1_OVR_IRQn);
    
    }
    
    
    //somewhere in main.c, the read out loop:
    uint32_t ADCBuffer[2];
    
    GPIOF -> BSRR = (col) | (((~col) & 0xFF) << (16)); //r_col case, F0:7
    HAL_ADC_Start_DMA(&hadc1, ADCBuffer, 2);
    
    
    //And the callbacks and handlers in STM32L4xx_it.c:
    
    void DMA2_Channel3_IRQHandler(void)
    {
      HAL_DMA_IRQHandler(&hdma_adc1);
      HAL_ADC_IRQHandler(&hadc1);
    }
    
    void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) {
        if(vCol==159){
            HAL_ADC_Stop_DMA(hadc);
        }
    }
    
    void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef *hadc)
    {
        vCol++;
        GPIOF -> BSRR = (vCol) | (((~vCol) & 0xFF) << (16)); //r_col case, F0:7
        
        rsum = rsum + ADCBuffer[0] + ADCBuffer[1];
    }
    
    void ADC_IRQHandler()
    {
        HAL_ADC_IRQHandler(&hadc1);
    }
    
    
    static void MX_ADC1_Init(void)
    {
    ADC_ChannelConfTypeDef sConfig={0};
    hadc1.Instance=ADC1;
    hadc1.Init.ClockPrescaler=ADC\u CLOCK\u ASYNC\u DIV1;
    hadc1.Init.Resolution=ADC_Resolution_12B;
    hadc1.Init.DataAlign=ADC\u DataAlign\u RIGHT;
    hadc1.Init.ScanConvMode=ADC\u SCAN\u ENABLE;
    hadc1.Init.EOCSelection=ADC_EOC_SEQ_CONV;
    hadc1.Init.LowPowerAutoWait=禁用;
    hadc1.Init.ContinuousConvMode=启用;
    hadc1.Init.nbrof转换=2;
    hadc1.Init.nbrofdisconversion=0;
    hadc1.Init.DiscontinuousConvMode=禁用;
    hadc1.Init.ExternalTrigConv=ADC\u软件\u启动;
    hadc1.Init.externaltrigconverge=ADC_externaltrigconverge_NONE;
    hadc1.Init.DMAContinuousRequests=ENABLE;
    hadc1.Init.overflow=ADC\u OVR\u数据保存;
    hadc1.Init.OversamplingMode=禁用;
    if(HAL_ADC_Init(&hadc1)!=HAL_OK)
    {
    错误处理程序();
    }
    sConfig.Channel=ADC\u Channel\u 1;
    sConfig.Rank=1;
    sConfig.SamplingTime=ADC_SAMPLETIME_12周期_5;
    sConfig.SingleDiff=ADC\u SINGLE\u end;
    sConfig.OffsetNumber=ADC\u OFFSET\u NONE;
    sConfig.Offset=0;
    if(HAL_ADC_配置通道(&hadc1,&sConfig)!=HAL_正常)
    {
    错误处理程序();
    }
    sConfig.Channel=ADC\u Channel\u 2;
    sConfig.Rank=2;
    if(HAL_ADC_配置通道(&hadc1,&sConfig)!=HAL_正常)
    {
    错误处理程序();
    }
    HAL_NVIC_SetPriority(ADC1_IRQn,0,0);
    HAL_NVIC_EnableIRQ(ADC1_IRQn);
    }
    静态void MX_DMA_Init(void)
    {
    /*DMA控制器时钟启用*/
    __HAL_RCC_DMAMUX1_CLK_ENABLE();
    __HAL_RCC_DMA2_CLK_ENABLE();
    __DMA2_时钟_启用();
    /*DMA中断初始化*/
    hdma_adc1.Instance=DMA2_Channel3;
    hdma_adc1.Init.Direction=DMA_PERIPH_TO_内存;
    hdma_adc1.Init.PeriphInc=DMA_PINC_DISABLE;
    hdma_adc1.Init.MemInc=DMA_MINC_ENABLE;
    hdma_adc1.Init.PeriphDataAlignment=DMA_PDATAALIGN_字;
    hdma_adc1.Init.MemDataAlignment=DMA_MDATAALIGN_字;
    hdma_adc1.Init.Mode=DMA_循环;
    hdma_adc1.Init.Priority=DMA_Priority_HIGH;
    //HAL_DMA_Denit(&hdma_adc1);
    HAL_DMA_Init(&hdma_adc1);
    __HAL_LINKDMA(和hadc1、DMA_句柄、hdma_adc1);
    DMAMUX1_信道9->CCR=0x5;
    /*DMA中断初始化*/
    /*DMA1\U通道1\U IRQn中断配置*/
    HAL_NVIC_SetPriority(DMA2_信道3_IRQn,0,0);
    HAL_NVIC_EnableIRQ(DMA2_信道3_IRQn);
    /*DMAMUX1_OVR_IRQn中断配置*/
    HAL_NVIC_SetPriority(DMAMUX1_OVR_IRQn,0,0);
    HAL_NVIC_EnableIRQ(DMAMUX1_OVR_IRQn);
    }
    //在main.c中的某个地方,读取循环:
    uint32_t ADCBuffer[2];
    
    GPIOF->BSRR=(col)|((~col)和0xFF)BSRR=(vCol)|((~vCol)和0xFF)为每个像素触发一个中断是毫无意义的。进入和离开中断的开销,特别是HAL开销太大。行方式当然是可行的。这种方式可以有固定的时间来设置地址和采样值。采样端可以通过定时器实现gger ADC和ADC使用DMA存储整行。我不太熟悉基于计时器的机制来设置列地址以及如何同步两个并行操作。另一种方法是使用低级别(直接注册