RTOS精进(二):FreeRTOS 系统移植

头脑风暴—POSIX与FreeRTOS的联系 虽然本文不具有实际指导价值,但可以通过横向对比两个系统的异同点,相似之处加深我们对知识的理解。而且某些FreeRTOS变种(如FreeRTOS+POSIX)支持部分POSIX接口,允许开发者使用标准API(如pthread线程库)编写更便携的代码。学习POSIX接口的设计(如目录操作opendirreaddir),可为后续在FreeRTOS中实现类似功能提供参考。若项目需要从Linux迁移到FreeRTOS,了解POSIX接口的底层实现(如无缓冲I/O)有助于优化嵌入式场景下的性能。 阅读详情

FreeRTOS 系统移植

目录

移植步骤

  1. RTOS核心代码 , 比如task.c , queue.c 和list.c
  2. 一些与处理器架构相关的代码, FreeRTOS源码已经自带实现了很多不同的处理器和平台,它们位于FreeRTOS/Source/Portable/[相应编译器]/[相应处理器架构]文件夹下 ,即port.c 和 portmacro.h
  3. 堆栈分配, 在FreeRTOS/Source/portable/MemMang文件夹下具有各种类型的堆栈分配方案 , 即heap_x.c
  4. 编写FreeRTOSConfig.h文件, 在这其中做一些配置 ,比如时钟节拍(时钟频率是多少), 任务调度策略, 任务优先级划分成多少个, 具体可以参见FreeRTOS内核配置说明
  5. 编写一些钩子函数, 如果你在FreeRTOSConfig.h中设置了configUSE_TICK_HOOK=1,则必须编写voidvApplicationTickHook( void )函数。如果你在FreeRTOSConfig.h中设置了configCHECK_FOR_STACK_OVERFLOW=1或=2,则必须编写voidvApplicationStackOverflowHook( xTaskHandle pxTask, signed char *pcTaskName )函数

Cortex-M3移植

Cortex-M3 处理器 - 流水灯 - 博客园 Cortex-M3 系列处理器是基于 ARMv7-M 架构的处理器,应用非常广泛,为了能够深入的分析在此平台上跑 RTOS 的各种细节,所以有必要写一篇关于 CM3 处理器的结构相关的文章(CM4 类 https://www.cnblogs.com/god-of-death/p/14856578.html

  • Cortex M3是ARM针对微控制器设计的一种CPU Core , STM32某个型号就是Cortex M3的
  • 查看port.c 和 portmacro.h里涉及到哪些文件, 就知道该怎么移植了 (路径为FreeRTOS/Source/portable/RVDS/ARM_CM3), 参考移植步骤, 这里主要讲下port.c和portmacro.h的细节
移植细节
  1. pxPortInitialiseStack 这里面设置了当前任务的PC和LR等的地址 ,成为类似如下图所示的排列 。一个任务肯定需要存储PC等程序地址以及SP栈地址, 才能再调度切换后知道从哪里开始,或者从其它任务切换回自己的时候,知道从上次暂停的地方开始 . 这里跟TCB还是不同的, TCB是Freertos内核创建的, 本函数调用前就创建好了.
    StackType_t * pxPortInitialiseStack( StackType_t * pxTopOfStack,
                                         TaskFunction_t pxCode,
                                         void * pvParameters )
    {
        /* Simulate the stack frame as it would be created by a context switch
         * interrupt. */
        pxTopOfStack--;                                                      /* Offset added to account for the way the MCU uses the stack on entry/exit of interrupts. */
        *pxTopOfStack = portINITIAL_XPSR;                                    /* xPSR */
        pxTopOfStack--;
        *pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK; /* PC (Program Counter)为程序计数器,
                                           ,PC是当前取指的代码行,如果指针在动态库,则无法定位源文件及行数, 这里指的是自身的执行地址  */
        pxTopOfStack--;
        *pxTopOfStack = ( StackType_t ) prvTaskExitError;                    /* LR , 函数的调用者地址 */
    
        pxTopOfStack -= 5;                                                   /* R12, R3, R2 and R1. */
        *pxTopOfStack = ( StackType_t ) pvParameters;                        /* R0 */
        
        
        pxTopOfStack -= 8;                                                   /* R11, R10, R9, R8, R7, R6, R5 and R4. */
    
        return pxTopOfStack;
    }
    

在这里插入图片描述

这里XPSR的意思如下, 就是存储状态的寄存器组
    - xPSR(程序状态字寄存器组),32位,可分为三个寄存器分别进行访问,也可以PSR或xPSR的名字直接组合访问。
        - 应用程序PSR(APSR)
        - 中断号PSR(IPSR)
        - 执行PSR (EPSR)

R0-R3
用作传入函数参数,传出函数返回值。在子程序调用之间,可以将 r0-r3 用于任何用途


R4-R11
被用来存放函数的局部变量


  1. prvStartFirstTask
    //ldr r0,r1 //表示把r1寄存器中的值放入r0
    //ldr r0,[r1] // [r1]表示r1中值对应内存的地址,所以是把r1中的数当作一个地址,把这个地址中的值放入r0
    //ldr r0, =0x12345678 这样,就把0x12345678这个地址写到r0中了
    
    __asm void prvStartFirstTask( void )
    {
        PRESERVE8    // 要求代码段按8字节对齐
     
        /* Use the NVIC offset register to locate the stack. */
        ldr r0, =0xE000ED08   //  0xE000ED08 地址处为VTOR(向量表偏移量)寄存器,存储向量表起始地址
     
        ldr r0, [r0]   //  启动文件中, 最初地址放置的__initial_sp 
        ldr r0, [r0]  //  根据向量表实际存储地址,取出向量表中的第一项,向量表第一项存储主堆栈指针 MSP 的初始值
        /* Set the msp back to the start of the stack. */
        msr msp, r0   //  将 __initial_sp的初始值写入 MSP 中 
        /* Globally enable interrupts.  接下来四个字令就是中断相关的*/
        cpsie i
        cpsie f
        dsb
        isb
        /* Call SVC to start the first task. */
        svc 0   //    调用SVC中断, 就是调用vPortSVCHandler
        nop
        nop
    }
    /*
    Cortex-M3 处理器,上电默认进入线程的特权模式,使用 MSP 作为堆栈指针,从上电跑到这里,经过一系列的函数调用,出栈,入栈,MSP 自然已经不是最开始的初始化的位置,这里通过 MSR 重新初始化 MSP,岂不是堆栈都没了么?是的,因为这是一条不归路,代码跑到这里,首先不会返回,之前压栈的内容再也不会用到,所以破坏之前的堆栈也没关系
    */
    
    FreeRTOS 启动第一个任务 prvStartFirstTask vPortSVCHandler_jltsun的博客-优快云博客
  2. vPortSVCHandler 参考这个地址来理解 https://www.cnblogs.com/god-of-death/p/14855444.html
    // 相当于把当前函数的地址以及 rx 放到PC里去执行。
    __asm void vPortSVCHandler( void )
    {
        PRESERVE8
       
        /* Get the location of the current TCB. */
        ldr r3, =pxCurrentTCB    //pxCurrentTCB 指向的是最高优先级的 Ready 状态的任务指针
        ldr r1, [r3]          //  根据 tskTCB结构体定义, 首地址存的是 pxTopOfStack   
        ldr r0, [r1]          //  r0得到TopOfStack的值
        /* Pop the core registers. */
        ldmia r0!, {r4-r11}   /* ldmia用于将寄存器弹出栈, 将TopOfStack对应的地址中的内容放到将r4-r11和r14寄存器中 
                                  这里为什么要把TopOfStack对应的地址中对应的r4-r11放到r4-r11寄存器里呢 ? 因为r4-r11是给
                                  程序使用的,可以任意使用,为此任务启用时,就应该把这些值放到r4-r11寄存器中, 当任务挂起时,则
                                  需要把r4-r11保存起来,放到TopOfStack对应的地址中, 下次切换到当前任务,就又会弹出到r4-r11
                                  寄存器中*/
                              
        msr psp, r0           /* 将此刻的 R0 赋值给 PSP, 因为出栈的时候,处理器会按照入栈的顺序去取 R4-R11、R14 ,
                                 而这些寄存器在我们创建任务的时候已经手动压栈*/
        isb
        mov r0, #0         // r0赋值为0
        msr basepri, r0    // 将 BASEPRI 寄存器赋值为 0,也就是允许任何中断
        orr r14, r14, #13  /* 因为当前在 ISR 中还是使用的 MSP,启动任务后,我们期望在任务执行过程中,处于线程模式,
                             并使用 PSP(前面几行已经给 PSP 赋值了),所以我们需要将 LR 设计成为 0xFFFF_FFFD,
                             让处理器知道返回的时候呢,使用线程模式+PSP堆栈;*/
                              
        bx r14             /* BX 指令跳转到指令中所指定的目标地址, 跳转到r14. r14就是lr, lr (总是)包含着退出时要装载到 pc 中的值。
                              最后执行 bx R14,告诉处理器 ISR 完成,需要返回,此刻处理器便会使用 PSP 做为堆栈指针,进行出栈操作,
                              将xPSR、PC、LR、R12、R3~R0 出栈,初始化的时候,PC 被我们赋值成为了执行任务的函数的入口,
                              所以呢,就正常跳入到了优先级最高的 Ready 状态的第一个任务的入口函数了;*/
                           // https://blog.youkuaiyun.com/qwe5959798/article/details/122717894
    }
    
    
    1. SVC用于产生系统函数的调用请求。例如,操作系统通常不允许用户程序直接访问硬件,而是通过提供一些系统服务函数,让用户程序使用SVC发出对系统服务函数的调用请求,以这种方法调用它们来间接访问硬件。因此,当用户程序想要控制特定的硬件时,它就要产生一个SVC异常,然后操作系统提供的SVC异常服务程序得到执行,它再调用相关的操作系统函数,后者完成用户程序请求的服务。
    2. 结合prvStartFirstTask和 pxPortInitialiseStack来看 , 就是将pxPortInitialiseStack里申请的那段空间里存放的R4-R11放到寄存器R4-R11中, 然后修改堆栈指针psp的值,使得取指PC指向最高优先级的 Ready 状态的任务指针(这其中还包含了xPSR,LR,R12,R3-R0, 退出函数时,栈的值会减少8(处理器自动操作), 并载入这8个值作为后续执行的参数,这其中就包括PC) 。 要完全理解可以参考这篇文章 https://blog.youkuaiyun.com/u012351051/article/details/125125796
  3. xPortPendSVHandler 参考这个地址 https://blog.youkuaiyun.com/tao475824827/article/details/105622087
    • PendSV是为系统级服务提供的中断驱动。在一个操作系统环境中,当没有其他异常正在执行时,可以使用PendSV来进行上下文的切换。
    • 使用PendSV时,一般会将PendSV的优先级设置为最低,让外部IRQ先执行,等到没有外部中断后,在执行上下文切换
    __asm void xPortPendSVHandler( void )
    {
        extern uxCriticalNesting;
        extern pxCurrentTCB;
        extern vTaskSwitchContext;
    
    /* *INDENT-OFF* */
        PRESERVE8
    
        mrs r0, psp
        isb
    
        ldr r3, =pxCurrentTCB /* Get the location of the current TCB. */
        ldr r2, [ r3 ]
    
        stmdb r0 !, { r4 - r11 } /* Save the remaining registers. 把r4-r11寄存器内容压入r0开始的地址。 保存寄存器到值, 
                                    STMDB r0!, {r4-r11} r0的存储地址由高到低递减,将r4-r11里的内容存储到r0任务栈里面。*/
        str r0, [ r2 ] /* Save the new top of stack into the first member of the TCB. */
    
        stmdb sp !, { r3, r14 }             /* SP是MSP和PSP的代言人,即SP是MSP和PSP的逻辑地址,SP始终指向各种模式、各种场景下使用的堆栈指针,
                                               只不过在OS或Handler模式下,SP先指向MSP,或者说SP拷贝了MSP的值,可以直接访问主堆栈。而在线程模式下,
                                               SP拷贝了PSP的值,可以直接访问线程(任务)堆栈对于裸机程序,我们只需要知道SP即可,而对于OS系统,
                                               尤其涉及中断、任务上下文切换时,就需要知道PSP和MSP了,OS底层也会直接针对PSP进行编程。这里后续分析FreeRTOS调度实现原理时再详细解释。*/
        mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
        msr basepri, r0
        dsb
        isb
        bl vTaskSwitchContext。/* 调用vTaskSwitchContext, 这个函数在task.c里,
                                  这里bl的作用跳转,但是跳转之前,会在寄存器R14 中保存PC 的当前内容。 
                                  其实就相当于调用vTaskSwitchContext后继续执行接下来的命令, 就相当于C语言里的一次函数调用
                               */
        mov r0, #0
        msr basepri, r0
        ldmia sp !, { r3, r14 }
    
        ldr r1, [ r3 ]
        ldr r0, [ r1 ] /* The first item in pxCurrentTCB is the task top of stack. */
        ldmia r0 !, { r4 - r11 } /* Pop the registers and the critical nesting count.  
                                    将r0存储地址里面的内容手动加载到 CPU 寄存器 r4-r11里 */ 
        msr psp, r0   // r0 给 psp
        isb
        bx r14
        nop
    /* *INDENT-ON* */
    }
    
  4. xPortSysTickHandler , 其实在这里面是需要考虑是否需要切换任务的,这里通过PendSV中断处理去进行这个操作
    • SysTick是一个中断, SysTick的优先级设置为最低, 因为SysTick的优先级为最低,并且将任务调度放到了PendSV,可以最大限度的减少SysTick中断服务程序消耗时间,IRQ不会被中断,并且能够最快的时间去响应。
    • SysTick的优先级低,影响RTOS时钟精度怎么办? 不怎么办, RTOS的时钟节拍不需要很精准,只要有周期的心跳,就能保证操作系统周期的运行、周期的调度。 OTS的软件定时器只能用于【短时间】、对时间要求不严苛的应用场景
    void xPortSysTickHandler( void )
    {
        /* The SysTick runs at the lowest interrupt priority, so when this interrupt
         * executes all interrupts must be unmasked.  There is therefore no need to
         * save and then restore the interrupt mask value as its value is already
         * known - therefore the slightly faster vPortRaiseBASEPRI() function is used
         * in place of portSET_INTERRUPT_MASK_FROM_ISR(). */
        vPortRaiseBASEPRI();
        {
            /* Increment the RTOS tick. , 增加tick计数*/
            if( xTaskIncrementTick() != pdFALSE )
            {
                /* A context switch is required.  Context switching is performed in
                 * the PendSV interrupt.  Pend the PendSV interrupt. */
                portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
            }
        }
    
        vPortClearBASEPRIFromISR();
    }
    
  5. xPortStartScheduler ,就是使能PendSV中断, 使能时钟节拍中断(就是xPortSysTickHandler), 开启第一个任务
    {
        /* Make PendSV and SysTick the lowest priority interrupts. */
        portNVIC_SHPR3_REG |= portNVIC_PENDSV_PRI;
    
        portNVIC_SHPR3_REG |= portNVIC_SYSTICK_PRI;
    
        /* Start the timer that generates the tick ISR.  Interrupts are disabled
         * here already. */
        vPortSetupTimerInterrupt();
    
        /* Initialise the critical nesting count ready for the first task. */
        uxCriticalNesting = 0;
    
        /* Start the first task. */
        prvStartFirstTask();
    
        /* Should not get here! */
        return 0;
    }
    
    
  6. vPortEndScheduler
    // 不需要实现, 直接放置一个断言
    void vPortEndScheduler( void )
    {
        /* Not implemented in ports where there is nothing to return to.
         * Artificially force an assert. */
        configASSERT( uxCriticalNesting == 1000UL );
    }
    
  7. vPortEnterCritical
    // 本质上就是不使能中断(操作下寄存器), 因为只有通过中断才能切换任务 , 记住这点就可以了, 下面的注释无需细看。
    
    void vPortEnterCritical( void )
    {
        portDISABLE_INTERRUPTS();
        uxCriticalNesting++;
    
        /* This is not the interrupt safe version of the enter critical function so
         * assert() if it is being called from an interrupt context.  Only API
         * functions that end in "FromISR" can be used in an interrupt.  Only assert if
         * the critical nesting count is 1 to protect against recursive calls if the
         * assert function also uses a critical section. */
        if( uxCriticalNesting == 1 )
        {
            configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
        }
    }
    
    
  8. vPortExitCritical
    // 本质就是使能中断
    
    void vPortExitCritical( void )
    {
        configASSERT( uxCriticalNesting );
        uxCriticalNesting--;
    
        if( uxCriticalNesting == 0 )
        {
            portENABLE_INTERRUPTS();
        }
    }
    
    
  9. portYIELD // 触发一个pendSV中断
    // portmacro.h中
    
    #define portYIELD() 
    {                                                   \
        /* Set a PendSV to request a context switch. */ \
        portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; \
                                                        \
        /* Barriers are normally not required but do ensure the code is completely \
         * within the specified behaviour for the architecture. */ \
        __dsb( portSY_FULL_READ_WRITE );                           \
        __isb( portSY_FULL_READ_WRITE );                           \
    }
    
    
  10. vPortGetIPSR 获取当前的中断号 , 为啥这里没有返回值也行 ? 返回值是放在r0里的,实际上,在汇编语言中,函数调用的参数和返回值均可以通过寄存器来传送,只要函数内外相互配合,就可以精确地进行参数和返回值传递。 汇编认为返回的值就在r0里
    __asm uint32_t vPortGetIPSR( void )
    {
    /* *INDENT-OFF* */
        PRESERVE8 //代码段按8字节对齐
    
        mrs r0, ipsr   
        bx r14
    /* *INDENT-ON* */
    }
    
  11. 中断相关
    #define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
    #define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 )
    
移植总结
  • 主要涉及到的其实就是中断, 任务切换通过出发PendSV中断,Enter和Exit Critial通过禁止和恢复中断实现。 另外为了开启或切换任务 , 为了切换到当前最高优先级的任务, 需要将PC等切换到当前任务。 为此根据中断的特性, 进入中断时CPU自动保存现场寄存器,包括R0~R3, R12, R14(LR), 返回地址(PC), xPSR ,将这8个寄存器值存放到堆栈。 为了切换到选定的任务, 则将psp指针修改为选定任务的r0位置(创建任务时会initialStack时有r0) ,后续中断退出时, CPU会恢复栈,这时PC就被修改为选定任务的PC了,也就跳转到选定任务执行 。 另外还有一个r4-r11寄存器, r4-r11用于人为用到的寄存器, 我不知道会被用到什么地方, 但我这就是需要假定被用到了. 所以在切换一个任务前, 我会把当前任务的这些值保存好, 等到切换任务后时候 ,我会手动将切换后到任务的这些值出栈到r4-r11
  • 另外涉及三个中断, SVC中断(只在第一次启动任务调度的时候用), PendSV中断(用于任务切换), SysTick(系统滴答定时器的中断, 这个中断函数主要就是做了xTaskIncrementTick, 如果该函数返回值为真(不等于pdFALSE),说明处于就绪态任务的优先级比当前运行的任务优先级高, 就出发PendSV中断)
  • 另外宏根据需要设置, 时钟设置成硬件时钟的频率即可
参考资料

RTOS系列文章(8):深入分析中断处理过程_猪哥-嵌入式的博客-优快云博客_rtos的中断和裸机中断 在上一篇文章中,我们深入分析了函数调用返回、中断调用返回使用的LR和EXC_TURN,也简单说明了函数调用流程、中断调用流程。在FreeRTOS中,任务的调度是通过SysTick + PendSV 中断来实现的,尤其在PenSV中断中进行任务上下文切换原理,是理解FreeRTOS任务调度原理的重中之重。而SysTick和PendSV都是中断,所以我们有必要深入分析一下在ARM CM3/4中中断的完 https://blog.youkuaiyun.com/u012351051/article/details/125125796

【FreeRTOS】FreeRTOS 源码学习笔记 (5) 任务调度器 + vTaskStartScheduler、xPortPendSVHandler、xPortSysTickHandler_taotao830的博客-优快云博客_xportstartscheduler 1. 引言FreeRTOS的任务调度是个大头,也是一个操作系统的核心。其实个人理解,FreeRTOS调度规则很好理解,原则就是“优先级高抢占”,因为FreeRTOS是一个抢占式实时内核,一定会保证就绪态的高优先级任务可以先运行。所有的调度都是为了实现这个目的来做的。一些个人思考可以看4.1节。2. 原理分析2.1 什么情况会任务切换个人理解,只有当一个更高优先级的任务进入到就绪列表… https://blog.youkuaiyun.com/tao475824827/article/details/105622087

FreeRTOS 启动第一个任务 prvStartFirstTask vPortSVCHandler_jltsun的博客-优快云博客__ asm void prvStartFirstTask( void )_ _asm void prvStartFirstTask( void ){ PRESERVE8 /* Use the NVIC offset register to locate the stack.* / ldr r0, =0xE000ED08 //0xE000ED08 地址处为VTOR(向量表偏移量)寄存器,存储向量表起始 https://blog.youkuaiyun.com/jltsun/article/details/118383820

Linux Posix环境下的移植

  • 参考网址FreeRTOS simulator for Posix/Linux实现这个功能
  • 查看port.c 和 portmacro.h里涉及到哪些文件就知道如何移植, 文件路径为FreeRTOS/Source/portable/ThirdParty/GCC/Posix/port.c, 下面详述细节
移植细节
  1. pxPortInitialiseStack xTaskCreate会调用这个函数
    /* 一些背景知识
    
    pthread_sigmask要说明下
    int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset);
        How: 三个参数都是设置屏蔽信号量的, 只是设置方法不同而已
        SIG_BLOCK:     结果集是当前集合参数集的并集 , set和oldset的并集, 如果oldset为空,则就是set
        SIG_UNBLOCK: 结果集是当前集合参数集的差集, set和oldset的差集, 如果oldset为空,则就是set
        SIG_SETMASK: 结果集是由参数集指向的集, 即就是set, 另外会复制一份set到oldset里
    
    sigaction信号处理, 有多个线程都不屏蔽的情况下, 是选定一个线程来处理的. 具体怎么选可以暂时不管
    正确的处理方法, 主线程和工作线程都屏蔽信号, 用一个专门的线程来处理信号. 这里使用了这种方法 , 
    区别就是只有唤醒的任务线程才会处理, 其它的都屏蔽了.
    
    沉睡唤醒是通过条件变量来的, 和信号相比是两个独立的机制
    
    */
    static void prvSetupSignalsAndSchedulerPolicy( void )
    {
        struct sigaction sigresume, sigtick;
        int iRet;
    
        hMainThread = pthread_self();
    
        /* Initialise common signal masks. */
        sigemptyset( &xResumeSignals );
        sigaddset( &xResumeSignals, SIG_RESUME );
        sigfillset( &xAllSignals );
        /* Don't block SIGINT so this can be used to break into GDB while
         * in a critical section. */
        sigdelset( &xAllSignals, SIGINT );
    
        /*
         * Block all signals in this thread so all new threads
         * inherits this mask.
         *
         * When a thread is resumed for the first time, all signals
         * will be unblocked.
         */
         /*
           会设置信号量, 屏蔽所有信号。 后续所有pthread_create创建的线程都会继承这个设置, 
           即创建后会屏蔽所有信号量。有一个信号SIG_INT, 不屏蔽这个信号, 这个只是为了gdb调试, 
           这里不细究,了解即可
         */
        (void)pthread_sigmask( SIG_SETMASK, &xAllSignals,
                               &xSchedulerOriginalSignalMask );
    
        /* SIG_RESUME is only used with sigwait() so doesn't need a
           handler. 这个只在一个特殊的地方vPortEndScheduler里用到,并且不需要处理函数。 */
        sigresume.sa_flags = 0;
        sigresume.sa_handler = SIG_IGN;
        sigfillset( &sigresume.sa_mask );
    
        sigtick.sa_flags = 0;
        sigtick.sa_handler = vPortSystemTickHandler;
        sigfillset( &sigtick.sa_mask );
    
        iRet = sigaction( SIG_RESUME, &sigresume, NULL );
        if ( iRet )
        {
            prvFatalError( "sigaction", errno );
        }
    
        iRet = sigaction( SIGALRM, &sigtick, NULL );
        if ( iRet )
        {
            prvFatalError( "sigaction", errno );
        }
    }
    
    
    portSTACK_TYPE *pxPortInitialiseStack( portSTACK_TYPE *pxTopOfStack,
                                           portSTACK_TYPE *pxEndOfStack,
                                           pdTASK_CODE pxCode, void *pvParameters )
    {
        Thread_t *thread;
        pthread_attr_t xThreadAttributes;
        size_t ulStackSize;
        int iRet;
    
        /* 只调用一次prvSetupSignalsAndSchedulerPolicy, 会设置信号量, 屏蔽所有信号。 后续所有
           pthread_create创建的线程都会继承这个设置, 即创建后会屏蔽所有信号量。
           同时会 设置收到SIGALRM后调用vPortSystemTickHandler处理(接触屏蔽情况下, sigaction用于定义所有子线程处理方法), 
                 设置收到SIG_RESUME(其实就是SIGUSR1, 用户自定义信号处理)没有处理函数。只接收不需要处理
                 另外还有一个信号SIG_INT, 不屏蔽这个信号, 这个只是为了gdb调试, 这里不细究,了解即可
           后续线程第一次被开启后, 会使能所有信号, 但只会处理SIG_ALRM, 只有这个信号定义了处理函数。其它都
           为空或者没定义。 
        */
        (void)pthread_once( &hSigSetupThread, prvSetupSignalsAndSchedulerPolicy );
    
        /*
         * Store the additional thread data at the start of the stack.
         */
        thread = (Thread_t *)(pxTopOfStack + 1) - 1;
        pxTopOfStack = (portSTACK_TYPE *)thread - 1;
        ulStackSize = (pxTopOfStack + 1 - pxEndOfStack) * sizeof(*pxTopOfStack);
    
        thread->pxCode = pxCode;   //xTaskCreate创建的任务函数
        thread->pvParams = pvParameters;
        thread->xDying = pdFALSE;
    
        pthread_attr_init( &xThreadAttributes );
        pthread_attr_setstack( &xThreadAttributes, pxEndOfStack, ulStackSize );
    
        thread->ev = event_create();
    
        vPortEnterCritical();
        
        // prvWaitForStart 一开始会阻塞,需要signal唤醒后才执行, 
        iRet = pthread_create( &thread->pthread, &xThreadAttributes,
                               prvWaitForStart, thread );
        if ( iRet )
        {
            prvFatalError( "pthread_create", iRet );
        }
    
        vPortExitCritical();
    
        return pxTopOfStack;
    }
    
    
    static void *prvWaitForStart( void * pvParams )
    {
        Thread_t *pxThread = pvParams;
        prvSuspendSelf(pxThread);      // cond_wait, 等价于任务线程沉睡
    
        /* Resumed for the first time, unblocks all signals. */
        uxCriticalNesting = 0;
        vPortEnableInterrupts();       /* 当前线程使能所有信号, 只有当前线程会处理SIG_ALRM ,其它都不能(因为屏蔽了) 
                                          pthread_sigmask( SIG_UNBLOCK, &xAllSignals, NULL );*/
    
        /* Call the task's entry point. */  
        pxThread->pxCode( pxThread->pvParams );  // 这个其实就是执行任务函数
    
        /* A function that implements a task must not exit or attempt to return to
        * its caller as there is nothing to return to. If a task wants to exit it
        * should instead call vTaskDelete( NULL ). Artificially force an assert()
        * to be triggered if configASSERT() is defined, so application writers can
            * catch the error. */
        configASSERT( pdFALSE );
        return NULL;
    }
    
    
  2. xPortStartScheduler 见思维导图
    portBASE_TYPE xPortStartScheduler( void )
    {
    int iSignal;
    sigset_t xSignals;
    
        hMainThread = pthread_self();
    
        /* Start the timer that generates the tick ISR(SIGALRM).
           Interrupts are disabled here already. */
        prvSetupTimerInterrupt();  // 使能时间片信号, 时间片到了后会发送SIGALRM信号
    
        /* Start the first task. */
        vPortStartFirstTask();     // 发送cond_signal给最高优先级的线程, 唤醒它
    
        /* Wait until signaled by vPortEndScheduler(). */
        sigemptyset( &xSignals );
        sigaddset( &xSignals, SIG_RESUME );
    
        while ( !xSchedulerEnd )
        {
           /*阻塞 , 直到vPortEndScheduler设置标志位xSchedulerEnd并发送信号SIG_RESUME 让主线程退出.
             暂时可以不管, 非核心功能*/
            sigwait( &xSignals, &iSignal );
        }
    
        /* Cancel the Idle task and free its resources */
    #if ( INCLUDE_xTaskGetIdleTaskHandle == 1 )
        vPortCancelThread( xTaskGetIdleTaskHandle() );
    #endif
    
    #if ( configUSE_TIMERS == 1 )
        /* Cancel the Timer task and free its resources */
        vPortCancelThread( xTimerGetTimerDaemonTaskHandle() );
    #endif /* configUSE_TIMERS */
    
        /* Restore original signal mask. */
        (void)pthread_sigmask( SIG_SETMASK, &xSchedulerOriginalSignalMask,  NULL );
    
        return 0;
    }
    
  3. vPortEndScheduler
    void vPortEndScheduler( void )
    {
    struct itimerval itimer;
    struct sigaction sigtick;
    Thread_t *xCurrentThread;
    
        /* Stop the timer and ignore any pending SIGALRMs that would end
         * up running on the main thread when it is resumed. */
        itimer.it_value.tv_sec = 0;
        itimer.it_value.tv_usec = 0;
    
        itimer.it_interval.tv_sec = 0;
        itimer.it_interval.tv_usec = 0;
        (void)setitimer( ITIMER_REAL, &itimer, NULL );
    
        sigtick.sa_flags = 0;
        sigtick.sa_handler = SIG_IGN;
        sigemptyset( &sigtick.sa_mask );
        sigaction( SIGALRM, &sigtick, NULL );
    
        /* Signal the scheduler to exit its loop. */
        xSchedulerEnd = pdTRUE;
        (void)pthread_kill( hMainThread, SIG_RESUME );
    
        xCurrentThread = prvGetThreadFromTask( xTaskGetCurrentTaskHandle() );
        prvSuspendSelf(xCurrentThread);
    }
    
  4. vPortEnterCritical 和 vPortExitCritical, 这里其实就是不使能中断, 由于这里时钟和线程都是通过线程信号来实现的, 其实就是屏蔽所有的信号pthread_sigmask( SIG_BLOCK, &xAllSignals, NULL );
    void vPortEnterCritical( void )
    {
        if ( uxCriticalNesting == 0 )
        {
            vPortDisableInterrupts();
        }
        uxCriticalNesting++;
    }
    /*-----------------------------------------------------------*/
    
    void vPortExitCritical( void )
    {
        uxCriticalNesting--;
    
        /* If we have reached 0 then re-enable the interrupts. */
        if( uxCriticalNesting == 0 )
        {
            vPortEnableInterrupts();
        }
    }
    
  5. vPortDisableInterrupts和vPortEnableInterrupts其实就是屏蔽和开启信号
    void vPortDisableInterrupts( void )
    {
        pthread_sigmask( SIG_BLOCK, &xAllSignals, NULL );
    }
    /*-----------------------------------------------------------*/
    
    void vPortEnableInterrupts( void )
    {
        pthread_sigmask( SIG_UNBLOCK, &xAllSignals, NULL );
    }
    
  6. vPortYield 切换任务
    void vPortYield( void )
    {
        vPortEnterCritical();
    
        vPortYieldFromISR();
    
        vPortExitCritical();
    }
    
    /*-----------------------------------------------------------*/
    
    void vPortYieldFromISR( void )
    {
    Thread_t *xThreadToSuspend;
    Thread_t *xThreadToResume;
    
        xThreadToSuspend = prvGetThreadFromTask( xTaskGetCurrentTaskHandle() );
    
        vTaskSwitchContext();
    
        xThreadToResume = prvGetThreadFromTask( xTaskGetCurrentTaskHandle() );
    
        prvSwitchThread( xThreadToResume, xThreadToSuspend );
    }
    
    
  7. prvSetupTimerInterrupt设置时钟周期 , 到点发送一个SIG_ALRM信号
    void prvSetupTimerInterrupt( void )
    {
    struct itimerval itimer;
    int iRet;
    
        /* Initialise the structure with the current timer information. */
        iRet = getitimer( ITIMER_REAL, &itimer );
        if ( iRet )
        {
            prvFatalError( "getitimer", errno );
        }
    
        /* Set the interval between timer events. */
        itimer.it_interval.tv_sec = 0;
        itimer.it_interval.tv_usec = portTICK_RATE_MICROSECONDS;
    
        /* Set the current count-down. */
        itimer.it_value.tv_sec = 0;
        itimer.it_value.tv_usec = portTICK_RATE_MICROSECONDS;
    
        /* Set-up the timer interrupt. */
        iRet = setitimer( ITIMER_REAL, &itimer, NULL );
        if ( iRet )
        {
            prvFatalError( "setitimer", errno );
        }
    
        prvStartTimeNs = prvGetTimeNs();
    }
    
  8. vPortSystemTickHandler 增加时钟计数 ,获取最高优先级的,如果最高优先级跟当前的不是同一个的话, 切换到最高优先级
    static void vPortSystemTickHandler( int sig )
    {
    Thread_t *pxThreadToSuspend;
    Thread_t *pxThreadToResume;
    /* uint64_t xExpectedTicks; */
    
        uxCriticalNesting++; /* Signals are blocked in this signal handler. */
    
    #if ( configUSE_PREEMPTION == 1 )
        pxThreadToSuspend = prvGetThreadFromTask( xTaskGetCurrentTaskHandle() );
    #endif
    
        /* Tick Increment, accounting for any lost signals or drift in
         * the timer. */
    /*
     *      Comment code to adjust timing according to full demo requirements
     *      xExpectedTicks = (prvGetTimeNs() - prvStartTimeNs)
     *        / (portTICK_RATE_MICROSECONDS * 1000);
     * do { */
            xTaskIncrementTick();
    /*        prvTickCount++;
     *    } while (prvTickCount < xExpectedTicks);
    */
    
    #if ( configUSE_PREEMPTION == 1 )
        /* Select Next Task. */
        vTaskSwitchContext();
    
        pxThreadToResume = prvGetThreadFromTask( xTaskGetCurrentTaskHandle() );
        
        // 如果两个不同才会切换
        prvSwitchThread(pxThreadToResume, pxThreadToSuspend);
    #endif
    
        uxCriticalNesting--;
    }
    
移植总结
  1. 堆内存分配选用heap3, 使用标准库进行堆内存分配
  2. 时钟模拟,采用linux的时间相关函数,每隔一个时钟周期发送SIG_ALRM信号
  3. 与移植到ARM硬件通过中断处理不同, 这里通过线程间的信号来处理, 信号来了跳转到信号处理函数里处理, 这个处理逻辑跟中断其实非常像 , 这里pendsv中断是没有的, 直接调用函数执行切换就好了,中断本质无非就是跳转某处去执行一个函数
  4. 另外栈空间直接保存在专用的堆里(一开始创建时会申请), 就不会有硬件移植过程中出现的寄存器出入栈保存的问题
  5. 通过线程间的同步进制进行任务优先级调度, 具体为任务开始后沉睡, 并由FreeRTOS的调度器决定唤醒哪个,沉睡和唤醒通过线程间的同步机制即条件变量(cond_wait,cond_signal)
  6. 条件变量和信号量没有任何关系 , 是两个独立的机制
  7. FreeRTOS的任务调度主要通过两种方式来触发 . 第一种是时间 , 即每隔固定时间触发中断 , 在中断里进行调度; 第二种是主动切换调度 ; 第三种是有新的高优先级任务创建出来了。
    1. 只有工作线程才会处理信号,当时间片信号来的时候,如果发现时间片已到,就会切换
    2. vTaskStartScheduler是在一个单独的线程里的,会唤醒优先级最高的任务,然后自身沉睡。后续任务的切换其实都是工作线程发起并切换的
    3. 新创建任务 ,如果任务优先级最高,则会将pxCurrentTCB指向当前新创建的任务TCB(确保pxCurrentTCB指向优先级最高的就绪任务) , 并且加入就绪列表, 最后按下面代码执行上下文切换
      if( xReturn == pdPASS )
      {
          if( xSchedulerRunning != pdFALSE ) // 如果调度器已经开启,否则等开启
          {
              /* 如果新创建的任务优先级大于当前任务优先级,则新创建的任务应该被立即执行。*/
              if(pxCurrentTCB->uxPriority < uxPriority )
              {
                  taskYIELD_IF_USING_PREEMPTION();
              }
          }
      }
      
成果物程序

gw_freertos_simu.zip

参考链接

FreeRTOS系列第5篇—FreeRTOS在Cortex-M3上的移植 - 极术社区 - 连接开发者与智能计算生态 下载包内的总文件数量多的令人生畏,但文件结构却很简洁。《FreeRTOS入门指南》一文的第3节详细描述了下载包文件结构,我们这里只是简单提一下。 https://aijishu.com/a/1060000000137854

FreeRTOS系列第6篇—FreeRTOS内核配置说明_研究是为了理解的博客-优快云博客 FreeRTOS内核是高度可定制的,使用配置文件FreeRTOSConfig.h进行定制。每个FreeRTOS应用都必须包含这个头文件,用户根据实际应用来裁剪定制FreeRTOS内核。这个配置文件是针对用户程序的,而非内核,因此配置文件一般放在应用程序目录下,不要放在RTOS内核源码目录下。 在下载的FreeRTOS文件包中,每个演示例程都有一个FreeRTOSCon… https://blog.youkuaiyun.com/zhzht19861011/article/details/50134883

FreeRTOS simulator for Posix/Linux FreeRTOS is a portable, open source, mini Real Time kernel. A free RTOS for small embedded systems https://www.freertos.org/FreeRTOS-simulator-for-Linux.html

从51单片机到FreeRTOS:一个嵌入式小白的五年自学路线图(附避坑指南) 本文分享了从51单片机到FreeRTOS的五年嵌入式自学路线图,详细介绍了基础筑基、STM32进阶、FreeRTOS操作系统启蒙等关键阶段,并附有实用避坑指南。通过真实项目案例,帮助初学者掌握嵌入式开发的核心技能,如寄存器操作、硬件抽象层和任务调度等,适合机械转行或自学嵌入式的开发者参考。 阅读详情

相关推荐

嵌入式开发转型实战:从C语言到RTOS,零基础到月薪18K的进阶之路

嵌入式系统开发是融合硬件与软件的关键技术领域,其核心在于对底层资源的精确控制与高效管理。其工作原理基于微控制器(MCU)直接操作寄存器与外设,并通过实时操作系统(RTOS)实现多任务调度与协同。掌握嵌入式开发的技术价值在于能够构建稳定、可靠且资源受限的智能设备,广泛应用于物联网终端、工业控制、消费电子等场景。本文聚焦于嵌入式软件开发的实践路径,通过系统学习C语言、数据结构和STM32等主流MCU,并深入应用FreeRTOS进行项目整合,为读者提供从基础到进阶的完整学习框架与工程实践参考。

weixin_30566111的博客 807

FreeRTOS优先级详解

1. STM32Cube中中断优先级分布 HAL_StatusTypeDef HAL_Init(void) { /* Configure Flash prefetch */ #if (PREFETCH_ENABLE != 0) #if defined(STM32F101x6) || defined(STM32F101xB) || defined(STM32F101xE) || defined(STM32F101xG) || \ defined(STM32F102x6) || defined(S

TrueLink的博客 3249

嵌入式工程师第一份工作怎么选:MCU、RTOS、Linux、BSP四维决策指南

嵌入式开发是软硬协同的系统工程,其核心能力围绕MCU(微控制器)、RTOS(实时操作系统)、Linux(嵌入式Linux系统)和BSP(板级支持包)四大技术坐标展开。MCU代表硬件交互底层,强调寄存器操作与物理世界建模;RTOS聚焦确定性响应与资源调度数学约束;Linux体现系统级整合能力,涵盖设备树、内核模块与用户空间协同;BSP则是芯片能力与操作系统之间的翻译枢纽。这四者并非并列选项,而是构成嵌入式工程师能力光谱的纵深维度。不同雇主——芯片原厂、大厂设备商、IoT初创公司——在这些维度上的技术权重差异显

weixin_34414650的博客 300

STM32上使用POSIX接口 (基于freertos)

STM32上使用POSIX接口 (基于freertos) 絮絮叨叨:如果不是博客粉丝,我都快要遗忘公众号这回事了,感谢所有认真阅读的读者的支持! FreeRTOS 和 POSIX 接口简介 POSIX 接口的全称是:可移植性操作系统接口(Portable Operating System Interface, POSIX)。它是由电器和电子工程师协会(IEEE)开发的开放操作系统接口标准,并且得到 IOS 和 ANSI 的认可。 其主要目的是提升应用程序在各种 UNIX 系统环境之间的可移植性。使用 PO

非 典型技术宅的博客 6034

智能车竞赛技术解析:从PID控制到图像处理的嵌入式系统实战

嵌入式系统开发是连接硬件与软件的核心技术领域,其核心在于实现高效、稳定的实时控制与数据处理。在自动控制原理中,PID控制算法作为经典闭环控制策略,通过比例、积分、微分环节的动态调节,实现对系统偏差的精准修正,广泛应用于电机调速、姿态稳定等场景。结合传感器融合技术,系统能整合多源信息,提升环境感知的鲁棒性。这些技术的工程价值在于将理论模型转化为可靠的产品功能,例如在智能车竞赛中,参赛者需综合运用嵌入式开发、PID整定和图像处理算法,实现模型车的自主循迹与高速稳定行驶。通过摄像头或电磁传感器采集赛道信息,经滤波

weixin_33748818的博客 301

STM32开发环境搭建全攻略:从零开始配置CubeIDE与CubeMX

嵌入式开发中,微控制器(MCU)是核心硬件平台,其开发环境搭建是工程师必备技能。ARM Cortex-M内核因其高性能、低功耗和丰富生态,成为主流选择。通过集成开发环境(IDE)和硬件抽象层(HAL)库,开发者能高效管理外设驱动与底层配置,大幅提升开发效率与代码可移植性。在物联网、工业控制等应用场景中,稳定的开发环境是项目成功基石。本文聚焦STM32平台,详解如何利用STM32CubeIDE与STM32CubeMX工具链,快速构建专业级开发环境,涵盖从芯片选型、图形化引脚配置、时钟树设置到程序下载调试的全流

weixin_30652897的博客 299

【原创】不同RTOS中POSIX接口的实现差异

以精确定时为例,精确定时是我们常用的操作系统服务之一,比如工业以太网(ECAT、PN...)中的通信周期,通信周期的准确控制离不开精确定时;在无线基站和终端的slot调度中,精确定时确保了数据传输的有序与高效;在实时仿真中,仿真步长的精确控制更是模拟真实场景的关键等等,这些都离不开POSIX定时接口。本文首先简要概述POSIX标准,随后深入剖析POSIX定时接口在常用开放内核源码的RTOS(实时操作系统)上的上的实现原理做简单介绍。通过本文的介绍,希望能为读者在实际应用中提供有价值的参考,避免在RTOS中使

wsg1100 1881

31、FreeRTOS API选择与内存管理

本文详细探讨了在FreeRTOS中选择POSIX、CMSIS-RTOS和原生API的优劣,以及如何根据项目需求进行内存管理。内容涵盖POSIX API的移植与使用、FreeRTOS提供的多种堆实现、动态与静态内存分配的对比,以及如何通过内存钩子、MPU等机制提升系统健壮性。适用于希望在嵌入式系统中高效使用FreeRTOS的开发人员。

win55的博客 112

FreeRTOS实战指南:从核心概念到项目架构的嵌入式开发路线

实时操作系统(RTOS)是嵌入式开发中管理多任务、确保实时响应的核心技术,其核心原理在于通过任务调度、内存管理和进程间通信机制,高效协调多个并发任务。在物联网、工业控制等场景中,RTOS能显著提升系统的可靠性与可维护性。FreeRTOS作为轻量、开源的RTOS代表,通过任务、队列、信号量与互斥量等机制,帮助开发者构建稳定的多任务应用。本文针对常见开发痛点,如堆栈溢出检测与串口通信集成,详解从环境搭建、外设驱动适配到中间件协作的全流程,为嵌入式开发者提供从入门到精通的系统化实践路径。

weixin_34008933的博客 425

手动移植freertos hal库,时基源需要修改吗?

🏆本文收录于 《全栈Bug调优(实战版)》 专栏,该专栏专注于分享我在真实项目开发中遇到的各类疑难Bug及其深层成因,并系统提供高效、可复现的解决思路和实操方案。无论你是刚入行的新手开发者,还是拥有多年项目经验的资深工程师,本专栏都将为你提供一条系统化、高质量的问题排查与优化路径,助力你加速成长,攻克技术壁垒,迈向技术价值最大化与职业发展的更高峰🚀!    📌 特别说明: 文中部分技术问题来源于真实生产环境及网络公开案例,均经过精挑细选与系统化整理,并结合多位一线资深架构师和工程师多年实战经验沉淀,提

**My Coding Family** 965

FreeRTOS仿真器:基于POSIX的FreeRTOS仿真器,具有SDL2图形接口和多个异步通信接口,旨在使FreeRTOS无需嵌入式硬件即可使用类似的过程进行教学

FreeRTOS仿真器:基于POSIX的FreeRTOS仿真器,具有SDL2图形接口和多个异步通信接口,旨在使FreeRTOS无需嵌入式硬件即可使用类似的过程进行教学

嵌入式开发学习路线全解析:从单片机到Linux,避坑指南与实战项目

嵌入式系统作为连接物理世界与数字世界的核心技术,其核心在于通过微控制器或微处理器,在资源受限的硬件平台上实现特定功能。其工作原理涉及硬件抽象层、实时操作系统内核及外设驱动,技术价值体现在对可靠性、实时性和低功耗的极致追求,广泛应用于物联网终端、工业控制、消费电子等领域。本文聚焦嵌入式开发学习路径,针对新手常见误区,系统梳理了从STM32、RT-Thread到嵌入式Linux的技术栈演进,并融入**RTOS**调度与**UART**调试等关键热词,提供清晰的阶段目标与可验证的实战项目,助力开发者构建从底层驱动

weixin_30425949的博客 423

别再手动抓波形了!用Synopsys AXI VIP的Port Monitor自动收集事务数据(附完整连接Scoreboard代码)

当标准Port Monitor无法满足需求时,Synopsys提供的callback机制就像一套精密的手术工具,允许你深入VIP内部进行定制化改造。创建自定义监控点的四步法继承基类// 其他自定义字段...endclass重写目标phase方法// 克隆事务并发送到自定义端口在env中注册callbackcustom_cb);连接自定义端口到分析组件经典应用场景地址phase特定检查数据beat级监控自定义性能统计异常注入检测。

weixin_28716181的博客 397

一文搞懂嵌入式三大处理器:MCU、MPU、AP 核心区别与选型指南

MCU、MPU、AP 到底有什么区别?本文从硬件架构、MMU、操作系统、算力、功耗、场景六大维度彻底讲清。MCU是单芯片整机,跑裸机/RTOS,适合控制;MPU需外挂存储跑Linux,适合轻量智能;AP是高端MPU,跑Android,主打多媒体与AI交互。附快速选型口诀,嵌入式入门必读。

桃蹊、的博客 1137

5G-A无源物联网:深度解析“不插电“智能的底层技术原理

北京移动推出5G-A无源物联网技术,实现电动车标签"零功耗"运行。该技术通过射频能量收集(利用5G-A基站高功率信号转换为电能)和反向散射通信(调制反射信号传输数据)两大核心技术,使设备无需电池即可工作。5G-A相比4G在信号能量密度、通信速率和定位精度等方面显著提升,结合通感一体等创新技术,为无源物联网提供商用基础。该方案已在北京小区试点应用,实现电动车精准定位管理,标签寿命可达5年以上。专家预测,这项技术将大幅降低物联网部署成本,推动"万物智联"时代的到来。

通信行业老兵、物联网从业者,做一个有趣的普通人。 2012

深入浅出DDR系列(二)--DDR工作原理

学习完了DDR的基本组成和硬件结构原理之后,从整体上了解了DDR的特性和静态图,包括原理、管脚图等。那么本章就要从动态的角度来分析时序结构,包括read/write的整个过程到数据返回发生了什么,其中包括以下内容 DDR是如何完成Read、Write 等的操作 DDR的基本命令 DDR的时序参数 DDR的性能分析 1. DRAM基本组成 对于DRAM,其主要由行和列组成,每一个bit中都是由类似右下图的类晶体管的结构组成,对于sdram的数据,可以通过控制column和row就可以访问sd

奇小葩 3万+

嵌入式工程师成长路线图:从单片机到Linux驱动的实战能力构建

嵌入式系统开发是融合硬件与软件的系统工程,其核心在于理解计算机系统的基本原理与硬件交互机制。从微控制器(MCU)的最小系统开始,开发者需要掌握C语言在嵌入式环境下的精要,如位操作、指针与内存地址的直接访问,以及volatile关键字对编译器优化的控制,这是与硬件寄存器对话的基础。理解这些底层原理,是为了构建高效、可靠的嵌入式软件,其技术价值在于实现对物理设备的精准控制与实时响应。随着系统复杂度的提升,实时操作系统(RTOS)引入了多任务并发与任务间通信的机制,而Linux环境则进一步拓展了网络、多进程等高级

weixin_34331102的博客 360

解决fetch_california_housing数据集403错误的三种实用方法

本文针对机器学习初学者加载fetch_california_housing数据集时常见的HTTPError 403: Forbidden错误,提供了三种经过实测的解决方案。详细介绍了手动下载与本地替换、修改源码下载路径以及配置网络访问策略的具体步骤,帮助用户根据自身网络环境和开发场景选择最佳方法,确保数据集顺利加载。

weixin_29074295的博客 550

痞子衡嵌入式半月刊:开发者如何高效追踪技术前沿与构建知识体系

在嵌入式开发领域,持续追踪技术动态是开发者面临的核心挑战。面对快速迭代的芯片架构、RTOS更新及开发工具,信息过载与碎片化问题尤为突出。其技术价值在于通过系统化梳理,帮助开发者从海量信息中筛选出对实际工程有指导意义的内容,如RISC-V生态进展、低功耗设计实践等,从而提升开发效率与代码质量。应用场景涵盖从芯片选型、调试工具链优化到通信协议深度实践等多个环节。痞子衡嵌入式半月刊正是针对这一需求,通过定期精选与评注,为开发者提供经过验证的技术视野拓展与实战问题排查指南,助力构建个人知识图谱。

weixin_30388677的博客 420

Qt C++ HTTP 调用通义千问大模型 X 学术论文润色工具(SCI 格式适配)

请求地址:`https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation`;| Windows | 打包时携带 `Qt6Network.dll`/`libcrypto-1_1-x64.dll` 等依赖;| Linux | 安装 `libssl-dev` 和 `libcurl4-openssl-dev`;| API 调用失败 | 检查 API-KEY 是否有效;

寒寒豆豆软件公司的博客 104

EXCEL VBA从入门到精通 前言

本书通过丰富的实例和案例,帮助读者更好地理解和应用Excel VBA编程,同时也介绍了常用的工具、资源、社区和协作平台等,方便读者进行进一步的学习和开发。我强烈推荐这本书给所有希望学习和掌握Excel VBA编程的读者。多样化的资源和工具介绍:本书介绍了丰富的工具和资源,包括官方文档、在线教程、代码库、论坛、博客等,方便读者进行进一步的学习和开发。详细的讲解和丰富的示例:本书从语法和对象开始讲解,通过丰富的实例和案例,帮助读者更好地理解和应用Excel VBA编程。第一节:什么是VBA 5。

1728

嵌入式开发与互联网编程:技术栈、职业路径与选择指南

在计算机技术领域,嵌入式系统和应用软件开发代表了两种核心的技术路径。嵌入式开发聚焦于软硬件协同,涉及微控制器、实时操作系统和底层驱动,要求开发者具备硬件认知和资源受限环境下的编程能力,其技术价值在于实现对物理世界的精确控制与实时响应,广泛应用于工业自动化、汽车电子和物联网设备。应用软件开发则构建于抽象层之上,关注业务逻辑、分布式架构和高并发处理,依托丰富的框架与中间件生态快速迭代。从技术原理看,嵌入式开发更强调纵向深度与确定性,而应用开发侧重横向广度与扩展性。在职业发展上,嵌入式工程师经验累积效应明显,扎根

weixin_34293246的博客 380

[DevExpress]GridControl拖拽行记录排序

DevExpress的GridControl拖拽行记录排序需求效果代码 需求 现在用户对按钮点击排序不满意了, 希望拖拽排序。 重载GridControl控件,对MouseMove、MouseDown、MouseUp事件修改一下。 效果 代码 using System; using System.Drawing; using System.Windows.Forms; using xiaoid.forms; using System.Data; using System.Collections.Gener

德仔 1561

一些不错的技术网站书签(持续更新)

基础:

fanged的专栏 1767

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

嵌入式开发岗位薪资解析:从Linux驱动到AI部署的职业发展路径

嵌入式系统作为连接物理世界与数字世界的核心技术,其核心原理在于通过专用计算机系统实现对特定设备的控制、监视和管理。从单片机到复杂片上系统,嵌入式技术通过软硬件协同设计,在资源受限环境下实现高可靠、实时性的功能。这一技术价值体现在其作为物联网、智能硬件、汽车电子等领域的底层支撑,是产业数字化转型的基础设施。当前,随着人工智能与边缘计算的发展,嵌入式AI部署、Linux内核优化等方向成为行业热点,相关岗位如嵌入式Linux驱动工程师、AI部署优化工程师的技术壁垒与市场需求持续攀升。本文基于嵌入式开发者的职业发展

weixin_30333885的博客 358

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

上一篇: 编码基本功(四):C++准标准库Boost知识点
下一篇: RTOS精进(一):深入FreeRTOS内核
文可道
博客等级 码龄14年 576粉丝 57原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值