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

Поделиться

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

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


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

В этой статье (перевод документации [1]) приведен пример обмена с внешними устройствами по шине SPI на слое приложения. Архив с кодом можно скачать с Google-диска Архив с кодом можно скачать с Google-диска (см. ссылку на Code.zip в оригинальной документации [1]).

[1. Подсистема SPI]

В операционной системе Linux подсистема SPI представляет собой ключевой фреймворк драйверов, используемый для управления и контроля различных внешних устройств, подключенных через шину SPI. Для получения более подробной информации о подсистеме SPI можно обратиться к каталогу < Linux kernel source>/Documentation/spi.

Ключевые компоненты подсистемы SPI:

Интерфейс sysfs: подсистема SPI представлена набором файлов и директорий через файловую систему sysfs, предназначенных для конфигурирования и обслуживания шин SPI и устройств SPI. Эти файлы и директории обычно находятся в каталогах /sys/class/spi_master и /sys/bus/spi/devices, давая пользователям возможность просматривать и модифицировать свойства устройств SPI.

[root@luckfox ]# ls /sys/class/spi_master
spi2
[root@luckfox ]# ls /sys/bus/spi/devices
spi2.0

Device Nodes: каждое подключенное устройство SPI создает device в директории /dev, разрешая обмен с устройством через программы рабочего пространства пользователя (user-space) с использования стандартных файловых операций ввода/вывода. Обычно эти device nodes имеют имена наподобие /dev/spidevX.Y, где X представляет номер шины SPI, а Y представляет номер устройства SPI.

[2. Просмотр SPI через шелл]

2.1. Цоколевка выводов платы. На платах разработчика обычно по умолчанию разрешен интерфейс SPI0. Вы можете определить соответствующие выводы интерфейса по картинкам, приведенным ниже. Например, у LuckFox Pico интерфейс SPI0 соответствует номерам выводов 48, 49, 50, 51 и 58 (вывод 58 изначально запрещен).

Цоколевка LuckFox Pico:

Pico pinout

Цоколевка LuckFox Pico Mini A/B:

Pico Mini AB pinout

Цоколевка LuckFox Pico Plus:

Pico Plus pinout

Цоколевка LuckFox Pico Pro/Max:

Pico Pro Max pinout

Цоколевка LuckFox Pico Ultra/Ultra W:

Pico Ultra Ultra W pinout

2.2. Просмотр устройств. В директории /sys/bus/spi/devices у каждого SPI-устройства есть своя отдельная папка. Эти папки в своем имени обычно содержат "spi" и номер устройства. Например, /sys/bus/spi/devices/spi0.0 представляет устройство 0 на SPI-шине 0. Для просмотра доступных в системе шин SPI можно использовать команду ls:

# ls /sys/bus/spi/devices/
spi2.0  spi0.0

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

3.1. В следующей программе реализован обмен данными через SPI.

import spidev

def main(): tx_buffer = [ord(char) for char in "hello world!"] rx_buffer = [0] * len(tx_buffer)
try: spi = spidev.SpiDev() spi.open(0, 0) spi.max_speed_hz = 1000000
rx_buffer = spi.xfer2(tx_buffer[:]) print("tx_buffer:\n\r", ''.join(map(chr, tx_buffer))) print("rx_buffer:\n\r", ''.join(map(chr, rx_buffer)))
except Exception as e: print(f"An error occurred: {e}")
finally: if spi: spi.close()

if __name__ == "__main__": main()

3.2. Открытие устройства SPI. Показанный выше код использует класс SpiDev из библиотеки spidev для создания объекта SPI. В вызове метода open указывается номер шины SPI и номер устройства, в этом примере SPI bus 0 и device 0. Максимальная скорость обмена SPI установлена в значение 1000000 Гц, т. е. 1 МГц. На этом шаге происходит конфигурирование базовых параметров устройства SPI, необходимое для последующего обмена данными.

spi = spidev.SpiDev()
spi.open(0, 0)
spi.max_speed_hz = 1000000

3.3. Прием и передача данных. Код приведенного выше примера использует метод xfer2 для передачи данных через SPI. Здесь tx_buffer это отправляемые данные, а в rx_buffer будут сохраняться принимаемые данные. Обратите внимание, что оригинальное значение в tx_buffer остается неизмененным, в xfer2 передается копия, т. е. tx_buffer[:]. И наконец, оператор print выводит в лог отправленные и принятые данные как строки, чтобы пользователь мог с ними ознакомиться.

rx_buffer = spi.xfer2(tx_buffer[:])
print("tx_buffer:\n\r", ''.join(map(chr, tx_buffer)))
print("rx_buffer:\n\r", ''.join(map(chr, rx_buffer)))

3.4. Запуск программы. Используйте текстовый редактор vi (или nano), чтобы скопировать текст программы и вставить его в файл. После этого сохраните программу как файл i2c.py.

# nano i2c.py

Запуск программы осуществляется командой:

# python3 i2c.py

Соедините друг с другом выводы MOSI и MISO интерфейса SPI и запустите программу. Вы увидите такой лог:

# python3 i2c.py
tx_buffer:
 hello world!
rx_buffer:
 hello world!

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

4.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, благодаря чему можно выполнить несколько операций.

4.2. Следующая программа демонстрирует обмен данными SPI на языке C.

#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[50] = "hello world!";
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 = 1000000, // скорость SPI в Гц
.bits_per_word = 8, };

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

// Вывод содержимого буферов передачи и приема:
printf("\rtx_buffer: \n %s\n ", tx_buffer);
printf("\rrx_buffer: \n %s\n ", rx_buffer);

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

4.3. Файловые пути. В следующей строке кода определен макрос, используемый для сохранение пути к файлу устройства SPI.

#define SPI_DEVICE_PATH "/dev/spidev0.0"

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

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

4.5. Конфигурирование SPI. Этот код используется для конфигурирования режима обмена SPI Mode 0 (полярность тактов 0, фаза тактов 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; }

В переменной структуры типа spi_ioc_transfer с именем 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(tx_buffer),
.delay_usecs = 0,
.speed_hz = 1000000, // скорость SPI в Гц
.bits_per_word = 8, };

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

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

4.7. Кросс-компиляция. Процесс компиляции происходит на хосте, где установлен SDK LuckFox [].

1. Подготовка инструментария для компиляции (Cross-Compilation Tool).

Сначала вам нужно добавить путь до утилит кросс-компиляции в переменную окружения PATH, чтобы можно было запускать эти утилиты из любой папки на диске. Вы можете сделать это, добавив следующую строку с файл конфигурации вашего шелла (обычно ~/.bashrc, или ~/.bash_profile, или ~/.zshrc, в зависимости от того, какой шелл используется), либо подготовив скрипт, который делает экспорт модифицированной переменной окружения PATH. Обатите внимание, что путь до инструментов кросс-компиляции должен быть вставлен сразу после PATH=.

Предположим, что SDK находится в профиле текущего пользователя, в папке по абсолютному пути /home/username/luckfox-pico, тогда строка экспорта должна выглядеть так:

export PATH=/home/username/luckfox-pico/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/bin:$PATH

Если вы добавили эту строку в файл конфигурации шелла, то перезапустите шелл командой:

$ source ~/.bashrc

2. После этого вы можете скомпилировать код spi.c командой:

$ arm-rockchip830-linux-uclibcgnueabihf-gcc spi.c -o spi

После успешного завершения компиляции полученный исполняемый код в файле spi можно запустить на вашей целевой плате LuckFox.

# ls
spi  spi.c

4.8. Запуск программы на плате LuckFox.

1. Передача файла. Сначала нам нужно передать файл скомпилированной программы в память платы LuckFox. Передать файл можно через TFTP или ADB. Ниже показан пример команды для передачи файла spi в каталог платы /oem/spitest с помощью ADB:

$ adb push ./spi /oem/spitest

2. Запуск программы. Для запуска программы запустите шелл на плате, поменяйте права доступа к файлу и запустите программу:

$ adb shell
# cd ~/spitest
# pwd
/oem/spitest
# chmod 777 spi
# ./spi

Запущенная программа выведет следующий лог:

# ./spi
tx_buffer:
 hello world!
rx_buffer:
 hello world!

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

Интерфейс SPI изначально разрешен по умолчанию в файлах дерева устройств SDK, так что вы можете использовать их непосредственно. Однако вы можете изменить конфигурацию выводов SPI, как описано далее. Когда через SPI подключается несколько устройств, вы можете выбрать использование вывода CS как порт GPIO, и независимо управлять уровнем сигнала выборки CS. Например, вы можете сконфигурировать GPIO1_C0_d как обычный порт ввода-вывода (general-purpose IO, GPIO).

5.1. Модификация файла дерева устройств.

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. Определение GPIO. Определение выводов GPIO обычно требует добавление двух сегментов кода в файл дерева устройств. Ниже приведен пример определения вывода GPIO1_C0_d:

dts4

dts5

Здесь добавлены следующие сегменты кода:

/{
gpio1pc:gpio1pc0 {
compatible = "regulator-fixed";
pinctrl-names = "default";
pinctrl-0 = < &gpio1_pc0>;
regulator-name = "gpio1_pc0";
regulator-always-on;
}; };

&pinctrl {
gpio1-pc0 {
gpio1_pc0:gpio1-pc0 {
rockchip,pins = < 1 RK_PC0 RK_FUNC_GPIO &pcfg_pull_none>;
};
}; };

3. Комментирование функции периферийного устройства для вывода. В соответствии с цоколевкой выводов вашей платы (см. выше картинки в разделе "2.1. Цоколевка выводов платы") вы можете обнаружить, что для вывода GPIO1_C0_d по умолчанию назначена функция PWM. Но в своей программе мы хотим сконфигурировать этот вывод как GPIO, так что требуется закомментировать функцию периферийного устройства этого вывода. Ниже показан пример, как запретить функцию PWM вывода GPIO1_C0_d в файле дерева устройств:

// &pwm2 {
// status = "okay"
// pinctrl-names = "active"
// pinctrl-0 = < &pwm2m2_pins>;
// };

4. Конфигурирование SPI. Интерфейс SPI0 по умолчанию разрешен в файлах дерева устройств SDK. Здесь мы не будем настраивать GPIO1_C0_d в качестве вывода CS для SPI0 (см. комментарий к строке 167). Ниже приведен пример настройки SPI в дереве устройств:

dts6

Добавляется следующий сегмент кода:

&pinctrl {
spi0 {
/omit-if-no-ref/
spi0m0_pins: spi0m0-pins {
rockchip,pins =
/* spi0_clk_m0 */
< 1 RK_PC1 4 &pcfg_pull_none>,
/* spie_miso_m0 */
< 1 RK_PC3 6 &pcfg_pull_none>,
/* spi_mosi_m0 */
< 1 RK_PC2 6 &pcfg_pull_none>;
};
}; };

5.2. Компиляция Kernel

Конфигурирование компиляции осуществляется выбором в меню скрипта ./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

5.3. Повторная прошивка firmware.

1. После того, как ядро было успешно скомпилировано, сгенерированные файлы будут находиться в директории < SDK directory>/output/image:

~/luckfox-pico$ ls -1 output/image/
boot.img
download.bin
env.img
idblock.img
oem.img
rootfs.img
sd_update.txt
tftp_update.txt
uboot.img
update.img
userdata.img

2. Замените файлы boot.img и env.txt в оригинальном firmware.

3. Заново создайте загрузочную SD-карту. Для Luckfox Pico Plus вам может только понадобиться поменять соответствующий раздел.

[Ссылки]

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

 

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


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

Top of Page