外设与接口
硬件资源图
序号 | 接口 | 序号 | 接口 |
---|---|---|---|
1 | RTC 电池接口 | 10 | 电源 Type-C 接口 |
2 | Micro USB (UART 调试) | 11 | PWR 按键 |
3 | TurboX C6490P SOM | 12 | EDL 按键 |
4 | 3.5mm 耳机接口 | 13 | 摄像头接口 2 |
5 | USB Type-C with DP (USB 3.1) | 14 | 摄像头接口 1 |
6 | USB Type-A (USB 2.0) | 15 | Wi-Fi/蓝牙模块 |
7 | 2 x USB Type-A (USB 3.0) | 16 | 风扇接口 |
8 | 1000M 以太网 | 17 | 40-pin 连接器 |
9 | HDMI OUT | 18 | M.2 Key M 接口 |
40 pin 连接器
GPIO
引脚分布
下图是 RUBIK Pi 3 40-pin 连接器的引脚默认功能,其中大部分引脚和树梅派 40-pin 连接器引脚的默认功能兼容。
下表是 40-pin连接器支持的所有功能,图中蓝色字体表明默认功能。
使用 shell 命令控制
在 RUBIK Pi 3 中执行下面的步骤控制 GPIO。
GPIO 引脚通过 /sys/class/gpio 目录进行管理。运行ls -alt
命令可查看当前目录引脚信息。
GPIO 引脚并非随意分布,而是被组织成多个 GPIO Chip(引脚组),每个 Chip 管理一组连续的引脚,并有唯一的编号。例如上图中,gpiochip336 基地址为 336,管理从引脚 336 开始的引脚。
gpiochip336 由主控制器(pinctrl 控制器)管理,pinctrl 负责配置引脚功能(如 GPIO、SPI 等),适用于通用引脚,而其他 GPIO Chip 则由专用控制器管理,负责特定任务的引脚。
如果想要引用该组中 的某个 GPIO 引脚,需要使用基地址加上偏移量(下表中的引脚编号)。
以控制 12 号引脚 GPIO_101 为例,需进行如下操作:
-
进入 /sys/class/gpio 目录:
cd /sys/class/gpio
-
导出要控制的 GPIO。gpio336 基地址 336 + 偏移量 12(即引脚编号)= 348,运行以下命令:
echo 348 > export
-
进入到 gpio348 目录设置 GPIO 属性:
cd gpio 348
ls -alt
-
direction(方向):
-
输入:in
-
输出:out
-
-
value(值):
-
低电平:0
-
高电平:1
-
-
edge (中断边沿):
-
上升沿触发:rising
-
下降沿触发:falling
-
双边沿触发:both
-
禁用中断:none
-
如设置 12 号引脚输出高电平:
echo out > direction
echo 1 > value
取消导出 12 号引脚到用户空间:
echo 348 > unexport
I2C
I2C 是飞利浦公司在 20 世纪 80 年代开发的一种双向 2 线制总线,用于实现高效的 IC 间控制总线。总线上的每个设备都有其唯一的地址(由飞利浦公司领导的 I2C 总机构注册)。I2C 核心支持多控制器模式,以及 10 位目标地址和 10 位可扩展地址。关于 I2C 的更多信息,请参阅 https://www.i2c-bus.org/fileadmin/ftp/i2c_bus_specification_1995.pdf。
引脚分布
下图是 RUBIK Pi 3 40-pin 连接器的引脚默认功能,其中大部分引脚和树梅派 40-pin 连接器引脚的默认功能兼容。
3 号引脚和 5 号引脚默认已设置配为 I2C1。
下表是 40-pin 连接器支持的所有功能,图中蓝色字体表明默认功能。
使用 shell 命令测试
在 RUBIK Pi 3 中执行下面步骤控制 I2C 总线。
-
找到要测试的 I2C 设备
cd /sys/class/i2c-dev/
ls -alt
-
使用 i2cdetect 工具,以下操作以i2c-8为例。
-
查看 I2C8 接口上的设备:
i2cdetect -a -y -r 8
-
每个后缀的含义
-a
:扫描所有可能的 I2C 地址。-y
:跳过交互式确认。-r
:使用 SMBus 的receive byte
命令进行检测 。8
:指定要扫描的 I2C 总线号。
-
这里的 UU 代表内核中已经有了I2C8这个驱动,可以进行通信,表示成功使能。
-
SPI
串行外设接口 (SPI) 是在全双工模式下工作的同步串行数据链路。SPI 又称为 4 线制串行总线。
引脚分布
下图是 RUBIK Pi 3 40-pin 连接器的引脚默认功能,其中大部分引脚和树梅派 40-pin 连接器引脚的默认功能兼容。
19 号、21 号、23 号、24 号引脚默认已设置配为 SPI。
下表是 40-pin 连接器支持的所有功能,图中蓝色字体表明默认功能。
使用 C 语言程序 SPI 通信
-
以下代码示例,代码使用 SPI 总线进行数据收发通信:
#include <stdint.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <linux/spi/spidev.h>
#include <sys/time.h>
#include <signal.h>
#include <string.h>
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
static void pabort(const char *s)
{
perror(s);
abort();
}
static const char *device = "/dev/spidev0.0"; /*spi controller device file*/
static uint8_t mode;
static uint8_t bits = 8;
static uint32_t speed = 10000000; //5242880hz-->655360B/s-->5mbps
static uint16_t delay;
int flag = 1;
void prompt_info(int signo)
{
flag = 0;
}
void init_sigaction(void)
{
struct sigaction tact;
tact.sa_handler = prompt_info;
tact.sa_flags = 0;
sigemptyset(&tact.sa_mask);
sigaction(SIGALRM, &tact, NULL);
}
void init_time()
{
struct itimerval value;
value.it_value.tv_sec = 5; // Transmission time
value.it_value.tv_usec = 0;
value.it_interval = value.it_value;
setitimer(ITIMER_REAL, &value, NULL);
}
static void transfer(int fd)
{
int ret;
uint8_t temp[] = {
'a', 'a', 'a', 'a', 'a', 'a',
'n', 'o', 'p', 'q', 'r', 's',
'a', 'a', 'a', 'a', 'a', 'a',
'a', 'a', 'a', 'a', 'a', 'a',
'a', 'a', 'a', 'a', 'a', 'a',
'b', 'c', 'd', 'e', 'f', 'g',
'b', 'c', 'd', 'e', 'f', 'g',
'b', 'c', 'd', 'e', 'f', 'g',
't', 'u',
'a', 'a', 'a', 'a', 'a', 'a',
'h', 'i', 'j', 'k', 'l', 'm',
'a', 'a', 'a', 'a', 'a', 'a',
'a', 'a', 'a', 'a', 'a', 'a',
'a', 'a', 'a', 'a', 'a', 'a',
'b', 'c', 'd', 'e', 'f', 'g',
'b', 'c', 'd', 'e', 'f', 'g',
'b', 'c', 'd', 'e', 'f', 'g',
'v', 'w',
};
uint8_t tx[ARRAY_SIZE(temp)] = {0, }; //Array of data to be sent
memcpy(tx, temp, ARRAY_SIZE(tx));
uint8_t rx[ARRAY_SIZE(tx)] = {0, }; //Received data array
struct spi_ioc_transfer tr = {
.tx_buf = (unsigned long)tx,
.rx_buf = (unsigned long)rx,
.len = ARRAY_SIZE(tx),
.delay_usecs = delay,
.speed_hz = speed,
.bits_per_word = bits,
};
unsigned long size = 0;
init_sigaction();
init_time();
FILE *file_write = NULL, *file_read = NULL;
file_write = fopen("writebuf","w+");
file_read = fopen("readbuf","w+");
while (flag)
{
ret = ioctl(fd, SPI_IOC_MESSAGE(1), tr); //ioctl default operation, transfer data
if (ret < 1)
pabort("can't send spi message");
size++;
fwrite(tx, 1, 100, file_write);
fwrite(rx, 1, 100, file_read);
memcpy(tx, temp, ARRAY_SIZE(tx));
memset(rx, 0x00, ARRAY_SIZE(tx));
}
fclose(file_write);
fclose(file_read);
}
static void print_usage(const char *prog) //If the parameter is wrong, print the help information
{
printf("Usage: %s [-DsbdlHOLC3]\n", prog);
puts(" -D --device device to use (default /dev/spidev3.0)\n"
" -s --speed max speed (Hz)\n"
" -d --delay delay (usec)\n"
" -b --bpw bits per word \n"
" -l --loop loopback\n"
" -H --cpha clock phase\n"
" -O --cpol clock polarity\n"
" -L --lsb least significant bit first\n"
" -C --cs-high chip select active high\n"
" -3 --3wire SI/SO signals shared\n");
exit(1);
}
static void parse_opts(int argc, char *argv[])
{
while (1) {
static const struct option lopts[] = { //Parameter command table
{ "device", 1, 0, 'D' },
{ "speed", 1, 0, 's' },
{ "delay", 1, 0, 'd' },
{ "bpw", 1, 0, 'b' },
{ "loop", 0, 0, 'l' },
{ "cpha", 0, 0, 'H' },
{ "cpol", 0, 0, 'O' },
{ "lsb", 0, 0, 'L' },
{ "cs-high", 0, 0, 'C' },
{ "3wire", 0, 0, '3' },
{ "no-cs", 0, 0, 'N' },
{ "ready", 0, 0, 'R' },
{ NULL, 0, 0, 0 },
};
int c;
c = getopt_long(argc, argv, "D:s:d:b:lHOLC3NR", lopts, NULL);
if (c == -1)
break;
switch (c) {
case 'D':
device = optarg;
break;
case 's':
speed = atoi(optarg);
break;
case 'd':
delay = atoi(optarg);
break;
case 'b':
bits = atoi(optarg);
break;
case 'l':
mode |= SPI_LOOP;
break;
case 'H':
mode |= SPI_CPHA;
break;
case 'O':
mode |= SPI_CPOL;
break;
case 'L':
mode |= SPI_LSB_FIRST;
break;
case 'C':
mode |= SPI_CS_HIGH;
break;
case '3':
mode |= SPI_3WIRE;
break;
case 'N':
mode |= SPI_NO_CS;
break;
case 'R':
mode |= SPI_READY;
break;
default:
print_usage(argv[0]);
break;
}
}
}
int main(int argc, char *argv[])
{
int ret = 0;
int fd;
parse_opts(argc, argv);
fd = open(device, O_RDWR);
puts(device);
if (fd < 0)
pabort("can't open device");
/*
* spi mode
*/
ret = ioctl(fd, SPI_IOC_WR_MODE, &mode);
if (ret == -1)
pabort("can't set spi mode");
ret = ioctl(fd, SPI_IOC_RD_MODE, &mode);
if (ret == -1)
pabort("can't get spi mode");
/*
* bits per word
*/
ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);
if (ret == -1)
pabort("can't set bits per word");
ret = ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits);
if (ret == -1)
pabort("can't get bits per word");
/*
* max speed hz
*/
ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
if (ret == -1)
pabort("can't set max speed hz");
ret = ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed);
if (ret == -1)
pabort("can't get max speed hz");
printf("spi mode: %d\n", mode);
printf("bits per word: %d\n", bits);
printf("max speed: %d Hz (%d KHz)\n", speed, speed/1000);
transfer(fd);
close(fd);
return ret;
} -
编译程序:
-
交叉编译
aarch64-linux-gnu-gcc {filename} -o {输出filename} -static
-
使用的交叉编译,需要将 spi 传输到 RUBIK Pi 3 中,如果使用 ADB 传输,命令如下:
adb push spi /dev
-
-
将 19 号引脚和 21 号引脚使用杜邦线短接,验证 SPI 总线通信,如下图所示:
-
设备检查(确认 SPI 设备存在)
- 进入设备终端:
adb shell
- 切换到 root 权限:
su
- 进入 /dev 目录并检查 SPI 设备文件:
cd /dev
ls | grep spidev
预期结果:
存在类似 spidev0.0 的设备节点(名称可能略有差异)。
-
测试文件推送与执行
- 另开终端,推送测试程序:
adb push spi_test /dev
- 回到设备终端,确认文件传输成功:
ls
应看到新文件 spi_test(与 spidev0.0 区分)。
- 赋予 spi_test 执行权限并运行测试:
chmod 777 spi_test
./spi_test
预期结果:
生成 writebuf(发送数据)和 readbuf(接收数据)文件。
- 验证数据传输
对比两个文件内容:
diff writebuf readbuf
预期结果:
无输出(文件完全一致),表示 SPI 传输成功且无丢包。
-
UART
引脚分布
下图是 RUBIK Pi 3 40-pin 连接器的引脚默认功能,其中大部分引脚和树梅派 40-pin 连接器引脚的默认功能兼容。
8 号和 10 号引脚默认已设置配为 UART,设备节点为 /dev/ttyHS3。
下表是 40-pin 连接器支持的所有功能,图中蓝色字体表明默认功能。
使用 C 语言程序 UART 通信
-
以下代码示例,使用 UART 进行数据收发通信:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <errno.h>
#include <pthread.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <getopt.h>
#include <string.h>
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
#define MAX_SIZE 1024
static int bps_speed = 115200;
static const char *device = "/dev/ttyHS1";
static int data_bits = 8;
static int stop_bits = 1;
static int g_parity = 'N';
static char *type = "read";
static void pabort(const char *s)
{
perror(s);
abort();
}
int speed_arr[] = {B1152000, B1000000, B921600, B576000, B500000, B460800, B230400, B115200, B57600, B38400, B19200, B9600, B4800, B2400, B1200, B300};
int name_arr[] = { 1152000, 1000000, 921600, 576000, 500000, 460800, 230400, 115200, 57600, 38400, 19200, 9600, 4800, 2400, 1200, 300};
// 设置波特率
void set_speed(int fd, int speed)
{
int i;
int status;
struct termios Opt;
tcgetattr(fd, &Opt);
for (i = 0; i < ARRAY_SIZE(speed_arr); i++)
{
if (speed == name_arr[i])
{
// 缓冲区里的数据都废弃
tcflush(fd, TCIOFLUSH);
/* 设置串口的波特率 */
cfsetispeed(&Opt, speed_arr[i]);
cfsetospeed(&Opt, speed_arr[i]);
status = tcsetattr(fd, TCSANOW, &Opt);
if (status != 0)
pabort("tcsetattr fd");
return;
}
tcflush(fd, TCIOFLUSH);
}
pabort("error speed!");
}
int set_Parity(int fd, int databits, int stopbits, int parity)
{
char cmd_fileclear[40] = {0};
// sprintf(cmd_fileclear, ": > %s", device);
// system(cmd_fileclear);
// memset(cmd_fileclear, 0, ARRAY_SIZE(cmd_fileclear));
sprintf(cmd_fileclear, "stty -F %s -echo", device);
system(cmd_fileclear);
memset(cmd_fileclear, 0, ARRAY_SIZE(cmd_fileclear));
// printf("%s\n", cmd_fileclear);
struct termios options;
if (tcgetattr(fd, &options) != 0)
{
pabort("SetupSerial 1");
}
options.c_cflag &= ~CSIZE;
switch (databits) /*设置数据位数*/
{
case 7:
options.c_cflag |= CS7;
break;
case 8:
options.c_cflag |= CS8;
break;
default:
pabort("Unsupported data sizen");
}
switch (parity)
{
case 'n':
case 'N':
options.c_cflag &= ~PARENB; /* Clear parity enable */
options.c_iflag &= ~INPCK; /* Enable parity checking */
break;
case 'o':
case 'O':
options.c_cflag |= (PARODD | PARENB); /* 设置为奇效验*/
options.c_iflag |= INPCK; /* Disnable parity checking */
break;
case 'e':
case 'E':
options.c_cflag |= PARENB; /* Enable parity */
options.c_cflag &= ~PARODD; /* 转换为偶效验*/
options.c_iflag |= INPCK; /* Disnable parity checking */
break;
case 'S':
case 's': /*as no parity*/
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
break;
default:
pabort("Unsupported parityn");
}
switch (stopbits)
{
case 1:
options.c_cflag &= ~CSTOPB;
break;
case 2:
options.c_cflag |= CSTOPB;
break;
default:
pabort("Unsupported stop bits");
}
/* Set input parity option */
if (parity != 'n' && parity != 'N')
options.c_iflag |= INPCK;
options.c_cc[VTIME] = 150; // 15 seconds
options.c_cc[VMIN] = 0;
tcflush(fd, TCIFLUSH); /* Update the options and do it NOW */
if (tcsetattr(fd, TCSANOW, &options) != 0)
{
pabort("SetupSerial 3");
}
return 0;
}
static void print_usage(const char *prog) //If the parameter is wrong, print the help information
{
printf("Usage: %s [-DsbdlHOLC3t]\n", prog);
puts(" -D --device device to use (default /dev/ttyHS3)\n"
" -S --speed bps\n"
" -d --databits \n"
" -s --stopbits \n"
" -p --parity \n"
" -T --type read or write\n");
exit(1);
}
static void parse_opts(int argc, char *argv[])
{
while (1)
{
static const struct option lopts[] = {
//Parameter command table
{"device", 1, 0, 'D'},
{"speed", 1, 0, 'S'},
{"databits", 1, 0, 'd'},
{"stopbits", 1, 0, 's'},
{"parity", 1, 0, 'p'},
{"type", 1, 0, 'T'},
{NULL, 0, 0, 0},
};
int c;
c = getopt_long(argc, argv, "D:S:d:s:p:T:", lopts, NULL);
if (c == -1)
break;
switch (c)
{
case 'D':
device = optarg;
break;
case 'S':
bps_speed = atoi(optarg);
break;
case 'd':
data_bits = atoi(optarg);
break;
case 's':
stop_bits = atoi(optarg);
break;
case 'p':
g_parity = atoi(optarg);
break;
case 'T':
type = optarg;
break;
default:
print_usage(argv[0]);
break;
}
}
}
int main(int argc, char *argv[])
{
int ret = 0;
int fd;
char string1[] = "hello_world\n";
char string2[] = "hello_world_abcdefghijklmnopqrstuvwxyz_abcdefghijklmnopqrstuvwxyz_abcdefghijklmnopqrstuvwxyz_abcdefghijklmnopqrstuvwxyz_abcdefghijklmnopqrstuvwxyz_abcdefghijklmnopqrstuvwxyz\n";
char *string = string2;
parse_opts(argc, argv);
puts(device);
fd = open(device, O_RDWR);
if (fd < 0)
pabort("can't open device");
// 设置波特率
set_speed(fd, bps_speed);
set_Parity(fd, data_bits, stop_bits, g_parity);
close(fd);
fd = open(device, O_RDWR);
int nread = 0,nwrite = 0;
char buff[MAX_SIZE] = {0};
int num = 0;
char send_msg[200] = {0};
printf("%d %d-%d-%c\n", bps_speed, data_bits, stop_bits, g_parity);
printf("type:%s\n", strcmp(type, "read") == 0 ? "read" : "write");
memset(send_msg, 0, ARRAY_SIZE(send_msg));
FILE *read_file;
while (1)
{
if (strcmp(type, "read") == 0)
{
// tcflush(fd, TCIOFLUSH);
nread = read(fd, buff, MAX_SIZE - 2);
if (nread > 1)
{
printf("(Len %d):", nread);
buff[nread] = '\n';
buff[nread + 1] = '\0';
printf("read %s", buff);
if (nread < strlen(string))
{
read_file = fopen("read_file","aw+");
fwrite(buff, strlen(buff), 1, read_file);
fclose(read_file);
}
memset(buff, 0, ARRAY_SIZE(buff));
num++;
}
nread = 0;
}
else
{
sprintf(send_msg, "%d%s", num, string);
nwrite = write(fd, send_msg, strlen(send_msg));
printf("Send test data---%d---%s\n", nwrite, send_msg);
num++;
sleep(1);
}
}
close(fd);
return ret;
} -
编译程序:
-
交叉编译
aarch64-linux-gnu-gcc {filename} -o {输出filename} -static
-
使用交叉编译,需要将编译出的产物 uart 传输到 RUBIK Pi 3 中,如果使用 ADB 传输,命令如下:
adb push uart /opt
-
-
将 8 号引脚和 10 号引脚使用杜邦线短接,验证串口通信,如下图所示
-
测试读功能(接收数据)
- 赋权并启动监听终端
chmod 777 uart_test
./uart -D /dev/ttyHS1- 另开终端发送数据
./uart -D /dev/ttyHS1 -T write
- 另开终端 B 监听数据
./uart -D /dev/ttyHS1
验证:
第一个终端会显示发送的数据,确认数据接收正常。
-
测试写功能(发送数据)
- 在终端 A 发送数据
./uart -D /dev/ttyHS1 -T write
- 另开终端 B 监听数据
./uart -D /dev/ttyHS1
-