STM32提高篇: 以太网通讯

STM32提高篇: 以太网通讯

一.以太网通讯介绍

以太网(Ethernet)是一种计算机局域网技术。IEEE组织的IEEE 802.3标准制定了以太网的技术标准,它规定了包括物理层的连线、电子信号和介质访问控制的内容。以太网是目前应用最普遍的局域网技术,取代了其他局域网标准如令牌环、FDDI和ARCNET。

以太网的标准拓扑结构为总线型拓扑,但目前的快速以太网(100BASE-T、1000BASE-T标准)为了减少冲突,将能提高的网络速度和使用效率最大化,使用交换机(Switch hub)来进行网络连接和组织。如此一来,以太网的拓扑结构就成了星型;但在逻辑上,以太网仍然使用总线型拓扑和CSMA/CD(Carrier Sense Multiple Access/Collision Detection,即载波多重存取/碰撞侦测)的总线技术。

经过长期的发展,以太网已成为应用最为广泛的局域网,包括标准以太网(10 Mbit/s)、快速以太网(100 Mbit/s)、千兆以太网(1000 Mbit/s)和万兆以太网(10 Gbit/s)等。IEEE 802.3规范则是基于以太网的标准制定的,并与以太网标准相互兼容。

二.W5500芯片介绍

要进行通讯,需要相应的硬件支持,在嵌入式应用领域,应用最广泛的一个以太网芯片就是W5500,素有以太之王的称号。

是韩国半导体公司WIZnet提供的一款高性价比的以太网芯片。其全球独一无二的全硬件TCPIP协议栈专利技术,解决了嵌入式以太网的接入问题,简单易用,安全稳定,是物联网设备的首选解决方案。

W5500 集成了 TCP/IP 协议栈,10/100M 以太网数据链路层(MAC)及物理层(PHY),使得用户使用单芯片就能够在他们的应用中拓展网络连接。

久经市场考验的WIZnet全硬件 TCP/IP 协议栈支持 TCP,UDP,IPv4,ICMP,ARP,IGMP以及PPPoE协议。W5500内嵌32K字节片上缓存以供以太网包处理。 如果你使用 W5500,你只需要一些简单的Socket编程就能实现以太网应用。这将会比其他嵌入式以太网方案更加快捷、简便。

用户可以同时使用8个硬件Socket独立通讯。 W5500 提供了SPI(外设串行接口)从而能够更加容易与外设 MCU 整合。而且,W5500的使用了新的高效SPI协议支持80MHz 速率,从而能够更好的实现高速网络通讯。为了减少系统能耗,W5500提供了网络唤醒模式(WOL)及掉电模式供客户选择使用。

1.W5500芯片特点

(1)支持硬件TCP/IP协议:TCP,UDP,ICMP,IPv4,ARP,IGMP,PPPoE。

(2)支持8个独立端口(Socket)同时通讯。

(3)支持掉电模式。

(4)支持网络唤醒。

(5)支持高速串行外设接口(SPI模式 0,3)。

(6)内部32K字节收发缓存。

(7)内嵌10BaseT/100BaseTX 以太网物理层(PHY)。

(8)支持自动协商(10/100-Based 全双工/半双工)。

(9)不支持 IP 分片。

(10)3.3V工作电压,I/O 信号口5V耐压。

(11)LED状态显示(全双工/半双工,网络连接,网络速度,活动状态)。

(12)LQFP48无铅封装(7x7mm,间距 0.5mm)

2.W5500应用目标

W5500适合于以下嵌入式应用:

(1)家庭网络设备:机顶盒、个人录像机、数码媒体适配器。

(2)串行转以太网:门禁控制、LED 显示屏、无线 AP 继电器等。

(3)并行转以太网:POS/微型打印机、复印机。

(4)USB 转以太网:存储设备、网络打印机。

(5)GPIO 转以太网:家庭网络传感器。

(6)安全系统:数字录像机、网络摄像机、信息亭。

(7)工厂和楼宇自动化控制系统。

(8)医疗监测设备。

(9)嵌入式服务器。

3.接入框图

三.驱动移植

注册函数:

c 复制代码
void user_register_function(void)
{
    /* 注册片选函数 */
    reg_wizchip_cs_cbfunc(wizchip_cs_select, wizchip_cs_deselect);

    /* 注册spi读写的函数 */
    reg_wizchip_spi_cbfunc(wizchip_spi_readbyte, wizchip_spi_writebyte);

    /* 注册临界区函数  (有实时操作系统的时候才会用到) */
    reg_wizchip_cris_cbfunc(wizchip_cris_enter, wizchip_cris_exit);
}

复位和设置初始地址:

c 复制代码
static void App_Eth_RST(void)
{
    RCC->APB2ENR |= RCC_APB2ENR_IOPGEN;
    /* pg7:  推挽输出  mode=11 cnf=00 */
    GPIOG->CRL |= GPIO_CRL_MODE7;
    GPIOG->CRL &= ~GPIO_CRL_CNF7;

    /* RST引脚拉低不低于500us */
    GPIOG->ODR &= ~GPIO_ODR_ODR7;
    Delay_ms(1);
    GPIOG->ODR |= GPIO_ODR_ODR7;
    Delay_ms(100);
}

uint8_t ip[]  = {192, 168, 32, 223};
uint8_t ga[]  = {192, 168, 32, 1};
uint8_t sub[] = {255, 255, 255, 0};
uint8_t mac[] = {110, 120, 13, 140, 150, 160};
void    App_Eth_Init(void)
{
    /* 初始化spi驱动 */
    Driver_SPI_Init();

    /* 软重启芯片 */
    App_Eth_RST();

    /* 注册函数 */
    user_register_function();

    /* 设置ip地址相关 */

    /* ip地址 */
    setSIPR(ip);
    /* mac地址 */
    setSHAR(mac);
    /* 子网掩码 */
    setSUBR(sub);
    /*  设置网关 */
    setGAR(ga);

    printf("ip地址设置完成\r\n");
}

数据导通:

四.tcp通讯

使用TCP协议

面向连接的通信协议,通过三次握手建立连接,通讯完成时要拆除连接,由于TCP时面向连接的所以只能用端到端的通讯。

启动TCP服务器:

单片机作为服务器,使用本地电脑连接,成功则打印串口数据。

c 复制代码
#define SN 0 /* socket 的编号 */
#define CLIENT 0
#define SERVER 1
uint8_t connected = 0;
uint8_t self = SERVER;
/* 启动一个TCP的服务器 */
void App_Tcp_ServerStart(void)
{
    self = SERVER;
    uint8_t status = getSn_SR(SN);

    // printf("%#x\r\n", status);

    if(status == SOCK_CLOSED) /* 表示socket已经关闭了 */
    {
        uint8_t r = socket(SN, Sn_MR_TCP, 8888, SF_TCP_NODELAY);
        if(r == SN)
        {
            printf("socket 0打开成功\r\n");
        }
        else
        {
            printf("socket 0打开失败 %d\r\n", r);
        }
        connected = 0;
    }
    else if(status == SOCK_INIT) /* 表示socket的已经打开, 并处于tcp模式 */
    {
        /* 监听客户端的连接 */
        uint8_t r = listen(SN);
        if(r == SOCK_OK)
        {
            printf("socket 0监听成功\r\n");
        }
        else
        {
            printf("socket 0监听失败 %d\r\n", r);
        }
        connected = 0;
    }
    else if(status == SOCK_CLOSE_WAIT)
    {
        printf("失去与客户端的连接\r\n");
        close(SN);
    }
}

打印成功:

TCP协议接收数据:

c 复制代码
uint8_t  clientIp[4];
uint16_t clientPort;
/* 接收tcp协议传输的数据 */
void App_Tcp_ReceiveData(uint8_t data[], uint16_t *dataLen)
{
    uint8_t status = getSn_SR(SN);
    // printf("%#x\r\n", status);

    if(status == SOCK_ESTABLISHED) /* 表示客户端已经连接成功服务器 */
    {
        if(connected == 0 && self == SERVER)
        {
            /* 获取对方的ip地址和端口号 */
            getSn_DIPR(SN, clientIp);
            clientPort = getSn_DPORT(SN);
            printf("客户端连接建立成功:ip =  %d.%d.%d.%d, port = %d\r\n",
                   clientIp[0],
                   clientIp[1],
                   clientIp[2],
                   clientIp[3],
                   clientPort);
            connected = 1;
        }

        if(getSn_IR(SN) & Sn_IR_RECV) /* 收到tcp数据 */
        {
            setSn_IR(SN, Sn_IR_RECV); /* 写1清除 */
            /* 收到的数据长度 */
            *dataLen = getSn_RX_RSR(SN);

            recv(SN, data, *dataLen); /* 接收数据 */
        }
    }
}

启动TCP客户端:

单片机作为客户端连接电脑端服务器。

c 复制代码
/* 启动一个客户端 */
uint8_t  serverIp[4] = {192, 168, 32, 228}; /* 服务端的ip地址: (电脑的ip地址) */
uint16_t serverPort  = 9999;
void     App_Tcp_ClientStart(void)
{
    self = CLIENT;
    uint8_t status = getSn_SR(SN);

    if(status == SOCK_CLOSED) /* 表示socket已经关闭了 */
    {
        uint8_t r = socket(SN, Sn_MR_TCP, 8888, SF_TCP_NODELAY);
        if(r == SN)
        {
            printf("socket 0打开成功\r\n");
        }
        else
        {
            printf("socket 0打开失败 %d\r\n", r);
        }
        connected = 0;
    }
    else if(status == SOCK_INIT) /* 表示socket的已经打开, 并处于tcp模式 */
    {
        /* 客户端需要主动区连接tcp服务器 */
        int8_t r = connect(SN, serverIp, serverPort);
        if(r == SOCK_OK)
        {
            printf("客户端连接服务器成功\r\n");
            App_Tcp_SendData("hello, this stm32 tcp client!", 29);
        }
        else
        {
            printf("客户端连接服务器失败 %d\r\n", r);
        }
        connected = 0;
    }
    else if(status == SOCK_CLOSE_WAIT)
    {
        printf("失去与服务端的连接\r\n");
        close(SN);
    }
}


TCP协议发送数据:

c 复制代码
void App_Tcp_SendData(uint8_t data[], uint16_t dataLen)
{
    if(dataLen == 0) return;

    uint8_t status = getSn_SR(SN);
    if(status == SOCK_ESTABLISHED)
    {
        send(SN, data, dataLen);
    }
}

五.udp通讯

使用UDP协议

面向无连接的通信协议,UDP数据包括目的端口号和源端口号信息,由于通讯不需要连接,所以可以实现广播发送。不需要接收方确认,属于不可靠传输,可能丢包实际使用要求程序员编程验证。
发送数据:

c 复制代码
void App_UDP_SendData(uint8_t data[], uint16_t dataLen, uint8_t *sIp, uint16_t sPort)
{

    uint8_t status = getSn_SR(SN);

    if(status == SOCK_CLOSED) /* 表示socket已经关闭了 */
    {
        uint8_t r = socket(SN, Sn_MR_UDP, 8888, 0);
        if(r == SN)
        {
            printf("socket 0打开成功\r\n");
        }
        else
        {
            printf("socket 0打开失败 %d\r\n", r);
        }
    }
    else if(status == SOCK_UDP)
    {
        /* 发送数据 */
        int32_t r = sendto(SN, data, dataLen, sIp, sPort);
        printf("发送完毕.... %d\r\n", r);
    }
}

接收数据:

c 复制代码
void App_UDP_ReceiveData(uint8_t data[], uint16_t *dataLen, uint8_t *rIp, uint16_t *rPort)
{
    memset(data, 0, strlen(data));
    *dataLen = 0;

    uint8_t status = getSn_SR(SN);
    if(status == SOCK_CLOSED) /* 表示socket已经关闭了 */
    {
        uint8_t r = socket(SN, Sn_MR_UDP, 8888, 0);
        if(r == SN)
        {
            printf("socket 0打开成功\r\n");
        }
        else
        {
            printf("socket 0打开失败 %d\r\n", r);
        }
    }
    else if(status == SOCK_UDP)
    {
        /* 先判断是否收到数据 */
        if(getSn_IR(SN) & Sn_IR_RECV)
        {
            setSn_IR(SN, Sn_IR_RECV);
            /* 从寄存器读取到数据的长度, 比实际大8个字节 */
            uint16_t tmp = getSn_RX_RSR(SN);
            if(tmp > 0)
            {
                *dataLen = tmp - 8;

                recvfrom(SN, data, *dataLen, rIp, rPort);
            }
        }
    }
}

六.http_server

初始化:

c 复制代码
void App_HttpSever_Init(void)
{
    Driver_LED_Init();
    /* 初始化一个httpserver */
    httpServer_init(txBuff, rxBuff, 8, sockeList);
    /* 注册一个网页: 首页 */
    reg_httpServer_webContent((uint8_t *)"index.html", (uint8_t *)content);
}

启动初始化:

c 复制代码
void App_HttpServer_Start(void)
{

    /* 启动服务器 */
    for(size_t i = 0; i < sizeof(sockeList); i++)
    {
        httpServer_run(i);
    }
}

解析URL和控制小灯:

c 复制代码
void App_HttpServer_DoAction(uint8_t action);
// 这个函数由http驱动层调用
void App_HttpServer_ParseUrl(uint8_t url[])
{
    // /index.html?action=2
    char *index = strstr((char *)url, "action");
    if(index != NULL)
    {
        uint8_t action = (uint8_t)(*(index + 7));
        App_HttpServer_DoAction(action);
    }
}
void App_HttpServer_DoAction(uint8_t action)
{
    if(action == '1')
    {
        Drviver_LED_On(LED_2);
    }
    else if(action == '2')
    {
        Drviver_LED_Off(LED_2);
    }
    else if(action == '3')
    {
        Drviver_LED_Toggle(LED_2);
    }
}
相关推荐
SY师弟42 分钟前
51单片机——交通指示灯控制器设计
c语言·单片机·嵌入式硬件·51单片机
----云烟----43 分钟前
使用libUSB-win32的简单读写例程参考
网络
weixin_473894771 小时前
前端服务器部署分类总结
前端·网络·性能优化
weixin_413920611 小时前
标签部件(lv_label)
单片机·嵌入式硬件
2301_800399721 小时前
stm32 ADC单通道转换
stm32·单片机·嵌入式硬件
上海云盾-高防顾问2 小时前
SCDN如何有效防护网站免受CC攻击?——安全加速网络的实战解析
网络·安全
alden_ygq2 小时前
nginx 出现大量connect reset by peer
服务器·网络·nginx
高工智能汽车2 小时前
抢跑「中央计算+区域控制」市场,芯驰科技高端智控MCU“芯”升级
科技·单片机·嵌入式硬件
xiao--xin2 小时前
计算机网络笔记(二十三)——4.5IPv6
网络·笔记·计算机网络·ipv6
HHONGQI1232 小时前
STM32 之网口资源
stm32·单片机·嵌入式硬件