从零入门 FreeRTOS 操作系统之创建任务流程
从零入门 FreeRTOS 操作系统之创建任务
1 启动方式
在 main()
函数中将硬件和 RTOS
系统先初始化好,在主函数内部创建一个启动任务后就启动调度器,然后在启动任务里面创建各种应用任务,当所有任务都创建成功后,启动任务把自己删除。
由于 AppTaskCreate
任务执行一次之后就进行删除,并不影响系统的运行,所以,只执行一次的任务在执行完毕要记得及时删除。
2 创建静态内存任务
创建 SRAM
静态内存任务的要点是:configSUPPORT_STATIC_ALLOCATION
这个宏定义必须为 1
(在 FreeRTOSConfig.h
文件中)。并且任务使用的栈和任务控制块都使用 静态内存
,即预先定义好的全局变量,这些预先定义好的全局变量都存在内部的 SRAM
中。
代码编写流程1
打开 FreeRTOSConfig.h
文件,将宏定义 configSUPPORT_STATIC_ALLOCATION
的值改为 1
,代码如下所示:
//支持静态内存
#define configSUPPORT_STATIC_ALLOCATION 1
2.1 定义任务函数
任务实际上就是一个无限循环且不带返回值的 C
函数。
注意事项1: 任务必须是一个死循环,否则任务将通过 LR
返回,如果 LR
指向了非法的内存就会产生 HardFault_Handler
,而 FreeRTOS
指向一个死循环,那么任务返回之后就在死循环中执行,这样子的任务是不安全的,所以避免这种情况,任务一般都是死循环并且无返回值的。
注意事项2: 任务里面的延时函数必须使用 FreeRTOS
里面提供的延时函数,并不能使用我们裸机编程中的那种延时。
这两种的延时的区别是 FreeRTOS
里面的延时是阻塞延时,即调用 vTaskDelay()
函数的时候,当前任务会被挂起,调度器会切换到其它就绪的任务,从而实现多任务。如果还是使用裸机编程中的那种延时,那么整个任务就成为了一个死循环,如果恰好该任务的优先级是最高的,那么系统永远都是在这个任务中运行,比它优先级更低的任务无法运行,根本无法实现多任务。
代码编写流程2
根据启动方式,我们需要创建一个启动任务,让它专门负责创建各种应用任务,因此需要同时创建启动任务、应用任务。
首先,我们在主函数的后面定义应用任务函数,如下代码所示:
/*********************************************************************** @ 函数名 : LED_Task* @ 功能说明: LED_Task任务主体* @ 参数 : * @ 返回值 : 无********************************************************************/
static void LED_Task(void* parameter)
{ while (1){LED1_ON;vTaskDelay(500); /* 延时500个tick */printf("LED_Task Running,LED1_ON\r\n");LED1_OFF; vTaskDelay(500); /* 延时500个tick */ printf("LED_Task Running,LED1_OFF\r\n");}
}
通过上述代码,可以看出此应用任务的功能是让 LED
间隔 1
秒闪烁一次,通过串口输出文字的方式查看当前任务的运行情况。
之后,我们同样在主函数的后面定义 启动任务函数
,代码如下所示:
/************************************************************************ @ 函数名 : AppTaskCreate* @ 功能说明: 为了方便管理,所有的任务创建函数都放在这个函数里面* @ 参数 : 无 * @ 返回值 : 无**********************************************************************/
static void AppTaskCreate(void)
{taskENTER_CRITICAL(); //进入临界区/* 创建LED_Task任务 */LED_Task_Handle = xTaskCreateStatic((TaskFunction_t )LED_Task, //任务函数(const char* )"LED_Task", //任务名称(uint32_t )128, //任务堆栈大小(void* )NULL, //传递给任务函数的参数(UBaseType_t )4, //任务优先级(StackType_t* )LED_Task_Stack, //任务堆栈(StaticTask_t* )&LED_Task_TCB); //任务控制块 if(NULL != LED_Task_Handle) /* 创建成功 */printf("LED_Task任务创建成功!\n");elseprintf("LED_Task任务创建失败!\n");vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务taskEXIT_CRITICAL(); //退出临界区
}
此启动任务的函数的功能就是创建应用任务,里面的代码下面会着重讲,在这只需要知道其创建应用任务即可。
同时通过上面这个函数我们还可以看到,当创建应用任务完毕之后,启动任务自行删除:vTaskDelete(AppTaskCreate_Handle);
为了代码的规范性,还需将 应用任务函数
、启动任务的功能函数
在主函数的前面进行声明。
2.2 空闲任务与定时器任务堆栈函数
用户设定空闲(Idle
)任务与定时器(Timer
)任务的堆栈大小分别由 vApplicationGetIdleTaskMemory()
与 vApplicationGetTimerTaskMemory()
两个函数实现,且必须由用户自己分配,而不能是动态分配。
代码编写流程3
在主函数的前面创建如下代码所示的 空闲、定时器任务堆栈及空闲、定时器任务控制块
代码:
// 声明空闲任务、定时器任务堆栈及控制块
/* 空闲任务任务堆栈 */
static StackType_t Idle_Task_Stack[configMINIMAL_STACK_SIZE];
/* 定时器任务堆栈 */
static StackType_t Timer_Task_Stack[configTIMER_TASK_STACK_DEPTH];/* 空闲任务控制块 */
static StaticTask_t Idle_Task_TCB;
/* 定时器任务控制块 */
static StaticTask_t Timer_Task_TCB;
之后需要在主函数的后面,创建获取空闲、定时器任务堆栈及任务控制块的代码,如下所示:
// 获取空闲任务、定时器任务堆栈及控制块内存
/************************************************************************* @brief 获取空闲任务的任务堆栈和任务控制块内存* ppxTimerTaskTCBBuffer : 任务控制块内存* ppxTimerTaskStackBuffer : 任务堆栈内存* pulTimerTaskStackSize : 任务堆栈大小* @author fire* @version V1.0* @date 2018-xx-xx***********************************************************************/
void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize)
{*ppxIdleTaskTCBBuffer=&Idle_Task_TCB;/* 任务控制块内存 */*ppxIdleTaskStackBuffer=Idle_Task_Stack;/* 任务堆栈内存 */*pulIdleTaskStackSize=configMINIMAL_STACK_SIZE;/* 任务堆栈大小 */
}/************************************************************************ @brief 获取定时器任务的任务堆栈和任务控制块内存* ppxTimerTaskTCBBuffer : 任务控制块内存* ppxTimerTaskStackBuffer : 任务堆栈内存* pulTimerTaskStackSize : 任务堆栈大小* @author fire* @version V1.0* @date 2018-xx-xx***********************************************************************/
void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTimerTaskTCBBuffer, StackType_t **ppxTimerTaskStackBuffer, uint32_t *pulTimerTaskStackSize)
{*ppxTimerTaskTCBBuffer=&Timer_Task_TCB;/* 任务控制块内存 */*ppxTimerTaskStackBuffer=Timer_Task_Stack;/* 任务堆栈内存 */*pulTimerTaskStackSize=configTIMER_TASK_STACK_DEPTH;/* 任务堆栈大小 */
}
2.3 定义任务栈
目前我们只创建了一个任务,当任务进入延时的时候,因为没有另外就绪的用户任务,那么系统就会进入空闲任务,空闲任务是 FreeRTOS
系统自己启动的一个任务,优先级最低。当整个系统都没有就绪任务的时候,系统必须保证有一个任务在运行,空闲任务就是为这个设计的。当用户任务延时到期,又会从空闲任务切换回用户任务。
在 FreeRTOS
系统中,每一个任务都是独立的,他们的运行环境都单独的保存在他们的栈空间当中。那么在定义好任务函数之后,我们还要为任务定义一个栈,目前我们使用的是静态内存,所以任务栈是一个独立的全局变量。
任务的栈占用的是 MCU
内部的 RAM
,当任务越多的时候,需要使用的栈空间就越大,即需要使用的RAM 空间就越多。一个 MCU
能够支持多少任务,就得看你的 RAM
空间有多少。
代码编写流程4
从上文可以知道,我们的 FreeRTOS
操作系统的启动方式是:通过创建一个启动任务,然后启动此任务,启动完毕之后,再创建应用任务,创建完毕自行删除。
因此,我们需要将启动任务、应用任务所占用的栈空间定义出来。
接下来在主函数的前面定义 启动任务 AppTaskCreate 任务堆栈及应用任务 LED 任务堆栈
,代码如下所示:
/* AppTaskCreate 任务任务堆栈 */
static StackType_t AppTaskCreate_Stack[128];
/* LED 任务堆栈 */
static StackType_t LED_Task_Stack[128];
在大多数系统中需要做栈空间地址对齐,在 FreeRTOS
中是以 8
字节大小对齐,并且会检查堆栈是否已经对齐,其中 portBYTE_ALIGNMENT
是在 portmacro.h
里面定义的一个宏,其值为 8
,就是配置为按 8
字节对齐,当然用户可以选择按 1、2、4、8、16、32
等字节对齐,目前默认为 8
。如果有需要,可以打开 portmacro.h
文件,找到 portBYTE_ALIGNMENT
宏定义,并更改其值。
在文件中声明 portBYTE_ALIGNMENT
宏可以应有的值,如下所示:
#if portBYTE_ALIGNMENT == 32#define portBYTE_ALIGNMENT_MASK ( 0x001f )
#endif#if portBYTE_ALIGNMENT == 16#define portBYTE_ALIGNMENT_MASK ( 0x000f )
#endif#if portBYTE_ALIGNMENT == 8#define portBYTE_ALIGNMENT_MASK ( 0x0007 )
#endif#if portBYTE_ALIGNMENT == 4#define portBYTE_ALIGNMENT_MASK ( 0x0003 )
#endif#if portBYTE_ALIGNMENT == 2#define portBYTE_ALIGNMENT_MASK ( 0x0001 )
#endif#if portBYTE_ALIGNMENT == 1#define portBYTE_ALIGNMENT_MASK ( 0x0000 )
#endif
2.4 定义任务控制块
定义好任务函数和任务栈之后,我们还需要为任务定义一个任务控制块,通常我们称这个任务控制块为任务的身份证。在 C
代码上,任务控制块就是一个结构体,里面有非常多的成员,这些成员共同描述了任务的全部信息。
代码编写流程5
在主函数的前面定义如下代码所示的 AppTaskCreate 、LED_Task
任务控制块。
/* AppTaskCreate 任务控制块 */
static StaticTask_t AppTaskCreate_TCB;
/* LED_Task 任务控制块 */
static StaticTask_t LED_Task_TCB;
2.5 静态创建任务
一个任务的三要素是 任务主体函数、任务栈、任务控制块
。FreeRTOS
里面有一个叫静态任务创建函数 xTaskCreateStatic()
,它将任务主体函数、任务栈(静态的)和任务控制块(静态的)这三者联系在一起,让任务可以随时被系统启动。
代码编写流程6
首先在主函数的前面定义启动任务的任务句柄,是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄,以后我们要想操作这个任务都需要通过这个任务句柄。
/* 创建任务句柄 */
static TaskHandle_t AppTaskCreate_Handle;
之后,在主函数内部创建如下代码所示的启动任务。
/* 创建 AppTaskCreate 任务 */
AppTaskCreate_Handle = xTaskCreateStatic((TaskFunction_t)AppTaskCreate, //任务函数(const char* )"AppTaskCreate", //任务名称(uint32_t )128, //任务堆栈大小(void* )NULL, //传递给任务函数的参数(UBaseType_t )3, //任务优先级(StackType_t* )AppTaskCreate_Stack, //任务堆栈(StaticTask_t* )&AppTaskCreate_TCB); //任务控制块if(NULL != AppTaskCreate_Handle)/* 创建成功 */vTaskStartScheduler(); /* 启动任务,开启调度 */
其中:
- 任务入口函数,即任务函数的名称,需要我们自己定义并且实现。
- 任务名字,字符串形式,最大长度由
FreeRTOSConfig.h
中定义的configMAX_TASK_NAME_LEN
宏指定,多余部分会被自动截掉,这里任务名字最好要与任务函数入口名字一致,方便进行调试。 - 任务堆栈大小,单位为字,在32 位的处理器下(STM32),一个字等于
4
个字节,那么任务大小就为128 * 4
字节。 - 任务入口函数形参,不用的时候配置为
0
或者NULL
即可。 - 任务的优先级的范围根据
FreeRTOSConfig.h
中的宏
configMAX_PRIORITIES
决定,如果使能configUSE_PORT_OPTIMISED_TASK_SELECTION
这个宏定义,则最多支持32
个优先级;如果不用特殊方法查找下一个运行的任务,那么则不强制要求限制最大可用优先级数目。在FreeRTOS
中,数值越大优先级越高,0
代表最低优先级。 - 任务栈起始地址,只有在使用静态内存的时候才需要提供,在使用动态内存的时候会根据提供的任务栈大小自动创建。
- 任务控制块指针,在使用静态内存的时候,需要给任务初始化函数
xTaskCreateStatic()
传递预先定义好的任务控制块的指针。在使用动态内存的时候,任务创建函数xTaskCreate()
会返回一个指针指向任务控制块,该任务控制块是xTaskCreate()
函数里面动态分配的一块内存。
上面所述代码为创建启动任务,根据启动方式,我们需要在启动任务中创建更多的应用任务,代码在定义任务函数部分已列出来。
至此,我们的配置部分已经完毕。
2.6 启动任务
当任务创建好后,任务就处于就绪状态(Ready),就绪态的任务可以参与操作系统的调度。但是此时任务仅仅是创建了,还未开启任务调度器,也没创建空闲任务与定时器任务(如果使能了 configUSE_TIMERS
这个宏定义),而这两个任务的实现就是在启动任务调度器中完成的。每个操作系统,任务调度器只启动一次,之后就不会再次执行了,FreeRTOS
中启动任务调度器的函数是 vTaskStartScheduler()
,并且启动任务调度器的时候就不会返回,从此任务管理都由 FreeRTOS
管理,此时才是真正进入实时操作系统中的第一步。
因此,需要在主函数最后添加如下代码来开启调度器。
if(NULL != AppTaskCreate_Handle) /* 创建成功 */vTaskStartScheduler(); /* 启动任务,开启调度 */
3 创建动态内存任务
动态内存,及堆,也属于 SRAM
。
任务使用的栈和任务控制块是在创建任务的时候 FreeRTOS
动态分配的,并不是预先定义好的全局变量。
在创建 SRAM
静态内存任务的时候,任务控制块和任务栈的内存空间都是从内部的 SRAM
里面分配的,具体分配到哪个地址由编译器决定。那么创建 SRAM
动态内存任务的时候,FreeRTOS
做法是在 SRAM
里面定义一个大数组,也就是堆内存,供 FreeRTOS
的动态内存分配函数使用,在第一次使用的时候,系统会将定义的堆内存进行初始化,在 FreeRTOS
提供的内存管理方案中实现(heap_1.c、heap_2.c、heap_4.c
等)。
首先,在 FreeRTOSConfig.h
文件中,定义有堆内存大小的宏为 configTOTAL_HEAP_SIZE
,同时定义有宏 configSUPPORT_DYNAMIC_ALLOCATION
,但是要注意这个宏
定义在使用 FreeRTOS
操作系统的时候必须开启。
//支持动态内存申请
#define configSUPPORT_DYNAMIC_ALLOCATION 1
//系统所有总的堆大小
#define configTOTAL_HEAP_SIZE ((size_t)(36*1024))
由于我们使用 heap_4.c
,因此在此文件中有如下代码,从内部 SRAMM
里面定义一个静态数组 ucHeap
,大小由 configTOTAL_HEAP_SIZE
这个宏决定,目前定义为 36KB
。要注意定义的堆大小不能超过内部 SRAM
的总大小。
static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
同时在 heap_4.c
文件中,有如下代码用来将堆进行初始化(如果这是第一次调用 malloc
),以设置空闲块列表,方便以后分配内存,初始化完成之后会取得堆的结束地址。
/* If this is the first call to malloc then the heap will requireinitialisation to setup the list of free blocks. */
if( pxEnd == NULL )
{prvHeapInit();
}
else
{mtCOVERAGE_TEST_MARKER();
}
3.1 定义任务函数
使用动态内存的时候,任务的主体函数与使用静态内存时是一样的,代码如下所示:
/*********************************************************************** @ 函数名 : LED_Task* @ 功能说明: LED_Task任务主体* @ 参数 : * @ 返回值 : 无********************************************************************/
static void LED_Task(void* parameter)
{ while (1){LED1_ON;vTaskDelay(500); /* 延时500个tick */printf("LED_Task Running,LED1_ON\r\n");LED1_OFF; vTaskDelay(500); /* 延时500个tick */ printf("LED_Task Running,LED1_OFF\r\n");}
}
同时,要注意代码的规范性,记得将函数在主函数前面进行声明。
3.2 定义任务栈
使用动态内存的时候,任务栈在任务创建的时候创建,不用跟使用静态内存那样要预先定义好一个全局的静态的栈空间,动态内存就是按需分配内存,随用随取。
3.3 定义任务控制块指针
使用动态内存时候,不用跟使用静态内存那样要预先定义好一个全局的静态的任务控制块空间。任务控制块是在任务创建的时候分配内存空间创建,任务创建函数会返回一个指针,用于指向任务控制块,所以要预先为任务栈定义一个任务控制块指针,也是我们常说的任务句柄。
在主函数的前面,预先声明任务句柄,代码如下所示:
/**************************** 任务句柄 ********************************/
/* * 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄* 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么* 这个句柄可以为NULL。*/
/* 创建任务句柄 */
static TaskHandle_t AppTaskCreate_Handle = NULL;
/* LED任务句柄 */
static TaskHandle_t LED_Task_Handle = NULL;
3.4 动态创建任务
使用静态内存时,使用 xTaskCreateStatic()
函数来创建一个任务,而使用动态内存的时,则使用 xTaskCreate()
函数来创建一个任务,两者的函数名不一样,具体的形参也有区别。
由于启动方式还是通过创建一个启动任务,之后由启动任务来创建应用任务,因此需要在主函数的内部创建启动任务,代码如下所示:
xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate, /* 任务入口函数 */(const char* )"AppTaskCreate", /* 任务名字 */(uint16_t )512, /* 任务栈大小 */(void* )NULL, /* 任务入口函数参数 */(UBaseType_t )1, /* 任务的优先级 */(TaskHandle_t* )&AppTaskCreate_Handle); /* 任务控制块指针 */
代码详解:
- 任务入口函数:即任务函数的名称,需要我们自己定义并且实现。
- 任务名字:字符串形式,最大长度由
FreeRTOSConfig.h
中定义的configMAX_TASK_NAME_LEN
宏指定,多余部分会被自动截掉,这里任务名字最好要与任务函数入口名字一致,方便进行调试。 - 任务堆栈大小:单位为字,在
32
位的处理器下(STM32),一个字等于4
个字节,那么任务大小就为128 * 4
字节。 - 任务入口函数形参:不用的时候配置为
0
或者NULL
即可。 - 任务的优先级:优先级范围根据
FreeRTOSConfig.h
中的宏
configMAX_PRIORITIES
决定,如果使能configUSE_PORT_OPTIMISED_TASK_SELECTION
这个宏定义,则最多支持32
个优先级;如果不用特殊方法查找下一个运行的任务,那么则不强制要求限制最大可用优先级数目。在FreeRTOS
中,数值越大优先级越高,0
代表最低优先级。 - 任务控制块指针:在使用内存的时候,需要给任务初始化函数
xTaskCreateStatic()
传递预先定义好的任务控制块的指针。在使用动态内存的时候,任务创建函数xTaskCreate()
会返回一个指针指向任务控制块,该任务控制块是xTaskCreate()
函数里面动态分配的一块内存。
3.5 启动任务
当任务创建好后,任务处于就绪状态(Ready),在就绪态的任务可以参与操作系统的调度。但是此时任务仅仅是创建了,还未开启任务调度器,也没创建空闲任务与定时器任务(如果使能了 configUSE_TIMERS
这个宏定义),那这两个任务就是在启动任务调度器中实现。每个操作系统,任务调度器只启动一次,之后就不会再次执行了,FreeRTOS
中启动任务调度器的函数是 vTaskStartScheduler()
,并且启动任务调度器的时候就不会返回,从此任务管理都由 FreeRTOS
管理,此时才是真正进入实时操作系统中的第一步。
因此,需要在上面创建启动任务之后开启调度器,具体代码如下所示:
/* 启动任务调度 */
if(pdPASS == xReturn)vTaskStartScheduler(); /* 启动任务,开启调度 */
elsereturn -1;
从零入门 FreeRTOS 操作系统之创建任务流程相关推荐
- 从零入门 FreeRTOS操作系统之信号量
从零入门 FreeRTOS操作系统之信号量 1 信号量的基本概念 信号量 (Semaphore) 是一种实现任务间通信的机制,可以实现任务之间同步或临界资源的互斥访问,常用于协助一组相互竞争的任务来访 ...
- 从零入门 FreeRTOS 操作系统之任务调度器
从零入门 FreeRTOS 操作系统之任务调度器 1 任务调度器的概念 FreeRTOS 中提供的任务调度器是基于优先级的全抢占式调度:在系统中除了中断处理函数.调度器上锁部分的代码和禁止中断的代码是 ...
- 从零入门 FreeRTOS 操作系统之任务的概念
从零入门 FreeRTOS 操作系统之任务的概念 从系统的角度看,任务是竞争系统资源的最小运行单元.FreeRTOS 是一个支持多任务的操作系统.在 FreeRTOS 中,任务可以使用或等待 CPU. ...
- 0基础能学mysql数据库吗_mysql学习入门:零基础如何使用mysql创建数据库表?
零基础如何自学Mysql创建数据库,是Mysql学习者必经之路,Mysql是受欢迎的关系数据库管理系统,WEB应用方面MySQL是很好的RDBMS应用软件之一.如何使用Mysql创建数据库表,打开My ...
- 转载:【opencv入门教程之六】创建Trackbar图片对比度、亮度值调整
[OpenCV入门教程之六] 创建Trackbar & 图像对比度.亮度值调整 浅墨_毛星云 2014-03-18 21:43:18 103746 收藏 21 最后发布:2014-03-18 ...
- FreeRtos(1)-----任务创建与管理
freeRTOS实时操作系统移植 以上是历史FreeRtos博客.FreeRtos系列我会坚持更下去. FreeRtos的任务创建 任务的创建函数 BaseType_t xTaskCreate( Ta ...
- 零入门kubernetes网络实战-20->golang编程syscall操作tun设备介绍
<零入门kubernetes网络实战>视频专栏地址 https://www.ixigua.com/7193641905282875942 本篇文章视频地址(稍后上传) 本篇文章主要是使用g ...
- 从零入门 HTML、CSS、JS、React,构建 ToDo 待办事项管理项目!
在今天,前端工程师已经成为研发体系中的重要岗位之一.可是与此相对的是,极少大学的计算机专业愿意开设前端课程,大部分前端工程师的知识,也都是在实践和工作中不断学习的. 最近收到很多同学的后台留言,说希望 ...
- xsemaphoretake返回_【FreeRTOS操作系统教程】第21章 FreeRTOS计数信号量
第21章 FreeRTOS计数信号量 本章节开始讲解FreeRTOS任务间的同步和资源共享机制,计数信号量.FreeRTOS中计数信号量的源码实现是基于消息队列实现的. 本章教程配套的例子含Corte ...
最新文章
- python语言只采用解释一种翻译方式对吗_python-guide翻译
- PLSQL Developer设置及快捷键设置
- java日历表打印_Java打印日历表
- kafka java 查询信息_Kafka查看topic、consumer group状态命令
- Win7无线网络共享设置方法
- Radio stations CodeForces - 762E (cdq分治)
- 电大计算机组成原理ppt,四川电大计算机组成原理(0023)第二次形考作业(课程号:5110023).docx...
- 第 133 章 FAQ
- RowVersion字段从SqlServer到PostgreSQL的迁移
- community 计算模块度_光模块深度:国内光模块企业快速崛起
- ST_LINK/V2 SWIM和SWD、JTAG下载口说明
- python如何运行py程序_如何用Python汇款:Web3.py教程
- angular 引入编辑器遇到的各种问题。。。
- 算法笔记-------基数排序
- PHP踩坑:对象的引用
- unity播放视频代码
- matlab单回路和串级控制回路,单回路和串级控制系统仿真研究
- PyTorch模型训练实战技巧,突破速度瓶颈
- KNEEL: Knee Anatomical Landmark Localization Using Hourglass Networks
- SHA1摘要算法(带示例)
热门文章
- STM32 进入Stop模式后电流还是很大怎么办?
- 你想带一顶什么样的硕士帽(转载)
- 洛谷 [SDOI2009]晨跑
- luogu P4238 多项式求逆 (模板题、FFT)
- oracle sql 艺术,Oracle PL/SQL 从if 到 then的“艺术鉴赏”
- python函数名的运用,闭包,迭代器
- RPAD()和LPAD()函数进行字符串的填充
- python3.6.0安装步骤
- win2003系统网络安装——基于linux+pxe+dhcp+tftp+samba+ris
- CentOS常用指令