STM32F1+HAL库+FreeTOTS学习8——第一个任务,启动!

news2024/9/19 22:34:11

STM32F1+HAL库+FreeTOTS学习8——第一个任务,启动!

  • 开启任务调度器
    • 1. 函数 vTaskStartScheduler()
    • 2. 函数xPortStartScheduler()
  • 启动第一个任务
    • 1. 函数 prvStartFirstTask()
    • 2. 函数 vPortSVCHandler()

上一期我们学习了列表和列表项的相关内容和API函数实验,接下来我们来学习FreeRTOS是如何启动第一个任务开启任务调度的,以及期间发生了什么

开启任务调度器

1. 函数 vTaskStartScheduler()

void freertos_demo(void)
{
	taskENTER_CRITICAL();           /* 进入临界区,关闭中断,此时停止任务调度*/
    /* 创建任务1 */
    xTaskCreate((TaskFunction_t )task1,
                (const char*    )"task1",
                (uint16_t       )TASK1_STK_SIZE,
                (void*          )NULL,
                (UBaseType_t    )TASK1_PRIO,
                (TaskHandle_t*  )&Task1Task_Handler);
    /* 创建任务2 */
    xTaskCreate((TaskFunction_t )task2,
                (const char*    )"task2",
                (uint16_t       )TASK2_STK_SIZE,
                (void*          )NULL,
                (UBaseType_t    )TASK2_PRIO,
                (TaskHandle_t*  )&Task2Task_Handler);
    taskEXIT_CRITICAL();            /* 退出临界区,重新开启中断,开启任务调度 */

/*这里是开启任务调度*/
    vTaskStartScheduler();		//开启任务调度
}

在前面,我们已经使用过函数 vTaskStartScheduler(),作用就是开启FreeRTOS的任务调度,下面我们来具体的看一下内部实现:

/*开启任务调度器函数*/
void vTaskStartScheduler( void )
{
    BaseType_t xReturn;
	// 1. 创建空闲函数
    /* 如果使用的是静态内存管理,则使用静态的方式创建空闲函数 */
    #if ( configSUPPORT_STATIC_ALLOCATION == 1 )
        {
            StaticTask_t * pxIdleTaskTCBBuffer = NULL;
            StackType_t * pxIdleTaskStackBuffer = NULL;
            uint32_t ulIdleTaskStackSize;

            /* 空闲任务的创建是使用用户提供的RAM,获取到相应的地址后才会进行创建。*/
            vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, &pxIdleTaskStackBuffer, &ulIdleTaskStackSize );
            xIdleTaskHandle = xTaskCreateStatic( prvIdleTask,
                                                 configIDLE_TASK_NAME,
                                                 ulIdleTaskStackSize,
                                                 ( void * ) NULL,       /*强制类型转换对于所有编译器都是多余的. */
                                                 portPRIVILEGE_BIT,     /* 实际上这里应该是是 ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), 但是 tskIDLE_PRIORITY 为0. */
                                                 pxIdleTaskStackBuffer,
                                                 pxIdleTaskTCBBuffer ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */
			/*创建成功*/
            if( xIdleTaskHandle != NULL )
            {
                xReturn = pdPASS;
            }
            /*创建不成功*/
            else
            {
                xReturn = pdFAIL;
            }
        }
        /* 否则使用动态的方式,创建空闲函数 */
    #else /* if ( configSUPPORT_STATIC_ALLOCATION == 1 ) */
        {
            /* 空闲函数动态分配RAM空间. */
            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 /* 结束空闲任务创建*/
   // 2. 创建软件定时器任务 
	/*如果使能软件定时器,则需要创建定时器服务任务*/
    #if ( configUSE_TIMERS == 1 )
        {
            if( xReturn == pdPASS )
            {	
            	/*函数内部会完成定时器服务任务的创建,创建方式参照空闲任务*/
                xReturn = xTimerCreateTimerTask();
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
    #endif /* 结束软件定时器配置*/

    if( xReturn == pdPASS )
    {
        /* 此函数用于添加一些附加初始化,不用理会*/
        #ifdef FREERTOS_TASKS_C_ADDITIONS_INIT
            {
                freertos_tasks_c_additions_init();
            }
        #endif
        
	// 3、关闭中断,防止调度器开启之前或过程中,受中断干扰,会在运行第一个任务时打开中断
        portDISABLE_INTERRUPTS();
        
		/* Newlib 相关的一些东西,这里我也看不懂 */
        #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 */
	
	// 4. 初始化一些全局变量
        xNextTaskUnblockTime = portMAX_DELAY; 					// 下一个距离取消任务阻塞的时间,设置为最大,因为此时还没有运行任务
        														// 避免阻塞超时导致任务切换
        xSchedulerRunning = pdTRUE;				//设置任务调度标志为开启
        xTickCount = ( TickType_t ) configINITIAL_TICK_COUNT;	//系统节拍计数器,初始化为0

    // 5. 为任务运行时间统计功能初始化功能时基定时器,是否使用该功能可在 FreeRTOSConfig.h 文件中进行配置
        portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();
		/* 调试使用 */
        traceTASK_SWITCHED_IN();

        /* xPortStartScheduler() 设置用于系统时钟节拍的硬件定时器(SysTick) 会在这个函数中进入第一个任务,并开始任务调度
 		* 任务调度开启后,便不会再返回 */
        if( xPortStartScheduler() != pdFALSE )
        {
            /* 代码不会运行到这里 */
        }
        else
        {
            /* 当调用关闭任务调度器函数 xTaskEndScheduler()时会运行到这里. */
        }
    }
    else
    {
        /*动态方式创建空闲任务和定时器服务任务时,堆栈空间不足,会导致无法创建,进入这里 */
        configASSERT( xReturn != errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY );
    }

    /* 防止编译器警告,不用管*/
    ( void ) xIdleTaskHandle;

    /* 调试使用,不用管*/
    ( void ) uxTopUsedPriority;
}

上述代码来自FreeRTOS官方提供源码,为了跟方便看懂,去除了大部分的英文注释,该为中文注释,更适合中国宝宝体质!!!

结合上述的注释,我们可以大概明白 vTaskStartScheduler() 完成了如下内容:

  1. 创建空闲任务
  2. 创建软件定时器任务
  3. 关闭中断(确切的说是关闭FreeRTOS能够控制的中断),防止调度器开启之前或过程中,受中断干扰,会在运行第一个任务时打开中断
  4. 初始化全局变量,并将任务调度器的运行标志设置为已运行
  5. 初始化任务运行时间统计功能的时基定时器,任务运行时间统计功能需要一个硬件定时器提供高精度的计数,这个硬件定时器就在这里进行配置,如果配置不启用任务运行时间统计功能的,就无需进行这项硬件定时器的配置。
  6. 最后就是调用函数 xPortStartScheduler(),由于里面的内容比较多,所以我们下面再起一部分讲解。

需要注意以下几点:

  1. 如果使用的是静态创建任务的方式,则空闲任务和定时器任务需要用户自行定义任务堆栈和TCB(任务控制块)
  2. 软件定时器人的优先级为31(最高),空闲任务的优先级为0(最低)

2. 函数xPortStartScheduler()

函数 xPortStartScheduler()完成启动任务调度器中与硬件架构相关的配置部分,以及启动第一个任务,具体的代码如下所示:

BaseType_t xPortStartScheduler( void )
{
 /*1. 检测用户在 FreeRTOSConfig.h 文件中对中断相关部分的配置是否有误*/
    #if ( configASSERT_DEFINED == 1 )
        {
            volatile uint32_t ulOriginalPriority;
            volatile uint8_t * const pucFirstUserPriorityRegister = ( uint8_t * ) ( portNVIC_IP_REGISTERS_OFFSET_16 + portFIRST_USER_INTERRUPT_NUMBER );
            volatile uint8_t ucMaxPriorityValue;

            /* 确定可以从哪个最高优先级调用ISR安全的FreeRTOS API函数 
            ISR安全函数是以“FromISR”结尾的函数。FreeRTOS维护了单独的线程和ISR API函数,以确保中断进入尽可能快速和简单。
            保存即将被覆盖的中断优先级值。 */
            ulOriginalPriority = *pucFirstUserPriorityRegister;

            /* 确定可用的优先级位数。首先写入所有可能的位。. */
            *pucFirstUserPriorityRegister = portMAX_8_BIT_VALUE;

            /* Read the value back to see how many bits stuck. */
            ucMaxPriorityValue = *pucFirstUserPriorityRegister;

            /* The kernel interrupt priority should be set to the lowest
             * priority. */
            configASSERT( ucMaxPriorityValue == ( configKERNEL_INTERRUPT_PRIORITY & ucMaxPriorityValue ) );

            /* Use the same mask on the maximum system call priority. */
            ucMaxSysCallPriority = configMAX_SYSCALL_INTERRUPT_PRIORITY & ucMaxPriorityValue;

            /* Calculate the maximum acceptable priority group value for the number
             * of bits read back. */
            ulMaxPRIGROUPValue = portMAX_PRIGROUP_BITS;

            while( ( ucMaxPriorityValue & portTOP_BIT_OF_BYTE ) == portTOP_BIT_OF_BYTE )
            {
                ulMaxPRIGROUPValue--;
                ucMaxPriorityValue <<= ( uint8_t ) 0x01;
            }

            #ifdef __NVIC_PRIO_BITS
                {
                    /* Check the CMSIS configuration that defines the number of
                     * priority bits matches the number of priority bits actually queried
                     * from the hardware. */
                    configASSERT( ( portMAX_PRIGROUP_BITS - ulMaxPRIGROUPValue ) == __NVIC_PRIO_BITS );
                }
            #endif

            #ifdef configPRIO_BITS
                {
                    /* Check the FreeRTOS configuration that defines the number of
                     * priority bits matches the number of priority bits actually queried
                     * from the hardware. */
                    configASSERT( ( portMAX_PRIGROUP_BITS - ulMaxPRIGROUPValue ) == configPRIO_BITS );
                }
            #endif

            /* Shift the priority group value back to its position within the AIRCR
             * register. */
            ulMaxPRIGROUPValue <<= portPRIGROUP_SHIFT;
            ulMaxPRIGROUPValue &= portPRIORITY_GROUP_MASK;

            /* Restore the clobbered interrupt priority register to its original
             * value. */
            *pucFirstUserPriorityRegister = ulOriginalPriority;
        }
    #endif /* configASSERT_DEFINED */
    
/* 2. 设置 PendSV 和 SysTick 的中断优先级为最低优先级  */

    portNVIC_SHPR3_REG |= portNVIC_PENDSV_PRI;

    portNVIC_SHPR3_REG |= portNVIC_SYSTICK_PRI;

 /* 3. 开启并配置SYSTick,设置系统节拍、时钟源、然后开启SysTick和中断 */
    vPortSetupTimerInterrupt();

 /* 4. 初始化临界区嵌套计数器为 0 */
    uxCriticalNesting = 0;

/* 5. 开启FPU,但是在Crotex_M3的内核里面,没有FPU,所以这里没有相关代码*/


/* 6. 开启第一个任务:第一个任务,启动! */
    prvStartFirstTask();

    /* Should not get here! */
    return 0;
}

上述代码来自FreeRTOS官方提供源码,为了跟方便看懂,去除了大部分的英文注释,该为中文注释,更适合中国宝宝体质!!!

结合上述的注释,我们可以大概明白 xPortStartScheduler() 完成了如下内容:

  1. 在启用断言的情况下,函数 xPortStartScheduler()会检测用户在 FreeRTOSConfig.h 文件中对中断的相关配置是否有误
  2. 配置 PendSV 和 SysTick 的中断优先级为最低优先级
  3. 调用函数 vPortSetupTimerInterrupt()配置 SysTick,函数 vPortSetupTimerInterrupt()首先会将 SysTick 当 前 计 数 值 清 空 , 并 根 据 FreeRTOSConfig.h 文件中配置的configSYSTICK_CLOCK_HZ(SysTick 时钟源频率)和 configTICK_RATE_HZ(系统时钟节拍频率)计算并设置 SysTick 的重装载值,然后启动 SysTick 计数和中断。
  4. 初始化临界区嵌套计数器,将其置为0
  5. 调用函数 prvEnableVFP()使能 FPU,因为 ARM Cortex-M3 内核 MCU 无 FPU,此函数仅在 ARM Cortex-M4/M7 内核 MCU 平台上被调用,执行改函数后 FPU 被开启。(这里因为我们使用的是STM32F1系列,使用的Crotex_M3内核,所以没有FPU)
  6. 调用prvStartFirstTask() 函数,第一个任务启动!

启动第一个任务

1. 函数 prvStartFirstTask()

prvStartFirstTask() 函数的调用,标志着我们正式迈步FreeRTOS的领域,该函数用于初始化启动第一个任务的环境:重新设置MSP指针和使能全局中断,最后使用SVC指令,触发SVC中断。

我们来看一下是如何实现的:

__asm void prvStartFirstTask( void )
{
 /* 8 字节对齐 */
 PRESERVE8
 
 ldr r0, =0xE000ED08 /* 0xE000ED08 为 VTOR 地址 */
 ldr r0, [ r0 ] /* 获取 VTOR 的值 */
 ldr r0, [ r0 ] /* 获取 MSP 的初始值 */
 
 /* 初始化 MSP */
 msr msp, r0
 
 /* 使能全局中断 */
 cpsie i
 cpsie f
 dsb
 isb
 
 /* 调用 SVC 启动第一个任务 */
 svc 0
 nop
 nop
}

咋一看,这一段代码有点让人看不懂,因为是汇编,但是请你放心,它一点都不简单,我们这里先来补充几个重要的知识:

  1. 0xE000ED08 是什么东西?

事实上,0xE000ED08 是向量表偏移量寄存器的地址,“ ldr r0, =0xE000ED08 ” 这一步的目的就是将向量表偏移量寄存器的地址读取到" r0 "寄存器,紧接着 “ ldr r0, [ r0 ] ” 则是获取向量表偏移量寄存器中所指向的内容,也就是我们的中断向量表,但是我们知道,中断向量表,里面存放着各个中断服务函数的地址,“ ldr r0, [ r0 ] ” 就是获取第一个中断服务函数的地址 ,查阅start_stm32xxxxxx.s文件,就可以知道,这里的目的就是获取MSP指针的初始值。

在这里插入图片描述
2. MSP指针是干嘛的?

在这里插入图片描述
Cortex‐M3 处理器拥有 R0‐R15 的寄存器组。其中 R13 作为堆栈指针 SP。SP 有两个,但在同一时刻只能有一个可以看到,这也就是所谓的“banked”寄存器。

程序在运行过程中需要一定的栈空间来保存局部变量等一些信息。对应任务的当有信息保存到栈中时,MCU 会自动更新 SP 指针,ARM Cortex-M 内核提供了两个栈空间,

  • 主堆栈指针(MSP):复位后默认使用的堆栈指针,用于操作系统内核以及异常处理例程(包
    括中断服务例程)
  • 进程堆栈指针(PSP):由用户的应用程序代码使用。

看到这里,相信你就一目了然了,复位后默认使用的是MSP指针,所以我们先需要查找中断向量表获取MSP指针初始值,将其赋值给msp寄存器,让MSP指针回到原点,紧接着,开启全局中断,进入SVC服务函数,由操作系统进行接管。
在这里插入图片描述

在这里插入图片描述

我们来总结一下prvStartFirstTask() 里面到底干了什么:

  1. 首先是使用了 PRESERVE8,进行 8 字节对齐,这是因为,栈在任何时候都是需要 4 字节对齐的,而在调用入口得 8 字节对齐,在进行 C 编程的时候,编译器会自动完成的对齐的操作,而对于汇编,就需要开发者手动进行对齐。
  2. 了获得 MSP 指针的初始值
  3. 对MSP指针进行初始化,这个操作相当于丢弃了程序之前保存在栈中的数据,因为FreeRTOS从开启任务调度器到启动第一个任务都是不会返回的,是一条不归路,因此将栈中的数据丢弃,也不会有影响。
  4. 使能全局中断,因为之前关闭了FreeRTOS管理的中断
  5. 最后使用 SVC 指令,并传入系统调用号 0,触发 SVC 中断。

2. 函数 vPortSVCHandler()

__asm void vPortSVCHandler( void )
{
 /* 8 字节对齐 */
 PRESERVE8
 
 /* 获取任务栈地址 */
 ldr r3, = pxCurrentTCB /* r3 指向优先级最高的就绪态任务的任务控制块 */
 ldr r1, [ r3 ] /* r1 为任务控制块地址 */
 ldr r0, [ r1 ] /* r0 为任务控制块的第一个元素(栈顶) */
 
 /* 模拟出栈,并设置 PSP */
 ldmia r0 !, { r4 - r11 } /* 任务栈r0地址从低到高,将r0存储地址里面的内容手动加载到 CPU寄存器r4到r11 */
 msr psp, r0 /* 设置 PSP 为任务栈指针 */
 isb
 
 /* 使能所有中断 */
 mov r0, # 0
 msr basepri,
 
 /* 使用 PSP 指针,并跳转到任务函数 */
 orr r14, # 0xd
 bx r14
}

同样的我们来分析一些,这个函数里做了什么:

  1. 同样进行了字节对齐,因为这里是汇编的世界!
  2. 获取任务栈的地址:pxCurrentTCB 是一个全局变量,用于指向系统中优先级最高的就绪态任务的任务控制块,之前我们创建了两个任务,一个空闲任务,优先级为0,一个软件定时器服务函数,优先级为31(这部没有包含用户自己创建的任务), 这里pxCurrentTCB 是软件定时器任务的任务控制块。那么对应的就是获取软件定时器任务的栈顶地址
  3. 将软件定时器任务栈的内容出栈到CPU寄存器组内,如何设置PSP指针。
  4. 使能所有中断
  5. 对CPU寄存器里面的r14(连接寄存器)或上0x0d ,使得r14的值为 0xFFFFFFFD

R14 是链接寄存器 LR,在 ISR 中(此刻我们在 SVC 的 ISR 中),它记录了异常返回值 EXC_RETURN,而EXC_RETURN 只有 6 个合法的值(M4、M7),如下表所示:

描述使用浮点单元(M4、M7的内核)未使用浮点单元(M3的内核)
中断返回后进入Hamdler模式,并使用MSP0xFFFFFFE10xFFFFFFF1
中断返回后进入线程模式,并使用 MSP0xFFFFFFE90xFFFFFFF9
中断返回后进入线程模式,并使用 PSP0xFFFFFFED0xFFFFFFFD

经过以上步骤,最终进入线程模式,使用PSP指针,开始运行第一个任务,软件定时器任务,至此第一个任务正式启动!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2103941.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

python-小理帮老师改错

题目描述 老师给小理发了一封电子邮件&#xff0c;任务如下。 写一个程序&#xff0c;给你 n 个数&#xff0c;输出 X。 Xnum1^p1​​num2^p2​​⋯numn^pn​​ num1​&#xff0c;num2​&#xff0c;⋯⋯&#xff0c;numn​ 都是整数&#xff0c;p1​&#xff0c;p2​&#xf…

十二、集合

文章目录 一、集合的理解和好处二、集合框架体系图三、Collection接口 特点 方法3.1 Collection基本介绍3.2 Collection接口常用方法3.3 Collection接口遍历元素3.3.1 方式1-使用Iterator(迭代器)3.3.2 方式2-增强for循环 四、Collection接口的子接口&#xff1a;List 实现类&a…

【C++ 第二十章】使用 shared_ptr 会出现严重 循环引用 问题?

众所周知&#xff0c;智能指针 shared_ptr 能够允许拷贝行为&#xff0c;是因为其内部使用 引用计数的 方式&#xff0c;使得多个智能指针对象共享同一个资源&#xff08;如果不了解 shared_ptr &#xff0c;可以先自己了解学习一下&#xff09; 而 引用计数 却可能引起 循环引…

.NetCore+vue3上传图片 Multipart body length limit 16384 exceeded.

实现目标。点击图片上传头像 效果图 前端部分图片上传关键代码 <div class"avatar-wrap"><el-imagestyle"width: 154px; height: 154px":src"form.headPic":fit"fit"/></div><div class"upload-box"…

【数据结构】二叉树之入门,树与二叉树的相关介绍

文章目录 1. 树1.1 树的概念与结构1.2 树相关术语1.3 树的表示1.4 树形结构实际运用场景 2. 二叉树2.1 二叉树的概念与结构2.2 特殊的二叉树2.2.1 满二叉树2.2.2 完全二叉树 3. 结语 1. 树 1.1 树的概念与结构 树是一种非线性的数据结构&#xff0c;它是由 n ( n > 0 ) n…

零风险!零付费!我把 AI 接入微信群,爸妈玩嗨了~附教程(下):大模型 API 接入

上篇&#xff0c;带大家玩转高德开放平台 API&#xff0c;为大模型提供和本地生活相关的可靠信息。 本文将带大家&#xff0c;结合免费的大模型API&#xff0c;基于微信机器人开发框架&#xff0c;打造完整的 Bot。 友情提醒&#xff1a;注册一个小号使用&#xff0c;严禁用于…

数组和指针 笔试题(3)

目录 11.笔试题11 12.笔试题12 13.笔试题13 14.笔试题14 11.笔试题11 //笔试题11(难&#xff09;int a[5][5];//创建25个int类//p[]1[]2[]3[]4[]5 []6[]7[]8[]9[]10 []11[]12[]13[]14[]15 []16&#xff08;p[4]&#xff09;[][]&#xff08;p[4][2]&#xff09;[][] [][]&a…

如何使用“在页面上查找?

如何使用“在页面上查找&#xff1f; 按 CTRL F &#xff0c;然后输入要搜索的单词或短语。

如何在Pyqt中渲染使用svggraphicsItem的SVG字形?

在使用 PyQt 构建应用程序时&#xff0c;有时需要在图形用户界面中渲染 SVG&#xff08;可缩放矢量图形&#xff09;文件&#xff0c;特别是当你需要显示图标或自定义字体时。QGraphicsSvgItem 是 PyQt 提供的一个类&#xff0c;用于在 QGraphicsView 或 QGraphicsScene 中渲染…

Python数据分析实战,兰州市二手房市场深度分析

作为购房者&#xff0c;除了关注地段与价格外&#xff0c;房屋的总价与面积的关系&#xff0c;以及房屋朝向的选择&#xff0c;同样是决策过程中的关键因素。那么&#xff0c;兰州市的二手房市场中&#xff0c;房屋总价与面积之间究竟存在怎样的关系&#xff1f;各个朝向的房源…

【人工智能】AI时代是失业的噩梦,还是效率的提升?

我们都知道&#xff0c;人工智能&#xff08;AI&#xff09;正以前所未有的速度渗透到我们生活的方方面面。有人说&#xff0c;AI的发展将导致大部分人失业。然而&#xff0c;另一部分人则认为&#xff0c;AI是提升工作效率的利器。那么&#xff0c;真相究竟是什么呢&#xff1…

怎样在公司将手机屏幕(远程)投屏到家里的大电视上?

我不住家里&#xff0c;前几次回去都会替老爸老妈清理手机。这两个星期没空回去&#xff0c;老爸吐槽手机用几天就又卡了&#xff0c;其实就是清理一些手机缓存的问题。 我说我远程控制他的手机&#xff0c;给他清理一下。他一听“控制”就不喜欢&#xff0c;说我大了&#xf…

年薪80万,成功入职字节跳动!

前言&#xff1a; 最近AI相关就业岗位爆了。。。无论是**华为、**百度、阿里、字节等互联网巨头&#xff0c;还是中小型的科技公司都在高薪挖 AI 人才。 上周找 字节的面试官朋友&#xff08;职级3-1&#xff09;要来了几套高质量AI****内部资料和2024吴恩达机器学习资料。既…

Linux_kernel汇编指令05

一、温故知新 1、跳转指令 b{cond} <target_label> 相当于C语言中的goto <target_label>是跳转地址&#xff0c;32M {cond}是条件码&#xff0c;先决条件 根据CPSR寄存器的NZCV位来决定是跳转还是不跳转 bl l&#xff1a;带链接状态&#xff0c;将PC寄存器的值保存…

浅谈人工智能之VSCode:使用插件与ollama本地大模型交互

浅谈人工智能之VSCode&#xff1a;使用插件与ollama本地大模型交互 我们在之前的文档中已经说明如何使用Ollama部署本地大模型&#xff0c;这里就赘述&#xff0c;具体如何部署可参考文档&#xff1a; 浅谈人工智能之Windows&#xff1a;基于ollama进行本地化大模型部署 准备…

CDKF(中心差分卡尔曼滤波)的MATLAB代码(附下载链接)

文章目录 CDKF介绍运行结果代码下载链接部分代码如下 CDKF介绍 另有关于EKF和CDKF的对比程序&#xff1a;EKFCDKF两个滤波的MATLAB程序&#xff0c;估计三轴位置&#xff0c;带中文注释——https://blog.csdn.net/callmeup/article/details/136610153。此文章所在的专栏还有更…

传统CV算法——基于Opencv的图像绘制

直线绘制 参数解析&#xff1a; &#xff08;图像矩阵&#xff0c;直线起始坐标&#xff0c; 直线终止坐标、颜色、线条厚度&#xff09; cv2.line()是OpenCV中用于绘制直线的函数。 参数说明&#xff1a;img&#xff1a;要绘制直线的图像矩阵。(100,30)&#xff1a;直线的起…

移动端支持风格设计,更个性化的体验

08/28 主要更新模块概览 风格设计 风格配置 列表模式 跳转按钮 01 表单管理 1.1 【表单设计】-新增移动端风格设计 说明&#xff1a; 在此之前&#xff0c;移动端风格一直继承自 PC 端风格&#xff0c;无法进行自定义。…

Ollama拉起本地模型以及rag系统部署。

什么是 Ollama &#xff1f; Ollama 是一个简明易用的本地大模型运行框架。能在本地启动并运行 Llama、qwen、Gemma 及其他大语言模型&#xff0c;没有GPU资源照样可以拉起模型&#xff0c;和LocalAI 比较类似&#xff0c;但是加载模型更容易。 1.安装 安装后运行&#xff0c…

带你0到1之QT编程:四、打地基QHash的高效用法

此为QT编程的第四谈&#xff01;关注我&#xff0c;带你快速学习QT编程的学习路线&#xff01; 每一篇的技术点都是很很重要&#xff01;很重要&#xff01;很重要&#xff01;但不冗余&#xff01; 我们通常采取总-分-总和生活化的讲解方式来阐述一个知识点&#xff01; 码…