嵌入式_一种非常简单实用的基于GD32的裸机程序框架

嵌入式_一种非常简单实用的基于GD32的裸机程序框架

搜索了一下关于GD或ST裸机程序的问题,网上有非常多也非常的例子,但是针对裸机开发的程序框架却比较少,这里简单整理了一下在项目中使用过的一种比较小巧便携的裸机程序框架(确切点说算不上框架,只能说一种写法)。


文章目录

  • 嵌入式_一种非常简单实用的基于GD32的裸机程序框架
  • 前言
  • 一、框架原理
  • 二、步骤分析
    • 1.定时器中断配置
    • 2.中断函数
    • 3.任务管理函数
    • 4.主函数
  • 总结


前言

之前常见的裸机程序框架就是流水账框架,比如某个做STM32开发系列教程的****团队,先是一堆初始化,然后一个大while循环,循环里面可以无限添加代码,一个功能一个函数往里加,随着经手的coder越来越多,一行一行往上加,慢慢得就成了屎山代码。我在第一家公司曾经接收手一个七代单传流传下来的祖传代码,一个main函数就有260多行,while循环占了一大半,结构冗余,效率极低。后来花大力气改了框架直接重构。

这个框架基于GD32F103CBT6硬件平台,标准的72MHz系统时钟, 使用标准库GD32F10x_Firmware_Library_V1.0.0此库坑多慎用


一、框架原理

框架大致原理就是使用定时器模拟一个实时操作系统,每1ms产生一个中断并进行累加计数,计数到10、50、100、500、1000时会对相关的标记位置位,表示此时间对应的函数应该执行,在主函数中while(1)循环就是对标记位进行判断并执行对应的任务函数和对标记位进行复位,说白了就是模拟的一个非常简单的μC-OS操作系统,大致原理图如图所示。
嵌入式_一种非常简单实用的基于GD32的裸机程序框架_第1张图片

二、步骤分析

1.定时器中断配置

为了不占用资源,我们选择使用系统自带的滴答定时器,是其产生1ms中断,并且要保证其优先级最高,使其不被其他中断打断。

代码如下(示例):

/**
  * @brief  Configure SysTick.
  * @param  None
  * @retval None
  */
void SysTick_Configuration(void)
{    
    /* Setup SysTick Timer for 108Hz interrupts */
    if (SysTick_Config(SystemCoreClock / 1000)) // 1ms tick
    {
        /* Capture error */
        while (1);
    }
    /* Configure the SysTick handler priority */
    NVIC_SetPriority(SysTick_IRQn, 0x00);
}

2.中断函数

中断函数中,我们使用静态局部变量对中断次数进行累计并判断,在时间性要求不高的情况下,为了保证各个任务可错开,我们进行取模运算来判断是否触发相关任务,如果触发则对标记位数组中的对应位置位。
代码如下(示例):

#define TASKFUNNUMBER  		6
#define TASKMAXCOUNTER		6000

typedef struct
{
	uint16_t Period;
	uint16_t Remainder;
}TaskTimeType;

static uint8_t TaskFlag[TASKFUNNUMBER] = {0, 0, 0, 0, 0, 0};
static TaskTimeType TimeArr[TASKFUNNUMBER] = 
 {
   {10, 2},
   {50, 49},
   {100, 97},
   {500, 497},
   {1000, 999},
   {2000, 1999}
 };
 
void Task_1msNotification(void)
 {
   static uint16_t Counterms = 0;
   uint8_t i = 0;
  
	 if(1msCounter > TASKMAXCOUNTER)
	 {
			1msCounter = 0;
	 }
	 else
	 {
			Counterms ++;

			for(i = 0; i < TASKFUNNUMBER; i++)
			{
				if((Counterms % TimeArr[i].Period) == TimeArr[i].Remainder)
				{
					TaskFlag[i] = TRUE;
				}
			}
	 }
}

3.任务管理函数

轮询判断标记位数组中的各个标记位,如果被置位,则通过函数指针调用相关时间片函数即可。
代码如下(示例):

typedef void (*TaskFunType)(void);

static TaskFunType TaskFunPtr[TASKFUNNUMBER] = 
 {
   &TASK_10msFunc,
   &TASK_50msFunc,
   &TASK_100msFunc,
   &TASK_500msFunc,
   &TASK_1000msFunc
   &TASK_2000msFunc
 };
 
void MainFunction(void)
 {
   uint8_t i = 0;
   
   for(i = 0; i < TASKFUNNUMBER; i++)
   {
     if(TaskFlag[i] == TRUE)
     {
       if(TaskFunPtr[i] != NULL)
       {
         TaskFunPtr[i]();
         TaskFlag[i] = FALSE;
       }
     }
   }
 }

4.主函数

主函数中先对各个模块进行初始化,初始化完成后在while(1)循环中调用上面的MainFunction()函数,轮流调用执行时间片函数,其他各个模块例如LED或串口模块的使用只需要在对应的时间片函数中即可。
代码如下(示例):

int main(void)
{
  SystemInit();
  SysTick_Configuration();
  IO_Init();
  USART0_Init();
  SPI_Init();
  LED_Init();
  Appliction_Init();
  FWDG_Init();
  while(1)
  {
    MainFunction();
  }  
}

void TASK_10msFunc(void)
{
	//Appliction();
}
void TASK_50msFunc(void)
{
	//LED_ManageFunc();
	//SPI_SendManageFunc();
}
void TASK_100msFunc(void)
{
}
void TASK_500msFunc(void)
{	
	//Usart_SendManageFunc();
}
void TASK_1000msFunc(void)
{
	//LED_ManageFunc();
}
void TASK_2000msFunc(void)
{
	//FWDG_FeedWdg();
}

总结

之前在一个项目中使用这种框架,还是比较方便,可以根据各个模块的时间要求选择对应的时间片函数,比如我需要1秒反转一次LED灯,我就直接将反转函数写入1s任务中,不会像“流水账”框架由于某些任务执行时间长短不一而导致我的LED反转时间误差较大,这种框架(确切点说算不上框架)主打就是一个简单便携,如果有什么疑问欢迎留言讨论,有言必回!

你可能感兴趣的:(单片机嵌入式,单片机,stm32,c语言)