This project is a step counter using a STM32 NUCLEO64 board.
The step counter includes the following outputs
- OLED display to show information such as steps, distance, and goal progress
- Four LEDs to visualize goal progress
- A buzzer to indicate goal reached
And the following inputs
- Buttons to increment step goal manually, and enter test mode
- Joystick to increment and decrement steps in the test mode, as well as change between display modes in the standard mode
- Joystick button to enter and exit goal setting mode
- Potentiometer to control step goal
Turning on the STM32 device will start the step counter. On the start screen, the steps will be displayed at 0, with a goal progress at 0/1000. Walking will increase the number of steps displayed, and the device will buzz once the goal is reached.
Pushing the Joystick to the left will change the display to the distance travelled screen, where one step is 0.9m. Pushing the joystick to the left from the distance travelled screen will transition to the goal progress screen, and pushing to the left again will bring you back current steps. To change the units, push the joystick upwards. On the current steps screen, units will swap between steps, and goal percentage. On the distance travelled screen, units will swap between kilometers and yards.
To change the step goal, hold the joystick down on the goal progress screen for 1 second, scroll with the potentiometer to set the goal, and hold the joystick button again for 1 second. To revert changes, short press the joystick.
To enter and exit test mode, double press the button labelled SW2. While in test mode, you can increase the step count but holding up the joystick and decrease the step count by holding down the joystick. The rate at which steps increment and decrement will be proportional to joystick displacement. You cannot increase the step count past the goal or below zero in test mode.
The step counter uses plenty of modules to achieve a functional program. It was important to modularize the program for a multitude of reasons. Most important of these reasons was to ensure the readability and future proofing of the project. By separating each module to either control one piece of hardware, or control one major feature of the step counter, development becomes much smoother as code becomes flexible, easily editable, and much more streamlined as it does not need to be refactored when a new feature is added. Furthermore, future programmers who may pick up the project later can easily understand the code, how it functions, and improve it.
The way the step counter code is modularized is through a hierarchy. The main.c module keeps the app_main() function running in a while(1) loop which controls the rest of the modules. App.c, acts as a task scheduler, handling when each task needs to be called to keep the functionality of the step counter consistent.
Each task controls a major piece component of the stepcounter, for example task_blinky controls the LEDs, task_buzz the buzzer etc. The reason for structuring the top of the module hierarchy like this was to streamline the development of the program, being able to change when a specific piece of code is run or when an input is read in one module is a very powerful tool to improve the consistency of the step counter while reducing the load of the board.
Most tasks uses a number of smaller modules to help it achieve the desired functions. These smaller modules are sometimes only a couple of functions but greatly help with the readability of the code. We chose this approach for lower level modules as it greatly helps read and understand code that a partner has written while allowing for easier adjustments in the future.
A full modularization hierarchy can be seen below.
The step counter program needs to provide a consistent experience; to achieve this, the firmware needs to behave in very specific ways.
Firstly, the firmware needs to check not only for any steps from the accelerometer but also for any inputs from the user themselves and then behave in a timely manner such that the user is not waiting for the system to respond. Because of this we decided to choose frequencies of 40Hz for the LED, joystick, potentiometer, and buzzer related tasks, and frequencies of 80Hz for the button and accelerometer related tasks. We chose 40 Hz for these tasks as they are mainly user inputs and responses, so it is not as important that they are checked and performed at a very high frequency as the user will not register any difference in response time after a certain speed. This means we can reduce the CPU load by performing the tasks less often. For the other tasks performed at 80Hz frequency, we chose the faster frequency because they handle the step detection and the button debouncing so it is more important for the data being used and collected to be updated more regularly and kept up to date. This is because the step detection relies on the data from the accelerometer to measure the change in acceleration of the step counter device, and some steps may be small, it is important to update frequently to ensure accuracy for the system and the user. Additionally, these tasks are handled by an interrupt based kernel which further guarantees that the tasks are run when they need to be.
The main step detection algorithm starts with our peak detection algorithm. This involves taking the values for acceleration in the x, y, and z axis from the accelerometer and then taking away the offset due to the sensors. We then put the values through an averaging filter to provide more stable data when detecting steps. Then as we do not know which way the user may be holding the step counter we must take the square magnitude of the acceleration, to compare to a threshold for acceleration to determine whether a step has been taken by the user. However, as the microcontroller does not have a floating point bit, it cannot handle floating point calculations in a timely manner, this includes the square root involved with our square mean calculation. As such, as it is important that the peak detection happens quickly in order to accurately count the steps of the user, we only take the sum of the acceleration squares as the will still show the difference in acceleration between movement of the board and the user staying still. Upon getting the acceleration magnitude it is then compared to a threshold value which to see if the user has actually stepped or if the board just moved a little bit. In addition to this there is a slight cooldown period between peak detections to prevent one step being read multiple times.
To find our peak threshold value of 279000000, we tested the board by viewing the square magnitude values through UART communication and tera term to see what the base value would be and then what the value was when the user took a step while holding the board. We then further refined the threshold through manual testing with the step counter.
The step counter required rigorous testing, debugging, and modularization to meet all the required specifications. In the end, all required features had been completed, with a step counter accuracy of +-10%. Despite the successful completion of the project, some aspects could be improved.
A lot of times during the development of the step counter project, modules had to be restructured, this included turning external variables into getter functions, modularizing large modules further, and moving functionality from one module from another to improve readability and consistency. If this project were to be done again, having a clear plan from the beginning of what all functions and modules the program will need, would help greatly in streamlining the coding process.
