当前位置:首页 > 公众号精选 > 嵌入式云IOT技术圈
[导读]前两天世伟兄发了一篇RJ45以太网模块的技术分享文章,用的是W5500以太网模块,他也将他的学习成果和实验共享到我们的私聊小蜜圈里,这是他分享的文章。

前两天世伟兄发了一篇RJ45以太网模块的技术分享文章,用的是W5500以太网模块,他也将他的学习成果和实验共享到我们的私聊小蜜圈里,这是他分享的文章。


最近我也在用类似的模块,但我选的这个模块更简单,没有W5500那么复杂,它就是峰汇物联开发的一款ETH-01串口以太网模块,外观如下:

1、硬件管脚说明

2、STM32CubeMX配置

以下根据目前需要配置为TCP客户端模式,方便后面与云平台通信:

2.1、时钟配置

2.2、调试接口配置

2.3、调试串口配置

2.4、网口模块配置

网口模块通信串口配置如下,这里用的是USART3:

然后采用串口+DMA的方式来处理。

以下是读TCP状态的IO,配置为上拉输入模式,用于监测网卡是否已经连接服务器

下是配置模式IO,当输出电平为低时为指令配置模式,当输出电平为高时为数据透传模式:

2.5、调试灯配置

2.6、生成工程

3、软件编程

由于官方没有提供MCU的例程,所以只能从头到尾自己写啦,由于篇幅原因,这里仅分享其中一部分代码,完整工程请从我的码云上clone获取,以下根据目前需要配置为TCP客户端模式,方便后面与云平台通信:

3.1、串口指令配置模块之写命令操作

命令头1
命令头2 命令码
数据
0x57
0xAB


由于需要进行TCP传输,所以只设置红框圈起来的这几个指令就好了,还有一个更新指令到EEPROM的在手册示例里出现。

根据要求,简单实现如下函数(暂时不优化,先保证能用即可):

rj45_eth.h头文件实现如下:

#ifndef __RJ45_ETH_H #define __RJ45_ETH_H #include "main.h" #define UART_NNUM      USART3 #define UART_PORT      &huart3 #define RJ45_CONFIG_PORT  GPIOC #define RJ45_CONFIG_PIN     GPIO_PIN_9 #define RJ45_READ_TCP_STATUS_PORT   GPIOA #define RJ45_READ_TCP_STATUS_PIN     GPIO_PIN_8 #define RJ45_RXBUFFER_SIZE 1024 #define RJ45_TXBUFFER_SIZE 1024 #define NR_RJ45(x)  (sizeof(x)/sizeof(x[0])) #define Delay_ms(x) HAL_Delay(x) #define ACK_OK 0 #define ACK_TIMEOUT 1 typedef struct
{
    __IO uint8_t  BufferReady ;
    uint8_t  RJ45TxBuffer[RJ45_TXBUFFER_SIZE];
    uint8_t  RJ45RxBuffer[RJ45_RXBUFFER_SIZE];
} RJ45HandleTypeDef;
extern RJ45HandleTypeDef RJ45r_Handler ;

typedef struct _DEVICEPORT_CONFIG
{
    uint8_t  dataMode;  /* 数据模式:0:命令模式 1:透传模式*/
    uint8_t  bNetMode;    /* 网络工作模式: 0: TCP SERVER;1: TCP CLENT; 2: UDP SERVER 3:UDP CLIENT; */
    uint8_t  gDesIP[4];   /* 目的IP地址 */
    uint16_t gNetPort;    /* 目的端口号 */
    uint8_t  bMacAddr[4]; /* 芯片MAC地址*/
    __IO uint8_t tcp_status ; /*服务器连接状态*/
} DevicePortConfigS;
extern DevicePortConfigS Deice_Para_Handledef ;


/**********************写指令函数*************************/
/*使能RJ45配置模式*/
void Enable_RJ45_Config_Mode(void);
/*RJ45设置模式*/
uint8_t RJ45_Set_Mode(uint8_t mode, uint16_t delay_ms);
/*设置模块目的端口号*/
uint8_t Set_Module_Gobal_Port_Number(uint16_t number, uint16_t delay_ms);
/*RJ45设置目标IP*/
uint8_t Set_Module_Gobal_Ipaddr(uint8_t bit0, uint8_t bit1, uint8_t bit2, uint8_t bit3, uint16_t delay_ms);

/*更新配置参数到EEPROM*/
uint8_t Update_Config_Para_To_EEPROM(uint16_t delay_ms);
/*执行配置参数*/
uint8_t Runing_Config_Para_To_EEPROM(uint16_t delay_ms);
/*配置RJ45模块参数*/
uint8_t Config_RJ45_Module_Para(void);
/**********************写指令函数*************************/

/**********************读指令函数*************************/
/*获取芯片工作模式*/
void Get_RJ45_Chip_Work_Mode(uint16_t delay_ms);
/*获取芯片目的IP地址*/
void Get_RJ45_Chip_Gobal_Ipaddr(uint16_t delay_ms);
/*获取芯片目的端口号*/
void Get_RJ45_Chip_Gobal_Port_Number(uint16_t delay_ms);
/*获取芯片Mac地址*/
void Get_RJ45_Chip_Mac_Addr(uint16_t delay_ms);
/*获取RJ45模块参数*/
uint8_t Get_RJ45_Module_Config_Para(void);
/**********************读指令函数*************************/

/*使能RJ45配置模式*/
void Enable_RJ45_Config_Mode(void);
/*失能RJ45配置模式*/
void Disable_RJ45_Config_Mode(void);
/*检测TCP状态,返回1则为未连接,返回0则已连接*/

/*进入数据透传模式*/
uint8_t Enter_Data_Penetrate_Mode(void);
/*退出数据透传模式*/
uint8_t Quit_Data_Penetrate_Mode(void);
//RJ45发送网络透传数据函数,必须在透传模式下使用
void RJ45_Send_NetWork_Penetrate_Data(char* fmt, ...);
uint8_t Check_TCP_Status(void); #endif //__RJ45_ETH_H 

以设置模式为例编写函数:

/*使能RJ45配置模式*/
void Enable_RJ45_Config_Mode(void)
{
  /*关闭空闲中断,此时不接收非配置模式的数据,只接收模块本身指令收发的回复数据*/
  __HAL_UART_DISABLE_IT(UART_PORT, UART_IT_IDLE);
    HAL_GPIO_WritePin(RJ45_CONFIG_PORT, RJ45_CONFIG_PIN, GPIO_PIN_RESET);
}

/*使能DMA,清除数据包*/
static void Enable_And_Clear_Data_Packet(void)
{
    HAL_UART_DMAStop(UART_PORT);
    memset(RJ45r_Handler.RJ45TxBuffer, 0, RJ45_TXBUFFER_SIZE);
    memset(RJ45r_Handler.RJ45RxBuffer, 0, RJ45_RXBUFFER_SIZE);
    HAL_UART_Receive_DMA(UART_PORT, RJ45r_Handler.RJ45RxBuffer, RJ45_RXBUFFER_SIZE);
}

/*0 成功  其他失败*/
static uint8_t RJ45_Check_Cmd_Ack(uint8_t ack)
{ if(RJ45r_Handler.RJ45RxBuffer[0] == ack) return 0; return 1;
}

/*RJ45设置模式*/
uint8_t RJ45_Set_Mode(uint8_t mode, uint16_t delay_ms)
{
    uint8_t Res = 0 ;
    Enable_And_Clear_Data_Packet();
    RJ45r_Handler.RJ45TxBuffer[0] = 0x57 ;
    RJ45r_Handler.RJ45TxBuffer[1] = 0xab ;
    RJ45r_Handler.RJ45TxBuffer[2] = 0x10 ;
    RJ45r_Handler.RJ45TxBuffer[3] = mode ;
    wifi_uart_write_data( RJ45r_Handler.RJ45TxBuffer, 4); while(delay_ms--)
    {
        Res = RJ45_Check_Cmd_Ack(0xAA) ; if(0 == Res) return 0 ;

        Delay_ms(1);
    } return ACK_TIMEOUT ;
}

在调用如上设置指令前,先要将配置引脚拉低,然后开启DMA接收,接下来按照通信协议要求将对应的格式填入到发送Buffer,然后调用wifi_uart_write_data函数将协议数据通过串口发给模块,在一定超时延时以后,需要检测DMA接收缓存区是否有协议回复AA,如果有则表示该指令设置成果,这样就完成了写数据的过程,其它指令也是类似的,我们只需要照着手册实现即可。

3.2、串口指令配置模块之读命令操作

命令1

命令头2

命令码

0x57

0xAB


读命令比写命令要简洁许多,查看手册主要支持以下指令:

同样的,由于例程需要进行TCP传输,所以只实现红框圈起来的这几个指令就好了。

以获取芯片工作模式、获取芯片目的IP地址为例,实现如下函数:

/*获取芯片工作模式*/
void Get_RJ45_Chip_Work_Mode(uint16_t delay_ms)
{
    Enable_RJ45_Config_Mode();
    Enable_And_Clear_Data_Packet();
    RJ45r_Handler.RJ45TxBuffer[0] = 0x57 ;
    RJ45r_Handler.RJ45TxBuffer[1] = 0xab ;
    RJ45r_Handler.RJ45TxBuffer[2] = 0x60 ;
    wifi_uart_write_data( RJ45r_Handler.RJ45TxBuffer, 3);
    Delay_ms(delay_ms);
    Deice_Para_Handledef.bNetMode = RJ45r_Handler.RJ45RxBuffer[0];
}

/*获取芯片目的IP地址*/
void Get_RJ45_Chip_Gobal_Ipaddr(uint16_t delay_ms)
{
    Enable_RJ45_Config_Mode();
    Enable_And_Clear_Data_Packet();
    RJ45r_Handler.RJ45TxBuffer[0] = 0x57 ;
    RJ45r_Handler.RJ45TxBuffer[1] = 0xab ;
    RJ45r_Handler.RJ45TxBuffer[2] = 0x65 ;
    wifi_uart_write_data( RJ45r_Handler.RJ45TxBuffer, 3);
    Delay_ms(delay_ms);
    Deice_Para_Handledef.gDesIP[0] = RJ45r_Handler.RJ45RxBuffer[0] ;
    Deice_Para_Handledef.gDesIP[1] = RJ45r_Handler.RJ45RxBuffer[1] ;
    Deice_Para_Handledef.gDesIP[2] = RJ45r_Handler.RJ45RxBuffer[2] ;
    Deice_Para_Handledef.gDesIP[3] = RJ45r_Handler.RJ45RxBuffer[3] ;
}

与写命令操作一样,在调用如上读指令前,先要将配置引脚拉低,然后开启DMA接收,接下来按照通信协议要求将对应的格式填入到发送Buffer,然后延时一段时间,直接查看串口缓存区对应数据即可,但是如上写法并不严谨,更严谨的做法是是否判断串口一共回复了多少个字节,然后对每个字节进行校验,如果正确才获取,这里先不考虑优化问题,先保证能用即可,其它读指令函数也是差不多的逻辑,由于篇幅有限,这里就不贴出来了。

3.3、初始化函数及与服务器通信过程实现

初始化部分分为配置参数和获取参数两部分,这里我配置的服务器IP和端口号是移动OneNet的,分别实现如下

/*配置RJ45模块参数*/
uint8_t Config_RJ45_Module_Para(void)
{
    uint8_t ret = 1;
    Enable_RJ45_Config_Mode();
    Deice_Para_Config_Handledef.bNetMode = 0x01 ;
    ret = RJ45_Set_Mode(Deice_Para_Config_Handledef.bNetMode, 300); if(ret != 0) return 1;
    Deice_Para_Config_Handledef.gDesIP[0] = 0xB7 ; //180
    Deice_Para_Config_Handledef.gDesIP[1] = 0xE6 ; //230
    Deice_Para_Config_Handledef.gDesIP[2] = 0x28 ; //40
    Deice_Para_Config_Handledef.gDesIP[3] = 0x21 ; //33
    ret = Set_Module_Gobal_Ipaddr(Deice_Para_Config_Handledef.gDesIP[0],  \
    Deice_Para_Config_Handledef.gDesIP[1], Deice_Para_Config_Handledef.gDesIP[2], \
    Deice_Para_Config_Handledef.gDesIP[3], 300); if(ret != 0) return 2;
    Deice_Para_Config_Handledef.gNetPort = 80 ;  //80
    ret = Set_Module_Gobal_Port_Number(Deice_Para_Config_Handledef.gNetPort, 300); if(ret != 0) return 3;
    ret = Update_Config_Para_To_EEPROM(300); if(ret != 0) return 4;
    ret = Runing_Config_Para_To_EEPROM(300); if(ret != 0) return 5; printf("配置RJ45模块参数如下:\n"); printf("1.配置RJ45模块工作模式:%d\n",Deice_Para_Config_Handledef.bNetMode); printf("2.配置RJ45模块目的IP地址:%d.%d.%d.%d\n",Deice_Para_Config_Handledef.gDesIP[0], \
    Deice_Para_Config_Handledef.gDesIP[1],Deice_Para_Config_Handledef.gDesIP[2],
    Deice_Para_Config_Handledef.gDesIP[3]); printf("3.配置RJ45模块端口号:%d\n",Deice_Para_Config_Handledef.gNetPort); return 0 ;
}

/*获取RJ45模块参数*/
uint8_t Get_RJ45_Module_Config_Para(void)
{ printf("读取RJ45模块配置参数如下:\n");
 /*读取芯片工作模式*/
  Get_RJ45_Chip_Work_Mode(300); printf("1.读取芯片工作模式:%d\n",Deice_Para_Handledef.bNetMode);
  /*读取芯片目的IP地址*/
  Get_RJ45_Chip_Gobal_Ipaddr(300); printf("2.读取目的IP地址:%d.%d.%d.%d\n", Deice_Para_Handledef.gDesIP[0], Deice_Para_Handledef.gDesIP[1], \
               Deice_Para_Handledef.gDesIP[2], Deice_Para_Handledef.gDesIP[3]);
  /*读取芯片目的端口号*/
  Get_RJ45_Chip_Gobal_Port_Number(300); printf("3.读取芯片目的端口号:%d\n", Deice_Para_Handledef.gNetPort);
  /*读取芯片Mac地址*/
  Get_RJ45_Chip_Mac_Addr(300); printf("4.读取芯片Mac地址:%d.%d.%d.%d\n", Deice_Para_Handledef.bMacAddr[0], Deice_Para_Handledef.bMacAddr[1], \
               Deice_Para_Handledef.bMacAddr[2], Deice_Para_Handledef.bMacAddr[3]); return 0 ;
}

在配置完毕以后获取模块配置参数,如果获取到的模块配置参数正确,接下来在网口连接正确的情况下即可以进入数据透传模式,就是直接和服务器打交道了,实现如下:

/*进入数据透传模式*/
uint8_t Enter_Data_Penetrate_Mode(void)
{
 /*失能配置模式*/
 Disable_RJ45_Config_Mode();
 /*使能DMA,清除数据包*/
 Enable_And_Clear_Data_Packet();
 /*开启空闲中断,此时接收的是TCP/IP协议收发的数据*/
  __HAL_UART_ENABLE_IT(UART_PORT, UART_IT_IDLE);
 Deice_Para_Config_Handledef.dataMode = 1 ; return 0 ;
}

首先需要将配置引脚拉高,然后使能DMA,开启空闲中断,然后在中断服务函数处编写空闲中断处理逻辑:

/**
  * @brief This function handles USART3 global interrupt.
  */
void USART3_IRQHandler(void)
{
    /* USER CODE BEGIN USART3_IRQn 0 */ if(RESET != __HAL_UART_GET_FLAG(&huart3, UART_FLAG_IDLE))
    {
        __HAL_UART_CLEAR_IDLEFLAG(&huart3);
        HAL_UART_DMAStop(&huart3);
        //如果支持RTOS,则数据接收完毕时发送信号量,否则发一个全局变量标志位 #ifdef CMSIS_RTOS_SUPPORT osSemaphoreRelease(reciver_rj45_sem); #else RJ45r_Handler.BufferReady = 1 ; #endif }

    /* USER CODE END USART3_IRQn 0 */
    HAL_UART_IRQHandler(&huart3);
    /* USER CODE BEGIN USART3_IRQn 1 */

    /* USER CODE END USART3_IRQn 1 */
}

当串口触发了空闲中断,则表示一包数据已经接收完了,这时候就可以将整包数据获取出来,处理获取数据的逻辑在main函数的while循环中实现:

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
    /* USER CODE BEGIN 1 */

    /* USER CODE END 1 */

    /* MCU Configuration--------------------------------------------------------*/

    /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
    HAL_Init();

    /* USER CODE BEGIN Init */

    /* USER CODE END Init */

    /* Configure the system clock */
    SystemClock_Config();

    /* USER CODE BEGIN SysInit */

    /* USER CODE END SysInit */

    /* Initialize all configured peripherals */
    MX_GPIO_Init();
    MX_DMA_Init();
    MX_USART1_UART_Init();
    MX_USART3_UART_Init();
    /* USER CODE BEGIN 2 */ printf("RJ45 dEMO\n");
    /*配置模块参数*/
    Config_RJ45_Module_Para(); printf("\r\n");
    Read_Config_Para:
    /*获取RJ45模块参数*/
    Get_RJ45_Module_Config_Para();
    /*进入数据透传模式*/
    Enter_Data_Penetrate_Mode();
    /* USER CODE END 2 */

    /* Infinite loop */
    /* USER CODE BEGIN WHILE */ while (1)
    {
        /* USER CODE END WHILE */

        /* USER CODE BEGIN 3 */
        /*1.检查与远端服务器的连接状况,返回1表示已连接服务器*/
        Deice_Para_Handledef.tcp_status = Check_TCP_Status(); if(1 == Deice_Para_Handledef.tcp_status)
        { if(Count_LED_Timer > 500)
            {
                Count_LED_Timer = 0 ;
                HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
            }
        } else { if(Count_LED_Timer > 500)
            {
                Count_LED_Timer = 0 ;
                HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);
            }
        }

        /*2.每1s透传一次数据给服务器*/ if(Count_Timer >= 10000)
        {
            Count_Timer = 0 ; printf("透传数据:\n%s\n", post_http_data); if(1 == Deice_Para_Handledef.tcp_status)
            {
                RJ45_Send_NetWork_Penetrate_Data(post_http_data); printf("服务器已连接,发送成功!\n");
            } else { printf("服务器未连接,发送失败!\n");
            }
        }

        /*3.接收服务器下发的数据*/ if(RJ45r_Handler.BufferReady)
        {
            RJ45r_Handler.BufferReady = 0 ; printf("接收网络数据:\n%s\n", RJ45r_Handler.RJ45RxBuffer);
            /*退出透传模式*/
            //Quit_Data_Penetrate_Mode();
            //goto Read_Config_Para ;
            memset(RJ45r_Handler.RJ45RxBuffer, 0, RJ45_RXBUFFER_SIZE);
            HAL_UART_Receive_DMA(UART_PORT, RJ45r_Handler.RJ45RxBuffer, RJ45_RXBUFFER_SIZE);
        }
    }

    /* USER CODE END 3 */
}

通过自己的服务器发送测试协议进行测试,由于这是我私人创建的设备,所以就不将设备ID和api-key公布出来了,结果如下:

之前写过类似的文章,参考如下即可:

ESP8266实战贴:使用HTTP POST请求上传数据到公有云OneNet

上传数据流展示:

4、项目开源地址

本节代码已同步到码云的代码仓库中,获取方法如下:

免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

电路分析计算是电子科技领域中的一项重要技术,它涉及电流、电压、功率等物理量的分析和计算,为电路的设计、优化和故障排除提供了理论支持。本文将详细介绍电路分析计算的基本概念、常用方法以及科技应用,帮助读者更深入地了解这一领域...

关键字: 电路分析 支路电流法

业内消息,近日Alphabet旗下谷歌公司发言人表示为控制成本正在进行最新裁员,但具体人数不详。该发言人表示,裁员并非全公司范围,受影响的员工将能够申请内部职位,但没有具体说明受影响的员工人数或涉及的团队。

关键字: 谷歌 裁员

几天前,以南印度组织零售商协会(ORA)为代表的20多家零售连锁店和4300家商店宣布将从5月1日起停止销售一加设备。4月17日,全印度移动零售商协会(AIMRA)代表印度15万多家线下智能手机零售商通知一加,其成员正在...

关键字: 印度 一加 小米 Poco

业内消息,近日光刻机制造商阿斯麦(ASML)公布了2024年第一季度业绩,财报显示,该公司当季总净销售额53亿欧元,环比下降27%;毛利率51.0%,上季度为51.4%;净利润12亿欧元(当前约92.4亿元人民币),环比...

关键字: 光刻机 ASML

近日有韩媒称,由于薪资谈判破裂,劳资双方未能缩小对涨薪的意见分歧,三星电子全国工会(NSEU)即日起将发起公司成立以来首次集体行动,工会当天在华城市(Hwaseong)京畿道华城园区的组件研究大楼(DSR)前举行文化活动...

关键字: 三星

昨天上午,华为终端官微宣布全新重磅新机开售,约一分钟线上即告售罄,线下门店排起长队,气势丝毫不输苹果,和 Mate 60 Pro 一样没有发布会,这次官方同样推出了 “HUAWEI Pura 70 系列 先锋计划”,可以...

关键字: 麒麟芯片 华为

针对光伏优化器(MPPT)的非隔离DC-DC升压电路,推荐瑞森半导体低压MOS-SGT系列。极低导通电阻,低损耗,高雪崩耐量,高效率,非常适合高频应用。

关键字: 光伏优化器 MPPT

业内消息,近日东芝正在计划在日本裁员5000名员工,相当于日本员工总数的7%左右,这是其加速重组的最新举措。据知情人士透露,裁员的主要对象是总部的后勤部门,同时该公司将寻求员工自愿退休。

关键字: 裁员 东芝

昨天海能达发布公告,称针对产品禁售等判令提起的上诉结果已出:法院决定暂停执行一审法院对公司颁布的产品禁售令及罚款等,公司将与全球合作伙伴紧密合作,即刻恢复正常的商业活动并启动相关产品的销售。

关键字: 禁售令 海能达

业内消息,华为Pura 70第一批现货已到总代,部分经销商也已完成打款,等待华为打响开售“发令枪”。

关键字: 华为Pura 70 华为
关闭
关闭