理解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发送实测波形

可以看到,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;
}

浙公网安备 33010602011771号