FreeRTOS源码学习_01-任务调度器-2021-10-28

2023-05-16

FreeRTOS源码学习_01-任务调度器

    • 一、写在前面
    • 二、源码分析
      • 1、开始任务调度:void vTaskStartScheduler()
      • 2、创建软件定时器任务:
      • 3、检查链表队列是否有效:prvCheckForValidListAndQueue()
      • 4、开启任务调度:xPortStartScheduler()

一、写在前面

  • FreeRTOS版本:V10.4.5
  • 内存分配方式:动态分配
  • Port.c版本:GCC/ARM_CM4F_V10.4.5

我自己最近在学习FreeRTOS操作系统,在使用中发现,虽然官方的英文注释十分的详尽,但是很多地方不是特别好理解,初学者看了注释后还是一头雾水。因此决定将自己的使用理解以及注释写在这里,方便大家参考。

注意:因为加了注释符后,注释会变成斜体,看着很别扭,因此文中注释统一采用 “ ** … ** ” 的方式


第一阶段为基础篇 分为四篇:

01-任务调度器:分析开启任务调度函数
02-创建任务:分析任务是如何被创建的(待更新)
03-链表操作:系统中的任务、消息、信号量等都与链表息息相关,分析FreeRTOS中的链表操作(待更新)
04-汇编指令解析:分析FreeRTOS中的汇编代码(待更新)


注释写的十分详细,直接写在源码中,篇幅较长,希望耐心看完!
如果这篇文章帮助到了您,作者甚是欣慰。若文中有不正确或不恰当的地方,也请大家及时指正!



二、源码分析

1、开始任务调度:void vTaskStartScheduler()

void vTaskStartScheduler( void )
{
    BaseType_t xReturn;
    **
     静态方式创建任务 这里暂不考虑 
    **
    #if ( configSUPPORT_STATIC_ALLOCATION == 1 )
        {
            StaticTask_t * pxIdleTaskTCBBuffer = NULL;
            StackType_t * pxIdleTaskStackBuffer = NULL;
            uint32_t ulIdleTaskStackSize;

            vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, &pxIdleTaskStackBuffer, &ulIdleTaskStackSize );
            xIdleTaskHandle = xTaskCreateStatic( prvIdleTask,
                                                 configIDLE_TASK_NAME,
                                                 ulIdleTaskStackSize,
                                                 ( void * ) NULL,       
                                                 portPRIVILEGE_BIT,     
                                                 pxIdleTaskStackBuffer,
                                                 pxIdleTaskTCBBuffer ); 

            if( xIdleTaskHandle != NULL )
            {
                xReturn = pdPASS;
            }
            else
            {
                xReturn = pdFAIL;
            }
        }
    #else /* if ( configSUPPORT_STATIC_ALLOCATION == 1 ) */
        {
            **
             创建空闲任务 采用动态方式 具体见第二篇文章 《 02-创建任务 》 
            **
            xReturn = xTaskCreate( prvIdleTask,
                                   configIDLE_TASK_NAME,
                                   configMINIMAL_STACK_SIZE,
                                   ( void * ) NULL,
                                   portPRIVILEGE_BIT,  /* In effect ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), but tskIDLE_PRIORITY is zero. */
                                   &xIdleTaskHandle ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */
        }
    #endif /* configSUPPORT_STATIC_ALLOCATION */

	** 
	 如果使用软件定时器 则创建软件定时器任务 
	**
    #if ( configUSE_TIMERS == 1 )
        {
        	**
        	 如果空闲任务创建成功 则创建定时器任务
        	**
            if( xReturn == pdPASS )
            {
            	**
            	 定时器任务创建 见第二小节 2、创建软件定时器任务 
            	**
                xReturn = xTimerCreateTimerTask();
            }
            **
             内存不足 空闲任务创建失败 这个宏需要由用户实现 用来提示任务创建失败 
            **
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
    #endif /* configUSE_TIMERS */

	/* 内存充足 任务创建成功 */
    if( xReturn == pdPASS )
    {
        /* freertos_tasks_c_additions_init() should only be called if the user
         * definable macro FREERTOS_TASKS_C_ADDITIONS_INIT() is defined, as that is
         * the only macro called by the function. */
        #ifdef FREERTOS_TASKS_C_ADDITIONS_INIT
            {
                freertos_tasks_c_additions_init();
            }
        #endif

        ** 
         这里禁止中断 因为接下来的操作会设置Systick,关闭中断可以防止在任务开始调度前产生Tick 
         代码为关闭中断操作 源码为汇编 参考第四篇文章 《 04-汇编指令解析 》
        **
        portDISABLE_INTERRUPTS();

		** 
		 这里用来配置是否使用 newlib 默认不使能 即使用glibc 
		**
        #if ( configUSE_NEWLIB_REENTRANT == 1 )
            {
                /* Switch Newlib's _impure_ptr variable to point to the _reent
                 * structure specific to the task that will run first.
                 * See the third party link http://www.nadler.com/embedded/newlibAndFreeRTOS.html
                 * for additional information. */
                _impure_ptr = &( pxCurrentTCB->xNewLib_reent );
            }
        #endif /* configUSE_NEWLIB_REENTRANT */
		
		** 
		 从名字就可以知道 这个变量的意思是下个任务解除阻塞的时间 现在没有运行中任务 所以初始化为最大值 portMAX_DELAY 
		**
        xNextTaskUnblockTime = portMAX_DELAY;
        
        
        **
         xSchedulerRunning 是任务调度器运行的标志 这里置位 表示开启任务调度器 具体开启的操作在下面代码 
        **
        xSchedulerRunning = pdTRUE;
        
        
        
        ** 
         这里初始化FreeRTOS系统的计时时间 configINITIAL_TICK_COUNT 这个宏默认为0 即从0开始计时 
		 xTickCount 是个静态全局变量 会在SysTick中断中进行自加操作 
		**
        xTickCount = ( TickType_t ) configINITIAL_TICK_COUNT;



        **
         这个函数是个宏 需要用户手动配置 用来设置一个硬件定时器,用来统计任务的运行时间 
        **
        portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();

		**
		 这个宏用来实现回调函数 需要用户手动添加回调函数的功能 
		**
        traceTASK_SWITCHED_IN();



        ** 
        【十分重要】 xPortStartScheduler()用来真正开启任务调度 
        			源码分析 见第四小节 4、开启任务调度:xPortStartScheduler()
					调度器开启成功后会去执行任务相关代码 即if后面的代码不再执行 
		**
        if( xPortStartScheduler() != pdFALSE )
        {
            /* Should not reach here as if the scheduler is running the
             * function will not return. */
        }
        else
        {
            /* Should only reach here if a task calls xTaskEndScheduler(). */
        }
    }
    ** 
     内存不足 无法开启任务调度 
    **
    else
    {
        /* This line will only be reached if the kernel could not be started,
         * because there was not enough FreeRTOS heap to create the idle task
         * or the timer task. */
        configASSERT( xReturn != errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY );
    }

    **
     下面两个参数没有用到 前面加(void)可以防止编译器报警告
    **
    ( void ) xIdleTaskHandle;
    ( void ) uxTopUsedPriority;
}

2、创建软件定时器任务:

BaseType_t xTimerCreateTimerTask( void )
    {
        BaseType_t xReturn = pdFAIL;

        **
         软件定时器是基于链表和队列来实现的 这里检查必须的资源是否就绪 若没有就创建 
         源码分析 见第三小节 3、检查链表队列是否有效:prvCheckForValidListAndQueue()
        **
        prvCheckForValidListAndQueue();


		**
		 prvCheckForValidListAndQueue() 这个函数会创建定时器队列 因此代码到这里时 xTimerQueue 保存的是软件定时器队列的句柄,正常不应该为NULL 
		**
        if( xTimerQueue != NULL )
        {
        	**
        	 静态方式创建软件定时器任务 暂不考虑 
        	**
            #if ( configSUPPORT_STATIC_ALLOCATION == 1 )
                {
                    StaticTask_t * pxTimerTaskTCBBuffer = NULL;
                    StackType_t * pxTimerTaskStackBuffer = NULL;
                    uint32_t ulTimerTaskStackSize;

                    vApplicationGetTimerTaskMemory( &pxTimerTaskTCBBuffer, &pxTimerTaskStackBuffer, &ulTimerTaskStackSize );
                    xTimerTaskHandle = xTaskCreateStatic( prvTimerTask,
                                                          configTIMER_SERVICE_TASK_NAME,
                                                          ulTimerTaskStackSize,
                                                          NULL,
                                                          ( ( UBaseType_t ) configTIMER_TASK_PRIORITY ) | portPRIVILEGE_BIT,
                                                          pxTimerTaskStackBuffer,
                                                          pxTimerTaskTCBBuffer );

                    if( xTimerTaskHandle != NULL )
                    {
                        xReturn = pdPASS;
                    }
                }
            #else /* if ( configSUPPORT_STATIC_ALLOCATION == 1 ) */
                {
                	**
                	 动态方式创建软件定时器任务 具体见第二篇文章《 02-创建任务 》 
                	**
                    xReturn = xTaskCreate( prvTimerTask,
                                           configTIMER_SERVICE_TASK_NAME,
                                           configTIMER_TASK_STACK_DEPTH,
                                           NULL,
                                           ( ( UBaseType_t ) configTIMER_TASK_PRIORITY ) | portPRIVILEGE_BIT,
                                           &xTimerTaskHandle );
                }
            #endif /* configSUPPORT_STATIC_ALLOCATION */
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }

        configASSERT( xReturn );
        return xReturn;
    }

3、检查链表队列是否有效:prvCheckForValidListAndQueue()

static void prvCheckForValidListAndQueue( void )
    {
        **
         进入临界区 即关闭部分中断 目的是保护资源 汇编代码 具体见 第四篇文章《 04-汇编指令解析 》  
        **
        taskENTER_CRITICAL();
        {
	        **
	         第一次初始化 xTimerQueue 默认为NULL 
	        **
            if( xTimerQueue == NULL )
            {
            	**
            	 初始化软件定时器管理的两个链表 (在FreeRTOS中也叫列表) 
            	 xActiveTimerList1 用来保存当前定时器列表,并且按周期阻塞时间以升序的方式排列 
            	(比如:软件定时器1周期为5个Tick 软件定时器210个Tick,则软件定时器2会放在1后面,
            	 若软件定时器3得到周期为0xfffffff0,则加上当前的Tick后会溢出,则软件定时器3会挂在xActiveTimerList2 上) 
            	**
                vListInitialise( &xActiveTimerList1 );


				** 
				 xActiveTimerList2 用来保存溢出的列表 当xActiveTimerList2 也溢出时,xActiveTimerList2 和xActiveTimerList1的功能会对调,即 xActiveTimerList2 保存当前定时器列表 xActiveTimerList1保存溢出列表 
				**
                vListInitialise( &xActiveTimerList2 );


                **
                 pxCurrentTimerList 和 pxOverflowTimerList 是两个链表指针 用来指向上面创建的两个链表 
                **
                pxCurrentTimerList = &xActiveTimerList1;
                pxOverflowTimerList = &xActiveTimerList2;

                #if ( configSUPPORT_STATIC_ALLOCATION == 1 )
				**
				 这里是采用静态的方式创建队列 暂不考虑 (因为代码注释太长 影响观看 暂时删除) 
				**
                #else
                    {
                    	**
                    	 创建一个消息队列 长度通过 configTIMER_QUEUE_LENGTH 这个宏来配置 
                    	 消息队列的源码分析会在第二阶段进行展示 
                    	**
                        xTimerQueue = xQueueCreate( ( UBaseType_t ) configTIMER_QUEUE_LENGTH, sizeof( DaemonTaskMessage_t ) );
                    }
                #endif /* if ( configSUPPORT_STATIC_ALLOCATION == 1 ) */

				**
				 configQUEUE_REGISTRY_SIZE 是用来配置调试消息队列的个数 方便调试消息队列 
				**
                #if ( configQUEUE_REGISTRY_SIZE > 0 )
                    {
                        if( xTimerQueue != NULL )
                        {
                        	**
                        	 将消息队列进行注册(想要调试消息队列 在创建完成后必须添加注册) 
                        	**
                            vQueueAddToRegistry( xTimerQueue, "TmrQ" );
                        }
                        else
                        {
                            mtCOVERAGE_TEST_MARKER();
                        }
                    }
                #endif /* configQUEUE_REGISTRY_SIZE */
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        taskEXIT_CRITICAL();
    }

4、开启任务调度:xPortStartScheduler()

BaseType_t xPortStartScheduler( void )
{
	** 
	 断言FreeRTOS管理的最高优先级不为0 
	 configMAX_SYSCALL_INTERRUPT_PRIORITY 是个宏,用来设置FreeRTOS管理的最大中断优先级 
	 用户可通过配置 configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 这个宏来设置系统管理的最大中断优先级 
	**
    configASSERT( configMAX_SYSCALL_INTERRUPT_PRIORITY );
    

	** 
	 portCPUID 是个宏 取的是SCB寄存器的CPU-ID寄存器地址 可以用来识别CPU的类型
	 第一小节 一、写在前面 说了,源码是cm4的内核,因此M7的不可用
	**
    configASSERT( portCPUID != portCORTEX_M7_r0p1_ID );
    configASSERT( portCPUID != portCORTEX_M7_r0p0_ID );
    

	** 
	 下面的代码用来断言NVIC中可用的优先级数量 
	 我们知道,中断优先级的位数最大为8位,但是并不是8位全部都采用了,MCU厂商会进行裁剪,而大部分都不会用到8位
	 并且是高位有效 比如只用了3位,则设置优先级时应该左移5位,因为低5位无效
	**
    #if ( configASSERT_DEFINED == 1 )
        {
			**
			 保存原始优先级 
			**
            volatile uint32_t ulOriginalPriority;


            **
             获取外部中断#0的中断优先级 
            (portNVIC_IP_REGISTERS_OFFSET_16 + portFIRST_USER_INTERRUPT_NUMBER )的地址为0xE000E400,是管理外部中断#0的寄存器 
            **
            volatile uint8_t * const pucFirstUserPriorityRegister = ( volatile uint8_t * const ) ( portNVIC_IP_REGISTERS_OFFSET_16 + portFIRST_USER_INTERRUPT_NUMBER );
            volatile uint8_t ucMaxPriorityValue;
            

			**
			 首先获取原始的寄存器值 默认为0 
			**
            ulOriginalPriority = *pucFirstUserPriorityRegister;


			**
			 给寄存器赋值 portMAX_8_BIT_VALUE,这个宏的值为0xff 
			**
            *pucFirstUserPriorityRegister = portMAX_8_BIT_VALUE;


			**
			 再次将寄存器的值读出 保存到ucMaxPriorityValue变量中
			 有朋友可能会疑惑问什么要这么做,前面也说过,大部分MCU的中断优先级位数不是8位,因此写入0xff后,再次读取的值不一定是8位
			 举个例子:MCU的NVIC可用优先级数量为4,那么低4位无效,则写入0xff,实际写入的值为0xf0,因此再次读出后的值为0xf0,而不是0xff
			**
            ucMaxPriorityValue = *pucFirstUserPriorityRegister;


          	** 
          	 configMAX_SYSCALL_INTERRUPT_PRIORITY 这个宏是FreeRTOS管理的最高优先级 
          	**
            ucMaxSysCallPriority = configMAX_SYSCALL_INTERRUPT_PRIORITY & ucMaxPriorityValue;


            **
             portMAX_PRIGROUP_BITS 这个宏为7,是最大分组优先级位数 因为还有子优先级 
             子优先级 + 分组优先级 = NVIC使用的中断优先级位数 
             至于子优先级和分组优先级各自分配多少位,可由软件来配置 
            **
            ulMaxPRIGROUPValue = portMAX_PRIGROUP_BITS;


			**
			 这个while循环可以获取从寄存器中读取的数ucMaxPriorityValue 高位有多少个1 即该MCU的NVIC有多少位可用 
			**
            while( ( ucMaxPriorityValue & portTOP_BIT_OF_BYTE ) == portTOP_BIT_OF_BYTE )
            {
                ulMaxPRIGROUPValue--;
                ucMaxPriorityValue <<= ( uint8_t ) 0x01;
            }


			**
			 下面代码用来断言NVIC实际使用位数与软件中设置的是否相同 
			**
            #ifdef __NVIC_PRIO_BITS
                {
                    
                    configASSERT( ( portMAX_PRIGROUP_BITS - ulMaxPRIGROUPValue ) == __NVIC_PRIO_BITS );
                }
            #endif

            #ifdef configPRIO_BITS
                {
                    
                    configASSERT( ( portMAX_PRIGROUP_BITS - ulMaxPRIGROUPValue ) == configPRIO_BITS );
                }
            #endif


           **
            ulMaxPRIGROUPValue 这个全局静态变量用来下面的断言检测 可不必关心 
           **
            ulMaxPRIGROUPValue <<= portPRIGROUP_SHIFT;
            ulMaxPRIGROUPValue &= portPRIORITY_GROUP_MASK;


			**
			 断言完成后 将最初的优先级还原,即设置为默认的0 
			**
            *pucFirstUserPriorityRegister = ulOriginalPriority;
        }
    #endif /* conifgASSERT_DEFINED */


	**
	 0xE000ED18 ~ 0xE000ED23为系统的中断优先级设置寄存器 
	**


    ** 
     设置PENDSV中断为最低优先级 portNVIC_SHPR3_REG 为PENDSV中断优先级寄存器地址 
    **
    portNVIC_SHPR3_REG |= portNVIC_PENDSV_PRI;


    **
     设置SYSTICK中断为最低优先级 portNVIC_SHPR3_REG 为SYSTICK中断优先级寄存器地址 
    **
    portNVIC_SHPR3_REG |= portNVIC_SYSTICK_PRI;


    **
     这个函数比较简单 用来设置Systick的定时周期 并打开SysTick中断开关 
    **
    vPortSetupTimerInterrupt();


    **
     初始化临界区变量 这个变量用来记录进入临界区的嵌套次数 
    **
    uxCriticalNesting = 0;


    **
     使能硬件浮点,这里是为了确保硬件浮点开启,因为之前可能已经开启了 
     汇编源码 具体见 《 04-汇编指令解析 》 文章
    **
    vPortEnableVFP();

    /* Lazy save always. */
    *( portFPCCR ) |= portASPEN_AND_LSPEN_BITS;


    **
     开启第一个任务 SVC中断实现 
     汇编源码 具体见 《 04-汇编指令解析 》 文章
    **
    prvPortStartFirstTask();

    /* Should never get here as the tasks will now be executing!  Call the task
     * exit error function to prevent compiler warnings about a static function
     * not being called in the case that the application writer overrides this
     * functionality by defining configTASK_RETURN_ADDRESS.  Call
     * vTaskSwitchContext() so link time optimisation does not remove the
     * symbol. */
    vTaskSwitchContext();
    prvTaskExitError();

    /* Should not get here! */
    return 0;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

FreeRTOS源码学习_01-任务调度器-2021-10-28 的相关文章

  • FreeRTOS-内核控制函数

    FreeRTOS 内核控制函数 FreeRTOS中有一些内核函数 一般来说这些内核函数在应用层不会使用 但是内核控制函数是理解FreeRTOS中断的基础 接下来我们逐一分析这些内核函数 taskYIELD 该函数的作用是进行任务切换 这是一
  • 解决错误“ #error “include FreeRTOS.h“ must appear in source files before “include event_groups.““例子分享

    今天来给大家分享一下 关于之前自己在学习FreeRTOS过程中遇到的一个错误提示 话不多说 我们直接来看 错误分析 首先 我们看一下错误的提示 error 35 error directive include FreeRTOS h must
  • FreeRTOS学习笔记 6 - 互斥量

    目录 1 创建 2 获取 3 释放 4 测试 FreeRTOS不支持调度方式的设置 所以下面2个宏定义可以随意设置值 define RTOS IPC FLAG FIFO 0x00 define RTOS IPC FLAG PRIO 0x01
  • 【FreeRTOS开发问题】FreeRTOS内存溢出

    FreeRTOS内存溢出 如下图所示 FreeRTOS编译完成后可以看到 系统提示无法分配内存到堆 Objects Template axf Error L6406E No space in execution regions with A
  • 【Spring Boot 源码学习】@SpringBootApplication 注解

    Spring Boot 源码学习系列 SpringBootApplication 注解 引言 主要内容 1 创建 Spring Boot 项目 2 Spring Boot 入口类 3 SpringBootApplication 介绍 总结
  • FreeRTOS学习笔记<中断>

    中断概念 Cortex M的NVIC最多支持240个IRQ 中断请求 1个不可屏蔽中断 NMI 1个Systick 滴答定时器 定时器中断和多个系统异常 Cortex M处理器有多个用于管中断和异常的可编程寄存器 这些寄存器大多数都在 NV
  • freeRTOS使用uxTaskGetStackHighWaterMark函数查看任务堆栈空间的使用情况

    摘要 每个任务都有自己的堆栈 堆栈的总大小在创建任务的时候就确定了 此函数用于检查任务从创建好到现在的历史剩余最小值 这个值越小说明任务堆栈溢出的可能性就越大 FreeRTOS 把这个历史剩余最小值叫做 高水位线 此函数相对来说会多耗费一点
  • FreeRTOS_中断

    传送门 博客汇总帖 传送门 Cortex M3 中断 异常 传送门 Cortex M3笔记 基础 笔记内容参考 正点原子的FreeRTOS开发手册 cortex m3权威指南 Cortex M3和Cortex M4权威指南等 文中stm32
  • 【FreeRTOS】任务通知的使用

    作者主页 凉开水白菜 作者简介 共同学习 互相监督 热于分享 多加讨论 一起进步 专栏资料 https pan baidu com s 1nc1rfyLiMyw6ZhxiZ1Cumg pwd free 点赞 收藏 再看 养成习惯 订阅的粉丝
  • FreeRTOS学习(三)开关中断

    声明及感谢 跟随正点原子资料学习 在此作为学习的记录和总结 环境 keil stm32f103 背景知识 Cotex M3的NVIC最多支持240个IRQ 中断请求 1个不可屏蔽 NMI 1个Systick 滴答定时器 Cortex M处理
  • FreeRTOS之事件

    FreeRTOS之事件 声明 本人按照正点原子的FreeRTOS例程进行学习的 欢迎各位大佬指责和批评 谢谢 一 事件定义 事件 事件集 与高数上的集合意义差不多 事件啊 其实是实现任务间通信的机制 主要用于实现多任务间的同步 但是事件类型
  • 基于STM32的FreeRTOS学习之中断测试实验(五)

    记录一下 方便以后翻阅 本章内容是接着上一章节进行的实际演练 1 实验目的 FreeRTOS可以屏蔽优先级低于configMAX SYSCALL INTERRUPT PRIORITY的中断 不会屏蔽高于其的中断 本次实验就是验证这个说法 本
  • FreeRTOS笔记(十)中断

    中断 当CPU在执行某一事件A时 发生另外一个更重要紧急的事件B请求CPU去处理 产生了中断 于是CPU暂时中断当前正在执行的事件A任务而对对事件B进行处理 CPU处理完事件B后再返回之前中断的位置继续执行原来的事件A 这一过程统称为中断
  • freeRTOS出现任务卡死的情况。

    最近在做一个产品二代升级的项目 代码是上一任工程师留下的 很多BUG 而且融合了HAL库和LL库 以及github上下载的GSM源码 很不好用 我这边是将2G模块换成了4G 且添加了单独的BLE模块 因此只在源码的基础上 去除2G和BLE代
  • FreeRTOS之系统配置

    1 FreeRTOS的系统配置文件为FreeRTOSConfig h 在此配置文件中可以完成FreeRTOS的裁剪和配置 在官方的demo中 每个工程都有一个该文件 2 先说一下 INCLUDE 开始的宏 使用 INCLUDE 开头的宏用来
  • FreeRTOS多任务调度器基础

    Cortex M4中SysTick调度器核心 Cortex M4中的中断管理 Cortex M4中影子栈指针 Cortex M4中SVC和PendSV异常 1 Cortex M4中SysTick调度器核心 systick每一次中断都会触发内
  • FreeRTOS 匈牙利表示法 [重复]

    这个问题在这里已经有答案了 我是 RTOS 和 C 编程的新手 而且我仍在习惯 C 的良好实践 因此 我打开了一个使用 FreeRTOS 的项目 我注意到操作系统文件使用匈牙利表示法 我知道一点符号 但面临一些新的 标准 FreeRTOS
  • 哪些变量类型/大小在 STM32 微控制器上是原子的?

    以下是 STM32 微控制器上的数据类型 http www keil com support man docs armcc armcc chr1359125009502 htm http www keil com support man d
  • 有关 CMake 错误的问题:没有为目标提供源

    我正在尝试使用 cmake 和 eclipse 将 FreeRtos 添加到我的项目中 但出现错误 我运行的是 debian 10 我的 cmake 版本是 3 13 4 cmake 的文件可以在以下位置找到这个 git 仓库 https
  • 当 Cortex-M3 出现硬故障时如何保留堆栈跟踪?

    使用以下设置 基于 Cortex M3 的 C gcc arm 交叉工具链 https launchpad net gcc arm embedded 使用 C 和 C FreeRtos 7 5 3 日食月神 Segger Jlink 与 J

随机推荐

  • javascript:数据结构——队列

    什么是对列 是一种 先进先出 的数据结构 xff08 如排队候车 xff0c 肯定是先排队的人先上车 xff09 实际用处 如打印机 队列操作 使用数组实现队列结构 使用类封装队列操作 span class token keyword co
  • javascript:数据结构——链表

    什么是链表 xff1f 链表是有序的列表 xff0c 链表是以节点的方式来存储 xff0c 是链式存储 每个节点包含item域 xff0c next指针 xff08 指向下一个节点 xff09 xff0c 即就是链表中的每一个元素都带有下一
  • JavaScript中的事件循环机制

    我们知道JavaScript语言是单线程的 xff0c 至于为啥是单线程 xff1f 假设有两个线程 xff0c 一个在页面上新增一个div xff0c 另一个线程在页面上删除div xff0c 那最终听谁的 xff1f 那JavaScri
  • 彻底搞懂递归

    什么是递归 xff1f 简单的来说 xff1a 递归就是函数自己调自己 下来我们来看几个例子让你彻底搞懂递归 一 计算n的阶乘 顾名思义阶乘就是所有小于及等于该数的正整数的积 xff08 0和1的阶乘是1 xff09 下面我们先用循环的方式
  • javascript:求最大公约数的几种方式

    什么是最大公约数 xff1f 几个数所共有的约数中最大的一个 即可以整除这几个数的最大的数 叫做这几个数的最大公约数 方法一 xff1a 计算机思维 span class token keyword function span span c
  • javascript深浅拷贝的实现和区别

    什么是深拷贝和浅拷贝 所谓拷贝就是赋值 xff0c 把a的值赋值给b 区别 最明显的区别就是 xff1a 把a的值赋值给b xff0c 然后你改变b xff0c 看a会不会有变化 xff0c 如果a变了那就是浅拷贝 xff0c 如果a没有变
  • javascript判断数据类型的几种方法

    首先先回顾一下javascript的数据类型都有哪些 xff1f 基本数据类型 xff1a number xff0c undefined xff0c boolean xff0c string xff0c null 复杂数据类型 xff1a
  • O-ComTool修复中文显示问题

    O ComTool Pro我个人认为是一款很好用的串口调试软件 xff0c hex与ascii互转 xff0c 报文格式化 xff0c 打印窗口停留 xff0c 加载发送文件 xff0c 内容复制等细节体验很棒 xff0c 但是有一个问题体
  • 类的关系(泛化, 实现,关联,聚合,组合,依赖)

    类的关系 在UML类图中 xff0c 常见的有以下几种关系 泛化 xff08 Generalization xff09 实现 xff08 Realization xff09 xff0c 关联 xff08 Association xff0c
  • XMLHttpRequest获取后台response返回的数据

    XMLHttpRequest获取后台response返回的数据 开发MVC网站的过程中遇到令人头疼的bug 在js中通过XMLHttpRequest获取后台返回的数据竟然是当前页面的Html代码 xff01 xff01 xff01 后台Co
  • npm 安装 chromedriver依赖超时,导致项目打包进程失败

    npm 安装 chromedriver依赖超时 xff0c 导致项目打包进程失败 网络上大部分解决方式是npm 安装换源 xff1a npm install chromedriver chromedriver cdnurl 61 http
  • 12个Visual Studio调试效率技巧

    在这篇文章中 xff0c 我们假定读者了解VS基本的调试知识 xff0c 如 xff1a F5 开始使用调试器运行程序F9 在当前行设置断点F10 运行到下一个断点处F5 从被调试的已停止程序恢复执行F11 步进到函数内 xff08 如果当
  • 思岚RPLIDAR A2激光雷达使用及问题解决

    思岚RPLIDAR A2激光雷达使用及问题解决1 下载源码第一步 xff0c 下载雷达源代码 xff0c 第一种方法是输入下列网址 xff1a http slamtec com rplidar a2 download xff0c 界面有雷达
  • 雷达调制

    雷达通常有两种基本类型 xff1a 连续波 xff08 CW xff09 雷达和脉冲雷达 连续波雷达发射连续波 xff0c 并且发射的同时可以接收反射回来的的回波信号 xff0c 即收发可以同时进行 脉冲雷达间歇式发射脉冲周期信号 xff0
  • 各类优化算法综述

    目录 优化算法综述 数学规划法 精确算法 xff08 exact algorithm xff09 启发式 VS 元启发式 启发式算法 元启发式算法 What is the difference between heuristics and
  • Qt与halcon联合开发实现基于形状的模板匹配

    目录 前言 一 基于形状的模板匹配是什么 xff1f 二 具体实现 1 算子介绍 2 关键代码实现 总结 前言 第一次在CSDN写博客 xff0c 准备写一个简单的形状匹配算子的用法及实现的介绍 一 基于形状的模板匹配是什么 xff1f 基
  • VMware Wrokstation Ubuntu18虚拟机遇到ip能ping通,但是浏览器却无法访问情况

    VMware Wrokstation Ubuntu18虚拟机遇到ip能ping通 xff0c 但是浏览器却无法访问情况 解决方案 xff1a 安装防火墙 xff0c 先查询端口有没有权限 xff0c 没有就添加端口外部访问权限 xff0c
  • 使用码云、github时常用的git命令

    常用命令 边学习边总结的命令 xff0c 可能不全 xff0c 单纯做个笔记用 git branch 查看当前分支git checkout b branchname 创建branchname新分支并切换到新分支git push u orig
  • 常用涡识别方法的Tecplot实现(Q准则、λ2 准则、delta准则、Omega准则)

    常用涡识别方法的Tecplot实现 xff08 Q准则 2 准则 delta准则 Omega准则 xff09 0 前言0 1 欧拉法涡识别0 2 Tecplot中的涡识别 1 涡量法2 Q方法2 1 2D的Tecplot公式2 2 3D的T
  • FreeRTOS源码学习_01-任务调度器-2021-10-28

    FreeRTOS源码学习 01 任务调度器 一 写在前面二 源码分析1 开始任务调度 xff1a void vTaskStartScheduler 2 创建软件定时器任务 xff1a 3 检查链表队列是否有效 xff1a prvCheckF