SPI(串行端口通信)问题,卡在 ioctl()

Posted

技术标签:

【中文标题】SPI(串行端口通信)问题,卡在 ioctl()【英文标题】:Issue with SPI (Serial Port Comm), stuck on ioctl() 【发布时间】:2012-05-31 06:01:12 【问题描述】:

我正在尝试使用 SPIDEV 驱动程序访问 SPI 传感器,但我的代码卡在 IOCTL 上。

我在 SAM9X5EK(安装 AT91SAM9G25)上运行嵌入式 Linux。该设备连接到 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 没有启动...有什么想法吗?

【问题讨论】:

内核调试的蛮力方法是加载所有可能与 printk 相关的东西,这样你就可以获得关于它最后尝试做什么的大量信息。作为猜测,您可能会等待不会发生的硬件事件,因为它取决于未运行的时钟或没有启用、电源启用或时钟启用的芯片外围区域位设置。 感谢您的提示。但是,这很奇怪,因为内核加载时没有发生真正的错误,设备出现在 /dev/spidevX.X 中。我检查了时钟,启动过程中的一行显示“时钟:CPU 400 MHz,主时钟 133 MHz,主时钟 12.000 MHz”。这是有道理的,因为如果我尝试以 500kHz(133MHz/255 > 500khz)使用 spidev,则会出现引导错误,而如果我将 spidev 设置为 1MHz,它会正确引导。显然这里一定有我遗漏的东西...... 许多 SOC 有大量的外设时钟和时钟、电源等支持片上外设(例如 spi 引擎),因此除非您通过挂在同一个 spi 引擎上的东西启动,否则成功启动很少(甚至那么,在内核随后破坏或禁用 spi 配置之前,引导加载程序可能已经读取了内核)。您最好的选择可能是查看您是否可以从该板上找到与任何设备(无论您是否实际拥有它)交谈的示例代码,并查看它是否可以在没有挂起的情况下运行。如果是,请开始调查差异。 我设法让它运行了一半,时钟没有开始...... 【参考方案1】:

当我使用 SPI 时,我总是使用示波器来查看 io 的输出。如果您有 4 通道示波器,ypu 可以轻松调试问题,并确定您是否使用了正确的 io,使用了正确的速度等。我通常将得到的信号与数据表图进行比较。

【讨论】:

【参考方案2】:

我认为这里有几个问题。首先SPI是双向的。所以如果你想通过公共汽车发送一些东西,你也会得到一些东西。因此,您必须始终为 rx_buf 和 tx_buf 提供有效的缓冲区。

其次,结构 spi_ioc_transfer 的所有成员都必须使用有效值进行初始化。否则它们只是指向某个内存地址,而底层进程正在访问任意数据,从而导致未知行为。

第三,为什么要在 ioctl 中使用 for 循环?你已经告诉 ioctl 你有一个 spi_ioc_transfer 结构数组。因此,所有定义的事务都将通过一个 ioctl 调用执行。

第四个 ioctl 需要一个指向结构数组的指针。所以 ioctl 应该是这样的:

 ret = ioctl(fd, SPI_IOC_MESSAGE(num_tr), &mesg);

您发现您的代码还有改进的余地。

这就是我在树莓派的 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);
    

【讨论】:

以上是关于SPI(串行端口通信)问题,卡在 ioctl()的主要内容,如果未能解决你的问题,请参考以下文章

串行与 SPI

为啥 ioctl 返回“错误地址”

串口通信,spi通信和i^2c通信的数据帧有啥不同?

STM32通信模拟SPI

STM32通信模拟SPI

ARM与FPGA通过spi通信设计1.spi基础知识