Skip to content

Basics of Software Design

From Requirements to Implementation

Software design is a structured process that helps transform an idea into a working program. Whether you're working on a university project or developing software in a team, understanding the key phases of software design can improve code quality, efficiency, and collaboration.

Developing embedded systems for the first time can feel overwhelming. How do you go from an idea to a working system? Let’s break it down step by step, like solving a puzzle.

Understanding the Problem (Requirement Analysis)

Before writing any code, clearly define the problem you aim to solve. Coding is merely a tool for problem-solving, and software should be designed to address a specific need effectively. In embedded systems, hardware and software are often developed together to meet the requirements of a given task. Properly defining the problem ensures an efficient and well-integrated solution.

πŸ’‘ Tip: Writing a short requirement document or drawing a simple diagram helps clarify ideas before implementation.

Before jumping into coding, ask yourself:
βœ… What does my system need to do?
βœ… What inputs and outputs will it have?
βœ… How should different components communicate?

Imagine you're building a line-following robot. The robot must:

  1. Detect the line using sensors.
  2. Decide what to do (keep moving, turn, or stop).
  3. Control the motors accordingly.

Now, let’s turn this into a structured plan.


Breaking Down the Problem (Abstraction & Decomposition)

The problem in most cases should be divided into smaller, manageable parts. Before thinking about any coding just take a step back and just think about the problem in general.

Just like building a house, embedded systems need a solid structure. You wouldn’t start with the roof, right?

Techniques to Decompose a Problem:

  • Abstraction – Identify core functionality without focusing on details.
    Example: Instead of β€œa program to control robot movements,” break it into β€œInput -> Process -> Output.”
  • Modularization – Divide software into independent modules/functions.
  • Top-Down Design – Start with high-level tasks, then break them into smaller ones.
  • Bottom-Up Design – Start by building small components and integrating them.

πŸ’‘ Tip: A function should do one task only. Keep it simple and reusable.

3. Designing the Software Architecture

After breaking the problem into components, define how they interact.

Software Architectures

Behavioral Architectures (How the system behaves)

  • Superloop:

    • The simplest approach. Code runs in a continuous loop, checking sensors and acting accordingly.
    • 🟒 Good for: Simple projects with predictable timing.
    • πŸ”΄ Challenges: Hard to handle multiple tasks efficiently.
  • State Machine:

    • Defines different "states" (e.g., moving, turning) and transitions between them.
    • 🟒 Good for: Systems with clear modes of operation.
    • πŸ”΄ Challenges: Can become complex with many states.
  • Multitasking Approaches (advanced):

    • Cooperative Multitasking: Tasks take turns executing. The program must "yield" control.
    • RTOS (Real-Time Operating System): Manages multiple tasks efficiently with precise timing.
    • 🟒 Good for: Complex systems that require parallel execution.
    • πŸ”΄ Challenges: Adds overhead and complexity.

πŸ“Œ Example: - A superloop might check sensors and update motors in sequence.
- A state machine could have defined modes: "following line," "turning left," "stopping."
- An RTOS could handle sensor reading, motor control, and communication in separate tasks, but it is complex and not recommended for simple projects.

πŸ’‘ Question: When should you use a superloop, and when is a state machine better?


Structural Architectures (How the system is built)

  • Layered Approach:
    • Application (App) – Implements logic, processes data, and controls behavior.
    • Drivers – Interface between HW and software, handling low-level operations.
    • Hardware (HW) – Physical components like sensors & motors.

πŸ“Œ Example for a Robot: - Hardware (HW): Sensors, motors, microcontroller.
- Drivers: Read sensor values, control motors.
- Application: Interprets sensor data and makes movement decisions.

πŸ’‘ Question: Why separate hardware access from the main logic?


How the Components Interact – A Practical Example

Let’s take the line-following robot and see how the code interacts.

πŸ“Œ Step-by-step data flow: 1. Sensors detect the line and send data to the processor.
2. Main logic (state machine or superloop) decides the next action: move forward, turn, or stop.
3. Motor control module receives movement commands and adjusts speed or direction.

This modular approach makes debugging and extending functionality easier.

A simple State Machine Example for line detection:

StartForwardTurn RightTurn LeftStop Line DetectedLine Lost LeftLine Lost RightLine DetectedLine DetectedObstacleClear Path
StartForwardTurn RightTurn LeftStop Line DetectedLine Lost LeftLine Lost RightLine DetectedLine DetectedObstacleClear Path

For Larger Team Projects

  • Use layered architecture (e.g., UI ↔ Logic ↔ Database).
  • Use APIs and well-defined interfaces between parts.

πŸ“Œ Example Structure for a Student Project:

/project_folder
  β”œβ”€β”€ main.py        # Entry point
  β”œβ”€β”€ logic.py       # Core functions
  β”œβ”€β”€ ui.py          # User interface
  β”œβ”€β”€ database.py    # Handles data storage
  β”œβ”€β”€ tests.py       # Automated tests

Working in a Team

If multiple people work on the project, divide tasks.

πŸ“Œ Example Role Distribution

Team Member Task
Alice Motor control module (motors.py)
Bob Sensor handling (sensors.py)
Charlie Main logic (main.py)
Dave OLED display (display.py)

How to Ensure Parts Work Together

  • Define function names & data formats before coding.
  • Write a short README explaining how the modules interact.
  • Test individual components first, then integrate them.

Implementation & Writing Code

Once the design is clear, start coding following best practices:

Key Principles:

βœ” Write Clean & Readable Code (use comments and good variable names)
βœ” Follow DRY Principle (Don’t Repeat Yourself)
βœ” Use Functions and Classes for Modularity
βœ” Test Your Code Often

Example (Python)

def calculate_area(width, height):
    """Calculate the area of a rectangle."""
    return width * height

Working in a Group

When working in a team, divide responsibilities and ensure smooth collaboration.

How to Divide the Work:

  • Assign different modules to different team members.
  • Clearly define how modules interact (e.g., function parameters, return values).
  • Use interfaces (APIs) or shared data structures.

πŸ“Œ Example Role Distribution

Team Member Responsibility
Alice UI design
Bob Backend logic
Charlie Database management
Dave Testing & debugging

πŸ’‘ Tip: Hold short meetings to sync up and avoid redundant work.


Version Control & Collaboration with Git

Using Git allows multiple developers to work together without conflicts.

Basic Git Commands:

git init  # Initialize a new repository
git clone <repo_url>  # Copy an existing repo
git branch feature_x  # Create a new branch
git checkout feature_x  # Switch to a branch
git add .  # Stage changes
git commit -m "Added new feature"  # Commit changes
git push origin feature_x  # Push to remote repo
git pull  # Get latest changes

Best Practices in Git:

βœ” Use Branches – Each developer works on a separate branch.
βœ” Write Clear Commit Messages – Example: "Fixed bug in login system"
βœ” Review Code Before Merging – Helps prevent errors.


Testing and Debugging

βœ” Write unit tests to test individual components.
βœ” Use print statements or logging for debugging.
βœ” Automate testing where possible.

Example Test (Python)

def test_calculate_area():
    assert calculate_area(5, 10) == 50

Finalizing & Deployment

βœ” Ensure all modules work together
βœ” Optimize performance (remove unnecessary code, improve efficiency)
βœ” Write clear documentation for users & developers
βœ” Package and distribute the software


Summary

Step Description
1. Requirement Analysis Understand the problem and define the goals.
2. Decomposition Break the problem into small, manageable parts.
3. Software Architecture Define how different parts of the software interact.
4. Implementation Write clean, modular, and reusable code.
5. Working in a Team Divide tasks and ensure modules can integrate smoothly.
6. Version Control Use Git to track changes and collaborate efficiently.
7. Testing & Debugging Ensure the software works correctly before finalizing.
8. Deployment Package, document, and release the software.

Final Thoughts

Whether you're building a simple student project or a larger group software, following structured software design principles helps create better, more maintainable code.

Using Git, proper modularization, and testing can improve collaboration and make your software more reliable.