本文共 10314 字,大约阅读时间需要 34 分钟。
1,修改 #include "w5500_conf.h"头文件的配置
PS:CS、RESET、INI三个引脚的初始化不用在CubeMX中完成,那样配置也麻烦。gpio_for_w5500_config(void)中初始化
#ifndef _W5500_CONF_H_#define _W5500_CONF_H_#include "stm32f1xx_hal.h"#include "stdio.h"#include "types.h"#define __GNUC__typedef void (*pFunction)(void);//typedef void (*pFunction)(void);extern uint8 remote_ip[4]; /* 远端IP地址 */extern uint16 remote_port; /* 远端端口号 */extern uint16 local_port; /* 定义本地端口 */extern uint8 use_dhcp; /* 是否使用DHCP获取IP */extern uint8 use_eeprom; /* 是否使用EEPROM中的IP配置信息 *//*定义SPI作为W5500的硬件接口*/#define WIZ_SPIx SPI2 /* 定义W5500所用的SPI接口 */#define WIZ_SPIx_RCC_CLK_ENABLE() __HAL_RCC_SPI2_CLK_ENABLE() /* 定义W5500所用的SPI接口时钟 */#define WIZ_SPI_GPIO_ClK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE() /* GPIO端口时钟 */#define WIZ_SPIx_GPIO_PORT GPIOB /* GPIO端口 */#define WIZ_SPIx_SCLK_PIN GPIO_PIN_13 /* 定义W5500的时钟管脚 */#define WIZ_SPIx_MISO_PIN GPIO_PIN_14 /* 定义W5500的MISO管脚 */#define WIZ_SPIx_MOSI_PIN GPIO_PIN_15 /* 定义W5500的MOSI管脚 */#define WIZ_SPIx_SCS_PIN GPIO_PIN_9 /* 定义W5500的片选管脚 WIZ_SPIx_SCS_PIN上拉推挽输出 */#define WIZ_SPIx_SCS_PORT GPIOG /* GPIO端口 */#define WIZ_SPIx_SCS_CLK_ENABLE() __HAL_RCC_GPIOG_CLK_ENABLE() /* GPIO端口时钟 */#define WIZ_INT_PIN GPIO_PIN_8 /* 定义W5500的INT管脚 WIZ_INT_PIN 上拉输入 */#define WIZ_INT_PORT GPIOG /* GPIO端口 */#define WIZ_INT_CLK_ENABLE() __HAL_RCC_GPIOG_CLK_ENABLE() /* GPIO端口时钟 */#define WIZ_RESET_PIN GPIO_PIN_15 /* 定义W5500的INT管脚 WIZ_RESET_PIN上拉输出 */#define WIZ_RESET_PORT GPIOG /* GPIO端口 */#define WIZ_RESET_CLK_ENABLE() __HAL_RCC_GPIOG_CLK_ENABLE() /* GPIO端口时钟 */
2,w5500_conf.C下面的几点注意
2-1,这个非常重要,所有的W5500的SPI读写都是基于这个函数的
uint8_t SPI_SendByte(uint8_t byte) //关键点1{ uint8_t d_read,d_send=byte; if(HAL_SPI_TransmitReceive(&hspi_w5500,&d_send,&d_read,1,0xFFFFFF)!=HAL_OK) d_read=0XFF; return d_read; }
2-2,定时器2直接在MX中初始化就可以,不用单独写bsp文件
/***@brief STM32定时器2初始化*@param 无*@return 无*/void timer2_init(void){ MX_TIM2_Init();// GENERAL_TIMx_Init(); /* TIM2开始计时 */ /* 在中断模式下启动定时器 */ HAL_TIM_Base_Start_IT(&htim2);}
2-3,暂时没有用到EEPROM,所有bsp文件移除
2-4,延时函数使用vTaskDelay(time_ms);来代替
/***@brief 毫秒延时函数*@param time_ms:要延时毫秒时间数*@return 无*/void delay_ms( uint32 time_ms ){ vTaskDelay(time_ms);// HAL_Delay(time_ms);// uint32_t temp; // SysTick->LOAD=(uint32_t)time_ms*fac_ms; /*时间加载(SysTick->LOAD为24bit)*/// SysTick->VAL =0x00; /*清空计数器*/// SysTick->CTRL=0x01 ; /*开始倒数*/ // do// {// temp=SysTick->CTRL;// }// while(temp&0x01&&!(temp&(1<<16))); /*等待时间到达*/// SysTick->CTRL=0x00; /*关闭计数器*/// SysTick->VAL =0X00; /*清空计数器*/ }
3,对于任何操作系统而言,任务堆栈大小和任务优先级都非常关键。
3-1,FreeRTOS的任务优先级是数字越大越高。当任务优先级为5的阻塞了,那么低于任务优先级5的所有任务都不会运行
3-2,设置总的堆栈大小为9000,内存动态分配。和前面说的类似LED5_STACK_SIZE<9000/4=2250
当LED5_STACK_SIZE=4000的时候,任务5不运行。
4,C#上位机兼容GBK编码。经过多次测试,C#上位机是采用UTF-8 编码,也支持GBK编码
GBK是在国家标准GB2312基础上扩容后兼容GB2312的标准(好像还不是国家标准)。GBK编码专门用来解决中文编码的,是双字节的。不论中英文都是双字节的。
UTF-8 编码是用以解决国际上字符的一种多字节编码,它对英文使用8位(即一个字节),中文使用24位(三个字节)来编码。对于英文字符较多的论坛则用UTF-8 节省空间。另外,如果是外国人访问你的GBK网页,需要下载中文语言包支持。访问UTF-8编码的网页则不出现这问题。可以直接访问。
GBK包含全部中文字符;
GBK编码,是在标准基础上的扩展规范,使用了双编码方案,其编码范围从8140至FEFE(剔除xx7F),共23940个码位,共收录了21003个汉字
5,FreeRTOS的中断与STM32的一样,0为最高,15位最低
5-1,添加FreeRTOS系统下,NVIC固定全部是抢占优先级0-15,不可修改。
FreeRTOS的配置中没有处理亚优先级这种情况,直接16个优先级0-15.
优先级小于5的不归FreeRTOS管
FreeRTOS最小的优先级
当优先级低于5时候,中断不会被FreeRTOS禁止
5-2,在不添加FreeRTOS系统下
5-3 RTOS的中断测试。成功的关键在于延时函数要使用软件延时(阻塞模式)
void led6_task(void *p_arg) //中断测试任务函数{ printf("进入中断任务测试6\r\n"); //只会表达一次 HAL_TIM_Base_Start_IT(&htim3); HAL_TIM_Base_Start_IT(&htim4); static uint8 total_num=0; while(1) { total_num+=1; if(total_num==5) { printf("关闭中断。。。。。。\r\n"); portDISABLE_INTERRUPTS(); //vTaskDelay(5000);//如果是这个延时函数,将出现问题 blockdelay_ms(5000); printf("打开中断。。。。。。\r\n"); portENABLE_INTERRUPTS(); total_num=0; } printf("中断测试\r\n"); //只会表达一次 vTaskDelay(1000); }}
/*2019/1/7 软件阻塞延时函数,主要用来测试中断处理函数*/void blockdelay_ms(uint16 time){ uint16 i=0; while(time--) { i=9600; //软件的延时不是很准确 while(i--) ; }}
6,从定时器设置
6-2,示波器实际测量工作频率为72M下的输出情况
T=[1/(PSC+1)] *(ARR+1) us
占空比=CRC/(ARR+1)
/* TIM5 init function */
void MX_TIM5_Init(void) { TIM_MasterConfigTypeDef sMasterConfig; TIM_OC_InitTypeDef sConfigOC;htim5.Instance = TIM5;
htim5.Init.Prescaler = 72-1; //72M工作频率 PSC=72-1 htim5.Init.CounterMode = TIM_COUNTERMODE_UP; htim5.Init.Period =10-1; //示波器显示T=10us ARR=10-1 htim5.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim5.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; if (HAL_TIM_PWM_Init(&htim5) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); }sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; if (HAL_TIMEx_MasterConfigSynchronization(&htim5, &sMasterConfig) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); }sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse =10-1; //示波器显示占空比为90%,即高电平=9us CRC=9 sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; if (HAL_TIM_PWM_ConfigChannel(&htim5, &sConfigOC, TIM_CHANNEL_3) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); }HAL_TIM_MspPostInit(&htim5);
}
7,FreeRTOS 支持的调度方式
void led1_task(void *p_arg){ printf("进入任务LED1\r\n"); uint8 task1_num=0; while(1) { task1_num++; HAL_GPIO_TogglePin(LED3_R_GPIO_Port,LED3_R_Pin); taskENTER_CRITICAL(); //进入临界区 printf(" 任务1已经执行:%d次\r\n",task1_num); taskEXIT_CRITICAL(); blockdelay_ms(10); //这个主要用来模拟在CPU正常处理大容量数据的情况 //vTaskDelay(10); }}
本章教程为大家将介绍 FreeRTOS 操作系统支持的任务调度方式:抢占式,时间片和合作式,这部分
算是 FreeRTOS 操作系统的核心了。 对于初学者来说,要一下子就能够理解这些比较困难,需要多花些时 间把这些基本概念搞清楚,然后阅读下源码,深入理解实现方法。关于合作式调度器的特别说明
FreeRTOS 支持的调度方式
FreeRTOS 操作系统支持三种调度方式:抢占式调度,时间片调度和合作式调度。 实际应用主要是 抢占式调度和时间片调度,合作式调度用到的很少。 抢占式调度
每个任务都有不同的优先级,任务会一直运行直到被高优先级任务抢占或者遇到阻塞式的 API 函数, 比如 vTaskDelay。 时间片调度 每个任务都有相同的优先级,任务会运行固定的时间片个数或者遇到阻塞式的 API 函数,比如 vTaskDelay,才会执行同优先级任务之间的任务切换。 什么是调度器 简单的说,调度器就是使用相关的调度算法来决定当前需要执行的任务。所有的调度器有一个共同的 特性: 调度器可以区分就绪态任务和挂起任务(由于延迟,信号量等待,邮箱等待,事件组等待等原因而使 得任务被挂起)。 调度器可以选择就绪态中的一个任务,然后激活它(通过执行这个任务)。 当前正在执行的任务是运 行态的任务。 不同调度器之间最大的区别就是如何分配就绪态任务间的完成时间。 嵌入式实时操作系统的核心就是调度器和任务切换,调度器的核心就是调度算法。任务切换的实现在 不同的嵌入式实时操作系统中区别不大,基本相同的硬件内核架构,任务切换也是相似的。调度算法就有 些区别了。下面我们主要了解一下抢占式调度器和时间片调度器。 抢占式调度器基本概念 在实际的应用中,不同的任务需要不同的响应时间。例如,我们在一个应用中需要使用电机,键盘和 LCD 显示。电机比键盘和 LCD 需要更快速的响应,如果我们使用合作式调度器或者时间片调度,那么电 机将无法得到及时的响应,这时抢占式调度是必须的。 如果使用了抢占式调度,最高优先级的任务一旦就绪,总能得到 CPU 的控制权。 比如,当一个运行 着的任务被其它高优先级的任务抢占,当前任务的 CPU 使用权就被剥夺了,或者说被挂起了,那个高优 先级的任务立刻得到了 CPU 的控制权并运行。 又比如,如果中断服务程序使一个高优先级的任务进入就 绪态,中断完成时,被中断的低优先级任务被挂起,优先级高的那个任务开始运行。 使用抢占式调度器,使得最高优先级的任务什么时候可以得到 CPU 的控制权并运行是可知的,同时 使得任务级响应时间得以最优化。 总的来说,学习抢占式调度要掌握的最关键一点是:每个任务都被分配了不同的优先级,抢占式调度 器会获得就绪列表中优先级最高的任务,并运行这个任务。 FreeRTOS 抢占式调度器的实现 如果用户在 FreeRTOS 的配置文件 FreeRTOSConfig.h 中禁止使用时间片调度, 那么每个任务必须配 置不同的优先级。当 FreeRTOS 多任务启动执行后,基本会按照如下的方式去执行: 首先执行的最高优先级的任务 Task1, Task1 会一直运行直到遇到系统阻塞式的 API 函数,比如延迟, 事件标志等待,信号量等待,Task1 任务会被挂起,也就是释放 CPU 的执行权,让低优先级的任务 得到执行。 FreeRTOS 操作系统继续执行任务就绪列表中下一个最高优先级的任务 Task2,Task2 执行过程中有 两种情况: Task1由于延迟时间到, 接收到信号量消息等方面的原因, 使得 Task1从挂起状态恢复到就绪态, 在抢占式调度器的作用下,Task2 的执行会被 Task1 抢占。 Task2 会一直运行直到遇到系统阻塞式的 API 函数,比如延迟,事件标志等待,信号量等待, Task2 任务会被挂起,继而执行就绪列表中下一个最高优先级的任务。 如果用户创建了多个任务并且采用抢占式调度器的话,基本都是按照上面两条来执行。 根据抢占式调 度器,当前的任务要么被高优先级任务抢占,要么通过调用阻塞式 API 来释放 CPU 使用权让低优先 级任务执行,没有用户任务执行时就执行空闲任务。运行条件:
这里仅对抢占式调度进行说明。 创建 3 个任务 Task1,Task2 和 Task3。 Task1 的优先级为 1,Task2 的优先级为 2,Task3 的优先级为 3。 FreeRTOS 操作系统是设置的数值 越小任务优先级越低,故 Task3 的优先级最高,Task1 的优先级最低。 此框图是 FreeRTOS 操作系统运行过程中的一部分。 运行过程描述如下: 此时任务 Task1 在运行中,运行过程中由于 Task2 就绪,在抢占式调度器的作用下任务 Task2 抢占
Task1 的执行。 Task2 进入到运行态,Task1 由运行态进入到就绪态。 任务 Task2 在运行中,运行过程中由于 Task3 就绪,在抢占式调度器的作用下任务 Task3 抢占 Task2 的执行。 Task3 进入到运行态,Task2 由运行态进入到就绪态。 任务 Task3 运行过程中调用了阻塞式 API 函数,比如 vTaskDelay,任务 Task3 被挂起,在抢占式调 度器的作用下查找到下一个要执行的最高优先级任务是 Task2,任务 Task2 由就绪态进入到运行态。 任务 Task2 在运行中,运行过程中由于 Task3 再次就绪,在抢占式调度器的作用下任务 Task3 抢占 Task2 的执行。 Task3 进入到运行态,Task2 由运行态进入到就绪态。 上面就是一个简单的不同优先级任务通过抢占式调度进行任务调度和任务切换的过程。
时间片调度器基本概念
在小型的嵌入式 RTOS 中,最常用的的时间片调度算法就是 Round-robin 调度算法。这种调度算法 可以用于抢占式或者合作式的多任务中。另外,时间片调度适合用于不要求任务实时响应的情况。 实现 Round-robin 调度算法需要给同优先级的任务分配一个专门的列表,用于记录当前就绪的任务, 并为每个任务分配一个时间片(也就是需要运行的时间长度,时间片用完了就进行任务切换)。FreeRTOS 时间片调度器的实现
在 FreeRTOS 操作系统中只有同优先级任务才会使用时间片调度,另外还需要用户在 FreeRTOSConfig.h 文件中使能宏定义: #define configUSE_TIME_SLICING 1 默认情况下,此宏定义已经在 FreeRTOS.h 文件里面使能了,用户可以不用在 FreeRTOSConfig.h 文 件中再单独使能。 下面我们通过如下的框图来说明一下时间片调度在 FreeRTOS 中的运行过程,让大家有一个形象的认识。
运行条件:
这里仅对时间片调度进行说明。 创建 4 个同优先级任务 Task1,Task2,Task3 和 Task4。 每个任务分配的时间片大小是 5 个系统时钟节拍。 运行过程描述如下: 先运行任务 Task1,运行够 5 个系统时钟节拍后,通过时间片调度切换到任务 Task2。 任务 Task2 运行够 5 个系统时钟节拍后,通过时间片调度切换到任务 Task3。 任务 Task3 在运行期间调用了阻塞式 API 函数,调用函数时,虽然 5 个系统时钟节拍的时间片大小 还没有用完,此时依然会通过时间片调度切换到下一个任务 Task4。 (注意,没有用完的时间片不会 再使用,下次任务 Task3 得到执行还是按照 5 个系统时钟节拍运行) 任务 Task4 运行够 5 个系统时钟节拍后,通过时间片调度切换到任务 Task1。 上面就是一个简单的同优先级任务通过时间片调度进行任务调度和任务切换的过程。Summary:
时间片调度和抢占式调度我们一般都是开启了的,这样优先级相同时,使用时间片调度,优先级不同时,使用抢占式调度。默认情况下,在freertos.h中使能了时间片调度:
而抢占式调度需要我们用户自己开启,一般在freertosconfig.h中使能:
8,阻塞与非阻塞
阻塞
阻塞调用是指调用结果返回之前,当前线程会被挂起。函数只有在得到结果之后才会返回。有人也许会把阻塞调用和同步调用等同起来,实际上它们是不同的。对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回而已。例如,我们在CSocket中调用Receive函数,如果缓冲区中没有数据,这个函数就会一直等待,直到有数据才返回。而此时,当前线程还会继续处理各种各样的消息。如果主窗口和调用函数在同一个线程中,除非你在特殊的界面操作函数中调用,其实主界面还是应该可以刷新。socket接收数据的另外一个函数recv则是一个阻塞调用的例子。当socket工作在阻塞模式的时候, 如果没有数据的情况下调用该函数,则当前线程就会被挂起,直到有数据为止。
非阻塞
非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。
如果delay()是用while,for写的,则会一直占用CPU的使用权,此时不叫阻塞函数。
阻塞式 API 函数的典型函数 vTaskDelay()
9,【一起来玩RTOS系列】之RT-Thread Nano快速创建工程
10,在固件库向HAL库移植的时候,注意重定义问题project warning: #47-D: incompatible redefinition of
11,目前使用FreeRTOS已经实现了HTTP,MQTT一起运行
转载地址:http://qavhn.baihongyu.com/