AL
Allen Lin 4 years ago
按下右上角綠圈白色三角型可以開始模擬
- 狀態機程序控制_使用Arduino 生態系硬體
- 優點
- 功能
- 寫作目標
- 關於作者
- 本文內容
- 授權使用
- 目錄
- A. 狀態機在Arduino上的實作方式
- A.1 思路
- A.1.1 使用狀態機技巧 使用C#語言 + Raspberry (MONO ) + Arduino MEGA環境來做產線測試治具
- A.1.3 使用狀態機技巧 配合Ardupilot Lua Script (STM32H7XX, )來控制無人機行為
- A.1.4 使用狀態機技巧 配合Python + STM32 CORE (STM32DUINO )來做實作智慧型機車收費架控制
- A.1.5 使用狀態機技巧 搭配CRT+ Arduino UNO來做實作戰鬥機模擬儀表
- A.1.6 使用狀態機技巧,配合Arduino 加上ARM Mbed (顯示部分),實作戰鬥機模擬器儀表
- A.1.7 使用狀態機技巧,使用Arduino 生態系 (NRF52832)加上手機APP V7RC ,實作BLE 遙控坦克車
- A.2 使用狀態機寫程式可以解決的問題
- A.2.1 狀態機是啥?
- A.2.2 用狀態機來寫描述系統有什麼優點 ?
- A.2.3 怎麼用狀態機的思路來拆解一個設計需求
- 後續的程式我們都會使用Wokwi 模器來進行模擬,讀者可以嘗試著改變程式來玩玩看,這樣會更有感覺。
- A.3 可以掌控執行時間的迴圈
- A.4 可以互動/ 測試的程式結構
- A.4.1 CLI 怎麼導入?
- A.4.2 CLI 怎麼用?
- A.5 系統的狀態解構
- A.6 狀態機的實作
- A.7 全部放在一起完工
- A.7.1 同時跑很多個不同的工作
- A.7.2 前景跑前景的,然後叫背景去跑一個時間比較長的工作
- A.7.3 偵測按鈕輸入不用debounce
- A.7.4 動態的改變目前的運行狀態
- A.7.5 簡單的Timeout 機制
- A.7.6 論real-time
- A.8 後記
- C. 補充說明
- C.1 我會好好記得你 使用EEPROM (2022 0303)
- B. 網路上的資源
- B.1 之前寫的,但是永遠寫不完的一個Arduino 使用分享 GITHUB
- B.2 之前寫的,但是永遠沒有時間再多寫的BLOG
寫作目標
關於作者
本文內容
授權使用
目錄
A. 狀態機在Arduino上的實作方式
A.1 思路
A.2 使用狀態機寫程式可以解決的問題
(我用了狀態機寫程式之後,頭腦就靈光了很多,每次考試都考一百分)A.3 可以掌控執行時間的迴圈
/*
* A.3 可以掌控執行時間的迴圈
* 使用Arduino AVR MCU.. (UNO,MEGA.. )
*/
#include <avr/sleep.h>.
#include <avr/wdt.h>
#define VERSION "ARDUINO STATE MACHINE 2022 00"
#define DEBUG 1
#if(DEBUG)
#define DEBUGP(x) Serial.print(x)
#define DEBUGN(x) Serial.println(x)
#else
#define DEBUGP(x)
#define DEBUGN(x)
#endif
//HZ Control
#define HZ_SETTING 10
int mainLoop_count;
unsigned long fast_loopTimer; // Time in miliseconds of main control loop
const int hzCount = (1000 / HZ_SETTING)-1;
const int timeRemind = 1000 / HZ_SETTING;
void(* resetFunc) (void) = 0; //declare reset function @ address 0 call resetFunc(); if you want to reset
#define LED_PIN 13
void hw_init(){
// 啟動Serial
Serial.begin(115200);
// 初始化 IO
pinMode(LED_PIN,OUTPUT);
}
void setup() {
//Setup I/O
hw_init();
//setup WDT
wdt_enable(WDTO_4S);
}
int secCount =0;
void loop() {
if (millis()-fast_loopTimer > hzCount) {
fast_loopTimer = millis();
mainLoop_count++;
wdt_reset(); // Reset WDT ...
/// do things ********************************************************************
Serial.println(mainLoop_count);
if(mainLoop_count%HZ_SETTING==0){
secCount++;
mainLoop_count = 0;
DEBUGP("1 S :");
DEBUGN(secCount);
digitalWrite(LED_PIN,!digitalRead(LED_PIN));
}
// Time Remind for this Loop ------------------------------------------
DEBUGP("Time Remind ms :");
DEBUGN(timeRemind - (millis()-fast_loopTimer));
}
}
A.4 可以互動/ 測試的程式結構
/*
* ARDUINO STATE MACHINE 2022 01
*
*/
#include <SoftwareSerial.h>
#include <SerialCommand.h>
#include <avr/sleep.h>.
#include <avr/wdt.h>
#define VERSION "ARDUINO STATE MACHINE 2022 01"
#define DEBUG 1
#if(DEBUG)
#define DEBUGP(x) Serial.print(x)
#define DEBUGN(x) Serial.println(x)
#else
#define DEBUGP(x)
#define DEBUGN(x)
#endif
SerialCommand SCmd; // The demo SerialCommand object
//HZ Control
#define HZ_SETTING 20
int mainLoop_count;
unsigned long fast_loopTimer; // Time in miliseconds of main control loop
const int hzCount = (1000 / HZ_SETTING)-1;
const int timeRemind = 1000 / HZ_SETTING;
void(* resetFunc) (void) = 0; //declare reset function @ address 0 call resetFunc(); if you want to reset
#define LED_PIN 13
int LED_HZ = 2;
int HZ_COUNT = HZ_SETTING;
void hw_init(){
Serial.begin(115200);
pinMode(LED_PIN,OUTPUT);
}
void process_command()
{
int aNumber,bNumber;
char *arg;
Serial.println("We're in process_command");
arg = SCmd.next();
if (arg != NULL)
{
aNumber=atoi(arg); // Converts a char string to an integer
Serial.print("First argument was: ");
Serial.println(aNumber);
}
else {
Serial.println("No arguments");
aNumber =999;
}
arg = SCmd.next();
if (arg != NULL)
{
bNumber=atol(arg);
Serial.print("Second argument was: ");
Serial.println(bNumber);
}
else {
Serial.println("No second argument");
bNumber =999;
}
switch(aNumber){
case 0 :
Serial.println("0");
break;
case 1 :
Serial.println("1 HZ");
LED_HZ =1 *2;
break;
case 2 :
Serial.println("2 HZ");
LED_HZ =2 *2;
break;
case 5 :
Serial.println("5 Hz");
LED_HZ =5 *2;
break;
case 10 :
Serial.println("10 Hz");
LED_HZ =10 *2;
break;
case 104 :
Serial.println(VERSION);
break;
default:
Serial.println("default");
break;
}
}
// This gets set as the default handler, and gets called when no other command matches.
void unrecognized()
{
Serial.println("What?");
}
void setup() {
//Setup I/O
hw_init();
// register CMD
SCmd.addCommand("P",process_command);
SCmd.setDefaultHandler(unrecognized);
//setup WDT
wdt_enable(WDTO_4S);
}
int secCount =0;
///Test Pattern is ON for 60 SEC, OFF for 180 Sec
void loop() {
// put your main code here, to run repeatedly:
SCmd.readSerial(); // We don't do much, just
// system Loop HZ define in #define HZ_SETTING 5
if (millis()-fast_loopTimer > hzCount) {
fast_loopTimer = millis();
mainLoop_count++;
wdt_reset(); // Reset WDT ...
/// do things
if(mainLoop_count% (HZ_COUNT/LED_HZ) ==0){
digitalWrite(LED_BUILTIN,!digitalRead(LED_BUILTIN));
}
// Time Remind for this Loop ----------------------------------------- // DEBUGP("Time Remind ms :");
// DEBUGN(timeRemind - (millis()-fast_loopTimer));
}
}
A.5 系統的狀態解構
A.6 狀態機的實作
stateLast =stateCurrent 這個會更新stateLast 到最新的狀態,配合LINE#5 這個判斷式 下次就不會再進來這個綠色的區塊了
這兩個變數設定成0 的原因是,每個狀態都會有自己要維持的變數,因為通常這種小系統不會太複雜,所以我們宣搞兩個參數來讓我們知道
taskCounter =0; /// 這個變數用來記憶某件事情的變動
taskTimer =0; //// 這個變數用來記憶這個狀態被呼叫了幾次
taskTimer++; //每次進來這個狀態一次,就會被呼叫一次,所以這個變數可以用來記錄狀態被呼叫的次數
因為整個程式是以特定的時間隔在執行的,所以可以這樣說,我們可以用這個變數來算計算時間
呼應上面LINE#17 的變數變化跟我們程式的設定,每進入這個狀態10 次,就(大約)是1 秒鐘,所以只要算
10 *HZ_SETTING 次,就是經過10 秒了
if(taskTimer> 10*HZ_SETTING ){
stateCurrent = TASK_STATE_2;
}
void state1(){
/// 下面這區只有進來的時候會作一次 -------------------------------
if(stateCurrent!=stateLast){
/// 顯示一下目前的狀態
showStateName(stateCurrent);
/// 把狀態裡面的變數清空
stateLast =stateCurrent;
taskCounter =0;
taskTimer =0;
/// 故意清0 讓大家看一下
secCount=0;
}
taskTimer++;
// 10 秒後離開這個狀態
if(taskTimer> 10*HZ_SETTING ){
stateCurrent = TASK_STATE_2;
}
}
/*
* ARDUINO STATE MACHINE 2022 02
*
*/
//#include <SoftwareSerial.h>
//#include <SerialCommand.h>
#include "SerialCommand.h"
#include <avr/sleep.h>.
#include <avr/wdt.h>
#define VERSION "ARDUINO STATE MACHINE 2022 01"
#define DEBUG 1
#if (DEBUG)
#define DEBUGP(x) Serial.print(x)
#define DEBUGN(x) Serial.println(x)
#else
#define DEBUGP(x)
#define DEBUGN(x)
#endif
SerialCommand SCmd; // The demo SerialCommand object
//HZ Control
#define HZ_SETTING 10
int mainLoop_count;
unsigned long fast_loopTimer; // Time in miliseconds of main control loop
const int hzCount = (1000 / HZ_SETTING) - 1;
const int timeRemind = 1000 / HZ_SETTING;
void (*resetFunc)(void) = 0; //declare reset function @ address 0 call resetFunc(); if you want to reset
#define LED_PIN 13
//----------------- 在這裡定義State 的編號
#define TASK_STATE_INIT 0
#define TASK_STATE_1 1
#define TASK_STATE_2 2
#define TASK_STATE_3 3
#define TASK_STATE_ERROR 100
#define TASK_STATE_UNKNOW 255
byte stateCurrent = TASK_STATE_INIT; /// 初始的狀態
byte stateLast = TASK_STATE_UNKNOW;
unsigned long taskCounter =0; // State 裡面專用的計數器
unsigned long taskTimer =0; // State 裡面專用的計時器
int secCount = 0;
void showStateName(byte st){
switch(st){
case TASK_STATE_INIT:
Serial.println("TASK_STATE_INIT");
break;
case TASK_STATE_1:
Serial.println("TASK_STATE_1");
break;
case TASK_STATE_2:
Serial.println("TASK_STATE_2");
break;
case TASK_STATE_3:
Serial.println("TASK_STATE_3");
break;
case TASK_STATE_ERROR:
Serial.println("TASK_STATE_ERROR");
break;
default:
Serial.println("TASK_STATE_UNKNOW");
break;
}
}
void stateInit(){
/// 下面這區只有進來的時候會作一次 -------------------------------
if(stateCurrent!=stateLast){
/// 顯示一下目前的狀態
showStateName(stateCurrent);
/// 把狀態裡面的變數清空
stateLast =stateCurrent;
taskCounter =0;
taskTimer =0;
}
taskTimer++;
// 2 秒後離開這個狀態
if(taskTimer> 2*HZ_SETTING ){
stateCurrent = TASK_STATE_1;
}
}
void state1(){
/// 下面這區只有進來的時候會作一次 -------------------------------
if(stateCurrent!=stateLast){
/// 顯示一下目前的狀態
showStateName(stateCurrent);
/// 把狀態裡面的變數清空
stateLast =stateCurrent;
taskCounter =0;
taskTimer =0;
/// 故意清0 讓大家看一下
secCount=0;
}
taskTimer++;
// 10 秒後離開這個狀態
if(taskTimer> 10*HZ_SETTING ){
stateCurrent = TASK_STATE_2;
}
}
void state2(){
/// 下面這區只有進來的時候會作一次 -------------------------------
if(stateCurrent!=stateLast){
/// 顯示一下目前的狀態
showStateName(stateCurrent);
/// 把狀態裡面的變數清空
stateLast =stateCurrent;
taskCounter =0;
taskTimer =0;
}
taskTimer++;
// 10 秒後離開這個狀態
if(taskTimer> 10*HZ_SETTING ){
stateCurrent = TASK_STATE_3;
}
}
void state3(){
/// 下面這區只有進來的時候會作一次 -------------------------------
if(stateCurrent!=stateLast){
/// 顯示一下目前的狀態
showStateName(stateCurrent);
/// 把狀態裡面的變數清空
stateLast =stateCurrent;
taskCounter =0;
taskTimer =0;
}
taskTimer++;
// 10 秒後離開這個狀態
if(taskTimer> 10*HZ_SETTING ){
stateCurrent = TASK_STATE_1;
}
}
void stateError(){
/// 下面這區只有進來的時候會作一次 -------------------------------
if(stateCurrent!=stateLast){
/// 顯示一下目前的狀態
showStateName(stateCurrent);
/// 把狀態裡面的變數清空
stateLast =stateCurrent;
taskCounter =0;
taskTimer =0;
}
taskTimer++;
// 10 秒 秀一下目前的狀態
if(( taskTimer % 10*HZ_SETTING )==0 ){
/// 顯示一下目前的狀態
showStateName(stateCurrent);
}
}
void stateUnknow(){
/// 下面這區只有進來的時候會作一次 -------------------------------
if(stateCurrent!=stateLast){
/// 顯示一下目前的狀態
showStateName(stateCurrent);
/// 把狀態裡面的變數清空
stateLast =stateCurrent;
taskCounter =0;
taskTimer =0;
}
taskTimer++;
// 10 秒 秀一下目前的狀態
if(( taskTimer % 10*HZ_SETTING )==0 ){
/// 顯示一下目前的狀態
showStateName(stateCurrent);
}
}
void doSM(){
switch(stateCurrent){
case TASK_STATE_INIT:
stateInit();
break;
case TASK_STATE_1:
state1();
break;
case TASK_STATE_2:
state2();
break;
case TASK_STATE_3:
state3();
break;
case TASK_STATE_ERROR:
stateError();
break;
default:
stateUnknow();
break;
}
}
void hw_init()
{
pinMode(LED_PIN, OUTPUT);
}
void process_command()
{
int aNumber, bNumber;
char *arg;
Serial.println("We're in process_command");
arg = SCmd.next();
if (arg != NULL)
{
aNumber = atoi(arg); // Converts a char string to an integer
Serial.print("First argument was: ");
Serial.println(aNumber);
}
else
{
Serial.println("No arguments");
aNumber = 999;
}
arg = SCmd.next();
if (arg != NULL)
{
bNumber = atol(arg);
Serial.print("Second argument was: ");
Serial.println(bNumber);
}
else
{
Serial.println("No second argument");
bNumber = 999;
}
switch (aNumber)
{
case 0:
Serial.print("stateCueent =");
Serial.println(stateCurrent);
break;
case 1:
//// 如果使用者沒有輸入參數,直接跳走
if(bNumber==999){
return;
}
stateCurrent = bNumber;
Serial.print("stateCueent =");
Serial.println(stateCurrent);
break;
case 2:
Serial.println("2");
break;
case 104:
Serial.println(VERSION);
break;
default:
break;
}
}
// This gets set as the default handler, and gets called when no other command matches.
void unrecognized()
{
Serial.println("What?");
}
void setup()
{
// put your setup code here, to run once:
Serial.begin(115200);
//Setup I/O
hw_init();
// register CMD
SCmd.addCommand("P", process_command); // Converts two arguments to integers and echos them back
SCmd.setDefaultHandler(unrecognized); // Handler for command that isn't matched (says "What?")
//setup WDT
wdt_enable(WDTO_4S);
}
void loop()
{
// put your main code here, to run repeatedly:
SCmd.readSerial(); // We don't do much, just
// system Loop HZ define in #define HZ_SETTING 5
if (millis() - fast_loopTimer > hzCount)
{
fast_loopTimer = millis();
mainLoop_count++;
wdt_reset(); // Reset WDT ...
/// do things
doSM();
if (mainLoop_count % HZ_SETTING == 0)
{
secCount++;
digitalWrite(LED_PIN,!digitalRead(LED_PIN));
DEBUGP("1 S :");
DEBUGN(secCount);
}
// Time Remind for this Loop ---------------------------------------------------------------
// DEBUGP("Time Remind ms :");
// DEBUGN(timeRemind - (millis()-fast_loopTimer));
}
}
A.7 全部放在一起完工
loop(){
digitalWrite(LED_PIN,HIGH)
delay(500)
digitalWrite(LED_PIN,LOW)
delay(500)
}
A.8 後記
C. 補充說明
C.1 我會好好記得你 使用EEPROM (2022 0303)
B. 網路上的資源
B.1 之前寫的,但是永遠寫不完的一個Arduino 使用分享 GITHUB
B.2 之前寫的,但是永遠沒有時間再多寫的BLOG