STM32CubeMX | | 使用小熊派串口驱动峰汇ETH-01以太网模块上传数据到OneNet

简介: STM32CubeMX | | 使用小熊派串口驱动峰汇ETH-01以太网模块上传数据到OneNet

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

STM32CubeMX系列 | 使用小熊派硬件SPI驱动W5500以太网模块

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

640.png


640.png

1、硬件管脚说明

640.png

2、STM32CubeMX配置

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

2.1、时钟配置

640.png

640.png

2.2、调试接口配置

640.png

2.3、调试串口配置

640.png

2.4、网口模块配置

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

640.png

640.png

640.png

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

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

640.png

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

640.png

2.5、调试灯配置

640.png

2.6、生成工程

640.png

640.png

3、软件编程

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

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

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



640.png

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

640.png

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

640.png

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


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

640.png

同样的,由于例程需要进行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公布出来了,结果如下:

640.png

640.png

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

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

640.png

640.png

上传数据流展示:

640.png

4、项目开源地址

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

640.png

码云仓库:

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视频播放(很简单也很硬很肝!)


一些值得被定义为常用C语言头文件库的漂亮宏定义(值得收藏,以备使用参考)

目录
相关文章
STM32Cubemx PWM驱动加湿器模拟火山喷发效果
STM32Cubemx PWM驱动加湿器模拟火山喷发效果
352 14
STM32Cubemx PWM驱动SG90舵机
STM32Cubemx PWM驱动SG90舵机
821 13
STM32CubeMX mpu6050驱动
STM32CubeMX mpu6050驱动
258 10
STM32CubeMX EC11旋转编码器驱动
STM32CubeMX EC11旋转编码器驱动
1421 10
STM32CubeMX OLED驱动
STM32CubeMX OLED驱动
277 10
|
芯片
STM32CubeMX TM1637驱动数码管
STM32CubeMX TM1637驱动数码管
986 6
STM32CubeMX WS2812B灯驱动
STM32CubeMX WS2812B灯驱动
1230 1
STM32CubeMX ULN2003步进电机驱动
STM32CubeMX ULN2003步进电机驱动
397 0
|
传感器
手把手在STM32F103C8T6上构建可扩展可移植的DHT11驱动
【8月更文挑战第29天】本文详细介绍在STM32F103C8T6上构建可扩展且可移植的DHT11温湿度传感器驱动的步骤,包括硬件与软件准备、硬件连接、驱动代码编写及测试。通过这些步骤,可根据实际项目需求优化和扩展代码。
698 0
STM32Cubemx TB6612直流电机驱动
STM32Cubemx TB6612直流电机驱动
1001 0