通信

前提

Mini_F0140原理图上I2C连接着24C02:

1.png

一.IIC介绍

I2C(Inter-Integrated Circuit)是一种通用的总线协议,实现I2C需要两根信号线完成信息交换,SCL时钟作为信号线,SDA作为数据输入/输出线。I2C属于同步通信,由于输入输出数据均使用一根线,因此通信方向为半双工。

主要特征有:

1. I2C 总线协议转换器/并行总线

2. 半双工同步操作

3. 支持主从模式

4. 支持 7 位和 10 位地址格式

5. 支持起始(START)、停止(STOP)、重新起始(RESTART)以及应答(ACK)信号的生成和检测

6. 支持标准模式(最大 100Kbps)、快速模式(最大 400Kbps)

7. 分别有 2 字节的发送和接收缓冲

8. 支持过滤毛刺功能

9. 支持 DMA 操作

10. 支持中断和查询操作

11. 支持多个从地址(详细见用户手册)

下图为信号变化及其特定含义:

2.png

当主发送器如下图所示传输数据时,从接收器在接收到的每个字节后产生一个 ACK 来响应主发送器。

3.png

二.24C02

24C02是低工作电压的2K位串行电可擦除只读存储器,内部组织为256个字节,每个字节8位,该芯片被广泛应用于低电压及低功耗的工商业领域。

主器件通过发送一个起始信号启动发送过程,然后发送它所要寻址的从器件的地址。8位从器件地址的高4位固定为1010。接下来的3位(A2、A1、A0)为器件的地址位。当接收数据时,与I2C连接的从器件名为0xA0。

三.例程

向从机写数据后,再读取数据。

1. I2C配置

RCC_APB1PeriphClockCmd(RCC_APB1ENR_I2C1, ENABLE);
I2C_StructInit(&I2C_InitStruct);    
//Configure I2C as master mode    
I2C_InitStruct.Mode = I2C_CR_MASTER;    
I2C_InitStruct.OwnAddress = 0;    
I2C_InitStruct.Speed = I2C_CR_STD;    
I2C_InitStruct.ClockSpeed = 100000;    
I2C_Init(I2Cx, &I2C_InitStruct);    
I2C_Send7bitAddress(I2Cx, 0xA0, I2C_Direction_Transmitter);    
I2C_Cmd(I2Cx, ENABLE);

2. 定义24C02结构体

typedef struct {
    u8 busy;
    u8 ack;
    u8 fault;
    u8 opt;
    u8 sub;
    u8 cnt;
    u8* ptr;
    u8 sadd;
} gEepromTypeDef;

3. 写数据

EEPROM_WriteByte(sub);
while (cnt --) {
        EEPROM_WriteByte(*ptr);
        ptr++;
}
I2C_GenerateSTOP(I2C1, ENABLE);
while((I2C_GetITStatus(I2C1, I2C_IT_STOP_DET)) == 0);    
gEeprom.ack = true;    
gEeprom.busy = false;    
EEPROM_WaitEEready();    
return true;

4. 读数据

u8 i, flag = 0, _cnt = 0;
for (i = 0; i < gEeprom.cnt; i++) {
    while(1) {
        //Write command is sent when RX FIFO is not full
        if ((I2C_GetFlagStatus(I2C1, I2C_STATUS_FLAG_TFNF)) && (flag == 0)) {
            I2C_ReadCmd(I2C1);
            _cnt++;
            if (_cnt == gEeprom.cnt)
                flag = 1;
        }
        //Check receive FIFO not empty
        if (I2C_GetFlagStatus(I2C1, I2C_STATUS_FLAG_RFNE)) {
            gEeprom.ptr[i] = I2C_ReceiveData(I2C1);
            break;
        }
    }
}

5. 结果

4.png

来源:灵动MM32MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 21

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的不同状态分成的四种工作模式如下表所示:

1.jpg

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

2.png

图中的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<NumByteToWrite;i++)SPI1_ReadWriteByte(pBuffer[i]);   
    W25QXX_CS=1;                              
    W25QXX_Wait_Busy();            
}

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

代码清单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;i<secremain;i++)     
        {      
            if(W25QXX_BUF[secoff+i]!=0XFF)break;         
        }    
        if(i<secremain)     
        {      
            W25QXX_Erase_Sector(secpos);       
            for(i=0;i<secremain;i++)         
            {    
                W25QXX_BUF[i+secoff]=pBuffer[i];          
            }      
            W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);     
        }else W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);     
        if(NumByteToWrite==secremain)break;     
        else     
        {      
            secpos++;      
            secoff=0;            
            pBuffer+=secremain;        
            WriteAddr+=secremain;              
            NumByteToWrite-=secremain;              
            if(NumByteToWrite>4096)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<NumByteToRead;i++)  
    {         
        pBuffer[i]=SPI1_ReadWriteByte(0XFF);   //循环读数      
    }  
    W25QXX_CS=1;                      
}

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

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

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

uint8_t w25q32_Test(void)
{  
    u16 i;   
    printf("写入的数据:\r\n");  
    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("读出的数据:\r\n");    
    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("\r\n");    
    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\r\n");    
        delay_ms(500);    
        delay_ms(500);  
    }   
    printf("W25Q32 init success\r\n");   
    w25q32_Test(); 
     while(1)  
     {   
     }       
 }

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

来源:中科芯MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 29

概述

使用TCP执行通讯时,会有Server与Client的差别。Server是经由开启特定的Port来等待来自Client端的连线需求;而Client端则是向Server端提出连线需求,此例程使用LwIP作为协议栈,可以使用tcp_bind(), tcp_listen()与tcp_accept()这三种系统呼叫来使Server端接受Client的连线要求。

本使用指南会分别说明TCP client与TCP server该如何与PC端通信。

硬件资源

1) AT-START-F407 V1.0实验板
2) DM9162以太网模块
3) 以太网线

软件资源

tcp_client, TCP client源程序,运行TCP客户端程序

tcp_server, TCP server源程序,运行TCP服务端程序

AT32 TCP client/server程序设置

管脚配置

表1. 管脚配置

“AT32

LwIP设置

硬件资源只提供从PHY到MAC的信号处理,若要进行开发,则需要实作TCP/IP协议栈,在本应用中使用LwIP协议栈,该协议栈主要关注的是怎样减少减少内存的使用和程序代码的大小,这样就可以让LwIP适用于资源有限的小型平台例如嵌入式系统,更详细的内容可以访问官方网站。

由于整个协议栈已经整合到代码中,大部分的内容都无须修改,使用者只要根据自己的网段去设定IP地址及闸口地址即可,这两个全局变量宣告在netconf.c的上头。

“图1.
图1. 配置芯片的IP地址

TCP client project设置

在初始化LwIP协议栈之后,就可以配置TCP client的应用了,芯片作为客户端是发出请求的一方,所以必须要配置往哪里发送数据,也就是说要知道服务端的socket。所谓的socket就是IP地址加上端口,在代码中我们宣告了几个宏定义代表服务端的socket,可以在tcp_client.h中找到TCP_SERVER_IP跟TCP_SERVER_PORT这两个宏定义,用户可以根据网段跟应用修改这两个宏。

“图2.
图2. 配置服务端的socket

确认网段与服务端一致后,下载代码到芯片上,服务器端会一秒打印一次”tcp client experiment!”,同时LED2, 3, 4也会跟着闪烁。

上位机服务端设置

1) 设定上位机的IP地址、网路屏蔽及闸口,IP地址与闸口需要跟芯片设置在同一个网段下。

2) 打开上位机端的服务器软件,这里使用网路调适助手为例。协议类型选择TCP server,本地IP地址为代码中的TCP_SERVER_IP,本地端口号为代码中的TCP_SERVER_PORT,按下连线即建立连线。

3) 连线建立后,会出现以下画面,不断打印来自TCP client的数据。

“图3.
图3. 设置PC端的IP、网路屏蔽与闸口

“图4.
图4. 设置服务器软件

“图5.
图5. 接收TCP client的数据

TCP server project设置

在初始化LwIP协议栈之后,就可以配置TCP server的应用了,芯片作为客户端是接受请求的一方,所以必须要配置在哪里接受数据,也就是说要开个端口给客户端丢数据进来。在代码中我们宣告了一个宏定义代表服务端的端口,可以在tcp_server.h中找到TCP_LOCAL_PORT这个宏定义,用户可以根据应用修改这个宏。

“图6.
图6. 服务器端口的宏定义

上位机客户端设置

1) 设定上位机的IP地址、网路屏蔽及闸口,IP地址与闸口需要跟芯片设置在同一个网段下,请参考图3。

2) 打开上位机端的服务器软件,这里使用网路调适助手为例。协议类型选择TCP client,服务器IP地址为芯片的IP地址,本地端口号为代码中的TCP_LOCAL_PORT,按下连线即建立连线。

3) 此时会出现讯息在视窗中,可以透过调试助手下方的区块输入字符串发送给服务器,服务器会响应输入的字符串到视窗中。

“图7.
图7. 设置上位机的客户端软件

图8.
图8. 显示讯息到视窗中并响应发送的字符串

关于雅特力

雅特力科技于2016年成立,是一家致力于推动全球市场32位微控制器(MCU)创新趋势的芯片设计公司,专注于ARM ®Cortex®-M4/M0+的32位微控制器研发与创新,全系列采用55nm先进工艺及ARM® Cortex®-M4高效能或M0+低功耗内核,缔造M4业界最高主频288MHz运算效能,并支持工业级别芯片工作温度范围(-40°~105°)。
雅特力目前已累积相当多元的终端产品成功案例:如微型打印机、扫地机、光流无人机、热成像仪、激光雷达、工业缝纫机、伺服驱控、电竞周边市场、断路器、ADAS、T-BOX、数字电源、电动工具等终端设备应用,广泛地覆盖5G、物联网、消费、商务及工控等领域。

来源:AT32 MCU 雅特力科技
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 121

今天我们来讲讲,下面这个“电平转换”电路,理解后令人心情愉快。电路设计其实也可以很有趣。先说一说这个电路的用途:当两个MCU在不同的工作电压下工作(如MCU1 工作电压5V;MCU2 工作电压3.3V),那么MCU1 与MCU2之间怎样进行串口通信呢?很明显是不能将对应的TX、RX引脚直接相连的,否测可能造成较低工作电压的MCU烧毁!下面的“电平双向转换电路”就可以实现不同VDD(芯片工作电压)的MCU之间进行串口通信。

“不同的电平信号的MCU怎么通信?"

该电路的核心在于电路中的MOS场效应管(2N7002)。他和三极管的功能很相似,可做开关使用,即可控制电路的通和断。不过比起三极管,MOS管有挺多优势,后面将会详细讲起。下图是MOS管实物3D图和电路图。简单的讲,要让他当做开关,只要让Vgs(导通电压)达到一定值,引脚D、S就会导通,Vgs没有达到这个值就截止。

“不同的电平信号的MCU怎么通信?"

那么如何将2N7002应用到上面电路中呢,又起着什么作用呢?下面我们来分析一下。

“不同的电平信号的MCU怎么通信?"

如果沿着a、b两条线,将电路切断。那么MCU1的TX引脚被上拉为5V,MCU2的RX引脚也被上拉为3.3V。2N7002的S、D引脚(对应图中的2、3引脚)截止就相当于a、b两条线,将电路切断。也就是说,此电路在2N7002截止的时候是可以做到,给两个MCU引脚输送对应的工作电压。

下面进一步分析:

数据传输方向MCU1-->MCU2。

“不同的电平信号的MCU怎么通信?"

1. MCU1 TX发送高电平(5V),MCU2 RX配置为串口接收引脚,此时2N7002的S、D引脚(对应图中的2、3引脚)截止,2N7002里面的二极管3-->2方向不通。那么MCU2 RX被VCC2上拉为3.3V。

2. MCU1 TX发送低电平(0V),此时2N7002的S、D引脚依然截止,但是2N7002里面的二极管2-->3方向通,即VCC2、R2、2N7002里的二极管、MCU1 TX组成一个回路。2N7002的2引脚被拉低,此时MCU2 RX为0V。该电路从MCU1到MCU2方向,数据传输,达到了电平转换的效果。

接下来分析

数据传输方向MCU2-->MCU1

“不同的电平信号的MCU怎么通信?"

1. MCU2 TX发送高电平(3.3V),此时Vgs(图中1、2引脚电压差)电压差约等于0,2N7002截止,2N7002里面的二极管3-->2方向不通,此时MCU1 RX引脚被VCC1上拉为5V。

2. MCU2 TX发送低电平(0V),此时Vgs(图中1、2引脚电压差)电压差约等于3.3V,2N7002导通,2N7002里面的二极管3-->2方向不通,VCC1、R1、2N7002里的二极管、MCU2 TX组成一个回路。2N7002的3引脚被拉低,此时MCU1 RX为0V。

该电路从MCU2到MCU1方向,数据传输,达到了电平转换的效果。

到此,该电路就分析完了,这是一个双向的串口电平转换电路。

MOS的优势:

1、场效应管的源极S、栅极G、漏极D分别对应于三极管的发射极e、基极b、集电极c,它们的作用相似,图一所示是N沟道MOS管和NPN型晶体三极管引脚,图二所示是P沟道MOS管和PNP型晶体三极管引脚对应图。

“不同的电平信号的MCU怎么通信?"

2、场效应管是电压控制电流器件,由VGS控制ID,普通的晶体三极管是电流控制电流器件,由IB控制IC。MOS管道放大系数是(跨导gm)当栅极电压改变一伏时能引起漏极电流变化多少安培。晶体三极管是电流放大系数(贝塔β)当基极电流改变一毫安时能引起集电极电流变化多少。

3、场效应管栅极和其它电极是绝缘的,不产生电流;而三极管工作时基极电流IB决定集电极电流IC。因此场效应管的输入电阻比三极管的输入电阻高的多。

4、场效应管只有多数载流子参与导电;三极管有多数载流子和少数载流子两种载流子参与导电,因少数载流子浓度受温度、辐射等因素影响较大,所以场效应管比三极管的温度稳定性好。

5、场效应管在源极未与衬底连在一起时,源极和漏极可以互换使用,且特性变化不大,而三极管的集电极与发射极互换使用时,其特性差异很大,b 值将减小很多。

6、场效应管的噪声系数很小,在低噪声放大电路的输入级及要求信噪比较高的电路中要选用场效应管。

7、场效应管和普通晶体三极管均可组成各种放大电路和开关电路,但是场效应管制造工艺简单,并且又具有普通晶体三极管不能比拟的优秀特性,在各种电路及应用中正逐步的取代普通晶体三极管,目前的大规模和超大规模集成电路中,已经广泛的采用场效应管。

8、输入阻抗高,驱动功率小:由于栅源之间是二氧化硅(SiO2)绝缘层,栅源之间的直流电阻基本上就是SiO2绝缘电阻,一般达100MΩ左右,交流输入阻抗基本上就是输入电容的容抗。由于输入阻抗高,对激励信号不会产生压降,有电压就可以驱动,所以驱动功率极小(灵敏度高)。一般的晶体三极管必需有基极电压Vb,再产生基极电流Ib,才能驱动集电极电流的产生。晶体三极管的驱动是需要功率的(Vb×Ib)。

9、开关速度快:MOSFET的开关速度和输入的容性特性的有很大关系,由于输入容性特性的存在,使开关的速度变慢,但是在作为开关运用时,可降低驱动电路内阻,加快开关速度(输入采用了后述的“灌流电路”驱动,加快了容性的充放电的时间)。MOSFET只靠多子导电,不存在少子储存效应,因而关断过程非常迅速,开关时间在10—100ns之间,工作频率可达100kHz以上,普通的晶体三极管由于少数载流子的存储效应,使开关总有滞后现象,影响开关速度的提高(目前采用MOS管的开关电源其工作频率可以轻易的做到100K/S~150K/S,这对于普通的大功率晶体三极管来说是难以想象的)。

10、无二次击穿:由于普通的功率晶体三极管具有当温度上升就会导致集电极电流上升(正的温度~电流特性)的现象,而集电极电流的上升又会导致温度进一步的上升,温度进一步的上升,更进一步的导致集电极电流的上升这一恶性循环。而晶体三极管的耐压VCEO随管温度升高是逐步下降,这就形成了管温继续上升、耐压继续下降最终导致晶体三极管的击穿,这是一种导致电视机开关电源管和行输出管损坏率占95%的破环性的热电击穿现象,也称为二次击穿现象。MOS管具有和普通晶体三极管相反的温度~电流特性,即当管温度(或环境温度)上升时,沟道电流IDS反而下降。例如;一只IDS=10A的MOS FET开关管,当VGS控制电压不变时,在250C温度下IDS=3A,当芯片温度升高为1000C时,IDS降低到2A,这种因温度上升而导致沟道电流IDS下降的负温度电流特性,使之不会产生恶性循环而热击穿。也就是MOS管没有二次击穿现象,可见采用MOS管作为开关管,其开关管的损坏率大幅度的降低,近两年电视机开关电源采用MOS管代替过去的普通晶体三极管后,开关管损坏率大大降低也是一个极好的证明。

11、MOS管导通后其导通特性呈纯阻性:普通晶体三极管在饱和导通是,几乎是直通,有一个极低的压降,称为饱和压降,既然有一个压降,那么也就是;普通晶体三极管在饱和导通后等效是一个阻值极小的电阻,但是这个等效的电阻是一个非线性的电阻(电阻上的电压和流过的电流不能符合欧姆定律),而MOS管作为开关管应用,在饱和导通后也存在一个阻值极小的电阻,但是这个电阻等效一个线性电阻,其电阻的阻值和两端的电压降和流过的电流符合欧姆定律的关系,电流大压降就大,电流小压降就小,导通后既然等效是一个线性元件,线性元件就可以并联应用,当这样两个电阻并联在一起,就有一个自动电流平衡的作用,所以MOS管在一个管子功率不够的时候,可以多管并联应用,且不必另外增加平衡措施(非线性器件是不能直接并联应用的)。

来源:STM32嵌入式开发
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 275

全球半导体解决方案供应商瑞萨电子集团(TSE:6723)今日宣布,面向数据中心、服务器和网络基础设施领域推出抖动低于100fs的全新“使用点”时钟解决方案,以扩展瑞萨时钟解决方案。全新FemtoClock2产品家族包括超低抖动时钟发生器和抖动衰减器,采用4mm x 4mm小型封装,可为新一代高速互连设计配置经济且简单的时钟树。

“”

FemtoClock2可提供达64fs RMS的超低抖动性能,使客户能够轻松满足新交换机或路由器设计对新一代PAM4的要求。FemtoClock2产品家族为4mm x 4mm封装,不足市场同类解决方案尺寸的三分之一。这使得设计人员可将时钟源放置于使用点(即非常靠近接收时钟信号的器件),以简化PCB布局、减少串扰,获得更干净的信号。优异的灵活性使该系列产品适用于诸多应用。FemtoClock2可配置为DCO、时钟发生器或抖动衰减器,从而提供极具价值的设计灵活性和复用性。

瑞萨电子数据中心事业部时钟产品副总裁Bobby Matinpour表示:“PAM4技术正在推动通信和数据中心领域数据传输速率的重大飞跃,由此也对该类系统中的时钟提出严苛要求。此次我们扩展广受欢迎的FemtoClock产品阵容,推出全新高性能产品家族,以小尺寸提供超低抖动和低功耗,让时钟可放置于板上任何便于使用的位置。通过消除与电路板上时钟走线相关的附加抖动,大幅简化设计。”

FemtoClock2产品家族的关键特性

  • 抖动低至64fs(典型值)RMS
  • 600mW低功耗
  • 采用24和28引脚,4mm x 4mm QFN封装
  • 具有四个差分输出的单、双输入选项
  • 电源噪声抑制优于-80dB
  • 输出到输出隔离为-80dBc
  • 符合ITU-T G.8262和G.8262.1标准,用于增强型同步以太网/OTN
  • 集成晶体选件,可节省更多空间

用户可将全新FemtoClock2解决方案与瑞萨小型、单输出、高性能振荡器,或更广泛的ClockMatrixTM系列时钟解决方案相结合,以满足高性能服务器和网络基础设施设计中对时钟产品的严苛要求。FemtoClock2也可与新近推出的基于IEEE1588协议的PTP Clock Manager无缝结合, 亦可与瑞萨丰富的电源、微控制器相结合, 开发多种应用, 如IEEE1588成功产品组合等。FemtoClock2作为下游时钟,能够在不中断同步的情况下在引脚上实现100fs低抖动。

瑞萨电子作为先进供应商,提供“一站式”时钟解决方案,涵盖从全功能系统解决方案到简单的时钟树构建模块的专业技术及产品。

供货信息

RC32504A和RC22504A FemtoClock解决方案及评估板现已上市。更多信息,请访问:renesas.com/femtoclock2。

关于瑞萨电子集团

瑞萨电子集团 (TSE: 6723) ,提供专业可信的创新嵌入式设计和完整的半导体解决方案,旨在通过使用其产品的数十亿联网智能设备改善人们的工作和生活方式。作为全球微控制器、模拟、电源和SoC产品供应商,瑞萨电子为汽车、工业、家居、基础设施及物联网等各种应用提供综合解决方案,期待与您携手共创无限未来。更多信息,敬请访问renesas.com。

围观 18

所谓“时序”从字面意义上来理解,一是“时间问题”;二是“顺序问题”。

先说一下“顺序问题”,这个相对简单一些。我们在学UART串口通信的时候,先1位起始位,再8位数据位,最后1位停止位,这个先后顺序不能错。我们在学1602液晶的时候,比如写指令,RS=L,R/W=L,D0~D7=指令码,这三者的顺序是无所谓的,但是最终的E=高脉冲,必须是在这三条程序之后,这个顺序一旦错误,写的数据也可会出错。

而“时间问题”内容相对复杂,比如UART通信,每一位的时间宽度是1/baud。我们初中就学过一个概念,世界上没有绝对的准确。那么每一位的时间宽度1/baud要求精确到什么范围内呢?

单片机读取UART的RXD引脚数据的时候,一位数据,单片机平均分成了16份,取其中的7、8、9三次读到的结果,这三次中有2次是高电平那这一位就是1,有2次是低电平,那这一次就是0。如果我们的波特率稍微有些偏差,只要累计下来到最后一位停止位,这7、8、9还在范围内即可。如下图所示:

“UART信号采集时序图"
UART信号采集时序图

我们用三个箭头来表示7、8、9这三次的采集位置,大家可以看到,当采集到 D7的时候,已经有一次采集偏出去了,但我们采集到的数据还是不会错,因为有2次采集正确。至于这个偏差允许多大,大家自己可以详细算一下。实际上UART通信的波特率是允许一定范围内误差存在的,但不能过大,否则就会采集错误。

大家在计算波特率的时候,发现没有整除,有小数部分的时候,就要特别小心了,因为小数部分是一概被舍掉的,于是计算误差就产生了。我们用11.0592M晶振计算的过程中,11059200/12/32/9600得到的是一个整数,如果用12M晶振计算12000000/12/32/9600就会得到一个小数,大家可以算一下误差多少,是否在误差范围内。

1602的时序问题,大家要学会通过LCD1602的数据手册提供的时序图和时序参数表格来进行研究,而且看懂时序图是学习单片机所必须掌握的一项技能,如下图所示:

“1602时序图"
1602时序图

大家看到这种图的时候不要感觉害怕,说句不过分的话,单片机这些逻辑上的问题,只要小学毕业就可以理解的,很多时候是因为大家把问题想象的太难才学不下去的。

我们先来看一下读操作时序的RS引脚和R/W引脚,这两个引脚先进行变化,因为是读操作,所以R/W引脚首先要置为高电平,而不管它原来是什么。读指令还是读数据,都是读操作,而且都有可能,所以RS引脚既有可能是置为高电平,也有可能是置为低电平,大家注意上图的画法。而RS和R/W变化了经过Tsp1这么长时间后,使能引脚E才能从低电平到高电平发生变化。

而使能引脚E拉高经过了tD这么长时间后,LCD1602输出DB的数据就是有效数据了,我们就可以来读取DB的数据了。读完了之后,我们要先把使能E拉低,经过一段时间后RS、R/W和DB才可以变化继续为下一次读写做准备了。

而写操作时序和读操作时序的差别,就是写操作时序中,DB的改变是由单片机来完成的,因此要放到使能引脚E的变化之前进行操作,其它区别大家可以自行对比一下。

细心的话就会发现,这个时序图上还有很多时间标签。比如E的上升时间tR,下降时间时间tF,使能引脚E从一个上升沿到下一个上升沿之间的长度周期tC,使能E下降沿后,R/W和RS变化时间间隔tHD1等等很多时间要求,这些要求怎么看呢?放心,只要是正规的数据手册,都会把这些时间要求给大家标记出来的。

“”

大家要善于把手册中的这个表格和时序图结合起来看,上面表中的数据都是时序参数,大家务必要学会自己看时序图,这个很重要。此外,看以下解释也需要结合时序图来看。

tC:指的是使能引脚E从本次上升沿到下次上升沿的最短时间是400ns,而我们单片机因为速度较慢,一个机器周期就是1us多,而一条C语言指令肯定是一个或者几个机器周期的,所以这个条件完全满足。

tPW:指的是使能引脚E高电平的持续时间最短是150ns,同样由于我们的单片机比较慢,这个条件也完全满足。

tR,tF:指的是使能引脚E的上升沿时间和下降沿时间,不能超过25ns,别看这个数很小,其实这个时间限值是很宽裕的,我们实际用示波器测了一下开发板的这个引脚上升沿和下降沿时间大概是10ns到15ns之间,完全满足。

tSP1:指的是RS和R/W引脚使能后至少保持30ns,使能引脚E才可以变成高电平,这个条件同样也完全满足。

tHD1:指的是使能引脚E变成低电平后,至少保持10ns之后,RS和R/W才能进行变化,这个条件也完全满足。

tD:指的是使能引脚E变成高电平后,最多100ns后,1602就把数据送出来了,那么我们就可以正常去读取状态或者数据了。

tHD2:指的是读操作过程中,使能引脚E变成低电平后,至少保持20ns,DB数据总线才可以进行变化,这个条件也完全满足。

tSP2:指的是DB数据总线准备好后,至少保持40ns,使能引脚E才可以从低到高进行使能变化,这个条件也完全满足。

tHD2:指的是写操作过程中,要引脚E变成低电平后,至少保持10ns,DB数据总线才可以变化,这个条件也完全满足。

好了,LCD1602的时序参数表已经解析完成了,看完之后,是不是感觉比你想象的要简单,没有你想的那么困难。大家自己也得慢慢学会看这种时序图和表格,在今后的学习中,这方面的能力尤为重要。如果以后换用了其它型号的单片机,那么就根据单片机的执行速度来评估你的程序是否满足时序要求。整体来说,器件都是有一个最快速度的限制,而没有最慢限制,所以当换用高速的单片机后,通常都是靠在各步骤间插入软件延时来满足较慢的时序要求。

本文转载自:网络
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:
cathy@eetrend.com)。

围观 25

在上一次的灵动微课堂中和大家分享过MM32F013x-UART 9bit通信实例,本次微课堂在此实例的基础上实现UART多处理器通信。MM32F013x系列MCU支持UART多处理器通信,其工作原理是主从机设备采用复用漏极开路,主从机外部接上拉电阻,在空闲时使从机处于静默模式,主机要控制从机执行任务时主机发送指令唤醒从机并发送数据控制从机执行相应任务。

1、UART静默模式

MM32F013x系列MCU UART静默模式的特点

  • 任何接收状态位都不会被设置
  • 所有的接收中断都被禁止
  • UART_CCR寄存器中的RWU位被置1。RWU可以被硬件自动控制或在某个条件下由软件写入。

根据UART_CCR寄存器中的WAKE位状态,UART多处理器通信有二种方法进入或退出静默模式分别是:

  • WAKE 位被设置0:进行空闲总线检测。
  • WAKE 位被设置1:进行地址标记检测。

空闲总线检测

空闲帧唤醒可以同时唤醒所有从机,在从机处于静默模式时主机发送空闲帧(即所有位均为1的数据)实现多个从机同步被唤醒。

地址标记检测

地址标记唤醒可以唤醒单个从机,从机进入静默模式时,主机向从机寻址发送地址帧,从机自动比对地址,地址配对正确则该从机被唤醒,否则继续进入静默模式。这样只有被主机寻址的从机才被唤醒激活并接收数据,从而减少未被寻址的从机参与带来的多余的UART服务开销。

2、配置流程

与UART多处理器通信相关的主要寄存器有UART通用控制寄存器、UART接收地址寄存器UART_RXADDR和UART接收掩码寄存器UART_RXMASK其描述如下寄存器表所示:

图1

如上图1所示为UART通用控制寄存器UART_CCR,在MM32F013x UM手册的第489和第490页有关于该寄存器位的详细描述。本实例UART多处理器通信要用到的相关于UART通用控制寄存器UART_CCR位的说明如下:

Bit13

WAKE(rw,reset:0x00)唤醒方法,该位决定UART多机通信从机唤醒的方法。

1:地址标记唤醒。
库函数设置:
UART_WakeUpConfig(UART1,UART_WakeUp_AddressMark);

0:空闲总线唤醒。
库函数设置:
UART_WakeUpConfig(UART1, UART_WakeUp_IdleLine);

Bit12

RWU(rw, reset:0x00)接收唤醒,该位用来决定是否把UART置于静默模式。该位可以由软件设置或清除。当唤醒序列到来时,硬件也会自动将其清零。

1:接收器处于静默模式。
库函数设置:
UART_ReceiverWakeUpCmd(UART1, ENABLE);

0:接收器处于正常工作模式。
库函数设置:
UART_ReceiverWakeUpCmd(UART1, DISABLE);

注:在设置地址标记唤醒时,如果接收 buffer 非空则不能软件修改。

Bit11

B8EN(rw, reset:0x00)UART同步帧发送第9bit使能控制位。该位使能后校验使能PEN不起作用。

1:使能同步帧第9bit发送。
库函数设置:
UART_Enable9bit(UART1, ENABLE);

0:禁止同步帧第9bit发送。
库函数设置:
UART_Enable9bit(UART1, DISABLE);

Bit10

B8TOG(rw,reset:0x00)UART同步帧发送第9bit自动翻转控制位。

1:使能第9bit自动翻转。
库函数设置:
UART_Set9bitAutomaticToggle(UART1, ENABLE);

0:禁止第9bit自动翻转。
库函数设置:

UART_Set9bitAutomaticToggle(UART1, DISABLE);
注:在 B8TXD 和 B8POL 的值相同时,在配置完寄存器后传输的第二个数据开始翻转,第一个数据默认为地址位。

Bit8

B8TXD(rw,reset:0x00)UART同步帧发送数据第9bit。

1:发送同步帧第9bit为高电平。
库函数设置:
UART_Set9bitLevel(UART1, ENABLE);

0:发送同步帧第9bit为低电平。
库函数设置:
UART_Set9bitLevel(UART1, DISABLE)

图2

如上图2所示为UART接收地址寄存器UART_RXADDR,在MM32F013x UM手册的第491页有关于该寄存器位的详细描述。本实例UART多处理器通信要用到的相关于UART_RXADDR接收地址寄存器位的说明如下:

Bit31:8:

Reserved,始终读为0x00

Bit7:0:

RXADDR(rw,reset:0x00)UART 同步帧数据本机匹配地址。

库函数设置:
UART_SetRXAddress(UART1, SLAVEADDR);

其中地址参数SLAVEADDR可以设置为宏定义形式。

如果 RXMASK =0xFF时接收到的同步帧数据与本机匹配地址相同时,产生RXB8_INTF。地址 0是广播地址,收到后都会响应。

图3

如上图3所示为UART接收掩码寄存器UART_RXMASK,在MM32F013x UM手册的第492页有关于该寄存器位的详细描述。本实例UART多处理器通信要用到的相关于UART_RXMSK接收掩码寄存器位的说明如下:

Bit31:8:

Reserved,始终读为0x00

Bit7:0:

RXMASK(rw,reset:0xFF)UART数据位全为“0”时,接收到任何数据都产生同步帧中断请求。如果数据位为“1”,RDR和RXADDR的相应位匹配时,产生同步帧中断请求。

库函数设置:

UART_SetRXMASK(UART1,SLAVEADDR); 其中地址参数SLAVEADDR可以设置为宏定义形式。

根据上文与UART 9bit多处理器通信相关的寄存器位的描述,本实例从机唤醒模式使用标记从机地址方式,在MM32F013x-UART 9bit通信实例的基础上增加从机UART 9bit多处理器通信相关的寄存器位的初始化,这里以库函数方式给出,增加的4行代码如下所示:

//Wake up method
UART_WakeUpConfig(UART1, UART_WakeUp_AddressMark);
//Synchronous frame match address
UART_SetRXAddress(UART1, SLAVEADDR);
//Synchronous frame match address mask
UART_SetRXMASK(UART1,SLAVEADDR);
//Receive wake up method
UART_ReceiverWakeUpCmd(UART1, ENABLE);

3、程序配置

01、初始化主机MM32F013x UART1 9bit通信

本实例使用MM32F0133C7P核心板作为UART多处理器通信,主机初始化代码如下所示:

bsp_UART1_Master_irq_9Bit_Init(u32 baudrate)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    UART_InitTypeDef UART_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOA, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_UART1, ENABLE);

    GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_1);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_1);

    GPIO_StructInit(&GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    UART_StructInit(&UART_InitStructure);
    UART_InitStructure.BaudRate = baudrate;
    UART_InitStructure.WordLength = UART_WordLength_8b;
    UART_InitStructure.StopBits = UART_StopBits_1;
    UART_InitStructure.Parity = UART_Parity_No;
    UART_InitStructure.HWFlowControl = UART_HWFlowControl_None;
    UART_InitStructure.Mode = UART_Mode_Rx | UART_Mode_Tx;

    UART_Enable9bit(UART1, ENABLE);
    UART_Set9bitLevel(UART1, DISABLE);
    UART_Set9bitAutomaticToggle(UART1, ENABLE);
    UART_Init(UART1, &UART_InitStructure);

    UART_ITConfig(UART1, UART_IT_RXIEN, ENABLE);

    NVIC_InitStructure.NVIC_IRQChannel = UART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPriority = 3;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    UART_Cmd(UART1, ENABLE);
}

02、初始化从机MM32F013x UART1 9bit通信

MM32F0133C7P UART1从机初始化代码如下所示:

注意:多从机通信,初始化从机串口时需要修改从机地址宏为0x01、0x02等。

#define SLAVEADDR  (0x01)
void bsp_UART1_Slave_irq_9Bit_Init(u32 baudrate)
{
   GPIO_InitTypeDef GPIO_InitStructure;
   UART_InitTypeDef UART_InitStructure;
   NVIC_InitTypeDef NVIC_InitStructure;

   RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOA, ENABLE);
   RCC_APB2PeriphClockCmd(RCC_APB2Periph_UART1, ENABLE);

   GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_1);
   GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_1);

   GPIO_StructInit(&GPIO_InitStructure);
   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
   GPIO_Init(GPIOA, &GPIO_InitStructure);

   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
   GPIO_Init(GPIOA, &GPIO_InitStructure);

   UART_StructInit(&UART_InitStructure);
   UART_InitStructure.BaudRate = baudrate;
   UART_InitStructure.WordLength = UART_WordLength_8b;
   UART_InitStructure.StopBits = UART_StopBits_1;
   UART_InitStructure.Parity = UART_Parity_No;
   UART_InitStructure.HWFlowControl = UART_HWFlowControl_None;
   UART_InitStructure.Mode = UART_Mode_Rx | UART_Mode_Tx;

   UART_WakeUpConfig(UART1, UART_WakeUp_AddressMark);
   UART_WakeUpConfig(UART1, UART_WakeUp_IdleLine);
   UART_SetRXAddress(UART1, SLAVEADDR);
   UART_SetRXMASK(UART1,0x02);
   UART_ReceiverWakeUpCmd(UART1, ENABLE);
   UART_Enable9bit(UART1, ENABLE);
   UART_Set9bitLevel(UART1, DISABLE);
   UART_Set9bitAutomaticToggle(UART1, ENABLE);
   UART_Init(UART1, &UART_InitStructure);
   UART_ITConfig(UART1, UART_IT_RXIEN, ENABLE);

   NVIC_InitStructure.NVIC_IRQChannel = UART1_IRQn;
   NVIC_InitStructure.NVIC_IRQChannelPriority = 0;
   NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
   NVIC_Init(&NVIC_InitStructure); 

   UART_Cmd(UART1, ENABLE);
}

03、编写MM32F013x UART1主机中断服务函数

MM32F0133C7P UART1主机中断服务函数,代码如下所示:

#define RX_MASTER_LEN   (3)
u8 g_Rx_Master_Buf[RX_MASTER_LEN] = {0x00};
u8 g_Rx_Master_Cnt = 0;
void UART1_IRQHandler(void)
{
     u8 res;
     if(UART_GetITStatus(UART1, UART_IT_RXIEN) != RESET) 
     {
          UART_ClearITPendingBit(UART1, UART_IT_RXIEN);
          res = UART_ReceiveData(UART1);
          g_Rx_Master_Buf[g_Rx_Master_Cnt] = res;
          if(g_Rx_Master_Cnt < RX_MASTER_LEN-1)
          {
             g_Rx_Master_Cnt++;
          }
          else
          {
             g_Rx_Master_Cnt = 0;
          }
     }
}

04、编写MM32F013x UART1从机中断服务函数

MM32F0133C7P UART1从机中断服务函数,代码如下所示:

#define RX_SLAVE_LEN    (3)
u8 g_Rx_Slave_Buf[RX_SLAVE_LEN] = {0x00};
u8 g_Rx_Slave_Cnt = 0;
void UART1_IRQHandler(void)
{
     u8 res;
     if(UART_GetITStatus(UART1, UART_IT_RXIEN) != RESET) 
     {
         UART_ClearITPendingBit(UART1, UART_IT_RXIEN);
         res = UART_ReceiveData(UART1);
         g_Rx_Slave_Buf[g_Rx_Slave_Cnt] = res;
         if(g_Rx_Slave_Cnt < RX_SLAVE_LEN-1)
         {
             g_Rx_Slave_Cnt++;
         }
         else
         {
             g_Rx_Slave_Cnt = 0;
         }  
     }
}

05、编写MM32F013x UART1主从机

通用发送数据函数

MM32F0133C7P UART1主从机通用发送函数,代码如下所示:

u8 g_Tx_Master_Buf[2] = {0xAA,0x55};
void bsp_UART_Send_Byte_Data(UART_TypeDef* uart,u8 data)
{
    UART_SendData(uart, data);
    while(!UART_GetFlagStatus(uart, UART_FLAG_TXEPT));
}
void bsp_UART_Send_Bytes_Data(UART_TypeDef* uart, u8* buf, u16 len)
{
    while(len--)
    {
        bsp_UART_Send_Byte_Data(uart,*buf++);
    }
}

06、编写MM32F013x UART1主机发送从机地址函数

MM32F0133C7P UART1主机发送从机地址函数,代码如下所示:

u8 g_Tx_Master_Buf[2] = {0xAA,0x55};
#define SLAVEADDR1       (0x01)
#define SLAVEADDR2       (0x02)
void bsp_UART_Send_SlaveAddr(UART_TypeDef* uart,u8 data)
{
    UART_SendData(uart, data);
    while(!UART_GetFlagStatus(uart, UART_FLAG_TXEPT));
}

07、编写MM32F013x UART1主机按键

发送从机地址和数据函数

宏定义按键GPIO端口和管脚,本实例只用到MM32F013x核心板的PA0作为KEY1对应核心板。

#define KEY1_GPIO_Port  GPIOA
#define KEY1_Pin         GPIO_Pin_0
#define KEY2_GPIO_Port  GPIOB
#define KEY2_Pin         GPIO_Pin_2
#define KEY3_GPIO_Port  GPIOB
#define KEY3_Pin        GPIO_Pin_10
#define KEY4_GPIO_Port  GPIOB
#define KEY4_Pin        GPIO_Pin_11
#define KEY1         GPIO_ReadInputDataBit(KEY1_GPIO_Port,KEY1_Pin)  //read key1
#define KEY2         GPIO_ReadInputDataBit(KEY2_GPIO_Port,KEY2_Pin)  //read key2
#define KEY3         GPIO_ReadInputDataBit(KEY3_GPIO_Port,KEY3_Pin)  //read key3
#define KEY4         GPIO_ReadInputDataBit(KEY4_GPIO_Port,KEY4_Pin)  //read key4
#define KEY1_DOWN_VALUE     1   //KEY1
#define KEY2_DOWN_VALUE     0   //KEY2
#define KEY3_DOWN_VALUE     0   //KEY3
#define KEY4_DOWN_VALUE     0   //KEY4
#define KEY1_PRES           1   //KEY1
#define KEY2_PRES           2   //KEY2
#define KEY3_PRES           3   //KEY3
#define KEY4_PRES           4   //KEY4
//Init Key GPIO
void bsp_Key_GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA | RCC_AHBPeriph_GPIOB ,ENABLE);

    GPIO_StructInit(&GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin  = KEY1_Pin;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
    GPIO_Init(KEY1_GPIO_Port, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin  = KEY2_Pin;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(KEY2_GPIO_Port, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin  = KEY3_Pin;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(KEY3_GPIO_Port, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin  = KEY4_Pin;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(KEY4_GPIO_Port, &GPIO_InitStructure);    
}

u8 bsp_Key_Scan(u8 mode)
{
    static u8 key_up = 1;
    if(mode)
    {
        key_up = 1;
    }
    if(key_up && ((KEY1 == KEY1_DOWN_VALUE) || (KEY2 == KEY2_DOWN_VALUE) || \
                  (KEY3 == KEY3_DOWN_VALUE) || (KEY4 == KEY4_DOWN_VALUE))) 
    {
        DELAY_Ms(10);

        key_up = 0;

        if(KEY1 == KEY1_DOWN_VALUE)
        {
            return KEY1_PRES;
        }
        else if(KEY2 == KEY2_DOWN_VALUE)
        {
            return KEY2_PRES;
        }
        else if(KEY3 == KEY3_DOWN_VALUE)
        {
            return KEY3_PRES;
        }
        else if(KEY4 == KEY4_DOWN_VALUE)
        {
            return KEY4_PRES;
        }
    }
    else if((KEY1 != KEY1_DOWN_VALUE) && (KEY3 != KEY3_DOWN_VALUE) && \
            (KEY4 != KEY4_DOWN_VALUE) && (KEY2 != KEY2_DOWN_VALUE))
    {
        key_up = 1;
    }

    return 0;
}

u8 Key_Nnum = 0;
void bsp_Process_Key_Task(void)
{
    static u8 Key_Value = 0;
    Key_Value = bsp_Key_Scan(0);
    switch(Key_Value)
    {
        case KEY1_PRES:           
           if(Key_Nnum == 0)
           {
                Key_Nnum = 1;
                bsp_UART_Send_SlaveAddr(UART1, SLAVEADDR1); //Send SlaveAddr1
                bsp_UART_Send_Bytes_Data(UART1, g_Tx_Master_Buf, sizeof(g_Tx_Master_Buf)); //Send data           
            }
            else if(Key_Nnum == 1)
            {
                Key_Nnum = 0;                
                bsp_UART_Send_SlaveAddr(UART1, SLAVEADDR2); //Send SlaveAddr2
                bsp_UART_Send_Bytes_Data(UART1, g_Tx_Master_Buf, sizeof(g_Tx_Master_Buf)); //Send data 
            }          
            break;
        case KEY2_PRES:
            break;
        case KEY3_PRES:
            break;
        case KEY4_PRES:
            break;
        default :
            break;        
    }
}

08、编写MM32F013x UART1主机

接收从机返回的数据函数

处理MM32F0133C7P UART1主机接收数据函数,代码如下所示:

 void bsp_UART_Master_Rec_Task(void)
{
  if(((g_Rx_Master_Buf[0] == SLAVEADDR1 ) || (g_Rx_Master_Buf[0] == SLAVEADDR2)) && (g_Rx_Master_Buf[1] == 0xAA) && (g_Rx_Master_Buf[2] == 0x55))
   {
      LED1_TOGGLE();   
      g_Rx_Master_Cnt = 0;
       memset(g_Rx_Master_Buf,0,sizeof(g_Rx_Master_Buf));
    }
}

09、编写MM32F013x UART1从机

接收主机发送的数据函数

处理MM32F0133C7P UART1主机接收数据函数,代码如下所示:

注意:g_Rx_Slave_Buf[1]为主机发送给从机的地址由从机接收判断,多从机通信需要修改从机地址宏:#define SLAVEADDR (0x02)等。

void bsp_UART_Slave_Rec_Task(void)
{
  if((g_Rx_Slave_Buf[0] == SLAVEADDR) && (g_Rx_Slave_Buf[1] == 0xAA) && (g_Rx_Slave_Buf[2] == 0x55))
  {
     LED1_TOGGLE();   
     bsp_UART_Send_Bytes_Data(UART1, g_Rx_Slave_Buf, sizeof(g_Rx_Slave_Buf));
     g_Rx_Slave_Cnt = 0;
     memset(g_Rx_Slave_Buf,0,sizeof(g_Rx_Slave_Buf));
  }
}

10、MM32F013x UART1 9bit多处理器通信功能演示

MM32F0133C7P多处理器通信功能演示:

主机main函数中调用DELAY_Init、LED_Init、bsp_Key_GPIO_Init和主机串口初始化函数bsp_UART1_Master_irq_9Bit_Init,在while(1)后台调用按键发送从机地址和发送数据函数bsp_Process_Key_Task以及处理接收从机返回数据函数bsp_UART_Master_Rec_Task代码如下所示:

s32 main(void)
{
    //SysTick init
    DELAY_Init();
    //LED Init
    LED_Init();
    //Key GPIO Init
    bsp_Key_GPIO_Init();
    //UART1 9Bit irt Init
    bsp_UART1_Master_irq_9Bit_Init(115200);
    while(1) 
    {
        //Key processing multi-processor communication
        bsp_Process_Key_Task();
        //Processing master receiving tasks
        bsp_UART_Master_Rec_Task();
    }
}

从机main函数中调用LED_Init和从机串口初始化函数bsp_UART1_Slave_irq_9Bit_Init,在while(1)后台调用处理从机接收任务函数bsp_UART_Slave_Rec_Task代码如下所示:

s32 main(void)
{
    //LED Init
    LED_Init();
    //UART1 9bit init
    bsp_UART1_Slave_irq_9Bit_Init(115200);
    while(1) 
    {
        //Processing slave receiving tasks
        bsp_UART_Slave_Rec_Task(); 
    }
}

分别编译以上主机和从机工程代码,然后烧录软件到MM32F0133C7P核心板上,烧录多从机时需修改从机工程代码的从机地址宏SLAVEADDR,编译后再烧录。本实例从机烧录0x01和x02为例作为演示,即1台主机和2台从机作为演示。

4、接线方式

MM32F0133C7P多处理器通信UART主机和从机接线方法:

各从机TX线与连接,RX线与连接,从机线与连接的TX接到主机RX端,从机线与连接的RX接到主机的TX端,主从机分别外接5.1~10K值范围之一的上拉电阻,本实例接5.1K上拉电阻。

MM32F0133C7P UART多处理器通信演示1台主机和2台从机分别发送和接收数据:

主机第1次按下MM32F0133C7P核心板上的S1按键,主机发送1字节从机地址0x01,接着发送2字节数据0xAA和0x55,从机1(地址0x01)在静默模式下自动比对主机寻址从机的地址0x01比对成功从机1被唤醒继续接收0xAA和0x55数据,收到完整的3字节数据时从机LED1状态翻转LED1亮并原样返回收到的数据给主机,此时从机2仍处于静默模式,主机完全收到从机1返回的3字节数据即0x01,0XAA,0x55时主机LED1状态翻转LED亮,实物现象如下图4所示。

图4(上从机1、中主机、下从机2)

主机第2次按下MM32F0133C7P核心板上的S1按键,主机发送1字节从机地址0x02,接着发送2字节数据0xAA和0x55,从机2(地址0x02)在静默模式下自动比对主机寻址从机的地址0x02比对成功从机2被唤醒继续接收0xAA和0x55数据,收到完整的3字节数据时从机LED1状态翻转LED1亮并原样返回收到的数据给主机,此时从机1仍处于静默模式其LED1维持上次点亮状态,主机完全收到从机2返回的3字节数据时即0x02,0XAA,0x55时主机LED1状态翻转LED灭,实物现象如下图5所示:

图5(上从机1、中主机、下从机2)

5、波形抓取

逻辑分析仪抓取到的MM32F0133C7多处理器通信波形图如下图6和图7所示:

图6上波形红色框注为主机发送:0x01、0xAA、0x55寻址从机1,从机(地址0x01)从静默模式唤醒并收到主机发送的数据0xAA和0x55时原样返回给主机如图6下波形蓝色框注:0x01、0xAA、0x55,此时从机2(地址0x02)并未作响应仍处于静默模式。

图6

图7上红色框注为主机发送:0x02、0xAA、0x55寻址从机2,从机(地址0x02)从静默模式唤醒并收到主机发送的数据0xAA和0x55时原样返回给主机如图7下波形蓝色框注:0x02、0xAA、0x55,此时从机1(地址0x01)并未作响应仍处于静默模式。

图7

转自:灵动MM32MCU

围观 110

本文对单片机通信性能的分析和评价方法进行研究,指出了物理接口电路分布参数的分析方法和保证通信系统通信接口控制性能固件正确性的组合选择法。对单片机通信性能评价时,可以使用本文提出单片机通信性能分析参数。这些参数是比特吞吐系数ξ、数据传输有效性σ和数据识别率η。利用这些参数,可以定量地对单片机通信性能进行分析,同时也可以利用这些参数进行单片机的选择和应用系统设计。

现代信息网络技术的一个突出特点,就是使工业控制系统6中的所有设备连接成网,从而在一个核心软件管理下工作(这个软件可能是分布式的操作系统,也可能是嵌入式操作系统),形成一个有机的整体。这种整体网络方式的现代工业控制系统具有传统独立控制系统所无法比拟的先进性,不仅能极大地提高工业设备的生产效率,还可以大大提高系统的安全性和可靠性。

目前,为了实现网络化系统,工业设备都必须以网络终端的形式出现在系统中,而这种网络终端大多用单片机或数字信号处理来实现。由于工业控制系统设备的多样性和分布性,智能模块方式的单片机终端已经成为主流设备控制方式。

随着信息和电子技术的发展,各种工业控制系统网络不断改进,这就对单片机通信功能的要求不断增加。特别是对各种现场总线技术中通信接口和通信协议,更是提出了新的通信要求:不仅能适应某一个通信协议,还希望能成为一种通信协议自动转换的智能终端。因此,单片机通信功能是否满足设计目标要求是必须设计中的一个关键问题。

在单片机应用技术中,需要有一个对单片机通信性能进行分析评价的理论方法,只有这样才能解决对单片机通信性能进行评价和设计的问题。本文的目的就是通过研究讨论,提出一个对单片机通信性能的分析和评价方法。 本文从单片机通信接口的物理结构和固件特性两个方面对单片机通信接口进行了分析,提出了接口电路物理性能分析模型和通信特性定量评价分析的参数。

一、串行通信口的物理特性

对单片机的通信性能可以从通信接口性能和软件处理能两个方面进行考虑。通信接口性能主要表现在与通信有关的电路物理性能上;而软件处理性能则与单片机的指令系统和CPU性能直接有关,所以,软件处理性能可以用单片机的固件特性来表示。

单片机串行通信接口一般分为两种,一种是同步串行通信接口(SPI),另一种是异步串行通信接口(SCI)。SPI具有串行通信速度高的特点,但一般需要 1条发送和1条接收串行数据通信线、1条通信目标选择线和1条同步时钟线,一共需要4条通信线(占用单片机的4个I/O引脚)。SOC虽然数据传输速度低于SPI,但一般只需要2条数据线和1条地线即可,也就是只需要3根通信线(占用单片机的3个引脚)。 SPI和SCI串行通信接口的基本电路结构如图1和图2所示。

单片机的通信性能分析与评价方法

单片机的通信性能分析与评价方法

从图中可以看到SPI和SCI的物理结构有很大差别:SPI的通信速率和信号接收正确率由时钟和接口物理特性决定;SCI的通信速率和信号接收正确率由物理接口和时钟分频率决定。

通信接口接收的是数字电平信号,因此,存在电平判别的问题。如果输入电路的物理特性对输入电平波形有影响,则会直接影响接收信号的正确性。因此,要求接收和发送电路的物理特性必须满足波形的要求。

通信息的物理特性还与通信介质和连接状态有关。图3是通信接口电路和驱动电路之间的待效电路图。

当数据传输速率远小于电路电压跟踪速度时,可认为每一位数据的电平保护足够长的时间。根据图3可以得到输出和输入信号的阶段响应: u=U(1-e -at) (1) 式中,u是数字信号高电平,a=1/[R(C1+C2)]是上升时间常数。图4是数字信号受到分布参数影响后的波形。 根据式(1)可知,在5V电源电压条件下,通信信号电平幅度上升到判别电压幅度(TTL高电平或CMOS高电平)所需时间是 T=-R(C1+C2)ln0.46(对TTL电路)或T=-R(C1+C2)ln0.72(对CMOS电路)。如果信号的波特率为fb,为确保正确接收,信号上升时间应当为信号脉冲宽度的1/4~1/10。 由此,当数字信号为TTL电平时,要求单片机接口电路的分布参数范围是

单片机的通信性能分析与评价方法

当数字系统为CMOS电平时,要求单片机接口电路的分布参数范围是
单片机的通信性能分析与评价方法

如果单片机的物理分布参数已经确定,则式(2)和式(3)就是最高通信速率的限制条件。 通过以上分析可以看出,单片机通信接口的物理特性对通信性能直接影响,主要反映在接口电路分布参数对数字信号波形的影响,进而引起接口电平判别失误。

二、单片机串行通信接口的固件特性

所谓单片机串行通信接口的固件特性,是指单片机串行通信接口的控制和支持硬件在串行通信时所具有技术特性。固件特性包括两个方面问题,一个方面是软件行为对硬件的要求条件,另一个是硬件电路所能提供的结构和功能特性。

1.单片机通信接口功能的控制方式

单片机串行通信接口功能的控制是在单片机内部硬件结构支持下的软件操作。单片机是面向寄存器的工作方式,因此,对于通信接口的控制是通过一系列的寄存器操作实现的。

基本控制步骤如下:

(1)设置必要参数(如通信速率、时钟源、终端方式等);

(2)设置数据结构(对于异步通信口);

(3)通过向发送寄存器写入数据,启动发送/接收——读取数据。 每一次通信(发送一个字节)都需要重复最后一个步骤。

由此可知,单片机串行通信接口寄存器的操作会直接影响通信接口的功能和性能。由于单片机的每一步骤操作都是执行1条指令,所以,单片机串行通信的真正发送时间,是向发送寄存器写数据指令结束的时刻。

单片机的通信性能分析与评价方法

2.单片机串行通信接口基本固件特性

单片机串行通信接口一般包括发送数据、接收数据、发送时钟、接收时钟、线路监测、碰撞处理、波特率设置、帧结构设备等。这些固件的特性有一个共同的特点,就是全部以寄存器为操作对象,并在每一个操作指令结束时执行寄存器操作,通过寄存器的输出电路实现相应的功能。由此可知,寄存器操作的特性,就是单片机串行通信接口固件的特性。

(1)次序固定的固件特性。
单片机串行以通信接口操作中,必须先进行相应的设置,才能实施通信。这种固定的次序是单片机串行通信接口的重要固件特性,如果忽略了这种次序特性,必然会导致通信失败。

(2)协议相关的固件特性。
通信协议是有效利用单片机串行通信接口的基本保证之一。如果在单片机的通信协议执行过程中出现问题,则通信功能就会丧失。

(3)隐含协调性。
所谓隐含协调性指隐含在通信指令集中的协议规定,如发送方与接收方的等待协议规定等。

由此,可以把单片机的固件特性用3个不同集合之交表示:设有通信接口的操作次序集合A、相关协议集合B和隐含规约集合C。A中的每一个元素都是一种正确的通信接口操作次序;B中每一个元素都是一种体现相关协议的通信接口操作要求;C中每一个元素都是一种隐含规约,则符合要求的通信接口控制固件必然是三者之交: Y=A∩B∩C

(4) 由此可知,要检查单片机串行通信接口的基本固件特性,可以使用式(2)进行判别;如果不能满足式(2),就表明单片机串行通信接口的固件特性有问题,会引起通信失效。

三、单片机通信性能分析

上述对单片机串行通信接口的物理特性和因件特性的分析,提供了单片机通信性能分析的基础。单片机通信性能分析包括比特吞吐特性分析、有效性分析和数据安全性分析。

1.比特吞吐特性分析

比特吞吐特性,是指单片机执行通信任务时单位时间内发送和接收的比特数目。比特吞吐特性不能用串行特性的波特率代替。比特吞特性不仅与波特率有关,更与单片机的固件特性有关。不同的通信协议会引起不同的通信操作,形成不同的操作固件。这种固件操作的有效性才是决定比特吞吐特性的关键。

在处理一组数据时,设通信协议处理数据的时间为tp,串行通信发送数据的时间为tc,则总的通信时间为t=tp+tc,因此,串行通信接口的比特吞吐特性可用如下函数表示: ξ=fctc/(tp+tc) (5) 式中,ξ叫做比特吞吐系数,fc是固件设备的串行通信波特率。可见,在fc固定的条件下,要提高比特吞吐特性,就必须尽量减少tp+tc并增加tc。在 tp=0这种极端情况下,比特吞吐系数才能等于通信波特率。 利用比特吞吐系数可以方便地检查单片机的串行通信能力能否满足应用系统的要求。例如,应用系统要求每秒钟内传输10个字节数据,相当于要求每秒钟的比特吞吐系数为80K。这里K是数据帧格式有效系数:异步串行通信中K>1;同步串行通信中K=1。

单片机的通信性能分析与评价方法

根据这个要求,可以对应用系统所设计的软件结构进行核实,如果不能满足,则说明应用系统不能满足对通信的要求,必须进行调整。

2.有效性分析

所谓有效性,是指对包括通信协议在内的整个通信过程的有效性。设单片机应用系统发送和接收的控制信息数据是随机函数y=P(x),其中x代表单位时间内的数据传输量。Y的数据期望是: σ=E[y]=E[P(x)] (6) σ叫做单片机串行通信系统的数据传输有效性系数。显然,在满足应用系统功能的前提下,σ的值小,说明数据传输的有效性高;反之,系统数据传输量大,说明系数控制信息的有效性差。必须注意,这里只考虑控制信息,并不包括必要的数据采集。

有效性分析就是要根据系数数据传输的随机分布,估计系统数据的有效性。可以直接利用有效性系数作为检验的标准。

此外,数据识别率也是衡量单片机通信效果和应用系统固件特性的一种重要参数。

数据识别率是指对接收到的数据语义理解的速度。设单片机接收到能代表完整意义的X个字节所需要的时间是tx,语义理解(翻译和判别)需要的时间为tY,则数据识别率为 η=X/(tx+ty) (7) 数据识别率表示了单位时间内单片机通信的效果,η值越大,表明单片机的整体固件特性越好,通信效率越高。

3.数据完全性

数据安全性不仅与系统的串行通信物理特征有关,更主要的是与应用系统通信的固件特性有关,尤其是通信协议中的应用层,直接关系系统的数据安全。

数据安全性可以用通信稳定性和数据识别率表示。 通信稳定性与通信线路和物理接口的侵入和干扰状态有关。对于单片机通信系统来说,当多个单片机采用串行连接的方法连接在1条总线上时,通信线路的侵入状态由通信协议直接确定。如使用令牌通信方式时,基本上没有侵入状态;但如果用客户/服务器方式,侵入状态将随线路上单片机数量的增加而恶化。这是比较复杂的通信系统行为特性分析问题,本文不再分析。

结论

本文对单片机通信接口的物理特性和固件特性进行了分析。指出了确定物理接口电路分布参数的分析方法,并导出了在不考虑传输线电感(PCB板)条件下电路分布参数与保证通信可靠条件之间的关系计算公式。这些计算表明,接口电路的分布参数是限制通信速度的主要因素之一。

通信接口控制的固件特性对单片机通信性能有重要影响。通信接口控制的固件特性需要用通信接口操作次序集合A、相关协议集合B和隐含规约集合C的交集进行检查。选择一组合适的固件结构组合,以保证通信系统通信接口控制性能固件的正确性。

对单片机通信性能评价时,可以使用本文提出的单片机通信性能分析参数。这些参数是比特吞吐系数ξ、数据传输有效性系数σ和数据识别率η。利用这些参数,可以定量地对单片机通信特性进行分析,同时也中以利用这些参数进行单片机的选择和应用系统设计。

必须指出,单片机通信系统的安全特性也是一个重要的应用基础,限于篇幅本文不再讨论。

转自:广电电器

围观 535

一、通信概述

按照数据传送方式分:

串行通信(一条数据线、适合远距离传输、控制较复杂)
并行通信(多条数据线、成本高、抗干扰性差)

按照通信的数据同步方式分:

异步通信(以1个字符为1帧、发送与接收时钟不一致)
同步通信(位同步、时钟一致)

按照数据的传输方向分:

单工(只能往一个方向传播)
半双工(数据传输可以沿两个方向,但是需要分时)
全双工(同时双向传输)

通信速率通常以比特率来表示,单位是:位/秒(bps),即每秒传输二进制代码的位数。之后会遇到一个波特率的概念,它表示每秒传输多少个码元。一般情况下,码元都是表示两种状态,即比特率=波特率。

二、串口通信

串口通信属于串行通信方式,它规定了接口的电气标准,没有规定接口插件电缆以及使用的协议。在此基础上用户可以建立自己的高层通信协议。

串口通信的接口标准有很多,有RS-232、RS-232C、RS-422A、RS-485等。比较常用的就是RS-232和RS-485。

RS-232有两种接口:25针(DB25、标准)、9针(DB9、非标准),其逻辑电平如下:

在TxD和RxD上:

逻辑1(MARK)=-3V~-15V
逻辑0(SPACE)=+3~+15V

在RTS、CTS、DSR、DTR和DCD等控制线上:

信号有效(接通,ON状态,正电压)=+3V~+15V
信号无效(断开,OFF状态,负电压)=-3V~-15V

当stm32与计算机串口通信时,需要用电平转换芯片MAX232,进行TTL电平和RS-232电平的转换。

RS-232的通信协议比较简单,通常遵循96-N-8-1格式。其是全双工的,且是异步通讯。 

stm32之通信

RS485没有规定或推荐任何数据协议,用户根据需要自己建立高层通信协议。 

区别于RS232, RS485的特性包括:
1. RS-485的电气特性:采用“2线制”,半双工通信,逻辑“1”以两线间的电压差为+(2—6) V表示;逻辑“0”以两线间的电压差为-(2—6)V表示。接口信号电平比RS -232-C降低了,就不易损坏接口电路的芯片,且该电平与TTL电平兼容,可方便与TTL 电路连接,其收发器芯片一般采用SP3485。

2. RS-485的数据最高传输速率为10Mbps

3. RS-485接口是采用平衡驱动器和差分接收器的组合,抗共模干能力增强,即抗噪声干扰性好。

4. RS-485接口的最大传输距离标准值为4000英尺,实际上可达 3000米,另外RS-232-C接口在总线上只允许连接1个收发器,即单站能力。而 RS-485接口在总线上是允许连接多达128个收发器。即具有多站能力,这样用户可以利用单一的RS-485接口方便地建立起设备网络。 因RS-485接口具有良好的抗噪声干扰性,长的传输距离和多站能力等上述优点就使其成为首选的串行接口。因为RS485接口组成的半双工网络 ,一般只需二根连线,所以RS485接口均采用屏蔽双绞线传输。 RS485接口连接器采用DB-9的9芯插头座,与智能终端RS485接口采用DB-9(孔) ,与键盘连接的键盘接口RS485采用DB-9(针)。

在stm32中,我们会接触到USART的概念,即通用同步异步收发器,同步和异步主要看其时钟是否需要对外提供。其能够满足外部设备对串行通信的要求,只不过其是TTL电平,需要进行电平转换。

注:stm32的奇偶校验位是算在数据位中的(8位-9位),与PC不同。
注:串口用于传输ASCII码字符,我们进行数据传送时,要转换为对应ASCII码的16进制数或字符串。
注:TXE是指弹仓满,TC是指枪膛满。

三、I2C通信

I2C是两线式串行总线,接口少、控制简单、通信速率较高。I2C总线只有两根双向信号线,一根是数据线SDA,另一根是时钟线SCL。

支持多主控多从控,不过同一时间点只能一个主控,连接节点数受地址限制。通过地址访问从控设备,通过仲裁决定主控设备优先级。因其采用时钟线,所以是同步传输。I2C还是半双工的。具体通信采用I2C的通信协议(寻址、起始/停止、应答等)。I2C用硬件实现起来较复杂,一般采用软件模拟I2C,移植方便。

stm32之通信

stm32之通信

四、CAN通信

CAN(Controller Area Network)是ISO国际标准化的串行通信协议。广泛应用于汽车、船舶等分布式控制系统。具有已经被大家认可的高性能和可靠性。CAN控制器通过组成总线的2根线(CAN-H和CAN-L)的电位差来确定总线的电平,在任一时刻,总线上有2种电平:显性电平和隐性电平。“显性”具有“优先”的意味,只要有一个单元输出显性电平,总线上即为显性电平,并且,“隐性”具有“包容”的意味,只有所有的单元都输出隐性电平,总线上才为隐性电平。(显性电平比隐性电平更强)。

总线上执行逻辑上的线“与”时,显性电平的逻辑值为“0”,隐性电平为“1”。

CAN总线是半双工的,虽然没有单独的时钟线,但是其采用位时序的方法进行同步传输。

CAN总线协议具有以下特点:

• 多主控制(ID仲裁)
• 系统的柔软性(没有地址信息)
• 通信速度快、距离远
• 具有错误检测、错误通知和错误恢复功能
• 故障封闭功能
• 连接节点多(理论上无限制,但是受总线时间延迟、电气负载影响,太多会降低速度)

CAN总线有两种标准如下图所示:

stm32之通信

CAN总线具有多节点可组网特性,如下图所示:
stm32之通信

从上面可以看出,CAN控制器(stm32芯片中带有)和CAN总线直接需要一个CAN收发器,可以采用TJA1040芯片。

CAN总线的工作原理

CAN总线使用串行数据传输方式,可以1Mb/s的速率在40m的双绞线上运行,也可以使用光缆连接,而且在这种总线上总线协议支持多主控制器。CAN与I2C总线的许多细节很类似,但也有一些明显的区别。当CAN总线上的一个节点(站)发送数据时,它以报文形式广播给网络中所有节点。对每个节点来说,无论数据是否是发给自己的,都对其进行接收。每组报文开头的11位字符为标识符,定义了报文的优先级,这种报文格式称为面向内容的编址方案。在同一系统中标识符是唯一的,不可能有两个站发送具有相同标识符的报文。当几个站同时竞争总线读取时,这种配置十分重要。当一个站要向其它站发送数据时,该站的CPU将要发送的数据和自己的标识符传送给本站的CAN芯片,并处于准备状态;当它收到总线分配时,转为发送报文状态。CAN芯片将数据根据协议组织成一定的报文格式发出,这时网上的其它站处于接收状态。每个处于接收状态的站对接收到的报文进行检测,判断这些报文是否是发给自己的,以确定是否接收它。由于CAN总线是一种面向内容的编址方案,因此很容易建立高水准的控制系统并灵活地进行配置。我们可以很容易地在CAN总线中加进一些新站而无需在硬件或软件上进行修改。当所提供的新站是纯数据接收设备时,数据传输协议不要求独立的部分有物理目的地址。它允许分布过程同步化,即总线上控制器需要测量数据时,可由网上获得,而无须每个控制器都有自己独立的传感器。

五、SPI通信

SPI 是Serial Peripheral Interface的缩写,直译为串行外围设备接口,SPI是Motorola公司推出的一种同步串行通讯方式,是一种四线同步总线,因其硬件功能很强,与SPI有关的软件就相当简单,使MCU有更多的时间处理其他事务。SPI内部结构简易图如下图所示:

stm32之通信

SPI接口一般使用4条线通信,分别如下:

• MISO:主设备输入/从设备输出
• MOSI:主设备输出/从设备输入
• SCLK:时钟信号线
• CS:从设备选择信号线

SPI一般用于一主多从,其结构示意图如下图所示:

stm32之通信

此外,SPI通信有4种不同的模式。下图是一种模式下的时序图:
stm32之通信

六、I2S通信

I2S是数字音频总线,在嵌入式音频系统设计中,并不是所有的MCU都支持I2S总线格式,再加上I2S还没有统一的接口标准,不同的厂家生产的设备接口也是五花八门,采用软件模拟实现I2S总线可有效解决在不支持其的MCU和设备之间通过I2S总线实现数据传输时出现的问题。

I2S为三线总线,3个信号分别为:

(1)串行时钟SCK,也叫位时钟(BCK)。即每发送1位数字音频数据,SCK上都有1个脉冲。SCK的频率=2×采样频率×采样位数。在数据传输过程中,I2S总线的发送器和接收器都可以作为系统的主机来提供系统的时钟频率。

(2)帧时钟WS,即命令(声道)选择,用于切换左右声道的数据。WS的频率等于采样频率,由系统主机提供。WS为“1”表示传输的是左声道的数据,WS为“0”表示传输的是右声道的数据。

(3)串行数据信号SD,用于传输二进制补码表示的音频数据。

I2S格式的信号无论有多少位有效数据,数据位的最高位(MSB)总是被最先传输,1次能够发送的数据决定于I2S格式的有效位数。

如下图所示为典型的时序图:

stm32之通信

七、USB通信

USB(Universal Serial Bus)是一种新的PC串行通信协议。是PC体系中的一套较新的工业标准,它支持单个主机与多个外设同时进行数据交换,大大满足了当今计算机外设追求高速度和高通用性的要求。

PC上的USB主机包括3个部分:USB主控制器/根Hub,USB系统软件和用户软件。下图是完整的USB系统组成。

stm32之通信

USB主机与设备之间的传输过程是这样的:在PC上,设备驱动程序通过调用USB驱动程序USBD,发出输入输出请求包IRP;这样,在USB驱动程序接到请求之后,调用主控制器驱动程序HCD,将IRP转化为USB的传输。当然,一个IRP可以包含一个或多个USB传输;接着,主控制器驱动程序将USB传输分解为总线事务,主控制器以包的形式发送给设备。  

USB设备类协议(USB DevICe Class Specification)与USB协议是互为补充的。针对USB的每一种设备类,都有一套特殊的设备类协议。正是USB采用了设备类的方式来对各种设备进行分类,才使USB总线能够有效的控制和管理各种设备,也使得各种设备的开发变的规范、简便。

此外,USB OTG既可以充当主机,也可以充当设备。

八、其他通信

比如无线通信、以太网通信。

转自: steed

围观 310

页面

订阅 RSS - 通信