我正在尝试使用SPIDEV驱动程序访问SPI传感器,但我的代码卡在IOCTL上。
我在SAM9X5EK上运行嵌入式Linux(安装AT91SAM9G25)。该器件连接到SPI0。我在menuconfig中启用了CONFIG_SPI_SPIDEV和CONFIG_SPI_ATMEL,并将正确的代码添加到BSP文件中:
static struct spi_board_info spidev_board_info[] {
{
.modalias = "spidev",
.max_speed_hz = 1000000,
.bus_num = 0,
.chips_select = 0,
.mode = SPI_MODE_3,
},
...
};
spi_register_board_info(spidev_board_info, ARRAY_SIZE(spidev_board_info));
1MHz是传感器接受的最大值,我尝试了500kHz但是在Linux启动期间出现错误(显然太慢)。 .bus_num和.chips_select应该更正(我也尝试了所有其他组合)。 SPI_MODE_3我检查了数据表。
启动时没有错误,设备正确显示为/dev/spidevX.X。我设法打开文件并获取有效的文件描述符。我现在正尝试使用以下代码访问设备(灵感来自我在网上找到的示例)。
#define MY_SPIDEV_DELAY_USECS 100
// #define MY_SPIDEV_SPEED_HZ 1000000
#define MY_SPIDEV_BITS_PER_WORD 8
int spidevReadRegister(int fd,
unsigned int num_out_bytes,
unsigned char *out_buffer,
unsigned int num_in_bytes,
unsigned char *in_buffer)
{
struct spi_ioc_transfer mesg[2] = { {0}, };
uint8_t num_tr = 0;
int ret;
// Write data
mesg[0].tx_buf = (unsigned long)out_buffer;
mesg[0].rx_buf = (unsigned long)NULL;
mesg[0].len = num_out_bytes;
// mesg[0].delay_usecs = MY_SPIDEV_DELAY_USECS,
// mesg[0].speed_hz = MY_SPIDEV_SPEED_HZ;
mesg[0].bits_per_word = MY_SPIDEV_BITS_PER_WORD;
mesg[0].cs_change = 0;
num_tr++;
// Read data
mesg[1].tx_buf = (unsigned long)NULL;
mesg[1].rx_buf = (unsigned long)in_buffer;
mesg[1].len = num_in_bytes;
// mesg[1].delay_usecs = MY_SPIDEV_DELAY_USECS,
// mesg[1].speed_hz = MY_SPIDEV_SPEED_HZ;
mesg[1].bits_per_word = MY_SPIDEV_BITS_PER_WORD;
mesg[1].cs_change = 1;
num_tr++;
// Do the actual transmission
if(num_tr > 0)
{
ret = ioctl(fd, SPI_IOC_MESSAGE(num_tr), mesg);
if(ret == -1)
{
printf("Error: %d\n", errno);
return -1;
}
}
return 0;
}
然后我正在使用这个功能:
#define OPTICAL_SENSOR_ADDR "/dev/spidev0.0"
...
int fd;
fd = open(OPTICAL_SENSOR_ADDR, O_RDWR);
if (fd<=0) {
printf("Device not found\n");
exit(1);
}
uint8_t buffer1[1] = {0x3a};
uint8_t buffer2[1] = {0};
spidevReadRegister(fd, 1, buffer1, 1, buffer2);
当我运行它时,代码卡在IOCTL上!
我这样做是因为,为了读取传感器上的寄存器,我需要发送一个带有地址的字节,然后在不改变CS的情况下得到答案(但是,当我尝试使用write()时read()函数,在学习时,我得到了相同的结果,坚持了它们)。 我知道指定.speed_hz会导致Atmel上的ENOPROTOOPT错误(我检查了spidev.c)所以我评论了那部分。
为什么会卡住?我虽然它可以是设备创建但实际上并没有“感觉”任何硬件。由于我不确定硬件SPI0是否对应于bus_num 0或1,我尝试了两种,但仍然没有成功(顺便说一下,哪一个是它?)。
更新:我设法让SPI正常工作!一半.. MOSI正在传输正确的数据,但CLK无法启动......任何想法?
答案 0 :(得分:3)
当我使用SPI时,我总是使用oscyloscope来查看io的输出。如果你有一个4通道范围,ypu可以很容易地调试问题,并找出你是否正在使用合适的速度等正确的io,我通常比较我得到的数据表图。
答案 1 :(得分:2)
我认为这里有几个问题。首先,SPI是双向的。所以如果你想通过公共汽车发送一些东西你也会得到一些东西。因此,您必须为rx_buf和tx_buf提供有效的缓冲区。
其次,必须使用有效值初始化struct spi_ioc_transfer的所有成员。否则他们只是指向一些内存地址,而底层进程正在访问任意数据,从而导致未知行为。
第三,为什么要在ioctl中使用for循环?你已经告诉ioctl你有一系列spi_ioc_transfer结构。因此,所有定义的事务都将通过一次ioctl调用来执行。
第四个ioctl 需要一个指针到你的struct数组。所以ioctl应该是这样的:
ret = ioctl(fd, SPI_IOC_MESSAGE(num_tr), &mesg);
您认为代码中存在改进的空间。
这是我在覆盆子pi的c ++库中的方法。整个图书馆很快就会出现在github上。完成后我会更新我的答案。
void SPIBus::spiReadWrite(std::vector<std::vector<uint8_t> > &data, uint32_t speed,
uint16_t delay, uint8_t bitsPerWord, uint8_t cs_change)
{
struct spi_ioc_transfer transfer[data.size()];
int i = 0;
for (std::vector<uint8_t> &d : data)
{
//see <linux/spi/spidev.h> for details!
transfer[i].tx_buf = reinterpret_cast<__u64>(d.data());
transfer[i].rx_buf = reinterpret_cast<__u64>(d.data());
transfer[i].len = d.size(); //number of bytes in vector
transfer[i].speed_hz = speed;
transfer[i].delay_usecs = delay;
transfer[i].bits_per_word = bitsPerWord;
transfer[i].cs_change = cs_change;
i++
}
int status = ioctl(this->fileDescriptor, SPI_IOC_MESSAGE(data.size()), &transfer);
if (status < 0)
{
std::string errMessage(strerror(errno));
throw std::runtime_error("Failed to do full duplex read/write operation "
"on SPI Bus " + this->deviceNode + ". Error message: " +
errMessage);
}
}