正点原子阿波罗F429+STM32CubeMX+LAN8720+LWIP:不带操作系统实现网络热插拔

1.前言

此文章是基于正点原子阿波罗F429开发版的,
利用STM32CubeMX新建一个项目带串口printf输出的,请查看我前面的文章,这里跳过新建项目的那些.
点击跳转

2.TM32CubeMX配置

在这里插入图片描述
在这里插入图片描述
1.ETH配置.,除了标记的地方,其它都是默认值
在这里插入图片描述
2.lwip配置.除了标记的地方,其它都是默认值.
3.生成代码.打开项目.

3 代码调试

新建四个文件,分别为:pcf8574.c,pcf8574.h,myiic.c,myiic.h.这四个文件是基于正点原子代码改的,主要是为了复位LAN8720用的.因为LAN8720的复位脚不是直接MCU上的,而是接在PCF8574那里的.但是正点原子的代码是适合在他的教程代码的.不适合我们STM32CubeMX直接生成的代码.我稍作了修改.修改后代码如下.

3.1 pcf8574.c全部代码

#include "pcf8574.h"

//	 
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32F429开发板
//PCF8574驱动代码	   
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//创建日期:2016/1/13
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2014-2024
//All rights reserved									  
// 	

//初始化PCF8574
uint8_t PCF8574_Init(void)
{
    uint8_t temp=0;
    GPIO_InitTypeDef GPIO_Initure;
    __HAL_RCC_GPIOB_CLK_ENABLE();           //使能GPIOB时钟
	
    GPIO_Initure.Pin=GPIO_PIN_12;           //PB12
    GPIO_Initure.Mode=GPIO_MODE_INPUT;      //输入
    GPIO_Initure.Pull=GPIO_PULLUP;          //上拉
    GPIO_Initure.Speed=GPIO_SPEED_HIGH;     //高速
    HAL_GPIO_Init(GPIOB,&GPIO_Initure);     //初始化
    IIC_Init();					            //IIC初始化 	
	//检查PCF8574是否在位
    IIC_Start();    	 	   
	IIC_Send_Byte(PCF8574_ADDR);            //写地址			   
	temp=IIC_Wait_Ack();		            //等待应答,通过判断是否有ACK应答,来判断PCF8574的状态
    IIC_Stop();					            //产生一个停止条件
    PCF8574_WriteOneByte(0XFF);	            //默认情况下所有IO输出高电平
	return temp;
}

//读取PCF8574的8位IO值
//返回值:读到的数据
uint8_t PCF8574_ReadOneByte(void)
{				  
	uint8_t temp=0;		  	    																 
    IIC_Start();    	 	   
	IIC_Send_Byte(PCF8574_ADDR|0X01);   //进入接收模式			   
	IIC_Wait_Ack();	 
    temp=IIC_Read_Byte(0);		   
    IIC_Stop();							//产生一个停止条件	    
	return temp;
}
//向PCF8574写入8位IO值  
//DataToWrite:要写入的数据
void PCF8574_WriteOneByte(uint8_t DataToWrite)
{				   	  	    																 
    IIC_Start();  
    IIC_Send_Byte(PCF8574_ADDR|0X00);   //发送器件地址0X40,写数据 	 
	IIC_Wait_Ack();	    										  		   
	IIC_Send_Byte(DataToWrite);    	 	//发送字节							   
	IIC_Wait_Ack();      
    IIC_Stop();							//产生一个停止条件 
	HAL_Delay(10);	 
}

//设置PCF8574某个IO的高低电平
//bit:要设置的IO编号,0~7
//sta:IO的状态;0或1
void PCF8574_WriteBit(uint8_t bit,uint8_t sta)
{
    __IO uint8_t data;
    data=PCF8574_ReadOneByte(); //先读出原来的设置
    if(sta==0)data&=~(1<<bit);     
    else data|=1<<bit;
    PCF8574_WriteOneByte(data); //写入新的数据
}

//读取PCF8574的某个IO的值
//bit:要读取的IO编号,0~7
//返回值:此IO的值,0或1
uint8_t PCF8574_ReadBit(uint8_t bit)
{
    uint8_t data;
    data=PCF8574_ReadOneByte(); //先读取这个8位IO的值 
    if(data&(1<<bit))return 1;
    else return 0;   
}  
    

3.2 pcf8574.h全部代码

#ifndef __PCF8574_H
#define __PCF8574_H
#include "myiic.h"
//	 
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32F429开发板
//PCF8574驱动代码	   
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//创建日期:2016/1/13
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2014-2024
//All rights reserved									  
// 	

#define PCF8574_INT  PBin(12) //PCF8574 INT脚

#define PCF8574_ADDR 	0X40	//PCF8574地址(左移了一位)

//PCF8574各个IO的功能
#define BEEP_IO         0		//蜂鸣器控制引脚  	P0
#define AP_INT_IO       1   	//AP3216C中断引脚	P1
#define DCMI_PWDN_IO    2    	//DCMI的电源控制引脚	P2
#define USB_PWR_IO      3    	//USB电源控制引脚	P3
#define EX_IO      		4    	//扩展IO,自定义使用 	P4
#define MPU_INT_IO      5   	//MPU9250中断引脚	P5
#define RS485_RE_IO     6    	//RS485_RE引脚		P6
#define ETH_RESET_IO    7    	//以太网复位引脚		P7

uint8_t PCF8574_Init(void); 
uint8_t PCF8574_ReadOneByte(void); 
void PCF8574_WriteOneByte(uint8_t DataToWrite);
void PCF8574_WriteBit(uint8_t bit,uint8_t sta);
uint8_t PCF8574_ReadBit(uint8_t bit);
#endif


3.3 myiic.c全部代码

#include "myiic.h"

//	 
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32F429开发板
//IIC驱动代码	   
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//创建日期:2016/1/13
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2014-2024
//All rights reserved									  
// 	
void delay_us(uint32_t nus)
{		
	uint32_t ticks;
	uint32_t told,tnow,tcnt=0;
	uint32_t reload=SysTick->LOAD;				//LOAD的值	    	 
	ticks=nus*180; 						//需要的节拍数 
	told=SysTick->VAL;        				//刚进入时的计数器值
	while(1)
	{
		tnow=SysTick->VAL;	
		if(tnow!=told)
		{	    
			if(tnow<told)tcnt+=told-tnow;	//这里注意一下SYSTICK是一个递减的计数器就可以了.
			else tcnt+=reload-tnow+told;	    
			told=tnow;
			if(tcnt>=ticks)break;			//时间超过/等于要延迟的时间,则退出.
		}  
	};
}
//IIC初始化
void IIC_Init(void)
{
    GPIO_InitTypeDef GPIO_Initure;
    
    __HAL_RCC_GPIOH_CLK_ENABLE();   //使能GPIOH时钟
    
    //PH4,5初始化设置
    GPIO_Initure.Pin=GPIO_PIN_4|GPIO_PIN_5;
    GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP;  //推挽输出
    GPIO_Initure.Pull=GPIO_PULLUP;          //上拉
    GPIO_Initure.Speed=GPIO_SPEED_FAST;     //快速
    HAL_GPIO_Init(GPIOH,&GPIO_Initure);
    
    IIC_SDA(1);
    IIC_SCL(1);  
	
}

//产生IIC起始信号
void IIC_Start(void)
{
	SDA_OUT();     //sda线输出
	IIC_SDA(1);	  	  
	IIC_SCL(1);
	delay_us(4);
 	IIC_SDA(0);//START:when CLK is high,DATA change form high to low 
	delay_us(4);
	IIC_SCL(0);//钳住I2C总线,准备发送或接收数据 
}	  
//产生IIC停止信号
void IIC_Stop(void)
{
	SDA_OUT();//sda线输出
	IIC_SCL(0);
	IIC_SDA(0);//STOP:when CLK is high DATA change form low to high
 	delay_us(4);
	IIC_SCL(1); 
	IIC_SDA(1);//发送I2C总线结束信号
	delay_us(4);							   	
}
//等待应答信号到来
//返回值:1,接收应答失败
//        0,接收应答成功
uint8_t IIC_Wait_Ack(void)
{
	uint8_t ucErrTime=0;
	SDA_IN();      //SDA设置为输入  
	IIC_SDA(1);delay_us(1);	   
	IIC_SCL(1);delay_us(1);	 
	while(READ_SDA)
	{
		ucErrTime++;
		if(ucErrTime>250)
		{
			IIC_Stop();
			return 1;
		}
	}
	IIC_SCL(0);//时钟输出0 	   
	return 0;  
} 
//产生ACK应答
void IIC_Ack(void)
{
	IIC_SCL(0);
	SDA_OUT();
	IIC_SDA(0);
	delay_us(2);
	IIC_SCL(1);
	delay_us(2);
	IIC_SCL(0);
}
//不产生ACK应答		    
void IIC_NAck(void)
{
	IIC_SCL(0);
	SDA_OUT();
	IIC_SDA(1);
	delay_us(2);
	IIC_SCL(1);
	delay_us(2);
	IIC_SCL(0);
}					 				     
//IIC发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答			  
void IIC_Send_Byte(uint8_t txd)
{                        
    uint8_t t;   
	SDA_OUT(); 	    
    IIC_SCL(0);//拉低时钟开始数据传输
    for(t=0;t<8;t++)
    {              
        IIC_SDA( (txd&0x80)>>7 );
        txd<<=1; 	  
		delay_us(2);   //对TEA5767这三个延时都是必须的
		IIC_SCL(1);
		delay_us(2); 
		IIC_SCL(0);	
		delay_us(2);
    }	 
} 	    
//读1个字节,ack=1时,发送ACK,ack=0,发送nACK   
uint8_t IIC_Read_Byte(unsigned char ack)
{
	unsigned char i,receive=0;
	SDA_IN();//SDA设置为输入
    for(i=0;i<8;i++ )
	{
        IIC_SCL(0); 
        delay_us(2);
		IIC_SCL(1);
        receive<<=1;
        if(READ_SDA)receive++;   
		delay_us(1); 
    }					 
    if (!ack)
        IIC_NAck();//发送nACK
    else
        IIC_Ack(); //发送ACK   
    return receive;
}



3.4 myiic.h全部代码

#ifndef _MYIIC_H
#define _MYIIC_H

#include "main.h"
#include "lwip.h"
#include "usart.h"
#include "gpio.h"
//	 
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32F429开发板
//IIC驱动代码	   
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//创建日期:2016/1/13
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2014-2024
//All rights reserved									  
// 	
//IO方向设置
#define SDA_IN()  {GPIOH->MODER&=~(3<<(5*2));GPIOH->MODER|=0<<5*2;}	//PH5输入模式
#define SDA_OUT() {GPIOH->MODER&=~(3<<(5*2));GPIOH->MODER|=1<<5*2;} //PH5输出模式
//IO操作
#define IIC_SCL(a)   	if(a)	HAL_GPIO_WritePin(GPIOH,GPIO_PIN_4,GPIO_PIN_SET); else HAL_GPIO_WritePin(GPIOH,GPIO_PIN_4,GPIO_PIN_RESET);//SCL
#define IIC_SDA(a)  	if(a)	HAL_GPIO_WritePin(GPIOH,GPIO_PIN_5,GPIO_PIN_SET); else HAL_GPIO_WritePin(GPIOH,GPIO_PIN_5,GPIO_PIN_RESET);//SCL
#define READ_SDA  		HAL_GPIO_ReadPin(GPIOH,GPIO_PIN_5)

//IIC所有操作函数
void IIC_Init(void);                //初始化IIC的IO口				 
void IIC_Start(void);				//发送IIC开始信号
void IIC_Stop(void);	  			//发送IIC停止信号
void IIC_Send_Byte(uint8_t txd);			//IIC发送一个字节
uint8_t IIC_Read_Byte(unsigned char ack);//IIC读取一个字节
uint8_t IIC_Wait_Ack(void); 				//IIC等待ACK信号
void IIC_Ack(void);					//IIC发送ACK信号
void IIC_NAck(void);				//IIC不发送ACK信号

void IIC_Write_One_Byte(uint8_t daddr,uint8_t addr,uint8_t data);
uint8_t IIC_Read_One_Byte(uint8_t daddr,uint8_t addr);	 


#endif


3.5代码调试

main.c中添加头文件:#include “pcf8574.h”

/* USER CODE BEGIN Includes */
#include "stdio.h"
#include "pcf8574.h"
/* USER CODE END Includes */

在main函数中添加PCF8574的初始化函数:PCF8574_Init();

  /* USER CODE BEGIN SysInit */
	PCF8574_Init();
  /* USER CODE END SysInit */

打开ethernetif.c
ethernetif.c添加头文件:#include “pcf8574.h”
在==static void low_level_init(struct netif *netif)==函数中添加下面代码:

  /* USER CODE BEGIN MACADDRESS */
    PCF8574_WriteBit(ETH_RESET_IO,1);       //硬件复位
	HAL_Delay(50);
	PCF8574_WriteBit(ETH_RESET_IO,0);       //复位结束
	HAL_Delay(50);
  /* USER CODE END MACADDRESS */

打开stm32f4xx_hal_eth.c找到==HAL_StatusTypeDef HAL_ETH_Init(ETH_HandleTypeDef *heth)==函数修改里面的两端代码(不修改也没事,只是开机有时候会判断为连接错误,具体原因没找.)

      if((HAL_GetTick() - tickstart ) > ETH_TIMEOUT_LINKED_STATE)
      {
//        /* In case of write timeout */
//        err = ETH_ERROR;
//      
//        /* Config MAC and DMA */
//        ETH_MACDMAConfig(heth, err);
//        
//        heth->State= HAL_ETH_STATE_READY;
//  
//        /* Process Unlocked */
//        __HAL_UNLOCK(heth);
//    
//        return HAL_TIMEOUT;
		  break;
      }
    } while (((phyreg & PHY_LINKED_STATUS) != PHY_LINKED_STATUS));
      if((HAL_GetTick() - tickstart ) > ETH_TIMEOUT_AUTONEGO_COMPLETED)
      {
//        /* In case of write timeout */
//        err = ETH_ERROR;
//      
//        /* Config MAC and DMA */
//        ETH_MACDMAConfig(heth, err);
//        
//        heth->State= HAL_ETH_STATE_READY;
//  
//        /* Process Unlocked */
//        __HAL_UNLOCK(heth);
//    
//        return HAL_TIMEOUT;
		  break;
      }
      
    } while (((phyreg & PHY_AUTONEGO_COMPLETE) != PHY_AUTONEGO_COMPLETE));

新建use_lwip.cuse_lwip.h.为了处理网络热插拔.

3.6 use_lwip.c源码

#include "use_lwip.H"

extern struct netif *netif_default;
extern struct netif gnetif;
extern ETH_HandleTypeDef heth;

static uint8_t b_ip_state = 0;			//ip状态
static uint8_t b_network_cable = 0;		//网线状态

/*插入网线的话重新获取IP操作*/
void ethernetif_notify_conn_changed(struct netif *netif)
{
    if(netif_is_link_up(netif) && !netif_is_up(netif))
    {
        netif_set_up(netif);
        extern err_t dhcp_start(struct netif *netif);
		dhcp_start(netif);
    }
}

/*
读取网线状态
读取IP值
*/
void use_read_network_cable(void)
{

	uint32_t phy_data = 0;
	
	HAL_ETH_ReadPHYRegister(&heth, PHY_BSR, &phy_data);			//读取PHY数据
	
	if(((phy_data & PHY_LINKED_STATUS) != PHY_LINKED_STATUS))	//
	{
		if(!b_network_cable) return;
		b_network_cable = 0;
		printf("未插网线\r\n");
		
		b_ip_state = 0;
		gnetif.ip_addr.addr = 0;
		
		return;
	}
	
	if(!b_network_cable)
	{
		b_network_cable = 1;
		printf("已插网线\r\n");
		return;
	}
	
	if(b_ip_state)return;
	if(gnetif.ip_addr.addr)
	{
		b_ip_state = 1;
		
		uint8_t ip4_number[6];
		ip4_number[0] = gnetif.hwaddr[0];
		ip4_number[1] = gnetif.hwaddr[1];
		ip4_number[2] = gnetif.hwaddr[2];
		ip4_number[3] = gnetif.hwaddr[3];
		ip4_number[4] = gnetif.hwaddr[4];
		ip4_number[5] = gnetif.hwaddr[5];
		printf("MAC地址..............%x.%x.%x.%x.%x.%x\r\n",ip4_number[0],ip4_number[1],ip4_number[2],ip4_number[3],ip4_number[4],ip4_number[5]);
		ip4_number[3] = gnetif.ip_addr.addr >> 24;
		ip4_number[2] = gnetif.ip_addr.addr >> 16;
		ip4_number[1] = gnetif.ip_addr.addr >> 8;
		ip4_number[0] = gnetif.ip_addr.addr >> 0;
		printf("通过DHCP获取到IP地址..............%d.%d.%d.%d\r\n",ip4_number[0],ip4_number[1],ip4_number[2],ip4_number[3]);
		
		ip4_number[3] = gnetif.netmask.addr >> 24;
		ip4_number[2] = gnetif.netmask.addr >> 16;
		ip4_number[1] = gnetif.netmask.addr >> 8;
		ip4_number[0] = gnetif.netmask.addr >> 0;
		printf("通过DHCP获取到子网掩码..............%d.%d.%d.%d\r\n",ip4_number[0],ip4_number[1],ip4_number[2],ip4_number[3]);
		
		ip4_number[3] = gnetif.gw.addr >> 24;
		ip4_number[2] = gnetif.gw.addr >> 16;
		ip4_number[1] = gnetif.gw.addr >> 8;
		ip4_number[0] = gnetif.gw.addr >> 0;
		printf("通过DHCP获取到默认网关..............%d.%d.%d.%d\r\n",ip4_number[0],ip4_number[1],ip4_number[2],ip4_number[3]);
	}
}

void use_lwip_process(void)
{
	MX_LWIP_Process();					//LWIP轮询任务
	ethernetif_set_link(netif_default);	//重新插网线进行获取IP的任务
	
	use_read_network_cable();
}


3.5 use_lwip.h源码

#ifndef __use_lwip_h_
#define __use_lwip_h_

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "lwip.h"
#include "usart.h"
#include "gpio.h"

#include "pcf8574.h"

#include <string.h>

void use_lwip_process(void);

#endif


main.c中添加头文件:#include “use_lwip.h”
在main函数里添加一段如下代码:

  /* USER CODE BEGIN WHILE */
  printf("程序开始\r\n");
  while (1)
  {
	  use_lwip_process();
    /* USER CODE END WHILE */

编译,连接开发版.打开串口调试助手,烧录代码到开发版.可以尝试插拔网线,并查看串口返回的信息.
在这里插入图片描述

要使用STM32CubeMXLAN8720LwIP和FreeRTOS实现网络通信,需要注意以下几点。 首先,STM32CubeMX是一个图形化配置工具,用于为STM32微控制器生成初始化的代码框架。我们可以通过选择所需的外设(如以太网控制器)和配置参数来生成代码。 其次,LAN8720是一个用于实现以太网通信的PHY(物理层)芯片,负责将数据从媒介访问控制层(MAC层)转换为物理传输信号。 接下来,LwIP(Lightweight IP)是一个轻量级的网络协议栈,用于实现TCP/IP协议。我们需要将LwIP集成到项目中,并配置好网络参数,如IP地址、子网掩码和网关。 最后,FreeRTOS是一个流行的实时操作系统,用于管理任务调度和资源管理。我们可以将网络通信任务添加到FreeRTOS的任务列表中,并通过队列和信号量等机制进行任务间通信和同步。 总体实现步骤如下: 1. 使用STM32CubeMX选择并配置以太网控制器和PHY,并生成初始化代码。 2. 配置LwIP网络参数,如IP地址、子网掩码和网关。 3. 将LwIP集成到项目中,包括源代码和相应的头文件。 4. 添加网络通信任务到FreeRTOS的任务列表中。 5. 在网络任务中,使用LwIP提供的API进行网络初始化、连接设置以及数据收发等操作。 6. 通过使用队列或信号量等机制,实现不同任务间的数据共享和同步。 7. 在主函数中初始化FreeRTOS,并启动任务调度器。 通过以上步骤,我们可以利用STM32CubeMXLAN8720LwIP和FreeRTOS实现网络通信功能。这样我们就可以在STM32微控制器上实现网络连接、数据传输等网络应用。
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值