前两天世伟兄发了一篇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头文件实现如下:
代码语言:javascript复制#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
以设置模式为例编写函数:
代码语言:javascript复制/*使能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地址为例,实现如下函数:
代码语言:javascript复制/*获取芯片工作模式*/
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的,分别实现如下
代码语言:javascript复制/*配置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模块工作模式:%dn",Deice_Para_Config_Handledef.bNetMode);
printf("2.配置RJ45模块目的IP地址:%d.%d.%d.%dn",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模块端口号:%dn",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.读取芯片工作模式:%dn",Deice_Para_Handledef.bNetMode);
/*读取芯片目的IP地址*/
Get_RJ45_Chip_Gobal_Ipaddr(300);
printf("2.读取目的IP地址:%d.%d.%d.%dn", 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.读取芯片目的端口号:%dn", Deice_Para_Handledef.gNetPort);
/*读取芯片Mac地址*/
Get_RJ45_Chip_Mac_Addr(300);
printf("4.读取芯片Mac地址:%d.%d.%d.%dn", Deice_Para_Handledef.bMacAddr[0], Deice_Para_Handledef.bMacAddr[1],
Deice_Para_Handledef.bMacAddr[2], Deice_Para_Handledef.bMacAddr[3]);
return 0 ;
}
在配置完毕以后获取模块配置参数,如果获取到的模块配置参数正确,接下来在网口连接正确的情况下即可以进入数据透传模式,就是直接和服务器打交道了,实现如下:
代码语言:javascript复制/*进入数据透传模式*/
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,开启空闲中断,然后在中断服务函数处编写空闲中断处理逻辑:
代码语言:javascript复制/**
* @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循环中实现:
代码语言:javascript复制/**
* @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 dEMOn");
/*配置模块参数*/
Config_RJ45_Module_Para();
printf("rn");
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%sn", 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%sn", 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、项目开源地址
本节代码已同步到码云的代码仓库中,获取方法如下:
码云仓库:
代码语言:javascript复制https://gitee.com/morixinguan/bear-pi/tree/master/24.RJ45_ETH-1
获取项目方法:
代码语言:javascript复制git clone https://gitee.com/morixinguan/bear-pi.git