最近在南方老树的网站里看到了一些关于将状态机应用于嵌入式软件设计的问题的转帖 也想谈些应用问题,自己也发表过I2C/TWI/USI实现状态机控制的实战例程: I2C(TWI/SMBUS)LPC213X主机通讯例程 TWI一主多从实战程序片段
状态机在EDA中设计很普遍,有些甚至离不开它. 虽然在MCU设计中应用也很广,但有时即使可以也不得不放弃,这主要和MCU的众多因素有关. 不可否认,有时事例用状态机设计很简单,而且思路很清晰. 例如xf.zhu的状态机键盘扫描程序
但是在小容量程序空间的MCU中,可能菜农的"零耗时键盘"更能节省空间. 例如零耗时键盘各种事件及消抖处理模板裸奔程序详解
以菜农的倒塌思想去理解"状态机": 状态机就是我们预先编排的程序序列,我们设计的是如何诱导程序正常地进入此序列. 当程序不遵从序列(状态)时,将会引发错误的出现. 当程序遵从序列(状态)时,程序将根据此时的状态来安排下一步的状态(序列).
现在摘录菜农从未发表过的USI从机实战程序片段举例.
void UsiObj::Start(void) { set_sleep_mode(SLEEP_MODE_IDLE); Status = 0;//状态初始化 USICR = (1 << USISIE) | (1 << USIOIE) | (1 << USIWM1) |(1 << USIWM0) |(1 << USICS1);// |(1 << USICS0); USISR = 0xf0; //清除USISIF,USIOIF,USIPF,USIDC标志和设置计数器16次即接收1个字节8位(SCL上升及下降沿都计数) DDRB &= ~(1 << SCL); //SCL设置为输入方式,SCL信号由主机提供 DDRB &= ~(1 << SDA); //SDA设置为输入方式 Count = 0;//数组指针计数器归零 } 因为在从机收到Start硬件信号后会产生中断,从机运行Usi.Start()函数. 进行状态机的初始化,即Status = 0;程序被逼迫下次中断进行芯片读写地址分析. 下次中断时运行: void UsiObj::Exec(void) { unsigned char tmp; Count &= 0x03;//防止数组溢出 /*------------------------------------------------------------------- 优化示例: DDRB &= ~((1 << SCL) | (1 << SDA)); //SCL设置为输入方式,SCL信号由主机提供 上句将比下两句代码多编译2个字节 DDRB &= ~(1 << SCL); //SCL设置为输入方式,SCL信号由主机提供 DDRB &= ~(1 << SDA); //SDA设置为输入方式 --------------------------------------------------------------------*/ DDRB &= ~(1 << SCL); //SCL设置为输入方式,SCL信号由主机提供 DDRB &= ~(1 << SDA); //SDA设置为输入方式 /*------------------------------------------------------------------- 优化示例: while (PINB & (1 << SCL));//等待SCL=0主机处理结束 上句将比下句代码多编译16个字节 while ((unsigned char)tmp = (PINB & (1 << SCL)));//等待SCL=0主机处理结束 --------------------------------------------------------------------*/ while ((tmp = (PINB & (1 << SCL))) != 0);//等待SCL=0主机处理结束 PORTB &= ~(1 << SCL);//保持低电平 DDRB |= (1 << SCL);//占用SCL总线,以便长期处理 switch(Status) { case 0: //状态TW_START,从机地址判别 Address = USIDR;//取本机滚动地址0b0000000R/W~0b1111111R/W Status = ((Address & 0x01) + 1);//Status下次主发为1W,主收为2R Count = 0;//数组指针计数器归零 SendAck(); break; case 1: //状态TW_MT_SLA_ACK,主发从收模式 Status = 3;//下次进入接收命令状态 USISR = 0x00; //准备接收8位命令 break; case 2: //状态TW_MR_SLA_ACK或TW_MR_DATA_ACK,主收从发模式 Status = 4; USIDR = TxBuffer[Count++] ^ Address;//发送并加密8位数据1个字节 USISR = 0x00; //继续发送8位数据 DDRB |= (1 << SDA);//从机SDA需要输出数据,故设置为输出方式 break; case 3: //状态主发从收模式(接收数据状态) //.... 因为I2C中断是有序的,即模块的状态是"有序"的,也可认为这是硬件状态机. 那么在软件状态机Status=0时,就必定要对应硬件状态TW_START.否则出错处理进入休眠; 在从机收到从机地址后,若地址的最低为D0=0(W)时,在发送ACK后下次进入主发从收模式Status=0+1=1 在从机收到从机地址后,若地址的最低为D0=1(R)时,在发送ACK后下次进入主收从发模式Status=1+1=2
依此类推,从机的接收程序被诱导入了我们实现安排好的圈套~~~ 从状态图来看,它很像二叉树,故"搜索"即诱导程序序列很快,而且条理很清.
当然菜农恶搞的关于USI接口密文传送增加I2C模块拦截难度的问题就只能点到为止了~~~ 否则菜农的菜碗就保不住了~~~ |