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学习笔记 6 - 互斥量

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

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

    1 常见的使用场景 void vLED Task void pvParameters while 1 Heartbeat LED vTaskDelay 1000 portTICK RATE MS 说明 上面这段代码的意思是 led翻转后经过
  • 【FreeRTOS开发问题】FreeRTOS内存溢出

    FreeRTOS内存溢出 如下图所示 FreeRTOS编译完成后可以看到 系统提示无法分配内存到堆 Objects Template axf Error L6406E No space in execution regions with A
  • FreeRTOS基础五:软件定时器

    软件定时器简介 软件定时器的作用 在指定的时间到来时执行指定的函数 或者以某个频率周期性地执行某个函数 被执行的函数叫做软件定时器回调函数 软件定时器由FreeRTOS内核实现 不需要硬件支持 软件定时器只有在软件定时器回调函数被调用时才需
  • ES6 Symbol

    概览 const mySymbol Symbol mySymbol console log mySymbol Symbol mySymbol console log mySymbol Symbol mySymbol false consol
  • FreeRTOS,串口中断接收中使用xQueueOverwriteFromISR()函数,程序卡死在configASSERT

    原因 UART的中断优先级设置的太高 高于了configMAX SYSCALL INTERRUPT PRIORITY宏定义的安全中断等级 UART的中断等级小于等于宏定义的优先等级即可
  • FreeRTOS之软件定时器

    FreeRTOS之软件定时器 声明 本人按照正点原子的FreeRTOS例程进行学习的 欢迎各位大佬指责和批评 谢谢 include sys h include delay h include usart h include led h in
  • FreeRTOS笔记(一)简介

    这个笔记主要依据韦东山freertos快速入门系列记录 感谢韦东山老师的总结 什么是实时操作系统 操作系统是一个控制程序 负责协调分配计算资源和内存资源给不同的应用程序使用 并防止系统出现故障 操作系统通过一个调度算法和内存管理算法尽可能把
  • Arduino IDE将FreeRTOS用于STM32

    介绍 适用于STM32F103C8的FreeRTOS STM32F103C是一种能够使用FreeRTOS的ARM Cortex M3处理器 我们直接在Arduino IDE中开始使用STM32F103C8的FreeRTOS 我们也可以使用K
  • FreeRTOS笔记(十)中断

    中断 当CPU在执行某一事件A时 发生另外一个更重要紧急的事件B请求CPU去处理 产生了中断 于是CPU暂时中断当前正在执行的事件A任务而对对事件B进行处理 CPU处理完事件B后再返回之前中断的位置继续执行原来的事件A 这一过程统称为中断
  • 【Spring Boot 源码学习】OnClassCondition 详解

    Spring Boot 源码学习系列 OnClassCondition 详解 引言 往期内容 主要内容 1 getOutcomes 方法 2 多处理器拆分处理 3 StandardOutcomesResolver 内部类 4 getMatc
  • RT-Thread记录(五、RT-Thread 临界区保护与FreeRTOS的比较)

    本文聊聊临界区 以及RT Thread对临界区的处理 通过源码分析一下 RT Thread 对临界区保护的实现以及与 FreeRTOS 处理的不同 目录 前言 一 临界区 1 1 什么是临界区 1 2 RTOS中的临界区 二 RT Thre
  • 13-FreeRTOS任务创建与删除

    任务创建和删除API函数位于文件task c中 需要包含task h头文件 task h里面包函数任务的类型函数 例如 对xTaskCreate的调用 通过指针方式 返回一个TaskHandle t 变量 然后可将该变量用vTaskDele
  • FreeRTOS实时操作系统(三)任务挂起与恢复

    系列文章 FreeRTOS实时操作系统 一 RTOS的基本概念 FreeRTOS实时操作系统 二 任务创建与任务删除 HAL库 FreeRTOS实时操作系统 三 任务挂起与恢复 FreeRTOS实时操作系统 四 中断任务管理 FreeRTO
  • FreeRTOS多任务调度器基础

    Cortex M4中SysTick调度器核心 Cortex M4中的中断管理 Cortex M4中影子栈指针 Cortex M4中SVC和PendSV异常 1 Cortex M4中SysTick调度器核心 systick每一次中断都会触发内
  • 如何将 void* 转换为函数指针?

    我在 FreeRTOS 中使用 xTaskCreate 其第四个参数 void const 是传递给新线程调用的函数的参数 void connect to foo void const task params void on connect
  • 小型 ARM 微控制器的 RTOS 内核之间的可量化差异 [关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 有许多不同的 RTOS 可用于微控制器 我专门寻找支持 ARM Cortex M 处理器的 RTOS 另外 我对闭源解决方案不感兴趣 试图从网站
  • 有关 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