Программирование ARM LuckFox SPI: обмен данными между устройствами Master и Slave Fri, September 19 2025  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.


LuckFox SPI: обмен данными между устройствами Master и Slave Печать
Добавил(а) microsin   

В этой статье демонстрируется пример конфигурирования обмена между двумя SPI-устройствами Rockchip, работающих в качестве Master и Slave, и непосредственного манипулирования интерфейсом SPI в user space, чтобы обеспечить обмен данными между Master и Slave (перевод документации [1]). Архив с кодом можно скачать с Google-диска (см. ссылку на Code.zip в оригинальной документации [1]).

[1. Модификация конфигурации Kernel]

1.1. Сохраните файл и выполните очистку, после чего выполните перекомпиляцию.

$ cd ~/SDK_directory/sysdrv/source/kernel
$ cp ./arch/arm/configs/luckfox_rv1106_linux_defconfig .config
$ make ARCH=arm menuconfig

1.2. Изменение конфигурации

1. Выберите "Device Drivers":

Pico Kernel Config1 SPI

2. Выберите "Select "SPI support":

Pico Kernel Config2 SPI

3. Сконфигурируйте user space на непосредственную работу с SPI:

Pico Kernel Config3 SPI

4. Разрешите "Rockchip SPI controller driver" для Master выбором "Y":

Pico Kernel Config4 SPI

5. Разрешите "SPI slave protocol handlers" для Slave выбором "Y":

Pico Kernel Config5 SPI

1.3. Сохраните config, вернитесь в директорию SDK и перекомпилируйте ядро (kernel).

$ make ARCH=arm savedefconfig
$ cp defconfig arch/arm/configs/luckfox_rv1106_linux_defconfig
$ cd ~/SDK_directory
$ ./build kernel

[2. Модификация Device Tree]

1. Файлы конфигурации устройств всех плат находятся в каталоге:

< SDK directory>/project/cfg/BoardConfig_IPC/

Каждый *.mk файл в этом каталоге описывает конфигурационные параметры отдельной модели платы Luckfox Pico, такие как целевая архитектура (target architecture), загрузочный носитель (boot medium), используемый загрузчик (Uboot), ядро (kernel) и настройки таблицы разделов (partition settings).

Переменная RK_KERNEL_DTS файла конфигурации платы указывает файл дерева устройств (Device Tree Source, DTS) для ядра. Если взять в качестве примера Luckfox Pico и открыть его файл конфигурации BoardConfig-EMMC-NONE-RV1103_Luckfox_Pico-IPC.mk, то мы увидим, что переменная RK_KERNEL_DTS указывает файл rv1103g-luckfox-pico.dts.

dts1

Основываясь на значении переменной RK_KERNEL_DTS, путь файла дерева устройств платы Luckfox Pico определен следующим образом:

< SDK directory>/sysdrv/source/kernel/arch/arm/boot/dts/rv1103g-luckfox-pico.dts

2. Измените файл Device Tree.

На стороне Master:

&spi0 {
status = "okay";
spi_test@00 {
compatible = "rockchip,spidev";
reg = < 0>;
spi-cpha;
spi-cpol;
spi-lsb-first;
spi-max-frequency = < 49000000>;
status = "okay";
}; };

На стороне Slave:

&spi0 {
status = "okay";
spi-slave;
slave {
compatible ="rockchip,spidev";
reg = < 0>;
id = < 0>;
}; };

2.2. Компиляция ядра

1. Конфигурирование компиляции осуществляется выбором в меню скрипта ./build.sh lunch одной из конфигураций для плат LuckFox Pico, LuckFox Pico Mini A, LuckFox Pico Mini B, LuckFox Pico Plus и LuckFox Pico Pro/Max:

~/luckfox-pico$ ./build.sh lunch
ls: cannot access 'BoardConfig*.mk': No such file or directory
You're building on Linux Lunch menu...pick a combo:
BoardConfig-*.mk naming rules: BoardConfig-"Boot Media"-"Power Plan"-"Hardware Version"-"Application Scenario".mk BoardConfig-"boot medium"-"power solution"-"hardware version"-"applicaton".mk
---------------------------------------------------------------- 0. BoardConfig_IPC/BoardConfig-EMMC-NONE-RV1103_Luckfox_Pico-IPC.mk boot medium: EMMC power solution (power plan): NONE hardware version: RV1103_Luckfox_Pico applicaton (scenario): IPC ----------------------------------------------------------------
---------------------------------------------------------------- 1. BoardConfig_IPC/BoardConfig-EMMC-NONE-RV1103_Luckfox_Pico_Mini_A-IPC.mk boot medium: EMMC power solution (power plan): NONE hardware version: RV1103_Luckfox_Pico_Mini_A applicaton (scenario): IPC ----------------------------------------------------------------
---------------------------------------------------------------- 2. BoardConfig_IPC/BoardConfig-SPI_NAND-NONE-RV1103_Luckfox_Pico_Mini_B-IPC.mk boot medium: SPI_NAND power solution (power plan): NONE hardware version: RV1103_Luckfox_Pico_Mini_B applicaton (scenario): IPC ----------------------------------------------------------------
---------------------------------------------------------------- 3. BoardConfig_IPC/BoardConfig-SPI_NAND-NONE-RV1103_Luckfox_Pico_Plus-IPC.mk boot medium: SPI_NAND power solution (power plan): NONE hardware version: RV1103_Luckfox_Pico_Plus applicaton (scenario): IPC ----------------------------------------------------------------
---------------------------------------------------------------- 4. BoardConfig_IPC/BoardConfig-SPI_NAND-NONE-RV1106_Luckfox_Pico_Pro_Max-IPC.mk boot medium: SPI_NAND power solution (power plan): NONE hardware version: RV1106_Luckfox_Pico_Pro_Max applicaton (scenario): IPC ----------------------------------------------------------------
Which would you like? [0]: 0 [build.sh:info] switching to board: /home/luckfox/luckfox-pico/project/cfg/ BoardConfig_IPC/BoardConfig-EMMC-NONE-RV1103_Luckfox_Pico-IPC.mk [build.sh:info] Running build_select_board succeeded.

В этом примере был выбран вариант 0, т. е. плата Luckfox Pico.

2. Перекомпилируйте ядро командой:

~/luckfox-pico$ ./build.sh kernel

2.3. Перепрошейте скомпилированное firmware.

[3. SPI-обмен в программе на C]

3.1. Функция ioctl. При написании программы приложения функция ioctl используется для конфигурирования настроек SPI. Прототип этой функции следующий:

#include < sys/ioctl.h>

int ioctl(int fd, unsigned long request, ...);

Когда функция ioctl используется для обмена через SPI, обычно применяют следующие параметры запроса:

SPI_IOC_RD_MODE: используется для чтения настроек текущего режима обмена SPI. Этот параметр запроса читает информацию режима в целочисленную переменную для проверки текущих настроек полярности и фазы SPI (CPOL и CPHA).

SPI_IOC_WR_MODE: используется для установки режима обмена SPI. Вам нужно предоставить целочисленное значение, обычно составленное из двух двоичных цифр для представления полярности и фазы обмена SPI.

SPI_IOC_RD_BITS_PER_WORD: используется для чтения количества бит в слове данных. Прочитанное значение будет сохранено в предоставленную целочисленную переменную.

SPI_IOC_WR_BITS_PER_WORD: используется для установки количества бит в слове данных. Вам нужно предоставить целочисленное значение, чтобы указать размер передаваемого и принимаемого слова в битах.

SPI_IOC_RD_MAX_SPEED_HZ: используется для чтения максимальной скорости данных (частоты тактов) шины SPI. Этот параметр запроса считывает информацию скорости в целочисленную переменную.

SPI_IOC_WR_MAX_SPEED_HZ: используется для установки максимальной скорости шины SPI. Вам нужно предоставить целочисленное значение для указания частоты тактов шины SPI.

SPI_IOC_MESSAGE(N): используется для выполнения операций чтения и записи по шине SPI. Этот параметр запроса требует указателя на массив элементов структур spi_ioc_transfer, где каждый элемент описывает операцию транзакции SPI, благодаря чему можно выполнить несколько операций.

3.2. Пример программы. Следующие две программы осуществляют SPI-обмен друг с другом в качестве главного (Master) и подчиненного (Slave) устройства шины.

На стороне Master:

#include < stdio.h>
#include < stdlib.h>
#include < stdint.h>
#include < fcntl.h>
#include < unistd.h>
#include < linux/spi/spidev.h>
#include < sys/ioctl.h>

#define SPI_DEVICE_PATH "/dev/spidev0.0"

int main() {
int spi_file;
uint8_t tx_buffer[255];
uint8_t rx_buffer[50];

// Открытие устройства SPI:
if ((spi_file = open(SPI_DEVICE_PATH, O_RDWR)) < 0) {
perror("Failed to open SPI device");
return -1;
}

// Конфигурирование режима SPI и количества бит в слове данных:
uint8_t mode = SPI_MODE_0;
uint8_t bits = 8;
if (ioctl(spi_file, SPI_IOC_WR_MODE, &mode) < 0) {
perror("Failed to set SPI mode");
close(spi_file);
return -1;
}
if (ioctl(spi_file, SPI_IOC_WR_BITS_PER_WORD, &bits) < 0) {
perror("Failed to set SPI bits per word");
close(spi_file);
return -1;
}

// Подготовка транзакции SPI:
struct spi_ioc_transfer transfer = {
.tx_buf = (unsigned long)tx_buffer,
.rx_buf = (unsigned long)rx_buffer,
.len = sizeof(tx_buffer),
.delay_usecs = 0,
.speed_hz = 49000000, // частота тактов SPI в Гц
.bits_per_word = 8,
};

// Отправка данных:
for (int i = 0; i < transfer.len; i++)
tx_buffer[i] = i;

if (ioctl(spi_file, SPI_IOC_MESSAGE(1), &transfer) < 0) {
perror("Failed to perform SPI transfer");
close(spi_file);
return -1;
}

printf("Send %d bytes of data max speed: %d Hz.\n",
transfer.len,transfer.speed_hz);

// Закрытие устройства SPI:
close(spi_file);
return 0; }

На стороне Slave:

#include < stdio.h>
#include < stdlib.h>
#include < stdint.h>
#include < fcntl.h>
#include < unistd.h>
#include < linux/spi/spidev.h>
#include < sys/ioctl.h>
#include < string.h>

#define SPI_DEVICE_PATH "/dev/spidev0.0"

int main() {
int spi_file,ret;
uint8_t tx_buffer[50];
uint8_t rx_buffer[255];

// Открытие устройства SPI:
if ((spi_file = open(SPI_DEVICE_PATH, O_RDWR)) < 0) {
perror("Failed to open SPI device");
return -1;
}

// Конфигурирование режима SPI и количества бит в слове данных:
uint8_t mode = SPI_MODE_0;
uint8_t bits = 8;
if (ioctl(spi_file, SPI_IOC_WR_MODE, &mode) < 0) {
perror("Failed to set SPI mode");
close(spi_file);
return -1;
}
if (ioctl(spi_file, SPI_IOC_WR_BITS_PER_WORD, &bits) < 0) {
perror("Failed to set SPI bits per word");
close(spi_file);
return -1;
}

// Подготовка транзакции SPI:
struct spi_ioc_transfer transfer = {
.tx_buf = (unsigned long)tx_buffer,
.rx_buf = (unsigned long)rx_buffer,
.len = sizeof(rx_buffer),
.delay_usecs = 0,
.speed_hz = 49000000, // частота тактов SPI в Гц
.bits_per_word = 8,
};

while(1)
{
// Очистка буфера приема:
memset(rx_buffer,0,sizeof(rx_buffer));

// Ожидание данных:
do {
ret = ioctl(spi_file, SPI_IOC_MESSAGE(1), &transfer);
} while (ret < 0);

// Вывод на печать содержимого rx_buffer:
printf("--Receive %d bytes of data max speed:%d Гц---\n",
ret, transfer.speed_hz);
printf("SPI RX: 0x%08X:", 0);
for (int i = 0; i < ret; i++) {
printf(" %02X",rx_buffer[i] );
if ((i + 1) % 16 == 0){
printf("\nSPI RX: 0x%08X:", i+1);
}
}
printf("\n");
}

// Закрытие устройства SPI:
close(spi_file);

return 0; }

3.3. Путь файла для устройства SPI задается строкой:

#define SPI_DEVICE_PATH "/dev/spidev0.0"

3.4. Открытие устройства SPI. Эта секция кода пытается открыть указанный файл устройства SPI.

if ((spi_file = open(SPI_DEVICE_PATH, O_RDWR)) < 0) {
perror("Failed to open SPI device");
return -1; }

3.5. Конфигурирование SPI. Следующий код используется для конфигурирования обмена в режиме SPI Mode 0 (clock polarity 0, clock phase 0) и устанавливает размер слова данных 8 бит, чтобы обеспечить корректность обмена данными SPI.

uint8_t mode = SPI_MODE_0;
uint8_t bits = 8;
if (ioctl(spi_file, SPI_IOC_WR_MODE, &mode) < 0) {
perror("Failed to set SPI mode");
close(spi_file);
return -1; }
if (ioctl(spi_file, SPI_IOC_WR_BITS_PER_WORD, &bits) < 0) {
perror("Failed to set SPI bits per word");
close(spi_file);
return -1; }

Переменная transfer определяет структуру данных типа spi_ioc_transfer, содержащую параметры транзакции SPI. Здесь указываются буферы данных передачи и приема, длина транзакции, задержка, скорость SPI в Герцах и размер слова данных. Эта структура будет передаваться в SPI_IOC_MESSAGE функции ioctl, чтобы выполнилась транзакция SPI.

struct spi_ioc_transfer transfer = {
.tx_buf = (unsigned long)tx_buffer,
.rx_buf = (unsigned long)rx_buffer,
.len = sizeof(rx_buffer),
.delay_usecs = 0,
.speed_hz = 49000000, // частота тактов SPI в Гц
.bits_per_word = 8, };

3.6. Отправка данных. Следующий код использует функцию ioctl для выполнения транзакции SPI. Указывается количество транзакций 1 с помощью макроса SPI_IOC_MESSAGE(1), а в третьем параметре передается ссылка на переменную транзакции, сконфигурированной ранее. Если транзакция SPI была неудачной, то печатается сообщение об ошибке, и дескриптор файла устройства SPI закрывается.

// Отправка данных:
for (int i = 0; i < transfer.len; i++)
tx_buffer[i] = i;

if (ioctl(spi_file, SPI_IOC_MESSAGE(1), &transfer) < 0) {
perror("Failed to perform SPI transfer");
close(spi_file);
return -1; }

3.7. Прием данных на стороне Slave

Следующий сегмент кода начинается с очистки буфера данных, затем проверяется возвращаемое значение из функции ioctl, чтобы определить, были ли приняты данные. Если данные не были приняты, то происходит прокрутка цикла, таким способом происходит ожидание поступления данных от Master. При получении данных функция ioctl возвратит длину принятых данных. Затем принятые данные печатаются в hex-формате, и процесс повторяется - снова запускается цикл ожидания данных.

while(1)
{
// Очистка буфера приема:
memset(rx_buffer,0,sizeof(rx_buffer));

// Ожидание данных:
do {
ret = ioctl(spi_file, SPI_IOC_MESSAGE(1), &transfer);
} while (ret < 0);

// Вывод на печать содержимого rx_buffer:
printf("---Receive %d bytes of data max speed:%d Hz---\n",
ret, transfer.speed_hz);
printf("SPI RX: 0x%08X:", 0);
for (int i = 0; i < ret; i++) {
printf(" %02X",rx_buffer[i] );
if ((i + 1) % 16 == 0){
printf("\nSPI RX: 0x%08X:", i+1);
}
} printf("\n"); }

3.8. Кросс компиляция. См. описание процесса компиляции в статье [2].

[4. Запуск программ]

4.1. Передача файла. Передача может быть осуществлена с помощью TFTP или ADB (см. [2]).

$ adb push path_to_file destination_on_development_board

.. или:

$ adb -s device_serial_number push path_to_file destination_on_development_board

Серийный номер в команде adb необходимо указывать, когда к хосту через USB подключено несколько устройств:

$ adb -s d48936ed7d155xxx push spi /

4.2. Соединения между устройствами Master и Slave.

1. Соедините друг с другом землю устройств GND.
2. Соедините друг с другом сигналы SPI0_MISO, SPI0_MOSI, SPI0_CLK и CPI0_CS0.

4.3. Запуск программы. Поменяйте права доступа на исполняемый файл spi и запустите программу на обоих устройствах:

# chmod +x spi
# ./spi

4.4. Результаты работы программ

На стороне Master осуществляется передача данных:

Pico SPI Master running

На стороне Slave происходит вывод полученных данных:

Pico SPI Slave running

[Ссылки]

1. LuckFox SPI Master-Slave Communication site:wiki.luckfox.com.
2. LuckFox SPI: обмен данными с внешним устройством.

 

Добавить комментарий


Защитный код
Обновить

Top of Page