1.TCP/UDP通信部分TCP通信流程A 和 B 之间先建立一条稳定连接然后再传输数据。服务端创建 Socket (socket)准备一部“电话机”。绑定地址 (bind)给电话机分配一个固定的电话号码IP 地址和端口号以便客户端知道该打给谁。监听端口 (listen)接通电话线让电话处于“可以接听”的状态进入LISTEN状态。接受连接 (accept)阻塞等待原地待命。当客户端拨打电话并发起三次握手时服务端完成握手。关键点握手成功后服务端会为这个客户端生成一个全新的、专属的 Socket用于后续的数据传输而最初的那个 Socket 继续保持监听等待下一个客户。数据交互 (recv/send)通过专属 Socket 与客户端进行全双工双向的数据收发。关闭连接 (close)通信结束参与四次挥手回收系统资源。客户端创建 Socket (socket)准备一部“电话机”。(通常不需要手动绑定bind操作系统会随机分配一个本地端口)。.发起连接 (connect)拨打服务端的电话号码IP 和端口主动发送SYN报文触发三次握手。数据交互 (send/recv)连接建立ESTABLISHED状态后像读写本地文件一样向服务端发送请求并接收响应。关闭连接 (close)数据发完后主动发送FIN报文发起四次挥手断开连接。UDP通信流程UDP 的流程非常轻量没有连接建立和断开的过程服务端和客户端处于更加“平等”的数据收发状态。服务端创建 Socket (socket)准备一个“公共邮箱”。绑定地址 (bind)将邮箱设立在一个公开的地址IP 和端口号告诉所有人信件寄到这里。接收数据 (recvfrom)随时查看邮箱。收到客户端发来的数据报时不仅提取数据还会同时记录下发件人客户端的 IP 和端口。发送数据 (sendto)如果需要回复服务端会根据刚才记录下来的客户端地址直接将回信寄出。客户端创建 Socket (socket)准备信件和邮筒。发送数据 (sendto)只要知道服务端的 IP 和端口直接把数据打包发送出去。它不会管服务端是不是开机、是不是在监听发出去就不管了。接收数据 (recvfrom)如果业务逻辑需要服务端的响应就在这里等待接收回信。TCP和UDP核心区别对比项TCPUDP是否需要连接需要连接不需要连接socket 类型SOCK_STREAMSOCK_DGRAM服务端流程socket → bind → listen → accept → recv/sendsocket → bind → recvfrom/sendto客户端流程socket → connect → send/recvsocket → sendto/recvfrom数据形式字节流数据报是否保证到达保证丢了会重传不保证丢了就丢了是否保证顺序保证顺序不保证顺序是否可能粘包/拆包会需要自己处理包边界一次sendto对应一个 UDP 数据报速度和延迟稳定可靠但机制更重更轻量延迟低适合场景文件传输、控制命令、可靠通信丢包测试、视频流、实时数据、广播、多播在应用层编程层面除了通信流程不同更大的不同还在于数据格式不同UDP的数据格式是数据报一次sendto对应的就是一个UDP数据报所以不会存在粘包/拆包的情况而TCP的数据格式是字节流并不是分好的一个个包所以需要字节处理包边界防止粘包的情况。解决方案static ssize_t readn(int fd, void *buf, size_t len) { size_t left len; char *ptr (char *)buf; while (left 0) { ssize_t n recv(fd, ptr, left, 0); if (n 0) { if (errno EINTR) { continue; } return -1; } if (n 0) { return len - left; } left - n; ptr n; } return len; }代码意思是我要读 len 个字节。如果一次 recv 没读够就继续 recv。直到累计读够 len 个字节才返回成功。假设一个包是 52 字节。第一次调用left 52; ptr buf;第一次recv()只收到 20 字节n 20; left 52 - 20 32; ptr ptr 20;这时缓冲区里已经有前 20 字节了但还不完整所以继续循环。第二次recv()收到 10 字节n 10; left 32 - 10 22; ptr ptr 10;现在累计收到 30 字节还差 22 字节。第三次recv()收到 22 字节n 22; left 22 - 22 0;left 0说明已经读满 52 字节。函数返回return len;也就是返回 52。这时候外层代码才开始解析Packet。2. RK3506控制DSL200部分RK3506-USB转RS485-DSL2002.1 为什么要使用USB-RS485而不直接控制DSL200物理电平与信号类型的根本不匹配RK3506 (SoC侧)芯片直接输出的串口信号是 TTL 电平通常是 1.8V 或 3.3V 的单端信号。这种信号非常微弱只适合在同一块电路板上进行几厘米内的短距离通信。DSL200伺服 (设备侧)工业设备使用的是 RS485 电平标准。它不使用共地单根线传数据而是通过两根线A和B之间的电压差来表示逻辑 0 和 1。2.2 USB-RS485在RK3506的表达在RK3506以及绝大多数运行Linux系统的嵌入式板卡上接入USB转RS485模块后系统在底层会将其识别为USB设备但在应用层它会映射为一个串口TTY设备节点。由于RS485只是物理层的电平标准USB转RS485模块的内部核心其实是一颗“USB转UART串口”的芯片如CH340/CH341、CP2102、FT232等外加一个RS485的收发器PHY。因此Linux内核并不知道外部接的是RS485它只会把它当成普通的USB转串口来处理。应用层的表现形态/dev/ttyUSB*在你的应用程序中直接把它当作普通的串口如/dev/ttyUSB0进行打开、配置波特率和读写即可。2.3 Modbus RTU 一帧数据一般由哪些部分组成一般包括从站地址、功能码、寄存器地址、数据内容、CRC16 校验。比如写单个寄存器时功能码是0x06读保持寄存器时功能码是0x03。2.4 为什么 Modbus RTU 要加 CRC16因为 RS485 是工业现场常用总线可能存在电磁干扰、线缆接触不良、传输错误等问题。CRC16 用于判断接收到的数据帧是否被破坏。如果 CRC 不一致说明数据不可信程序就不能继续使用这帧数据。2.5 B_TCP开机自启是怎么做的可以用 systemd 服务实现。把B_TCP编译后放到固定路径比如/usr/local/bin/B_TCP然后写一个.service文件。3.问题排查部分3.1 如果开机后 A 端连接 B 端失败你怎么排查在我的实际过程中确实遇到过这个问题问题的大部分的原因是我的B板的TCP接收转发端的进程还没起来A端就开始发送数据了导致客户端服务端通信失败。理论上排查过程可以分为1.在B端检查自启服务有没有启动2.使用 ip addr查看ip是否正确查看AB端ip是否在同一个网段3.ping一下等3.2 电机不转怎么排查1.首先插拔一下USB-RS485模块检查一下有没有在 /dev下生成tty节点然后我会重新检查一下波特率。2.排除上述原因我会查看一下dsl200的status状态读取一下速度和模式以及警告码Alarm code对照用户手册查看一下问题。3.我还会测试一下是只有位置模式下有问题还是速度模式和位置模式下都有问题。4.BMI088驱动4.1.字符设备注册流程1.申请设备号 2.初始化字符设备并绑定file_operations 3.创建设备类 4.创建设备节点4.2 IIO注册流程分配 IIO 设备使用devm_iio_device_alloc分配一个 IIO 结构体实例附带分配私有数据空间iio_priv。填充 IIO 信息主要是设置回调函数类似file_operations)与设置channelsinfo结构体提供底层数据读取回调最核心的是read_raw函数。channels数组定义传感器有几个轴、什么类型如IIO_ANGL_VEL表示角速度、是否需要比例因子Scale等。modes设置为INDIO_DIRECT_MODE直接读取模式。注册 IIO 设备使用devm_iio_device_register向内核注册。4.2 BMI088通过SPI读取寄存器的流程是什么主机先发送一个寄存器地址并把最高位置 1 表示读操作。由因为SPI是全双工的所以后续读数据也需要主机一直向从机发一些无效数据产生时钟。4.3BMI088 陀螺仪初始化流程是什么配置 SPI 参数比如 mode、bits_per_word、max_speed_hz。向 soft reset 寄存器写入0xB6让芯片复位。延时等待芯片内部状态恢复。读取 CHIP ID判断是否等于0x0F测试SPI通信是否成功。配置陀螺仪量程比如 ±2000 dps。设置带宽比如2000HZ。注册字符设备或 IIO 设备。4.3.问题排查1.SPI通讯失败阶段1软件排查在接上BMI088模块后导入bmi088_gyro.ko驱动模块发现SPI通讯失败1.首先排查接线是否正确其次排查PS是否接地BMI088在SPI模式下要求PS接地发现购买的BMI088模块是开关模式开关已经打到SPI2.排除上述原因后我们取下BMI088短接开发板上MSIO和MISO引脚排查是BMI088模块的问题还是开发板的问题短接MOSI和MISO后依然SPI通讯失败那问题大概率就在开发板上大概率是设备树有问题3.查看设备树发现已设置引脚复用查看pinctrl子系统下的引脚cat /sys/kernel/debug/pinctrl/pinctrl-rockchip-pinctrl/pinmux-pins发现SPI0对应的pin口为 pin15pin16pin17pin18pin19对应的GPIO为GPIOB7,GPIOC0,GPIOC1,GPIOC2,GPIOC3然后我们查看原理图可以看到原理图中的SPI几个接口是GPIOB0-B3即接口对应错误。然后我们查看SDK中的设备树可以发现设备树的配置的确是GPIOC xx与原理图中的不同。然后我们按照原理图修改SPI 的pinctrl的引脚修改后查看发现pin789居然不是spi而是uart2说明这个引脚目前是被uart2占用了然后我们在设备树中将uart2的status设为disabled发现pin7-10变成spi了。这时我们以为应该可以了然后连接模块进行实验发现依然报错。阶段2示波器排查所以由此我们现在依然怀疑是开发板SPI输出有问题我们撰写一个持续发送消息的demo然后使用示波器查看CLK,MOSI,MISO波形。通过抓取CLK波形来看CLK无波形那么说明要么设备树依然有问题要么SPI控制器等坏掉了。阶段3继续排查设备树继续排查设备树我们发现编译设备树时除了rk3506-pinctrl.dtsi文件外还有一个rk3506-pinctrl-rmio.dtsi文件然后我们查看原理图发现的确是存在IO重映射机制然后观察rk3506-pinctrl-rmio.dtsi文件文件将所有引脚的重映射功能全部描述出来了如图哪怕GPIOA_0……并没有SPI功能他也描述出来了如果不理解可以打开一下这个文件然后输入 /spi0一直按n会发现有几百个spi的复用实则里面只有几个是我们需要的。最后我们修改板级设备树里面的io9io9io10根据原理图实际情况来就是GPIOB1,GPIOB2等。修改完设备树我们重新编译烧录运行cat /sys/kernel/debug/pinctrl/pinctrl-rockchip-pinctrl/pinmux-pins可以发现正确的引脚情况了。最后无论是示波器显示还是短接回环还是BMI088模块都没问题了。