STM32
直播中

h1654155272.9717

8年用户 1259经验值
擅长:电源/新能源
私信 关注
[问答]

如何实现Tiny4412通过NRF24L01 2.4G无线模块发送数据呢

Linux下SPI设备驱动该怎样去编写呢?
如何实现tiny4412通过NRF24L01 2.4G无线模块发送数据呢?

回帖(2)

孙巍

2021-12-17 11:11:49
硬件平台  :友善之臂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");
就当是记笔记。
举报

李万寿

2021-12-17 11:12:50
编译驱动,并加载模块,在sys/bus/spi/drivers/目录下出现我们的nrf24xxspi驱动,如下图:  

  ..................................................................................下面直接给出STM32的源码.........................................................................


#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

#include "nrf24xx.h"

#define DEVICE_NUM        1
#define GPX3CON  0x11000C60
#define GPX3DAT         0x11000C64

#define DEV_NAME_01  "nrf24xx_spi_01"
#define DEV_NAME_02  "nrf24xx_spi_02"
#define DEV_NAME        DEV_NAME_01


#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)

unsigned char TX_ADDRESS[TX_ADR_WIDTH_5]={0x34,0x43,0x10,0x10,0x01}; //发送地址
unsigned char RX_ADDRESS[TX_ADR_WIDTH_5]={0x34,0x43,0x10,0x10,0x01};


static struct work_struct work;
struct timer_list timer;
struct spi_device *nrf24xx_spi;

static int nrf24l01_spi_probe(struct spi_device *spi);
static int nrf24l01_spi_remove(struct spi_device *spi);

void Nrf24L10_RX_Mode(void);
void Nrf24L10_TX_Mode(void);
unsigned char SPI1_ReadBuf(unsigned char reg,unsigned char *buf,unsigned char length);
unsigned char SPI1_SendBuf(unsigned char reg,unsigned char *buf,unsigned char length);
unsigned char SPI1_Write_Register(unsigned char reg,unsigned char data);
void Active_Nrf24l10(unsigned char cmd);
unsigned int SPI1_Read_Reg(unsigned char reg,unsigned char *status);
unsigned char Nrf24l10_SendBuf(unsigned char* data_buffer, unsigned char length);
unsigned char Nrf24l0_ReceiveBuf(unsigned char *buf);
unsigned char Nrf24l10_Irq(void);

char tmp=0,counter =0;
static struct spi_driver nrf24l01_spi_driver={
        .driver={
                .name         = DEV_NAME,
                .bus    =&spi_bus_type,
                .owner  = THIS_MODULE,
        },
        .probe = nrf24l01_spi_probe,
        .remove =__devexit_p(nrf24l01_spi_remove),
};

static void timer_function(unsigned long counter)
{
        //printk(KERN_INFO"current func is-----%sn",__FUNCTION__);
        tmp = tmp?0:1;
        gpio_set_value(DEV2_CE_PIN,tmp);       
        mod_timer(&timer,jiffies+HZ);       
        schedule_work(&work);
       
}
/*中断底半部中发送数据*/
static void irqBottom(struct work_struct *work)
{
        char wbuf[50]={0};
        int ret =0;
//        char reg = EN_AA;
        counter = counter>30? 0:counter;
        sprintf(wbuf,"Message Comes From Linux -%d %c",counter,'');
        counter++;
        ret = Nrf24l10_SendBuf(wbuf,RX_PLOAD_WIDTH_32);//该函数会导致休眠,不能在中断函数中使用
        printk("Message Send to STM3 .......%dn",counter);
}
static int nrf24l01_spi_probe(struct spi_device *spi)
{
        nrf24xx_spi = spi;
        printk(KERN_INFO"current func is-----%sn",__FUNCTION__);
        printk(KERN_INFO"max_speed_hz:%dn",spi->max_speed_hz);       
        printk(KERN_INFO"chip_select:%dn",spi->chip_select);       
        INIT_WORK(&work, irqBottom);
        s3c_gpio_cfgpin(DEV1_CS_PIN, S3C_GPIO_OUTPUT);//设置片选为输出
        s3c_gpio_cfgpin(DEV2_CS_PIN, S3C_GPIO_OUTPUT);//设置片选为输出
       
        s3c_gpio_cfgpin(DEV1_CE_PIN, S3C_GPIO_OUTPUT);//设置片选为输出
        s3c_gpio_cfgpin(DEV2_CE_PIN, S3C_GPIO_INPUT);//设置片选为输出
        Nrf24L10_TX_Mode();
        init_timer(&timer);//初始化内核定时器
        timer.function = timer_function;  
        add_timer(&timer);               
        return 0;
}

static int nrf24l01_spi_remove(struct spi_device *spi)
{
        printk(KERN_INFO"current func is-----%sn",__FUNCTION__);
        del_timer(&timer);
        return 0;
}

static int __init demo_init(void)
{
        int err;
        printk(KERN_INFO"current func is-----%sn",__FUNCTION__);
        err = spi_register_driver(&nrf24l01_spi_driver);
        return err;
}

static void __exit demo_exit(void)
{       
        printk(KERN_INFO"current func is-----%sn",__FUNCTION__);
        spi_unregister_driver(&nrf24l01_spi_driver);
}

module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");

void Nrf24L10_RX_Mode(void)
{
        Active_Nrf24l10(0);//进入设置模式
        SPI1_SendBuf(WRITE_REG1 + RX_ADDR_P0, RX_ADDRESS, TX_ADR_WIDTH_5);         //数据通道0发送地址,最大5个字节
        SPI1_Write_Register(WRITE_REG1 + EN_AA, 0x01);    // 使能通道0-通道5接收自动应答         
        SPI1_Write_Register(WRITE_REG1 + EN_RXADDR, 0x01);         //接收通道0-5 使能
        SPI1_Write_Register(WRITE_REG1 + RF_CH, 40);         // 选择射频工作频道0   范围0-127  频道选择寄存器
        SPI1_Write_Register(WRITE_REG1+RX_PW_P0,RX_PLOAD_WIDTH_32);
        SPI1_Write_Register(WRITE_REG1 + RF_SETUP, 0x0f);          
        SPI1_Write_Register(WRITE_REG1 + CONFIG, 0x0f);     
        Active_Nrf24l10(1);//激活发送
}

void Nrf24L10_TX_Mode(void)
{       
        Active_Nrf24l10(0);
        SPI1_SendBuf(WRITE_REG1 + TX_ADDR, TX_ADDRESS, TX_ADR_WIDTH_5);  //数据通道0发送地址,最大5个字节
        SPI1_SendBuf(WRITE_REG1 + RX_ADDR_P0, RX_ADDRESS, TX_ADR_WIDTH_5);//数据通道0发送地址,最大5个字节

        SPI1_Write_Register(WRITE_REG1 + EN_AA, 0x01);// 使能通道0-通道5接收自动应答         
        SPI1_Write_Register(WRITE_REG1 + EN_RXADDR, 0x01);//接收通道0-5 使能
        SPI1_Write_Register(WRITE_REG1+SETUP_RETR,0x1a);
        SPI1_Write_Register(WRITE_REG1 + RF_CH, 40);// 选择射频工作频道0   范围0-127  频道选择寄存器
        SPI1_Write_Register(WRITE_REG1 + RF_SETUP, 0x0f);          
        SPI1_Write_Register(WRITE_REG1 + CONFIG, 0x0e);   
        Active_Nrf24l10(1);        // 使能发送模式
}

unsigned int SPI1_Read_Reg(unsigned char reg,unsigned char *status)
{
        unsigned char wbuf=reg;
        struct spi_message        m;
        struct spi_transfer        t[2] = {
                {
                        .tx_buf                = &wbuf,
                        .len                = 1,                       
                },
                {
                        .rx_buf                = status,
                        .len                = 1,
                },       
        };
        spi_message_init(&m);
        spi_message_add_tail(&t[0], &m);
        spi_message_add_tail(&t[1], &m);       
        return spi_sync(nrf24xx_spi, &m);  
}

unsigned char SPI1_Write_Register(unsigned char reg,unsigned char data)
{
        unsigned char wbuf[2]={0};
       
        struct spi_message        m;
        struct spi_transfer        t = {
                .tx_buf                = wbuf,
                .len                = 2,       
        };
        //printk(KERN_INFO"current func is-----%sn",__FUNCTION__);       
        wbuf[0]=reg;
        wbuf[1]=data;
        spi_message_init(&m);
        spi_message_add_tail(&t, &m);
        return spi_sync(nrf24xx_spi, &m);  
}

/*
        SPI1发送一个缓冲区
*/

unsigned char SPI1_SendBuf(unsigned char reg,unsigned char *buf,unsigned char length)
{
        struct spi_message        m;
        unsigned char wbuf=reg;
        struct spi_transfer        t[2] = {
                {
                        .tx_buf                = &wbuf,
                        .len                = 1,                       
                },
                {
                        .tx_buf                = buf,
                        .len                = length,
                },
                       
        };
        spi_message_init(&m);
        spi_message_add_tail(&t[0], &m);
        spi_message_add_tail(&t[1], &m);       
        return spi_sync(nrf24xx_spi, &m);
}
unsigned char SPI1_ReadBuf(unsigned char reg,unsigned char *buf,unsigned char length)
{
        unsigned char wbuf=reg;
        struct spi_message        m;
        struct spi_transfer        t[2] = {
                {
                        .tx_buf                = &wbuf,
                        .len                = 1,       
                },
                {
                        .rx_buf                = buf,
                        .len                = length,       
                }

                };
        spi_message_init(&m);
        spi_message_add_tail(&t[0], &m);
        spi_message_add_tail(&t[1], &m);       
        return spi_sync(nrf24xx_spi, &m);
}
void Active_Nrf24l10(unsigned char cmd)
{
        gpio_set_value(DEV2_CE_PIN,cmd);
}

unsigned char Nrf24l0_ReceiveBuf(unsigned char *buf)
{
        unsigned char status = 0;
        SPI1_Read_Reg(STATUS,&status);
        SPI1_Write_Register(WRITE_REG1 +STATUS ,status);//清除相应的标志位
        if(status & RX_OK)
        {
                SPI1_ReadBuf(RD_RX_PLOAD,buf,RX_PLOAD_WIDTH_32);
                SPI1_Write_Register(FLUSH_RX,0xff);
                return RX_OK;                                                                                                                                        //接收成功返回RX_OK
        }       
        return OTHER_ERROR;
}

unsigned char Nrf24l10_SendBuf(unsigned char* data_buffer, unsigned char length)
{
        unsigned char status = 0;
        Active_Nrf24l10(0);
        SPI1_SendBuf(WR_TX_PLOAD,data_buffer,TX_PLOAD_WIDTH_32);//写数据到TX BUF  32个字节
//        SPI1_SendBuf(WR_TX_PLOAD,data_buffer,length);//写数据到TX BUF  32个字节
        Active_Nrf24l10(1);
        while(1 == Nrf24l10_Irq())//等待发送完成事件
        {
                //printk("Nrf24l10_Irq..............n");
                schedule_timeout(HZ/50);//HZ=100 , 每10ms jiffies+1
        }
        SPI1_Read_Reg(STATUS,&status);
        SPI1_Write_Register(WRITE_REG1 +STATUS ,status);//清除相应的标志位
        if(status & TX_OK)
        {
                return TX_OK;                                                                                                                                        //接收成功返回RX_OK
        }else if(status & MAX_RET_FLAG)
        {
                SPI1_Write_Register(FLUSH_TX,0xff);       
                return MAX_RET_FLAG;                                                                                                                                        //否则返回ERROR
        }       
        return OTHER_ERROR;
}
unsigned char Nrf24l10_Irq(void)
{
        return gpio_get_value(DEV2_CE_PIN);
}

新手整整搞了3天才调通,还有有些地方说的不好,见谅。
举报

更多回帖

发帖
×
20
完善资料,
赚取积分