diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..4c38333 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,70 @@ +name: CI + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main, develop, 'feature/**', 'copilot/**' ] + +jobs: + discover-cpu-families: + name: Discover CPU Families + runs-on: ubuntu-latest + permissions: + contents: read + outputs: + cpu_families: ${{ steps.list-families.outputs.cpu_families }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: List available CPU families + id: list-families + run: | + # Find all CPU families by looking for directories containing port.c + CPU_FAMILIES=$(find src/port -mindepth 2 -maxdepth 2 -name "port.c" -type f -printf '%h\n' | \ + xargs -n1 basename | \ + sort | \ + jq -R -s -c 'split("\n") | map(select(length > 0))') + + echo "Found CPU families: $CPU_FAMILIES" + echo "cpu_families=$CPU_FAMILIES" >> $GITHUB_OUTPUT + + build-dmuart: + name: Build dmuart for ${{ matrix.cpu_family }} + needs: discover-cpu-families + runs-on: ubuntu-latest + permissions: + contents: read + strategy: + matrix: + cpu_family: ${{ fromJson(needs.discover-cpu-families.outputs.cpu_families) }} + + container: + image: chocotechnologies/dmod:1.0.4 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Build dmuart for ${{ matrix.cpu_family }} + run: | + set -e + CPU_FAMILY="${{ matrix.cpu_family }}" + mkdir -p build_$CPU_FAMILY + cd build_$CPU_FAMILY + + cmake .. -DDMUART_MCU_SERIES="${CPU_FAMILY}" + cmake --build . + + echo "Build completed for CPU family: ${CPU_FAMILY}" + ls -lh dmf/ + + - name: Verify dmuart_port module files + run: | + CPU_FAMILY="${{ matrix.cpu_family }}" + echo "Checking for dmuart_port module files for ${CPU_FAMILY}..." + ls -lh build_$CPU_FAMILY/dmf/ + test -f build_$CPU_FAMILY/dmf/dmuart_port.dmf + test -f build_$CPU_FAMILY/dmf/dmuart_port_version.txt + echo "Module files present for ${CPU_FAMILY}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..b7d1785 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,252 @@ +name: Release + +on: + release: + types: [created] + +jobs: + discover-cpu-families: + name: Discover CPU Families + runs-on: ubuntu-latest + permissions: + contents: read + outputs: + cpu_families: ${{ steps.list-families.outputs.cpu_families }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: List available CPU families + id: list-families + run: | + # Find all CPU families by looking for directories containing port.c + CPU_FAMILIES=$(find src/port -mindepth 2 -maxdepth 2 -name "port.c" -type f -printf '%h\n' | \ + xargs -n1 basename | \ + sort | \ + jq -R -s -c 'split("\n") | map(select(length > 0))') + + echo "Found CPU families: $CPU_FAMILIES" + echo "cpu_families=$CPU_FAMILIES" >> $GITHUB_OUTPUT + + build-dmuart: + name: Build dmuart_port for ${{ matrix.cpu_family }} + needs: discover-cpu-families + runs-on: ubuntu-latest + permissions: + contents: write + strategy: + matrix: + cpu_family: ${{ fromJson(needs.discover-cpu-families.outputs.cpu_families) }} + + container: + image: chocotechnologies/dmod:1.0.4 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Extract version from tag + id: get_version + run: | + # Extract version from tag (e.g., v1.2 -> 1.2) + VERSION="${{ github.event.release.tag_name }}" + VERSION="${VERSION#v}" # Remove 'v' prefix if present + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Extracted version: $VERSION" + + - name: Build dmuart for ${{ matrix.cpu_family }} + run: | + set -e + CPU_FAMILY="${{ matrix.cpu_family }}" + BUILD_NAME="port-${CPU_FAMILY}" + echo "BUILD_NAME=$BUILD_NAME" >> $GITHUB_ENV + echo "ARTIFACT_NAME=release-$BUILD_NAME" >> $GITHUB_ENV + mkdir -p build_$BUILD_NAME + cd build_$BUILD_NAME + + cmake .. \ + -DDMOD_MODULE_VERSION="${{ steps.get_version.outputs.version }}" \ + -DDMUART_MCU_SERIES="${CPU_FAMILY}" + + cmake --build . + + echo "Build completed for CPU family: ${CPU_FAMILY}" + ls -la dmf/ || echo "No dmf directory" + ls -la dmfc/ || echo "No dmfc directory" + + - name: Add release notes and create release archives + run: | + set -e + BUILD_DIR="build_$BUILD_NAME" + TAG="${{ github.event.release.tag_name }}" + CPU="${{ matrix.cpu_family }}" + + # Add release notes to dmuart package and create release archive + echo "${{ github.event.release.body }}" > "$BUILD_DIR/packages/dmuart/RELEASE_NOTES.txt" + cd "$BUILD_DIR/packages/dmuart" + zip -r "${OLDPWD}/dmuart-${TAG}-${CPU}.zip" . + cd - + + # Add release notes to dmuart_port package and create release archive + echo "${{ github.event.release.body }}" > "$BUILD_DIR/packages/dmuart_port/RELEASE_NOTES.txt" + cd "$BUILD_DIR/packages/dmuart_port" + zip -r "${OLDPWD}/dmuart_port-${TAG}-${CPU}.zip" . + cd - + + echo "Created archives:" + ls -lh dmuart-*.zip dmuart_port-*.zip + + - name: Upload dmuart and dmuart_port artifacts + uses: actions/upload-artifact@v4.4.3 + with: + name: ${{ env.ARTIFACT_NAME }} + path: "dmuart*.zip" + retention-days: 1 + + generate-versions-manifest: + name: Generate versions.dmm + needs: [discover-cpu-families] + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch all history to get all tags + + - name: Generate versions.dmm + run: | + set -e + echo "# List of available versions for dmuart modules" > versions.dmm + echo "# Generated automatically by CI" >> versions.dmm + echo "" >> versions.dmm + + # Get all version tags (starting with 'v') and extract version numbers + VERSIONS=$(git tag -l 'v*' | sed 's/^v//' | sort -V | tr '\n' ' ' | sed 's/ $//') + + # Remove vlatest from the list if present + VERSIONS=$(echo $VERSIONS | sed 's/\blatest\b//g' | xargs) + + if [ -z "$VERSIONS" ]; then + echo "Warning: No version tags found" + VERSIONS="${{ github.event.release.tag_name }}" + VERSIONS="${VERSIONS#v}" + fi + + echo "Found versions: $VERSIONS" + + # Add $version-available directives for the modules + echo "\$version-available dmuart $VERSIONS" >> versions.dmm + echo "\$version-available dmuart_port $VERSIONS" >> versions.dmm + + echo "Generated versions.dmm:" + cat versions.dmm + + - name: Upload versions.dmm as artifact + uses: actions/upload-artifact@v4.4.3 + with: + name: versions-manifest + path: versions.dmm + retention-days: 1 + + upload-release-assets: + name: Upload Release Assets + needs: [build-dmuart, generate-versions-manifest] + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4.1.3 + with: + path: artifacts + + - name: Display artifact structure + run: | + echo "Downloaded artifacts:" + ls -lR artifacts/ + + - name: Upload release assets to versioned tag + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + set -e + # Upload all release archives to the GitHub release + shopt -s nullglob + zip_files=(artifacts/release-*/*.zip) + + if [ ${#zip_files[@]} -eq 0 ]; then + echo "Error: No artifacts found to upload" + exit 1 + fi + + for zip_file in "${zip_files[@]}"; do + echo "Uploading $zip_file to ${{ github.event.release.tag_name }}..." + gh release upload ${{ github.event.release.tag_name }} \ + "$zip_file" \ + --repo ${{ github.repository }} \ + --clobber + done + + # Upload versions.dmm to the versioned release + if [ -f artifacts/versions-manifest/versions.dmm ]; then + echo "Uploading versions.dmm to ${{ github.event.release.tag_name }}..." + gh release upload ${{ github.event.release.tag_name }} \ + artifacts/versions-manifest/versions.dmm \ + --repo ${{ github.repository }} \ + --clobber + fi + + echo "Successfully uploaded ${#zip_files[@]} artifact(s) to ${{ github.event.release.tag_name }}" + + - name: Create or update latest release + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + set -e + + # Check if vlatest release exists + if gh release view vlatest --repo ${{ github.repository }} >/dev/null 2>&1; then + echo "Release vlatest exists, deleting it..." + gh release delete vlatest --repo ${{ github.repository }} --yes + fi + + # Create new vlatest release + echo "Creating vlatest release..." + gh release create vlatest \ + --repo ${{ github.repository }} \ + --title "Latest Release (based on ${{ github.event.release.tag_name }})" \ + --notes "This release always points to the latest stable version. Currently based on ${{ github.event.release.tag_name }}." + + - name: Upload release assets to latest tag + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + set -e + shopt -s nullglob + zip_files=(artifacts/release-*/*.zip) + + for zip_file in "${zip_files[@]}"; do + echo "Uploading $zip_file to vlatest..." + gh release upload vlatest \ + "$zip_file" \ + --repo ${{ github.repository }} \ + --clobber + done + + # Upload versions.dmm to the latest release + if [ -f artifacts/versions-manifest/versions.dmm ]; then + echo "Uploading versions.dmm to vlatest..." + gh release upload vlatest \ + artifacts/versions-manifest/versions.dmm \ + --repo ${{ github.repository }} \ + --clobber + fi + + echo "Successfully uploaded ${#zip_files[@]} artifact(s) to vlatest" diff --git a/.gitignore b/.gitignore index 1f99f9d..e2843d9 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,15 @@ CTestTestfile.cmake _deps CMakeUserPresets.json +# Build directory +build/ +build_*/ +_codeql_build_dir/ +_codeql_detected_source_root +*.dmf +*.dmfc + + # CLion # JetBrains specific template is maintained in a separate JetBrains.gitignore that can # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..86d5026 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "files.associations": { + "dmfsi.h": "c" + }, + "C_Cpp.default.compileCommands": "${workspaceFolder}/build/compile_commands.json", + "C_Cpp.intelliSenseEngine": "default", + "C_Cpp.errorSquiggles": "enabled" +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..780821b --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,61 @@ +{ + // See https://code.visualstudio.com/docs/editor/tasks for more information + "version": "2.0.0", + "tasks": [ + { + "label": "CMake: Configure", + "type": "shell", + "command": "cmake", + "args": [ + "-S", + "${workspaceFolder}", + "-B", + "${workspaceFolder}/build", + "-DDMOD_DIR=${input:dmodDir}" + ], + "group": "build", + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": [] + }, + { + "label": "CMake: Build", + "type": "shell", + "command": "cmake", + "args": [ + "--build", + "${workspaceFolder}/build" + ], + "group": "build", + "problemMatcher": [] + }, + { + "label": "CMake: Clean", + "type": "shell", + "command": "cmake", + "args": [ + "--build", + "${workspaceFolder}/build", + "--target", + "clean" + ], + "group": "build", + "problemMatcher": [] + } + ], + "inputs": [ + { + "id": "dmodDir", + "description": "Provide the path to the DMOD_DIR directory", + "default": "${workspaceFolder}/../dmod", + "type": "promptString" + }, + { + "id": "repoPath", + "description": "Repository path", + "default": "${workspaceFolder}", + "type": "promptString" + } + ] +} diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..1680fbe --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,109 @@ +# ===================================================================== +# DMOD UART Driver Module +# ===================================================================== +cmake_minimum_required(VERSION 3.18) + +# ====================================================================== +# Parameters +# ====================================================================== +set(DMUART_MCU_SERIES "stm32f7" CACHE STRING "Target MCU series") + +# ====================================================================== +# Include target architecture configuration +# ====================================================================== +include(${CMAKE_CURRENT_SOURCE_DIR}/src/port/${DMUART_MCU_SERIES}/config.cmake) + +# ====================================================================== +# For VS Code +# ====================================================================== +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# ====================================================================== +# dmuart Version +# ====================================================================== +# Allow version to be passed as a parameter, default to 0.1 +if(NOT DEFINED DMOD_MODULE_VERSION) + set(DMOD_MODULE_VERSION "0.1" CACHE STRING "DMOD module version") +endif() + +# ====================================================================== +# Fetch DMOD repository +# ====================================================================== +include(FetchContent) +FetchContent_Declare( + dmod + GIT_REPOSITORY https://github.com/choco-technologies/dmod.git + GIT_TAG develop +) + +# ====================================================================== +# DMOD Configuration +# ====================================================================== +set(DMOD_MODE "DMOD_MODULE" CACHE STRING "DMOD build mode") +set(DMOD_BUILD_TESTS OFF CACHE BOOL "Build tests") +set(DMOD_BUILD_EXAMPLES OFF CACHE BOOL "Build examples") +set(DMOD_BUILD_TOOLS OFF CACHE BOOL "Build tools") +set(DMOD_BUILD_TEMPLATES OFF CACHE BOOL "Build templates") + +FetchContent_MakeAvailable(dmod) + +project(dmuart +VERSION ${DMOD_MODULE_VERSION} +DESCRIPTION "DMOD UART Driver Module" +LANGUAGES C CXX) + +set(DMOD_DIR ${dmod_SOURCE_DIR} CACHE PATH "DMOD source directory") + + +# ====================================================================== +# Import dmod functions and macros +# ====================================================================== +set(DMOD_DIR ${dmod_SOURCE_DIR} CACHE PATH "DMOD source directory") +set(DMOD_SCRIPTS_DIR ${DMOD_DIR}/scripts CACHE PATH "DMOD scripts directory") +include(${DMOD_DIR}/paths.cmake) +dmod_setup_external_module() + +# ====================================================================== +# Subdirectories +# ====================================================================== +add_subdirectory(src/port) + +# ====================================================================== +# dmuart Module Configuration +# ====================================================================== +# Name of the module +set(DMOD_MODULE_NAME dmuart) + +# Version is already set above and used in project() +# No need to set it again here + +# Author (should be string) +set(DMOD_AUTHOR_NAME "Patryk Kubiak") + +# Stack size for the module (should be integer) +set(DMOD_STACK_SIZE 1024) + +# +# dmod_add_library - create a library module +# it has the same signature as add_library +# and can be used in the same way after the creation +# (for example, to link libraries) +# +dmod_add_library(${DMOD_MODULE_NAME} ${DMOD_MODULE_VERSION} + # List of source files - can include C and C++ files + src/dmuart.c +) + +dmod_link_modules(${DMOD_MODULE_NAME} + dmdrvi + dmini + dmhaman +) + +target_link_libraries(${DMOD_MODULE_NAME} + dmuart_port_if +) + +target_include_directories(${DMOD_MODULE_NAME} PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/include +) \ No newline at end of file diff --git a/README.md b/README.md index 180ae19..d1e41f9 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,222 @@ -# dmuart -Repo with UART driver +# DMUART - DMOD UART Driver Module + +[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) + +A DMOD (Dynamic Modular System) module for UART (Universal Asynchronous Receiver-Transmitter) communication on embedded microcontrollers. + +## Features + +- **Standard DMDRVI Interface**: Read/write/ioctl device access pattern +- **Configurable Parameters**: Baud rate, data bits, parity, stop bits, flow control +- **Multiple Instances**: Support for multiple UART peripherals simultaneously +- **Hardware Abstraction**: Platform-independent API with hardware-specific implementations +- **DMDRVI Integration**: Full DMOD driver interface implementation +- **STM32 Support**: STM32F7 family currently supported +- **Extensible**: Easy to add support for additional microcontroller families + +## Quick Start + +### Installation + +Using `dmf-get` from the DMOD release package: + +```bash +dmf-get install dmuart +``` + +Or install with a pre-configured setup for your board: + +```bash +# Create a dependencies file (deps.dmd) +echo "dmuart@latest board/stm32f746g-disco.ini" > deps.dmd + +# Install with configuration +dmf-get -d deps.dmd --config-dir ./config +``` + +### Basic Usage + +1. **Create a configuration file** (`config.ini`): + +```ini +[dmuart] +baudrate=115200 +databits=8 +parity=none +stopbits=1 +flowcontrol=none +instance=1 +``` + +2. **Use in your code**: + +```c +#include "dmuart.h" +#include "dmdrvi.h" +#include "dmini.h" + +// Load configuration and create device +dmini_context_t config = dmini_load("config.ini"); +dmdrvi_dev_num_t dev_num = {0}; +dmdrvi_context_t uart_ctx = dmuart_dmdrvi_create(config, &dev_num); + +// Open device for read/write +void* handle = dmuart_dmdrvi_open(uart_ctx, DMDRVI_O_RDWR); + +// Write data +const char* msg = "Hello UART!\n"; +dmuart_dmdrvi_write(uart_ctx, handle, msg, strlen(msg), 0); + +// Read data +char buffer[64]; +size_t n = dmuart_dmdrvi_read(uart_ctx, handle, buffer, sizeof(buffer), 0); + +// Cleanup +dmuart_dmdrvi_close(uart_ctx, handle); +dmuart_dmdrvi_free(uart_ctx); +dmini_free(config); +``` + +## Building + +### Prerequisites + +- CMake 3.18 or higher +- ARM GCC toolchain (for embedded targets) +- DMOD framework (automatically fetched) + +### Build Commands + +```bash +# Configure for STM32F7 +cmake -DDMUART_MCU_SERIES=stm32f7 -B build + +# Build +cmake --build build +``` + +## Documentation + +Comprehensive documentation is available in the `docs/` directory: + +- **[dmuart.md](docs/dmuart.md)** - Module overview and architecture +- **[api-reference.md](docs/api-reference.md)** - Complete API documentation +- **[configuration.md](docs/configuration.md)** - Configuration guide with examples +- **[port-implementation.md](docs/port-implementation.md)** - Guide for adding hardware support + +View documentation using `dmf-man`: + +```bash +dmf-man dmuart # Main documentation +dmf-man dmuart api # API reference +dmf-man dmuart config # Configuration guide +dmf-man dmuart port # Port implementation guide +``` + +## Supported Platforms + +| Platform | Status | Notes | +|----------|--------|-------| +| STM32F7 | ✅ Supported | Full UART support (USART1-6, UART4-5, UART7-8) | +| Other STM32 | 🔧 In Progress | Easy to add using STM32 common code | +| Other MCUs | 📋 Planned | Contributions welcome | + +## Configuration Examples + +### Standard 115200 8N1 + +```ini +[dmuart] +baudrate=115200 +databits=8 +parity=none +stopbits=1 +flowcontrol=none +instance=1 +``` + +### 9600 Baud with Even Parity + +```ini +[dmuart] +baudrate=9600 +databits=8 +parity=even +stopbits=1 +flowcontrol=none +instance=2 +``` + +### High-Speed with Hardware Flow Control + +```ini +[dmuart] +baudrate=921600 +databits=8 +parity=none +stopbits=1 +flowcontrol=rts_cts +instance=1 +``` + +## Development + +### Project Structure + +``` +dmuart/ +├── configs/ # Pre-configured board and MCU configurations +│ ├── board/ # Board-specific configurations +│ └── mcu/ # MCU-specific configurations +├── docs/ # Documentation (markdown format) +├── examples/ # Example configurations +├── include/ # Public headers +│ ├── dmuart.h # Main API +│ ├── dmuart_port.h # Port layer API +│ └── port/ # Port-specific headers +├── src/ +│ ├── dmuart.c # Core implementation +│ └── port/ # Hardware-specific implementations +│ ├── stm32_common/ # Common STM32 code +│ └── stm32f7/ # STM32F7 port +├── tests/ # Test applications +├── CMakeLists.txt # Build configuration +└── manifest.dmm # DMOD manifest +``` + +### Adding New Platform Support + +See [Port Implementation Guide](docs/port-implementation.md) for detailed instructions on adding support for new microcontrollers. + +## Contributing + +Contributions are welcome! Please: + +1. Fork the repository +2. Create a feature branch +3. Make your changes +4. Submit a pull request + +## License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. + +## Authors + +- Patryk Kubiak - Initial work + +## Related Projects + +- [DMOD](https://github.com/choco-technologies/dmod) - Dynamic Modular System framework +- [DMINI](https://github.com/choco-technologies/dmini) - INI configuration parser for DMOD +- [DMDRVI](https://github.com/choco-technologies/dmdrvi) - DMOD Driver Interface +- [DMGPIO](https://github.com/choco-technologies/dmgpio) - GPIO driver module +- [DMCLK](https://github.com/choco-technologies/dmclk) - Clock configuration module + +## Support + +For issues, questions, or contributions: + +- Open an issue on GitHub +- Check the documentation in `docs/` +- Use `dmf-man dmuart` for command-line help diff --git a/configs/README.md b/configs/README.md new file mode 100644 index 0000000..fa7b362 --- /dev/null +++ b/configs/README.md @@ -0,0 +1,91 @@ +# DMUART Configuration Files + +This directory contains pre-configured UART settings for various development boards and MCUs. + +## Directory Structure + +``` +configs/ +├── board/ # Board-specific configurations +│ ├── nucleo-f401re/ # NUCLEO-F401RE +│ │ ├── usart1.ini +│ │ └── usart2.ini +│ ├── nucleo-f411re/ # NUCLEO-F411RE +│ │ ├── usart1.ini +│ │ └── usart2.ini +│ ├── nucleo-f446re/ # NUCLEO-F446RE +│ │ ├── usart1.ini +│ │ └── usart2.ini +│ ├── nucleo-f767zi/ # NUCLEO-F767ZI +│ │ ├── usart3.ini +│ │ └── usart6.ini +│ ├── stm32f4-discovery/ # STM32F4-DISCOVERY +│ │ ├── usart1.ini +│ │ └── usart2.ini +│ ├── stm32f429i-discovery/ # STM32F429I-DISCOVERY +│ │ └── usart1.ini +│ ├── stm32f746g-disco/ # STM32F746G-DISCO +│ │ ├── usart1.ini +│ │ └── usart6.ini +│ └── stm32f769i-discovery/ # STM32F769I-DISCOVERY +│ ├── usart1.ini +│ └── usart6.ini +└── mcu/ # MCU-specific configurations + └── stm32f746zg.ini +``` + +## Configuration Format + +Each UART configuration file contains: +- A UART section with `driver_name=dmuart` and UART parameters +- GPIO pin sections with `driver_name=dmgpio` for TX/RX pin configuration + +This allows `dmdevfs` to automatically configure both the UART and its GPIO pins. + +### Example (stm32f746g-disco/usart1.ini) + +```ini +[stlink_vcp] +driver_name=dmuart +baudrate=921600 +databits=8 +parity=none +stopbits=1 +flowcontrol=none +instance=1 + +[stlink_vcp_tx] +driver_name=dmgpio +pin=PA9 +mode=alternate +alternate_function=7 +speed=maximum +output_circuit=push_pull +pull=up + +[stlink_vcp_rx] +driver_name=dmgpio +pin=PB7 +mode=alternate +alternate_function=7 +pull=up +``` + +## Board Configurations + +| Board | Folder | Default Baud Rate | UART Instances | +|-------|--------|-------------------|----------------| +| NUCLEO-F401RE | `board/nucleo-f401re/` | 921600 | USART1, USART2 | +| NUCLEO-F411RE | `board/nucleo-f411re/` | 921600 | USART1, USART2 | +| NUCLEO-F446RE | `board/nucleo-f446re/` | 921600 | USART1, USART2 | +| NUCLEO-F767ZI | `board/nucleo-f767zi/` | 921600 | USART3, USART6 | +| STM32F4-DISCOVERY | `board/stm32f4-discovery/` | 921600 | USART1, USART2 | +| STM32F429I-DISCOVERY | `board/stm32f429i-discovery/` | 921600 | USART1 | +| STM32F746G-DISCO | `board/stm32f746g-disco/` | 921600 | USART1, USART6 | +| STM32F769I-DISCOVERY | `board/stm32f769i-discovery/` | 921600 | USART1, USART6 | + +## MCU Configurations + +| MCU | File | Notes | +|-----|------|-------| +| STM32F746ZG | `mcu/stm32f746zg.ini` | Default 921600 8N1 | diff --git a/configs/board/nucleo-f401re/usart1.ini b/configs/board/nucleo-f401re/usart1.ini new file mode 100644 index 0000000..3b13717 --- /dev/null +++ b/configs/board/nucleo-f401re/usart1.ini @@ -0,0 +1,27 @@ +; NUCLEO-F401RE USART1 (Arduino connector D0/D1) +; Pins: PA9 (TX), PA10 (RX) + +[arduino_uart_tx] +driver_name=dmgpio +pin=PA9 +mode=alternate +alternate_function=7 +speed=maximum +output_circuit=push_pull +pull=up + +[arduino_uart_rx] +driver_name=dmgpio +pin=PA10 +mode=alternate +alternate_function=7 +pull=up + +[arduino_uart] +driver_name=dmuart +baudrate=921600 +databits=8 +parity=none +stopbits=1 +flowcontrol=none +instance=1 diff --git a/configs/board/nucleo-f401re/usart2.ini b/configs/board/nucleo-f401re/usart2.ini new file mode 100644 index 0000000..567c559 --- /dev/null +++ b/configs/board/nucleo-f401re/usart2.ini @@ -0,0 +1,27 @@ +; NUCLEO-F401RE USART2 (ST-Link Virtual COM Port) +; Pins: PA2 (TX), PA3 (RX) + +[stlink_vcp_tx] +driver_name=dmgpio +pin=PA2 +mode=alternate +alternate_function=7 +speed=maximum +output_circuit=push_pull +pull=up + +[stlink_vcp_rx] +driver_name=dmgpio +pin=PA3 +mode=alternate +alternate_function=7 +pull=up + +[stlink_vcp] +driver_name=dmuart +baudrate=921600 +databits=8 +parity=none +stopbits=1 +flowcontrol=none +instance=2 diff --git a/configs/board/nucleo-f411re/usart1.ini b/configs/board/nucleo-f411re/usart1.ini new file mode 100644 index 0000000..5f079d9 --- /dev/null +++ b/configs/board/nucleo-f411re/usart1.ini @@ -0,0 +1,27 @@ +; NUCLEO-F411RE USART1 (Arduino connector D0/D1) +; Pins: PA9 (TX), PA10 (RX) + +[arduino_uart_tx] +driver_name=dmgpio +pin=PA9 +mode=alternate +alternate_function=7 +speed=maximum +output_circuit=push_pull +pull=up + +[arduino_uart_rx] +driver_name=dmgpio +pin=PA10 +mode=alternate +alternate_function=7 +pull=up + +[arduino_uart] +driver_name=dmuart +baudrate=921600 +databits=8 +parity=none +stopbits=1 +flowcontrol=none +instance=1 diff --git a/configs/board/nucleo-f411re/usart2.ini b/configs/board/nucleo-f411re/usart2.ini new file mode 100644 index 0000000..101516c --- /dev/null +++ b/configs/board/nucleo-f411re/usart2.ini @@ -0,0 +1,27 @@ +; NUCLEO-F411RE USART2 (ST-Link Virtual COM Port) +; Pins: PA2 (TX), PA3 (RX) + +[stlink_vcp_tx] +driver_name=dmgpio +pin=PA2 +mode=alternate +alternate_function=7 +speed=maximum +output_circuit=push_pull +pull=up + +[stlink_vcp_rx] +driver_name=dmgpio +pin=PA3 +mode=alternate +alternate_function=7 +pull=up + +[stlink_vcp] +driver_name=dmuart +baudrate=921600 +databits=8 +parity=none +stopbits=1 +flowcontrol=none +instance=2 diff --git a/configs/board/nucleo-f446re/usart1.ini b/configs/board/nucleo-f446re/usart1.ini new file mode 100644 index 0000000..dc911f0 --- /dev/null +++ b/configs/board/nucleo-f446re/usart1.ini @@ -0,0 +1,27 @@ +; NUCLEO-F446RE USART1 (Arduino connector D0/D1) +; Pins: PA9 (TX), PA10 (RX) + +[arduino_uart_tx] +driver_name=dmgpio +pin=PA9 +mode=alternate +alternate_function=7 +speed=maximum +output_circuit=push_pull +pull=up + +[arduino_uart_rx] +driver_name=dmgpio +pin=PA10 +mode=alternate +alternate_function=7 +pull=up + +[arduino_uart] +driver_name=dmuart +baudrate=921600 +databits=8 +parity=none +stopbits=1 +flowcontrol=none +instance=1 diff --git a/configs/board/nucleo-f446re/usart2.ini b/configs/board/nucleo-f446re/usart2.ini new file mode 100644 index 0000000..4d73873 --- /dev/null +++ b/configs/board/nucleo-f446re/usart2.ini @@ -0,0 +1,27 @@ +; NUCLEO-F446RE USART2 (ST-Link Virtual COM Port) +; Pins: PA2 (TX), PA3 (RX) + +[stlink_vcp_tx] +driver_name=dmgpio +pin=PA2 +mode=alternate +alternate_function=7 +speed=maximum +output_circuit=push_pull +pull=up + +[stlink_vcp_rx] +driver_name=dmgpio +pin=PA3 +mode=alternate +alternate_function=7 +pull=up + +[stlink_vcp] +driver_name=dmuart +baudrate=921600 +databits=8 +parity=none +stopbits=1 +flowcontrol=none +instance=2 diff --git a/configs/board/nucleo-f767zi/usart3.ini b/configs/board/nucleo-f767zi/usart3.ini new file mode 100644 index 0000000..3f7f227 --- /dev/null +++ b/configs/board/nucleo-f767zi/usart3.ini @@ -0,0 +1,27 @@ +; NUCLEO-F767ZI USART3 (ST-Link Virtual COM Port) +; Pins: PD8 (TX), PD9 (RX) + +[stlink_vcp_tx] +driver_name=dmgpio +pin=PD8 +mode=alternate +alternate_function=7 +speed=maximum +output_circuit=push_pull +pull=up + +[stlink_vcp_rx] +driver_name=dmgpio +pin=PD9 +mode=alternate +alternate_function=7 +pull=up + +[stlink_vcp] +driver_name=dmuart +baudrate=921600 +databits=8 +parity=none +stopbits=1 +flowcontrol=none +instance=3 diff --git a/configs/board/nucleo-f767zi/usart6.ini b/configs/board/nucleo-f767zi/usart6.ini new file mode 100644 index 0000000..9eb3ce7 --- /dev/null +++ b/configs/board/nucleo-f767zi/usart6.ini @@ -0,0 +1,27 @@ +; NUCLEO-F767ZI USART6 (Arduino connector D0/D1) +; Pins: PG14 (TX), PG9 (RX) + +[arduino_uart_tx] +driver_name=dmgpio +pin=PG14 +mode=alternate +alternate_function=8 +speed=maximum +output_circuit=push_pull +pull=up + +[arduino_uart_rx] +driver_name=dmgpio +pin=PG9 +mode=alternate +alternate_function=8 +pull=up + +[arduino_uart] +driver_name=dmuart +baudrate=921600 +databits=8 +parity=none +stopbits=1 +flowcontrol=none +instance=6 diff --git a/configs/board/stm32f4-discovery/usart1.ini b/configs/board/stm32f4-discovery/usart1.ini new file mode 100644 index 0000000..fb506f4 --- /dev/null +++ b/configs/board/stm32f4-discovery/usart1.ini @@ -0,0 +1,27 @@ +; STM32F4-DISCOVERY USART1 +; Pins: PB6 (TX), PB7 (RX) + +[usart1_tx] +driver_name=dmgpio +pin=PB6 +mode=alternate +alternate_function=7 +speed=maximum +output_circuit=push_pull +pull=up + +[usart1_rx] +driver_name=dmgpio +pin=PB7 +mode=alternate +alternate_function=7 +pull=up + +[usart1] +driver_name=dmuart +baudrate=921600 +databits=8 +parity=none +stopbits=1 +flowcontrol=none +instance=1 diff --git a/configs/board/stm32f4-discovery/usart2.ini b/configs/board/stm32f4-discovery/usart2.ini new file mode 100644 index 0000000..a73f022 --- /dev/null +++ b/configs/board/stm32f4-discovery/usart2.ini @@ -0,0 +1,27 @@ +; STM32F4-DISCOVERY USART2 +; Pins: PA2 (TX), PA3 (RX) + +[usart2_tx] +driver_name=dmgpio +pin=PA2 +mode=alternate +alternate_function=7 +speed=maximum +output_circuit=push_pull +pull=up + +[usart2_rx] +driver_name=dmgpio +pin=PA3 +mode=alternate +alternate_function=7 +pull=up + +[usart2] +driver_name=dmuart +baudrate=921600 +databits=8 +parity=none +stopbits=1 +flowcontrol=none +instance=2 diff --git a/configs/board/stm32f429i-discovery/usart1.ini b/configs/board/stm32f429i-discovery/usart1.ini new file mode 100644 index 0000000..b79dc13 --- /dev/null +++ b/configs/board/stm32f429i-discovery/usart1.ini @@ -0,0 +1,27 @@ +; STM32F429I-DISCOVERY USART1 +; Pins: PA9 (TX), PA10 (RX) + +[usart1_tx] +driver_name=dmgpio +pin=PA9 +mode=alternate +alternate_function=7 +speed=maximum +output_circuit=push_pull +pull=up + +[usart1_rx] +driver_name=dmgpio +pin=PA10 +mode=alternate +alternate_function=7 +pull=up + +[usart1] +driver_name=dmuart +baudrate=921600 +databits=8 +parity=none +stopbits=1 +flowcontrol=none +instance=1 diff --git a/configs/board/stm32f746g-disco/usart1.ini b/configs/board/stm32f746g-disco/usart1.ini new file mode 100644 index 0000000..8337529 --- /dev/null +++ b/configs/board/stm32f746g-disco/usart1.ini @@ -0,0 +1,27 @@ +; STM32F746G-DISCO USART1 (ST-Link Virtual COM Port) +; Pins: PA9 (TX), PB7 (RX) + +[stlink_vcp_tx] +driver_name=dmgpio +pin=PA9 +mode=alternate +alternate_function=7 +speed=maximum +output_circuit=push_pull +pull=up + +[stlink_vcp_rx] +driver_name=dmgpio +pin=PB7 +mode=alternate +alternate_function=7 +pull=up + +[stlink_vcp] +driver_name=dmuart +baudrate=921600 +databits=8 +parity=none +stopbits=1 +flowcontrol=none +instance=1 diff --git a/configs/board/stm32f746g-disco/usart6.ini b/configs/board/stm32f746g-disco/usart6.ini new file mode 100644 index 0000000..e30591a --- /dev/null +++ b/configs/board/stm32f746g-disco/usart6.ini @@ -0,0 +1,27 @@ +; STM32F746G-DISCO USART6 (Arduino connector D0/D1) +; Pins: PC6 (TX), PC7 (RX) + +[arduino_uart_tx] +driver_name=dmgpio +pin=PC6 +mode=alternate +alternate_function=8 +speed=maximum +output_circuit=push_pull +pull=up + +[arduino_uart_rx] +driver_name=dmgpio +pin=PC7 +mode=alternate +alternate_function=8 +pull=up + +[arduino_uart] +driver_name=dmuart +baudrate=921600 +databits=8 +parity=none +stopbits=1 +flowcontrol=none +instance=6 diff --git a/configs/board/stm32f769i-discovery/usart1.ini b/configs/board/stm32f769i-discovery/usart1.ini new file mode 100644 index 0000000..5cd52ca --- /dev/null +++ b/configs/board/stm32f769i-discovery/usart1.ini @@ -0,0 +1,27 @@ +; STM32F769I-DISCOVERY USART1 (ST-Link Virtual COM Port) +; Pins: PA9 (TX), PA10 (RX) + +[stlink_vcp_tx] +driver_name=dmgpio +pin=PA9 +mode=alternate +alternate_function=7 +speed=maximum +output_circuit=push_pull +pull=up + +[stlink_vcp_rx] +driver_name=dmgpio +pin=PA10 +mode=alternate +alternate_function=7 +pull=up + +[stlink_vcp] +driver_name=dmuart +baudrate=921600 +databits=8 +parity=none +stopbits=1 +flowcontrol=none +instance=1 diff --git a/configs/board/stm32f769i-discovery/usart6.ini b/configs/board/stm32f769i-discovery/usart6.ini new file mode 100644 index 0000000..fa78804 --- /dev/null +++ b/configs/board/stm32f769i-discovery/usart6.ini @@ -0,0 +1,27 @@ +; STM32F769I-DISCOVERY USART6 (Arduino connector D0/D1) +; Pins: PC6 (TX), PC7 (RX) + +[arduino_uart_tx] +driver_name=dmgpio +pin=PC6 +mode=alternate +alternate_function=8 +speed=maximum +output_circuit=push_pull +pull=up + +[arduino_uart_rx] +driver_name=dmgpio +pin=PC7 +mode=alternate +alternate_function=8 +pull=up + +[arduino_uart] +driver_name=dmuart +baudrate=921600 +databits=8 +parity=none +stopbits=1 +flowcontrol=none +instance=6 diff --git a/configs/mcu/stm32f746zg.ini b/configs/mcu/stm32f746zg.ini new file mode 100644 index 0000000..b981000 --- /dev/null +++ b/configs/mcu/stm32f746zg.ini @@ -0,0 +1,8 @@ +; STM32F746ZG MCU UART configuration +[dmuart] +baudrate=921600 +databits=8 +parity=none +stopbits=1 +flowcontrol=none +instance=1 diff --git a/dmuart.dmr b/dmuart.dmr new file mode 100644 index 0000000..5e9cba5 --- /dev/null +++ b/dmuart.dmr @@ -0,0 +1,34 @@ +# DMOD Resource File for dmuart +# This file specifies where resources should be installed + +# === Core Module === +# Main module file - always installed +dmf=./dmuart.dmf => ${destination}/${module}.dmf + +# Compressed module +dmfc=./dmuart.dmfc => ${destination}/${module}.dmfc + +# Dependencies file (if exists) +dmd=./dmuart.dmd => ${destination}/${module}.dmd + +# Version information +version=./dmuart_version.txt => ${destination}/${module}_version.txt + +# === Documentation === +# Module documentation in markdown format for dmf-man tool +docs=./docs => ${destination}/${module}/docs + +# README file +readme=./README.md => ${destination}/${module}/README.md + +# === Header Files === +# Include directory with all headers (dmuart.h and dmuart_defs.h) +inc=./include => ${destination}/${module}/include + +# === Configuration Files === +# Configuration directory with board and MCU specific configs +configs=./configs => ${destination}/${module}/configs + +# === License === +# License file +license=./LICENSE => ${destination}/${module}/LICENSE diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..39f504a --- /dev/null +++ b/docs/README.md @@ -0,0 +1,21 @@ +# DMUART Documentation + +This directory contains the documentation for the DMUART module. + +## Contents + +- **[dmuart.md](dmuart.md)** - Module overview and architecture +- **[api-reference.md](api-reference.md)** - Complete API documentation +- **[configuration.md](configuration.md)** - Configuration guide with examples +- **[port-implementation.md](port-implementation.md)** - Guide for adding hardware support + +## Viewing Documentation + +You can view these documents using `dmf-man`: + +```bash +dmf-man dmuart # Main documentation +dmf-man dmuart api # API reference +dmf-man dmuart config # Configuration guide +dmf-man dmuart port # Port implementation guide +``` diff --git a/docs/api-reference.md b/docs/api-reference.md new file mode 100644 index 0000000..28c2b04 --- /dev/null +++ b/docs/api-reference.md @@ -0,0 +1,179 @@ +# DMUART API Reference + +## DMDRVI Interface Functions + +### dmuart_dmdrvi_create + +```c +dmdrvi_context_t dmuart_dmdrvi_create(dmini_context_t config, dmdrvi_dev_num_t* dev_num); +``` + +Creates a new UART device context from INI configuration. + +**Parameters:** +- `config` - DMINI context with UART configuration +- `dev_num` - Output pointer for device numbering + +**Returns:** DMDRVI context on success, NULL on failure. + +--- + +### dmuart_dmdrvi_free + +```c +void dmuart_dmdrvi_free(dmdrvi_context_t context); +``` + +Frees the UART device context and deinitializes the hardware. + +--- + +### dmuart_dmdrvi_open + +```c +void* dmuart_dmdrvi_open(dmdrvi_context_t context, int flags); +``` + +Opens a handle to the UART device. + +**Parameters:** +- `context` - DMDRVI context +- `flags` - Open flags (DMDRVI_O_RDONLY, DMDRVI_O_WRONLY, DMDRVI_O_RDWR) + +**Returns:** Device handle on success, NULL on failure. + +--- + +### dmuart_dmdrvi_close + +```c +void dmuart_dmdrvi_close(dmdrvi_context_t context, void* handle); +``` + +Closes the device handle. + +--- + +### dmuart_dmdrvi_read + +```c +size_t dmuart_dmdrvi_read(dmdrvi_context_t context, void* handle, void* buffer, size_t size, uint32_t offset); +``` + +Reads received data from the UART. + +**Parameters:** +- `context` - DMDRVI context +- `handle` - Device handle +- `buffer` - Buffer to read data into +- `size` - Maximum bytes to read +- `offset` - Not used (stream device) + +**Returns:** Number of bytes actually read. + +--- + +### dmuart_dmdrvi_write + +```c +size_t dmuart_dmdrvi_write(dmdrvi_context_t context, void* handle, const void* buffer, size_t size, uint32_t offset); +``` + +Transmits data via UART. + +**Parameters:** +- `context` - DMDRVI context +- `handle` - Device handle +- `buffer` - Data to transmit +- `size` - Number of bytes to transmit +- `offset` - Not used (stream device) + +**Returns:** Number of bytes transmitted. + +--- + +### dmuart_dmdrvi_ioctl + +```c +int dmuart_dmdrvi_ioctl(dmdrvi_context_t context, void* handle, int command, void* arg); +``` + +Performs I/O control operations. + +**Commands (dmuart_ioctl_cmd_t):** + +| Command | Direction | Arg Type | Description | +|---------|-----------|----------|-------------| +| `dmuart_ioctl_cmd_get_baudrate` | Read | `dmuart_baudrate_t*` | Get current baud rate | +| `dmuart_ioctl_cmd_set_baudrate` | Write | `dmuart_baudrate_t*` | Set baud rate | +| `dmuart_ioctl_cmd_get_parity` | Read | `dmuart_parity_t*` | Get parity setting | +| `dmuart_ioctl_cmd_set_parity` | Write | `dmuart_parity_t*` | Set parity setting | +| `dmuart_ioctl_cmd_get_stopbits` | Read | `dmuart_stopbits_t*` | Get stop bits | +| `dmuart_ioctl_cmd_set_stopbits` | Write | `dmuart_stopbits_t*` | Set stop bits | +| `dmuart_ioctl_cmd_get_flowcontrol` | Read | `dmuart_flowcontrol_t*` | Get flow control | +| `dmuart_ioctl_cmd_set_flowcontrol` | Write | `dmuart_flowcontrol_t*` | Set flow control | +| `dmuart_ioctl_cmd_get_databits` | Read | `dmuart_databits_t*` | Get data bits | +| `dmuart_ioctl_cmd_set_databits` | Write | `dmuart_databits_t*` | Set data bits | +| `dmuart_ioctl_cmd_reconfigure` | - | NULL | Reconfigure with current settings | + +--- + +## Port Layer API + +### dmuart_port_init + +```c +int dmuart_port_init(uint32_t instance, dmuart_baudrate_t baudrate, dmuart_databits_t databits, uint8_t parity, uint8_t stopbits, uint8_t flowcontrol); +``` + +Initialize a UART hardware instance. + +--- + +### dmuart_port_deinit + +```c +int dmuart_port_deinit(uint32_t instance); +``` + +Deinitialize a UART hardware instance. + +--- + +### dmuart_port_transmit + +```c +int dmuart_port_transmit(uint32_t instance, const uint8_t* data, size_t size); +``` + +Transmit data bytes over UART. + +--- + +### dmuart_port_receive + +```c +int dmuart_port_receive(uint32_t instance, uint8_t* data, size_t size, size_t* received); +``` + +Receive data bytes from UART. + +--- + +### dmuart_port_set_baudrate + +```c +int dmuart_port_set_baudrate(uint32_t instance, dmuart_baudrate_t baudrate); +``` + +Change the baud rate at runtime. + +--- + +### dmuart_port_get_baudrate + +```c +dmuart_baudrate_t dmuart_port_get_baudrate(uint32_t instance); +``` + +Get the current baud rate. diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 0000000..67e797e --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,108 @@ +# DMUART Configuration Guide + +## Configuration File Format + +DMUART uses INI-format configuration files parsed by the DMINI module. + +## Configuration Parameters + +All parameters are in the `[dmuart]` section: + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `baudrate` | integer | (required) | Baud rate in bps | +| `databits` | integer | 8 | Data bits (7, 8, or 9) | +| `parity` | string | "none" | Parity: "none", "even", "odd" | +| `stopbits` | string | "1" | Stop bits: "1", "2" | +| `flowcontrol` | string | "none" | Flow control: "none", "rts_cts" | +| `instance` | integer | 1 | UART instance number (1-8) | + +## Examples + +### Basic 115200 8N1 + +```ini +[dmuart] +baudrate=115200 +databits=8 +parity=none +stopbits=1 +flowcontrol=none +instance=1 +``` + +### 9600 baud with even parity + +```ini +[dmuart] +baudrate=9600 +databits=8 +parity=even +stopbits=1 +flowcontrol=none +instance=2 +``` + +### High-speed with hardware flow control + +```ini +[dmuart] +baudrate=921600 +databits=8 +parity=none +stopbits=1 +flowcontrol=rts_cts +instance=1 +``` + +### 7-bit data with 2 stop bits + +```ini +[dmuart] +baudrate=19200 +databits=7 +parity=even +stopbits=2 +flowcontrol=none +instance=3 +``` + +## Usage with DMDRVI + +```c +#include "dmuart.h" +#include "dmdrvi.h" +#include "dmini.h" + +// Load configuration +dmini_context_t config = dmini_load("config.ini"); +dmdrvi_dev_num_t dev_num = {0}; + +// Create UART device +dmdrvi_context_t uart_ctx = dmuart_dmdrvi_create(config, &dev_num); + +// Open device for read/write +void* handle = dmuart_dmdrvi_open(uart_ctx, DMDRVI_O_RDWR); + +// Write data +const char* msg = "Hello UART!\n"; +dmuart_dmdrvi_write(uart_ctx, handle, msg, strlen(msg), 0); + +// Read data +char buffer[64]; +size_t n = dmuart_dmdrvi_read(uart_ctx, handle, buffer, sizeof(buffer), 0); + +// Change baud rate via ioctl +dmuart_baudrate_t new_baud = 9600; +dmuart_dmdrvi_ioctl(uart_ctx, handle, dmuart_ioctl_cmd_set_baudrate, &new_baud); +dmuart_dmdrvi_ioctl(uart_ctx, handle, dmuart_ioctl_cmd_reconfigure, NULL); + +// Cleanup +dmuart_dmdrvi_close(uart_ctx, handle); +dmuart_dmdrvi_free(uart_ctx); +dmini_free(config); +``` + +## Pre-configured Boards + +See [`configs/README.md`](../configs/README.md) for available board and MCU configurations. diff --git a/docs/dmuart.md b/docs/dmuart.md new file mode 100644 index 0000000..c2aef7b --- /dev/null +++ b/docs/dmuart.md @@ -0,0 +1,41 @@ +# DMUART - DMOD UART Driver Module + +## Overview + +DMUART is a DMOD module providing a hardware-abstracted UART (Universal Asynchronous Receiver-Transmitter) driver interface. It follows the DMOD driver interface (DMDRVI) pattern for consistent device access across different hardware platforms. + +## Architecture + +``` +┌──────────────────────────────────────┐ +│ Application │ +├──────────────────────────────────────┤ +│ DMDRVI Interface │ +│ (open/close/read/write/ioctl) │ +├──────────────────────────────────────┤ +│ DMUART Core │ +│ (configuration, state management) │ +├──────────────────────────────────────┤ +│ DMUART Port Layer │ +│ (hardware-specific implementation) │ +├──────────────────────────────────────┤ +│ Hardware (UART) │ +└──────────────────────────────────────┘ +``` + +## Features + +- Standard DMDRVI device interface (read/write/ioctl) +- Configurable baud rate, data bits, parity, stop bits, and flow control +- Multiple UART instance support +- Platform-independent core with hardware-specific port layer +- INI-based configuration via DMINI + +## Device Path + +UART devices are registered as `/dev/dmuart` where N is the minor number (instance). + +## Dependencies + +- `dmdrvi` - DMOD Driver Interface +- `dmini` - INI configuration parser diff --git a/docs/port-implementation.md b/docs/port-implementation.md new file mode 100644 index 0000000..6ea6d34 --- /dev/null +++ b/docs/port-implementation.md @@ -0,0 +1,124 @@ +# DMUART Port Implementation Guide + +## Overview + +This guide explains how to add UART hardware support for a new microcontroller family. + +## Port Layer Architecture + +The port layer provides hardware-specific UART implementations. Each MCU family has its own port directory: + +``` +src/port/ +├── CMakeLists.txt # Port build configuration +├── stm32_common/ # Common STM32 code +│ ├── stm32_common.c +│ └── stm32_common.h +└── stm32f7/ # STM32F7-specific port + ├── config.cmake # Architecture toolchain config + └── port.c # UART implementation +``` + +## Required Port Functions + +A port must implement all functions declared in `include/dmuart_port.h`: + +### dmuart_port_init + +```c +int dmuart_port_init(uint32_t instance, dmuart_baudrate_t baudrate, + dmuart_databits_t databits, uint8_t parity, + uint8_t stopbits, uint8_t flowcontrol); +``` + +Initialize the UART hardware: +1. Enable peripheral clock +2. Configure GPIO pins (TX/RX, and optionally RTS/CTS) +3. Set word length, parity, stop bits +4. Configure baud rate +5. Enable UART peripheral + +### dmuart_port_deinit + +```c +int dmuart_port_deinit(uint32_t instance); +``` + +Disable the UART peripheral and release resources. + +### dmuart_port_transmit + +```c +int dmuart_port_transmit(uint32_t instance, const uint8_t* data, size_t size); +``` + +Transmit data bytes. Can be blocking (polling) or use interrupts/DMA. + +### dmuart_port_receive + +```c +int dmuart_port_receive(uint32_t instance, uint8_t* data, size_t size, size_t* received); +``` + +Receive data bytes. Should handle timeouts gracefully. + +### dmuart_port_set_baudrate + +```c +int dmuart_port_set_baudrate(uint32_t instance, dmuart_baudrate_t baudrate); +``` + +Change baud rate at runtime. Must disable/re-enable UART. + +### dmuart_port_get_baudrate + +```c +dmuart_baudrate_t dmuart_port_get_baudrate(uint32_t instance); +``` + +Return current baud rate by reading hardware registers. + +## Adding a New Port + +### Step 1: Create Port Directory + +```bash +mkdir src/port/ +``` + +### Step 2: Create config.cmake + +Set the appropriate toolchain: + +```cmake +set(DMOD_TOOLS_NAME "arch/armv7/cortex-m4" CACHE STRING "Name of the tools configuration") +``` + +### Step 3: Implement port.c + +Implement all port API functions for your hardware. + +### Step 4: Add Register Definitions + +Create `include/port/_regs.h` with UART register definitions. + +### Step 5: Update Build Configuration + +The port CMakeLists.txt should automatically handle new MCU families when `DMUART_MCU_SERIES` matches. + +## Example: Adding STM32F4 Support + +1. Create `src/port/stm32f4/config.cmake`: +```cmake +set(DMOD_TOOLS_NAME "arch/armv7/cortex-m4" CACHE STRING "Name of the tools configuration") +``` + +2. Create `src/port/stm32f4/port.c` implementing all port functions + +3. Create `include/port/stm32f4_regs.h` with STM32F4 UART base addresses + +4. Build with: +```bash +cmake -DDMUART_MCU_SERIES=stm32f4 -B build +cmake --build build +``` diff --git a/examples/config.ini b/examples/config.ini new file mode 100644 index 0000000..536485b --- /dev/null +++ b/examples/config.ini @@ -0,0 +1,8 @@ +; Configuration file for DMUART example +[dmuart] +baudrate=115200 +databits=8 +parity=none +stopbits=1 +flowcontrol=none +instance=1 diff --git a/include/dmuart.h b/include/dmuart.h new file mode 100644 index 0000000..5d7823b --- /dev/null +++ b/include/dmuart.h @@ -0,0 +1,25 @@ +#ifndef DMUART_H +#define DMUART_H + +#include "dmuart_defs.h" +#include "dmuart_types.h" + +/** + * @brief UART driver configuration structure + */ +typedef struct +{ + dmuart_instance_t instance; /**< UART instance number (1-based) */ + dmuart_baudrate_t baudrate; /**< Baud rate */ + dmuart_word_length_t word_length; /**< Word length (data bits) */ + dmuart_parity_t parity; /**< Parity setting */ + dmuart_stop_bit_t stop_bit; /**< Stop bit setting */ + dmuart_flow_control_t flow_control; /**< Flow control setting */ + dmuart_bit_order_t bit_order; /**< Bit order (LSB/MSB first) */ + dmuart_invert_t invert; /**< Signal inversion */ + dmuart_loopback_t loopback; /**< Loopback mode */ + dmuart_int_trigger_t interrupt_trigger; /**< Interrupt trigger source */ + dmuart_interrupt_handler_t interrupt_handler; /**< Interrupt handler (NULL = not used) */ +} dmuart_config_t; + +#endif // DMUART_H diff --git a/include/dmuart_port.h b/include/dmuart_port.h new file mode 100644 index 0000000..8f3b966 --- /dev/null +++ b/include/dmuart_port.h @@ -0,0 +1,66 @@ +#ifndef DMUART_PORT_H +#define DMUART_PORT_H + +#include "dmod_types.h" +#include "dmuart_port_defs.h" +#include "dmuart_types.h" + +/* --- Lifecycle --- */ + +dmod_dmuart_port_api(1.0, int, _init, ( dmuart_instance_t instance ) ); +dmod_dmuart_port_api(1.0, int, _deinit, ( dmuart_instance_t instance ) ); + +/* --- Data transfer --- */ + +dmod_dmuart_port_api(1.0, int, _transmit, ( dmuart_instance_t instance, const uint8_t* data, size_t size ) ); +dmod_dmuart_port_api(1.0, int, _receive, ( dmuart_instance_t instance, uint8_t* data, size_t size, size_t* received ) ); +dmod_dmuart_port_api(1.0, int, _flush, ( dmuart_instance_t instance ) ); + +/* --- Baud rate --- */ + +dmod_dmuart_port_api(1.0, int, _set_baudrate, ( dmuart_instance_t instance, dmuart_baudrate_t baudrate ) ); +dmod_dmuart_port_api(1.0, dmuart_baudrate_t,_get_baudrate, ( dmuart_instance_t instance ) ); + +/* --- Word length --- */ + +dmod_dmuart_port_api(1.0, int, _set_word_length, ( dmuart_instance_t instance, dmuart_word_length_t word_length ) ); +dmod_dmuart_port_api(1.0, dmuart_word_length_t, _get_word_length, ( dmuart_instance_t instance ) ); + +/* --- Parity --- */ + +dmod_dmuart_port_api(1.0, int, _set_parity, ( dmuart_instance_t instance, dmuart_parity_t parity ) ); +dmod_dmuart_port_api(1.0, dmuart_parity_t, _get_parity, ( dmuart_instance_t instance ) ); + +/* --- Stop bit --- */ + +dmod_dmuart_port_api(1.0, int, _set_stop_bit, ( dmuart_instance_t instance, dmuart_stop_bit_t stop_bit ) ); +dmod_dmuart_port_api(1.0, dmuart_stop_bit_t, _get_stop_bit, ( dmuart_instance_t instance ) ); + +/* --- Bit order --- */ + +dmod_dmuart_port_api(1.0, int, _set_bit_order, ( dmuart_instance_t instance, dmuart_bit_order_t bit_order ) ); +dmod_dmuart_port_api(1.0, dmuart_bit_order_t, _get_bit_order, ( dmuart_instance_t instance ) ); + +/* --- Signal inversion --- */ + +dmod_dmuart_port_api(1.0, int, _set_invert, ( dmuart_instance_t instance, dmuart_invert_t invert ) ); +dmod_dmuart_port_api(1.0, dmuart_invert_t, _get_invert, ( dmuart_instance_t instance ) ); + +/* --- Loopback --- */ + +dmod_dmuart_port_api(1.0, int, _set_loopback, ( dmuart_instance_t instance, dmuart_loopback_t loopback ) ); +dmod_dmuart_port_api(1.0, dmuart_loopback_t, _get_loopback, ( dmuart_instance_t instance ) ); + +/* --- Interrupt trigger --- */ + +dmod_dmuart_port_api(1.0, int, _set_interrupt_trigger, ( dmuart_instance_t instance, dmuart_int_trigger_t trigger ) ); +dmod_dmuart_port_api(1.0, int, _read_interrupt_trigger, ( dmuart_instance_t instance, dmuart_int_trigger_t *out_trigger ) ); + +/* --- Interrupt handler registration --- */ + +dmod_dmuart_port_api(1.0, int, _add_interrupt_handler, + ( dmuart_instance_t instance, dmuart_port_interrupt_handler_t handler, void *user_ptr ) ); +dmod_dmuart_port_api(1.0, int, _remove_interrupt_handler, + ( dmuart_instance_t instance, void *user_ptr ) ); + +#endif // DMUART_PORT_H diff --git a/include/dmuart_types.h b/include/dmuart_types.h new file mode 100644 index 0000000..87afbc8 --- /dev/null +++ b/include/dmuart_types.h @@ -0,0 +1,162 @@ +#ifndef DMUART_TYPES_H +#define DMUART_TYPES_H + +#include +#include + +/** + * @brief UART instance type + */ +typedef uint8_t dmuart_instance_t; + +/** + * @brief UART baud rate type + */ +typedef uint32_t dmuart_baudrate_t; + +/** + * @brief UART data bits (word length) configuration + */ +typedef enum +{ + dmuart_word_length_7 = 7, /**< 7 data bits */ + dmuart_word_length_8 = 8, /**< 8 data bits */ + dmuart_word_length_9 = 9, /**< 9 data bits */ +} dmuart_word_length_t; + +/** + * @brief UART parity configuration + */ +typedef enum +{ + dmuart_parity_none = 0, /**< No parity */ + dmuart_parity_even, /**< Even parity */ + dmuart_parity_odd, /**< Odd parity */ +} dmuart_parity_t; + +/** + * @brief UART stop bits configuration + */ +typedef enum +{ + dmuart_stop_bit_1 = 0, /**< 1 stop bit */ + dmuart_stop_bit_2, /**< 2 stop bits */ +} dmuart_stop_bit_t; + +/** + * @brief UART flow control configuration + */ +typedef enum +{ + dmuart_flow_control_none = 0, /**< No flow control */ + dmuart_flow_control_rts_cts, /**< RTS/CTS hardware flow control */ +} dmuart_flow_control_t; + +/** + * @brief UART bit order configuration + */ +typedef enum +{ + dmuart_bit_order_lsb_first = 0, /**< LSB first (standard) */ + dmuart_bit_order_msb_first, /**< MSB first */ +} dmuart_bit_order_t; + +/** + * @brief UART signal inversion + */ +typedef enum +{ + dmuart_invert_none = 0, /**< No inversion */ + dmuart_invert_tx = (1 << 0), /**< Invert TX line */ + dmuart_invert_rx = (1 << 1), /**< Invert RX line */ + dmuart_invert_both = (1 << 0) | (1 << 1), /**< Invert both TX and RX */ +} dmuart_invert_t; + +/** + * @brief UART loopback mode + */ +typedef enum +{ + dmuart_loopback_off = 0, /**< Loopback disabled */ + dmuart_loopback_on, /**< Loopback enabled (TX connected to RX internally) */ +} dmuart_loopback_t; + +/** + * @brief UART interrupt trigger source + */ +typedef enum +{ + dmuart_int_trigger_off = 0, /**< Interrupts disabled */ + dmuart_int_trigger_rx_not_empty = (1 << 0), /**< RX data register not empty */ + dmuart_int_trigger_tx_empty = (1 << 1), /**< TX data register empty */ + dmuart_int_trigger_tx_complete = (1 << 2), /**< Transmission complete */ + dmuart_int_trigger_idle = (1 << 3), /**< Idle line detected */ + dmuart_int_trigger_error = (1 << 4), /**< Error (overrun, framing, parity) */ +} dmuart_int_trigger_t; + +/** + * @brief IOCTL commands for DMUART device + */ +typedef enum +{ + dmuart_ioctl_cmd_get_baudrate = 1, /**< Get current baud rate */ + dmuart_ioctl_cmd_set_baudrate, /**< Set baud rate */ + dmuart_ioctl_cmd_get_word_length, /**< Get word length */ + dmuart_ioctl_cmd_set_word_length, /**< Set word length */ + dmuart_ioctl_cmd_get_parity, /**< Get parity setting */ + dmuart_ioctl_cmd_set_parity, /**< Set parity setting */ + dmuart_ioctl_cmd_get_stop_bit, /**< Get stop bit setting */ + dmuart_ioctl_cmd_set_stop_bit, /**< Set stop bit setting */ + dmuart_ioctl_cmd_get_flow_control, /**< Get flow control setting */ + dmuart_ioctl_cmd_set_flow_control, /**< Set flow control setting */ + dmuart_ioctl_cmd_get_bit_order, /**< Get bit order setting */ + dmuart_ioctl_cmd_set_bit_order, /**< Set bit order setting */ + dmuart_ioctl_cmd_get_invert, /**< Get signal inversion setting */ + dmuart_ioctl_cmd_set_invert, /**< Set signal inversion setting */ + dmuart_ioctl_cmd_get_loopback, /**< Get loopback mode */ + dmuart_ioctl_cmd_set_loopback, /**< Set loopback mode */ + dmuart_ioctl_cmd_set_interrupt_handler, /**< Set interrupt handler; arg = dmuart_interrupt_handler_t* */ + dmuart_ioctl_cmd_reconfigure, /**< Reconfigure UART with current settings */ + + dmuart_ioctl_cmd_max +} dmuart_ioctl_cmd_t; + +/** + * @brief Opaque driver context type (forward declaration) + */ +struct dmdrvi_context; +typedef struct dmdrvi_context *dmdrvi_context_t; + +/** + * @brief Parameters passed to a dmhaman-registered interrupt handler. + */ +typedef struct +{ + dmuart_instance_t instance; /**< UART instance that generated the interrupt */ + dmuart_int_trigger_t trigger; /**< Which trigger fired */ + uint8_t data; /**< Received data byte (valid for rx_not_empty trigger) */ +} dmuart_interrupt_params_t; + +/** + * @brief UART interrupt handler function type + * + * @param context Context of the driver instance + * @param instance UART instance that generated the interrupt + * @param trigger Which interrupt trigger fired + * @param data Received data byte (valid when trigger includes rx_not_empty) + */ +typedef void (*dmuart_interrupt_handler_t)(dmdrvi_context_t context, dmuart_instance_t instance, dmuart_int_trigger_t trigger, uint8_t data); + +/** + * @brief UART port interrupt handler function type + * + * Called by the port layer when an interrupt occurs on a UART instance. + * + * @param user_ptr User pointer supplied at registration time (e.g. driver context) + * @param instance UART instance that generated the interrupt + * @param trigger Which interrupt trigger fired + * @param data Received data byte (valid when trigger includes rx_not_empty) + */ +typedef void (*dmuart_port_interrupt_handler_t)(void *user_ptr, dmuart_instance_t instance, dmuart_int_trigger_t trigger, uint8_t data); + +#endif /* DMUART_TYPES_H */ diff --git a/include/port/stm32_common_regs.h b/include/port/stm32_common_regs.h new file mode 100644 index 0000000..f46b899 --- /dev/null +++ b/include/port/stm32_common_regs.h @@ -0,0 +1,89 @@ +#ifndef DMUART_STM32_COMMON_REGS_H +#define DMUART_STM32_COMMON_REGS_H + +#include + +/* ====================================================================== + * Common STM32 USART Register Definitions + * ====================================================================== */ + +/** + * @brief USART register structure (common across STM32 families) + */ +typedef struct { + volatile uint32_t CR1; /**< Control register 1 */ + volatile uint32_t CR2; /**< Control register 2 */ + volatile uint32_t CR3; /**< Control register 3 */ + volatile uint32_t BRR; /**< Baud rate register */ + volatile uint32_t GTPR; /**< Guard time and prescaler register */ + volatile uint32_t RTOR; /**< Receiver timeout register */ + volatile uint32_t RQR; /**< Request register */ + volatile uint32_t ISR; /**< Interrupt and status register */ + volatile uint32_t ICR; /**< Interrupt flag clear register */ + volatile uint32_t RDR; /**< Receive data register */ + volatile uint32_t TDR; /**< Transmit data register */ +} USART_TypeDef; + +/* USART CR1 register bits */ +#define USART_CR1_UE (1U << 0) /**< USART enable */ +#define USART_CR1_RE (1U << 2) /**< Receiver enable */ +#define USART_CR1_TE (1U << 3) /**< Transmitter enable */ +#define USART_CR1_PCE (1U << 10) /**< Parity control enable */ +#define USART_CR1_PS (1U << 9) /**< Parity selection (0=even, 1=odd) */ +#define USART_CR1_M0 (1U << 12) /**< Word length bit 0 */ +#define USART_CR1_M1 (1U << 28) /**< Word length bit 1 */ +#define USART_CR1_OVER8 (1U << 15) /**< Oversampling mode */ + +/* USART CR2 register bits */ +#define USART_CR2_STOP_Pos 12 +#define USART_CR2_STOP_Msk (0x3U << USART_CR2_STOP_Pos) +#define USART_CR2_STOP_1BIT (0x0U << USART_CR2_STOP_Pos) +#define USART_CR2_STOP_2BIT (0x2U << USART_CR2_STOP_Pos) + +/* USART CR3 register bits */ +#define USART_CR3_RTSE (1U << 8) /**< RTS enable */ +#define USART_CR3_CTSE (1U << 9) /**< CTS enable */ + +/* USART ISR register bits */ +#define USART_ISR_TXE (1U << 7) /**< Transmit data register empty */ +#define USART_ISR_RXNE (1U << 5) /**< Read data register not empty */ +#define USART_ISR_TC (1U << 6) /**< Transmission complete */ +#define USART_ISR_ORE (1U << 3) /**< Overrun error */ +#define USART_ISR_FE (1U << 1) /**< Framing error */ +#define USART_ISR_PE (1U << 0) /**< Parity error */ +#define USART_ISR_TEACK (1U << 21) /**< Transmit enable acknowledge */ +#define USART_ISR_REACK (1U << 22) /**< Receive enable acknowledge */ + +/* USART ICR register bits */ +#define USART_ICR_ORECF (1U << 3) /**< Overrun error clear flag */ +#define USART_ICR_FECF (1U << 1) /**< Framing error clear flag */ +#define USART_ICR_PECF (1U << 0) /**< Parity error clear flag */ + +/* RCC register structure (subset for UART clock enable) */ +typedef struct { + volatile uint32_t CR; + volatile uint32_t PLLCFGR; + volatile uint32_t CFGR; + volatile uint32_t CIR; + volatile uint32_t AHB1RSTR; + volatile uint32_t AHB2RSTR; + volatile uint32_t AHB3RSTR; + volatile uint32_t RESERVED0; + volatile uint32_t APB1RSTR; + volatile uint32_t APB2RSTR; + volatile uint32_t RESERVED1[2]; + volatile uint32_t AHB1ENR; + volatile uint32_t AHB2ENR; + volatile uint32_t AHB3ENR; + volatile uint32_t RESERVED2; + volatile uint32_t APB1ENR; + volatile uint32_t APB2ENR; +} UART_RCC_TypeDef; + +/* Common clock values */ +#define HSI_VALUE 16000000U /**< HSI clock value in Hz */ + +/* Timeout values */ +#define UART_TIMEOUT_VALUE 10000U /**< Default timeout for UART operations */ + +#endif // DMUART_STM32_COMMON_REGS_H diff --git a/include/port/stm32f4_regs.h b/include/port/stm32f4_regs.h new file mode 100644 index 0000000..b72a9c7 --- /dev/null +++ b/include/port/stm32f4_regs.h @@ -0,0 +1,84 @@ +#ifndef DMUART_STM32F4_REGS_H +#define DMUART_STM32F4_REGS_H + +#include + +/* ====================================================================== + * STM32F4 Specific UART Definitions + * ====================================================================== */ + +/* Base addresses for STM32F4 */ +#define STM32F4_RCC_BASE 0x40023800U +#define STM32F4_USART1_BASE 0x40011000U +#define STM32F4_USART2_BASE 0x40004400U +#define STM32F4_USART3_BASE 0x40004800U +#define STM32F4_UART4_BASE 0x40004C00U +#define STM32F4_UART5_BASE 0x40005000U +#define STM32F4_USART6_BASE 0x40011400U + +/* RCC APB1ENR UART clock enable bits */ +#define STM32F4_RCC_APB1ENR_USART2EN (1U << 17) +#define STM32F4_RCC_APB1ENR_USART3EN (1U << 18) +#define STM32F4_RCC_APB1ENR_UART4EN (1U << 19) +#define STM32F4_RCC_APB1ENR_UART5EN (1U << 20) + +/* RCC APB2ENR UART clock enable bits */ +#define STM32F4_RCC_APB2ENR_USART1EN (1U << 4) +#define STM32F4_RCC_APB2ENR_USART6EN (1U << 5) + +/* Maximum number of UART instances on STM32F4 */ +#define STM32F4_UART_MAX_INSTANCES 6 + +/* STM32F4 APB clock frequencies (default with HSI) */ +#define STM32F4_APB1_FREQ 16000000U /**< APB1 clock (default HSI) */ +#define STM32F4_APB2_FREQ 16000000U /**< APB2 clock (default HSI) */ + +/* ====================================================================== + * STM32F4 USART Register Structure (legacy layout) + * + * STM32F4 uses SR/DR instead of ISR/ICR/RDR/TDR + * ====================================================================== */ + +typedef struct { + volatile uint32_t SR; /**< Status register */ + volatile uint32_t DR; /**< Data register */ + volatile uint32_t BRR; /**< Baud rate register */ + volatile uint32_t CR1; /**< Control register 1 */ + volatile uint32_t CR2; /**< Control register 2 */ + volatile uint32_t CR3; /**< Control register 3 */ + volatile uint32_t GTPR; /**< Guard time and prescaler register */ +} STM32F4_USART_TypeDef; + +/* USART SR register bits (STM32F4) */ +#define STM32F4_USART_SR_TXE (1U << 7) /**< Transmit data register empty */ +#define STM32F4_USART_SR_RXNE (1U << 5) /**< Read data register not empty */ +#define STM32F4_USART_SR_TC (1U << 6) /**< Transmission complete */ +#define STM32F4_USART_SR_ORE (1U << 3) /**< Overrun error */ +#define STM32F4_USART_SR_FE (1U << 1) /**< Framing error */ +#define STM32F4_USART_SR_PE (1U << 0) /**< Parity error */ +#define STM32F4_USART_SR_IDLE (1U << 4) /**< Idle line detected */ + +/* USART CR1 register bits (STM32F4) */ +#define STM32F4_USART_CR1_UE (1U << 13) /**< USART enable */ +#define STM32F4_USART_CR1_RE (1U << 2) /**< Receiver enable */ +#define STM32F4_USART_CR1_TE (1U << 3) /**< Transmitter enable */ +#define STM32F4_USART_CR1_PCE (1U << 10) /**< Parity control enable */ +#define STM32F4_USART_CR1_PS (1U << 9) /**< Parity selection */ +#define STM32F4_USART_CR1_M (1U << 12) /**< Word length (0=8, 1=9) */ +#define STM32F4_USART_CR1_RXNEIE (1U << 5) /**< RXNE interrupt enable */ +#define STM32F4_USART_CR1_TXEIE (1U << 7) /**< TXE interrupt enable */ +#define STM32F4_USART_CR1_TCIE (1U << 6) /**< TC interrupt enable */ +#define STM32F4_USART_CR1_IDLEIE (1U << 4) /**< IDLE interrupt enable */ + +/* USART CR2 register bits (STM32F4) */ +#define STM32F4_USART_CR2_STOP_Pos 12 +#define STM32F4_USART_CR2_STOP_Msk (0x3U << STM32F4_USART_CR2_STOP_Pos) +#define STM32F4_USART_CR2_STOP_1BIT (0x0U << STM32F4_USART_CR2_STOP_Pos) +#define STM32F4_USART_CR2_STOP_2BIT (0x2U << STM32F4_USART_CR2_STOP_Pos) + +/* USART CR3 register bits (STM32F4) */ +#define STM32F4_USART_CR3_RTSE (1U << 8) /**< RTS enable */ +#define STM32F4_USART_CR3_CTSE (1U << 9) /**< CTS enable */ +#define STM32F4_USART_CR3_EIE (1U << 0) /**< Error interrupt enable */ + +#endif // DMUART_STM32F4_REGS_H diff --git a/include/port/stm32f7_regs.h b/include/port/stm32f7_regs.h new file mode 100644 index 0000000..d442d39 --- /dev/null +++ b/include/port/stm32f7_regs.h @@ -0,0 +1,40 @@ +#ifndef DMUART_STM32F7_REGS_H +#define DMUART_STM32F7_REGS_H + +#include + +/* ====================================================================== + * STM32F7 Specific UART Definitions + * ====================================================================== */ + +/* Base addresses for STM32F7 */ +#define STM32F7_RCC_BASE 0x40023800U +#define STM32F7_USART1_BASE 0x40011000U +#define STM32F7_USART2_BASE 0x40004400U +#define STM32F7_USART3_BASE 0x40004800U +#define STM32F7_UART4_BASE 0x40004C00U +#define STM32F7_UART5_BASE 0x40005000U +#define STM32F7_USART6_BASE 0x40011400U +#define STM32F7_UART7_BASE 0x40007800U +#define STM32F7_UART8_BASE 0x40007C00U + +/* RCC APB1ENR UART clock enable bits */ +#define RCC_APB1ENR_USART2EN (1U << 17) +#define RCC_APB1ENR_USART3EN (1U << 18) +#define RCC_APB1ENR_UART4EN (1U << 19) +#define RCC_APB1ENR_UART5EN (1U << 20) +#define RCC_APB1ENR_UART7EN (1U << 30) +#define RCC_APB1ENR_UART8EN (1U << 31) + +/* RCC APB2ENR UART clock enable bits */ +#define RCC_APB2ENR_USART1EN (1U << 4) +#define RCC_APB2ENR_USART6EN (1U << 5) + +/* Maximum number of UART instances on STM32F7 */ +#define STM32F7_UART_MAX_INSTANCES 8 + +/* STM32F7 APB clock frequencies (default with HSI) */ +#define STM32F7_APB1_FREQ 16000000U /**< APB1 clock (default HSI) */ +#define STM32F7_APB2_FREQ 16000000U /**< APB2 clock (default HSI) */ + +#endif // DMUART_STM32F7_REGS_H diff --git a/manifest.dmm b/manifest.dmm new file mode 100644 index 0000000..c723f46 --- /dev/null +++ b/manifest.dmm @@ -0,0 +1,7 @@ +# Include dynamically generated versions list from latest release +$include https://github.com/choco-technologies/dmuart/releases/download/vlatest/versions.dmm + +# Module entries with version placeholder - will be expanded by $version-available +dmuart https://github.com/choco-technologies/dmuart/releases/download/v/dmuart-v-.zip +dmuart_port https://github.com/choco-technologies/dmuart/releases/download/v/dmuart-v-.zip +dmuart_test https://github.com/choco-technologies/dmuart/releases/download/v/dmuart-v-.zip diff --git a/src/dmuart.c b/src/dmuart.c new file mode 100644 index 0000000..e26c0f6 --- /dev/null +++ b/src/dmuart.c @@ -0,0 +1,506 @@ +#define DMOD_ENABLE_REGISTRATION ON +#include "dmod.h" +#include "dmuart.h" +#include "dmuart_port.h" +#include "dmdrvi.h" +#include "dmhaman.h" +#include "dmini.h" +#include +#include + +/* Magic set to UART */ +#define DMUART_CONTEXT_MAGIC 0x44555254 + +/** + * @brief DMDRVI context structure + */ +struct dmdrvi_context +{ + uint32_t magic; /**< Magic number for validation */ + dmuart_config_t config; /**< Configuration parameters */ + dmuart_baudrate_t current_baudrate; /**< Current baud rate */ + char *interrupt_handler_name; /**< dmhaman handler name (NULL = not used) */ +}; + +static int is_valid_context(dmdrvi_context_t context) +{ + return (context != NULL && context->magic == DMUART_CONTEXT_MAGIC); +} + +/* ---- dmhaman interrupt dispatch ---- */ + +/** + * @brief Internal port interrupt handler that dispatches to a dmhaman-registered handler. + */ +static void dmhaman_interrupt_handler(void *user_ptr, dmuart_instance_t instance, + dmuart_int_trigger_t trigger, uint8_t data) +{ + dmdrvi_context_t ctx = (dmdrvi_context_t)user_ptr; + dmuart_interrupt_params_t params; + params.instance = instance; + params.trigger = trigger; + params.data = data; + dmhaman_call_handler(ctx->interrupt_handler_name, ¶ms); +} + +/* ---- String conversion helpers ---- */ + +static dmuart_parity_t string_to_parity(const char *s) +{ + if (s != NULL) + { + if (strcmp(s, "even") == 0) return dmuart_parity_even; + if (strcmp(s, "odd") == 0) return dmuart_parity_odd; + } + return dmuart_parity_none; +} + +static dmuart_stop_bit_t string_to_stop_bit(const char *s) +{ + if (s != NULL) + { + if (strcmp(s, "2") == 0) return dmuart_stop_bit_2; + } + return dmuart_stop_bit_1; +} + +static dmuart_flow_control_t string_to_flow_control(const char *s) +{ + if (s != NULL) + { + if (strcmp(s, "rts_cts") == 0) return dmuart_flow_control_rts_cts; + } + return dmuart_flow_control_none; +} + +static dmuart_bit_order_t string_to_bit_order(const char *s) +{ + if (s != NULL) + { + if (strcmp(s, "msb_first") == 0) return dmuart_bit_order_msb_first; + } + return dmuart_bit_order_lsb_first; +} + +static dmuart_invert_t string_to_invert(const char *s) +{ + if (s != NULL) + { + if (strcmp(s, "tx") == 0) return dmuart_invert_tx; + if (strcmp(s, "rx") == 0) return dmuart_invert_rx; + if (strcmp(s, "both") == 0) return dmuart_invert_both; + } + return dmuart_invert_none; +} + +static dmuart_loopback_t string_to_loopback(const char *s) +{ + if (s != NULL) + { + if (strcmp(s, "on") == 0) return dmuart_loopback_on; + } + return dmuart_loopback_off; +} + +static dmuart_int_trigger_t string_to_interrupt_trigger(const char *s) +{ + if (s != NULL) + { + if (strcmp(s, "rx_not_empty") == 0) return dmuart_int_trigger_rx_not_empty; + if (strcmp(s, "tx_empty") == 0) return dmuart_int_trigger_tx_empty; + if (strcmp(s, "tx_complete") == 0) return dmuart_int_trigger_tx_complete; + if (strcmp(s, "idle") == 0) return dmuart_int_trigger_idle; + if (strcmp(s, "error") == 0) return dmuart_int_trigger_error; + } + return dmuart_int_trigger_off; +} + +static dmuart_word_length_t int_to_word_length(int val) +{ + switch (val) + { + case 7: return dmuart_word_length_7; + case 9: return dmuart_word_length_9; + default: return dmuart_word_length_8; + } +} + +/* ---- Configuration ---- */ + +static int check_config_parameters(dmuart_config_t *cfg) +{ + if (cfg->baudrate == 0) + { + DMOD_LOG_ERROR("Baud rate not set in configuration\n"); + return -EINVAL; + } + if (cfg->word_length != dmuart_word_length_7 && + cfg->word_length != dmuart_word_length_8 && + cfg->word_length != dmuart_word_length_9) + { + DMOD_LOG_ERROR("Invalid word length in configuration (must be 7, 8, or 9)\n"); + return -EINVAL; + } + return 0; +} + +static int read_config_parameters(dmdrvi_context_t context, dmini_context_t config, const char *section) +{ + context->config.baudrate = (dmuart_baudrate_t)dmini_get_int(config, section, "baudrate", 0); + context->config.word_length = int_to_word_length(dmini_get_int(config, section, "databits", 8)); + context->config.parity = string_to_parity(dmini_get_string(config, section, "parity", "none")); + context->config.stop_bit = string_to_stop_bit(dmini_get_string(config, section, "stopbits", "1")); + context->config.flow_control = string_to_flow_control(dmini_get_string(config, section, "flowcontrol", "none")); + context->config.bit_order = string_to_bit_order(dmini_get_string(config, section, "bit_order", "lsb_first")); + context->config.invert = string_to_invert(dmini_get_string(config, section, "invert", "none")); + context->config.loopback = string_to_loopback(dmini_get_string(config, section, "loopback", "off")); + context->config.instance = (dmuart_instance_t)dmini_get_int(config, section, "instance", 1); + context->config.interrupt_trigger = string_to_interrupt_trigger(dmini_get_string(config, section, "interrupt_trigger", "off")); + context->config.interrupt_handler = NULL; + + const char *handler_name = dmini_get_string(config, section, "interrupt_handler", NULL); + context->interrupt_handler_name = (handler_name != NULL) ? Dmod_StrDup(handler_name) : NULL; + + return check_config_parameters(&context->config); +} + +/** + * @brief Apply configuration to the port layer + */ +static int configure(dmdrvi_context_t context) +{ + dmuart_config_t *c = &context->config; + int ret; + + ret = dmuart_port_init(c->instance); + if (ret != 0) + { + DMOD_LOG_ERROR("Failed to initialize UART%u\n", c->instance); + return ret; + } + + ret = dmuart_port_set_baudrate(c->instance, c->baudrate); + if (ret != 0) goto err; + + ret = dmuart_port_set_word_length(c->instance, c->word_length); + if (ret != 0) goto err; + + ret = dmuart_port_set_parity(c->instance, c->parity); + if (ret != 0) goto err; + + ret = dmuart_port_set_stop_bit(c->instance, c->stop_bit); + if (ret != 0) goto err; + + ret = dmuart_port_set_bit_order(c->instance, c->bit_order); + if (ret != 0) goto err; + + ret = dmuart_port_set_invert(c->instance, c->invert); + if (ret != 0) goto err; + + ret = dmuart_port_set_loopback(c->instance, c->loopback); + if (ret != 0) goto err; + + if (c->interrupt_trigger != dmuart_int_trigger_off) + { + ret = dmuart_port_set_interrupt_trigger(c->instance, c->interrupt_trigger); + if (ret != 0) goto err; + } + + context->current_baudrate = dmuart_port_get_baudrate(c->instance); + + DMOD_LOG_INFO("UART%u configured: %u baud, %u%c%s\n", + c->instance, c->baudrate, + (unsigned)c->word_length, + (c->parity == dmuart_parity_none) ? 'N' : + (c->parity == dmuart_parity_even) ? 'E' : 'O', + (c->stop_bit == dmuart_stop_bit_1) ? "1" : "2"); + + return 0; + +err: + DMOD_LOG_ERROR("Failed to configure UART%u\n", c->instance); + dmuart_port_deinit(c->instance); + return ret; +} + +/* ---- IOCTL helpers ---- */ + +static int update_configuration(dmuart_config_t *cfg, int command, void *arg) +{ + switch (command) + { + case dmuart_ioctl_cmd_set_baudrate: + cfg->baudrate = *(dmuart_baudrate_t *)arg; + break; + case dmuart_ioctl_cmd_set_word_length: + cfg->word_length = *(dmuart_word_length_t *)arg; + break; + case dmuart_ioctl_cmd_set_parity: + cfg->parity = *(dmuart_parity_t *)arg; + break; + case dmuart_ioctl_cmd_set_stop_bit: + cfg->stop_bit = *(dmuart_stop_bit_t *)arg; + break; + case dmuart_ioctl_cmd_set_flow_control: + cfg->flow_control = *(dmuart_flow_control_t *)arg; + break; + case dmuart_ioctl_cmd_set_bit_order: + cfg->bit_order = *(dmuart_bit_order_t *)arg; + break; + case dmuart_ioctl_cmd_set_invert: + cfg->invert = *(dmuart_invert_t *)arg; + break; + case dmuart_ioctl_cmd_set_loopback: + cfg->loopback = *(dmuart_loopback_t *)arg; + break; + default: + return -EINVAL; + } + return check_config_parameters(cfg); +} + +static int read_configuration(dmdrvi_context_t context, int command, void *arg) +{ + dmuart_config_t *c = &context->config; + switch (command) + { + case dmuart_ioctl_cmd_get_baudrate: + *(dmuart_baudrate_t *)arg = c->baudrate; + break; + case dmuart_ioctl_cmd_get_word_length: + *(dmuart_word_length_t *)arg = c->word_length; + break; + case dmuart_ioctl_cmd_get_parity: + *(dmuart_parity_t *)arg = c->parity; + break; + case dmuart_ioctl_cmd_get_stop_bit: + *(dmuart_stop_bit_t *)arg = c->stop_bit; + break; + case dmuart_ioctl_cmd_get_flow_control: + *(dmuart_flow_control_t *)arg = c->flow_control; + break; + case dmuart_ioctl_cmd_get_bit_order: + *(dmuart_bit_order_t *)arg = c->bit_order; + break; + case dmuart_ioctl_cmd_get_invert: + *(dmuart_invert_t *)arg = c->invert; + break; + case dmuart_ioctl_cmd_get_loopback: + *(dmuart_loopback_t *)arg = c->loopback; + break; + default: + return -EINVAL; + } + return 0; +} + +/* ---- DMOD lifecycle ---- */ + +int dmod_init(const Dmod_Config_t *Config) +{ + DMOD_LOG_INFO("DMUART interface module initialized\n"); + return 0; +} + +int dmod_deinit(void) +{ + DMOD_LOG_INFO("DMUART interface module deinitialized\n"); + return 0; +} + +/* ---- DMDRVI interface ---- */ + +dmod_dmdrvi_dif_api_declaration(1.0, dmuart, dmdrvi_context_t, _create, ( dmini_context_t config, dmdrvi_dev_num_t* dev_num )) +{ + if (config == NULL || dev_num == NULL) + { + DMOD_LOG_ERROR("Invalid parameters to dmuart_dmdrvi_create\n"); + return NULL; + } + + dev_num->major = 0; + dev_num->minor = 0; + dev_num->flags = DMDRVI_NUM_MINOR; + + dmdrvi_context_t context = Dmod_Malloc(sizeof(struct dmdrvi_context)); + if (context != NULL) + { + memset(context, 0, sizeof(*context)); + context->magic = DMUART_CONTEXT_MAGIC; + + /* Determine the INI section name - use section name from config or default */ + const char *section = dmini_get_string(config, NULL, "driver_section", "dmuart"); + + if (read_config_parameters(context, config, section) != 0 || + configure(context) != 0) + { + DMOD_LOG_ERROR("Failed to create DMDRVI context with provided configuration\n"); + Dmod_Free(context->interrupt_handler_name); + Dmod_Free(context); + return NULL; + } + + /* Register interrupt handler if configured */ + if (context->interrupt_handler_name != NULL) + { + if (dmuart_port_add_interrupt_handler(context->config.instance, + dmhaman_interrupt_handler, context) != 0) + { + DMOD_LOG_ERROR("Failed to register interrupt handler '%s'\n", + context->interrupt_handler_name); + Dmod_Free(context->interrupt_handler_name); + context->interrupt_handler_name = NULL; + } + } + + DMOD_LOG_INFO("UART configured at %u baud\n", context->current_baudrate); + } + return context; +} + +dmod_dmdrvi_dif_api_declaration(1.0, dmuart, void, _free, ( dmdrvi_context_t context )) +{ + if (is_valid_context(context)) + { + if (context->interrupt_handler_name != NULL) + { + dmuart_port_remove_interrupt_handler(context->config.instance, context); + Dmod_Free(context->interrupt_handler_name); + } + dmuart_port_deinit(context->config.instance); + context->magic = 0; + Dmod_Free(context); + } +} + +dmod_dmdrvi_dif_api_declaration(1.0, dmuart, void*, _open, ( dmdrvi_context_t context, int flags )) +{ + if (!is_valid_context(context)) + { + DMOD_LOG_ERROR("Invalid DMDRVI context in dmuart_dmdrvi_open\n"); + return NULL; + } + return context; +} + +dmod_dmdrvi_dif_api_declaration(1.0, dmuart, void, _close, ( dmdrvi_context_t context, void* handle )) +{ + /* No specific action needed to close the UART device handle */ +} + +dmod_dmdrvi_dif_api_declaration(1.0, dmuart, size_t, _read, ( dmdrvi_context_t context, void* handle, void* buffer, size_t size, uint32_t offset )) +{ + if (!is_valid_context(context) || buffer == NULL || size == 0) + { + return 0; + } + + size_t received = 0; + int ret = dmuart_port_receive(context->config.instance, (uint8_t *)buffer, size, &received); + if (ret != 0) + { + return 0; + } + return received; +} + +dmod_dmdrvi_dif_api_declaration(1.0, dmuart, size_t, _write, ( dmdrvi_context_t context, void* handle, const void* buffer, size_t size, uint32_t offset )) +{ + if (!is_valid_context(context) || buffer == NULL || size == 0) + { + return 0; + } + + int ret = dmuart_port_transmit(context->config.instance, (const uint8_t *)buffer, size); + if (ret != 0) + { + return 0; + } + return size; +} + +dmod_dmdrvi_dif_api_declaration(1.0, dmuart, int, _ioctl, ( dmdrvi_context_t context, void* handle, int command, void* arg )) +{ + int ret = 0; + if (!is_valid_context(context)) + { + DMOD_LOG_ERROR("Invalid DMDRVI context in dmuart_dmdrvi_ioctl\n"); + return -EINVAL; + } + + if (command >= dmuart_ioctl_cmd_max) + { + DMOD_LOG_ERROR("Invalid ioctl command %d\n", command); + return -EINVAL; + } + + if (command == dmuart_ioctl_cmd_reconfigure) + { + dmuart_port_deinit(context->config.instance); + ret = configure(context); + return ret; + } + + if (command == dmuart_ioctl_cmd_set_interrupt_handler) + { + if (arg == NULL) + { + /* Remove handler */ + dmuart_port_remove_interrupt_handler(context->config.instance, context); + return 0; + } + return dmuart_port_add_interrupt_handler( + context->config.instance, + (dmuart_port_interrupt_handler_t)*(dmuart_interrupt_handler_t *)arg, + context); + } + + if (arg == NULL) + { + DMOD_LOG_ERROR("Null argument for ioctl command %d\n", command); + return -EINVAL; + } + + /* Try read first */ + ret = read_configuration(context, command, arg); + if (ret != 0) + { + /* Not a read command - try write */ + dmuart_config_t new_config; + memcpy(&new_config, &context->config, sizeof(dmuart_config_t)); + + ret = update_configuration(&new_config, command, arg); + if (ret == 0) + { + memcpy(&context->config, &new_config, sizeof(dmuart_config_t)); + dmuart_port_deinit(context->config.instance); + ret = configure(context); + } + } + + return ret; +} + +dmod_dmdrvi_dif_api_declaration(1.0, dmuart, int, _flush, ( dmdrvi_context_t context, void* handle )) +{ + if (!is_valid_context(context)) + { + DMOD_LOG_ERROR("Invalid DMDRVI context in dmuart_dmdrvi_flush\n"); + return -EINVAL; + } + + return dmuart_port_flush(context->config.instance); +} + +dmod_dmdrvi_dif_api_declaration(1.0, dmuart, int, _stat, ( dmdrvi_context_t context, const char* path, dmdrvi_stat_t* stat )) +{ + if (!is_valid_context(context) || stat == NULL) + { + DMOD_LOG_ERROR("Invalid parameters in dmuart_dmdrvi_stat\n"); + return -EINVAL; + } + + stat->size = 0; /* Stream device, no fixed size */ + stat->mode = 0666; /* Read-write permissions */ + return 0; +} diff --git a/src/port/CMakeLists.txt b/src/port/CMakeLists.txt new file mode 100644 index 0000000..3dac0d2 --- /dev/null +++ b/src/port/CMakeLists.txt @@ -0,0 +1,48 @@ + +# ====================================================================== +# Parameters +# ====================================================================== +set(DMUART_MCU_SERIES "stm32f7" CACHE STRING "Target MCU series") + +# ====================================================================== +# dmuart_port Module Configuration +# ====================================================================== +# Name of the module +set(DMOD_MODULE_NAME dmuart_port) + +# Version is already set above and used in project() +# No need to set it again here + +# Author (should be string) +set(DMOD_AUTHOR_NAME "Patryk Kubiak") + +# Stack size for the module (should be integer) +set(DMOD_STACK_SIZE 1024) + +# Determine common source files based on MCU family +set(COMMON_SOURCES "") +if(DMUART_MCU_SERIES STREQUAL "stm32f7") + # STM32F7 uses stm32_common helper + set(COMMON_SOURCES stm32_common/stm32_common.c) +endif() + +# +# dmod_add_library - create a library module +# it has the same signature as add_library +# and can be used in the same way after the creation +# (for example, to link libraries) +# +dmod_add_library(${DMOD_MODULE_NAME} ${DMOD_MODULE_VERSION} + # List of source files - can include C and C++ files + ${DMUART_MCU_SERIES}/port.c + ${COMMON_SOURCES} +) + +target_include_directories(${DMOD_MODULE_NAME} PRIVATE + ${CMAKE_SOURCE_DIR}/include + ${CMAKE_CURRENT_SOURCE_DIR} +) + +target_include_directories(${DMOD_MODULE_NAME}_if INTERFACE + ${CMAKE_SOURCE_DIR}/include +) diff --git a/src/port/README.md b/src/port/README.md new file mode 100644 index 0000000..c9d19ee --- /dev/null +++ b/src/port/README.md @@ -0,0 +1,14 @@ +# DMUART Port Layer + +This directory contains hardware-specific UART implementations. + +## Available Ports + +| MCU Family | Directory | Status | +|------------|-----------|--------| +| STM32F7 | `stm32f7/` | ✅ Supported | +| STM32 Common | `stm32_common/` | Shared utilities | + +## Adding a New Port + +See [Port Implementation Guide](../../docs/port-implementation.md) for detailed instructions. diff --git a/src/port/stm32_common/stm32_common.c b/src/port/stm32_common/stm32_common.c new file mode 100644 index 0000000..14ec6ff --- /dev/null +++ b/src/port/stm32_common/stm32_common.c @@ -0,0 +1,25 @@ +#include "stm32_common.h" +#include "port/stm32_common_regs.h" + +/** + * @brief Wait for a UART status flag with timeout + * + * @param usart_base USART base address + * @param flag ISR flag to wait for + * @param timeout Timeout counter + * + * @return int 0 on success, -1 on timeout + */ +int stm32_uart_wait_flag(uint32_t usart_base, uint32_t flag, uint32_t timeout) +{ + volatile USART_TypeDef *USART = (USART_TypeDef *)usart_base; + + while (!(USART->ISR & flag)) + { + if (--timeout == 0) + { + return -1; + } + } + return 0; +} diff --git a/src/port/stm32_common/stm32_common.h b/src/port/stm32_common/stm32_common.h new file mode 100644 index 0000000..161b4c9 --- /dev/null +++ b/src/port/stm32_common/stm32_common.h @@ -0,0 +1,17 @@ +#ifndef STM32_COMMON_H +#define STM32_COMMON_H + +#include + +/** + * @brief Wait for a UART status flag with timeout + * + * @param usart_base USART base address + * @param flag ISR flag to wait for + * @param timeout Timeout counter + * + * @return int 0 on success, -1 on timeout + */ +int stm32_uart_wait_flag(uint32_t usart_base, uint32_t flag, uint32_t timeout); + +#endif // STM32_COMMON_H diff --git a/src/port/stm32f4/config.cmake b/src/port/stm32f4/config.cmake new file mode 100644 index 0000000..bd5bb15 --- /dev/null +++ b/src/port/stm32f4/config.cmake @@ -0,0 +1 @@ +set(DMOD_TOOLS_NAME "arch/armv7/cortex-m4" CACHE STRING "Name of the tools configuration") diff --git a/src/port/stm32f4/port.c b/src/port/stm32f4/port.c new file mode 100644 index 0000000..9ea84e2 --- /dev/null +++ b/src/port/stm32f4/port.c @@ -0,0 +1,496 @@ +#define DMOD_ENABLE_REGISTRATION ON +#include "dmuart_port.h" +#include "dmod.h" +#include "port/stm32f4_regs.h" + +/* UART instance base addresses */ +static const uint32_t uart_base_addresses[STM32F4_UART_MAX_INSTANCES] = { + STM32F4_USART1_BASE, + STM32F4_USART2_BASE, + STM32F4_USART3_BASE, + STM32F4_UART4_BASE, + STM32F4_UART5_BASE, + STM32F4_USART6_BASE, +}; + +/* Per-instance interrupt handler registration */ +static dmuart_port_interrupt_handler_t irq_handlers[STM32F4_UART_MAX_INSTANCES]; +static void *irq_user_ptrs[STM32F4_UART_MAX_INSTANCES]; + +/* Timeout value */ +#define STM32F4_UART_TIMEOUT 10000U + +/* RCC register structure (subset) */ +typedef struct { + volatile uint32_t CR; + volatile uint32_t PLLCFGR; + volatile uint32_t CFGR; + volatile uint32_t CIR; + volatile uint32_t AHB1RSTR; + volatile uint32_t AHB2RSTR; + volatile uint32_t AHB3RSTR; + volatile uint32_t RESERVED0; + volatile uint32_t APB1RSTR; + volatile uint32_t APB2RSTR; + volatile uint32_t RESERVED1[2]; + volatile uint32_t AHB1ENR; + volatile uint32_t AHB2ENR; + volatile uint32_t AHB3ENR; + volatile uint32_t RESERVED2; + volatile uint32_t APB1ENR; + volatile uint32_t APB2ENR; +} STM32F4_RCC_TypeDef; + +static int validate_instance(dmuart_instance_t instance) +{ + return (instance >= 1 && instance <= STM32F4_UART_MAX_INSTANCES) ? 0 : -1; +} + +static volatile STM32F4_USART_TypeDef *get_usart(dmuart_instance_t instance) +{ + return (volatile STM32F4_USART_TypeDef *)uart_base_addresses[instance - 1]; +} + +static uint32_t get_uart_clock(dmuart_instance_t instance) +{ + if (instance == 1 || instance == 6) + { + return STM32F4_APB2_FREQ; + } + return STM32F4_APB1_FREQ; +} + +static void enable_uart_clock(dmuart_instance_t instance) +{ + volatile STM32F4_RCC_TypeDef *RCC = (STM32F4_RCC_TypeDef *)STM32F4_RCC_BASE; + + switch (instance) + { + case 1: RCC->APB2ENR |= STM32F4_RCC_APB2ENR_USART1EN; break; + case 2: RCC->APB1ENR |= STM32F4_RCC_APB1ENR_USART2EN; break; + case 3: RCC->APB1ENR |= STM32F4_RCC_APB1ENR_USART3EN; break; + case 4: RCC->APB1ENR |= STM32F4_RCC_APB1ENR_UART4EN; break; + case 5: RCC->APB1ENR |= STM32F4_RCC_APB1ENR_UART5EN; break; + case 6: RCC->APB2ENR |= STM32F4_RCC_APB2ENR_USART6EN; break; + default: break; + } +} + +/* ---- DMOD lifecycle ---- */ + +int dmod_init(const Dmod_Config_t *Config) +{ + Dmod_Printf("DMUART port module initialized (STM32F4)\n"); + for (int i = 0; i < STM32F4_UART_MAX_INSTANCES; i++) + { + irq_handlers[i] = NULL; + irq_user_ptrs[i] = NULL; + } + return 0; +} + +int dmod_deinit(void) +{ + Dmod_Printf("DMUART port module deinitialized (STM32F4)\n"); + return 0; +} + +/* ---- Port API implementation ---- */ + +dmod_dmuart_port_api_declaration(1.0, int, _init, ( dmuart_instance_t instance )) +{ + if (validate_instance(instance) != 0) return -1; + + enable_uart_clock(instance); + + volatile STM32F4_USART_TypeDef *USART = get_usart(instance); + + /* Disable USART for configuration */ + USART->CR1 &= ~STM32F4_USART_CR1_UE; + + /* Default: 8N1, no flow control */ + USART->CR1 &= ~(STM32F4_USART_CR1_M | STM32F4_USART_CR1_PCE | STM32F4_USART_CR1_PS); + USART->CR2 &= ~STM32F4_USART_CR2_STOP_Msk; + USART->CR3 &= ~(STM32F4_USART_CR3_RTSE | STM32F4_USART_CR3_CTSE); + + /* Enable USART, transmitter and receiver */ + USART->CR1 |= STM32F4_USART_CR1_UE | STM32F4_USART_CR1_TE | STM32F4_USART_CR1_RE; + + return 0; +} + +dmod_dmuart_port_api_declaration(1.0, int, _deinit, ( dmuart_instance_t instance )) +{ + if (validate_instance(instance) != 0) return -1; + + volatile STM32F4_USART_TypeDef *USART = get_usart(instance); + USART->CR1 &= ~(STM32F4_USART_CR1_UE | STM32F4_USART_CR1_TE | STM32F4_USART_CR1_RE); + + return 0; +} + +dmod_dmuart_port_api_declaration(1.0, int, _transmit, ( dmuart_instance_t instance, const uint8_t* data, size_t size )) +{ + if (validate_instance(instance) != 0 || data == NULL) return -1; + + volatile STM32F4_USART_TypeDef *USART = get_usart(instance); + + for (size_t i = 0; i < size; i++) + { + uint32_t timeout = STM32F4_UART_TIMEOUT; + while (!(USART->SR & STM32F4_USART_SR_TXE)) + { + if (--timeout == 0) return -1; + } + USART->DR = data[i]; + } + + uint32_t timeout = STM32F4_UART_TIMEOUT; + while (!(USART->SR & STM32F4_USART_SR_TC)) + { + if (--timeout == 0) return -1; + } + + return 0; +} + +dmod_dmuart_port_api_declaration(1.0, int, _receive, ( dmuart_instance_t instance, uint8_t* data, size_t size, size_t* received )) +{ + if (validate_instance(instance) != 0 || data == NULL || received == NULL) return -1; + + volatile STM32F4_USART_TypeDef *USART = get_usart(instance); + *received = 0; + + for (size_t i = 0; i < size; i++) + { + uint32_t timeout = STM32F4_UART_TIMEOUT; + while (!(USART->SR & STM32F4_USART_SR_RXNE)) + { + if (--timeout == 0) return 0; + } + + if (USART->SR & (STM32F4_USART_SR_ORE | STM32F4_USART_SR_FE | STM32F4_USART_SR_PE)) + { + /* Clear errors by reading SR then DR */ + (void)USART->DR; + return -1; + } + + data[i] = (uint8_t)(USART->DR & 0xFF); + (*received)++; + } + + return 0; +} + +dmod_dmuart_port_api_declaration(1.0, int, _flush, ( dmuart_instance_t instance )) +{ + if (validate_instance(instance) != 0) return -1; + + volatile STM32F4_USART_TypeDef *USART = get_usart(instance); + + /* Wait for TX complete */ + uint32_t timeout = STM32F4_UART_TIMEOUT; + while (!(USART->SR & STM32F4_USART_SR_TC)) + { + if (--timeout == 0) return -1; + } + + /* Discard pending RX data */ + while (USART->SR & STM32F4_USART_SR_RXNE) + { + (void)USART->DR; + } + + return 0; +} + +dmod_dmuart_port_api_declaration(1.0, int, _set_baudrate, ( dmuart_instance_t instance, dmuart_baudrate_t baudrate )) +{ + if (validate_instance(instance) != 0 || baudrate == 0) return -1; + + volatile STM32F4_USART_TypeDef *USART = get_usart(instance); + USART->CR1 &= ~STM32F4_USART_CR1_UE; + + uint32_t pclk = get_uart_clock(instance); + USART->BRR = (pclk + (baudrate / 2)) / baudrate; + + USART->CR1 |= STM32F4_USART_CR1_UE; + return 0; +} + +dmod_dmuart_port_api_declaration(1.0, dmuart_baudrate_t, _get_baudrate, ( dmuart_instance_t instance )) +{ + if (validate_instance(instance) != 0) return 0; + + volatile STM32F4_USART_TypeDef *USART = get_usart(instance); + uint32_t pclk = get_uart_clock(instance); + uint32_t brr = USART->BRR; + + if (brr == 0) return 0; + return (dmuart_baudrate_t)(pclk / brr); +} + +dmod_dmuart_port_api_declaration(1.0, int, _set_word_length, ( dmuart_instance_t instance, dmuart_word_length_t word_length )) +{ + if (validate_instance(instance) != 0) return -1; + + volatile STM32F4_USART_TypeDef *USART = get_usart(instance); + USART->CR1 &= ~STM32F4_USART_CR1_UE; + + /* STM32F4 only supports 8-bit and 9-bit (M bit) */ + USART->CR1 &= ~STM32F4_USART_CR1_M; + if (word_length == dmuart_word_length_9) + { + USART->CR1 |= STM32F4_USART_CR1_M; + } + + USART->CR1 |= STM32F4_USART_CR1_UE; + return 0; +} + +dmod_dmuart_port_api_declaration(1.0, dmuart_word_length_t, _get_word_length, ( dmuart_instance_t instance )) +{ + if (validate_instance(instance) != 0) return dmuart_word_length_8; + + volatile STM32F4_USART_TypeDef *USART = get_usart(instance); + + if (USART->CR1 & STM32F4_USART_CR1_M) return dmuart_word_length_9; + return dmuart_word_length_8; +} + +dmod_dmuart_port_api_declaration(1.0, int, _set_parity, ( dmuart_instance_t instance, dmuart_parity_t parity )) +{ + if (validate_instance(instance) != 0) return -1; + + volatile STM32F4_USART_TypeDef *USART = get_usart(instance); + USART->CR1 &= ~STM32F4_USART_CR1_UE; + + USART->CR1 &= ~(STM32F4_USART_CR1_PCE | STM32F4_USART_CR1_PS); + if (parity == dmuart_parity_even) + { + USART->CR1 |= STM32F4_USART_CR1_PCE; + } + else if (parity == dmuart_parity_odd) + { + USART->CR1 |= STM32F4_USART_CR1_PCE | STM32F4_USART_CR1_PS; + } + + USART->CR1 |= STM32F4_USART_CR1_UE; + return 0; +} + +dmod_dmuart_port_api_declaration(1.0, dmuart_parity_t, _get_parity, ( dmuart_instance_t instance )) +{ + if (validate_instance(instance) != 0) return dmuart_parity_none; + + volatile STM32F4_USART_TypeDef *USART = get_usart(instance); + + if (!(USART->CR1 & STM32F4_USART_CR1_PCE)) return dmuart_parity_none; + if (USART->CR1 & STM32F4_USART_CR1_PS) return dmuart_parity_odd; + return dmuart_parity_even; +} + +dmod_dmuart_port_api_declaration(1.0, int, _set_stop_bit, ( dmuart_instance_t instance, dmuart_stop_bit_t stop_bit )) +{ + if (validate_instance(instance) != 0) return -1; + + volatile STM32F4_USART_TypeDef *USART = get_usart(instance); + USART->CR1 &= ~STM32F4_USART_CR1_UE; + + USART->CR2 &= ~STM32F4_USART_CR2_STOP_Msk; + if (stop_bit == dmuart_stop_bit_2) + { + USART->CR2 |= STM32F4_USART_CR2_STOP_2BIT; + } + + USART->CR1 |= STM32F4_USART_CR1_UE; + return 0; +} + +dmod_dmuart_port_api_declaration(1.0, dmuart_stop_bit_t, _get_stop_bit, ( dmuart_instance_t instance )) +{ + if (validate_instance(instance) != 0) return dmuart_stop_bit_1; + + volatile STM32F4_USART_TypeDef *USART = get_usart(instance); + + if ((USART->CR2 & STM32F4_USART_CR2_STOP_Msk) == STM32F4_USART_CR2_STOP_2BIT) + return dmuart_stop_bit_2; + return dmuart_stop_bit_1; +} + +dmod_dmuart_port_api_declaration(1.0, int, _set_bit_order, ( dmuart_instance_t instance, dmuart_bit_order_t bit_order )) +{ + if (validate_instance(instance) != 0) return -1; + + /* STM32F4 does not support MSBFIRST - only LSB first is available */ + if (bit_order == dmuart_bit_order_msb_first) return -1; + + return 0; +} + +dmod_dmuart_port_api_declaration(1.0, dmuart_bit_order_t, _get_bit_order, ( dmuart_instance_t instance )) +{ + /* STM32F4 only supports LSB first */ + return dmuart_bit_order_lsb_first; +} + +dmod_dmuart_port_api_declaration(1.0, int, _set_invert, ( dmuart_instance_t instance, dmuart_invert_t invert )) +{ + if (validate_instance(instance) != 0) return -1; + + /* STM32F4 does not support signal inversion */ + if (invert != dmuart_invert_none) return -1; + + return 0; +} + +dmod_dmuart_port_api_declaration(1.0, dmuart_invert_t, _get_invert, ( dmuart_instance_t instance )) +{ + return dmuart_invert_none; +} + +dmod_dmuart_port_api_declaration(1.0, int, _set_loopback, ( dmuart_instance_t instance, dmuart_loopback_t loopback )) +{ + if (validate_instance(instance) != 0) return -1; + + volatile STM32F4_USART_TypeDef *USART = get_usart(instance); + USART->CR1 &= ~STM32F4_USART_CR1_UE; + + /* STM32F4 does not have a true internal loopback mode. + * HDSEL (half-duplex) mode is used here as the closest available option; + * note that it is not a true loopback — it shares the TX pin for both + * directions, so external wiring is still needed for loopback testing. */ + if (loopback == dmuart_loopback_on) + { + USART->CR3 |= (1U << 3); /* HDSEL */ + } + else + { + USART->CR3 &= ~(1U << 3); + } + + USART->CR1 |= STM32F4_USART_CR1_UE; + return 0; +} + +dmod_dmuart_port_api_declaration(1.0, dmuart_loopback_t, _get_loopback, ( dmuart_instance_t instance )) +{ + if (validate_instance(instance) != 0) return dmuart_loopback_off; + + volatile STM32F4_USART_TypeDef *USART = get_usart(instance); + + if (USART->CR3 & (1U << 3)) return dmuart_loopback_on; + return dmuart_loopback_off; +} + +dmod_dmuart_port_api_declaration(1.0, int, _set_interrupt_trigger, ( dmuart_instance_t instance, dmuart_int_trigger_t trigger )) +{ + if (validate_instance(instance) != 0) return -1; + + volatile STM32F4_USART_TypeDef *USART = get_usart(instance); + + /* Clear all interrupt enable bits */ + USART->CR1 &= ~(STM32F4_USART_CR1_RXNEIE | STM32F4_USART_CR1_TXEIE | + STM32F4_USART_CR1_TCIE | STM32F4_USART_CR1_IDLEIE); + USART->CR3 &= ~STM32F4_USART_CR3_EIE; + + if (trigger & dmuart_int_trigger_rx_not_empty) + USART->CR1 |= STM32F4_USART_CR1_RXNEIE; + if (trigger & dmuart_int_trigger_tx_empty) + USART->CR1 |= STM32F4_USART_CR1_TXEIE; + if (trigger & dmuart_int_trigger_tx_complete) + USART->CR1 |= STM32F4_USART_CR1_TCIE; + if (trigger & dmuart_int_trigger_idle) + USART->CR1 |= STM32F4_USART_CR1_IDLEIE; + if (trigger & dmuart_int_trigger_error) + USART->CR3 |= STM32F4_USART_CR3_EIE; + + return 0; +} + +dmod_dmuart_port_api_declaration(1.0, int, _read_interrupt_trigger, ( dmuart_instance_t instance, dmuart_int_trigger_t *out_trigger )) +{ + if (validate_instance(instance) != 0 || out_trigger == NULL) return -1; + + volatile STM32F4_USART_TypeDef *USART = get_usart(instance); + *out_trigger = dmuart_int_trigger_off; + + if (USART->CR1 & STM32F4_USART_CR1_RXNEIE) *out_trigger = (dmuart_int_trigger_t)(*out_trigger | dmuart_int_trigger_rx_not_empty); + if (USART->CR1 & STM32F4_USART_CR1_TXEIE) *out_trigger = (dmuart_int_trigger_t)(*out_trigger | dmuart_int_trigger_tx_empty); + if (USART->CR1 & STM32F4_USART_CR1_TCIE) *out_trigger = (dmuart_int_trigger_t)(*out_trigger | dmuart_int_trigger_tx_complete); + if (USART->CR1 & STM32F4_USART_CR1_IDLEIE) *out_trigger = (dmuart_int_trigger_t)(*out_trigger | dmuart_int_trigger_idle); + if (USART->CR3 & STM32F4_USART_CR3_EIE) *out_trigger = (dmuart_int_trigger_t)(*out_trigger | dmuart_int_trigger_error); + + return 0; +} + +dmod_dmuart_port_api_declaration(1.0, int, _add_interrupt_handler, + ( dmuart_instance_t instance, dmuart_port_interrupt_handler_t handler, void *user_ptr )) +{ + if (validate_instance(instance) != 0 || handler == NULL) return -1; + + irq_handlers[instance - 1] = handler; + irq_user_ptrs[instance - 1] = user_ptr; + return 0; +} + +dmod_dmuart_port_api_declaration(1.0, int, _remove_interrupt_handler, + ( dmuart_instance_t instance, void *user_ptr )) +{ + if (validate_instance(instance) != 0) return -1; + + if (irq_user_ptrs[instance - 1] == user_ptr) + { + irq_handlers[instance - 1] = NULL; + irq_user_ptrs[instance - 1] = NULL; + } + return 0; +} + +/* ---- ISR handlers ---- */ + +static void stm32f4_uart_irq_handler(dmuart_instance_t instance) +{ + volatile STM32F4_USART_TypeDef *USART = get_usart(instance); + dmuart_port_interrupt_handler_t handler = irq_handlers[instance - 1]; + + if (handler == NULL) return; + + uint8_t data = 0; + dmuart_int_trigger_t trigger = dmuart_int_trigger_off; + + if (USART->SR & STM32F4_USART_SR_RXNE) + { + data = (uint8_t)(USART->DR & 0xFF); + trigger = (dmuart_int_trigger_t)(trigger | dmuart_int_trigger_rx_not_empty); + } + if (USART->SR & STM32F4_USART_SR_TXE) + { + trigger = (dmuart_int_trigger_t)(trigger | dmuart_int_trigger_tx_empty); + } + if (USART->SR & STM32F4_USART_SR_TC) + { + USART->SR &= ~STM32F4_USART_SR_TC; + trigger = (dmuart_int_trigger_t)(trigger | dmuart_int_trigger_tx_complete); + } + if (USART->SR & (STM32F4_USART_SR_ORE | STM32F4_USART_SR_FE | STM32F4_USART_SR_PE)) + { + /* Clear errors by reading SR then DR */ + (void)USART->DR; + trigger = (dmuart_int_trigger_t)(trigger | dmuart_int_trigger_error); + } + + if (trigger != dmuart_int_trigger_off) + { + handler(irq_user_ptrs[instance - 1], instance, trigger, data); + } +} + +/* NVIC IRQ numbers for STM32F4 UART instances */ +DMOD_IRQ_HANDLER(37) { stm32f4_uart_irq_handler(1); } /* USART1 */ +DMOD_IRQ_HANDLER(38) { stm32f4_uart_irq_handler(2); } /* USART2 */ +DMOD_IRQ_HANDLER(39) { stm32f4_uart_irq_handler(3); } /* USART3 */ +DMOD_IRQ_HANDLER(52) { stm32f4_uart_irq_handler(4); } /* UART4 */ +DMOD_IRQ_HANDLER(53) { stm32f4_uart_irq_handler(5); } /* UART5 */ +DMOD_IRQ_HANDLER(71) { stm32f4_uart_irq_handler(6); } /* USART6 */ diff --git a/src/port/stm32f7/config.cmake b/src/port/stm32f7/config.cmake new file mode 100644 index 0000000..bf89c73 --- /dev/null +++ b/src/port/stm32f7/config.cmake @@ -0,0 +1 @@ +set(DMOD_TOOLS_NAME "arch/armv7/cortex-m7" CACHE STRING "Name of the tools configuration") diff --git a/src/port/stm32f7/port.c b/src/port/stm32f7/port.c new file mode 100644 index 0000000..a54eea6 --- /dev/null +++ b/src/port/stm32f7/port.c @@ -0,0 +1,514 @@ +#define DMOD_ENABLE_REGISTRATION ON +#include "dmuart_port.h" +#include "dmod.h" +#include "../stm32_common/stm32_common.h" +#include "port/stm32_common_regs.h" +#include "port/stm32f7_regs.h" + +/* UART instance base addresses */ +static const uint32_t uart_base_addresses[STM32F7_UART_MAX_INSTANCES] = { + STM32F7_USART1_BASE, + STM32F7_USART2_BASE, + STM32F7_USART3_BASE, + STM32F7_UART4_BASE, + STM32F7_UART5_BASE, + STM32F7_USART6_BASE, + STM32F7_UART7_BASE, + STM32F7_UART8_BASE, +}; + +/* Per-instance interrupt handler registration */ +static dmuart_port_interrupt_handler_t irq_handlers[STM32F7_UART_MAX_INSTANCES]; +static void *irq_user_ptrs[STM32F7_UART_MAX_INSTANCES]; + +static int validate_instance(dmuart_instance_t instance) +{ + return (instance >= 1 && instance <= STM32F7_UART_MAX_INSTANCES) ? 0 : -1; +} + +static volatile USART_TypeDef *get_usart(dmuart_instance_t instance) +{ + return (volatile USART_TypeDef *)uart_base_addresses[instance - 1]; +} + +static uint32_t get_uart_clock(dmuart_instance_t instance) +{ + /* USART1 and USART6 are on APB2, others are on APB1 */ + if (instance == 1 || instance == 6) + { + return STM32F7_APB2_FREQ; + } + return STM32F7_APB1_FREQ; +} + +static void enable_uart_clock(dmuart_instance_t instance) +{ + volatile UART_RCC_TypeDef *RCC = (UART_RCC_TypeDef *)STM32F7_RCC_BASE; + + switch (instance) + { + case 1: RCC->APB2ENR |= RCC_APB2ENR_USART1EN; break; + case 2: RCC->APB1ENR |= RCC_APB1ENR_USART2EN; break; + case 3: RCC->APB1ENR |= RCC_APB1ENR_USART3EN; break; + case 4: RCC->APB1ENR |= RCC_APB1ENR_UART4EN; break; + case 5: RCC->APB1ENR |= RCC_APB1ENR_UART5EN; break; + case 6: RCC->APB2ENR |= RCC_APB2ENR_USART6EN; break; + case 7: RCC->APB1ENR |= RCC_APB1ENR_UART7EN; break; + case 8: RCC->APB1ENR |= RCC_APB1ENR_UART8EN; break; + default: break; + } +} + +/* ---- DMOD lifecycle ---- */ + +int dmod_init(const Dmod_Config_t *Config) +{ + Dmod_Printf("DMUART port module initialized (STM32F7)\n"); + for (int i = 0; i < STM32F7_UART_MAX_INSTANCES; i++) + { + irq_handlers[i] = NULL; + irq_user_ptrs[i] = NULL; + } + return 0; +} + +int dmod_deinit(void) +{ + Dmod_Printf("DMUART port module deinitialized (STM32F7)\n"); + return 0; +} + +/* ---- Port API implementation ---- */ + +dmod_dmuart_port_api_declaration(1.0, int, _init, ( dmuart_instance_t instance )) +{ + if (validate_instance(instance) != 0) return -1; + + enable_uart_clock(instance); + + volatile USART_TypeDef *USART = get_usart(instance); + + /* Disable USART for configuration */ + USART->CR1 &= ~USART_CR1_UE; + + /* Default: 8N1, no flow control */ + USART->CR1 &= ~(USART_CR1_M0 | USART_CR1_M1 | USART_CR1_PCE | USART_CR1_PS); + USART->CR2 &= ~USART_CR2_STOP_Msk; + USART->CR3 &= ~(USART_CR3_RTSE | USART_CR3_CTSE); + + /* Enable USART, transmitter and receiver */ + USART->CR1 |= USART_CR1_UE | USART_CR1_TE | USART_CR1_RE; + + return 0; +} + +dmod_dmuart_port_api_declaration(1.0, int, _deinit, ( dmuart_instance_t instance )) +{ + if (validate_instance(instance) != 0) return -1; + + volatile USART_TypeDef *USART = get_usart(instance); + USART->CR1 &= ~(USART_CR1_UE | USART_CR1_TE | USART_CR1_RE); + + return 0; +} + +dmod_dmuart_port_api_declaration(1.0, int, _transmit, ( dmuart_instance_t instance, const uint8_t* data, size_t size )) +{ + if (validate_instance(instance) != 0 || data == NULL) return -1; + + volatile USART_TypeDef *USART = get_usart(instance); + + for (size_t i = 0; i < size; i++) + { + uint32_t timeout = UART_TIMEOUT_VALUE; + while (!(USART->ISR & USART_ISR_TXE)) + { + if (--timeout == 0) return -1; + } + USART->TDR = data[i]; + } + + /* Wait for transmission complete */ + uint32_t timeout = UART_TIMEOUT_VALUE; + while (!(USART->ISR & USART_ISR_TC)) + { + if (--timeout == 0) return -1; + } + + return 0; +} + +dmod_dmuart_port_api_declaration(1.0, int, _receive, ( dmuart_instance_t instance, uint8_t* data, size_t size, size_t* received )) +{ + if (validate_instance(instance) != 0 || data == NULL || received == NULL) return -1; + + volatile USART_TypeDef *USART = get_usart(instance); + *received = 0; + + for (size_t i = 0; i < size; i++) + { + uint32_t timeout = UART_TIMEOUT_VALUE; + while (!(USART->ISR & USART_ISR_RXNE)) + { + if (--timeout == 0) return 0; /* Timeout - return what we have */ + } + + if (USART->ISR & (USART_ISR_ORE | USART_ISR_FE | USART_ISR_PE)) + { + USART->ICR |= USART_ICR_ORECF | USART_ICR_FECF | USART_ICR_PECF; + return -1; + } + + data[i] = (uint8_t)(USART->RDR & 0xFF); + (*received)++; + } + + return 0; +} + +dmod_dmuart_port_api_declaration(1.0, int, _flush, ( dmuart_instance_t instance )) +{ + if (validate_instance(instance) != 0) return -1; + + volatile USART_TypeDef *USART = get_usart(instance); + + /* Wait for TX complete */ + uint32_t timeout = UART_TIMEOUT_VALUE; + while (!(USART->ISR & USART_ISR_TC)) + { + if (--timeout == 0) return -1; + } + + /* Discard any pending RX data */ + while (USART->ISR & USART_ISR_RXNE) + { + (void)USART->RDR; + } + + /* Clear any error flags */ + USART->ICR |= USART_ICR_ORECF | USART_ICR_FECF | USART_ICR_PECF; + + return 0; +} + +dmod_dmuart_port_api_declaration(1.0, int, _set_baudrate, ( dmuart_instance_t instance, dmuart_baudrate_t baudrate )) +{ + if (validate_instance(instance) != 0 || baudrate == 0) return -1; + + volatile USART_TypeDef *USART = get_usart(instance); + USART->CR1 &= ~USART_CR1_UE; + + uint32_t pclk = get_uart_clock(instance); + USART->BRR = (pclk + (baudrate / 2)) / baudrate; + + USART->CR1 |= USART_CR1_UE; + return 0; +} + +dmod_dmuart_port_api_declaration(1.0, dmuart_baudrate_t, _get_baudrate, ( dmuart_instance_t instance )) +{ + if (validate_instance(instance) != 0) return 0; + + volatile USART_TypeDef *USART = get_usart(instance); + uint32_t pclk = get_uart_clock(instance); + uint32_t brr = USART->BRR; + + if (brr == 0) return 0; + return (dmuart_baudrate_t)(pclk / brr); +} + +dmod_dmuart_port_api_declaration(1.0, int, _set_word_length, ( dmuart_instance_t instance, dmuart_word_length_t word_length )) +{ + if (validate_instance(instance) != 0) return -1; + + volatile USART_TypeDef *USART = get_usart(instance); + USART->CR1 &= ~USART_CR1_UE; + + USART->CR1 &= ~(USART_CR1_M0 | USART_CR1_M1); + if (word_length == dmuart_word_length_9) + { + USART->CR1 |= USART_CR1_M0; + } + else if (word_length == dmuart_word_length_7) + { + USART->CR1 |= USART_CR1_M1; + } + + USART->CR1 |= USART_CR1_UE; + return 0; +} + +dmod_dmuart_port_api_declaration(1.0, dmuart_word_length_t, _get_word_length, ( dmuart_instance_t instance )) +{ + if (validate_instance(instance) != 0) return dmuart_word_length_8; + + volatile USART_TypeDef *USART = get_usart(instance); + + if (USART->CR1 & USART_CR1_M0) return dmuart_word_length_9; + if (USART->CR1 & USART_CR1_M1) return dmuart_word_length_7; + return dmuart_word_length_8; +} + +dmod_dmuart_port_api_declaration(1.0, int, _set_parity, ( dmuart_instance_t instance, dmuart_parity_t parity )) +{ + if (validate_instance(instance) != 0) return -1; + + volatile USART_TypeDef *USART = get_usart(instance); + USART->CR1 &= ~USART_CR1_UE; + + USART->CR1 &= ~(USART_CR1_PCE | USART_CR1_PS); + if (parity == dmuart_parity_even) + { + USART->CR1 |= USART_CR1_PCE; + } + else if (parity == dmuart_parity_odd) + { + USART->CR1 |= USART_CR1_PCE | USART_CR1_PS; + } + + USART->CR1 |= USART_CR1_UE; + return 0; +} + +dmod_dmuart_port_api_declaration(1.0, dmuart_parity_t, _get_parity, ( dmuart_instance_t instance )) +{ + if (validate_instance(instance) != 0) return dmuart_parity_none; + + volatile USART_TypeDef *USART = get_usart(instance); + + if (!(USART->CR1 & USART_CR1_PCE)) return dmuart_parity_none; + if (USART->CR1 & USART_CR1_PS) return dmuart_parity_odd; + return dmuart_parity_even; +} + +dmod_dmuart_port_api_declaration(1.0, int, _set_stop_bit, ( dmuart_instance_t instance, dmuart_stop_bit_t stop_bit )) +{ + if (validate_instance(instance) != 0) return -1; + + volatile USART_TypeDef *USART = get_usart(instance); + USART->CR1 &= ~USART_CR1_UE; + + USART->CR2 &= ~USART_CR2_STOP_Msk; + if (stop_bit == dmuart_stop_bit_2) + { + USART->CR2 |= USART_CR2_STOP_2BIT; + } + + USART->CR1 |= USART_CR1_UE; + return 0; +} + +dmod_dmuart_port_api_declaration(1.0, dmuart_stop_bit_t, _get_stop_bit, ( dmuart_instance_t instance )) +{ + if (validate_instance(instance) != 0) return dmuart_stop_bit_1; + + volatile USART_TypeDef *USART = get_usart(instance); + + if ((USART->CR2 & USART_CR2_STOP_Msk) == USART_CR2_STOP_2BIT) + return dmuart_stop_bit_2; + return dmuart_stop_bit_1; +} + +dmod_dmuart_port_api_declaration(1.0, int, _set_bit_order, ( dmuart_instance_t instance, dmuart_bit_order_t bit_order )) +{ + if (validate_instance(instance) != 0) return -1; + + volatile USART_TypeDef *USART = get_usart(instance); + USART->CR1 &= ~USART_CR1_UE; + + /* STM32F7 CR2 bit 19 = MSBFIRST */ + if (bit_order == dmuart_bit_order_msb_first) + { + USART->CR2 |= (1U << 19); + } + else + { + USART->CR2 &= ~(1U << 19); + } + + USART->CR1 |= USART_CR1_UE; + return 0; +} + +dmod_dmuart_port_api_declaration(1.0, dmuart_bit_order_t, _get_bit_order, ( dmuart_instance_t instance )) +{ + if (validate_instance(instance) != 0) return dmuart_bit_order_lsb_first; + + volatile USART_TypeDef *USART = get_usart(instance); + + if (USART->CR2 & (1U << 19)) return dmuart_bit_order_msb_first; + return dmuart_bit_order_lsb_first; +} + +dmod_dmuart_port_api_declaration(1.0, int, _set_invert, ( dmuart_instance_t instance, dmuart_invert_t invert )) +{ + if (validate_instance(instance) != 0) return -1; + + volatile USART_TypeDef *USART = get_usart(instance); + USART->CR1 &= ~USART_CR1_UE; + + /* STM32F7 CR2: bit 17 = TXINV, bit 16 = RXINV */ + USART->CR2 &= ~((1U << 17) | (1U << 16)); + if (invert & dmuart_invert_tx) USART->CR2 |= (1U << 17); + if (invert & dmuart_invert_rx) USART->CR2 |= (1U << 16); + + USART->CR1 |= USART_CR1_UE; + return 0; +} + +dmod_dmuart_port_api_declaration(1.0, dmuart_invert_t, _get_invert, ( dmuart_instance_t instance )) +{ + if (validate_instance(instance) != 0) return dmuart_invert_none; + + volatile USART_TypeDef *USART = get_usart(instance); + dmuart_invert_t result = dmuart_invert_none; + + if (USART->CR2 & (1U << 17)) result = (dmuart_invert_t)(result | dmuart_invert_tx); + if (USART->CR2 & (1U << 16)) result = (dmuart_invert_t)(result | dmuart_invert_rx); + return result; +} + +dmod_dmuart_port_api_declaration(1.0, int, _set_loopback, ( dmuart_instance_t instance, dmuart_loopback_t loopback )) +{ + if (validate_instance(instance) != 0) return -1; + + volatile USART_TypeDef *USART = get_usart(instance); + USART->CR1 &= ~USART_CR1_UE; + + /* STM32F7 does not have a true internal loopback mode. + * SWAP bit (CR2 bit 15) swaps TX/RX pin assignment, which combined with + * external loopback wiring can be useful for testing. */ + if (loopback == dmuart_loopback_on) + { + USART->CR2 |= (1U << 15); /* SWAP */ + } + else + { + USART->CR2 &= ~(1U << 15); + } + + USART->CR1 |= USART_CR1_UE; + return 0; +} + +dmod_dmuart_port_api_declaration(1.0, dmuart_loopback_t, _get_loopback, ( dmuart_instance_t instance )) +{ + if (validate_instance(instance) != 0) return dmuart_loopback_off; + + volatile USART_TypeDef *USART = get_usart(instance); + + if (USART->CR2 & (1U << 15)) return dmuart_loopback_on; + return dmuart_loopback_off; +} + +dmod_dmuart_port_api_declaration(1.0, int, _set_interrupt_trigger, ( dmuart_instance_t instance, dmuart_int_trigger_t trigger )) +{ + if (validate_instance(instance) != 0) return -1; + + volatile USART_TypeDef *USART = get_usart(instance); + + /* Clear all interrupt enable bits first */ + USART->CR1 &= ~((1U << 5) | (1U << 7) | (1U << 6) | (1U << 4)); /* RXNEIE, TXEIE, TCIE, IDLEIE */ + USART->CR3 &= ~(1U << 0); /* EIE */ + + /* Set requested triggers */ + if (trigger & dmuart_int_trigger_rx_not_empty) + USART->CR1 |= (1U << 5); /* RXNEIE */ + if (trigger & dmuart_int_trigger_tx_empty) + USART->CR1 |= (1U << 7); /* TXEIE */ + if (trigger & dmuart_int_trigger_tx_complete) + USART->CR1 |= (1U << 6); /* TCIE */ + if (trigger & dmuart_int_trigger_idle) + USART->CR1 |= (1U << 4); /* IDLEIE */ + if (trigger & dmuart_int_trigger_error) + USART->CR3 |= (1U << 0); /* EIE */ + + return 0; +} + +dmod_dmuart_port_api_declaration(1.0, int, _read_interrupt_trigger, ( dmuart_instance_t instance, dmuart_int_trigger_t *out_trigger )) +{ + if (validate_instance(instance) != 0 || out_trigger == NULL) return -1; + + volatile USART_TypeDef *USART = get_usart(instance); + *out_trigger = dmuart_int_trigger_off; + + if (USART->CR1 & (1U << 5)) *out_trigger = (dmuart_int_trigger_t)(*out_trigger | dmuart_int_trigger_rx_not_empty); + if (USART->CR1 & (1U << 7)) *out_trigger = (dmuart_int_trigger_t)(*out_trigger | dmuart_int_trigger_tx_empty); + if (USART->CR1 & (1U << 6)) *out_trigger = (dmuart_int_trigger_t)(*out_trigger | dmuart_int_trigger_tx_complete); + if (USART->CR1 & (1U << 4)) *out_trigger = (dmuart_int_trigger_t)(*out_trigger | dmuart_int_trigger_idle); + if (USART->CR3 & (1U << 0)) *out_trigger = (dmuart_int_trigger_t)(*out_trigger | dmuart_int_trigger_error); + + return 0; +} + +dmod_dmuart_port_api_declaration(1.0, int, _add_interrupt_handler, + ( dmuart_instance_t instance, dmuart_port_interrupt_handler_t handler, void *user_ptr )) +{ + if (validate_instance(instance) != 0 || handler == NULL) return -1; + + irq_handlers[instance - 1] = handler; + irq_user_ptrs[instance - 1] = user_ptr; + return 0; +} + +dmod_dmuart_port_api_declaration(1.0, int, _remove_interrupt_handler, + ( dmuart_instance_t instance, void *user_ptr )) +{ + if (validate_instance(instance) != 0) return -1; + + if (irq_user_ptrs[instance - 1] == user_ptr) + { + irq_handlers[instance - 1] = NULL; + irq_user_ptrs[instance - 1] = NULL; + } + return 0; +} + +/* ---- ISR handlers ---- */ + +static void stm32f7_uart_irq_handler(dmuart_instance_t instance) +{ + volatile USART_TypeDef *USART = get_usart(instance); + dmuart_port_interrupt_handler_t handler = irq_handlers[instance - 1]; + + if (handler == NULL) return; + + uint8_t data = 0; + dmuart_int_trigger_t trigger = dmuart_int_trigger_off; + + if (USART->ISR & USART_ISR_RXNE) + { + data = (uint8_t)(USART->RDR & 0xFF); + trigger = (dmuart_int_trigger_t)(trigger | dmuart_int_trigger_rx_not_empty); + } + if (USART->ISR & USART_ISR_TXE) + { + trigger = (dmuart_int_trigger_t)(trigger | dmuart_int_trigger_tx_empty); + } + if (USART->ISR & USART_ISR_TC) + { + USART->ICR |= (1U << 6); /* TCCF */ + trigger = (dmuart_int_trigger_t)(trigger | dmuart_int_trigger_tx_complete); + } + if (USART->ISR & (USART_ISR_ORE | USART_ISR_FE | USART_ISR_PE)) + { + USART->ICR |= USART_ICR_ORECF | USART_ICR_FECF | USART_ICR_PECF; + trigger = (dmuart_int_trigger_t)(trigger | dmuart_int_trigger_error); + } + + if (trigger != dmuart_int_trigger_off) + { + handler(irq_user_ptrs[instance - 1], instance, trigger, data); + } +} + +/* NVIC IRQ numbers for STM32F7 UART instances */ +DMOD_IRQ_HANDLER(37) { stm32f7_uart_irq_handler(1); } /* USART1 */ +DMOD_IRQ_HANDLER(38) { stm32f7_uart_irq_handler(2); } /* USART2 */ +DMOD_IRQ_HANDLER(39) { stm32f7_uart_irq_handler(3); } /* USART3 */ +DMOD_IRQ_HANDLER(52) { stm32f7_uart_irq_handler(4); } /* UART4 */ +DMOD_IRQ_HANDLER(53) { stm32f7_uart_irq_handler(5); } /* UART5 */ +DMOD_IRQ_HANDLER(71) { stm32f7_uart_irq_handler(6); } /* USART6 */ +DMOD_IRQ_HANDLER(82) { stm32f7_uart_irq_handler(7); } /* UART7 */ +DMOD_IRQ_HANDLER(83) { stm32f7_uart_irq_handler(8); } /* UART8 */