前言
??ADC(Analog to Digital Converter)模数转换器,它的作用是将外界的模拟信号转化为离散数字信号,什么是外界的模拟信号呢?比较好理解,例如温湿度,无线信号的RSSI强度,声光,角度等等。对于一些低端的微控制器,片上只能依靠另外的ADC转换芯片实现模拟信号的采集,对于一些好一点的单片机,基本上都拥有片上ADC资源。ADC的转换一般过程是:取样->保持和量化->数字编码。 ??本文为RT_thread操作系统下的ADC设备学习笔记,使用stm32来掌握RT_thread系统下的ADC设备编程思路,通过对ADC设备的学习,我们最终能使用IO设备接口来访问ADC。
1 ADC设备驱动层
1.1 ADC的配置参数
??对于STM32的片上ADC,配置参数主要有ADC时钟的分频因子,转换分辨率,数据对齐,采样的模式,规则通道的采样等级等等,这里不做记录,令ADC1的配置如下:
#define ADC1_CONFIG \
{ \
.Instance = ADC1, \
.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4, \
.Init.Resolution = ADC_RESOLUTION_12B, \
.Init.DataAlign = ADC_DATAALIGN_RIGHT, \
.Init.ScanConvMode = ADC_SCAN_DISABLE, \
.Init.EOCSelection = ADC_EOC_SINGLE_CONV, \
.Init.LowPowerAutoWait = DISABLE, \
.Init.ContinuousConvMode = DISABLE, \
.Init.NbrOfConversion = 1, \
.Init.DiscontinuousConvMode = DISABLE, \
.Init.NbrOfDiscConversion = 1, \
.Init.ExternalTrigConv = ADC_SOFTWARE_START, \
.Init.DMAContinuousRequests = DISABLE, \
.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN, \
}
static ADC_HandleTypeDef adc_config[] =
{
#ifdef BSP_USING_ADC1
ADC1_CONFIG,
#endif
}
??RT_thread中先定义一个adc的底层结构体,如下:
struct stm32_adc
{
ADC_HandleTypeDef ADC_Handler;
struct rt_adc_device stm32_adc_device;
};
??这里主要看第二个成员,RT_thread给ADC做了一个设备结构体,定义如下:
struct rt_adc_device
{
struct rt_device parent;
const struct rt_adc_ops *ops;
};
typedef struct rt_adc_device *rt_adc_device_t;
??ADC的操作函数只有两个,使能控制函数和转换控制函数:
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);
};
(1)使能函数
static rt_err_t stm32_adc_enabled(struct rt_adc_device *device, rt_uint32_t channel, rt_bool_t enabled)
{
ADC_HandleTypeDef *stm32_adc_handler = device->parent.user_data;
RT_ASSERT(device != RT_NULL);
if (enabled)
{
ADC_Enable(stm32_adc_handler);
}
else
{
ADC_Disable(stm32_adc_handler);
}
return RT_EOK;
}
(2)转换函数
static rt_err_t stm32_get_adc_value(struct rt_adc_device *device, rt_uint32_t channel, rt_uint32_t *value)
{
ADC_ChannelConfTypeDef ADC_ChanConf;
ADC_HandleTypeDef *stm32_adc_handler = device->parent.user_data;
RT_ASSERT(device != RT_NULL);
RT_ASSERT(value != RT_NULL);
rt_memset(&ADC_ChanConf, 0, sizeof(ADC_ChanConf));
if (channel <= 18)
{
ADC_ChanConf.Channel = stm32_adc_get_channel(channel);
}
else
{
LOG_E("ADC channel must be between 0 and 18.");
return -RT_ERROR;
}
ADC_ChanConf.Rank = 1;
ADC_ChanConf.SamplingTime = ADC_SAMPLETIME_247CYCLES_5;
ADC_ChanConf.Offset = 0;
ADC_ChanConf.OffsetNumber = ADC_OFFSET_NONE;
ADC_ChanConf.SingleDiff = LL_ADC_SINGLE_ENDED;
HAL_ADC_ConfigChannel(stm32_adc_handler, &ADC_ChanConf);
HAL_ADC_Start(stm32_adc_handler);
HAL_ADC_PollForConversion(stm32_adc_handler, 100);
*value = (rt_uint32_t)HAL_ADC_GetValue(stm32_adc_handler);
return RT_EOK;
}
??下面看看RT_thread下的ADC初始化代码是怎么写的:
static struct stm32_adc stm32_adc_obj[sizeof(adc_config) / sizeof(adc_config[0])];
static int stm32_adc_init(void)
{
int result = RT_EOK;
char name_buf[5] = {'a', 'd', 'c', '0', 0};
int i = 0;
for (i = 0; i < sizeof(adc_config) / sizeof(adc_config[0]); i++)
{
name_buf[3] = '0';
stm32_adc_obj[i].ADC_Handler = adc_config[i];
#if defined(ADC1)
if (stm32_adc_obj[i].ADC_Handler.Instance == ADC1)
{
name_buf[3] = '1';
}
#endif
#if defined(ADC2)
if (stm32_adc_obj[i].ADC_Handler.Instance == ADC2)
{
name_buf[3] = '2';
}
#endif
#if defined(ADC3)
if (stm32_adc_obj[i].ADC_Handler.Instance == ADC3)
{
name_buf[3] = '3';
}
#endif
if (HAL_ADC_Init(&stm32_adc_obj[i].ADC_Handler) != HAL_OK)
{
LOG_E("%s init failed", name_buf);
result = -RT_ERROR;
}
else
{
if (rt_hw_adc_register(&stm32_adc_obj[i].stm32_adc_device, name_buf, &stm_adc_ops, &stm32_adc_obj[i].ADC_Handler) == RT_EOK)
{
LOG_D("%s init success", name_buf);
}
else
{
LOG_E("%s register failed", name_buf);
result = -RT_ERROR;
}
}
}
return result;
}
2 ADC设备框架驱动层
??上面的注册ADC设备函数rt_hw_adc_register就属于ADC设备框架驱动层。其函数原型如下:
rt_err_t rt_hw_adc_register(rt_adc_device_t device, const char *name, const struct rt_adc_ops *ops, const void *user_data)
{
rt_err_t result = RT_EOK;
RT_ASSERT(ops != RT_NULL && ops->convert != RT_NULL);
device->parent.type = RT_Device_Class_Miscellaneous;
device->parent.rx_indicate = RT_NULL;
device->parent.tx_complete = RT_NULL;
device->parent.init = RT_NULL;
device->parent.open = RT_NULL;
device->parent.close = RT_NULL;
device->parent.read = _adc_read;
device->parent.write = RT_NULL;
device->parent.control = _adc_control;
device->ops = ops;
device->parent.user_data = (void *)user_data;
result = rt_device_register(&device->parent, name, RT_DEVICE_FLAG_RDWR);
return result;
}
??在ADC设备驱动层的使能函数和获取转换结果函数中,有这么一句:
ADC_HandleTypeDef *stm32_adc_handler = device->parent.user_data;
??所以在注册ADC设备的时候,函数rt_hw_adc_register的形参user_data必须选择传入&stm32_adc_obj[i].ADC_Handler。 另外,上面的parent还赋值了两个函数,它们的原型如下:
static rt_size_t _adc_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size)
{
rt_err_t result = RT_EOK;
rt_size_t i;
struct rt_adc_device *adc = (struct rt_adc_device *)dev;
rt_uint32_t *value = (rt_uint32_t *)buffer;
for (i = 0; i < size; i += sizeof(int))
{
result = adc->ops->convert(adc, pos + i, value);
if (result != RT_EOK)
{
return 0;
}
value++;
}
return i;
}
??注意:其中的形参pos是转换的通道。
static rt_err_t _adc_control(rt_device_t dev, int cmd, void *args)
{
rt_err_t result = RT_EOK;
rt_adc_device_t adc = (struct rt_adc_device *)dev;
if (adc->ops->enabled == RT_NULL)
{
return -RT_ENOSYS;
}
if (cmd == RT_ADC_CMD_ENABLE)
{
result = adc->ops->enabled(adc, (rt_uint32_t)args, RT_TRUE);
}
else if (cmd == RT_ADC_CMD_DISABLE)
{
result = adc->ops->enabled(adc, (rt_uint32_t)args, RT_FALSE);
}
return result;
}
3 ADC应用层
??ADC的应用层相对简单,即使用IO设备函数接口访问ADC设备。下面直接实践测试,使用ADC1的通道13来做测试,采集通道引脚的电压信号,打印到控制台。 线程如下:
#define ADC_transfer_channel 13
#define CONVERT_BITS (1<<12)
#define REFER_VOLTAGE 330
void ADCtest_task(void *parameter)
{
rt_uint32_t l_raw_adc_val;
rt_uint32_t vol;
rt_device_t adc_device = rt_device_find("adc1");
if(RT_NULL != adc_device)
{
LOG_D("adc1 device has exited");
adc_device->control(adc_device, RT_ADC_CMD_ENABLE, RT_NULL);
}
while(1)
{
adc_device->read(adc_device, ADC_transfer_channel, &l_raw_adc_val, sizeof(l_raw_adc_val));
vol = l_raw_adc_val * REFER_VOLTAGE / CONVERT_BITS;
rt_kprintf("vol :%d.%02d \n", vol / 100, vol % 100);
rt_thread_mdelay(500);
}
}
打开串口助手可以看到: ??成功创建一个ADC线程。 ??成功注册了adc1设备,并开始采集电压信号。 注意:函数rt_kprintf不能打印浮点型数据。
|