目录
一、前言
本篇文章整理了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;
}
}