System Architecture
Relevant source files
The following files were used as context for generating this wiki page:- README.md
- Firmware/App/Version.h
- Firmware/App/Config/Config.h
- Firmware/Vendor/Artery/Platform/AT32F421/Config/mcu_config.h
- Firmware/Vendor/Artery/Application/HAL/HAL_Config.h
- Firmware/App/Utils/Shell/Shell.h
- Firmware/App/Utils/Shell/Shell.c
- Firmware/App/Service/DataProc/DP_Version.cpp
- Firmware/App/Service/DataProc/Def/Version.h
- Firmware/Vendor/Artery/ArduinoAPI/Arduino.h
- Firmware/Vendor/Artery/Platform/AT32F421/MDK-ARM/RTE/_AT32F421/RTE_Components.h
- Firmware/Vendor/Artery/ArduinoAPI/Wire.cpp
- Firmware/Vendor/Artery/ArduinoAPI/WireBase.h
- Firmware/App/Service/DataProc/Def/Power.h
The DutyCycle project is an embedded firmware for a "Work-Life Duty Cycle Clock". The system is built upon an Artery AT32F421 microcontroller and features a layered architecture that separates application logic, services, hardware abstraction, and low-level MCU drivers. Key architectural features include a command-line shell for configuration via UART, a data processing service layer for managing system state, and an Arduino-compatible API for simplified hardware interaction.
The firmware is designed to be compiled using Keil MDK v5.25 or higher. It provides user interactions through button presses for power control, battery level display, and mute functionality. Time is displayed unconventionally by mapping it to the scale of a power factor meter or a volt/amp meter.
Sources: README.md, Firmware/Vendor/Artery/Platform/AT32F421/MDK-ARM/RTE/_AT32F421/RTE_Components.h
High-Level Architecture
The firmware is structured in several distinct layers, promoting modularity and separation of concerns. The main layers are the Application, Service Layer, Hardware Abstraction Layer (HAL), and the Vendor/MCU Layer.
This diagram illustrates the layered structure of the DutyCycle firmware.
- Application: Contains the main business logic of the clock, including button press interpretation and the unique mapping of time to the meter's display.
- Service Layer: Provides system-level services. This includes the
DataProcmodules for managing state (e.g., version, power) and the interactive command shell for configuration. - Hardware Abstraction Layer (HAL): An Arduino-compatible API and custom HAL functions provide a simplified interface to control hardware peripherals like motors, buzzers, and buttons.
- Vendor/MCU Layer: Consists of the low-level drivers provided by the MCU vendor (Artery), including the Standard Peripherals Library and the CMSIS core for ARM Cortex-M processors.
Sources: README.md, Firmware/App/Service/DataProc/DP_Version.cpp, Firmware/App/Utils/Shell/Shell.h, Firmware/Vendor/Artery/ArduinoAPI/Arduino.h, Firmware/Vendor/Artery/Platform/AT32F421/MDK-ARM/RTE/_AT32F421/RTE_Components.h
Firmware Configuration
Configuration is managed across several files, defining everything from application behavior to low-level hardware settings.
Application Configuration
High-level application settings are defined in Config.h.
| Parameter | Default Value | Description |
|---|---|---|
CONFIG_WATCHDOG_TIMEOUT | 10 | Watchdog timeout in seconds. |
Sources: Firmware/App/Config/Config.h:23-26
MCU Configuration
Core microcontroller and peripheral settings are located in mcu_config.h. This file configures timers, serial communication, I2C, SPI, and other fundamental hardware aspects.
| Parameter | Default Value | Description |
|---|---|---|
SYSTICK_TICK_FREQ | 1000 | System tick frequency in Hz. |
SERIAL_RX_BUFFER_SIZE | 64 | Size of the hardware serial receive buffer. |
SERIAL_1_ENABLE | 1 | Enables USART1 for serial communication. |
WIRE_BUFF_SIZE | 32 | Buffer size for the I2C (Wire) library. |
WIRE_SCL_PIN | PB6 | Pin for I2C Clock. |
WIRE_SDA_PIN | PB7 | Pin for I2C Data. |
PWM_RESOLUTION_DEFAULT | 1000 | Default resolution for PWM signals. |
PWM_FREQUENCY_DEFAULT | 10000 | Default frequency for PWM signals in Hz. |
TONE_TIMER_DEFAULT | TIM1 | Default timer used for the tone() function. |
Sources: Firmware/Vendor/Artery/Platform/AT32F421/Config/mcu_config.h
Hardware Abstraction (HAL) Configuration
Pin mappings for specific hardware components are defined in HAL_Config.h. This file abstracts the physical pin connections for different hardware versions of the device.
| Parameter | Definition | Description |
|---|---|---|
CONFIG_MOTOR_OUT1_PIN | PA2 | Motor driver output 1. |
CONFIG_MOTOR_OUT2_PIN | PA3 | Motor driver output 2. |
CONFIG_BUZZ_PIN | HAL_PinRemap(CONFIG_BUZZ_V1_2_PIN) | Buzzer pin, remapped based on hardware version. |
CONFIG_BATT_DET_PIN | HAL_PinRemap(CONFIG_BATT_DET_V1_2_PIN) | Battery voltage detection pin. |
CONFIG_PWR_EN_PIN | HAL_PinRemap(CONFIG_PWR_EN_V1_2_PIN) | Power enable pin. |
CONFIG_BUTTON_SEL_PIN | HAL_PinRemap(CONFIG_BUTTON_SEL_V1_2_PIN) | Button input pin. |
CONFIG_LOG_SERIAL | Serial | The serial interface used for logging. |
Sources: Firmware/Vendor/Artery/Application/HAL/HAL_Config.h
Command Shell Interface
The system includes a command-line shell for configuration and diagnostics, accessible via a serial terminal (115200 8N1). This component is a key part of the service layer, allowing developers and users to interact with the device at runtime.
The shell is based on a lightweight, portable C library. It is initialized with callbacks for reading and writing characters and for getting system ticks.
// Shell.h
bool shell_init(
shell_reader_t reader,
shell_writer_t writer,
shell_tick_get_t tick_get,
const char * msg,
const char * prompt
);
int shell_task(void);
Sources: Firmware/App/Utils/Shell/Shell.h:202-212, Firmware/App/Utils/Shell/Shell.h:268-274
The core of the shell's operation is the shell_task() function, which must be called periodically. It reads characters from the input stream, parses them into commands and arguments, and executes the corresponding registered function.
The following sequence diagram shows the process of handling a command.
Commands are registered with the shell using shell_register, which associates a command string with a function pointer.
// Shell.h
struct shell_command_entry
{
shell_program_t shell_program;
const char * shell_command_string;
};
bool shell_register(shell_program_t program, const char * string);
Sources: Firmware/App/Utils/Shell/Shell.h:145-150, Firmware/App/Utils/Shell/Shell.h:225-233
Data Processing Service
The DataProc service is a modular system for handling different data domains within the application. Each module, like DP_Version, encapsulates logic related to a specific feature. These modules appear to be built around a DataNode concept, which uses an event-based callback system.
The DP_Version module, for example, is responsible for providing firmware, hardware, and build information. It registers a callback for the EVENT_PULL event. When this event is triggered, the onEvent method populates a Version_Info_t structure with the relevant data.
This class diagram shows the structure of the DP_Version module.
The data structures used by DataProc modules are defined in the Firmware/App/Service/DataProc/Def/ directory.
Version Information
The Version_Info_t struct holds all version-related strings.
// Firmware/App/Service/DataProc/Def/Version.h
typedef struct Version_Info {
const char* name;
const char* software;
const char* hardware;
const char* author;
const char* website;
const char* compiler;
const char* buildDate;
const char* buildTime;
} Version_Info_t;
Sources: Firmware/App/Service/DataProc/Def/Version.h:26-42
This information is sourced from Version.h and compiler macros at build time.
Power Information
Similarly, the Power_Info_t struct defines the data model for power management, including battery status, voltage, and uptime.
// Firmware/App/Service/DataProc/Def/Power.h
typedef struct Power_Info {
POWER_CMD cmd;
bool isReady;
bool isCharging;
bool isBatteryLow;
uint8_t level;
uint16_t voltage;
int autoShutdownTime;
uint64_t batteryUseTime;
uint64_t uptime;
} Power_Info_t;
Sources: Firmware/App/Service/DataProc/Def/Power.h:32-48
Hardware Abstraction & Arduino API
The project leverages a custom Arduino-compatible API to simplify hardware interactions. This layer sits on top of the Artery Standard Peripherals Library.
Arduino API
Arduino.h provides familiar functions like pinMode, digitalWrite, analogRead, and delay. This allows for rapid development and portability of application-level code. It also defines standard constants like HIGH, LOW, INPUT, and OUTPUT.
Sources: Firmware/Vendor/Artery/ArduinoAPI/Arduino.h
I2C (Wire) Implementation
The I2C communication is handled by the TwoWire class, which is a software implementation of the I2C protocol. It inherits from the abstract WireBase class.
This diagram shows the relationship between the I2C classes.
The TwoWire class implements the process() method, which handles the low-level bit-banging of the I2C protocol, including start/stop conditions, address shifting, and data transfer. An instance of TwoWire is globally available as Wire.
Sources: Firmware/Vendor/Artery/ArduinoAPI/WireBase.h, Firmware/Vendor/Artery/ArduinoAPI/Wire.cpp
MCU Peripherals
The project utilizes various on-chip peripherals of the AT32F421 MCU. The enabled Artery firmware library components are specified in RTE_Components.h, indicating usage of:
- ADC, CRM (Clock and Reset Management), DMA, EXINT (External Interrupts), FLASH, GPIO, I2C, TMR (Timers), USART, WDT (Watchdog Timer), and more.
This direct access to vendor libraries, combined with the Arduino API, provides a flexible and powerful hardware interaction model.
Sources: Firmware/Vendor/Artery/Platform/AT32F421/MDK-ARM/RTE/_AT32F421/RTE_Components.h