前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >单片机程序构架

单片机程序构架

作者头像
杨永贞
发布2020-08-04 17:10:19
1.2K0
发布2020-08-04 17:10:19
举报

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

一种简单的信号量实现:

代码语言: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] = '\0';
		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] = '\0';
PSW_INPUT_LOOP:
	DispString(x, y, buf);
	keyevt = delay_and_wait_key(0, EXIT_KEY_ALL, 0);
	if (keyevt == qevent)
	{
		psw[0] = '\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();
		}
	}
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2014-01-07 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档