Проблема с SPI (Serial Port Comm), зависшая в 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 не запускается... есть идеи?
2 ответа
Когда я работаю с SPI, я всегда использую осциллограф, чтобы увидеть результаты ввода-вывода. Если у вас есть 4-х канальная область видимости, ypu может легко отладить проблему и выяснить, используете ли вы правильные значения скорости ввода-вывода, правильную скорость и т. Д. Я обычно сравниваю получаемый сигнал с диаграммой таблицы.
Я думаю, что здесь есть несколько вопросов. Прежде всего, SPI является двунаправленным. Так что, если вы хотите отправить что-то через автобус, вы также получите что-то. Поэтому всегда необходимо предоставить действительный буфер для rx_buf и tx_buf.
Во-вторых, все члены структуры spi_ioc_transfer должны быть инициализированы с допустимым значением. В противном случае они просто указывают на некоторый адрес памяти, и основной процесс получает доступ к произвольным данным, что приводит к неизвестному поведению.
В-третьих, почему вы используете цикл for с ioctl? Вы уже сказали ioctl, что у вас есть массив структур spi_ioc_transfer. Таким образом, все определенные транзакции будут выполняться одним вызовом ioctl.
Четвертый ioctl нуждается в указателе на ваш массив struct. Итак, ioctl должен выглядеть так:
ret = ioctl(fd, SPI_IOC_MESSAGE(num_tr), &mesg);
Вы видите, что в вашем коде есть место для улучшения.
Вот как я делаю это в библиотеке C++ для 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);
}
}