Проблема с SPI (последовательный порт связи), застрявшая на ioctl()

Я пытаюсь получить доступ к датчику SPI с помощью драйвера SPIDEV, но мой код зависает на IOCTL.

Я запускаю встроенный Linux на SAM9X5EK (устанавливаю AT91SAM9G25). Устройство подключено к SPI0. Я включил CONFIG_SPI_SPIDEV и CONFIG_SPI_ATMEL в menuconfig и добавил правильный код в файл 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));

1 МГц - это максимум, принимаемый датчиком, я пробовал 500 кГц, но при загрузке 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 вызывает ошибку ENOPROTOOPT в Atmel (я проверил spidev.c), поэтому я прокомментировал эту часть.

Почему он застревает? Я, хотя это может быть, поскольку устройство создано, но на самом деле оно не «чувствует» никакого оборудования. Поскольку я не был уверен, соответствует ли аппаратный SPI0 bus_num 0 или 1, я попробовал оба, но безуспешно (кстати, какой именно?).

ОБНОВЛЕНИЕ: мне удалось заставить работать SPI! Половина.. MOSI передает правильные данные, но CLK не запускается... есть идеи?


person stef    schedule 31.05.2012    source источник
comment
Метод грубой силы отладки ядра состоит в том, чтобы загрузить все, что может иметь отношение, с помощью printk, чтобы вы получили поток информации о том, что он пытался сделать в последний раз. Как предположение, вы можете застрять в ожидании аппаратного события, которое не произойдет, поскольку оно зависит от часов, которые не работают, или периферийной области чипа, у которой нет включения, включения питания или включения часов. набор бит.   -  person Chris Stratton    schedule 04.06.2012
comment
Спасибо за подсказку. Однако это странно, потому что при загрузке ядра не возникает реальной ошибки, устройства появляются в /dev/spidevX.X. Проверил часы, в одной строчке при загрузке написано Clocks: CPU 400MHz, master 133MHz, main 12.000MHz. Это имеет смысл, так как я получаю сообщение об ошибке загрузки, если пытаюсь использовать spidev на частоте 500 кГц (133 МГц/255 > 500 кГц), в то время как он загружается правильно, если я устанавливаю spidev на 1 МГц. Очевидно, здесь должно быть что-то, чего мне не хватает...   -  person stef    schedule 04.06.2012
comment
Многие SOC имеют множество периферийных часов, а часы, питание и т. д. позволяют использовать встроенные периферийные устройства, такие как spi-движки, поэтому, если вы не загрузились через что-то, подвешенное к тому же spi-движку, успешная загрузка мало что покажет (и даже в этом случае загрузчик мог прочитать ядра до того, как ядро ​​впоследствии сломало или отключило конфигурацию spi). Возможно, вам лучше всего посмотреть, сможете ли вы найти пример кода для общения с любым устройством (независимо от того, физически у вас его нет) с этой платы и посмотреть, работает ли он без зависаний. Если это так, начните исследовать различия.   -  person Chris Stratton    schedule 04.06.2012
comment
Мне удалось запустить его, на одну половину часы не запускаются ...   -  person stef    schedule 10.06.2012


Ответы (2)


Когда я работаю с SPI, я всегда использую осциллограф, чтобы увидеть вывод ввода-вывода. Если у вас есть 4-канальный осциллограф, вы можете легко отладить проблему и выяснить, используете ли вы правильный ввод-вывод, используете ли правильную скорость и т. д. Я обычно сравниваю сигнал, который я получаю, с диаграммой таблицы данных.

person stdcall    schedule 31.05.2012

Я думаю, что здесь есть несколько проблем. Во-первых, SPI двунаправленный. Так что, если вы хотите отправить что-то по автобусу, вы тоже что-то получите. Поэтому вы всегда должны предоставлять допустимый буфер для rx_buf и tx_buf.

Во-вторых, все члены структуры spi_ioc_transfer должны быть инициализированы допустимым значением. В противном случае они просто указывают на некоторый адрес памяти, а базовый процесс обращается к произвольным данным, что приводит к неизвестному поведению.

В-третьих, почему вы используете цикл for с ioctl? Вы уже сказали ioctl, что у вас есть массив структур spi_ioc_transfer. Таким образом, вся определенная транзакция будет выполняться одним вызовом ioctl.

Четвертому ioctl требуется указатель на ваш массив структур. Итак, ioctl должен выглядеть так:

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

Вы видите, что в вашем коде есть место для улучшения.

Вот как я это делаю в библиотеке С++ для Raspberry Pi. Вся библиотека скоро будет на 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);
    }
}
person Christian Rapp    schedule 01.02.2014