前言
本文档为 基于 RT-Thread 的分布式无线温度监控系统DIY项目的第二周任务:使用 nrf24l01 软件包发送与接收温度数据。关于如何获取nrf24l01软件包,如何使用消息队列、邮箱来实现线程间的通信等知识就不在此赘述了,官方教程写得很详细,这里主要讲一下如何使用I/O设备模型框架来管理设备。还有nrf24l01无线模块的通信机制、配置设置也不多讲,芯片数据手册写得很详细,网上资料一大堆。这里就只重点记录几个我自己在调试过程中遇过的坑!!!
主要内容如下:
- rt-thread I/O设备模型框架了解
- 如何将nrf24l01注册到I/O设备管理器
- 修改nrf24l01软件包,实现多点通信功能
- nrf24l01使用注意事项
rt-thread I/O设备模型框架了解
介绍(rt-thread官方讲得很详细,先抄为敬)
1、RT-Thread 的 I/O 设备模型框架位于硬件和应用程序之间,共分成三层,从上到下分别是 I/O设备管理层、设备驱动框架层、设备驱动层。
2、应用程序通过 I/O 设备管理接口获得正确的设备驱动,然后通过这个设备驱动与底层 I/O 硬件设备进行数据(或控制)交互
3、设备驱动框架层是对同类硬件设备驱动的抽象,将不同厂家的同类硬件设备驱动中相同的部分抽取出来,将不同部分留出接口,由驱动程序实现。使用设备驱动框架层可以简单快速的将实体设备注册到I/O设备管理层中
4、设备驱动层是一组驱使硬件设备工作的程序,实现访问硬件设备的功能。它负责创建和注册 I/O 设备
创建I/O设备:rt_device_create(int type, int attach_size)
rt_device_register(rt_device_t dev, const char* name, rt_uint8_t flags)
- 对于操作逻辑简单的设备,可以不经过设备驱动框架层,直接将设备注册到 I/O 设备管理器中
- 对于另一些设备,如看门狗、SPI、传感器等,则会将创建的设备实例先注册到对应的设备驱动框架中,再由设备驱动框架向 I/O 设备管理器进行注册
SPI FLASH设备驱动使用流程
梳理SPI FLASH设备的使用流程,为后面将nrf24l01无线设备注册到系统中做准备
要将SPI FLASH设备用起来,需要先将spi总线(如:spi1)和spi设备(如:spi10)通过spi设备驱动框架(spi_core.c、spi_dev.c)注册到I/O设备管理器中;很nice的是rtt在给我们提供的spi驱动drv_spi.c中已经全部都做好了,具体流程如下的第1、2步
注册好spi总线和spi设备后,还需将FLASH作为块设备注册到直接注册到I/O设备管理器中,以w25qxx为例,该系列的FLASH驱动rtt也已经在spi_flash_w25qx.c中给我们写好了,是不是很爽!具体流程如下的第3步
1、注册spi总线到I/O设备管理器中
int rt_hw_spi_init(void)
{
stm32_get_dma_info();
return rt_hw_spi_bus_init();
}
INIT_BOARD_EXPORT(rt_hw_spi_init);
这里已经使用rt_hw_spi_init自动将选择的spi总线注册到了系统中,所以不再需要手册注册。函数调用流程为:
rt_hw_spi_bus_init()--->
/* register a SPI bus */
rt_err_t rt_spi_bus_register(struct rt_spi_bus *bus,
const char *name,
const struct rt_spi_ops *ops) --->
/*将spi总线定义为RT_Device_Class_SPIBUS类型注册到系统中*/
rt_err_t rt_spi_bus_device_init(struct rt_spi_bus *bus, const char *name) --->
/* register to device manager */
rt_device_register(device, name, RT_DEVICE_FLAG_RDWR);
2、注册spi设备到I/O设备管理器中,并附加到一个spi总线上,函数调用流程为:
/** 1、调用rt_spi_bus_attach_device(spi_device, device_name, bus_name, (void *)cs_pin)
2、attach a device on SPI bus
*/
rt_err_t rt_hw_spi_device_attach(const char *bus_name,
const char *device_name,
GPIO_TypeDef *cs_gpiox,
uint16_t cs_gpio_pin) -->
/** 1、根据bus_name找到spi_bus设备
2、将spi_bus设备赋值给spi_dev设备的bus
3、调用rt_spidev_device_init
4、将user_data赋值给device->parent.user_data
struct rt_spi_device
{
struct rt_device parent;
struct rt_spi_bus *bus;
struct rt_spi_configuration config;
void *user_data;
};
*/
rt_err_t rt_spi_bus_attach_device(struct rt_spi_device *device,
const char *name,
const char *bus_name,
void *user_data)-->
/*将spi_dev设备RT_Device_Class_SPIDevice注册到系统中*/
rt_err_t rt_spidev_device_init(struct rt_spi_device *dev, const char *name) --->
/* register to device manager */
rt_device_register(device, name, RT_DEVICE_FLAG_RDWR);
使用示例:rt_hw_spi_device_attach(“spi1”,“spi10”,GPIOA,GPIO_PIN_5);
3、注册FLASH设备到系统中,并附加到一个spi设备上
struct spi_flash_device
{
struct rt_device flash_device;
struct rt_device_blk_geometry geometry;
struct rt_spi_device * rt_spi_device;
struct rt_mutex lock;
void * user_data;
};
/** 1、根据spi_device_name找到spi_dev设备
2、将spi_dev设备赋值给spi_flash_device设备的rt_spi_device
3、将spi_flash_device设备RT_Device_Class_SPIDevice注册到系统中
*/
rt_err_t w25qxx_init(const char * flash_device_name, const char * spi_device_name) -->
rt_device_register(&spi_flash_device.flash_device,flash_device_name,RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_STANDALONE);
使用示例:
1、w25qxx_init(“w25q128”,“spi10”); /* 使用spi_flash_w25qxx驱动 /
2、rt_sfud_flash_probe(“w25q128”,“spi10”); / 使用spi_flash_sfud驱动 */
如何将nrf24l01注册到I/O设备管理器
原nrf24l01软件包,在nrfl24_port.c中完成了hal_nrf24l01_port芯片spi驱动的移植,在nrf24l01.c中使用hal_nrf24l01_port完成了芯片初始化、芯片读写操作等API的封装。只需要修改hal_nrf24l01_port就可以很方便的移植到其他的rtos中了,且提供了sample使用起来非常简单。
但是nrf24l01软件包没有使用I/O设备框架模型,感觉少了一点rtt的味道;然后也是为了练习下如何将设备注册到I/O设备管理器中。所以想在nrf24l01软件包基础上,将nrf24l01作为网络设备(RT_Device_Class_NetIf)注册到了I/O设备管理器中,然后有了本节内容
将nrf24l01文件自动添加至工程中
1、在rt-threadcomponentsdriversKconfig中添加如下字段,就可在env中选择配置nrf24l01了
config RT_USING_SPI_NRF24L01
bool "Using nRF24L01 SPI 2.4G wireless interface"
default n
2、在rt-threadcomponentsdriversspiSConscript中添加如下字段,在scons编译的时候就能自动将spi_wire_24l01.c添加至工程中了
if GetDepend('RT_USING_SPI_NRF24L01'):
src_device += ['spi_wire_24l01.c']
创建spi无线设备
/* nrf24l01配置内容 */
struct nrf24_cfg
{
struct nrf24_e*** e***; //自动重发
nrf24_role_et role; //角色选择 (PTX、PRX)
nrf24_power_et power; //功率选择
nrf24_adr_et adr; //空中速率(1Mbps、2Mbps)
nrf24_crc_et crc; //crc长度选择(1byte、2bytes)
char selchx; //选用通道:bit0->pipe0,..bit3->pipe3,..bit5->pipe5
char ch0t1revaddr[2][5]; //ch0 to ch1 接收地址 (address[0]为最低字节)
char ch2t5revaddr[4][1]; //ch2 to ch5 接收地址 (address[0]为最低字节)
char sendaddr[5];//发送地址
uint8_t channel; //频率选择(0 : 125 对应 2.4GHz : 2.525GHz)
uint8_t use_irq; //是否使用中断
void *ud; //用来传递对底层的配置 struct hal_nrf24l01_port_cfg
};
/* nrf24l01操作接口 */
struct nrf24_ops
{
int (*nrf_init) (struct nrf24_cfg cfg, rt_base_t pin_ce, rt_base_t pin_irq);
int (*ptx_run) (uint8_t *pb_rx, const uint8_t *pb_tx, uint8_t tlen);
int (*prx_cycle) (uint8_t *pb_rx, const uint8_t *pb_tx, uint8_t tlen, uint8_t *rx_chnum);
int (*irq_ptx_run)(uint8_t *pb_rx, const uint8_t *pb_tx, uint8_t tlen);
int (*irq_prx_run)(uint8_t *pb_rx, const uint8_t *pb_tx, uint8_t tlen, uint8_t *rx_chnum);
};
/* nrf24l01私有结构 */
struct nrf24
{
rt_base_t pin_ce; //CE引脚
rt_base_t pin_irq;//IRQ引脚
struct nrf24_cfg cfg; //nrf24l01配置内容
struct nrf24_ops *ops; //nrf24l01操作接口
};
/* spi无线设备(这里是我自己这么叫的哈 哈哈)*/
struct spi_wire_device
{
struct rt_device wire_device;
struct rt_spi_device *rt_spi_device;
struct rt_mutex lock;
struct rt_semaphore irq_sem;
struct nrf24 nrf24l01; /* nrf24l01私有结构 */
void *user_data;
};
封装nrf24l01操作接口
这里就是直接将nrf24l01软件包中的操作接口copy过来了,然后修改了init函数,使支持多通道通信;在prx_cycle函数和irq_prx_cycle函数中增加了rx_chnum字段,用以获取接收数据的通道号
static struct nrf24_ops nrf24l01_ops =
{
nrf24l01_init,
nrf24l01_ptx_run,
nrf24l01_prx_cycle,
nrf24l01_irq_ptx_run,
nrf24l01_irq_prx_run,
};
新增spi无线设备注册函数
rt_err_t nrf24xx_init(const char * wire_device_name, const char * spi_device_name)
{
struct rt_spi_device * rt_spi_device;
/* initialize mutex */
if (rt_mutex_init(&spi_wire_nrf24l01.lock, spi_device_name, RT_IPC_FLAG_FIFO) != RT_EOK)
{
rt_kprintf("init wire lock mutex failedn");
return -RT_ENOSYS;
}
rt_spi_device = (struct rt_spi_device *)rt_device_find(spi_device_name);
if(rt_spi_device == RT_NULL)
{
rt_kprintf("spi device %s not found!rn", spi_device_name);
return -RT_ENOSYS;
}
spi_wire_nrf24l01.rt_spi_device = rt_spi_device;
/* register device */
spi_wire_nrf24l01.wire_device.type = RT_Device_Class_NetIf;
#ifdef RT_USING_DEVICE_OPS
spi_wire_nrf24l01.wire_device.ops = &spi_device_ops;
#else
spi_wire_nrf24l01.wire_device.init = spi_wire_init;
spi_wire_nrf24l01.wire_device.open = spi_wire_open;
spi_wire_nrf24l01.wire_device.close = spi_wire_close;
spi_wire_nrf24l01.wire_device.read = spi_wire_read;
spi_wire_nrf24l01.wire_device.write = spi_wire_write;
spi_wire_nrf24l01.wire_device.control = spi_wire_control;
#endif
/* private */
spi_wire_nrf24l01.nrf24l01.ops = &nrf24l01_ops;
spi_wire_nrf24l01.user_data = RT_NULL;
rt_device_register(&spi_wire_nrf24l01.wire_device, wire_device_name,
RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_STANDALONE);
return RT_EOK;
}
使用示例:rt_hw_nrf24l01_port
static int rt_hw_nrf24l01_port(void)
{
__HAL_RCC_GPIOA_CLK_ENABLE();
rt_hw_spi_device_attach("spi1", SPI_DEV_NAME, GPIOA, GPIO_PIN_4);
nrf24xx_init(SPI_WIRE_DEV_NAME, SPI_DEV_NAME);
return RT_EOK;
}
修改nrf24l01软件包,实现多点通信功能
修改nrf24l01_default_param函数,给6个pipe的RX_ADDR,和TX_ADDR赋初值
修改init函数,使能和配置selchx选中的通道
修改prx_cycle和irq_prx_cycle接收函数,添加rx_chnum字段获取接收通道号
配置示例,如何使用selchx来选择想要的pipe
修改nrf24l01_default_param
void nrf24l01_default_param(struct nrf24_cfg *pt)
{
const char ch0t1addr0[5] = {0xE7,0xE7,0xE7,0xE7,0xE7};
const char ch0t1addr1[5] = {0xC2,0xC2,0xC2,0xC2,0xC2};
const char ch2t5addr2[1] = {0xC3};
const char ch2t5addr3[1] = {0xC4};
const char ch2t5addr4[1] = {0xC5};
const char ch2t5addr5[1] = {0xC6};
const char sendaddr[5] = {0xE7,0xE7,0xE7,0xE7,0xE7};
pt->power = RF_POWER_0dBm;
pt->e***.ard = 5; // (5+1)*250 = 1500us
pt->e***.arc = 6; // up to 6 times
pt->crc = CRC_2_BYTE; // crc; fcs is two bytes
pt->adr = ADR_1Mbps; // air data rate 1Mbps
pt->channel = 6; // rf channel 6
pt->selchx = 0x01; // select pipe,bit0->pipe0,..bit3->pipe3,..bit5->pipe5,default : pipe 0
rt_strncpy(pt->ch0t1revaddr[0],ch0t1addr0,5);
rt_strncpy(pt->ch0t1revaddr[1],ch0t1addr1,5);
rt_strncpy(pt->ch2t5revaddr[0],ch2t5addr2,1);
rt_strncpy(pt->ch2t5revaddr[1],ch2t5addr3,1);
rt_strncpy(pt->ch2t5revaddr[2],ch2t5addr4,1);
rt_strncpy(pt->ch2t5revaddr[3],ch2t5addr5,1);
rt_strncpy(pt->sendaddr,sendaddr,5);
}
修改nrf24l01_init
static int nrf24l01_init(struct nrf24_cfg cfg, rt_base_t pin_ce, rt_base_t pin_irq)
{
RT_ASSERT(&cfg);
RT_ASSERT(pin_ce >= 0);
char i =0;
rt_err_t err = RT_EOK;
/* config spi */
{
struct rt_spi_configuration spi_cfg;
spi_cfg.data_width = 8;
spi_cfg.mode = RT_SPI_MASTER | RT_SPI_MODE_0 | RT_SPI_MSB; /* SPI Compatible: Mode 0 and Mode 3 */
spi_cfg.max_hz = 5 * 1000 * 1000; /* 50M */
rt_spi_configure(spi_wire_nrf24l01.rt_spi_device, &spi_cfg);
}
/* ce config*/
{
rt_pin_mode(pin_ce, PIN_MODE_OUTPUT);
rt_pin_write(pin_ce, PIN_LOW);
}
/* if irq, config irq pin*/
{
if (pin_irq)
{
err = rt_sem_init(&spi_wire_nrf24l01.irq_sem, "nrfIRQ", 0, RT_IPC_FLAG_FIFO);
if (err != RT_EOK)
{
rt_kprintf("init wire irq sem failedn");
return -RT_ENOSYS;
}
rt_pin_attach_irq(pin_irq, PIN_IRQ_MODE_FALLING, nrf24l01_irqsem_release, 0);
rt_pin_irq_enable(pin_irq, PIN_IRQ_ENABLE);
}
}
if ((cfg.role != ROLE_PTX) && (cfg.role != ROLE_PRX))
{
rt_kprintf("[nrf24-warning]: unknown ROLErn");
_reset_reg_bits(NRF24REG_CONFIG, NRF24BITMASK_PWR_UP);
return -1;
}
send_activate_command(); // it doesn't work?
set_address_width5();//设置地址位宽
enable_dpl();
for (i=0; i<6; i++)
{
if ((cfg.selchx >> i) & 0x01)
{
enable_chx_ackpayload(i);//允许pipe:0--5自动应答和接收数据
if (i < 2)
{
rt_strncpy(&cfg.sendaddr[0], cfg.ch0t1revaddr
, 5);
}
else
{
rt_strncpy(&cfg.sendaddr[0], cfg.ch2t5revaddr[i-2], 1);
rt_strncpy(&cfg.sendaddr[1], cfg.ch0t1revaddr[1], 4);
}
set_tx_address5(&cfg.sendaddr[0]);//设置发送地址
set_chx_rx_address5(i,(i<2? cfg.ch0t1revaddr:cfg.ch2t5revaddr[i-2]));//设置pipe0--5接收节点地址
if (cfg.role == ROLE_PTX)
{
//发送模式下需要将pipe0地址设置为TX_ADDR,因为发送模式下pipe0是用于接收应答信号的,否则会收不到应答信号而发送失败
set_chx_rx_address5(0, &cfg.sendaddr[0]);
}
set_chx_rx_pw(i,32);//设置pipe:0-5有效数据宽度
}
}
set_rf_power (cfg.power); //功率配置
set_rf_channel (cfg.channel); //频率配置
set_air_data_rate(cfg.adr); //速率配置
set_crc (cfg.crc); //CRC配置
set_e***_param (&cfg.e***); //自动重发配置
if (cfg.use_irq) //enable all irq
{
enabled_irq(NRF24BITMASK_RX_DR | NRF24BITMASK_TX_DS | NRF24BITMASK_MAX_RT);
}
else //disable all irq
{
disable_irq(NRF24BITMASK_RX_DR | NRF24BITMASK_TX_DS | NRF24BITMASK_MAX_RT);
}
flush_rx_fifo();//清空接收FIFO
flush_tx_fifo();//清空发送FIFO
reset_status(NRF24BITMASK_RX_DR | NRF24BITMASK_TX_DS | NRF24BITMASK_MAX_RT);//清空中断标志
reset_observe_tx();//开启发送监测功能,数据包丢失计数、重发计数
if (cfg.role == ROLE_PTX)
{
_set_reg_bits(NRF24REG_CONFIG, NRF24BITMASK_PWR_UP);
_reset_reg_bits(NRF24REG_CONFIG, NRF24BITMASK_PRIM_RX);
}
else if (cfg.role == ROLE_PRX)
{
_set_reg_bits(NRF24REG_CONFIG, NRF24BITMASK_PWR_UP);
_set_reg_bits(NRF24REG_CONFIG, NRF24BITMASK_PRIM_RX);
rt_pin_write(pin_ce, PIN_HIGH);
}
else
{
// never run to here
;
}
spi_wire_nrf24l01.nrf24l01.cfg = cfg;
spi_wire_nrf24l01.nrf24l01.pin_ce = pin_ce;
spi_wire_nrf24l01.nrf24l01.pin_irq = pin_irq;
return RT_EOK;
}
修改nrf24l01_prx_cycle
int nrf24l01_prx_cycle(uint8_t *pb_rx, const uint8_t *pb_tx, uint8_t tlen, uint8_t *rx_chnum)
{
uint8_t chnum = 0, sta, rlen = 0;
sta = _read_reg(NRF24REG_FIFO_STATUS);
if (!(sta & NRF24BITMASK_RX_EMPTY))
{
sta = _read_reg(NRF24REG_STATUS);//读状态寄存器,必须在read_rxpayload前,否则状态寄存器会被清空
chnum = (sta >> 1) & 0x07;//获取通道号
*rx_chnum = chnum;//接收通道号
rlen = get_top_rxfifo_width();
read_rxpayload(pb_rx, rlen);
// flush_rx_fifo();
if ((tlen > 0) && (tlen <= 32))
{
write_ack_payload(chnum, pb_tx, tlen);
}
}
return rlen;
}
配置示例
接收端:
nrf24l01_default_param(&nrf24l01_cfg);
nrf24l01_cfg.role = ROLE_PRX;
nrf24l01_cfg.selchx = 0X3F;//使能所以通道。pipe0:0x01, pipe1:0x02, pipe2:0x04, pipe3:0x08, pipe4:0x10, pipe5:0x20
nrf24l01_cfg.use_irq = 1;
nrf24l01_dev->nrf24l01.ops->nrf_init(nrf24l01_cfg, NRF24L01_CE_PIN, NRF24L01_IRQ_PIN);
发送端:
nrf24l01_default_param(&nrf24l01_cfg);
nrf24l01_cfg.role = ROLE_PTX;
nrf24l01_cfg.selchx = 0X03;//使能pipe1。pipe0:0x01, pipe1:0x02, pipe2:0x04, pipe3:0x08, pipe4:0x10, pipe5:0x20
nrf24l01_cfg.use_irq = 1;
nrf24l01_dev->nrf24l01.ops->nrf_init(nrf24l01_cfg, NRF24L01_CE_PIN, NRF24L01_IRQ_
nrf24l01使用注意事项
- nrf24l01组网方式
如果使用的是第二种方式,每个发送端都使用pipe0与接收端通信,也就是教程工程中所使用的方法,那么直接使用教程的工程就可以顺风顺水了。但如果使用的第一种,发送端使用与接收端相同的pipe进行通信,那你就准备好爬坑吧。
- 地址设置:pipe0与pipe1的RX_ADDR为5Bytes可设,需要写入5字节地址;pipe2-pipe5的RX_ADDR只有低位地址1Byte可设,只能写入1字节地址,且剩余4字节需与pipe1高4字节地址一致。不允许不同的数据通道设置完全相同的地址。TX_ADDR为5Bytes可设,必须写入5字节地址。
- 发送端地址设置:如果使能了自动应答模式,nRF24L01接收端在确认收到数据后记录发送端地址,并以此地址为目标地址发送应答信号。在发送端,pipe0被用做接收应答信号,因此,pipe0的接收地址要与发送端地址相等以确保接收到正确的应答信号
比如:在发送端使用pipe3与接收端通信,则发送端的TX_ADDR = RX_ADDR_P3 = RX_ADDR_P0
- 通道选择:在接收端一般是开启所以通道,所以不会遇到这个问题。但如果是只开启其中的某个通道,则该通道前面的通道也必须开启,具体原因我也不知道。比如:开启pipe2,则pipe0和pipe1也必须开启。
- 接收端通道号获取:如果想在接收端获取当前通信的发送端通道号,可通过读取STATUS寄存器的RX_P_NO位来获取
但是必须还读取RX FIFO前面获取,否则获取的RX_P_NO会是’111’,原因在于
前言
本文档为 基于 RT-Thread 的分布式无线温度监控系统DIY项目的第二周任务:使用 nrf24l01 软件包发送与接收温度数据。关于如何获取nrf24l01软件包,如何使用消息队列、邮箱来实现线程间的通信等知识就不在此赘述了,官方教程写得很详细,这里主要讲一下如何使用I/O设备模型框架来管理设备。还有nrf24l01无线模块的通信机制、配置设置也不多讲,芯片数据手册写得很详细,网上资料一大堆。这里就只重点记录几个我自己在调试过程中遇过的坑!!!
主要内容如下:
- rt-thread I/O设备模型框架了解
- 如何将nrf24l01注册到I/O设备管理器
- 修改nrf24l01软件包,实现多点通信功能
- nrf24l01使用注意事项
rt-thread I/O设备模型框架了解
介绍(rt-thread官方讲得很详细,先抄为敬)
1、RT-Thread 的 I/O 设备模型框架位于硬件和应用程序之间,共分成三层,从上到下分别是 I/O设备管理层、设备驱动框架层、设备驱动层。
2、应用程序通过 I/O 设备管理接口获得正确的设备驱动,然后通过这个设备驱动与底层 I/O 硬件设备进行数据(或控制)交互
3、设备驱动框架层是对同类硬件设备驱动的抽象,将不同厂家的同类硬件设备驱动中相同的部分抽取出来,将不同部分留出接口,由驱动程序实现。使用设备驱动框架层可以简单快速的将实体设备注册到I/O设备管理层中
4、设备驱动层是一组驱使硬件设备工作的程序,实现访问硬件设备的功能。它负责创建和注册 I/O 设备
创建I/O设备:rt_device_create(int type, int attach_size)
rt_device_register(rt_device_t dev, const char* name, rt_uint8_t flags)
- 对于操作逻辑简单的设备,可以不经过设备驱动框架层,直接将设备注册到 I/O 设备管理器中
- 对于另一些设备,如看门狗、SPI、传感器等,则会将创建的设备实例先注册到对应的设备驱动框架中,再由设备驱动框架向 I/O 设备管理器进行注册
SPI FLASH设备驱动使用流程
梳理SPI FLASH设备的使用流程,为后面将nrf24l01无线设备注册到系统中做准备
要将SPI FLASH设备用起来,需要先将spi总线(如:spi1)和spi设备(如:spi10)通过spi设备驱动框架(spi_core.c、spi_dev.c)注册到I/O设备管理器中;很nice的是rtt在给我们提供的spi驱动drv_spi.c中已经全部都做好了,具体流程如下的第1、2步
注册好spi总线和spi设备后,还需将FLASH作为块设备注册到直接注册到I/O设备管理器中,以w25qxx为例,该系列的FLASH驱动rtt也已经在spi_flash_w25qx.c中给我们写好了,是不是很爽!具体流程如下的第3步
1、注册spi总线到I/O设备管理器中
int rt_hw_spi_init(void)
{
stm32_get_dma_info();
return rt_hw_spi_bus_init();
}
INIT_BOARD_EXPORT(rt_hw_spi_init);
这里已经使用rt_hw_spi_init自动将选择的spi总线注册到了系统中,所以不再需要手册注册。函数调用流程为:
rt_hw_spi_bus_init()--->
/* register a SPI bus */
rt_err_t rt_spi_bus_register(struct rt_spi_bus *bus,
const char *name,
const struct rt_spi_ops *ops) --->
/*将spi总线定义为RT_Device_Class_SPIBUS类型注册到系统中*/
rt_err_t rt_spi_bus_device_init(struct rt_spi_bus *bus, const char *name) --->
/* register to device manager */
rt_device_register(device, name, RT_DEVICE_FLAG_RDWR);
2、注册spi设备到I/O设备管理器中,并附加到一个spi总线上,函数调用流程为:
/** 1、调用rt_spi_bus_attach_device(spi_device, device_name, bus_name, (void *)cs_pin)
2、attach a device on SPI bus
*/
rt_err_t rt_hw_spi_device_attach(const char *bus_name,
const char *device_name,
GPIO_TypeDef *cs_gpiox,
uint16_t cs_gpio_pin) -->
/** 1、根据bus_name找到spi_bus设备
2、将spi_bus设备赋值给spi_dev设备的bus
3、调用rt_spidev_device_init
4、将user_data赋值给device->parent.user_data
struct rt_spi_device
{
struct rt_device parent;
struct rt_spi_bus *bus;
struct rt_spi_configuration config;
void *user_data;
};
*/
rt_err_t rt_spi_bus_attach_device(struct rt_spi_device *device,
const char *name,
const char *bus_name,
void *user_data)-->
/*将spi_dev设备RT_Device_Class_SPIDevice注册到系统中*/
rt_err_t rt_spidev_device_init(struct rt_spi_device *dev, const char *name) --->
/* register to device manager */
rt_device_register(device, name, RT_DEVICE_FLAG_RDWR);
使用示例:rt_hw_spi_device_attach(“spi1”,“spi10”,GPIOA,GPIO_PIN_5);
3、注册FLASH设备到系统中,并附加到一个spi设备上
struct spi_flash_device
{
struct rt_device flash_device;
struct rt_device_blk_geometry geometry;
struct rt_spi_device * rt_spi_device;
struct rt_mutex lock;
void * user_data;
};
/** 1、根据spi_device_name找到spi_dev设备
2、将spi_dev设备赋值给spi_flash_device设备的rt_spi_device
3、将spi_flash_device设备RT_Device_Class_SPIDevice注册到系统中
*/
rt_err_t w25qxx_init(const char * flash_device_name, const char * spi_device_name) -->
rt_device_register(&spi_flash_device.flash_device,flash_device_name,RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_STANDALONE);
使用示例:
1、w25qxx_init(“w25q128”,“spi10”); /* 使用spi_flash_w25qxx驱动 /
2、rt_sfud_flash_probe(“w25q128”,“spi10”); / 使用spi_flash_sfud驱动 */
如何将nrf24l01注册到I/O设备管理器
原nrf24l01软件包,在nrfl24_port.c中完成了hal_nrf24l01_port芯片spi驱动的移植,在nrf24l01.c中使用hal_nrf24l01_port完成了芯片初始化、芯片读写操作等API的封装。只需要修改hal_nrf24l01_port就可以很方便的移植到其他的rtos中了,且提供了sample使用起来非常简单。
但是nrf24l01软件包没有使用I/O设备框架模型,感觉少了一点rtt的味道;然后也是为了练习下如何将设备注册到I/O设备管理器中。所以想在nrf24l01软件包基础上,将nrf24l01作为网络设备(RT_Device_Class_NetIf)注册到了I/O设备管理器中,然后有了本节内容
将nrf24l01文件自动添加至工程中
1、在rt-threadcomponentsdriversKconfig中添加如下字段,就可在env中选择配置nrf24l01了
config RT_USING_SPI_NRF24L01
bool "Using nRF24L01 SPI 2.4G wireless interface"
default n
2、在rt-threadcomponentsdriversspiSConscript中添加如下字段,在scons编译的时候就能自动将spi_wire_24l01.c添加至工程中了
if GetDepend('RT_USING_SPI_NRF24L01'):
src_device += ['spi_wire_24l01.c']
创建spi无线设备
/* nrf24l01配置内容 */
struct nrf24_cfg
{
struct nrf24_e*** e***; //自动重发
nrf24_role_et role; //角色选择 (PTX、PRX)
nrf24_power_et power; //功率选择
nrf24_adr_et adr; //空中速率(1Mbps、2Mbps)
nrf24_crc_et crc; //crc长度选择(1byte、2bytes)
char selchx; //选用通道:bit0->pipe0,..bit3->pipe3,..bit5->pipe5
char ch0t1revaddr[2][5]; //ch0 to ch1 接收地址 (address[0]为最低字节)
char ch2t5revaddr[4][1]; //ch2 to ch5 接收地址 (address[0]为最低字节)
char sendaddr[5];//发送地址
uint8_t channel; //频率选择(0 : 125 对应 2.4GHz : 2.525GHz)
uint8_t use_irq; //是否使用中断
void *ud; //用来传递对底层的配置 struct hal_nrf24l01_port_cfg
};
/* nrf24l01操作接口 */
struct nrf24_ops
{
int (*nrf_init) (struct nrf24_cfg cfg, rt_base_t pin_ce, rt_base_t pin_irq);
int (*ptx_run) (uint8_t *pb_rx, const uint8_t *pb_tx, uint8_t tlen);
int (*prx_cycle) (uint8_t *pb_rx, const uint8_t *pb_tx, uint8_t tlen, uint8_t *rx_chnum);
int (*irq_ptx_run)(uint8_t *pb_rx, const uint8_t *pb_tx, uint8_t tlen);
int (*irq_prx_run)(uint8_t *pb_rx, const uint8_t *pb_tx, uint8_t tlen, uint8_t *rx_chnum);
};
/* nrf24l01私有结构 */
struct nrf24
{
rt_base_t pin_ce; //CE引脚
rt_base_t pin_irq;//IRQ引脚
struct nrf24_cfg cfg; //nrf24l01配置内容
struct nrf24_ops *ops; //nrf24l01操作接口
};
/* spi无线设备(这里是我自己这么叫的哈 哈哈)*/
struct spi_wire_device
{
struct rt_device wire_device;
struct rt_spi_device *rt_spi_device;
struct rt_mutex lock;
struct rt_semaphore irq_sem;
struct nrf24 nrf24l01; /* nrf24l01私有结构 */
void *user_data;
};
封装nrf24l01操作接口
这里就是直接将nrf24l01软件包中的操作接口copy过来了,然后修改了init函数,使支持多通道通信;在prx_cycle函数和irq_prx_cycle函数中增加了rx_chnum字段,用以获取接收数据的通道号
static struct nrf24_ops nrf24l01_ops =
{
nrf24l01_init,
nrf24l01_ptx_run,
nrf24l01_prx_cycle,
nrf24l01_irq_ptx_run,
nrf24l01_irq_prx_run,
};
新增spi无线设备注册函数
rt_err_t nrf24xx_init(const char * wire_device_name, const char * spi_device_name)
{
struct rt_spi_device * rt_spi_device;
/* initialize mutex */
if (rt_mutex_init(&spi_wire_nrf24l01.lock, spi_device_name, RT_IPC_FLAG_FIFO) != RT_EOK)
{
rt_kprintf("init wire lock mutex failedn");
return -RT_ENOSYS;
}
rt_spi_device = (struct rt_spi_device *)rt_device_find(spi_device_name);
if(rt_spi_device == RT_NULL)
{
rt_kprintf("spi device %s not found!rn", spi_device_name);
return -RT_ENOSYS;
}
spi_wire_nrf24l01.rt_spi_device = rt_spi_device;
/* register device */
spi_wire_nrf24l01.wire_device.type = RT_Device_Class_NetIf;
#ifdef RT_USING_DEVICE_OPS
spi_wire_nrf24l01.wire_device.ops = &spi_device_ops;
#else
spi_wire_nrf24l01.wire_device.init = spi_wire_init;
spi_wire_nrf24l01.wire_device.open = spi_wire_open;
spi_wire_nrf24l01.wire_device.close = spi_wire_close;
spi_wire_nrf24l01.wire_device.read = spi_wire_read;
spi_wire_nrf24l01.wire_device.write = spi_wire_write;
spi_wire_nrf24l01.wire_device.control = spi_wire_control;
#endif
/* private */
spi_wire_nrf24l01.nrf24l01.ops = &nrf24l01_ops;
spi_wire_nrf24l01.user_data = RT_NULL;
rt_device_register(&spi_wire_nrf24l01.wire_device, wire_device_name,
RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_STANDALONE);
return RT_EOK;
}
使用示例:rt_hw_nrf24l01_port
static int rt_hw_nrf24l01_port(void)
{
__HAL_RCC_GPIOA_CLK_ENABLE();
rt_hw_spi_device_attach("spi1", SPI_DEV_NAME, GPIOA, GPIO_PIN_4);
nrf24xx_init(SPI_WIRE_DEV_NAME, SPI_DEV_NAME);
return RT_EOK;
}
修改nrf24l01软件包,实现多点通信功能
修改nrf24l01_default_param函数,给6个pipe的RX_ADDR,和TX_ADDR赋初值
修改init函数,使能和配置selchx选中的通道
修改prx_cycle和irq_prx_cycle接收函数,添加rx_chnum字段获取接收通道号
配置示例,如何使用selchx来选择想要的pipe
修改nrf24l01_default_param
void nrf24l01_default_param(struct nrf24_cfg *pt)
{
const char ch0t1addr0[5] = {0xE7,0xE7,0xE7,0xE7,0xE7};
const char ch0t1addr1[5] = {0xC2,0xC2,0xC2,0xC2,0xC2};
const char ch2t5addr2[1] = {0xC3};
const char ch2t5addr3[1] = {0xC4};
const char ch2t5addr4[1] = {0xC5};
const char ch2t5addr5[1] = {0xC6};
const char sendaddr[5] = {0xE7,0xE7,0xE7,0xE7,0xE7};
pt->power = RF_POWER_0dBm;
pt->e***.ard = 5; // (5+1)*250 = 1500us
pt->e***.arc = 6; // up to 6 times
pt->crc = CRC_2_BYTE; // crc; fcs is two bytes
pt->adr = ADR_1Mbps; // air data rate 1Mbps
pt->channel = 6; // rf channel 6
pt->selchx = 0x01; // select pipe,bit0->pipe0,..bit3->pipe3,..bit5->pipe5,default : pipe 0
rt_strncpy(pt->ch0t1revaddr[0],ch0t1addr0,5);
rt_strncpy(pt->ch0t1revaddr[1],ch0t1addr1,5);
rt_strncpy(pt->ch2t5revaddr[0],ch2t5addr2,1);
rt_strncpy(pt->ch2t5revaddr[1],ch2t5addr3,1);
rt_strncpy(pt->ch2t5revaddr[2],ch2t5addr4,1);
rt_strncpy(pt->ch2t5revaddr[3],ch2t5addr5,1);
rt_strncpy(pt->sendaddr,sendaddr,5);
}
修改nrf24l01_init
static int nrf24l01_init(struct nrf24_cfg cfg, rt_base_t pin_ce, rt_base_t pin_irq)
{
RT_ASSERT(&cfg);
RT_ASSERT(pin_ce >= 0);
char i =0;
rt_err_t err = RT_EOK;
/* config spi */
{
struct rt_spi_configuration spi_cfg;
spi_cfg.data_width = 8;
spi_cfg.mode = RT_SPI_MASTER | RT_SPI_MODE_0 | RT_SPI_MSB; /* SPI Compatible: Mode 0 and Mode 3 */
spi_cfg.max_hz = 5 * 1000 * 1000; /* 50M */
rt_spi_configure(spi_wire_nrf24l01.rt_spi_device, &spi_cfg);
}
/* ce config*/
{
rt_pin_mode(pin_ce, PIN_MODE_OUTPUT);
rt_pin_write(pin_ce, PIN_LOW);
}
/* if irq, config irq pin*/
{
if (pin_irq)
{
err = rt_sem_init(&spi_wire_nrf24l01.irq_sem, "nrfIRQ", 0, RT_IPC_FLAG_FIFO);
if (err != RT_EOK)
{
rt_kprintf("init wire irq sem failedn");
return -RT_ENOSYS;
}
rt_pin_attach_irq(pin_irq, PIN_IRQ_MODE_FALLING, nrf24l01_irqsem_release, 0);
rt_pin_irq_enable(pin_irq, PIN_IRQ_ENABLE);
}
}
if ((cfg.role != ROLE_PTX) && (cfg.role != ROLE_PRX))
{
rt_kprintf("[nrf24-warning]: unknown ROLErn");
_reset_reg_bits(NRF24REG_CONFIG, NRF24BITMASK_PWR_UP);
return -1;
}
send_activate_command(); // it doesn't work?
set_address_width5();//设置地址位宽
enable_dpl();
for (i=0; i<6; i++)
{
if ((cfg.selchx >> i) & 0x01)
{
enable_chx_ackpayload(i);//允许pipe:0--5自动应答和接收数据
if (i < 2)
{
rt_strncpy(&cfg.sendaddr[0], cfg.ch0t1revaddr
, 5);
}
else
{
rt_strncpy(&cfg.sendaddr[0], cfg.ch2t5revaddr[i-2], 1);
rt_strncpy(&cfg.sendaddr[1], cfg.ch0t1revaddr[1], 4);
}
set_tx_address5(&cfg.sendaddr[0]);//设置发送地址
set_chx_rx_address5(i,(i<2? cfg.ch0t1revaddr:cfg.ch2t5revaddr[i-2]));//设置pipe0--5接收节点地址
if (cfg.role == ROLE_PTX)
{
//发送模式下需要将pipe0地址设置为TX_ADDR,因为发送模式下pipe0是用于接收应答信号的,否则会收不到应答信号而发送失败
set_chx_rx_address5(0, &cfg.sendaddr[0]);
}
set_chx_rx_pw(i,32);//设置pipe:0-5有效数据宽度
}
}
set_rf_power (cfg.power); //功率配置
set_rf_channel (cfg.channel); //频率配置
set_air_data_rate(cfg.adr); //速率配置
set_crc (cfg.crc); //CRC配置
set_e***_param (&cfg.e***); //自动重发配置
if (cfg.use_irq) //enable all irq
{
enabled_irq(NRF24BITMASK_RX_DR | NRF24BITMASK_TX_DS | NRF24BITMASK_MAX_RT);
}
else //disable all irq
{
disable_irq(NRF24BITMASK_RX_DR | NRF24BITMASK_TX_DS | NRF24BITMASK_MAX_RT);
}
flush_rx_fifo();//清空接收FIFO
flush_tx_fifo();//清空发送FIFO
reset_status(NRF24BITMASK_RX_DR | NRF24BITMASK_TX_DS | NRF24BITMASK_MAX_RT);//清空中断标志
reset_observe_tx();//开启发送监测功能,数据包丢失计数、重发计数
if (cfg.role == ROLE_PTX)
{
_set_reg_bits(NRF24REG_CONFIG, NRF24BITMASK_PWR_UP);
_reset_reg_bits(NRF24REG_CONFIG, NRF24BITMASK_PRIM_RX);
}
else if (cfg.role == ROLE_PRX)
{
_set_reg_bits(NRF24REG_CONFIG, NRF24BITMASK_PWR_UP);
_set_reg_bits(NRF24REG_CONFIG, NRF24BITMASK_PRIM_RX);
rt_pin_write(pin_ce, PIN_HIGH);
}
else
{
// never run to here
;
}
spi_wire_nrf24l01.nrf24l01.cfg = cfg;
spi_wire_nrf24l01.nrf24l01.pin_ce = pin_ce;
spi_wire_nrf24l01.nrf24l01.pin_irq = pin_irq;
return RT_EOK;
}
修改nrf24l01_prx_cycle
int nrf24l01_prx_cycle(uint8_t *pb_rx, const uint8_t *pb_tx, uint8_t tlen, uint8_t *rx_chnum)
{
uint8_t chnum = 0, sta, rlen = 0;
sta = _read_reg(NRF24REG_FIFO_STATUS);
if (!(sta & NRF24BITMASK_RX_EMPTY))
{
sta = _read_reg(NRF24REG_STATUS);//读状态寄存器,必须在read_rxpayload前,否则状态寄存器会被清空
chnum = (sta >> 1) & 0x07;//获取通道号
*rx_chnum = chnum;//接收通道号
rlen = get_top_rxfifo_width();
read_rxpayload(pb_rx, rlen);
// flush_rx_fifo();
if ((tlen > 0) && (tlen <= 32))
{
write_ack_payload(chnum, pb_tx, tlen);
}
}
return rlen;
}
配置示例
接收端:
nrf24l01_default_param(&nrf24l01_cfg);
nrf24l01_cfg.role = ROLE_PRX;
nrf24l01_cfg.selchx = 0X3F;//使能所以通道。pipe0:0x01, pipe1:0x02, pipe2:0x04, pipe3:0x08, pipe4:0x10, pipe5:0x20
nrf24l01_cfg.use_irq = 1;
nrf24l01_dev->nrf24l01.ops->nrf_init(nrf24l01_cfg, NRF24L01_CE_PIN, NRF24L01_IRQ_PIN);
发送端:
nrf24l01_default_param(&nrf24l01_cfg);
nrf24l01_cfg.role = ROLE_PTX;
nrf24l01_cfg.selchx = 0X03;//使能pipe1。pipe0:0x01, pipe1:0x02, pipe2:0x04, pipe3:0x08, pipe4:0x10, pipe5:0x20
nrf24l01_cfg.use_irq = 1;
nrf24l01_dev->nrf24l01.ops->nrf_init(nrf24l01_cfg, NRF24L01_CE_PIN, NRF24L01_IRQ_
nrf24l01使用注意事项
- nrf24l01组网方式
如果使用的是第二种方式,每个发送端都使用pipe0与接收端通信,也就是教程工程中所使用的方法,那么直接使用教程的工程就可以顺风顺水了。但如果使用的第一种,发送端使用与接收端相同的pipe进行通信,那你就准备好爬坑吧。
- 地址设置:pipe0与pipe1的RX_ADDR为5Bytes可设,需要写入5字节地址;pipe2-pipe5的RX_ADDR只有低位地址1Byte可设,只能写入1字节地址,且剩余4字节需与pipe1高4字节地址一致。不允许不同的数据通道设置完全相同的地址。TX_ADDR为5Bytes可设,必须写入5字节地址。
- 发送端地址设置:如果使能了自动应答模式,nRF24L01接收端在确认收到数据后记录发送端地址,并以此地址为目标地址发送应答信号。在发送端,pipe0被用做接收应答信号,因此,pipe0的接收地址要与发送端地址相等以确保接收到正确的应答信号
比如:在发送端使用pipe3与接收端通信,则发送端的TX_ADDR = RX_ADDR_P3 = RX_ADDR_P0
- 通道选择:在接收端一般是开启所以通道,所以不会遇到这个问题。但如果是只开启其中的某个通道,则该通道前面的通道也必须开启,具体原因我也不知道。比如:开启pipe2,则pipe0和pipe1也必须开启。
- 接收端通道号获取:如果想在接收端获取当前通信的发送端通道号,可通过读取STATUS寄存器的RX_P_NO位来获取
但是必须还读取RX FIFO前面获取,否则获取的RX_P_NO会是’111’,原因在于
举报