当前位置:首页>Linux>一篇文章快速理解,嵌入式Linux常用通信外设.

一篇文章快速理解,嵌入式Linux常用通信外设.

  • 2026-04-16 15:51:17
一篇文章快速理解,嵌入式Linux常用通信外设.

本次主讲:CAN、UART、RS485 / RS232、USB、I2C、SPI

一:说人话,它在硬件上到底是什么

比如:

  • UART:两个人约好“节奏”后按节奏说话
  • I2C:一条公共走廊,所有设备共用,但每个房间有门牌号
  • SPI:主设备拿着时钟节拍器,点名让谁说谁就说
  • CAN:一群节点共用一条总线,但谁优先级高谁先说
  • USB:主机像老师,设备像学生,必须老师先点名
  • RS485 / RS232:不是“协议”,而是“电气说话方式”

二:Linux 里如何抽象表示

嵌入式 Linux 应用开发本质上不是直接访问寄存器,而是基于内核已经暴露出来的抽象工作。

常见抽象有:

  • 字符设备:/dev/ttyS*/dev/i2c-*/dev/spidev*
  • 网络接口:can0 can1
  • USB 类设备:/dev/video*/dev/ttyUSB*wlan0

三:用户态怎么访问

用户态程序不是“凭感觉”访问,而是通过固定 API:

  • open / ioctl / read / write
  • socket / bind / send / recv
  • termios
  • i2c-dev
  • spidev
  • libusb

CAN 


一、先用人话理解 CAN

你可以把 CAN 想成:

一条公共发言总线,很多设备都能说话,但大家同时开口时,不是乱成一团,而是“优先级高的人先说”。

普通串口像两个人打电话,一对一。

 CAN 像一个会议系统,所有节点都连在同一总线上。

但这个会议系统很聪明:

  • 不会因为两个人同时说话就全毁掉
  • 它能通过“谁 ID 小谁优先”实现仲裁
  • 仲裁输掉的设备不是报文损坏,而是主动停下来等下次再发

这就是为什么说 CAN 是非破坏性仲裁


二、为什么 CAN 很适合控制系统

控制系统很看重三件事:

  1. 抗干扰
  2. 可靠性
  3. 实时性

CAN 恰好都比较强:

1. 抗干扰强

因为它是差分信号:

  • CANH
  • CANL

外界同时对两根线造成的干扰,在接收端大概率会被抵消。

2. 可靠性高

因为 CAN 有:

  • CRC 校验
  • ACK
  • 错误帧
  • 自动重发
  • 错误计数器

3. 实时性强

因为优先级高的报文可以先发。

这就是为什么:

  • 汽车 ECU
  • 工业控制器
  • BMS
  • 电机控制器

三、CAN 的关键概念

1. CAN ID

CAN ID 不只是“报文编号”,它还是总线仲裁优先级。

ID 越小,优先级越高。

为什么?

因为 CAN 仲裁时是逐位比较的,“0” 会压过 “1”,所以二进制更小的 ID 会赢。


2. Bitrate  比特率

can总线通信速率。

例如:

  • 125 kbps
  • 250 kbps
  • 500 kbps
  • 1 Mbps

同一条 CAN 总线上的节点,bitrate 必须一致。

如果一个节点 500k,另一个节点 250k,基本不可能正常通信。


3. 终端电阻

真实 CAN 总线通常在两端各放一个 120 欧电阻。

为什么要加?

因为传输线不是理想导线,信号沿着线传播时会产生反射。 终端电阻的作用是做阻抗匹配,减少反射。


4. SocketCAN

设计思想:

Linux 把 CAN 当成一种“网络接口”来管理,而不是普通字符设备。

所以会看到:

can0

而不是:

/dev/can0

四、Linux 下为什么 CAN 用 socket

因为 Linux 希望统一网络类通信接口。

这样做的好处:

  • 管理风格统一
  • 可以复用 socket 体系
  • 可以方便做过滤
  • 可以方便配合网络工具

所以 CAN 用户态编程很像网络编程:

  • socket()
  • bind()
  • read()
  • write()

五、CAN 相关常用命令

命令
作用
理解
ip link show can0
看接口状态
CAN 是 netdev
ip -details link show can0
看详细参数
bitrate / state / timing
ip link set can0 up
启动接口
和网卡类似
ip link set can0 down
关闭接口
常用于改配置
candump can0
抓报文
调试利器
cansend can0 123#11223344
发报文
熟悉格式
cangen can0
连续造帧
压测工具

六、CAN 用户态代码

#include<stdio.h>              // printf, perror#include<string.h>             // strcpy#include<unistd.h>             // close, read, write#include<sys/socket.h>         // socket, bind#include<sys/ioctl.h>          // ioctl#include<net/if.h>             // struct ifreq#include<linux/can.h>          // struct can_frame#include<linux/can/raw.h>      // CAN_RAW/* * 文件名:can_send_verbose.c * 编译命令: * gcc can_send_verbose.c -o can_send_verbose * * 功能: * 发送一帧标准 CAN 报文到 can0 */intmain(void){int sockfd;                 // CAN 套接字文件描述符structifreqifr;// 用于通过接口名获取接口索引structsockaddr_canaddr;// CAN socket 地址结构structcan_frameframe;// CAN 报文结构体/*     * 第 1 步:创建一个原始 CAN socket     *     * PF_CAN   : 协议族为 CAN     * SOCK_RAW : 原始套接字     * CAN_RAW  : 原始 CAN 协议     */    sockfd = socket(PF_CAN, SOCK_RAW, CAN_RAW);if (sockfd < 0) {        perror("socket");return1;    }/*     * 第 2 步:指定我们要使用的接口名     * 这里假设接口名是 can0     */strcpy(ifr.ifr_name, "can0");/*     * 第 3 步:通过 ioctl 获取 can0 的接口索引     *     * 为什么要拿索引?     * 因为 bind 的时候不是直接绑定字符串 "can0",     * 而是绑定它对应的接口编号。     */if (ioctl(sockfd, SIOCGIFINDEX, &ifr) < 0) {        perror("ioctl SIOCGIFINDEX");        close(sockfd);return1;    }/*     * 第 4 步:设置 CAN 地址结构     *     * can_family  : 地址族,固定 AF_CAN     * can_ifindex : 接口索引,对应 can0     */    addr.can_family = AF_CAN;    addr.can_ifindex = ifr.ifr_ifindex;/*     * 第 5 步:把 socket 绑定到 can0     *     * 绑定后,后续 read/write 都针对 can0 收发     */if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {        perror("bind");        close(sockfd);return1;    }/*     * 第 6 步:构造一帧 CAN 报文     */    frame.can_id = 0x123;       // 标准帧 ID = 0x123    frame.can_dlc = 8;          // 数据长度为 8 字节/*     * 给数据区填入 8 个测试字节     */for (int i = 0; i < 8; i++) {        frame.data[i] = i;      // 依次写入 0,1,2,3,4,5,6,7    }/*     * 第 7 步:发送报文     *     * write 的数据对象是整个 struct can_frame     */if (write(sockfd, &frame, sizeof(frame)) != sizeof(frame)) {        perror("write");        close(sockfd);return1;    }/*     * 第 8 步:打印提示     */printf("CAN frame sent successfully.\n");/*     * 第 9 步:关闭 socket     */    close(sockfd);return0;}

七、CAN 接收代码

#include<stdio.h>              // printf, perror#include<string.h>             // strcpy#include<unistd.h>             // close, read#include<sys/socket.h>         // socket, bind#include<sys/ioctl.h>          // ioctl#include<net/if.h>             // struct ifreq#include<linux/can.h>          // struct can_frame#include<linux/can/raw.h>      // CAN_RAW/* * 文件名:can_recv_verbose.c * 编译: * gcc can_recv_verbose.c -o can_recv_verbose * * 功能: * 阻塞等待接收一帧 CAN 报文 */intmain(void){int sockfd;                 // CAN 套接字structifreqifr;// 用于获取接口索引structsockaddr_canaddr;// CAN 地址结构structcan_frameframe;// 接收报文缓冲区/*     * 创建 CAN 原始 socket     */    sockfd = socket(PF_CAN, SOCK_RAW, CAN_RAW);if (sockfd < 0) {        perror("socket");return1;    }/*     * 指定要监听的接口名     */strcpy(ifr.ifr_name, "can0");/*     * 获取 can0 对应的接口索引     */if (ioctl(sockfd, SIOCGIFINDEX, &ifr) < 0) {        perror("ioctl");        close(sockfd);return1;    }/*     * 设置地址结构     */    addr.can_family = AF_CAN;    addr.can_ifindex = ifr.ifr_ifindex;/*     * 绑定到 can0     */if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {        perror("bind");        close(sockfd);return1;    }printf("Waiting for CAN frame on can0...\n");/*     * read 会阻塞,直到收到一帧 CAN 报文     */if (read(sockfd, &frame, sizeof(frame)) < 0) {        perror("read");        close(sockfd);return1;    }/*     * 打印接收到的报文 ID 和数据     */printf("Received CAN ID = 0x%X, DLC = %d, DATA =", frame.can_id, frame.can_dlc);for (int i = 0; i < frame.can_dlc; i++) {printf(" %02X", frame.data[i]);    }printf("\n");    close(sockfd);return0;}

八、面试快答版

问:为什么 Linux 下 CAN 不用 /dev/can0

答:因为 Linux 使用 SocketCAN,把 CAN 设备抽象为网络接口,统一纳入 socket 体系和 netdev 管理框架。

问:为什么 CAN 比普通串口更适合控制总线?

答:因为 CAN 具有总线仲裁、错误检测、自动重发和多节点共享能力,更适合分布式控制。

问:为什么 CAN 要用终端电阻?

答:为了阻抗匹配,减少信号反射,保证差分总线的波形质量。


UART 


一、先用直觉理解 UART

UART 像什么?

像两个人不带节拍器打暗号。 他们事先约定:

  • 说话速度一样
  • 每个字几位
  • 有没有校验
  • 什么时候算说完

于是即使没有时钟线,也能通信。

这就是“异步串口”的核心。


二、为什么 UART 不需要时钟线

因为它不是同步通信。 它靠的是:

  • 双方约定相同波特率
  • 双方约定相同帧格式
  • 接收端通过起始位对齐采样时刻

所以 UART 虽然简单,但对参数匹配要求非常高。


三、什么叫 8N1

8N1 =

  • 8:8 位数据位
  • N:No parity,无校验
  • 1:1 位停止位

一帧一般长这样:

  • 1 位起始位
  • 8 位数据位
  • 0 位校验位
  • 1 位停止位

为什么会有起始位?

因为接收方平时不知道什么时候开始来数据。 起始位就像“我开始说了”的信号。

为什么需要停止位?

因为接收方要知道“一帧结束了,可以准备下一帧”。


四、Linux 为什么把 UART 抽象成 tty

因为 Linux 的串口、终端、伪终端,本质上都走 tty 子系统。

所以会看到:

/dev/ttyS0/dev/ttyS1/dev/ttyUSB0

建立认知:

  • ttyS* 多数是 SoC 自带 UART
  • ttyUSB* 多数是 USB 转串口
  • 应用层大多数时候根本不关心它后面是 UART IP 还是 USB 芯片,只关心“这是个 tty”

五、termios 是什么

termios 可以理解成:

Linux 给 tty 设备的“通信规则配置器”

你通过它告诉系统:

  • 波特率是多少
  • 数据位多少
  • 有没有校验
  • 停止位几位
  • 要不要流控
  • 读的时候怎么阻塞

六、UART 代码

#include<stdio.h>      // printf, perror#include<unistd.h>     // close, read, write#include<fcntl.h>      // open#include<termios.h>    // termios 串口配置接口#include<string.h>     // strlen/* * 函数名:uart_config * 功能:把一个串口 fd 配置成 115200 8N1 原始模式 */staticintuart_config(int fd){structtermiostty;// 保存串口参数的结构体/*     * 先读取当前串口参数     * 为什么先读?     * 因为通常是在当前基础上修改,避免把某些字段意外清掉。     */if (tcgetattr(fd, &tty) != 0) {        perror("tcgetattr");return-1;    }/*     * 设置输入波特率为 115200     */    cfsetispeed(&tty, B115200);/*     * 设置输出波特率为 115200     */    cfsetospeed(&tty, B115200);/*     * 先清除数据位掩码     * CSIZE 代表数据位相关配置位     */    tty.c_cflag &= ~CSIZE;/*     * 设为 8 位数据位     */    tty.c_cflag |= CS8;/*     * 关闭校验位     * PARENB = parity enable     */    tty.c_cflag &= ~PARENB;/*     * 设置为 1 个停止位     * 如果要 2 个停止位,需要打开 CSTOPB     */    tty.c_cflag &= ~CSTOPB;/*     * 关闭硬件流控 RTS/CTS     */    tty.c_cflag &= ~CRTSCTS;/*     * CLOCAL:忽略调制解调器控制线     * CREAD :使能接收     */    tty.c_cflag |= CLOCAL | CREAD;/*     * 输入模式设置为原始     * 不做特殊处理     */    tty.c_iflag = 0;/*     * 输出模式设置为原始     */    tty.c_oflag = 0;/*     * 本地模式设置为原始     * 不做行编辑、不做回显     */    tty.c_lflag = 0;/*     * VMIN = 1     * 表示 read 至少等到 1 个字节才返回     */    tty.c_cc[VMIN] = 1;/*     * VTIME = 1     * 表示超时单位为 0.1 秒,这里是 0.1 秒     */    tty.c_cc[VTIME] = 1;/*     * 立即生效配置     */if (tcsetattr(fd, TCSANOW, &tty) != 0) {        perror("tcsetattr");return-1;    }return0;}intmain(void){/*     * 打开串口设备     *     * O_RDWR   : 可读可写     * O_NOCTTY : 不把该串口当作控制终端     */int fd = open("/dev/ttyS1", O_RDWR | O_NOCTTY);if (fd < 0) {        perror("open");return1;    }/*     * 配置串口     */if (uart_config(fd) != 0) {        close(fd);return1;    }/*     * 要发送的字符串     */constchar *msg = "hello uart\r\n";/*     * 发送数据     */if (write(fd, msg, strlen(msg)) < 0) {        perror("write");        close(fd);return1;    }printf("UART configured and data sent.\n");/*     * 关闭串口     */    close(fd);return0;}

七、UART 接收代码

#include<stdio.h>      // printf, perror#include<unistd.h>     // close, read#include<fcntl.h>      // openintmain(void){/*     * 打开串口     */int fd = open("/dev/ttyS1", O_RDWR | O_NOCTTY);if (fd < 0) {        perror("open");return1;    }/*     * 接收缓冲区     * 多留一个字节,用于字符串结尾 '\0'     */char buf[128];/*     * 阻塞读取串口数据     * 实际是否阻塞取决于 termios 配置     */int n = read(fd, buf, sizeof(buf) - 1);if (n < 0) {        perror("read");        close(fd);return1;    }/*     * 手工补 '\0',便于按字符串打印     */    buf[n] = '\0';/*     * 打印接收到的内容     */printf("recv: %s\n", buf);    close(fd);return0;}

八、为什么 UART 项目里不能只会 read/write

因为实际项目中,串口数据几乎总是协议数据,而不是“人类一句话”。

所以必须考虑:

  • 一帧从哪开始
  • 一帧到哪结束
  • 长度是多少
  • 命令字是什么
  • 校验怎么做
  • 收到一半怎么办
  • 超时怎么办
  • 丢包怎么办

这就是为什么真正的串口项目里,通常会有:

  • 接收缓冲区
  • 状态机
  • 帧头解析
  • CRC / checksum
  • 超时处理

九、UART 面试快答版

问:UART 为什么叫异步?

答:因为没有单独时钟线,双方依赖预先约定的波特率和帧格式完成同步。

问:8N1 是什么?

答:8 位数据位、无校验、1 位停止位。

问:termios 是干什么的?

答:用于配置 tty 设备的通信参数,如波特率、数据位、停止位、校验和读写行为。


RS485 / RS232 


一、先把最容易混淆的事讲清楚

很多人把 UART、TTL、RS232、RS485 混在一起讲。

UART

异步串行通信逻辑 / 控制器层

TTL

逻辑电平形式通常 3.3V 或 5V

RS232

单端串口电气标准

RS485

差分串口电气标准

一句话总结:

UART 解决“怎么按位发”,RS232/RS485 解决“电气上怎么把这位发出去”。


二、RS232 用直觉怎么理解

RS232 像传统的一对一电话线通信。

特点:

  • 一对一
  • 单端
  • 电压摆幅大
  • 历史悠久

它不是为多节点总线设计的。

所以它更像“设备 A 和设备 B 点对点串口通信”。


三、RS485 用直觉怎么理解

RS485 像工业现场的一条公共差分通信线。

特点:

  • 抗干扰
  • 可长距离
  • 多节点
  • 半双工常见

为什么工业现场喜欢它?

因为工厂很吵,电机很多,继电器很多,地线环境也复杂。 普通单端信号容易受干扰,差分更稳。


四、RS485 的难点不是收发,而是“切方向”

半双工 RS485 的核心难点不是 read/write,而是:

什么时候发,什么时候收,什么时候切换方向。

如果方向切换太早:

  • 最后几个字节可能没真正发完
  • 对方收到半截包
  • 你自己又马上进接收
  • 系统就会非常诡异

这就是为什么 tcdrain() 很关键。


五、为什么 Modbus RTU 经常跑在 RS485 上

因为:

  • RS485 只是电气传输方式
  • 它不定义业务格式
  • 工业现场需要一个统一协议

于是 Modbus RTU 很自然成为常见组合:

  • 物理 / 电气层:RS485
  • 协议层:Modbus RTU

六、RS485 代码

#include<stdio.h>          // printf, perror#include<string.h>         // memset#include<unistd.h>         // close#include<fcntl.h>          // open#include<sys/ioctl.h>      // ioctl#include<linux/serial.h>   // struct serial_rs485/* * 文件名:rs485_verbose.c * 编译: * gcc rs485_verbose.c -o rs485_verbose * * 作用: * 尝试把一个 tty 串口配置成 RS485 模式 * * 注意: * 该示例是否成功,取决于内核驱动是否支持 TIOCSRS485 */intmain(void){/*     * 打开底层串口     * 注意:RS485 在 Linux 应用层常常仍然是从 tty 进入     */int fd = open("/dev/ttyS1", O_RDWR | O_NOCTTY);if (fd < 0) {        perror("open");return1;    }/*     * 定义 RS485 配置结构体     */structserial_rs485rs485;/*     * 清零,避免脏数据     */memset(&rs485, 0sizeof(rs485));/*     * 启用 RS485 模式     */    rs485.flags |= SER_RS485_ENABLED;/*     * 发送时拉高 RTS     * 某些平台用这个来控制发送方向     */    rs485.flags |= SER_RS485_RTS_ON_SEND;/*     * 发送结束后,不保持发送状态     * 即准备回到接收方向     */    rs485.flags &= ~SER_RS485_RTS_AFTER_SEND;/*     * 把 RS485 配置下发给驱动     */if (ioctl(fd, TIOCSRS485, &rs485) < 0) {        perror("TIOCSRS485");        close(fd);return1;    }/*     * 如果成功,说明驱动接受了 RS485 模式配置     * 真正业务中,还需要:     * 1. termios 配串口参数     * 2. write() 发送协议帧     * 3. tcdrain() 等待真正发完     * 4. read() 读响应     * 5. 校验 CRC16     * 6. 超时重试     */printf("RS485 mode configured successfully.\n");    close(fd);return0;}

七、RS232 / RS485 面试快答版

问:UART 和 RS485 是什么关系?

答:UART 是异步串行控制逻辑,RS485 是差分电气标准,二者常组合使用。

问:为什么 RS485 抗干扰更强?

答:因为它采用差分传输,对共模噪声不敏感。

问:为什么 RS485 常是半双工?

答:因为多个节点共享同一对线,常通过方向控制在发送和接收之间切换。

问:为什么工业场景常用 RS485 + Modbus RTU?

答:因为 RS485 适合长距离多节点抗干扰传输,而 Modbus RTU 提供统一工业协议格式。


USB 


一、先用直觉理解 USB

USB 不像 UART / I2C / SPI 那样只是“一个简单外设接口”。

USB 更像一个完整的小系统:

  • 主机是“管理者”
  • 设备是“被管理对象”
  • 通信前必须先点名(枚举)
  • 设备必须先上报“我是谁、我会什么”
  • 主机才能决定如何驱动它

这就是 USB 的核心味道。


二、为什么 USB 一定是主机主导

因为 USB 设计里,主机负责:

  • 供电管理
  • 设备枚举
  • 配置选择
  • 数据调度

设备不会自己突然乱发。

记住一句:

USB 是 host-driven bus。


三、什么是枚举

枚举可以理解为:

主机插上一个 USB 设备后,先问它:你是谁?你有哪些功能?我该怎么跟你说话?

设备就通过各种描述符回答:

  • 我的厂商是谁
  • 我的产品是谁
  • 我是摄像头还是串口还是网卡
  • 我有哪些接口
  • 每个接口有哪些端点

然后 Linux 再决定绑定哪个驱动。


四、为什么 USB 设备最后可能变成不同 Linux 节点

因为 USB 只是底层总线。

真正给应用用的,通常是驱动绑定后的抽象。

例如:

  • 摄像头 → /dev/video0
  • USB 转串口 → /dev/ttyUSB0
  • Wi-Fi → wlan0
  • U 盘 → /dev/sda

这一步必须吃透,因为这正是 Linux 应用开发和底层协议开发的边界。


五、USB 代码为什么很多时候不直接写

因为现实项目中,如果已有成熟类驱动,应用通常直接使用更高层抽象:

  • 摄像头走 V4L2
  • 串口走 tty
  • 网卡走 socket
  • 块设备走文件系统 / 块设备接口

只有在没有合适类驱动,或设备协议非常定制化时,才会直接用 libusb


六、libusb 初始化代码

#include<stdio.h>              // printf, fprintf#include<libusb-1.0/libusb.h>  // libusb API/* * 文件名:usb_libusb_verbose.c * 编译: * gcc usb_libusb_verbose.c -lusb-1.0 -o usb_libusb_verbose * * 作用: * 演示 libusb 的最小初始化流程 */intmain(void){/*     * libusb 上下文指针     * 可理解为 libusb 运行环境句柄     */    libusb_context *ctx = NULL;/*     * 返回值     * libusb 大多数函数返回 0 表示成功,负值表示错误     */int ret;/*     * 初始化 libusb     * 如果成功,ctx 会被设置     */    ret = libusb_init(&ctx);if (ret < 0) {fprintf(stderr"libusb_init failed\n");return1;    }/*     * 到这里说明 libusb 初始化成功     */printf("libusb initialized successfully.\n");/*     * 实际项目中,后续常见步骤包括:     *     * 1. 枚举设备列表     * 2. 根据 VID/PID 查找目标设备     * 3. 打开设备句柄     * 4. claim interface     * 5. 发 control/bulk/interrupt 传输     * 6. 关闭设备     *//*     * 退出 libusb,释放上下文     */    libusb_exit(ctx);return0;}

七、USB 面试快答版

问:为什么 USB 被称为主机控制总线?

答:因为设备的枚举、配置和通信调度都由主机发起并管理。

问:VID/PID 是干什么的?

答:用于唯一标识 USB 设备型号,是驱动匹配的重要依据。

问:为什么 USB 摄像头会变成 /dev/videoX

答:因为内核中的 UVC 类驱动绑定后,把它暴露为 V4L2 视频设备。

问:什么时候会用 libusb?

答:当没有现成类驱动,或者设备协议需要用户态直接访问 USB 端点时。


I2C 


一、先用直觉理解 I2C

I2C 像什么?

像一条公共走廊,很多房间共用这条走廊,但每个房间门口有门牌号。

主机想找谁,就先喊门牌号:

  • “0x50 在吗?”
  • “0x68 在吗?”

谁的地址对,谁就应答。

所以 I2C 的核心味道是:

  • 一条共享总线
  • 多个从设备
  • 靠地址区分
  • 主机发起访问

二、为什么 I2C 只用两根线

因为它把控制简化到了极致:

  • SCL:时钟
  • SDA:数据

时钟由主机提供。 数据在线上双向流动。

这也是它很适合板级低速外设的原因:

  • 省引脚
  • 布线简单
  • 适合寄存器配置型设备

三、为什么 I2C 要 ACK/NACK

因为主机不是在和空气说话,它需要知道“有人应不应”。

所以每个关键字节后,会有一位 ACK/NACK:

  • ACK:我收到了
  • NACK:我没收到,或者我不是这个地址

可以把它想成“点名答到”。


四、为什么 I2C 要上拉

这是 I2C 最容易被问的点之一。

I2C 的线通常是开漏结构:

  • 设备可以主动把线拉低
  • 但不能主动把线推高

所以必须靠上拉电阻把线恢复成高电平。

如果没有上拉:

  • 总线可能漂
  • 波形不对
  • 根本通信不了

五、为什么很多 I2C 读操作是“先写后读”

因为很多 I2C 芯片内部是寄存器模型。

主机真正想做的是:

“我要读你 0x10 寄存器里的值。”

所以必须先告诉设备寄存器地址,然后再读数据。

这就是为什么 I2C 读寄存器常表现为:

  1. 先 write 一个寄存器地址
  2. 再 read 真正数据

六、I2C 代码

#include<stdio.h>          // printf, perror#include<stdint.h>         // uint8_t#include<fcntl.h>          // open#include<unistd.h>         // close, read, write#include<sys/ioctl.h>      // ioctl#include<linux/i2c-dev.h>  // I2C_SLAVE/* * 文件名:i2c_verbose.c * 编译: * gcc i2c_verbose.c -o i2c_verbose * * 作用: * 演示最简单的“先写寄存器地址,再读 1 字节数据” */intmain(void){/*     * 打开 I2C 总线设备节点     * 这里示例使用 /dev/i2c-1     */int fd = open("/dev/i2c-1", O_RDWR);if (fd < 0) {        perror("open");return1;    }/*     * 指定目标从设备地址     * 这里用 0x50 作为示例     */int addr = 0x50;/*     * 告诉内核:接下来这个 fd 要访问地址为 0x50 的 I2C 从设备     */if (ioctl(fd, I2C_SLAVE, addr) < 0) {        perror("ioctl I2C_SLAVE");        close(fd);return1;    }/*     * 假设我们想读设备内部 0x00 这个寄存器     */uint8_t reg = 0x00;/*     * 先把寄存器地址写给设备     * 注意:这里不是写数据,而是“告诉设备我要读哪个寄存器”     */if (write(fd, &reg, 1) != 1) {        perror("write reg");        close(fd);return1;    }/*     * 用于保存读回来的数据     */uint8_t data = 0;/*     * 从该寄存器读 1 个字节     */if (read(fd, &data, 1) != 1) {        perror("read data");        close(fd);return1;    }/*     * 打印结果     */printf("Read value: 0x%02X\n", data);    close(fd);return0;}

七、I2C 组合事务代码

#include<stdio.h>          // printf, perror#include<stdint.h>         // uint8_t#include<fcntl.h>          // open#include<unistd.h>         // close#include<sys/ioctl.h>      // ioctl#include<linux/i2c-dev.h>  // i2c-dev 接口#include<linux/i2c.h>      // struct i2c_msg/* * 文件名:i2c_rdwr_verbose.c * 编译: * gcc i2c_rdwr_verbose.c -o i2c_rdwr_verbose * * 作用: * 使用 I2C_RDWR 进行标准组合事务 */intmain(void){/*     * 打开 I2C 总线节点     */int fd = open("/dev/i2c-1", O_RDWR);if (fd < 0) {        perror("open");return1;    }/*     * 要访问的寄存器地址     */uint8_t reg = 0x00;/*     * 保存读回来的数据     */uint8_t data = 0;/*     * msgs[0]:写消息     * 用来把寄存器地址发给从设备     */structi2c_msgmsgs[2];    msgs[0].addr  = 0x50;       // 从设备地址    msgs[0].flags = 0;          // 0 表示写    msgs[0].len   = 1;          // 写 1 字节    msgs[0].buf   = &reg;       // 数据内容是寄存器地址/*     * msgs[1]:读消息     * 从同一个从设备读 1 字节数据     */    msgs[1].addr  = 0x50;       // 同一从设备    msgs[1].flags = I2C_M_RD;   // 表示读    msgs[1].len   = 1;          // 读 1 字节    msgs[1].buf   = &data;      // 读到 data/*     * ioctl 所需封装结构     */structi2c_rdwr_ioctl_dataioctl_data;    ioctl_data.msgs  = msgs;    // 指向消息数组    ioctl_data.nmsgs = 2;       // 一共 2 个消息/*     * 执行组合事务     */if (ioctl(fd, I2C_RDWR, &ioctl_data) < 0) {        perror("ioctl I2C_RDWR");        close(fd);return1;    }/*     * 打印读到的数据     */printf("Read value: 0x%02X\n", data);    close(fd);return0;}

八、I2C 面试快答版

问:为什么 I2C 需要上拉电阻?

答:因为 SDA、SCL 常为开漏结构,设备只能主动拉低,必须依赖上拉恢复高电平。

问:ACK not received 说明什么?

答:主机发出了地址和方向位,但没有从设备应答。

问:为什么 I2C 适合寄存器型外设?

答:因为它低速、线少、总线共享,特别适合传感器、EEPROM、RTC 等按寄存器访问的器件。


SPI 


一、先用直觉理解 SPI

SPI 像什么?

像主设备手里拿着节拍器,同时点名某一个从设备讲话。

它的关键味道:

  • 主设备给时钟
  • 主设备控制谁被选中
  • 发送和接收能同时发生
  • 协议简单,速度容易做高

二、为什么 SPI 通常比 I2C 快

因为 SPI 更直接:

  • 没有复杂地址仲裁
  • 没有 ACK/NACK 开销
  • 有独立时钟
  • MOSI / MISO 分开
  • 可以高频工作

代价就是:

  • 线更多
  • 多设备时片选管理更麻烦

三、SPI 的四根基础线要真正理解

1. SCLK

主设备提供的时钟线。 没有它,从设备不知道什么时候采样。

2. MOSI

Master Out Slave In 主设备发给从设备的数据线

3. MISO

Master In Slave Out 从设备回给主设备的数据线

4. CS

Chip Select 片选信号,用于指明“我现在在跟谁说话”。


四、CPOL / CPHA 别死记,怎么快速理解

很多人死记 mode 0/1/2/3,容易乱。

可以这样理解:

CPOL

决定时钟空闲时是高还是低。

CPHA

决定在第几个边沿采样。

于是四种组合就出来了。

真正工程里你通常不是自己推,而是:

  • 看数据手册
  • 确认 mode 0/1/2/3
  • 配好测试

五、为什么 Linux 下不一定有 spidev

这个问题特别有面试价值。

很多人以为:

“SPI 控制器驱动起来了,就应该有 /dev/spidev0.0。”

这是错的。

真正关系是:

  • SPI 控制器驱动起来 只说明“主机控制器能工作”
  • 是否出现 spidev 取决于设备树 / 驱动绑定策略

所以:

控制器存在 ≠ 用户态泛化节点一定存在


六、SPI 代码

#include<stdio.h>              // printf, perror#include<stdint.h>             // uint8_t, uint32_t#include<fcntl.h>              // open#include<unistd.h>             // close#include<sys/ioctl.h>          // ioctl#include<linux/spi/spidev.h>   // SPI_IOC_* 宏定义/* * 文件名:spi_config_verbose.c * 编译: * gcc spi_config_verbose.c -o spi_config_verbose * * 作用: * 配置一个 spidev 设备的模式、字宽和时钟 */intmain(void){/*     * 打开 spidev 设备节点     * 注意:只有系统已经导出 spidev 时,这里才会成功     */int fd = open("/dev/spidev0.0", O_RDWR);if (fd < 0) {        perror("open");return1;    }/*     * SPI_MODE_0:     * CPOL = 0, CPHA = 0     */uint8_t mode = SPI_MODE_0;/*     * 每个数据单元 8 位     */uint8_t bits = 8;/*     * SPI 时钟 1MHz     */uint32_t speed = 1000000;/*     * 设置 SPI 模式     */if (ioctl(fd, SPI_IOC_WR_MODE, &mode) < 0) {        perror("SPI_IOC_WR_MODE");        close(fd);return1;    }/*     * 设置每字数据位数     */if (ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits) < 0) {        perror("SPI_IOC_WR_BITS_PER_WORD");        close(fd);return1;    }/*     * 设置最大时钟频率     */if (ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed) < 0) {        perror("SPI_IOC_WR_MAX_SPEED_HZ");        close(fd);return1;    }printf("SPI configured successfully.\n");    close(fd);return0;}

七、SPI 传输代码

#include<stdio.h>              // printf, perror#include<stdint.h>             // uint8_t#include<fcntl.h>              // open#include<unistd.h>             // close#include<sys/ioctl.h>          // ioctl#include<linux/spi/spidev.h>   // spi_ioc_transfer#include<string.h>             // memset/* * 文件名:spi_transfer_verbose.c * 编译: * gcc spi_transfer_verbose.c -o spi_transfer_verbose * * 作用: * 演示一次最小 SPI 传输 */intmain(void){/*     * 打开 SPI 设备节点     */int fd = open("/dev/spidev0.0", O_RDWR);if (fd < 0) {        perror("open");return1;    }/*     * tx 为要发出去的数据     * 这里用 0x9F 作为示例命令(很多 SPI Flash 用它读 JEDEC ID)     */uint8_t tx[3] = {0x9F0x000x00};/*     * rx 用于接收返回数据     * 先清零     */uint8_t rx[3] = {0};/*     * 描述一次 SPI 传输的结构体     */structspi_ioc_transfertr;/*     * 清零,避免未初始化字段带来问题     */memset(&tr, 0sizeof(tr));/*     * 发送缓冲区地址     * 注意这里传的是用户空间缓冲区指针     */    tr.tx_buf = (unsignedlong)tx;/*     * 接收缓冲区地址     */    tr.rx_buf = (unsignedlong)rx;/*     * 一次传输的长度:3 字节     */    tr.len = 3;/*     * 本次传输使用的时钟频率     */    tr.speed_hz = 1000000;/*     * 每个数据单元 8 位     */    tr.bits_per_word = 8;/*     * 执行一次 SPI 传输     *     * SPI_IOC_MESSAGE(1) 表示发送 1 个 transfer 结构     */if (ioctl(fd, SPI_IOC_MESSAGE(1), &tr) < 0) {        perror("SPI_IOC_MESSAGE");        close(fd);return1;    }/*     * 打印接收到的数据     */printf("RX: %02X %02X %02X\n", rx[0], rx[1], rx[2]);    close(fd);return0;}

八、SPI 面试快答版

问:SPI 为什么一般比 I2C 快?

答:因为 SPI 协议更简单,没有地址仲裁和 ACK 开销,且是同步时钟、独立收发路径。

问:SPI 为什么需要片选?

答:因为主设备可能连接多个从设备,需要用片选信号决定当前和谁通信。

问:为什么 SPI 控制器起来了却没有 spidev?

答:因为控制器驱动和用户态泛化从设备节点不是一回事,是否出现 spidev 取决于设备树和驱动绑定。


最新文章

随机文章

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-04-17 10:53:19 HTTP/2.0 GET : https://f.mffb.com.cn/a/485338.html
  2. 运行时间 : 0.091350s [ 吞吐率:10.95req/s ] 内存消耗:4,502.96kb 文件加载:140
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=a89a4093921838b9c4539f4dcbc8e86a
  1. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/public/index.php ( 0.79 KB )
  2. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/autoload.php ( 0.17 KB )
  3. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/platform_check.php ( 0.90 KB )
  5. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_static.php ( 4.90 KB )
  7. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  10. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  11. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  12. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  13. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  14. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  15. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  16. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  17. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  18. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  19. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  20. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  21. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  22. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/provider.php ( 0.19 KB )
  23. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  24. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  25. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  26. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/common.php ( 0.03 KB )
  27. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  28. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  29. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/app.php ( 0.95 KB )
  30. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cache.php ( 0.78 KB )
  31. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/console.php ( 0.23 KB )
  32. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cookie.php ( 0.56 KB )
  33. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/database.php ( 2.48 KB )
  34. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  35. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/filesystem.php ( 0.61 KB )
  36. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/lang.php ( 0.91 KB )
  37. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/log.php ( 1.35 KB )
  38. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/middleware.php ( 0.19 KB )
  39. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/route.php ( 1.89 KB )
  40. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/session.php ( 0.57 KB )
  41. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/trace.php ( 0.34 KB )
  42. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/view.php ( 0.82 KB )
  43. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/event.php ( 0.25 KB )
  44. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  45. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/service.php ( 0.13 KB )
  46. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/AppService.php ( 0.26 KB )
  47. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  48. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  49. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  50. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  51. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  52. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/services.php ( 0.14 KB )
  53. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  54. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  55. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  56. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  57. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  58. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  59. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  60. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  61. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  62. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  63. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  64. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  65. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  66. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  67. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  68. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  69. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  70. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  71. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  72. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  73. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  74. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  75. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  76. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  77. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  78. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  79. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  80. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  81. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  82. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  83. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/Request.php ( 0.09 KB )
  84. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  85. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/middleware.php ( 0.25 KB )
  86. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  87. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  88. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  89. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  90. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  91. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  92. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  93. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  94. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  95. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  96. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  97. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  98. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  99. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/route/app.php ( 1.72 KB )
  100. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  101. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  102. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  103. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/controller/Index.php ( 4.81 KB )
  104. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/BaseController.php ( 2.05 KB )
  105. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  106. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  107. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  108. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  109. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  110. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  111. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  112. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  113. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  114. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  115. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  116. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  117. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  118. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  119. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  120. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  121. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  122. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  123. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  124. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  125. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  126. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  127. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  128. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  129. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  130. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  131. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  132. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  133. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  134. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  135. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  136. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  137. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  138. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  139. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/runtime/temp/067d451b9a0c665040f3f1bdd3293d68.php ( 11.98 KB )
  140. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.000675s ] mysql:host=127.0.0.1;port=3306;dbname=f_mffb;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.001060s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.000367s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.000243s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.000615s ]
  6. SELECT * FROM `set` [ RunTime:0.000236s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.000748s ]
  8. SELECT * FROM `article` WHERE `id` = 485338 LIMIT 1 [ RunTime:0.001164s ]
  9. UPDATE `article` SET `lasttime` = 1776394399 WHERE `id` = 485338 [ RunTime:0.014501s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 67 LIMIT 1 [ RunTime:0.000410s ]
  11. SELECT * FROM `article` WHERE `id` < 485338 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.000684s ]
  12. SELECT * FROM `article` WHERE `id` > 485338 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.000453s ]
  13. SELECT * FROM `article` WHERE `id` < 485338 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.000757s ]
  14. SELECT * FROM `article` WHERE `id` < 485338 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.002750s ]
  15. SELECT * FROM `article` WHERE `id` < 485338 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.001273s ]
0.093042s