CKS32F4xx系列产品SPI通信

描述

SPI协议是由摩托罗拉公司提出的通讯协议(Serial Peripheral Interface),即串行外围设备接口,是一种高速全双工的通信总线。它在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB在布局上节省了空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信协议,它被广泛地使用在ADC、LCD、FLASH等设备与MCU之间的通信。

 

CKS32F4xx系列产品SPI介绍

 

CKS32F4xx系列的SPI外设可用作通讯的主机及从机,支持最高的SCK时钟频率为fpclk/2(CKS32F407型号的芯片默认fpclk为142MHz,fpclk2为84MHz),完全支持SPI协议的4种模式。SPI协议根据CPOL及CPHA的不同状态分成的四种工作模式如下表所示:

通讯协议

CKS32F4xx系列的SPI架构如下图所示:

通讯协议

图中的1处是SPI的引脚MOSI、MISO、SCK、NSS。CKS32F4xx芯片有多个 SPI外设,它们的SPI通讯信号引出到不同GPIO引脚上,使用时必须配置到这些指定的引脚。关于GPIO引脚的复用功能可以查阅芯片数据手册。各个引脚的作用介绍如下:

(1)NSS:从设备选择信号线,常称为片选信号线。当有多个SPI从设备与 SPI主机相连时,设备的其它信号线SCK、MOSI及MISO同时并联到相同的SPI 总线上,即无论有多少个从设备,都共同只使用这3条总线;而每个从设备都有独立的一条NSS信号线,当主机要选择从设备时,把该从设备的NSS信号线设置为低电平,该从设备即被选中,即片选有效,接着主机开始与被选中的从设备进行SPI通讯。所以SPI通讯以NSS线置低电平为开始信号,以NSS线被拉高作为结束信号。

(2)SCK:时钟信号线,用于通讯数据同步。它由通讯主机产生,决定了通讯的速率,不同的设备支持的最高时钟频率不一样,两个设备之间通讯时,通讯速率受限于低速设备。

(3)MOSI:主设备输出/从设备输入引脚。主机的数据从这条信号线输出,从机由这条信号线读入主机发送的数据,即这条线上数据的方向为主机到从机。

(4)MISO:主设备输入/从设备输出引脚。主机从这条信号线读入数据,从机的数据由这条信号线输出到主机,即在这条线上数据的方向为从机到主机。

图中的2处是SCK线的时钟信号,由波特率发生器根据“控制寄存器CR1”中的BR[0:2]位控制,该位是对fpclk时钟的分频因子,对fpclk的分频结果就是SCK引脚的输出时钟频率。

图中的3处是SPI的数据控制逻辑。SPI的MOSI及MISO都连接到数据移位寄存器上,数据移位寄存器的内容来源于接收缓冲区及发送缓冲区以及MISO、MOSI线。当向外发送数据的时候,数据移位寄存器以“发送缓冲区”为数据源,把数据一位一位地通过数据线发送出去;当从外部接收数据的时候,数据移位寄存器把数据线采样到的数据一位一位地存储到“接收缓冲区”中。通过写SPI 的“数据寄存器DR”把数据填充到发送缓冲区中,通过“数据寄存器DR”,可以获取接收缓冲区中的内容。其中数据帧的长度可以通过“控制寄存器CR1”的“DFF位”配置成8位及16位模式;配置“LSBFIRST位”可选择MSB先行还是 LSB先行。

图中的4处是SPI的整体控制逻辑。整体控制逻辑负责协调整个SPI外设,控制逻辑的工作模式根据我们配置的“控制寄存器(CR1/CR2)”的参数而改变,基本的控制参数包括SPI模式、波特率、LSB先行、主从模式、单双向模式等等。在外设工作时,控制逻辑会根据外设的工作状态修改“状态寄存器(SR)”,我们只要读取状态寄存器相关的寄存器位,就可以了解SPI的工作状态了。除此之外,控制逻辑还根据要求,负责控制产生SPI中断信号、DMA请求及控制NSS信号线。实际应用中,我们一般不使用CKS32 SPI外设的标准NSS信号线,而是更简单地使用普通的GPIO,软件控制它的电平输出,从而产生通讯起始和停止信号。

CKS32F4xx系列的SPI作为通讯主机端时收发数据的过程如下:

(1) 控制NSS信号线,产生起始信号;

(2) 把要发送的数据写入到“数据寄存器DR”中,该数据会被存储到发送缓冲区;

(3) 通讯开始,SCK时钟开始运行。MOSI把发送缓冲区中的数据一位一位地传输出去;MISO则把数据一位一位地存储进接收缓冲区中;

(4) 当发送完一帧数据的时候,“状态寄存器SR”中的“TXE标志位”会被置1,表示传输完一帧,发送缓冲区已空;类似地,当接收完一帧数据的时候,“RXNE标志位”会被置1,表示传输完一帧,接收缓冲区非空;

(5) 等待到“TXE标志位”为1时,若还要继续发送数据,则再次往“数据寄存器DR”写入数据即可;等待到“RXNE标志位”为1时,通过读取“数据寄存器DR”可以获取接收缓冲区中的内容。

假如我们使能了TXE或RXNE中断,TXE或RXNE置1时会产生SPI中断信号,进入同一个中断服务函数,到SPI中断服务程序后,可通过检查寄存器位来了解是哪一个事件,再分别进行处理。也可以使用DMA方式来收发“数据寄存器 DR”中的数据。

 

CKS32F4xx系列产品SPI的配置

接下来我们讲解如何利用CKS32F4xx系列固件库来完成对SPI的配置使用。跟其它外设一样,CKS32标准库提供了SPI初始化结构体及初始化函数来配置 SPI外设。了解初始化结构体后我们就能对SPI外设运用自如了,代码如下:

 

typedef struct
{
  uint16_t SPI_Direction;          
  uint16_t SPI_Mode;              
  uint16_t SPI_DataSize;          
  uint16_t SPI_CPOL;             
  uint16_t SPI_CPHA;               
  uint16_t SPI_NSS;               
  uint16_t SPI_BaudRatePrescaler;  
  uint16_t SPI_FirstBit;           
  uint16_t SPI_CRCPolynomial;       
}SPI_InitTypeDef;

 

结构体中各个成员变量的介绍及初始化时可被赋的值如下:

1) SPI_Direction:本成员设置SPI的通讯方向,可设置为双线全双工 (SPI_Direction_2Lines_FullDuplex),双线只接收 (SPI_Direction_2Lines_RxOnly),单线只接收(SPI_Direction_1Line_Rx)、单线只发送模式(SPI_Direction_1Line_Tx)。

2) SPI_Mode:本成员设置SPI工作在主机模式(SPI_Mode_Master)或从机模式(SPI_Mode_Slave ),这两个模式的最大区别为SPI的SCK信号线的时序,SCK的时序是由通讯中的主机产生的。若被配置为从机模式,CKS32的SPI外设将接受外来的SCK信号:

3) SPI_DataSize: 本成员可以选择SPI通讯的数据帧大小是为8位 (SPI_DataSize_8b)还是16位(SPI_DataSize_16b)。

4) SPI_CPOL和SPI_CPHA: 这两个成员配置SPI的时钟极性CPOL和时钟相位CPHA,前面讲过这两个配置影响到SPI的通讯模式。时钟极性CPOL成员可设置为高电平(SPI_CPOL_High)或低电平(SPI_CPOL_Low )。时钟相位CPHA则 可以设置为SPI_CPHA_1Edge(在SCK的奇数边沿采集数据)或 SPI_CPHA_2Edge(在SCK的偶数边沿采集数据)。

5) SPI_NSS: 本成员配置NSS引脚的使用模式,可以选择为硬件模式 (SPI_NSS_Hard )与软件模式(SPI_NSS_Soft ),在硬件模式中的SPI片选信号由 SPI硬件自动产生,而软件模式则需要我们自己把相应的GPIO端口拉高或置低产生非片选和片选信号。实际中软件模式应用比较多。

6) SPI_BaudRatePrescaler: 本成员设置波特率分频因子,分频后的时钟即为SPI的SCK信号线的时钟频率。这个成员参数可设置为fpclk的2、4、6、8、16、32、64、128、256分频。可选的值如下所示:

 

SPI_BaudRatePrescaler_2    //2分频
SPI_BaudRatePrescaler_4    //4分频
SPI_BaudRatePrescaler_6    //6分频
SPI_BaudRatePrescaler_8    //8分频
SPI_BaudRatePrescaler_16   //16分频
SPI_BaudRatePrescaler_32   //32分频
SPI_BaudRatePrescaler_64   //64分频
SPI_BaudRatePrescaler_128  //128分频
SPI_BaudRatePrescaler_256  //256分频

 

7) SPI_FirstBit: 所有串行的通讯协议都会有MSB先行(高位数据在前)还是 LSB先行(低位数据在前)的问题,而CKS32F4xx系列的SPI模块可以通过这个结构体成员,对这个特性编程控制。

 

SPI_FirstBit_MSB    //高位数据在前
SPI_FirstBit_LSB     //低位数据在前

 

8) SPI_CRCPolynomial: 这是SPI的CRC校验中的多项式,若我们使用CRC 校验时,就使用这个成员的参数(多项式),来计算CRC的值。

配置完这些结构体成员的值,调用库函数SPI_Init即可把结构体的配置写入到寄存器中。

 

 

CKS32F4xx读写SPI FLASH实验

串口的DMA接发通信实验是存储器到外设和外设到存储器的数据传输。在第24

本小节以一种使用SPI通讯的串行FLASH存储芯片的读写实验为大家讲解 CKS32F4xx系列的SPI使用方法。实验中的FLASH芯片(型号:W25Q32)是一种使用SPI通讯协议的NORFLASH存储器,它的CS/CLK/DIO/DO引脚分别连接到了CKS32F4xx对应的SPI引脚NSS/SCK/MOSI/MISO上,其中CKS32F4xx的NSS引脚是一个普通的GPIO,不是SPI的专用NSS引脚,所以程序中我们要使用软件控制的方式。

1.编程要点

(1) 初始化通讯使用的目标引脚及端口时钟;  

(2) 使能SPI外设的时钟;

(3) 配置SPI外设的模式、地址、速率等参数并使能SPI外设;

(4) 编写基本SPI按字节收发的函数; 

(5) 编写对FLASH擦除及读写操作的的函数; 

(6) 编写测试程序,对读写数据进行校验。 

2.代码分析

代码清单1:W25Q32初始化配置

 

void W25QXX_Init(void)
{ 
  GPIO_InitTypeDef  GPIO_InitStructure;
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE); 
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG, ENABLE); 
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; 
  GPIO_Init(GPIOD, &GPIO_InitStructure); 
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
  GPIO_Init(GPIOG, &GPIO_InitStructure);
GPIO_SetBits(GPIOG,GPIO_Pin_3); 
  W25QXX_CS=1;                        //SPI FLASH不选中
  SPI1_Init();                               //初始化SPI
  SPI1_SetSpeed(SPI_BaudRatePrescaler_4);    //设置为21M时钟
  W25QXX_TYPE=W25QXX_ReadID();         //读取FLASH ID.
}  

 

上面的代码主要是完成对W25Q32片选引脚的初始化,SPI初始化。SPI通信速率设置和读取W25Q32的ID。

代码清单2:SPI初始化函数

 

void SPI1_Init(void)
{   
  GPIO_InitTypeDef  GPIO_InitStructure;
  SPI_InitTypeDef  SPI_InitStructure;
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; 
  GPIO_Init(GPIOB, &GPIO_InitStructure); 
GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1);
GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1); 
GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_SPI1); 
  RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,ENABLE);
  RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,DISABLE);
  SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; 
  SPI_InitStructure.SPI_Mode = SPI_Mode_Master;    
  SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;  
  SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;    
  SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; 
  SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;    
  SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;  
  SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;  
  SPI_InitStructure.SPI_CRCPolynomial = 7;  
  SPI_Init(SPI1, &SPI_InitStructure);  
  SPI_Cmd(SPI1, ENABLE); 
  SPI1_ReadWriteByte(0xff);   
}   

 

上面这段代码主要是完成对SPI1的初始化,首先是配置了SPI1使用的引脚SPI1_SCK、SPI1_MOSI和SPI1_MISO。然后是根据第2小节的内容完成对SPI1外设模式的配置。根据FLASH芯片W25Q32的说明,它支持SPI模式0及模式3,支持双线全双工,使用MSB先行模式,支持最高通讯时钟为104MHz,数据帧长度为8位。我们要把CKS32F4的SPI外设中的这些参数配置一致。

代码清单3:SPI1单字节收发函数

 

u8 SPI1_ReadWriteByte(u8 TxData)
{            
  while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET){} 
  SPI_I2S_SendData(SPI1, TxData); 
  while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET){} 
  return SPI_I2S_ReceiveData(SPI1); 
}

 

本函数中不包含SPI起始和停止信号,只是收发的主要过程,所以在调用本函数前后要做好起始和停止信号的操作。通过检测TXE标志,获取发送缓冲区的状态,若发送缓冲区为空,则表示可能存在的上一个数据已经发送完毕;等待至发送缓冲区为空后,调用库函数SPI_I2S_SendData把要发送的数据“TxData”写入到SPI的数据寄存器DR,写入SPI数据寄存器的数据会存储到发送缓冲区,由SPI外设发送出去;写入完毕后等待RXNE事件,即接收缓冲区非空事件。由于SPI双线全双工模式下MOSI与MISO数据传输是同步的,当接收缓冲区非空时,表示上面的数据发送完毕,且接收缓冲区也收到新的数据;等待至接收缓冲区非空时,通过调用库函数SPI_I2S_ReceiveData读取SPI的数据寄存器DR,就可以获取接收缓冲区中的新数据了。代码中使用关键字“return”把接收到的这个数据作为SPI1_ReadWriteByte函数的返回值。

搞定了SPI的基本收发单元后,还需要了解如何对FLASH芯片进行读写。FLASH 芯片自定义了很多指令,我们通过控制CKS32F4利用SPI总线向FLASH 芯片发送指令,FLASH芯片收到后就会执行相应的操作。具体的指令代码可以查看W25Q32芯片的数据手册。

代码清单4:读取FLASH芯片ID函数

 

u16 W25QXX_ReadID(void)
{
  u16 Temp = 0;    
  W25QXX_CS=0;            
  SPI1_ReadWriteByte(0x90);      
  SPI1_ReadWriteByte(0x00);       
  SPI1_ReadWriteByte(0x00);       
  SPI1_ReadWriteByte(0x00);             
  Temp|=SPI1_ReadWriteByte(0xFF)<<8;  
  Temp|=SPI1_ReadWriteByte(0xFF);   
  W25QXX_CS=1;            
  return Temp;
}     

 

这段代码利用控制CS引脚电平的宏“W25QXX_CS”以及前面编写的单字节收发函数SPI1_ReadWriteByte,很清晰地实现了读ID指令的时序,最后把读 取到的这3个数据合并到一个变量Temp中,然后作为函数返回值,把该返回值与我们定义的芯片ID对比,即可知道FLASH芯片是否正常。

代码清单5:W25Q32写使能和写禁止函数

 

void W25QXX_Write_Enable(void)   
{
  W25QXX_CS=0;                              
  SPI1_ReadWriteByte(W25X_WriteEnable);     
  W25QXX_CS=1;                                    
}
void W25QXX_Write_Disable(void)   
{  
  W25QXX_CS=0;                            
  SPI1_ReadWriteByte(W25X_WriteDisable);        
  W25QXX_CS=1;                                      
}   

 

由于FLASH存储器的特性决定了它只能把原来为“1”的数据位改写成“0”,而原来为“0”的数据位不能直接改写为“1”。所以在写入前,必须要对目标存储矩阵进行擦除操作,把矩阵中的数据位擦除为“1”,在数据写入的时候,如果要存储数据“1”, 那就不修改存储矩阵,在要存储数据“0”时,需要更改该位。W25Q32支持“扇区擦除”、“块擦除”以及“整片擦除”。 扇区擦除指令的第一个字节为指令编码,紧接着发送的3个字节用于表示要擦除的存储矩阵地址。要注意的是在扇区擦除指令前,还需要先发送“写使能”指令,发送扇区擦除指令后,通过读取寄存器状态等待扇区擦除操作完毕。

代码清单6:W25Q32扇区擦除函数

 

void W25QXX_Erase_Sector(u32 Dst_Addr)   
{      
   Dst_Addr*=4096;
    W25QXX_Write_Enable();                    
    W25QXX_Wait_Busy();   
    W25QXX_CS=0;                              
    SPI1_ReadWriteByte(W25X_SectorErase);      
    SPI1_ReadWriteByte((u8)((Dst_Addr)>>16));     
    SPI1_ReadWriteByte((u8)((Dst_Addr)>>8));   
    SPI1_ReadWriteByte((u8)Dst_Addr);  
  W25QXX_CS=1;                                   
    W25QXX_Wait_Busy();             
}  

 

目标扇区被擦除完毕后,就可以向它写入数据了。与EEPROM类似,FLASH芯片也有页写入命令,使用页写入命令最多可以一次向FLASH传输256个字节的数据,我们把这个单位称为页大小。在进行页写入时第1个字节为“页写入指令”编码,2-4字节为要写入的“地址A”,接着的是要写入的内容,最多可以发送 256字节数据,这些数据将会从“地址A”开始,按顺序写入到FLASH的存储矩阵。若发送的数据超出256个,则会覆盖前面发送的数据。

代码清单7:W25Q32页写入函数

 

void W25QXX_Write_Page(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
{
   u16 i;  
    W25QXX_Write_Enable();                 
  W25QXX_CS=0;                               
    SPI1_ReadWriteByte(W25X_PageProgram);         
    SPI1_ReadWriteByte((u8)((WriteAddr)>>16));   
    SPI1_ReadWriteByte((u8)((WriteAddr)>>8));   
    SPI1_ReadWriteByte((u8)WriteAddr);   
    for(i=0;i

 

应用的时候我们常常要写入不定量的数据,直接调用“页写入”函数并不是特别方便,所以我们页写入函数的基础上编写了“不定量数据写入”的函数。在实际调用这个“不定量数据写入”函数时,还要注意确保目标扇区处于擦除状态

代码清单8:W25Q32不定量数据写入函数

 

void W25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)   
{ 
  u32 secpos;
  u16 secoff;
  u16 secremain;     
   u16 i;    
  u8 * W25QXX_BUF;    
     W25QXX_BUF=W25QXX_BUFFER;       
   secpos=WriteAddr/4096; 
  secoff=WriteAddr%4096; 
  secremain=4096-secoff;  
   if(NumByteToWrite<=secremain)secremain=NumByteToWrite;
  while(1) 
  {  
    W25QXX_Read(W25QXX_BUF,secpos*4096,4096); 
    for(i=0;i4096)secremain=4096;  
      else secremain=NumByteToWrite;      
    }   
  };   
}

 

函数的入口参数pBuffer是数据存储区、WriteAd是开始写入的地址(24bit)、NumByteToWrite是要写入的字节数(最大65535)gaojp。

相对于写入,FLASH芯片W25Q32的数据读取要简单的多,发送了指令编码及要读的起始地址和要读取的字节数之后,FLASH 芯片W25Q32就会按地址递增的方式返回存储矩阵中一定字节数量的数据。

代码清单9:W25Q32读取数据函数

 

void W25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead)   
{ 
   u16 i;                           
  W25QXX_CS=0;                              
    SPI1_ReadWriteByte(W25X_ReadData);          
    SPI1_ReadWriteByte((u8)((ReadAddr)>>16));     
    SPI1_ReadWriteByte((u8)((ReadAddr)>>8));   
    SPI1_ReadWriteByte((u8)ReadAddr);   
    for(i=0;i

 

函数的入口参数pBuffer是数据存储区、ReadAddr是开始读取的地址(24bit)、NumByteToRead是要读取的字节数(最大65535)。

完成基本的读写函数后,接下来我们编写一个读写测试函数来检验驱动程。

代码清单10:W25Q32读写测试函数

 

uint8_t w25q32_Test(void)
{
  u16 i;
   printf("写入的数据:
");
  for ( i=0; i<=10; i++ ) 
  {   
    spi_Buf_Write[i] = i;
    printf("0x%02X ", spi_Buf_Write[i]);
   }
   W25QXX_Write((u8*)spi_Buf_Write,FLASH_SIZE-100,11);     
     printf("写成功,");
     printf("读出的数据:
");
    W25QXX_Read(datatemp,FLASH_SIZE-100,11);          
  for (i=0; i<11; i++)
  {  
    if(datatemp[i] != spi_Buf_Write[i])
    {
      printf("0x%02X ", datatemp[i]);
      printf("错误:I2C EEPROM写入与读出的数据不一致");
      return 0;
    }
    printf("0x%02X ", datatemp[i]);
  }
  printf("
");
    printf("spi(w25q32)读写测试成功");
  return 1;
}

 

代码中先填充一个数组,数组的内容为0,1至10,接着把这个数组的内容写入到SPI FLASH中,并将写入的数据打印输出到串口调试助手。写入完毕后再从SPI FLASH的地址中读取数据,把读取到的数据与写入的数据进行校验,若一致说明读写正常,否则读写过程有问题或者SPI FLASH芯片不正常,然后再将读取到的数据打印输出到串口调试助手。

代码清单11:主函数

 

int main(void)
{ 
    u16 id = 0;  
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
  delay_init(168);    
   USART_Configuration();
  W25QXX_Init();      
  while(1)
  {
    id = W25QXX_ReadID();
    if (id == W25Q32 || id == NM25Q32)
      break;
      printf("W25Q32 init failed
");
    delay_ms(500);
    delay_ms(500);
  }
   printf("W25Q32 init success
");
   w25q32_Test();
  while(1)
  {
      
  }       
}

 

主函数代码比较简单,主要是完成串口初始化和W25Q32的初始化,初始化完成之后会执行W25QXX_ReadID函数,读取W25Q32的ID,同时对ID进行判断,并将结果通过串口调试助手打印输出。然后会执行一次W25Q32测试函数,并将一些测试结果通过串口调试助手打印输出。

审核编辑:汤梓红

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分