stm32数据对齐、PRESERVE8、freertos堆栈

这篇具有很好参考价值的文章主要介绍了stm32数据对齐、PRESERVE8、freertos堆栈。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

为什么需要数据对齐?

避免数据在内存中跨边界存储,减少读取数据次数,提高效率,本质上是以空间换时间的做法

下图中属于同一水平位置的为同一边界
变量在同一边界里的一次存储周期就可以读取
一旦跨了上下两个边界来存储就需要至少两个存储周期来读取

根据存储器结构,如下图,参考链接:多体并行:高位/低位交叉编址
stm32数据对齐、PRESERVE8、freertos堆栈
CPU数据线有32位一次最多可以从内存读取32数据,这里的一次指一次存储周期
LDR r1 , [pc,#4], 是从pc+#4地址处开始连续读取4个字节的数据到r1寄存器
LDRH r1 , [pc,#4], 是从pc+#4地址处开始连续读取2个字节的数据到r1寄存器
LDRB r1 , [pc,#4] 直接取pc+#4当前那个地址的数据
上述指令都是在一个存储周期里完成的

一般地址线只能确定一个字节所在的地址,而上述指令地址都一样,却不止读到仅仅一个字节的数据,还能读到2个字节或者4个字节的数据,可以看到上述指令除了操作码不一样其它都一样,按照上图的存储结构,指令的地址一样那么体内地址肯定是一样的,体内地址一样就横向选中了同一水平位置的存储,操作码的不同说明控制线输出的信号应该不同,就可以控制一个类似体号的控制器,选中连续的好几个字节的存储
地址线加上控制线就可以实现上述的操作

LDM r1 ,{r0-r1} ; 是将r1指向的内存地址开始连续8个字节的数据存放到r0和r1寄存器中
该指令是在2个存储周期里完成的

详解边界对齐
为什么要内存对齐?字节对齐和边界对齐介绍

对于数据在内存中的位置,需要确定两个因素:大小、起始位置
根据存储器结构,就是通过起始位置和数据大小获取内存中的数据

一、全局变量静态变量,地址从小增大

1、原子类型数据

stm32数据对齐、PRESERVE8、freertos堆栈
按照开始的存储结构的图,short型变量起始位置放在地址1的位置应该也放得下,也能一次性读取,为什么必须得是0,2,4,6.。。。
那是因为放在不好判断地址1和地址3的不同,放在地址3就会跨界,而为了区分地址1和3做出来的电路更为复杂得不偿失,
8字节的也是同理

2、组合类型数据

详细参考链接:
详解边界对齐

// structure C 
typedef struct structc_tag 
{ 
   char        c; 
   double      d; 
   int         s; 
} structc_t; 

stm32数据对齐、PRESERVE8、freertos堆栈
这个结构的大小应该是 sizeof(char) + 7 + sizeof(double) + sizeof(int) = 1 +7 + 8 + 4 = 20

然而,正确答案是24

所以,所有结构的起始存储位置必须是结构中对边界要求最严格的数据类型所要求的位置。

stm32数据对齐、PRESERVE8、freertos堆栈

	struct {
		int a;
		short b;
		int c;
		char d;
	}A;

	struct {
		double a;
		short b;
		int c;
		char d;
	}B;

stm32数据对齐、PRESERVE8、freertos堆栈
上述总结还有另一个表达,组合数据结构的大小是组合结构中最大原子数据大小的整数倍:上述结构B就是20不是8的整数倍,24是8的整数倍

总结:

第一,编译器按照成员列表的顺序给每个成员分配内存.   
第二,当成员需要满足正确的边界对齐时,成员之间用额外字节填充.
第三,结构体的首地址必须满足结构体中边界要求最为严格的数据类型所要求的地址.  (也即首地址为最宽基本类型的整数倍) 
第四,结构体的大小为其最宽基本类型的整数倍.

3、#pragma pack (2)的作用

详细参考链接:为什么要内存对齐?字节对齐和边界对齐介绍。
文章中说的编译器默认4字节对齐是错误的,默认是8字节对齐其它分析都对

这里的指定几字节对齐,意思是数据本身的对齐数大于指定的话就按指定的对齐数来,也就是数据起始地址为指定对齐数的倍数
下面的struct中int a的自身对齐数为4大于指定对齐数2,所以按照2来,它存放的起始地址就为0,2,4,6.。。。这些

stm32数据对齐、PRESERVE8、freertos堆栈

4、结构体中嵌套结构体

#pragma  pack(8)
struct example1 {
    short a;
    long b;
};

struct example2 {
    char c;
    struct example1 e;
    short s;
};

struct example2数据结构的大小是16个字节,这个值是这样计算出来的:
1(char c) + 3(padding) + 8(struct example1 e) + 2(short s) + 2(padding) = 16。

该类型也是按照组合类型数据的规则来计算大小

数据结构内存边界对齐的三条原则

二、局部变量,栈中的变量

不同的数据(或数据结构)按顺序从地址大处向地址低处,同一数据内按顺序地址从小到大
sp要8字节对齐,sp的地址只会是8的倍数,(现象就是每次压栈出栈都是8的倍数个,参考下面的)
栈中的数据与全局数据相同的是大小的计算方法一样,不同的是栈中的数据起始地址不是自身对齐值的倍数而是4的倍数或8的倍数(如果自身对齐值小于4就是4的倍数大于4就是8的倍数)

1、结构体变量加lr寄存器大小小于8字节

typedef struct
{
        char a;
        char b;
} Tchar;

void temp()
{
	
    Tchar a2 = {'!','s'};	
	a2.a = a2.a + a2.b;
	
	printf("the size of struct Tchar  is %d\r\n ",sizeof(a2));
	printf("the address of struct Tchar  is %p\r\n ",&a2);
	printf("the address of struct Tchar a is %p\r\n ",&a2.a);
	printf("the address of struct Tchar b is %p\r\n ",&a2.b);
		
}

int main()
{
	temp();
	
	return 1;
}

stm32数据对齐、PRESERVE8、freertos堆栈

stm32数据对齐、PRESERVE8、freertos堆栈
进入temp()前sp为 0x20000420,执行完Tchar a2 = {‘!’,‘s’};sp为 0x20000418,可以看到汇编指令是执行了
PUSH {r3,lr},执行前是栈指针8字节对齐,执行后也是8字节对齐。
stm32数据对齐、PRESERVE8、freertos堆栈

结构体变量加lr寄存器大小小于8字节,也是要在栈中分配8字节让栈指针8字节对齐

2、结构体变量加lr寄存器大小大于8字节,小于16字节

struct naturalalign
{
char a;
short b;
char c;
};

void temp()
{
	
	struct naturalalign a1 = {'a',511,'5'};			
	a1.b = a1.b + 5;
				
	printf("the size of struct naturalalign is %d\r\n ",sizeof(a1));
				
	printf("the address of struct naturalalign is %p\r\n ",&a1);
	printf("the address of struct naturalalign char a is %p\r\n ",&a1.a);
	printf("the address of struct naturalalign char b is %p\r\n ",&a1.b);
	printf("the address of struct naturalalign char c is %p\r\n ",&a1.c);
		
}

int main()
{			
	temp();
	return 1;
}

stm32数据对齐、PRESERVE8、freertos堆栈

stm32数据对齐、PRESERVE8、freertos堆栈
进入temp()前sp为 0x20000420,执行完struct naturalalign a1 = {‘a’,511,‘5’}; sp为 0x20000410,可以看到汇编指令是执行了
PUSH {r2-r4,lr},执行前是栈指针8字节对齐,执行后也是8字节对齐。
stm32数据对齐、PRESERVE8、freertos堆栈

结构体变量加lr寄存器大小大于8字节小于16字节,也是要在栈中分配16字节让栈指针8字节对齐

3、结构体数组加lr寄存器大小大于8字节,等于16字节

struct naturalalign
{
char a;
short b;
char c;
};

void temp()
{
	
	struct naturalalign a1[2] = {'a',511,'5'};

				
	printf("the size of struct naturalalign is %d\r\n ",sizeof(a1));			
	printf("the address of struct naturalalign is %p\r\n ",&a1);
	printf("the address of struct naturalalign char a is %p\r\n ",&a1[0].a);
	printf("the address of struct naturalalign char b is %p\r\n ",&a1[0].b);
	printf("the address of struct naturalalign char c is %p\r\n ",&a1[0].c);
	printf("the address of struct naturalalign char a is %p\r\n ",&a1[1].a);
	printf("the address of struct naturalalign char b is %p\r\n ",&a1[1].b);
	printf("the address of struct naturalalign char c is %p\r\n ",&a1[1].c);		
}

int main()
{

	temp();
	return 1;
}

stm32数据对齐、PRESERVE8、freertos堆栈
stm32数据对齐、PRESERVE8、freertos堆栈

进入temp()前sp为 0x20000420,执行完struct naturalalign a1[2] = {‘a’,511,‘5’}; sp为 0x20000410,可以看到汇编指令是执行了
PUSH {r1-r3,lr},执行前是栈指针8字节对齐,执行后也是8字节对齐。

stm32数据对齐、PRESERVE8、freertos堆栈

结构体数组加lr寄存器大小等于16字节,也是要在栈中分配16字节让栈指针8字节对齐

4、两个结构体加lr寄存器大小大于8字节,等于16字节

struct naturalalign
{
char a;
short b;
char c;
};

void temp()
{
	
	struct naturalalign a1 = {'a',511,'5'};
	struct naturalalign a2 = {'s',511,'b'};
	printf("the size of struct naturalalign is %d\r\n ",sizeof(a1));

				
	printf("the address of struct naturalalign is %p\r\n ",&a1);
	printf("the address of struct naturalalign char a is %p\r\n ",&a1.a);
	printf("the address of struct naturalalign char b is %p\r\n ",&a1.b);
	printf("the address of struct naturalalign char c is %p\r\n ",&a1.c);
	printf("the address of struct naturalalign char a is %p\r\n ",&a2.a);
	printf("the address of struct naturalalign char b is %p\r\n ",&a2.b);
	printf("the address of struct naturalalign char c is %p\r\n ",&a2.c);
	}


int main()
{

				
				
	temp();
	return 1;
}

stm32数据对齐、PRESERVE8、freertos堆栈
stm32数据对齐、PRESERVE8、freertos堆栈
进入temp()前sp为 0x20000420,执行完struct naturalalign a1 = {‘a’,511,‘5’};
struct naturalalign a2 = {‘s’,511,‘b’};
sp为 0x20000408,可以看到汇编指令是执行了
PUSH {r0-r4,lr} ,执行前是栈指针8字节对齐,执行后也是8字节对齐。
stm32数据对齐、PRESERVE8、freertos堆栈

结构体数组加lr寄存器大小等于16字节,也是要在栈中分配16字节让栈指针8字节对齐

这里做一下对比,如果该两个结构体存放在全局区,是怎么样

struct naturalalign
{
char a;
short b;
char c;
};

struct naturalalign a1 = {'a',511,'5'};
	struct naturalalign a2 = {'s',511,'b'};
	
void temp()
{			
	printf("the size of struct naturalalign is %d\r\n ",sizeof(a1));				
	printf("the address of struct naturalalign is %p\r\n ",&a1);
	printf("the address of struct naturalalign char a is %p\r\n ",&a1.a);
	printf("the address of struct naturalalign char b is %p\r\n ",&a1.b);
	printf("the address of struct naturalalign char c is %p\r\n ",&a1.c);
	printf("the address of struct naturalalign char a is %p\r\n ",&a2.a);
	printf("the address of struct naturalalign char b is %p\r\n ",&a2.b);
	printf("the address of struct naturalalign char c is %p\r\n ",&a2.c);		
}

int main()
{

				
	temp();	
	return 1;
}

stm32数据对齐、PRESERVE8、freertos堆栈
stm32数据对齐、PRESERVE8、freertos堆栈

三、stm32中涉及到数据对齐的地方

0、硬件结构和指令代码

stm32的sp寄存器和pc寄存器的后两位都是为0的,栈指针起码是保持4字节对齐的
由于Cortex-M3架构采用16位和32位指令集,因此其PC指针地址是4字节对齐的,换言之其最低2位一定全是0。指令代码都是4字节对齐的

CM3硬件为了保持8字节对齐,在中断发生时会自动入栈8字节的寄存器数据 退出中断时会自动出栈8字节的寄存器数据

这也是CM3多任务执行的关键

详情可以查看2、task.c中prvInitialiseNewTask()

1、startup_stm32f10xxx.s启动文件

; Amount of memory (in bytes) allocated for Stack
;为Stack分配的内存量(以字节为单位)
; Tailor this value to your application needs
;根据您的应用需求定制此值
; <h> Stack Configuration
;   <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>

Stack_Size      EQU     0x800      ;2K

                AREA    STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem       SPACE   Stack_Size
__initial_sp


ALIGN=3 :表示首地址按照 2 的 3 次方对齐,也就是按照 8 字节对齐(SP mod 8 = 0)。

PRESERVE8
THUMB

PRESERVE8 指定当前文件保持堆栈八字节对齐。
THUMB 表示后面的指令是 THUMB 指令集 ,CM4 采用的是 THUMB -2指令集

这里的PRESERVE8 仅仅是一个声明,声明该文件里的堆栈是8字节对齐的,也即__initial_sp的值是8的倍数的地址,怎么保证__initial_sp的值是8的倍数呢?
首先分配的内存段首地址是8的倍数(ALIGN=3),然后分配的size 2k也是8的倍数,栈顶也就是__initial_sp就是8的倍数

为什么要保持堆栈的8字节对齐?

PRESERVE8的参考资料:
为什么要加 REQUIRE8 and PRESERVE8? 栈的8字节对齐
一般4字节对齐也没什么问题,但是在调用第三方库文件,比如在stm32中调用microlib 里的printf()输出float型变量时就会产生问题
stm32数据对齐、PRESERVE8、freertos堆栈
为什么会造成上述原因?
根据我粗浅的理解:第三方库为了保持兼容性,既能在32位上使用也能在64位上使用,该第三方库采用的8字节对齐的方式,stm32还是采用4字节对齐就会造成数据跨界
对堆栈8字节对齐问题的讨论

STM32 终极字节对齐解析

2、task.c中prvInitialiseNewTask()

#define portBYTE_ALIGNMENT_MASK    ( 0x0007 )

pxTopOfStack = &( pxNewTCB->pxStack[ ulStackDepth - ( uint32_t ) 1 ] );
pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
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 */
    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;
}

创建任务栈时:
1、分配任务栈空间
通过将栈顶指针变量的后3位变为0,将该指针指向的地址8字节对齐
2、手动初始化栈空间,初始化后还是8字节对齐。
后续R11, R10, R9, R8, R7, R6, R5 and R4手动出栈,保持8字节对齐
xPSP, PC, LR,R12以及R3~R0寄存器的值后续会根据psp栈针自动出栈,保持8字节对齐
每个任务最开始运行时的栈顶还是保持着最开始分配的栈顶

具体代码如下:

__asm void prvStartFirstTask( void )
{
	/*1首先是使用了 PRESERVE8,进行 8 字节对齐,这是因为,
	栈在任何时候都是需要 4 字节对齐的,而在调用入口得8字节对齐,
	在进行C编程的时候,编译器会自动完成的对齐的操作,而对于汇编,
	就需要开发者手动进行对齐。*/
	
	/*8字节对齐*/
	PRESERVE8
 
	/* Use the NVIC offset register to locate the stack. */
	
	/*向量表开始寄存器地址*/
	ldr r0, =0xE000ED08
	/* 获取向量表的值*/
	ldr r0, [r0]
	/*获取MSP的初始值(栈底指针)*/
	ldr r0, [r0]
 
	/* Set the msp back to the start of the stack. */
	/*初始化MSP*/
	msr msp, r0
	/* Globally enable interrupts. */
	
	/*使能全局中断*/
	cpsie i
	cpsie f
	dsb
	isb
	/* Call SVC to start the first task. */
	
	/*触发svc中断启动第一个任务*/
	svc 0
	nop
	nop
}
__asm void vPortSVCHandler( void )
{
  /*在进入异常前 会将 把xPSP, PC, LR,R12以及R3~R0寄存器的值压入栈 ,由硬件完成
  因为这个函数是不返回,这个可以不关心。
  */
    extern pxCurrentTCB;
    
    PRESERVE8

	ldr	r3, =pxCurrentTCB	/* 加载pxCurrentTCB的地址到r3 */
	ldr r1, [r3]			     /* 加载pxCurrentTCB到r1 */
	ldr r0, [r1]			 /* 加载pxCurrentTCB指向的值到r0,目前r0的值等于第一个任务堆栈的栈顶
	ldmia r0!, {r4-r11}		/* 以r0为基地址,将栈里面的内容加载到r4~r11寄存器,同时r0会递增 */
	msr psp, r0				/* 将r0的值,即任务的栈指针更新到psp 后面异常退出时,根据PSP进行出栈,                        就是前面任务栈初始化值出栈给到寄存器*/
	isb
	mov r0, #0              /* 设置r0的值为0 */
	msr	basepri, r0         /* 设置basepri寄存器的值为0,即所有的中断都没有被屏蔽 */
	orr r14, #0xd           /* 当从SVC中断服务退出前,通过向r14寄存器最后4位按位或上0x0D,
                               使得硬件在退出时使用进程堆栈指针PSP完成出栈操作并返回后进入线程模式、返回Thumb状态 */
    
	bx r14                  /* 异常返回,这个时候栈中的剩下内容将会自动加载到CPU寄存器:
                               xPSR,PC(任务入口地址),R14,R12,R3,R2,R1,R0(任务的形参)
                               同时PSP的值也将更新,即指向任务栈的栈顶 */
}

当任务切换时,和任务栈初始化入栈到任务启动出栈差不多,也是手动和自动两部分,都能保持8字节对齐
具体入栈出栈的流程如下:
R11, R10, R9, R8, R7, R6, R5 and R4手动出栈,保持8字节对齐
xPSP, PC, LR,R12以及R3~R0寄存器的值后续会根据psp栈针自动出栈,保持8字节对齐
stm32数据对齐、PRESERVE8、freertos堆栈
代码如下:

__asm void xPortPendSVHandler( void )
{
	extern pxCurrentTCB;
	extern vTaskSwitchContext;

	PRESERVE8

    /* 当进入PendSVC Handler时,上一个任务运行的环境即:
       xPSR,PC(任务入口地址),R14,R12,R3,R2,R1,R0(任务的形参)
       这些CPU寄存器的值会自动保存到任务的栈中,剩下的r4~r11需要手动保存 */
    /* 获取任务栈指针到r0 */
	mrs r0, psp
	isb

	ldr	r3, =pxCurrentTCB		/* 加载pxCurrentTCB的地址到r3 */
	ldr	r2, [r3]                /* 加载pxCurrentTCB到r2 */

	stmdb r0!, {r4-r11}			/* 将CPU寄存器r4~r11的值存储到r0指向的地址 */
	str r0, [r2]                /* 将任务栈的新的栈顶指针存储到当前任务TCB的第一个成员,即栈顶指针 */				
   //以上 上下文保存

	stmdb sp!, {r3, r14}        /* 将R3和R14临时压入堆栈,因为即将调用函数vTaskSwitchContext,
                                  调用函数时,返回地址自动保存到R14中,所以一旦调用发生,R14的值会被覆盖,因此需要入栈保护;
                                  R3保存的当前激活的任务TCB指针(pxCurrentTCB)地址,函数调用后会用到,因此也要入栈保护 */
	mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY    /* 进入临界段 */
	msr basepri, r0
	dsb
	isb
	bl vTaskSwitchContext       /* 调用函数vTaskSwitchContext,寻找新的任务运行,通过使变量pxCurrentTCB指向新的任务来实现任务切换 */ 
	mov r0, #0                  /* 退出临界段 */
	msr basepri, r0
	ldmia sp!, {r3, r14}        /* 恢复r3和r14 */

	ldr r1, [r3]
	ldr r0, [r1] 				/* 当前激活的任务TCB第一项保存了任务堆栈的栈顶,现在栈顶值存入R0*/
	ldmia r0!, {r4-r11}			/* 出栈 */
	msr psp, r0
	isb
	bx r14          /* 异常发生时,R14中保存异常返回标志,包括返回后进入线程模式还是处理器模式、
                    使用PSP堆栈指针还是MSP堆栈指针,当调用 bx r14指令后,硬件会知道要从异常返回,
                    然后出栈,这个时候堆栈指针PSP已经指向了新任务堆栈的正确位置,
                    当新任务的运行地址被出栈到PC寄存器后,新的任务也会被执行。*/
	nop
}

四、heap堆内存

除了heap_3.c外freertos的heap都是通过在头文件中申请一个全局数组空间

FreeRTOS Heap_1、Heap_2、Heap_3、Heap_4、Heap_5的区别

FreeRTOS Heap 1_2_3_4_5 比较

FreeRTOS的内存分配heap_1/2/3/4文章来源地址https://www.toymoban.com/news/detail-500047.html

到了这里,关于stm32数据对齐、PRESERVE8、freertos堆栈的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • STM32堆栈方面知识点

    最近弄json,发现经常的堆溢出,然后找问题。因为对STM32堆栈问题没有深刻认识,就花时间好好研究下了堆栈并且做了验证 1.栈地址区间确定 首先找到启动文件,我的启动文件在startup_stm32f40xx.s,一般的启动文件也都在startup_stm32fxxxx.s文件里 __initial_sp      这个参数是栈顶地

    2024年02月10日
    浏览(33)
  • STM32F429 Discovery开发板应用:使用FreeRTOS队列+DMA双缓存实现串口数据接收

      参考帖子:https://blog.csdn.net/freedompoi/article/details/122350866 目前想要实现STM32F4自带的DMA双缓冲区,尝试过一版,结果不能预期,就使用了RxHalfCplt和RxCplt去实现DMA双缓冲区的效果。 现在有时间了,又重新实现STM32F4自带的DMA双缓冲区,作为参考。   MCU:STM32F429ZIT6 开发环境:

    2024年02月08日
    浏览(48)
  • STM32堆栈的大小及内存四(五)区的分析

    一般在编程时,我们都不需要考虑堆栈空间的大小,因为在启动文件中都对堆栈空间的大小进行了设置。 1.1.1、直接修改启动文件 如以下截取stm32启动文件部分汇编代码, Stack栈的大小为:0x400(1024Byte),Heap堆的大小为:0x200(512Byte)。 这也是为什么一个空的的工程编译后

    2024年02月05日
    浏览(42)
  • FreeRTOS的任务堆栈

    一、 FreeRTOS的任务堆栈 在FreeRTOS中,每个任务都有自己的堆栈。堆栈是用来存储任务运行时临时变量、函数返回地址等数据的一段内存空间。堆栈的大小是通过xTaskCreate()函数的参数指定的,通常情况下需要根据任务所需的局部变量的大小及递归调用深度来合理设置。 FreeRTO

    2024年02月14日
    浏览(39)
  • 基于STM32的实时操作系统FreeRTOS移植教程(手动移植)_stm32移植freertos(1)

    直接意识代码: 这是我们大脑最希望的添加代码方式,很显然他是 错的 , 两个任务之间产生了相互的影响 ,使得两个任务都执行错误,这种思想在 裸机开发 中肯定是 错的 ,但是在我们的 RTOS 中他就可以是 对的 。 任务型代码: 这是 独立的两个任务内容 ,我们只需要把

    2024年04月10日
    浏览(103)
  • STM32内存分配以及堆栈、变量、代码等的存储位置理解与分析

    文中不足之处,欢迎各位同仁批评指正!         STM32的程序存储器、数据存储器、寄存器和输入输出端口被组织在同一个4GB的线性地址空间内, 地址范围为0x0000 0000至0xFFFF FFFF。其中FLASH为ROM类型,储存的数据掉电不易失;RAM中存储的数据掉电易失。以STM32F103系列为例,最多

    2024年02月03日
    浏览(38)
  • 【FreeRTOS】FreeRTOS移植stm32详细步骤介绍

    我在查找FreeRTOS移植的相关教程特别少,所以想非常详细的介绍FreeRTOS移植stm32详细步骤,包括源码的下载,源码介绍,系统移植,代码验证等,每一步都有对应的介绍和解释,希望可以帮助到你们。 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分

    2024年02月08日
    浏览(43)
  • 【GD32/STM32】FreeRtos移植

    ​ (1)下载FreeRtos源码包 ​ FreeRTOS - Market leading RTOS (Real Time Operating System) for embedded systems with Internet of Things extensions ​ (2)将源文件添加至工程 ​ (3)将GD32中的三个中断交由FreeRtos接管 ​ (4)配置管理FreeRTOS.h文件 ​ (5)FreeRtos基本使用方法 源码包结构 1.Demo中存放的

    2024年01月20日
    浏览(38)
  • STM32之FreeRTOS

    目录 FreeRTOS 介绍 什么是 FreeRTOS ? 为什么选择 FreeRTOS ?   FreeRTOS 资料与源码下载 祼机开发与 FreeRTOS  祼机开发: FreeRTOS: FreeRTOS 实现多任务的原理 二、移植 FreeRTOS  手动移植 使用CubeMX快速移植 快速移植流程 一些常见问题 1. Timebase Source 为什么不能设置为 SysTick ? 2. FreeRTO

    2024年02月15日
    浏览(58)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包