工控硬件通信基础:串口 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) | 温湿度传感器、EEPROM | 100kHz~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 0x554.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 读写流程:open→ioctl(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/SHT40 | I2C | 地址0x44,115200无需 |
| 温湿度传感器 | AM2302 | 单总线 | GPIO |
| 压力传感器 | BMP280 | I2C/SPI | 地址0x76/0x77 |
| 光照传感器 | BH1750 | I2C | 地址0x23 |
| 土壤湿度 | RS485型 | RS485 | 9600, 8N1, Modbus-RTU |
| 变频器 | 汇川/台达 | RS485 | 9600/19200, 8E1, Modbus-RTU |
| 电表 | DDS系列 | RS485 | 2400, 8N1, Modbus-RTU |
| ADC | MCP3208 | SPI | 1MHz, MODE0 |
| 继电器模块 | 通用 | RS485 | 9600, 8N1 |
| GPS模块 | ATGM336H | UART | 9600, 8N1 |
八、小结
工控硬件通信的本质就是"约定好协议格式,然后通过设备节点收发字节"。
- 串口:
/dev/ttyS*+ termios 配置 + read/write - I2C:
/dev/i2c-*+ ioctl 设地址 + read/write - SPI:
/dev/spidev*+ ioctl 传输
三条路径套路一致:打开设备节点 → 配置参数 → 收发数据。搞懂这个模式,换什么外设都能快速上手。
