硬件平台 :友善之臂Ting4412
: NRF24L01 2.4G无线模块SPI接口
内核版本:Linux3.5
硬件连接方式
以前在STM32F103C8T6上使用STM32的SPI控制器驱动过NRF24L01 2.4G无线模块,最近学习Linux设备驱动,刚好同学手边有一个Tiny4412开发板就当是练练手,顺便学习学习Linux下SPI设备驱动的编写。
NRF24L01 2.4G无线模块的SPI驱动暂时只是调试通过,能够实现Tiny4412通过NRF24L01 2.4G无线模块发送数据,暂时没写成字符设备驱动,在内核定时器中调用spi_write函数不断发送数据。
通信现象:
STM32没接到数据就会报错,如下:
一旦加载驱动模块,就立马接收正常,如下:
......................................................................................只说流程,不说原理............................................................................
1、构造struct spi_board_info对象,这里我们暂时构造两个,方便以后在SPI0控制器上挂载两个NFR24L01设备,实现相互收发数据。
struct s3c64xx_spi_csinfo cs0={
.fb_delay = 0,
.line = EXYNOS4_GPX3(2),//片选引脚
};
struct s3c64xx_spi_csinfo cs1={
.fb_delay = 0,
.line = EXYNOS4_GPX3(4),//片选引脚
};
static struct spi_board_info nrf24l01_spi_info[]={
{
.modalias = DEV_NAME_01,
.max_speed_hz = 10*1000*1000,//最大时钟,需比设备要求的时钟大,暂选10Mhz
.bus_num = 0,/*exynos4412 的spi0控制器*/
.chip_select = EXYNOS4_GPX3(2),/*由spi master确定其最大值*/
.mode = SPI_MODE_0,
.platform_data = (void*)EXYNOS4_GPX3(3), /*平台数据,作为NRF24LXX的CE引脚*/
.controller_data= (void*)&cs0,
},
{
.modalias = DEV_NAME_02,
.max_speed_hz = 10*1000*1000,//最大时钟,需比设备要求的时钟大,暂选10Mhz
.bus_num = 0,/*exynos4412 的spi0控制器*/
.chip_select = EXYNOS4_GPX3(4),/*由spi master确定其最大值*/
.mode = SPI_MODE_0,
.platform_data = (void*)EXYNOS4_GPX3(5), /*平台数据,作为NRF24LXX的CE引脚*/
.controller_data= (void*)&cs1,
},
};
2、注册它,内核会依据构造的struct spi_board_info 对象创建对应的struct spi_device对象。
在init函数中注册构造好的truct spi_board_info对象static struct spi_board_info nrf24l01_spi_info[]
static int __init demo_init(void)
{
printk(KERN_INFO"current func is-------------------------------------------%sn",__FUNCTION__);
/*spi_register_board_info
*依照spi_board_info 和spi_master的bus_num比较,匹配成功会生成一个对应的spi_device对象
*/
return spi_register_board_info(nrf24l01_spi_info,ARRAY_SIZE(nrf24l01_spi_info));
}
看看spi_register_board_info干了什么?
int spi_register_board_info(struct spi_board_info const *info, unsigned n)
//master和bi->board_info的bus_nus比较,相等则调用spi_new_device创建spi_device对象
spi_match_master_to_boardinfo(master, &bi->board_info);调用spi_new_device
spi_new_device(struct spi_master *master,struct spi_board_info *chip)
int spi_add_device(struct spi_device *spi)//注册struct spi_device
/*spi_add_device注册struct spi_device时候会做一些判断,一会进去分析 */
分析spi_new_device
int spi_register_board_info(struct spi_board_info const *info, unsigned n)
//master和bi->board_info的bus_nus比较,相等则调用spi_new_device创建spi_device对象
spi_match_master_to_boardinfo(master, &bi->board_info);调用spi_new_device
spi_new_device(struct spi_master *master,struct spi_board_info *chip)
int spi_add_device(struct spi_device *spi)//注册struct spi_device
/*spi_add_device注册struct spi_device时候会做一些判断,比如我们第一步构造
*static struct spi_board_info中的chip_select会和spi_master的num_chipselect做比较
*要求设置我们的片选信号不能大于spi_master的num_chipselect
* if (spi->chip_select >= spi->master->num_chipselect) {
* dev_err(dev, "cs%d >= max %dn",spi->chip_select,spi->master->num_chipselect);
* return -EINVAL;//直接报错返回,导致我们不能注册spi_device对象......
* 这个问题怎么解决?我们直接修改spi主控器驱动就好了。
*}
* 修改 vi arch/arm/mach-exynos/mach-tiny4412.c中设置spi控制器函数
* s3c64xx_spi0_set_platdata(NULL,0,1)这个函数就好了,内核默认设置
* 最大片选值为1,也就是说默认最多支持1个spi设备,那好了我们把它修改
* 大一些就好了,直接修改到0xffff,保证它一定能注册成功。
*/
至于程序中 struct s3c64xx_spi_csinfo cs0 和struct s3c64xx_spi_csinfo cs1怎么来的?为什么需要这两个?这个是安装驱动后看内核看内核打印错误信息找出来的。出错的地方为 driversspispi-s3c64xx.c的static int s3c64xx_spi_setup(struct spi_device *spi)函数中报if (IS_ERR_OR_NULL(cs)) {
dev_err(&spi->dev, "No CS for SPI(%d)n", spi->chip_select);
return -ENODEV;
}
意思是cs为空,那么追溯cs,发现 spi_device spi->controller_data = cs;那好,我们就在 static struct spi_board_info nrf24l01_spi_info[]添加.controller_data= (void*)&cs0即可。
struct s3c64xx_spi_csinfo cs0={
.fb_delay = 0,
.line = EXYNOS4_GPX3(2),
};
struct s3c64xx_spi_csinfo cs1={
.fb_delay = 0,
.line = EXYNOS4_GPX3(4),
};
好了下面编译成模块,但是编译的时候报WARNING: "spi_register_board_info"/nrf24xx_spi_device.ko] undefined!第一感觉是
spi_register_board_info相关的头文件没有,但是相关spi的头文件都加了,查资料发现原来是linux3.5源码中没有将spi_register_board_info函数导出符号表。那好,就编译进内核得了,将nrf24xx_spi_device.c复制drivers/spi目录下,修改Makefile,将nrf24xx_spi_device.c编译进内核。下面先给出nrf24xx_spi_device.c源码。
#include #include #include #include #include #include #include #include #include #include #include /******************硬件相关的信息****************/ #define DEV_NAME_01 "nrf24xx_spi_01" #define DEV_NAME_02 "nrf24xx_spi_02" #define DEV1_CS_PIN EXYNOS4_GPX3(2) #define DEV2_CS_PIN EXYNOS4_GPX3(4) #define DEV1_CE_PIN EXYNOS4_GPX3(3) #define DEV2_CE_PIN EXYNOS4_GPX3(5) /* 片选引脚接在GPX3_2 */ struct s3c64xx_spi_csinfo cs0={ .fb_delay = 0, .line = EXYNOS4_GPX3(2),//片选引脚 }; struct s3c64xx_spi_csinfo cs1={ .fb_delay = 0, .line = EXYNOS4_GPX3(4),//片选引脚 }; static struct spi_board_info nrf24l01_spi_info[]={ { .modalias = DEV_NAME_01, .max_speed_hz = 2*1000*1000,//最大时钟,需比设备要求的时钟大,暂选2Mhz .bus_num = 0,/*exynos4412 的spi0控制器*/ .chip_select = EXYNOS4_GPX3(2),/*由spi master确定其最大值*/ .mode = SPI_MODE_0, .platform_data = (void*)EXYNOS4_GPX3(3), /*平台数据,作为NRF24LXX的CE引脚*/ .controller_data= (void*)&cs0, }, { .modalias = DEV_NAME_02, .max_speed_hz = 2*1000*1000,//最大时钟,需比设备要求的时钟大,暂选2Mhz .bus_num = 0,/*exynos4412 的spi0控制器*/ .chip_select = EXYNOS4_GPX3(4),/*由spi master确定其最大值*/ .mode = SPI_MODE_0, .platform_data = (void*)EXYNOS4_GPX3(5), /*平台数据,作为NRF24LXX的CE引脚*/ .controller_data= (void*)&cs1, }, }; static int __init demo_init(void) { printk(KERN_INFO"current func is-------------------------------------------%sn",__FUNCTION__); /*spi_register_board_info *依照spi_board_info 和spi_master的bus_num比较,匹配成功会生成一个对应的spi_device对象 */ return spi_register_board_info(nrf24l01_spi_info,ARRAY_SIZE(nrf24l01_spi_info)); } static void __exit demo_exit(void) { printk(KERN_INFO"current func is-----%sn",__FUNCTION__); } module_init(demo_init); module_exit(demo_exit); MODULE_LICENSE("GPL");
make zImage,启动内核。在sys/bus/spi/devices目录下出现下面spi0.220 spi0.222两个节点
其中0表示bus_num,220和222表示我们的static struct spi_board_info nrf24l01_spi_info[0]的.chip_select = EXYNOS4_GPX3(2),//这个宏的值就是220同理222就是EXYNOS4_GPX3(4),默认内核只创建spi0.0、spi2.0。注意现在只是在内核中创建了spi_device,还没nrf24xx的驱动。nrf24xx_spi_driver就是在STM32程序基础上简单的修修改改就好了,对比Tiny4412和STM32的程序你会发现关于nrf24l01的代码名字是一样的。
..............................................................下面就直接给出nrf24xx_spi_driver.c的源码.........................................................................
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/******************硬件相关的信息****************/
#define DEV_NAME_01 "nrf24xx_spi_01"
#define DEV_NAME_02 "nrf24xx_spi_02"
#define DEV1_CS_PIN EXYNOS4_GPX3(2)
#define DEV2_CS_PIN EXYNOS4_GPX3(4)
#define DEV1_CE_PIN EXYNOS4_GPX3(3)
#define DEV2_CE_PIN EXYNOS4_GPX3(5)
/*
片选引脚接在GPX3_2
*/
struct s3c64xx_spi_csinfo cs0={
.fb_delay = 0,
.line = EXYNOS4_GPX3(2),//片选引脚
};
struct s3c64xx_spi_csinfo cs1={
.fb_delay = 0,
.line = EXYNOS4_GPX3(4),//片选引脚
};
static struct spi_board_info nrf24l01_spi_info[]={
{
.modalias = DEV_NAME_01,
.max_speed_hz = 2*1000*1000,//最大时钟,需比设备要求的时钟大,暂选2Mhz
.bus_num = 0,/*exynos4412 的spi0控制器*/
.chip_select = EXYNOS4_GPX3(2),/*由spi master确定其最大值*/
.mode = SPI_MODE_0,
.platform_data = (void*)EXYNOS4_GPX3(3), /*平台数据,作为NRF24LXX的CE引脚*/
.controller_data= (void*)&cs0,
},
{
.modalias = DEV_NAME_02,
.max_speed_hz = 2*1000*1000,//最大时钟,需比设备要求的时钟大,暂选2Mhz
.bus_num = 0,/*exynos4412 的spi0控制器*/
.chip_select = EXYNOS4_GPX3(4),/*由spi master确定其最大值*/
.mode = SPI_MODE_0,
.platform_data = (void*)EXYNOS4_GPX3(5), /*平台数据,作为NRF24LXX的CE引脚*/
.controller_data= (void*)&cs1,
},
};
static int __init demo_init(void)
{
printk(KERN_INFO"current func is-------------------------------------------%sn",__FUNCTION__);
/*spi_register_board_info
*依照spi_board_info 和spi_master的bus_num比较,匹配成功会生成一个对应的spi_device对象
*/
return spi_register_board_info(nrf24l01_spi_info,ARRAY_SIZE(nrf24l01_spi_info));
}
static void __exit demo_exit(void)
{
printk(KERN_INFO"current func is-----%sn",__FUNCTION__);
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
就当是记笔记。
硬件平台 :友善之臂Ting4412
: NRF24L01 2.4G无线模块SPI接口
内核版本:Linux3.5
硬件连接方式
以前在STM32F103C8T6上使用STM32的SPI控制器驱动过NRF24L01 2.4G无线模块,最近学习Linux设备驱动,刚好同学手边有一个Tiny4412开发板就当是练练手,顺便学习学习Linux下SPI设备驱动的编写。
NRF24L01 2.4G无线模块的SPI驱动暂时只是调试通过,能够实现Tiny4412通过NRF24L01 2.4G无线模块发送数据,暂时没写成字符设备驱动,在内核定时器中调用spi_write函数不断发送数据。
通信现象:
STM32没接到数据就会报错,如下:
一旦加载驱动模块,就立马接收正常,如下:
......................................................................................只说流程,不说原理............................................................................
1、构造struct spi_board_info对象,这里我们暂时构造两个,方便以后在SPI0控制器上挂载两个NFR24L01设备,实现相互收发数据。
struct s3c64xx_spi_csinfo cs0={
.fb_delay = 0,
.line = EXYNOS4_GPX3(2),//片选引脚
};
struct s3c64xx_spi_csinfo cs1={
.fb_delay = 0,
.line = EXYNOS4_GPX3(4),//片选引脚
};
static struct spi_board_info nrf24l01_spi_info[]={
{
.modalias = DEV_NAME_01,
.max_speed_hz = 10*1000*1000,//最大时钟,需比设备要求的时钟大,暂选10Mhz
.bus_num = 0,/*exynos4412 的spi0控制器*/
.chip_select = EXYNOS4_GPX3(2),/*由spi master确定其最大值*/
.mode = SPI_MODE_0,
.platform_data = (void*)EXYNOS4_GPX3(3), /*平台数据,作为NRF24LXX的CE引脚*/
.controller_data= (void*)&cs0,
},
{
.modalias = DEV_NAME_02,
.max_speed_hz = 10*1000*1000,//最大时钟,需比设备要求的时钟大,暂选10Mhz
.bus_num = 0,/*exynos4412 的spi0控制器*/
.chip_select = EXYNOS4_GPX3(4),/*由spi master确定其最大值*/
.mode = SPI_MODE_0,
.platform_data = (void*)EXYNOS4_GPX3(5), /*平台数据,作为NRF24LXX的CE引脚*/
.controller_data= (void*)&cs1,
},
};
2、注册它,内核会依据构造的struct spi_board_info 对象创建对应的struct spi_device对象。
在init函数中注册构造好的truct spi_board_info对象static struct spi_board_info nrf24l01_spi_info[]
static int __init demo_init(void)
{
printk(KERN_INFO"current func is-------------------------------------------%sn",__FUNCTION__);
/*spi_register_board_info
*依照spi_board_info 和spi_master的bus_num比较,匹配成功会生成一个对应的spi_device对象
*/
return spi_register_board_info(nrf24l01_spi_info,ARRAY_SIZE(nrf24l01_spi_info));
}
看看spi_register_board_info干了什么?
int spi_register_board_info(struct spi_board_info const *info, unsigned n)
//master和bi->board_info的bus_nus比较,相等则调用spi_new_device创建spi_device对象
spi_match_master_to_boardinfo(master, &bi->board_info);调用spi_new_device
spi_new_device(struct spi_master *master,struct spi_board_info *chip)
int spi_add_device(struct spi_device *spi)//注册struct spi_device
/*spi_add_device注册struct spi_device时候会做一些判断,一会进去分析 */
分析spi_new_device
int spi_register_board_info(struct spi_board_info const *info, unsigned n)
//master和bi->board_info的bus_nus比较,相等则调用spi_new_device创建spi_device对象
spi_match_master_to_boardinfo(master, &bi->board_info);调用spi_new_device
spi_new_device(struct spi_master *master,struct spi_board_info *chip)
int spi_add_device(struct spi_device *spi)//注册struct spi_device
/*spi_add_device注册struct spi_device时候会做一些判断,比如我们第一步构造
*static struct spi_board_info中的chip_select会和spi_master的num_chipselect做比较
*要求设置我们的片选信号不能大于spi_master的num_chipselect
* if (spi->chip_select >= spi->master->num_chipselect) {
* dev_err(dev, "cs%d >= max %dn",spi->chip_select,spi->master->num_chipselect);
* return -EINVAL;//直接报错返回,导致我们不能注册spi_device对象......
* 这个问题怎么解决?我们直接修改spi主控器驱动就好了。
*}
* 修改 vi arch/arm/mach-exynos/mach-tiny4412.c中设置spi控制器函数
* s3c64xx_spi0_set_platdata(NULL,0,1)这个函数就好了,内核默认设置
* 最大片选值为1,也就是说默认最多支持1个spi设备,那好了我们把它修改
* 大一些就好了,直接修改到0xffff,保证它一定能注册成功。
*/
至于程序中 struct s3c64xx_spi_csinfo cs0 和struct s3c64xx_spi_csinfo cs1怎么来的?为什么需要这两个?这个是安装驱动后看内核看内核打印错误信息找出来的。出错的地方为 driversspispi-s3c64xx.c的static int s3c64xx_spi_setup(struct spi_device *spi)函数中报if (IS_ERR_OR_NULL(cs)) {
dev_err(&spi->dev, "No CS for SPI(%d)n", spi->chip_select);
return -ENODEV;
}
意思是cs为空,那么追溯cs,发现 spi_device spi->controller_data = cs;那好,我们就在 static struct spi_board_info nrf24l01_spi_info[]添加.controller_data= (void*)&cs0即可。
struct s3c64xx_spi_csinfo cs0={
.fb_delay = 0,
.line = EXYNOS4_GPX3(2),
};
struct s3c64xx_spi_csinfo cs1={
.fb_delay = 0,
.line = EXYNOS4_GPX3(4),
};
好了下面编译成模块,但是编译的时候报WARNING: "spi_register_board_info"/nrf24xx_spi_device.ko] undefined!第一感觉是
spi_register_board_info相关的头文件没有,但是相关spi的头文件都加了,查资料发现原来是linux3.5源码中没有将spi_register_board_info函数导出符号表。那好,就编译进内核得了,将nrf24xx_spi_device.c复制drivers/spi目录下,修改Makefile,将nrf24xx_spi_device.c编译进内核。下面先给出nrf24xx_spi_device.c源码。
#include #include #include #include #include #include #include #include #include #include #include /******************硬件相关的信息****************/ #define DEV_NAME_01 "nrf24xx_spi_01" #define DEV_NAME_02 "nrf24xx_spi_02" #define DEV1_CS_PIN EXYNOS4_GPX3(2) #define DEV2_CS_PIN EXYNOS4_GPX3(4) #define DEV1_CE_PIN EXYNOS4_GPX3(3) #define DEV2_CE_PIN EXYNOS4_GPX3(5) /* 片选引脚接在GPX3_2 */ struct s3c64xx_spi_csinfo cs0={ .fb_delay = 0, .line = EXYNOS4_GPX3(2),//片选引脚 }; struct s3c64xx_spi_csinfo cs1={ .fb_delay = 0, .line = EXYNOS4_GPX3(4),//片选引脚 }; static struct spi_board_info nrf24l01_spi_info[]={ { .modalias = DEV_NAME_01, .max_speed_hz = 2*1000*1000,//最大时钟,需比设备要求的时钟大,暂选2Mhz .bus_num = 0,/*exynos4412 的spi0控制器*/ .chip_select = EXYNOS4_GPX3(2),/*由spi master确定其最大值*/ .mode = SPI_MODE_0, .platform_data = (void*)EXYNOS4_GPX3(3), /*平台数据,作为NRF24LXX的CE引脚*/ .controller_data= (void*)&cs0, }, { .modalias = DEV_NAME_02, .max_speed_hz = 2*1000*1000,//最大时钟,需比设备要求的时钟大,暂选2Mhz .bus_num = 0,/*exynos4412 的spi0控制器*/ .chip_select = EXYNOS4_GPX3(4),/*由spi master确定其最大值*/ .mode = SPI_MODE_0, .platform_data = (void*)EXYNOS4_GPX3(5), /*平台数据,作为NRF24LXX的CE引脚*/ .controller_data= (void*)&cs1, }, }; static int __init demo_init(void) { printk(KERN_INFO"current func is-------------------------------------------%sn",__FUNCTION__); /*spi_register_board_info *依照spi_board_info 和spi_master的bus_num比较,匹配成功会生成一个对应的spi_device对象 */ return spi_register_board_info(nrf24l01_spi_info,ARRAY_SIZE(nrf24l01_spi_info)); } static void __exit demo_exit(void) { printk(KERN_INFO"current func is-----%sn",__FUNCTION__); } module_init(demo_init); module_exit(demo_exit); MODULE_LICENSE("GPL");
make zImage,启动内核。在sys/bus/spi/devices目录下出现下面spi0.220 spi0.222两个节点
其中0表示bus_num,220和222表示我们的static struct spi_board_info nrf24l01_spi_info[0]的.chip_select = EXYNOS4_GPX3(2),//这个宏的值就是220同理222就是EXYNOS4_GPX3(4),默认内核只创建spi0.0、spi2.0。注意现在只是在内核中创建了spi_device,还没nrf24xx的驱动。nrf24xx_spi_driver就是在STM32程序基础上简单的修修改改就好了,对比Tiny4412和STM32的程序你会发现关于nrf24l01的代码名字是一样的。
..............................................................下面就直接给出nrf24xx_spi_driver.c的源码.........................................................................
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/******************硬件相关的信息****************/
#define DEV_NAME_01 "nrf24xx_spi_01"
#define DEV_NAME_02 "nrf24xx_spi_02"
#define DEV1_CS_PIN EXYNOS4_GPX3(2)
#define DEV2_CS_PIN EXYNOS4_GPX3(4)
#define DEV1_CE_PIN EXYNOS4_GPX3(3)
#define DEV2_CE_PIN EXYNOS4_GPX3(5)
/*
片选引脚接在GPX3_2
*/
struct s3c64xx_spi_csinfo cs0={
.fb_delay = 0,
.line = EXYNOS4_GPX3(2),//片选引脚
};
struct s3c64xx_spi_csinfo cs1={
.fb_delay = 0,
.line = EXYNOS4_GPX3(4),//片选引脚
};
static struct spi_board_info nrf24l01_spi_info[]={
{
.modalias = DEV_NAME_01,
.max_speed_hz = 2*1000*1000,//最大时钟,需比设备要求的时钟大,暂选2Mhz
.bus_num = 0,/*exynos4412 的spi0控制器*/
.chip_select = EXYNOS4_GPX3(2),/*由spi master确定其最大值*/
.mode = SPI_MODE_0,
.platform_data = (void*)EXYNOS4_GPX3(3), /*平台数据,作为NRF24LXX的CE引脚*/
.controller_data= (void*)&cs0,
},
{
.modalias = DEV_NAME_02,
.max_speed_hz = 2*1000*1000,//最大时钟,需比设备要求的时钟大,暂选2Mhz
.bus_num = 0,/*exynos4412 的spi0控制器*/
.chip_select = EXYNOS4_GPX3(4),/*由spi master确定其最大值*/
.mode = SPI_MODE_0,
.platform_data = (void*)EXYNOS4_GPX3(5), /*平台数据,作为NRF24LXX的CE引脚*/
.controller_data= (void*)&cs1,
},
};
static int __init demo_init(void)
{
printk(KERN_INFO"current func is-------------------------------------------%sn",__FUNCTION__);
/*spi_register_board_info
*依照spi_board_info 和spi_master的bus_num比较,匹配成功会生成一个对应的spi_device对象
*/
return spi_register_board_info(nrf24l01_spi_info,ARRAY_SIZE(nrf24l01_spi_info));
}
static void __exit demo_exit(void)
{
printk(KERN_INFO"current func is-----%sn",__FUNCTION__);
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
就当是记笔记。
举报