Arduino之有限状态机

本文分享了一个全自动鱼缸控制系统的开发过程,包括3D模型、硬件和电路设计、HMI及程序编写等内容。采用Arduino和ESP8266,利用状态机优化控制逻辑,实现了鱼缸的智能管理。

        一直在完善之前的全自动鱼缸。在开发过程中遇到各种各样的问题:3D模型设计、硬件设计、电路设计、走线、HMI程序、ESP8266程序、Arduino程序……有时候一个东西重新设计两三次,有的时候重新3D打印两三次,因为才疏学浅所以会各种困难吧。好在硬件和软件都进入最后测试阶段(电导率传感器只在面包板上实验了一下还没加入),这是一个比较“大”的控制系统,当然这个大是指复杂程度,整个系统分为两个子系统:信息交换子系统和本地控制系统。控制系统包含传感器(温度、水位、浊度等)、控制器(Arduino mega)、执行器(水泵、进水管路控制电磁阀、出水管辂控制电磁阀、散热风扇、加热器等)。

        在ESP8266程序中,是在ino文件里面写的代码,因为做的事情简单——只有信息交换,所以只有二三百行代码,逻辑也不混乱;但是,在Arduino程序中包含信息交换、读取传感器、信息和状态分析以及全部决策、控制执行器等。所以,写了若干个类和模块,但是执行器的动作多数比较复杂,如果采取古老的模式用if语句来做,肯定焦头烂额,于是使用了状态机。

我没有自己写通用状态机而使用了arduino-fsm这个库,就以两个栗子来说明一下Arduino-fsm的使用:

一、LED控制

       这个例子很简单,LED控制有三种途径:点击HMI屏幕、远程控制、按设置时间自动。归结起来实际上只有两个:

1、手动控制:点击屏幕、APP控制、MQTT服务器页面控制……无论哪种,最终都由Arduino保存在数组中。

2、自动控制:到达设定时间自动开关。时间设置保存在Arduino数组中,而当前时间是由NTP同步后Arduino计时来的。

首先,我们分析其状态有几个:

1、关闭(默认状态)

2、打开

在fsm库中,每一个状态对应3个回调:进入、持续、离开,在控制LED时,我们只需要用到两个:进入——切换管脚状态,持续——检测按键控制和时间控制。这里需要明确的是:进入和离开只执行一次,而持续是一直被执行的。这部分的定义就像下面这样:

	//默认LED状态(LED关)
	void On_LED_Off_Enter();
	void On_LED_Off_State();
	//打开状态
	void On_LED_On_Enter();
	void On_LED_On_State();

	//关闭状态
	static State State_LED_Off(&On_LED_Off_Enter, &On_LED_Off_State, NULL);
	//打开状态
	static State State_LED_On(&On_LED_On_Enter, &On_LED_On_State, NULL);

然后,是状态机本身:

	//状态机
	static Fsm Fsm_LED(&State_LED_Off);

 

初始化状态机使用的状态就是状态机的默认状态,不需要用额外的代码切换到这一状态。在初始化函数中,我们给状态机添加状态切换:

void ActuatorLED::init(int8_t drivePin, ParameterConfigClass* config)
{
	//Actuator_Pin_LED = drivePin;
	//pinMode(Actuator_Pin_LED, OUTPUT);
	//Param_Config = config;
	Fsm_LED.add_transition(&State_LED_Off, &State_LED_On, 1, NULL);
	Fsm_LED.add_transition(&State_LED_On, &State_LED_Off, 0, NULL);
}

不必关心我注释掉的部分和形参。这里添加了两个状态切换路径,第1个参数是从哪个状态开始变化,第2个状态是要变化到的状态,第3个参数称为事件,更规范的写法是#define Event_Off2On 1,第4个参数是开始切换状态时的回调,在整个程序中都没有用到这个参数。这个参数使得我们可以在一个状态通往另一个状态时做一些额外处理。

介绍完整个框架之后,我们来看一下如何具体实现状态的切换:


void ActuatorLED::Run()
{
	Fsm_LED.run_machine();
}

void ActuatorLED::On_LED_Off_Enter()
{
	digitalWrite(Actuator_Pin_LED, LOW);
	//设置HMI
	HMIMessage::SetVal(HMI_ID_ControlButtonType, HMI_ID_ButtonLED, 0);
	//设置ESP
	ESPMessage::SendClickButton(HMI_ID_ButtonLED, 0);
}

void ActuatorLED::On_LED_Off_State()
{
	//当前时间等于开始时间
	if (Param_Config->GetHMIConfigByIndex(HMI_ID_SettingCurTimeHour)== Param_Config->GetHMIConfigByIndex(HMI_ID_SettingLEDStartHour) &&
		Param_Config->GetHMIConfigByIndex(HMI_ID_SettingCurTimeMinute)== Param_Config->GetHMIConfigByIndex(HMI_ID_SettingLEDStartMinute)) {
		if (HMIMessage::LoadControlButtonValue(HMI_ID_ButtonLED)==0) {	//防止多次调用HMI设置
			//设置HMI
			HMIMessage::SetVal(HMI_ID_ControlButtonType, HMI_ID_ButtonLED, 1);
			//设置ESP
			ESPMessage::SendClickButton(HMI_ID_ButtonLED, 1);
			//设置缓存按钮值。防闪烁(On_LED_On_State中的按钮判断导致闪烁)
			HMIMessage::SaveControlButtonValue(HMI_ID_ButtonLED, 1);	
		}
	}
	if (HMIMessage::LoadControlButtonValue(HMI_ID_ButtonLED) == 1) {
		Fsm_LED.trigger(1);
	}
}

run()方法是在轮询过程中被调用的,就是放在Arduino的loop()过程里。首先,看一下On_Led_Off_Enter()方法:设定管脚,修改屏幕和MQTT服务器上的值,这非常简单,它只在状态进入时被执行。然后,On_LED_Off_State()方法:如果处在State_LED_Off状态,那么这个方法每次运行Fsm_LED.run_machine()时都被调用。我们在这个方法中检查时间和按键,当时间到达或按键被按下时,调用Fsm_LED.trigger()方法,其参数就是Event,前面已经提到过,事件=1时将会从State_LED_Off状态切换到State_LED_On状态。接下来的具体切换过程就是由通用状态机的库来完成的,它会依次执行:从Off状态退出的回调(我们没有定义),进入On状态的回调,这样状态就被切换了。下次运行Fsm_LED.run_machine()时执行将会On_Led_On_State()回调函数。

简单的总结一下设计过程:

1、有哪些状态:定义状态

2、每个状态进、出、持续时需要做什么:定义每个状态对应的回调

3、哪些状态之间可以相互切换:添加状态转换的事件

4、在状态持续函数中进行状态转换判定

5、在Loop循环中运行run_machine()方法

 

二、换水时的混水控制

       这里除了水泵之外,还涉及到另外两个执行器:控制进水管路的三通电磁阀、控制出水管路的三通电磁阀。通过这两个电磁阀,可以分别控制水泵进出水管路从而实现鱼缸进水、循环、出水(当然,也可以和鱼缸没关系,作为一个外进外出的水泵)。所以这个控制过程相对复杂一些,但是和上面的LED区别不大。所以,下面要说的并不是整个有限状态机,而是一个子状态机,这个状态机用于在补水操作时每隔一定时间从鱼缸内进水——这当然会降低效率,但水质变化会更缓慢,对于硝化细菌、水草、鱼都是有好处的。arduino-fsm除了提供上面所说的事件驱动的状态切换,还提供了按时间的状态切换:

	//添加子状态机状态转换
	SubFsm_WaterPump_Flooding.add_timed_transition(&SubState_Flooding, &SubState_Cycle, 3000, NULL);	//从补水到循环
	SubFsm_WaterPump_Flooding.add_timed_transition(&SubState_Cycle, &SubState_Flooding, 3000, NULL);	//从循环到补水

这样一来,我们就可以实现在补水的时候,不断的:外部进水3秒——鱼缸循环3秒,直到水位达到预定值。使用有限状态机来实现控制可以让逻辑更加容易实现。

### 如何在 Arduino 中实现有限状态机 #### 定义有限状态机的概念 有限状态机是一种用于建模具有离散输入和输出系统的计算模型,在单片机和 Arduino 编程中广泛应用,这种思想有助于解决特定类别的问题[^1]。 #### 设计思路 设计有限状态机的核心在于定义好各个可能的状态以及不同状态下对外部事件的反应逻辑。对于Arduino而言,这通常涉及到编写一段可以循环运行并根据不同条件改变其行为模式的代码。 #### 实现方法 为了提高程序效率与响应速度,采用状态机方式来管理设备操作是非常有效的手段之一;比如控制多个舵机时就可以利用此技术达到更好的效果——不仅实现了同步运作还解决了因`delay()`函数造成的延迟难题[^2]。 下面给出一个简单的基于时间触发转换的例子: ```cpp const int ledPin = 13; // LED连接到数字端口13上 unsigned long previousMillis = 0; enum State { OFF, ON }; State currentState = OFF; void setup() { pinMode(ledPin, OUTPUT); } void loop() { unsigned long currentMillis = millis(); switch (currentState){ case OFF: digitalWrite(ledPin, LOW); if(currentMillis - previousMillis >= 5000){ // 经过五秒后切换至ON态 previousMillis = currentMillis; currentState = ON; } break; case ON: digitalWrite(ledPin, HIGH); if(currentMillis - previousMillis >= 2000){ // 经过两秒后返回OFF态 previousMillis = currentMillis; currentState = OFF; } break; } } ``` 上述代码展示了如何创建两个基本状态(开灯与关灯),并通过设定的时间间隔自动在这两者间来回切换。这种方式同样适用于更复杂的场景,只需增加更多枚举成员表示额外的状态,并相应调整内部处理逻辑即可。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

清晨曦月

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值