freertos内核 任务定义与切换 原理分析

freertos内核 任务定义与切换 原理分析

    • 主程序
    • 任务控制块
    • 任务创建函数
    • 任务栈初始化
    • 就绪列表
    • 调度器
    • 总结任务切换

主程序

这个程序目的就是,使用freertos让两个任务不断切换。看两个任务中变量的变化情况(波形)。

下面这个图是任务函数里面delay(100)的结果。

在这里插入图片描述

下面这个图是任务函数里面delay(2)的结果.

在这里插入图片描述

多任务系统,CPU好像在同时做两件事,也就是说,最好预期就是,两变量的波形应该是完全相同的。

这个实验,delay减少了,他们两变量波形中间间距仍然没有减少,说明这个实验只是一个入门,远没达到RTOS的效能。

这个实验特点,就是具有任务主动切换能力,这是如何实现的呢,值得研究。

下面两个图,直观显示了程序的主动切换。观察CurrentTCB这个参数,可以发现它是一直变动的。

在这里插入图片描述

它究竟为什么变动呢,采用逐步debug的方式,可找到,是因为调用了一个SwitchContext函数。

在这里插入图片描述

那么先看一下main里面都有啥:

从下面可知,这里面有任务栈、任务控制块、有任务函数、还得创建任务。有就绪列表、有调度器。

任务栈:

#define TASK1_STACK_SIZE                    20
StackType_t Task1Stack[TASK1_STACK_SIZE];
#define TASK2_STACK_SIZE                    20
StackType_t Task2Stack[TASK2_STACK_SIZE];

任务函数(任务入口):

void Task1_Entry( void *p_arg )
{for( ;; ){flag1 = 1;delay( 100 );		flag1 = 0;delay( 100 );/* 任务切换,这里是手动切换 */taskYIELD();}
}
void Task2_Entry( void *p_arg )
{for( ;; ){flag2 = 1;delay( 100 );		flag2 = 0;delay( 100 );/* 任务切换,这里是手动切换 */taskYIELD();}
}

任务控制块:

TCB_t Task1TCB;
TCB_t Task2TCB;

就绪列表初始化:

prvInitialiseTaskLists();

创建任务:

typedef void * TaskHandle_t;
TaskHandle_t Task1_Handle;
Task1_Handle = xTaskCreateStatic( (TaskFunction_t)Task1_Entry,   /* 任务入口 */(char *)"Task1",               /* 任务名称,字符串形式 */(uint32_t)TASK1_STACK_SIZE ,   /* 任务栈大小,单位为字 */(void *) NULL,                 /* 任务形参 */(StackType_t *)Task1Stack,     /* 任务栈起始地址 */(TCB_t *)&Task1TCB );          /* 任务控制块 */

任务添加到就绪列表:

vListInsertEnd( &( pxReadyTasksLists[1] ), &( ((TCB_t *)(&Task1TCB))->xStateListItem ) );

启动调度器:

vTaskStartScheduler(); 

任务控制块

多任务系统,任务执行由系统调度。任务的信息很多,于是就用任务控制块表示任务,这样方便系统调度。

任务控制块类型,包含了任务的所有信息,比如栈顶指针pxTopOfStack、任务节点xStateListItem、任务栈起始地址pxStack、任务名称pcTaskName。

typedef struct tskTaskControlBlock
{volatile StackType_t    *pxTopOfStack;    /* 栈顶 */ListItem_t			    xStateListItem;   /* 任务节点 */StackType_t             *pxStack;         /* 任务栈起始地址 *//* 任务名称,字符串形式 */char                    pcTaskName[ configMAX_TASK_NAME_LEN ];  
} tskTCB;
typedef tskTCB TCB_t;

任务创建函数

main里面调用xTaskCreateStatic创建了任务,观察可知这个函数其实改变的是Task1TCB任务控制块,这个任务控制块诞生之初,就没有进行过初始化。调用任务创建函数目的就是初始化任务控制块。

Task1_Handle = xTaskCreateStatic( (TaskFunction_t)Task1_Entry,   /* 任务入口 */(char *)"Task1",               /* 任务名称,字符串形式 */(uint32_t)TASK1_STACK_SIZE ,   /* 任务栈大小,单位为字 */(void *) NULL,                 /* 任务形参 */(StackType_t *)Task1Stack,     /* 任务栈起始地址 */(TCB_t *)&Task1TCB );          /* 任务控制块 */

直观表述这个函数内部:

在这里插入图片描述

任务控制块里面的任务节点:下面代码是初始化过程,其实就是进行链表的普通节点初始化。

    /* 初始化TCB中的xStateListItem节点 */vListInitialiseItem( &( pxNewTCB->xStateListItem ) );/* 设置xStateListItem节点的拥有者 */listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );

这个任务入口体现在哪呢,其实是体现在任务栈里面。在main.c里面初始化任务栈,仅仅开辟了一段内存空间,里面放什么东西都没有具体说明。调用任务创建函数之后,其实也一并初始化了任务栈(往里面放东西),任务入口就放到这个栈里了。任务栈也初始化完的时候,任务控制块才算圆满的初始化完了。

所以任务创建函数里面还得调用任务栈初始化函数。

任务栈初始化

初始化任务栈的函数代码在下面:

StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters )
{/* 异常发生时,自动加载到CPU寄存器的内容 */pxTopOfStack--;*pxTopOfStack = portINITIAL_XPSR;	                                    /* xPSR的bit24必须置1 */pxTopOfStack--;*pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK;	/* PC,即任务入口函数 */pxTopOfStack--;*pxTopOfStack = ( StackType_t ) prvTaskExitError;	                    /* LR,函数返回地址 */pxTopOfStack -= 5;	/* R12, R3, R2 and R1 默认初始化为0 */*pxTopOfStack = ( StackType_t ) pvParameters;	                        /* R0,任务形参 *//* 异常发生时,手动加载到CPU寄存器的内容 */    pxTopOfStack -= 8;	/* R11, R10, R9, R8, R7, R6, R5 and R4默认初始化为0 *//* 返回栈顶指针,此时pxTopOfStack指向空闲栈 */return pxTopOfStack;
}
static void prvTaskExitError( void )
{/* 函数停止在这里 */for(;;);
}

栈顶指针就是pxTopOfStack。pxStack是一个指针指向任务栈起始地址,ulStackDepth是任务栈大小。下面是获取栈顶指针的代码。栈是后进先出,先进去的后出。其实也就是,先进栈的被压到最底下去了(下标最靠后)。所以,如果栈里面什么都没有,栈顶的位置得在最后面(也就是地址最高的哪个位置)。

/* 获取栈顶地址 */
pxTopOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );

下面两个图表述的都是一个意思,只不过右边的可能好懂点(先进栈的被压到最底下去了)。

在这里插入图片描述

初始化任务栈的函数运行完,栈就发生了变化,里面有内容了,如下图所示。可以看到任务入口地址存进去了,任务形参也存进去了。

#define portINITIAL_XPSR			        ( 0x01000000 )

在这里插入图片描述

至此,通过任务创建函数,已经圆满的初始好了任务控制块,同时填充了任务栈,任务栈联系了任务入口地址(任务的函数实体)。任务控制块成员变量里面有栈顶指针,联系了任务栈。那么,任务的栈、任务的函数实体、任务的控制块通过任务创建函数就联系起来了。

这里面插一句:任务栈一个元素占四个字节!上面那个图,如果r0地址是0x40,那么pxTopOfStack地址就是0x20(因为0x40-0x20=32),32÷4=8,也就是说八个元素。

#define portSTACK_TYPE	uint32_t
typedef portSTACK_TYPE StackType_t;
StackType_t Task1Stack[TASK1_STACK_SIZE];uint32_t
u:代表 unsigned 即无符号,即定义的变量不能为负数;
int:代表类型为 int 整形;
32:代表四个字节,即为 int 类型;
_t:代表用 typedef 定义的;
整体代表:用 typedef 定义的无符号 int 型宏定义;
位(bit):每一位只有两种状态01。计算机能表示的最小数据单位。
字节(Byte)8位二进制数为一个字节。计算机基本存储单元内容用字节表示。

就绪列表

下面是main里面就绪列表的定义、初始化,添加任务到就绪列表。

首先绪列表的定义,简而言之,就绪列表是一个List_t类型的数组(其实数组中每个元素就相当于根节点),数组下标对应任务的优先级。

#define configMAX_PRIORITIES		            
/* 任务就绪列表 */
List_t pxReadyTasksLists[ configMAX_PRIORITIES ];
/* 初始化与任务相关的列表,如就绪列表 */
prvInitialiseTaskLists();
/* 将任务添加到就绪列表 */                                 
vListInsertEnd( &( pxReadyTasksLists[1] ), &( ((TCB_t *)(&Task1TCB))->xStateListItem ) );
/* 将任务添加到就绪列表 */                                 
vListInsertEnd( &( pxReadyTasksLists[2] ), &( ((TCB_t *)(&Task2TCB))->xStateListItem ) );

就绪列表初始化函数如下,简而言之,就是对List_t类型的数组里面每个元素进行初始化(根节点初始化)。

/* 初始化任务相关的列表 */
void prvInitialiseTaskLists( void )
{UBaseType_t uxPriority;for( uxPriority = ( UBaseType_t ) 0U; uxPriority < ( UBaseType_t ) configMAX_PRIORITIES; uxPriority++ ){vListInitialise( &( pxReadyTasksLists[ uxPriority ] ) );}
}

添加任务到就绪列表的函数是vListInsertEnd,这个在之前双向循环链表说过,其实就是把普通节点插到根节点后。

就绪列表在不同任务之间建立一种联系,图示如下。

在这里插入图片描述

调度器

启动调度器,是用了一个SVC中断。

从下面代码可以看出,pxCurrentTCB指向的是Task1TCB(任务控制块)的地址。

typedef struct tskTaskControlBlock
{volatile StackType_t    *pxTopOfStack;    /* 栈顶 */ListItem_t			    xStateListItem;   /* 任务节点 */StackType_t             *pxStack;         /* 任务栈起始地址 *//* 任务名称,字符串形式 */char                    pcTaskName[ configMAX_TASK_NAME_LEN ];  
} tskTCB;
typedef tskTCB TCB_t;//void vTaskStartScheduler( void )函数里
pxCurrentTCB = &Task1TCB;

下面这个svc的中断函数,里面第一步就是把任务栈的栈顶指针给r0寄存器。

可以认为:r0=pxTopOfStack(任务栈的栈顶指针的地址)。

//__asm void vPortSVCHandler( void )函数里
ldr	r3, =pxCurrentTCB	//加载pxCurrentTCB的地址到r3
ldr r1, [r3]			//把r3指向的内容给r1,内容就是Task1TCB的地址
ldr r0, [r1]  //把r1指向的内容给r0,内容就是Task1TCB的地址里面的第一个内容,也就是pxTopOfStack

接下来:以r0(任务栈的栈顶指针的地址)为基地址,将任务栈里面向上增长的8字节内容加载到CPU寄存器r4-r11。

ldmia r0!, {r4-r11}

然后将r0存到psp里。

msr psp, r0

下面这个代码,目的是改EXC_RETURN值为0xFFFFFFD,这样的话中断返回就进入线程模式,使用线程堆栈(sp=psp)。

orr r14, #0xd

看下面这个图,异常返回时,出栈用的是PSP指针。PSP指针把任务栈里面剩余的内容(没有读到寄存器里的内容)全部给弄出去(自动将栈中的剩余内容加载到cpu寄存器)。那么任务函数的地址就给到了PC,程序就跳到任务函数的地方继续运行。

在这里插入图片描述

图1如下:注意,动的是psp,pxTopOfStack是不动的。

在这里插入图片描述

下面是实验证明上面关于psp指针运动描述的正确性:

r0一开始存的就是pxTopOfStack的值(任务栈的栈顶指针的地址)

在这里插入图片描述

接下来把运动过的r0给psp,此时的psp位置就在图1psp2那个地方。

在这里插入图片描述

下图这个psp地址仍然是0x40。

在这里插入图片描述

程序运行完bx r14,就跑到任务函数里面了,此时的psp=0x60,位置就在图1的psp3。

在这里插入图片描述

现在程序跑到任务函数里面去了,任务函数里面调了taskYIELD()函数,目的就是触发PendSV中断(优先级最低,没有其他中断运行时才响应)。下面这个图是进到PendSV中断服务函数之前的寄存器组状态。

在这里插入图片描述

下面这个图是进到PendSV中断服务函数时的寄存器组状态。可以观察psp,从0x60变成了0x40。

在这里插入图片描述

现在psp的位置就可以知道了,如下图所示。这是因为,进到xPortPendSVHandler函数之后,上个任务运行的环境将会自动存储到任务的栈中,同时psp自动更新。

在这里插入图片描述

下面这个代码,把psp的值存到r0里面。

//__asm void xPortPendSVHandler( void )函数
mrs r0, psp
//void vTaskStartScheduler( void )函数里
pxCurrentTCB = &Task1TCB;/*pxCurrentTCB有一个地址,这个地址里面的内容是当前任务的地址*//*当前任务地址的第一个内容就是当前任务的栈顶指针*///__asm void xPortPendSVHandler( void )函数里
ldr	r3, =pxCurrentTCB		/* 加载pxCurrentTCB的地址到r3 */
ldr	r2, [r3]         /* 把r3指向的内容给r2,内容就是Task1TCB(当前任务)的地址*//*[r2]是当前任务栈的栈顶指针*/
stmdb r0!, {r4-r11}			/* 将CPU寄存器r4~r11的值存储到r0指向的地址 */
str r0, [r2]                /* 把r0的地址给当前任务栈的栈顶指针 */	

经过上面这个代码,现在r0的位置如下。psp在上面这个过程是没变化的,变的只有r0。

在这里插入图片描述

对照着下面这个图,更清晰点。r2存的是当前任务的地址。r0存的是栈顶指针的地址。

在这里插入图片描述

下面对r3进行说明:r3=0x2000000C,这个地址里面存的第一个内容是当前任务块的地址0x20000068如下图所示。

在这里插入图片描述

下面对当前任务块的地址进行说明:当前任务块的地址0x20000068里面存的第一个内容就是栈顶指针的地址。

在这里插入图片描述

下面对栈顶指针的地址进行说明:栈顶指针地址里面内容刚好就是当前任务的任务栈。

在这里插入图片描述

可以对比下图,观察当前任务栈里面的内容,与此同时内容也对应了地址,地址就可以通过上图推出,比如,0x20000060地址里面存的就是0x10000000。

在这里插入图片描述

下面这个代码:目的是将r3和r14临时压入主栈(MSP指向的栈),因为接下来需要调用任务切换函数,调用函数时,返回地址自动保存到r14里面。r3的内容是当前任务块的地址(ldr r3, =pxCurrentTCB),调用函数后,pxCurrentTCB会被更新。

stmdb sp!, {r3, r14}

执行代码之前,MSP指向0x20000058这个地址。

在这里插入图片描述

执行代码之后,MSP指向的地址少了8个字节,与此同时r3和r14存到了MSP指向的地址里面。

在这里插入图片描述

msp指向的栈里面的具体信息其实可以反推出来,如下绿字:

在这里插入图片描述

下面这个代码:basepri是中断屏蔽寄存器,下面这个设置,优先级大于等于11的中断都将被屏蔽。相当于关中断进入临界段。

mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY 
msr basepri, r0
/*
#define configMAX_SYSCALL_INTERRUPT_PRIORITY 	191   /* 高四位有效,即等于0xb0,或者是11 */
191转成二进制就是11000000,高四位就是1100
*/

下面这个代码:调用了函数vTaskSwitchContext,这个函数目的是选择优先级最高的任务,然后更新pxCurrentTCB。目前这里面使用的是手动切换。

bl vTaskSwitchContext 
void vTaskSwitchContext( void )
{    /* 两个任务轮流切换 */if( pxCurrentTCB == &Task1TCB ){pxCurrentTCB = &Task2TCB;}else{pxCurrentTCB = &Task1TCB;}
}

现在说明一下调用这个函数产生什么后果:

从下图可知,此时r3=0x2000000C,这个地址里面的的内容就是当前任务块的地址。

在这里插入图片描述

进行到下面这一步,当前任务块的地址变了,与此同时,0x2000000C地址里面的的内容也变了。也就是说,走出调用函数之后,通过r3就能找到变化后新的任务地址了。

那么此时豁然开朗,为什么调用函数前要把r3入栈呢,看下图正中间上方的汇编代码,这个c语言背后的汇编代码是调用寄存器r0、r1存一些中间变量,为了防止运行函数时往r3寄存器里面存中间变量,才把r3入栈保护起来。想一下,如果往r3寄存器里面存中间变量,那么0x2000000C地址就不存到r3寄存器里了,那也无法通过r3找到变化后新的任务地址了。

在这里插入图片描述

下面这个代码:优先级高于0的中断被屏蔽,相当于是开中断退出临界段。

mov r0, #0                  /* 退出临界段 */
msr basepri, r0

下面这个代码恢复r3和r14

ldmia sp!, {r3, r14}        /* 恢复r3和r14 */

如下图,r3和r14被恢复,而且MSP从0x20000550变成了0x20000558。

在这里插入图片描述

这里面有个细节,MSP变动之后,MSP指向的栈前面的数(存的r3和r14)却被留了下来。这让人不禁思考出栈究竟是什么意思,这里不就只是动了MSP指针吗。

此时观察psp地址里面的内容,可发现,还是之前的那个任务栈。看了出栈和c语言里面实体的出(c语言里面出栈后,出去的内容就不在栈里面了)还不太一样,这个出栈,动的是指针,内容还在栈里面。

在这里插入图片描述

下面这个代码,进行完,r0里面存的是当前任务栈的栈顶指针的地址。

ldr r1, [r3]
ldr r0, [r1] 				/* 当前激活的任务TCB第一项保存了任务堆栈的栈顶,现在栈顶值存入R0*/

在这里插入图片描述

下面是当前的任务栈里面的内容。

在这里插入图片描述

ldmia r0!, {r4-r11}			/* 出栈 */

这个时候r0位置变到了0x200000c0。

在这里插入图片描述

然后下面把r0给了psp。记得吧,之前psp指向的可是0x20000040,也就是上一个任务的任务栈,这里面切到了另一个任务的任务栈里面了。也就是psp指向0x200000c0。

msr psp, r0

下面这个代码运行完效果如下图。

bx r14  

仔细观察,异常退出时,会以psp作为基地址,将任务栈里面剩下的内容自动加载到CPU寄存器。然后PC指针就拿到了任务栈里面的任务函数地址,然后就跳到任务函数里了。至此,切换完成。

在这里插入图片描述

最后,观察一下psp:由下面两张图,就明白了,psp出栈是什么意思。

下面是返回Thread Mode后(进入到了任务函数里面)psp的指向。

在这里插入图片描述

下图是没有返回到Thread Mode时psp的指向。

在这里插入图片描述

总结任务切换

总结一下核心思路:

1.首先是这张图,在任务函数里面,处于Thread Mode状态(为什么呢,因为bx r14 指令,里面r14的值设置的是0xFFFFFFFD),然后通过任务函数里面的taskYIELD()函数,进入Handler Mode状态,里面进行了任务切换操作,就是说,psp指向的任务栈切换了(所以一会pc指向的任务函数也改了),然后结束异常的时候,psp出栈,pc现在指向的是切换后的任务函数地址,于是就又跳到另一个任务函数里。

在这里插入图片描述

2.要明白切到任务函数里面的原理

之前创建任务时,已经把任务函数保存在了任务栈内。

出栈的话,psp指向的栈里面剩下的东西,会加载到寄存器里面,如下图所示:那么任务函数地址就给到pc指针了,那么异常返回之后,程序就跳到任务函数的地方继续运行,那么就切到任务函数里了。

在这里插入图片描述

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

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

相关文章

freertos临界段保护

freertos临界段保护中断的基础知识cortex-m里面开中断、关中断指令关中断和开中断进入临界段和退出临界段中断的基础知识 嵌套&#xff1a; 嵌套向量中断控制器 NVIC(Nested Vectored Interrupt Controller与内核是紧耦合的。提供如下的功能&#xff1a;可嵌套中断支持、向量…

改进版的CBOW模型

复习 首先复习一下之前的CBOW笔记。 采用推理的方法认知单词、CBOW模型这里面主要是&#xff1a; CBOW模型的核心思路&#xff1a;给出周围的单词&#xff08;上下文&#xff09;时&#xff0c;预测目标词处会出现什么单词。 要用神经网络处理单词&#xff0c;需要先将单词…

freertos空闲任务、阻塞延时

freertos空闲任务、阻塞延时空闲任务阻塞延时SysTick实验现象阻塞态&#xff1a;如果一个任务当前正在等待某个外部事件&#xff0c;则称它处于阻塞态。 rtos中的延时叫阻塞延时&#xff0c;即任务需要延时的时候&#xff0c;会放弃CPU的使用权,进入阻塞状态。在任务阻塞的这段…

树莓派配置

树莓派配置1.安装操作系统2.修改镜像源3.配置VNC连接1.安装操作系统 安装操作系统&#xff0c;首先在官网下载https://www.raspberrypi.com/software/operating-systems/ 下载出来的解压后是一个光盘映像文件&#xff0c;接下来就需要把这个文件写入到树莓派的tf卡里。 安装树…

远程桌面连机器人

这里需要PC和机器人处于同一局域网下。 PC使用xhell连接机器人&#xff0c;通过 ifconfig 查看机器人的无线 IP 地址。 然后退出xshell&#xff0c;打开NoMachine软件进行远程桌面连接。输入机器人无线ip地址。 输入用户名和密码 然后就远程连接上了 现在开一个终端如下所示&…

RNNLM

RNNLM 基于RNN的语言模型称为RNNLM(Language Model)。 Embedding 层&#xff1a;将单词ID转化为单词的分布式表示&#xff08;单词向量&#xff09;。 RNN层&#xff1a;向下一层(上方)输出隐藏状态&#xff0c;同时也向下一时刻的RNN层(右边)输出隐藏状态。 对于“you say …

使用c#制作赛尔号登录器

使用c#制作赛尔号登录器 需求&#xff1a; 打开赛尔号官网&#xff0c;发现我的chrome浏览器无法运行Flash。这是因为Adobe 公司放弃了对 Flash Player 的支持。 那么如果我想要玩游戏&#xff0c;又不想下载别的浏览器&#xff0c;只好自己写一个登陆器了。 创建项目 首先…

普通RNN的缺陷—梯度消失和梯度爆炸

之前的RNN&#xff0c;无法很好地学习到时序数据的长期依赖关系。因为BPTT会发生梯度消失和梯度爆炸的问题。 RNN梯度消失和爆炸 对于RNN来说&#xff0c;输入时序数据xt时&#xff0c;RNN 层输出ht。这个ht称为RNN 层的隐藏状态&#xff0c;它记录过去的信息。 语言模型的任…

LSTM的结构

RNN和LSTM 简略表示RNN层&#xff1a;长方形节点中包含了矩阵乘积、偏置的和、tanh函数的变换。将下面这个公式表示成一个tanh节点。 LSTM&#xff1a;Long Short-Term Memory&#xff08;长短期记忆&#xff09;&#xff0c;长时间维持短期记忆。 LSTM与RNN的接口(输入输出)…

STM32 USART 补充

串口通讯的数据包&#xff1a;发送设备通过自身的TXD接口传输到接收设备的RXD接口。 串口通讯的协议层中&#xff0c;规定了数据包的内容&#xff0c;由起始位、主体数据、校验位、停止位组成&#xff0c;通讯双方的数据包格式要约定一致才能正常收发数据。 异步通讯&#xf…

ROS TF变换

静态坐标转换&#xff1a;机器人本体中心到雷达中心的转换。因为激光雷达可能没安装到机器人的中心。 动态坐标转换&#xff1a;机器人中心和里程计坐标的变换。机器人从起点出发后&#xff0c;里程计坐标相对于本体就会产生一个偏移&#xff0c;这个偏移随着机器人的运动不断…

ROS底盘控制节点 源码分析

先在机器人端通过launch文件启动底盘控制。 robot:~$ roslaunch base_control base_control.launch ... logging to /home/jym/.ros/log/3e52acda-914a-11ec-beaa-ac8247315e93/roslaunch-robot-8759.log Checking log directory for disk usage. This may take a while. Pres…

ROS + OpenCV

视觉节点测试 先进行一些测试。并记录数据。 圆的是节点&#xff0c;方的是话题。 1.robot_camera.launch robot:~$ roslaunch robot_vision robot_camera.launch ... logging to /home/jym/.ros/log/bff715b6-9201-11ec-b271-ac8247315e93/roslaunch-robot-8830.log Check…

ROS+雷达 运行数据记录

先测试一下雷达&#xff0c;记录数据。方便接下来分析源码。 1.roslaunch robot_navigation lidar.launch robot:~$ roslaunch robot_navigation lidar.launch ... logging to /home/jym/.ros/log/7136849a-92cc-11ec-acff-ac8247315e93/roslaunch-robot-9556.log Checking l…

ROS 找C++算法源码的方法

在gmapping的launch文件中看到&#xff0c;type“slam_gmapping”&#xff0c;这里的slam_gmapping是c编译后的可执行文件。 如果想要修改gmapping算法&#xff0c;就需要找到slam_gmapping的c源码。 但是这是用apt下载的包&#xff0c;是二进制类型的&#xff0c;没有下载出…

ros 雷达 slam 导航 文件分析

ros 雷达 slam 导航 文件分析robot_slam_laser.launchrobot_lidar.launchlidar.launchraplidar.launchkarto.launchgmapping.launchcartographer.launchrobot_navigation.launchmap.yamlmap.pgmamcl_params.yamlmove_base.launchcostmap_common_params.yamllocal_costmap_param…

Apprentissage du français partie 1

Apprentissage du franais partie 1 键盘转换图&#xff1a; 字母&#xff1a;26个 元音字母&#xff1a;a、e、i、o、u、y b浊辅音(声带)-p清辅音 d-t 音符 音符&#xff1a;改变字母发音。 &#xff1a;闭音符 [e] &#xff1a;开音符 /ε/ &#xff1a;长音符 /ε/…

stm32基本定时器

定时器分类 stm32f1系列&#xff0c;8个定时器&#xff0c;基本定时器(TIM6,7)、通用定时器(TIM2,3,4,5)、高级定时器(TIM1,8)。 基本定时器&#xff1a;16位&#xff0c;只能向上计数的定时器&#xff0c;只能定时&#xff0c;没有外部IO 通用定时器&#xff1a;16位&#…

stm32高级定时器 基础知识

stm32高级定时器 高级定时器时基单元&#xff1a; 包含一个16位自动重装载寄存器 ARR 一个16位的计数器CNT&#xff0c;可向上/下计数 一个16位可编程预分频器PSC&#xff0c;预分频器时钟源有多种可选&#xff0c;有内部的时钟、外部时钟。 一个8位的重复计数器 RCR&…

stm32 PWM互补输出

stm32高级定时器例子—stm32 PWM互补输出 定时器初始化结构体 TIM_TimeBaseInitTypeDef 时基结构体&#xff0c;用于定时器基础参数设置&#xff0c;与TIM_TimeBaseInit函数配合使用&#xff0c;完成配置。 typedef struct { TIM_Prescaler /*定时器预分频器设置&…