STM32CubeMX | | 使用小熊派串口驅(qū)動峰匯ETH-01以太網(wǎng)模塊上傳數(shù)據(jù)到OneNet
前兩天世偉兄發(fā)了一篇RJ45以太網(wǎng)模塊的技術分享文章,用的是W5500以太網(wǎng)模塊,他也將他的學習成果和實驗共享到我們的私聊小蜜圈里,這是他分享的文章。
最近我也在用類似的模塊,但我選的這個模塊更簡單,沒有W5500那么復雜,它就是峰匯物聯(lián)開發(fā)的一款ETH-01串口以太網(wǎng)模塊,外觀如下:
1、硬件管腳說明
2、STM32CubeMX配置
以下根據(jù)目前需要配置為TCP客戶端模式,方便后面與云平臺通信:
2.1、時鐘配置
2.2、調(diào)試接口配置
2.3、調(diào)試串口配置
2.4、網(wǎng)口模塊配置
網(wǎng)口模塊通信串口配置如下,這里用的是USART3:
然后采用串口+DMA的方式來處理。
以下是讀TCP狀態(tài)的IO,配置為上拉輸入模式,用于監(jiān)測網(wǎng)卡是否已經(jīng)連接服務器
以下是配置模式IO,當輸出電平為低時為指令配置模式,當輸出電平為高時為數(shù)據(jù)透傳模式:
2.5、調(diào)試燈配置
2.6、生成工程
3、軟件編程
由于官方?jīng)]有提供MCU的例程,所以只能從頭到尾自己寫啦,由于篇幅原因,這里僅分享其中一部分代碼,完整工程請從我的碼云上clone獲取,以下根據(jù)目前需要配置為TCP客戶端模式,方便后面與云平臺通信:
3.1、串口指令配置模塊之寫命令操作
命令頭1 |
命令頭2 |
命令碼 |
數(shù)據(jù) |
0x57 |
0xAB |
|
|
由于需要進行TCP傳輸,所以只設置紅框圈起來的這幾個指令就好了,還有一個更新指令到EEPROM的在手冊示例里出現(xiàn)。
根據(jù)要求,簡單實現(xiàn)如下函數(shù)(暫時不優(yōu)化,先保證能用即可):
rj45_eth.h頭文件實現(xiàn)如下:
#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; /* 數(shù)據(jù)模式:0:命令模式 1:透傳模式*/
uint8_t bNetMode; /* 網(wǎng)絡工作模式: 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 ; /*服務器連接狀態(tài)*/
} DevicePortConfigS;
extern DevicePortConfigS Deice_Para_Handledef ;
/**********************寫指令函數(shù)*************************/
/*使能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);
/*更新配置參數(shù)到EEPROM*/
uint8_t Update_Config_Para_To_EEPROM(uint16_t delay_ms);
/*執(zhí)行配置參數(shù)*/
uint8_t Runing_Config_Para_To_EEPROM(uint16_t delay_ms);
/*配置RJ45模塊參數(shù)*/
uint8_t Config_RJ45_Module_Para(void);
/**********************寫指令函數(shù)*************************/
/**********************讀指令函數(shù)*************************/
/*獲取芯片工作模式*/
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模塊參數(shù)*/
uint8_t Get_RJ45_Module_Config_Para(void);
/**********************讀指令函數(shù)*************************/
/*使能RJ45配置模式*/
void Enable_RJ45_Config_Mode(void);
/*失能RJ45配置模式*/
void Disable_RJ45_Config_Mode(void);
/*檢測TCP狀態(tài),返回1則為未連接,返回0則已連接*/
/*進入數(shù)據(jù)透傳模式*/
uint8_t Enter_Data_Penetrate_Mode(void);
/*退出數(shù)據(jù)透傳模式*/
uint8_t Quit_Data_Penetrate_Mode(void);
//RJ45發(fā)送網(wǎng)絡透傳數(shù)據(jù)函數(shù),必須在透傳模式下使用
void RJ45_Send_NetWork_Penetrate_Data(char* fmt, ...);
uint8_t Check_TCP_Status(void); #endif //__RJ45_ETH_H
以設置模式為例編寫函數(shù):
/*使能RJ45配置模式*/
void Enable_RJ45_Config_Mode(void)
{
/*關閉空閑中斷,此時不接收非配置模式的數(shù)據(jù),只接收模塊本身指令收發(fā)的回復數(shù)據(jù)*/
__HAL_UART_DISABLE_IT(UART_PORT, UART_IT_IDLE);
HAL_GPIO_WritePin(RJ45_CONFIG_PORT, RJ45_CONFIG_PIN, GPIO_PIN_RESET);
}
/*使能DMA,清除數(shù)據(jù)包*/
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 ;
}
在調(diào)用如上設置指令前,先要將配置引腳拉低,然后開啟DMA接收,接下來按照通信協(xié)議要求將對應的格式填入到發(fā)送Buffer,然后調(diào)用wifi_uart_write_data函數(shù)將協(xié)議數(shù)據(jù)通過串口發(fā)給模塊,在一定超時延時以后,需要檢測DMA接收緩存區(qū)是否有協(xié)議回復AA,如果有則表示該指令設置成果,這樣就完成了寫數(shù)據(jù)的過程,其它指令也是類似的,我們只需要照著手冊實現(xiàn)即可。
3.2、串口指令配置模塊之讀命令操作
命令頭1
命令頭2
命令碼
0x57
0xAB
讀命令比寫命令要簡潔許多,查看手冊主要支持以下指令:
同樣的,由于例程需要進行TCP傳輸,所以只實現(xiàn)紅框圈起來的這幾個指令就好了。
以獲取芯片工作模式、獲取芯片目的IP地址為例,實現(xiàn)如下函數(shù):
/*獲取芯片工作模式*/
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] ;
}
與寫命令操作一樣,在調(diào)用如上讀指令前,先要將配置引腳拉低,然后開啟DMA接收,接下來按照通信協(xié)議要求將對應的格式填入到發(fā)送Buffer,然后延時一段時間,直接查看串口緩存區(qū)對應數(shù)據(jù)即可,但是如上寫法并不嚴謹,更嚴謹?shù)淖龇ㄊ鞘欠衽袛啻谝还不貜土硕嗌賯€字節(jié),然后對每個字節(jié)進行校驗,如果正確才獲取,這里先不考慮優(yōu)化問題,先保證能用即可,其它讀指令函數(shù)也是差不多的邏輯,由于篇幅有限,這里就不貼出來了。
3.3、初始化函數(shù)及與服務器通信過程實現(xiàn)
初始化部分分為配置參數(shù)和獲取參數(shù)兩部分,這里我配置的服務器IP和端口號是移動OneNet的,分別實現(xiàn)如下
/*配置RJ45模塊參數(shù)*/
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模塊參數(shù)如下:\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模塊參數(shù)*/
uint8_t Get_RJ45_Module_Config_Para(void)
{ printf("讀取RJ45模塊配置參數(shù)如下:\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 ;
}
在配置完畢以后獲取模塊配置參數(shù),如果獲取到的模塊配置參數(shù)正確,接下來在網(wǎng)口連接正確的情況下即可以進入數(shù)據(jù)透傳模式,就是直接和服務器打交道了,實現(xiàn)如下:
/*進入數(shù)據(jù)透傳模式*/
uint8_t Enter_Data_Penetrate_Mode(void)
{
/*失能配置模式*/
Disable_RJ45_Config_Mode();
/*使能DMA,清除數(shù)據(jù)包*/
Enable_And_Clear_Data_Packet();
/*開啟空閑中斷,此時接收的是TCP/IP協(xié)議收發(fā)的數(shù)據(jù)*/
__HAL_UART_ENABLE_IT(UART_PORT, UART_IT_IDLE);
Deice_Para_Config_Handledef.dataMode = 1 ; return 0 ;
}
首先需要將配置引腳拉高,然后使能DMA,開啟空閑中斷,然后在中斷服務函數(shù)處編寫空閑中斷處理邏輯:
/**
* @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,則數(shù)據(jù)接收完畢時發(fā)送信號量,否則發(fā)一個全局變量標志位 #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 */
}
當串口觸發(fā)了空閑中斷,則表示一包數(shù)據(jù)已經(jīng)接收完了,這時候就可以將整包數(shù)據(jù)獲取出來,處理獲取數(shù)據(jù)的邏輯在main函數(shù)的while循環(huán)中實現(xiàn):
/**
* @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");
/*配置模塊參數(shù)*/
Config_RJ45_Module_Para(); printf("\r\n");
Read_Config_Para:
/*獲取RJ45模塊參數(shù)*/
Get_RJ45_Module_Config_Para();
/*進入數(shù)據(jù)透傳模式*/
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透傳一次數(shù)據(jù)給服務器*/ if(Count_Timer >= 10000)
{
Count_Timer = 0 ; printf("透傳數(shù)據(jù):\n%s\n", post_http_data); if(1 == Deice_Para_Handledef.tcp_status)
{
RJ45_Send_NetWork_Penetrate_Data(post_http_data); printf("服務器已連接,發(fā)送成功!\n");
} else { printf("服務器未連接,發(fā)送失敗!\n");
}
}
/*3.接收服務器下發(fā)的數(shù)據(jù)*/ if(RJ45r_Handler.BufferReady)
{
RJ45r_Handler.BufferReady = 0 ; printf("接收網(wǎng)絡數(shù)據(jù):\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 */
}
通過自己的服務器發(fā)送測試協(xié)議進行測試,由于這是我私人創(chuàng)建的設備,所以就不將設備ID和api-key公布出來了,結果如下:
之前寫過類似的文章,參考如下即可:
ESP8266實戰(zhàn)貼:使用HTTP POST請求上傳數(shù)據(jù)到公有云OneNet
上傳數(shù)據(jù)流展示:
4、項目開源地址
本節(jié)代碼已同步到碼云的代碼倉庫中,獲取方法如下:
免責聲明:本文內(nèi)容由21ic獲得授權后發(fā)布,版權歸原作者所有,本平臺僅提供信息存儲服務。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!