├── .github └── workflows │ ├── cmake_clang.yml │ ├── cmake_ubuntu.yml │ └── cmake_windows.yml ├── .gitignore ├── CMakeLists.txt ├── CentrifugeTest.c ├── CentrifugeTest.h ├── CentrifugeTest.png ├── DataTypes.h ├── Fault.cpp ├── Fault.h ├── LICENSE ├── LockGuard.cpp ├── LockGuard.h ├── Motor.c ├── Motor.h ├── Motor.png ├── README.md ├── StateMachine.c ├── StateMachine.h ├── fb_allocator.c ├── fb_allocator.h ├── main.c ├── sm_allocator.c ├── sm_allocator.h ├── x_allocator.c └── x_allocator.h /.github/workflows/cmake_clang.yml: -------------------------------------------------------------------------------- 1 | name: Clang 2 | 3 | on: 4 | push: 5 | branches: 6 | - master # Trigger on push to 'master' branch 7 | pull_request: 8 | branches: 9 | - master # Trigger on pull request targeting 'master' branch 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest # Use Ubuntu environment for the build 14 | 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v2 # Checkout the repository code 18 | 19 | - name: Configure CMake with Clang 20 | run: cmake -S . -B Build -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ # Configure CMake with Clang as the compiler 21 | 22 | - name: Build 23 | run: cmake --build Build # Build the project using CMake 24 | 25 | - name: Run C_StateMachineApp 26 | run: ./Build/C_StateMachineApp # Run the built executable 27 | -------------------------------------------------------------------------------- /.github/workflows/cmake_ubuntu.yml: -------------------------------------------------------------------------------- 1 | name: Ubuntu 2 | 3 | on: 4 | push: 5 | branches: 6 | - master # Trigger on push to 'master' branch 7 | pull_request: 8 | branches: 9 | - master # Trigger on pull request targeting 'master' branch 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest # Use Ubuntu environment for the build 14 | 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v2 # Checkout the repository code 18 | 19 | - name: Configure CMake 20 | run: cmake -S . -B Build # Configure CMake to generate build files in 'Build' directory 21 | 22 | - name: Build 23 | run: cmake --build Build # Build the project using CMake 24 | 25 | - name: Run C_StateMachineApp 26 | run: ./Build/C_StateMachineApp # Run the built executable 27 | -------------------------------------------------------------------------------- /.github/workflows/cmake_windows.yml: -------------------------------------------------------------------------------- 1 | name: Windows 2 | 3 | on: 4 | push: 5 | branches: 6 | - master # Trigger on push to 'main' branch 7 | pull_request: 8 | branches: 9 | - master # Trigger on pull request targeting 'main' branch 10 | 11 | jobs: 12 | build: 13 | runs-on: windows-latest # Use Windows environment for the build 14 | 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v2 # Checkout the repository code 18 | 19 | - name: Set up Visual Studio 20 | uses: microsoft/setup-msbuild@v1.1 # Set up Visual Studio environment (MSBuild) 21 | 22 | - name: Configure CMake 23 | run: cmake -S . -B Build -G "Visual Studio 17 2022" -A Win32 # Configure CMake for Visual Studio 24 | 25 | - name: Build 26 | run: cmake --build Build --config Release # Build the project using CMake with Release configuration 27 | 28 | - name: Run C_StateMachineApp 29 | run: .\Build\Release\C_StateMachineApp.exe # Run the built executable (adjust path for MSBuild) 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # Benchmark Results 46 | BenchmarkDotNet.Artifacts/ 47 | 48 | # .NET Core 49 | project.lock.json 50 | project.fragment.lock.json 51 | artifacts/ 52 | **/Properties/launchSettings.json 53 | 54 | *_i.c 55 | *_p.c 56 | *_i.h 57 | *.ilk 58 | *.meta 59 | *.obj 60 | *.pch 61 | *.pdb 62 | *.pgc 63 | *.pgd 64 | *.rsp 65 | *.sbr 66 | *.tlb 67 | *.tli 68 | *.tlh 69 | *.tmp 70 | *.tmp_proj 71 | *.log 72 | *.vspscc 73 | *.vssscc 74 | .builds 75 | *.pidb 76 | *.svclog 77 | *.scc 78 | 79 | # Chutzpah Test files 80 | _Chutzpah* 81 | 82 | # Visual C++ cache files 83 | ipch/ 84 | *.aps 85 | *.ncb 86 | *.opendb 87 | *.opensdf 88 | *.sdf 89 | *.cachefile 90 | *.VC.db 91 | *.VC.VC.opendb 92 | 93 | # Visual Studio profiler 94 | *.psess 95 | *.vsp 96 | *.vspx 97 | *.sap 98 | 99 | # Visual Studio Trace Files 100 | *.e2e 101 | 102 | # TFS 2012 Local Workspace 103 | $tf/ 104 | 105 | # Guidance Automation Toolkit 106 | *.gpState 107 | 108 | # ReSharper is a .NET coding add-in 109 | _ReSharper*/ 110 | *.[Rr]e[Ss]harper 111 | *.DotSettings.user 112 | 113 | # JustCode is a .NET coding add-in 114 | .JustCode 115 | 116 | # TeamCity is a build add-in 117 | _TeamCity* 118 | 119 | # DotCover is a Code Coverage Tool 120 | *.dotCover 121 | 122 | # AxoCover is a Code Coverage Tool 123 | .axoCover/* 124 | !.axoCover/settings.json 125 | 126 | # Visual Studio code coverage results 127 | *.coverage 128 | *.coveragexml 129 | 130 | # NCrunch 131 | _NCrunch_* 132 | .*crunch*.local.xml 133 | nCrunchTemp_* 134 | 135 | # MightyMoose 136 | *.mm.* 137 | AutoTest.Net/ 138 | 139 | # Web workbench (sass) 140 | .sass-cache/ 141 | 142 | # Installshield output folder 143 | [Ee]xpress/ 144 | 145 | # DocProject is a documentation generator add-in 146 | DocProject/buildhelp/ 147 | DocProject/Help/*.HxT 148 | DocProject/Help/*.HxC 149 | DocProject/Help/*.hhc 150 | DocProject/Help/*.hhk 151 | DocProject/Help/*.hhp 152 | DocProject/Help/Html2 153 | DocProject/Help/html 154 | 155 | # Click-Once directory 156 | publish/ 157 | 158 | # Publish Web Output 159 | *.[Pp]ublish.xml 160 | *.azurePubxml 161 | # Note: Comment the next line if you want to checkin your web deploy settings, 162 | # but database connection strings (with potential passwords) will be unencrypted 163 | *.pubxml 164 | *.publishproj 165 | 166 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 167 | # checkin your Azure Web App publish settings, but sensitive information contained 168 | # in these scripts will be unencrypted 169 | PublishScripts/ 170 | 171 | # NuGet Packages 172 | *.nupkg 173 | # The packages folder can be ignored because of Package Restore 174 | **/[Pp]ackages/* 175 | # except build/, which is used as an MSBuild target. 176 | !**/[Pp]ackages/build/ 177 | # Uncomment if necessary however generally it will be regenerated when needed 178 | #!**/[Pp]ackages/repositories.config 179 | # NuGet v3's project.json files produces more ignorable files 180 | *.nuget.props 181 | *.nuget.targets 182 | 183 | # Microsoft Azure Build Output 184 | csx/ 185 | *.build.csdef 186 | 187 | # Microsoft Azure Emulator 188 | ecf/ 189 | rcf/ 190 | 191 | # Windows Store app package directories and files 192 | AppPackages/ 193 | BundleArtifacts/ 194 | Package.StoreAssociation.xml 195 | _pkginfo.txt 196 | *.appx 197 | 198 | # Visual Studio cache files 199 | # files ending in .cache can be ignored 200 | *.[Cc]ache 201 | # but keep track of directories ending in .cache 202 | !*.[Cc]ache/ 203 | 204 | # Others 205 | ClientBin/ 206 | ~$* 207 | *~ 208 | *.dbmdl 209 | *.dbproj.schemaview 210 | *.jfm 211 | *.pfx 212 | *.publishsettings 213 | orleans.codegen.cs 214 | 215 | # Since there are multiple workflows, uncomment next line to ignore bower_components 216 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 217 | #bower_components/ 218 | 219 | # RIA/Silverlight projects 220 | Generated_Code/ 221 | 222 | # Backup & report files from converting an old project file 223 | # to a newer Visual Studio version. Backup files are not needed, 224 | # because we have git ;-) 225 | _UpgradeReport_Files/ 226 | Backup*/ 227 | UpgradeLog*.XML 228 | UpgradeLog*.htm 229 | 230 | # SQL Server files 231 | *.mdf 232 | *.ldf 233 | *.ndf 234 | 235 | # Business Intelligence projects 236 | *.rdl.data 237 | *.bim.layout 238 | *.bim_*.settings 239 | 240 | # Microsoft Fakes 241 | FakesAssemblies/ 242 | 243 | # GhostDoc plugin setting file 244 | *.GhostDoc.xml 245 | 246 | # Node.js Tools for Visual Studio 247 | .ntvs_analysis.dat 248 | node_modules/ 249 | 250 | # Typescript v1 declaration files 251 | typings/ 252 | 253 | # Visual Studio 6 build log 254 | *.plg 255 | 256 | # Visual Studio 6 workspace options file 257 | *.opt 258 | 259 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 260 | *.vbw 261 | 262 | # Visual Studio LightSwitch build output 263 | **/*.HTMLClient/GeneratedArtifacts 264 | **/*.DesktopClient/GeneratedArtifacts 265 | **/*.DesktopClient/ModelManifest.xml 266 | **/*.Server/GeneratedArtifacts 267 | **/*.Server/ModelManifest.xml 268 | _Pvt_Extensions 269 | 270 | # Paket dependency manager 271 | .paket/paket.exe 272 | paket-files/ 273 | 274 | # FAKE - F# Make 275 | .fake/ 276 | 277 | # JetBrains Rider 278 | .idea/ 279 | *.sln.iml 280 | 281 | # CodeRush 282 | .cr/ 283 | 284 | # Python Tools for Visual Studio (PTVS) 285 | __pycache__/ 286 | *.pyc 287 | 288 | # Cake - Uncomment if you are using it 289 | # tools/** 290 | # !tools/packages.config 291 | 292 | # Tabs Studio 293 | *.tss 294 | 295 | # Telerik's JustMock configuration file 296 | *.jmconfig 297 | 298 | # BizTalk build output 299 | *.btp.cs 300 | *.btm.cs 301 | *.odx.cs 302 | *.xsd.cs 303 | 304 | # OpenCover UI analysis results 305 | OpenCover/ -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Example CMake command line to create project build files: 2 | # 3 | # *** Windows *** 4 | # cmake -G "Visual Studio 17 2022" -A Win32 -B Build -S . 5 | # 6 | # *** Linux *** 7 | # cmake -G "Unix Makefiles" -B Build -S . 8 | 9 | # Specify the minimum CMake version required 10 | cmake_minimum_required(VERSION 3.10) 11 | 12 | # Project name and language (C or C++) 13 | project(C_StateMachine VERSION 1.0 LANGUAGES C CXX) 14 | 15 | # Collect all source files in the current directory 16 | file(GLOB SOURCES 17 | "${CMAKE_SOURCE_DIR}/*.cpp" 18 | "${CMAKE_SOURCE_DIR}/*.c" 19 | "${CMAKE_SOURCE_DIR}/*.h" 20 | ) 21 | 22 | # Add an executable target 23 | add_executable(C_StateMachineApp ${SOURCES}) 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /CentrifugeTest.c: -------------------------------------------------------------------------------- 1 | #include "CentrifugeTest.h" 2 | #include "StateMachine.h" 3 | #include 4 | 5 | // CentrifugeTest object structure 6 | typedef struct 7 | { 8 | INT speed; 9 | BOOL pollActive; 10 | } CentrifugeTest; 11 | 12 | // Define private instance of motor state machine 13 | CentrifugeTest centrifugeTestObj; 14 | SM_DEFINE(CentrifugeTestSM, ¢rifugeTestObj) 15 | 16 | // State enumeration order must match the order of state 17 | // method entries in the state map 18 | enum States 19 | { 20 | ST_IDLE, 21 | ST_COMPLETED, 22 | ST_FAILED, 23 | ST_START_TEST, 24 | ST_ACCELERATION, 25 | ST_WAIT_FOR_ACCELERATION, 26 | ST_DECELERATION, 27 | ST_WAIT_FOR_DECELERATION, 28 | ST_MAX_STATES 29 | }; 30 | 31 | // State machine state functions 32 | STATE_DECLARE(Idle, NoEventData) 33 | ENTRY_DECLARE(Idle, NoEventData) 34 | STATE_DECLARE(Completed, NoEventData) 35 | STATE_DECLARE(Failed, NoEventData) 36 | STATE_DECLARE(StartTest, NoEventData) 37 | GUARD_DECLARE(StartTest, NoEventData) 38 | STATE_DECLARE(Acceleration, NoEventData) 39 | STATE_DECLARE(WaitForAcceleration, NoEventData) 40 | EXIT_DECLARE(WaitForAcceleration) 41 | STATE_DECLARE(Deceleration, NoEventData) 42 | STATE_DECLARE(WaitForDeceleration, NoEventData) 43 | EXIT_DECLARE(WaitForDeceleration) 44 | 45 | // State map to define state function order 46 | BEGIN_STATE_MAP_EX(CentrifugeTest) 47 | STATE_MAP_ENTRY_ALL_EX(ST_Idle, 0, EN_Idle, 0) 48 | STATE_MAP_ENTRY_EX(ST_Completed) 49 | STATE_MAP_ENTRY_EX(ST_Failed) 50 | STATE_MAP_ENTRY_ALL_EX(ST_StartTest, GD_StartTest, 0, 0) 51 | STATE_MAP_ENTRY_EX(ST_Acceleration) 52 | STATE_MAP_ENTRY_ALL_EX(ST_WaitForAcceleration, 0, 0, EX_WaitForAcceleration) 53 | STATE_MAP_ENTRY_EX(ST_Deceleration) 54 | STATE_MAP_ENTRY_ALL_EX(ST_WaitForDeceleration, 0, 0, EX_WaitForDeceleration) 55 | END_STATE_MAP_EX(CentrifugeTest) 56 | 57 | EVENT_DEFINE(CFG_Start, NoEventData) 58 | { 59 | BEGIN_TRANSITION_MAP // - Current State - 60 | TRANSITION_MAP_ENTRY(ST_START_TEST) // ST_IDLE 61 | TRANSITION_MAP_ENTRY(CANNOT_HAPPEN) // ST_COMPLETED 62 | TRANSITION_MAP_ENTRY(CANNOT_HAPPEN) // ST_FAILED 63 | TRANSITION_MAP_ENTRY(EVENT_IGNORED) // ST_START_TEST 64 | TRANSITION_MAP_ENTRY(EVENT_IGNORED) // ST_ACCELERATION 65 | TRANSITION_MAP_ENTRY(EVENT_IGNORED) // ST_WAIT_FOR_ACCELERATION 66 | TRANSITION_MAP_ENTRY(EVENT_IGNORED) // ST_DECELERATION 67 | TRANSITION_MAP_ENTRY(EVENT_IGNORED) // ST_WAIT_FOR_DECELERATION 68 | END_TRANSITION_MAP(CentrifugeTest, pEventData) 69 | } 70 | 71 | EVENT_DEFINE(CFG_Cancel, NoEventData) 72 | { 73 | BEGIN_TRANSITION_MAP // - Current State - 74 | TRANSITION_MAP_ENTRY(EVENT_IGNORED) // ST_IDLE 75 | TRANSITION_MAP_ENTRY(CANNOT_HAPPEN) // ST_COMPLETED 76 | TRANSITION_MAP_ENTRY(CANNOT_HAPPEN) // ST_FAILED 77 | TRANSITION_MAP_ENTRY(ST_FAILED) // ST_START_TEST 78 | TRANSITION_MAP_ENTRY(ST_FAILED) // ST_ACCELERATION 79 | TRANSITION_MAP_ENTRY(ST_FAILED) // ST_WAIT_FOR_ACCELERATION 80 | TRANSITION_MAP_ENTRY(ST_FAILED) // ST_DECELERATION 81 | TRANSITION_MAP_ENTRY(ST_FAILED) // ST_WAIT_FOR_DECELERATION 82 | END_TRANSITION_MAP(CentrifugeTest, pEventData) 83 | } 84 | 85 | EVENT_DEFINE(CFG_Poll, NoEventData) 86 | { 87 | BEGIN_TRANSITION_MAP // - Current State - 88 | TRANSITION_MAP_ENTRY(EVENT_IGNORED) // ST_IDLE 89 | TRANSITION_MAP_ENTRY(EVENT_IGNORED) // ST_COMPLETED 90 | TRANSITION_MAP_ENTRY(EVENT_IGNORED) // ST_FAILED 91 | TRANSITION_MAP_ENTRY(EVENT_IGNORED) // ST_START_TEST 92 | TRANSITION_MAP_ENTRY(ST_WAIT_FOR_ACCELERATION) // ST_ACCELERATION 93 | TRANSITION_MAP_ENTRY(ST_WAIT_FOR_ACCELERATION) // ST_WAIT_FOR_ACCELERATION 94 | TRANSITION_MAP_ENTRY(ST_WAIT_FOR_DECELERATION) // ST_DECELERATION 95 | TRANSITION_MAP_ENTRY(ST_WAIT_FOR_DECELERATION) // ST_WAIT_FOR_DECELERATION 96 | END_TRANSITION_MAP(CentrifugeTest, pEventData) 97 | } 98 | 99 | static void StartPoll(void) 100 | { 101 | centrifugeTestObj.pollActive = TRUE; 102 | } 103 | 104 | static void StopPoll(void) 105 | { 106 | centrifugeTestObj.pollActive = FALSE; 107 | } 108 | 109 | BOOL CFG_IsPollActive(void) 110 | { 111 | return centrifugeTestObj.pollActive; 112 | } 113 | 114 | STATE_DEFINE(Idle, NoEventData) 115 | { 116 | printf("%s ST_Idle\n", self->name); 117 | } 118 | 119 | ENTRY_DEFINE(Idle, NoEventData) 120 | { 121 | printf("%s EN_Idle\n", self->name); 122 | centrifugeTestObj.speed = 0; 123 | StopPoll(); 124 | } 125 | 126 | STATE_DEFINE(Completed, NoEventData) 127 | { 128 | printf("%s ST_Completed\n", self->name); 129 | SM_InternalEvent(ST_IDLE, NULL); 130 | } 131 | 132 | STATE_DEFINE(Failed, NoEventData) 133 | { 134 | printf("%s ST_Failed\n", self->name); 135 | SM_InternalEvent(ST_IDLE, NULL); 136 | } 137 | 138 | // Start the centrifuge test state. 139 | STATE_DEFINE(StartTest, NoEventData) 140 | { 141 | printf("%s ST_StartTest\n", self->name); 142 | SM_InternalEvent(ST_ACCELERATION, NULL); 143 | } 144 | 145 | // Guard condition to determine whether StartTest state is executed. 146 | GUARD_DEFINE(StartTest, NoEventData) 147 | { 148 | printf("%s GD_StartTest\n", self->name); 149 | if (centrifugeTestObj.speed == 0) 150 | return TRUE; // Centrifuge stopped. OK to start test. 151 | else 152 | return FALSE; // Centrifuge spinning. Can't start test. 153 | } 154 | 155 | // Start accelerating the centrifuge. 156 | STATE_DEFINE(Acceleration, NoEventData) 157 | { 158 | printf("%s ST_Acceleration\n", self->name); 159 | 160 | // Start polling while waiting for centrifuge to ramp up to speed 161 | StartPoll(); 162 | } 163 | 164 | // Wait in this state until target centrifuge speed is reached. 165 | STATE_DEFINE(WaitForAcceleration, NoEventData) 166 | { 167 | printf("%s ST_WaitForAcceleration : Speed is %d\n", self->name, centrifugeTestObj.speed); 168 | if (++centrifugeTestObj.speed >= 5) 169 | SM_InternalEvent(ST_DECELERATION, NULL); 170 | } 171 | 172 | // Exit action when WaitForAcceleration state exits. 173 | EXIT_DEFINE(WaitForAcceleration) 174 | { 175 | printf("%s EX_WaitForAcceleration\n", self->name); 176 | 177 | // Acceleration over, stop polling 178 | StopPoll(); 179 | } 180 | 181 | // Start decelerating the centrifuge. 182 | STATE_DEFINE(Deceleration, NoEventData) 183 | { 184 | printf("%s ST_Deceleration\n", self->name); 185 | 186 | // Start polling while waiting for centrifuge to ramp down to 0 187 | StartPoll(); 188 | } 189 | 190 | // Wait in this state until centrifuge speed is 0. 191 | STATE_DEFINE(WaitForDeceleration, NoEventData) 192 | { 193 | printf("%s ST_WaitForDeceleration : Speed is %d\n", self->name, centrifugeTestObj.speed); 194 | if (centrifugeTestObj.speed-- == 0) 195 | SM_InternalEvent(ST_COMPLETED, NULL); 196 | } 197 | 198 | // Exit action when WaitForDeceleration state exits. 199 | EXIT_DEFINE(WaitForDeceleration) 200 | { 201 | printf("%s EX_WaitForDeceleration\n", self->name); 202 | 203 | // Deceleration over, stop polling 204 | StopPoll(); 205 | } 206 | 207 | 208 | -------------------------------------------------------------------------------- /CentrifugeTest.h: -------------------------------------------------------------------------------- 1 | #ifndef _CENTRIFUGE_TEST_H 2 | #define _CENTRIFUGE_TEST_H 3 | 4 | #include "DataTypes.h" 5 | #include "StateMachine.h" 6 | 7 | // Declare the private instance of CentrifugeTest state machine 8 | SM_DECLARE(CentrifugeTestSM) 9 | 10 | // State machine event functions 11 | EVENT_DECLARE(CFG_Start, NoEventData) 12 | EVENT_DECLARE(CFG_Cancel, NoEventData) 13 | EVENT_DECLARE(CFG_Poll, NoEventData) 14 | 15 | BOOL CFG_IsPollActive(); 16 | 17 | #endif // _CENTRIFUGE_TEST_H 18 | -------------------------------------------------------------------------------- /CentrifugeTest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endurodave/C_StateMachine/659fccb2dc9f830270c7aa7cee18939f01d326d2/CentrifugeTest.png -------------------------------------------------------------------------------- /DataTypes.h: -------------------------------------------------------------------------------- 1 | #ifndef _DATA_TYPES_H 2 | #define _DATA_TYPES_H 3 | 4 | #if WIN32 5 | #include "windows.h" 6 | #else 7 | typedef signed char INT8; 8 | typedef unsigned char UINT8; 9 | typedef signed short INT16; 10 | typedef unsigned short UINT16; 11 | typedef unsigned int UINT32; 12 | typedef int INT32; 13 | typedef char CHAR; 14 | typedef short SHORT; 15 | typedef long LONG; 16 | typedef int INT; 17 | typedef unsigned int UINT; 18 | typedef unsigned long DWORD; 19 | typedef unsigned char BYTE; 20 | typedef unsigned short WORD; 21 | typedef float FLOAT; 22 | typedef double DOUBLE; 23 | typedef int BOOL; 24 | 25 | #ifndef NULL 26 | #ifdef __cplusplus 27 | #define NULL 0 28 | #else 29 | #define NULL ((void *)0) 30 | #endif 31 | #endif 32 | 33 | #ifndef FALSE 34 | #define FALSE 0 35 | #endif 36 | 37 | #ifndef TRUE 38 | #define TRUE 1 39 | #endif 40 | #endif 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /Fault.cpp: -------------------------------------------------------------------------------- 1 | #include "Fault.h" 2 | #include 3 | #if WIN32 4 | #include "windows.h" 5 | #endif 6 | 7 | //---------------------------------------------------------------------------- 8 | // FaultHandler 9 | //---------------------------------------------------------------------------- 10 | void FaultHandler(const char* file, unsigned short line) 11 | { 12 | #if WIN32 13 | // If you hit this line, it means one of the ASSERT macros failed. 14 | DebugBreak(); 15 | #endif 16 | 17 | assert(0); 18 | } -------------------------------------------------------------------------------- /Fault.h: -------------------------------------------------------------------------------- 1 | #ifndef _FAULT_H 2 | #define _FAULT_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #pragma warning(disable : 4005) // Disable warning C4005 9 | 10 | // Used for compile-time checking for array sizes. On Windows VC++, you get 11 | // an "error C2118: negative subscript" error. On GCC: size of "unnamed array is negative" 12 | #ifndef C_ASSERT 13 | //#define C_ASSERT(expr) {char uname[(expr)?1:-1];uname[0]=0;} // Original macro 14 | #define C_ASSERT(expr) ((void)sizeof(char[(expr) ? 1 : -1])) // New macro to fix GCC warning 15 | #endif 16 | 17 | #define ASSERT() \ 18 | FaultHandler(__FILE__, (unsigned short) __LINE__) 19 | 20 | #define ASSERT_TRUE(condition) \ 21 | do {if (!(condition)) FaultHandler(__FILE__, (unsigned short) __LINE__);} while (0) 22 | 23 | /// Handles all software assertions in the system. 24 | /// @param[in] file - the file name that the software assertion occurred on 25 | /// @param[in] line - the line number that the software assertion occurred on 26 | void FaultHandler(const char* file, unsigned short line); 27 | 28 | #ifdef __cplusplus 29 | } 30 | #endif 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 David Lafreniere 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LockGuard.cpp: -------------------------------------------------------------------------------- 1 | #include "LockGuard.h" 2 | #include "Fault.h" 3 | #include 4 | 5 | // A lock is a mutex 6 | #define LOCK std::mutex 7 | 8 | //------------------------------------------------------------------------------ 9 | // LK_Create 10 | //------------------------------------------------------------------------------ 11 | LOCK_HANDLE LK_Create(void) 12 | { 13 | LOCK* lock = new LOCK; 14 | return lock; 15 | } 16 | 17 | //------------------------------------------------------------------------------ 18 | // LK_Destroy 19 | //------------------------------------------------------------------------------ 20 | void LK_Destroy(LOCK_HANDLE hLock) 21 | { 22 | ASSERT_TRUE(hLock); 23 | LOCK* lock = (LOCK*)(hLock); 24 | delete lock; 25 | } 26 | 27 | //------------------------------------------------------------------------------ 28 | // LK_Lock 29 | //------------------------------------------------------------------------------ 30 | void LK_Lock(LOCK_HANDLE hLock) 31 | { 32 | ASSERT_TRUE(hLock); 33 | LOCK* lock = (LOCK*)(hLock); 34 | lock->lock(); 35 | } 36 | 37 | //------------------------------------------------------------------------------ 38 | // LK_Unlock 39 | //------------------------------------------------------------------------------ 40 | void LK_Unlock(LOCK_HANDLE hLock) 41 | { 42 | ASSERT_TRUE(hLock); 43 | LOCK* lock = (LOCK*)(hLock); 44 | lock->unlock(); 45 | } 46 | 47 | -------------------------------------------------------------------------------- /LockGuard.h: -------------------------------------------------------------------------------- 1 | #ifndef _LOCK_GUARD_H 2 | #define _LOCK_GUARD_H 3 | 4 | #include "DataTypes.h" 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | typedef void* LOCK_HANDLE; 11 | 12 | #define LK_CREATE() LK_Create() 13 | #define LK_DESTROY(h) LK_Destroy(h) 14 | #define LK_LOCK(h) LK_Lock(h) 15 | #define LK_UNLOCK(h) LK_Unlock(h) 16 | 17 | LOCK_HANDLE LK_Create(void); 18 | void LK_Destroy(LOCK_HANDLE hLock); 19 | void LK_Lock(LOCK_HANDLE hLock); 20 | void LK_Unlock(LOCK_HANDLE hLock); 21 | 22 | #ifdef __cplusplus 23 | } 24 | #endif 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /Motor.c: -------------------------------------------------------------------------------- 1 | #include "Motor.h" 2 | #include "StateMachine.h" 3 | #include 4 | 5 | // State enumeration order must match the order of state 6 | // method entries in the state map 7 | enum States 8 | { 9 | ST_IDLE, 10 | ST_STOP, 11 | ST_START, 12 | ST_CHANGE_SPEED, 13 | ST_MAX_STATES 14 | }; 15 | 16 | // State machine state functions 17 | STATE_DECLARE(Idle, NoEventData) 18 | STATE_DECLARE(Stop, NoEventData) 19 | STATE_DECLARE(Start, MotorData) 20 | STATE_DECLARE(ChangeSpeed, MotorData) 21 | 22 | // State map to define state function order 23 | BEGIN_STATE_MAP(Motor) 24 | STATE_MAP_ENTRY(ST_Idle) 25 | STATE_MAP_ENTRY(ST_Stop) 26 | STATE_MAP_ENTRY(ST_Start) 27 | STATE_MAP_ENTRY(ST_ChangeSpeed) 28 | END_STATE_MAP(Motor) 29 | 30 | // Set motor speed external event 31 | EVENT_DEFINE(MTR_SetSpeed, MotorData) 32 | { 33 | // Given the SetSpeed event, transition to a new state based upon 34 | // the current state of the state machine 35 | BEGIN_TRANSITION_MAP // - Current State - 36 | TRANSITION_MAP_ENTRY(ST_START) // ST_Idle 37 | TRANSITION_MAP_ENTRY(CANNOT_HAPPEN) // ST_Stop 38 | TRANSITION_MAP_ENTRY(ST_CHANGE_SPEED) // ST_Start 39 | TRANSITION_MAP_ENTRY(ST_CHANGE_SPEED) // ST_ChangeSpeed 40 | END_TRANSITION_MAP(Motor, pEventData) 41 | } 42 | 43 | // Halt motor external event 44 | EVENT_DEFINE(MTR_Halt, NoEventData) 45 | { 46 | // Given the Halt event, transition to a new state based upon 47 | // the current state of the state machine 48 | BEGIN_TRANSITION_MAP // - Current State - 49 | TRANSITION_MAP_ENTRY(EVENT_IGNORED) // ST_Idle 50 | TRANSITION_MAP_ENTRY(CANNOT_HAPPEN) // ST_Stop 51 | TRANSITION_MAP_ENTRY(ST_STOP) // ST_Start 52 | TRANSITION_MAP_ENTRY(ST_STOP) // ST_ChangeSpeed 53 | END_TRANSITION_MAP(Motor, pEventData) 54 | } 55 | 56 | // State machine sits here when motor is not running 57 | STATE_DEFINE(Idle, NoEventData) 58 | { 59 | printf("%s ST_Idle\n", self->name); 60 | } 61 | 62 | // Stop the motor 63 | STATE_DEFINE(Stop, NoEventData) 64 | { 65 | // Get pointer to the instance data and update currentSpeed 66 | Motor* pInstance = SM_GetInstance(Motor); 67 | pInstance->currentSpeed = 0; 68 | 69 | // Perform the stop motor processing here 70 | printf("%s ST_Stop: %d\n", self->name, pInstance->currentSpeed); 71 | 72 | // Transition to ST_Idle via an internal event 73 | SM_InternalEvent(ST_IDLE, NULL); 74 | } 75 | 76 | // Start the motor going 77 | STATE_DEFINE(Start, MotorData) 78 | { 79 | ASSERT_TRUE(pEventData); 80 | 81 | // Get pointer to the instance data and update currentSpeed 82 | Motor* pInstance = SM_GetInstance(Motor); 83 | pInstance->currentSpeed = pEventData->speed; 84 | 85 | // Set initial motor speed processing here 86 | printf("%s ST_Start: %d\n", self->name, pInstance->currentSpeed); 87 | } 88 | 89 | // Changes the motor speed once the motor is moving 90 | STATE_DEFINE(ChangeSpeed, MotorData) 91 | { 92 | ASSERT_TRUE(pEventData); 93 | 94 | // Get pointer to the instance data and update currentSpeed 95 | Motor* pInstance = SM_GetInstance(Motor); 96 | pInstance->currentSpeed = pEventData->speed; 97 | 98 | // Perform the change motor speed here 99 | printf("%s ST_ChangeSpeed: %d\n", self->name, pInstance->currentSpeed); 100 | } 101 | 102 | // Get current speed 103 | GET_DEFINE(MTR_GetSpeed, INT) 104 | { 105 | Motor* pInstance = SM_GetInstance(Motor); 106 | return pInstance->currentSpeed; 107 | } 108 | 109 | -------------------------------------------------------------------------------- /Motor.h: -------------------------------------------------------------------------------- 1 | #ifndef _MOTOR_H 2 | #define _MOTOR_H 3 | 4 | #include "DataTypes.h" 5 | #include "StateMachine.h" 6 | 7 | // Motor object structure 8 | typedef struct 9 | { 10 | INT currentSpeed; 11 | } Motor; 12 | 13 | // Event data structure 14 | typedef struct 15 | { 16 | INT speed; 17 | } MotorData; 18 | 19 | // State machine event functions 20 | EVENT_DECLARE(MTR_SetSpeed, MotorData) 21 | EVENT_DECLARE(MTR_Halt, NoEventData) 22 | 23 | // Public accessor 24 | GET_DECLARE(MTR_GetSpeed, INT); 25 | 26 | #endif // _MOTOR_H 27 | -------------------------------------------------------------------------------- /Motor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endurodave/C_StateMachine/659fccb2dc9f830270c7aa7cee18939f01d326d2/Motor.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![License MIT](https://img.shields.io/github/license/BehaviorTree/BehaviorTree.CPP?color=blue) 2 | [![conan Ubuntu](https://github.com/endurodave/C_StateMachine/actions/workflows/cmake_ubuntu.yml/badge.svg)](https://github.com/endurodave/C_StateMachine/actions/workflows/cmake_ubuntu.yml) 3 | [![conan Ubuntu](https://github.com/endurodave/C_StateMachine/actions/workflows/cmake_clang.yml/badge.svg)](https://github.com/endurodave/C_StateMachine/actions/workflows/cmake_clang.yml) 4 | [![conan Windows](https://github.com/endurodave/C_StateMachine/actions/workflows/cmake_windows.yml/badge.svg)](https://github.com/endurodave/C_StateMachine/actions/workflows/cmake_windows.yml) 5 | 6 | # State Machine Design in C 7 | 8 | A compact C finite state machine (FSM) implementation that's easy to use on embedded and PC-based systems. 9 | 10 | # Table of Contents 11 | 12 | - [State Machine Design in C](#state-machine-design-in-c) 13 | - [Table of Contents](#table-of-contents) 14 | - [Preface](#preface) 15 | - [Related repositories](#related-repositories) 16 | - [Introduction](#introduction) 17 | - [Background](#background) 18 | - [Project Build](#project-build) 19 | - [Windows Visual Studio](#windows-visual-studio) 20 | - [Linux Make](#linux-make) 21 | - [Why use a state machine?](#why-use-a-state-machine) 22 | - [State machine design](#state-machine-design) 23 | - [Internal and external events](#internal-and-external-events) 24 | - [Event data](#event-data) 25 | - [State transitions](#state-transitions) 26 | - [StateMachine module](#statemachine-module) 27 | - [Motor example](#motor-example) 28 | - [External events](#external-events) 29 | - [State enumerations](#state-enumerations) 30 | - [State functions](#state-functions) 31 | - [State map](#state-map) 32 | - [State machine objects](#state-machine-objects) 33 | - [Transition map](#transition-map) 34 | - [New state machine steps](#new-state-machine-steps) 35 | - [State engine](#state-engine) 36 | - [Generating events](#generating-events) 37 | - [No heap usage](#no-heap-usage) 38 | - [CentrifugeTest example](#centrifugetest-example) 39 | - [Multithread safety](#multithread-safety) 40 | - [Conclusion](#conclusion) 41 | - [References](#references) 42 | 43 | # Preface 44 | 45 | Originally published on CodeProject at: State Machine Design in C 46 | 47 | Based on original design published in C\C++ Users Journal (Dr. Dobb's) at: State Machine Design in C++ 48 | 49 |

CMake is used to create the build files. CMake is free and open-source software. Windows, Linux and other toolchains are supported. See the CMakeLists.txt file for more information.

50 | 51 | ## Related repositories 52 | 53 | 58 | 59 | # Introduction 60 | 61 |

In 2000, I wrote an article entitled "State Machine Design in C++" for C/C++ Users Journal (R.I.P.). Interestingly, that old article is still available and (at the time of writing this article) the #1 hit on Google when searching for C++ state machine. The article was written over 15 years ago, but I continue to use the basic idea on numerous projects. It's compact, easy to understand and, in most cases, has just enough features to accomplish what I need.

62 | 63 |

Sometimes C is the right tool for the job. This article provides an alternate C language state machine implementation based on the ideas presented within the article “State Machine Design in C++”. The design is suitable for any platform, embedded or PC, with any C compiler. This state machine has the following features:

64 | 65 |
    66 |
  • C language – state machine written in C
  • 67 |
  • Compact – consumes a minimum amount of resources.
  • 68 |
  • Objects – support multiple instantiations of a single state machine type.
  • 69 |
  • Transition tables – transition tables precisely control state transition behavior.
  • 70 |
  • Events – every event is a simple function with any argument types.
  • 71 |
  • State action – every state action is a separate function with a single, unique event data argument if desired.
  • 72 |
  • Guards/entry/exit actions – optionally a state machine can use guard conditions and separate entry/exit action functions for each state.
  • 73 |
  • Macros – optional multiline macro support simplifies usage by automating the code "machinery".
  • 74 |
  • Error checking – compile time and runtime checks catch mistakes early.
  • 75 |
  • Thread-safe – adding software locks to make the code thread-safe is easy.
  • 76 |
77 | 78 |

The article is not a tutorial on the best design decomposition practices for software state machines. I'll be focusing on state machine code and simple examples with just enough complexity to facilitate understanding the features and usage.

79 | 80 | ## Background 81 | 82 |

A common design technique in the repertoire of most programmers is the venerable finite state machine (FSM). Designers use this programming construct to break complex problems into manageable states and state transitions. There are innumerable ways to implement a state machine.

83 | 84 |

A switch statement provides one of the easiest to implement and most common version of a state machine. Here, each case within the switch statement becomes a state, implemented something like:

85 | 86 |
 87 | switch (currentState) {
 88 |    case ST_IDLE:
 89 |        // do something in the idle state
 90 |        break;
 91 | 
 92 |     case ST_STOP:
 93 |        // do something in the stop state
 94 |        break;
 95 | 
 96 |     // etc...
 97 | }
98 | 99 |

This method is certainly appropriate for solving many different design problems. When employed on an event driven, multithreaded project, however, state machines of this form can be quite limiting.

100 | 101 |

The first problem revolves around controlling what state transitions are valid and which ones are invalid. There is no way to enforce the state transition rules. Any transition is allowed at any time, which is not particularly desirable. For most designs, only a few transition patterns are valid. Ideally, the software design should enforce these predefined state sequences and prevent the unwanted transitions. Another problem arises when trying to send data to a specific state. Since the entire state machine is located within a single function, sending additional data to any given state proves difficult. And lastly these designs are rarely suitable for use in a multithreaded system. The designer must ensure the state machine is called from a single thread of control.

102 | 103 | # Project Build 104 | 105 | CMake is used to create the build files. CMake is free and open-source software. Windows, Linux and other toolchains are supported. Example CMake console commands executed inside the project root directory: 106 | 107 | ## Windows Visual Studio 108 | 109 | cmake -G "Visual Studio 17 2022" -A Win32 -B Build -S . 110 | 111 | After executed, open the Visual Studio project from within the Build directory. 112 | 113 | ## Linux Make 114 | 115 | cmake -G "Unix Makefiles" -B Build -S . 116 | 117 | After executed, build the software from within the Build directory using the command make. Run the console app using ./C_StateMachineApp. 118 | 119 | # Why use a state machine? 120 | 121 |

Implementing code using a state machine is an extremely handy design technique for solving complex engineering problems. State machines break down the design into a series of steps, or what are called states in state-machine lingo. Each state performs some narrowly defined task. Events, on the other hand, are the stimuli, which cause the state machine to move, or transition, between states.

122 | 123 |

To take a simple example, which I will use throughout this article, let's say we are designing motor-control software. We want to start and stop the motor, as well as change the motor's speed. Simple enough. The motor control events to be exposed to the client software will be as follows:

124 | 125 |
    126 |
  1. Set Speed – sets the motor going at a specific speed.
  2. 127 |
  3. Halt – stops the motor.
  4. 128 |
129 | 130 |

These events provide the ability to start the motor at whatever speed desired, which also implies changing the speed of an already moving motor. Or we can stop the motor altogether. To the motor-control module, these two events, or functions, are considered external events. To a client using our code, however, these are just plain functions.

131 | 132 |

These events are not state machine states. The steps required to handle these two events are different. In this case the states are:

133 | 134 |
    135 |
  1. Idle — the motor is not spinning but is at rest.
  2. 136 |
137 | 138 |
    139 |
  • Do nothing.
  • 140 |
141 | 142 |
    143 |
  1. Start — starts the motor from a dead stop.
  2. 144 |
145 | 146 |
    147 |
  • Turn on motor power.
  • 148 |
  • Set motor speed.
  • 149 |
150 | 151 |
    152 |
  1. Change Speed — adjust the speed of an already moving motor.
  2. 153 |
154 | 155 |
    156 |
  • Change motor speed.
  • 157 |
158 | 159 |
    160 |
  1. Stop — stop a moving motor.
  2. 161 |
162 | 163 |
    164 |
  • Turn off motor power.
  • 165 |
  • Go to the Idle state.
  • 166 |
167 | 168 |

As can be seen, breaking the motor control into discreet states, as opposed to having one monolithic function, we can more easily manage the rules of how to operate the motor.

169 | 170 |

Every state machine has the concept of a "current state." This is the state the state machine currently occupies. At any given moment in time, the state machine can be in only a single state. Every instance of a particular state machine instance can set the initial state when defined. That initial state, however, does not execute during object creation. Only an event sent to the state machine causes a state function to execute.

171 | 172 |

To graphically illustrate the states and events, we use a state diagram. Figure 1 below shows the state transitions for the motor control module. A box denotes a state and a connecting arrow indicates the event transitions. Arrows with the event name listed are external events, whereas unadorned lines are considered internal events. (I cover the differences between internal and external events later in the article.)

173 | 174 |

175 | 176 |
Figure 1: Motor state diagram
177 | 178 |

As you can see, when an event comes in the state transition that occurs depends on state machine's current state. When a SetSpeed event comes in, for instance, and the motor is in the Idle state, it transitions to the Start state. However, that same SetSpeed event generated while the current state is Start transitions the motor to the ChangeSpeed state. You can also see that not all state transitions are valid. For instance, the motor can't transition from ChangeSpeed to Idle without first going through the Stop state.

179 | 180 |

In short, using a state machine captures and enforces complex interactions, which might otherwise be difficult to convey and implement.

181 | 182 | # State machine design 183 | 184 | ## Internal and external events 185 | 186 |

As I mentioned earlier, an event is the stimulus that causes a state machine to transition between states. For instance, a button press could be an event. Events can be broken out into two categories: external and internal. The external event, at its most basic level, is a function call into a state-machine module. These functions are public and are called from the outside or from code external to the state-machine object. Any thread or task within a system can generate an external event. If the external event function call causes a state transition to occur, the state will execute synchronously within the caller's thread of control. An internal event, on the other hand, is self-generated by the state machine itself during state execution.

187 | 188 |

A typical scenario consists of an external event being generated, which, again, boils down to a function call into the module's public interface. Based upon the event being generated and the state machine's current state, a lookup is performed to determine if a transition is required. If so, the state machine transitions to the new state and the code for that state executes. At the end of the state function, a check is performed to determine whether an internal event was generated. If so, another transition is performed and the new state gets a chance to execute. This process continues until the state machine is no longer generating internal events, at which time the original external event function call returns. The external event and all internal events, if any, execute within the caller's thread of control.

189 | 190 |

Once the external event starts the state machine executing, it cannot be interrupted by another external event until the external event and all internal events have completed execution if locks are used. This run to completion model provides a multithread-safe environment for the state transitions. Semaphores or mutexes can be used in the state machine engine to block other threads that might be trying to be simultaneously access the same state machine instance. See source code function _SM_ExternalEvent() comments for where the locks go.

191 | 192 | ## Event data 193 | 194 |

When an event is generated, it can optionally attach event data to be used by the state function during execution. Event data is a single const or non-const pointer to any built-in or user-defined data type.

195 | 196 |

Once the state has completed execution, the event data is considered used up and must be deleted. Therefore, any event data sent to a state machine must be dynamically created via SM_XAlloc().  The state machine engine automatically frees allocated event data using SM_XFree().

197 | 198 | ## State transitions 199 | 200 |

When an external event is generated, a lookup is performed to determine the state transition course of action. There are three possible outcomes to an event: new state, event ignored, or cannot happen. A new state causes a transition to a new state where it is allowed to execute. Transitions to the existing state are also possible, which means the current state is re-executed. For an ignored event, no state executes. However, the event data, if any, is deleted. The last possibility, cannot happen, is reserved for situations where the event is not valid given the current state of the state machine. If this occurs, the software faults.

201 | 202 |

In this implementation, internal events are not required to perform a validating transition lookup. The state transition is assumed to be valid. You could check for both valid internal and external event transitions, but in practice, this just takes more storage space and generates busywork for very little benefit. The real need for validating transitions lies in the asynchronous, external events where a client can cause an event to occur at an inappropriate time. Once the state machine is executing, it cannot be interrupted. It is under the control of the private implementation, thereby making transition checks unnecessary. This gives the designer the freedom to change states, via internal events, without the burden of updating transition tables.

203 | 204 | # StateMachine module 205 | 206 |

The state machine source code is contained within the StateMachine.c and StateMachine.h files. The code below shows the partial header. The StateMachine header contains various preprocessor multiline macros to ease implementation of a state machine.

207 | 208 |
209 | enum { EVENT_IGNORED = 0xFE, CANNOT_HAPPEN = 0xFF };
210 | 
211 | typedef void NoEventData;
212 | 
213 | // State machine constant data
214 | typedef struct
215 | {
216 |     const CHAR* name;
217 |     const BYTE maxStates;
218 |     const struct SM_StateStruct* stateMap;
219 |     const struct SM_StateStructEx* stateMapEx;
220 | } SM_StateMachineConst;
221 | 
222 | // State machine instance data
223 | typedef struct 
224 | {
225 |     const CHAR* name;
226 |     void* pInstance;
227 |     BYTE newState;
228 |     BYTE currentState;
229 |     BOOL eventGenerated;
230 |     void* pEventData;
231 | } SM_StateMachine;
232 | 
233 | // Generic state function signatures
234 | typedef void (*SM_StateFunc)(SM_StateMachine* self, void* pEventData);
235 | typedef BOOL (*SM_GuardFunc)(SM_StateMachine* self, void* pEventData);
236 | typedef void (*SM_EntryFunc)(SM_StateMachine* self, void* pEventData);
237 | typedef void (*SM_ExitFunc)(SM_StateMachine* self);
238 | 
239 | typedef struct SM_StateStruct
240 | {
241 |     SM_StateFunc pStateFunc;
242 | } SM_StateStruct;
243 | 
244 | typedef struct SM_StateStructEx
245 | {
246 |     SM_StateFunc pStateFunc;
247 |     SM_GuardFunc pGuardFunc;
248 |     SM_EntryFunc pEntryFunc;
249 |     SM_ExitFunc pExitFunc;
250 | } SM_StateStructEx;
251 | 
252 | // Public functions
253 | #define SM_Event(_smName_, _eventFunc_, _eventData_) \
254 |     _eventFunc_(&_smName_##Obj, _eventData_)
255 | 
256 | // Protected functions
257 | #define SM_InternalEvent(_newState_, _eventData_) \
258 |     _SM_InternalEvent(self, _newState_, _eventData_)
259 | #define SM_GetInstance(_instance_) \
260 |     (_instance_*)(self->pInstance);
261 | 
262 | // Private functions
263 | void _SM_ExternalEvent(SM_StateMachine* self, const SM_StateMachineConst* selfConst, BYTE newState, void* pEventData);
264 | void _SM_InternalEvent(SM_StateMachine* self, BYTE newState, void* pEventData);
265 | void _SM_StateEngine(SM_StateMachine* self, const SM_StateMachineConst* selfConst);
266 | void _SM_StateEngineEx(SM_StateMachine* self, const SM_StateMachineConst* selfConst);
267 | 
268 | #define SM_DECLARE(_smName_) \
269 |     extern SM_StateMachine _smName_##Obj; 
270 | 
271 | #define SM_DEFINE(_smName_, _instance_) \
272 |     SM_StateMachine _smName_##Obj = { #_smName_, _instance_, \
273 |         0, 0, 0, 0 }; 
274 | 
275 | #define EVENT_DECLARE(_eventFunc_, _eventData_) \
276 |     void _eventFunc_(SM_StateMachine* self, _eventData_* pEventData);
277 | 
278 | #define EVENT_DEFINE(_eventFunc_, _eventData_) \
279 |     void _eventFunc_(SM_StateMachine* self, _eventData_* pEventData)
280 | 
281 | #define STATE_DECLARE(_stateFunc_, _eventData_) \
282 |     static void ST_##_stateFunc_(SM_StateMachine* self, _eventData_* pEventData);
283 | 
284 | #define STATE_DEFINE(_stateFunc_, _eventData_) \
285 |     static void ST_##_stateFunc_(SM_StateMachine* self, _eventData_* pEventData)
286 | 
287 | 288 |

The SM_Event() macro is used to generate external events whereas SM_InternalEvent() generates an internal event during state function execution. SM_GetInstance() obtains a pointer to the current state machine object.

289 | 290 |

SM_DECLARE and SM_DEFINE are used to create a state machine instance. EVENT_DECLARE and EVENT_DEFINE create external event functions. And finally, STATE_DECLARE and STATE_DEFINE create state functions.

291 | 292 | # Motor example 293 | 294 |

Motor implements our hypothetical motor-control state machine, where clients can start the motor, at a specific speed, and stop the motor. The Motor header interface is shown below:

295 | 296 |
297 | #include "StateMachine.h"
298 | 
299 | // Motor object structure
300 | typedef struct
301 | {
302 |     INT currentSpeed;
303 | } Motor;
304 | 
305 | // Event data structure
306 | typedef struct
307 | {
308 |     INT speed;
309 | } MotorData;
310 | 
311 | // State machine event functions
312 | EVENT_DECLARE(MTR_SetSpeed, MotorData)
313 | EVENT_DECLARE(MTR_Halt, NoEventData)
314 | 315 |

The Motor source file uses macros to simplify usage by hiding the required state machine machinery.

316 | 317 |
318 | // State enumeration order must match the order of state
319 | // method entries in the state map
320 | enum States
321 | {
322 |     ST_IDLE,
323 |     ST_STOP,
324 |     ST_START,
325 |     ST_CHANGE_SPEED,
326 |     ST_MAX_STATES
327 | };
328 | 
329 | // State machine state functions
330 | STATE_DECLARE(Idle, NoEventData)
331 | STATE_DECLARE(Stop, NoEventData)
332 | STATE_DECLARE(Start, MotorData)
333 | STATE_DECLARE(ChangeSpeed, MotorData)
334 | 
335 | // State map to define state function order
336 | BEGIN_STATE_MAP(Motor)
337 |     STATE_MAP_ENTRY(ST_Idle)
338 |     STATE_MAP_ENTRY(ST_Stop)
339 |     STATE_MAP_ENTRY(ST_Start)
340 |     STATE_MAP_ENTRY(ST_ChangeSpeed)
341 | END_STATE_MAP(Motor)
342 | 
343 | // Set motor speed external event
344 | EVENT_DEFINE(MTR_SetSpeed, MotorData)
345 | {
346 |     // Given the SetSpeed event, transition to a new state based upon 
347 |     // the current state of the state machine
348 |     BEGIN_TRANSITION_MAP                        // - Current State -
349 |         TRANSITION_MAP_ENTRY(ST_START)          // ST_Idle       
350 |         TRANSITION_MAP_ENTRY(CANNOT_HAPPEN)     // ST_Stop       
351 |         TRANSITION_MAP_ENTRY(ST_CHANGE_SPEED)   // ST_Start      
352 |         TRANSITION_MAP_ENTRY(ST_CHANGE_SPEED)   // ST_ChangeSpeed
353 |     END_TRANSITION_MAP(Motor, pEventData)
354 | }
355 | 
356 | // Halt motor external event
357 | EVENT_DEFINE(MTR_Halt, NoEventData)
358 | {
359 |     // Given the Halt event, transition to a new state based upon 
360 |     // the current state of the state machine
361 |     BEGIN_TRANSITION_MAP                        // - Current State -
362 |         TRANSITION_MAP_ENTRY(EVENT_IGNORED)     // ST_Idle
363 |         TRANSITION_MAP_ENTRY(CANNOT_HAPPEN)     // ST_Stop
364 |         TRANSITION_MAP_ENTRY(ST_STOP)           // ST_Start
365 |         TRANSITION_MAP_ENTRY(ST_STOP)           // ST_ChangeSpeed
366 |     END_TRANSITION_MAP(Motor, pEventData)
367 | }
368 | 369 | ## External events 370 | 371 |

MTR_SetSpeed and MTR_Halt are considered external events into the Motor state machine. MTR_SetSpeed takes a pointer to MotorData event data, containing the motor speed. This data structure will be freed using SM_XFree() upon completion of the state processing, so it is imperative that it be created using SM_XAlloc() before the function call is made.

372 | 373 | ## State enumerations 374 | 375 |

Each state function must have an enumeration associated with it. These enumerations are used to store the current state of the state machine. In Motor, States provides these enumerations, which are used later for indexing into the transition map and state map lookup tables.

376 | 377 | ## State functions 378 | 379 |

State functions implement each state — one state function per state-machine state. STATE_DECLARE is used to declare the state function interface and STATE_DEFINE defines the implementation.

380 | 381 |
382 | // State machine sits here when motor is not running
383 | STATE_DEFINE(Idle, NoEventData)
384 | {
385 |     printf("%s ST_Idle\n", self->name);
386 | }
387 | 
388 | // Stop the motor 
389 | STATE_DEFINE(Stop, NoEventData)
390 | {
391 |     // Get pointer to the instance data and update currentSpeed
392 |     Motor* pInstance = SM_GetInstance(Motor);
393 |     pInstance->currentSpeed = 0;
394 | 
395 |     // Perform the stop motor processing here
396 |     printf("%s ST_Stop: %d\n", self->name, pInstance->currentSpeed);
397 | 
398 |     // Transition to ST_Idle via an internal event
399 |     SM_InternalEvent(ST_IDLE, NULL);
400 | }
401 | 
402 | // Start the motor going
403 | STATE_DEFINE(Start, MotorData)
404 | {
405 |     ASSERT_TRUE(pEventData);
406 | 
407 |     // Get pointer to the instance data and update currentSpeed
408 |     Motor* pInstance = SM_GetInstance(Motor);
409 |     pInstance->currentSpeed = pEventData->speed;
410 | 
411 |     // Set initial motor speed processing here
412 |     printf("%s ST_Start: %d\n", self->name, pInstance->currentSpeed);
413 | }
414 | 
415 | // Changes the motor speed once the motor is moving
416 | STATE_DEFINE(ChangeSpeed, MotorData)
417 | {
418 |     ASSERT_TRUE(pEventData);
419 | 
420 |     // Get pointer to the instance data and update currentSpeed
421 |     Motor* pInstance = SM_GetInstance(Motor);
422 |     pInstance->currentSpeed = pEventData->speed;
423 | 
424 |     // Perform the change motor speed here
425 |     printf("%s ST_ChangeSpeed: %d\n", self->name, pInstance->currentSpeed);
426 | }
427 | 
428 | 429 |

STATE_DECLARE and STATE_DEFINE use two arguments. The first argument is the state function name. The second argument is the event data type. If no event data is required, use NoEventData. Macros are also available for creating guard, exit and entry actions which are explained later in the article.

430 | 431 |

The SM_GetInstance() macro obtains an instance to the state machine object. The argument to the macro is the state machine name.

432 | 433 |

In this implementation, all state machine functions must adhere to these signatures, which are as follows:

434 | 435 |
436 | // Generic state function signatures
437 | typedef void (*SM_StateFunc)(SM_StateMachine* self, void* pEventData);
438 | typedef BOOL (*SM_GuardFunc)(SM_StateMachine* self, void* pEventData);
439 | typedef void (*SM_EntryFunc)(SM_StateMachine* self, void* pEventData);
440 | typedef void (*SM_ExitFunc)(SM_StateMachine* self);
441 | 
442 | 443 |

Each SM_StateFunc accepts a pointer to a SM_StateMachine object and event data. If NoEventData is used, the pEventData argument will be NULL. Otherwise, the pEventData argument is of the type specified in STATE_DEFINE.

444 | 445 |

In Motor’s Start state function, the STATE_DEFINE(Start, MotorData) macro expands to:

446 | 447 |
448 | void ST_Start(SM_StateMachine* self, MotorData* pEventData)
449 | 450 |

Notice that every state function has self and pEventData arguments. self is a pointer to the state machine object and pEventData is the event data. Also note that the macro prepends “ST_” to the state name to create the function ST_Start().

451 | 452 |

Similarly, the Stop state function STATE_DEFINE(Stop, NoEventData) is expands to:

453 | 454 |
455 | void ST_Stop(SM_StateMachine* self, void* pEventData)
456 | 457 |

Stop doesn't accept event data so the pEventData argument is void*

458 | 459 |

Three characters are added to each state/guard/entry/exit function automatically within the macros. For instance, if declaring a function using STATE_DEFINE(Idle, NoEventData) the actual state function name is called ST_Idle().

460 | 461 |
    462 |
  1. ST_ - state function prepend characters
  2. 463 |
  3. GD_ - guard function prepend characters
  4. 464 |
  5. EN_ - entry function prepend characters
  6. 465 |
  7. EX_ - exit function prepend characters
  8. 466 |
467 | 468 |

SM_GuardFunc and SM_Entry function typedef’s also accept event data. SM_ExitFunc is unique in that no event data is allowed.

469 | 470 | ## State map 471 | 472 |

The state-machine engine knows which state function to call by using the state map. The state map maps the currentState variable to a specific state function. For instance, if currentState is 2, then the third state-map function pointer entry will be called (counting from zero). The state map table is created using these three macros:

473 | 474 |
475 |
476 | BEGIN_STATE_MAP
477 | STATE_MAP_ENTRY
478 | END_STATE_MAP
479 |
480 | 481 |

BEGIN_STATE_MAP starts the state map sequence. Each STATE_MAP_ENTRY has a state function name argument. END_STATE_MAP terminates the map. The state map for Motor is shown below.

482 | 483 |
484 | BEGIN_STATE_MAP(Motor)
485 |     STATE_MAP_ENTRY(ST_Idle)
486 |     STATE_MAP_ENTRY(ST_Stop)
487 |     STATE_MAP_ENTRY(ST_Start)
488 |     STATE_MAP_ENTRY(ST_ChangeSpeed)
489 | END_STATE_MAP
490 | 
491 | 492 |

Alternatively, guard/entry/exit features require utilizing the _EX (extended) version of the macros.

493 | 494 |
495 | BEGIN_STATE_MAP_EX
496 | STATE_MAP_ENTRY_EX or STATE_MAP_ENTRY_ALL_EX 
497 | END_STATE_MAP_EX
498 | 499 |

The STATE_MAP_ENTRY_ALL_EX macro has four arguments for the state action, guard condition, entry action and exit action in that order. The state action is mandatory but the other actions are optional. If a state doesn't have an action, then use 0 for the argument. If a state doesn't have any guard/entry/exit options, the STATE_MAP_ENTRY_EX macro defaults all unused options to 0. The macro snippet below is for an advanced example presented later in the article.

500 | 501 |
502 | // State map to define state function order
503 | BEGIN_STATE_MAP_EX(CentrifugeTest)
504 |     STATE_MAP_ENTRY_ALL_EX(ST_Idle, 0, EN_Idle, 0)
505 |     STATE_MAP_ENTRY_EX(ST_Completed)
506 |     STATE_MAP_ENTRY_EX(ST_Failed)
507 |     STATE_MAP_ENTRY_ALL_EX(ST_StartTest, GD_StartTest, 0, 0)
508 |     STATE_MAP_ENTRY_EX(ST_Acceleration)
509 |     STATE_MAP_ENTRY_ALL_EX(ST_WaitForAcceleration, 0, 0, EX_WaitForAcceleration)
510 |     STATE_MAP_ENTRY_EX(ST_Deceleration)
511 |     STATE_MAP_ENTRY_ALL_EX(ST_WaitForDeceleration, 0, 0, EX_WaitForDeceleration)
512 | END_STATE_MAP_EX(CentrifugeTest)
513 | 514 |

Don’t forget to add the prepended characters (ST_, GD_, EN_ or EX_) for each function.

515 | 516 | ## State machine objects 517 | 518 |

In C++, objects are integral to the language. Using C, you have to work a bit harder to accomplish similar behavior. This C language state machine supports multiple state machine objects (or instances) instead of having a single, static state machine implementation.

519 | 520 |

The SM_StateMachine data structure stores state machine instance data; one object per state machine instance. The SM_StateMachineConst data structure stores constant data; one constant object per state machine type.

521 | 522 |

The state machine is defined using SM_DEFINE macro. The first argument is the state machine name. The second argument is a pointer to a user defined state machine structure, or NULL if no user object.

523 | 524 |
525 | #define SM_DEFINE(_smName_, _instance_) \
526 |     SM_StateMachine _smName_##Obj = { #_smName_, _instance_, \
527 |         0, 0, 0, 0 };
528 | 529 |

In this example, the state machine name is Motor and two objects and two state machines are created.

530 | 531 |
532 | // Define motor objects
533 | static Motor motorObj1;
534 | static Motor motorObj2;
535 | 
536 | // Define two public Motor state machine instances
537 | SM_DEFINE(Motor1SM, &motorObj1)
538 | SM_DEFINE(Motor2SM, &motorObj2)
539 | 540 |

Each motor object handles state execution independent of the other. The Motor structure is used to store state machine instance-specific data. Within a state function, use SM_GetInstance() to obtain a pointer to the Motor object at runtime.

541 | 542 |
543 | // Get pointer to the instance data and update currentSpeed
544 | Motor* pInstance = SM_GetInstance(Motor);
545 | pInstance->currentSpeed = pEventData->speed;
546 | 
547 | 548 | ## Transition map 549 | 550 |

The last detail to attend to are the state transition rules. How does the state machine know what transitions should occur? The answer is the transition map. A transition map is lookup table that maps the currentState variable to a state enum constant. Every external event function has a transition map table created with three macros:

551 | 552 |
553 | BEGIN_TRANSITION_MAP
554 | TRANSITION_MAP_ENTRY
555 | END_TRANSITION_MAP
556 | 
557 | 558 |

The MTR_Halt event function in Motor defines the transition map as:

559 | 560 |
561 | // Halt motor external event
562 | EVENT_DEFINE(MTR_Halt, NoEventData)
563 | {
564 |     // Given the Halt event, transition to a new state based upon 
565 |     // the current state of the state machine
566 |     BEGIN_TRANSITION_MAP                        // - Current State -
567 |         TRANSITION_MAP_ENTRY(EVENT_IGNORED)     // ST_Idle
568 |         TRANSITION_MAP_ENTRY(CANNOT_HAPPEN)     // ST_Stop
569 |         TRANSITION_MAP_ENTRY(ST_STOP)           // ST_Start
570 |         TRANSITION_MAP_ENTRY(ST_STOP)           // ST_ChangeSpeed
571 |     END_TRANSITION_MAP(Motor, pEventData)
572 | }
573 | 
574 | 575 |

BEGIN_TRANSITION_MAP starts the map. Each TRANSITION_MAP_ENTRY that follows indicates what the state machine should do based upon the current state. The number of entries in each transition map table must match the number of state functions exactly. In our example, we have four state functions, so we need four transition map entries. The location of each entry matches the order of state functions defined within the state map. Thus, the first entry within the MTR_Halt function indicates an EVENT_IGNORED as shown below.

576 | 577 |
578 | TRANSITION_MAP_ENTRY (EVENT_IGNORED)    // ST_Idle
579 | 580 |

This is interpreted as "If a Halt event occurs while the current state is state Idle, just ignore the event."

581 | 582 |

Similarly, the third entry in the map is:

583 | 584 |
585 |
586 | TRANSITION_MAP_ENTRY (ST_STOP)         // ST_Start
587 |
588 | 589 |

This indicates "If a Halt event occurs while current is state Start, then transition to state Stop."

590 | 591 |

END_TRANSITION_MAP terminates the map. The first argument to this macro is the state machine name. The second argument is the event data.

592 | 593 |

The C_ASSERT() macro is used within END_TRANSITION_MAP. If there is a mismatch between the number of state machine states and the number of transition map entries, a compile time error is generated.

594 | 595 | ## New state machine steps 596 | 597 |

Creating a new state machine requires a few basic high-level steps:

598 | 599 |
    600 |
  1. Create a States enumeration with one entry per state function.
  2. 601 |
  3. Define state functions.
  4. 602 |
  5. Define event functions.
  6. 603 |
  7. Create one state map lookup table using the STATE_MAP macros.
  8. 604 |
  9. Create one transition map lookup table for each external event function using the TRANSITION_MAP macros.
  10. 605 |
606 | 607 | # State engine 608 | 609 |

The state engine executes the state functions based upon events generated. The transition map is an array of SM_StateStruct instances indexed by the currentState variable. When the _SM_StateEngine() function executes, it looks up the correct state function within the SM_StateStruct array. After the state function has a chance to execute, it frees the event data, if any, before checking to see if any internal events were generated via SM_InternalEvent().

610 | 611 |
612 | // The state engine executes the state machine states
613 | void _SM_StateEngine(SM_StateMachine* self, SM_StateMachineConst* selfConst)
614 | {
615 |     void* pDataTemp = NULL;
616 | 
617 |     ASSERT_TRUE(self);
618 |     ASSERT_TRUE(selfConst);
619 | 
620 |     // While events are being generated keep executing states
621 |     while (self->eventGenerated)
622 |     {
623 |         // Error check that the new state is valid before proceeding
624 |         ASSERT_TRUE(self->newState < selfConst->maxStates);
625 | 
626 |         // Get the pointers from the state map
627 |         SM_StateFunc state = selfConst->stateMap[self->newState].pStateFunc;
628 | 
629 |         // Copy of event data pointer
630 |         pDataTemp = self->pEventData;
631 | 
632 |         // Event data used up, reset the pointer
633 |         self->pEventData = NULL;
634 | 
635 |         // Event used up, reset the flag
636 |         self->eventGenerated = FALSE;
637 | 
638 |         // Switch to the new current state
639 |         self->currentState = self->newState;
640 | 
641 |         // Execute the state action passing in event data
642 |         ASSERT_TRUE(state != NULL);
643 |         state(self, pDataTemp);
644 | 
645 |         // If event data was used, then delete it
646 |         if (pDataTemp)
647 |         {
648 |             SM_XFree(pDataTemp);
649 |             pDataTemp = NULL;
650 |         }
651 |     }
652 | }
653 | 
654 | 655 |

The state engine logic for guard, entry, state, and exit actions is expressed by the following sequence. The _SM_StateEngine() engine implements only #1 and #5 below. The extended _SM_StateEngineEx() engine uses the entire logic sequence.

656 | 657 |
    658 |
  1. Evaluate the state transition table. If EVENT_IGNORED, the event is ignored and the transition is not performed. If CANNOT_HAPPEN, the software faults. Otherwise, continue with next step.
  2. 659 |
  3. If a guard condition is defined execute the guard condition function. If the guard condition returns FALSE, the state transition is ignored and the state function is not called. If the guard returns TRUE, or if no guard condition exists, the state function will be executed.
  4. 660 |
  5. If transitioning to a new state and an exit action is defined for the current state, call the current state exit action function.
  6. 661 |
  7. If transitioning to a new state and an entry action is defined for the new state, call the new state entry action function.
  8. 662 |
  9. Call the state action function for the new state. The new state is now the current state.
  10. 663 |
664 | 665 | # Generating events 666 | 667 |

At this point, we have a working state machine. Let's see how to generate events to it. An external event is generated by dynamically creating the event data structure using SM_XAlloc(), assigning the structure member variables, and calling the external event function using the SM_Event() macro. The following code fragment shows how a synchronous call is made.

668 | 669 |
670 | MotorData* data;
671 |  
672 | // Create event data
673 | data = SM_XAlloc(sizeof(MotorData));
674 | data->speed = 100;
675 | 
676 | // Call MTR_SetSpeed event function to start motor
677 | SM_Event(Motor1SM, MTR_SetSpeed, data);
678 | 
679 | 680 |

The SM_Event() first argument is the state machine name. The second argument is the event function to invoke. The third argument is the event data, or NULL if no data.

681 | 682 |

To generate an internal event from within a state function, call SM_InternalEvent(). If the destination doesn't accept event data, then the last argument is NULL. Otherwise, create the event data using SM_XAlloc().

683 | 684 |
685 | SM_InternalEvent(ST_IDLE, NULL);
686 | 687 |

In the example above, once the state function completes execution the state machine will transition to the ST_Idle state. If, on the other hand, event data needs to be sent to the destination state, then the data structure needs to be created on the heap and passed in as an argument.

688 | 689 |
690 | MotorData* data;    
691 | data = SM_XAlloc(sizeof(MotorData));
692 | data->speed = 100;
693 | SM_InternalEvent(ST_CHANGE_SPEED, data);
694 | 
695 | 696 | # No heap usage 697 | 698 |

All state machine event data must be dynamically created. However, on some systems using the heap is undesirable. The included x_allocator module is a fixed block memory allocator that eliminates heap usage. Define USE_SM_ALLOCATOR within StateMachine.c to use the fixed block allocator. See the References section below for x_allocator information.

699 | 700 | # CentrifugeTest example 701 | 702 |

The CentrifugeTest example shows how an extended state machine is created using guard, entry and exit actions. The state diagram is shown below.

703 | 704 |

705 | 706 |
Figure 2: CentrifugeTest state diagram
707 | 708 |

A CentrifgeTest object and state machine is created. The only difference here is that the state machine is a singleton, meaning the object is private and only one instance of CentrifugeTest can be created. This is unlike the Motor state machine where multiple instances are allowed.

709 | 710 |
711 | // CentrifugeTest object structure
712 | typedef struct
713 | {
714 |     INT speed;
715 |     BOOL pollActive;
716 | } CentrifugeTest;
717 | 
718 | // Define private instance of motor state machine
719 | CentrifugeTest centrifugeTestObj;
720 | SM_DEFINE(CentrifugeTestSM, &centrifugeTestObj)
721 | 
722 | 723 |

The extended state machine uses ENTRY_DECLARE, GUARD_DECLARE and EXIT_DECLARE macros.

724 | 725 |
726 | // State enumeration order must match the order of state
727 | // method entries in the state map
728 | enum States
729 | {
730 |     ST_IDLE,
731 |     ST_COMPLETED,
732 |     ST_FAILED,
733 |     ST_START_TEST,
734 |     ST_ACCELERATION,
735 |     ST_WAIT_FOR_ACCELERATION,
736 |     ST_DECELERATION,
737 |     ST_WAIT_FOR_DECELERATION,
738 |     ST_MAX_STATES
739 | };
740 | 
741 | // State machine state functions
742 | STATE_DECLARE(Idle, NoEventData)
743 | ENTRY_DECLARE(Idle, NoEventData)
744 | STATE_DECLARE(Completed, NoEventData)
745 | STATE_DECLARE(Failed, NoEventData)
746 | STATE_DECLARE(StartTest, NoEventData)
747 | GUARD_DECLARE(StartTest, NoEventData)
748 | STATE_DECLARE(Acceleration, NoEventData)
749 | STATE_DECLARE(WaitForAcceleration, NoEventData)
750 | EXIT_DECLARE(WaitForAcceleration)
751 | STATE_DECLARE(Deceleration, NoEventData)
752 | STATE_DECLARE(WaitForDeceleration, NoEventData)
753 | EXIT_DECLARE(WaitForDeceleration)
754 | 
755 | // State map to define state function order
756 | BEGIN_STATE_MAP_EX(CentrifugeTest)
757 |     STATE_MAP_ENTRY_ALL_EX(ST_Idle, 0, EN_Idle, 0)
758 |     STATE_MAP_ENTRY_EX(ST_Completed)
759 |     STATE_MAP_ENTRY_EX(ST_Failed)
760 |     STATE_MAP_ENTRY_ALL_EX(ST_StartTest, GD_StartTest, 0, 0)
761 |     STATE_MAP_ENTRY_EX(ST_Acceleration)
762 |     STATE_MAP_ENTRY_ALL_EX(ST_WaitForAcceleration, 0, 0, EX_WaitForAcceleration)
763 |     STATE_MAP_ENTRY_EX(ST_Deceleration)
764 |     STATE_MAP_ENTRY_ALL_EX(ST_WaitForDeceleration, 0, 0, EX_WaitForDeceleration)
765 | END_STATE_MAP_EX(CentrifugeTest)
766 | 
767 | 768 |

Notice the _EX extended state map macros so the guard/entry/exit features are supported. Each guard/entry/exit DECLARE macro must be matched with the DEFINE. For instance, a guard condition for the StartTest state function is declared as:

769 | 770 |
771 | GUARD_DECLARE(StartTest, NoEventData)
772 | 773 |

The guard condition function returns TRUE if the state function is to be executed or FALSE otherwise.

774 | 775 |
776 | // Guard condition to determine whether StartTest state is executed.
777 | GUARD_DEFINE(StartTest, NoEventData)
778 | {
779 |     printf("%s GD_StartTest\n", self->name);
780 |     if (centrifugeTestObj.speed == 0)
781 |         return TRUE;    // Centrifuge stopped. OK to start test.
782 |     else
783 |         return FALSE;   // Centrifuge spinning. Can't start test.
784 | }
785 | 
786 | 787 | # Multithread safety 788 | 789 |

To prevent preemption by another thread when the state machine is in the process of execution, the StateMachine module can use locks within the _SM_ExternalEvent() function. Before the external event is allowed to execute, a semaphore can be locked. When the external event and all internal events have been processed, the software lock is released, allowing another external event to enter the state machine instance.

790 | 791 |

Comments indicate where the lock and unlock should be placed if the application is multithreaded and mutiple threads are able to access a single state machine instance. Note that each StateMachine object should have its own instance of a software lock. This prevents a single instance from locking and preventing all other StateMachine objects from executing. Software locks are only required if a StateMachine instance is called by multiple threads of control. If not, then locks are not required.

792 | 793 |
    794 |
795 | 796 | # Conclusion 797 | 798 |

Implementing a state machine using this method as opposed to the old switch statement style may seem like extra effort. However, the payoff is in a more robust design that is capable of being employed uniformly over an entire multithreaded system. Having each state in its own function provides easier reading than a single huge switch statement, and allows unique event data to be sent to each state. In addition, validating state transitions prevents client misuse by eliminating the side effects caused by unwanted state transitions.

799 | 800 |

This C language version is a close translation of the C++ implementation I’ve used for many years on different projects. Consider the C++ implementation within the References section if using C++.

801 | 802 | # References 803 | 804 | 809 | -------------------------------------------------------------------------------- /StateMachine.c: -------------------------------------------------------------------------------- 1 | #include "Fault.h" 2 | #include "StateMachine.h" 3 | 4 | // @see https://github.com/endurodave/C_StateMachine 5 | 6 | // Generates an external event. Called once per external event 7 | // to start the state machine executing 8 | void _SM_ExternalEvent(SM_StateMachine* self, const SM_StateMachineConst* selfConst, BYTE newState, void* pEventData) 9 | { 10 | // If we are supposed to ignore this event 11 | if (newState == EVENT_IGNORED) 12 | { 13 | // Just delete the event data, if any 14 | if (pEventData) 15 | SM_XFree(pEventData); 16 | } 17 | else 18 | { 19 | // TODO - capture software lock here for thread-safety if necessary 20 | 21 | // Generate the event 22 | _SM_InternalEvent(self, newState, pEventData); 23 | 24 | // Execute state machine based on type of state map defined 25 | if (selfConst->stateMap) 26 | _SM_StateEngine(self, selfConst); 27 | else 28 | _SM_StateEngineEx(self, selfConst); 29 | 30 | // TODO - release software lock here 31 | } 32 | } 33 | 34 | // Generates an internal event. Called from within a state 35 | // function to transition to a new state 36 | void _SM_InternalEvent(SM_StateMachine* self, BYTE newState, void* pEventData) 37 | { 38 | ASSERT_TRUE(self); 39 | 40 | self->pEventData = pEventData; 41 | self->eventGenerated = TRUE; 42 | self->newState = newState; 43 | } 44 | 45 | // The state engine executes the state machine states 46 | void _SM_StateEngine(SM_StateMachine* self, const SM_StateMachineConst* selfConst) 47 | { 48 | void* pDataTemp = NULL; 49 | 50 | ASSERT_TRUE(self); 51 | ASSERT_TRUE(selfConst); 52 | 53 | // While events are being generated keep executing states 54 | while (self->eventGenerated) 55 | { 56 | // Error check that the new state is valid before proceeding 57 | ASSERT_TRUE(self->newState < selfConst->maxStates); 58 | 59 | // Get the pointers from the state map 60 | SM_StateFunc state = selfConst->stateMap[self->newState].pStateFunc; 61 | 62 | // Copy of event data pointer 63 | pDataTemp = self->pEventData; 64 | 65 | // Event data used up, reset the pointer 66 | self->pEventData = NULL; 67 | 68 | // Event used up, reset the flag 69 | self->eventGenerated = FALSE; 70 | 71 | // Switch to the new current state 72 | self->currentState = self->newState; 73 | 74 | // Execute the state action passing in event data 75 | ASSERT_TRUE(state != NULL); 76 | state(self, pDataTemp); 77 | 78 | // If event data was used, then delete it 79 | if (pDataTemp) 80 | { 81 | SM_XFree(pDataTemp); 82 | pDataTemp = NULL; 83 | } 84 | } 85 | } 86 | 87 | // The state engine executes the extended state machine states 88 | void _SM_StateEngineEx(SM_StateMachine* self, const SM_StateMachineConst* selfConst) 89 | { 90 | BOOL guardResult = TRUE; 91 | void* pDataTemp = NULL; 92 | 93 | ASSERT_TRUE(self); 94 | ASSERT_TRUE(selfConst); 95 | 96 | // While events are being generated keep executing states 97 | while (self->eventGenerated) 98 | { 99 | // Error check that the new state is valid before proceeding 100 | ASSERT_TRUE(self->newState < selfConst->maxStates); 101 | 102 | // Get the pointers from the extended state map 103 | SM_StateFunc state = selfConst->stateMapEx[self->newState].pStateFunc; 104 | SM_GuardFunc guard = selfConst->stateMapEx[self->newState].pGuardFunc; 105 | SM_EntryFunc entry = selfConst->stateMapEx[self->newState].pEntryFunc; 106 | SM_ExitFunc exit = selfConst->stateMapEx[self->currentState].pExitFunc; 107 | 108 | // Copy of event data pointer 109 | pDataTemp = self->pEventData; 110 | 111 | // Event data used up, reset the pointer 112 | self->pEventData = NULL; 113 | 114 | // Event used up, reset the flag 115 | self->eventGenerated = FALSE; 116 | 117 | // Execute the guard condition 118 | if (guard != NULL) 119 | guardResult = guard(self, pDataTemp); 120 | 121 | // If the guard condition succeeds 122 | if (guardResult == TRUE) 123 | { 124 | // Transitioning to a new state? 125 | if (self->newState != self->currentState) 126 | { 127 | // Execute the state exit action on current state before switching to new state 128 | if (exit != NULL) 129 | exit(self); 130 | 131 | // Execute the state entry action on the new state 132 | if (entry != NULL) 133 | entry(self, pDataTemp); 134 | 135 | // Ensure exit/entry actions didn't call SM_InternalEvent by accident 136 | ASSERT_TRUE(self->eventGenerated == FALSE); 137 | } 138 | 139 | // Switch to the new current state 140 | self->currentState = self->newState; 141 | 142 | // Execute the state action passing in event data 143 | ASSERT_TRUE(state != NULL); 144 | state(self, pDataTemp); 145 | } 146 | 147 | // If event data was used, then delete it 148 | if (pDataTemp) 149 | { 150 | SM_XFree(pDataTemp); 151 | pDataTemp = NULL; 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /StateMachine.h: -------------------------------------------------------------------------------- 1 | // https://github.com/endurodave/C_StateMachine 2 | // 3 | // The StateMachine module is a C language implementation of a finite state 4 | // machine (FSM). 5 | // 6 | // All event data must be created dynamically using SM_XAlloc. Use a fixed 7 | // block allocator or the heap as desired. 8 | // 9 | // The standard version (non-EX) supports state and event functions. The 10 | // extended version (EX) supports the additional guard, entry and exit state 11 | // machine features. 12 | // 13 | // Macros are used to assist in creating the state machine machinery. 14 | 15 | #ifndef _STATE_MACHINE_H 16 | #define _STATE_MACHINE_H 17 | 18 | #include "DataTypes.h" 19 | #include "Fault.h" 20 | 21 | #ifdef __cplusplus 22 | extern "C" { 23 | #endif 24 | 25 | // Define USE_SM_ALLOCATOR to use the fixed block allocator instead of heap 26 | #define USE_SM_ALLOCATOR 27 | #ifdef USE_SM_ALLOCATOR 28 | #include "sm_allocator.h" 29 | #define SM_XAlloc(size) SMALLOC_Alloc(size) 30 | #define SM_XFree(ptr) SMALLOC_Free(ptr) 31 | #else 32 | #include 33 | #define SM_XAlloc(size) malloc(size) 34 | #define SM_XFree(ptr) free(ptr) 35 | #endif 36 | 37 | enum { EVENT_IGNORED = 0xFE, CANNOT_HAPPEN = 0xFF }; 38 | 39 | typedef void NoEventData; 40 | 41 | // State machine constant data 42 | typedef struct 43 | { 44 | const CHAR* name; 45 | const BYTE maxStates; 46 | const struct SM_StateStruct* stateMap; 47 | const struct SM_StateStructEx* stateMapEx; 48 | } SM_StateMachineConst; 49 | 50 | // State machine instance data 51 | typedef struct 52 | { 53 | const CHAR* name; 54 | void* pInstance; 55 | BYTE newState; 56 | BYTE currentState; 57 | BOOL eventGenerated; 58 | void* pEventData; 59 | } SM_StateMachine; 60 | 61 | // Generic state function signatures 62 | typedef void (*SM_StateFunc)(SM_StateMachine* self, void* pEventData); 63 | typedef BOOL (*SM_GuardFunc)(SM_StateMachine* self, void* pEventData); 64 | typedef void (*SM_EntryFunc)(SM_StateMachine* self, void* pEventData); 65 | typedef void (*SM_ExitFunc)(SM_StateMachine* self); 66 | 67 | typedef struct SM_StateStruct 68 | { 69 | SM_StateFunc pStateFunc; 70 | } SM_StateStruct; 71 | 72 | typedef struct SM_StateStructEx 73 | { 74 | SM_StateFunc pStateFunc; 75 | SM_GuardFunc pGuardFunc; 76 | SM_EntryFunc pEntryFunc; 77 | SM_ExitFunc pExitFunc; 78 | } SM_StateStructEx; 79 | 80 | // Public functions 81 | #define SM_Event(_smName_, _eventFunc_, _eventData_) \ 82 | _eventFunc_(&_smName_##Obj, _eventData_) 83 | #define SM_Get(_smName_, _getFunc_) \ 84 | _getFunc_(&_smName_##Obj) 85 | 86 | // Protected functions 87 | #define SM_InternalEvent(_newState_, _eventData_) \ 88 | _SM_InternalEvent(self, _newState_, _eventData_) 89 | #define SM_GetInstance(_instance_) \ 90 | (_instance_*)(self->pInstance); 91 | 92 | // Private functions 93 | void _SM_ExternalEvent(SM_StateMachine* self, const SM_StateMachineConst* selfConst, BYTE newState, void* pEventData); 94 | void _SM_InternalEvent(SM_StateMachine* self, BYTE newState, void* pEventData); 95 | void _SM_StateEngine(SM_StateMachine* self, const SM_StateMachineConst* selfConst); 96 | void _SM_StateEngineEx(SM_StateMachine* self, const SM_StateMachineConst* selfConst); 97 | 98 | #define SM_DECLARE(_smName_) \ 99 | extern SM_StateMachine _smName_##Obj; 100 | 101 | #define SM_DEFINE(_smName_, _instance_) \ 102 | SM_StateMachine _smName_##Obj = { #_smName_, _instance_, \ 103 | 0, 0, 0, 0 }; 104 | 105 | #define EVENT_DECLARE(_eventFunc_, _eventData_) \ 106 | void _eventFunc_(SM_StateMachine* self, _eventData_* pEventData); 107 | 108 | #define EVENT_DEFINE(_eventFunc_, _eventData_) \ 109 | void _eventFunc_(SM_StateMachine* self, _eventData_* pEventData) 110 | 111 | #define GET_DECLARE(_getFunc_, _getData_) \ 112 | _getData_ _getFunc_(SM_StateMachine* self); 113 | 114 | #define GET_DEFINE(_getFunc_, _getData_) \ 115 | _getData_ _getFunc_(SM_StateMachine* self) 116 | 117 | #define STATE_DECLARE(_stateFunc_, _eventData_) \ 118 | static void ST_##_stateFunc_(SM_StateMachine* self, _eventData_* pEventData); 119 | 120 | #define STATE_DEFINE(_stateFunc_, _eventData_) \ 121 | static void ST_##_stateFunc_(SM_StateMachine* self, _eventData_* pEventData) 122 | 123 | #define GUARD_DECLARE(_guardFunc_, _eventData_) \ 124 | static BOOL GD_##_guardFunc_(SM_StateMachine* self, _eventData_* pEventData); 125 | 126 | #define GUARD_DEFINE(_guardFunc_, _eventData_) \ 127 | static BOOL GD_##_guardFunc_(SM_StateMachine* self, _eventData_* pEventData) 128 | 129 | #define ENTRY_DECLARE(_entryFunc_, _eventData_) \ 130 | static void EN_##_entryFunc_(SM_StateMachine* self, _eventData_* pEventData); 131 | 132 | #define ENTRY_DEFINE(_entryFunc_, _eventData_) \ 133 | static void EN_##_entryFunc_(SM_StateMachine* self, _eventData_* pEventData) 134 | 135 | #define EXIT_DECLARE(_exitFunc_) \ 136 | static void EX_##_exitFunc_(SM_StateMachine* self); 137 | 138 | #define EXIT_DEFINE(_exitFunc_) \ 139 | static void EX_##_exitFunc_(SM_StateMachine* self) 140 | 141 | #define BEGIN_STATE_MAP(_smName_) \ 142 | static const SM_StateStruct _smName_##StateMap[] = { 143 | 144 | #define STATE_MAP_ENTRY(_stateFunc_) \ 145 | { (SM_StateFunc)_stateFunc_ }, 146 | 147 | #define END_STATE_MAP(_smName_) \ 148 | }; \ 149 | static const SM_StateMachineConst _smName_##Const = { #_smName_, \ 150 | (sizeof(_smName_##StateMap)/sizeof(_smName_##StateMap[0])), \ 151 | _smName_##StateMap, NULL }; 152 | 153 | #define BEGIN_STATE_MAP_EX(_smName_) \ 154 | static const SM_StateStructEx _smName_##StateMap[] = { 155 | 156 | #define STATE_MAP_ENTRY_EX(_stateFunc_) \ 157 | { (SM_StateFunc)_stateFunc_, NULL, NULL, NULL }, 158 | 159 | #define STATE_MAP_ENTRY_ALL_EX(_stateFunc_, _guardFunc_, _entryFunc_, _exitFunc_) \ 160 | { (SM_StateFunc)_stateFunc_, (SM_GuardFunc)_guardFunc_, (SM_EntryFunc)_entryFunc_, (SM_ExitFunc)_exitFunc_ }, 161 | 162 | #define END_STATE_MAP_EX(_smName_) \ 163 | }; \ 164 | static const SM_StateMachineConst _smName_##Const = { #_smName_, \ 165 | (sizeof(_smName_##StateMap)/sizeof(_smName_##StateMap[0])), \ 166 | NULL, _smName_##StateMap }; 167 | 168 | #define BEGIN_TRANSITION_MAP \ 169 | static const BYTE TRANSITIONS[] = { \ 170 | 171 | #define TRANSITION_MAP_ENTRY(_entry_) \ 172 | _entry_, 173 | 174 | #define END_TRANSITION_MAP(_smName_, _eventData_) \ 175 | }; \ 176 | _SM_ExternalEvent(self, &_smName_##Const, TRANSITIONS[self->currentState], _eventData_); \ 177 | C_ASSERT((sizeof(TRANSITIONS)/sizeof(BYTE)) == (sizeof(_smName_##StateMap)/sizeof(_smName_##StateMap[0]))); 178 | 179 | #ifdef __cplusplus 180 | } 181 | #endif 182 | 183 | #endif // _STATE_MACHINE_H 184 | -------------------------------------------------------------------------------- /fb_allocator.c: -------------------------------------------------------------------------------- 1 | #include "fb_allocator.h" 2 | #include "DataTypes.h" 3 | #include "Fault.h" 4 | #include 5 | 6 | // Define USE_LOCK to use the default lock implementation 7 | #define USE_LOCKS 8 | #ifdef USE_LOCKS 9 | #include "LockGuard.h" 10 | static LOCK_HANDLE _hLock; 11 | #else 12 | #pragma message("WARNING: Define software lock.") 13 | typedef int LOCK_HANDLE; 14 | static LOCK_HANDLE _hLock; 15 | 16 | #define LK_CREATE() (1) 17 | #define LK_DESTROY(h) 18 | #define LK_LOCK(h) 19 | #define LK_UNLOCK(h) 20 | #endif 21 | 22 | // Get a pointer to the client's area within a memory block 23 | #define GET_CLIENT_PTR(_block_ptr_) \ 24 | (_block_ptr_ ? ((void*)((char*)_block_ptr_)) : NULL) 25 | 26 | // Get a pointer to the block using a client pointer 27 | #define GET_BLOCK_PTR(_client_ptr_) \ 28 | (_client_ptr_ ? ((void*)((char*)_client_ptr_)) : NULL) 29 | 30 | static void* ALLOC_NewBlock(ALLOC_Allocator* alloc); 31 | static void ALLOC_Push(ALLOC_Allocator* alloc, void* pBlock); 32 | static void* ALLOC_Pop(ALLOC_Allocator* alloc); 33 | 34 | //---------------------------------------------------------------------------- 35 | // ALLOC_NewBlock 36 | //---------------------------------------------------------------------------- 37 | static void* ALLOC_NewBlock(ALLOC_Allocator* self) 38 | { 39 | ALLOC_Block* pBlock = NULL; 40 | 41 | LK_LOCK(_hLock); 42 | 43 | // If we have not exceeded the pool maximum 44 | if (self->poolIndex < self->maxBlocks) 45 | { 46 | // Get pointer to a new fixed memory block within the pool 47 | pBlock = (void*)(self->pPool + (self->poolIndex++ * self->blockSize)); 48 | } 49 | 50 | LK_UNLOCK(_hLock); 51 | 52 | if (!pBlock) 53 | { 54 | // Out of fixed block memory 55 | ASSERT(); 56 | } 57 | 58 | return pBlock; 59 | } 60 | 61 | //---------------------------------------------------------------------------- 62 | // ALLOC_Push 63 | //---------------------------------------------------------------------------- 64 | static void ALLOC_Push(ALLOC_Allocator* self, void* pBlock) 65 | { 66 | if (!pBlock) 67 | return; 68 | 69 | // Get a pointer to the client's location within the block 70 | ALLOC_Block* pClient = (ALLOC_Block*)GET_CLIENT_PTR(pBlock); 71 | 72 | LK_LOCK(_hLock); 73 | 74 | // Point client block's next pointer to head 75 | pClient->pNext = self->pHead; 76 | 77 | // The client block is now the new head 78 | self->pHead = pClient; 79 | 80 | LK_UNLOCK(_hLock); 81 | } 82 | 83 | //---------------------------------------------------------------------------- 84 | // ALLOC_Pop 85 | //---------------------------------------------------------------------------- 86 | static void* ALLOC_Pop(ALLOC_Allocator* self) 87 | { 88 | ALLOC_Block* pBlock = NULL; 89 | 90 | LK_LOCK(_hLock); 91 | 92 | // Is the free-list empty? 93 | if (self->pHead) 94 | { 95 | // Remove the head block 96 | pBlock = self->pHead; 97 | 98 | // Set the head to the next block 99 | self->pHead = self->pHead->pNext; 100 | } 101 | 102 | LK_UNLOCK(_hLock); 103 | return GET_BLOCK_PTR(pBlock); 104 | } 105 | 106 | //---------------------------------------------------------------------------- 107 | // ALLOC_Init 108 | //---------------------------------------------------------------------------- 109 | void ALLOC_Init() 110 | { 111 | _hLock = LK_CREATE(); 112 | } 113 | 114 | //---------------------------------------------------------------------------- 115 | // ALLOC_Term 116 | //---------------------------------------------------------------------------- 117 | void ALLOC_Term() 118 | { 119 | LK_DESTROY(_hLock); 120 | } 121 | 122 | //---------------------------------------------------------------------------- 123 | // ALLOC_Alloc 124 | //---------------------------------------------------------------------------- 125 | void* ALLOC_Alloc(ALLOC_HANDLE hAlloc, size_t size) 126 | { 127 | ALLOC_Allocator* self = NULL; 128 | void* pBlock = NULL; 129 | 130 | ASSERT_TRUE(hAlloc); 131 | 132 | // Convert handle to an ALLOC_Allocator instance 133 | self = (ALLOC_Allocator*)hAlloc; 134 | 135 | // Ensure requested size fits within memory block 136 | ASSERT_TRUE(size <= self->blockSize); 137 | 138 | // Get a block from the free-list 139 | pBlock = ALLOC_Pop(self); 140 | 141 | // If the free-list empty? 142 | if (!pBlock) 143 | { 144 | // Get a new block from the pool 145 | pBlock = ALLOC_NewBlock(self); 146 | } 147 | 148 | if (pBlock) 149 | { 150 | // Keep track of usage statistics 151 | self->allocations++; 152 | self->blocksInUse++; 153 | if (self->blocksInUse > self->maxBlocksInUse) 154 | { 155 | self->maxBlocksInUse = self->blocksInUse; 156 | } 157 | } 158 | 159 | return GET_CLIENT_PTR(pBlock); 160 | } 161 | 162 | //---------------------------------------------------------------------------- 163 | // ALLOC_Calloc 164 | //---------------------------------------------------------------------------- 165 | void* ALLOC_Calloc(ALLOC_HANDLE hAlloc, size_t num, size_t size) 166 | { 167 | void* pMem = NULL; 168 | size_t n = 0; 169 | 170 | ASSERT_TRUE(hAlloc); 171 | 172 | // Compute the total size of the block 173 | n = num * size; 174 | 175 | // Allocate the memory 176 | pMem = ALLOC_Alloc(hAlloc, n); 177 | 178 | if (pMem != NULL) 179 | { 180 | // Initialize memory to 0 per calloc behavior 181 | memset(pMem, 0, n); 182 | } 183 | 184 | return pMem; 185 | } 186 | 187 | //---------------------------------------------------------------------------- 188 | // ALLOC_Free 189 | //---------------------------------------------------------------------------- 190 | void ALLOC_Free(ALLOC_HANDLE hAlloc, void* pBlock) 191 | { 192 | ALLOC_Allocator* self = NULL; 193 | 194 | if (!pBlock) 195 | return; 196 | 197 | ASSERT_TRUE(hAlloc); 198 | 199 | // Cast handle to an allocator instance 200 | self = (ALLOC_Allocator*)hAlloc; 201 | 202 | // Get a pointer to the block 203 | pBlock = GET_BLOCK_PTR(pBlock); 204 | 205 | // Push the block onto a stack (i.e. the free-list) 206 | ALLOC_Push(self, pBlock); 207 | 208 | // Keep track of usage statistics 209 | self->deallocations++; 210 | self->blocksInUse--; 211 | } 212 | 213 | 214 | 215 | -------------------------------------------------------------------------------- /fb_allocator.h: -------------------------------------------------------------------------------- 1 | // https://github.com/endurodave/C_Allocator 2 | // 3 | // The fb_allocator is a fixed block memory allocator that handles a 4 | // single block size. 5 | // 6 | // Create an allocator instance using the ALLOC_DEFINE macro. Call 7 | // ALLOC_Init() one time at startup. ALLOC_Alloc() allocates a fixed 8 | // memory block. ALLOC_Free() frees the block. 9 | // 10 | // #include "fb_allocator.h" 11 | // ALLOC_DEFINE(myAllocator, 32, 5) 12 | // 13 | // void main() 14 | // { 15 | // void* block; 16 | // ALLOC_Init(); 17 | // block = ALLOC_Alloc(myAllocator, 32); 18 | // ALLOC_Free(myAllocator, block); 19 | // } 20 | 21 | #ifndef _FB_ALLOCATOR_H 22 | #define _FB_ALLOCATOR_H 23 | 24 | #include 25 | #include "DataTypes.h" 26 | 27 | #ifdef __cplusplus 28 | extern "C" { 29 | #endif 30 | 31 | typedef void* ALLOC_HANDLE; 32 | 33 | typedef struct 34 | { 35 | void* pNext; 36 | } ALLOC_Block; 37 | 38 | // Use ALLOC_DEFINE to declare an ALLOC_Allocator object 39 | typedef struct 40 | { 41 | const char* name; 42 | const char* pPool; 43 | const size_t objectSize; 44 | const size_t blockSize; 45 | const UINT32 maxBlocks; 46 | ALLOC_Block* pHead; 47 | UINT16 poolIndex; 48 | UINT16 blocksInUse; 49 | UINT16 maxBlocksInUse; 50 | UINT16 allocations; 51 | UINT16 deallocations; 52 | } ALLOC_Allocator; 53 | 54 | // Align fixed blocks on X-byte boundary based on CPU architecture. 55 | // Set value to 1, 2, 4 or 8. 56 | #define ALLOC_MEM_ALIGN (1) 57 | 58 | // Get the maximum between a or b 59 | #define ALLOC_MAX(a,b) (((a)>(b))?(a):(b)) 60 | 61 | // Round _numToRound_ to the next higher _multiple_ 62 | #define ALLOC_ROUND_UP(_numToRound_, _multiple_) \ 63 | (((_numToRound_ + _multiple_ - 1) / _multiple_) * _multiple_) 64 | 65 | // Ensure the memory block size is: (a) is aligned on desired boundary and (b) at 66 | // least the size of a ALLOC_Allocator*. 67 | #define ALLOC_BLOCK_SIZE(_size_) \ 68 | (ALLOC_MAX((ALLOC_ROUND_UP(_size_, ALLOC_MEM_ALIGN)), sizeof(ALLOC_Allocator*))) 69 | 70 | // Defines block memory, allocator instance and a handle. On the example below, 71 | // the ALLOC_Allocator instance is myAllocatorObj and the handle is myAllocator. 72 | // _name_ - the allocator name 73 | // _size_ - fixed memory block size in bytes 74 | // _objects_ - number of fixed memory blocks 75 | // e.g. ALLOC_DEFINE(myAllocator, 32, 10) 76 | #define ALLOC_DEFINE(_name_, _size_, _objects_) \ 77 | static char _name_##Memory[ALLOC_BLOCK_SIZE(_size_) * (_objects_)] = { 0 }; \ 78 | static ALLOC_Allocator _name_##Obj = { #_name_, _name_##Memory, _size_, \ 79 | ALLOC_BLOCK_SIZE(_size_), _objects_, NULL, 0, 0, 0, 0, 0 }; \ 80 | static ALLOC_HANDLE _name_ = &_name_##Obj; 81 | 82 | void ALLOC_Init(void); 83 | void ALLOC_Term(void); 84 | void* ALLOC_Alloc(ALLOC_HANDLE hAlloc, size_t size); 85 | void* ALLOC_Calloc(ALLOC_HANDLE hAlloc, size_t num, size_t size); 86 | void ALLOC_Free(ALLOC_HANDLE hAlloc, void* pBlock); 87 | 88 | #ifdef __cplusplus 89 | } 90 | #endif 91 | 92 | #endif // _FB_ALLOCATOR_H 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include "fb_allocator.h" 2 | #include "StateMachine.h" 3 | #include "Motor.h" 4 | #include "CentrifugeTest.h" 5 | 6 | // @see https://github.com/endurodave/C_StateMachine 7 | // 8 | // Other related repos: 9 | // @see https://github.com/endurodave/C_StateMachineWithThreads 10 | // @see https://github.com/endurodave/C_Allocator 11 | 12 | // Define motor objects 13 | static Motor motorObj1; 14 | static Motor motorObj2; 15 | 16 | // Define two public Motor state machine instances 17 | SM_DEFINE(Motor1SM, &motorObj1) 18 | SM_DEFINE(Motor2SM, &motorObj2) 19 | 20 | int main(void) 21 | { 22 | ALLOC_Init(); 23 | 24 | MotorData* data; 25 | 26 | // Create event data 27 | data = SM_XAlloc(sizeof(MotorData)); 28 | data->speed = 100; 29 | 30 | // Call MTR_SetSpeed event function to start motor 31 | SM_Event(Motor1SM, MTR_SetSpeed, data); 32 | 33 | // Call MTR_SetSpeed event function to change motor speed 34 | data = SM_XAlloc(sizeof(MotorData)); 35 | data->speed = 200; 36 | SM_Event(Motor1SM, MTR_SetSpeed, data); 37 | 38 | // Get current speed from Motor1SM 39 | INT currentSpeed = SM_Get(Motor1SM, MTR_GetSpeed); 40 | 41 | // Stop motor again will be ignored 42 | SM_Event(Motor1SM, MTR_Halt, NULL); 43 | 44 | // Motor2SM example 45 | data = SM_XAlloc(sizeof(MotorData)); 46 | data->speed = 300; 47 | SM_Event(Motor2SM, MTR_SetSpeed, data); 48 | SM_Event(Motor2SM, MTR_Halt, NULL); 49 | 50 | // CentrifugeTestSM example 51 | SM_Event(CentrifugeTestSM, CFG_Cancel, NULL); 52 | SM_Event(CentrifugeTestSM, CFG_Start, NULL); 53 | while (CFG_IsPollActive()) 54 | SM_Event(CentrifugeTestSM, CFG_Poll, NULL); 55 | 56 | ALLOC_Term(); 57 | 58 | return 0; 59 | } 60 | 61 | -------------------------------------------------------------------------------- /sm_allocator.c: -------------------------------------------------------------------------------- 1 | // SMALLOC allocates either a 32 or 128 byte block depending 2 | // on the requested size. 3 | 4 | #include "sm_allocator.h" 5 | #include "x_allocator.h" 6 | 7 | // Maximum number of blocks for each size 8 | #define MAX_32_BLOCKS 10 9 | #define MAX_128_BLOCKS 5 10 | 11 | // Define size of each block including meta data overhead 12 | #define BLOCK_32_SIZE 32 + XALLOC_BLOCK_META_DATA_SIZE 13 | #define BLOCK_128_SIZE 128 + XALLOC_BLOCK_META_DATA_SIZE 14 | 15 | // Define individual fb_allocators 16 | ALLOC_DEFINE(smDataAllocator32, BLOCK_32_SIZE, MAX_32_BLOCKS) 17 | ALLOC_DEFINE(smDataAllocator128, BLOCK_128_SIZE, MAX_128_BLOCKS) 18 | 19 | // An array of allocators sorted by smallest block first 20 | static ALLOC_Allocator* allocators[] = { 21 | &smDataAllocator32Obj, 22 | &smDataAllocator128Obj 23 | }; 24 | 25 | #define MAX_ALLOCATORS (sizeof(allocators) / sizeof(allocators[0])) 26 | 27 | static XAllocData self = { allocators, MAX_ALLOCATORS }; 28 | 29 | //---------------------------------------------------------------------------- 30 | // SMALLOC_Alloc 31 | //---------------------------------------------------------------------------- 32 | void* SMALLOC_Alloc(size_t size) 33 | { 34 | return XALLOC_Alloc(&self, size); 35 | } 36 | 37 | //---------------------------------------------------------------------------- 38 | // SMALLOC_Free 39 | //---------------------------------------------------------------------------- 40 | void SMALLOC_Free(void* ptr) 41 | { 42 | XALLOC_Free(ptr); 43 | } 44 | 45 | //---------------------------------------------------------------------------- 46 | // SMALLOC_Realloc 47 | //---------------------------------------------------------------------------- 48 | void* SMALLOC_Realloc(void *ptr, size_t new_size) 49 | { 50 | return XALLOC_Realloc(&self, ptr, new_size); 51 | } 52 | 53 | //---------------------------------------------------------------------------- 54 | // SMALLOC_Calloc 55 | //---------------------------------------------------------------------------- 56 | void* SMALLOC_Calloc(size_t num, size_t size) 57 | { 58 | return XALLOC_Calloc(&self, num, size); 59 | } 60 | 61 | -------------------------------------------------------------------------------- /sm_allocator.h: -------------------------------------------------------------------------------- 1 | #ifndef _SM_ALLOCATOR_H 2 | #define _SM_ALLOCATOR_H 3 | 4 | #include 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | void* SMALLOC_Alloc(size_t size); 11 | void SMALLOC_Free(void* ptr); 12 | void* SMALLOC_Realloc(void *ptr, size_t new_size); 13 | void* SMALLOC_Calloc(size_t num, size_t size); 14 | 15 | #ifdef __cplusplus 16 | } 17 | #endif 18 | 19 | #endif // _SM_ALLOCATOR_H 20 | -------------------------------------------------------------------------------- /x_allocator.c: -------------------------------------------------------------------------------- 1 | #include "x_allocator.h" 2 | #include "fb_allocator.h" 3 | #include "DataTypes.h" 4 | #include "Fault.h" 5 | #include 6 | 7 | static void* XALLOC_PutAllocatorPtrInBlock(void* block, ALLOC_Allocator* allocator); 8 | static ALLOC_Allocator* XALLOC_GetAllocatorPtrFromBlock(void* block); 9 | static ALLOC_Allocator* XALLOC_GetAllocator(XAllocData* self, size_t size); 10 | 11 | //---------------------------------------------------------------------------- 12 | // XALLOC_PutAllocatorPtrInBlock 13 | //---------------------------------------------------------------------------- 14 | static void* XALLOC_PutAllocatorPtrInBlock(void* block, ALLOC_Allocator* allocator) 15 | { 16 | ALLOC_Allocator** pAllocatorInBlock; 17 | 18 | ASSERT_TRUE(block); 19 | ASSERT_TRUE(allocator); 20 | 21 | // Cast raw block memory to ALLOC_Allocator** 22 | pAllocatorInBlock = (ALLOC_Allocator**)(block); 23 | 24 | // Store the allocator pointer in the memory block 25 | *pAllocatorInBlock = allocator; 26 | 27 | // Advance the pointer past the ALLOC_Allocator* and return a 28 | // pointer to the client's memory region 29 | return ++pAllocatorInBlock; 30 | } 31 | 32 | //---------------------------------------------------------------------------- 33 | // XALLOC_GetAllocatorPtrFromBlock 34 | //---------------------------------------------------------------------------- 35 | static ALLOC_Allocator* XALLOC_GetAllocatorPtrFromBlock(void* block) 36 | { 37 | ALLOC_Allocator** pAllocatorInBlock; 38 | 39 | ASSERT_TRUE(block); 40 | 41 | // Cast raw memory block to ALLOC_Allocator** 42 | pAllocatorInBlock = (ALLOC_Allocator**)(block); 43 | 44 | // Backup one ALLOC_Allocator* position to get the stored allocator instance 45 | pAllocatorInBlock--; 46 | 47 | // Return the allocator instance stored within the memory block 48 | return *pAllocatorInBlock; 49 | } 50 | 51 | //---------------------------------------------------------------------------- 52 | // XALLOC_GetBlockPtr 53 | //---------------------------------------------------------------------------- 54 | static void* XALLOC_GetBlockPtr(void* block) 55 | { 56 | ALLOC_Allocator** pAllocatorInBlock; 57 | 58 | ASSERT_TRUE(block); 59 | 60 | // Cast the client memory to ALLOC_Allocator* 61 | pAllocatorInBlock = (ALLOC_Allocator**)(block); 62 | 63 | // Back up one ALLOC_Allocator* position and return raw memory block pointer 64 | return --pAllocatorInBlock; 65 | } 66 | 67 | //---------------------------------------------------------------------------- 68 | // XALLOC_GetAllocator 69 | //---------------------------------------------------------------------------- 70 | static ALLOC_Allocator* XALLOC_GetAllocator(XAllocData* self, size_t size) 71 | { 72 | UINT16 i = 0; 73 | ALLOC_Allocator* pAllocator = NULL; 74 | 75 | ASSERT_TRUE(self); 76 | 77 | // Each block stores additional meta data (i.e. an ALLOC_Allocator pointer). 78 | // Add overhead for the additional memory required. 79 | size += XALLOC_BLOCK_META_DATA_SIZE; 80 | 81 | // Iterate over all allocators 82 | for (i=0; imaxAllocators; i++) 83 | { 84 | // Can the allocator instance handle the requested size? 85 | if (self->allocators[i] && self->allocators[i]->blockSize >= size) 86 | { 87 | // Return allocator instance to handle memory request 88 | pAllocator = self->allocators[i]; 89 | break; 90 | } 91 | } 92 | 93 | return pAllocator; 94 | } 95 | 96 | //---------------------------------------------------------------------------- 97 | // XALLOC_Alloc 98 | //---------------------------------------------------------------------------- 99 | void* XALLOC_Alloc(XAllocData* self, size_t size) 100 | { 101 | ALLOC_Allocator* pAllocator; 102 | void* pBlockMemory = NULL; 103 | void* pClientMemory = NULL; 104 | 105 | ASSERT_TRUE(self); 106 | 107 | // Get an allocator instance to handle the memory request 108 | pAllocator = XALLOC_GetAllocator(self, size); 109 | 110 | // An allocator found to handle memory request? 111 | if (pAllocator) 112 | { 113 | // Get a fixed memory block from the allocator instance 114 | pBlockMemory = ALLOC_Alloc(pAllocator, size + XALLOC_BLOCK_META_DATA_SIZE); 115 | if (pBlockMemory) 116 | { 117 | // Set the block ALLOC_Allocator* ptr within the raw memory block region 118 | pClientMemory = XALLOC_PutAllocatorPtrInBlock(pBlockMemory, pAllocator); 119 | } 120 | } 121 | else 122 | { 123 | // Too large a memory block requested 124 | ASSERT(); 125 | } 126 | 127 | return pClientMemory; 128 | } 129 | 130 | //---------------------------------------------------------------------------- 131 | // XALLOC_Free 132 | //---------------------------------------------------------------------------- 133 | void XALLOC_Free(void* ptr) 134 | { 135 | ALLOC_Allocator* pAllocator = NULL; 136 | void* pBlock = NULL; 137 | 138 | if (!ptr) 139 | return; 140 | 141 | // Extract the original allocator instance from the caller's block pointer 142 | pAllocator = XALLOC_GetAllocatorPtrFromBlock(ptr); 143 | if (pAllocator) 144 | { 145 | // Convert the client pointer into the original raw block pointer 146 | pBlock = XALLOC_GetBlockPtr(ptr); 147 | 148 | // Deallocate the fixed memory block 149 | ALLOC_Free(pAllocator, pBlock); 150 | } 151 | } 152 | 153 | //---------------------------------------------------------------------------- 154 | // XALLOC_Realloc 155 | //---------------------------------------------------------------------------- 156 | void* XALLOC_Realloc(XAllocData* self, void *ptr, size_t new_size) 157 | { 158 | void* pNewMem = NULL; 159 | ALLOC_Allocator* pOldAllocator = NULL; 160 | size_t oldSize = 0; 161 | 162 | ASSERT_TRUE(self); 163 | 164 | if (!ptr) 165 | pNewMem = XALLOC_Alloc(self, new_size); 166 | else if (0 == new_size) 167 | XALLOC_Free(ptr); 168 | else 169 | { 170 | // Create a new memory block 171 | pNewMem = XALLOC_Alloc(self, new_size); 172 | if (pNewMem != 0) 173 | { 174 | // Get the original allocator instance from the old memory block 175 | pOldAllocator = XALLOC_GetAllocatorPtrFromBlock(ptr); 176 | oldSize = pOldAllocator->blockSize - XALLOC_BLOCK_META_DATA_SIZE; 177 | 178 | // Copy the bytes from the old memory block into the new (as much as will fit) 179 | memcpy(pNewMem, ptr, (oldSize < new_size) ? oldSize : new_size); 180 | 181 | // Free the old memory block 182 | XALLOC_Free(ptr); 183 | } 184 | } 185 | 186 | // Return the client pointer to the new memory block 187 | return pNewMem; 188 | } 189 | 190 | //---------------------------------------------------------------------------- 191 | // XALLOC_Calloc 192 | //---------------------------------------------------------------------------- 193 | void* XALLOC_Calloc(XAllocData* self, size_t num, size_t size) 194 | { 195 | void* pMem = NULL; 196 | size_t n; 197 | 198 | ASSERT_TRUE(self); 199 | 200 | // Compute the total block size 201 | n = num * size; 202 | 203 | // Allocate the memory 204 | pMem = XALLOC_Alloc(self, n); 205 | 206 | if (pMem) 207 | { 208 | // Initialize memory to 0 209 | memset(pMem, 0, n); 210 | } 211 | 212 | return pMem; 213 | } 214 | 215 | 216 | 217 | -------------------------------------------------------------------------------- /x_allocator.h: -------------------------------------------------------------------------------- 1 | // @see https://github.com/endurodave/C_Allocator 2 | // 3 | // The x_allocator is a fixed block memory allocator that handles multiple 4 | // block sizes by using two or more fb_allocator objects. Typically users 5 | // create a thin wrapper module for each x_allocator managed memory blocks. 6 | // 7 | // For example, create a XAllocData instance and wrapper functions 8 | // in my_allocator.c: 9 | // 10 | // #define MAX_32_BLOCKS 10 11 | // #define MAX_128_BLOCKS 5 12 | // #define MAX_512_BLOCKS 2 13 | // 14 | // #define BLOCK_32_SIZE 32 + XALLOC_BLOCK_META_DATA_SIZE 15 | // #define BLOCK_128_SIZE 128 + XALLOC_BLOCK_META_DATA_SIZE 16 | // #define BLOCK_512_SIZE 512 + XALLOC_BLOCK_META_DATA_SIZE 17 | // 18 | // // Define each fb_allocator instance 19 | // ALLOC_DEFINE(myAllocator32, BLOCK_32_SIZE, MAX_32_BLOCKS) 20 | // ALLOC_DEFINE(myAllocator128, BLOCK_128_SIZE, MAX_128_BLOCKS) 21 | // ALLOC_DEFINE(myAllocator512, BLOCK_512_SIZE, MAX_512_BLOCKS) 22 | // 23 | // // An array of allocators sorted by smallest to largest block 24 | // static ALLOC_Allocator* allocators[] = { 25 | // &myAllocator32Obj, 26 | // &myAllocator128Obj, 27 | // &myAllocator512Obj 28 | // }; 29 | // 30 | // #define MAX_ALLOCATORS (sizeof(allocators) / sizeof(allocators[0])) 31 | // 32 | // static XAllocData self = { allocators, MAX_ALLOCATORS }; 33 | // 34 | // // Thin allocator wrapper function implementations call XALLOC 35 | // void* MYALLOC_Alloc(size_t size) { return XALLOC_Alloc(&self, size); } 36 | // void MYALLOC_Free(void* ptr) { XALLOC_Free(ptr); } 37 | // void* MYALLOC_Realloc(void *ptr, size_t new_size) { return XALLOC_Realloc(&self, ptr, new_size); } 38 | // void* MYALLOC_Calloc(size_t num, size_t size) { return XALLOC_Calloc(&self, num, size); } 39 | // 40 | // Expose the allocator functions in my_allocator.h: 41 | // 42 | // void* MYALLOC_Alloc(size_t size); 43 | // void MYALLOC_Free(void* ptr); 44 | // void* MYALLOC_Realloc(void *ptr, size_t new_size); 45 | // void* MYALLOC_Calloc(size_t num, size_t size); 46 | 47 | #ifndef _X_ALLOCATOR_H 48 | #define _X_ALLOCATOR_H 49 | 50 | #include "fb_allocator.h" 51 | #include 52 | 53 | #ifdef __cplusplus 54 | extern "C" { 55 | #endif 56 | 57 | // Overhead bytes added to each XALLOC memory block 58 | #define XALLOC_BLOCK_META_DATA_SIZE sizeof(ALLOC_Allocator*) 59 | 60 | typedef struct 61 | { 62 | // Array of allocator instances sorted from smallest to largest block 63 | ALLOC_Allocator* const *allocators; 64 | 65 | // Number of allocator instances stored within the allocators array 66 | const UINT16 maxAllocators; 67 | } XAllocData; 68 | 69 | void* XALLOC_Alloc(XAllocData* self, size_t size); 70 | void XALLOC_Free(void* ptr); 71 | void* XALLOC_Realloc(XAllocData* self, void *ptr, size_t new_size); 72 | void* XALLOC_Calloc(XAllocData* self, size_t num, size_t size); 73 | 74 | #ifdef __cplusplus 75 | } 76 | #endif 77 | 78 | #endif // _X_ALLOCATOR_H 79 | --------------------------------------------------------------------------------