单片机三种应用程序架构
时间:2021-11-17 05:28:00
1. 简略的前背景次第施行步伐,这种写法是大多数人应用的要领,不需用思索步伐的详细架构,间接经由过程施行次第编写使用步伐即可。
2. 时候片轮询法,此要领是介于次第施行与操纵体系之间的一种要领。
3. 操纵体系,此法应该是使用步伐编写的最高境地。
上面就分手谈谈这三种要领的利害和顺应局限等。 一、次第施行法 这类要领,这使用步伐比拟简略,及时性,并行性要求不过高的情况下是不错的要领,步伐设想简略,思绪比拟清楚。然则当使用步伐比拟庞杂的时间,假如没有一个残缺的流程图,生怕他人很难看懂步伐的运转状况,并且跟着步伐性能的增添,编写使用步伐的工程师的大脑也开端紊乱。即无益于进级保护,也无益于代码优化。自己写个几个比拟庞杂一点的使用步伐,刚开始便是应用此法,终究尽管可以或许完成性能,然则本人的思想始终处于紊乱状况。致使步伐始终不能让本人合意。
这类要领大多数人都市接纳,并且咱们接收的教导也基础都是应用此法。关于咱们这些基础没有进修过数据布局,步伐架构的单片机工程师来讲,无疑很难在使用步伐的设想上有一个很大的进步,也致使了分歧工程师编写的使用步伐很难互相利于和进修。
自己倡议,假如爱好应用此法的网友,假如编写比拟庞杂的使用步伐,一定要先理清脑子,设计好残缺的流程图再编写步伐,不然前因很严重。当然应当步伐自身很简单,此法仍是一个异常必需的抉择。
上面就写一个次第施行的步伐模子,便利和上面两种要领比照:
代 码 /**************************************************************************************
* FunctionName : main()
* Description : 主函数
* EntryParameter : None
* ReturnValue : None
**************************************************************************************/
int main(void)
{
uint8 keyValue; InitSys(); // 初始化 while (1)
{
TaskDisplayClock();
keyValue = TaskKeySan();
switch (keyValue)
{
case x: TaskDispStatus(); break;
...
default: break;
}
}
}
二、时候片轮询法 时候片轮询法,在不少册本中有提到,并且有不少时间都是与操纵体系一路涌现,也就是说不少时间是操纵体系中使用了这一要领。无非咱们这里要说的这个时候片轮询法并非挂在操纵体系下,而是在前背景步伐中应用此法。也是本贴要细致解释和先容的要领。 关于时候片轮询法,虽然有很多册本都有先容,但大多说得其实不体系,只是提提观点罢了。上面自己将细致先容这类模式,并参考他人的代码创建的一个时候片轮询架构步伐的要领,我想将给初学者有必定的自创性。
在这里咱们先先容一下定时器的复用性能。 应用1个定时器,可所以肆意的定时器,这里不做非凡解释,上面假设有3个使命,那末咱们应该做以下事情:
1. 初始化定时器,这里假定定时器的准时中缀为1ms(当然你能够改为10ms,这个和操纵体系同样,中缀过于频仍服从就低,中缀过长,及时性差)。
2. 界说一个数值:
代 码 #define TASK_NUM (3) // 这里界说的使命数为3,暗示有三个使命会应用此定时器准时。 uint16 TaskCount[TASK_NUM] ; // 这里为三个使命界说三个变量寄放时价 uint8 TaskMark[TASK_NUM]; // 异样对应三个标记暗示时候没到暗示准时时候到。
3. 在定时器中缀办事函数增添:
代 码 /**************************************************************************************
* FunctionName : TimerInterrupt()
* Description准时中缀办事函数
* EntryParameter : None
* ReturnValue : None
**************************************************************************************/
void TimerInterrupt(void)
{
uint8 i;
for (i=0; i
if (TaskCount[i])
{
TaskCount[i]--;
if (TaskCount[i] == 0)
{
TaskMark[i] = 0x01;
}
}
}
}
4.
代 码 TaskCount[0] = 20; // 延时20ms TaskMark[0] = 0x00; // 启动使命的定时器 到此咱们只要要在使命判别TaskMark[0] 是不是为0x01即可其余使命增添沟通,至此一个定时器的复用题目完成需求伴侣能够尝尝结果不错哦。。。。。。。。。。。
1.
代 码 这个布局设想异常首要,一个用4个参数正文说的异常细致,这里不在描绘。
2.
代 码 /**************************************************************************************
* FunctionName : TaskRemarks()
* Description 使命标记处置
* EntryParameter : None
* ReturnValue : None
**************************************************************************************/
void TaskRemarks(void)
{
uint8 i; for (i=0; i
if (TaskComps[i].Timer) 时候不为0
{
TaskComps[i].Timer--; // 减去一个节奏
if (TaskComps[i].Timer == 0) 时候减完了
{
TaskComps[i].Timer = TaskComps[i].ItvTime; 复原计时器重新下一次
TaskComps[i].Run = 1; 使命能够运转
}
}
}
}
代 码 /**************************************************************************************
* FunctionName : TaskProcess()
* Description 使命处置
* EntryParameter : None
* ReturnValue : None
**************************************************************************************/
void TaskProcess(void)
{
uint8 i; for (i=0; i
if (TaskComps[i].Run) 时候不为0
{
TaskComps[i].TaskHook(); 运转使命
TaskComps[i].Run = 0; 标记清0
}
}
}
此函数便是判别甚么时间施行那一个使命完成使命治理操纵使用只要要在main()函数挪用此函数就能其实不需要去分手挪用处置使命函数。
到此,一个时候片轮询使用步伐的架构就建好了人人看看是否异常简略呢?此架构只需要两个函数,一个布局体,为了使用方面上面创建一个罗列型变量。
1.
代 码 /**************************************************************************************
* Variable definition
**************************************************************************************/
static TASK_COMPONENTS TaskComps[] =
{
{0, 60, 60, TaskDisplayClock}, 表现时钟
{0, 20, 20, TaskKeySan}, // 按键扫描
{0, 30, 30, TaskDispStatus}, 表现事情状况 // 这里增添使命。。。。 };
③为了可以或许表现按键其余提醒事情界面咱们这里设想每30ms表现一次假如认为反映慢了能够让这些值小一背面的名称是对应的函数名,你必须在使用步伐中编写这函数称号和这三个同样使命。
2.
代 码 好好看看咱们这里界说这个使命清单目标实在便是参数TASKS_MAX其余值是没有详细意思的,只是为了清楚外貌使命瓜葛罢了。
3. 编写使命函数:
代 码 /**************************************************************************************
* FunctionName : TaskDisplayClock()
* Description 表现使命 * EntryParameter : None
* ReturnValue : None
**************************************************************************************/
void TaskDisplayClock(void)
{ } /**************************************************************************************
* FunctionName : TaskKeySan()
* Description : 扫描使命
* EntryParameter : None
* ReturnValue : None
**************************************************************************************/
void TaskKeySan(void)
{
} /**************************************************************************************
* FunctionName : TaskDispStatus()
* Description 事情状况表现
* EntryParameter : None
* ReturnValue : None
**************************************************************************************/
void TaskDispStatus(void)
{
} // 这里增添其余使命。。。。。。。。。
4. 主函数:
代 码 /**************************************************************************************
* FunctionName : main()
* Description : 主函数
* EntryParameter : None
* ReturnValue : None
**************************************************************************************/
int main(void)
{
InitSys(); // 初始化 while (1)
{
TaskProcess(); 使命处置
}
} 到此咱们时候片轮询这个使用步伐的架构成为了只要要在咱们提醒处所增添本人使命函数就能是否很简单有无操纵体系觉得在里面? 不防尝尝把,看看使命之间是否互相其实不滋扰?并行运转呢?当然首要的是,还需要注重使命之间举行数据通报需求接纳全局变量,除此以外还需要注意划分使命以及使命施行时候,在编写使命尽可能使命尽快施行实现。。。。。。。。
这里自己并不想过量先容操纵体系自身由于不是一两句话分明上面列出UCOS下编写应当步伐模子人人能够比照一下,这三种体式格局下的各自的优缺点。
代 码 /**************************************************************************************
* FunctionName : main()
* Description : 主函数
* EntryParameter : None
* ReturnValue : None
**************************************************************************************/
int main(void)
{
OSInit(); // 初始化uCOS-II OSTaskCreate((void (*) (void *)) TaskStart, 使命指针
(void *) 0, // 参数
(OS_STK *) &TaskStartStk[TASK_START_STK_SIZE客栈指针
(INT8U ) TASK_START_PRIO); 使命优先级 OSStart(); // 启动多任务环境
return (0);
}
代 码 /**************************************************************************************
* FunctionName : TaskStart()
* Description 使命建立建立使命实现其余事情
* EntryParameter : None
* ReturnValue : None
**************************************************************************************/
void TaskStart(void* p_arg)
{
OS_CPU_SysTickInit(); // Initialize the SysTick. #if (OS_TASK_STAT_EN > 0)
OSStatInit(); 货色能够丈量CPU使用量
#endif OSTaskCreate((void (*) (void *)) TaskLed, 使命1
(void *) 0, // 不带参数
(OS_STK *) &TaskLedStk[TASK_LED_STK_SIZE - 1], 客栈指针
(INT8U ) TASK_LED_PRIO); // 优先级 // Here the task of creating your
while (1)
{
OSTimeDlyHMSM(0, 0, 0, 100);
}
} 不难看出时候片轮询上风仍是比较大的,即由次第施行好处,也有操纵体系好处布局清楚简略异常轻易懂得。
你是单片机高手仍是菜鸟?看看步伐框架就知道了
从大学列入电子设想大赛到现在,在单片机进修的道路上也有几年试探本人的一些心得体味分享人人。
初学单片机时每每都市纠结于其各个模块性能使用,如串口(232,485种种性能IC操纵机电操纵PWM中缀使用,定时器使用,人机界面使用,CAN总线等. 这是一个进修过程当中必须的阶段,是基本功高兴列入电子设想大赛赛前培训时,MCU四周操纵锻炼踏实经由这个阶段起初打仗分歧的MCU就会发明,都大同小异,各有上风罢了,学任何一种新的MCU都很轻易动手包孕一些庞杂的处理器并且对MCU的编程操纵晋升一个高度表面便是种种核心举行操纵(如果是庞杂算法的运算就会用DSP核心与MCU通讯体式格局普通也就几种时序:IIC,SPI,intel8080,M6800如许看来MCU四周的编程便是一个很简单货色了。
然而这只是嵌入式开辟中的一点外相罢了打仗过多种MCU打仗庞杂设想请求,跑过操纵体系等等咱们在回到单片机的裸机开辟时,就不知不觉的就会考虑到全部步伐设想的架构题目;一个步伐架构,是一个教训的工程师和一个初学者的分水岭。
任何对时间请求刻薄需要都是咱们仇敌需要时间咱们惟独增添硬件原来歼灭比方你要8个数码管表现咱们在没有相干的硬件支撑时间必须用MCU静态扫描体式格局来使其事情精良静态扫描阻拦了MCU处置其余工作。在MCU担负很重场所,我会抉择选用一个近似max8279核心ic来解决这个搅扰;
然而高兴的是,有着许多不是对时间请求刻薄工作:
键盘的扫描,人们敲击键盘速度无限咱们无需及时扫描着键盘以至能够每隔几十ms才去扫描一下;然而这个几十ms隔断咱们的MCU能够实现许多工作;
单片机虽然是裸机奔驰然则每每理想需求抉择咱们必需跑出操纵体系姿势——多任务步伐;
1、键盘扫描;
2、led数码管表现;
3、串口数据需求接收处置;
4、串口需求发送数据;
如何来构架这个单片机步伐将是咱们的重点;
然而必需指出不当的地方:
1、键盘扫描
键盘扫描是单片机经常使用函数如下指出经常使用的键盘扫描步伐紧张障碍体系及时功能处所;
众所周知,一个以后的波形如许假设无效):
在有键按下后,数据线上旌旗灯号涌现一段时候颤动,然后为而后当按键开释旌旗灯号颤动一段时候后变高。当然,在数据线或许过程当中,都有大概涌现一些很窄滋扰旌旗灯号。
unsigned char kbscan(void)
{
unsigned char sccode,recode;
P2=0xf8;
if ((P2&0xf8)!=0xf8)
{
delay(100); //延时20ms去抖--------这里太费时蹩脚
if((P2&0xf8)!=0xf8)
{
sccode=0xfe;
while((sccode&0x08)!=0)
{
P2=sccode;
if ((P2&0xf8)!=0xf8)
break;
sccode=(sccode<<1)|0x01;
}
recode=(P2&0xf8)|0x0f;
return(sccode&recode);
}
}
return (KEY_NONE);
}
键盘扫描需求软件去抖的,这没有争议,然而该函数顶用软件延时往来来往抖(ms级别的延时),这是一个维持体系及时功能的一个隐讳;
While( kbscan() != KEY_NONE)
; //死循环等候
有人如许举行处置:
While(kbsan() != KEY_NONE )
{
Delay(10);
If(Num++ > 10)
Break;
}
即在必定得时间内假如键盘始终按下,将作为无效处置如许尽管致使全部体系别的使命不克不及运转,但也很大程度上减弱体系及时功能由于他用了延时函数;
1、在按键性能比拟简略的情况下咱们依然用上面的kbscan()函数举行扫描,只是把其中去抖用的软件延时去了,把去抖以及判别按键开释用一个函数处置不消软件延时,而是用定时器的计时普通的计时也行实现;代码以下
void ClearKeyFlag(void)
{
KeyDebounceFlg = 0;
KeyReleaseFlg = 0;
}
void ScanKey(void)
{
++KeyDebounceCnt;//去抖计时(这个计时能够放在背景定时器计时函数处置)
KeyCode = kbscan();
if (KeyCode != KEY_NONE)
{
if (KeyDebounceFlg)//进入状况标记位
{
if (KeyDebounceCnt > DEBOUNCE_TIME)//大于划定时候
{
if (KeyCode == KeyOldCode)//按键依旧存在前往键值
{
KeyDebounceFlg = 0;
KeyReleaseFlg开释标记
return; //Here exit with keycode
}
ClearKeyFlag(); //KeyCode != KeyOldCode,只是颤动罢了
}
}else{
if (KeyReleaseFlg == 0)
{
KeyOldCode = KeyCode;
KeyDebounceFlg = 1;
KeyDebounceCnt = 0;
}else{
if (KeyCode != KeyOldCode)
ClearKeyFlag();
}
}
}else{
ClearKeyFlag();//没有按键则清零标记
}
KeyCode = KEY_NONE;
}
在按键情形庞杂情形若有长按键,组合键,连键等一些庞杂性能的按键时间咱们跟倾向于用状态机完成键盘的扫描;
//
avr 单片机 中4*3扫描状态机完成
char read_keyboard_FUN2()
{
static char key_state = 0, key_value, key_line,key_time;
char key_return = No_key,i;
switch (key_state)
{
case最后状况举行3*4的键盘扫描
key_line = 0b00001000;
for (i=1; i<=4; i++) // 扫描键盘
{
PORTD = ~key_line; // 输出行线电平
PORTD = ~key_line; // 必须送2次!!!(注1)
key_value = Key_mask & PIND; // 读列电平
if (key_value == Key_mask)
key_line <<= 1; // 没有按键连续扫描
else
{
key_state++; // 有按键休止扫描
break; // 转消抖确认状况
}
}
break;
case状况判别按键是否颤动惹起的
if (key_value == (Key_mask & PIND)) // 再次读列电平,
{
key_state++; // 转入等候按键开释状况
key_time=0;
}
else
key_state--; // 两次列电平分歧前往状况处置)
break;
case等候按键开释状况
PORTD = 0b00000111全数输入低电平
PORTD = 0b00000111; // 重复送一次
if ( (Key_mask & PIND) == Key_mask)
{
key_state全数为高电平前往状况0
key_return= (key_line | key_value);//获得了键值
}
else if(++key_time>=100假如长期没有开释
{
key_time=0;
key_state=3;//进入状况
key_return= (key_line | key_value);
}
break;
case 3://对于连键,每隔50ms失掉一次键值,windows xp体系便是如许做的
PORTD = 0b00000111全数输入低电平
PORTD = 0b00000111; // 重复送一次
if ( (Key_mask & PIND) == Key_mask)
key_state全数为高电平前往状况0
else if(++key_time>=5) //每隔50MS为一次连击的按键
{
key_time=0;
key_return= (key_line | key_value);
}
break;
}
return key_return;
}
以上用了4个状况普通的键盘扫描只用后面3个状况就能背面一个状况增添性能设想假如按下某个键不放敏捷屡次应当键值,直到开释。在主循环中每隔10ms让该键盘扫描函数施行一次即可咱们当时限为10ms,当然请求其实不严峻。
2、数码管表现
// Timer比拟立室中缀办事,4ms准时
interrupt [TIM0_COMP] void timer0_comp_isr(void)
{
display挪用LED扫描表现
……………………
}
void display(void) // 8位LED数码管静态扫描函数
{
PORTC = 0xff; // 这里封闭是很必要不然数码管发生拖影
PORTA = led_7[dis_buff[posit]];
PORTC = position[posit];
if (++posit >=8 )
posit = 0;
}
3、串口接受数据帧
串口接受中缀体式格局的,这无可厚非假如你试图中缀办事步伐实现一帧数据接受贫苦永久记着中缀办事函数越短越好不然影响这个步伐及时功能。一个数据普通包孕若干个字节咱们需求判别一帧是不是实现,校验是不是精确。在这个过程当中咱们不能用软件延时,更不能用死循环等候体式格局;
至于构成帧,以及查抄事情咱们在主循环中解决而且每次循环中咱们处置一个数据每一个字节数据处置隔断的弹性比较大由于咱们曾经缓存在行列内里。
/*==========================================
==========================================*/
void UARTimeEvent(void)
{
if (TxTimer != 0)//发送需求等候时候递加
--TxTimer;
if (++RxTimer > RX_FRAME_RESET) //
RxCnt假如接收超时(即不完整或许接受一帧实现接受的不完整遮盖
}
/*==========================================
==========================================*/
interrupt [USART_RXC] void uart_rx_isr(void)
{
INT8U status,data;
status = UCSRA;
data = UDR;
if ((status & (FRAMING_ERROR | PARITY_ERROR | DATA_OVERRUN))==0){
RxBuf[RxBufWrIdx] = data;
if (++RxBufWrIdx == RX_BUFFER_SIZE接受数据于缓冲中
RxBufWrIdx = 0;
if (++RxBufCnt == RX_BUFFER_SIZE){
RxBufCnt = 0;
//RxBufferOvf=1;
}
}
}
/*==========================================
放在大循环施行
!=0:数据帧命令字
==========================================*/
INT8U ChkRxFrame(void)
{
INT8U dat;
INT8U cnt;
INT8U sum;
INT8U ret;
ret = RX_NULL;
if (RxBufCnt != 0){
RxTimer接受计数时候,UARTimeEvent关于接受超时做了废弃整帧数据处置
//Display();
cnt = RxCnt;
dat = RxBuf[RxBufRdIdx]; // Get Char
if (++RxBufRdIdx == RX_BUFFER_SIZE)
RxBufRdIdx = 0;
Cli();
--RxBufCnt;
Sei();
FrameBuf[cnt++] = dat;
if (cnt >= FRAME_LEN构成一帧
{
sum = 0;
for (cnt = 0;cnt < (FRAME_LEN - 1);cnt++)
sum+= FrameBuf[cnt];
if (sum == dat)
ret = FrameBuf[0];
cnt = 0;
}
RxCnt = cnt;
}
return ret;
}
以上的代码ChkRxFrame能够放于串口接受数据处置函数RxProcess而后放入主循环中施行即可。以上用一个计时变量RxTimer玄妙的解决接受帧超时废弃处置,它没有用任何等候并且主循环中每次只是接受一个字节数据时候很短。
在meg8咱们代码以下:
// Timer 0 overflow interrupt service routine
interrupt [TIM0_OVF] void timer0_ovf_isr(void)
{
// Reinitialize Timer 0 value
TCNT0=0x83;
// Place your code here
if ((++Time1ms & 0x03) == 0)
TimeIntFlg = 1;
}
void TimeEvent (void)
{
if (TimeIntFlg){
TimeIntFlg = 0;
ClearWatchDog();
display(); // 在4ms事情挪用LED扫描表现,以及喂狗
if (++Time4ms > 5){
Time4ms = 0;
TimeEvent20ms();//在20ms事情咱们处置键盘扫描read_keyboard_FUN2()
if (++Time100ms > 10){
Time100ms = 0;
TimeEvent1Hz();// 在1s事情咱们事情指示灯闪耀
}
}
UARTimeEvent();//串口的数据接受事情,在4ms事情处置
}
}
void RunTime250Hz (INT8U delay)//此延时函数单元为4ms体系基准节奏)
{
while (delay){
if (TimeIntFlg){
--delay;
TimeEvent();
}
TxProcess();
RxProcess();
}
}
好了如许咱们的主函数main()将很简短:
Void main (voie)
{
Init_all();
while (1)
{
TimeEvent关于轮回事情处置
RxProcess(); //串口接受的数据处置
TxProcess();// 串口发送数据处置
}
}