FreeRTOS内存管理 基于STM32

2023-05-16

目录

一、内存管理的基本概念

二、内存管理的应用场景

三、heap_4.c

1.内存申请函数 pvPortMalloc()

2.内存释放函数 vPortFree()

 四、内存管理的实验

五、内存管理的实验现象


一、内存管理的基本概念

      在计算系统中,变量、中间数据一般存放在系统存储空间中,只有在实际使用时才将 它们从存储空间调入到中央处理器内部进行运算。通常存储空间可以分为两种:内部存储 空间和外部存储空间。内部存储空间访问速度比较快,能够按照变量地址随机地访问,也 就是我们通常所说的 RAM(随机存储器),或电脑的内存;而外部存储空间内所保存的内 容相对来说比较固定,即使掉电后数据也不会丢失,可以把它理解为电脑的硬盘。在这一 章中我们主要讨论内部存储空间(RAM)的管理——内存管理。

      FreeRTOS 操作系统将内核与内存管理分开实现,操作系统内核仅规定了必要的内存管 理函数原型,而不关心这些内存管理函数是如何实现的,所以在 FreeRTOS 中提供了多种 内存分配算法(分配策略),但是上层接口(API)却是统一的。这样做可以增加系统的 灵活性:用户可以选择对自己更有利的内存管理策略,在不同的应用场合使用不同的内存 分配策略。

      在嵌入式程序设计中内存分配应该是根据所设计系统的特点来决定选择使用动态内存 分配还是静态内存分配算法,一些可靠性要求非常高的系统应选择使用静态的,而普通的 业务系统可以使用动态来提高内存使用效率。静态可以保证设备的可靠性但是需要考虑内 存上限,内存使用效率低,而动态则是相反。

      FreeRTOS 内存管理模块管理用于系统中内存资源,它是操作系统的核心模块之一。主 要包括内存的初始化、分配以及释放。

      很多人会有疑问,什么不直接使用 C 标准库中的内存管理函数呢?在电脑中我们可以 用 malloc()和 free()这两个函数动态的分配内存和释放内存。但是,在嵌入式实时操作系统 中,调用 malloc()和 free()却是危险的,原因有以下几点:

 1.这些函数在小型嵌入式系统中并不总是可用的,小型嵌入式设备中的 RAM 不足。

 2.它们的实现可能非常的大,占据了相当大的一块代码空间。

 3.他们几乎都不是安全的。

 4.它们并不是确定的,每次调用这些函数执行的时间可能都不一样。

 5. 它们有可能产生碎片。

 6. 这两个函数会使得链接器配置得复杂。

 7.如果允许堆空间的生长方向覆盖其他变量占据的内存,它们会成为 debug 的灾难。

      在一般的实时嵌入式系统中,由于实时性的要求,很少使用虚拟内存机制。所有的内 存都需要用户参与分配,直接操作物理内存,所分配的内存不能超过系统的物理内存,所 有的系统堆栈的管理,都由用户自己管理。

      同时,在嵌入式实时操作系统中,对内存的分配时间要求更为苛刻,分配内存的时间 必须是确定的。一般内存管理算法是根据需要存储的数据的长度在内存中去寻找一个与这段数据相适应的空闲内存块,然后将数据存储在里面。而寻找这样一个空闲内存块所耗费 的时间是不确定的,因此对于实时系统来说,这就是不可接受的,实时系统必须要保证内 存块的分配过程在可预测的确定时间内完成,否则实时任务对外部事件的响应也将变得不可确定。

      而在嵌入式系统中,内存是十分有限而且是十分珍贵的,用一块内存就少了一块内存, 而在分配中随着内存不断被分配和释放,整个系统内存区域会产生越来越多的碎片,因为 在使用过程中,申请了一些内存,其中一些释放了,导致内存空间中存在一些小的内存块, 它们地址不连续,不能够作为一整块的大内存分配出去,所以一定会在某个时间,系统已 经无法分配到合适的内存了,导致系统瘫痪。其实系统中实际是还有内存的,但是因为小 块的内存的地址不连续,导致无法分配成功,所以我们需要一个优良的内存分配算法来避 免这种情况的出现。

      不同的嵌入式系统具有不同的内存配置和时间要求。所以单一的内存分配算法只可能 适合部分应用程序。因此,FreeRTOS 将内存分配作为可移植层面(相对于基本的内核代码 部分而言),FreeRTOS 有针对性的提供了不同的内存分配管理算法,这使得应用于不同场 景的设备可以选择适合自身内存算法。

      FreeRTOS 对内存管理做了很多事情,FreeRTOS 的 V9.0.0 版本为我们提供了 5 种内存 管理算法,分别是 heap_1.c、heap_2.c、heap_3.c、heap_4.c、heap_5.c,源文件存放于 FreeRTOS\Source\portable\MemMang 路径下,在使用的时候选择其中一个添加到我们的工 程中去即可。

      FreeRTOS 的内存管理模块通过对内存的申请、释放操作,来管理用户和系统对内存的 使用,使内存的利用率和使用效率达到最优,同时最大限度地解决系统可能产生的内存碎片问题。

二、内存管理的应用场景

      首先,在使用内存分配前,必须明白自己在做什么,这样做与其他的方法有什么不同, 特别是会产生哪些负面影响,在自己的产品面前,应当选择哪种分配策略。

      内存管理的主要工作是动态划分并管理用户分配好的内存区间,主要是在用户需要使 用大小不等的内存块的场景中使用,当用户需要分配内存时,可以通过操作系统的内存申 请函数索取指定大小内存块,一旦使用完毕,通过动态内存释放函数归还所占用内存,使 之可以重复使用(heap_1.c 的内存管理除外)。

      例如我们需要定义一个 float 型数组:floatArr[];

      但是,在使用数组的时候,总有一个问题困扰着我们:数组应该有多大?在很多的情 况下,你并不能确定要使用多大的数组,可能为了避免发生错误你就需要把数组定义得足 够大。即使你知道想利用的空间大小,但是如果因为某种特殊原因空间利用的大小有增加 或者减少,你又必须重新去修改程序,扩大数组的存储范围。这种分配固定大小的内存分 配方法称之为静态内存分配。这种内存分配的方法存在比较严重的缺陷,在大多数情况下会浪费大量的内存空间,在少数情况下,当你定义的数组不够大时,可能引起下标越界错 误,甚至导致严重后果。

      我们用动态内存分配就可以解决上面的问题。所谓动态内存分配就是指在程序执行的 过程中动态地分配或者回收存储空间的分配内存的方法。动态内存分配不象数组等静态内 存分配方法那样需要预先分配存储空间,而是由系统根据程序的需要即时分配,且分配的 大小就是程序要求的大小。

三、heap_4.c

     我们这里只讲heap_4.c 因为这个最常用 。 内 存 分 配 时 需 要 的 总 的 堆 空 间 由 文 件 FreeRTOSConfig.h 中 的 宏 configTOTAL_HEAP_SIZE 配置,单位为字。通过调用函数 xPortGetFreeHeapSize() 我们可 以知道还剩下多少内存没有使用,但是并不包括内存碎片。这样一来我们可以实时的调整 和优化 configTOTAL_HEAP_SIZE 的大小。

      heap_4.c 方案的空闲内存块也是以单链表的形式连接起来的,BlockLink_t 类型的局部 静态变量 xStart 表示链表头,但 heap_4.c 内存管理方案的链表尾部则保存在内存堆空间最 后位置,并使用 BlockLink_t 指针类型局部静态变量 pxEnd 指向这个区域(而 heap_2.c 内 存管理方案则使用 BlockLink_t 类型的静态变量 xEnd 表示链表尾)

      heap_4.c 内存管理方案的空闲块链表不是以内存块大小进行排序的,而是以内存块起 始地址大小排序,内存地址小的在前,地址大的在后,因为 heap_4.c 方案还有一个内存合 并算法,在释放内存的时候,假如相邻的两个空闲内存块在地址上是连续的,那么就可以 合并为一个内存块,这也是为了适应合并算法而作的改变。

      heap_4.c 方案具有以下特点:

     1、可用于重复删除任务、队列、信号量、互斥量等的应用程序

     2、可用于分配和释放随机字节内存的应用程序,但并不像 heap2.c 那样产生严重的内 存碎片。

     3、具有不确定性,但是效率比标准 C 库中的 malloc 函数高得多。

1.内存申请函数 pvPortMalloc()

      heap_4.c 方案的内存申请函数与 heap_2.c 方案的内存申请函数大同小异,同样是从链 表头 xStart 开始遍历查找合适的内存块,如果某个空闲内存块的大小能容得下用户要申请 的内存,则将这块内存取出用户需要内存空间大小的部分返回给用户,剩下的内存块组成 一个新的空闲块,按照空闲内存块起始地址大小顺序插入到空闲块链表中,内存地址小的 在前,内存地址大的在后。在插入到空闲内存块链表的过程中,系统还会执行合并算法将 地址相邻的内存块进行合并:判断这个空闲内存块是相邻的空闲内存块合并成一个大内存 块,如果可以则合并,合并算法是 heap_4.c 内存管理方案和 heap_2.c 内存管理方案最大的 不同之处,这样一来,会导致的内存碎片就会大大减少,内存管理方案适用性就很强,能 一样随机申请和释放内存的应用中,灵活性得到大大的提高,heap_4.c 内存初始化完成示意图具体见图1。

heap_4.c 的内存 申请源码

void *pvPortMalloc( size_t xWantedSize )
{
BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink;
void *pvReturn = NULL;

	vTaskSuspendAll();
	{
		/* If this is the first call to malloc then the heap will require
		initialisation to setup the list of free blocks. */
		if( pxEnd == NULL )
		{
			prvHeapInit();
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}

		/* Check the requested block size is not so large that the top bit is
		set.  The top bit of the block size member of the BlockLink_t structure
		is used to determine who owns the block - the application or the
		kernel, so it must be free. */
		if( ( xWantedSize & xBlockAllocatedBit ) == 0 )
		{
			/* The wanted size is increased so it can contain a BlockLink_t
			structure in addition to the requested amount of bytes. */
			if( xWantedSize > 0 )
			{
				xWantedSize += xHeapStructSize;

				/* Ensure that blocks are always aligned to the required number
				of bytes. */
				if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0x00 )
				{
					/* Byte alignment required. */
					xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
					configASSERT( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) == 0 );
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}

			if( ( xWantedSize > 0 ) && ( xWantedSize <= xFreeBytesRemaining ) )
			{
				/* Traverse the list from the start	(lowest address) block until
				one	of adequate size is found. */
				pxPreviousBlock = &xStart;
				pxBlock = xStart.pxNextFreeBlock;
				while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) )
				{
					pxPreviousBlock = pxBlock;
					pxBlock = pxBlock->pxNextFreeBlock;
				}

				/* If the end marker was reached then a block of adequate size
				was	not found. */
				if( pxBlock != pxEnd )
				{
					/* Return the memory space pointed to - jumping over the
					BlockLink_t structure at its start. */
					pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + xHeapStructSize );

					/* This block is being returned for use so must be taken out
					of the list of free blocks. */
					pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;

					/* If the block is larger than required it can be split into
					two. */
					if( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE )
					{
						/* This block is to be split into two.  Create a new
						block following the number of bytes requested. The void
						cast is used to prevent byte alignment warnings from the
						compiler. */
						pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize );
						configASSERT( ( ( ( size_t ) pxNewBlockLink ) & portBYTE_ALIGNMENT_MASK ) == 0 );

						/* Calculate the sizes of two blocks split from the
						single block. */
						pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize;
						pxBlock->xBlockSize = xWantedSize;

						/* Insert the new block into the list of free blocks. */
						prvInsertBlockIntoFreeList( pxNewBlockLink );
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}

					xFreeBytesRemaining -= pxBlock->xBlockSize;

					if( xFreeBytesRemaining < xMinimumEverFreeBytesRemaining )
					{
						xMinimumEverFreeBytesRemaining = xFreeBytesRemaining;
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}

					/* The block is being returned - it is allocated and owned
					by the application and has no "next" block. */
					pxBlock->xBlockSize |= xBlockAllocatedBit;
					pxBlock->pxNextFreeBlock = NULL;
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}

		traceMALLOC( pvReturn, xWantedSize );
	}
	( void ) xTaskResumeAll();

	#if( configUSE_MALLOC_FAILED_HOOK == 1 )
	{
		if( pvReturn == NULL )
		{
			extern void vApplicationMallocFailedHook( void );
			vApplicationMallocFailedHook();
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
	#endif

	configASSERT( ( ( ( size_t ) pvReturn ) & ( size_t ) portBYTE_ALIGNMENT_MASK ) == 0 );
	return pvReturn;
}

图1 内存初始化完成示意图

2.内存释放函数 vPortFree()

    heap_4.c 内存管理方案的内存释放函数 vPortFree()也比较简单,根据传入要释放的内 存块地址,偏移之后找到链表节点,然后将这个内存块插入到空闲内存块链表中,在内存 块插入过程中会执行合并算法,这个我们已经在内存申请中讲过了(而且合并算法多用于 释放内存中)。最后是将这个内存块标志为“空闲”(内存块节点的 xBlockSize 成员变量 最高位清 0)、再更新未分配的内存堆大小即可,下面来看看 vPortFree()的源码实现过程。

void vPortFree( void *pv )
{
uint8_t *puc = ( uint8_t * ) pv;
BlockLink_t *pxLink;

	if( pv != NULL )
	{
		/* The memory being freed will have an BlockLink_t structure immediately
		before it. */
		puc -= xHeapStructSize;

		/* This casting is to keep the compiler from issuing warnings. */
		pxLink = ( void * ) puc;

		/* Check the block is actually allocated. */
		configASSERT( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 );
		configASSERT( pxLink->pxNextFreeBlock == NULL );

		if( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 )
		{
			if( pxLink->pxNextFreeBlock == NULL )
			{
				/* The block is being returned to the heap - it is no longer
				allocated. */
				pxLink->xBlockSize &= ~xBlockAllocatedBit;

				vTaskSuspendAll();
				{
					/* Add this block to the list of free blocks. */
					xFreeBytesRemaining += pxLink->xBlockSize;
					traceFREE( pv, pxLink->xBlockSize );
					prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );
				}
				( void ) xTaskResumeAll();
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
}

      调用 prvInsertBlockIntoFreeList()函数将释放的内存块添加到空闲 内存块链表中,在这过程中,如果内存块可以合并就会进行内存块合并,否则就单纯插入 空闲内存块链表(按内存地址排序)。 按照内存释放的过程,当我们释放一个内存时,如果与它相邻的内存块都不是空闲的, 那么该内存块并不会合并,只会被添加到空闲内存块链表中,其过程示意图具体见图 2。而如果某个时间段释放了另一个内存块,发现该内存块前面有一个空闲内存块与它 在地址上是连续的,那么这两个内存块会合并成一个大的内存块,并插入空闲内存块链表 中,其过程示意图具体见图 3,

图二释放一个内存块(无法合并)

图3 释放一个内存块(可以合并)

 四、内存管理的实验

      内存管理实验使用 heap_4.c 方案进行内存管理测试,创建了两个任务,分别是 LED 任 务与内存管理测试任务,内存管理测试任务通过检测按键是否按下来申请内存或释放内存, 当申请内存成功就像该内存写入一些数据,如当前系统的时间等信息,并且通过串口输出 相关信息;LED 任务是将 LED 翻转,表示系统处于运行状态。在不需要再使用内存时,注 意要及时释放该段内存,避免内存泄露。

/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
/* 开发板硬件bsp头文件 */
#include "bsp_led.h"
#include "bsp_usart.h"
#include "bsp_key.h"
/**************************** 任务句柄 ********************************/
/* 
 * 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄
 * 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么
 * 这个句柄可以为NULL。
 */
static TaskHandle_t AppTaskCreate_Handle = NULL;/* 创建任务句柄 */
static TaskHandle_t LED_Task_Handle = NULL;/* LED_Task任务句柄 */
static TaskHandle_t Test_Task_Handle = NULL;/* Test_Task任务句柄 */



/******************************* 全局变量声明 ************************************/
/*
 * 当我们在写应用程序的时候,可能需要用到一些全局变量。
 */
uint8_t *Test_Ptr = NULL;


/*
*************************************************************************
*                             函数声明
*************************************************************************
*/
static void AppTaskCreate(void);/* 用于创建任务 */

static void LED_Task(void* pvParameters);/* LED_Task任务实现 */
static void Test_Task(void* pvParameters);/* Test_Task任务实现 */

static void BSP_Init(void);/* 用于初始化板载相关资源 */

/*****************************************************************
  * @brief  主函数
  * @param  无
  * @retval 无
  * @note   第一步:开发板硬件初始化 
            第二步:创建APP应用任务
            第三步:启动FreeRTOS,开始多任务调度
  ****************************************************************/
int main(void)
{	
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  
  /* 开发板硬件初始化 */
  BSP_Init();
	printf("这是一个FreeRTOS内存管理实验\n");
  printf("按下KEY1申请内存,按下KEY2释放内存\n");
   /* 创建AppTaskCreate任务 */
  xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate,  /* 任务入口函数 */
                        (const char*    )"AppTaskCreate",/* 任务名字 */
                        (uint16_t       )512,  /* 任务栈大小 */
                        (void*          )NULL,/* 任务入口函数参数 */
                        (UBaseType_t    )1, /* 任务的优先级 */
                        (TaskHandle_t*  )&AppTaskCreate_Handle);/* 任务控制块指针 */ 
  /* 启动任务调度 */           
  if(pdPASS == xReturn)
    vTaskStartScheduler();   /* 启动任务,开启调度 */
  else
    return -1;  
  
  while(1);   /* 正常不会执行到这里 */    
}


/***********************************************************************
  * @ 函数名  : AppTaskCreate
  * @ 功能说明: 为了方便管理,所有的任务创建函数都放在这个函数里面
  * @ 参数    : 无  
  * @ 返回值  : 无
  **********************************************************************/
static void AppTaskCreate(void)
{
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  
  taskENTER_CRITICAL();           //进入临界区

  /* 创建LED_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )LED_Task, /* 任务入口函数 */
                        (const char*    )"LED_Task",/* 任务名字 */
                        (uint16_t       )512,   /* 任务栈大小 */
                        (void*          )NULL,	/* 任务入口函数参数 */
                        (UBaseType_t    )2,	    /* 任务的优先级 */
                        (TaskHandle_t*  )&LED_Task_Handle);/* 任务控制块指针 */
  if(pdPASS == xReturn)
    printf("创建LED_Task任务成功\n");
  
  /* 创建Test_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )Test_Task,  /* 任务入口函数 */
                        (const char*    )"Test_Task",/* 任务名字 */
                        (uint16_t       )512,  /* 任务栈大小 */
                        (void*          )NULL,/* 任务入口函数参数 */
                        (UBaseType_t    )3, /* 任务的优先级 */
                        (TaskHandle_t*  )&Test_Task_Handle);/* 任务控制块指针 */ 
  if(pdPASS == xReturn)
    printf("创建Test_Task任务成功\n\n");
  
  vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
  
  taskEXIT_CRITICAL();            //退出临界区
}



/**********************************************************************
  * @ 函数名  : LED_Task
  * @ 功能说明: LED_Task任务主体
  * @ 参数    :   
  * @ 返回值  : 无
  ********************************************************************/
static void LED_Task(void* parameter)
{	
  while (1)
  {
    LED1_TOGGLE;
    vTaskDelay(1000);/* 延时1000个tick */
  }
}

/**********************************************************************
  * @ 函数名  : Test_Task
  * @ 功能说明: Test_Task任务主体
  * @ 参数    :   
  * @ 返回值  : 无
  ********************************************************************/
static void Test_Task(void* parameter)
{	 
  uint32_t g_memsize;
  while (1)
  {
    if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON )
    {
      /* KEY1 被按下 */
      if(NULL == Test_Ptr)
      {
                  
        /* 获取当前内存大小 */
        g_memsize = xPortGetFreeHeapSize();
        printf("系统当前内存大小为 %d 字节,开始申请内存\n",g_memsize);
        Test_Ptr = pvPortMalloc(1024);
        if(NULL != Test_Ptr)
        {
          printf("内存申请成功\n");
          printf("申请到的内存地址为%#x\n",(int)Test_Ptr);

          /* 获取当前内剩余存大小 */
          g_memsize = xPortGetFreeHeapSize();
          printf("系统当前内存剩余存大小为 %d 字节\n",g_memsize);
                  
          //向Test_Ptr中写入当数据:当前系统时间
          sprintf((char*)Test_Ptr,"当前系统TickCount = %d \n",xTaskGetTickCount());
          printf("写入的数据是 %s \n",(char*)Test_Ptr);
        }
      }
      else
      {
        printf("请先按下KEY2释放内存再申请\n");
      }
    } 
    if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON )
    {
      /* KEY2 被按下 */
      if(NULL != Test_Ptr)
      {
        printf("释放内存\n");
        vPortFree(Test_Ptr);	//释放内存
        Test_Ptr=NULL;
        /* 获取当前内剩余存大小 */
        g_memsize = xPortGetFreeHeapSize();
        printf("系统当前内存大小为 %d 字节,内存释放完成\n",g_memsize);
      }
      else
      {
        printf("请先按下KEY1申请内存再释放\n");
      }
    }
    vTaskDelay(20);/* 延时20个tick */
  }
}

/***********************************************************************
  * @ 函数名  : BSP_Init
  * @ 功能说明: 板级外设初始化,所有板子上的初始化均可放在这个函数里面
  * @ 参数    :   
  * @ 返回值  : 无
  *********************************************************************/
static void BSP_Init(void)
{
	/*
	 * STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15
	 * 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
	 * 都统一用这个优先级分组,千万不要再分组,切忌。
	 */
	NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
	
	/* LED 初始化 */
	LED_GPIO_Config();

	/* 串口初始化	*/
	USART_Config();
  
  /* 按键初始化	*/
  Key_GPIO_Config();

}

/********************************END OF FILE****************************/

五、内存管理的实验现象

程序编译好,用 USB 线连接电脑和开发板的 USB 接口(对应丝印为 USB 转串口), 用 DAP 仿真器把配套程序下载到 STM32 开发板,在电脑上打开串口调试助手,然后复位开发板,我 们按下 KEY1 申请内存,然后按下 KEY2 释放内存,可以在调试助手中看到串口打印信息 与运行结果,开发板的 LED 也在闪烁。

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

FreeRTOS内存管理 基于STM32 的相关文章

  • docker || 制作镜像

    从镜像的制作到代码编写 xff0c 启动容器 Dockerfile 是制作镜像的配方文件 xff08 配置文件 xff09 第1步 xff1a 编辑Dockerfile root 64 sc docker server mkdir mydo
  • Vue生命周期

    私人博客 许小墨 Blog 菜鸡博客直通车 系列文章完整版 xff0c 配图更多 xff0c CSDN博文图片需要手动上传 xff0c 因此文章配图较少 xff0c 看不懂的可以去菜鸡博客参考一下配图 xff01 系列文章目录 前端系列文章
  • 为什么需要串级PID控制(结合智能小车,四轴飞行器来解释)

    先说四轴飞行器 四轴飞行器中串级PID控制是由角度环与角速度环一起控制的 可以这么简单的理解 xff1a 角度环可以可以保证飞机按期望的角度飞行 xff0c 单环控制时 xff0c 具有很好的自稳性 但是打舵的时候跟随性就不够完美 可能会出
  • 搭建简单的gazebo仿真环境

    1 下载模型库 可以去网址 xff1a https github com osrf gazebo models下载一些通用的模型库 2 复制模型库 将下载好的模型复制放在gazebo默认文件夹下 xff0c 这个默认位置一般在gazebo的
  • 已有项目如何添加到gitee仓库

    一 在gitee上创建仓库 二 初始化本地git文件 输入git init 输入 git remote add origin XXX 输入命令 xff1a git pull origin master 命令 xff1a git add xf
  • Windows安装使用Docker,方便你的开发和部署(DockerDesktop篇)

    前言 首先声明 xff0c 此篇不是完全的Docker技术文章 xff0c 而是单纯的教你使用Docker xff0c 不包含Docker的一些命令 如何打包Docker镜像等等 为什么要用Docker xff1f 大家好 xff0c 我是
  • ROS多机器人-gazebo仿真-问题总结及解决方法

    近期进行多机器人编队控制仿真 xff08 gps camera laser xff09 xff0c 遇到些许问题 xff0c 总结如下 基于一个机器人URDF如何在gazebo中仿真显示多机器人 launch文件中使用group标签 lt
  • git使用命令行拉取代码更新到站点

    1 把服务器端的公钥添加到gitee或者阿里云效 xff1b 2 创建一个全新干净的站点目录 xff1b 3 进入后这个目录 xff0c 执行 git clone lt repository gt 在这里 xff0c 点 表示当前目录 4
  • 单片机寄存器的理解

    学习目标 xff1a 对寄存器的理解 学习内容 xff1a 本来在学DMA xff0c 突然看到江科大的b站视频 xff0c 在他的DMA视频中对寄存器进行了理解 xff0c 我觉得十分巧妙 xff0c 这是当时截的图 xff0c 中间竖着
  • python 类变量详解

    强烈建议先看我之前的面向对象基本了解 python中 xff0c 类变量又叫做类属性 然而类属性有分为3个不同的类型 类属性 xff0c 实例属性 xff0c 局部变量 class a 类属性 hobby 61 39 play sport
  • Python运行环境Ngrok内网穿透

    这次就教大家怎么在手机或机顶盒上运行Ngrok内网穿透 首先下载安卓上的Python运行环QPython apk 官网 http www qpython com 下载完后开始安装 xff0c 怎么安装安卓软件不用我怎么教了吧 复制pytho
  • 记一次PWN机(Vmware和Docker+VNC)的搭建

    基于Vmware搭建虚拟机 学长已经提供了镜像 xff0c 在Vmware界面打开那个镜像 xff0c 开机即可 Vmware和镜像下载地址 xff1a 链接 xff1a https pan quark cn s 057a23e631f5
  • vue.runtime.esm.js?2b0e:619 [Vue warn]: Invalid prop: type check failed for prop “index“. Expected S

    vue runtime esm js 2b0e 619 Vue warn Invalid prop type check failed for prop 34 index 34 Expected String with value 34 1
  • 解决vscode上边菜单栏不显示的问题

    由于我们不小心点击了哪个键 xff0c 导致上边的菜单栏不显示 两种解决方案 方法一 xff1a 点击下面的这个按钮 xff0c 会弹出一个弹框 点击一下菜单栏的可见性 xff0c 即可出来 方式二 xff1a 使用快捷键Ctrl 43 S
  • node+vue搜索和分页功能实现

    前端代码 lt template gt lt div class 61 34 main box 34 gt lt 卡片区 gt lt el card gt lt 搜索 gt lt el input placeholder 61 34 请输入
  • React面试题最全

    1 什么是虚拟DOM xff1f 虚拟DOM是真实DOM在内存中的表示 xff0c ul的表示形式保存在内存中 xff0c 并且与实际的DOM同步 xff0c 这是一个发生在渲染函数被调用和元素在屏幕上显示的步骤 xff0c 整个过程被称为
  • vue项目页面空白但不报错产生的原因分析

    vue项目中我们请求一个路由 xff0c 打开页面发现页面是空白的 xff0c 产生的主要原因有四种 xff1a 1 路由重复 如果配置了两个路由是重复的 xff0c 比如配置了两个 path xff0c 那么访问就会看到空白页面 xff0
  • react--电商商品列表使用

    目录 整体页面效果 项目技术点 拦截器的配置 主页面 添加商品 分页 xff0c 搜索 修改商品 删除商品 完整代码 整体页面效果 项目技术点 antd组件库 xff0c 64 ant design icons antd的图标库axios
  • 服务器端升级或者切换node版本

    1 查看版本 nvm list 2 选择你需要的版本 nvm use v18 15 0
  • lodash防抖节流

    应用场景 xff1a 当用户高频率的触发事件 xff0c 事件较短 xff0c 内部出现卡顿现象 解决方法 xff1a 防抖节流 防抖节流功作用 xff1a 主要目的是为了降低高频事件触发 xff0c 减少dom操作或请求次数 xff0c

随机推荐

  • 使用webpack(4版本)搭建vue2项目

    在学习webpack之前 xff0c 也从网上搜过一些用webpack搭建vue项目的博客 xff0c 但是在自己使用的时候会报各种的问题 xff0c 报错的根本原因其实就是版本的问题 xff0c 以下代码是经过解决了许多报错问题研究出来最
  • VUE调用摄像头PC

    页面效果 实现代码 lt template gt lt div id 61 34 app 34 gt lt router view gt lt 开启摄像头 gt lt Button type 61 34 primary 34 64 clic
  • koa2的脚手架koa-generator使用

    我们在搭建项目的时候 xff0c 会使用一些脚手架 xff0c 今天我们使用koa2的脚手架koa generator 使用非常简单 首先 xff0c 我们需要全局安装 npm install g koa generator 创建项目 ko
  • css行内元素、块元素、行内块元素的区别

    行内元素的特点 xff1a 1 和其他元素在一行显示 2 元素的宽度 高度 行高及底部边距不可编辑 3 元素的宽度就是它包含的文字或图片的宽度 xff0c 不可改变 4 行内元素只能容纳纯文本或者是其他的行内元素 xff08 a标签除外 x
  • STM32CubeMX安装

    一 STM32CubeMX下载 官网地址 xff1a STM32CubeMX STM32Cube初始化代码生成器 意法半导体STMicroelectronics 官网下载需要注册账号 网盘链接 xff08 6 8 xff09 xff1a 链
  • 关于汇编指令sar右移32位的情况,记录一下

    网上 对sar指令右移超过31位的情况 比如右移32位 右移33位 很少有详细的说明 为了防止下一次还有像我这样傻x的人不会 我就记录一下 我不会过多说基础 基础了解就行 xff0c 能懒就懒 1字节 sar 我们以一字节为例子 00412
  • vs2022 汇编环境配置,xxx.inc头文件找不到,出现报错,不影响正常代码生成的解决方案

    解决方案前提 1 你的include和lib路径已经配置好了 然后代码可以正常的生成一个exe 2 安装了的AsmDube 其实inc文件找不到 是AsmDub找不到 不是vs2022找不到 所以你就去AsmDube修改一下 所以你只需要告
  • 关于8皇后解决方法的浅析

    关于8皇后解决方法的浅析 众所周知 xff0c 解决8皇后最普遍的方法是回溯法 那具体是怎么样的呢 xff1f 大概思路 xff1a 定义一个int型数组queen xff0c 角标 1 2 7 代表皇后所在的行 xff0c 值代表皇后所在
  • Latex闲谈

    关于latex呢 xff0c 首先它是一个排版工具 xff0c 是一个将文章排版和文章内容分开的一个排版工具 有个问题是你是边写边排版呢还是用word写完之后再来说是进行这个排版呢 xff0c 对于我而言 xff0c 我更倾向于后者 xff
  • Linux下phpmyadmin忘记root的登录密码,找回方法

    第一步 xff1a 执行 etc init d mysql stop 结束当前正在运行的mysql进程 第二步 xff1a 执行 usr bin mysqld safe skip grant tables 用mysql安全模式运行并跳过权限
  • matlab郭彦甫-听课笔记-02

    可以分块 xff0c 分块之后可以进行分块执行run section 关系运算符 xff1a 61 不等于 取余函数 xff1a mod a b rem a b switch case case case otherwise 连乘函数 xf
  • 51单片机硬件介绍

    1 单片机是啥 单片机 xff0c 简称MCU xff0c 是微型计算机 xff0c 集成了一部计算机许多硬件功能 xff0c 有CPU 存储器 xff08 ROM RAM xff09 等 2 有了这样一个单片机芯片后 xff0c 怎么将程
  • matlab硬件支持包离线安装-(安装文件夹错误)

    dSupport Software Downloader MATLAB amp Simulinkhttps ww2 mathworks cn support install support software downloader html
  • 小结:卸载SolidWorks2018->重新安装系统->安装SolidWorks2020

    因为卸载SW2018卸载不干净 xff0c 所以在安装SW20版一直在出错 xff0c 错误如下 xff1a 这个错误解决后继续安装 xff0c 又发现没有出现原本序列号的那一界面 xff0c 然后还有异型孔向导安装不了 xff0c 最后还
  • FreeRTOS信号量 基于STM32

    目录 概述 一 信号量基本概念 1 二值信号量 2 计数信号量 3 互斥信号量 4 递归信号量 二 二值信号量运作机制 三 计数信号量运作机制 四 常用信号量函数接口讲解 1 创建二值信号量 xSemaphoreCreateBinary 2
  • FreeRTOS互斥量 基于STM32

    文章目录 一 互斥量基本概念 二 互斥量的优先级继承机制 三 互斥量应用场景 四 互斥量运作机制 五 互斥量函数接口讲解 1 互斥量创建函数 xSemaphoreCreateMutex 2 递归xSemaphoreCreateRecursi
  • FreeRTOS事件组 基于STM32

    概述 文章对事件组的 xff0c 应用场景 xff0c 运作机制 xff0c 以及事件的创建 xff0c 删除 xff0c 等待 xff0c 置位 xff0c 同步等操作 文章目录 概述 一 事件标志组简介 1 事件位 事件标志 2 事件组
  • FreeRTOS任务通知 基于STM32

    文章目录 一 任务通知简介 二 任务通知的运作机制 三 任务通知的函数接口讲解 1 xTaskGenericNotify 2 xTaskNotifyGive 3 vTaskNotifyGiveFromISR 4 xTaskNotify 5
  • FreeRTOS软件定时器 基于STM32

    文章目录 一 软件定时器的基本概念 二 软件定时器应用场景 三 软件定时器的精度 四 软件定时器的运作机制 五 软件定时器函数接口讲解 1 软件定时器创建函数 xTimerCreate 2 软件定时器启动函数 xTimerStart 3 软
  • FreeRTOS内存管理 基于STM32

    目录 一 内存管理的基本概念 二 内存管理的应用场景 三 heap 4 c 1 内存申请函数 pvPortMalloc 2 内存释放函数 vPortFree 四 内存管理的实验 五 内存管理的实验现象 一 内存管理的基本概念 在计算系统中