本文介绍了如何在 RT-Thread Studio 上使用 RT-Thread Nano,并基于 BearPI-IOT STM32L431RCT6 的基础工程进行讲解如何使用 ADC 设备接口。
BearPI-IOT board
为什么需要设备接口
RT-Thread 分为标准版本和 Nano 版本,其特点如下:
- RT-Thread 标准版:拥有驱动框架,软件包等组件,软件包都是基于设备驱动接口来实现。
- RT-Thread Nano :仅仅只是一个 RTOS 内核。没有任何组件。
- Nano 是无法直接使用 RT-Thread 丰富软件包功能。
- Nano 是一个面向低资源的 MCU 等芯片,不可能增加如同标准版的设备驱动框架。
- Nano 需要一套统一设备驱动 API,屏蔽不同芯片的 HAL 层的区别。方便移植工程到不同的平台。
- Nano 需要一套设备驱动 API,可以方便使用丰富软件包组件。
准备工作
- 使用 RT-Thread Studio 建立一个 STM32L431RCT6 的 RT-Thread Nano 基础工程。
- 基础工程创建可参考:在 RT-Thread Studio 上使用 RT-Thread Nano
ADC 设备接口
- 在 RT-Thread 标准版中,ADC设备驱动提供了一套设备管理接口来访问 ADC,用户程序可以直接使用该 API 操作 ADC 的功能,设备管理接口如下:
- 由于 RT-Thread Nano 不使用设备驱动框架,所以没有对应的 rt_device_find() 这个API获取设备对象。为了能够与 RT-Thread 标准版的接口相近,我们需要做了简单的修改,设备管理接口如下:
- 对于 RT-Thread Nano,只需要适配如上这套 API,便可简单修改后使用 RT-Thread 丰富软件包功能。
适配 ADC 设备驱动接口
- 复制 RT-Thread 完整版工程中的 adc.h 文件(路径:rt-thread\components\drivers\include\drivers\adc.h)到我们准备好的 STM32L431RCT6 的 RT-Thread Nano 基础工程中。
- 由于 RT-Thread Nano 没有驱动框架,所以我们要把 adc.h 中有关完整版的内容去掉。整理完之后的 adc.h 文件如下:
/*
* Copyright (c) 2006-2021, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2021-08-22 RiceChen first version
*/
#ifndef __ADC_H__
#define __ADC_H__
#include <rtthread.h>
struct rt_adc_device
{
uint8_t *user_data;
};
typedef struct rt_adc_device *rt_adc_device_t;
typedef enum
{
RT_ADC_CMD_ENABLE,
RT_ADC_CMD_DISABLE,
} rt_adc_cmd_t;
struct rt_adc_device *rt_adc_device_find(const char *name);
rt_uint32_t rt_adc_read(rt_adc_device_t dev, rt_uint32_t channel);
rt_err_t rt_adc_enable(rt_adc_device_t dev, rt_uint32_t channel);
rt_err_t rt_adc_disable(rt_adc_device_t dev, rt_uint32_t channel);
#endif /* __ADC_H__ */
- 我们需要适配如上4个 ADC 设备 API,参考实例:drv_adc.c 和 drv_adc.h。
- drv_adc.c实例:
/*
* Copyright (c) 2006-2021, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2021-08-21 RiceChen the first version
*/
#include <board.h>
#include "drv_adc.h"
#ifdef RT_USING_ADC
struct rt_i2c_config
{
char *name;
ADC_HandleTypeDef ADC_Handler;
};
struct rt_i2c_config adc_config[] =
{
#ifdef RT_USING_ADC1
ADC1_CONFIG,
#endif
};
struct stm32_adc
{
struct rt_i2c_config *config;
struct rt_adc_device stm32_adc_device;
};
static struct stm32_adc stm32_adc_obj[sizeof(adc_config) / sizeof(adc_config[0])];
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;
RT_ASSERT(device != RT_NULL);
stm32_adc_handler = (ADC_HandleTypeDef *)device->user_data;
rt_kprintf("%d: 0x%08x\r\n", __LINE__, stm32_adc_handler);
if (enabled)
{
ADC_Enable(stm32_adc_handler);
}
else
{
ADC_Disable(stm32_adc_handler);
}
return RT_EOK;
}
static rt_uint32_t stm32_adc_get_channel(rt_uint32_t channel)
{
rt_uint32_t stm32_channel = 0;
switch (channel)
{
case 0:
stm32_channel = ADC_CHANNEL_0;
break;
case 1:
stm32_channel = ADC_CHANNEL_1;
break;
case 2:
stm32_channel = ADC_CHANNEL_2;
break;
case 3:
stm32_channel = ADC_CHANNEL_3;
break;
case 4:
stm32_channel = ADC_CHANNEL_4;
break;
case 5:
stm32_channel = ADC_CHANNEL_5;
break;
case 6:
stm32_channel = ADC_CHANNEL_6;
break;
case 7:
stm32_channel = ADC_CHANNEL_7;
break;
case 8:
stm32_channel = ADC_CHANNEL_8;
break;
case 9:
stm32_channel = ADC_CHANNEL_9;
break;
case 10:
stm32_channel = ADC_CHANNEL_10;
break;
case 11:
stm32_channel = ADC_CHANNEL_11;
break;
case 12:
stm32_channel = ADC_CHANNEL_12;
break;
case 13:
stm32_channel = ADC_CHANNEL_13;
break;
case 14:
stm32_channel = ADC_CHANNEL_14;
break;
case 15:
stm32_channel = ADC_CHANNEL_15;
break;
}
return stm32_channel;
}
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;
RT_ASSERT(device != RT_NULL);
RT_ASSERT(value != RT_NULL);
stm32_adc_handler = (ADC_HandleTypeDef *)device->user_data;
rt_memset(&ADC_ChanConf, 0, sizeof(ADC_ChanConf));
if (channel <= 18)
{
/* set stm32 ADC channel */
ADC_ChanConf.Channel = stm32_adc_get_channel(channel);
}
else
{
rt_kprintf("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);
if (HAL_ADCEx_Calibration_Start(stm32_adc_handler, ADC_ChanConf.SingleDiff) != HAL_OK)
{
rt_kprintf("ADC calibration error!\n");
return -RT_ERROR;
}
/* start ADC */
HAL_ADC_Start(stm32_adc_handler);
/* Wait for the ADC to convert */
HAL_ADC_PollForConversion(stm32_adc_handler, 100);
/* get ADC value */
*value = (rt_uint32_t)HAL_ADC_GetValue(stm32_adc_handler);
return RT_EOK;
}
rt_uint32_t rt_adc_read(rt_adc_device_t dev, rt_uint32_t channel)
{
rt_uint32_t value;
RT_ASSERT(dev);
stm32_get_adc_value(dev, channel, &value);
return value;
}
rt_err_t rt_adc_enable(rt_adc_device_t dev, rt_uint32_t channel)
{
rt_err_t result = RT_EOK;
RT_ASSERT(dev);
result = stm32_adc_enabled(dev, channel, RT_TRUE);
return result;
}
rt_err_t rt_adc_disable(rt_adc_device_t dev, rt_uint32_t channel)
{
rt_err_t result = RT_EOK;
RT_ASSERT(dev);
result = stm32_adc_enabled(dev, channel, RT_FALSE);
return result;
}
struct rt_adc_device *rt_adc_device_find(const char *name)
{
int i = 0;
for (i = 0; i < sizeof(adc_config) / sizeof(adc_config[0]); i++)
{
if(rt_strncmp(stm32_adc_obj[i].config->name, name, RT_NAME_MAX) == 0)
{
return &stm32_adc_obj[i].stm32_adc_device;
}
}
return RT_NULL;
}
static int rt_hw_adc_init(void)
{
int i = 0;
for (i = 0; i < sizeof(adc_config) / sizeof(adc_config[0]); i++)
{
stm32_adc_obj[i].config = &adc_config[i];
stm32_adc_obj[i].stm32_adc_device.user_data = (uint8_t *)&stm32_adc_obj[i].config->ADC_Handler;
rt_kprintf("%d: 0x%08x\r\n", __LINE__, &stm32_adc_obj[i].config->ADC_Handler);
rt_kprintf("%d: 0x%08x\r\n", __LINE__, ADC1);
rt_kprintf("%d: 0x%08x\r\n", __LINE__, stm32_adc_obj[i].config->ADC_Handler.Instance);
if (HAL_ADC_Init(&stm32_adc_obj[i].config->ADC_Handler) != HAL_OK)
{
rt_kprintf("%s init failed", stm32_adc_obj[i].config->name);
return -RT_ERROR;
}
}
return RT_EOK;
}
INIT_APP_EXPORT(rt_hw_adc_init);
void adc_get_obj(void)
{
int32_t value = 0;
struct rt_adc_device *dev = RT_NULL;
dev = rt_adc_device_find("adc1");
if(dev == RT_NULL)
{
rt_kprintf("%s not found\r\n", "adc1");
return;
}
else
{
rt_adc_enable(dev, 3);
value = rt_adc_read(dev, 3);
rt_kprintf("adc value: %d\r\n", value);
}
}
MSH_CMD_EXPORT(adc_get_obj, adc_get_obj);
#endif /* RT_USING_ADC */
- drv_adc.h实例:
/*
* Copyright (c) 2006-2021, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2021-04-20 RiceChen first version
*/
#ifndef __DRV_ADC_H__
#define __DRV_ADC_H__
#include <drv_common.h>
#include <board.h>
#include "adc.h"
#ifdef __cplusplus
extern "C" {
#endif
#ifdef RT_USING_ADC1
#ifndef ADC1_CONFIG
#define ADC1_CONFIG \
{ \
.name = "adc1", \
.ADC_Handler.Instance = ADC1, \
.ADC_Handler.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4, \
.ADC_Handler.Init.Resolution = ADC_RESOLUTION_12B, \
.ADC_Handler.Init.DataAlign = ADC_DATAALIGN_RIGHT, \
.ADC_Handler.Init.ScanConvMode = DISABLE, \
.ADC_Handler.Init.EOCSelection = DISABLE, \
.ADC_Handler.Init.ContinuousConvMode = DISABLE, \
.ADC_Handler.Init.NbrOfConversion = 1, \
.ADC_Handler.Init.DiscontinuousConvMode = DISABLE, \
.ADC_Handler.Init.NbrOfDiscConversion = 0, \
.ADC_Handler.Init.ExternalTrigConv = ADC_SOFTWARE_START, \
.ADC_Handler.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE, \
.ADC_Handler.Init.DMAContinuousRequests = DISABLE, \
}
#endif /* ADC1_CONFIG */
#endif /* RT_USING_ADC1 */
#ifdef __cplusplus
}
#endif
#endif /* __DRV_ADC_H__ */
编写 ADC 设备使用示例
void adc_test(void)
{
int32_t value = 0;
struct rt_adc_device *dev = RT_NULL;
dev = rt_adc_device_find("adc1");
if(dev == RT_NULL)
{
rt_kprintf("%s not found\r\n", "adc1");
return;
}
else
{
rt_adc_enable(dev, 3);
value = rt_adc_read(dev, 3);
rt_kprintf("adc value: %d\r\n", value);
}
}
MSH_CMD_EXPORT(adc_test, adc test);
- 实例代码运行现象:
msh >adc_test
adc value: 565
msh >
总结
- 通过适配PIN设备接口,我们可以无缝对接到软件包的使用。
- 对于低资源的芯片使用 Nano 并且能够使用 RT-THREAD 丰富的软件,无疑是一个非常完美的做法。也没有庞大的驱动框架。
- 通过这样的方式,学习完 RT-THREAD Nano 在转移到 RT-THREAD 标准版的学习,更加简单方便。
首发:Rice 嵌入式开发技术分享
作者:RiceDIY
推荐阅读
- RT-Thread Nano如何适配I2C设备API,并在RT-Thread Nano使用软件包
- RT-Thread Nano如何适配pin设备API,并在RT-Thread Nano使用软件包
- 教你动手移植RT-Thread到国产MCU
更多嵌入式技术干货请关注Rice 嵌入式开发技术分享