MM32F3270

交互式界面被越来越多地集成于多种应用中,例如医疗设备、过程控制、手机和其它手持设备。这些界面主要基于使用彩色TFT-LCD的图形HMI(人机界面)。随着技术的变革,OLED技术的显示方案也得到长足的发展与广泛的应用。

OLED技术与其它技术相比,具有以下优点:

(1)功耗低

(2)响应速度快

(3)较宽的视角

(4)能实现高分辨率显示

(5)宽温度特性

(6)OLED能够实现软屏

(7)OLED成品的质量比较轻

电子产品领域中,OLED应用最为广泛的就是智能手机,其次是笔记本、显示屏、电视、平板、数码相机等领域,嵌入式应用领域中,OLED有很大一部分用作工业仪表、GPS手环、可视电话等小尺寸的显示屏。

本文介绍了如何使用 MM32F3270的FSMC(灵活的静态存储控制器)来驱动6800接口的OLED。

1、MM32F3270 FSMC的简要介绍

FSMC是Flexible static memory controller(灵活的静态存储控制器)的简称,支持并行接口的SRAM、PSRAM 、NOR FLASH 、TFT-LCD和OLED。

“图1
图1 FSMC的功能框图

2、FSMC 的功能特性

MM32的FSMC具有以下特性:

1) 可配置的静态存储器接口包括:

a) SRAM

b) PSRAM

c) NOR FLASH

2) 支持 Intel 8080 协议

3) 支持 Moto 6800 协议

4) 8位,16位,32位可配置的数据总线宽度,支持非复用与复用模式

5) BANK1 分为 4 块子 BANK,每块 64Mbit 空间

6) 时序可编程以满足不同的需求

a) 等待周期可编程

b) 总线恢复周期可编程

c) 写,读控制周期可编程

7) 可将32位的AHB访问请求,转换为对外接设备连续的8位,16位的访问

MM32F3270的FSMC提供了对多个并行外设的控制与连接,具体配置取决于存储器类型,主要涉及如下寄存器设置。

(1)SMCTLR 的 sm_data_width[2:0],定义了外部存储器的数据宽度,需根据实际数据宽度配置为8位,16位,32 位,此时需要保障实现数据传输的一致性。

(2)SMCTLR的sm_data_width_set0/1/2 来设置存储器的数据宽度,有三种情况:AHB 操作的数据宽度与存储器数据宽度相同,无数据传输一致性的问题;AHB 操作的数据宽度大于存储器的数据宽度时,AHB 接口将对 hwdata[15:0],hwdatabit[31:16]进行连续写操作,以适应外部设备的数据宽度,读操作时,hrdata[31:0]的低 16 位是有效数据;AHB 操作的数据宽度小于存储器的数据宽度时,若存储设备没有高低字节片选,不允许进行写操作,若存储设备有高低字节选择,通过 BL 控制访问对应字节。可以进行读操作,但有效数据需要用户自己处理。

(3)SYSCFG_CFGR1[30:29]:mode_sel来配置不同模式,默认值为 01

00:兼容 NOR FLASH 接口

01:兼容 8080 协议接口

10:兼容 6800 协议接口

(4)SMSKR0[10:8]用来选择三组不同的寄存器 register set0/set1/set2,以配置不同的时序

FMSC支持的外部接口

“表1
表1 FSMC控制器外部信号

3、6800和8080总线的区别

OLED的可支持串行接口(SPI,I2C)和并行接口(主要又可以分为8080模式和6800模式);8080模式和6800模式都需数据总线和地址总线,数据位传输可支持8位,9位,16位,18位,24位,32位,对于数据的寻址,都是一样的。8080模式和6800模式的区别主要是总线的控制方式上。

以SSD1306 的OLED 驱动芯片为例,其接口与MCU连接所需要的信号线为:

“使用MM32F3270

I(intel)8080模式

I8080模式管脚的控制脚有5个及Data信号:

“使用MM32F3270

“使用MM32F3270

M(Motorola)6800模式

“使用MM32F3270

“使用MM32F3270

通过上面分析,其实不难发现,它们主要区别就是:

● 8080通过“读使能(RD)”和“写使能(WR)”进行读写操作

● 6800通过“总使能(E)”和“读写选择(W/R)”进行读写操作

4、FSMC 控制OLED的硬件设计

FSMC是如何控制OLED的呢?

OLED控制使用:DC信号可以使用地址线(如A0~A18中的一根)、数据线(如D0~D7)、使能信号(E)、读写信号(RW)、片选信号(CS)。OLED通过DC信号来决定传送的数据是数据还是命令,本质上可以理解为一个地址信号,比如MB039是把DC接在A18上面,那么当FSMC控制器写地址0的时候,会使得A18 变为0,对OLED来说,就是写命令。而FSMC写地址1的时候,A0 将会变为1,对OLED来说,就是写数据。这样,就把数据和命令区分开了,其实就是对应 OLED 操作两个地址。当然DC也可以接在其他地址线上,MB039是把DC连接在PD13上面的。MM32F3270的FSMC支持8/16/32位数据宽度,我们这里用到的OLED是8位宽度的,在设置的时候需要选择8位宽。

FSMC 控制OLED 的Demo应用中,使用的开发板为MB-039,它支持外接使用SSD1306为驱动器的128 x 24 点阵OLED。

“图2
图2 OLED模块实物图

下图是OLED模块接口原理图:

“图3
图3 OLED接口原理图

各个信号作用对应如下:

“表2
表2 OLED信号对应的电源、复位与MCU接口的引脚说明

MB039可通过转接板接到OLED模块。

5、FSMC 控制OLED的软件设计

FMSC Demo应用中,使在库函数样例工程中使用选用:

MM32F327x_Samples\LibSamples\FSMC\FSMC_Ex6800OLED\ 中的 FSMC_Ex6800OLED.uvprojx

样例展示如何初始化OLED接口与实现OLED并行驱动显示。

软件分为两个部分:

(1)FSMC接口GPIO与FSMC接口参数初始化

(2)OLED显示初始化与OLED显示

FSMC接口GPIO与FSMC接口参数初始化

void FSMC_Ex6800OLED_Demo(void)
{
    u8 t = 0;
    u8* p = (u8*)0x60080000;

    OLED_nRST_Pin_Config();
    OLED_GPIO_Init();
    FSMC_Init_6800();
    OLED_nRST_Setting();
    OLED_Init();
    //其他用户代码
}

① 在OLED_GPIO_Init ()中实现OLED对应IO初始化

包括OLED对应nRST引脚,背光控制引脚,FSMC相关的片选,读写,E信号,数据/命令,数据D0~D7引脚的初始化。

② 在FSMC_Init_6800 ()中实现FSMC功能配置初始化

A. 写操作周期

B. 单个bit数据写入保持时间

C. 写操作时,地址线的建立时间

D. 读操作周期长度设置

E. 存储器数据总线位宽

F. 模式选择:6800模式

G. 外接设备的内存大小

void FSMC_Init_6800(void)
{
    FSMC_InitTypeDef  FSMC_InitStructure;
    FSMC_NORSRAM_Bank_InitTypeDef  FSMC_BankInitStructure;

    FSMC_NORSRAM_BankStructInit(&FSMC_BankInitStructure);
    FSMC_NORSRAMStructInit(&FSMC_InitStructure);

    RCC_AHB3PeriphClockCmd(RCC_AHB3ENR_FSMC, ENABLE);

    FSMC_BankInitStructure.FSMC_SMReadPipe    = 0;
    FSMC_BankInitStructure.FSMC_ReadyMode     = 0;
    FSMC_BankInitStructure.FSMC_WritePeriod   = 7;
    FSMC_BankInitStructure.FSMC_WriteHoldTime = 0;
    FSMC_BankInitStructure.FSMC_AddrSetTime   = 1;
    FSMC_BankInitStructure.FSMC_ReadPeriod    = 9;
    FSMC_BankInitStructure.FSMC_DataWidth     = FSMC_DataWidth_16bits;
    FSMC_NORSRAM_Bank_Init(&FSMC_BankInitStructure, FSMC_NORSRAM_BANK0);

    FSMC_InitStructure.FSMC_Mode = FSMC_Mode_6800;
    FSMC_InitStructure.FSMC_TimingRegSelect = FSMC_TimingRegSelect_0;
    FSMC_InitStructure.FSMC_MemSize = FSMC_MemSize_64MB;
    FSMC_InitStructure.FSMC_MemType = FSMC_MemType_NorSRAM;
    FSMC_InitStructure.FSMC_AddrDataMode = FSMC_AddrDataMUX;


    FSMC_NORSRAMInit(&FSMC_InitStructure);
}

OLED显示初始化

Bank0地址为0x60000000,0x80000=(0x01 << 19)则是地址线A18的偏移量。首先完成写OLED_WR_REG和OLED_WR_DATA驱动:

void OLED_WR_DATA(u16 data)
{
    *(vu16*)0x60000000 = data;
}
void OLED_WR_REG(u16 regval)
{
    *(vu16*)(0x60000000 | (0x01 << 19)) = regval;
}
void OLED_WR_Byte(u8 dat, u8 cmd)
{
    if(cmd) {
        OLED_WR_REG(dat);
    }
    else {
        OLED_WR_DATA(dat);
    }
}    

读DATA和REG是一样的操作,不同的是从相应地址读取数据。

通过OLED_Refresh_Gram()函数,可以实现数据的实时刷新。

void OLED_Refresh_Gram(void)
{
    u8 i, n;
    for(i = 0; i < 8; i++) {
        OLED_WR_Byte (0xb0 + i, OLED_CMD);  // Set page address (0~7)
        OLED_WR_Byte (0x00, OLED_CMD);      // Set display location - column low address
        OLED_WR_Byte (0x10, OLED_CMD);      // Set display location - column height address
        for(n = 0; n < 128; n++) {
            OLED_WR_Byte(OLED_GRAM[n][i], OLED_DATA);
        }
    }
} 

结合OLED_Fill和OLED_ShowChar函数可以实现OLED的显示填充与字符输出。

通过演示,观察到在OLED上显示出了MindMotion 的字符与年月日等打印信息。

Demo程序可登录MindMotion的官网下载MM32F3270 lib_Samples:

https://www.mindmotion.com.cn/products/mm32mcu/mm32f/mm32f_mainstream/mm32f3270/

工程路径如下:

~MM32F327x_Samples\LibSamples\FSMC\FSMC_Ex6800OLED。

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

围观 151

MM32系列微控制器为用户提供了丰富的选择,可适用于工业控制、智能家电、建筑安防、医疗设备以及消费类电子产品等多方位嵌入式系统设计。在某些应用中,需要较大容量的存储空间用于存储数据;这时可以通过SPI 外扩NOR Flash,NAND Flash, 或者通过SDIO扩展SD Card或TF-Card。但有些需要高速存储数据,上述方式还是不够快速,这时可以使用MM32F3270系列的FSMC来外扩并行NOR Flash来实现。

并行NOR Flash与并行SRAM和PSRAM的读写接口大部分相同,但NOR Flash的写入速度与SRAM和PSRAM比较,相对较慢,需要通过NWAIT 信号检查NOR Flash的操作状态,并做一些等待,相应的时序需要根据不同的NOR Flash芯片所规定的参数而做相应的设置即可。

本文接下来就使用MM32F3270外挂S29GL128P NOR Flash芯片来演示FSMC对NOR Flash芯片的设置与读写。

前文已经介绍了MM32F3270的FSMC的接口功能与特色。结合MM32F3270 的FSMC外部接口信号,可使用异步方式访问NOR Flash,可以选用复用或非复用方式扩展NOR Flash,还可以通过配置实现外扩8位总线或16位总线接口的NOR Flash。

“表1:FSMC控制器外部信号"
表1:FSMC控制器外部信号

MM32F3270系列MCU因为封装的原因,导致只有部分MCU产品可以通过硬件复用出全部或部分的FSMC接口的相关GPIO;外扩NOR Flash也只有使用 LQFP144引脚封装MCU芯片才能支持连接地址数据非复用和复用方式外扩并行NOR Flash;而LQFP100引脚封装芯片因地址线缩减,仅支持连接地址数据复用方式外扩并行NOR Flash。LQFP64因为无法引出足够的地址与数据总线,同样不支持外扩并行NOR Flash。

“表2:MM32F3270不同封装芯片与NOR
表2:MM32F3270不同封装芯片与NOR Flash接口

目前市场上非复用型16位数据总线接口的NOR Flash也是较为普遍,下面针对非复用方式,介绍MCU与NOR Flash的硬件原理图设计和软件寄存器配置。

在此用MM32F3270的FSMC接口扩展S29GL128P NOR Flash,其原理框图如下:

“图1:NOR
图1:NOR Flash原理框图

S29GL128P的数据按 16 位的Half Word寻址,容量128M Bit, 16M字节,从芯片手册中可以查询到S29GL128P的引脚功能描述如下:

“表3:NOR
表3:NOR Flash引脚信号

S29GL128P可以通过CS, OE, WR, WP#, RY/BY#控制电路,结合Address与Data I/O实现对NOR Flash的读写操作。

1、FSMC非复用方式控制NOR Flash的硬件设计

“表4:NOR
表4:NOR Flash数据, 地址, 读写信号与MCU接口的引脚说明

外部设备地址映像从FSMC的角度看,FSMC外扩寻址空间用于访问最多4个FSMC地址映射空间,可以用于访问4个NOR闪存或SRAM/PSRAM存储设备,并对应的有4个专用的片选FSMC_NE[4:1]。

外部存储器划分为固定大小为64M字节的四个存储块,见下图。

“使用MM32F3270

存储区块与片选信号对应关系:

“使用MM32F3270

HADDR是需要转换到外部存储器的内部AHB地址线。HADDR[25:0]包含外部存储器地址。HADDR是字节地址,而存储器访问不都是按字节访问,因此接到存储器的地址线依存储器的数据宽度有所不同,如下表:

“使用MM32F3270

对于16位宽度的外部存储器,FSMC将在内部使用HADDR[25:1]产生外部存储器的地址FSMC_A[24:0]。不论外部存储器的宽度是多少(16位或8位),FSMC_A[0]始终应该连到外部存储器的地址线A[0]。

根据外部NOR Flash设计原理图:

“使用MM32F3270

2、FSMC非复用方式控制NOR Flash的程序设计

根据配置的接口电路配置GPIO初始化程序与FSMC初始化程序。

void FSMC_NOR_Init(void)
{
    FSMC_InitTypeDef  FSMC_InitStructure;
    FSMC_NORSRAM_Bank_InitTypeDef  FSMC_BankInitStructure;

    FSMC_NORSRAM_BankStructInit(&FSMC_BankInitStructure);
    FSMC_NORSRAMStructInit(&FSMC_InitStructure);
    RCC_AHB3PeriphClockCmd(RCC_AHB3ENR_FSMC, ENABLE);

    FSMC_BankInitStructure.FSMC_SMReadPipe    = 0;
    FSMC_BankInitStructure.FSMC_ReadyMode     = 0;
    FSMC_BankInitStructure.FSMC_WritePeriod   = 15;
    FSMC_BankInitStructure.FSMC_WriteHoldTime = 3;
    FSMC_BankInitStructure.FSMC_AddrSetTime   = 3;
    FSMC_BankInitStructure.FSMC_ReadPeriod    = 15;
    FSMC_BankInitStructure.FSMC_DataWidth     = FSMC_DataWidth_16bits;
    FSMC_NORSRAM_Bank_Init(&FSMC_BankInitStructure, \
    FSMC_NORSRAM_BANK1);

    FSMC_InitStructure.FSMC_Mode = FSMC_Mode_NorFlash;
    FSMC_InitStructure.FSMC_TimingRegSelect = FSMC_TimingRegSelect_0;
    FSMC_InitStructure.FSMC_MemSize = FSMC_MemSize_64MB;
    FSMC_InitStructure.FSMC_MemType = FSMC_MemType_FLASH;
    FSMC_InitStructure.FSMC_AddrDataMode = FSMC_AddrDataDeMUX;
    FSMC_NORSRAMInit(&FSMC_InitStructure);
}

GPIO初始化

void FSMC_GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_StructInit(&GPIO_InitStructure);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOB | RCC_AHBENR_GPIOC | \
    RCC_AHBENR_GPIOA | RCC_AHBENR_GPIOD | RCC_AHBENR_GPIOE | \
    RCC_AHBENR_GPIOF | RCC_AHBENR_GPIOG, ENABLE);

    GPIO_PinAFConfig(GPIOD, GPIO_PinSource4, GPIO_AF_12);  //NOE
    GPIO_PinAFConfig(GPIOD, GPIO_PinSource5, GPIO_AF_12);  //NWE
    GPIO_PinAFConfig(GPIOD, GPIO_PinSource6, GPIO_AF_12);  //NWAIT
    GPIO_PinAFConfig(GPIOD, GPIO_PinSource11, GPIO_AF_12); //A16
    GPIO_PinAFConfig(GPIOD, GPIO_PinSource12, GPIO_AF_12); //A17
    GPIO_PinAFConfig(GPIOD, GPIO_PinSource13, GPIO_AF_12); //A18
    GPIO_PinAFConfig(GPIOD, GPIO_PinSource14, GPIO_AF_12); //D0
    GPIO_PinAFConfig(GPIOD, GPIO_PinSource15, GPIO_AF_12); //D1
    //省略部分代码
    GPIO_PinAFConfig(GPIOF, GPIO_PinSource0, GPIO_AF_12);  //A0
    GPIO_PinAFConfig(GPIOF, GPIO_PinSource1, GPIO_AF_12);  //A1
    GPIO_PinAFConfig(GPIOF, GPIO_PinSource2, GPIO_AF_12);  //A2
    GPIO_PinAFConfig(GPIOF, GPIO_PinSource3, GPIO_AF_12);  //A3
    //省略部分代码
    GPIO_InitStructure.GPIO_Pin  =  GPIO_Pin_All;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOD, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin  =  GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_FLOATING;
    GPIO_Init(GPIOD, &GPIO_InitStructure);

    //省略部分代码

}

从选择的片选信号与FSMC外扩存储映像空间可以得出Bank2地址为0x64000000,使用该地址作为读写外部NOR Flash的基地址。

#define NOR_FLASH_START_ADDR       ((u32)0x64000000)
#define NOR_FLASH_END_ADDR         ((u32)0x67FFFFFF)
//读一个半字
u16 FSMC_NOR_ReadHalfWord(u32 ReadAddr)
{
    NOR_WRITE(ADDR_SHIFT(0x00555), 0x00AA);
    NOR_WRITE(ADDR_SHIFT(0x002AA), 0x0055);
    NOR_WRITE((NOR_FLASH_START_ADDR + ReadAddr), 0x00F0 );
/* exit autoselect (write reset command) */
    return (*(vu16*)((NOR_FLASH_START_ADDR + ReadAddr)));
}
//连续读一块半字数据
void FSMC_NOR_ReadBuffer(u16* pBuffer, u32 ReadAddr, u32 NumHalfwordToRead)
{
    NOR_WRITE(ADDR_SHIFT(0x0555), 0x00AA);
    NOR_WRITE(ADDR_SHIFT(0x02AA), 0x0055);
    NOR_WRITE((NOR_FLASH_START_ADDR + ReadAddr), 0x00F0);
/* exit autoselect (write reset command) */
    for(; NumHalfwordToRead != 0x00; NumHalfwordToRead--) {
        // Read a Halfword from the NOR
        *pBuffer++ = *(vu16*)((NOR_FLASH_START_ADDR + ReadAddr));
        ReadAddr = ReadAddr + 2;
    }
}

读写外部NOR Flash与读写外部SRAM的操作,地址寻址方式是一样的,但NOR Flash的写数据有较大的不同。

以单个Word编程为例,如下为写单个Word的流程图与实现代码:

“使用MM32F3270
NOR_Status FSMC_NOR_WriteHalfWord(u32 WriteAddr, u16 Data)
{
    NOR_WRITE(ADDR_SHIFT(0x0555), 0x00AA);
    NOR_WRITE(ADDR_SHIFT(0x02AA), 0x0055);
    NOR_WRITE(ADDR_SHIFT(0x0555), 0x00A0);
    NOR_WRITE((NOR_FLASH_START_ADDR + WriteAddr), Data);

    return (FSMC_NOR_GetStatus(Program_Timeout));
}

通过MindMotion的官网下载MM32F3270 lib_Samples:

https://www.mindmotion.com.cn/products/mm32mcu/mm32f/mm32f_mainstream/mm32f3270/

工程路径如下:

~MM32F327x_Samples\LibSamples\FSMC\FSMC_NOR\

可以看到详细的样例与功能操作。

下章的题目为《使用MM32F3270 的FSMC驱动外部OLED》讲解通过FSMC外扩并口OLED的实现。

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

围观 37

MM32系列微控制器为用户提供了丰富的选择,可适用于工业控制、智能家电、建筑安防、医疗设备以及消费类电子产品等多方位嵌入式系统设计。在一些应用中,使用MM32F3270系列的片内SRAM还不够支持应用的需要,就要用外扩SRAM/PSRAM的方式来扩展。这时可以采用MM32F3270片内的FSMC接口来扩展SRAM/PSRAM。

因为SRAM和PSRAM的异步读写接口完全相同,只是时序方面需要根据不同的芯片所规定的参数不同而做相关的设置即可。本文接下来就使用SRAM来代指对SRAM和PSRAM芯片的支持。

前文已经介绍了MM32F3270的FMSC的接口功能与特色。结合MM32F3270 的FMSC外部接口信号,可使用异步方式访问SRAM,可以选用复用或非复用方式扩展SRAM,还可以通过配置实现外扩8位总线或16位总线接口的SRAM。

“表1、FSMC控制器外部信号"
表1、FSMC控制器外部信号

MM32F3270系列MCU因为封装的原因,导致只有部分MCU产品可以通过硬件复用出全部或部分的FSMC接口的相关GPIO,才能支持外接SRAM存储器扩展存储空间。其中LQFP144引脚封装MCU芯片支持连接地址数据非复用和复用方式外扩并行SRAM;而LQFP100引脚封装芯片因地址线缩减,仅支持连接地址数据复用方式外扩并行SRAM。LQFP64因为无法引出足够的地址与数据总线,不支持外扩并行SRAM。

“表2、MM32F3270不同封装芯片与SRAM接口"
表2、MM32F3270不同封装芯片与SRAM接口

目前市场上非复用型16位数据总线接口的SRAM/PSRAM较为普遍,下面就非复用方式,介绍MCU与SRAM的硬件原理图设计和软件寄存器配置。

在此用MM32F3270扩展ISSI的SRAM :IS62WV51216,其原理框图如下:

“图1、SRAM原理框图"
图1、SRAM原理框图

“表3、SRAM引脚信号"
表3、SRAM引脚信号

IS62WV51216的数据按 16 位的Half Word寻址,容量1M字节。IS62WV51216可以通过CS, OE, WR, UB, LB控制电路,结合Address与Data I/O实现数据的高速读写。

01、FSMC非复用方式控制SRAM的硬件设计

“表4、SRAM信号对应的电源、复位与MCU接口的引脚说明"
表4、SRAM信号对应的电源、复位与MCU接口的引脚说明

外部设备地址映像从FSMC的角度看,FMSC外扩寻址空间用于访问最多4个FSMC地址映射空间,可以用于访问4个NOR闪存或SRAM/PSRAM存储设备,并对应的有4个专用的片选FSMC_NE[4:1]。

外部存储器划分为固定大小为64M字节的四个存储块,见下图。

“使用MM32F3270

存储区块与片选信号对应关系:

“使用MM32F3270

HADDR是需要转换到外部存储器的内部AHB地址线。HADDR[25:0]包含外部存储器地址。HADDR是字节地址,而存储器访问不都是按字节访问,因此接到存储器的地址线依存储器的数据宽度有所不同,如下表:

“使用MM32F3270

对于16位宽度的外部存储器,FSMC将在内部使用HADDR[25:1]产生外部存储器的地址FSMC_A[24:0]。不论外部存储器的宽度是多少(16位或8位),FSMC_A[0]始终应该连到外部存储器的地址线A[0]。

根据外部SRAM设计原理图:

“使用MM32F3270

02、FSMC非复用方式控制SRAM的硬件设计

根据配置的接口电路配置GPIO初始化程序与FSMC初始化程序。

void FSMC_SRAM_Init(void)
{
    FSMC_InitTypeDef  FSMC_InitStructure;
FSMC_NORSRAM_Bank_InitTypeDef  FSMC_BankInitStructure;

    FSMC_NORSRAM_BankStructInit(&FSMC_BankInitStructure);
    FSMC_NORSRAMStructInit(&FSMC_InitStructure);
RCC_AHB3PeriphClockCmd(RCC_AHB3ENR_FSMC, ENABLE);

    FSMC_BankInitStructure.FSMC_SMReadPipe    = 0;
    FSMC_BankInitStructure.FSMC_ReadyMode     = 0;
    FSMC_BankInitStructure.FSMC_WritePeriod   = 7;
    FSMC_BankInitStructure.FSMC_WriteHoldTime = 0;
    FSMC_BankInitStructure.FSMC_AddrSetTime   = 1;
    FSMC_BankInitStructure.FSMC_ReadPeriod    = 9;
    FSMC_BankInitStructure.FSMC_DataWidth     = FSMC_DataWidth_16bits;
FSMC_NORSRAM_Bank_Init(&FSMC_BankInitStructure, \ 
FSMC_NORSRAM_BANK1);
    FSMC_InitStructure.FSMC_Mode = FSMC_Mode_NorFlash;
    FSMC_InitStructure.FSMC_TimingRegSelect = FSMC_TimingRegSelect_0;
    FSMC_InitStructure.FSMC_MemSize = FSMC_MemSize_64MB;
    FSMC_InitStructure.FSMC_MemType = FSMC_MemType_NorSRAM;
    FSMC_InitStructure.FSMC_AddrDataMode = FSMC_AddrDataDeMUX;
    FSMC_NORSRAMInit(&FSMC_InitStructure);
}

GPIO初始化

void SRAM_PIN_GPIO_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOB | RCC_AHBENR_GPIOC | \
RCC_AHBENR_GPIOA | RCC_AHBENR_GPIOD | RCC_AHBENR_GPIOE | \
RCC_AHBENR_GPIOF | RCC_AHBENR_GPIOG, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
GPIO_PinAFConfig(GPIOD, GPIO_PinSource0, GPIO_AF_12); //DA2
GPIO_PinAFConfig(GPIOD, GPIO_PinSource1, GPIO_AF_12); //DA3
// ……部分代码请参考样例程序
    GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
 }

从选择的片选信号与FSMC外扩存储映像空间可以得出Bank1地址为0x64000000,使用该地址作为读写外部SRAM的基地址。

#define Bank1_SRAM3_ADDR    ((uint32_t)(0x64000000))  //used NE2  PG9

p = (vu16*)Bank1_SRAM3_ADDR;
for (i = 0x00; i < BUFFERLEN; i++) {        
   *p++ = (u16)writebuffer[i];        
}
p = (vu16*)Bank1_SRAM3_ADDR;
for (i = 0x00; i < BUFFERLEN; i++) {        
    readbbuffer[i] = *p++;
}

读写外部SRAM与读写片内SRAM是一样的操作,不同的是从不同的对应地址读写数据。通过上述代码可以观察到写入数据与读出的数据相同,表明配置准确,Demo实验成功。

Demo程序可登录MindMotion的官网下载MM32F3270 lib_Samples:

https://www.mindmotion.com.cn/products/mm32mcu/mm32f/mm32f_mainstream/mm32f3270/

工程路径如下:

~MM32F327x_Samples\LibSamples\FSMC\FSMC_SRAM\

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

围观 19

交互式界面被越来越多地集成于多种应用中,例如医疗设备、过程控制、手机和其它手持设备。这些界面主要基于使用彩色LCD的图形HMI(人机界面)。对TFT-LCD的需求在全世界范围内极大增长。本文介绍了如何使用 MM32F3270的FSMC(灵活的静态存储控制器)来驱动8080接口的TFT-LCD。

01、MM32F3270 FSMC的简要介绍

FSMC是Flexible static memory controller(灵活的静态存储控制器)的简称,能够与异步存储器和 LCD等并行外设相连。MM32F3270 的 FSMC支持并行接口的SRAM、PSRAM 、NOR FLASH 和TFT-LCD。

“图1
图1 FSMC的功能框图

02、FSMC 的功能特性

MM32的FSMC具有以下特性:

1) 可配置的静态存储器接口包括:

a) SRAM

b) PSRAM

c) NOR FLASH

2) 支持 Intel 8080 协议

3) 支持 moto 6800 协议

4) 8位,16位,32位可配置的数据总线宽度,支持非复用与复用模式

5) BANK1 分为 4 块子 BANK,每块 64Mbit 空间

6) 时序可编程以满足不同的需求

a) 等待周期可编程

b) 总线恢复周期可编程

c) 写,读控制周期可编程

7) 可将32位的AHB访问请求,转换为对外接设备连续的8位,16位的访问

MM32F3270的FSMC提供了对多个并行外设的控制与连接,具体配置取决于存储器类型,主要涉及如下寄存器设置。

01、SMCTLR 的 sm_data_width[2:0],定义了外部存储器的数据宽度,需根据实际数据宽度配置为8位,16位,32 位,此时需要保障实现数据传输的一致性。

02、SMCTLR的sm_data_width_set0/1/2 来设置存储器的数据宽度,有三种情况:AHB 操作的数据宽度与存储器数据宽度相同,无数据传输一致性的问题;AHB 操作的数据宽度大于存储器的数据宽度时,AHB 接口将对 hwdata[15:0],hwdatabit[31:16]进行连续写操作,以适应外部设备的数据宽度,读操作时,hrdata[31:0]的低 16 位是有效数据;AHB 操作的数据宽度小于存储器的数据宽度时,若存储设备没有高低字节片选,不允许进行写操作,若存储设备有高低字节选择,通过 BL 控制访问对应字节。可以进行读操作,但有效数据需要用户自己处理。

03、SYSCFG_CFGR1[30:29]:mode_sel来配置不同模式,默认值为 01

00:兼容 NOR FLASH 接口
01:兼容 8080 协议接口
10:兼容 6800 协议接口

04、SMSKR0[10:8]用来选择三组不同的寄存器 register set0/set1/set2,以配置不同的时序

FMSC支持的外部接口

“表1
表1 FSMC控制器外部信号

03、FSMC 控制LCD的硬件设计

FSMC是如何控制TFTLCD的呢?

我们可以把TFTLCD当成 SRAM 设备使用:外部SRAM的控制一般有:地址线(如A0~A18)、数据线(如D0~D15)、写信号(WE)、读信号(OE)、片选信号(CS)。TFTLCD的信号我们包括:RS、D0~D15、WR、RD、CS、 RST和BL等,其中真正在操作LCD的时候需要用到的就只有:RS、D0~D15、WR、 RD 和 CS。其操作时序和 SRAM的控制完全类似,唯一不同就是 TFT-LCD 有 RS 信号,但是没有地址信号。TFT-LCD通过RS信号来决定传送的数据是数据还是命令,本质上可以理解为一个地址信号,比如MB039是把RS接在A18上面,那么当FSMC控制器写地址0的时候,会使得A18 变为0,对TFT-LCD来说,就是写命令。而FSMC写地址1的时候,A0 将会变为1,对TFT-LCD来说,就是写数据。这样,就把数据和命令区分开了,其实就是对应 SRAM 操作的两个连续地址。当然RS也可以接在其他地址线上,MB039是把RS连接在PD13上面的。MM32F3270的FSMC支持8/16/32位数据宽度,我们这里用到的LCD是16位宽度的,在设置的时候需要选择16位宽。

FSMC 控制LCD 的Demo应用中,使用的开发板为MB-039,它支持外接MDM2802与MDM2803两种TFT-LCD (320x240 2.8’ 液晶显示屏)。

“图2
图2 MB-039实物效果图

下图是MB-039的FSMC与TFT-LCD的接口原理图部分,完整原理图可以通过MM32官网下载。

“图3
图3 TFT-LCD接口原理图

各个信号作用对应如下:

“表2
表2 LCD信号对应的电源、复位与MCU接口的引脚说明

04、FSMC 控制LCD的软件设计

FMSC Demo应用中,使在库函数样例工程中使用选用:

FSMC_Ex8080TFTLCD.uvprojx

实验展示如何初始化LCD接口与实现LCD并行驱动显示。

软件分为两个部分:

01)FSMC接口GPIO与FSMC接口参数初始化

02)LCD显示初始化与LCD显示

FSMC接口GPIO与FSMC接口参数初始化

void BSP_LCD_Configure()
{
    initGPIO_LCD();
    initFSMC();
    LCDC_Init_Reg();
    lcdFillColor(Black);
    lcdBlcH();
}

① 在initGPIO_LCD()中实现LCD对应IO初始化

包括LCD对应nRST引脚,背光控制引脚,FSMC相关的片选,读写,数据/命令,数据D0~D15引脚的初始化。

② 在initFSMC()中实现FSMC功能配置初始化

A. 写操作周期

B. 单个bit数据写入保持时间

C. 写操作时,地址线的建立时间

D. 读操作周期长度设置

E. 存储器数据总线位宽

F. 式选择:8080模式

G. 外接设备的内存大小

void initFSMC(void)
{
    FSMC_InitTypeDef                FSMC_InitStructure;
    FSMC_NORSRAM_Bank_InitTypeDef   FSMC_BankInitStructure;

    RCC_AHB3PeriphClockCmd(RCC_AHB3ENR_FSMC, ENABLE);

    FSMC_BankInitStructure.FSMC_SMReadPipe    = 0;
    FSMC_BankInitStructure.FSMC_ReadyMode     = 0;
    FSMC_BankInitStructure.FSMC_WritePeriod   = 0x2;
    FSMC_BankInitStructure.FSMC_WriteHoldTime = 1;
    FSMC_BankInitStructure.FSMC_AddrSetTime   = 3;
    FSMC_BankInitStructure.FSMC_ReadPeriod    = 0x1;
    FSMC_BankInitStructure.FSMC_DataWidth     = FSMC_DataWidth_16bits;
    FSMC_NORSRAM_Bank_Init(&FSMC_BankInitStructure, FSMC_NORSRAM_BANK0);

    FSMC_InitStructure.FSMC_Mode = FSMC_Mode_8080;
    FSMC_InitStructure.FSMC_TimingRegSelect = FSMC_TimingRegSelect_0;
    FSMC_InitStructure.FSMC_MemSize = FSMC_MemSize_64MB;
    FSMC_InitStructure.FSMC_MemType = FSMC_MemType_NorSRAM;
    FSMC_InitStructure.FSMC_AddrDataMode = FSMC_AddrDataMUX;
    FSMC_NORSRAMInit(&FSMC_InitStructure);
}

LCD显示初始化

Bank0地址为0x60000000,0x80000=(0x01 << 19)则是地址线A18的偏移量。首先完成写CMD和DATA驱动:

void lcdCmd(u8 cmd)
{
    *(u16*)(0x60000000) = cmd;
}

////////////////////////////////////////////////////////////////////////////////
void lcdData(u8 dat)
{
    *(u16*)(0x60000000 | (0x01 << 19)) = dat;
}

////////////////////////////////////////////////////////////////////////////////
void lcdData16(u16 dat)
{
    *(u16*)(0x60000000 | (0x01<< 19)) = dat;
}     

读CMD和REG也是一样的操作,不同的是从相应地址读取数据。

01)在LCDC_Init_Reg ()中调用上述3种函数实现LCD对应驱动芯片中寄存器器的初始设置

02)在lcdFillColor(Black); & lcdBlcH();中实现配置LCD的初始显示页面为全黑色和打开背光

LCD驱动显示

LCD的画点流程都可以概括为:设置坐标→写入GRAM指令→写入颜色;

LCD的读点的流程可以概括为:设置坐标→读取GRAM指令→读取颜色。

通过画点的操作到画方块、线、圆、字符等功能。

该Demo中通过Systick定时刷新要显示的数据,实现了LCD的功能演示。

void randRefresh()
{
    u16 x, y, w, h, c;
    drawSquare(dx,  dy,  dw, dh, SPACE, NUL);
    if (drawBlockCnt++ % 2) {
        x = rand();
        x %= (dw - 2);
        y = rand();
        y %= (dh - 2);
        w = rand();
        w %= DMAX;
        h = rand();
        h %= DMAX;
        c = rand();
        c &= 0x0f;
        if ((x + w) > (dw - 2)) x = dw - w - 2;
        if ((y + h) > (dh - 2)) y = dh - h - 2;
        drawRec (x + dx + 1, y + dy + 1, w, h, getColor(c));
    }
    else {
        c = rand();
        c &= 0x0f;
        drawRec (dx + 1, dy + 1, dw - 2, dh - 2, getColor(c));
    }
}

将程序下载进入板子我们可以观察到,TFTLCD上显示出了下列MindMotion Logo:

“图4
图4 TFT-LCD接口显示Logo图

还可以观察到屏幕快速画出不同的颜色方框,表明实验成功。

Demo程序可登录MindMotion的官网下载MM32F3270 lib_Samples:

https://www.mindmotion.com.cn/products/mm32mcu/mm32f/mm32f_mainstream/mm32f3270/

工程路径如下:

~MM32F327x_Samples\LibSamples\FSMC\FSMC_Ex8080TFT-LCD

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

围观 69

HTTP(Hyper Text Transfer Protocol超文本传输协议)是用于从万维网服务器传输超文本到本地浏览器的传输协议,它基于TCP/IP协议通信,因此也是基于<客户端-服务器>模型运作的。HTTP属于应用层协议,我们可以用它来传输服务器的各种资源,如文本、图片、音频等,具有简单、快捷、灵活、无连接、无状态等优点。

在一次完整的HTTP通信过程中,浏览器与服务器之间大致可以分为七个步骤:

01、建立TCP连接

HTTP工作前,浏览器首先要通过网络与服务器建立连接,该连接通过TCP完成。HTTP属于应用层协议,根据规则只有低层协议建立之后才能进行更深层协议的连接。因此,首先要建立TCP连接,端口号为80。

02、浏览器向服务器发送请求命令

TCP连接成功建立后,浏览器就会向服务器发送请求命令。例如:GET/sample/hello.jsp HTTP/1.1

03、浏览器发送请求头信息

浏览器发送请求命令后,还要以头信息的形式向服务器发送一些别的信息,之后浏览器发送了一空白行来通知服务器,它已经结束了该头信息的发送。

04、服务器应答

客户机向服务器发出请求后,服务器会客户机回送应答,例如:HTTP/1.1 200 OK

应答的第一部分是协议的版本号和应答状态码。

05、服务器发送应答头信息

正像客户端会随同请求发送关于自身的信息一样,服务器也会随同应答向用户发送关于它自己的数据及被请求的文档。

06、服务器向浏览器发送数据

服务器向浏览器发送头信息后,它会发送一个空白行来表示头信息的发送到此为结束,接着会按照应答头信息所描述的格式发送用户所请求的实际数据。

07、服务器关闭TCP连接

一般情况下,一旦服务器完成了数据,就会关闭TCP连接。

在创建工程前我们还需要了解HTTP请求格式,HTTP请求由三部分构成:请求方法URI协议、请求头、请求正文。

请求方法URI(URL相当于URI的子集)协议

我们使用前文中的例子“GET/sample.jsp HTTP/1.1”。“GET“代表请求方法,“/sample.jsp”表示URI,“HTTP/1.1代表协议和协议的版本。

请求头

请求头包含许多有关的客户端环境和请求正文的有用信息。例如,请求头可以声明浏览器所用的语言,请求正文的长度等。

请求正文

请求头和请求正文之间是一个空行,这个行非常重要,它表示请求头已经结束,接下来的是请求正文,请求正文中可以包含客户提交的查询字符串信息。

实验使用MB-039开发板,在工程中使用LwIP+FreeRTOS,实验展示如何实现HTTP服务器,实验使用到的硬件如下:

“基于MM32F3270

如图是MB-039(完整原理图可以通过MM32官网下载)的ETH部分。

各个信号引脚对应如下:

“基于MM32F3270

“基于MM32F3270

我们创建HTTP工程:

static void
http_server_netconn_thread(void* arg)
{
*********************************************************
 do {
        err = netconn_accept(conn, &newconn);     //(1)
        if (err == ERR_OK) {
            http_server_netconn_serve(newconn);    //(2)
            netconn_delete(newconn);            
        }
    } while(err == ERR_OK);
*********************************************************
}
static void http_server_netconn_serve(struct netconn* conn)
{
    struct netbuf* inbuf;
    char* buf;
    u16_t buflen;
err_t err;

    err = netconn_recv(conn, &inbuf);           //(3)
    if (err == ERR_OK) {
        netbuf_data(inbuf, (void**)&buf, &buflen);
        if (buflen >= 5 &&
                buf[0] == 'G' &&
                buf[1] == 'E' &&
                buf[2] == 'T' &&
                buf[3] == ' ' &&
                buf[4] == '/' ) {        //(4)

            netconn_write(conn, http_html_hdr, sizeof(http_html_hdr) - 1, NETCONN_NOCOPY);              //(5)

            netconn_write(conn, http_index_html, sizeof(http_index_html) - 1, NETCONN_NOCOPY);           //(6)
        }
    }

    netconn_close(conn);      //(7)

    netbuf_delete(inbuf);
}

01、等待连接请求

02、执行数据发送的主要函数,工程的主要部分

03、接收客户端(浏览器)发送的数据

04、通过前面五个字节判断是否为请求方法,我们只是为例简单的功能展示不需要去关注正文部分

05、发送应答头信息

06、发送网页信息(html)

07、关闭连接

到这里已经完成了工程的创建,看一下PC的IP地址,设备需要处于同一网段方便测试。打开命令行窗口输入:ipconfig

“基于MM32F3270

PC的地址为:192.168.105.34,在sys_arch.h文件中对DEST_IP_ADDR0 、DEST_IP_ADDR1、DEST_IP_ADDR2、DEST_IP_ADDR3进行修改,DEST_PORT 随意修改。

#define LOCAL_PORT                 2021

#define IP_ADDR0                    192
#define IP_ADDR1                    168
#define IP_ADDR2                    105
#define IP_ADDR3                    35

将程序下载入开发板中,打开浏览器,输入设备地址:192.168.105.35

“基于MM32F3270

(1)点击灵动微电子即可跳转到我们的官网

“基于MM32F3270

(2)我们按F12来查看网页的信息

“基于MM32F3270

关注画圈部分,点击灵动微电子可以执行跳转的原因是他的href属性指向的超链接目标的URL为我们的官网地址。同样,使图片显示的方式是类似的,我们先找到一张图片,将他的地址填入img src中,此时图片并非存储在MCU中,是通过网络访问读取的。

我们也可以使用Wireshark进行监视抓取整个过程,选择IP过滤ip.addr==192.168.105.35。

“基于MM32F3270

可以看到整个过程和前文介绍的步骤完全一致。

实验程序请登录我们的官网(https://www.mindmotion.com.cn/products/mm32mcu/mm32f/mm32f_mainstream/mm32f3270/)下载MM32F3270 SDK,工程路径如下:

~\MM32F3270_Lib_Samples_V0.90\Demo_app\Ethernet_Demo\ETH_RTOS\Freertos_http

到这里已经完成了以太网专题的介绍,在样例包中,还提供了其他的样例工程,如:mqtt_onenet、mqtt_baidu、Freertos_dns、Freertos_dhcp、TFTP等。以太网协议本身非常庞大,需要用户花费时间去研究。专题通过对六个简单实验的介绍,起到抛砖引玉的作用,更大的意义在于使刚接触以太网的用户有个着手点,实现更复杂的功能。

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

围观 39

前面重点对Client的创建方式及使用方式进行了介绍,本节通过Server实验对TCP通信过程进行一次介绍。

在TCP/IP协议中,传输层及以下层的机制是由内核提供的。应用层由用户提供,应用层程序对通信数据进行解析处理,传输层及以下层处理通信的细节(将数据从一端传入另外一端)。应用层数据通过协议栈发送到网络上时,每层协议都要增加一个数据部首(header),进行一次封装。其中不同的协议层对数据包有不同的称谓,在传输层叫段(segment),在网络层叫做数据报(datagram),在链路层称为帧(frame)。

“基于MM32F3270

“基于MM32F3270

在通信过程中,发送端执行以下动作:首先程序进行编码,确定通信的建立连接、发送数据的时间。接着建立TCP连接,TCP根据应用指示负责建立连接、发送数据及断开连接。TCP首部包括源端口号和目标端口号、序号和校验和,加完首部后数据包继续往下传递到IP层,IP层加上IP首部包括地址等信息用于寻址操作,之后将数据继续往下传递附加数据链路层首部。最后发送时的分组数据包会加上以太网包尾(用于循环冗余校验)。

主机端:收到数据包后会在以太网数据包中找到MAC地址,判断是否为自己的数据包,如果不是则丢弃。如果是传递给IP层处理,以此类推,不断往上传递到TCP层。在TCP层通过校验和判断数据是否损坏,然后检查是否按序号接收数据,最后检查端口号。处理完成这一切后数据包继续往上层发送,即应用层。如果出现主机空间已满等情况,主机则会发送“处理异常”通知发送端。

“基于MM32F3270

实验使用MB-039开发板,在工程中使用LwIP+FreeRTOS,实验展示如何制作一个TCP Server,并收发数据,实验使用到的硬件如下:

“基于MM32F3270

如图是MB-039(完整原理图可以通过MM32官网下载)的ETH部分。

各个信号引脚对应如下:

“基于MM32F3270

“基于MM32F3270

在进行Server实验前,我们先了解需要使用到的API:

1)netconn_bind ()

2)netconn_listen ()

3)netconn_accept ()

以下分API展开介绍:

01、netconn_bind ()

从源码中可以看出其主要功能:为conn(服务器端)绑定地址与端口号。

err_t netconn_bind(struct netconn* conn, const ip_addr_t* addr, u16_t port)
{
    API_MSG_VAR_DECLARE(msg);
    err_t err;

    LWIP_ERROR("netconn_bind: invalid conn", (conn != NULL), return ERR_ARG;);
#if LWIP_IPV4
    /* Don't propagate NULL pointer (IP_ADDR_ANY alias) to subsequent functions */
    if (addr == NULL) {
        addr = IP4_ADDR_ANY;
    }
#endif /* LWIP_IPV4 */
#if LWIP_IPV4 && LWIP_IPV6
    if ((netconn_get_ipv6only(conn) == 0) &&
            ip_addr_cmp(addr, IP6_ADDR_ANY)) {
        addr = IP_ANY_TYPE;
    }
#endif /* LWIP_IPV4 && LWIP_IPV6 */
    API_MSG_VAR_ALLOC(msg);
    API_MSG_VAR_REF(msg).conn = conn;
    API_MSG_VAR_REF(msg).msg.bc.ipaddr = API_MSG_VAR_REF(addr);
    API_MSG_VAR_REF(msg).msg.bc.port = port;
    err = netconn_apimsg(lwip_netconn_do_bind, &API_MSG_VAR_REF(msg));
    API_MSG_VAR_FREE(msg);
    return err;
}

02、netconn_listen ()

netconn_listen指向的函数是:netconn_listen_with_backlog,作用:使服务器进入监听状态,等待远端的连接请求。

err_t netconn_listen_with_backlog(struct netconn* conn, u8_t backlog)
{
#if LWIP_TCP
    API_MSG_VAR_DECLARE(msg);
    err_t err;

    /* This does no harm. If TCP_LISTEN_BACKLOG is off, backlog is unused. */
    LWIP_UNUSED_ARG(backlog);

    LWIP_ERROR("netconn_listen: invalid conn", (conn != NULL), return ERR_ARG;);

    API_MSG_VAR_ALLOC(msg);
    API_MSG_VAR_REF(msg).conn = conn;
#if TCP_LISTEN_BACKLOG
    API_MSG_VAR_REF(msg).msg.lb.backlog = backlog;
#endif /* TCP_LISTEN_BACKLOG */
    err = netconn_apimsg(lwip_netconn_do_listen, &API_MSG_VAR_REF(msg));
    API_MSG_VAR_FREE(msg);
    return err;
#else /* LWIP_TCP */
    LWIP_UNUSED_ARG(conn);
    LWIP_UNUSED_ARG(backlog);
    return ERR_ARG;
#endif /* LWIP_TCP */
}

03、netconn_accept ()

netconn_accept(代码较长,这里不进行粘贴)用于TCP服务器中,等待着远端主机的连接请求,并且建立一个新的TCP连接,在调用这个函数之前需要通过调用 listen()函数让服务器进入监听状态。accept()函数的调用会阻塞应用线程直至与远程主机建立TCP连接。参数addr是一个返回结果参数,它的值由accept()函数设置,其实就是远程主机的地址与端口号等信息,当新的连接已经建立后,远端主机的信息将保存在连接句柄中,能够标识连接对象。

了解了以上3个API,我们开始创建Server工程:

static void server(void* thread_param)
{
    struct netconn* conn, *newconn;
    err_t err;
    LWIP_UNUSED_ARG(arg);

#if LWIP_IPV6
    conn = netconn_new(NETCONN_TCP_IPV6);
    netconn_bind(conn, IP6_ADDR_ANY, LOCAL_PORT);         
#else /* LWIP_IPV6 */
    conn = netconn_new(NETCONN_TCP);               //①
    netconn_bind(conn, IP_ADDR_ANY, LOCAL_PORT);      //②
#endif /* LWIP_IPV6 */
    LWIP_ERROR("tcpecho: invalid conn", (conn != NULL), return;);

    printf("The local port number is%d\n\n", LOCAL_PORT);
    netconn_listen(conn);                              //③
    while (1) {
        err = netconn_accept(conn, &newconn);         //④
        if (err == ERR_OK) {
            struct netbuf* buf;
            void* data;
            u16_t len;

            while ((err = netconn_recv(newconn, &buf)) == ERR_OK) {    //⑤
                do {
                    netbuf_data(buf, &data, &len);
                    err = netconn_write(newconn, data, len, NETCONN_COPY);  //⑥
                } while (netbuf_next(buf) >= 0);
                netbuf_delete(buf);                 //⑦
            }
            netconn_close(newconn);         //⑧
            netconn_delete(newconn);        //⑨
        }
    }
}

1、申请一个连接结构,指定参数是NETCONN_TCP,即TCP连接

2、绑定本地的IP地址与端口号

3、使TCP服务器进入监听状态

4、处理客户端的连接请求,当只有当有客户端发送连接请求的时候才会处理,否则将进入阻塞态,而客户端的信息保存在newconn连接结构中

5、接收数据,并装填进buf

6、对接收的数据进行转发(指定为不拷贝方式NETCONN_COPY)

7、释放数据空间

8、主动关闭客户端的连接

9、释放newconn空间

到这里已经完成了工程的创建,看一下PC的IP地址,设备需要处于同一网段,以方便测试。

打开命令行窗口输入:ipconfig

“基于MM32F3270

PC的地址为:192.168.105.34,在sys_arch.h文件中对DEST_IP_ADDR0 、DEST_IP_ADDR1、DEST_IP_ADDR2、DEST_IP_ADDR3进行修改,DEST_PORT 随意修改。

#define LOCAL_PORT                 2021

#define IP_ADDR0                   192
#define IP_ADDR1                   168
#define IP_ADDR2                   105
#define IP_ADDR3                   21

将程序下载入开发板中,使用NetAssist进行如下设置:

1)协议设置,此时设备为Server,则PC为Client

2)设置远程主机地址(即设备地址)

3)端口号

“基于MM32F3270

点击连接,若提示连接失败,则Ping一下开发板地址,可以正常Ping通则检查端口号;如果无法Ping通则需要对工程进行检查。

“基于MM32F3270

任意输入字符进行发送。

“基于MM32F3270

通过上图可以观察到发送成功,并且设备返回数据与发送数据一致,表明实验成功。实验程序请登录我们的官网(https://www.mindmotion.com.cn/download.aspx?cid=2542&page=2)下载MM32F3270 SDK,工程路径如下:~\MM32F3270_Lib_Samples_V0.90\Demo_app\Ethernet_Demo\ETH_RTOS\Freertos_Server。

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

围观 45

前面介绍了基于Socket方式的以太网通讯,接下来给大家介绍基于TCP包的通讯。内容分为基于MM32F3270以太网Client的使用与基于MM32F3270以太网Server的使用。

首先,对TCP有个简单的介绍:

TCP是一种面向连接的、可靠的、基于字节流的传输层通信协议。即客户端和服务器之间在交换数据之前会先建立一个TCP连接,才能相互传输数据。并且提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一端传到另一端。

TCP优点:可靠、稳定,TCP的可靠体现在TCP在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制,在数据传完后,还会断开连接用来节约系统资源。

TCP的缺点:慢,效率低,占用系统资源高,易被攻击,TCP在传递数据之前,要先建连接,这会消耗时间,而且在数据传递时,确认机制、重传机制、拥塞控制机制等都会消耗大量的时间,而且要在每台设备上维护所有的传输连接,事实上,每个连接都会占用系统的CPU、内存等硬件资源。由于TCP存在确认机制和三次握手机制,这些是导致TCP容易被人利用,实现DOS、DDOS、CC等攻击。

接下来,介绍Client 的使用实现:

Demo使用MB-039开发板,在工程中使用LwIP+FreeRTOS,实验展示如何制作一个Client端,并发送数据,实验使用到的硬件如下:

“基于MM32F3270

如图是MB-039(完整原理图可以通过MM32官网下载)的ETH部分。

各个信号引脚对应如下:

“基于MM32F3270

“基于MM32F3270

在进行Client实验前,我们先了解需要使用到的API:

1)netconn_new ()

2)netconn_connect ()

3)netconn_write ()

每一个函数实现的功能:

01、netconn_new ()

netconn_new的功能为创建一个新的连接结构,结构类型可以为TCP/UDP其源码如下:

struct netconn*
netconn_new_with_proto_and_callback(enum netconn_type t, u8_t proto, netconn_callback callback)
{
    struct netconn* conn;
    API_MSG_VAR_DECLARE(msg);
    API_MSG_VAR_ALLOC_RETURN_NULL(msg);

    conn = netconn_alloc(t, callback);
    if (conn != NULL) {
        err_t err;

        API_MSG_VAR_REF(msg).msg.n.proto = proto;
        API_MSG_VAR_REF(msg).conn = conn;
        err = netconn_apimsg(lwip_netconn_do_newconn, &API_MSG_VAR_REF(msg));
        if (err != ERR_OK) {
            LWIP_ASSERT("freeing conn without freeing pcb", conn->pcb.tcp == NULL);
            LWIP_ASSERT("conn has no recvmbox", sys_mbox_valid(&conn->recvmbox));
#if LWIP_TCP
            LWIP_ASSERT("conn->acceptmbox shouldn't exist", !sys_mbox_valid(&conn->acceptmbox));
#endif /* LWIP_TCP */
#if !LWIP_NETCONN_SEM_PER_THREAD
            LWIP_ASSERT("conn has no op_completed", sys_sem_valid(&conn->op_completed));
            sys_sem_free(&conn->op_completed);
#endif /* !LWIP_NETCONN_SEM_PER_THREAD */
            sys_mbox_free(&conn->recvmbox);
            memp_free(MEMP_NETCONN, conn);
            API_MSG_VAR_FREE(msg);
            return NULL;
        }
    }
    API_MSG_VAR_FREE(msg);
    return conn;
}

从源码中可以看出,其功能为申请并初始化一个netconn结构体,同时在netconn_alloc函数中为conn变量创建一个接收邮箱(recvmbox),和一个信号量(conn->op_completed)。内存申请成功后使用netconn_apimsg函数构建一个消息,使用OS的系统邮箱发送给内核,请求以太网协议栈去执行lwip_netconn_do_newconn()函数,在执行时使用conn->op_completed进行信号量同步,任务处理完成后,释放一个信号量表示任务完成。

02、netconn_connect ()

netconn_connect作用为建立连接,在调用时将服务器端IP地址、端口号和本地的netconn结构绑定,源码如下:

err_t  netconn_connect(struct netconn* conn, const ip_addr_t* addr, u16_t port)
{
    API_MSG_VAR_DECLARE(msg);
    err_t err;

    LWIP_ERROR("netconn_connect: invalid conn", (conn != NULL), return ERR_ARG;);

#if LWIP_IPV4
    /* Don't propagate NULL pointer (IP_ADDR_ANY alias) to subsequent functions */
    if (addr == NULL) {
        addr = IP4_ADDR_ANY;
    }
#endif /* LWIP_IPV4 */

    API_MSG_VAR_ALLOC(msg);
    API_MSG_VAR_REF(msg).conn = conn;
    API_MSG_VAR_REF(msg).msg.bc.ipaddr = API_MSG_VAR_REF(addr);
    API_MSG_VAR_REF(msg).msg.bc.port = port;
    err = netconn_apimsg(lwip_netconn_do_connect, &API_MSG_VAR_REF(msg));
    API_MSG_VAR_FREE(msg);

    return err;
}

从源码中可以看出,其功能为使用netconn_apimsg创建一个消息,通过执行lwip_netconn_do_connect进行信号量的同步,将addr、port与conn进行绑定。

03、netconn_write ()

netconn_write()为处于稳定状态的TCP协议发送数据。TCP协议数据以数据流的方式传递,因此只需要知道地址、长度及需要发送的数据即可,其实际函数为netconn_write_vectors_partly(源码较长,就不贴出来了)。重点关注一下官方API文档对于apiflags参数的介绍:

* @param apiflags combination of following flags :
* - NETCONN_COPY: data will be copied into memory belonging to the stack
* - NETCONN_MORE: for TCP connection, PSH flag will be set on last segment sent
* - NETCONN_DONTBLOCK: only write the data if all data can be written at once

apiflags的值为NETCONN_COPY时,dataptr指针指向的数据将会被拷贝到为这些数据分配的内部缓冲区,在调用本函数之后可以直接对这些数据进行修改而不会影响数据,但是拷贝的过程是需要消耗系统资源的,CPU需要参与数据的拷贝,而且还会占用新的内存空间。

apiflags值为NETCONN_NOCOPY时,数据不会被拷贝而是直接使用dataptr指针来引用。但是这些数据在函数调用后不能立即被修改,因为这些数据可能会被放在当前TCP连接的重传队列中,以防对方未收到数据进行重传,而这段时间是不确定的。但是如果用户需要发送的数据在ROM中(静态数据),这样子就无需拷贝数据,直接引用数据即可。

apiflags值为NETCONN_MORE时,那么接收端在组装这些TCP报文段的时候,会将报文段首部的PSH标志置一,这些数据完成组装的时候,将会被立即递交给上层应用。

apiflags值为NETCONN_DONTBLOCK时,表示在内核发送缓冲区满的时候,再调用netconn_write()函数将不会被阻塞,而是会直接返回一个错误代码ERR_VAL告诉应用程序发送数据失败,应用程序可以自行处理这些数据,在适当的时候进行重传操作。

apiflags值为NETCONN_NOAUTORCVD时,表示在TCP协议接收到数据的时候,调用netconn_recv_data_tcp()函数的时候不会去更新接收窗口,只能由用户自己调用netconn_tcp_recvd()函数完成接收窗口的更新操作。

了解了以上3个API,我们开始创建Client工程:

static void client(void* thread_param)
{
    struct netconn* conn;
    int ret;
    ip4_addr_t ipaddr;
    uint8_t send_buf[] = "This is MM32F3270 TCP Client Demo\n";         //(1)
    while(1) {
        conn = netconn_new(NETCONN_TCP);                       //(2)
        if (conn == NULL) {                                       // (3)
            printf("create conn failed!\n");
            vTaskDelay(10);
            continue;
        }
        IP4_ADDR(&ipaddr, DEST_IP_ADDR0, DEST_IP_ADDR1, DEST_IP_ADDR2, DEST_IP_ADDR3);                                           // (4)
        ret = netconn_connect(conn, &ipaddr, DEST_PORT);      // (5)
        if (ret == -1) {
            printf("Connect failed!\n");
            netconn_close(conn);
            vTaskDelay(10);
            continue;
        }
        while (1) {
            ret = netconn_write(conn, send_buf, sizeof(send_buf), 0);  // (6)
            vTaskDelay(1000);
        }
    }

}

1)将需要发送的数据装填进send_buf中

2)申请一个内存区域,类型为TCP

3)如果conn为空表示申请内存失败

4)将地址赋值给ipaddr

5)创建连接,如果失败则删除conn

6)执行数据发送

到这里已经完成了工程的创建,但是还有一步比较重要的,配置我们的IP,将数据发送给服务器端,则需要知道服务器的地址。打开命令行窗口输入:ipconfig

“基于MM32F3270

PC地址为:192.168.105.34,在sys_arch.h文件中对DEST_IP_ADDR0 、DEST_IP_ADDR1、DEST_IP_ADDR2、DEST_IP_ADDR3进行修改,DEST_PORT随意修改,值得注意的同一个设备是如果创建多个网卡,PORT成不同的值即可,后面我们会进行这类实验,设备IP需要设置在同一个网段内通信才能进行IP_ADDR0、IP_ADDR1、IP_ADDR2,需要与PC地址保持一致,IP_ADDR3可以随意设置(和PC地址不一致即可)。

#define DEST_IP_ADDR0               192
#define DEST_IP_ADDR1               168
#define DEST_IP_ADDR2               105
#define DEST_IP_ADDR3               34

#define DEST_PORT                  5001

#define IP_ADDR0                    192
#define IP_ADDR1                    168
#define IP_ADDR2                    105
#define IP_ADDR3                    137

将程序下载入开发板中,使用SSCOM工具进行如下设置:

“”

点击侦听:

“基于MM32F3270

可以正常侦听并接收到数据,表明实验成功。实验程序请登录我们的官网(https://www.mindmotion.com.cn/download.aspx?cid=2542&page=2)下载MM32F3270 SDK,工程路径如下:

~\MM32F3270_Lib_Samples_V0.90\Demo_app\Ethernet_Demo\ETH_RTOS\Freertos_Client

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

围观 44

UDP(User Datagram Protocol用户数据报协议)是一种无连接、不可靠的协议。UDP协议没有流控制,没有应答确认机制,不能解决丢包、重发、错序问题。它只是简单地实现从一端主机到另一端主机的数据传输功能,数据通过IP层发送,在网络中传输,到达目标主机的顺序是无法预知的,因此需要应用程序对这些数据进行排序处理。在发送端,UDP只是把上层应用的数据封装到UDP报文中;在差错检测方面,仅仅是对数据进行了简单的校验,然后将其封装到IP数据报中发送出去。在接收端,无论是否收到数据,都不会产生一个应答返回送给源主机。如果接收到数据校验错误,接收端丢弃该UDP报文,也不会告诉源主机,这样数据是无法保障其准确性的。但是在如今的网络环境下,UDP协议传输出现错误的概率很小,并且它的实时性是非常好,常用于实时视频的传输,比如直播、网络电话等,即使是出现了数据丢失的情况,导致视频卡帧,也不会产生大的影响。UDP被广泛应用于与对传输速度有要求,并且可以容忍出现差错的数据传输中。

对于UDP通信来说,可以没有服务器,服务器只是一种规定。主动发送的一方为客户端,被动接受的一方为服务器(下图为通信过程,程序也是按照这个步骤进行设计)。

“基于MM32F3270以太网

实验使用MB-039开发板,在工程中使用LwIP+FreeRTOS,实验展示如何实现UDP数据收发,实验使用到的硬件如下:

“基于MM32F3270以太网

如图是MB-039(完整原理图可以通过MM32官网下载)的ETH部分接口电路。

各个信号引脚对应如下:

“基于MM32F3270以太网

“基于MM32F3270以太网

实验用到的API前面几节已经进行了讲解,我们直接创建UDP工程:

static void udpecho_thread(void* arg)
{
    struct netconn* conn;
    struct netbuf* buf;
    char buffer[4096];
    err_t err;
    LWIP_UNUSED_ARG(arg);

    conn = netconn_new(NETCONN_UDP);        // (1)
    netconn_bind(conn, IP_ADDR_ANY, LOCAL_PORT);   // (2)

    while (1) {
        err = netconn_recv(conn, &buf);          // (3)
        if (err == ERR_OK) {
            if(netbuf_copy(buf, buffer, sizeof(buffer)) != buf->p->tot_len) {   // (4)
                LWIP_DEBUGF(LWIP_DBG_ON, ("copy failed\n"));
            }
            else {
                buffer[buf->p->tot_len] = '\0';
                err = netconn_send(conn, buf);                 // (5)
                if(err != ERR_OK) {
                   LWIP_DEBUGF(LWIP_DBG_ON, ("netconn_send failed: %d\n", (int)err));
                }
                else {
                    LWIP_DEBUGF(LWIP_DBG_ON, ("got %s\n", buffer));
                }
            }
            netbuf_delete(buf);
        }
    }
}

1)创建一个UDP类型的连接结构

2)绑定IP地址与端口

3)接收客户端发送的数据(值得指出的是UDP可以一对一,也可以一对多以广播的形式发送)

4)拷贝出接收的数据

5)将数据发送给目标主机

到这里已经完成了工程的创建,看一下PC的IP地址,设备需要处于同一网段,以方便测试。打开命令行窗口输入:ipconfig

“基于MM32F3270以太网

PC的地址为:192.168.105.34,在sys_arch.h文件中对DEST_IP_ADDR0 、DEST_IP_ADDR1、DEST_IP_ADDR2、DEST_IP_ADDR3进行修改,DEST_PORT 随意修改。

#define LOCAL_PORT                 2021

#define IP_ADDR0                    192
#define IP_ADDR1                    168
#define IP_ADDR2                    105
#define IP_ADDR3                    35

将程序下载入开发板中,使用NetAssist进行如下设置:

1)协议设置,选择UDP

2)本机主机地址(即PC地址)

3)端口号

4)设置远程主机地址(即设备地址)

“基于MM32F3270以太网

点击打开,在进行这一步前可以则Ping一下开发板地址,可以正常Ping通则检查端口号;如果无法Ping通则需要对工程进行检查。任意输入字符进行发送。

“基于MM32F3270以太网

可以发现对接收到的数据进行了正确的转发,我们也可以使用Wireshark进行监视抓取整个过程,选择IP过滤:ip.addr==192.168.105.35。

“基于MM32F3270以太网

通过上图表明实验成功。

实验程序请登录我们的官网(https://www.mindmotion.com.cn/download.aspx?cid=2542&page=2)下载MM32F3270 SDK,工程路径如下:

~\MM32F3270_Lib_Samples_V0.90\Demo_app\Ethernet_Demo\ETH_RTOS\Freertos_UDP

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

围观 40

上一节我们对TCP的报文和连接过程做了介绍,本节通过Socket的方式对整个通信过程再次进行一次整理(使用Socket方式易于加深对以太网通信过程的理解,在此讲解使用的是完整版Socket)。

“基于MM32F3270

服务器端初始化

1)调用socket,向内核申请一个套接字sock

2)调用bind将sock与服务器端的IP与PORT绑定

3)调用listen将套接字设为监听模式,准备接收客户端连接请求

4)调用accept等待并接收客户端的连接请求,建立好TCP连接后,该函数会返回一个新的已连接套接字connected

创建连接

1)客户端调用socket创建套接字

2)调用connect,向服务器发送连接请求

3)connect会发送一个请求SYN段并阻塞等待服务器应答(第一次握手)

4)服务器收到SYN,会给客户端发送一个确认应答ACK,同时发送一个请求(SYN)建立连接(第二次握手)

5)客户端收到服务器发的SYN+ACK段,表明客户端连接已建立成功,进入已连接状态。客户端再向服务器发送一个ACK段,服务器收到后则服务器连接成功

数据传输

1)服务器端使用accept连接建立成功后(通信双方可同时写数据,支持全双工),调用read开始读数据,若没有数据则阻塞等待

2)客户端调用write向服务器发送数据请求,客户端收到之后调用read处理请求,此过程服务器调用read阻塞等待

3)服务器调用write将处理好的请求发送给客户端,再次调用read等待下一个请求

4)服务器收到SYN,会给客户端发送一个确认应答ACK,同时发送一个请求(SYN)建立连接(第二次握手)

4)客户端收到后从read返回,发送下一条请求,如此循环下去

断开连接

1)没有数据,则客户端调用close关闭连接,给服务器发送一个断开连接请求FIN段(第一次握手)

2)服务器收到客户端的FIN段,给客户端发送一个确认应答ACK段,表明同意断开连接。客户端收到ACK段并调用read返回0,表明客户端连接已经断开(第二次握手)

3)read返回0后,服务器知道客户端已经断开连接,它也调用close关闭连接,给客户端发送一个断开连接请求FIN段(第三次握手)

4)客户端收到服务器发送的FIN段,就给服务器一个确认应答ACK段,表明同意断开连接。客户端进入TIME_WAIT状态,服务器收到客户端的ACK段后也断开连接

“基于MM32F3270

实验使用MB-039开发板,在应用工程中使用LwIP+FreeRTOS,实验展示如何制作一个TCP Server_socket,并收发数据,实验使用到的硬件如下:

“基于MM32F3270

如图是MB-039(完整原理图可以通过MM32官网下载)的Ethermac部分。

各个信号引脚对应如下:

“基于MM32F3270

“基于MM32F3270

Server_socket实验用到的API大部分在前面已经进行讲解(只是对NETCONN接口编辑方式进行二次封装),本节只介绍一个比较关键的API:setsockopt(s,level,optname,opval,optlen)。

从名称中就可以看出函数功能用于设置套接字的一些选项,我们关注一下参数:

(1)level有多个常用的选项

SOL_SOCKET:表示在Socket层
IPPROTO_TCP:表示在TCP层
IPPROTO_IP:表示在IP层

(2)optname 表示该层的具体选项名称

level为SOL_SOCKET时,有以下选项:SO_REUSEADDR(允许重用本地地址和端口)、

SO_SNDTIMEO(设置发送数据超时时间)、SO_SNDTIMEO(设置接收数据超时时间)、SO_RCVBUF(设置发送数据缓冲区大小)等。

level为IPPROTO_TCP时,有以下选项:TCP_NODELAY(不使用Nagle算法)、TCP_KEEPALIVE(设置TCP保活时间)等。

level为IPPROTO_IP选项,有以下选项:IP_TTL(设置生存时间)、IP_TOS(设置服务类型)等。

实现Server_socket函数:

static void server_socket(void* thread_param)
{ 
    int sock = -1, connected;
    char* recv_data;
    struct sockaddr_in server_addr, client_addr;
    socklen_t sin_size;
    int recv_data_len;

    printf("The local port number is%d\n\n", LOCAL_PORT);
    recv_data = (char*)pvPortMalloc(RECV_DATA);
    if (recv_data == NULL) {
        printf("No memory\n");
        goto __exit;
    }
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        printf("Socket error\n");
        goto __exit;
    }
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(LOCAL_PORT);
    memset(&(server_addr.sin_zero), 0, sizeof(server_addr.sin_zero));
    if (bind(sock, (struct sockaddr*)&server_addr, sizeof(struct sockaddr)) == -1) {
        printf("Unable to bind\n");
        goto __exit;
    }
    if (listen(sock, 5) == -1) {                         // (1)
        printf("Listen error\n");
        goto __exit;
    }
    while(1) {
        sin_size = sizeof(struct sockaddr_in);
        connected = accept(sock, (struct sockaddr*)&client_addr, &sin_size);  // (2)
        printf("new client connected from (%s, %d)\n",
               inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
        {
            int flag = 1;
            setsockopt(connected,
                       IPPROTO_TCP,     /* set option at TCP level */
                       TCP_NODELAY,     /* name of option */
                       (void*) &flag,   /* the cast is historical cruft */
                       sizeof(int));    /* length of option value */      // (3)
        }
        while(1) {
            recv_data_len = recv(connected, recv_data, RECV_DATA, 0);    // (4)
            if (recv_data_len <= 0)
                break;
            printf("recv %d len data\n", recv_data_len);
            write(connected, recv_data, recv_data_len);   // (5)
        }
        if (connected >= 0)
            closesocket(connected);                  //  (6)
        connected = -1;
    }
__exit:
    if (sock >= 0) closesocket(sock);
    if (recv_data) free(recv_data);
}

1)进入监听状态

2)阻塞应用线程直至与远端主机建立TCP连接,建立成功后远程主机的信息将保持在连接句柄中(connected)

3)对套接字connected进行设置:在TCP层,不使用Nagle算法

4)处理客户端的连接请求,接收远程主机信息

5)将接收的数据进行转发

6)主动关闭客户端的连接

到这里已经完成了Server_socket函数的创建,看一下PC的IP地址,设备需要处于同一网段方便测试。打开命令行窗口输入:ipconfig

“基于MM32F3270

PC的地址为:192.168.105.34,在sys_arch.h文件中对DEST_IP_ADDR0 、DEST_IP_ADDR1、DEST_IP_ADDR2、DEST_IP_ADDR3进行修改,DEST_PORT 随意修改。

#define LOCAL_PORT                 2021

#define IP_ADDR0                    192
#define IP_ADDR1                    168
#define IP_ADDR2                    105
#define IP_ADDR3                    26

将程序下载入开发板中,使用NetAssist进行如下设置:

1)协议设置,此时设备为Server,则PC为Client

2)设置远程主机地址(即设备地址)

3)端口号

“基于MM32F3270

点击连接,若提示连接失败,则Ping一下开发板地址,可以正常Ping通则检查端口号;如果无法Ping通则需要对工程进行检查。

“基于MM32F3270

任意输入字符进行发送。

“基于MM32F3270

通过上图可以观察到发送成功,并且设备返回数据与发送数据一致,表明实验成功。

实验程序请登录我们的官网(http://www.mindmotion.com.cn/download.aspx?cid=2542&page=2)下载MM32F3270 SDK,工程路径如下:

~\MM32F3270_Lib_Samples_V0.90\Demo_app\Ethernet_Demo\ETH_RTOS\Freertos_Server_socket

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

围观 18

在进行本节之前,首先解决大家的一个疑惑点:Client和Client_Socket有什么区别或分别代表的含义?

Socket标准定义为套接字,应用于主流的网络设计程序,具有使用简单,多平台移植方便的特点。在Socket应用中,使用一个套接字来记录网络的一个连接,套接字是一个整数,就像操作文件一样,利用一个文件描述符,进行打开、读、写、关闭等操作。在网络中,可以对Socket 套接字进行类似的操作,比如开启一个网络的连接、读取连接主机发送来的数据、向连接的主机发送数据、终止连接等操作。LwIP设计目的主要应用于嵌入式平台,对于Socket的支持并不完全,只是通过对netconn进行封装实现部分功能,使得LwIP也具有多平台应用的特性,通过Socket方式的了解能够极大简化通信过程的理解,快速实现应用开发。

Demo应用中,使用的开发板为MB-039,在工程中使用LwIP+FreeRTOS,实验展示如何制作一个客户端并发送数据,板载Ethernet相关的硬件部分电路如下:

“基于MM32F3270

MB-039 完整原理图可以通过MM32官网下载。

各个信号引脚对应如下:

“基于MM32F3270

“基于MM32F3270

通过配置复用相关引脚为RMII相关的功能,初始化以太网功能,执行FreeRTOS的启动。具体过程可参考样例初始化程序中代码。

在进行Client_Socket实验前,我们先了解需要使用到的应用功能函数:

(1)socket ()

(2)connect ()

(3)write ()

(1) socket ()

Socket()指向lwip_socket(),功能为申请一个套接字,lwip_socket()源码如下:

int
lwip_socket(int domain, int type, int protocol)
{
  struct netconn *conn;
  int i;

  LWIP_UNUSED_ARG(domain); /* @todo: check this */

  /* create a netconn */
  switch (type) {
    case SOCK_RAW:
      conn = netconn_new_with_proto_and_callback(DOMAIN_TO_NETCONN_TYPE(domain, NETCONN_RAW),
             (u8_t)protocol, DEFAULT_SOCKET_EVENTCB);
      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_socket(%s, SOCK_RAW, %d) = ",
                                  domain == PF_INET ? "PF_INET" : "UNKNOWN", protocol));
      break;
    case SOCK_DGRAM:
      conn = netconn_new_with_callback(DOMAIN_TO_NETCONN_TYPE(domain,
                                       ((protocol == IPPROTO_UDPLITE) ? NETCONN_UDPLITE : NETCONN_UDP)),
                                       DEFAULT_SOCKET_EVENTCB);
      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_socket(%s, SOCK_DGRAM, %d) = ",
                                  domain == PF_INET ? "PF_INET" : "UNKNOWN", protocol));
#if LWIP_NETBUF_RECVINFO
      if (conn) {
        /* netconn layer enables pktinfo by default, sockets default to off */
        conn->flags &= ~NETCONN_FLAG_PKTINFO;
      }
#endif /* LWIP_NETBUF_RECVINFO */
      break;
    case SOCK_STREAM:
      conn = netconn_new_with_callback(DOMAIN_TO_NETCONN_TYPE(domain, NETCONN_TCP), DEFAULT_SOCKET_EVENTCB);
      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_socket(%s, SOCK_STREAM, %d) = ",
                                  domain == PF_INET ? "PF_INET" : "UNKNOWN", protocol));
      break;
    default:
      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_socket(%d, %d/UNKNOWN, %d) = -1\n",
                                  domain, type, protocol));
      set_errno(EINVAL);
      return -1;
  }

  if (!conn) {
    LWIP_DEBUGF(SOCKETS_DEBUG, ("-1 / ENOBUFS (could not create netconn)\n"));
    set_errno(ENOBUFS);
    return -1;
  }

  i = alloc_socket(conn, 0);

  if (i == -1) {
    netconn_delete(conn);
    set_errno(ENFILE);
    return -1;
  }
  conn->socket = i;
  done_socket(&sockets[i - LWIP_SOCKET_OFFSET]);
  LWIP_DEBUGF(SOCKETS_DEBUG, ("%d\n", i));
  set_errno(0);
  return i;
}

从源码中我们可以看出,本质上是对netconn_new()进行封装。我们关注一下其参数,domain表示协议簇,对于IP/TCP来说该值始终为AF_INET。重点需要关注一下type,我们查看API手册对于几种类型的解释如下:

1. SOCK_STREAM:提供可靠的(即能保证数据正确传送到对方)面向连接Socket服务,多用于资料(如文件)传输,如TCP协议。

2. SOCK_DGRAM:是提供无保障的面向消息的Socket服务,主要用于在网络上发广播信息,如UDP协议,提供无连接不可靠的数据报交付服务。

3. SOCK_RAW:表示原始套接字,它允许应用程序访问网络层的原始数据包,这个套接字用得比较少,暂时不用理会它。

Protocol指定套接字使用的协议,对于IPv4,TCP协议提供SOCK_STREAM服务,只有UDP协议提供SOCK_DGRAM服务。

(2) connect ()

connect()指向lwip_connect()(源码较长,就不进行粘贴了),函数的作用与前文介绍netconn_connect功能一致,通过源码可以知道其是通过对netconn_connect的封装实现。在TCP客户端连接中,调用这个函数将发生握手过程,并最终建立新的TCP连接。对于UDP来说调用这个函数只是在UDP控制块中记录远端IP地址与端口号。

(3) write ()

Write()指向lwip_write,源码如下,其通过调用lwip_send实现,flags为0。

ssize_t
lwip_write(int s, const void *data, size_t size)
{
  return lwip_send(s, data, size, 0);
}

了解了以上3个API,接下来开始创建Client_Socket工程:

static void client(void *thread_param)
{
  int sock = -1;
  struct sockaddr_in client_addr;  
  ip4_addr_t ipaddr;  
  uint8_t send_buf[]= " This is MM32F3270 TCP Client_Socket Demo \n";

  IP4_ADDR(&ipaddr,DEST_IP_ADDR0,DEST_IP_ADDR1,DEST_IP_ADDR2,DEST_IP_ADDR3);
  while(1)
  {
    sock = socket(AF_INET, SOCK_STREAM, 0);    //(1)
    if (sock < 0)
    {
      vTaskDelay(10);
      continue;
    } 

    client_addr.sin_family = AF_INET;           //(2)
    client_addr.sin_port = htons(DEST_PORT);   //(3)
    client_addr.sin_addr.s_addr = ipaddr.addr;   //(4)
    memset(&(client_addr.sin_zero), 0, sizeof(client_addr.sin_zero));    

    if (connect(sock, 
               (struct sockaddr *)&client_addr, 
                sizeof(struct sockaddr)) == -1)    //(5)
    {
        printf("Connect failed!\n");
        closesocket(sock);
        vTaskDelay(10);
        continue;
    }                                               
    while (1)
    {
      if(write(sock,send_buf,sizeof(send_buf)) < 0)   //(6)
        break; 
      vTaskDelay(1000);
    } 
    closesocket(sock);
  }
}

(1)申请一个套接字:socket

(2)协议簇类型(AF_INET用于TCP/IP协议)

(3)将端口赋值给client_addr的sin_port成员

(4)将地址赋值给client_addr的sin_addr.s_addr成员

(5)创建连接,将sock与地址端口进行绑定,建立连接

(6)发送数据

到这里已经完成了Client Socket工程的创建,还有一步比较重要的是配置Client与Server端的IP,将数据发送给服务器端。

在Windows下,通过打开命令行窗口输入:ipconfig可以获取本机地址与服务器的地址。

“基于MM32F3270

可以观察到PC地址为:192.168.105.34,在sys_arch.h文件中对DEST_IP_ADDR0 、DEST_IP_ADDR1、DEST_IP_ADDR2、DEST_IP_ADDR3进行修改,DEST_PORT可选用空闲端口,设备IP需要设置在同一个网段内通信才能进行IP_ADDR0、IP_ADDR1 、IP_ADDR2,需要与PC地址保持一致,IP_ADDR3可以随意设置(和PC地址不一致即可)。

#define DEST_IP_ADDR0               192
#define DEST_IP_ADDR1               168
#define DEST_IP_ADDR2               105
#define DEST_IP_ADDR3               34

#define DEST_PORT                  5001

#define IP_ADDR0                    192
#define IP_ADDR1                    168
#define IP_ADDR2                    105
#define IP_ADDR3                    130

将程序下载入开发板中,使用SSCOM工具进行如下设置:

“基于MM32F3270以太网

点击侦听:

“基于MM32F3270以太网

可以观察到正常侦听并接收到数据,表明实验成功。Demo程序可登录MindMotion的官网(http://www.mindmotion.com.cn/download.aspx?cid=2542&page=2)下载MM32F3270 lib_Samples,工程路径如下:

~\MM32F3270_Lib_Samples_V0.90\Demo_app\Ethernet_Demo\ETH_RTOS\Freertos_Client_socket

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

围观 25

页面

订阅 RSS - MM32F3270