本文基于STM32F4 LWIP开发手册V3.0中第二章LWIP带操作系统移植和STM32F4 FreeRTOS开发手册_V1.1.并参照博客文章STM32F4XX LWIP+freeRTOS移植(一)_u014453443的博客-CSDN博客_freertos lwip
类似于UCOS+LWIP的移植,需要在sys_arch.c中为协议栈提供邮箱、信号量等机制,并在sys_arch.h中封装定义信号量和消息邮箱。此外,在sys_arch.c中针对邮箱,信号量机制需实现的函数正点原子官方已经在lwip_sys.h中进行了声明,沿用函数声明即可,如下:
直接从正点原子官方FreeRTOS例程进行修改,省的再做FreeRTOS在STM32F407上的移植,本文重点也不是这个。本文选择以FreeRTOS实验20-1 FreeRTOS内存管理实验为基础。将LWIP正点原子官方例程网络实验2 LWIP带UCOS操作系统移植中LWIP文件夹,HARDWEAR文件夹中ETHERNET文件,FWLIB中STM32F4x7_ETH_Driver和STM32F4xx_StdPeriph_Driver文件复制进入相应文件夹位置位置。其中LWIP文件夹为LWIP协议栈相关文件,ETHERNET文件为以太网相关文件,STM32F4x7_ETH_Driver和STM32F4xx_StdPeriph_Driver文件为STM32官方外设驱动文件。打开工程, 在工程上右键 manage project item,然后新增LWIP_APP、LWIP_CORE、LWIP_NETIF、LWIP_API、LWIP_ARCH,实则按照STM32F4 LWIP开发手册V3.0中1.3.5至1.3.6节内容添加相应文件即可。
1.1 FreeRTOS+LWIP移植
1)修改cc.h文件
原代码如下:
首先,在cc.h中修改添加头文件,
cc.h中代码修改如下:
其中,SCB_ICSR_REG定义的寄存器是用于区分当前是任务级还是中断级的,主要在Enter_Critical()与Exit_Critical()中使用,Enter_Critical()与Exit_Critical()这两个函数是在sys_arch.c中实现的,所以此处加extern声明为外部函数,Enter_Critical()用于声明进入保护临界区,Exit_Critical()声明退出保护临界区。此时保留了了三个关于临界区保护的宏定义,这是因为我们对于lwip_sys.h不做修改,仍沿用其中的声明。cc.h修改完成。
2)修改sys_arch.h头文件
因为操作系统从UCOS替换为FreeRTOS ,则相应的消息邮箱,信号量等也要从FreeRTOS来提供。
原代码如下:
代码修改如下:
其中,添加FreeRTOS.h头文件和消息队列及信号量对应的头文件,并根据头文件对LWIP使用的信号量等进行定义。此外,相比之于官方例程,修改部分中定义并未使用指针,参照大佬文章,实则使用过程中区别不大。
3)修改sys_arch.c文件
相应的操作系统头文件要做替换
(1)创建一个消息邮箱
修改如下:
因为FreeRTOS中,使用动态方式创建消息队列,因此并不需要手动分配内存。
(2)释放并删除一个消息邮箱
修改如下:
修改如下:
向队列中发送消息时需要区分是在任务中发送消息还是在中断中发送消息。
(4)
修改如下:
可以看到,发送一次消息和一直发送消息直到成功的区别只是等待时间的区别。
(5)
修改如下:
等待一条消息实则是从消息队列中获取一条消息,一般认为,获取消息不成功即可认为是请求超时。另外,相比之于UCOS操作系统,FreeRTOS 操作系统从消息队列中获取一条消息是直接将消息赋给msg。另外,这里通过设置portMAX_DELAY和portMAX_DELAY-1来表示一直等待获取消息和等待有限时间消息并获取等待时间这两种情况。
(6)
该函数不做修改,因为它只是前一个函数的固定参数简单封装。
(7)
修改如下:
Free RTOS本身并不提供相应的检测函数,因为我们以该消息邮箱中的队列是否为空作为消息邮箱是否有效的判定,另外,在第一个函数中,存在一个LWIP_ASSERT函数,其判定标准即为消息邮箱中的队列是为空作为消息邮箱是否成功创建。
(8)
修改如下:
与上一个函数相对应。
(9)
修改如下:
修改函数并未如同UCOS系统般进行信号量命名。
(10)
修改如下,从函数描述可知,其和之前等待一个消息函数类似。
(11)
涉及到发送信号量则需要考虑任务和中断两种情况。
(12)
修改如下:
Free RTOS所提供的信号量删除函数中已经对*sem做了动态释放,后续再令*sem=NULL,感觉多此一举,但是参照博文就是这样,就不改了。
(13)
修改如下
信号量大于0即有效,我们可以通过这一点来判断信号量是否有效
(14)
修改如下:
令信号量值为0即设置该信号量无效。
(15)
修改如下:
此时可以看到,该函数只是将一个任务创建函数进行封装而已,同时可以看到,原声明中没有任务句柄存在,从而需要我们自己在函数外进行定义,因此,做进一步封装,将任务句柄也封装进来。btw!lwip中tcpip.c文件沿用的是之前的格式的函数,那我们还是不改了吧,即最终修改如上图。
(16)
修改如下:
(17)
因此,我们要完成它
如下:
4)修改lwip_comm.c文件
(1)按照FreeRTOS来写DHCP创建任务函数和DHCP删除函数。如下:
(2)因为在Free RTOS我们采用动态分配内存方式,不需要像UCOS中一样手动申请内存,所以在涉及到任务堆栈申请的部分需要去除。
(3)添加头文件task.h和FreeRTOS.h
5)main.c文件修改
我们在 LWIP带UCOS操作系统移植这个例程的main.c文件为蓝本进行修改。
代码如下:
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "timer.h"
#include "lcd.h"
#include "key.h"
#include "beep.h"
#include "string.h"
#include "malloc.h"
#include "FreeRTOS.h"
#include "task.h"
#include "sram.h"
//**********做LWIP移植添加以下头文件
#include "lan8720.h"
//#include "lwip/netif.h"
#include "lwip_comm.h"
//#include "lwipopts.h"
//#include "cc.h"
/************************************************
ALIENTEK 探索者STM32F407开发板 FreeRTOS实验20-1
FreeRTOS内存管理实验-库函数版本
技术支持:www.openedv.com
淘宝店铺:http://eboard.taobao.com
关注微信公众平台微信号:"正点原子",免费获取STM32资料。
广州市星翼电子科技有限公司
作者:正点原子 @ALIENTEK
************************************************/
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 128
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
//在LCD上显示地址信息任务
//任务优先级
#define DISPLAY_TASK_PRIO 2
//任务堆栈大小
#define DISPLAY_STK_SIZE 128
//任务句柄
TaskHandle_t DisplayTask_Handler;
//任务函数
void display_task(void *pdata);
//LED任务
//任务优先级
#define LED_TASK_PRIO 3
//任务堆栈大小
#define LED_STK_SIZE 64
//任务句柄
TaskHandle_t LedTask_Handler;
//任务函数
void led_task(void *pdata);
//在LCD上显示地址信息
//mode:1 显示DHCP获取到的地址
// 其他 显示静态地址
void show_address(u8 mode)
{
u8 buf[30];
if(mode==2)
{
sprintf((char*)buf,"DHCP IP :%d.%d.%d.%d",lwipdev.ip[0],lwipdev.ip[1],lwipdev.ip[2],lwipdev.ip[3]); //打印动态IP地址
LCD_ShowString(30,130,210,16,16,buf);
sprintf((char*)buf,"DHCP GW :%d.%d.%d.%d",lwipdev.gateway[0],lwipdev.gateway[1],lwipdev.gateway[2],lwipdev.gateway[3]); //打印网关地址
LCD_ShowString(30,150,210,16,16,buf);
sprintf((char*)buf,"NET MASK:%d.%d.%d.%d",lwipdev.netmask[0],lwipdev.netmask[1],lwipdev.netmask[2],lwipdev.netmask[3]); //打印子网掩码地址
LCD_ShowString(30,170,210,16,16,buf);
}
else
{
sprintf((char*)buf,"Static IP:%d.%d.%d.%d",lwipdev.ip[0],lwipdev.ip[1],lwipdev.ip[2],lwipdev.ip[3]); //打印动态IP地址
LCD_ShowString(30,130,210,16,16,buf);
sprintf((char*)buf,"Static GW:%d.%d.%d.%d",lwipdev.gateway[0],lwipdev.gateway[1],lwipdev.gateway[2],lwipdev.gateway[3]); //打印网关地址
LCD_ShowString(30,150,210,16,16,buf);
sprintf((char*)buf,"NET MASK :%d.%d.%d.%d",lwipdev.netmask[0],lwipdev.netmask[1],lwipdev.netmask[2],lwipdev.netmask[3]); //打印子网掩码地址
LCD_ShowString(30,170,210,16,16,buf);
}
}
int main(void)
{
delay_init(168); //延时初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); //中断分组配置
uart_init(115200); //串口波特率设置
//usmart_dev.init(84); //初始化USMART
LED_Init(); //LED初始化
KEY_Init(); //按键初始化
LCD_Init(); //LCD初始化
FSMC_SRAM_Init(); //SRAM初始化
my_mem_init(SRAMIN); //初始化内部内存池
my_mem_init(SRAMEX); //初始化外部内存池
my_mem_init(SRAMCCM); //初始化CCM内存池
POINT_COLOR = RED; //红色字体
LCD_ShowString(30,30,200,20,16,"Explorer STM32F4");
LCD_ShowString(30,50,200,20,16,"LWIP+FreeRTOS Test");
LCD_ShowString(30,70,200,20,16,"ATOM@ALIENTEK");
LCD_ShowString(30,90,200,20,16,"2022/2/28");
xTaskCreate ((TaskFunction_t )start_task, //任务函数
(const char* )"start_task", //任务名称
(uint16_t )START_STK_SIZE ,//任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO ,//任务优先级
(TaskHandle_t* )&StartTask_Handler );//任务句柄
vTaskStartScheduler(); //开启任务调度
}
//start任务
void start_task(void *pdata)
{
while(lwip_comm_init()) //lwip初始化
{
LCD_ShowString(30,110,200,20,16,"Lwip Init failed!"); //lwip初始化失败
delay_ms(500);
LCD_Fill(30,110,230,150,WHITE);
delay_ms(500);
}
LCD_ShowString(30,110,200,20,16,"Lwip Init Success!"); //lwip初始化成功
taskENTER_CRITICAL(); //进入临界区
#if LWIP_DHCP
lwip_comm_dhcp_creat(); //创建DHCP任务
#endif
//******创建TASK1任务
xTaskCreate ((TaskFunction_t )display_task ,
(const char* )"display_task",
(uint16_t )DISPLAY_STK_SIZE ,
(void* )NULL,
(UBaseType_t )DISPLAY_TASK_PRIO ,
(TaskHandle_t* )&DisplayTask_Handler);
//******创建TASK2任务
xTaskCreate ((TaskFunction_t )led_task, //任务函数
(const char* )"led_task", //任务名称
(uint16_t )LED_STK_SIZE ,//任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )LED_TASK_PRIO ,//任务优先级
(TaskHandle_t* )&LedTask_Handler );//任务句柄
vTaskDelete(StartTask_Handler ); //删除开始任务
taskEXIT_CRITICAL (); //退出临界区
}
//显示地址等信息
void display_task(void *pdata)
{
while(1)
{
#if LWIP_DHCP //当开启DHCP的时候
if(lwipdev.dhcpstatus != 0) //开启DHCP
{
show_address(lwipdev.dhcpstatus ); //显示地址信息
vTaskSuspend(DisplayTask_Handler); //显示完地址信息后挂起自身任务
}
#else
show_address(0); //显示静态地址
vTaskSuspend(DisplayTask_Handler); //显示完地址信息后挂起自身任务
#endif //LWIP_DHCP
vTaskDelay(250);
}
}
//led任务
void led_task(void *pdata)
{
while(1)
{
LED0 = !LED0;
vTaskDelay(500); //延时500ms
}
}
(6)在lwipopts.h文件中修改TCP内核任务优先级为最高
#define TCPIP_THREAD_PRIO (configMAX_PRIORITIES - 1) //内核任务优先级
该设置只是沿袭了参照博文,实则在LWIP例程2中没有用到
(7)点开LAN8720.c文件,找到NVIC_IRQChannelPreemptionPriority,可以看到,赋值为0,使ETH_IRQn的优先级是0,比5大,要改成6.
CORTEX内核的优先级是数值越小优先级越高,即0是最高优先级。FreeRTOS为了满足某些应用对中断实时性要求高的需求,使得中断优先级高于某个值之后,就不能调用操作系统的内核函数来提高实时性。这个问题是将中断的优先级设置的高于这个值,却还在中断中调用操作系统提供的API引起的。当把优先级改小也就是数值改大之后,程序能够正常运行。
(8)添加lan8720.h中stm32f4x7_eth.h的头文件路径,
(9)在LWIP_CORE中特别注意去掉lwip_sys.c文件,该文件中的sys_msleep()函数我们在sys_arch.c文件中已经重写,不影响程序运行。
(10)用manage Project按键在FWLB下添加文件stm32f4x7_eth.c。因为在lan8720.c中的DMARxDescToGet等定义是在stm32f4x7_eth.c中给出的。