Как выполнять преобразование adc каждые 1 мкс с помощью Nucleo-F303K8?

Я использую STM32 Cube IDE. Что я пробовал сейчас, так это включить MSM в TIM2 и output_compare_no_output на канале 1 и выбрать Reset в качестве триггерного события. Затем я перешел к ADC1 и включил Regular_Conversion_Mode, установил Number_Of_Conversions равным 1, а External_Trigger_Conversion_Source - событие Timer 2 Trigger Out. После этого я установил DMA в циклическом режиме, который помещает полуслова в буфер RAM. Для тестирования я установил частоту таймера намного ниже (10 Гц) и отправил некоторые показания АЦП из буфера через UART в полных обратных вызовах ConvHalfCoplt и ConvCoplt. Но на данный момент это не работает. Можете ли вы вспомнить ошибки в моем подходе?

#include "main.h"

#include <stdio.h>
#include <string.h>

#define ADC_BUF_LEN 4096
ADC_HandleTypeDef hadc1;
DMA_HandleTypeDef hdma_adc1;

DAC_HandleTypeDef hdac1;
DMA_HandleTypeDef hdma_dac1_ch1;

TIM_HandleTypeDef htim2;

UART_HandleTypeDef huart2;

/* USER CODE BEGIN PV */

uint8_t adc_buf[ADC_BUF_LEN];
char msg[16];

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_USART2_UART_Init(void);
static void MX_ADC1_Init(void);
static void MX_DAC1_Init(void);
static void MX_TIM2_Init(void);

/* Private user code ---------------------------------------------------------*/

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* Configure the system clock */
  SystemClock_Config();

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_USART2_UART_Init();
  MX_ADC1_Init();
  MX_DAC1_Init();
  MX_TIM2_Init();
  /* USER CODE BEGIN 2 */

  HAL_TIM_Base_Start(&htim2);
  HAL_ADC_Start_DMA(&hadc1, (uint32_t*) adc_buf, ADC_BUF_LEN);

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL4;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
  {
    Error_Handler();
  }
  PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC12;
  PeriphClkInit.Adc12ClockSelection = RCC_ADC12PLLCLK_DIV16;
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief ADC1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_ADC1_Init(void)
{
  ADC_MultiModeTypeDef multimode = {0};
  ADC_ChannelConfTypeDef sConfig = {0};

  /** Common config
  */
  hadc1.Instance = ADC1;
  hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1;
  hadc1.Init.Resolution = ADC_RESOLUTION_12B;
  hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
  hadc1.Init.ContinuousConvMode = DISABLE;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;
  hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T2_TRGO;
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.NbrOfConversion = 1;
  hadc1.Init.DMAContinuousRequests = DISABLE;
  hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
  hadc1.Init.LowPowerAutoWait = DISABLE;
  hadc1.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;
  if (HAL_ADC_Init(&hadc1) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure the ADC multi-mode
  */
  multimode.Mode = ADC_MODE_INDEPENDENT;
  if (HAL_ADCEx_MultiModeConfigChannel(&hadc1, &multimode) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure Regular Channel
  */
  sConfig.Channel = ADC_CHANNEL_1;
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SingleDiff = ADC_SINGLE_ENDED;
  sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
  sConfig.OffsetNumber = ADC_OFFSET_NONE;
  sConfig.Offset = 0;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief DAC1 Initialization Function
  * @param None
  * @retval None
  */

/**
  * @brief TIM2 Initialization Function
  * @param None
  * @retval None
  */
static void MX_TIM2_Init(void)
{

  /* USER CODE BEGIN TIM2_Init 0 */

  /* USER CODE END TIM2_Init 0 */

  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_OC_InitTypeDef sConfigOC = {0};

  /* USER CODE BEGIN TIM2_Init 1 */

  /* USER CODE END TIM2_Init 1 */
  htim2.Instance = TIM2;
  htim2.Init.Prescaler = 800 - 1;
  htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim2.Init.Period = 1000 - 1;
  htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_OC_Init(&htim2) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_ENABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.OCMode = TIM_OCMODE_TIMING;
  sConfigOC.Pulse = 0;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
  if (HAL_TIM_OC_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM2_Init 2 */

  /* USER CODE END TIM2_Init 2 */

}

/**
  * @brief USART2 Initialization Function
  * @param None
  * @retval None
  */
static void MX_USART2_UART_Init(void)
{

  /* USER CODE BEGIN USART2_Init 0 */

  /* USER CODE END USART2_Init 0 */

  /* USER CODE BEGIN USART2_Init 1 */

  /* USER CODE END USART2_Init 1 */
  huart2.Instance = USART2;
  huart2.Init.BaudRate = 38400;
  huart2.Init.WordLength = UART_WORDLENGTH_8B;
  huart2.Init.StopBits = UART_STOPBITS_1;
  huart2.Init.Parity = UART_PARITY_NONE;
  huart2.Init.Mode = UART_MODE_TX_RX;
  huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart2.Init.OverSampling = UART_OVERSAMPLING_16;
  huart2.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
  huart2.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
  if (HAL_UART_Init(&huart2) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN USART2_Init 2 */

  /* USER CODE END USART2_Init 2 */

}

/**
  * Enable DMA controller clock
  */
static void MX_DMA_Init(void)
{

  /* DMA controller clock enable */
  __HAL_RCC_DMA1_CLK_ENABLE();

  /* DMA interrupt init */
  /* DMA1_Channel1_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
  /* DMA1_Channel3_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Channel3_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Channel3_IRQn);

}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOF_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOB, GPIO_PIN_3, GPIO_PIN_RESET);

  /*Configure GPIO pin : PB3 */
  GPIO_InitStruct.Pin = GPIO_PIN_3;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

}

/* USER CODE BEGIN 4 */

// Called when first half of buffer is filled
void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc){
  HAL_GPIO_WritePin(GPIOB, GPIO_PIN_3, GPIO_PIN_SET);
  sprintf(msg, "%ho\r\n", adc_buf[0]);
  HAL_UART_Transmit(&huart2, (uint8_t*) msg, strlen(msg), HAL_MAX_DELAY);
}
// Called when buffer is completely filled
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc){
  HAL_GPIO_WritePin(GPIOB, GPIO_PIN_3, GPIO_PIN_RESET);
  sprintf(msg, "%ho\r\n", adc_buf[ADC_BUF_LEN / 2]);
  HAL_UART_Transmit(&huart2, (uint8_t*) msg, strlen(msg), HAL_MAX_DELAY);
}

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */

  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

########################################################################### ############################ Старый: #################### ########################################################################### ########

До сих пор я пытался настроить TIM2 так, чтобы он сбрасывал каждую микросекунду и запускал преобразование в обратном вызове прерывания:

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
  // Check which timer triggered this callback
  if (htim == &htim2){
    HAL_ADC_Start(&hadc1);
    HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY);
    adc_val = HAL_ADC_GetValue(&hadc1);
  }
}

Но насколько я знаю, PollForConversion может занять некоторое время.

Лучше создать буфер и использовать DMA для постоянной передачи данных с АЦП в буфер и считывания оттуда значения каждую микросекунду? Разве я не стал бы так читать старые данные?


person le_lemon    schedule 08.10.2020    source источник
comment
Запускать прерывание каждые 1 мкс очень дорого, и вы правы, сомневаясь в подходе. DMA - это то, что нужно. Его можно связать с АЦП. Таким образом, он будет передавать одно значение всякий раз, когда преобразование завершено. Вам также необходимо изменить настройку таймера. Вместо того, чтобы запускать прерывание, он должен запускать начало преобразования. Этот конкретный вариант использования и настройка (таймер - ›ADC -› DMA) явно поддерживаются.   -  person Codo    schedule 08.10.2020
comment
Вызов процедуры прерывания + весь ваш код HAL заберет более 72 часов, которые у вас есть (72e6 / 1e6). Ваша программа даже не сможет обрабатывать прерывания :)   -  person 0___________    schedule 08.10.2020
comment
Новый код намного лучше, но время все еще на грани: обработчики прерываний вызываются примерно 400 раз в секунду, в обработчике прерываний сообщение форматируется и передается по медленному последовательному соединению в режиме блокировки, в результате чего передается около 2500 символов. в секунду. Это может просто сработать, а может быть, это слишком ...   -  person Codo    schedule 09.10.2020
comment
UART предназначен только для тестирования, позже значения будут использоваться для декодирования сообщения, содержащегося во входном сигнале. Но все же это не работает, в настоящий момент обратные вызовы не выполняются по какой-то причине, например, я ничего не вижу в UART.   -  person le_lemon    schedule 09.10.2020


Ответы (1)


Запуск преобразования АЦП каждые 1 мкс - довольно сложная задача, поскольку ядро ​​микроконтроллера STM32F3 работает на макс. Только 72 МГц. Поэтому решать эту задачу следует только с использованием аппаратной функциональности:

  1. set up a timer to create a trigger output event every 1us (see description of Master mode selection in TIM control register of Reference Manual). Instead of generating an interrupt your timer can generate a trigger output on an update event:
    • set Master mode selection bits MSM in TIM2_CR2 to 010 (Update).
    • бит MSM в TIM2_SMCR должен оставаться на 0
  2. set up the ADC to run a conversion when triggered by the external trigger generated by the timer (see section Conversion on external trigger in ADC chapter of Reference Manual):
    • set EXTEN to 01 (HW trigger on rising edge) in ADC1_CFGR
    • установите EXTSEL в 1011 (событие TIM2_TRGO) в ADC1_CFGR
  3. настроить АЦП для генерации запроса DMA после каждого преобразования (см. раздел Управление преобразованиями с помощью DMA в главе АЦП Справочного руководства)
  4. настроить DMA для хранения данных, считанных из ADC, в буфер RAM (см. главу о контроллере DMA в Справочном руководстве). Я рекомендую запустить канал DMA в циклическом режиме на большом буфере RAM. Это позволяет избежать любой необходимости перенастроить DMA во время выполнения.

При такой настройке вы можете использовать все такты MCU для обработки большого количества данных, генерируемых АЦП в этой настройке (1 МБ / с). Вы можете опросить контроллер DMA для проверки наличия новых данных или использовать флаги DMA Half Transfer Complete и Transfer Complete, чтобы получать уведомление IRQ каждый раз, когда половина буфера заполняется. с новыми данными.

Вам придется довольно много изучить документацию по ADC, Timer и DMA, чтобы запустить эту настройку, но это того стоит, поскольку она аккуратно решит вашу задачу!

person Blue    schedule 08.10.2020
comment
Я отредактировал сообщение и попытался последовать вашему ответу, но у меня все еще остались проблемы. - person le_lemon; 09.10.2020
comment
У меня появилась и другая идея. Я могу отредактировать предварительный делитель АЦП на 1/16, чтобы он получал тактовую частоту 1 МГц и в любом случае выполнял ровно столько сэмплов. Или я здесь что-то не так? - person le_lemon; 09.10.2020
comment
У меня все еще есть проблемы: если вы ожидаете какой-либо помощи, вам, вероятно, следует предоставить дополнительную информацию. Что касается настройки предделителя: это альтернатива. Но ваш код не работает, и если все еще есть проблемы с настройкой ADC или DMA, он не будет работать. - person Codo; 09.10.2020
comment
Это правда, но у меня есть рабочий пример конфигурации ADC DMA (только без таймера в качестве триггера), так что это не проблема. - person le_lemon; 09.10.2020
comment
Я только что прочитал в справочном руководстве, что преобразование АЦП занимает 15 циклов при 12-битной точности, поэтому нет необходимости использовать предварительный делитель 1/16. - person le_lemon; 09.10.2020
comment
Я думаю, что TIM2 TRGO еще неправильно настроен в вашем коде - попробуйте использовать TIM_TRGO_UPDATE вместо TIM_TRGO_RESET. - person Blue; 09.10.2020
comment
Я изменил его на TIM_TRGO_UPDATE, но в буфере все еще нет значений. - person le_lemon; 12.10.2020
comment
Можете ли вы проверить, сработал ли таймер хотя бы одно преобразование АЦП? Бит EOC должен быть очищен после инициализации и установлен после передачи. Код в обновленном вопросе завершен? Мне не хватает конфигурации для DMA1 CH1: исходный адрес, целевой адрес, циклический режим и т. Д. - person Blue; 12.10.2020
comment
Спасибо за вашу помощь. Все, что я изменил, это: TIM2 (глупая ошибка), период предварительного делителя и счетчика были слишком большими по сравнению с предыдущими примерами, поэтому таймер срабатывал один раз в секунду, а в буфере было 4096 записей. Таким образом, потребовалось бы 34 минуты, пока светодиод не переключился в первый раз: D, также я выбрал внутренние часы в качестве источника часов, ранее он был отключен, хотя у меня есть другой пример, когда таймер работает без этой настройки. Также включены непрерывные запросы DMA на ADC. - person le_lemon; 13.10.2020
comment
Приятно слышать, что теперь это работает! Спасибо за ответ. :) - person Blue; 13.10.2020