LoRaWAN网关源码分析(SPI篇)

目录

一、前言

二、lgw_spi_open函数

三、lgw_spi_w函数

四、lgw_spi_r函数

五、lgw_spi_wb函数

六、lgw_spi_rb函数


一、前言

        本篇文章整理了LoRaWAN网关如何处理与 LoRa 前端设备之间的 SPI通信(在loralgw_spi.c文件中)。对SPI协议不了解的可以看这篇文章---《SPI总线协议》。

二、lgw_spi_open函数

        该函数通常用于初始化 SPI 接口并准备与 LoRa 网关的前端设备进行通信。我们需要打开指定的 SPI 设备文件,并配置必要的 SPI 参数(如模式、速度等)。

主要流程:

1、打开SPI设备文件

spi_fd = open(SPI_DEVICE, O_RDWR);
  • 调用 open 函数以读写模式 (O_RDWR) 打开指定的 SPI 设备文件(如 /dev/spidev0.0)。
  • 如果打开失败,open 将返回 -1,并且 spi_fd 将保持为 -1。
  • 错误处理:如果 open 失败,调用 perror 打印错误信息,然后返回 -1。

2、设置SPI模式

ret = ioctl(spi_fd, SPI_IOC_WR_MODE, &SPI_MODE);
  • 使用 ioctl 函数将 SPI 模式设置为指定的模式(这里是模式 0)。
  • SPI_MODE 变量的值决定了时钟相位和极性。
  • 错误处理:如果 ioctl 失败,调用 perror 打印错误信息,关闭 SPI 设备文件并返回 -1。

3、设置每个传输字的位数

ret = ioctl(spi_fd, SPI_IOC_WR_BITS_PER_WORD, &SPI_BITS);
  • 使用 ioctl 函数设置每个 SPI 传输字的位数(这里是 8 位)。
  • 错误处理:如果 ioctl 失败,调用 perror 打印错误信息,关闭 SPI 设备文件并返回 -1。

4、设置SPI总线速度

ret = ioctl(spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &SPI_SPEED);
  • 使用 ioctl 函数设置 SPI 总线的最大速度(这里是 500 kHz)。
  • 错误处理:如果 ioctl 失败,调用 perror 打印错误信息,关闭 SPI 设备文件并返回 -1。

5、返回0表示成功

         如果所有设置都成功,函数返回 0 表示 SPI 初始化成功。

int lgw_spi_open(const char * com_path, void **com_target_ptr) {
    int *spi_device = NULL;
    int dev;
    int a=0, b=0;
    int i;

    /* check input variables */
    CHECK_NULL(com_path);
    CHECK_NULL(com_target_ptr);

    /* allocate memory for the device descriptor */
    spi_device = malloc(sizeof(int));
    if (spi_device == NULL) {
        DEBUG_MSG("ERROR: MALLOC FAIL\n");
        return LGW_SPI_ERROR;
    }

    /* open SPI device */
    dev = open(com_path, O_RDWR);
    if (dev < 0) {
        DEBUG_PRINTF("ERROR: failed to open SPI device %s\n", com_path);
        return LGW_SPI_ERROR;
    }

    /* setting SPI mode to 'mode 0' */
    i = SPI_MODE_0;
    a = ioctl(dev, SPI_IOC_WR_MODE, &i);
    b = ioctl(dev, SPI_IOC_RD_MODE, &i);
    if ((a < 0) || (b < 0)) {
        DEBUG_MSG("ERROR: SPI PORT FAIL TO SET IN MODE 0\n");
        close(dev);
        free(spi_device);
        return LGW_SPI_ERROR;
    }

    /* setting SPI max clk (in Hz) */
    i = SPI_SPEED;
    a = ioctl(dev, SPI_IOC_WR_MAX_SPEED_HZ, &i);
    b = ioctl(dev, SPI_IOC_RD_MAX_SPEED_HZ, &i);
    if ((a < 0) || (b < 0)) {
        DEBUG_MSG("ERROR: SPI PORT FAIL TO SET MAX SPEED\n");
        close(dev);
        free(spi_device);
        return LGW_SPI_ERROR;
    }

    /* setting SPI to MSB first */
    i = 0;
    a = ioctl(dev, SPI_IOC_WR_LSB_FIRST, &i);
    b = ioctl(dev, SPI_IOC_RD_LSB_FIRST, &i);
    if ((a < 0) || (b < 0)) {
        DEBUG_MSG("ERROR: SPI PORT FAIL TO SET MSB FIRST\n");
        close(dev);
        free(spi_device);
        return LGW_SPI_ERROR;
    }

    /* setting SPI to 8 bits per word */
    i = 0;
    a = ioctl(dev, SPI_IOC_WR_BITS_PER_WORD, &i);
    b = ioctl(dev, SPI_IOC_RD_BITS_PER_WORD, &i);
    if ((a < 0) || (b < 0)) {
        DEBUG_MSG("ERROR: SPI PORT FAIL TO SET 8 BITS-PER-WORD\n");
        close(dev);
        return LGW_SPI_ERROR;
    }

    *spi_device = dev;
    *com_target_ptr = (void *)spi_device;
    DEBUG_MSG("Note: SPI port opened and configured ok\n");
    return LGW_SPI_SUCCESS;
}

        相对应的,竟然要打开,就要关闭,关闭相应的SPI文件我们要使用lgw_spi_close函数,代码如下:

int lgw_spi_close(void *com_target) {
    int spi_device;
    int a;

    /* check input variables */
    CHECK_NULL(com_target);

    /* close file & deallocate file descriptor */
    spi_device = *(int *)com_target; /* must check that spi_target is not null beforehand */
    a = close(spi_device);
    free(com_target);

    /* determine return code */
    if (a < 0) {
        DEBUG_MSG("ERROR: SPI PORT FAILED TO CLOSE\n");
        return LGW_SPI_ERROR;
    } else {
        DEBUG_MSG("Note: SPI port closed\n");
        return LGW_SPI_SUCCESS;
    }
}

三、lgw_spi_w函数

        该函数通过 SPI 接口向指定的目标设备(由 spi_mux_target 标识)写入一个字节的数据到指定的寄存器地址。

主要流程:

1、获取SPI设备文件描述符

spi_device = *(int *)com_target;

        将 com_target 解释为指向 int 的指针,并解引用以获取 SPI 设备文件描述符。

2、准备要发送的帧

out_buf[0] = spi_mux_target;
out_buf[1] = WRITE_ACCESS | ((address >> 8) & 0x7F);
out_buf[2] =                ((address >> 0) & 0xFF);
out_buf[3] = data;
command_size = 4;
  • 构建要发送的 SPI 帧:
    • out_buf[0]:目标设备选择(SPI 多路复用目标)。
    • out_buf[1]:写访问标志和地址的高位(写命令)。
    • out_buf[2]:地址的低位。
    • out_buf[3]:要写入的数据字节。
  • command_size:设置为 4,表示总共发送 4 个字节。

3、准备I/O传输结构体

memset(&k, 0, sizeof(k));
k.tx_buf = (unsigned long) out_buf;
k.len = command_size;
k.speed_hz = SPI_SPEED;
k.cs_change = 0;
k.bits_per_word = 8;

4、执行I/O传输

a = ioctl(spi_device, SPI_IOC_MESSAGE(1), &k);

5、判断返回值并处理错误

if (a != (int)k.len) {
    DEBUG_MSG("ERROR: SPI WRITE FAILURE\n");
    return LGW_SPI_ERROR;
} else {
    DEBUG_MSG("Note: SPI write success\n");
    return LGW_SPI_SUCCESS;
}
int lgw_spi_w(void *com_target, uint8_t spi_mux_target, uint16_t address, uint8_t data) {
    int spi_device;
    uint8_t out_buf[4];
    uint8_t command_size;
    struct spi_ioc_transfer k;
    int a;

    /* check input variables */
    CHECK_NULL(com_target);

    spi_device = *(int *)com_target; /* must check that spi_target is not null beforehand */

    /* prepare frame to be sent */
    out_buf[0] = spi_mux_target;
    out_buf[1] = WRITE_ACCESS | ((address >> 8) & 0x7F);
    out_buf[2] =                ((address >> 0) & 0xFF);
    out_buf[3] = data;
    command_size = 4;

    /* I/O transaction */
    memset(&k, 0, sizeof(k)); /* clear k */
    k.tx_buf = (unsigned long) out_buf;
    k.len = command_size;
    k.speed_hz = SPI_SPEED;
    k.cs_change = 0;
    k.bits_per_word = 8;
    a = ioctl(spi_device, SPI_IOC_MESSAGE(1), &k);

    /* determine return code */
    if (a != (int)k.len) {
        DEBUG_MSG("ERROR: SPI WRITE FAILURE\n");
        return LGW_SPI_ERROR;
    } else {
        DEBUG_MSG("Note: SPI write success\n");
        return LGW_SPI_SUCCESS;
    }
}

四、lgw_spi_r函数

        该函数通过 SPI 接口从指定的目标设备(由 spi_mux_target 标识)的指定寄存器地址读取一个字节的数据。

主要流程:

1、获取SPI设备文件描述符

spi_device = *(int *)com_target;

        将 com_target 解释为指向 int 的指针,并解引用以获取 SPI 设备文件描述符。

2、准备要发送的帧

out_buf[0] = spi_mux_target;
out_buf[1] = READ_ACCESS | ((address >> 8) & 0x7F);
out_buf[2] = ((address >> 0) & 0xFF);
out_buf[3] = 0x00;
out_buf[4] = 0x00;
command_size = 5;
  • out_buf[0]:目标设备选择(SPI 多路复用目标)。
  • out_buf[1]:读访问标志和地址的高位(读命令)。
  • out_buf[2]:地址的低位。
  • out_buf[3]out_buf[4]:填充字节,用于接收数据时的占位符。

3、准备I/O传输结构体

memset(&k, 0, sizeof(k)); /* 清空 k */
k.tx_buf = (unsigned long) out_buf;
k.rx_buf = (unsigned long) in_buf;
k.len = command_size;
k.cs_change = 0;

4、执行I/O传输

a = ioctl(spi_device, SPI_IOC_MESSAGE(1), &k);

5、判断返回值并处理错误

if (a != (int)k.len) {
    DEBUG_MSG("ERROR: SPI READ FAILURE\n");
    return LGW_SPI_ERROR;
} else {
    DEBUG_MSG("Note: SPI read success\n");
    *data = in_buf[command_size - 1];
    return LGW_SPI_SUCCESS;
}
int lgw_spi_r(void *com_target, uint8_t spi_mux_target, uint16_t address, uint8_t *data) {
    int spi_device;
    uint8_t out_buf[5];
    uint8_t command_size;
    uint8_t in_buf[ARRAY_SIZE(out_buf)];
    struct spi_ioc_transfer k;
    int a;

    /* check input variables */
    CHECK_NULL(com_target);
    CHECK_NULL(data);

    spi_device = *(int *)com_target; /* must check that com_target is not null beforehand */

    /* prepare frame to be sent */
    out_buf[0] = spi_mux_target;
    out_buf[1] = READ_ACCESS | ((address >> 8) & 0x7F);
    out_buf[2] =               ((address >> 0) & 0xFF);
    out_buf[3] = 0x00;
    out_buf[4] = 0x00;
    command_size = 5;

    /* I/O transaction */
    memset(&k, 0, sizeof(k)); /* clear k */
    k.tx_buf = (unsigned long) out_buf;
    k.rx_buf = (unsigned long) in_buf;
    k.len = command_size;
    k.cs_change = 0;
    a = ioctl(spi_device, SPI_IOC_MESSAGE(1), &k);

    /* determine return code */
    if (a != (int)k.len) {
        DEBUG_MSG("ERROR: SPI READ FAILURE\n");
        return LGW_SPI_ERROR;
    } else {
        DEBUG_MSG("Note: SPI read success\n");
        *data = in_buf[command_size - 1];
        return LGW_SPI_SUCCESS;
    }
}

五、lgw_spi_wb函数

        该函数用于在 SPI 设备上执行一个批量写操作,可以一次性写入多个字节的数据到指定的寄存器地址。

主要流程:

1、SPI设备选择

spi_device = *(int *)com_target;

2、准备命令字节

    command[0] = spi_mux_target;
    command[1] = WRITE_ACCESS | ((address >> 8) & 0x7F);
    command[2] =                ((address >> 0) & 0xFF);
    command_size = 3;
    size_to_do = size;
  • command[0]:设备选择字节,设置为 spi_mux_target
  • command[1]command[2]:组合地址和写操作标志,其中 (address >> 8) & 0x7F 是地址的高 7 位,(address >> 0) & 0xFF 是地址的低 8 位。写操作标志由 WRITE_ACCESS 定义。

3、准备SPI传输结构体

    memset(&k, 0, sizeof(k)); /* clear k */
    k[0].tx_buf = (unsigned long) &command[0];
    k[0].len = command_size;
    k[0].cs_change = 0;
    k[1].cs_change = 0;
    
  • 使用 struct spi_ioc_transfer k[2] 数组来准备两个传输结构体,因为这是一个批量操作。
  • k[0] 结构体用于发送命令字节。
  • k[1] 结构体用于发送数据部分。

4、执行批量写操作

  for (i=0; size_to_do > 0; ++i) {
        chunk_size = (size_to_do < LGW_BURST_CHUNK) ? size_to_do : LGW_BURST_CHUNK;
        offset = i * LGW_BURST_CHUNK;
        k[1].tx_buf = (unsigned long)(data + offset);
        k[1].len = chunk_size;
        byte_transfered += (ioctl(spi_device, SPI_IOC_MESSAGE(2), &k) - k[0].len );
        DEBUG_PRINTF("BURST WRITE: to trans %d # chunk %d # transferred %d \n", size_to_do, chunk_size, byte_transfered);
        size_to_do -= chunk_size; /* subtract the quantity of data already transferred */
    }
  • 使用循环来处理数据的分块写入,每次写入不超过 LGW_BURST_CHUNK 大小的数据块。
  • chunk_size:计算每次实际写入的数据块大小。
  • offset:计算当前写入的数据在 data 数组中的偏移量。
  • 调用 ioctl 函数发送 k 数组中的两个结构体,分别发送命令和数据部分。
  • 累计 byte_transfered 变量记录实际传输的字节数。

5、返回状态

int lgw_spi_wb(void *com_target, uint8_t spi_mux_target, uint16_t address, const uint8_t *data, uint16_t size) {
    int spi_device;
    uint8_t command[3];
    uint8_t command_size;
    struct spi_ioc_transfer k[2];
    int size_to_do, chunk_size, offset;
    int byte_transfered = 0;
    int i;

    /* check input parameters */
    CHECK_NULL(com_target);
    CHECK_NULL(data);
    if (size == 0) {
        DEBUG_MSG("ERROR: BURST OF NULL LENGTH\n");
        return LGW_SPI_ERROR;
    }

    spi_device = *(int *)com_target; /* must check that com_target is not null beforehand */

    /* prepare command byte */
    command[0] = spi_mux_target;
    command[1] = WRITE_ACCESS | ((address >> 8) & 0x7F);
    command[2] =                ((address >> 0) & 0xFF);
    command_size = 3;
    size_to_do = size;

    /* I/O transaction */
    memset(&k, 0, sizeof(k)); /* clear k */
    k[0].tx_buf = (unsigned long) &command[0];
    k[0].len = command_size;
    k[0].cs_change = 0;
    k[1].cs_change = 0;
    for (i=0; size_to_do > 0; ++i) {
        chunk_size = (size_to_do < LGW_BURST_CHUNK) ? size_to_do : LGW_BURST_CHUNK;
        offset = i * LGW_BURST_CHUNK;
        k[1].tx_buf = (unsigned long)(data + offset);
        k[1].len = chunk_size;
        byte_transfered += (ioctl(spi_device, SPI_IOC_MESSAGE(2), &k) - k[0].len );
        DEBUG_PRINTF("BURST WRITE: to trans %d # chunk %d # transferred %d \n", size_to_do, chunk_size, byte_transfered);
        size_to_do -= chunk_size; /* subtract the quantity of data already transferred */
    }

    /* determine return code */
    if (byte_transfered != size) {
        DEBUG_MSG("ERROR: SPI BURST WRITE FAILURE\n");
        return LGW_SPI_ERROR;
    } else {
        DEBUG_MSG("Note: SPI burst write success\n");
        return LGW_SPI_SUCCESS;
    }
}

六、lgw_spi_rb函数

       该函数用于 在 SPI 设备上执行一个批量读取操作,将多个字节的数据从指定地址的寄存器中读取到给定的数据数组中。

主要流程:

1、SPI设备选择

spi_device = *(int *)com_target;

2、准备命令字节

    command[0] = spi_mux_target;
    command[1] = READ_ACCESS | ((address >> 8) & 0x7F);
    command[2] =               ((address >> 0) & 0xFF);
    command[3] = 0x00;
    command_size = 4;
    size_to_do = size;
    • command[0]:设备选择字节,设置为 spi_mux_target
    • command[1]command[2]command[3]:组合地址和读操作标志,其中 (address >> 8) & 0x7F 是地址的高 7 位,(address >> 0) & 0xFF 是地址的低 8 位。读操作标志由 READ_ACCESS 定义,最后一个字节设为 0x00

 3、准备SPI传输结构体

    memset(&k, 0, sizeof(k)); /* clear k */
    k[0].tx_buf = (unsigned long) &command[0];
    k[0].len = command_size;
    k[0].cs_change = 0;
    k[1].cs_change = 0;
  • 使用 struct spi_ioc_transfer k[2] 数组来准备两个传输结构体,因为这是一个批量操作。
  • k[0] 结构体用于发送命令字节。
  • k[1] 结构体用于接收数据部分。

4、执行批量读取操作

for (i=0; size_to_do > 0; ++i) {
        chunk_size = (size_to_do < LGW_BURST_CHUNK) ? size_to_do : LGW_BURST_CHUNK;
        offset = i * LGW_BURST_CHUNK;
        k[1].rx_buf = (unsigned long)(data + offset);
        k[1].len = chunk_size;
        byte_transfered += (ioctl(spi_device, SPI_IOC_MESSAGE(2), &k) - k[0].len );
        DEBUG_PRINTF("BURST READ: to trans %d # chunk %d # transferred %d \n", size_to_do, chunk_size, byte_transfered);
        size_to_do -= chunk_size;  /* subtract the quantity of data already transferred */
    }
  • 使用循环来处理数据的分块读取,每次读取不超过 LGW_BURST_CHUNK 大小的数据块。
  • chunk_size:计算每次实际读取的数据块大小。
  • offset:计算当前读取的数据在 data 数组中的偏移量。
  • 调用 ioctl 函数发送 k 数组中的两个结构体,分别发送命令和接收数据部分。
  • 累计 byte_transfered 变量记录实际传输的字节数。

5、返回状态

int lgw_spi_rb(void *com_target, uint8_t spi_mux_target, uint16_t address, uint8_t *data, uint16_t size) {
    int spi_device;
    uint8_t command[4];
    uint8_t command_size;
    struct spi_ioc_transfer k[2];
    int size_to_do, chunk_size, offset;
    int byte_transfered = 0;
    int i;

    /* check input parameters */
    CHECK_NULL(com_target);
    CHECK_NULL(data);
    if (size == 0) {
        DEBUG_MSG("ERROR: BURST OF NULL LENGTH\n");
        return LGW_SPI_ERROR;
    }

    spi_device = *(int *)com_target; /* must check that com_target is not null beforehand */

    /* prepare command byte */
    command[0] = spi_mux_target;
    command[1] = READ_ACCESS | ((address >> 8) & 0x7F);
    command[2] =               ((address >> 0) & 0xFF);
    command[3] = 0x00;
    command_size = 4;
    size_to_do = size;

    /* I/O transaction */
    memset(&k, 0, sizeof(k)); /* clear k */
    k[0].tx_buf = (unsigned long) &command[0];
    k[0].len = command_size;
    k[0].cs_change = 0;
    k[1].cs_change = 0;
    for (i=0; size_to_do > 0; ++i) {
        chunk_size = (size_to_do < LGW_BURST_CHUNK) ? size_to_do : LGW_BURST_CHUNK;
        offset = i * LGW_BURST_CHUNK;
        k[1].rx_buf = (unsigned long)(data + offset);
        k[1].len = chunk_size;
        byte_transfered += (ioctl(spi_device, SPI_IOC_MESSAGE(2), &k) - k[0].len );
        DEBUG_PRINTF("BURST READ: to trans %d # chunk %d # transferred %d \n", size_to_do, chunk_size, byte_transfered);
        size_to_do -= chunk_size;  /* subtract the quantity of data already transferred */
    }

    /* determine return code */
    if (byte_transfered != size) {
        DEBUG_MSG("ERROR: SPI BURST READ FAILURE\n");
        return LGW_SPI_ERROR;
    } else {
        DEBUG_MSG("Note: SPI burst read success\n");
        return LGW_SPI_SUCCESS;
    }
}

  • 29
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力学代码的小信

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值