理解nordic ncs设备驱动模型

一、 Zephyr Project介绍

Zephyr Project是Linux基金会推出的一个Apache2.0开源项目,版权非常友好,适合用于商业项目开发。包含RTOS、编译系统、各类第三方库。NCS中的例程基本都跑在Zephyr RTOS上,Zephyr不单单是一个用来做多线程的RTOS,它更大的价值在于其自带的各种开源的协议栈、框架、软件包、驱动代码等。如果不是为了使用这些现成的协议栈和软件包,只是单纯使用RTOS,就和其他RTOS没有区别了。

Zephyr采用Kconfig对这些软件包进行管理,可以方便地使能或剪裁。而为了使Zephyr自带的硬件驱动代码能够通用,Zephyr采用了DeviceTree来描述硬件。各个半导体厂商把自己的硬件描述成标准DeviceTree,并且按照Zephyr的接口提供驱动代码,然后一起提交给Zephyr。在方便地使用Zephyr中协议栈的同时,用户还能简单方便地使用到各个半导体厂家的硬件功能。

二、SPIM使用示例

1. prj.conf(系统剪裁)

#使能SPI驱动器
CONFIG_SPI=y
#如果使用SPI ASYNC
CONFIG_SPI_ASYNC=y   

#使能RTT控制台
CONFIG_CONSOLE=n
CONFIG_UART_CONSOLE=n
CONFIG_LOG=y
CONFIG_USE_SEGGER_RTT=y
CONFIG_LOG_BACKEND_RTT=y
CONFIG_LOG_BACKEND_UART=n

2.设备树配置

//pinctrl 是一个模拟节点,用于引脚分配
&pinctrl {
    spi_master_default: spi_master_default {
        group1 {
            psels = <NRF_PSEL(SPIM_SCK, 0, 4)>,
                    <NRF_PSEL(SPIM_MOSI, 0, 5)>,
                    <NRF_PSEL(SPIM_MISO, 0, 6)>;
        };
    };

    spi_master_sleep: spi_master_sleep {
        group1 {
            psels = <NRF_PSEL(SPIM_SCK, 0, 4)>,
                    <NRF_PSEL(SPIM_MOSI, 0, 5)>,
                    <NRF_PSEL(SPIM_MISO, 0, 6)>;
            low-power-enable;
        };
    };
};

//配置SPI1设备节点信息
my_spi_master: &spi1 {
    compatible = "nordic,nrf-spim";
    status = "okay";
    pinctrl-0 = <&spi_master_default>;
    pinctrl-1 = <&spi_master_sleep>;
    pinctrl-names = "default", "sleep";
    cs-gpios = <&gpio0 7 GPIO_ACTIVE_LOW>;
    reg_my_spi_master:  spi-dev-a@0 {
        reg = <0>;
    };
};

3.spim_driver.c编写


#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/spi.h>
#include <zephyr/logging/log.h>

/* 1000 msec = 1 sec */
#define SLEEP_TIME_MS   200

//
#define MY_SPI_MASTER DT_NODELABEL(my_spi_master)
#define MY_SPI_MASTER_CS_DT_SPEC SPI_CS_GPIOS_DT_SPEC_GET(DT_NODELABEL(reg_my_spi_master))

//定义一个device,并通过设备树获取设备节点
const struct device *spi_dev =  DEVICE_DT_GET(MY_SPI_MASTER);

//RTT logger info
#define LOG_MODULE_NAME spi_master
LOG_MODULE_REGISTER(LOG_MODULE_NAME);

/**
 * @brief 初始化SPI设备
 * 
 * 该函数用于初始化SPI主设备及其相关的片选GPIO引脚。
 * 主要包括获取设备句柄、检查设备是否就绪等操作。
 * 
 * @param 无
 * 
 * @return 无
 */
static void spi_master_init(void)
{
    //spi_dev = DEVICE_DT_GET(MY_SPI_MASTER);
    if(!device_is_ready(spi_dev)) {
        printk("SPI master device not ready!\n");
    }
    struct gpio_dt_spec spim_cs_gpio = MY_SPI_MASTER_CS_DT_SPEC;
    if(!device_is_ready(spim_cs_gpio.port)){
        printk("SPI master chip select device not ready!\n");
    }
}

static struct spi_config spi_cfg = {
    .operation = SPI_WORD_SET(8) | SPI_TRANSFER_MSB,
    .frequency = 4000000,
    .slave = 0,
    //通过reg_my_spi_master找到父节点(spi1),并获取cs-gpios节点spec
    //.cs = {.gpio = MY_SPI_MASTER_CS_DT_SPEC, .delay = 0},
    //或者通过父节点找到cs_gpios节点信息
    .cs = {
    .gpio = {
        .port = DEVICE_DT_GET(DT_GPIO_CTLR(DT_NODELABEL(my_spi_master), cs_gpios)),
        .pin = DT_GPIO_PIN(DT_NODELABEL(my_spi_master), cs_gpios),
        .dt_flags = GPIO_ACTIVE_LOW,
    },
    .delay = 0,
    }
};

/**
 * @brief 通过SPI总线同时发送和接收数据
 * 
 * @param tx_buffer 指向发送数据缓冲区的指针
 * @param rx_buffer 指向接收数据缓冲区的指针
 * @param len       要传输的数据长度(字节数)
 * 
 * @return 返回spi_transceive函数的执行结果,通常为0表示成功,负值表示失败
 * 
 * 该函数使用SPI全双工通信方式,同时发送和接收指定长度的数据。
 * 发送数据来自tx_buffer,接收到的数据存储在rx_buffer中。
 */
static int spim_write_read_data(uint8_t *tx_buffer, uint8_t *rx_buffer, size_t len)
{

    const struct spi_buf tx_buf = {
        .buf = tx_buffer,
        .len = len,
    };
    const struct spi_buf_set tx = {
        .buffers = &tx_buf,
        .count = 1
    };

    struct spi_buf rx_buf = {
        .buf = rx_buffer,
        .len = len,
    };
    const struct spi_buf_set rx = {
        .buffers = &rx_buf,
        .count = 1
    };
    return spi_transceive(spi_dev, &spi_cfg, &tx, &rx);
}

int main(void)
{
    int ret;
    uint8_t tx_buffer[2] = {0x00,0x01};
    uint8_t rx_buffer[2];

    spi_master_init();

    LOG_INF("SPI master example started\n");

    while (1) {
        ret = spim_write_read_data(tx_buffer,rx_buffer,2);
        if(ret != 0){
            LOG_INF("SPI master error: %i\n", ret);
        }
        k_msleep(SLEEP_TIME_MS);
        LOG_INF("APP Running\r\n");
    }

    return 0;
}

4.spi发送实测波形

image
可以看到,CS引脚的拉低时间过长,约48us,后续会介绍通过nrfx配置寄存器的方式,直接配置spi驱动器,解决该问题。

三、IIC/TWI使用示例

1. prj.conf

CONFIG_I2C=y

2. 设备树配置

&i2c2 {
    status = "okay";
    clock-frequency = <I2C_BITRATE_FAST>;
    pinctrl-0 = < &i2c2_default >;
    pinctrl-1 = < &i2c2_sleep >;
    pinctrl-names = "default", "sleep";
    cw2215b: cw2215b@64 {
        compatible = "i2c-device";
        reg = <0x64>;
    };
};

//引脚分配,使用pinctrl节点
&pinctrl {
        i2c2_default: i2c2_default {
        group1 {
            psels = <NRF_PSEL(TWIM_SDA, 1, 2)>,
                <NRF_PSEL(TWIM_SCL, 1, 3)>;
            bias-pull-up;
            nordic,drive-mode = <NRF_DRIVE_S0D1>;               
        };
    };
    i2c2_sleep: i2c2_sleep {
        group1 {
            psels = <NRF_PSEL(TWIM_SDA, 1, 2)>,
                <NRF_PSEL(TWIM_SCL, 1, 3)>;
            low-power-enable;
        };
    };
};

3. iic_driver.c编写

#include "iic_driver.h"
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/logging/log.h>

#define LOG_MODULE_NAME iic_driver
LOG_MODULE_REGISTER(LOG_MODULE_NAME);

//获取iic设备设备树节点
const struct i2c_dt_spec CW2215_IICDev = I2C_DT_SPEC_GET(DT_NODELABEL(cw2215b));

//iic driver read
int iic_driver_read(uint8_t *pdata, int size)
{
    int ret = 0;

    ret = i2c_read_dt(&CW2215_IICDev, pdata, size);

    return ret;
}

//iic driver write
int iic_driver_write(uint8_t *pdata, int size)
{
    int ret = 0;

    ret = i2c_write_dt(&CW2215_IICDev, pdata, size);

    return ret;
}

//iic driver initial
int iic_driver_init(void)
{
    int ret = 0;

    ret = i2c_is_ready_dt(&CW2215_IICDev);
    if (ret == false) {
        LOG_ERR("I2C bus %s dev0 not ready", CW2215_IICDev.bus->name);
        return ret;
    }
    LOG_INF("I2C bus %s dev ready, slave addr = %x", CW2215_IICDev.bus->name, CW2215_IICDev.addr);
      
    return 0;
}

4. CW2215读写示例

int CW2215_Write_Data(uint8_t RegisterAddress, uint8_t *pData, uint32_t size)
{
    uint8_t buf[2];

    memset(buf, 0, 2);

    buf[0] = RegisterAddress;
    memcpy(&buf[1], pData, size);
    return iic_driver_write(buf,size+1);
}

void CW2215_Read_Data(uint8_t RegisterAddress, uint8_t *pData, uint8_t size)
{
    uint8_t cmd = RegisterAddress;

  iic_driver_write(&cmd, 1);

  iic_driver_read(pData, size);
}

四、ADC

1. prj.conf

## ADC example ##
CONFIG_ADC=y
CONFIG_ADC_ASYNC=y

2. 设备树配置

&adc {
    status = "okay";
    #address-cells = <1>;
    #size-cells = <0>;

    NTC1@0 {
        reg = <0>;
        zephyr,gain = "ADC_GAIN_1_6";
        zephyr,reference = "ADC_REF_INTERNAL";
        zephyr,acquisition-time = <ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 20)>;
        zephyr,input-positive = <NRF_SAADC_AIN1>;   //P0.05 for AIN1
        zephyr,vref-mv = <900>;
        zephyr,resolution = <12>;   
        zephyr,oversampling = <3>;                  
    };
};

//配置io-channel
/ {
    zephyr,user{
        io-channels             = <&adc 0>;
    };  
};

3. adc_driver.c编写

#include "adc_driver.h"

#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/logging/log.h>
#include <zephyr/drivers/adc.h>
#include <hal/nrf_saadc.h>

#define LOG_MODULE_NAME adc_driver
LOG_MODULE_REGISTER(LOG_MODULE_NAME);

K_MUTEX_DEFINE(ADCSample_Mutex);

#define DT_SPEC_AND_COMMA(node_id, prop, idx) \
    ADC_DT_SPEC_GET_BY_IDX(node_id, idx),

/* Data of ADC io-channels specified in devicetree. */
static const struct adc_dt_spec adc_channels[] = {
    DT_FOREACH_PROP_ELEM(DT_PATH(zephyr_user), io_channels, DT_SPEC_AND_COMMA)
};

uint16_t buf;
struct adc_sequence sequence = {
    .buffer = &buf,
    /* buffer size in bytes, not number of samples */
    .buffer_size = sizeof(buf),
};

//adc sampling 
int adc_sample_sync(int ChannelId, int *AdcValueMV)
{
    int err;

    k_mutex_lock(&ADCSample_Mutex, K_MSEC(1000));

    if(ChannelId >= ARRAY_SIZE(adc_channels))
    {
        LOG_ERR("adc_sample_sync ChannelId is invaild, ChannelId need to Less than 4\n");
        err = -1;
        return err;
    }

   // LOG_INF("- %s, channel %d: ", adc_channels[ChannelId].dev->name, adc_channels[ChannelId].channel_id);

    err = adc_sequence_init_dt(&adc_channels[ChannelId], &sequence);
    if(err)
    {
        LOG_ERR("adc_sequence_init_dt ChannelId[:%d] is fail\n", ChannelId);
        return err;
    }

    err = adc_read_dt(&adc_channels[ChannelId], &sequence);
    if (err < 0) 
    {
        LOG_ERR("adc_read_dt ChannelId[:%d] is fail\n", ChannelId);
        return err;
    }

    if (adc_channels[ChannelId].channel_cfg.differential) {
        *AdcValueMV = (int32_t)((int16_t)buf);
    } else {
        *AdcValueMV = (int32_t)buf;
    }
   // LOG_INF("AdcValueMV: %"PRId32, *AdcValueMV);
    err = adc_raw_to_millivolts_dt(&adc_channels[ChannelId], AdcValueMV);
    /* conversion to mV may not be supported, skip if not */
    if (err < 0) {
        LOG_ERR(" (value in mV not available)\n");
        return err;
    } else {
       // LOG_INF("AdcValueMV ChannelId[:%d] = %"PRId32" mV\n", ChannelId, *AdcValueMV);
    }

    k_mutex_unlock(&ADCSample_Mutex);
    return err;
}

int adc_driver_init(void)
{
    int err;
    
    /* Configure channels individually prior to sampling. */
    for (size_t i = 0U; i < ARRAY_SIZE(adc_channels); i++) {
        if (!device_is_ready(adc_channels[i].dev)) {
            LOG_ERR("ADC controller device %s not ready\n", adc_channels[i].dev->name);
            return -1;
        }
        LOG_INF("zephyr,gain[%d] = %d", i, adc_channels[i].channel_cfg.gain);
        LOG_INF("oversampling = %d",adc_channels[i].oversampling);
        err = adc_channel_setup_dt(&adc_channels[i]);
        if (err < 0) {
            LOG_ERR("Could not setup channel #%d (%d)\n", i, err);
            return -1;
        }
    }

    return 0;
}

五、NVS

在同时使用Ble和NVS时可能会导致区域重叠导致NVS写失败,需要pm_static约束

1. pm_static.yml

user_storage:
    address: 0xfe000
    size: 0x2000
    end_address: 0x100000
    placement:
    before:
    - end
    region: flash_primary

2. nvs_driver.c

#include "nvs_driver.h"

#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <string.h>
#include <zephyr/drivers/flash.h>
#include <zephyr/storage/flash_map.h>
#include <zephyr/fs/nvs.h>
#include <zephyr/settings/settings.h>
#include <zephyr/logging/log.h>

#define LOG_MODULE_NAME nvs_driver
LOG_MODULE_REGISTER(LOG_MODULE_NAME);

#define STORAGE_NODE_LABEL user_storage  //与pm_static一致
static struct nvs_fs UserFs;


//nvs driver initital
int nvs_driver_init(void)
{
    int rc;
    struct flash_pages_info info;

    UserFs.flash_device = FLASH_AREA_DEVICE(STORAGE_NODE_LABEL);
    if (!device_is_ready(UserFs.flash_device)) {
        printk("Flash device %s is not ready\n", UserFs.flash_device->name);
        return -EINVAL;
    }
    UserFs.offset = FLASH_AREA_OFFSET(STORAGE_NODE_LABEL);
    rc = flash_get_page_info_by_offs(UserFs.flash_device, UserFs.offset, &info);
    if (rc) {
        printk("Unable to get page info\n");
        return -EINVAL;
    }
    UserFs.sector_size = info.size;
    UserFs.sector_count = 2U;

    LOG_INF("NVS sector size=%d sector count=%d\n", UserFs.sector_size, UserFs.sector_count);
    
    rc = nvs_mount(&UserFs);
    if (rc)
    {
        LOG_ERR("NVS Init failed %d", rc);
        return -EINVAL;
    }

    return 0;
}

//nvs write api
int nvs_driver_write(uint16_t fsID, uint8_t *data, int len)
{
    int err = 0;
    err = nvs_write(&UserFs, fsID, data, len);
    if (err<0)
    {
        LOG_ERR("nvs write item id:%d fail, err=%d", fsID, err);
        return err;
    }
    
    return 0;
}

//nvs read api
int nvs_driver_read(uint16_t fsID, uint8_t *data, int len)
{
    int err = 0;
    /* KEY_ID is used to store a key, lets see if we can read it from flash
     */
    err = nvs_read(&UserFs, fsID, data, len);
    if (err != len)
    { /* item was found, show it */
        LOG_ERR("nvs read item id:%d fail, err=%d", fsID, err);
        return err;
    }

    return 0;
}

六、USB_CDC

1. prj.conf

#enable usb-cdc
CONFIG_USB_DEVICE_STACK=y
CONFIG_USB_DEVICE_PRODUCT="Zephyr CDC ACM sample DY"
CONFIG_USB_DEVICE_PID=0x0001

CONFIG_SERIAL=y
CONFIG_UART_INTERRUPT_DRIVEN=y
CONFIG_UART_LINE_CTRL=y

2. 设备树

&zephyr_udc0 {
    cdc_acm_uart0 {
        compatible = "zephyr,cdc-acm-uart";
    };
};

3. usb_cdc_driver.c

/*
 * Copyright (c) 2019 Intel Corporation
 *
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @file
 * @brief Sample echo app for CDC ACM class
 *
 * Sample app for USB CDC ACM class driver. The received data is echoed back
 * to the serial port.
 */

#include <stdio.h>
#include <string.h>
#include <zephyr/device.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/kernel.h>

#include <zephyr/usb/usb_device.h>
#include <zephyr/usb/usbd.h>
#include <zephyr/logging/log.h>
#include "usb_cdc_driver.h"
LOG_MODULE_REGISTER(cdc_acm_echo, LOG_LEVEL_INF);

const struct device *const uart_dev = DEVICE_DT_GET_ONE(zephyr_cdc_acm_uart);

static inline void print_baudrate(const struct device *dev)
{
    uint32_t baudrate;
    int ret;

    ret = uart_line_ctrl_get(dev, UART_LINE_CTRL_BAUD_RATE, &baudrate);
    if (ret) {
        LOG_WRN("Failed to get baudrate, ret code %d", ret);
    } else {
        LOG_INF("Baudrate %u", baudrate);
    }
}

static void interrupt_handler(const struct device *dev, void *user_data)
{
    ARG_UNUSED(user_data);

    while (uart_irq_update(dev) && uart_irq_is_pending(dev)) {
        if (uart_irq_rx_ready(dev)) {
            int recv_len;
            uint8_t buffer[256];
            recv_len = uart_fifo_read(dev, buffer, 256);
            if (recv_len < 0) {
                LOG_ERR("Failed to read UART FIFO");
                recv_len = 0;
            };

            LOG_INF("uart_fifo_read %d bytes", recv_len);
            LOG_INF("uart_fifo_read %s", buffer);

            //接收数据处理

        }

    }
}

int usb_cdc_init(void)
{
    int ret;

    if (!device_is_ready(uart_dev)) {
        LOG_ERR("CDC ACM device not ready");
        return 0;
    }

#if defined(CONFIG_USB_DEVICE_STACK_NEXT)
        ret = enable_usb_device_next();
#else
        ret = usb_enable(NULL);
#endif

    if (ret != 0) {
        LOG_ERR("Failed to enable USB");
        return 0;
    }

    LOG_INF("Wait for DTR");

#if defined(CONFIG_USB_DEVICE_STACK_NEXT)
    k_sem_take(&dtr_sem, K_FOREVER);
#else
    while (true) {
        uint32_t dtr = 0U;

        uart_line_ctrl_get(uart_dev, UART_LINE_CTRL_DTR, &dtr);
        if (dtr) {
            break;
        } else {
            /* Give CPU resources to low priority threads. */
            k_sleep(K_MSEC(100));
        }
    }
#endif

    LOG_INF("DTR set");

    /* They are optional, we use them to test the interrupt endpoint */
    ret = uart_line_ctrl_set(uart_dev, UART_LINE_CTRL_DCD, 1);
    if (ret) {
        LOG_WRN("Failed to set DCD, ret code %d", ret);
    }

    ret = uart_line_ctrl_set(uart_dev, UART_LINE_CTRL_DSR, 1);
    if (ret) {
        LOG_WRN("Failed to set DSR, ret code %d", ret);
    }

    /* Wait 100ms for the host to do all settings */
    k_msleep(100);

#ifndef CONFIG_USB_DEVICE_STACK_NEXT
    print_baudrate(uart_dev);
#endif
    uart_irq_callback_set(uart_dev, interrupt_handler);

    /* Enable rx interrupts */
    uart_irq_rx_enable(uart_dev);

    return 0;
}
posted @ 2025-12-11 17:25  羊的第七章  阅读(35)  评论(0)    收藏  举报