工控硬件通信基础:串口 RS232/485、I2C、SPI 用户层读写实操

工控硬件通信基础:串口 RS232/485、I2C、SPI 用户层读写实操

搞工控,不会跟硬件"说话"怎么行?这篇带你从串口到 I2C 再到 SPI,把用户层读写一次性撸明白。

一、工控常用硬件接口总览

工控场景下,主板和各种外设之间靠的是各种硬件接口协议。常见的有这几种:

接口通信方式线数典型场景速率
RS232点对点全双工3线(TX/RX/GND)调试串口、GPS模块115200bps常见
RS485多点半双工2线(A/B)Modbus总线、工业仪表10Mbps理论值
I2C多主多从2线(SCL/SDA)温湿度传感器、EEPROM100kHz~3.4MHz
SPI主从全双工4线+(SCLK/MOSI/MISO/CS)ADC、显示屏、Flash几十MHz
CAN多主差分2线(CAN_H/CAN_L)汽车电子、运动控制1Mbps

选型逻辑很简单:短距离调试用串口,工业总线用485或CAN,板级传感器用I2C,高速外设用SPI。

二、串口通信基础

2.1 RS232 vs RS485

  • RS232:单端信号,点对点通信,传输距离短(一般15米以内),常见的DB9串口就是它。工控机上调试口基本都用RS232。
  • RS485:差分信号,多点通信,一条总线上可以挂多个设备(最多32个标准节点),传输距离可达1200米。工业现场传感器、变频器、电表大多走485总线。

两者电气特性不同,但传输的数据帧格式完全一样——都是 UART 帧(起始位+数据位+校验位+停止位)。所以从软件角度看,编程方式一模一样,区别只在硬件层。

2.2 串口参数配置

串口通信需要收发双方约定好以下参数,否则收到的是乱码:

  • 波特率:通信速率,常见9600、19200、38400、115200
  • 数据位:5/6/7/8 位,常用8位
  • 校验位:None(无校验)、Even(偶校验)、Odd(奇校验)
  • 停止位:1位或2位,常用1位

三、Linux 串口编程

3.1 打开串口设备

Linux 下串口设备节点为/dev/ttyS*(传统串口)或/dev/ttyUSB*(USB转串口)。

intfd=open("/dev/ttyS0",O_RDWR|O_NOCTTY|O_NDELAY);if(fd<0){perror("open serial failed");return-1;}// 恢复阻塞模式fcntl(fd,F_SETFL,0);
  • O_NOCTTY:不把串口设为控制终端
  • O_NDELAY:不等待DCD信号,打开后可用fcntl恢复阻塞

3.2 termios 结构体配置

Linux 串口配置的核心是termios结构体:

#include<termios.h>structtermiosoptions;tcgetattr(fd,&options);// 获取当前配置// 设置波特率cfsetispeed(&options,B115200);cfsetospeed(&options,B115200);// 设置数据位(8位)options.c_cflag&=~CSIZE;options.c_cflag|=CS8;// 设置校验位(无校验)options.c_cflag&=~PARENB;options.c_cflag&=~CSTOPB;// 1位停止位// 使能接收和本地模式options.c_cflag|=(CLOCAL|CREAD);// 关闭软件流控options.c_iflag&=~(IXON|IXOFF|IXANY);// 原始模式输出options.c_oflag&=~OPOST;// 原始模式输入(不做特殊处理)options.c_lflag&=~(ICANON|ECHO|ECHOE|ISIG);// 设置超时:读取1个字符后等待100msoptions.c_cc[VMIN]=1;// 最少读取1个字符options.c_cc[VTIME]=1;// 超时0.1秒tcsetattr(fd,TCSANOW,&options);// 立即生效

3.3 数据收发

配置好后,直接用read/write就行:

// 发送数据chartx_buf[]="Hello";intn=write(fd,tx_buf,sizeof(tx_buf));// 接收数据charrx_buf[256];n=read(fd,rx_buf,sizeof(rx_buf));if(n>0){printf("Received %d bytes: %s\n",n,rx_buf);}

注意:RS485是半双工,发送前要切换收发方向。有些硬件通过RTS引脚控制(ioctl(fd, TIOCMSET, ...)),具体看硬件手册。

四、I2C 用户层操作

4.1 i2c-tools 工具集

Linux 提供了i2c-tools工具包,先装上:

sudoaptinstalli2c-tools

常用命令:

# 查看总线列表i2cdetect-l# 扫描i2c-1总线上的设备地址(0x03~0x77)i2cdetect-y1# 读取总线1上地址0x48的设备寄存器0x00的值i2cget-y10x48 0x00# 向总线1上地址0x48的设备寄存器0x01写入值0x55i2cset-y10x48 0x01 0x55

4.2 C 程序读取 I2C 传感器

以 SHT30 温湿度传感器为例(地址0x44,总线1):

#include<fcntl.h>#include<linux/i2c-dev.h>#include<sys/ioctl.h>#include<unistd.h>#include<stdint.h>intread_sht30(float*temp,float*humi){intfd=open("/dev/i2c-1",O_RDWR);if(fd<0)return-1;// 设置从设备地址if(ioctl(fd,I2C_SLAVE,0x44)<0){close(fd);return-1;}// 发送测量命令:单次高精度测量 0x2400uint8_tcmd[2]={0x24,0x00};if(write(fd,cmd,2)!=2){close(fd);return-1;}usleep(15000);// 等待测量完成// 读取6字节数据:温度高/低/CRC + 湿度高/低/CRCuint8_tdata[6];if(read(fd,data,6)!=6){close(fd);return-1;}close(fd);// 转换uint16_traw_t=(data[0]<<8)|data[1];uint16_traw_h=(data[3]<<8)|data[4];*temp=-45.0+175.0*raw_t/65535.0;*humi=100.0*raw_h/65535.0;return0;}

I2C 读写流程:openioctl(I2C_SLAVE)设地址 →write发命令 →read读数据。就这么简单。

五、SPI 用户层操作

5.1 spidev 设备节点

SPI 设备节点为/dev/spidevX.Y(X为总线号,Y为片选号)。使用前需在设备树中使能 spidev。

5.2 ioctl 传输数据

SPI 是全双工,通过ioctl(fd, SPI_IOC_MESSAGE(1), &transfer)一次完成收发:

#include<linux/spi/spidev.h>#include<fcntl.h>#include<sys/ioctl.h>#include<unistd.h>intspi_transfer(intfd,uint8_t*tx,uint8_t*rx,intlen){structspi_ioc_transfertr={.tx_buf=(unsignedlong)tx,.rx_buf=(unsignedlong)rx,.len=len,.delay_usecs=0,.speed_hz=1000000,// 1MHz.bits_per_word=8,};returnioctl(fd,SPI_IOC_MESSAGE(1),&tr);}intmain(){intfd=open("/dev/spidev0.0",O_RDWR);uint8_tmode=SPI_MODE_0;ioctl(fd,SPI_IOC_WR_MODE,&mode);uint8_ttx[]={0xAA,0xBB,0xCC};uint8_trx[3]={0};spi_transfer(fd,tx,rx,3);// rx中即为接收到的数据close(fd);return0;}

关键点:SPI 传输时 MOSI 和 MISO 同时工作,发一个字节的同时收一个字节。想只读不写,tx 填0x00 即可。

六、完整串口配置+收发代码

把前面的串口部分整合成一个可直接用的模块:

#include<stdio.h>#include<stdlib.h>#include<string.h>#include<unistd.h>#include<fcntl.h>#include<termios.h>#include<errno.h>intserial_open(constchar*dev,intbaudrate){intfd=open(dev,O_RDWR|O_NOCTTY|O_NDELAY);if(fd<0){perror("open failed");return-1;}fcntl(fd,F_SETFL,0);structtermiosopt;tcgetattr(fd,&opt);speed_tspeed;switch(baudrate){case9600:speed=B9600;break;case19200:speed=B19200;break;case38400:speed=B38400;break;case115200:speed=B115200;break;default:speed=B115200;break;}cfsetispeed(&opt,speed);cfsetospeed(&opt,speed);opt.c_cflag&=~CSIZE;opt.c_cflag|=CS8;opt.c_cflag&=~PARENB;opt.c_cflag&=~CSTOPB;opt.c_cflag|=(CLOCAL|CREAD);opt.c_iflag&=~(IXON|IXOFF|IXANY);opt.c_oflag&=~OPOST;opt.c_lflag&=~(ICANON|ECHO|ECHOE|ISIG);opt.c_cc[VMIN]=1;opt.c_cc[VTIME]=10;// 1秒超时tcsetattr(fd,TCSANOW,&opt);tcflush(fd,TCIFLUSH);returnfd;}intmain(){intfd=serial_open("/dev/ttyS0",115200);if(fd<0)return-1;// 发送constchar*msg="AT\r\n";write(fd,msg,strlen(msg));// 接收charbuf[256];intn=read(fd,buf,sizeof(buf)-1);if(n>0){buf[n]='\0';printf("RX: %s\n",buf);}close(fd);return0;}

编译:gcc serial.c -o serial,运行前确认有串口读写权限(或加sudo)。

七、工控常见传感器/外设通讯参数汇总

设备类型典型型号接口参数
温湿度传感器SHT30/SHT40I2C地址0x44,115200无需
温湿度传感器AM2302单总线GPIO
压力传感器BMP280I2C/SPI地址0x76/0x77
光照传感器BH1750I2C地址0x23
土壤湿度RS485型RS4859600, 8N1, Modbus-RTU
变频器汇川/台达RS4859600/19200, 8E1, Modbus-RTU
电表DDS系列RS4852400, 8N1, Modbus-RTU
ADCMCP3208SPI1MHz, MODE0
继电器模块通用RS4859600, 8N1
GPS模块ATGM336HUART9600, 8N1

八、小结

工控硬件通信的本质就是"约定好协议格式,然后通过设备节点收发字节"。

  • 串口/dev/ttyS*+ termios 配置 + read/write
  • I2C/dev/i2c-*+ ioctl 设地址 + read/write
  • SPI/dev/spidev*+ ioctl 传输

三条路径套路一致:打开设备节点 → 配置参数 → 收发数据。搞懂这个模式,换什么外设都能快速上手。