MM32F3270

SDIO(Secure Digital Input and Output)中文名称:安全数字输入输出,SDIO在SD标准上定义了一种外设接口。SDIO主要有两类应用——可移动和不可移动。可移动设备作为Palm和Windows Mobile的扩展设备,用来增加蓝牙、照相机、GPS和802.11b功能。不可移动设备遵循相同的电气标准,但不要求符合物理标准。某些手机内包含通过SDIO连接CPU的802.11芯片。此举将“珍贵”的I/ O管脚资源用于更重要的功能。

蓝牙、照相机、GPS和802.11b设备有专为它们定义的应用规范。这些应用规范与为PCI和USB设备定义的类规范很相像。它们允许任何宿主设备与任意外设“通话”,只要它们都支持应用规范。

SDIO和SD卡规范间的一个重要区别是增加了低速标准。SDIO卡只需要SPI和1位SD传输模式。低速卡的目标应用是以最小的硬件开支支持低速I/ O能力。低速卡支持类似调制解调器、条码扫描仪和GPS接受器等应用。对“组合”卡(存储器+ SDIO)而言,全速和4位操作对卡内存储器和SDIO部分都是强制要求的。

MM32F3270系列控制器支持SDIO接口,本文在接下来会对MM32F3270的SDIO进行介绍,并通过实验演示SDIO驱动SD卡。

01 、SDIO 简介

SD/MMC/SDIO 控制器是 AMBA AHB 从外设,用于控制外部 SD/MMC/SDIO 卡,并支持 DSP/MCU的读/写访问。它作为主机与连接的 SD/MMC/SDIO 卡进行通信。所述控制器是基于 AMBA HCLK 域的完全同步设计。

1.1、SDIO 功能框图

“图1
图1 SDIO功能框图

1) AHB 从模式接口:为 32 位 AHB 总线提供接口。

2) FIFO 控制:产生握手信号到 DMA 硬件接口,并控制对外部数据 FIFO(128x32)的读/写访问。

3) 总线接口单元:包括控制寄存器和命令缓冲单元。

4) 多重块控制:控制多块数据的读写。

5) 时钟控制:通用时钟基于寄存器中定义的分频值。

6) 命令通路:从总线接口单元或 irq 响应中加载新的命令,然后发送命令,并接收响应 crc7 检查和 8 个空时钟。

7) 数据通路:发送和接收数据,用 crc16 检查。

8) 缓冲接口:控制对数据 FIFO 的读写控制信号。

1.2、SDIO 功能描述

1) 完全兼容 SD 记忆卡规格 1.0

2) 完全兼容 SD 存储卡规格 1.1(高速)

3) 完全兼容 SD 记忆卡规格 2.0(SDHC)

4) 完全兼容 MMC 系统规格 2.0~4.2

5) 完全兼容 SDIO 存储卡规格 1.1.0

6) 标准的 MMC 模式接口支持

7) 可编程时钟速率

8) 自动命令/响应 CRC 生成/检查

9) 自动数据 CRC 生成/检查

10) 可编程超时检测

11) AMBA 2.0 32 位 AHB 接口

12) 用于 AHB 数据访问的总共外部 128*32 数据 FIFO

13) 32 位 DMA 硬件接口,用于更快的 DMA 访问

14) DMA 接口可以配置为启用/禁用

15) DMA 请求是可配置的

16) 组合中断输出

02 、SD 存储卡

2.1、Read-Write 属性

根据 Read-Write 属性不同可分为两种类型的 SD 存储卡:

1) 可读可写(RW)卡(FLASH, OTP, MTP)。这些卡通常作为空白媒介来售卖,用于海量终端用户的视频、音频或数字图像记录的存储。

2) 只读存储卡(ROM)。这些卡是用固定的数据内容生产出来的,它们通常用作软件、音频、视频等的传播媒介。

2.2、电源电压

根据工作电源电压不同可分为两种类型的 SD 存储卡:

1) 高电压 SD 存储卡,可以在 2.7-3.6V 的电压范围内工作;

2) 双电压 SD 存储卡,可以在 1.6-3.6V 的电压范围内工作。

2.3、卡容量

根据卡容量不同可分为两种类型的 SD 存储卡:

1) 标准容量的 SD 存储卡支持最大 2G 字节的容量。所有版本的物理规格都定义了标准容量 SD 存储卡;

2) 高容量 SD 存储卡支持超过 2G 字节的容量,此版本规范限制容量高达 32GB。高容量 SD 存储卡是物理层规范版本 2.00 中新定义的。

2.4、传输速度

定义了四种速度等级,表示 SD 卡的最低性能:

1) Class0:这类卡不指定性能;
2) Class2:速度不低于 2MB/s;
3) Class4:速度不低于 4MB/s;
4) Class6:速度不低于 6MB/s;

大容量 SD 存储卡应支持速度等级规范,性能大于或等于 2 级。

2.5、总线拓扑

SD 卡系统定义了两种通信协议:SD 和 SPI。主机系统可以选择任意一种。当收到 reset 命令的时候, SD 卡通过主机的信息来决定使用何种模式,并且之后的通讯都会使用相同模式。不推荐多卡槽用共同的总线信号。一个单独的 SD 总线应该连接一个单独的 SD 卡。SD 总线包含下面的信号:

1) CLK:时钟信号;
2) CMD:双向命令/响应信号;
3) DAT0-DAT3:双向数据信号;
4) Vdd, Vss1, Vss2:电源和地信号。

2.6、总线协议

SD 总线通信:

1) Command:命令是一次操作开始的令牌,从主机发送到一个卡片(编址命令)或者连接到主机的所有卡片(广播命令)。命令在 CMD 线上连续传输。

2) Response:响应是从已寻址的卡或从所有连接上的卡发送到主机的令牌,作为对先前接收到指令的应答。响应在 CMD 线上连续传输。

3) Data:数据可以通过 DATA 线双向传输。

卡片寻址通过使用会话地址来实现,会话地址会在初始化阶段分配给卡。SD 总线上的基本交互是命令/响应交互。这种总线交互直接在命令或者响应的结构里面传输他们的信息。此外,某些操作还有数据令牌。SD 卡发送或接收的数据在块(block)中完成。数据块以 CRC 位来保证传输成功。目前有单块和多块操作。

注:多块操作模式在快速写操作时更好一点。多块传输在 CMD 线上产生 stop 命令时结束。主机端可以配置数据传输是单线还是多线。

“SDIO“无响应”和“无数据”操作"
SDIO“无响应”和“无数据”操作

“SDIO(多)数据块读操作"
SDIO(多)数据块读操作

“SDIO连续读操作"
SDIO连续读操作

“SDIO连续写操作"
SDIO连续写操作

03、卡的初始化以及识别过程

初始化进程以命令 ACMD41 作为开始,通过设置工作条件和 OCR 来进行。HCS(HighCapacitySupport)位为 1 表示主机支持高容量 SD 卡。卡通过 OCR 的 busy 位来通知主机 ACMD41 的初始化完成了。busy 位为 0表示卡仍然在初始化;为 1 表示已经完成初始化。主机会重复发送 ACMD41,直到 busy 位被置 1。卡片旨在第一个 ACMD41 的命令时,检查工作条件和 OCR 里面的 HCS 位。当重复 ACMD41 的时候,除了 CMD0,主机不再发其他命令。接着主机会发送命令 ALL_SEND_CID(CMD2),来获得卡的 CID 号。未识别的卡(处于Ready 状态的)发送自己的 CID 作为响应。当卡发送 CID 后,进入卡识别(Identification)状态。之后主机发送 SEND_RELATIVE_ADDR(CMD3)命令要求卡发布新的相对地址(RCA),一旦收到 RCA,卡就会变为等待(Stand-by)状态。主机会重复识别进程,为系统中每个卡循环发送 CMD2 和 CMD3。对于 SDI/O 卡而言,总线被激活后 SDIO 卡主机先发送 IO_SEND_OP_COND(CMD5)命令,得到的响应是卡的工作条件寄存器的内容,之后再同上发送 CMD3 命令,执行后续操作。

“使用MM32F3270的SDIO驱动SD卡"

04、MM32F3270 SDIO驱动SD卡

MM32的SDIO支持SD/MMC/SDIO卡,其中SDIO卡与SD存储卡是有区别的。SDIO卡实际上就是利用SDIO接口的一些模块,插入SD的插槽中,扩展设备的功能,如:SDIO wifi, SDIO CMOS相机等。SD存储卡就是平时常见的用于存储数据的卡。

本实验中使用的Micro SD卡属于SDSC(标准容量,最大2G)卡。介绍卡的种类是因为SD协议中的命令也支持这三种类型的卡,因此对MM32中的SDIO接口进行初始化后,上电后就要对接入的卡进行检测、分类,这个过程是通过向卡发送一系列不同的命令,根据卡不同的响应来进行分类。

本实验使用MM32F3270的SDIO对SD卡进行读写测试,首先填充一个块大小的存储器,通过写入操作把数据写入到 SD卡内,然后通过读取操作读取数据到另外的存储器,然后再对比存储器内容,判断读写操作是否正确。

实现的大概流程包括:初始化SDIO 外设以及GPIO,配置 SDIO 基本通信环境进入卡识别模式,通过命令处理后获取卡信息及状态。如果是SD卡正常则进行数据传输,接下来就可以进行读、写以及擦除操作,否则打印SD卡错误信息,不再进行后续操作。

硬件设计

实验使用MB-039开发板,主控芯片为MM32F3277G9P,如图是MB-039开发板的SDIO/TF卡接口部分,完整原理图可以通过官网下载。

“使用MM32F3270的SDIO驱动SD卡"

各个信号引脚对应如下:

“使用MM32F3270的SDIO驱动SD卡"

程序设计

根据 SD 卡识别过程和数据传输过程理解 SD 卡驱动函数代码。这部分代码内容也较多,在本文中只对部分核心函数介绍其功能,详细代码可到灵动官网下载参考。

SPIO配置初始化

void SDIO_ConfigInit(void)
{
    SDIO_InitTypeDef SDIO_InitStruct;

    SDIO_PIN_GPIO_Config();
    SDIO_Detect_Pin_Config();

    RCC_AHBPeriphClockCmd(RCC_AHBENR_SDIO, ENABLE);
    SDIO_DeInit();
    RCC_AHBPeriphClockCmd(RCC_AHBENR_SDIO, DISABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBENR_SDIO, ENABLE);

    SDIO_ClockSet(0x2F);
    SDIO_StructInit(&SDIO_InitStruct);

    SDIO_InitStruct.SDIO_OPMSel = SDIO_MMC_CTRL_OPMSel;
    SDIO_InitStruct.SDIO_SelPTSM = SDIO_MMC_CTRL_SelSM;
    SDIO_InitStruct.SDIO_DATWT = SDIO_MMC_CTRL_DATWT;
    SDIO_Init(&SDIO_InitStruct);

    SDIO_CRCConfig(SDIO_MMC_CRCCTL_CMD_CRCEN | SDIO_MMC_CRCCTL_DAT_CRCEN, ENABLE);
}
void show_sdcard_info(void)
{
    switch(SDCardInfo.CardType) {
        case SDIO_STD_CAPACITY_SD_CARD_V1_1:
            printf("Card Type:SDSC V1.1\r\n");
            break;
        case SDIO_STD_CAPACITY_SD_CARD_V2_0:
            printf("Card Type:SDSC V2.0\r\n");
            break;
        case SDIO_HIGH_CAPACITY_SD_CARD:
            printf("Card Type:SDHC V2.0\r\n");
            break;
        case SDIO_MULTIMEDIA_CARD:
            printf("Card Type:MMC Card\r\n");
            break;
    }
    printf("Card ManufacturerID:%d\r\n", SDCardInfo.SD_cid.ManufacturerID); //The manufacturer ID
    printf("Card RCA:%d\r\n", SDCardInfo.RCA);                          //Card relative address
    printf("Card Capacity:%d MB\r\n", (u32)(SDCardInfo.CardCapacity >> 20));
    printf("Card BlockSize:%d\r\n\r\n", SDCardInfo.CardBlockSize);
}

SD卡的初始化主要进行卡识别和卡状态获取,定义SD_Init()如下:

SD_Error SD_Init(void)
{
    u32 clk;
    RCC_ClocksTypeDef bclk;
    u32 targetFreq;
    __IO SD_Error errorstatus = SD_OK;
    u8 clkdiv = 0;
    INTX_DISABLE();


    errorstatus = SD_PowerON();                 //SD Power On
    if (errorstatus != SD_OK) {
        return errorstatus;
    }
    errorstatus = SD_InitializeCards();         //Initialize SD Card
    if (errorstatus != SD_OK) {
        return errorstatus;
    }
    errorstatus = SD_GetCardInfo(&SDCardInfo);  //Get card information
    if (errorstatus != SD_OK) {
        return errorstatus;
    }
    errorstatus = SD_SelectDeselect((u32)(SDCardInfo.RCA << 16)); //Select the SD card
    if (errorstatus != SD_OK) {
        return errorstatus;
    }
    errorstatus = SD_EnableWideBusOperation(1); //4 bit width, if it is an MMC card, you cannot use 4 bit mode
    if ((errorstatus != SD_OK)) {
        if( (SDIO_MULTIMEDIA_CARD == CardType)) {
            if (SDCardInfo.CardType == SDIO_STD_CAPACITY_SD_CARD_V1_1 || SDCardInfo.CardType == SDIO_STD_CAPACITY_SD_CARD_V2_0) {
                clkdiv = 0; //V1.1/V2.0 card with the maximum setting of 48/4=12Mhz
            }
            else {
                clkdiv = 1; //For other cards such as SDHC, the maximum setting is 48/2=24Mhz
            }
            if(clkdiv != 0) {
                targetFreq = 24000000;

            }
            else {
                targetFreq = 12000000;
            }
            RCC_GetClocksFreq(&bclk);
            clk = (bclk.HCLK_Frequency / 2 / 2 / targetFreq - 1);
            SDIO_ClockSet(clk);

        }
        else {
            __NOP();
        }
    }
    else {
        if (SDCardInfo.CardType == SDIO_STD_CAPACITY_SD_CARD_V1_1 || SDCardInfo.CardType == SDIO_STD_CAPACITY_SD_CARD_V2_0) {
            clkdiv = 0;
        }
        else {
            clkdiv = 1;
        }
        if(clkdiv != 0) {
            targetFreq = 24000000;

        }
        else {
            targetFreq = 12000000;
        }
        RCC_GetClocksFreq(&bclk);
        clk = (bclk.HCLK_Frequency / 2 / 2 / targetFreq - 1);
        SDIO_ClockSet(clk);

    }
    INTX_ENABLE();
    return errorstatus;
}

1) SD_PowerON()函数用于查询卡的工作电压和时钟控制配置,并返回 SD_Error 类型错误,该函数是整个 SD 识别的关键函数。

2) SD_InitializeCards()函数初始化SD卡,并将其置入就绪状态;

3) SD_GetCardInfo()函数获取SD卡的信息;

4) SD_SelectDeselect()选择SD卡,发送CMD7命令,选择具有相对地址(RCA)的卡作为ADDR;

5) SD_EnableWideBusOperation()配置SDIO数据宽度;

6) SDIO_ClockSet()配置SDIO的时钟频率。

获取SD卡信息及数据的函数定义如下:

void read_sd_card_info(void)
{
    u16 i = 5;
    SD_Error result;
    u32 sd_size;

    SDIO_ConfigInit();
    printf("SDCARD TEST\r\n");
    while(1) {

        result = SD_Init();

        if(result == SD_OK) {
            break;
        }
        printf("SD Card Error!\r\n");

        DELAY_Ms(1);
    }
    show_sdcard_info();
    i = 5;
    while(i--) {
        if(SD_ReadDisk(&vbuf[0], 0, 1) == 0) { 
            printf("UART Sending Data...\r\n");
            printf("SECTOR 0 DATA:\r\n"); 
            for(sd_size = 0; sd_size < 512; sd_size++) {
                printf("%02x ", vbuf[sd_size]);  
            }
            printf("\r\nDATA ENDED\r\n");
            printf("UART Send Data Over!\r\n");
        }
        DELAY_Ms(50);
    }
}

实验演示

在MB-039开发板的SDIO/TF卡槽插入SD卡,运行程序,串口调试助手显示如下:

“使用MM32F3270的SDIO驱动SD卡"

如果SD卡可用,串口调试助手会打印SD卡的信息,包括卡类型、生产ID、RCA、容量和块大小,接着打印扇区0的数据,会连续打印5次该部分内容。

本次实验的例程可以通过MindMotion的官网下载MM32F3270 lib_Samples:

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

工程路径如下:

~MM32F327x_Samples\ LibSamples\SDIO\SDIO_ReadSDCardInfo

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

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

围观 205

音响数据的采集、处理和传输是多媒体技术的重要组成部分。众多的数字音频系统已经进入消费市场,例如数字音频录音带、数字声音处理器。对于设备和生产厂家来说,标准化的信息传输结构可以提高系统的适应性。

I2S(Inter—IC Sound)总线是飞利浦公司为数字音频设备之间的音频数据传输而制定的一种总线标准,该总线专责于音频设备之间的数据传输,广泛应用于各种多媒体系统。它采用了沿独立的导线传输时钟与数据信号的设计,通过将数据和时钟信号分离,避免了因时差诱发的失真,为用户节省了购买抵抗音频抖动的专业设备的费用。在飞利浦公司的 I2S 标准中,既规定了硬件接口规范,也规定了数字音频数据的格式。

I2S 总线接口有 3 个主要信号,但只能实现数据半双工传输,后来为实现全双工传输有些设备增加了扩展数据引脚。

MM32F3270 系列控制器支持 I2S 总线接口,本章节在接下来会对MM32F3270 I2S进行介绍,并使用MM32F3270和CS4344芯片进行I2S通信来演示播放MP3。

01、I2S 主要特征

1)半双工通信(仅发射机或接收机)

2)主操作或从操作

3)8 位可编程线性预分频器,以达到精确的音频采样频率( 8KHz 到 192KHz)

4)数据格式可以是 16 位、 24 位或 32 位

5)数据包帧固定为 16 位(16 位数据帧)或 32 位(16 位、 24 位、 32 位数据帧)

6)可编程时钟极性(稳定状态)

7)发射模式下的下溢标志(仅从机),接收模式下的上溢标志(主和从机)和接收/发射模式下的帧错误标志(仅从机)

8)用于传输和接收的 32 位寄存器为两个声道分时复用

9)支持 I2S 协议:

– 飞利浦标准
– MSB 对齐标准(左对齐)
– LSB 对齐标准(右对齐)
– PCM 标准(在 16 位信道帧上具有短帧和长帧同步或扩展到 32 位信道帧的 16 位数据帧)

10)数据方向始终是 MSB 优先

11)DMA 传输能力( 32 位宽)

12)可配置输出 MCLK 来驱动外部音频组件,比率固定在 256× FS(其中 FS 为音频采样频率)

02、I2S 总线接口

I2S 与 SPI 共用三个公共管脚:

1)SD:串行数据(映射在 MOSI 管脚上),用于发送或接收两次多路数据通道(仅在半双工模式下)。

2)WS:声道选择(映射在 NSS 引脚上),是 master 中的数据控制信号输出模式和从模式输入。

3)CK:串行时钟(映射在 SCK 引脚上),是主模式下的串行时钟输出以及从机模式下的串行时钟输入。

4)当某些外部设备需要主时钟输入时,可以使用一个附加的管脚输出时钟到音频设备。

5)MCK:驱动时钟(映射在 MISO 引脚上),用于驱动外部音频组件,仅主模式时使用。

03、I2S 数据格式

三线总线处理音频数据的线路必须经过分时复用两个声道:右声道和左声道。但是只有一个 32 位寄存器用于传输或接收。所以由软件依次配置寄存器 TXREG 为每个声道侧的值,或依次读取寄存器 RXREG的数据。总是先发送左声道,然后发送右声道( CHSIDE 对 PCM 协议没有意义)。

数据可采用以下格式发送:

1) 16 位数据打包在 16 位帧中

2) 16 位数据打包在 32 位帧中

3) 24 位数据打包在 32 位帧中

4) 32 位数据打包在 32 位帧中

当使用 32 位帧上发送 16 位数据时,前 16 位(MSB)是有效的位,16 位 LSB 制为 0,无需任何软件操作,通过硬件实现。其他格式相似。

04、通信标准

对于所有数据格式和通信标准,总是先发送最高位( MSB 优先)。I2S 接口支持四种音频标准,可通过配置 SPI_I2S_I2SCFGR 寄存器的 I2SSTD[1:0]和 PCMSYNC 进行切换。

飞利浦标准

对于本标准, WS 信号用于指示正在传输的声道。发射器在 CK 的下降沿锁存数据,接收器并在 CK的上升读取数据。WS 信号也在 CK 的下降沿被锁定。对于这种标准 I2S 格式的信号,无论有多少位有效数据,数据的最高位总是出现在 WS 变化(也就是一帧开始)后的第 2 个 CK 脉冲处。

“飞利浦标准示意图"
飞利浦标准示意图

MSB 对齐标准

对于这个标准,第一个数据在 WS 变化后的第一个沿有效。

“MSB
MSB 对齐标准示意图

LSB 对齐标准

“LSB
LSB 对齐标准示意图

PCM 标准

对于 PCM 标准,不需要使用声道信息。PCM 有两个模式:短帧模式和长帧模式,通过配置SPI_I2S_I2SCFGR 寄存器的 PCMSYNC 位进行切换。在 PCM 模式下,输出信号(WS, SD)在 CK 信号的上升沿进行采样。输入信号(WS, SD)在 CK 下降沿被捕获。注意在主模式下, CK 和 WS 被配置为输出。

“PCM
PCM 标准示意图

05、基于MM32F3270的音频播放实验

CS4344芯片是实现本次实验功能的重要器件之一。CS4344是一种立体声音频数模转换器 (DAC) ,可使用单个 +3.3 V 或 +5 V 电源,仅需要最小的支持电路。该系列线性模拟低通滤波器和自动速度模式检测,当自动选择 2 kHz 和 200 kHz 之间的采样率,使用采样率和主时钟速率方法。

本实验的基本原理是MM32F3270 读取SD卡中的MP3文件,并对其解码得到PCM信号,通过I2S接口将PCM信号传输给CS4344,由CS4344进行DA转换输出模拟信号,再经过TS4871(音频功率放大器)连接到耳机接口,可以接入耳机等音频播放装置。

硬件设计

如图是MB-039的I2S部分,完整原理图可以通过官网下载。

“基于MM32F3270

各个信号引脚对应如下:

“基于MM32F3270

程序设计

根据接口电路配置GPIO初始化

static void I2S3_GPIO_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    // I2S  MCLK, SD, CK and WS pins configuration
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA | RCC_AHBPeriph_GPIOB, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1ENR_SPI3, ENABLE);

    GPIO_PinAFConfig(GPIOA, GPIO_PinSource15, GPIO_AF_6); //I2S WS
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource3, GPIO_AF_6);      //I2S CK  I2S_SCK
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource5, GPIO_AF_6);     //I2S SD  I2S_DATAOUT  MOSI
    GPIO_PinAFConfig(GPIOC, GPIO_PinSource7, GPIO_AF_5);  //I2S MCK I2S_MCLK

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;
    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_3 | GPIO_Pin_5;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOB, &GPIO_InitStructure);


    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOC, &GPIO_InitStructure);

    // config as the control I/O for power on or enter standby
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
}

I2S配置初始化

   static void I2S_Mode_Config(SPI_I2S_STANDARD_TypeDef usStandard, SPI_I2S_DATAFORMAT_TypeDef usWordLen, SPI_I2S_AUDIO_FREQ_TypeDef usAudioFreq, SPI_I2S_TRANS_MODE_TypeDef usMode)
{
    I2S_InitTypeDef I2S_InitStructure;

    if ((usMode == I2S_Mode_SlaveTx) && (usMode == I2S_Mode_SlaveRx)) {
        return;
    }
    RCC_APB1PeriphClockCmd(RCC_APB1ENR_SPI3, ENABLE);
    SPI_DeInit(SPI3);
    RCC_APB1PeriphClockCmd(RCC_APB1ENR_SPI3, ENABLE);
    if (usMode == I2S_Mode_MasterTx) {
        I2S_InitStructure.I2S_Mode = I2S_Mode_MasterTx;
        I2S_InitStructure.I2S_Standard = usStandard;
        I2S_InitStructure.I2S_DataFormat = usWordLen;
        I2S_InitStructure.I2S_MCLKOutput = I2S_MCLKOutput_Enable;
        I2S_InitStructure.I2S_AudioFreq = usAudioFreq;
        I2S_InitStructure.I2S_CPOL = I2S_CPOL_Low;
        I2S_Init(SPI3, &I2S_InitStructure);
    }
    else if (usMode == I2S_Mode_MasterRx) {
        I2S_InitStructure.I2S_Mode = I2S_Mode_MasterRx;
        I2S_InitStructure.I2S_Standard = usStandard;
        I2S_InitStructure.I2S_DataFormat = usWordLen;
        I2S_InitStructure.I2S_MCLKOutput = I2S_MCLKOutput_Enable;
        I2S_InitStructure.I2S_AudioFreq = usAudioFreq;
        I2S_InitStructure.I2S_CPOL = I2S_CPOL_Low;
        I2S_Init(SPI3, &I2S_InitStructure);
    }

    SPI_DMACmd(SPI3, ENABLE);
    I2S_Cmd(SPI3, ENABLE);
}

(1)I2S_Mode:I2S 模式选择,可选主机发送、主机接收、从机发送以及从机接收模式,它设定SPI_I2S_GCTL寄存器MODE位的值。一般设置 MM32 控制器为主机模式,当播放声音时选择发送模式,当录制声音时选择接收模式。

(2) I2S_Standard:通信标准格式选择,可选 I2S Philips 标准、左对齐标准、右对齐标准、 PCM 短帧标准或 PCM 长帧标准,它设定SPI_I2S_I2SCFGR 寄存器 I2SSTD位和 PCMSYNC位的值。一般设置为 I2S Philips 标准即可。

(3)I2S_DataFormat:数据格式选择,设定有效数据长度和帧长度,可选标准 16bit 格式、扩展16bit(32bit 帧长度) 格式、 24bit 格式和 32bit 格式,它设定 SPI_I2SCFGR 寄存器 DATLEN 位和CHLEN 位的值。对应 16bit 数据长度可选 16bit 或 32bit 帧长度,其他都是 32bit 帧长度。

(4)I2S_MCLKOutput:主时钟输出使能控制,可选使能输出或禁止输出,它设定 SPI_I2SPR 寄存器 MCKOE 位的值。为提高系统性能一般使能主时钟输出。

(5)I2S_AudioFreq:采样频率设置,标准库提供采样采样频率选择,分别为 4KHz、8kHz、 11kHz、12KHz、16kHz、22kHz、32kHz、44kHz、48kHz、96kHz、192kHz 以及默认 2Hz,它设定 SPI_I2S_SPBRG 寄存器的值。

(6)I2S_CPOL:空闲状态的 CK 线电平,可选高电平或低电平,它设定 SPI_I2S_CCTL 寄存器 CPOL位的值。一般设置为低电平即可。

在I2S_StartPlay()函数中调用I2S_Mode_Config()函数,

   void I2S_StartPlay(SPI_I2S_STANDARD_TypeDef usStandard, SPI_I2S_DATAFORMAT_TypeDef usWordLen, SPI_I2S_AUDIO_FREQ_TypeDef usAudioFreq)
{
    // config I2S interface as standard, bit length, frequence ,the Master Tx mode
    I2S_Mode_Config(usStandard, usWordLen, usAudioFreq, I2S_Mode_MasterTx);
    SPI3->GCTL |= 0xF;
}

在PlayMP3FileDemo()函数中调用I2S_StartPlay()函数,并配置传输模式为主机发送I2S_Mode_MasterTx,选择Phillips标准,16位数据长度,采样频率配置为44KHz。

    I2S_StartPlay(I2S_Standard_Phillips, I2S_DataFormat_16b, I2S_AudioFreq_44k);

PlayMP3File()函数是 MP3 播放器的实现函数,定义如下:

   void PlayMP3File(void)
{
    DIR dirs;
    FILINFO finfo;
    FRESULT res;
    static UINT br;

    DELAY_Init();
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA | RCC_AHBPeriph_GPIOB | RCC_AHBPeriph_GPIOC | RCC_AHBPeriph_GPIOD | RCC_AHBPeriph_GPIOE, ENABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBENR_SDIO, ENABLE);
    CONSOLE_Init(115200);

    SDIO_ConfigInit();
    printf("SDCARD TEST\r\n");
    while(SD_Init()) {
        printf("SD Card Error!\r\n");
    }
    MX_FATFS_Init();
    f_mount(&SDFatFS, (TCHAR const*)SDPath, 0);

    u8 buff[2] = {0x01, 0x02};
    I2S_TX_DMA_Init(&buff[0], 1);
    DMA_Cmd(DMA2_Channel2, ENABLE);

//  while(1){
    if (f_opendir(&dirs, "") == FR_OK) {              //success to open directory
        while (f_readdir(&dirs, &finfo) == FR_OK) {       //if there is file in this directory
            if (finfo.fattrib & AM_ARC) {
                if(!finfo.fname[0])
                    break;
                printf("\r\n Now Playing:[");
                printf(finfo.fname);
                printf("]\r\n");
                res = f_open(&fsrc, finfo.fname, FA_OPEN_EXISTING | FA_READ);
                SET_BIT(SPI3->GCR, SPI_GCR_SPIEN);
                MODIFY_REG(DMA2_Channel2->CCR, DMA_CCR_EN, ENABLE <<DMA_CCR_EN_Pos);

                hMP3Decoder = MP3InitDecoder();

                readPtr = readBuf;
                res = f_read(&fsrc, readBuf, READBUF_SIZE, &br);
                bytesLeft += br;

                buffer_switch = 0;
                while(1) {
                    offset = MP3FindSyncWord(readPtr, bytesLeft); //assume EOF if no sync found
                    if(offset < 0)break;
                    readPtr += offset; //data start point
                    bytesLeft -= offset; //in buffer
                    if(bytesLeft < READBUF_SIZE) {
                        memmove(readBuf, readPtr, bytesLeft);
                        res = f_read(&fsrc, readBuf + bytesLeft, READBUF_SIZE - bytesLeft, &br);
                        if((res) || (br == 0)) break;
                        if(br < READBUF_SIZE - bytesLeft)
                            memset(readBuf + bytesLeft + br, 0, READBUF_SIZE - bytesLeft - br);
                        bytesLeft = READBUF_SIZE;
                        readPtr = readBuf;
                    }
                    MP3GetLastFrameInfo(hMP3Decoder, &mp3FrameInfo);

                    if((samprate != mp3FrameInfo.samprate) && (mp3FrameInfo.samprate != 0)) {
//                                               wm8978_CfgAudioIF(I2S_Standard_Phillips, mp3FrameInfo.bitsPerSample, SPI_Mode_Master);
                        //   I2S_StartPlay((SPI_I2S_STANDARD_TypeDef)I2S_Standard_Phillips, (SPI_I2S_DATAFORMAT_TypeDef)mp3FrameInfo.bitsPerSample, (SPI_I2S_AUDIO_FREQ_TypeDef)mp3FrameInfo.samprate);
                        samprate = (SPI_I2S_AUDIO_FREQ_TypeDef) mp3FrameInfo.samprate;
                    }
                    while(1) {
                        if(DMA_GetITStatus(DMA2_IT_TC2) == SET) {
                            DMA_ClearITPendingBit(DMA2_IT_TC2);

                            if(buffer_switch == 0) {
                                Audio_MAL_Play((u32)buffer4, BUFF_SIZE);
                                MP3Decode(hMP3Decoder, &readPtr, &bytesLeft, buffer3, 0);
                                buffer_switch = 1;
                                break;
                            }
                            else {
                                Audio_MAL_Play((u32)buffer3, BUFF_SIZE);
                                MP3Decode(hMP3Decoder, &readPtr, &bytesLeft, buffer4, 0);
                                buffer_switch = 0;
                                break;
                            }
                        }
                    }

                }
                CLEAR_BIT(SPI3->GCR, SPI_GCR_SPIEN);
                MODIFY_REG(DMA2_Channel2->CCR, DMA_CCR_EN, DISABLE << DMA_CCR_EN_Pos);
                f_close(&fsrc);
                bytesLeft = 0;
            }
        }
    }
    while(1);
//   }

}

MP3文件是经过压缩算法压缩而存在的,为得到 PCM 信号,需要对 MP3 文件进行解码。本实验使用Helix MP3解码器,Helix MP3 解码器的源代码是开源代码,受制于源代码随附文件中描述的许可协议。该算法支持浮点和定点实现,可移植到任意32位定点处理器上运行,提供对 MPEG-1、 MPEG-2 以及 MPEG-2.5 标准的 Layer3 解码,以及支持可变位速率、恒定位速率,以及立体声和单声道音频格式。关于Helix MP3解码器的移植,在本文中不做重点讲述,更多信息可访问网站:

https://datatype.helixcommunity.org/Mp3dec

f_open 函数用于打开文件,如果文件打开失败则直接退出播放。

MP3InitDecoder 函数用于初始化Helix 解码器,分配解码器必须内存空间,如果初始化解码器失败直接退出播放。

f_read 函数从 SD 卡读取 MP3 文件数据,存放在 readBuf缓冲区中, br变量保存实际读取到的数据的字节数。如果读取数据失败则运行 MP3FreeDecoder 函数关闭解码器后退出播放器。

MP3Decode 函数开始对源数据缓冲区中帧数据进行解码,通过函数返回值可判断得到解码状态,如果发生解码错误则执行对应的代码。

读取到文件末尾就退出循环, 此时MP3文件已经完整播放。

实验演示

SD卡中存储有MP3文件,并将SD卡、耳机设备接入MB-039开发板,运行程序,就可以听到音乐播放。

本次实验的例程可以通过MindMotion的官网下载MM32F3270 lib_Samples:

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

工程路径如下:

~MM32F327x_Samples\Demo_app\PlayWave_Demo\SPI_I2S_SDIO_FatFs\MP3_CS4344_Demo

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

下章的题目为《使用MM32F3270 的SDIO 驱动SD卡》讲解通过SDIO外接MicroSD卡的实现。

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

围观 219

灵动微电子全新主流型 MM32F3270 系列 MCU 现已开始批量供货。该系列 MCU 搭载了 Arm® Cortex®-M3 内核,最高主频 120MHz,提供最高 512KB Flash 和 128KB SRAM,并集成了丰富的通信接口、定时器和模拟资源。MM32F3270 系列 MCU 的典型应用包括工业控制、楼宇自动化、消防监控、指纹模块、家电控制、电源管理、打印机、扫描仪、通信转换模块等。

“灵动MM32F3270系列MCU开始批量供货!"

MM32F3270 的主要特点

  • Arm® Cortex®-M3 内核,最高主频 120MHz
  • 高达 512KB Flash和128KB SRAM
  • 外扩存储器接口 FSMC
  • 多达 8 组 UART、3 组 SPI、3 组 I2S、2 组 I2C
  • 1 组 10/100M 以太网控制器
  • 1 组 USB 2.0 FS OTG 控制器和内置 PHY
  • 1 组 CAN 2.0B 控制器
  • 1 组 SDIO接口
  • 2 组高级定时器,每组可输出 4 通道带互补端口的 PWM,支持死区和刹车
  • 2 组 32 位定时器、4 组 16 位定时器
  • 3 组 12-bit 1MSPS SAR ADC,提供多达 21 个外部通道
  • 2 组 12-bit DAC
  • 2 组高速模拟比较器
  • 2.0 – 5.5V 宽压设计,适用于各种电源供电场合
  • 高可靠性:支持高达 ±8000V HBM ESD
  • 提供 -40~85°C 和 -40~105°C 环温选项
  • 引脚兼容 MM32F103 系列

“灵动MM32F3270系列MCU开始批量供货!"

产品供货情况

MM32F3270现已批量供应 LQFP144、LQFP100、LQFP64、LQFP48 和 QFN40 五种可选封装形式,提供 256KB 和 512KB Flash 选项,全系列提供 -40~85°C 和 -40~105°C 产品型号,并配套完整的软件支持和开发板(MB-039)支持,具体选型信息参考数据手册。

有关芯片购买事宜,请洽灵动的销售、官方代理商和方案设计公司。

更多详细信息,请访问 www.mm32mcu.com

MM32F3270 相关技术链接

基于MM32F3270 以太网 Client_Socket使用

点击这里>>>

基于MM32F3270 以太网 Client使用

点击这里>>>

基于MM32F3270 以太网 Server使用

点击这里>>>

基于MM32F3270 以太网 Server_Socket

点击这里>>>

基于MM32F3270 以太网 UDP使用

点击这里>>>

基于MM32F3270 以太网 HTTP使用

点击这里>>>

使用MM32F3270 FSMC驱动TFT-LCD

点击这里>>>

使用MM32F3270 FSMC驱动外部NOR Flash

点击这里>>>

使用MM32F3270 FSMC驱动OLED

点击这里>>>

使用MM32F3270 FSMC驱动SRAM

点击这里>>>

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

围观 29

交互式界面被越来越多地集成于多种应用中,例如医疗设备、过程控制、手机和其它手持设备。这些界面主要基于使用彩色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)。

围观 396

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)。

围观 98

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)。

围观 46

交互式界面被越来越多地集成于多种应用中,例如医疗设备、过程控制、手机和其它手持设备。这些界面主要基于使用彩色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)。

围观 162

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)。

围观 55

前面重点对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)。

围观 86

前面介绍了基于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)。

围观 82

页面

订阅 RSS - MM32F3270