说明
目前RTThread的adc设备驱动采用轮询法,比较浪费cpu资源。话说我从开始使用STM32到现在,就没用过ADC轮询方式
这是我的某个项目中对RTThread的ADC设备驱动添加DMA方式
代码分析
1.相关宏定义
自行选择方便的地方定义,我是直接跟在board.h中ADC定义的后面
board.h中
#define BSP_USING_ADC1
#define BSP_USING_ADC_DMA
/*#define BSP_USING_ADC2*/
/*#define BSP_USING_ADC3*/
#ifdef BSP_USING_ADC_DMA
//每个通道缓存数据个数(因为我的项目中采用去除最大最小,移位求平均,所以取值2^n+2)
#define BSP_DMA_BUF_COUNT 34
//总共通道数
#define BSP_ADC_TOTAL_CHANNELS 15
//所有使用的通道编号
#define BSP_ADC_CHN_DEF {0,1,2,3,4,5,6,7,10,11,12,13,14,15,17}
//采样周期
#define BSP_ADC_SAMPLE_CYC_DEF ADC_SAMPLETIME_71CYCLES_5
#endif
adc_config.h中
#ifdef BSP_USING_ADC_DMA
#define ADC1_CONFIG \
{ \
.Instance = ADC1, \
.Init.DataAlign = ADC_DATAALIGN_RIGHT, \
.Init.ScanConvMode = ADC_SCAN_ENABLE, \
.Init.ContinuousConvMode = ENABLE, \
.Init.NbrOfConversion = BSP_ADC_TOTAL_CHANNELS, \
.Init.DiscontinuousConvMode = DISABLE, \
.Init.NbrOfDiscConversion = 1, \
.Init.ExternalTrigConv = ADC_SOFTWARE_START, \
}
#else
#define ADC1_CONFIG \
{ \
.Instance = ADC1, \
.Init.DataAlign = ADC_DATAALIGN_RIGHT, \
.Init.ScanConvMode = ADC_SCAN_DISABLE, \
.Init.ContinuousConvMode = DISABLE, \
.Init.NbrOfConversion = 1, \
.Init.DiscontinuousConvMode = DISABLE, \
.Init.NbrOfDiscConversion = 1, \
.Init.ExternalTrigConv = ADC_SOFTWARE_START, \
}
#endif //BSP_USING_ADC_DMA
adc.h中
typedef enum
{
RT_ADC_CMD_ENABLE,
RT_ADC_CMD_DISABLE,
#ifdef BSP_USING_ADC_DMA
RT_ADC_CMD_SET_DMA_BUF,
#endif
} rt_adc_cmd_t;
2. 在stm32_adc_init()中,直接对adc进行初始化
#ifdef BSP_USING_ADC_DMA
int j;
ADC_ChannelConfTypeDef sConfig = {0};
rt_uint32_t chn[BSP_ADC_TOTAL_CHANNELS]=BSP_ADC_CHN_DEF;
sConfig.SamplingTime = BSP_ADC_SAMPLE_CYC_DEF;
for(j=0; j<BSP_ADC_TOTAL_CHANNELS; j++)
{
sConfig.Channel = stm32_adc_get_channel(chn[j]);
sConfig.Rank = j+1;
if(HAL_ADC_ConfigChannel(&stm32_adc_obj[i].ADC_Handler, &sConfig) != HAL_OK)
{
rt_kprintf("channel error\n");
Error_Handler();
}
}
#endif
3. 在adc接口ops结构体中,添加set_buf接口,用于设置一个接收DMA数据缓存buffer。
struct rt_adc_ops
{
rt_err_t (*enabled)(struct rt_adc_device *device, rt_uint32_t channel, rt_bool_t enabled);
rt_err_t (*convert)(struct rt_adc_device *device, rt_uint32_t channel, rt_uint32_t *value);
#ifdef BSP_USING_ADC_DMA
rt_err_t (*set_buf)(struct rt_adc_device *device, rt_uint16_t *dma_buf);
#endif
};
_adc_control()中添加命令
#ifdef BSP_USING_ADC_DMA
case RT_ADC_CMD_SET_DMA_BUF:
result = adc->ops->set_buf(adc, (rt_uint16_t *)args);
break;
#endif
4. 在adc设备对象中,添加buffer指针,通过ops接口set_buf将指针指向用户建立的buffer。
struct rt_adc_device
{
struct rt_device parent;
const struct rt_adc_ops *ops;
#ifdef BSP_USING_ADC_DMA
rt_uint16_t *dma_buf;
#endif
};
rt_err_t stm32_set_dma_rxbuf(struct rt_adc_device *device, rt_uint16_t *dma_buf)
{
if(dma_buf == RT_NULL) return -RT_ERROR;
device->dma_buf = dma_buf;
return RT_EOK;
}
static const struct rt_adc_ops stm_adc_ops =
{
.enabled = stm32_adc_enabled,
.convert = stm32_get_adc_value,
.set_buf = stm32_set_dma_rxbuf,
};
5.在stm32_adc_enabled使能函数中,添加DMA使能
#ifdef BSP_USING_ADC_DMA
if(device->dma_buf != RT_NULL)
{
HAL_ADCEx_Calibration_Start(stm32_adc_handler);
HAL_ADC_Start_DMA(stm32_adc_handler, (rt_uint32_t *)device->dma_buf, BSP_ADC_TOTAL_CHANNELS*BSP_DMA_BUF_COUNT);
}
else
{
rt_kprintf("ADC DMA buffer not set!\n");
}
#else
__HAL_ADC_ENABLE(stm32_adc_handler);
#endif
使用流程
所以,根据思路,流程有四步:
1.查找adc,rt_device_find
2.control接口设置buf,rt_device_control
3.control接口使能,rt_device_control
4.取buf数据计算。因为数据一直由DMA更新到buffer中,所以不需要使用read的ops来读数据。
注:没有为关闭adc做任何改动,因为一般实际项目中,都是一直采集adc,需要关闭的自行研究
最后
这样做有点脱离rtthread的设备驱动框架的感觉,跟使用裸机的时候差不多,只是通过设备驱动初始化和配置而已。
改进思路,
可以在adc enable中做个定时器或者线程,循环计算数据,然后通过rt_device_read来获取计算好的值或者原始值。计算函数可以通过control设置。
本意提供一种思路供大家参考,代码有问题别找我,因为我在项目中调试通过了的,但如果有更好的思路请告诉我,841389568@qq.com
对了,用Cube配置ADC时,同时要配置好DMA,然后放到board.c中。具体配置,改天更新出来。
//---------------------------20-7-24更新DMA的HAL配置--------------//
void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(hadc->Instance==ADC1)
{
/* USER CODE BEGIN ADC1_MspInit 0 */
/* USER CODE END ADC1_MspInit 0 */
/* Peripheral clock enable */
__HAL_RCC_ADC1_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/**ADC1 GPIO Configuration
PC0 ------> ADC1_IN10
PC1 ------> ADC1_IN11
PC2 ------> ADC1_IN12
PC3 ------> ADC1_IN13
PA0-WKUP ------> ADC1_IN0
PA1 ------> ADC1_IN1
PA2 ------> ADC1_IN2
PA3 ------> ADC1_IN3
PA4 ------> ADC1_IN4
PA5 ------> ADC1_IN5
PA6 ------> ADC1_IN6
PA7 ------> ADC1_IN7
PC4 ------> ADC1_IN14
PC5 ------> ADC1_IN15
*/
GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3
|GPIO_PIN_4|GPIO_PIN_5;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3
|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
#ifdef BSP_USING_ADC_DMA
extern DMA_HandleTypeDef hdma_adc1;//在drv_adc.c里面定义了的变量
__HAL_RCC_DMA1_CLK_ENABLE();
/* ADC1 DMA Init */
/* ADC1 Init */
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;
if (HAL_DMA_Init(&hdma_adc1) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(hadc,DMA_Handle,hdma_adc1);
#endif //BSP_USING_ADC_DMA
/* USER CODE BEGIN ADC1_MspInit 1 */
/* USER CODE END ADC1_MspInit 1 */
}
}
代码下载
还有些小改动未在文中提及,让我赚两个积分吧,太缺积分,手动比心
https://download.youkuaiyun.com/download/qq_30659437/12598589