Rice我叫加饭? · 2021年09月08日

RT-Thread Nano如何适配ADC设备API

本文介绍了如何在 RT-Thread Studio 上使用 RT-Thread Nano,并基于 BearPI-IOT STM32L431RCT6 的基础工程进行讲解如何使用 ADC 设备接口。

image.png
BearPI-IOT board

为什么需要设备接口

  1. RT-Thread 分为标准版本和 Nano 版本,其特点如下:

    • RT-Thread 标准版:拥有驱动框架,软件包等组件,软件包都是基于设备驱动接口来实现。
    • RT-Thread Nano :仅仅只是一个 RTOS 内核。没有任何组件。
  2. Nano 是无法直接使用 RT-Thread 丰富软件包功能。
  3. Nano 是一个面向低资源的 MCU 等芯片,不可能增加如同标准版的设备驱动框架。
  4. Nano 需要一套统一设备驱动 API,屏蔽不同芯片的 HAL 层的区别。方便移植工程到不同的平台。
  5. Nano 需要一套设备驱动 API,可以方便使用丰富软件包组件。

准备工作

  1. 使用 RT-Thread Studio 建立一个 STM32L431RCT6 的 RT-Thread Nano 基础工程。
  2. 基础工程创建可参考:在 RT-Thread Studio 上使用 RT-Thread Nano

ADC 设备接口

  1. 在 RT-Thread 标准版中,ADC设备驱动提供了一套设备管理接口来访问 ADC,用户程序可以直接使用该 API 操作 ADC 的功能,设备管理接口如下:

image.png

  1. 由于 RT-Thread Nano 不使用设备驱动框架,所以没有对应的 rt_device_find() 这个API获取设备对象。为了能够与 RT-Thread 标准版的接口相近,我们需要做了简单的修改,设备管理接口如下:

image.png

  1. 对于 RT-Thread Nano,只需要适配如上这套 API,便可简单修改后使用 RT-Thread 丰富软件包功能。

适配 ADC 设备驱动接口

  1. 复制 RT-Thread 完整版工程中的 adc.h 文件(路径:rt-thread\components\drivers\include\drivers\adc.h)到我们准备好的 STM32L431RCT6 的 RT-Thread Nano 基础工程中。
  2. 由于 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__ */
  1. 我们需要适配如上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);
  1. 实例代码运行现象:
msh >adc_test
adc value: 565
msh >

总结

  • 通过适配PIN设备接口,我们可以无缝对接到软件包的使用。
  • 对于低资源的芯片使用 Nano 并且能够使用 RT-THREAD 丰富的软件,无疑是一个非常完美的做法。也没有庞大的驱动框架。
  • 通过这样的方式,学习完 RT-THREAD Nano 在转移到 RT-THREAD 标准版的学习,更加简单方便。
首发:Rice 嵌入式开发技术分享
作者:RiceDIY

推荐阅读

更多嵌入式技术干货请关注Rice 嵌入式开发技术分享
推荐阅读
关注数
1761
内容数
51
一个周末很无聊的嵌入式软件工程师,写写经验,写写总结。
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息