前两天世伟兄发了一篇RJ45以太网模块的技术分享文章,用的是W5500以太网模块,他也将他的学习成果和实验共享到我们的私聊小蜜圈里,这是他分享的文章,链接如下:
STM32CubeMX系列 | 使用小熊派硬件SPI驱动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、项目开源地址
本节代码已同步到码云的代码仓库中,获取方法如下:
码云仓库:
https://gitee.com/morixinguan/bear-pi/tree/master/24.RJ45_ETH-1
获取项目方法:
git clone https://gitee.com/morixinguan/bear-pi.git
我还将之前做的一些项目以及练习例程在近期内全部上传完毕,与大家一起分享交流,如果有任何问题或者对该项目感兴趣,欢迎加我微信:morixinguan一起交流学习。
往期精彩
U盘容量大小造假技术手段实现之8M变4G(以STM32 SPI_FLASH为例)
手把手教你在STM32上实现OLED视频播放(很简单也很硬很肝!)