STM32第二十三课(Freertos, HAL ,Cubemx)

如果使用了freertos,那么要把systick留给freertos使用。
这里,需要修改sys->timebase为其他的timer,例如TIM6,或者TIM7。
所以,这里,如果之前使用的delay_us是用systick来做的,如果修改了time base 的硬件定时器,就需要做相应的修改。
在cubemx中,要注意,将time base的优先级设置为最高,即0.
以TIM7为例。

我们首先找到TIM7的定义。

#define TIM7                ((TIM_TypeDef *) TIM7_BASE)

发现它被定义成一个指针,指向一个结构体对象的指针,所以,它是一个句柄。
来看看这个结构体。

typedef struct
{
  __IO uint32_t CR1;         /*!< TIM control register 1,              Address offset: 0x00 */
  __IO uint32_t CR2;         /*!< TIM control register 2,              Address offset: 0x04 */
  __IO uint32_t SMCR;        /*!< TIM slave mode control register,     Address offset: 0x08 */
  __IO uint32_t DIER;        /*!< TIM DMA/interrupt enable register,   Address offset: 0x0C */
  __IO uint32_t SR;          /*!< TIM status register,                 Address offset: 0x10 */
  __IO uint32_t EGR;         /*!< TIM event generation register,       Address offset: 0x14 */
  __IO uint32_t CCMR1;       /*!< TIM capture/compare mode register 1, Address offset: 0x18 */
  __IO uint32_t CCMR2;       /*!< TIM capture/compare mode register 2, Address offset: 0x1C */
  __IO uint32_t CCER;        /*!< TIM capture/compare enable register, Address offset: 0x20 */
  __IO uint32_t CNT;         /*!< TIM counter register,                Address offset: 0x24 */
  __IO uint32_t PSC;         /*!< TIM prescaler,                       Address offset: 0x28 */
  __IO uint32_t ARR;         /*!< TIM auto-reload register,            Address offset: 0x2C */
  __IO uint32_t RCR;         /*!< TIM repetition counter register,     Address offset: 0x30 */
  __IO uint32_t CCR1;        /*!< TIM capture/compare register 1,      Address offset: 0x34 */
  __IO uint32_t CCR2;        /*!< TIM capture/compare register 2,      Address offset: 0x38 */
  __IO uint32_t CCR3;        /*!< TIM capture/compare register 3,      Address offset: 0x3C */
  __IO uint32_t CCR4;        /*!< TIM capture/compare register 4,      Address offset: 0x40 */
  __IO uint32_t BDTR;        /*!< TIM break and dead-time register,    Address offset: 0x44 */
  __IO uint32_t DCR;         /*!< TIM DMA control register,            Address offset: 0x48 */
  __IO uint32_t DMAR;        /*!< TIM DMA address for full transfer,   Address offset: 0x4C */
  __IO uint32_t OR;          /*!< TIM option register,                 Address offset: 0x50 */
} TIM_TypeDef;

这个结构体,用来描述一个MMR,(Memory Mapped Region,简称为MMR。)
这个结构体对象,是一个资源描述块RDB(resource description block)。所描述的资源,是一个MMR,而这个MMR,不是RAM中的MMR,而是IO上的MMR,是一个寄存器组。所以,我们称之为RGDB。(Register Group Description Block)
(补充,
MMR具有如下特征,
第一,成块状地址区域,所有成员变量连续分布,每个成员变量均为uint32_t类型,
第二,每个成员均为IOMM,所以,所有成员均用volatile作为前导修饰,这里,用宏__IO表示volatile。)
从以上定义,可以看出,将一个base addr,强制转换成了一个RGDB的指针,作为RGDB的句柄。
这样,后续,当我们需要寻址访问MMR的某个offset时,只需要寻址RGDB中的某个成员变量即可。

来看看base addr的定义

#define TIM7_BASE             (APB1PERIPH_BASE + 0x1400UL)

这个base addr,是在另一个base addr的基础上,加上offset得到的。
对于TIM7的base addr,我们称之为device base addr ,
另一个更基础的base addr,我们称之为bus base addr,
来看看bus base addr,

#define APB1PERIPH_BASE       PERIPH_BASE
#define PERIPH_BASE           0x40000000UL /*!< Peripheral base address in the alias region              

所以,我们修改delay_us,只需要用RGDB的句柄来选择对应的寄存器即可。
对应于SysTick->LOAD,我们这里需要的是TIM7->ARR,
对应于SysTick->VAL,我们这里需要的是TIM7->CNT,

在HAL_InitTick中,可以找到,所以TIM7的计数值跳变时钟是1MHZ,即1us跳变一次。

htim7.Init.Period = (1000000U / 1000U) - 1U;

对应的,我们需要修改fac_us的算法。

//fac_us=1000000/1000/1000;	
fac_us=1;	

最终,修改后的代码如下:

static void delay_us_freertos_tim7(uint32_t nus)
{		
	uint32_t ticks;
	uint32_t told,tnow,tcnt=0;
	uint32_t reload=TIM7->ARR;				//LOAD的值
	ticks=nus;
                               //需要的节拍数 
	tnow=TIM7->CNT;        				//刚进入时的计数器值
	while(1)
	{
        told=tnow;
		tnow=TIM7->CNT;
    
        if(tnow>=told)
            tcnt+=tnow-told;	//这里注意一下SYSTICK是一个递减的计数器就可以了.
        else 
            tcnt+=reload-told+tnow;	    
			       
		if(tcnt>=ticks) break;			//时间超过/等于要延迟的时间,则退出.  
	}
}

void delay_us(uint32_t nus)
{
    delay_us_freertos_tim7(nus);
}

++++++++++++++++++++++++++++++++++++
freertos中的priority的概念和ucos是相反的。
在Freertos中,priority的值越大,优先级越高,所以,0是优先级最低的,
但是在ucos中,priority的值越小,优先级越高,所以,0是优先级最高的。

UCOS中的优先级的概念,和STM32的硬件中断优先级的概念是相同的,即,值越小,优先级越高。

freertos中,pensv和systick的库中断优先级,都是最低的。所谓的库中断优先级,其实就是cubemx中设置的NVIC优先级。
freertos中,还有一个max syscall priority,高于这个库中断优先级的硬件中断,freertos不管理。一般设置为5。
在freertos中,低于5的中断,在ISR中,可以安全的调用freertos的API,即那些fromISR后缀的API。
但是高于5的中断,在ISR中,禁止调用freertos的API,而且,freertos也不能屏蔽和禁止这些中断。
所以,这些超高优先级的中断,只能使用裸机函数。

+++++++++++++++++++++++++++++++++++++++
对于裸机程序,进入main后,首先是一系列的init函数,然后进入一个while(1)总任务体中。
可以理解为,裸机程序是一个单任务系统,除了前台的ISR,就只有后台一个单任务。在这个单任务里,又串行执行各个子任务。进入一个子任务时,相当于创建了这个子任务,而子任务调用返回时,又相当于删除了这个子任务。
整个while(1)总任务体执行完成后,马上又进入下一次迭代,再次开始反复创建子任务,删除子任务的过程。

在freertos中,进入main后 ,也是一样,首先是一系列的init函数,然后创建第一个start起始任务,以及其他一些需要的任务,然后,开始schedule,main之后进入自旋,永不退出。
调度器会调度已经创建的task,使用CPU的时间片,如果一个任务都没有了,那么就是main的自旋来消耗掉CPU的时间片。
一般建议是,在start起始任务中,创建其他的任务,创建需要的内核对象,内核对象是一些系统管理起来的全局对象。例如sem,mutex,queue等。

如果从前后台的角度来看,freertos中的所有任务,都处于后台,所有的ISR,仍然是处于前台。
不同的是,在裸机中,当系统从前台返回时,是有明确的唯一断点的,CPU从唯一的断点继续执行,而在freertos中,当系统从前台返回时,并不是明确的断点继续,因为后台有多个进程,都有自己的断点,CPU选择哪个断点继续,取决于调度。

++++++++++++++++++++++++++++++++++++
freertos对于malloc的管理,如果没有外部SRAM,那么推荐使用heap4,如果有外部SRAM连接在FSMC上,那么推荐使用heap5,因为heap5在heap4的基础上,进一步扩展了功能,能够支持多个HeapRegion。

一个块连续的内存用一个 HeapRegion_t 表示,多个连续内存通过 HeapRegion_t 数组的形式组织成为了所有的内存;
heap 5 要求,在调用正式的内存分配函数之前,需要定义 HeapRegion,并调用 vPortDefineHeapRegions 来初始化它;
官方给了一个 demo:

#define RAM1_START_ADDRESS ( ( uint8_t * ) 0x00010000 )
#define RAM1_SIZE ( 65 * 1024 )
 
#define RAM2_START_ADDRESS ( ( uint8_t * ) 0x00020000 )
#define RAM2_SIZE ( 32 * 1024 )
 
#define RAM3_START_ADDRESS ( ( uint8_t * ) 0x00030000 )
#define RAM3_SIZE ( 32 * 1024 )

const HeapRegion_t xHeapRegions[] =
{
    { RAM1_START_ADDRESS, RAM1_SIZE },
    { RAM2_START_ADDRESS, RAM2_SIZE },
    { RAM3_START_ADDRESS, RAM3_SIZE },
    { NULL, 0 } /* Marks the end of the array. */
};
int main( void ) {
	
	//init other module
	...
	
    /* Initialize heap_5. */
    vPortDefineHeapRegions( xHeapRegions );
    // after init heap5 , can create start task
    /* Add application code here. */
}

定义 HeapRegion_t 数组的时候,最后一定要定义成为 NULL 和 0,这样接口才知道这是终点;
被定义的 RAM 区域,都会去参与内存管理;

在真实使用的时候,有可能你很难去定义部分 RAM 的 Start Address!
在编译阶段,有可能你相关的代码和数据等等(.text,.data,.bss,等)都会占用一部分的 RAM,但凡是定义到这个 HeapRegion_t 数组表格的,都会参与内存管理的行为,这显然是我们不愿意的;
比如你芯片的 RAM 起始地址 0x2000_0000,你编译你的 Source Code 后,相关的代码和数据要占用 20KB,也就是 0x5000;那么你定义的 RAM1_START_ADDRESS 起始地址,就必须要大于 0x2000_0000+0x5000,这样才不会踩到你的其他数据;
考虑到实际的使用,官方给出参考 demo 的方法是:

#define RAM2_START_ADDRESS ( ( uint8_t * ) 0x00020000 )
#define RAM2_SIZE ( 32 * 1024 )
 
#define RAM3_START_ADDRESS ( ( uint8_t * ) 0x00030000 )
#define RAM3_SIZE ( 32 * 1024 )

#define RAM1_HEAP_SIZE ( 30 * 1024 )
 
static uint8_t ucHeap[ RAM1_HEAP_SIZE ];

const HeapRegion_t xHeapRegions[] =
{
    { ucHeap, RAM1_HEAP_SIZE },
    { RAM2_START_ADDRESS, RAM2_SIZE },
    { RAM3_START_ADDRESS, RAM3_SIZE },
    { NULL, 0 } /* Marks the end of the array. */
};

将链接的代码数据,根据链接器(Linker)配置后,这些都放置在第一段的区域,而ucHeap 也放在一样的地方,

+++++++++++++++++++++++++++++++++++
勾选cmsis v1,

++++++++++++++++++++++++++++++++++++++
config param:

enable FPU----默认disable
use preemption----默认enable
tick rate ----默认1000,即1ms一次tick中断。
max priority----默认7,即从0到6,一共7级。
mini stack size ----默认是128word,即128*4=512BYTE。
max task name len ----默认是16,
idle should yield ----默认是enable,修改为disable,
use mutex ----默认是enable,
use recursive mutex----默认是disable,
use counting semaphore----默认是disable,
queue registory size ----默认是8,
use application task tag----默认是disable,
use backward compatibility----默认是enable,
use tickless idle----默认是disable,
use task notifiy ----默认是enable,
record stack high addr ---- 默认是disable,

mem alloc ----默认是dynamic / static,
total heap size ----默认是15360,
mm scheme----默认是heap4,

use ilde hook ---- 默认是disable,
use tick hook ----默认是disable,
use malloc fail hook ---- 默认是disable,
use daemon task startup hook ---- 默认是disable,
check for stack overflow ----默认是disable,

generate run time stat ----默认是disable,
use trace facility ----默认是disable,
use stat formating func ----默认是disable,

use co routing ----默认是disable,
max co routing priority ----默认是2,

use software timer ----默认是disable,

library lowest interrupt priority ----默认是15,
library max syscall interruput priority ----默认是5,

use posix error number ----默认是disable,

++++++++++++++++++++++++++++++++++++
include param

task priority set get ----默认是enable,
task delete ----默认是enable,
task cleanup resource ----默认是disable,
task suspend ----默认是enable,

task delay until ----默认是disable,
task delay ---- 默认是enable,

task resume from ISR ----默认是enable,

queue get mutex holder ----默认是disable,
semaphore get mutex holder ----默认是disable,

task get task name ----默认是disable,
task get stack high water mark ----默认是disable,
task get current task handle ---- 默认是disable,
task get state ----默认是disable,

event group set bit from ISR ---- 默认是disable,
task abort delay ----默认是disable,
task get handle ----默认是disable,
task get stack high water mark2 ----默认是disable,

++++++++++++++++++++++++++++++++++
advance setting

use newlib reentrant ----默认是disable,

use fw pack heap file ----默认是enable,

+++++++++++++++++++++++++++++++++++
task and queue

default task -----作为最基本的一个task,

+++++++++++++++++++++++++++++++++++
timer and semaphore

如果使能了software timer ,那么可以在这里add software timer,
如果使能了counting semaphore,那么可以在这里add counting semaphore,
这些都是全局对象。
binary semaphore是必须使能的,同样,在这里add binary semaphore,

++++++++++++++++++++++++++++++++++
mutex
mutex是必须使能的,在这里add,
如果使能了recursive mutex,那么也在这里add,

+++++++++++++++++++++++++++++++++++
event

如果使能了event,那么在这里add,

++++++++++++++++++++++++++++++++++++
freertos heap usage
这里报告heap 的使用情况

+++++++++++++++++++++++++++++++++++++
在裸机系统中,前台ISR和后台任务之间,通信只能靠共享全局变量,
前台ISR打标,后台任务检查标志,清标。
这里后台对标志的使用,有两种方式,一种是让出,一种是自旋。
在让出方式时,子任务检查标志,如果不满足条件,则break,从而让后续的子任务得以执行。
在自旋方式时,子任务检查标志,如果不满足条件,则使用
while(!conditon);
自旋,直到满足条件后escape。继续执行本任务中的后续指令。

在freertos中,也是类似的。
前台ISR和后台task之间,通信有了更丰富的资源,通常使用共享内核对象。
前台ISR填充或者修改内核对象,后台任务检查内核对象的状态,满足条件则调度执行,不满足条件则继续阻塞。
所以,freertos中,从物理CPU的角度来看,任务对内核对象的使用,是让出方式的。在不满足通信条件的时候,任务task让出CPU,给其他的task使用CPU的时间片。
但是,从虚拟CPU的角度来看,任务对内核对象的时候,又是自旋方式的。
虚拟CPU是任务独享的,在不满足通信条件的时候,任务自旋,消耗掉虚拟CPU的时间片。

基于freertos进行系统设计时,最关键的设计思想,就是任务划分和任务通信。

+++++++++++++++++++++++++++++++++++++
来看看加入了freertos后的代码,有哪些改变。

osThreadId startTaskHandle;
osThreadId myTask02Handle;
osThreadId myTask03Handle;

定义了三个全局变量。这是三个TCB句柄。我们在cubemx中定义的taskname。

typedef TaskHandle_t osThreadId;
void StartTask(void const * argument);
void StartTask02(void const * argument);
void StartTask03(void const * argument);

声明了三个taskfunc,这是我们需要实现的任务函数。我们在cubemx中定义的taskfunc。

	/* Create the thread(s) */
  /* definition and creation of startTask */
  osThreadDef(startTask, StartTask, osPriorityLow, 0, 128);
  startTaskHandle = osThreadCreate(osThread(startTask), NULL);

  /* definition and creation of myTask02 */
  osThreadDef(myTask02, StartTask02, osPriorityBelowNormal, 0, 128);
  myTask02Handle = osThreadCreate(osThread(myTask02), NULL);

  /* definition and creation of myTask03 */
  osThreadDef(myTask03, StartTask03, osPriorityNormal, 0, 128);
  myTask03Handle = osThreadCreate(osThread(myTask03), NULL);

用宏拟函数osThreadDef,填充了一个TDB实体,即一个TDB的结构体对象。

const osThreadDef_t os_thread_def_##name = \
{ #name, (thread), (priority), (instances), (stacksz), NULL, NULL }

然后调用osThreadCreate函数,该函数利用TDB和argument,填充TCB实体,返回一个TCB句柄。
其中用到了宏拟函数osThread,用它来索引到对应名称的TDB。

#define osThread(name)  &os_thread_def_##name

如果是没有参数,那么可以用NULL作为参数,
如果我们需要一个参数名,那么在cubemx中定义的参数名,会被用来填充TCB。
例如:

/* definition and creation of startTask */
  osThreadDef(startTask, StartTask, osPriorityLow, 0, 128);
  startTaskHandle = osThreadCreate(osThread(startTask), (void*) &start_param);

  /* definition and creation of myTask02 */
  osThreadDef(myTask02, StartTask02, osPriorityBelowNormal, 0, 128);
  myTask02Handle = osThreadCreate(osThread(myTask02), (void*) &task2_param);

  /* definition and creation of myTask03 */
  osThreadDef(myTask03, StartTask03, osPriorityNormal, 0, 128);
  myTask03Handle = osThreadCreate(osThread(myTask03), (void*) &task3_param);

此时,keil会报错,因为定义的参数名被强转成(void*)类型的指针。它们是Parameter Description Block(PDB)的句柄。
所以,我们需要手工定义这些PDB,通常是定义成全局变量。然后将PDB的指针传递给TCB。
如果我们需要传递PDB的指针,在cubemx中输入时,前面要有"&"符号作为前导。
上述的NULL,是一个空指针,将NULL指针传递给TCB,是为了让参数传递合法,但是传递到任务函数中的NULL指针是不会被使用的。

这也给我们自己手工定义TDB和TCB,提供了样板。

接下来,就是main中调用调度器,进入永久循环。

/* Start scheduler */
  osKernelStart();

这个函数,实际上就是对调度器的一次封装。

osStatus osKernelStart (void)
{
  vTaskStartScheduler();
  
  return osOK;
}

++++++++++++++++++++++++++++++++++++++++++++
cubemx为我们生成了代码框架,
接下来,我们需要做的,就是编写taskfunc的代码,以及ISR的代码。

通常,我们会为每一个taskfunc建立一个单独的C文件,并辅以对应的H文件。
ISR代码,则仍旧在xx_it.c文件中编写。
我们需要覆盖定义弱函数,即那些我们使用到的ISR的callback函数。

如果任务代码比较简单,也可以直接在main中定义。
cubemx在main中,已经生成了taskfunc的框架代码。我们在其中填写代码即可。
例如:

void StartTask(void const * argument)
{
  /* init code for USB_DEVICE */
  MX_USB_DEVICE_Init();
  /* USER CODE BEGIN 5 */
  /* Infinite loop */
  for(;;)
  {
    	
      	taskENTER_CRITICAL();           //进入临界区
    
    	vTaskDelete(startTaskHandle); //删除开始任务
    
    	taskEXIT_CRITICAL();            //退出临界区

		osDelay(1);
  }
  /* USER CODE END 5 */
}

任务代码中,首先是一些init,然后是一些one-off ,每个任务中,只在开始任务时,执行一次的代码,然后进入任务主体。
任务主体,是一个永久循环。首句,是一个osDelay函数的调用。

osStatus osDelay (uint32_t millisec)
{
  TickType_t ticks = millisec / portTICK_PERIOD_MS;	 
  vTaskDelay(ticks ? ticks : 1);          /* Minimum delay = 1 tick */
  return osOK;
}

可以看到,osDelay函数实际上就是封装了vTaskDelay函数。

调用osDelay的目的,是为了调用一次SYSAPI,从而发起一次SWI。
SWI的作用,第一,为任务主体的执行,设置一个断点,第二,引起一次任务调度。
从物理CPU的角度看,当任务发起了SWI的时候,设置了一个断点,让出CPU,当任务被再次调用执行时,会从SWI之前设置的断点位置, 继续向下执行。
从虚拟CPU的角度看,任务在此自旋一个time slice,之后再次进入任务主体,开始下一次迭代。

这在逻辑上,也是很清晰的,明确指出,本任务的本次迭代结束,让出CPU给其他任务,本任务进入休息时间,当休息时间结束时,任务再次开始下一次迭代的业务操作。

必须谨慎使用vTaskDelete函数,
如果一个任务,调用了这种函数,则称为killer task,杀手任务的角色,必须谨慎部署。
必须能够逻辑自洽,否则任由系统中存在多个杀手任务,甚至有一些潜在的杀手任务,那么系统将是不安全的。
杀手任务,必须是一个系统中的管理者角色的任务。

+++++++++++++++++++++++++++++++++++++
来看看一个普通任务。

void StartTask03(void const * argument)
{
  /* USER CODE BEGIN StartTask03 */
    u8 task2_num=0;
	
	POINT_COLOR = BLACK;

	LCD_DrawRectangle(125,110,234,314); //画一个矩形	
	LCD_DrawLine(125,130,234,130);		//画线
	POINT_COLOR = BLUE;
	LCD_ShowString(126,111,110,16,16,1,(u8*)"Task2 Run:000");
  /* Infinite loop */
  for(;;)
  {
    
    
	
		task2_num++;	//任务2执行次数加1 注意task1_num2加到255的时候会清零!!
        LED1=!LED1;
		
		LCD_ShowxNum(206,111,task2_num,3,16,0x80);  //显示任务执行次数
		LCD_Fill(126,131,233,313,lcd_discolor[13-task2_num%14]); //填充区域
        vTaskDelay(1000);                           //延时1s,也就是1000个时钟节拍	
      
      osDelay(1);
	
  }
  /* USER CODE END StartTask03 */
}

首先是一些one-off工作,
然后进入任务主体,在一次迭代完成后,断点设置好,让出CPU,进入休息时间。等待休息时间结束后,进入下一次迭代。
++++++++++++++++++++++++++++++++++++
来看看一个潜在杀手任务。

void StartTask02(void const * argument)
{
  /* USER CODE BEGIN StartTask02 */
    u8 task1_num=0;
	
	POINT_COLOR = BLACK;

	LCD_DrawRectangle(5,110,115,314); 	//画一个矩形	
	LCD_DrawLine(5,130,115,130);		//画线
	POINT_COLOR = BLUE;
	LCD_ShowString(6,111,110,16,16,1,(u8*)"Task1 Run:000");
    
    
  /* Infinite loop */
  for(;;)
  {
		task1_num++;	//任务执1行次数加1 注意task1_num1加到255的时候会清零!!
		LED0=!LED0;
		
		if(task1_num==5) 
		{
            vTaskDelete(myTask02Handle);//任务1执行5次删除任务2
			
		}
		
		LCD_Fill(6,131,114,313,lcd_discolor[task1_num%14]); //填充区域
		LCD_ShowxNum(86,111,task1_num,3,16,0x80);	//显示任务执行次数
        LCD_ShowxNum(0,140,SysTick->LOAD,8,16,0x80);	//显示任务执行次数
        LCD_ShowxNum(0,160,SystemCoreClock/1000/1000*120,8,16,0x80);	//显示任务执行次数
        LCD_ShowxNum(0,180,SysTick->VAL,8,16,0x80);	//显示任务执行次数
       
      osDelay(1000);
	
  }
  /* USER CODE END StartTask02 */
}

在条件不满足的时候,这个任务就是一个普通任务,但是当条件满足时,这个任务执行了一个操作,就是把另外一个任务删除了。

这种代码风格,是不推荐使用,更好的方式是,专门设置一个管理者任务,由这个管理者任务担当杀手任务的角色。

+++++++++++++++++++++++++++++++++++

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是使用CubeMX配置FreeRTOS和DMA串口接收的示例代码: 1. 配置FreeRTOSCubeMX中,打开Project Manager视图,选择FreeRTOS,并启用RTOS。 2. 配置USART串口 在Pinout & Configuration视图中,选择需要使用的USART串口,并配置为接收模式。例如,将USART2配置为接收模式,波特率为115200。 3. 配置DMA 在Pinout & Configuration视图中,选择需要使用的DMA通道,并配置为USART2的接收通道。例如,将DMA1_Stream5配置为USART2的接收通道。 4. 生成代码并添加接收代码 在CubeMX中,点击"Generate Code"按钮,生成代码。然后,在main.c文件中添加以下代码,以接收USART2的数据: ``` #include "main.h" #include "FreeRTOS.h" #include "task.h" #include "usart.h" /* Buffer used for reception */ uint8_t uartRxBuffer[10]; /* Task handle */ TaskHandle_t xTaskHandle; void vTaskFunction(void *pvParameters) { while (1) { /* Wait for data to be received */ xSemaphoreTake(xSemaphore, portMAX_DELAY); /* Process received data */ printf("Received data: %s\r\n", uartRxBuffer); } } /* DMA USART2 interrupt callback */ void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { BaseType_t xHigherPriorityTaskWoken = pdFALSE; /* Give semaphore to unblock the task */ xSemaphoreGiveFromISR(xSemaphore, &xHigherPriorityTaskWoken); /* Clear the interrupt flag */ __HAL_DMA_DISABLE_IT(huart->hdmarx, DMA_IT_TC); __HAL_UART_CLEAR_OREFLAG(huart); /* Switch context if necessary */ portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } int main(void) { /* Initialize FreeRTOS */ xSemaphore = xSemaphoreCreateBinary(); xTaskCreate(vTaskFunction, "Task", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY, &xTaskHandle); /* Enable DMA interrupt */ __HAL_DMA_ENABLE_IT(&hdma_usart2_rx, DMA_IT_TC); /* Start USART reception */ HAL_UART_Receive_DMA(&huart2, uartRxBuffer, sizeof(uartRxBuffer)); /* Start FreeRTOS scheduler */ vTaskStartScheduler(); while (1) { } } ``` 在上述代码中,我们创建了一个FreeRTOS任务,在任务中等待接收到数据。当数据被接收时,DMA USART2中断会触发回调函数HAL_UART_RxCpltCallback(),它将释放一个信号量,以便任务可以继续处理接收到的数据。请注意,我们使用了xSemaphoreTake()和xSemaphoreGiveFromISR()函数来在任务和中断之间进行同步。 这就是使用CubeMX配置FreeRTOS和DMA串口接收的示例代码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值