使用STM32_HAL库实现W5500以太网通讯
STM32_HAL_W5500_以太网通讯
本文代码地址: github
本文使用STM32F103通过W5500与PC建立UDP通讯。
W5500与MCU连接如下图所示。
1.使用CubeMX建立工程
直接参考我GitHub仓库里的CubeMX工程即可。
注意SPI的配置,配置好SPI、CS、INT、RST即可。
2.下载W5500官方库
下载地址:github
将socket.c、wizchip_conf.c、w5500.c加入工程即可。
3.初始化W5500
初始化代码写在MyUDP.c中,具体如下:
初始化所需函数:
//片选
void W5500_Select(void) {
HAL_GPIO_WritePin(W5500_CS_GPIO_Port, W5500_CS_Pin, GPIO_PIN_RESET);
}
void W5500_Unselect(void) {
HAL_GPIO_WritePin(W5500_CS_GPIO_Port, W5500_CS_Pin, GPIO_PIN_SET);
}
void W5500_Restart(void) {
HAL_GPIO_WritePin(W5500_RST_GPIO_Port, W5500_RST_Pin, GPIO_PIN_RESET);
HAL_Delay(1); // delay 1ms
HAL_GPIO_WritePin(W5500_RST_GPIO_Port, W5500_RST_Pin, GPIO_PIN_SET);
HAL_Delay(1600); // delay 1600ms
}
void W5500_ReadBuff(uint8_t* buff, uint16_t len) {
HAL_SPI_Receive(&hspi1, buff, len, HAL_MAX_DELAY);
}
void W5500_WriteBuff(uint8_t* buff, uint16_t len) {
HAL_SPI_Transmit(&hspi1, buff, len, HAL_MAX_DELAY);
}
uint8_t W5500_ReadByte(void) {
uint8_t byte;
W5500_ReadBuff(&byte, sizeof(byte));
return byte;
}
void W5500_WriteByte(uint8_t byte) {
W5500_WriteBuff(&byte, sizeof(byte));
}
//配置W5500网络信息
wiz_NetInfo gSetNetInfo = {
.mac = {0x00, 0x08, 0xdc, 0x11, 0x11, 0x11},
.ip = {192, 168, 1, 1},
.sn = {255, 255, 255, 0},
.gw = {192, 168, 3, 1},
.dns = {144, 144, 144, 144},
.dhcp = NETINFO_STATIC};
wiz_NetInfo gGetNetInfo;
enum Status
{
Failed = 0,
Success = 1
};
/**
* @brief valid the result of set net info
* @return 1: Success
* 0: Failed
*/
uint8_t validSetNetInfoResult(wiz_NetInfo* _set, wiz_NetInfo* _get)
{
return (!memcmp(_set, _get, sizeof(wiz_NetInfo))); // if same, memcmp return 0
}
接着在主函数中调用Init函数:
void UDPinit(void)
{
reg_wizchip_cs_cbfunc(W5500_Select, W5500_Unselect);
reg_wizchip_spi_cbfunc(W5500_ReadByte, W5500_WriteByte);
W5500_Restart(); // hardware restart through RESET pin
ctlnetwork(CN_SET_NETINFO, (void*)&gSetNetInfo); // set net info
// maybe need delay
ctlnetwork(CN_GET_NETINFO, (void*)&gGetNetInfo); // get net info
// W5500 has 8 channel, 32k buffer, 2 means 2KBytes
uint8_t buffer_size_8channel_tx_rx[16] = {2, 2, 2, 2, 2, 2, 2, 2, // 8 channel tx
2, 2, 2, 2, 2, 2, 2, 2}; // 8 channel rx
if(ctlwizchip(CW_INIT_WIZCHIP,(void*)buffer_size_8channel_tx_rx))
{
// failed
}
uint8_t sta = getSn_SR(SOCK_UDPS);
if(sta == SOCK_CLOSED)
{
socket(SOCK_UDPS, Sn_MR_UDP, 5001, 0);
}
HAL_Delay(100);
}
回环测试函数与Send函数:
void do_udp(void)
{
uint16_t len=0;
switch(getSn_SR(SOCK_UDPS)) /*获取socket的状态*/
{
case SOCK_CLOSED: /*socket处于关闭状态*/
socket(SOCK_UDPS,Sn_MR_UDP,5001,0); /*初始化socket*/
break;
case SOCK_UDP: /*socket初始化完成*/
HAL_Delay(10);
if(getSn_IR(SOCK_UDPS) & Sn_IR_RECV) //检查是否有接收中断
{
setSn_IR(SOCK_UDPS, Sn_IR_RECV); /*清接收中断*/
}
if((len=getSn_RX_RSR(SOCK_UDPS))>0) /*接收到数据*/
{
recvfrom(SOCK_UDPS,buff, len, remote_ip,&remote_port); /*W5500接收计算机发送来的数据*/
sendto(SOCK_UDPS,buff,len-8, remote_ip, remote_port); /*W5500把接收到的数据发送*/
memset(buff, 0, sizeof(buff));
}
break;
}
}
void UDP_send(uint8_t* data,uint8_t len)
{
sendto(SOCK_UDPS, data, len, remote_ip, remote_port);
memset(data, 0, len);
}
利用定时器每隔1s向PC发送信息:
void TIM2_IRQHandler(void)
{
/* USER CODE BEGIN TIM2_IRQn 0 */
HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
if(a == 0)
{
UDP_send_buff[0] = 0x01;
UDP_send_buff[1] = 0x02;
UDP_send_buff[2] = 0x03;
UDP_send_buff[3] = 0x04;
UDP_send(UDP_send_buff,4);
a = 1;
}
else
{
UDP_send_buff[0] = 0x05;
UDP_send_buff[1] = 0x06;
UDP_send(UDP_send_buff,2);
a = 0;
}
/* USER CODE END TIM2_IRQn 0 */
HAL_TIM_IRQHandler(&htim2);
/* USER CODE BEGIN TIM2_IRQn 1 */
/* USER CODE END TIM2_IRQn 1 */
}
4.测试
可使用Github仓库中C#写的上位机进行测试,上位机每4S向STM32发送一次数据
(值得注意的是,实际上PC发送时的IP是由操作系统自动分配的,也就是您必须先给STM32发送一次数据,由recvfrom函数获取PC的IP,传值给remote_ip,您才能顺利发送数据给PC)
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Text;
class Program
{
static UdpClient receiver;
static IPEndPoint RemoteEndPoint = new IPEndPoint(IPAddress.Parse("192.168.1.1"), 5001); // STM32的IP和端口
static ManualResetEventSlim stopReceiver = new ManualResetEventSlim(false);
static void Main(string[] args)
{
receiver = new UdpClient(5002);
// 创建并启动接收数据的线程
Thread receiveThread = new Thread(ReceiveData);
receiveThread.Start();
// 创建并启动定时发送数据的线程
Thread sendThread = new Thread(SendDataPeriodically);
sendThread.Start();
Console.WriteLine("按任意键退出...");
Console.ReadKey();
// 请求线程停止
stopReceiver.Set();
// 等待线程结束
receiveThread.Join();
sendThread.Join();
receiver.Close();
}
static void ReceiveData()
{
while (!stopReceiver.IsSet)
{
try
{
byte[] data = receiver.Receive(ref RemoteEndPoint);
//Console.WriteLine($"接收到数据的来源: {RemoteEndPoint.Address}:{RemoteEndPoint.Port}");
Console.Write($"STM32 -> PC:({RemoteEndPoint.Address}:{RemoteEndPoint.Port}) ");
for (int i = 0; i < data.Length; i++)
{
Console.Write("{0:x2} ", data[i]);
}
Console.WriteLine(); // 换行
}
catch (ObjectDisposedException) // 可能在关闭receiver时抛出
{
return;
}
catch (SocketException ex)
{
Console.WriteLine($"Socket异常: {ex.Message}");
}
}
}
static void SendDataPeriodically()
{
//string messageToSend = "Hello from PC!";
//byte[] sendData = Encoding.UTF8.GetBytes(messageToSend);
string sourceIp = "192.168.1.2"; // 示例IP,根据实际情况替换
int sourcePort = 5002; // 假定的发送端口,如果是从特定端口发送则应获取该端口
byte[] sendData = {0xFF,0xFF, 0xFF, 0xFF };
string messageToSend = ByteArrayToHexadecimalString(sendData);
while (!stopReceiver.IsSet)
{
try
{
receiver.Send(sendData, sendData.Length, RemoteEndPoint);
//Console.WriteLine($"PC -> STM32:({sourceIp}:{sourcePort}) {messageToSend}");
Console.WriteLine($"\u001b[33mPC -> STM32:({sourceIp}:{sourcePort}) {messageToSend}\u001b[0m");
}
catch (Exception ex)
{
Console.WriteLine($"发送失败: {ex.Message}");
}
Thread.Sleep(4000); // 每隔2秒发送一次
}
}
public static string ByteArrayToHexadecimalString(byte[] byteArray)
{
StringBuilder hexString = new StringBuilder(byteArray.Length * 2);
foreach (byte b in byteArray)
{
hexString.Append(b.ToString("X2"));
hexString.Append(" ");
}
return hexString.ToString();
}
}
作者:zzZ··*