嵌入式软件开发之程序架构(一)

1 前言

该内容是工作一年来通过上网或其他方式不断搜索、实践、总结出来的嵌入式软件开发经验(本文仅适用于单片机的裸机开发),希望能帮到正在学习这方面的朋友,如有不好的地方,请多多见谅;在嵌入式软件开发过程中,程序架构的搭建尤为重要,下面介绍三种常用的程序框架设计方案:

  • 前后台顺序执行法
  • 时间片论法
  • 操作系统


2 程序框架设计

2.1 前后台顺序执行法

这是初学者们常用的程序框架设计方案,不用考虑太多东西,代码简单,或者对系统的整体实时性和并发性要求不高;初始化后通过 while(1){ } 或 for(;;){ } 循环不断调用自己编写完成的函数,也基本不考虑每个函数执行所需要的时间,大部分情况下函数中或多或少都存在毫秒级别的延时等待。

  • 优点:对于初学者来说,这是最容易也是最直观的程序架构,逻辑简单明了,适用于逻辑简单,复杂度比较低的软件开发。
  • 缺点:实时性低,由于每个函数或多或少存在毫秒级别的延时,即使是1ms,也会造成其他函数间隔执行时间的不同,虽然可通过定时器中断的方式,但是前提是中断执行函数花的时间必须短。当程序逻辑复杂度提升时,会导致后来维护人员的大脑混乱,很难理清楚该程序的运行状态。

以下是我在学校期间做的寝室防盗系统的部分代码(当时也存在部分BUG,没有解决。现在再看,其实很多问题,而且比较严重,比如中断服务函数内竟然有3000ms延时,这太可怕了,还有串口发送等等;由于实时性要求不算太高,因此主函数中的毫秒级别延时对系统运行没有多大影响,当然除BUG外;若是后期需要维护,那就是一个大工程,还不如推翻重写):

 int main(void)
 {	
    u8 temperature;  	    
    u8 humidity;   
	int a;
	delay_init();
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
//	 I2c_init(); 
	uart2_Init(9600);
	uart_init(9600);	 //串口初始化为115200	 
	TIM3_Int_Init(4999,7199);
	ds1302_init();
	while(DHT11_Init())	//DHT11初始化	
	{
		led2=0;
	}
	a1602_init();		
//	 Ds1302Init();
	EXTIX_Init();			
	GPIOX_Init();
	lcd12864_INIT();
	LcdInit();
	beep_init();
	RED_Init();
	led1=1;
	beep=0;
	while(1)
	{
		for(a=0;a<11;a++)
		{
			num[a+3]=At24c02Read(a+2)-208;
			delay_us(10);					
		}
		for(a=0;a<6;a++)
		{
			shuru[a]=At24c02Read(a+13)-208;
			delay_us(10);				
		}		
		delay_ms(10);  
		RED_Scan();
		Ds1302ReadTime();					//读取ds1302的日期时间
		shi=At24c02Read(0);				//读取闹钟保存的数据
		delay_ms(10);
		fen=At24c02Read(1);				//读取闹钟保存的数据			
		usart2_scan();						//蓝牙数据扫描
		usart2_bian();						//蓝牙处理数据
		usart2_gai();
		nao_scan();
		k++;
		if(k<20)
		{
			if(k==1)
				LcdWriteCom(0x01);  //清屏
				LcdDisplay();							//显示日期时间
 
		}
		if(RED==0)
			RED_Scan();
 
				
		if(k>=20&&k<30)
		{
			if(k==20)
				LcdWriteCom(0x01);  //清屏
			Lcddisplay();							//显示温湿度
			LcdWriteCom(0x80+6);	
			DHT11_Read_Data(&temperature,&humidity);	//读取温湿度值	
			Temp=temperature;Humi=humidity;
			LcdWriteData('0'+temperature/10);
			LcdWriteData('0'+temperature%10);
			LcdWriteCom(0x80+0X40+6);	
			LcdWriteData('0'+humidity/10);
			LcdWriteData('0'+humidity%10);
		}
		if(k==30)
			k=0;
		lcd12864();								//显示防盗闹钟状态
 
	}		
}
 
 
//定时器3中断服务程序
void TIM3_IRQHandler(void)   //TIM3中断
{
	int i;
	if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)  //检查TIM3更新中断发生与否
	{
		TIM_ClearITPendingBit(TIM3, TIM_IT_Update  );  //清除TIMx更新中断标志 
		if(key1==1&&FEN-fen==0&&SHI-shi==0)				//时间一到闹钟响起
		{
				f=1;						
		}
//		if(key1==0||FEN-fen!=0||SHI-shi!=0)
		else
		{
				f=0;
		}	
			if(USART_RX_BUF[0]=='R'&&USART_RX_BUF[1]=='I'&&USART_RX_BUF[2]=='N'&&USART_RX_BUF[3]=='G')
		{
			key0=1;
			for(i=0;i<17;i++)
			{
				USART_SendData(USART1, num[i]);//向串口1发送数据
				while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待发送结束
				USART_RX_STA=0;	
			}	
			delay_ms(3000);
			for(i=0;i<3;i++)
			{
				USART_SendData(USART1, num1[i]);//向串口1发送数据
				while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待发送结束
				USART_RX_STA=0;	
			}			
		}
	}
}

2.2 时间片论法

介于“前后台顺序执行法”和“操作系统”之间的一种程序架构设计方案。该设计方案需能帮助嵌入式软件开发者更上一层楼,在嵌入式软件开发过程中,若遇到以下几点,那么该设计方案可以说是最优选择,适用于程序较复杂的嵌入式系统;

  1. 目前的需求设计需要完全没有必要上操作系统
  2. 任务函数无需时刻执行,存在间隔时间(比如按键,一般情况下,都需要软件防抖,初学者的做法通常是延时10ms左右再去判断,但10ms极大浪费了CPU的资源,在这段时间内CPU完全可以处理很多其他事情)
  3. 实时性有一定的要求

该设计方案需要使用一个定时器,一般情况下定时1ms即可(定时时间可随意定,但中断过于频繁效率就低,中断太长,实时性差),因此需要考虑到每个任务函数的执行时间,建议不能超过1ms(能通过程序优化缩短执行时间则最好优化,如果不能优化的,则必须保证该任务的执行周期必须远大于任务所执行的耗时时间),同时要求主循环或任务函数中不能存在毫秒级别的延时。

如何确定每个函数的任务周期呢?根据任务的耗时和效果决定、如按键扫描任务周期为 10ms(为了提高响应),指示灯控制任务周期为 100ms(通常情况下最高100ms的闪烁频率正好,特殊需求除外),LCD/OLED 显示周期为 100ms(通过这种通过SPI/IIC等接口的方式耗时大约在 1~10ms,甚至更长,所以任务周期必须远大于耗时,同时为了满足人眼所能接受的刷屏效果,也不能太长,100ms 的任务周期比较合适)等

以下介绍两种不同的实现方案,分别针对无函数指针概念的朋友和想进一步学习的朋友。

2.2.1 无函数指针的设计方式

/**
  * @brief      主函数.
  * @param      None.
  * @return     None.
  */
int main(void)
{
    System_Init();
    
    while (1)
    {
        
        if (TIM_1msFlag)                            // 1ms
        {
            CAN_CommTask();                         // CAN发送/接收通信任务
            
            TIM_1msFlag = 0;
        }
        
        if (TIM_10msFlag)                           // 10ms
        {
            KEY_ScanTask();                         // 按键扫描处理任务
            
            TIM_10msFlag = 0;
        }
        
        if (TIM_20msFlag)                           // 20ms
        {
            LOGIC_HandleTask();                     // 逻辑处理任务
 
            TIM_20msFlag = 0;
        }
        
        if (TIM_100msFlag)                          // 100ms
        { 
            LED_CtrlTask();                         // 指示灯控制任务
            
            TIM_100msFlag = 0;
        }    
        
        if (TIM_500msFlag)                          // 500ms
        {
            TIM_500msFlag = 0;
        }    
        
        if (TIM_1secFlag)                           // 1s
        {
            WDog_Task();                            // 喂狗任务
            
            TIM_1secFlag = 0;
        }  
    }
}
 
/**
  * @brief      定时器3中断服务函数.
  * @param      None.
  * @return     None.
  */
void TIM3_IRQHandler(void)
{
    if(TIM_GetITStatus(TIM3,TIM_IT_Update) == SET) // 溢出中断
    {
        sg_1msTic++;
        
        sg_1msTic % 1 == 0 ? TIM_1msFlag = 1 : 0;
        
        sg_1msTic % 10 == 0 ? TIM_10msFlag = 1 : 0;
        
        sg_1msTic % 20 == 0 ? TIM_20msFlag = 1 : 0;
        
        sg_1msTic % 100 == 0 ? TIM_100msFlag = 1 : 0;
        
        sg_1msTic % 500 == 0 ? TIM_500msFlag = 1 : 0;
        
        sg_1msTic % 1000 == 0 ? (TIM_1secFlag  = 1, sg_1msTic = 0) : 0;
    }
    
    TIM_ClearITPendingBit(TIM3,TIM_IT_Update);  // 清除中断标志位
}

2.2.2 函数指针的设计方式

/**
  * @brief 任务函数相关信息结构体定义.
  */
typedef struct{
    uint8 m_runFlag;                 /*!< 程序运行标记:0-不运行,1运行 */
    uint16 m_timer;                  /*!< 计时器 */
    uint16 m_itvTime;                /*!< 任务运行间隔时间 */
    void (*m_pTaskHook)(void);       /*!< 要运行的任务函数 */
} TASK_InfoType; 
 
#define TASKS_MAX     5              // 定义任务数目
 
/** 任务函数相关信息 */
static TASK_InfoType sg_tTaskInfo[TASKS_MAX] = {
    {0, 1, 1, CAN_CommTask},         // CAN通信任务
    {0, 10, 10, KEY_ScanTask},       // 按键扫描任务
    {0, 20, 20, LOGIC_HandleTask},   // 逻辑处理任务
    {0, 100, 100, LED_CtrlTask},     // 指示灯控制任务
    {0, 1000, 1000, WDog_Task},      // 喂狗任务
};
 
 
/**
  * @brief      任务函数运行标志处理.
  * @note       该函数由1ms定时器中断调用
  * @param      None.
  * @return     None.
  */
void TASK_Remarks(void)
{
    uint8 i;
 
    for (i = 0; i < TASKS_MAX; i++)
    {
         if (sg_tTaskInfo[i].m_timer)
        {
            sg_tTaskInfo[i].m_timer--;
 
            if (0 == sg_tTaskInfo[i].m_timer)
            {
                 sg_tTaskInfo[i].m_timer = sg_tTaskInfo[i].m_itvTime;
                 sg_tTaskInfo[i].m_runFlag = 1;
            }
        }
   }
}
 
/**
  * @brief      任务函数运行处理.
  * @note       该函数由主循环调用
  * @param      None.
  * @return     None.
  */
void TASK_Process(void)
{
    uint8 i;
 
    for (i = 0; i < TASKS_MAX; i++)
    {
        if (sg_tTaskInfo[i].m_runFlag)
        {
             sg_tTaskInfo[i].m_pTaskHook();         // 运行任务
             sg_tTaskInfo[i].m_runFlag = 0;         // 标志清0
        }
    }   
}
 
/**
  * @brief      主函数.
  * @param      None.
  * @return     None.
  */
int main(void)
{
    System_Init();
    
    while (1)
    {
        TASK_Process();
    }
}
 
/**
  * @brief      定时器3中断服务函数.
  * @param      None.
  * @return     None.
  */
void TIM3_IRQHandler(void)
{
    if(TIM_GetITStatus(TIM3,TIM_IT_Update) == SET) // 溢出中断
    {
        TASK_Remarks();
    }
    
    TIM_ClearITPendingBit(TIM3,TIM_IT_Update);     // 清除中断标志位
}


2.3 操作系统

       嵌入式操作系统EOS(Embedded OperatingSystem)是一种用途广泛的系统软件,过去它主要应用于工业控制和国防系统领域,而对于单片机来说,比较常用的有 UCOS、FreeRTOS、RT-Thread Nano 和 RTX 等多种抢占式操作系统(其他如 Linux 等操作系统不适用于单片机)

操作系统和“时间片论法”,在任务执行方面来说,操作系统对每个任务的耗时没有过多的要求,需要通过设置每个任务的优先级,在高优先级的任务就绪时,会抢占低优先级的任务;操作系统相对复杂,因此这里没有详细介绍了。

关于如何选择合适的操作系统([RTOS]uCOS、FreeRTOS、RTThread、RTX等RTOS的对比之特点):

  • UCOS:网上资料丰富,非常适合学习,但是在产品上使用则需要收费
  • FreeRTOS:使用免费,因此很多产品都在用
  • RT-Thread:国产物联网操作系统,有着十分丰富的组件,也免费,资料:RT-Thread 文档中心
  • RTX:为ARM和Cortex-M设备设计的免版税,确定性的实时操作系统

借网上一张对比图:


总结

从上述的对比中可以看出,时间片轮询法的优势还是比较大的,它既有前后台顺序执行法的优点,也有操作系统的优点。结构清晰,简单,非常容易理解,所以这种是比较常用的单片机设计框架。

下篇:嵌入式软件开发之程序分层(二)

转自:嵌入式软件开发之程序架构(一)_程序架构设计_大橙子疯的博客-CSDN博客

  • 5
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 嵌入式软件开发是一个涉及到嵌入式系统的设计和实现的重要领域。嵌入式系统是指被嵌入在其它设备、产品或系统中的计算机系统,用于控制或实现特定功能。在嵌入式软件开发中,开发人员需要对硬件平台有深入的了解,掌握底层操作系统、驱动程序、中间件、应用程序等一系列技术,以保证嵌入式系统的稳定性、安全性和效率。 作为专业嵌入式软件开发人员,开发人员需要具备扎实的编程基础,熟悉各种嵌入式开发环境和开发工具。在软件开发过程中,需要充分理解客户需求,合理设计软件架构,考虑资源和性能的平衡,同时确保软件的可维护性和可扩展性,并在代码实现和测试阶段完成高质量的工作。 目前,嵌入式系统已经广泛应用于各行各业,如智能家居、汽车行业、医疗设备、工业控制等等。因此,嵌入式软件开发的市场需求十分巨大,为广大技术专家提供了广阔的就业发展空间。在这个领域,不断学习和自我提升是非常重要的,只有不断跟进行业发展,充分掌握新技术,才能在竞争中脱颖而出。 ### 回答2: 嵌入式软件开发是一种针对嵌入式系统制定软件的技术,这种系统通常用于控制设备、机器或其他实时应用。嵌入式软件开发需要对硬件的理解,涉及多种编程语言开发工具。专业的嵌入式软件开发人员需要具备良好的计算机基础知识和编程技能,能够理解硬件功能和系统交互,同时具有与设备制造商和不同功能团队沟通交流的能力。 在进行嵌入式软件开发时,需要考虑设备体积、电池寿命、系统安全等多种因素。随着移动端设备的发展,嵌入式软件开发越来越重要。嵌入式软件开发覆盖的领域越来越广泛,包括智能家居、智能出行、工业自动化、医疗器械等。 在CSDN上,有许多嵌入式软件开发领域的专家,他们可以为初学者和有经验的开发人员提供丰富的经验和知识。CSDN还提供许多开发资源,包括开发工具、开发项目案例和开发论坛。这些资源可以为嵌入式软件开发人员提供更好的学习和交流平台。 总之,嵌入式软件开发是一项非常重要的技术,需要开发人员具备丰富的知识和经验。CSDN上的专家和资源可以为开发人员提供支持和帮助,使他们能够更好地进行嵌入式软件开发

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值