工程源码下载:基于裸机和Freertos的W5500网络通信工程
目录
1. 移植W5500步骤
- 初始化SPI引脚
- 初始化SCS引脚(片选)
- 下载官方库文件
- 添加官方库到工程目录
- 实现SPI接口和库文件的对接
- 调用官方库提供的函数实现连接
2. Cobemx配置步骤
2.1 时钟配置Clock Configuration
所使用的单片机是正点原子的阿波罗底板STM32H743IIT6。
我们看原理图是再29.30引脚接的25MHz的无源晶振。
RCC 配置界面。以外部时钟为例,STM32CubeMX中外部时钟配置可选类型为
Disable
BYPASS Clock Source(旁路时钟源)
Crystal/Ceramic Resonator(石英/陶瓷 晶振)
三种类型,参考手册也将链接电路展示出来了,我们这里用的是无源晶振,故选择第三个方案。
H743凭借外部晶振的加持,最高主频可以达到480MHz。
呢么我们就通过配置时钟树使H743发挥出它最佳状态。
STM32H7 主频在 400~480MHz 下,SPI1,SPI2 和 SPI3 的最高时钟是 200MHz,而 SPI4,5,6 是 100MHz。这里特别注意一点,SPI 工作时最少选择二分频,也就是说 SPI1,2,3 实际通信时钟是100MHz
2.2 Trace and Debug配置
2.3 使能CPU ICache与DCache
STM32F7使用CubeMX生产代码时,可以在Cortex-M7设置里选择是否开启ART ACCLERATOR,ICache,DCache等,如下图
2.4 USART配置
配置调试串口,确定物理引脚PA10\PA9。
时钟配置完成以后,可以在 Connectivity 栏开启UART功能,USART1可以开启的模式有Asynchronous——异步通讯、synchronous——同步通讯、Single Wire(Half-Duplex)——单线(半双工)通讯,此处配置为异步通讯。
2.5 硬件SPI配置
2.6 GPIO的LED配置
PB0和PB1对应开发板LED0和LED1。
2.7 烧录程序时注意
为什么SWD烧录STM32时BOOT0脚要接高电平,否则SWD下载失败。
对配置的程序进行测试测试成功。
2.8 FREERTOS配置
2.9 下载程序后不能运行
进行软件仿真的时候寻踪按键需要执行三次程序测能启动,每次仿真程序都停留在:
解决办法:
记得将Uis Micro LIB勾选。
2.10 提升编译速度
在CobeMX生成的freertos之后,如果我们选择V6编译器会出现很多编译错误,解决办法是:
替换这两个文件,替换的路径如下:
Middlewares\Third_Party\FreeRTOS\Source\portable\RVDS\ARM_CM4F
替换之后编译就不会报错,而且在Browse information勾选的情况下快速编译。
链接在我提供的程序源文件里面:
2.11 下载完程序自动启动
3. W5500硬件连接
10个引脚中,真正使用的也就只有vcc gnd miso mosi sclk scs 这几个脚,RST和int引脚如果是引用官方的库的话,是不需要的(RST是复位引脚,INT是中断触发引脚)。我们这次也需要用到RST引脚。
3.1 初始化SPI
因为W5500模块内部自己实现了TCP/IP协议,在外部只需要通过提供的接口利用SPI把相关的参数传递进去就行了,比如物理地址,本机IP,本机端口,目标端口,目标IP等等。
3.2 初始化GPIO
除去SPI口之外还有一个片选引脚SCS,这个引脚很重要,在官方协议实现的4个函数中,起到的很关键的作用。同时也初始化了复位引脚。
#define CSPin(n) (n? HAL_GPIO_WritePin(SPI1_CS_GPIO_Port,SPI1_CS_Pin,GPIO_PIN_SET):HAL_GPIO_WritePin(SPI1_CS_GPIO_Port,SPI1_CS_Pin,GPIO_PIN_RESET))
#define RSTNPin(n) (n? HAL_GPIO_WritePin(SPI1_RST_GPIO_Port,SPI1_RST_Pin,GPIO_PIN_SET):HAL_GPIO_WritePin(SPI1_RST_GPIO_Port,SPI1_RST_Pin,GPIO_PIN_RESET))
4. W5500程序移植
最新程序下载网址如下:
https://github.com/Wiznet/ioLibrary_Driver
我们解压后需要下面几个
下面介绍以下主要的功能:
application/loopback是一个回环测试的示例程序。
Internet里则是上层多种协议的驱动程序。socket.c 文件夹里面主要是socket的一些函数,比如close connect socket等等函数,实现了一些应用层的封装
w5500.c 文件主要实现的是对W5500模块寄存器的读写和操作,包括读写那些寄存器,地址是多少等等,
wizchip_conf.c 就是一个公共文件主要包括和SPI的连接以及连接W5500读写寄存器函数和socket函数。有用户用于注册函数的几个api以及配置W5X00的一些驱动函数。
DHCP.c 主要就是初始化DHCP,启动DHCP,获取ip,端口等函数
4.1 修改wizchip_conf.h文件
5. 我们要实现的函数bsp_spi_w5500.c
首先根据提供的结构体,将我们要定义的网络配置信息进行赋值:
static wiz_NetInfo NetConf = {
.mac = {0x0c, 0x29, 0xab, 0x7c, 0x04, 0x02}, //mac地址
.ip = {192, 168, 0, 215}, //本机IP地址
.sn = {255, 255, 255, 0}, //子网掩码
.gw = {192, 168, 0, 1}, //网关地址
.dns = {0, 0, 0, 0}, //DNS服务器地址
.dhcp = NETINFO_STATIC //使用静态IP
};
这个结构体存在于wizchip_conf.c,其中NetConf是一个wiz_NetInfo类型的结构体变量,该结构体在wizchip_conf.h中定义,用于设置mac地址、IP地址等网络参数,具体如下:
typedef struct wiz_NetInfo_t
{
uint8_t mac[6]; ///< Source Mac Address
uint8_t ip[4]; ///< Source IP Address
uint8_t sn[4]; ///< Subnet Mask
uint8_t gw[4]; ///< Gateway IP Address
uint8_t dns[4]; ///< DNS server IP Address
dhcp_mode dhcp; ///< 1 - Static, 2 - DHCP
}wiz_NetInfo;
接下来我们就要编写网络初始化的配置函数,先把程序贴出来,然后再慢慢分解:
void set_w5500_conf(void)
{
reset();//芯片复位
//注册函数,用户通过实现SPI注册回调函数来访问WIZCHP
reg_wizchip_cris_cbfunc(SPI_CrisEnter, SPI_CrisExit); //注册临界区函数
reg_wizchip_cs_cbfunc(SPI_CS_Select, SPI_CS_Deselect); //注册SPI片选信号函数
reg_wizchip_spi_cbfunc(SPI_ReadByte, SPI_WriteByte); //注册读写函数
/***********************物理层配置*************************/
//初始化芯片参数
ctlwizchip(CW_RESET_WIZCHIP,(void *)0);//软件重置
uint8_t memsize[16] = {2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2};
uint8_t tmp;
//WIZCHIP SOCKET缓存区初始化
ctlwizchip(CW_INIT_WIZCHIP, &memsize);
//PHY物理层连接状态检查
ctlwizchip(CW_GET_PHYLINK, (void*)&tmp);
/*******************获取芯片ID********************/
uint8_t id[6];//获取芯片ID
ctlwizchip(CW_GET_ID,(void*)id);//获取ID
/*******************设置网络配置********************/
ctlnetwork(CN_SET_NETINFO, (void*)&NetConf);
/*******************读取网络配置********************/
wiz_NetInfo conf;
ctlnetwork(CN_GET_NETINFO, (void*)&conf); //读取设置网络状态
if(memcmp(&conf,&NetConf,sizeof(wiz_NetInfo)) == 0){
printf("set network parameters success");// 配置成功
}else{
printf("set network parameters fail"); // 配置失败
}
/*******************配置超时********************/
wiz_NetTimeout timeout;
timeout.retry_cnt = 8; // 重试次数,默认8次
timeout.time_100us = 2000; // 超时时间,默认2000*100us = 200ms
wizchip_settimeout(&timeout);
/*******************打印配置信息********************/
printf("w5500 network infomation:\r\n");
printf("w5500 -mac:%d:%d:%d:%d:%d:%d\r\n", conf.mac[0], conf.mac[1], conf.mac[2],
conf.mac[3], conf.mac[4], conf.mac[5]);
printf("w5500 -ip:%d.%d.%d.%d\r\n", conf.ip[0], conf.ip[1], conf.ip[2], conf.ip[3]);
printf("w5500 -sn:%d.%d.%d.%d\r\n", conf.sn[0], conf.sn[1], conf.sn[2], conf.sn[3]);
printf("w5500 -gw:%d.%d.%d.%d\r\n", conf.gw[0], conf.gw[1], conf.gw[2], conf.gw[3]);
printf("w5500 -dns:%d.%d.%d.%d\r\n", conf.dns[0], conf.dns[1], conf.dns[2], conf.dns[3]);
}
5.1 W5500芯片复位
reset();//芯片复位
void reset()
{
RSTNPin(0);
HAL_Delay(100);
RSTNPin(1);
HAL_Delay(1500);
}
5.2 将SPI函数进行与W5500进行注册
//注册函数,用户通过实现SPI注册回调函数来访问WIZCHP
reg_wizchip_cris_cbfunc(SPI_CrisEnter, SPI_CrisExit); //注册临界区函数
reg_wizchip_cs_cbfunc(SPI_CS_Select, SPI_CS_Deselect); //注册SPI片选信号函数
reg_wizchip_spi_cbfunc(SPI_ReadByte, SPI_WriteByte); //注册读写函数
在spi里实现一些函数,然后把函数名赋值给wizchip_conf.c里面的一些函数指针,这样W5500.c里面的函数实现读写的时候,就能够直接使用SPI接口了,具体实现的函数有以下6个。
void SPI1_WriteByte(u8 TxData)
uint8_t SPI1_ReadByte()
void SPI_CrisEnter(void)
void SPI_CrisExit(void)
void SPI_CS_Select(void)
void SPI_CS_Deselect(void)
/**
* @brief 写1字节数据到SPI总线
* @param TxData 写到总线的数据
* @retval None
*/
void SPI_WriteByte(uint8_t TxData)
{
HAL_SPI_Transmit(&SPI_HANDLE, &TxData, 1, 0xFF);
}
/**
* @brief 从SPI总线读取1字节数据
* @retval 读到的数据
*/
uint8_t SPI_ReadByte(void)
{
uint8_t send = 0xff;
uint8_t recv;
HAL_SPI_TransmitReceive(&SPI_HANDLE, &send, &recv, 1, 0xff);
return recv;
}
/**
* @brief 进入临界区
* @retval None
*/
void SPI_CrisEnter(void)
{
__set_PRIMASK(1);
}
/**
* @brief 退出临界区
* @retval None
*/
void SPI_CrisExit(void)
{
__set_PRIMASK(0);
}
/**
* @brief 片选信号输出低电平
* @retval None
*/
void SPI_CS_Select(void)
{
CSPin(0); //低电平有效
}
/**
* @brief 片选信号输出高电平
* @retval None
*/
void SPI_CS_Deselect(void)
{
CSPin(1);
}
实现了这几个函数之后,需要把他们注册到库函数里面,利用函数指针对上述函数进行调用。
前面说了库文件wizchip_conf.c 里面就包括了和SPI口的对接实现指针函数。例如:
void reg_wizchip_spi_cbfunc(uint8_t (*spi_rb)(void), void (*spi_wb)(uint8_t wb))
{
while(!(WIZCHIP.if_mode & _WIZCHIP_IO_MODE_SPI_));
if(!spi_rb || !spi_wb)
{
WIZCHIP.IF.SPI._read_byte = wizchip_spi_readbyte;
WIZCHIP.IF.SPI._write_byte = wizchip_spi_writebyte;
}
else
{
WIZCHIP.IF.SPI._read_byte = spi_rb;
WIZCHIP.IF.SPI._write_byte = spi_wb;
}
}
5.3 收发缓存设置
分别指定16个端口上收和发缓冲区的大小(KB),收和发分别加起来不能超过16K。默认每个端口的收发端口分别为2K。
/***********************物理层配置*************************/
//初始化芯片参数
ctlwizchip(CW_RESET_WIZCHIP,(void *)0);//软件重置
uint8_t memsize[16] = {2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2};
uint8_t tmp;
//WIZCHIP SOCKET缓存区初始化
ctlwizchip(CW_INIT_WIZCHIP, &memsize);
//PHY物理层连接状态检查
ctlwizchip(CW_GET_PHYLINK, (void*)&tmp);
5.4 获取芯片ID
id里是固定的 “W5500”。
/*******************获取芯片ID********************/
uint8_t id[6];//获取芯片ID
ctlwizchip(CW_GET_ID,(void*)id);//获取ID
5.5 设置网络配置
/*******************设置网络配置********************/
ctlnetwork(CN_SET_NETINFO, (void*)&NetConf);
5.6 读取网络配置
/*******************读取网络配置********************/
wiz_NetInfo conf;
ctlnetwork(CN_GET_NETINFO, (void*)&conf); //读取设置网络状态
if(memcmp(&conf,&NetConf,sizeof(wiz_NetInfo)) == 0){
printf("set network parameters success");// 配置成功
}else{
printf("set network parameters fail"); // 配置失败
}
配置成功会串口打印
5.7 配置超时
如发起tcp链接、发送数据等的时候,如果一段时间没有答复(即超过超时时间),W5500会重发,直到超过重试次数还没有得到答复,就会触发超时中断。
/*******************配置超时********************/
wiz_NetTimeout timeout;
timeout.retry_cnt = 8; // 重试次数,默认8次
timeout.time_100us = 2000; // 超时时间,默认2000*100us = 200ms
wizchip_settimeout(&timeout);
5.8 打印配置信息
加在配置网络函数内void set_w5500_ip(void),配置完成即可通过串口进行打印。
/*******************打印配置信息********************/
printf("w5500 network infomation:\r\n");
printf(" -mac:%d:%d:%d:%d:%d:%d\r\n", conf.mac[0], conf.mac[1], conf.mac[2],
conf.mac[3], conf.mac[4], conf.mac[5]);
printf(" -ip:%d.%d.%d.%d\r\n", conf.ip[0], conf.ip[1], conf.ip[2], conf.ip[3]);
printf(" -sn:%d.%d.%d.%d\r\n", conf.sn[0], conf.sn[1], conf.sn[2], conf.sn[3]);
printf(" -gw:%d.%d.%d.%d\r\n", conf.gw[0], conf.gw[1], conf.gw[2], conf.gw[3]);
printf(" -dns:%d.%d.%d.%d\r\n", conf.dns[0], conf.dns[1], conf.dns[2], conf.dns[3]);
6. 实验现象
网络配置到这里就算完成了,将这个函数进行初始化后就可以ping通了。