ESP32-C3控制器的RS485通信测试
本文主要介绍了RS485通信的原理及其在工业控制中的应用。RS485通过差分信号传输,具有抗干扰能力强、适合长距离传输的特点,支持多点总线结构和半双工通信。文章还详细描述了RS485的电气特性、抗干扰能力及典型应用场景。此外,文章通过硬件连接图和软件代码示例,展示了如何利用ESP32C3-N4开发板实现两个设备之间的RS485通信控制,包括从机模式和主从机模式的通信方式。
提示:本文内容仅供学习参考。Author: Jonnie Walker
目录
前言
本文主要介绍了RS485通信的原理及其在工业控制中的应用。RS485通过差分信号传输,具有抗干扰能力强、适合长距离传输的特点,支持多点总线结构和半双工通信。文章还详细描述了RS485的电气特性、抗干扰能力及典型应用场景。此外,文章通过硬件连接图和软件代码示例,展示了如何利用ESP32-C3-PLC控制器开发板实现两个设备之间的RS485通信控制,包括从机模式和主从机模式的通信方式。
关于RS485通信控制的简单测试!通过我设计的ESP32-C3-PLC控制器向你展示测试过程。测试环境使用的是ArduinoIDE2.0作为本次测试程序编译!主要还是简单。从文章的目录你就知道本文大概在讲什么!我就不废话了,请看下文。希望文章对你有帮助吧!
一、RS485通信原理?
RS-485(又称EIA-485)是一种广泛应用的串行通信标准,主要用于工业控制、仪器仪表等需要长距离、抗干扰的场景。以下是其原理的概括:
1. 差分信号传输
-
原理:RS-485通过两条线(A和B)的电压差表示逻辑状态,而非单线对地电压。
-
逻辑1:A线电压比B线高(差分电压 ≥ +0.2V)。
-
逻辑0:B线电压比A线高(差分电压 ≤ -0.2V)。
-
-
优势:差分信号能有效抑制共模干扰(如电磁噪声),适合长距离传输。
2. 多点总线结构
-
拓扑:支持总线型网络,多个设备(最多32个标准负载)可挂接在同一对双绞线上。
-
半双工通信:同一时间仅允许一个设备发送数据,其他设备接收,需通过协议(如Modbus)协调主从设备。
3. 电气特性
-
电压范围:驱动端输出差分电压为-7V至+12V,接收端灵敏度为±0.2V。
-
传输距离:理论最大距离约1200米(速率≤100kbps),速率与距离成反比(例如:10Mbps时仅支持短距离)。
-
终端电阻:总线两端需接120Ω电阻,匹配阻抗以减少信号反射。
4. 抗干扰能力
-
共模抑制:接收器可容忍高达-7V至+12V的共模电压(A、B线对地电压)。
-
双绞线屏蔽:推荐使用屏蔽双绞线,进一步降低电磁干扰。
5. 典型应用场景
-
工业自动化(PLC、传感器通信)
-
楼宇控制系统(HVAC、安防)
-
智能仪表(电表、水表集中抄表)
-
Modbus RTU协议(基于RS-485的常见工业协议)
二、测试步骤
1.硬件
图1是本次测试使用的两个相同的开发版,主控为ESP32C3-N4。开发版自带WiFi,蓝牙功能……。且开发板有4路输入输出功能。可以接传感器与负载设备。从图中可以看到我们分别给两个设备标记了:设备A(0x01),设备B(0x02),这个标记我们后面会在程序中用到。这次我们主要讲两个开发版之间485通信控制功能,关于485通信原理这里我们不详细讲,一时半会可能讲不明白!
如果你对485通信不太了解,可以先去阅读一下其他大佬的关于485通信工程文章。我个人认为即使你对485不熟悉也不影响你看懂本文内容。马上开始本次测试实验。以下为本次测试主要硬件:
图1

图2

在本次测试中我们会用到一个485转TTL调试工具,如图2。
图3

图3是本次测试RS-485部分硬件连接的原理图(自动流控)。电路中我没有加短路保护不够严谨,请原谅!如果你使用485电路不是自动流控,使用文章中的程序可能需要做一些修改才可以使用。
2.软件
1.注意的问题:
1. 半双工通信机制:
RS-485采用半双工模式,同一时间只能有一个设备在总线上发送数据。若主设备连续发送指令:
(1) 总线冲突:多个设备同时发送会导致信号叠加,数据损坏。
(2) 收发切换延迟:发送完成后需切换为接收模式,若未等待足够时间,可能错过响应。
2. 协议帧间隔要求:
常见协议(如Modbus RTU)要求帧间隔时间(如3.5字符时间):
(1)连续发送违反间隔:未留足间隔会导致接收方无法区分帧边界,引发解析错误。
(2)示例:9600波特率下,3.5字符时间≈4ms,需在帧间插入此延时。
3. 缓冲区溢出风险:
(1)发送过快:若接收方处理速度慢,连续发送会导致其缓冲区溢出,丢失数据。
(2)硬件限制:部分RS-485芯片的缓冲区较小,无法承受高频率数据流。
4. 电气特性限制:
(1)信号反射:长距离通信时,连续发送可能加剧信号反射问题,需终端电阻匹配。
(2)驱动能力:总线负载设备过多时,连续发送可能超出驱动芯片的带载能力。
5. 软件实现缺陷:
(1)未处理应答:发送后未等待响应直接发下一指令,导致协议逻辑混乱。
(2)缺乏流控机制:未实现硬件/软件流控,无法协调收发节奏。
总结:通过合理设计通信时序、严格遵循协议规范、优化硬件设计,可在保证可靠性的前提下实现高效通信。关键需在发送频率与系统响应速度之间找到平衡点。
2.从机模式通信方式:
图4

图4.中分别将设备A与设备B并联起来,然后通过PC端发送控制指令,分别对应 设备A/B进行控制。本次测试实验使用ArduinoIDE2.0工具不多说大家都知道!PC端使用调试工具为XCOM V2.0,主要用于发送控制指令与数据监测功能。好了现在开始上代码:
这里我们给两个设备A/B设定一个简单的通信协议:[设备地址][分隔符][指令内容][结束符]
示例:
0x01:ACN1_ON --> 控制设备A的继电器CN1打开
0x01:ACN1_OFF --> 控制设备A的继电器CN1关闭
.....
PC端使用调试工具发送控制指令:
发送指令:
控制设备A: 01:ACN1_OFF
返回:1:OK
控制设备B:02:BCN1_ON
返回:2:OK
图5

图5是本次测试硬件连接实物图,后面我还会用这个硬件连接做测试!
(1)设备A端代码:
/**
* @file Equipment-A.ino
* @author jonnie Walker iTE
* @brief 485_TestA
* @version 0.1
* @date 2025-05-13
*
* @copyright Copyright (c) 2025
*
* ------------------------------------------------------/
*
* Here we are using the automatic flow control 485 chip.
*
*/
#include <HardwareSerial.h>
//#define RE_DE_PIN 6
#define DEVICE_ADDR 0x01
#define BAUDRATE 9600
#define RELAY_BUILTIN 5
HardwareSerial RS485(1);
void setup() {
Serial.begin(115200);
pinMode(RELAY_BUILTIN,OUTPUT);
RS485.begin(BAUDRATE, SERIAL_8N1, 4, 3); // RX, TX
// pinMode(RE_DE_PIN, OUTPUT);
// digitalWrite(RE_DE_PIN, LOW);
}
void loop() {
if (RS485.available() > 0) {
String data = RS485.readStringUntil('\n');
data.trim();
int addr = data.substring(0, data.indexOf(':')).toInt();
if (addr == DEVICE_ADDR) { // Response when address matching
String cmd = data.substring(data.indexOf(':') + 1);
String response = executeCommand(cmd);
// ================= Response sending=================
RS485.print(DEVICE_ADDR);
RS485.print(":");
RS485.println(response);
RS485.flush();
}
}
}
//Instruction processing
String executeCommand(String cmd) {
if (cmd == "ACN1_ON") {
digitalWrite(RELAY_BUILTIN, HIGH);
return "OK";
} else if (cmd == "ACN1_OFF") {
digitalWrite(RELAY_BUILTIN, LOW);
return "OK";
}
return "ERROR";
}
(2)设备B端代码:
/**
* @file Equipment-B.ino
* @author jonnie Walker iTE
* @brief 485_TestB
* @version 0.1
* @date 2025-05-13
*
* @copyright Copyright (c) 2025
*
* -------------------------------------------------------------/
*
* Here we are using the automatic flow control 485 chip.
*
*
*
*/
#include <HardwareSerial.h>
#define RELAY_BUILTIN 6
#define BAUDRATE 9600 // 485 communication baud rate
#define DEVICE_ADDR 0x02 //ID
HardwareSerial RS485(1); // Use UART1
// =================init =================
void setup() {
Serial.begin(115200);
pinMode(RELAY_BUILTIN,OUTPUT);
RS485.begin(BAUDRATE, SERIAL_8N1, 4, 3); // RX, TX
//Serial.print("Device B Ready. Addr:");
//Serial.println(DEVICE_ADDR, HEX);
}
// ================= 主循环 =================
void loop() {
handleRS485Communication();
}
// ================= 通信处理函数 =================
void handleRS485Communication() {
// 接收数据
if (RS485.available() > 0) {
String data = RS485.readStringUntil('\n');
data.trim();
int addr = data.substring(0, data.indexOf(':')).toInt();
if (addr == DEVICE_ADDR) { // Response when address matching
String cmd = data.substring(data.indexOf(':') + 1);
String response = executeCommand(cmd);
// ================= Response sending =================
RS485.print(DEVICE_ADDR);
RS485.print(":");
RS485.println(response);
RS485.flush();
}
}
}
// ================= Instruction processing=================
String executeCommand(String cmd) {
if (cmd == "BCN1_ON") {
digitalWrite(RELAY_BUILTIN, HIGH);
return "OK";
} else if (cmd == "BCN1_OFF") {
digitalWrite(RELAY_BUILTIN, LOW);
return "OK";
}
return "ERROR";
}
3.主从机模式通信方式:
图5

从图5中就可以看出主从通信原理。这里我们依然需要给两个设备A/B设定一个简单的通信协议:
协议格式:[目标地址]:[源地址]:[指令内容]:[参数]
示例:
01:02:ACN1_R1:ON --> 设备B(02)控制设备A(01)的继电器打开
02:01:ACN1_R1;ON --> 设备A(01)控制设备B(02)的继电器打开
........
通过设备B发送指令给设备A:01:02:ACN1_R1:ON -->控制设备继电器CN1打开
通过设备B发送指令给设备A:01:02:ACN1_R1:OFF -->控制设备继电器CN1关闭
(1)设备A端代码:
/**
* @file Equipment-A_T2.ino
* @author jonnie Walker iTE
* @brief 485_A_Test2
* @version 0.1
* @date 2025-05-14
*
* @copyright Copyright (c) 2025
*
* --------------------------------------------/
*
* 示例:V2.0
* 01:02:ACN1_ON:ON # 设备B(02)请求设备A(01)的控制继电器
* 02:01:BCN1_ON:ON # 设备A(01)请求设备B(02)的控制继电器
*
*/
#include <HardwareSerial.h>
// 硬件配置(保持不变)
#define DEVICE_ADDR 0x01
#define BAUDRATE 9600
#define SEND_ADDR 0x02 //B.使用A设备才能开启
// 输出端子
#define RELAY_CN1_OUT1_PIN 5
#define RELAY_CN2_OUT2_PIN 6
#define RELAY_CN3_OUT3_PIN 7
#define RELAY_CN4_OUT4_PIN 10
// 输入端子
#define PCI_IN1_PIN 0
#define PCI_IN2_PIN 1
#define PCI_IN3_PIN 2
#define PCI_IN4_PIN 9
#define BUTTON_PIN 8
HardwareSerial RS485(1);
unsigned long lastSendTime = 0;
const unsigned long SEND_INTERVAL = 5000;
const unsigned long INPUT_READ_INTERVAL = 1000;
// 状态变量优化为位操作
volatile uint8_t inputStates = 0;
volatile uint8_t pendingCommands = 0;
void setup() {
Serial.begin(115200);
// 初始化输出
const uint8_t relayPins[] = {RELAY_CN1_OUT1_PIN, RELAY_CN2_OUT2_PIN,
RELAY_CN3_OUT3_PIN, RELAY_CN4_OUT4_PIN};
for(uint8_t i=0; i<4; i++) pinMode(relayPins[i], OUTPUT);
// 初始化输入(内部上拉)
const uint8_t inputPins[] = {PCI_IN1_PIN, PCI_IN2_PIN, PCI_IN3_PIN, PCI_IN4_PIN, BUTTON_PIN};
for(uint8_t i=0; i<5; i++) pinMode(inputPins[i], INPUT_PULLUP);
RS485.begin(BAUDRATE, SERIAL_8N1, 4, 3);
Serial.println("Device A Ready (Addr:01)");
}
void loop() {
static unsigned long lastInputCheck = 0;
processIncomingData(); // 优先处理接收数据
// 输入检测(1秒间隔)
if (millis() - lastInputCheck >= INPUT_READ_INTERVAL) {
readInputSignals();
lastInputCheck = millis();
}
// 命令发送处理(5秒间隔)
if (millis() - lastSendTime >= SEND_INTERVAL) {
processPendingCommands();
lastSendTime = millis();
}
}
// 输入信号读取优化
void readInputSignals() {
static const uint8_t inputPins[] = {PCI_IN1_PIN, PCI_IN2_PIN, PCI_IN3_PIN, PCI_IN4_PIN};
uint8_t newStates = 0;
for(uint8_t i=0; i<4; i++) {
// 修正1:补全括号并修正逻辑
if(digitalRead(inputPins[i])) { // 当使用INPUT_PULLUP时,HIGH表示未触发
continue; // 保持原状态
}
// 只有当输入为LOW(触发)时执行以下操作
newStates |= (1 << i); // 记录新状态
pendingCommands |= (1 << i); // 设置需要发送命令的标志位
sendImmediateCommand(i); // 立即发送ON命令
}
inputStates = newStates;
}
/*
// 优化输入信号读取
void readInputSignals() {
static const uint8_t inputPins[] = {PCI_IN1_PIN, PCI_IN2_PIN, PCI_IN3_PIN, PCI_IN4_PIN};
uint8_t newStates = 0;
for(uint8_t i=0; i<4; i++) {
if(digitalRead(inputPins[i])) continue; // INPUT_PULLUP模式下HIGH表示未触发
newStates |= (1 << i);
if(!(pendingCommands & (1 << i))) { // 避免重复发送
pendingCommands |= (1 << i);
sendImmediateCommand(i); // 立即发送ON命令
}
}
inputStates = newStates;
}
*/
// 立即发送ON命令
void sendImmediateCommand(uint8_t index) {
const char* commands[] = {
"BCN1_R1:ON", "BCN2_R2:ON", "BCN3_R3:ON", "BCN4_R4:ON"
};
sendCommand(SEND_ADDR, commands[index]);
}
// 命令处理优化
void processPendingCommands() {
static const char* commands[] = {
"BCN1_R1:OFF", "BCN2_R2:OFF", "BCN3_R3:OFF", "BCN4_R4:OFF"
};
for(uint8_t i=0; i<4; i++) {
if(pendingCommands & (1 << i)) {
sendCommand(SEND_ADDR, commands[i]);
pendingCommands &= ~(1 << i); // 清除对应位
}
}
}
// 数据接收处理优化(使用缓冲区)
void processIncomingData() {
static char buffer[32];
static size_t idx = 0;
while(RS485.available()) {
char c = RS485.read();
if(c == '\n' || idx >= sizeof(buffer)-1) {
buffer[idx] = '\0';
parseMessage(buffer);
idx = 0;
return;
}
buffer[idx++] = c;
}
}
// 消息解析优化
void parseMessage(char* msg) {
uint8_t targetAddr, sourceAddr;
char cmd[16], param[16];
if(sscanf(msg, "%hhu:%hhu:%15[^:]:%15s", &targetAddr, &sourceAddr, cmd, param) != 4)
return;
if(targetAddr != DEVICE_ADDR) return;
// 继电器控制统一处理
const struct {
const char* prefix;
uint8_t pin;
} relayMap[] = {
{"ACN1_R1", RELAY_CN1_OUT1_PIN},
{"ACN2_R2", RELAY_CN2_OUT2_PIN},
{"ACN3_R3", RELAY_CN3_OUT3_PIN},
{"ACN4_R4", RELAY_CN4_OUT4_PIN}
};
for(uint8_t i=0; i<4; i++) {
if(strcmp(cmd, relayMap[i].prefix) == 0) {
digitalWrite(relayMap[i].pin, (strcmp(param, "ON") == 0) ? HIGH : LOW);
sendResponse(sourceAddr, "OK:%s", param);
return;
}
}
sendResponse(sourceAddr, "ERR:UNKNOWN_CMD");
}
// 响应发送优化(支持格式化输出)
void sendResponse(uint8_t targetAddr, const char* format, ...) {
char buffer[32];
va_list args;
va_start(args, format);
vsnprintf(buffer, sizeof(buffer), format, args);
va_end(args);
RS485.printf("%d:%d:%s\n", targetAddr, DEVICE_ADDR, buffer);
delay(4);
}
// 命令发送优化
void sendCommand(uint8_t targetAddr, const char* cmd) {
RS485.printf("%d:%d:%s\n", targetAddr, DEVICE_ADDR, cmd);
delay(4);// 9600bps时插入3.5字符时间延时
}
(2)设备B端代码:
/**
* @file Equipment-B_T2.ino
* @author jonnie Walker iTE
* @brief 485_B_Test
* @version 0.1
* @date 2025-05-14
*
* @copyright Copyright (c) 2025
*
* ----------------------------------------------------/
*
*
* 示例:V2.0
* 01:02:ACN1_ON:ON # 设备B(02)请求设备A(01)的控制继电器
* 02:01:BCN1_ON:ON # 设备A(01)请求设备B(02)的控制继电器
*
*
*
*/
#include <HardwareSerial.h>
// 硬件配置(保持不变)
#define DEVICE_ADDR 0x02
#define BAUDRATE 9600
#define SEND_ADDR 0x01
// 输出端子
#define RELAY_CN1_OUT1_PIN 5
#define RELAY_CN2_OUT2_PIN 6
#define RELAY_CN3_OUT3_PIN 7
#define RELAY_CN4_OUT4_PIN 10
// 输入端子
#define PCI_IN1_PIN 0
#define PCI_IN2_PIN 1
#define PCI_IN3_PIN 2
#define PCI_IN4_PIN 9
#define BUTTON_PIN 8
HardwareSerial RS485(1);
unsigned long lastSendTime = 0;
const unsigned long SEND_INTERVAL = 5000;
const unsigned long INPUT_INTERVAL = 1000;
// 状态变量优化
volatile uint8_t pendingCommands = 0; // 使用位掩码管理状态
uint8_t inputStates = 0;
void setup() {
Serial.begin(115200);
// 初始化输出引脚
const uint8_t relayPins[] = {RELAY_CN1_OUT1_PIN, RELAY_CN2_OUT2_PIN,
RELAY_CN3_OUT3_PIN, RELAY_CN4_OUT4_PIN};
for(uint8_t i=0; i<4; i++) pinMode(relayPins[i], OUTPUT);
// 初始化输入引脚
const uint8_t inputPins[] = {PCI_IN1_PIN, PCI_IN2_PIN, PCI_IN3_PIN, PCI_IN4_PIN, BUTTON_PIN};
for(uint8_t i=0; i<5; i++) pinMode(inputPins[i], INPUT_PULLUP);
RS485.begin(BAUDRATE, SERIAL_8N1, 4, 3);
Serial.println("Device B Ready (Addr:02)");
}
void loop() {
processIncomingData(); // 启用接收处理
static unsigned long lastInputCheck = 0;
if (millis() - lastInputCheck >= INPUT_INTERVAL) {
readInputSignals();
lastInputCheck = millis();
}
if (millis() - lastSendTime >= SEND_INTERVAL) {
processPendingCommands();
lastSendTime = millis();
}
}
// 优化输入信号读取
void readInputSignals() {
static const uint8_t inputPins[] = {PCI_IN1_PIN, PCI_IN2_PIN, PCI_IN3_PIN, PCI_IN4_PIN};
uint8_t newStates = 0;
for(uint8_t i=0; i<4; i++) {
// 修正1:补全括号并修正逻辑
if(digitalRead(inputPins[i])) { // 当使用INPUT_PULLUP时,HIGH表示未触发
continue; // 保持原状态
}
// 只有当输入为LOW(触发)时执行以下操作
newStates |= (1 << i); // 记录新状态
pendingCommands |= (1 << i); // 设置需要发送命令的标志位
sendImmediateCommand(i); // 立即发送ON命令
}
inputStates = newStates;
}
/*
// 优化输入信号读取
void readInputSignals() {
static const uint8_t inputPins[] = {PCI_IN1_PIN, PCI_IN2_PIN, PCI_IN3_PIN, PCI_IN4_PIN};
uint8_t newStates = 0;
for(uint8_t i=0; i<4; i++) {
if(digitalRead(inputPins[i])) continue; // INPUT_PULLUP模式下HIGH表示未触发
newStates |= (1 << i);
if(!(pendingCommands & (1 << i))) { // 避免重复发送
pendingCommands |= (1 << i);
sendImmediateCommand(i); // 立即发送ON命令
}
}
inputStates = newStates;
}
*/
// 立即发送ON命令
void sendImmediateCommand(uint8_t index) {
const char* commands[] = {
"ACN1_R1:ON", "ACN2_R2:ON", "ACN3_R3:ON", "ACN4_R4:ON"
};
sendCommand(SEND_ADDR, commands[index]);
}
// 处理待发送命令
void processPendingCommands() {
const char* commands[] = {
"ACN1_R1:OFF", "ACN2_R2:OFF", "ACN3_R3:OFF", "ACN4_R4:OFF"
};
for(uint8_t i=0; i<4; i++) {
if(pendingCommands & (1 << i)) {
sendCommand(SEND_ADDR, commands[i]);
pendingCommands &= ~(1 << i); // 清除标志位
}
}
}
// 优化数据接收处理
void processIncomingData() {
static char buffer[32];
static size_t idx = 0;
while(RS485.available()) {
char c = RS485.read();
if(c == '\n' || idx >= sizeof(buffer)-1) {
buffer[idx] = '\0';
parseMessage(buffer);
idx = 0;
return;
}
buffer[idx++] = c;
}
}
// 统一消息解析
void parseMessage(char* msg) {
uint8_t targetAddr, sourceAddr;
char cmd[16], param[16];
if(sscanf(msg, "%hhu:%hhu:%15[^:]:%15s", &targetAddr, &sourceAddr, cmd, param) != 4)
return;
if(targetAddr != DEVICE_ADDR) return;
// 继电器控制映射表
const struct {
const char* prefix;
uint8_t pin;
} relayMap[] = {
{"BCN1_R1", RELAY_CN1_OUT1_PIN},
{"BCN2_R2", RELAY_CN2_OUT2_PIN},
{"BCN3_R3", RELAY_CN3_OUT3_PIN},
{"BCN4_R4", RELAY_CN4_OUT4_PIN}
};
for(uint8_t i=0; i<4; i++) {
if(strcmp(cmd, relayMap[i].prefix) == 0) {
digitalWrite(relayMap[i].pin, (strcmp(param, "ON") == 0) ? HIGH : LOW);
sendResponse(sourceAddr, "OK:%s", param);
return;
}
}
sendResponse(sourceAddr, "ERR:UNKNOWN_CMD");
}
// 优化响应发送
void sendResponse(uint8_t targetAddr, const char* format, ...) {
char buffer[32];
va_list args;
va_start(args, format);
vsnprintf(buffer, sizeof(buffer), format, args);
va_end(args);
RS485.printf("%d:%d:%s\n", targetAddr, DEVICE_ADDR, buffer);
delay(4);// 9600bps时插入3.5字符时间延时
}
// 优化命令发送
void sendCommand(uint8_t targetAddr, const char* cmd) {
RS485.printf("%d:%d:%s\n", targetAddr, DEVICE_ADDR, cmd);
delay(4);// 9600bps时插入3.5字符时间延时
}
程序实现是通过设备B端输入的开关量,将采集到的信号转换成设定的485通信协议然后发送给设备A,设备A接收数据处理然后控制继电器打开与关闭。反知同理.......
程序我就不详细分析了,特别注明:485通信过程中不要连续发送控制指令!其实这次485通信控制测试我是当成串口使用的!完全没有发挥485通信的全部功能!
图6

图6是测试时485调试工具接收的设备A与设备B之间数据传输内容,也可以通过 调试工具发送控制指令。
下面是我测试时 视频:
5月15日
总结
本文通过软/硬件结合大概的演示RS-485通信控制过程。 RS-485通信在工业控制应用中也是非常重要的,他的各方面优缺点也是决定在工业通信控制中的地位。经过本文阅读你大概知道RS-485通信控制原理了吧!非常感谢你能看到这里。 iTEM
如果文章内容有误的地方请谅解并指明!Thanks♪(・ω・)ノ
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐

所有评论(0)