8.1 SPI 简介

SPI(Serial Peripheral Interface,串行外设接口)是一种同步串行通信协议,广泛用于嵌入式系统中连接微控制器与外围设备,如传感器、存储器、显示屏等。

主要特点
1. 全双工通信:支持同时发送和接收数据。
2. 同步通信:依靠时钟信号(SCLK)同步数据传输。
3. 主从架构:一个主设备控制一个或多个从设备。
4. 高速传输:速度通常高于I2C和UART。

优点:高速传输,全双工通信,硬件简单。

缺点:需要更多引脚,无内置错误检测,协议复杂度较高。

全双工,这里就跟I2C区别开了,I2C只有一根数据线,发送数据时不能接收,接收数据时不能发送,所以SPI相较于I2C通信十分迅速,但是,主从身份是无法改变的,硬件(接的线)固定了,一个主机多个从机,主机是这些从机永远的主机。就是接的线相对多一点,I2C就用个SCL,SDA,SPI至少用四根。

信号线
1. SCLK(Serial Clock):主设备提供的时钟信号。
2. MOSI(Master Out Slave In):主设备发送数据,从设备接收。
3. MISO(Master In Slave Out):从设备发送数据,主设备接收。
4. SS/CS(Slave Select/Chip Select):主设备选择从设备。

应用场景
存储器:如Flash、EEPROM。
传感器:如温度、加速度传感器。
显示屏:如OLED、LCD。
通信模块:如Wi-Fi、蓝牙模块。

8.2 SPI 工作原理


1. 初始化:主设备配置时钟频率、数据格式等参数。
2. 选择从设备:主设备拉低(或者拉高,取决于从机)对应从设备的SS线。
3. 数据传输:主设备通过MOSI发送数据,同时通过MISO接收数据。
4. 结束通信:主设备拉高(或拉低)SS线,结束传输。

SPI(Serial Peripheral Interface)、SSI(Synchronous Serial Interface)和Microwire是三种常见的同步串行通信协议,它们在帧格式、通信方式和应用场景上有所不同。

8.3 XT25 简介

XT25F02E 含有2Mb串行FLASH,支持标准串行外围接口(SPI),并支持Dual SPI:串行时钟、芯片选择、串行数据I/O0(SI)、I/O1(SO)。Dual I/O数据以160Mbits/s的速度传输。下图为XT25内部方框图。

XT25F02ESOIGU_XTX(芯天下)_XT25F02ESOIGU中文资料_PDF手册_价格-立创商城

存储大小及地址

命令

相关注意事项如图

(图片来自江协科技)

更多详情请看数据手册

XT25F02ESOIGU_XTX(芯天下)_XT25F02ESOIGU中文资料_PDF手册_价格-立创商城

8.4 寄存器

8.4.1 控制寄存器0 SSP0CR0, SSP1CR0

        这里CPOL和CPHA可改变空闲状态和接收时刻,而I2C就是固定的

3:0 DSS

数据长度选择,控制每帧中传输的位的数目,0000~0010无效

0011 ~ 1111分别对应4-16位,相当于DSS的二进制值加一

5:4 FRF

帧格式

00 SPI

01 TI SPI

10 Microwire

11 不适用

6 CPOL

时钟输出极性 只用于SPI模式

0 使帧之间的总线时钟保持电平

1 使帧之间的总线时钟保持电平

7 CPHA

时钟输出相位

0 SSP控制器在帧传输第一次跳变(01 or 10)时捕获串行数据,

即跳变远离时钟线的帧间状态

1 SSP控制器在帧传输第二次跳变时捕获串行数据,即跳变靠近时钟线的帧间状态

15:8 SCR

串行时钟速率

位频率为 PCLK / (CPSDVSR * [SCR + 1]),

CPSDVSR为预分频器分频值

PCLK为APB时钟PCLK计时预分频器 

8.4.2 控制寄存器1 SSP0CR1 SSP1CR1

0 LBM

0 正常操作

1 串行输入脚可做串行输出脚

启用后,发送的数据会直接回环到接收端,用于测试或调试,无需外部连接。在LBM模式下,原本用于接收数据的串行输入脚(如MISO)可以临时用作串行输出脚(类似MOSI),直接输出发送的数据。

1 SSE

SSP使能

0 禁能

1 与总线上的其他设备相互通信,置位前先向其他SSP寄存器和中断控制寄存器写入合适的控制信息

2 MS

主master/从slave模式 SSE为0时才能写入(I/O是输入in/输出out)

0 SSP控制器作为总线主机,驱动SCLK,MOSI和SSEL接收MISO

1 SSP控制器作为总线从机,驱动MISO,接收SLCK、MOSI和SSEL

3 SOD

从模式下有用,从机输出禁能,

1 将禁止MISO,从机就发不出去了

7:4 保留

8.4.3 数据寄存器 SSP0DR、SSP1DR

       16位,可往寄存器写入,可从寄存器读出

        写入:

                状态寄存器 TNF 置1,即Tx FIFO未满时,软件就可以将帧数据写入,满了不能写;

                Tx FIFO 原来为空并且总线上SSP控制器空闲,写了立马发,不然排队等

                小于16位,写入寄存器的数据右对齐

        读出:

                状态寄存器 RNE 置一,即Rx FIFO未空,就能读出数据

                读的时候返回Rx FIFO最早接收到的帧数据

                读出的数据小于16位,高位补零

8.4.4 状态寄存器 SSP0SR、SSP1SR

        只读

E为empty

F为full

描述
0 TFE 1 发送FIFO为空
1 TNF 1 发送FIFO未满
2 RNF 1 接收FIFO未空
3 RFE 1 接收FIFO为空
4 BSY忙

0 空闲

1 发送/接收一个帧或TxFIFO非空

7:5 保留

8.4.5 时钟预分频寄存器 SSP0CPSR SSP1CPSR

        该寄存器对SSP外设时钟SSP_PCLK分频来获得预分频时钟,再被SSPnCR0(8.4.1)中的SCR因数分频得到位时钟

        [7:0] CPSDVSR 为2~256之中的一个偶数,第0位就保持0不变了

              

        实际硬件限制使得SCLK速率不能超过SSP外设时钟的1/12。

8.4.6 中断屏蔽置位/清零寄存器 SSP0IMSC SSP1IMSC

        4个中断条件的使能

0 ROIM

当SPI接收缓冲区已满(即已接收到数据但未被读取),而新的数据又到达时,会发生接收溢出。

使能ROIM中断后,当发生接收溢出时,SPI模块会触发中断,通知程序处理溢出情况。

1 RTIM 接收超时(Rx FIFO非空且在超时周期内没有读出任何数据)后,置位该位会触发接收超时中断
2 RXIM Rx FIFO至少有一半满,产生中断
3 TXIM Tx FIFO至少有一半空,产生中断
7:4 保留

8.4.7 原始中断状态寄存器 SSP0RIS SSP1RIS

        不管SSPnIMSC中的中断是否使能,只要出现有效的中断条件,SSP原始中断状态寄存器就将相应的位置一。

0 RORRIS 当SPI接收缓冲区已满(即已接收到数据但未被读取),而新的数据又到达时,会发生接收溢出。该位置一
1 RTRIS 接收超时
2 RXRIS Rx FIFO至少有一半满
3 TXRIS Tx FIFO至少有一半空
7:4 保留

8.4.8 屏蔽中断状态寄存器 SSP0MIS SSP1MIS

        中断条件触发且中断使能时,相应位置一

0 RORRIS 当SPI接收缓冲区已满(即已接收到数据但未被读取),而新的数据又到达时,会发生接收溢出。该位置一
1 RTRIS 接收超时
2 RXRIS Rx FIFO至少有一半满
3 TXRIS Tx FIFO至少有一半空
7:4 保留

8.4.9 SSP中断清零寄存器 SSP0ICR SSP1ICR

用于清零接收溢出Receive Overrun 和接收超时 Receive Timeout 的中断

另外两个RXIM、TXIM通过读/写 Rx/ Tx FIFO会自动清零

写1清除
0 RORIC 清除ROIM
1 RTRC 清除RTIM
7:2 保留

 8.5 SPI实现读取XT25

        复制UART工程,然后新建 XT25.c、XT25.h、SPI.c、SPI.h

main.c

#include <LPC11xx.h>
#include "LED.h"
#include "Button.h"
#include "TIMER.h"
#include "UART.h"
#include "string.h"
#include "SPI.h"
#include "XT25.h"

int main(void)
{
	LED_Init();
    LED_ON();
    UART_Init();
	SSPI1_Init(); // SPI初始化   
	
    char message[] = "Hello, SPI!";// 定义要发送的数据
    int length = strlen(message);// 计算数据长度,不包括结束符 '\0'
    UART_Send(message, length);	// 调用UART_Send函数发送数据
	UART_Send_Bit(0x0d);	//发送换行
	UART_Send_Bit(0x0a);
	
	// 读取唯一ID
	uint8_t ID[16]; 
	XT25_RUID(ID);
	UART_Send((char *)ID, 16);
	UART_Send_Bit(0x0d);	//发送换行
	UART_Send_Bit(0x0a);
	
	uint8_t data[8] = "01234567"; // 要发的数据
	uint8_t receive[20]; // 接收数据放这里
	

	// 擦除所有数据
	XT25_EraseAll();// 观察手册,擦整个芯片时间为1.7 - 5s, 耐心等一下
	
	long int ADDR = 0x000000; // 要操作的地址
	SPI1_Read_FLASH(ADDR, receive, 8);	// 读取该地址下的数据放到receive
	UART_Send((char *)receive, 8); // 擦除后所有位都为1,十六进制显示每个字节都是FF
	UART_Send_Bit(0x0d); // 发送换行
	UART_Send_Bit(0x0a);
	
	SPI1_Write_FLASH(ADDR, data, 8);    // 发送温度 以转换好的数组的形式发送 到xt25	
	SPI1_Read_FLASH(ADDR, receive, 8);	// 读取该地址下的数据放到receive
	UART_Send((char *)receive, 8);      // 串口发送收到的数据
	UART_Send_Bit(0x0d);//发送换行
	UART_Send_Bit(0x0a);
	
	ADDR = 0x010000; // 第二块扇区
	uint8_t data2[] = "20250217";
	SPI1_Write_FLASH(ADDR, data2, 8);    // 发送温度 以转换好的数组的形式发送 到xt25	
	SPI1_Read_FLASH(ADDR, receive, 8);	// 读取该地址下的数据放到receive
	UART_Send((char *)receive, 8);      // 串口发送收到的数据	
	UART_Send_Bit(0x0d);//发送换行
	UART_Send_Bit(0x0a);

	
	ADDR = 0x000000;
	XT25_EraseSector(ADDR); // 擦除第一块扇区
	SPI1_Read_FLASH(ADDR, receive, 8);
	UART_Send((char *)receive, 8); // 又是八个0xFF
	UART_Send_Bit(0x0d);//发送换行
	UART_Send_Bit(0x0a);
	

	
    while (1)
    {
		

    }
}

XT25.c

#include "XT25.h"


/*
*********************************************************************************************************
*	函 数 名: XT25_WriteEnable()
*	功能说明: 写入使能
*	形    参:无
*	返 回 值: 无
*********************************************************************************************************
*/
void XT25_WriteEnable()
{
	 SSP1_LOW(); // 开始通信
	 SPI1_comunication(WREN);// 写入使能
	 SSP1_HIGH(); // 结束通信 
}

/*
*********************************************************************************************************
*	函 数 名: XT25_ReadSR()
*	功能说明: 读状态寄存器
*	形    参:无
*	返 回 值: sr状态寄存器数据
*********************************************************************************************************
*/
uint8_t XT25_ReadSR()
{
	uint8_t sr;                   // 定义一个变量来存储读取到的状态寄存器数据
    SSP1_LOW();                   // 拉低SPI的片选信号,开始与SPI设备通信
    SPI1_comunication(RDSR);      // 向外设发送读取状态寄存器(RDSR)命令
    sr = SPI1_comunication(0xff); // 发送一个填充字节0xff以读取状态寄存器的数据
    SSP1_HIGH();                  // 拉高SPI的片选信号,结束与SPI设备通信
    return sr;                    // 返回读取到的状态寄存器数据
}


/*
*********************************************************************************************************
*	函 数 名: XT25_Write_Wait()
*	功能说明: 忙碌等待 写入等待
*	形    参:无
*	返 回 值: 无
*********************************************************************************************************
*/
void XT25_Write_Wait()
{
    uint8_t stat_code;
    do {
        stat_code = XT25_ReadSR();
    } while (stat_code & 0x01); // 等待 BUSY 位为 0
}


/*
*********************************************************************************************************
*	函 数 名: XT25_Read_Wait()
*	功能说明: 忙碌等待 读出等待
*	形    参:无
*	返 回 值: 无
*********************************************************************************************************
*/
void XT25_Read_Wait()
{
	 int stat_code=0; // 用于存储读取的状态寄存器值
	 while(1)
	 {
		stat_code=XT25_ReadSR();
		if((stat_code&1)==0)
			 break;
	 }
}

/*
*********************************************************************************************************
*	函 数 名: XT25_EraseAll() 
*	功能说明: 擦除整个芯片 需要等待1.7 - 5 S
*	形    参:无
*	返 回 值: 无
*********************************************************************************************************
*/
void XT25_EraseAll() 
{
	
	XT25_WriteEnable();//写入使能
	SSP1_LOW();
	SPI1_comunication(XT25_CMD_ERASE_ALL);//擦除整个芯片
	SSP1_HIGH();
}

/*
*********************************************************************************************************
*	函 数 名: XT25_RUID(uint8_t *res)   
*	功能说明: 发送读取唯一标识符命令
*	形    参:16位数组头地址
*	返 回 值: 无
*********************************************************************************************************
*/
void XT25_RUID(uint8_t *res) 
{
	XT25_WriteEnable();//写入使能
	SSP1_LOW();
	SPI1_comunication(XT25_CMD_RUID);// 发送读取唯一标识符指令
	SPI1_comunication(0x00);
	SPI1_comunication(0x00);
	SPI1_comunication(0x00);
	uint8_t i;
	for(i = 0; i < 16; i ++)
	{
		res[i] = SPI1_comunication(0xFF);
	}
	
	SSP1_HIGH();

}

/*
*********************************************************************************************************
*	函 数 名: XT25_EraseSector(uint32_t address) 
*	功能说明: 通过 SPI 向闪存芯片发送擦除命令,要求芯片擦除指定的扇区,相应地址分3段发送
			  温度25~85度等待75-1000ms、 -40~25度等待75-2000ms
*	形    参:无
*	返 回 值: 无
*********************************************************************************************************
*/
void XT25_EraseSector(uint32_t address) 
{
	XT25_WriteEnable();//写入使能
	SSP1_LOW();
	SPI1_comunication(SECTOR_ERASE);// 发送擦除扇区指令
	SPI1_comunication(address >> 16); // 发送地址的高8位
	SPI1_comunication(address >> 8);  // 发送地址的中间8位
	SPI1_comunication(address);       // 发送地址的低8位
	SSP1_HIGH();// 拉高CS
}

/*
*********************************************************************************************************
*	函 数 名: XT25_WriteSR(uint8_t sr) 
*	功能说明: 通过 SPI 接口将一个字节(sr)写入闪存芯片的状态寄存器
*	形    参:sr要写入的状态寄存器值
*	返 回 值: 无
*********************************************************************************************************
*/
void XT25_WriteSR(uint8_t sr) 
{
    SSP1_LOW(); // 拉低CS
    SPI1_comunication(WRSR); // 发送写入状态寄存器指令
    SPI1_comunication(sr); // 发送状态寄存器值
    SSP1_HIGH(); // 拉高CS
}

XT25.h

#ifndef _XT25_H_
#define _XT25_H_

#include <LPC11XX.h>
#include "SPI.h"

void XT25_WriteEnable(void);//写入使能
uint8_t XT25_ReadSR(void);//读状态寄存器
void XT25_Write_Wait(void);//忙碌等待 写入等待
void XT25_Read_Wait(void);//忙碌等待 读出等待
void XT25_EraseAll(void);//擦除整个芯片
void XT25_RUID(uint8_t *res);//读取唯一标识符指令
void XT25_EraseSector(uint32_t address);//擦除扇区
void XT25_WriteSR(uint8_t sr);//写入状态寄存器

#define WREN   0X06//写入使能
#define WRDI   0X04//写入禁止
#define RDSR   0X05//读取状态寄存器
#define WRSR   0X01//写入状态寄存器
#define SECTOR_ERASE 0x20
#define XT25_CMD_ERASE_ALL 0xC7
#define XT25_CMD_RUID      0x4B
#define CHIP_ERASE 0xC7 // 擦除整个芯片的指令代码

#endif

SPI.c

#include "SPI.h"
#include <stdio.h>

uint8_t src_addr[16]; //写
uint8_t dest_addr[16];//读

/*Delay 1s函数*/
void Delay_1s(void)
{
	int i=SystemCoreClock/500;//0.01s
	while(--i);
}

/*
*********************************************************************************************************
*	函 数 名: SSP1_IOConfig()
*	功能说明: 配置 MISO、MOSI 和 SCK(时钟)引脚
*	形    参:无
*	返 回 值: 无
*********************************************************************************************************
*/
void SSP1_IOConfig()
{
	LPC_SYSCON->SYSAHBCLKCTRL |=((1<<6)|(1<<16)); //IO 和GPIO
	LPC_IOCON->PIO2_2 &=~(0X07); 
	LPC_IOCON->PIO2_2 |=0X02;// 把PIO2_3选择为MISO
	LPC_IOCON->PIO2_3 &=~(0X07); 
	LPC_IOCON->PIO2_3 |=0X02;//把PIO2_2选择为MOSI
	LPC_IOCON->PIO2_1 &=~(0X07); 
	LPC_IOCON->PIO2_1 |=0X02;//把PIO2_1选择为LPC_SSP   CLK
	LPC_SYSCON -> SYSAHBCLKCTRL &= ~(1 << 16); // 禁能IOCON时钟
	LPC_GPIO2->DIR |= (1<<0);  //输出
	LPC_GPIO2->DATA |= (1<<0); //片选信号 拉高
}

// 开始通信
void SSP1_LOW()
{
	LPC_GPIO2->DATA &= ~(1<<0); //拉低
}
// 结束通信
void SSP1_HIGH()
{
	LPC_GPIO2->DATA |= (1<<0); //拉高
}

/*
*********************************************************************************************************
*	函 数 名: SSPI1_Init()
*	功能说明: 初始化 SSP1 接口,配置其为 SPI 主设备模式,并准备好进行数据传输
*	形    参:无
*	返 回 值: 无
*********************************************************************************************************
*/
void SSPI1_Init()
{
	 uint8_t Dummy=Dummy; //解决编译产生的Warning:never used
	 uint8_t i;
	 LPC_SYSCON->PRESETCTRL |=1<<2; //禁止LPC_ssp1复位
	 LPC_SYSCON->SYSAHBCLKCTRL |=(1<<18); //ssp1 时钟使能
	 LPC_SYSCON->SSP1CLKDIV=0X06 ;  //分频值 6 48/6=8MHz
	 SSP1_IOConfig(); //初始化SSP1 IO口
	 LPC_SSP1->CR0=0X0707;   //0x00000111 00000111 CPSR=7 DATAbit= 8 CPOL=0 CPHA=0 SCR=7  模式0空闲低电平,上升沿采样
	 LPC_SSP1->CPSR=50;   //0x06 偶数就行
	 LPC_SSP1->CR1 &=~(1<<0);//LBM=0 :正常模式
	 LPC_SSP1->CR1 &=~(1<<2);//MS=0 主机模式
	 LPC_SSP1->CR1 |=1<<1; //SSE=1 开启ssp1
	 //清空RXFIFO LPC1114收发均有8帧FIFO,每帧可放置4~16位数据
	 for(i=0 ; i < 8 ; i ++)
	 {
		Dummy=LPC_SSP1->DR; // 读取寄存器DR将清空RxFIFO
	 }
	 for(i=0;i<16;i++)
	 {
		dest_addr[i]=0;
		src_addr[i]=0;
	 }
}

/*
*********************************************************************************************************
*	函 数 名: SPI1_comunication(uint8_t TxData)
*	功能说明: 通过 SPI 协议发送并接收数据
*	形    参:TxData发送数据
*	返 回 值: 从SPI接收到的数据
*********************************************************************************************************
*/
uint8_t SPI1_comunication(uint8_t TxData)
{
	 while(((LPC_SSP1->SR)&(1<<4))==(1<<4));//忙时等待
	 LPC_SSP1->DR=TxData; //发送数据到TxFIFO
	 while(((LPC_SSP1->SR)&(1<<2))!=(1<<2));//接收非空就接收,等待数据接收完
	 return (LPC_SSP1->DR); //接收返回的数据
}


/*
*********************************************************************************************************
*	函 数 名: SSP1_Send(uint8_t *data,uint8_t Length)	
*	功能说明: 逐字节地发送一个数据数组
*	形    参:*data传入的要发送的数据数组的起始地址 Length需要发送的数据长度
*	返 回 值: 无
*********************************************************************************************************
*/
void SSP1_Send(uint8_t *data,uint8_t Length)	
{
	 uint8_t i;
	 for(i=0;i<Length;i++)
	{
		SPI1_comunication(data[i]);
	}
}


/*
*********************************************************************************************************
*	函 数 名: SSP1_Receive(uint8_t *data,int Length)
*	功能说明: 逐字节接收数据数组
*	形    参:*data接收数据的目标数组 Length要接收的数据长度
*	返 回 值: 无
*********************************************************************************************************
*/
void SSP1_Receive(uint8_t *data,int Length)
{
	 uint8_t Dummy=Dummy; //随机数 用于产生时钟
	 uint8_t i;
	 for(i=0 ; i<Length ;i++)
	{
		data[i]=SPI1_comunication(0xff);
	}
}


/*
*********************************************************************************************************
*	函 数 名: SPI1_Write_FLASH(uint8_t *data, uint8_t Length)
*	功能说明: 写flash
*	形    参:*data要写的数据, Length数据长度
*	返 回 值: 无
*********************************************************************************************************
*/
void SPI1_Write_FLASH(uint32_t ADDR, uint8_t *data, uint8_t Length)
{
	 uint8_t i;

	 XT25_WriteEnable();//写入使能

	 src_addr[0]=WRITE;//页面编程开始
	 //地址1~3
	 src_addr[1]=(uint8_t)((ADDR)>>16);
	 src_addr[2]=(uint8_t)((ADDR)>>8);
	 src_addr[3]=(uint8_t) ADDR;
	 //写入的数据
	 for(i=0;i<Length;i++)
	{
		src_addr[i+4]=data[i];
	}
	 XT25_Write_Wait(); //忙时等待
	 SSP1_LOW();//使能
	 SSP1_Send((uint8_t *)src_addr,4+Length);
	 SSP1_HIGH();//拉高

}

/*
*********************************************************************************************************
*	函 数 名: SPI1_Read_FLASH(uint8_t *data,uint8_t Length)
*	功能说明: 读flash
*	形    参:*data存储读的数据, Length数据长度
*	返 回 值: 无
*********************************************************************************************************
*/
void SPI1_Read_FLASH(uint32_t ADDR, uint8_t *data,uint8_t Length)
{
	int i;
	src_addr[0]=READ;
	//读取的地址
	src_addr[1]=(uint8_t)((ADDR)>>16);
	src_addr[2]=(uint8_t)((ADDR)>>8);
	src_addr[3]=(uint8_t) ADDR;
	XT25_Read_Wait();
	SSP1_LOW();
	SSP1_Send((uint8_t *)src_addr,4);
	SSP1_Receive((uint8_t *)dest_addr,Length);// 接收数据
	SSP1_HIGH();
	for(i=0;i<Length;i++) 
		data[i]=dest_addr[i];
}

SPI.h

#ifndef _SPI_H_
#define _SPI_H_

#include <LPC11XX.h>
#include "XT25.h"

//SSP1
void SSP1_IOConfig(void);//函数功能:SSP1IO初始化
void SSP1_LOW(void);  // 拉低
void SSP1_HIGH(void); // 拉高
void SSPI1_Init(void);//函数功能:SSP1初始化
uint8_t SPI1_comunication(uint8_t TxData);//SSP1通信接受和发送一个字符
void SSP1_Send(uint8_t *data,uint8_t Length);//SSP1发送
void SSP1_Receive(uint8_t *data,int Length);//SSP1接收
void SPI1_Write_FLASH(uint32_t ADDR, uint8_t *data, uint8_t Length);//写FLASH
void SPI1_Read_FLASH(uint32_t ADDR, uint8_t *data,uint8_t Length);//读FLASH
void Delay_1s(void);
#define READ   0X03
#define WRITE  0X02


#endif

串口显示

十六进制显示,选中的为唯一编号

正常显示

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐