Estoy usando un STM32F103C8 para leer 3 canales ADC y he usado CubeMX + HAL para configurar el ADC para transportar los valores ADC a un búfer.
He podido lograr esto con DMA y encuestas: he visto que ambas son formas generalmente aceptables de hacer esto en línea. Sin embargo, no he podido encontrar una configuración que me permita explorar los canales iniciando manualmente las conversiones en la interrupción EOC. En todos mis intentos, o no avanza los canales o la interrupción no se activa en absoluto.
Preferiría usar interrupciones porque mi único canal DMA en el dispositivo se usa para almacenar en búfer algunos datos de audio de bastante alta frecuencia, pero también me preocupa no poder entender la forma en que las interrupciones ADC funcionan junto con el escaneo. modo. He usado los siguientes enfoques:
DMA : esta parece ser la forma autorizada de escanear múltiples canales y almacenar sus respectivos resultados. En particular, el manual del usuario en §11.3.8 ¶3 dice:
Cuando se usa el modo de exploración, se debe configurar el bit DMA y se usa el controlador de acceso directo a la memoria para transferir los datos convertidos de los canales de grupo regulares a SRAM después de cada actualización del registro ADC_DR.
Pude hacerlo funcionar con la configuración intuitiva en CubeMX:
más un DMA alineado con media palabra circular y una llamada directa a HAL_ADC_Start_DMA()
la fuente.
Sondeo : intenté seguir esta respuesta que deshabilita los modos continuo y discontinuo, y puede recorrer los canales con llamadas sucesivas a HAL_ADC_PollForConversion
solo. Descubrí que necesitaba habilitar el modo discontinuo con tamaños de grupo de 1, es decir:
hadc1.Init.DiscontinuousConvMode = ENABLE;
hadc1.Init.NbrOfDiscConversion = 1;
Luego paso a través de los canales con HAL_ADC_PollForConversion
trabajado sin problemas.
Interrupciones: probé todas las permutaciones del modo de exploración, el modo discontinuo y el número de conversiones discontinuas, y ninguna de ellas me permite recorrer los canales en la HAL_ADC_ConvCpltCallback
rutina de interrupción. Aquí está la rutina que estoy usando:
#define NUM_ADC_BUF 8
#define NUM_ADC_CH 3
volatile uint16_t adc_buf[NUM_ADC_BUF][NUM_ADC_CH];
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) {
if(hadc->Instance == ADC1) {
adc_buf[adc_buf_idx & (NUM_ADC_BUF - 1)][adc_ch++] = HAL_ADC_GetValue(hadc);
if(adc_ch == NUM_ADC_CH) {
adc_ch = 0;
adc_buf_idx++;
}
HAL_ADC_Start_IT(hadc);
}
}
Dentro de ADC_Settings en CubeMX, aquí están mis intentos y sus resultados:
+-------------------+-----------+--------------------+---------------------+------------------------------------------------------+
| (Continuous mode) | Scan mode | Discontinuous mode | Number of | Outcome |
| | | | discontinuous conv. | |
+-------------------+-----------+--------------------+---------------------+------------------------------------------------------+
| DISABLED | ENABLED | ENABLED | 3 | Only highest-rank-# (rank 3) channel result is given |
| DISABLED | ENABLED | ENABLED | 1 | Interrupt never fires: EOC never set |
| DISABLED | ENABLED | DISABLED | N/A | Only highest-rank-# (rank 3) channel result is given |
| DISABLED | DISABLED | ENABLED | 3 | Only lowest-rank-# (rank 1) channel result is given |
| DISABLED | DISABLED | ENABLED | 1 | Only lowest-rank-# (rank 1) channel result is given |
| DISABLED | DISABLED | DISABLED | N/A | Only lowest-rank-# (rank 1) channel result is given |
+-------------------+-----------+--------------------+---------------------+------------------------------------------------------+
Como puede ver, ninguna combinación funciona correctamente. ¿Es esto simplemente imposible? Supongo que puedo tomar el extracto que cité del manual del usuario en el sentido de que la única forma de usar el escaneo es DMA, y que el sondeo funciona como una función que no se admite formalmente. ¿Es esto cierto?
Como referencia, aquí está mi generación automática intacta adc.c
de CubeMX:
/* Includes ------------------------------------------------------------------*/
#include "adc.h"
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
ADC_HandleTypeDef hadc1;
/* ADC1 init function */
void MX_ADC1_Init(void)
{
ADC_ChannelConfTypeDef sConfig = {0};
/** Common config
*/
hadc1.Instance = ADC1;
hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.DiscontinuousConvMode = ENABLE;
hadc1.Init.NbrOfDiscConversion = 3;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 3;
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_4;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_8;
sConfig.Rank = ADC_REGULAR_RANK_2;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_9;
sConfig.Rank = ADC_REGULAR_RANK_3;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
}
void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(adcHandle->Instance==ADC1)
{
/* USER CODE BEGIN ADC1_MspInit 0 */
/* USER CODE END ADC1_MspInit 0 */
/* ADC1 clock enable */
__HAL_RCC_ADC1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/**ADC1 GPIO Configuration
PA4 ------> ADC1_IN4
PB0 ------> ADC1_IN8
PB1 ------> ADC1_IN9
*/
GPIO_InitStruct.Pin = Xin_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
HAL_GPIO_Init(Xin_GPIO_Port, &GPIO_InitStruct);
GPIO_InitStruct.Pin = Zin_Pin|Yin_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* ADC1 interrupt Init */
HAL_NVIC_SetPriority(ADC1_2_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(ADC1_2_IRQn);
/* USER CODE BEGIN ADC1_MspInit 1 */
/* USER CODE END ADC1_MspInit 1 */
}
}
void HAL_ADC_MspDeInit(ADC_HandleTypeDef* adcHandle)
{
if(adcHandle->Instance==ADC1)
{
/* USER CODE BEGIN ADC1_MspDeInit 0 */
/* USER CODE END ADC1_MspDeInit 0 */
/* Peripheral clock disable */
__HAL_RCC_ADC1_CLK_DISABLE();
/**ADC1 GPIO Configuration
PA4 ------> ADC1_IN4
PB0 ------> ADC1_IN8
PB1 ------> ADC1_IN9
*/
HAL_GPIO_DeInit(Xin_GPIO_Port, Xin_Pin);
HAL_GPIO_DeInit(GPIOB, Zin_Pin|Yin_Pin);
/* ADC1 interrupt Deinit */
HAL_NVIC_DisableIRQ(ADC1_2_IRQn);
/* USER CODE BEGIN ADC1_MspDeInit 1 */
/* USER CODE END ADC1_MspDeInit 1 */
}
}
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
A diferencia de la serie F4, el ADC de la serie F1 puede generar solo una (única) interrupción al final de toda la secuencia de exploración. Es por eso que DMA es imprescindible cuando se usa el modo de escaneo.
Sin embargo, creo que todavía es posible configurar ADC para el análisis basado en interrupciones, pero debe hacerlo manualmente:
Para el muestreo periódico de múltiples canales, probablemente necesitará 2 módulos TIM y algunas interrupciones TIM. Un temporizador (Esclavo) controla el tiempo entre cada canal de un grupo de exploración y activa el módulo ADC. Este temporizador debe conocer su número de desbordamientos (usando una interrupción) para que pueda desactivarse después de que todos los canales de la secuencia se muestreen y conviertan. Otro temporizador (maestro) controla el tiempo entre las secuencias de escaneo y activa el temporizador esclavo (y probablemente el primer escaneo de la secuencia).
No sé qué tan ocupado está su DMA, pero definitivamente intentaría usar DMA antes de probar el método que describí anteriormente.
0___________
concat
0___________
concat
Tagli
concat
Tagli
concat