单片机程序构架

2020-08-04 17:10:19 浏览数 (1)

似乎软件架构,只有纯上位机软件才有,其实,嵌入式软件也有架构可言,只有好的架构,才能结构清晰,方便开发和让系统稳定的工作。在有嵌入式操作系统的情况下,可以利用多任务和信号量,事件等设计嵌入式软件。但是在没有操作系统的裸机中,更需要有好的架构。例如利用事件和状态机模拟实现多任务,或者利用定时器和消息队列,信号量等模拟实现多任务,有了多任务就能灵活的设计软件架构。

一种简单的信号量实现:

代码语言:javascript复制
void sem_init( volatile U08 *Sem )
{
	(*Sem)=0;
}

void sem_post( volatile U08 *Sem )
{
	if( 0 == (*Sem) )
		(*Sem)  ;
}

U08  sem_wait( volatile U08 *Sem ) 
{
	if(0 == *Sem)
		return 1;

	(*Sem)--;

	return 0;
}

在一个大的while(1)大循环中,利用信号量实现各个函数(任务)的同步。

代码语言:javascript复制
void Task_SysTime( void )
{   
	static int TaskInitFlg = 0;
	U32	Timer1sCount = 0; 			//时钟计数器个数 
	U32 disstat = 0;
	static int tmrid0 = 0, tmrid1 = 0, tmrid2 = 0, tmrid3 = 0;

	if( 0 == TaskInitFlg )
	{
		OSTimeDlyHMSM( 0, 0, 0, 50 //主要等待任务删除后才创建卡任务
		
		tmrid0 = TimerSet(20);	//定时器0(毫秒定时器)用于键盘、寻卡、定时器中断服务程序,20ms
		tmrid1 = TimerSet(1000);//定时器1(毫秒定时器)用于背显、GPS、定时连接检测、空闲显示
		tmrid2 = TimerSet(500);	//定时器2(毫秒定时器)用于信号显示,500ms
		tmrid3 = TimerSet(500);	//定时器3(毫秒定时器)用于电池显示,500ms

		sem_init( &gSem_EVT_CARDFLG_OK );  					//初始化为没有卡
		
		APP_DisIdle( 2 );								//显示一次时间
		APP_DisVoice();
		
		TaskInitFlg = 1;				//任务初始化完成
	}
	else
        {
		HW_IWDG_ReloadCounter();		 		//清看门狗

		if( 0 == TimerCheck(tmrid0) )
		{
			tmrid0 = TimerSet(20);			//定时器0重新定时, 20ms	

			Timer_ScanKeyboard();		      //20MS键盘扫描 
			Timer_FindCard(); 			//20MS寻卡处理
			TIM20MS_IRQHandler();		//20MS定时器中断服务程序
	       }
      }
}

void Task_Tick( void )
{
	Task_SysError();

	Task_CardProc();

	Task_SysTime();

	Task_MenuProc();

	Task_MtnLink();
	
	Task_CommProc();
}
int main( void )
{
	Sys_Init();	//系统初始化

	while( 1 )                                
	{ 
		Task_Tick();	//任务轮询

		if( 0 == sem_wait( &gSem_EVT_QUIT_APP ) ) 
		   break;     //应用退出
	}
}

以上为借助信号量和定时器实现的一种简单的模拟多任务,其实也算不上是多任务,因为如果一个函数执行时间很长,如何打断它?

以下为借住定时器和任务队列实现的一种模拟多任务:

代码语言:javascript复制
#include <stdio.h>
#include "timTask.h"
#include "disp.h"

/*=====================================================
=	变量定义
=====================================================*/
//任务队列
typedef struct{
	char 	flagState;	//运行方式  0: 无任务
						//			1: 运行 
	char 	flagRun;	//完成状态	0: 正在计数
						//			1: 计数完成
	char 	flagType;	//处理方式	0: 主任务处理
						//			1: 中断处理
	ulong	cntRun;		//运行计数器
	ulong	numCircle;	//循环计数器
	void (*pTaskFunc)(void);	//任务
}TypeTimTask;

TypeTimTask timTaskTab[TIM_TASK_NUMBER];

/*************************************************************************
* 函数原型:
* 功能描述:
* 入口参数:
* 出口参数:
* 返 回 值:
*************************************************************************/
void TimTaskInit(void)
{
	int i;
	
	for (i=0; i<TIM_TASK_NUMBER; i  )
	{
		timTaskTab[i].pTaskFunc = 0;
		timTaskTab[i].cntRun = 0;
		timTaskTab[i].numCircle = 0;
		timTaskTab[i].flagRun = 0;
		timTaskTab[i].flagState = 0;
	}
	SPT_register_call_back(TimTaskUpdate);
	SPT_set(TIM_TASK_PERIOD *64 / 1000);
}

/*************************************************************************
* 函数原型:
* 功能描述:
* 入口参数:
* 出口参数:
* 返 回 值:
*************************************************************************/
short TimTaskAdd(ulong fsttim, ulong cirtim, void (*pTaskFunc)(void), uchar type)
{
	int i;
	int pos = -1;
	//查找位置
	for (i=0; i<TIM_TASK_NUMBER; i  )
	{
		if (timTaskTab[i].pTaskFunc == pTaskFunc)
		{
			pos = i;
			break;
		}
		if ((pos == -1) && (timTaskTab[i].flagState == 0))
		{
			pos = i;
		}
	}
	//任务已满
	if (pos == -1)
	{
		return -1;
	}
	//
	timTaskTab[pos].pTaskFunc = pTaskFunc;
	timTaskTab[pos].cntRun = fsttim / TIM_TASK_PERIOD;
	timTaskTab[pos].numCircle = cirtim / TIM_TASK_PERIOD;
	timTaskTab[pos].flagRun = 0;
	timTaskTab[pos].flagType = type;
	timTaskTab[pos].flagState = 1;
	
	return 0;
}

/*************************************************************************
* 函数原型:
* 功能描述:
* 入口参数:
* 出口参数:
* 返 回 值:
*************************************************************************/
void TimTaskDel(void (*pTaskFunc)(void))
{
	int i;
	
	for (i=0; i<TIM_TASK_NUMBER; i  )
	{
		if (timTaskTab[i].pTaskFunc == pTaskFunc)
		{
			timTaskTab[i].flagState = 0;
			timTaskTab[i].flagRun = 0;
			return;
		}
	}
}

/*************************************************************************
* 函数原型:
* 功能描述:
* 入口参数:
* 出口参数:
* 返 回 值:
*************************************************************************/
void TimTaskUpdate(void)
{
	int i;
	
	SPT_set(TIM_TASK_PERIOD *64 / 1000);
	
	for (i=0; i<TIM_TASK_NUMBER; i  )
	{
		if (timTaskTab[i].flagState != 0)
		{
			if (timTaskTab[i].cntRun != 0)
			{
				timTaskTab[i].cntRun--;
			}
			else
			{
				//判断处理位置
				if (timTaskTab[i].flagType != 0)
					(*timTaskTab[i].pTaskFunc)();
				else
					timTaskTab[i].flagRun = 1;
				//判断重载
				if (timTaskTab[i].numCircle)
					timTaskTab[i].cntRun = timTaskTab[i].numCircle;
				else
					timTaskTab[i].flagState = 0;
			}
		}
	}
}

/*************************************************************************
* 函数原型:
* 功能描述:
* 入口参数:
* 出口参数:
* 返 回 值:
*************************************************************************/
void TimTaskProc(void)
{
	int i;
	
	for (i=0; i<TIM_TASK_NUMBER; i  )
	{
		if (timTaskTab[i].flagRun != 0)
		{
			timTaskTab[i].flagRun = 0;
			(*timTaskTab[i].pTaskFunc)();
		}
	}
}

更为巧妙的是,可以借住函数指针实现一种灵活的菜单和按键实时处理结构。类似于windows下win32的消息驱动机制, 通过中断等方式把实时事件封装成消息。以下为定义界面刷新显示和响应按键处理的结构:

代码语言:javascript复制
#ifndef __PAGE_H_
#define __PAGE_H_

#include "heads.h"

/*=====================================================
=	
=====================================================*/
typedef struct{
	void (* OnPaint)(void);
	void (* OnKey)(short);
}TypePage;

/*=====================================================
=	
=====================================================*/
void WndPageSet(const TypePage *pg, int type = 0);
TypePage * WndGetPage(void);
void WndPageEsc(void);
void WndOnKey(short key);
void WndOnPaint(void);
void WndMenuInit(const char *pmn, char mline);
void WndMenuSelet(int m);
char WndMenuGetSelet(void);
long WndGetPaseword(int x, int y, char *psw, int len, long qevent);
代码语言:javascript复制
#include "pageWnd.h"

/*=====================================================
=	
=====================================================*/
char  flagPaint = 0;
void (* pOnPaint)(void) = 0;
void (* pOnKey)(short) = 0;

const char *pMenuStr;
uchar menuSelect = 0;
uchar menuLine = 0;
uchar menuTop;


TypePage *pageCurrent;
TypePage *pageTreeTab[10];
uchar pageIndex = 0;

/*=====================================================
=	
=====================================================*/
void WndDrawMenu(void);

/*************************************************************************
* 函数原型:
* 功能描述:
* 入口参数:
* 出口参数:
* 返 回 值:
*************************************************************************/
void WndPageSet(const TypePage *pg, int type)
{
	if (pg == &pageMain)		//防止出错
	{
		pageIndex = 0;
	}
	else if (type == 0)
	{
		pageTreeTab[pageIndex  ] = pageCurrent;
	}
	pageCurrent = (TypePage *)pg;
	pOnPaint = pg->OnPaint;
	pOnKey = pg->OnKey;
	flagPaint = 1;
}

/*************************************************************************
* 函数原型:
* 功能描述:
* 入口参数:
* 出口参数:
* 返 回 值:
*************************************************************************/
TypePage * WndGetPage(void)
{
	return pageCurrent;
}

/*************************************************************************
* 函数原型:
* 功能描述:
* 入口参数:
* 出口参数:
* 返 回 值:
*************************************************************************/
void WndPageEsc(void)
{
	TypePage *pg;
	
	if (pageIndex != 0)
	{
		pageIndex--;
		pg = pageTreeTab[pageIndex];
	}
	else
	{
		pg = (TypePage *)&pageMain;
	}
	pageCurrent = pg;
	pOnPaint = pg->OnPaint;
	pOnKey = pg->OnKey;
	flagPaint = 1;
}

/*************************************************************************
* 函数原型:
* 功能描述:
* 入口参数:
* 出口参数:
* 返 回 值:
*************************************************************************/
void WndOnPaint(void)
{
	if (flagPaint != 0)
	{
		flagPaint = 0;
		(*pOnPaint)();
	}
}

/*************************************************************************
* 函数原型:
* 功能描述:
* 入口参数:
* 出口参数:
* 返 回 值:
*************************************************************************/
void WndOnKey(short key)
{
	if (pOnKey != 0)
	{
		(*pOnKey)(key);
	}
}

/*************************************************************************
* 函数原型:
* 功能描述:
* 入口参数:
* 出口参数:
* 返 回 值:
*************************************************************************/
void WndMenuInit(const char *pmn, char mline)
{
	menuSelect = 0;
	pMenuStr = pmn;
	menuLine = mline;
	menuTop = 0;
	WndDrawMenu();
}

/*************************************************************************
* 函数原型:
* 功能描述:
* 入口参数:
* 出口参数:
* 返 回 值:
*************************************************************************/
void WndMenuSelet(int m)
{
	//光标滑动
	if (m > 0)			//下移
	{
		menuSelect  ;
		if (menuSelect == menuLine)
			menuSelect = 0;

		if (menuSelect > menuTop   4)
		{
			if (menuLine < menuTop   4)
				menuTop = menuLine - 4;
			else
				menuTop = menuSelect - 4;
		}
	}
	else if (m < 0)		//上移
	{
		if (menuSelect == 0)
			menuSelect = menuLine - 1;
		else
			menuSelect--;
	}
	//图框移动
	if (menuSelect < menuTop)				//上移
	{
		menuTop = menuSelect;
	}
	else if (menuSelect >= menuTop   4)		//下移
	{
		menuTop = menuSelect - 3;
	}
	
	WndDrawMenu();
}

/*************************************************************************
* 函数原型:
* 功能描述:
* 入口参数:
* 出口参数:
* 返 回 值:
*************************************************************************/
char WndMenuGetSelet(void)
{
	return menuSelect   1;
}

/*************************************************************************
* 函数原型:
* 功能描述:
* 入口参数:
* 出口参数:
* 返 回 值:
*************************************************************************/
void WndDrawMenu(void)
{
	int i;

	char buf[17];
	const char *pmn = pMenuStr   menuTop * 16;

	DispClr();
	for (i=0; i<4; i  )
	{
		if (menuTop   i == menuLine)
			break;
		memcpy(buf, pmn, 16);
		buf[16] = '';
		if (menuSelect == menuTop   i)
			DispSetStyle(DISP_POSITION | DISP_REVERSE | DISP_7x9);
		else
			DispSetStyle(DISP_POSITION | DISP_NORMAL | DISP_7x9);
		DispString(0, i * 2, buf);
		pmn  = 16;
	}
}

/*************************************************************************
* 函数原型:
* 功能描述:
* 入口参数:
* 出口参数:
* 返 回 值:
*************************************************************************/
long WndGetPaseword(int x, int y, char *psw, int len, long qevent)
{
	int pin = 0;
	long keyevt;
	char key;
	char buf[20];
	
	memset(buf, '_', len);
	buf[len] = '';
PSW_INPUT_LOOP:
	DispString(x, y, buf);
	keyevt = delay_and_wait_key(0, EXIT_KEY_ALL, 0);
	if (keyevt == qevent)
	{
		psw[0] = '';
		return keyevt;
	}
	switch (keyevt)
	{
		case EXIT_KEY_0:
			key = '0';
			break;
		case EXIT_KEY_1:
			key = '1';
			break;
		case EXIT_KEY_2:
			key = '2';
			break;
		case EXIT_KEY_3:
			key = '3';
			break;
		case EXIT_KEY_4:
			key = '4';
			break;
		case EXIT_KEY_5:
			key = '5';
			break;
		case EXIT_KEY_6:
			key = '6';
			break;
		case EXIT_KEY_7:
			key = '7';
			break;
		case EXIT_KEY_8:
			key = '8';
			break;
		case EXIT_KEY_9:
			key = '9';
			break;
		case EXIT_KEY_COMM:
			if (pin != 0)
			{
				buf[--pin] = '_';
			}
			goto PSW_INPUT_LOOP;
			break;
		case EXIT_KEY_ENTER:
			psw[pin] = 0;
			return 0;
		default:
			goto PSW_INPUT_LOOP;
	}
	if (pin != len)
	{
		psw[pin] = key;
		buf[pin] = '*';
		pin  ;
	}
	goto PSW_INPUT_LOOP;
}

在软件设计时,如果添加界面和对应的按键处理,很灵活,只需要新添加一个文件就可以了,文件的内容,只需要实现OnPain和对应的OnKey

代码语言:javascript复制
#include "PageMenu.h"


/*=====================================================
=	
=====================================================*/
const char mainMenuTab[] = /*
1234567890123456*/"
1. 现场采集     
2. 数据上传     
3. 存储状态查询 
4. 时间设置     
5. 对比度设置   
6. 恢复出厂设置 
7. 关于         ";

/*=====================================================
=	
=====================================================*/
void PageMenuOnPain(void);
void WndMenuOnKey(short key);

const TypePage pageMenu = {PageMenuOnPain, WndMenuOnKey};

/*************************************************************************
* 函数原型:
* 功能描述:
* 入口参数:
* 出口参数:
* 返 回 值:
*************************************************************************/
void PageMenuOnPain(void)
{
	WndMenuInit(mainMenuTab, 7);
}

/*************************************************************************
* 函数原型:
* 功能描述:
* 入口参数:
* 出口参数:
* 返 回 值:
*************************************************************************/
void WndMenuOnKey(short key)
{
	int res;
	
	switch (key)
	{
		case KEY_F1:
		case KEY_ENTER:
			res = WndMenuGetSelet();
			switch (res)
			{
				case 1:
					WndPageSet(&pageSimp);
					break;
				case 2:
					WndPageSet(&pagePclink);
					break;
				case 3:
					WndPageSet(&pageInquire);
					break;
				case 4:
					WndPageSet(&pageRtc);
					break;
				case 5:
					WndPageSet(&pageGray);
					break;
				case 6:
					SPageInit();
					WndPageSet(&pageMenu, 1);
					break;
				case 7:
					WndPageSet(&pageAbout);
					break;
			}
			break;
		case KEY_F2:
		case KEY_F3:
			WndPageSet(&pageMain);
			break;
		case KEY_1:
			WndPageSet(&pageSimp);
			break;
		case KEY_2:
			WndPageSet(&pagePclink);
			break;
		case KEY_3:
			WndPageSet(&pageInquire);
			break;
		case KEY_4:
			WndPageSet(&pageRtc);
			break;
		case KEY_5:
			WndPageSet(&pageGray);
			break;
		case KEY_6:
			SPageInit();
			WndPageSet(&pageMenu, 1);
			break;
		case KEY_7:
			WndPageSet(&pageAbout);
			break;
		case KEY_UP:
			WndMenuSelet(-1);
			break;
		case KEY_DOWN:
			WndMenuSelet(1);
			break;
		case KEY_POWER:
			WndPageSet(&pagePower);
			break;
	}
}

pageMain,pageAbout,pageRtc,pagePclink等文件,他们的结构很类似。都是实现了OnPaint和OnKey函数。 如:pagePclink.c文件内容: 实现了PagePclinkOnPaint和PagePclinOnKey函数.

CommPclink函数是自己想要实现的功能,可以自己定义。

代码语言:javascript复制
#include "pagePclink.h"

/*=====================================================
=	
=====================================================*/
void PagePclinkOnPaint(void);
void PagePclinOnKey(short key);

const TypePage pagePclink = {PagePclinkOnPaint, PagePclinOnKey};

/*************************************************************************
* 函数原型:
* 功能描述:
* 入口参数:
* 出口参数:
* 返 回 值:
*************************************************************************/
void PagePclinkOnPaint(void)
{
	DispClr();
	DispSetStyle(DISP_CENTER | DISP_REVERSE | DISP_7x9);
	DispString(0, 0, "    数据上传    ");
	DispSetStyle(DISP_POSITION|DISP_NORMAL|DISP_7x9);
	DispString(0, 6, "[连接]    [返回]");
}

/*************************************************************************
* 函数原型:
* 功能描述:
* 入口参数:
* 出口参数:
* 返 回 值:
*************************************************************************/
void PagePclinOnKey(short key)
{
	switch (key)
	{
		case KEY_F1:
			CommPclink();
			break;
		case KEY_F3:
			WndPageEsc();
			break;
	}
}
代码语言:javascript复制
#ifndef __PAGE_POWER_H_
#define __PAGE_POWER_H_

#include "pageWnd.h"

/*=====================================================
=	
=====================================================*/
extern const TypePage pagePower;

#endif

#include "PagePower.h"
#include "disp.h"

/*=====================================================
=	
=====================================================*/
void PagePowerOnPaint(void);
void PagePowerOnKey(short key);

const TypePage pagePower = {PagePowerOnPaint, PagePowerOnKey};

/*************************************************************************
* 函数原型:
* 功能描述:
* 入口参数:
* 出口参数:
* 返 回 值:
*************************************************************************/
void PagePowerOnPaint(void)
{
	DispClr();
	DispSetStyle(DISP_CENTER | DISP_REVERSE | DISP_7x9);
	DispString(0, 0, "    电源管理    ");
	DispSetStyle(DISP_POSITION|DISP_NORMAL|DISP_7x9);
	DispString(0, 2, "  [Enter] 关机  ");
	DispString(0, 4, "  [F3   ] 返回  ");
}


/*************************************************************************
* 函数原型:
* 功能描述:
* 入口参数:
* 出口参数:
* 返 回 值:
*************************************************************************/
void PagePowerOnKey(short key)
{
	switch (key)
	{
		case KEY_ENTER:
		case KEY_POWER:
			Halt_EH0218(4);
			SysInit();
			break;
		case KEY_F3:
			WndPageEsc();
			break;
	}
}

这样的一种结构,很灵活,在主函数中只需要这样调用:

代码语言:javascript复制
int main(void)
{
	short key;
	typ_msg_word smw;
	
	SysInit();

	for ( ; ; )
	{
		/*
		  界面刷新
		*/
		WndOnPaint();

		/*
		  消息处理
		*/
		smw.s_word = sys_msg(SM_STAY_AWAKE);	//用SM_GOTO_SLEEP串口就不能用
		//按键处理
		if (smw.bits.key_available)	
		{
			LcdOffDelay(LCD_OFF_DELAY);
			
			key = KEY_read();
			if (key != -1)
			{
				WndOnKey(key);
			}
		}
		//插入充电电源
		if (smw.bits.charger_on)
		{
			LcdOffDelay(LCD_OFF_DELAY);
		}
		//断开充电电源
		if (smw.bits.charger_off)
		{
			LcdOffDelay(LCD_OFF_DELAY);
			RefreshBattery();
		}
		//串口
		if (smw.bits.comm_data)
		{
			CommReceive();
		}
		//实时任务
		if (smw.bits.time_out)
		{
			TimTaskProc();
		}
	}
}

0 人点赞