Merge initIal_demo: complete firmware stack with USB/UART/PIO/LED
This commit is contained in:
commit
f3ac2bca9f
64
.devcontainer/devcontainer.json
Normal file
64
.devcontainer/devcontainer.json
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
// ============================================================================
|
||||||
|
// devcontainer.json - VS Code Dev Containers configuration
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Tells the VS Code "Dev Containers" extension how to launch this project
|
||||||
|
// inside the Docker container defined by our docker-compose.yml. When you
|
||||||
|
// run "Dev Containers: Reopen in Container" from the command palette, VS
|
||||||
|
// Code uses this file to:
|
||||||
|
// 1. Reuse the existing docker-compose.yml / Dockerfile (no duplication)
|
||||||
|
// 2. Attach to the `pico-build` service and open /project as the workspace
|
||||||
|
// 3. Install clangd + CMake Tools INSIDE the container (so language servers
|
||||||
|
// can see the real Pico SDK at /opt/pico-sdk)
|
||||||
|
// 4. Run CMake once after creation to generate compile_commands.json so
|
||||||
|
// intellisense works from the moment the first file is opened
|
||||||
|
//
|
||||||
|
// NOTE: this file is JSONC (JSON with Comments + trailing commas allowed),
|
||||||
|
// which is why these // comments are legal.
|
||||||
|
// ============================================================================
|
||||||
|
{
|
||||||
|
// Display name shown in the VS Code status bar / window title
|
||||||
|
"name": "Color Switcher PICO",
|
||||||
|
|
||||||
|
// Path to the docker-compose file, relative to THIS file.
|
||||||
|
// .devcontainer/ is one level below the project root, so ../ reaches it.
|
||||||
|
"dockerComposeFile": "../docker-compose.yml",
|
||||||
|
|
||||||
|
// Which service in docker-compose.yml to attach to. We only have one.
|
||||||
|
"service": "pico-build",
|
||||||
|
|
||||||
|
// Folder inside the container to open as the VS Code workspace.
|
||||||
|
// Must match the WORKDIR set in the Dockerfile.
|
||||||
|
"workspaceFolder": "/project",
|
||||||
|
|
||||||
|
// Runs exactly once, the first time the container is created. We invoke
|
||||||
|
// CMake to configure the build so that compile_commands.json is written
|
||||||
|
// to build/ before clangd tries to parse the first source file.
|
||||||
|
"postCreateCommand": "cmake -S cmake -B build",
|
||||||
|
|
||||||
|
"customizations": {
|
||||||
|
"vscode": {
|
||||||
|
// VS Code extensions installed INSIDE the container (not on the
|
||||||
|
// host Mac). These run with access to /opt/pico-sdk and can
|
||||||
|
// resolve all Pico SDK headers correctly.
|
||||||
|
"extensions": [
|
||||||
|
"llvm-vs-code-extensions.vscode-clangd",
|
||||||
|
"ms-vscode.cmake-tools"
|
||||||
|
],
|
||||||
|
|
||||||
|
// Arguments forwarded to clangd when it starts.
|
||||||
|
// --compile-commands-dir tells clangd where CMake wrote the
|
||||||
|
// compile_commands.json (inside build/, not the project root)
|
||||||
|
// --header-insertion=never stops clangd from auto-adding
|
||||||
|
// #include lines as you type, which tends to be noisy
|
||||||
|
// --background-index builds a project-wide symbol index in
|
||||||
|
// the background for fast go-to-definition / find-references
|
||||||
|
"settings": {
|
||||||
|
"clangd.arguments": [
|
||||||
|
"--compile-commands-dir=${workspaceFolder}/build",
|
||||||
|
"--header-insertion=never",
|
||||||
|
"--background-index"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# ============================================================================
|
||||||
|
# .gitignore for the color_switcher Pico firmware project
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# Build artifacts directory created by CMake / build.sh.
|
||||||
|
# Contains Makefiles, object files, the final .elf/.uf2/.bin/.hex/.dis/.map
|
||||||
|
# outputs, and any fetched dependencies (e.g. picotool). Regenerated on
|
||||||
|
# every build, so it should never be committed.
|
||||||
|
build/
|
||||||
63
CLAUDE.md
63
CLAUDE.md
@ -4,4 +4,65 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|||||||
|
|
||||||
## Project
|
## Project
|
||||||
|
|
||||||
Demo project for pico-1, Anthropic's agentic coding tool.
|
Raspberry Pi Pico firmware that sends commands over UART to a host computer. Written entirely in C using the Pico SDK (RP2040 / Cortex-M0+).
|
||||||
|
|
||||||
|
## Build System
|
||||||
|
|
||||||
|
All builds run inside Docker — no local toolchain required.
|
||||||
|
|
||||||
|
- `docker compose build` — build the container image (first time / after Dockerfile changes)
|
||||||
|
- `docker compose run --rm pico-build bash build.sh` — compile the firmware, output lands in `build/`
|
||||||
|
- `docker compose run --rm pico-build bash` — interactive shell for debugging
|
||||||
|
- `./flash.sh` — flash the `.uf2` to a Pico in BOOTSEL mode (host-side only, not Docker)
|
||||||
|
- `docker compose run --rm pico-build bash build.sh && ./flash.sh` — build + flash in one command
|
||||||
|
|
||||||
|
The build uses CMake with the Pico SDK's ARM cross-compilation toolchain. The final artifact is a `.uf2` file in `build/`. Flashing must run on the host macOS because it copies to `/Volumes/RPI-RP2` (the Pico's USB mass storage mount).
|
||||||
|
|
||||||
|
## Repository layout
|
||||||
|
|
||||||
|
```
|
||||||
|
color_switcher/
|
||||||
|
├── cmake/ # all build-system files
|
||||||
|
│ ├── CMakeLists.txt # thin orchestrator, drives build phases in order
|
||||||
|
│ ├── pico_sdk_import.cmake # Pico SDK bootstrap (copied from SDK)
|
||||||
|
│ └── cmake_config/
|
||||||
|
│ ├── project_config.cmake # project-wide vars (name, version, languages)
|
||||||
|
│ ├── mcu_config.cmake # MCU helpers: mcu_init / mcu_sdk_config / mcu_link_target
|
||||||
|
│ └── sources_config.cmake # source glob + include dir list
|
||||||
|
├── src/
|
||||||
|
│ ├── STD_TYPES/inc/ # shared fixed-width types, status enums, macros
|
||||||
|
│ ├── MCU_UART/{inc,prg,cfg}/ # hardware UART peripheral abstraction
|
||||||
|
│ ├── MCU_USB/{inc,prg,cfg}/ # USB-CDC (virtual serial port) abstraction
|
||||||
|
│ ├── HAL_COM/{inc,prg,cfg}/ # transport-agnostic comm layer (dispatches to UART/USB)
|
||||||
|
│ ├── APP_CLSW/{inc,prg,cfg}/ # color switcher application logic
|
||||||
|
│ └── SYS_ECU/prg/ # top-level app orchestrator (main entry)
|
||||||
|
├── build.sh # out-of-source build wrapper
|
||||||
|
├── Dockerfile / docker-compose.yml # containerized build env
|
||||||
|
```
|
||||||
|
|
||||||
|
## Component file convention
|
||||||
|
|
||||||
|
Each component uses three subfolders:
|
||||||
|
- `inc/` — public API headers (safe for any other component to `#include`)
|
||||||
|
- `prg/` — private header (`*_priv.h`) + implementation (`*_prg.c`)
|
||||||
|
- `cfg/` — configuration header and definitions (`*_cfg.h` / `*_cfg.c`)
|
||||||
|
|
||||||
|
## CMake build phases
|
||||||
|
|
||||||
|
The top-level `cmake/CMakeLists.txt` runs in a strict order dictated by the Pico SDK:
|
||||||
|
|
||||||
|
1. `include(project_config)` — defines variables
|
||||||
|
2. `include(mcu_config)` — defines helper macros (no side effects)
|
||||||
|
3. `mcu_init()` — includes `pico_sdk_import.cmake` (must be before `project()`)
|
||||||
|
4. `project(...)`
|
||||||
|
5. `mcu_sdk_config()` — calls `pico_sdk_init()`
|
||||||
|
6. `include(sources_config)` — sets `PROJECT_SOURCES` and `PROJECT_INCLUDE_DIRS`
|
||||||
|
7. `add_executable(...)`
|
||||||
|
8. `mcu_link_target(...)` — `target_link_libraries`, stdio routing, UF2 output
|
||||||
|
|
||||||
|
`PROJECT_ROOT_DIR` is computed in the top-level `CMakeLists.txt` as the parent of `cmake/` and used everywhere that refers to paths outside the build system (e.g. `src/`).
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
- Always add descriptive comments to all code and config files
|
||||||
|
- Avoid magic numbers — use named constants in `config.h`
|
||||||
|
|||||||
39
Dockerfile
Normal file
39
Dockerfile
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# Base image: Ubuntu 24.04 provides a stable environment for the ARM cross-compilation toolchain
|
||||||
|
FROM ubuntu:24.04
|
||||||
|
|
||||||
|
# Install all dependencies required to cross-compile C code for the Raspberry Pi Pico (RP2040):
|
||||||
|
# - cmake: build system generator used by the Pico SDK
|
||||||
|
# - gcc-arm-none-eabi: ARM cross-compiler that produces binaries for the Pico's Cortex-M0+ CPU
|
||||||
|
# - libnewlib-arm-none-eabi: C standard library implementation for bare-metal ARM targets
|
||||||
|
# - build-essential: provides make and other essential build utilities
|
||||||
|
# - git: needed to clone the Pico SDK and its submodules
|
||||||
|
# - python3: required by the Pico SDK build scripts for UF2 generation and other tooling
|
||||||
|
# - clangd: C/C++ language server used by the VS Code Dev Container for
|
||||||
|
# intellisense (go-to-definition, hover types, autocomplete) -
|
||||||
|
# must live INSIDE the container so it can resolve Pico SDK
|
||||||
|
# headers under /opt/pico-sdk at their real paths
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
cmake \
|
||||||
|
gcc-arm-none-eabi \
|
||||||
|
libnewlib-arm-none-eabi \
|
||||||
|
build-essential \
|
||||||
|
git \
|
||||||
|
python3 \
|
||||||
|
clangd \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Clone the official Raspberry Pi Pico SDK into the container.
|
||||||
|
# The SDK provides hardware abstraction libraries, startup code, and linker scripts
|
||||||
|
# needed to build firmware for the RP2040 microcontroller.
|
||||||
|
# Submodules include tinyusb (USB stack) and other vendor-specific dependencies.
|
||||||
|
RUN git clone https://github.com/raspberrypi/pico-sdk.git /opt/pico-sdk \
|
||||||
|
&& cd /opt/pico-sdk \
|
||||||
|
&& git submodule update --init
|
||||||
|
|
||||||
|
# Tell CMake where to find the Pico SDK. The SDK's CMake scripts look for this
|
||||||
|
# environment variable to locate platform files, toolchain config, and libraries.
|
||||||
|
ENV PICO_SDK_PATH=/opt/pico-sdk
|
||||||
|
|
||||||
|
# All build commands will run from /project, which is where the host source code
|
||||||
|
# gets mounted via docker-compose volumes
|
||||||
|
WORKDIR /project
|
||||||
35
README.md
35
README.md
@ -1,15 +1,36 @@
|
|||||||
# Demo1
|
# Color Switcher
|
||||||
|
|
||||||
A demo project for [pico-1](https://github.com/anthropics/pico-1), Anthropic's agentic coding tool.
|
A Raspberry Pi Pico project that sends commands over UART to a connected computer. Written entirely in C using the Pico SDK.
|
||||||
|
|
||||||
## Overview
|
## Prerequisites
|
||||||
|
|
||||||
This project serves as a demonstration and playground for exploring pico-1 capabilities.
|
- [Docker](https://docs.docker.com/get-docker/) and Docker Compose
|
||||||
|
|
||||||
## Getting Started
|
No local toolchain installation needed — everything runs inside the container.
|
||||||
|
|
||||||
Clone the repository and start experimenting:
|
## Building
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd demo1
|
# Build the Docker image (first time only, or after Dockerfile changes)
|
||||||
|
docker compose build
|
||||||
|
|
||||||
|
# Compile the firmware
|
||||||
|
docker compose up
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The `.uf2` firmware file will appear in `build/`.
|
||||||
|
|
||||||
|
## Flashing
|
||||||
|
|
||||||
|
1. Hold the **BOOTSEL** button on the Pico and plug it into USB
|
||||||
|
2. It mounts as a USB mass storage device
|
||||||
|
3. Drag the `.uf2` file from `build/` onto the Pico
|
||||||
|
4. The Pico reboots and runs the firmware
|
||||||
|
|
||||||
|
## Interactive Shell
|
||||||
|
|
||||||
|
To drop into the build container for debugging or manual commands:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose run --rm pico-build bash
|
||||||
|
```
|
||||||
25
build.sh
Executable file
25
build.sh
Executable file
@ -0,0 +1,25 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Build script for the Pico firmware.
|
||||||
|
# Creates an out-of-source build directory to keep generated files
|
||||||
|
# separate from the project source code.
|
||||||
|
#
|
||||||
|
# Directory layout expected at project root:
|
||||||
|
# cmake/ - all build-system files (CMakeLists.txt + cmake_config/)
|
||||||
|
# src/ - application source code
|
||||||
|
# build/ - created by this script, holds all CMake/Make output
|
||||||
|
#
|
||||||
|
# Using -S and -B lets us point CMake at the cmake/ source folder while
|
||||||
|
# keeping the build artifacts in a sibling build/ folder at the project root.
|
||||||
|
|
||||||
|
# Fail fast on any error so a broken configure step doesn't silently lead
|
||||||
|
# to a confusing make error further down.
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Configure the build: tell CMake the source directory is cmake/ and the
|
||||||
|
# binary (build) directory is build/. CMake will create build/ if needed.
|
||||||
|
cmake -S cmake -B build
|
||||||
|
|
||||||
|
# Compile everything using all available CPU cores. The final output is a
|
||||||
|
# .uf2 file in build/ that can be dragged onto the Pico's USB mass storage.
|
||||||
|
cmake --build build -j"$(nproc)"
|
||||||
65
cmake/CMakeLists.txt
Normal file
65
cmake/CMakeLists.txt
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
# ============================================================================
|
||||||
|
# Top-level CMakeLists.txt for the color_switcher Pico firmware project
|
||||||
|
# ============================================================================
|
||||||
|
# This file is intentionally kept thin: it orchestrates the build phases in
|
||||||
|
# the strict order CMake requires. All real configuration lives in the
|
||||||
|
# cmake_config/*.cmake fragments so each concern is isolated and easy to find.
|
||||||
|
#
|
||||||
|
# Build phase order (forced by the Pico SDK + CMake semantics):
|
||||||
|
# 1. project_config - variables only, no side effects
|
||||||
|
# 2. mcu_config - defines mcu_init / mcu_sdk_config / mcu_link_target
|
||||||
|
# 3. mcu_init() - SDK bootstrap (MUST run before project())
|
||||||
|
# 4. project() - declares the CMake project using project_config vars
|
||||||
|
# 5. mcu_sdk_config() - pico_sdk_init() (MUST run after project())
|
||||||
|
# 6. sources_config - collects PROJECT_SOURCES and PROJECT_INCLUDE_DIRS
|
||||||
|
# 7. add_executable() - the actual firmware build target
|
||||||
|
# 8. mcu_link_target() - target_link_libraries + stdio + UF2 output
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# Require CMake 3.13 or newer. The Pico SDK uses features (like
|
||||||
|
# target_link_libraries on object libraries) that were added in 3.13,
|
||||||
|
# so older versions will fail to configure with cryptic errors.
|
||||||
|
cmake_minimum_required(VERSION 3.13)
|
||||||
|
|
||||||
|
# The real project root is one level above this file (this CMakeLists.txt
|
||||||
|
# lives in <project>/cmake/). Everything outside the build system - the
|
||||||
|
# src/ tree, the Dockerfile, etc. - is referenced via PROJECT_ROOT_DIR so
|
||||||
|
# we never have to sprinkle "../" paths throughout the cmake fragments.
|
||||||
|
get_filename_component(PROJECT_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/.." ABSOLUTE)
|
||||||
|
|
||||||
|
# Make cmake_config/ searchable so include(foo) resolves to
|
||||||
|
# cmake_config/foo.cmake without needing the full path each time.
|
||||||
|
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake_config)
|
||||||
|
|
||||||
|
# Phase 1 - project-wide variables (PROJECT_NAME, PROJECT_VERSION, etc.)
|
||||||
|
include(project_config)
|
||||||
|
|
||||||
|
# Phase 2 - MCU helper macros/functions (no side effects, just definitions)
|
||||||
|
include(mcu_config)
|
||||||
|
|
||||||
|
# Phase 3 - Pico SDK bootstrap. MUST run before project() so the ARM
|
||||||
|
# cross-compile toolchain is configured before CMake enables languages.
|
||||||
|
mcu_init()
|
||||||
|
|
||||||
|
# Phase 4 - declare the CMake project using variables from project_config.
|
||||||
|
# This triggers CMake to detect the (already-configured) cross compiler.
|
||||||
|
project(${PROJECT_NAME}
|
||||||
|
VERSION ${PROJECT_VERSION}
|
||||||
|
LANGUAGES ${PROJECT_LANGUAGES})
|
||||||
|
|
||||||
|
# Phase 5 - register every Pico SDK library as a CMake target so we can
|
||||||
|
# pick which ones to link later. This does NOT pull libraries into the
|
||||||
|
# final binary - mcu_link_target() does that.
|
||||||
|
mcu_sdk_config()
|
||||||
|
|
||||||
|
# Phase 6 - collect the list of .c files and include directories into
|
||||||
|
# PROJECT_SOURCES and PROJECT_INCLUDE_DIRS variables.
|
||||||
|
include(sources_config)
|
||||||
|
|
||||||
|
# Phase 7 - declare the firmware executable and its include paths.
|
||||||
|
add_executable(${PROJECT_NAME} ${PROJECT_SOURCES})
|
||||||
|
target_include_directories(${PROJECT_NAME} PRIVATE ${PROJECT_INCLUDE_DIRS})
|
||||||
|
|
||||||
|
# Phase 8 - link only the SDK libraries we actually use (pico_stdlib,
|
||||||
|
# hardware_uart), route stdio over USB-CDC, and emit the .uf2 firmware file.
|
||||||
|
mcu_link_target(${PROJECT_NAME})
|
||||||
106
cmake/cmake_config/mcu_config.cmake
Normal file
106
cmake/cmake_config/mcu_config.cmake
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
# ============================================================================
|
||||||
|
# mcu_config.cmake
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Encapsulates all Raspberry Pi Pico / RP2040 specific configuration.
|
||||||
|
# Depends on project_config.cmake having been included first (reads
|
||||||
|
# PROJECT_C_STANDARD).
|
||||||
|
#
|
||||||
|
# Because CMake forces a strict order (SDK bootstrap before project(),
|
||||||
|
# pico_sdk_init() after project(), linking after add_executable), this file
|
||||||
|
# exposes three macros/functions that the top-level CMakeLists.txt calls
|
||||||
|
# at the correct points in the build flow:
|
||||||
|
#
|
||||||
|
# mcu_init() - includes pico_sdk_import.cmake (pre-project)
|
||||||
|
# mcu_sdk_config() - runs pico_sdk_init() (post-project)
|
||||||
|
# mcu_link_target(target) - links SDK libs, configures stdio, emits UF2
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Step 1/3 - called BEFORE project() in the top-level CMakeLists.txt.
|
||||||
|
# Pulls in the Pico SDK bootstrap file so the ARM cross-compile toolchain
|
||||||
|
# (arm-none-eabi-gcc) is configured before CMake enables the project
|
||||||
|
# languages. Calling project() before this runs would cause CMake to detect
|
||||||
|
# the host compiler instead and produce a broken build.
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
macro(mcu_init)
|
||||||
|
# pico_sdk_import.cmake lives alongside the top-level CMakeLists.txt
|
||||||
|
# inside the cmake/ folder, so CMAKE_SOURCE_DIR (which points at cmake/
|
||||||
|
# when that file is the one being processed) is the right base path.
|
||||||
|
include(${CMAKE_SOURCE_DIR}/pico_sdk_import.cmake)
|
||||||
|
endmacro()
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Step 2/3 - called AFTER project() in the top-level CMakeLists.txt.
|
||||||
|
# Applies the C standard chosen in project_config.cmake and registers every
|
||||||
|
# Pico SDK library as a CMake target (pico_stdlib, hardware_uart,
|
||||||
|
# hardware_gpio, ...) so we can later pick only the ones we need.
|
||||||
|
# Note: pico_sdk_init() does NOT pull any libraries into the final binary
|
||||||
|
# by itself - it only makes them available as link targets.
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
macro(mcu_sdk_config)
|
||||||
|
set(CMAKE_C_STANDARD ${PROJECT_C_STANDARD})
|
||||||
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
|
pico_sdk_init()
|
||||||
|
endmacro()
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Step 3/3 - called AFTER add_executable() in the top-level CMakeLists.txt.
|
||||||
|
# Links only the Pico SDK libraries we actually use, routes stdio over
|
||||||
|
# USB-CDC so printf output is visible on the host serial monitor without
|
||||||
|
# any extra hardware, and emits the .uf2 file used for drag-and-drop
|
||||||
|
# flashing onto the Pico's mass-storage bootloader.
|
||||||
|
#
|
||||||
|
# Parameters:
|
||||||
|
# target - name of the executable target created with add_executable()
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
function(mcu_link_target target)
|
||||||
|
# Pick only the libraries we need:
|
||||||
|
# - pico_stdlib: core runtime, GPIO, clocks, basic init
|
||||||
|
# - hardware_uart: UART peripheral API (used by the MCU_UART driver)
|
||||||
|
# - hardware_dma: DMA controller API (used by MCU_UART + MCU_PIO async)
|
||||||
|
# - hardware_pio: PIO state machine API (used by MCU_PIO for WS2812 etc.)
|
||||||
|
target_link_libraries(${target} PRIVATE
|
||||||
|
pico_stdlib
|
||||||
|
hardware_uart
|
||||||
|
hardware_dma
|
||||||
|
hardware_pio
|
||||||
|
)
|
||||||
|
|
||||||
|
# Generate C header from PIO assembly programs. The generated header
|
||||||
|
# provides the compiled program struct (ws2812_program) and the
|
||||||
|
# ws2812_program_init() helper used by MCU_PIO_cfg.c. The header is
|
||||||
|
# placed in the build directory and automatically added to the
|
||||||
|
# target's include path.
|
||||||
|
pico_generate_pio_header(${target}
|
||||||
|
${PROJECT_ROOT_DIR}/src/MCU_PIO/pio/ws2812.pio)
|
||||||
|
|
||||||
|
# Route stdio over USB-CDC: the Pico will appear as a virtual serial
|
||||||
|
# port on the host when plugged in, so printf/getchar are visible in
|
||||||
|
# any serial monitor without needing a USB-to-UART adapter.
|
||||||
|
# 1 = enabled, 0 = disabled.
|
||||||
|
pico_enable_stdio_usb(${target} 1)
|
||||||
|
pico_enable_stdio_uart(${target} 0)
|
||||||
|
|
||||||
|
# Ask the SDK to generate the .uf2 (plus .hex, .bin, .map) alongside
|
||||||
|
# the .elf so the firmware can be flashed by dragging it onto the
|
||||||
|
# Pico's USB mass-storage device after holding BOOTSEL.
|
||||||
|
pico_add_extra_outputs(${target})
|
||||||
|
|
||||||
|
# Custom "flash" target: builds the firmware first (DEPENDS ensures
|
||||||
|
# the .uf2 is up to date), then runs flash.sh on the host to copy
|
||||||
|
# it to the Pico in BOOTSEL mode.
|
||||||
|
#
|
||||||
|
# Usage: cmake --build build --target flash
|
||||||
|
#
|
||||||
|
# NOTE: This target only works when cmake runs on the HOST macOS
|
||||||
|
# (not inside Docker), because it needs access to /Volumes/RPI-RP2.
|
||||||
|
# When running inside Docker, the target will fail with a clear error
|
||||||
|
# from flash.sh ("not found" or "/Volumes/RPI-RP2 not accessible").
|
||||||
|
add_custom_target(flash
|
||||||
|
COMMAND bash ${PROJECT_ROOT_DIR}/flash.sh
|
||||||
|
DEPENDS ${target}
|
||||||
|
WORKING_DIRECTORY ${PROJECT_ROOT_DIR}
|
||||||
|
COMMENT "Flashing firmware to Pico via USB mass storage"
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
endfunction()
|
||||||
38
cmake/cmake_config/project_config.cmake
Normal file
38
cmake/cmake_config/project_config.cmake
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# ============================================================================
|
||||||
|
# project_config.cmake
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Project-wide identity and language settings. No side effects - this file
|
||||||
|
# only sets variables that later fragments (mcu_config, sources) and the
|
||||||
|
# top-level CMakeLists.txt read from.
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# Human-readable project name. Used as the CMake project name AND as the
|
||||||
|
# build target name, so the final firmware artifact will be named
|
||||||
|
# Color_Switcher_PICO.uf2 / .elf / .bin etc.
|
||||||
|
set(PROJECT_NAME Color_Switcher_PICO)
|
||||||
|
|
||||||
|
# Semantic version of the firmware. Bump manually on releases.
|
||||||
|
set(PROJECT_VERSION 0.1.0)
|
||||||
|
|
||||||
|
# Languages this project compiles:
|
||||||
|
# - C : our application source files
|
||||||
|
# - CXX : parts of the Pico SDK are C++ internally, so CMake must enable it
|
||||||
|
# even though we write no C++ of our own
|
||||||
|
# - ASM : RP2040 startup code, vector table, and boot2 are .S files that
|
||||||
|
# must be passed through the assembler
|
||||||
|
set(PROJECT_LANGUAGES C CXX ASM)
|
||||||
|
|
||||||
|
# C standard used across all targets (C11 gives us _Static_assert, stdint,
|
||||||
|
# stdbool, etc. without relying on compiler extensions).
|
||||||
|
set(PROJECT_C_STANDARD 11)
|
||||||
|
|
||||||
|
# Tell CMake to emit a compile_commands.json file inside the build directory
|
||||||
|
# whenever the project is configured. This file lists every source file and
|
||||||
|
# the exact compile command (including every -I include path and -D define)
|
||||||
|
# that CMake will use to build it. clangd (the language server used by VS
|
||||||
|
# Code for C/C++ intellisense) reads it to resolve #include directives and
|
||||||
|
# provide accurate hover types, go-to-definition, and diagnostics. Without
|
||||||
|
# this flag, clangd would guess blindly and fail to find Pico SDK headers
|
||||||
|
# under /opt/pico-sdk. This setting is project-wide and safe - it costs
|
||||||
|
# nothing at build time and is ignored by the compiler itself.
|
||||||
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
71
cmake/cmake_config/sources_config.cmake
Normal file
71
cmake/cmake_config/sources_config.cmake
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
# ============================================================================
|
||||||
|
# sources_config.cmake
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Collects the list of source files and include directories for the build.
|
||||||
|
# Does NOT declare the executable - the top-level CMakeLists.txt handles
|
||||||
|
# add_executable() so the build target is visible in one obvious place.
|
||||||
|
#
|
||||||
|
# Exported variables:
|
||||||
|
# PROJECT_SOURCES - list of every .c file under src/
|
||||||
|
# PROJECT_INCLUDE_DIRS - list of include paths for each component
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# NOTE: all source/header paths below are rooted at PROJECT_ROOT_DIR, which
|
||||||
|
# the top-level CMakeLists.txt computes as the parent of its own directory
|
||||||
|
# (i.e. one level above cmake/). We do NOT use CMAKE_SOURCE_DIR here because
|
||||||
|
# that variable points at the cmake/ folder itself, not at the project root.
|
||||||
|
|
||||||
|
# Recursively collect every .c file under src/. CONFIGURE_DEPENDS makes
|
||||||
|
# CMake re-check the glob on every build, so newly added .c files are
|
||||||
|
# picked up without having to manually re-run cmake. The trade-off is a
|
||||||
|
# tiny build-time stat check on each file, which is well worth it for a
|
||||||
|
# small project where we add files often.
|
||||||
|
file(GLOB_RECURSE PROJECT_SOURCES CONFIGURE_DEPENDS
|
||||||
|
"${PROJECT_ROOT_DIR}/src/*.c")
|
||||||
|
|
||||||
|
# Every component follows the same folder convention:
|
||||||
|
# inc/ - public API headers (safe for any other component to include)
|
||||||
|
# prg/ - private headers and implementation files (component-internal)
|
||||||
|
# cfg/ - configuration headers and constants
|
||||||
|
#
|
||||||
|
# All three are added to the include path here so #include "MCU_UART.h"
|
||||||
|
# etc. resolves regardless of which translation unit is doing the include.
|
||||||
|
set(PROJECT_INCLUDE_DIRS
|
||||||
|
# Shared library layer - fundamental types used by every component
|
||||||
|
${PROJECT_ROOT_DIR}/src/STD_TYPES/inc
|
||||||
|
|
||||||
|
# MCU layer - hardware abstraction for the RP2040 UART peripheral
|
||||||
|
${PROJECT_ROOT_DIR}/src/MCU_UART/inc
|
||||||
|
${PROJECT_ROOT_DIR}/src/MCU_UART/prg
|
||||||
|
${PROJECT_ROOT_DIR}/src/MCU_UART/cfg
|
||||||
|
|
||||||
|
# MCU layer - PIO peripheral driver for programmable I/O state machines
|
||||||
|
${PROJECT_ROOT_DIR}/src/MCU_PIO/inc
|
||||||
|
${PROJECT_ROOT_DIR}/src/MCU_PIO/prg
|
||||||
|
${PROJECT_ROOT_DIR}/src/MCU_PIO/cfg
|
||||||
|
|
||||||
|
# MCU layer - hardware abstraction for the RP2040 USB-CDC peripheral
|
||||||
|
# (the Pico appears as a virtual serial port on the host computer)
|
||||||
|
${PROJECT_ROOT_DIR}/src/MCU_USB/inc
|
||||||
|
${PROJECT_ROOT_DIR}/src/MCU_USB/prg
|
||||||
|
${PROJECT_ROOT_DIR}/src/MCU_USB/cfg
|
||||||
|
|
||||||
|
# HAL layer - transport-agnostic communication abstraction that
|
||||||
|
# dispatches to MCU_UART, MCU_USB, or both depending on configuration
|
||||||
|
${PROJECT_ROOT_DIR}/src/HAL_COM/inc
|
||||||
|
${PROJECT_ROOT_DIR}/src/HAL_COM/prg
|
||||||
|
${PROJECT_ROOT_DIR}/src/HAL_COM/cfg
|
||||||
|
|
||||||
|
# HAL layer - LED strip/pixel abstraction over PIO-based WS2812 driver
|
||||||
|
${PROJECT_ROOT_DIR}/src/HAL_LED/inc
|
||||||
|
${PROJECT_ROOT_DIR}/src/HAL_LED/prg
|
||||||
|
${PROJECT_ROOT_DIR}/src/HAL_LED/cfg
|
||||||
|
|
||||||
|
# Application layer - color switcher application logic
|
||||||
|
${PROJECT_ROOT_DIR}/src/APP_CLSW/inc
|
||||||
|
${PROJECT_ROOT_DIR}/src/APP_CLSW/prg
|
||||||
|
${PROJECT_ROOT_DIR}/src/APP_CLSW/cfg
|
||||||
|
|
||||||
|
# Application layer - system ECU / top-level orchestrator
|
||||||
|
${PROJECT_ROOT_DIR}/src/SYS_ECU/prg
|
||||||
|
)
|
||||||
121
cmake/pico_sdk_import.cmake
Normal file
121
cmake/pico_sdk_import.cmake
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake
|
||||||
|
|
||||||
|
# This can be dropped into an external project to help locate this SDK
|
||||||
|
# It should be include()ed prior to project()
|
||||||
|
|
||||||
|
# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
|
||||||
|
# following conditions are met:
|
||||||
|
#
|
||||||
|
# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
|
||||||
|
# disclaimer.
|
||||||
|
#
|
||||||
|
# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
|
||||||
|
# disclaimer in the documentation and/or other materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
|
||||||
|
# derived from this software without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||||
|
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||||
|
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||||
|
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
|
||||||
|
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
|
||||||
|
message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
|
||||||
|
set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
|
||||||
|
message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
|
||||||
|
set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
|
||||||
|
message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG))
|
||||||
|
set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG})
|
||||||
|
message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG)
|
||||||
|
set(PICO_SDK_FETCH_FROM_GIT_TAG "master")
|
||||||
|
message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
|
||||||
|
set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
|
||||||
|
set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
|
||||||
|
set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK")
|
||||||
|
|
||||||
|
if (NOT PICO_SDK_PATH)
|
||||||
|
if (PICO_SDK_FETCH_FROM_GIT)
|
||||||
|
include(FetchContent)
|
||||||
|
set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
|
||||||
|
if (PICO_SDK_FETCH_FROM_GIT_PATH)
|
||||||
|
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
|
||||||
|
endif ()
|
||||||
|
FetchContent_Declare(
|
||||||
|
pico_sdk
|
||||||
|
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
|
||||||
|
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (NOT pico_sdk)
|
||||||
|
message("Downloading Raspberry Pi Pico SDK")
|
||||||
|
# GIT_SUBMODULES_RECURSE was added in 3.17
|
||||||
|
if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
|
||||||
|
FetchContent_Populate(
|
||||||
|
pico_sdk
|
||||||
|
QUIET
|
||||||
|
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
|
||||||
|
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
|
||||||
|
GIT_SUBMODULES_RECURSE FALSE
|
||||||
|
|
||||||
|
SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
|
||||||
|
BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
|
||||||
|
SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
|
||||||
|
)
|
||||||
|
else ()
|
||||||
|
FetchContent_Populate(
|
||||||
|
pico_sdk
|
||||||
|
QUIET
|
||||||
|
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
|
||||||
|
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
|
||||||
|
|
||||||
|
SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
|
||||||
|
BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
|
||||||
|
SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
|
||||||
|
)
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
|
||||||
|
endif ()
|
||||||
|
set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
|
||||||
|
else ()
|
||||||
|
message(FATAL_ERROR
|
||||||
|
"SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
|
||||||
|
)
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
|
||||||
|
if (NOT EXISTS ${PICO_SDK_PATH})
|
||||||
|
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
|
||||||
|
if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
|
||||||
|
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
|
||||||
|
|
||||||
|
include(${PICO_SDK_INIT_CMAKE_FILE})
|
||||||
37
docker-compose.yml
Normal file
37
docker-compose.yml
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# ============================================================================
|
||||||
|
# Docker Compose configuration for the Raspberry Pi Pico firmware project
|
||||||
|
# ============================================================================
|
||||||
|
# The container is configured to be long-running (sleep infinity) rather than
|
||||||
|
# running the build on startup. This lets the same container be used for:
|
||||||
|
# 1. One-shot builds from the host
|
||||||
|
# 2. Interactive shells (docker compose exec) for debugging
|
||||||
|
# 3. VS Code Dev Containers - which expects the container to stay alive
|
||||||
|
# so it can attach clangd, install extensions, and open a terminal
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# docker compose build - (re)build the image after Dockerfile changes
|
||||||
|
# docker compose up -d - start the persistent container in the background
|
||||||
|
# docker compose exec pico-build bash - shell into the running container
|
||||||
|
# docker compose run --rm pico-build bash build.sh - one-shot firmware build, container removed after
|
||||||
|
# docker compose down - stop and remove the persistent container
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
services:
|
||||||
|
pico-build:
|
||||||
|
# Build the image from the Dockerfile in the project root
|
||||||
|
build: .
|
||||||
|
|
||||||
|
# Mount the project source code into the container's working directory.
|
||||||
|
# This lets the container read our source files and write build artifacts
|
||||||
|
# (including the .uf2 firmware file) back to the host filesystem.
|
||||||
|
volumes:
|
||||||
|
- .:/project
|
||||||
|
|
||||||
|
# Keep the container alive indefinitely. We intentionally do NOT run the
|
||||||
|
# build on startup - `sleep infinity` lets the container stay up so it can
|
||||||
|
# be used as a persistent dev environment (VS Code Dev Containers, shells
|
||||||
|
# via `docker compose exec`, etc.). To trigger a build, run:
|
||||||
|
# docker compose run --rm pico-build bash build.sh
|
||||||
|
# or, if you're already inside the container:
|
||||||
|
# bash build.sh
|
||||||
|
command: sleep infinity
|
||||||
46
flash.sh
Executable file
46
flash.sh
Executable file
@ -0,0 +1,46 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# flash.sh - Flash firmware to the Raspberry Pi Pico
|
||||||
|
# ============================================================================
|
||||||
|
# This script MUST run on the host macOS (not inside Docker) because it
|
||||||
|
# needs access to /Volumes/RPI-RP2, the USB mass storage mount point that
|
||||||
|
# appears when the Pico is held in BOOTSEL mode during power-on.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# 1. Hold BOOTSEL on the Pico and plug it into USB
|
||||||
|
# 2. Run: ./flash.sh
|
||||||
|
#
|
||||||
|
# Or chain with a build:
|
||||||
|
# docker compose run --rm pico-build bash build.sh && ./flash.sh
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
PICO_MOUNT="/Volumes/RPI-RP2"
|
||||||
|
UF2_FILE="build/Color_Switcher_PICO.uf2"
|
||||||
|
|
||||||
|
# Verify the firmware file exists before waiting for the Pico
|
||||||
|
if [ ! -f "$UF2_FILE" ]; then
|
||||||
|
echo "Error: $UF2_FILE not found. Run the build first:"
|
||||||
|
echo " docker compose run --rm pico-build bash build.sh"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Wait for the Pico to appear in BOOTSEL mode
|
||||||
|
echo "Waiting for Pico in BOOTSEL mode ($PICO_MOUNT)..."
|
||||||
|
echo " -> Hold BOOTSEL and plug in the Pico via USB"
|
||||||
|
while [ ! -d "$PICO_MOUNT" ]; do
|
||||||
|
sleep 0.5
|
||||||
|
done
|
||||||
|
|
||||||
|
# Copy the firmware to the Pico's USB mass storage
|
||||||
|
echo "Pico detected. Copying $UF2_FILE..."
|
||||||
|
cp "$UF2_FILE" "$PICO_MOUNT/"
|
||||||
|
|
||||||
|
# Wait for the Pico to unmount (it reboots automatically after receiving the .uf2)
|
||||||
|
echo "Waiting for Pico to reboot..."
|
||||||
|
while [ -d "$PICO_MOUNT" ]; do
|
||||||
|
sleep 0.5
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Done! Pico rebooted with new firmware."
|
||||||
|
echo " -> Open a serial monitor: screen /dev/tty.usbmodem* 115200"
|
||||||
13
src/APP_CLSW/cfg/APP_CLSW_cfg.c
Normal file
13
src/APP_CLSW/cfg/APP_CLSW_cfg.c
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* File: APP_CLSW_cfg.c
|
||||||
|
* Component: APP_CLSW
|
||||||
|
* Description: Configuration implementation for the APP_CLSW component.
|
||||||
|
* Holds the actual configuration values consumed by
|
||||||
|
* APP_CLSW_prg.c.
|
||||||
|
*
|
||||||
|
* Layer: Application - configuration
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
#include "APP_CLSW_cfg.h"
|
||||||
|
|
||||||
|
/* Configuration definitions will go here */
|
||||||
31
src/APP_CLSW/cfg/APP_CLSW_cfg.h
Normal file
31
src/APP_CLSW/cfg/APP_CLSW_cfg.h
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* File: APP_CLSW_cfg.h
|
||||||
|
* Component: APP_CLSW
|
||||||
|
* Description: Configuration header for the color switcher application.
|
||||||
|
* Defines command buffer sizing, the HAL_COM channel used
|
||||||
|
* for host communication, and the command delimiter.
|
||||||
|
*
|
||||||
|
* Layer: Application - configuration
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
#ifndef APP_CLSW_CFG_H
|
||||||
|
#define APP_CLSW_CFG_H
|
||||||
|
|
||||||
|
#include "STD_TYPES.h"
|
||||||
|
|
||||||
|
/** @brief Maximum length of a single command string (excluding delimiter).
|
||||||
|
* Commands longer than this are truncated. */
|
||||||
|
#define APP_CLSW_CMD_BUFFER_SIZE 32U
|
||||||
|
|
||||||
|
/** @brief HAL_COM channel used for host communication. */
|
||||||
|
#define APP_CLSW_COM_CHANNEL ((u8)HAL_COM_CHANNEL_0)
|
||||||
|
|
||||||
|
/** @brief HAL_LED instance for the onboard LED.
|
||||||
|
* Maps to HAL_LED_INSTANCE_ONBOARD (0). */
|
||||||
|
#define APP_CLSW_LED_INSTANCE 0U
|
||||||
|
|
||||||
|
/** @brief Default LED brightness (0-255). 128 = half brightness to avoid
|
||||||
|
* blinding at close range and reduce current draw. */
|
||||||
|
#define APP_CLSW_LED_INTENSITY 128U
|
||||||
|
|
||||||
|
#endif /* APP_CLSW_CFG_H */
|
||||||
46
src/APP_CLSW/inc/APP_CLSW.h
Normal file
46
src/APP_CLSW/inc/APP_CLSW.h
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* File: APP_CLSW.h
|
||||||
|
* Component: APP_CLSW
|
||||||
|
* Description: Public interface for the Application Color Switcher component.
|
||||||
|
* Exposes the functions that the system orchestrator (SYS_ECU)
|
||||||
|
* calls to run the color-switching application logic. This
|
||||||
|
* component owns the "what to send" - it builds commands and
|
||||||
|
* passes them down through the communication stack (HAL_COM)
|
||||||
|
* to reach the host computer.
|
||||||
|
*
|
||||||
|
* Layer: Application
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
#ifndef APP_CLSW_H
|
||||||
|
#define APP_CLSW_H
|
||||||
|
|
||||||
|
/* STD_TYPES provides fixed-width typedefs (u8, u32) and the STD_tenuResult
|
||||||
|
* enum used to report success/failure from the init function. */
|
||||||
|
#include "STD_TYPES.h"
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* PUBLIC API */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize the color switcher application.
|
||||||
|
*
|
||||||
|
* Sets up all lower-layer dependencies needed by the application (currently
|
||||||
|
* MCU_USB for USB-CDC communication). Must be called exactly once from
|
||||||
|
* SYS_ECU before entering the main loop.
|
||||||
|
*
|
||||||
|
* @return STD_OK on success (all subsystems initialized),
|
||||||
|
* STD_NOK if any subsystem initialization fails.
|
||||||
|
*/
|
||||||
|
STD_tenuResult APP_CLSW_enuInit(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Execute one iteration of the color switcher application logic.
|
||||||
|
*
|
||||||
|
* Called once per super-loop tick from SYS_ECU. Performs whatever the
|
||||||
|
* application needs to do each cycle - for now, sends a test message
|
||||||
|
* over USB-CDC to prove the communication stack is alive.
|
||||||
|
*/
|
||||||
|
void APP_CLSW_vRunnable(void);
|
||||||
|
|
||||||
|
#endif /* APP_CLSW_H */
|
||||||
256
src/APP_CLSW/prg/APP_CLSW_prg.c
Normal file
256
src/APP_CLSW/prg/APP_CLSW_prg.c
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* File: APP_CLSW_prg.c
|
||||||
|
* Component: APP_CLSW
|
||||||
|
* Description: Color switcher application logic. Receives commands from the
|
||||||
|
* host via HAL_COM, parses them, and drives the onboard LED
|
||||||
|
* via HAL_LED.
|
||||||
|
*
|
||||||
|
* On startup, runs a rainbow HSV hue rotation (auto-mode).
|
||||||
|
* When a color command is received ("red", "green", "blue"),
|
||||||
|
* the rainbow stops and the LED is set to the requested color.
|
||||||
|
*
|
||||||
|
* Layer: Application
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
#include "APP_CLSW.h"
|
||||||
|
#include "APP_CLSW_priv.h"
|
||||||
|
#include "APP_CLSW_cfg.h"
|
||||||
|
|
||||||
|
#include "HAL_COM.h"
|
||||||
|
#include "HAL_LED.h"
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* INTERNAL STATE */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
static APP_CLSW_tstrState strState;
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* INTERNAL HELPERS */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Compare two null-terminated byte arrays.
|
||||||
|
* @return STD_TRUE if equal, STD_FALSE if different.
|
||||||
|
*/
|
||||||
|
static STD_tBool bStrEqual(const u8 *pu8A, const u8 *pu8B)
|
||||||
|
{
|
||||||
|
STD_tBool bResultLoc = STD_TRUE;
|
||||||
|
u8 u8IndexLoc = 0U;
|
||||||
|
|
||||||
|
while ((pu8A[u8IndexLoc] != 0U) || (pu8B[u8IndexLoc] != 0U))
|
||||||
|
{
|
||||||
|
if (pu8A[u8IndexLoc] != pu8B[u8IndexLoc])
|
||||||
|
{
|
||||||
|
bResultLoc = STD_FALSE;
|
||||||
|
}
|
||||||
|
u8IndexLoc++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bResultLoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Send a null-terminated string via HAL_COM on the configured channel.
|
||||||
|
*/
|
||||||
|
static void vSendString(const u8 *pu8Str)
|
||||||
|
{
|
||||||
|
u16 u16LenLoc = 0U;
|
||||||
|
|
||||||
|
while (pu8Str[u16LenLoc] != 0U)
|
||||||
|
{
|
||||||
|
u16LenLoc++;
|
||||||
|
}
|
||||||
|
|
||||||
|
HAL_COM_enuSendBuffer(APP_CLSW_COM_CHANNEL, pu8Str, u16LenLoc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Convert HSV color to RGB using integer-only math.
|
||||||
|
*
|
||||||
|
* Standard 6-sector HSV-to-RGB algorithm. All arithmetic uses u16/u32
|
||||||
|
* intermediates to avoid overflow — no floating point.
|
||||||
|
*
|
||||||
|
* @param u16Hue Hue angle in degrees (0-359).
|
||||||
|
* @param u8Sat Saturation (0-255). 255 = fully saturated.
|
||||||
|
* @param u8Val Value / brightness (0-255). 255 = full brightness.
|
||||||
|
* @param pu8R Output: red channel (0-255).
|
||||||
|
* @param pu8G Output: green channel (0-255).
|
||||||
|
* @param pu8B Output: blue channel (0-255).
|
||||||
|
*/
|
||||||
|
static void vHsvToRgb(u16 u16Hue, u8 u8Sat, u8 u8Val,
|
||||||
|
u8 *pu8R, u8 *pu8G, u8 *pu8B)
|
||||||
|
{
|
||||||
|
u8 u8SectorLoc;
|
||||||
|
u16 u16RemainderLoc;
|
||||||
|
u8 u8PLoc; /* chroma minimum */
|
||||||
|
u8 u8QLoc; /* descending transition */
|
||||||
|
u8 u8TLoc; /* ascending transition */
|
||||||
|
|
||||||
|
/* p = V * (255 - S) / 255 */
|
||||||
|
u8PLoc = (u8)(((u16)u8Val * (255U - (u16)u8Sat)) / 255U);
|
||||||
|
|
||||||
|
/* Which 60-degree sector of the hue wheel (0-5) */
|
||||||
|
u8SectorLoc = (u8)(u16Hue / 60U);
|
||||||
|
|
||||||
|
/* Remainder within the sector, scaled to 0-255 range.
|
||||||
|
* (hue % 60) * 255 / 60 gives a 0-255 ramp within the sector. */
|
||||||
|
u16RemainderLoc = (u16)(((u32)(u16Hue % 60U) * 255U) / 60U);
|
||||||
|
|
||||||
|
/* q = V * (255 - S * remainder / 255) / 255 — descending edge */
|
||||||
|
u8QLoc = (u8)(((u16)u8Val * (255U - ((u16)u8Sat * u16RemainderLoc / 255U))) / 255U);
|
||||||
|
|
||||||
|
/* t = V * (255 - S * (255 - remainder) / 255) / 255 — ascending edge */
|
||||||
|
u8TLoc = (u8)(((u16)u8Val * (255U - ((u16)u8Sat * (255U - u16RemainderLoc) / 255U))) / 255U);
|
||||||
|
|
||||||
|
/* Map sector to RGB channels */
|
||||||
|
if (u8SectorLoc == 0U)
|
||||||
|
{
|
||||||
|
*pu8R = u8Val; *pu8G = u8TLoc; *pu8B = u8PLoc;
|
||||||
|
}
|
||||||
|
else if (u8SectorLoc == 1U)
|
||||||
|
{
|
||||||
|
*pu8R = u8QLoc; *pu8G = u8Val; *pu8B = u8PLoc;
|
||||||
|
}
|
||||||
|
else if (u8SectorLoc == 2U)
|
||||||
|
{
|
||||||
|
*pu8R = u8PLoc; *pu8G = u8Val; *pu8B = u8TLoc;
|
||||||
|
}
|
||||||
|
else if (u8SectorLoc == 3U)
|
||||||
|
{
|
||||||
|
*pu8R = u8PLoc; *pu8G = u8QLoc; *pu8B = u8Val;
|
||||||
|
}
|
||||||
|
else if (u8SectorLoc == 4U)
|
||||||
|
{
|
||||||
|
*pu8R = u8TLoc; *pu8G = u8PLoc; *pu8B = u8Val;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
*pu8R = u8Val; *pu8G = u8PLoc; *pu8B = u8QLoc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Process a complete command string.
|
||||||
|
* Called when a delimiter (\r or \n) is received.
|
||||||
|
*/
|
||||||
|
static void vProcessCommand(void)
|
||||||
|
{
|
||||||
|
strState.au8CmdBuffer[strState.u8CmdIndex] = 0U;
|
||||||
|
|
||||||
|
if (strState.u8CmdIndex == 0U)
|
||||||
|
{
|
||||||
|
/* Empty command — do nothing */
|
||||||
|
}
|
||||||
|
else if (bStrEqual(strState.au8CmdBuffer, (const u8 *)"red") == STD_TRUE)
|
||||||
|
{
|
||||||
|
strState.bAutoMode = STD_FALSE;
|
||||||
|
HAL_LED_enuSetColor(APP_CLSW_LED_INSTANCE, 0U, 255U, 0U, 0U, APP_CLSW_LED_INTENSITY);
|
||||||
|
vSendString((const u8 *)"Color set to: RED\r\n");
|
||||||
|
}
|
||||||
|
else if (bStrEqual(strState.au8CmdBuffer, (const u8 *)"green") == STD_TRUE)
|
||||||
|
{
|
||||||
|
strState.bAutoMode = STD_FALSE;
|
||||||
|
HAL_LED_enuSetColor(APP_CLSW_LED_INSTANCE, 0U, 0U, 255U, 0U, APP_CLSW_LED_INTENSITY);
|
||||||
|
vSendString((const u8 *)"Color set to: GREEN\r\n");
|
||||||
|
}
|
||||||
|
else if (bStrEqual(strState.au8CmdBuffer, (const u8 *)"blue") == STD_TRUE)
|
||||||
|
{
|
||||||
|
strState.bAutoMode = STD_FALSE;
|
||||||
|
HAL_LED_enuSetColor(APP_CLSW_LED_INSTANCE, 0U, 0U, 0U, 255U, APP_CLSW_LED_INTENSITY);
|
||||||
|
vSendString((const u8 *)"Color set to: BLUE\r\n");
|
||||||
|
}
|
||||||
|
else if (bStrEqual(strState.au8CmdBuffer, (const u8 *)"rainbow") == STD_TRUE)
|
||||||
|
{
|
||||||
|
strState.bAutoMode = STD_TRUE;
|
||||||
|
vSendString((const u8 *)"Rainbow mode enabled\r\n");
|
||||||
|
}
|
||||||
|
else if (bStrEqual(strState.au8CmdBuffer, (const u8 *)"off") == STD_TRUE)
|
||||||
|
{
|
||||||
|
strState.bAutoMode = STD_FALSE;
|
||||||
|
HAL_LED_enuSetColor(APP_CLSW_LED_INSTANCE, 0U, 0U, 0U, 0U, 0U);
|
||||||
|
vSendString((const u8 *)"LED off\r\n");
|
||||||
|
}
|
||||||
|
else if (bStrEqual(strState.au8CmdBuffer, (const u8 *)"help") == STD_TRUE)
|
||||||
|
{
|
||||||
|
vSendString((const u8 *)"Commands: red, green, blue, rainbow, off, help\r\n");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
vSendString((const u8 *)"Unknown command: ");
|
||||||
|
vSendString(strState.au8CmdBuffer);
|
||||||
|
vSendString((const u8 *)"\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
strState.u8CmdIndex = 0U;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================================================================= */
|
||||||
|
/* INIT */
|
||||||
|
/* ========================================================================= */
|
||||||
|
|
||||||
|
STD_tenuResult APP_CLSW_enuInit(void)
|
||||||
|
{
|
||||||
|
STD_tenuResult enuResultLoc = STD_OK;
|
||||||
|
|
||||||
|
strState.u8CmdIndex = 0U;
|
||||||
|
strState.u16Hue = 0U;
|
||||||
|
strState.bAutoMode = STD_TRUE;
|
||||||
|
|
||||||
|
return enuResultLoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================================================================= */
|
||||||
|
/* RUNNABLE */
|
||||||
|
/* ========================================================================= */
|
||||||
|
|
||||||
|
void APP_CLSW_vRunnable(void)
|
||||||
|
{
|
||||||
|
STD_tBool bDataAvailableLoc = STD_FALSE;
|
||||||
|
u8 u8ByteLoc = 0U;
|
||||||
|
STD_tenuResult enuRxResultLoc = STD_OK;
|
||||||
|
|
||||||
|
/* --- Process incoming commands --- */
|
||||||
|
bDataAvailableLoc = HAL_COM_bIsRxDataAvailable(APP_CLSW_COM_CHANNEL);
|
||||||
|
|
||||||
|
while (bDataAvailableLoc == STD_TRUE)
|
||||||
|
{
|
||||||
|
enuRxResultLoc = HAL_COM_enuReadByte(APP_CLSW_COM_CHANNEL, &u8ByteLoc);
|
||||||
|
|
||||||
|
if (enuRxResultLoc == STD_OK)
|
||||||
|
{
|
||||||
|
HAL_COM_enuSendByte(APP_CLSW_COM_CHANNEL, u8ByteLoc);
|
||||||
|
|
||||||
|
if ((u8ByteLoc == (u8)'\r') || (u8ByteLoc == (u8)'\n'))
|
||||||
|
{
|
||||||
|
vSendString((const u8 *)"\r\n");
|
||||||
|
vProcessCommand();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (strState.u8CmdIndex < (APP_CLSW_CMD_BUFFER_SIZE - 1U))
|
||||||
|
{
|
||||||
|
strState.au8CmdBuffer[strState.u8CmdIndex] = u8ByteLoc;
|
||||||
|
strState.u8CmdIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bDataAvailableLoc = HAL_COM_bIsRxDataAvailable(APP_CLSW_COM_CHANNEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Rainbow auto-mode: cycle hue each tick --- */
|
||||||
|
if (strState.bAutoMode == STD_TRUE)
|
||||||
|
{
|
||||||
|
u8 u8RLoc = 0U;
|
||||||
|
u8 u8GLoc = 0U;
|
||||||
|
u8 u8BLoc = 0U;
|
||||||
|
|
||||||
|
vHsvToRgb(strState.u16Hue, 255U, 255U, &u8RLoc, &u8GLoc, &u8BLoc);
|
||||||
|
|
||||||
|
HAL_LED_enuSetColor(APP_CLSW_LED_INSTANCE, 0U,
|
||||||
|
u8RLoc, u8GLoc, u8BLoc, APP_CLSW_LED_INTENSITY);
|
||||||
|
|
||||||
|
strState.u16Hue = (strState.u16Hue + 1U) % 360U;
|
||||||
|
}
|
||||||
|
}
|
||||||
36
src/APP_CLSW/prg/APP_CLSW_priv.h
Normal file
36
src/APP_CLSW/prg/APP_CLSW_priv.h
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* File: APP_CLSW_priv.h
|
||||||
|
* Component: APP_CLSW
|
||||||
|
* Description: Private header for the color switcher application.
|
||||||
|
* Contains the internal command buffer state used to accumulate
|
||||||
|
* incoming bytes until a complete command is received.
|
||||||
|
*
|
||||||
|
* Layer: Application - internal use only
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
#ifndef APP_CLSW_PRIV_H
|
||||||
|
#define APP_CLSW_PRIV_H
|
||||||
|
|
||||||
|
#include "APP_CLSW.h"
|
||||||
|
#include "APP_CLSW_cfg.h"
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* COMMAND BUFFER STATE */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Internal state for command accumulation.
|
||||||
|
*
|
||||||
|
* As bytes arrive from the host, they are stored in au8CmdBuffer until
|
||||||
|
* a delimiter (\r or \n) is received. At that point the buffer is
|
||||||
|
* null-terminated, parsed, and the index is reset for the next command.
|
||||||
|
*/
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
u8 au8CmdBuffer[APP_CLSW_CMD_BUFFER_SIZE]; /**< Accumulated command bytes */
|
||||||
|
u8 u8CmdIndex; /**< Next write position */
|
||||||
|
u16 u16Hue; /**< Current hue for rainbow (0-359) */
|
||||||
|
STD_tBool bAutoMode; /**< STD_TRUE = rainbow, STD_FALSE = manual color */
|
||||||
|
} APP_CLSW_tstrState;
|
||||||
|
|
||||||
|
#endif /* APP_CLSW_PRIV_H */
|
||||||
106
src/HAL_COM/cfg/HAL_COM_cfg.c
Normal file
106
src/HAL_COM/cfg/HAL_COM_cfg.c
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* File: HAL_COM_cfg.c
|
||||||
|
* Component: HAL_COM
|
||||||
|
* Description: Configuration implementation for the HAL_COM abstraction.
|
||||||
|
* Defines the channel config array that wires each HAL_COM
|
||||||
|
* channel to a specific MCU-level transport driver via function
|
||||||
|
* pointers. Also contains thin wrapper functions to normalize
|
||||||
|
* driver signatures that don't match the generic prototype
|
||||||
|
* (e.g., MCU_USB which has no instance parameter).
|
||||||
|
*
|
||||||
|
* Layer: HAL - configuration
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
#include "HAL_COM.h"
|
||||||
|
#include "HAL_COM_cfg.h"
|
||||||
|
|
||||||
|
/* MCU drivers that channels may be wired to */
|
||||||
|
#include "MCU_USB.h"
|
||||||
|
#include "MCU_UART.h"
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* SIGNATURE NORMALIZATION WRAPPERS */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Wrapper for MCU_USB_enuSendByte to match HAL_COM_tpfSendByte.
|
||||||
|
*
|
||||||
|
* MCU_USB has only one instance (the RP2040's single USB peripheral),
|
||||||
|
* so its public API does not take a u8Instance parameter. This wrapper
|
||||||
|
* adds the parameter and ignores it, making the signature compatible
|
||||||
|
* with HAL_COM's generic function pointer type.
|
||||||
|
*/
|
||||||
|
static STD_tenuResult vUsbSendByte(u8 u8Instance, u8 u8Byte)
|
||||||
|
{
|
||||||
|
(void)u8Instance;
|
||||||
|
return MCU_USB_enuSendByte(u8Byte);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Wrapper for MCU_USB_enuSendBuffer to match HAL_COM_tpfSendBuffer.
|
||||||
|
*
|
||||||
|
* Same rationale as vUsbSendByte — normalizes the missing instance
|
||||||
|
* parameter so USB can be plugged into a HAL_COM channel.
|
||||||
|
*/
|
||||||
|
static STD_tenuResult vUsbSendBuffer(u8 u8Instance, const u8 *pu8Data, u16 u16Length)
|
||||||
|
{
|
||||||
|
(void)u8Instance;
|
||||||
|
return MCU_USB_enuSendBuffer(pu8Data, u16Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* MCU_UART already matches the HAL_COM function pointer signatures
|
||||||
|
* (u8 u8Instance as the first parameter), so no wrapper is needed
|
||||||
|
* for TX or RX. Its functions can be assigned directly. */
|
||||||
|
|
||||||
|
/* --- USB RX wrappers (normalize missing instance parameter) --- */
|
||||||
|
|
||||||
|
static STD_tenuResult vUsbReadByte(u8 u8Instance, u8 *pu8Byte)
|
||||||
|
{
|
||||||
|
(void)u8Instance;
|
||||||
|
return MCU_USB_enuReadByte(pu8Byte);
|
||||||
|
}
|
||||||
|
|
||||||
|
static STD_tenuResult vUsbReadBuffer(u8 u8Instance, u8 *pu8Data, u16 u16MaxLength, u16 *pu16Read)
|
||||||
|
{
|
||||||
|
(void)u8Instance;
|
||||||
|
return MCU_USB_enuReadBuffer(pu8Data, u16MaxLength, pu16Read);
|
||||||
|
}
|
||||||
|
|
||||||
|
static STD_tBool vUsbIsRxDataAvailable(u8 u8Instance)
|
||||||
|
{
|
||||||
|
(void)u8Instance;
|
||||||
|
return MCU_USB_bIsRxDataAvailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* CHANNEL CONFIGURATION ARRAY */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Per-channel config, indexed by HAL_COM_tenuChannel.
|
||||||
|
*
|
||||||
|
* [HAL_COM_CHANNEL_0] = USB-CDC (primary communication path).
|
||||||
|
*
|
||||||
|
* To add a UART channel:
|
||||||
|
* 1. Add HAL_COM_CHANNEL_1 to the enum in HAL_COM_cfg.h
|
||||||
|
* 2. Add an entry here with MCU_UART functions (no wrapper needed):
|
||||||
|
* [HAL_COM_CHANNEL_1] = {
|
||||||
|
* .pfSendByte = MCU_UART_enuSendByte,
|
||||||
|
* .pfSendBuffer = MCU_UART_enuSendBuffer,
|
||||||
|
* .pfReceiveByte = MCU_UART_enuReceiveByte,
|
||||||
|
* .pfIsRxDataAvailable = MCU_UART_bIsRxDataAvailable,
|
||||||
|
* .u8Instance = 0U,
|
||||||
|
* },
|
||||||
|
*/
|
||||||
|
const HAL_COM_tstrChannelConfig HAL_COM_astrChannelConfig[HAL_COM_NUM_CHANNELS] =
|
||||||
|
{
|
||||||
|
[HAL_COM_CHANNEL_0] =
|
||||||
|
{
|
||||||
|
.pfSendByte = vUsbSendByte,
|
||||||
|
.pfSendBuffer = vUsbSendBuffer,
|
||||||
|
.pfReadByte = vUsbReadByte,
|
||||||
|
.pfReadBuffer = vUsbReadBuffer,
|
||||||
|
.pfIsRxDataAvailable = vUsbIsRxDataAvailable,
|
||||||
|
.u8Instance = 0U,
|
||||||
|
},
|
||||||
|
};
|
||||||
39
src/HAL_COM/cfg/HAL_COM_cfg.h
Normal file
39
src/HAL_COM/cfg/HAL_COM_cfg.h
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* File: HAL_COM_cfg.h
|
||||||
|
* Component: HAL_COM
|
||||||
|
* Description: Configuration header for the HAL_COM abstraction.
|
||||||
|
* Defines the communication channels and which MCU-level
|
||||||
|
* transport each one routes through via function pointers.
|
||||||
|
* Adding a new channel or swapping a transport is a config-only
|
||||||
|
* change — no code in HAL_COM_prg.c needs to be modified.
|
||||||
|
*
|
||||||
|
* Layer: HAL - configuration
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
#ifndef HAL_COM_CFG_H
|
||||||
|
#define HAL_COM_CFG_H
|
||||||
|
|
||||||
|
#include "STD_TYPES.h"
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* CHANNEL ENUMERATION */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Enumeration of configured HAL_COM channels.
|
||||||
|
*
|
||||||
|
* Each channel is one logical communication path wired to a specific
|
||||||
|
* MCU-level driver. The enumerator values are used as array indices
|
||||||
|
* into HAL_COM_astrChannelConfig[].
|
||||||
|
*
|
||||||
|
* To add a channel: add an enumerator before HAL_COM_NUM_CHANNELS,
|
||||||
|
* define its function pointer macros below, and add an entry in
|
||||||
|
* HAL_COM_cfg.c.
|
||||||
|
*/
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
HAL_COM_CHANNEL_0 = 0U, /**< Primary channel (USB-CDC by default) */
|
||||||
|
HAL_COM_NUM_CHANNELS
|
||||||
|
} HAL_COM_tenuChannel;
|
||||||
|
|
||||||
|
#endif /* HAL_COM_CFG_H */
|
||||||
151
src/HAL_COM/inc/HAL_COM.h
Normal file
151
src/HAL_COM/inc/HAL_COM.h
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* File: HAL_COM.h
|
||||||
|
* Component: HAL_COM
|
||||||
|
* Description: Public interface for the HAL communication abstraction layer.
|
||||||
|
* Provides a transport-agnostic, multi-channel API for sending
|
||||||
|
* and receiving bytes. Each channel is independently configured
|
||||||
|
* with function pointers to an MCU-level driver (USB, UART,
|
||||||
|
* SPI, I2C, or any future transport that matches the function
|
||||||
|
* signatures).
|
||||||
|
*
|
||||||
|
* Higher layers (e.g. APP_CLSW) call HAL_COM_* with a channel
|
||||||
|
* number and stay unaware of which physical transport is in use.
|
||||||
|
* Adding a new transport requires zero changes to this file or
|
||||||
|
* to HAL_COM_prg.c — only the config needs a new channel entry.
|
||||||
|
*
|
||||||
|
* Layer: HAL (hardware abstraction, one level above MCU drivers)
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
#ifndef HAL_COM_H
|
||||||
|
#define HAL_COM_H
|
||||||
|
|
||||||
|
#include "STD_TYPES.h"
|
||||||
|
#include "HAL_COM_cfg.h"
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* TRANSPORT FUNCTION POINTER TYPES */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generic send-byte function pointer type.
|
||||||
|
*
|
||||||
|
* Any MCU driver that can send a single byte and matches this signature
|
||||||
|
* can be plugged into a HAL_COM channel. The u8Instance parameter
|
||||||
|
* identifies the peripheral instance within that driver (e.g., UART0
|
||||||
|
* vs UART1). Drivers that have only one instance (e.g., MCU_USB) use
|
||||||
|
* a thin wrapper that ignores the parameter.
|
||||||
|
*
|
||||||
|
* @param u8Instance Peripheral instance index within the driver.
|
||||||
|
* @param u8Byte The byte to transmit.
|
||||||
|
* @return STD_tenuResult
|
||||||
|
*/
|
||||||
|
typedef STD_tenuResult (*HAL_COM_tpfSendByte)(u8 u8Instance, u8 u8Byte);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generic send-buffer function pointer type.
|
||||||
|
*
|
||||||
|
* Same concept as HAL_COM_tpfSendByte but for multi-byte transfers.
|
||||||
|
*
|
||||||
|
* @param u8Instance Peripheral instance index within the driver.
|
||||||
|
* @param pu8Data Pointer to the byte buffer.
|
||||||
|
* @param u16Length Number of bytes to transmit.
|
||||||
|
* @return STD_tenuResult
|
||||||
|
*/
|
||||||
|
typedef STD_tenuResult (*HAL_COM_tpfSendBuffer)(u8 u8Instance, const u8 *pu8Data, u16 u16Length);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generic read-byte function pointer type (non-blocking).
|
||||||
|
*/
|
||||||
|
typedef STD_tenuResult (*HAL_COM_tpfReadByte)(u8 u8Instance, u8 *pu8Byte);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generic read-buffer function pointer type (non-blocking).
|
||||||
|
*/
|
||||||
|
typedef STD_tenuResult (*HAL_COM_tpfReadBuffer)(u8 u8Instance, u8 *pu8Data, u16 u16MaxLength, u16 *pu16Read);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generic RX-data-available check function pointer type.
|
||||||
|
*/
|
||||||
|
typedef STD_tBool (*HAL_COM_tpfIsRxDataAvailable)(u8 u8Instance);
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* CONFIGURATION STRUCTURE */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Per-channel communication configuration.
|
||||||
|
*
|
||||||
|
* One entry per HAL_COM channel, stored in HAL_COM_astrChannelConfig[].
|
||||||
|
* The array index is the channel number passed to all public functions.
|
||||||
|
*
|
||||||
|
* Each channel is wired to exactly one MCU-level transport by holding
|
||||||
|
* function pointers to that driver's send functions + the instance index
|
||||||
|
* to pass through. To route through a different transport, just change
|
||||||
|
* the function pointers in the config — no code changes needed.
|
||||||
|
*/
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
HAL_COM_tpfSendByte pfSendByte; /**< Driver's send-byte function */
|
||||||
|
HAL_COM_tpfSendBuffer pfSendBuffer; /**< Driver's send-buffer function */
|
||||||
|
HAL_COM_tpfReadByte pfReadByte; /**< Driver's read-byte function */
|
||||||
|
HAL_COM_tpfReadBuffer pfReadBuffer; /**< Driver's read-buffer function */
|
||||||
|
HAL_COM_tpfIsRxDataAvailable pfIsRxDataAvailable; /**< Driver's RX-available check */
|
||||||
|
u8 u8Instance; /**< Peripheral instance to pass through */
|
||||||
|
} HAL_COM_tstrChannelConfig;
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* PUBLIC API */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize the HAL communication layer's own internal state.
|
||||||
|
*
|
||||||
|
* Does NOT initialize the underlying MCU drivers — SYS_ECU owns the init
|
||||||
|
* sequence and calls each driver's enuInit() before calling this.
|
||||||
|
*
|
||||||
|
* @return STD_OK on success, STD_NOK on failure.
|
||||||
|
*/
|
||||||
|
STD_tenuResult HAL_COM_enuInit(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Send a single byte through the specified channel.
|
||||||
|
*
|
||||||
|
* @param u8Channel Channel index (from HAL_COM_cfg.h channel enum).
|
||||||
|
* @param u8Byte The byte to transmit.
|
||||||
|
* @return STD_OK on success, STD_NOK on failure.
|
||||||
|
*/
|
||||||
|
STD_tenuResult HAL_COM_enuSendByte(u8 u8Channel, u8 u8Byte);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Send a buffer through the specified channel.
|
||||||
|
*
|
||||||
|
* The underlying driver may be blocking or non-blocking depending on the
|
||||||
|
* MCU driver wired to this channel. When non-blocking (e.g., MCU_UART
|
||||||
|
* with DMA), the caller must keep the buffer valid until the transfer
|
||||||
|
* completes.
|
||||||
|
*
|
||||||
|
* @param u8Channel Channel index.
|
||||||
|
* @param pu8Data Pointer to the byte buffer. Must not be NULL.
|
||||||
|
* @param u16Length Number of bytes to transmit.
|
||||||
|
* @return STD_OK on success,
|
||||||
|
* STD_NULL_POINTER_ERROR if pu8Data is NULL,
|
||||||
|
* STD_NOK on failure.
|
||||||
|
*/
|
||||||
|
STD_tenuResult HAL_COM_enuSendBuffer(u8 u8Channel, const u8 *pu8Data, u16 u16Length);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read one byte from the specified channel (non-blocking).
|
||||||
|
*/
|
||||||
|
STD_tenuResult HAL_COM_enuReadByte(u8 u8Channel, u8 *pu8Byte);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read up to u16MaxLength bytes from the specified channel (non-blocking).
|
||||||
|
*/
|
||||||
|
STD_tenuResult HAL_COM_enuReadBuffer(u8 u8Channel, u8 *pu8Data, u16 u16MaxLength, u16 *pu16Read);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if the specified channel has RX data available.
|
||||||
|
*/
|
||||||
|
STD_tBool HAL_COM_bIsRxDataAvailable(u8 u8Channel);
|
||||||
|
|
||||||
|
#endif /* HAL_COM_H */
|
||||||
106
src/HAL_COM/prg/HAL_COM_prg.c
Normal file
106
src/HAL_COM/prg/HAL_COM_prg.c
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* File: HAL_COM_prg.c
|
||||||
|
* Component: HAL_COM
|
||||||
|
* Description: Program (implementation) file for the HAL_COM abstraction.
|
||||||
|
* Dispatches send operations to the MCU-level driver wired to
|
||||||
|
* each channel via function pointers in HAL_COM_astrChannelConfig.
|
||||||
|
*
|
||||||
|
* The dispatch is a single indirect call — no if/else chains,
|
||||||
|
* no transport-specific code. Adding a new transport (SPI, I2C,
|
||||||
|
* etc.) requires zero changes here — only a new config entry.
|
||||||
|
*
|
||||||
|
* Layer: HAL
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
#include "HAL_COM.h"
|
||||||
|
#include "HAL_COM_priv.h"
|
||||||
|
#include "HAL_COM_cfg.h"
|
||||||
|
|
||||||
|
/* ========================================================================= */
|
||||||
|
/* INIT */
|
||||||
|
/* ========================================================================= */
|
||||||
|
|
||||||
|
STD_tenuResult HAL_COM_enuInit(void)
|
||||||
|
{
|
||||||
|
STD_tenuResult enuResultLoc = STD_OK;
|
||||||
|
|
||||||
|
/* HAL_COM has no internal state to set up yet. The underlying MCU
|
||||||
|
* drivers are already initialized by SYS_ECU before this function
|
||||||
|
* is called. When internal queuing or channel-level state is added,
|
||||||
|
* initialize it here. */
|
||||||
|
|
||||||
|
return enuResultLoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================================================================= */
|
||||||
|
/* SEND BYTE */
|
||||||
|
/* ========================================================================= */
|
||||||
|
|
||||||
|
STD_tenuResult HAL_COM_enuSendByte(u8 u8Channel, u8 u8Byte)
|
||||||
|
{
|
||||||
|
STD_tenuResult enuResultLoc = STD_OK;
|
||||||
|
const HAL_COM_tstrChannelConfig *pstrCfgLoc = &HAL_COM_astrChannelConfig[u8Channel];
|
||||||
|
|
||||||
|
/* Dispatch through the function pointer configured for this channel.
|
||||||
|
* The driver's instance index is passed through transparently — the
|
||||||
|
* caller never sees it. */
|
||||||
|
enuResultLoc = pstrCfgLoc->pfSendByte(pstrCfgLoc->u8Instance, u8Byte);
|
||||||
|
|
||||||
|
return enuResultLoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================================================================= */
|
||||||
|
/* SEND BUFFER */
|
||||||
|
/* ========================================================================= */
|
||||||
|
|
||||||
|
STD_tenuResult HAL_COM_enuSendBuffer(u8 u8Channel, const u8 *pu8Data, u16 u16Length)
|
||||||
|
{
|
||||||
|
STD_tenuResult enuResultLoc = STD_OK;
|
||||||
|
const HAL_COM_tstrChannelConfig *pstrCfgLoc = &HAL_COM_astrChannelConfig[u8Channel];
|
||||||
|
|
||||||
|
/* Dispatch through the function pointer configured for this channel.
|
||||||
|
* Null-pointer validation is handled inside the MCU driver, so we
|
||||||
|
* don't duplicate the check here. */
|
||||||
|
enuResultLoc = pstrCfgLoc->pfSendBuffer(pstrCfgLoc->u8Instance, pu8Data, u16Length);
|
||||||
|
|
||||||
|
return enuResultLoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================================================================= */
|
||||||
|
/* RECEIVE BYTE (BLOCKING) */
|
||||||
|
/* ========================================================================= */
|
||||||
|
|
||||||
|
STD_tenuResult HAL_COM_enuReadByte(u8 u8Channel, u8 *pu8Byte)
|
||||||
|
{
|
||||||
|
STD_tenuResult enuResultLoc = STD_OK;
|
||||||
|
const HAL_COM_tstrChannelConfig *pstrCfgLoc = &HAL_COM_astrChannelConfig[u8Channel];
|
||||||
|
|
||||||
|
enuResultLoc = pstrCfgLoc->pfReadByte(pstrCfgLoc->u8Instance, pu8Byte);
|
||||||
|
|
||||||
|
return enuResultLoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================================================================= */
|
||||||
|
/* READ BUFFER (NON-BLOCKING) */
|
||||||
|
/* ========================================================================= */
|
||||||
|
|
||||||
|
STD_tenuResult HAL_COM_enuReadBuffer(u8 u8Channel, u8 *pu8Data, u16 u16MaxLength, u16 *pu16Read)
|
||||||
|
{
|
||||||
|
STD_tenuResult enuResultLoc = STD_OK;
|
||||||
|
const HAL_COM_tstrChannelConfig *pstrCfgLoc = &HAL_COM_astrChannelConfig[u8Channel];
|
||||||
|
|
||||||
|
enuResultLoc = pstrCfgLoc->pfReadBuffer(pstrCfgLoc->u8Instance, pu8Data, u16MaxLength, pu16Read);
|
||||||
|
|
||||||
|
return enuResultLoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================================================================= */
|
||||||
|
/* RX DATA AVAILABLE CHECK */
|
||||||
|
/* ========================================================================= */
|
||||||
|
|
||||||
|
STD_tBool HAL_COM_bIsRxDataAvailable(u8 u8Channel)
|
||||||
|
{
|
||||||
|
const HAL_COM_tstrChannelConfig *pstrCfgLoc = &HAL_COM_astrChannelConfig[u8Channel];
|
||||||
|
|
||||||
|
return pstrCfgLoc->pfIsRxDataAvailable(pstrCfgLoc->u8Instance);
|
||||||
|
}
|
||||||
32
src/HAL_COM/prg/HAL_COM_priv.h
Normal file
32
src/HAL_COM/prg/HAL_COM_priv.h
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* File: HAL_COM_priv.h
|
||||||
|
* Component: HAL_COM
|
||||||
|
* Description: Private header for the HAL_COM abstraction.
|
||||||
|
* Contains the extern declaration of the channel config array
|
||||||
|
* (only accessed internally by _prg.c) and any other private
|
||||||
|
* definitions.
|
||||||
|
*
|
||||||
|
* Layer: HAL - internal use only
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
#ifndef HAL_COM_PRIV_H
|
||||||
|
#define HAL_COM_PRIV_H
|
||||||
|
|
||||||
|
#include "HAL_COM.h"
|
||||||
|
#include "HAL_COM_cfg.h"
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* CONFIG ARRAY (EXTERN) */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Channel configuration array indexed by HAL_COM_tenuChannel.
|
||||||
|
*
|
||||||
|
* Defined in HAL_COM_cfg.c. Each entry holds function pointers to an
|
||||||
|
* MCU-level driver's send functions + the instance index to pass through.
|
||||||
|
* Only HAL_COM_prg.c accesses this — no external component should read
|
||||||
|
* the raw config.
|
||||||
|
*/
|
||||||
|
extern const HAL_COM_tstrChannelConfig HAL_COM_astrChannelConfig[HAL_COM_NUM_CHANNELS];
|
||||||
|
|
||||||
|
#endif /* HAL_COM_PRIV_H */
|
||||||
19
src/HAL_LED/cfg/HAL_LED_cfg.c
Normal file
19
src/HAL_LED/cfg/HAL_LED_cfg.c
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* File: HAL_LED_cfg.c
|
||||||
|
* Component: HAL_LED
|
||||||
|
* Description: Configuration array definition for the LED abstraction.
|
||||||
|
*
|
||||||
|
* Layer: HAL - configuration
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
#include "HAL_LED.h"
|
||||||
|
#include "HAL_LED_cfg.h"
|
||||||
|
|
||||||
|
const HAL_LED_tstrConfig HAL_LED_astrConfig[HAL_LED_NUM_INSTANCES] =
|
||||||
|
{
|
||||||
|
[HAL_LED_INSTANCE_ONBOARD] =
|
||||||
|
{
|
||||||
|
.u8NumLeds = HAL_LED_ONBOARD_NUM_LEDS,
|
||||||
|
.u8PioInstance = HAL_LED_ONBOARD_PIO_INSTANCE,
|
||||||
|
},
|
||||||
|
};
|
||||||
46
src/HAL_LED/cfg/HAL_LED_cfg.h
Normal file
46
src/HAL_LED/cfg/HAL_LED_cfg.h
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* File: HAL_LED_cfg.h
|
||||||
|
* Component: HAL_LED
|
||||||
|
* Description: Configuration header for the LED abstraction layer.
|
||||||
|
* Defines LED strip instances, maximum strip length,
|
||||||
|
* and per-instance settings.
|
||||||
|
*
|
||||||
|
* Layer: HAL - configuration
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
#ifndef HAL_LED_CFG_H
|
||||||
|
#define HAL_LED_CFG_H
|
||||||
|
|
||||||
|
#include "STD_TYPES.h"
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* INSTANCE ENUMERATION */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
HAL_LED_INSTANCE_ONBOARD = 0U, /**< Onboard WS2812B on RP2040-Zero */
|
||||||
|
HAL_LED_NUM_INSTANCES
|
||||||
|
} HAL_LED_tenuInstance;
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* BUFFER SIZING */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/** @brief Maximum number of LEDs any single instance can support.
|
||||||
|
* Determines the pixel buffer size in the control struct.
|
||||||
|
* Increase when adding longer strips. */
|
||||||
|
#define HAL_LED_MAX_LEDS_PER_INSTANCE 1U
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* INSTANCE 0 (ONBOARD) CONFIGURATION */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/** @brief Number of LEDs in the onboard strip (just 1 on the RP2040-Zero). */
|
||||||
|
#define HAL_LED_ONBOARD_NUM_LEDS 1U
|
||||||
|
|
||||||
|
/** @brief MCU_PIO instance index for the onboard LED.
|
||||||
|
* Maps to MCU_PIO_INSTANCE_WS2812 (defined in MCU_PIO_cfg.h). */
|
||||||
|
#define HAL_LED_ONBOARD_PIO_INSTANCE 0U
|
||||||
|
|
||||||
|
#endif /* HAL_LED_CFG_H */
|
||||||
70
src/HAL_LED/inc/HAL_LED.h
Normal file
70
src/HAL_LED/inc/HAL_LED.h
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* File: HAL_LED.h
|
||||||
|
* Component: HAL_LED
|
||||||
|
* Description: Public interface for the LED abstraction layer.
|
||||||
|
* Manages a pixel buffer for WS2812-style addressable LEDs
|
||||||
|
* and pushes color data through MCU_PIO. Supports indexed
|
||||||
|
* LED strips with per-pixel intensity scaling.
|
||||||
|
*
|
||||||
|
* SetColor sets one LED's color and immediately pushes the
|
||||||
|
* entire strip to the hardware — no separate Update call needed.
|
||||||
|
*
|
||||||
|
* Layer: HAL (hardware abstraction, one level above MCU drivers)
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
#ifndef HAL_LED_H
|
||||||
|
#define HAL_LED_H
|
||||||
|
|
||||||
|
#include "STD_TYPES.h"
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* CONFIGURATION STRUCTURE */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Per-instance LED strip/array configuration.
|
||||||
|
*
|
||||||
|
* One entry per LED strip, stored in HAL_LED_astrConfig[].
|
||||||
|
* The array index is the instance parameter in all public API calls.
|
||||||
|
*/
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
u8 u8NumLeds; /**< Number of LEDs in this strip (1 for single LED) */
|
||||||
|
u8 u8PioInstance; /**< MCU_PIO config index for the data output */
|
||||||
|
} HAL_LED_tstrConfig;
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* PUBLIC API */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize the LED abstraction layer.
|
||||||
|
*
|
||||||
|
* Clears the internal pixel buffer to all-off (black). Does NOT init
|
||||||
|
* MCU_PIO — SYS_ECU must call MCU_PIO_enuInit() before this.
|
||||||
|
*
|
||||||
|
* @return STD_OK on success.
|
||||||
|
*/
|
||||||
|
STD_tenuResult HAL_LED_enuInit(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the color of a single LED and push the entire strip.
|
||||||
|
*
|
||||||
|
* Scales each color channel by u8Intensity (0-255), stores the result
|
||||||
|
* in the internal pixel buffer, then immediately pushes all LEDs in
|
||||||
|
* this strip to the PIO state machine.
|
||||||
|
*
|
||||||
|
* @param u8Instance HAL_LED config instance.
|
||||||
|
* @param u8LedIndex LED position in the strip (0-based).
|
||||||
|
* @param u8Red Red intensity (0-255, before scaling).
|
||||||
|
* @param u8Green Green intensity (0-255, before scaling).
|
||||||
|
* @param u8Blue Blue intensity (0-255, before scaling).
|
||||||
|
* @param u8Intensity Global brightness scaler (0-255). 255 = full.
|
||||||
|
* @return STD_OK on success,
|
||||||
|
* STD_INDEX_OUT_OF_RANGE_ERROR if u8LedIndex >= u8NumLeds.
|
||||||
|
*/
|
||||||
|
STD_tenuResult HAL_LED_enuSetColor(u8 u8Instance, u8 u8LedIndex,
|
||||||
|
u8 u8Red, u8 u8Green, u8 u8Blue,
|
||||||
|
u8 u8Intensity);
|
||||||
|
|
||||||
|
#endif /* HAL_LED_H */
|
||||||
117
src/HAL_LED/prg/HAL_LED_prg.c
Normal file
117
src/HAL_LED/prg/HAL_LED_prg.c
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* File: HAL_LED_prg.c
|
||||||
|
* Component: HAL_LED
|
||||||
|
* Description: LED abstraction implementation. Manages a pixel buffer with
|
||||||
|
* intensity scaling and pushes color data to the hardware via
|
||||||
|
* MCU_PIO. SetColor updates one LED and immediately pushes the
|
||||||
|
* entire strip — no separate Update call needed.
|
||||||
|
*
|
||||||
|
* Layer: HAL
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
#include "HAL_LED.h"
|
||||||
|
#include "HAL_LED_priv.h"
|
||||||
|
#include "HAL_LED_cfg.h"
|
||||||
|
|
||||||
|
#include "MCU_PIO.h"
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* RUNTIME STATE */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
static HAL_LED_tstrControl strControl;
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* INTERNAL HELPERS */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Push the entire pixel buffer for one instance to the PIO FIFO.
|
||||||
|
*
|
||||||
|
* Iterates through the pixel buffer, packs each pixel into a 32-bit
|
||||||
|
* GRB word (left-justified: G in [31:24], R in [23:16], B in [15:8],
|
||||||
|
* bits [7:0] unused), and sends it via MCU_PIO_vPutBlocking.
|
||||||
|
*
|
||||||
|
* @param u8Instance HAL_LED instance index.
|
||||||
|
*/
|
||||||
|
static void vPushStrip(u8 u8Instance)
|
||||||
|
{
|
||||||
|
u8 u8PioInstLoc = HAL_LED_astrConfig[u8Instance].u8PioInstance;
|
||||||
|
u8 u8NumLedsLoc = HAL_LED_astrConfig[u8Instance].u8NumLeds;
|
||||||
|
u8 u8LedLoc;
|
||||||
|
|
||||||
|
for (u8LedLoc = 0U; u8LedLoc < u8NumLedsLoc; u8LedLoc++)
|
||||||
|
{
|
||||||
|
HAL_LED_tstrPixel *pstrPixLoc = &strControl.astrPixels[u8Instance][u8LedLoc];
|
||||||
|
|
||||||
|
/* Pack RGB into bits [31:8] of the 32-bit word.
|
||||||
|
* The WS2812 variant on the RP2040-Zero uses RGB byte order
|
||||||
|
* (red first, green second, blue third) rather than the standard
|
||||||
|
* GRB. Bits [7:0] are padding (shifted out but ignored). */
|
||||||
|
u32 u32RgbLoc = ((u32)pstrPixLoc->u8Red << 24U)
|
||||||
|
| ((u32)pstrPixLoc->u8Green << 16U)
|
||||||
|
| ((u32)pstrPixLoc->u8Blue << 8U);
|
||||||
|
|
||||||
|
MCU_PIO_vPutBlocking(u8PioInstLoc, u32RgbLoc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================================================================= */
|
||||||
|
/* INIT */
|
||||||
|
/* ========================================================================= */
|
||||||
|
|
||||||
|
STD_tenuResult HAL_LED_enuInit(void)
|
||||||
|
{
|
||||||
|
STD_tenuResult enuResultLoc = STD_OK;
|
||||||
|
u8 u8InstLoc;
|
||||||
|
u8 u8LedLoc;
|
||||||
|
|
||||||
|
/* Clear all pixels to off (black) */
|
||||||
|
for (u8InstLoc = 0U; u8InstLoc < (u8)HAL_LED_NUM_INSTANCES; u8InstLoc++)
|
||||||
|
{
|
||||||
|
for (u8LedLoc = 0U; u8LedLoc < HAL_LED_astrConfig[u8InstLoc].u8NumLeds; u8LedLoc++)
|
||||||
|
{
|
||||||
|
strControl.astrPixels[u8InstLoc][u8LedLoc].u8Green = 0U;
|
||||||
|
strControl.astrPixels[u8InstLoc][u8LedLoc].u8Red = 0U;
|
||||||
|
strControl.astrPixels[u8InstLoc][u8LedLoc].u8Blue = 0U;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return enuResultLoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================================================================= */
|
||||||
|
/* SET COLOR */
|
||||||
|
/* ========================================================================= */
|
||||||
|
|
||||||
|
STD_tenuResult HAL_LED_enuSetColor(u8 u8Instance, u8 u8LedIndex,
|
||||||
|
u8 u8Red, u8 u8Green, u8 u8Blue,
|
||||||
|
u8 u8Intensity)
|
||||||
|
{
|
||||||
|
STD_tenuResult enuResultLoc = STD_OK;
|
||||||
|
u8 u8NumLedsLoc = HAL_LED_astrConfig[u8Instance].u8NumLeds;
|
||||||
|
|
||||||
|
if (u8LedIndex >= u8NumLedsLoc)
|
||||||
|
{
|
||||||
|
enuResultLoc = STD_INDEX_OUT_OF_RANGE_ERROR;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Scale each channel by intensity: (channel * intensity) / 255.
|
||||||
|
* Use u16 intermediate to avoid overflow (255 * 255 = 65025,
|
||||||
|
* which fits in u16 but not u8). */
|
||||||
|
u8 u8ScaledRLoc = (u8)(((u16)u8Red * (u16)u8Intensity) / 255U);
|
||||||
|
u8 u8ScaledGLoc = (u8)(((u16)u8Green * (u16)u8Intensity) / 255U);
|
||||||
|
u8 u8ScaledBLoc = (u8)(((u16)u8Blue * (u16)u8Intensity) / 255U);
|
||||||
|
|
||||||
|
/* Store the scaled pixel in the buffer */
|
||||||
|
strControl.astrPixels[u8Instance][u8LedIndex].u8Red = u8ScaledRLoc;
|
||||||
|
strControl.astrPixels[u8Instance][u8LedIndex].u8Green = u8ScaledGLoc;
|
||||||
|
strControl.astrPixels[u8Instance][u8LedIndex].u8Blue = u8ScaledBLoc;
|
||||||
|
|
||||||
|
/* Immediately push the entire strip to the hardware */
|
||||||
|
vPushStrip(u8Instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
return enuResultLoc;
|
||||||
|
}
|
||||||
51
src/HAL_LED/prg/HAL_LED_priv.h
Normal file
51
src/HAL_LED/prg/HAL_LED_priv.h
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* File: HAL_LED_priv.h
|
||||||
|
* Component: HAL_LED
|
||||||
|
* Description: Private header — pixel struct, control struct with the
|
||||||
|
* pixel buffer, and extern config array declaration.
|
||||||
|
*
|
||||||
|
* Layer: HAL - internal use only
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
#ifndef HAL_LED_PRIV_H
|
||||||
|
#define HAL_LED_PRIV_H
|
||||||
|
|
||||||
|
#include "HAL_LED.h"
|
||||||
|
#include "HAL_LED_cfg.h"
|
||||||
|
|
||||||
|
extern const HAL_LED_tstrConfig HAL_LED_astrConfig[HAL_LED_NUM_INSTANCES];
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* PIXEL STRUCTURE */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Per-LED color data in GRB order (matching WS2812 protocol).
|
||||||
|
*
|
||||||
|
* Stored after intensity scaling has been applied, so these values
|
||||||
|
* are the actual brightnesses sent to the hardware.
|
||||||
|
*/
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
u8 u8Green;
|
||||||
|
u8 u8Red;
|
||||||
|
u8 u8Blue;
|
||||||
|
} HAL_LED_tstrPixel;
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* CONTROL STRUCTURE */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Internal runtime state for all HAL_LED instances.
|
||||||
|
*
|
||||||
|
* astrPixels is a 2D array: [instance][led_index]. Each pixel holds
|
||||||
|
* the intensity-scaled GRB values ready to be packed into 32-bit words
|
||||||
|
* for the PIO FIFO.
|
||||||
|
*/
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
HAL_LED_tstrPixel astrPixels[HAL_LED_NUM_INSTANCES][HAL_LED_MAX_LEDS_PER_INSTANCE];
|
||||||
|
} HAL_LED_tstrControl;
|
||||||
|
|
||||||
|
#endif /* HAL_LED_PRIV_H */
|
||||||
53
src/MCU_PIO/cfg/MCU_PIO_cfg.c
Normal file
53
src/MCU_PIO/cfg/MCU_PIO_cfg.c
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* File: MCU_PIO_cfg.c
|
||||||
|
* Component: MCU_PIO
|
||||||
|
* Description: Configuration array definition for the PIO driver.
|
||||||
|
* Wires each PIO instance to its compiled program and
|
||||||
|
* program-specific init function. The WS2812 init wrapper
|
||||||
|
* adapts the auto-generated ws2812_program_init() signature
|
||||||
|
* to match the generic MCU_PIO_tpfProgramInit callback type.
|
||||||
|
*
|
||||||
|
* Layer: MCU (hardware abstraction) - configuration
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
#include "MCU_PIO.h"
|
||||||
|
#include "MCU_PIO_cfg.h"
|
||||||
|
|
||||||
|
/* Auto-generated header from ws2812.pio — provides ws2812_program struct
|
||||||
|
* and ws2812_program_init() helper. Generated at build time by
|
||||||
|
* pico_generate_pio_header() in mcu_config.cmake. */
|
||||||
|
#include "ws2812.pio.h"
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* WS2812 INIT WRAPPER */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Adapts ws2812_program_init() to the MCU_PIO_tpfProgramInit
|
||||||
|
* signature by hardcoding the WS2812 bit frequency (800 kHz).
|
||||||
|
*
|
||||||
|
* The auto-generated ws2812_program_init() takes an extra `float freq`
|
||||||
|
* parameter that is not part of the generic callback type. This wrapper
|
||||||
|
* fills it in so the generic driver can call it without knowing about
|
||||||
|
* WS2812 specifics.
|
||||||
|
*/
|
||||||
|
static void vWs2812Init(PIO pstrPio, u8 u8Sm, u8 u8Pin, u32 u32Offset)
|
||||||
|
{
|
||||||
|
ws2812_program_init(pstrPio, (u32)u8Sm, (u32)u32Offset, (u32)u8Pin, 800000.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* CONFIGURATION ARRAY */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
const MCU_PIO_tstrConfig MCU_PIO_astrConfig[MCU_PIO_NUM_INSTANCES] =
|
||||||
|
{
|
||||||
|
[MCU_PIO_INSTANCE_WS2812] =
|
||||||
|
{
|
||||||
|
.pstrPio = pio0, /* use PIO block 0 */
|
||||||
|
.u8Sm = 0U, /* state machine 0 within pio0 */
|
||||||
|
.u8Pin = MCU_PIO_WS2812_PIN,
|
||||||
|
.pstrProgram = &ws2812_program,
|
||||||
|
.pfProgramInit = vWs2812Init,
|
||||||
|
},
|
||||||
|
};
|
||||||
40
src/MCU_PIO/cfg/MCU_PIO_cfg.h
Normal file
40
src/MCU_PIO/cfg/MCU_PIO_cfg.h
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* File: MCU_PIO_cfg.h
|
||||||
|
* Component: MCU_PIO
|
||||||
|
* Description: Configuration header for the generic PIO driver.
|
||||||
|
* Defines which PIO programs are loaded and on which
|
||||||
|
* state machines / GPIO pins they operate.
|
||||||
|
*
|
||||||
|
* Layer: MCU (hardware abstraction) - configuration
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
#ifndef MCU_PIO_CFG_H
|
||||||
|
#define MCU_PIO_CFG_H
|
||||||
|
|
||||||
|
#include "STD_TYPES.h"
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* INSTANCE ENUMERATION */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Enumeration of configured PIO program instances.
|
||||||
|
*
|
||||||
|
* Each entry represents one PIO program running on one state machine.
|
||||||
|
* The enumerator value is the array index into MCU_PIO_astrConfig[].
|
||||||
|
*/
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
MCU_PIO_INSTANCE_WS2812 = 0U, /**< WS2812 LED driver on GP16 */
|
||||||
|
MCU_PIO_NUM_INSTANCES
|
||||||
|
} MCU_PIO_tenuInstance;
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* WS2812 INSTANCE CONFIGURATION */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/** @brief GPIO pin for the WS2812B data line.
|
||||||
|
* GP16 is the onboard WS2812B on the Waveshare RP2040-Zero. */
|
||||||
|
#define MCU_PIO_WS2812_PIN 16U
|
||||||
|
|
||||||
|
#endif /* MCU_PIO_CFG_H */
|
||||||
107
src/MCU_PIO/inc/MCU_PIO.h
Normal file
107
src/MCU_PIO/inc/MCU_PIO.h
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* File: MCU_PIO.h
|
||||||
|
* Component: MCU_PIO
|
||||||
|
* Description: Public interface for the generic PIO driver component.
|
||||||
|
* Abstracts the RP2040's Programmable I/O hardware — loading
|
||||||
|
* PIO programs into instruction memory, configuring state
|
||||||
|
* machines, and pushing data through the TX FIFO.
|
||||||
|
*
|
||||||
|
* The driver is program-agnostic: each config entry holds a
|
||||||
|
* pointer to a compiled PIO program and a function pointer
|
||||||
|
* for the program-specific state machine init. WS2812 is one
|
||||||
|
* such program; future PIO uses (custom protocols, etc.)
|
||||||
|
* plug in the same way with zero driver code changes.
|
||||||
|
*
|
||||||
|
* Layer: MCU (hardware abstraction)
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
#ifndef MCU_PIO_H
|
||||||
|
#define MCU_PIO_H
|
||||||
|
|
||||||
|
#include "STD_TYPES.h"
|
||||||
|
#include "hardware/pio.h"
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* PROGRAM INIT FUNCTION POINTER TYPE */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Callback type for program-specific state machine configuration.
|
||||||
|
*
|
||||||
|
* Each PIO program has its own pin mapping, shift config, clock divider,
|
||||||
|
* etc. The generic MCU_PIO driver calls this function after loading the
|
||||||
|
* program into instruction memory. The function must fully configure and
|
||||||
|
* enable the state machine.
|
||||||
|
*
|
||||||
|
* @param pstrPio PIO instance (pio0 or pio1).
|
||||||
|
* @param u8Sm State machine index (0-3) within that PIO instance.
|
||||||
|
* @param u8Pin GPIO pin from the config struct.
|
||||||
|
* @param u32Offset Instruction memory offset where the program was loaded.
|
||||||
|
*/
|
||||||
|
typedef void (*MCU_PIO_tpfProgramInit)(PIO pstrPio, u8 u8Sm, u8 u8Pin, u32 u32Offset);
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* CONFIGURATION STRUCTURE */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Per-instance PIO configuration.
|
||||||
|
*
|
||||||
|
* One entry per PIO program/state-machine pair, stored in
|
||||||
|
* MCU_PIO_astrConfig[]. The array index is used as the instance
|
||||||
|
* parameter in all public API calls.
|
||||||
|
*/
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
PIO pstrPio; /**< PIO block: pio0 or pio1 */
|
||||||
|
u8 u8Sm; /**< State machine index (0-3) */
|
||||||
|
u8 u8Pin; /**< GPIO pin used by this program */
|
||||||
|
const pio_program_t *pstrProgram; /**< Pointer to compiled PIO program */
|
||||||
|
MCU_PIO_tpfProgramInit pfProgramInit; /**< Program-specific SM config callback */
|
||||||
|
} MCU_PIO_tstrConfig;
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* PUBLIC API */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize all configured PIO instances.
|
||||||
|
*
|
||||||
|
* For each config entry: loads the PIO program into the instruction
|
||||||
|
* memory of the selected PIO block, then calls the program-specific
|
||||||
|
* init callback to configure the state machine (pins, shift, clock).
|
||||||
|
*
|
||||||
|
* SYS_ECU calls this once during the init sequence.
|
||||||
|
*
|
||||||
|
* @return STD_OK on success, STD_NOK if any instance fails.
|
||||||
|
*/
|
||||||
|
STD_tenuResult MCU_PIO_enuInit(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Push a 32-bit word into a PIO state machine's TX FIFO (blocking).
|
||||||
|
*
|
||||||
|
* Blocks until the FIFO has space, then writes the data. For WS2812,
|
||||||
|
* each call sends one pixel (24-bit GRB left-justified in the 32-bit word).
|
||||||
|
*
|
||||||
|
* @param u8Instance Config array index (MCU_PIO_tenuInstance).
|
||||||
|
* @param u32Data The 32-bit value to push.
|
||||||
|
*/
|
||||||
|
void MCU_PIO_vPutBlocking(u8 u8Instance, u32 u32Data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Push a buffer of 32-bit words to the TX FIFO via DMA (non-blocking).
|
||||||
|
*
|
||||||
|
* Starts a DMA transfer from pu32Data into the PIO TX FIFO. Returns
|
||||||
|
* immediately. The DMA channel was pre-claimed during Init.
|
||||||
|
*
|
||||||
|
* The caller MUST keep pu32Data valid until the transfer completes.
|
||||||
|
*
|
||||||
|
* @param u8Instance Config array index.
|
||||||
|
* @param pu32Data Pointer to array of 32-bit words. Must not be NULL.
|
||||||
|
* @param u16Count Number of 32-bit words to transfer.
|
||||||
|
* @return STD_OK transfer started,
|
||||||
|
* STD_NULL_POINTER_ERROR if pu32Data is NULL.
|
||||||
|
*/
|
||||||
|
STD_tenuResult MCU_PIO_enuPutBufferAsync(u8 u8Instance, const u32 *pu32Data, u16 u16Count);
|
||||||
|
|
||||||
|
#endif /* MCU_PIO_H */
|
||||||
73
src/MCU_PIO/pio/ws2812.pio
Normal file
73
src/MCU_PIO/pio/ws2812.pio
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
; ============================================================================
|
||||||
|
; ws2812.pio — WS2812B NZR protocol driver for RP2040 PIO
|
||||||
|
; ============================================================================
|
||||||
|
; Written from scratch for the color_switcher project.
|
||||||
|
;
|
||||||
|
; WS2812 protocol: each bit is a fixed-length pulse (~1.25 us at 800 kHz).
|
||||||
|
; "1" bit: long high (~875 ns) + short low (~375 ns)
|
||||||
|
; "0" bit: short high (~375 ns) + long low (~875 ns)
|
||||||
|
;
|
||||||
|
; Timing: 10 PIO cycles per bit at 8 MHz PIO clock (sysclk 125 MHz / 15.625).
|
||||||
|
; "1" bit: high 7 cycles, low 3 cycles
|
||||||
|
; "0" bit: high 3 cycles, low 7 cycles
|
||||||
|
;
|
||||||
|
; Data: 24-bit GRB, MSB first, left-justified in 32-bit FIFO word.
|
||||||
|
; Autopull at 24 bits, shift left. Side-set pin drives the data line.
|
||||||
|
; ============================================================================
|
||||||
|
|
||||||
|
.program ws2812
|
||||||
|
.side_set 1
|
||||||
|
|
||||||
|
.wrap_target
|
||||||
|
bitloop:
|
||||||
|
out x, 1 side 0 [2] ; shift 1 bit into X, drive LOW, 3 cycles total
|
||||||
|
jmp !x do_zero side 1 [1] ; if bit=0 jump, drive HIGH, 2 cycles total
|
||||||
|
do_one:
|
||||||
|
jmp bitloop side 1 [4] ; bit=1: stay HIGH 5 more (total HIGH=7), loop
|
||||||
|
do_zero:
|
||||||
|
nop side 0 [4] ; bit=0: drive LOW 5 more (total LOW from next out=3+5)
|
||||||
|
.wrap
|
||||||
|
|
||||||
|
; ============================================================================
|
||||||
|
; C SDK helper — emitted into ws2812.pio.h by pico_generate_pio_header.
|
||||||
|
; Configures the state machine for WS2812 output on a single GPIO pin.
|
||||||
|
;
|
||||||
|
; Parameters:
|
||||||
|
; pio — PIO instance (pio0 or pio1)
|
||||||
|
; sm — state machine index (0-3)
|
||||||
|
; offset — instruction memory offset where the program was loaded
|
||||||
|
; pin — GPIO pin connected to the WS2812 data line
|
||||||
|
; freq — bit frequency in Hz (800000.0f for standard WS2812)
|
||||||
|
; ============================================================================
|
||||||
|
|
||||||
|
% c-sdk {
|
||||||
|
#include "hardware/clocks.h"
|
||||||
|
|
||||||
|
static inline void ws2812_program_init(PIO pio, uint sm, uint offset, uint pin, float freq)
|
||||||
|
{
|
||||||
|
/* Configure the GPIO pin for PIO output */
|
||||||
|
pio_gpio_init(pio, pin);
|
||||||
|
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
|
||||||
|
|
||||||
|
/* Get the default config (sets wrap points and sideset count from the .pio) */
|
||||||
|
pio_sm_config c = ws2812_program_get_default_config(offset);
|
||||||
|
|
||||||
|
/* Side-set pin = the WS2812 data line */
|
||||||
|
sm_config_set_sideset_pins(&c, pin);
|
||||||
|
|
||||||
|
/* Shift left, autopull at 24 bits (GRB = 3 bytes).
|
||||||
|
* Data must be left-justified in the 32-bit FIFO word (bits [31:8]). */
|
||||||
|
sm_config_set_out_shift(&c, false, true, 24);
|
||||||
|
|
||||||
|
/* Join both FIFOs into a single 8-entry TX FIFO for deeper buffering */
|
||||||
|
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
|
||||||
|
|
||||||
|
/* Clock divider: 10 PIO cycles per bit, so PIO freq = bit_freq * 10.
|
||||||
|
* clkdiv = sysclk / (freq * 10). E.g., 125 MHz / 8 MHz = 15.625 */
|
||||||
|
sm_config_set_clkdiv(&c, clock_get_hz(clk_sys) / (freq * 10.0f));
|
||||||
|
|
||||||
|
/* Apply the config and start the state machine */
|
||||||
|
pio_sm_init(pio, sm, offset, &c);
|
||||||
|
pio_sm_set_enabled(pio, sm, true);
|
||||||
|
}
|
||||||
|
%}
|
||||||
115
src/MCU_PIO/prg/MCU_PIO_prg.c
Normal file
115
src/MCU_PIO/prg/MCU_PIO_prg.c
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* File: MCU_PIO_prg.c
|
||||||
|
* Component: MCU_PIO
|
||||||
|
* Description: Generic PIO driver implementation. Loads PIO programs into
|
||||||
|
* instruction memory, calls program-specific init callbacks,
|
||||||
|
* claims DMA channels, and provides blocking + async writes.
|
||||||
|
*
|
||||||
|
* Layer: MCU (hardware abstraction)
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
#include "MCU_PIO.h"
|
||||||
|
#include "MCU_PIO_priv.h"
|
||||||
|
#include "MCU_PIO_cfg.h"
|
||||||
|
|
||||||
|
#include "hardware/pio.h"
|
||||||
|
#include "hardware/dma.h"
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* RUNTIME STATE */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
static MCU_PIO_tstrControl strControl;
|
||||||
|
|
||||||
|
/* ========================================================================= */
|
||||||
|
/* INIT */
|
||||||
|
/* ========================================================================= */
|
||||||
|
|
||||||
|
STD_tenuResult MCU_PIO_enuInit(void)
|
||||||
|
{
|
||||||
|
STD_tenuResult enuResultLoc = STD_OK;
|
||||||
|
u8 u8IndexLoc;
|
||||||
|
|
||||||
|
for (u8IndexLoc = 0U; u8IndexLoc < (u8)MCU_PIO_NUM_INSTANCES; u8IndexLoc++)
|
||||||
|
{
|
||||||
|
const MCU_PIO_tstrConfig *pstrCfgLoc = &MCU_PIO_astrConfig[u8IndexLoc];
|
||||||
|
|
||||||
|
/* Load the PIO program into instruction memory */
|
||||||
|
u32 u32OffsetLoc = (u32)pio_add_program(pstrCfgLoc->pstrPio,
|
||||||
|
pstrCfgLoc->pstrProgram);
|
||||||
|
|
||||||
|
/* Call the program-specific init callback to configure the SM */
|
||||||
|
pstrCfgLoc->pfProgramInit(pstrCfgLoc->pstrPio,
|
||||||
|
pstrCfgLoc->u8Sm,
|
||||||
|
pstrCfgLoc->u8Pin,
|
||||||
|
u32OffsetLoc);
|
||||||
|
|
||||||
|
/* Claim a DMA channel for async FIFO writes. Reserved for the
|
||||||
|
* lifetime of the application — never released. true = panic
|
||||||
|
* if no channels available (misconfiguration, not a runtime error). */
|
||||||
|
strControl.as8DmaChannel[u8IndexLoc] = (s8)dma_claim_unused_channel(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return enuResultLoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================================================================= */
|
||||||
|
/* PUT BLOCKING (SINGLE WORD) */
|
||||||
|
/* ========================================================================= */
|
||||||
|
|
||||||
|
void MCU_PIO_vPutBlocking(u8 u8Instance, u32 u32Data)
|
||||||
|
{
|
||||||
|
const MCU_PIO_tstrConfig *pstrCfgLoc = &MCU_PIO_astrConfig[u8Instance];
|
||||||
|
|
||||||
|
/* Blocks until the TX FIFO has space, then writes the 32-bit word */
|
||||||
|
pio_sm_put_blocking(pstrCfgLoc->pstrPio, pstrCfgLoc->u8Sm, u32Data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================================================================= */
|
||||||
|
/* PUT BUFFER ASYNC (DMA, NON-BLOCKING) */
|
||||||
|
/* ========================================================================= */
|
||||||
|
|
||||||
|
STD_tenuResult MCU_PIO_enuPutBufferAsync(u8 u8Instance, const u32 *pu32Data, u16 u16Count)
|
||||||
|
{
|
||||||
|
STD_tenuResult enuResultLoc = STD_OK;
|
||||||
|
|
||||||
|
if (pu32Data == STD_NULL)
|
||||||
|
{
|
||||||
|
enuResultLoc = STD_NULL_POINTER_ERROR;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const MCU_PIO_tstrConfig *pstrCfgLoc = &MCU_PIO_astrConfig[u8Instance];
|
||||||
|
s8 s8ChLoc = strControl.as8DmaChannel[u8Instance];
|
||||||
|
|
||||||
|
dma_channel_config strCfgLoc = dma_channel_get_default_config((u32)s8ChLoc);
|
||||||
|
|
||||||
|
/* 32-bit transfers — matches the PIO FIFO word size */
|
||||||
|
channel_config_set_transfer_data_size(&strCfgLoc, DMA_SIZE_32);
|
||||||
|
|
||||||
|
/* Source: increment through the caller's buffer */
|
||||||
|
channel_config_set_read_increment(&strCfgLoc, true);
|
||||||
|
|
||||||
|
/* Destination: PIO TX FIFO register (fixed address) */
|
||||||
|
channel_config_set_write_increment(&strCfgLoc, false);
|
||||||
|
|
||||||
|
/* Pace by PIO TX FIFO DREQ — DMA only pushes when SM can accept */
|
||||||
|
channel_config_set_dreq(&strCfgLoc, pio_get_dreq(pstrCfgLoc->pstrPio,
|
||||||
|
pstrCfgLoc->u8Sm,
|
||||||
|
true));
|
||||||
|
|
||||||
|
/* Start the DMA transfer. Runs autonomously until u16Count words
|
||||||
|
* have been pushed into the FIFO. Caller must keep pu32Data valid
|
||||||
|
* until transfer completes. */
|
||||||
|
dma_channel_configure(
|
||||||
|
(u32)s8ChLoc,
|
||||||
|
&strCfgLoc,
|
||||||
|
&pstrCfgLoc->pstrPio->txf[pstrCfgLoc->u8Sm], /* dest: PIO TX FIFO */
|
||||||
|
pu32Data, /* source: buffer */
|
||||||
|
u16Count, /* word count */
|
||||||
|
true /* start immediately */
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return enuResultLoc;
|
||||||
|
}
|
||||||
34
src/MCU_PIO/prg/MCU_PIO_priv.h
Normal file
34
src/MCU_PIO/prg/MCU_PIO_priv.h
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* File: MCU_PIO_priv.h
|
||||||
|
* Component: MCU_PIO
|
||||||
|
* Description: Private header — extern config array and runtime control
|
||||||
|
* struct holding pre-claimed DMA channels per instance.
|
||||||
|
*
|
||||||
|
* Layer: MCU (hardware abstraction) - internal use only
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
#ifndef MCU_PIO_PRIV_H
|
||||||
|
#define MCU_PIO_PRIV_H
|
||||||
|
|
||||||
|
#include "MCU_PIO.h"
|
||||||
|
#include "MCU_PIO_cfg.h"
|
||||||
|
|
||||||
|
extern const MCU_PIO_tstrConfig MCU_PIO_astrConfig[MCU_PIO_NUM_INSTANCES];
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* RUNTIME CONTROL STRUCTURE */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Per-instance runtime state.
|
||||||
|
*
|
||||||
|
* as8DmaChannel — DMA channel claimed during Init for async FIFO writes.
|
||||||
|
* One channel per PIO instance, reserved for the lifetime
|
||||||
|
* of the application.
|
||||||
|
*/
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
s8 as8DmaChannel[MCU_PIO_NUM_INSTANCES];
|
||||||
|
} MCU_PIO_tstrControl;
|
||||||
|
|
||||||
|
#endif /* MCU_PIO_PRIV_H */
|
||||||
26
src/MCU_UART/cfg/MCU_UART_cfg.c
Normal file
26
src/MCU_UART/cfg/MCU_UART_cfg.c
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* File: MCU_UART_cfg.c
|
||||||
|
* Component: MCU_UART
|
||||||
|
* Description: Configuration array definition for the MCU_UART driver.
|
||||||
|
*
|
||||||
|
* Layer: MCU (hardware abstraction) - configuration
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
#include "MCU_UART.h"
|
||||||
|
#include "MCU_UART_cfg.h"
|
||||||
|
|
||||||
|
const MCU_UART_tstrConfig MCU_UART_astrConfig[MCU_UART_NUM_INSTANCES] =
|
||||||
|
{
|
||||||
|
[MCU_UART_INSTANCE_0] =
|
||||||
|
{
|
||||||
|
.u8TxPin = MCU_UART_0_TX_PIN,
|
||||||
|
.u8RxPin = MCU_UART_0_RX_PIN,
|
||||||
|
.u32BaudRate = MCU_UART_0_BAUD_RATE,
|
||||||
|
.enuDataBits = MCU_UART_0_DATA_BITS,
|
||||||
|
.enuStopBits = MCU_UART_0_STOP_BITS,
|
||||||
|
.enuParity = MCU_UART_0_PARITY,
|
||||||
|
.enuTxAsyncMode = MCU_UART_0_TX_ASYNC_MODE,
|
||||||
|
.pfTxCompleteCallback = MCU_UART_0_TX_COMPLETE_CALLBACK,
|
||||||
|
.enuRxAsyncMode = MCU_UART_0_RX_ASYNC_MODE,
|
||||||
|
},
|
||||||
|
};
|
||||||
51
src/MCU_UART/cfg/MCU_UART_cfg.h
Normal file
51
src/MCU_UART/cfg/MCU_UART_cfg.h
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* File: MCU_UART_cfg.h
|
||||||
|
* Component: MCU_UART
|
||||||
|
* Description: Configuration for the MCU_UART driver. Defines instances,
|
||||||
|
* pin assignments, baud rates, data format, TX/RX async modes,
|
||||||
|
* and RX buffer sizing.
|
||||||
|
*
|
||||||
|
* Layer: MCU (hardware abstraction) - configuration
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
#ifndef MCU_UART_CFG_H
|
||||||
|
#define MCU_UART_CFG_H
|
||||||
|
|
||||||
|
#include "STD_TYPES.h"
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* INSTANCE ENUMERATION */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
MCU_UART_INSTANCE_0 = 0U,
|
||||||
|
MCU_UART_NUM_INSTANCES
|
||||||
|
} MCU_UART_tenuInstance;
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* RX RING BUFFER SIZING */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/** @brief Number of address bits for the RX ring buffer (2^N bytes).
|
||||||
|
* Must be power of 2 for DMA ring wrap.
|
||||||
|
* 5 = 32, 6 = 64, 7 = 128, 8 = 256. */
|
||||||
|
#define MCU_UART_RX_BUFFER_SIZE_BITS 6U
|
||||||
|
#define MCU_UART_RX_BUFFER_SIZE (1U << MCU_UART_RX_BUFFER_SIZE_BITS)
|
||||||
|
#define MCU_UART_RX_BUFFER_MASK (MCU_UART_RX_BUFFER_SIZE - 1U)
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* INSTANCE 0 CONFIGURATION */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
#define MCU_UART_0_TX_PIN 0U
|
||||||
|
#define MCU_UART_0_RX_PIN 1U
|
||||||
|
#define MCU_UART_0_BAUD_RATE 115200U
|
||||||
|
#define MCU_UART_0_DATA_BITS MCU_UART_DATA_BITS_8
|
||||||
|
#define MCU_UART_0_STOP_BITS MCU_UART_STOP_BITS_1
|
||||||
|
#define MCU_UART_0_PARITY MCU_UART_PARITY_NONE
|
||||||
|
#define MCU_UART_0_TX_ASYNC_MODE MCU_UART_ASYNC_DMA
|
||||||
|
#define MCU_UART_0_TX_COMPLETE_CALLBACK STD_NULL
|
||||||
|
#define MCU_UART_0_RX_ASYNC_MODE MCU_UART_ASYNC_ISR
|
||||||
|
|
||||||
|
#endif /* MCU_UART_CFG_H */
|
||||||
124
src/MCU_UART/inc/MCU_UART.h
Normal file
124
src/MCU_UART/inc/MCU_UART.h
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* File: MCU_UART.h
|
||||||
|
* Component: MCU_UART
|
||||||
|
* Description: Public interface for the MCU UART driver component.
|
||||||
|
*
|
||||||
|
* TX: SendByte (blocking), SendBuffer (non-blocking DMA/ISR),
|
||||||
|
* SendBufferBlocking.
|
||||||
|
* RX: background ring buffer filled by ISR or DMA.
|
||||||
|
* ReadByte / ReadBuffer read from the buffer (non-blocking).
|
||||||
|
*
|
||||||
|
* Layer: MCU (hardware abstraction)
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
#ifndef MCU_UART_H
|
||||||
|
#define MCU_UART_H
|
||||||
|
|
||||||
|
#include "STD_TYPES.h"
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* CONFIGURATION VALUE TYPES */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
MCU_UART_PARITY_NONE = 0U,
|
||||||
|
MCU_UART_PARITY_EVEN,
|
||||||
|
MCU_UART_PARITY_ODD
|
||||||
|
} MCU_UART_tenuParity;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
MCU_UART_DATA_BITS_5 = 5U,
|
||||||
|
MCU_UART_DATA_BITS_6 = 6U,
|
||||||
|
MCU_UART_DATA_BITS_7 = 7U,
|
||||||
|
MCU_UART_DATA_BITS_8 = 8U
|
||||||
|
} MCU_UART_tenuDataBits;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
MCU_UART_STOP_BITS_1 = 1U,
|
||||||
|
MCU_UART_STOP_BITS_2 = 2U
|
||||||
|
} MCU_UART_tenuStopBits;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
MCU_UART_ASYNC_DMA = 0U,
|
||||||
|
MCU_UART_ASYNC_ISR
|
||||||
|
} MCU_UART_tenuAsyncMode;
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* CONFIGURATION STRUCTURE */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
u8 u8TxPin;
|
||||||
|
u8 u8RxPin;
|
||||||
|
u32 u32BaudRate;
|
||||||
|
MCU_UART_tenuDataBits enuDataBits;
|
||||||
|
MCU_UART_tenuStopBits enuStopBits;
|
||||||
|
MCU_UART_tenuParity enuParity;
|
||||||
|
MCU_UART_tenuAsyncMode enuTxAsyncMode;
|
||||||
|
STD_tpfCallbackFunc pfTxCompleteCallback;
|
||||||
|
MCU_UART_tenuAsyncMode enuRxAsyncMode;
|
||||||
|
} MCU_UART_tstrConfig;
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* TX PUBLIC API */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
STD_tenuResult MCU_UART_enuInit(void);
|
||||||
|
|
||||||
|
STD_tenuResult MCU_UART_enuSendByte(u8 u8Instance, u8 u8Byte);
|
||||||
|
|
||||||
|
STD_tenuResult MCU_UART_enuSendBuffer(u8 u8Instance, const u8 *pu8Data, u16 u16Length);
|
||||||
|
|
||||||
|
STD_tenuResult MCU_UART_enuSendBufferBlocking(u8 u8Instance, const u8 *pu8Data, u16 u16Length);
|
||||||
|
|
||||||
|
STD_tBool MCU_UART_bIsTxBusy(u8 u8Instance);
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* RX PUBLIC API */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read one byte from the RX ring buffer (non-blocking).
|
||||||
|
*
|
||||||
|
* The ring buffer is filled in the background by ISR or DMA.
|
||||||
|
* Returns immediately — STD_OK with the byte, or STD_NOK if empty.
|
||||||
|
*
|
||||||
|
* @param u8Instance UART instance index.
|
||||||
|
* @param pu8Byte Pointer to store the received byte.
|
||||||
|
* @return STD_OK byte read,
|
||||||
|
* STD_NULL_POINTER_ERROR if pu8Byte is NULL,
|
||||||
|
* STD_NOK if ring buffer is empty.
|
||||||
|
*/
|
||||||
|
STD_tenuResult MCU_UART_enuReadByte(u8 u8Instance, u8 *pu8Byte);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read up to u16MaxLength bytes from the RX ring buffer (non-blocking).
|
||||||
|
*
|
||||||
|
* Copies as many bytes as are currently available (up to u16MaxLength)
|
||||||
|
* into pu8Data. Reports the actual number of bytes read via pu16Read.
|
||||||
|
* Returns immediately even if fewer than u16MaxLength bytes are available.
|
||||||
|
*
|
||||||
|
* @param u8Instance UART instance index.
|
||||||
|
* @param pu8Data Pointer to output buffer.
|
||||||
|
* @param u16MaxLength Maximum bytes to read.
|
||||||
|
* @param pu16Read Pointer to store actual bytes read. Must not be NULL.
|
||||||
|
* @return STD_OK at least one byte read,
|
||||||
|
* STD_NULL_POINTER_ERROR if pu8Data or pu16Read is NULL,
|
||||||
|
* STD_NOK if ring buffer is empty (zero bytes read).
|
||||||
|
*/
|
||||||
|
STD_tenuResult MCU_UART_enuReadBuffer(u8 u8Instance, u8 *pu8Data, u16 u16MaxLength, u16 *pu16Read);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if the RX ring buffer has data.
|
||||||
|
*
|
||||||
|
* @param u8Instance UART instance index.
|
||||||
|
* @return STD_TRUE if at least one byte available, STD_FALSE if empty.
|
||||||
|
*/
|
||||||
|
STD_tBool MCU_UART_bIsRxDataAvailable(u8 u8Instance);
|
||||||
|
|
||||||
|
#endif /* MCU_UART_H */
|
||||||
421
src/MCU_UART/prg/MCU_UART_prg.c
Normal file
421
src/MCU_UART/prg/MCU_UART_prg.c
Normal file
@ -0,0 +1,421 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* File: MCU_UART_prg.c
|
||||||
|
* Component: MCU_UART
|
||||||
|
* Description: TX: blocking + non-blocking (DMA or ISR) with callback.
|
||||||
|
* RX: ISR or DMA fills ring buffer in background. ReadByte
|
||||||
|
* and ReadBuffer read from the buffer non-blocking.
|
||||||
|
*
|
||||||
|
* Layer: MCU (hardware abstraction)
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
#include "MCU_UART.h"
|
||||||
|
#include "MCU_UART_priv.h"
|
||||||
|
#include "MCU_UART_cfg.h"
|
||||||
|
|
||||||
|
#include "hardware/uart.h"
|
||||||
|
#include "hardware/gpio.h"
|
||||||
|
#include "hardware/dma.h"
|
||||||
|
#include "hardware/irq.h"
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* INSTANCE LOOKUP TABLE */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
static uart_inst_t * const apstrInstances[] = { uart0, uart1 };
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* RUNTIME STATE */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
static MCU_UART_tstrControl strControl;
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* INTERNAL HELPERS */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
static void vCallTxCallback(u8 u8Instance)
|
||||||
|
{
|
||||||
|
STD_tpfCallbackFunc pfCbLoc = MCU_UART_astrConfig[u8Instance].pfTxCompleteCallback;
|
||||||
|
|
||||||
|
if (pfCbLoc != STD_NULL)
|
||||||
|
{
|
||||||
|
pfCbLoc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief Get current RX head. DMA mode derives from hw write pointer. */
|
||||||
|
static u16 u16GetRxHead(u8 u8Instance)
|
||||||
|
{
|
||||||
|
u16 u16HeadLoc;
|
||||||
|
|
||||||
|
if (MCU_UART_astrConfig[u8Instance].enuRxAsyncMode == MCU_UART_ASYNC_DMA)
|
||||||
|
{
|
||||||
|
s8 s8ChLoc = strControl.as8RxDmaChannel[u8Instance];
|
||||||
|
u32 u32WrLoc = (u32)dma_channel_hw_addr((u32)s8ChLoc)->write_addr;
|
||||||
|
u32 u32BaseLoc = (u32)(&strControl.aau8RxBuffer[u8Instance][0]);
|
||||||
|
u16HeadLoc = (u16)((u32WrLoc - u32BaseLoc) & MCU_UART_RX_BUFFER_MASK);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
u16HeadLoc = strControl.au16RxHead[u8Instance];
|
||||||
|
}
|
||||||
|
|
||||||
|
return u16HeadLoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* TX DMA IRQ HANDLER (INSTANCE 0) */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
static void vTxDmaIrqHandler0(void)
|
||||||
|
{
|
||||||
|
s8 s8ChLoc = strControl.as8TxDmaChannel[MCU_UART_INSTANCE_0];
|
||||||
|
u32 u32StatusLoc = dma_channel_get_irq0_status((u32)s8ChLoc);
|
||||||
|
|
||||||
|
if (u32StatusLoc != 0U)
|
||||||
|
{
|
||||||
|
dma_irqn_acknowledge_channel(0, (u32)s8ChLoc);
|
||||||
|
strControl.abTxBusy[MCU_UART_INSTANCE_0] = STD_FALSE;
|
||||||
|
vCallTxCallback((u8)MCU_UART_INSTANCE_0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* UART ISR HANDLER (RX + TX) */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
static void vUartIsrHandler(u8 u8Instance)
|
||||||
|
{
|
||||||
|
uart_inst_t *pstrUartLoc = apstrInstances[u8Instance];
|
||||||
|
|
||||||
|
/* --- RX: drain FIFO into ring buffer --- */
|
||||||
|
STD_tBool bReadableLoc = (uart_is_readable(pstrUartLoc) != 0) ? STD_TRUE : STD_FALSE;
|
||||||
|
|
||||||
|
while (bReadableLoc == STD_TRUE)
|
||||||
|
{
|
||||||
|
u8 u8ByteLoc = (u8)uart_getc(pstrUartLoc);
|
||||||
|
u16 u16HeadLoc = strControl.au16RxHead[u8Instance];
|
||||||
|
|
||||||
|
strControl.aau8RxBuffer[u8Instance][u16HeadLoc] = u8ByteLoc;
|
||||||
|
strControl.au16RxHead[u8Instance] = (u16HeadLoc + 1U) & MCU_UART_RX_BUFFER_MASK;
|
||||||
|
|
||||||
|
bReadableLoc = (uart_is_readable(pstrUartLoc) != 0) ? STD_TRUE : STD_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- TX: fill FIFO from buffer (if TX ISR active) --- */
|
||||||
|
STD_tBool bTxBusyLoc = strControl.abTxBusy[u8Instance];
|
||||||
|
|
||||||
|
if (bTxBusyLoc == STD_TRUE)
|
||||||
|
{
|
||||||
|
STD_tBool bFifoReady = (uart_is_writable(pstrUartLoc) != 0) ? STD_TRUE : STD_FALSE;
|
||||||
|
STD_tBool bDataLeft = (strControl.au16TxIndex[u8Instance] < strControl.au16TxLength[u8Instance]) ? STD_TRUE : STD_FALSE;
|
||||||
|
|
||||||
|
while ((bFifoReady == STD_TRUE) && (bDataLeft == STD_TRUE))
|
||||||
|
{
|
||||||
|
uart_putc_raw(pstrUartLoc,
|
||||||
|
strControl.apu8TxBuffer[u8Instance][strControl.au16TxIndex[u8Instance]]);
|
||||||
|
strControl.au16TxIndex[u8Instance]++;
|
||||||
|
|
||||||
|
bFifoReady = (uart_is_writable(pstrUartLoc) != 0) ? STD_TRUE : STD_FALSE;
|
||||||
|
bDataLeft = (strControl.au16TxIndex[u8Instance] < strControl.au16TxLength[u8Instance]) ? STD_TRUE : STD_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bDataLeft == STD_FALSE)
|
||||||
|
{
|
||||||
|
uart_set_irqs_enabled(pstrUartLoc, false, true);
|
||||||
|
strControl.abTxBusy[u8Instance] = STD_FALSE;
|
||||||
|
vCallTxCallback(u8Instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void vUart0IrqHandler(void)
|
||||||
|
{
|
||||||
|
vUartIsrHandler((u8)MCU_UART_INSTANCE_0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================================================================= */
|
||||||
|
/* INIT */
|
||||||
|
/* ========================================================================= */
|
||||||
|
|
||||||
|
STD_tenuResult MCU_UART_enuInit(void)
|
||||||
|
{
|
||||||
|
STD_tenuResult enuResultLoc = STD_OK;
|
||||||
|
u8 u8IndexLoc;
|
||||||
|
|
||||||
|
for (u8IndexLoc = 0U; u8IndexLoc < (u8)MCU_UART_NUM_INSTANCES; u8IndexLoc++)
|
||||||
|
{
|
||||||
|
strControl.apu8TxBuffer[u8IndexLoc] = STD_NULL;
|
||||||
|
strControl.au16TxLength[u8IndexLoc] = 0U;
|
||||||
|
strControl.au16TxIndex[u8IndexLoc] = 0U;
|
||||||
|
strControl.abTxBusy[u8IndexLoc] = STD_FALSE;
|
||||||
|
strControl.as8TxDmaChannel[u8IndexLoc] = -1;
|
||||||
|
strControl.au16RxHead[u8IndexLoc] = 0U;
|
||||||
|
strControl.au16RxTail[u8IndexLoc] = 0U;
|
||||||
|
strControl.as8RxDmaChannel[u8IndexLoc] = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (u8IndexLoc = 0U; u8IndexLoc < (u8)MCU_UART_NUM_INSTANCES; u8IndexLoc++)
|
||||||
|
{
|
||||||
|
const MCU_UART_tstrConfig *pstrCfgLoc = &MCU_UART_astrConfig[u8IndexLoc];
|
||||||
|
uart_inst_t *pstrUartLoc = apstrInstances[u8IndexLoc];
|
||||||
|
|
||||||
|
uart_init(pstrUartLoc, pstrCfgLoc->u32BaudRate);
|
||||||
|
gpio_set_function(pstrCfgLoc->u8TxPin, GPIO_FUNC_UART);
|
||||||
|
gpio_set_function(pstrCfgLoc->u8RxPin, GPIO_FUNC_UART);
|
||||||
|
uart_set_format(pstrUartLoc,
|
||||||
|
pstrCfgLoc->enuDataBits,
|
||||||
|
pstrCfgLoc->enuStopBits,
|
||||||
|
pstrCfgLoc->enuParity);
|
||||||
|
|
||||||
|
/* TX async setup */
|
||||||
|
if (pstrCfgLoc->enuTxAsyncMode == MCU_UART_ASYNC_DMA)
|
||||||
|
{
|
||||||
|
s8 s8ChLoc = (s8)dma_claim_unused_channel(true);
|
||||||
|
strControl.as8TxDmaChannel[u8IndexLoc] = s8ChLoc;
|
||||||
|
dma_channel_set_irq0_enabled((u32)s8ChLoc, true);
|
||||||
|
|
||||||
|
if (u8IndexLoc == (u8)MCU_UART_INSTANCE_0)
|
||||||
|
{
|
||||||
|
irq_set_exclusive_handler(DMA_IRQ_0, vTxDmaIrqHandler0);
|
||||||
|
irq_set_enabled(DMA_IRQ_0, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RX async setup */
|
||||||
|
if (pstrCfgLoc->enuRxAsyncMode == MCU_UART_ASYNC_DMA)
|
||||||
|
{
|
||||||
|
s8 s8RxChLoc = (s8)dma_claim_unused_channel(true);
|
||||||
|
strControl.as8RxDmaChannel[u8IndexLoc] = s8RxChLoc;
|
||||||
|
|
||||||
|
dma_channel_config strRxCfgLoc = dma_channel_get_default_config((u32)s8RxChLoc);
|
||||||
|
channel_config_set_transfer_data_size(&strRxCfgLoc, DMA_SIZE_8);
|
||||||
|
channel_config_set_read_increment(&strRxCfgLoc, false);
|
||||||
|
channel_config_set_write_increment(&strRxCfgLoc, true);
|
||||||
|
channel_config_set_dreq(&strRxCfgLoc, uart_get_dreq(pstrUartLoc, false));
|
||||||
|
channel_config_set_ring(&strRxCfgLoc, true, MCU_UART_RX_BUFFER_SIZE_BITS);
|
||||||
|
|
||||||
|
dma_channel_configure(
|
||||||
|
(u32)s8RxChLoc,
|
||||||
|
&strRxCfgLoc,
|
||||||
|
&strControl.aau8RxBuffer[u8IndexLoc][0],
|
||||||
|
&uart_get_hw(pstrUartLoc)->dr,
|
||||||
|
0xFFFFFFFFU,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (u8IndexLoc == (u8)MCU_UART_INSTANCE_0)
|
||||||
|
{
|
||||||
|
irq_set_exclusive_handler(UART0_IRQ, vUart0IrqHandler);
|
||||||
|
irq_set_enabled(UART0_IRQ, true);
|
||||||
|
}
|
||||||
|
uart_set_irqs_enabled(pstrUartLoc, false, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return enuResultLoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================================================================= */
|
||||||
|
/* SEND BYTE (BLOCKING) */
|
||||||
|
/* ========================================================================= */
|
||||||
|
|
||||||
|
STD_tenuResult MCU_UART_enuSendByte(u8 u8Instance, u8 u8Byte)
|
||||||
|
{
|
||||||
|
STD_tenuResult enuResultLoc = STD_OK;
|
||||||
|
|
||||||
|
uart_putc_raw(apstrInstances[u8Instance], u8Byte);
|
||||||
|
|
||||||
|
return enuResultLoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================================================================= */
|
||||||
|
/* SEND BUFFER (NON-BLOCKING) */
|
||||||
|
/* ========================================================================= */
|
||||||
|
|
||||||
|
STD_tenuResult MCU_UART_enuSendBuffer(u8 u8Instance, const u8 *pu8Data, u16 u16Length)
|
||||||
|
{
|
||||||
|
STD_tenuResult enuResultLoc = STD_OK;
|
||||||
|
STD_tBool bBusyLoc = strControl.abTxBusy[u8Instance];
|
||||||
|
MCU_UART_tenuAsyncMode enuModeLoc = MCU_UART_astrConfig[u8Instance].enuTxAsyncMode;
|
||||||
|
|
||||||
|
if (pu8Data == STD_NULL)
|
||||||
|
{
|
||||||
|
enuResultLoc = STD_NULL_POINTER_ERROR;
|
||||||
|
}
|
||||||
|
else if (bBusyLoc == STD_TRUE)
|
||||||
|
{
|
||||||
|
enuResultLoc = STD_NOK;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
strControl.apu8TxBuffer[u8Instance] = pu8Data;
|
||||||
|
strControl.au16TxLength[u8Instance] = u16Length;
|
||||||
|
strControl.au16TxIndex[u8Instance] = 0U;
|
||||||
|
strControl.abTxBusy[u8Instance] = STD_TRUE;
|
||||||
|
|
||||||
|
if (enuModeLoc == MCU_UART_ASYNC_DMA)
|
||||||
|
{
|
||||||
|
s8 s8ChLoc = strControl.as8TxDmaChannel[u8Instance];
|
||||||
|
uart_inst_t *pstrUartLoc = apstrInstances[u8Instance];
|
||||||
|
|
||||||
|
dma_channel_config strCfgLoc = dma_channel_get_default_config((u32)s8ChLoc);
|
||||||
|
channel_config_set_transfer_data_size(&strCfgLoc, DMA_SIZE_8);
|
||||||
|
channel_config_set_read_increment(&strCfgLoc, true);
|
||||||
|
channel_config_set_write_increment(&strCfgLoc, false);
|
||||||
|
channel_config_set_dreq(&strCfgLoc, uart_get_dreq(pstrUartLoc, true));
|
||||||
|
|
||||||
|
dma_channel_configure(
|
||||||
|
(u32)s8ChLoc, &strCfgLoc,
|
||||||
|
&uart_get_hw(pstrUartLoc)->dr, pu8Data, u16Length, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
uart_inst_t *pstrUartLoc = apstrInstances[u8Instance];
|
||||||
|
STD_tBool bFifoReady = (uart_is_writable(pstrUartLoc) != 0) ? STD_TRUE : STD_FALSE;
|
||||||
|
STD_tBool bDataLeft = (strControl.au16TxIndex[u8Instance] < u16Length) ? STD_TRUE : STD_FALSE;
|
||||||
|
|
||||||
|
while ((bFifoReady == STD_TRUE) && (bDataLeft == STD_TRUE))
|
||||||
|
{
|
||||||
|
uart_putc_raw(pstrUartLoc, pu8Data[strControl.au16TxIndex[u8Instance]]);
|
||||||
|
strControl.au16TxIndex[u8Instance]++;
|
||||||
|
bFifoReady = (uart_is_writable(pstrUartLoc) != 0) ? STD_TRUE : STD_FALSE;
|
||||||
|
bDataLeft = (strControl.au16TxIndex[u8Instance] < u16Length) ? STD_TRUE : STD_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bDataLeft == STD_FALSE)
|
||||||
|
{
|
||||||
|
strControl.abTxBusy[u8Instance] = STD_FALSE;
|
||||||
|
vCallTxCallback(u8Instance);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
uart_set_irqs_enabled(pstrUartLoc, true, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return enuResultLoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================================================================= */
|
||||||
|
/* SEND BUFFER (BLOCKING) */
|
||||||
|
/* ========================================================================= */
|
||||||
|
|
||||||
|
STD_tenuResult MCU_UART_enuSendBufferBlocking(u8 u8Instance, const u8 *pu8Data, u16 u16Length)
|
||||||
|
{
|
||||||
|
STD_tenuResult enuResultLoc = STD_OK;
|
||||||
|
u16 u16IndexLoc;
|
||||||
|
|
||||||
|
if (pu8Data == STD_NULL)
|
||||||
|
{
|
||||||
|
enuResultLoc = STD_NULL_POINTER_ERROR;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (u16IndexLoc = 0U; u16IndexLoc < u16Length; u16IndexLoc++)
|
||||||
|
{
|
||||||
|
uart_putc_raw(apstrInstances[u8Instance], pu8Data[u16IndexLoc]);
|
||||||
|
}
|
||||||
|
vCallTxCallback(u8Instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
return enuResultLoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================================================================= */
|
||||||
|
/* TX BUSY CHECK */
|
||||||
|
/* ========================================================================= */
|
||||||
|
|
||||||
|
STD_tBool MCU_UART_bIsTxBusy(u8 u8Instance)
|
||||||
|
{
|
||||||
|
return strControl.abTxBusy[u8Instance];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================================================================= */
|
||||||
|
/* READ BYTE (NON-BLOCKING) */
|
||||||
|
/* ========================================================================= */
|
||||||
|
|
||||||
|
STD_tenuResult MCU_UART_enuReadByte(u8 u8Instance, u8 *pu8Byte)
|
||||||
|
{
|
||||||
|
STD_tenuResult enuResultLoc = STD_OK;
|
||||||
|
|
||||||
|
if (pu8Byte == STD_NULL)
|
||||||
|
{
|
||||||
|
enuResultLoc = STD_NULL_POINTER_ERROR;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
u16 u16HeadLoc = u16GetRxHead(u8Instance);
|
||||||
|
u16 u16TailLoc = strControl.au16RxTail[u8Instance];
|
||||||
|
|
||||||
|
if (u16HeadLoc == u16TailLoc)
|
||||||
|
{
|
||||||
|
enuResultLoc = STD_NOK;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
*pu8Byte = strControl.aau8RxBuffer[u8Instance][u16TailLoc];
|
||||||
|
strControl.au16RxTail[u8Instance] = (u16TailLoc + 1U) & MCU_UART_RX_BUFFER_MASK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return enuResultLoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================================================================= */
|
||||||
|
/* READ BUFFER (NON-BLOCKING) */
|
||||||
|
/* ========================================================================= */
|
||||||
|
|
||||||
|
STD_tenuResult MCU_UART_enuReadBuffer(u8 u8Instance, u8 *pu8Data, u16 u16MaxLength, u16 *pu16Read)
|
||||||
|
{
|
||||||
|
STD_tenuResult enuResultLoc = STD_OK;
|
||||||
|
|
||||||
|
if ((pu8Data == STD_NULL) || (pu16Read == STD_NULL))
|
||||||
|
{
|
||||||
|
enuResultLoc = STD_NULL_POINTER_ERROR;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
u16 u16HeadLoc = u16GetRxHead(u8Instance);
|
||||||
|
u16 u16TailLoc = strControl.au16RxTail[u8Instance];
|
||||||
|
u16 u16CountLoc = 0U;
|
||||||
|
|
||||||
|
while ((u16HeadLoc != u16TailLoc) && (u16CountLoc < u16MaxLength))
|
||||||
|
{
|
||||||
|
pu8Data[u16CountLoc] = strControl.aau8RxBuffer[u8Instance][u16TailLoc];
|
||||||
|
u16TailLoc = (u16TailLoc + 1U) & MCU_UART_RX_BUFFER_MASK;
|
||||||
|
u16CountLoc++;
|
||||||
|
}
|
||||||
|
|
||||||
|
strControl.au16RxTail[u8Instance] = u16TailLoc;
|
||||||
|
*pu16Read = u16CountLoc;
|
||||||
|
|
||||||
|
if (u16CountLoc == 0U)
|
||||||
|
{
|
||||||
|
enuResultLoc = STD_NOK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return enuResultLoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================================================================= */
|
||||||
|
/* RX DATA AVAILABLE CHECK */
|
||||||
|
/* ========================================================================= */
|
||||||
|
|
||||||
|
STD_tBool MCU_UART_bIsRxDataAvailable(u8 u8Instance)
|
||||||
|
{
|
||||||
|
STD_tBool bResultLoc = STD_FALSE;
|
||||||
|
u16 u16HeadLoc = u16GetRxHead(u8Instance);
|
||||||
|
u16 u16TailLoc = strControl.au16RxTail[u8Instance];
|
||||||
|
|
||||||
|
if (u16HeadLoc != u16TailLoc)
|
||||||
|
{
|
||||||
|
bResultLoc = STD_TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bResultLoc;
|
||||||
|
}
|
||||||
34
src/MCU_UART/prg/MCU_UART_priv.h
Normal file
34
src/MCU_UART/prg/MCU_UART_priv.h
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* File: MCU_UART_priv.h
|
||||||
|
* Component: MCU_UART
|
||||||
|
* Description: Private header — control struct and extern config array.
|
||||||
|
*
|
||||||
|
* Layer: MCU (hardware abstraction) - internal use only
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
#ifndef MCU_UART_PRIV_H
|
||||||
|
#define MCU_UART_PRIV_H
|
||||||
|
|
||||||
|
#include "MCU_UART.h"
|
||||||
|
#include "MCU_UART_cfg.h"
|
||||||
|
|
||||||
|
extern const MCU_UART_tstrConfig MCU_UART_astrConfig[MCU_UART_NUM_INSTANCES];
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
/* TX state */
|
||||||
|
const u8 *apu8TxBuffer[MCU_UART_NUM_INSTANCES];
|
||||||
|
u16 au16TxLength[MCU_UART_NUM_INSTANCES];
|
||||||
|
u16 au16TxIndex[MCU_UART_NUM_INSTANCES];
|
||||||
|
STD_tBool abTxBusy[MCU_UART_NUM_INSTANCES];
|
||||||
|
s8 as8TxDmaChannel[MCU_UART_NUM_INSTANCES];
|
||||||
|
|
||||||
|
/* RX ring buffer — filled by ISR or DMA in the background */
|
||||||
|
u8 aau8RxBuffer[MCU_UART_NUM_INSTANCES][MCU_UART_RX_BUFFER_SIZE]
|
||||||
|
__attribute__((aligned(MCU_UART_RX_BUFFER_SIZE)));
|
||||||
|
u16 au16RxHead[MCU_UART_NUM_INSTANCES];
|
||||||
|
u16 au16RxTail[MCU_UART_NUM_INSTANCES];
|
||||||
|
s8 as8RxDmaChannel[MCU_UART_NUM_INSTANCES];
|
||||||
|
} MCU_UART_tstrControl;
|
||||||
|
|
||||||
|
#endif /* MCU_UART_PRIV_H */
|
||||||
14
src/MCU_USB/cfg/MCU_USB_cfg.c
Normal file
14
src/MCU_USB/cfg/MCU_USB_cfg.c
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* File: MCU_USB_cfg.c
|
||||||
|
* Component: MCU_USB
|
||||||
|
* Description: Configuration implementation for the MCU_USB driver.
|
||||||
|
* Holds the actual configuration values (timeouts, buffer
|
||||||
|
* sizes, mode flags) defined as constants or configuration
|
||||||
|
* structures consumed by MCU_USB_prg.c.
|
||||||
|
*
|
||||||
|
* Layer: MCU (hardware abstraction) - configuration
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
#include "MCU_USB_cfg.h"
|
||||||
|
|
||||||
|
/* Configuration definitions will go here */
|
||||||
86
src/MCU_USB/cfg/MCU_USB_cfg.h
Normal file
86
src/MCU_USB/cfg/MCU_USB_cfg.h
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* File: MCU_USB_cfg.h
|
||||||
|
* Component: MCU_USB
|
||||||
|
* Description: Configuration header for the MCU_USB driver.
|
||||||
|
* Declares configuration structures and constants that can be
|
||||||
|
* edited to adapt the USB-CDC driver to the application's
|
||||||
|
* needs (e.g., enable/disable connection wait, timeout values,
|
||||||
|
* transmit buffer sizes).
|
||||||
|
*
|
||||||
|
* Layer: MCU (hardware abstraction) - configuration
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
#ifndef MCU_USB_CFG_H
|
||||||
|
#define MCU_USB_CFG_H
|
||||||
|
|
||||||
|
/* STD_TYPES is needed for STD_TRUE / STD_FALSE and the u8/u16/u32 typedefs
|
||||||
|
* used by the config values and timeout comparisons below. */
|
||||||
|
#include "STD_TYPES.h"
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* CONNECTION / INIT BEHAVIOR */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Whether MCU_USB_enuInit() should block until the host has opened
|
||||||
|
* the CDC serial port before returning.
|
||||||
|
*
|
||||||
|
* Set to STD_TRUE to avoid losing the first bytes sent by the application
|
||||||
|
* (the host needs ~1-2 s after power-up to enumerate and open the port).
|
||||||
|
* Set to STD_FALSE for a fire-and-forget init that returns immediately -
|
||||||
|
* useful if the firmware must not stall when no host is attached.
|
||||||
|
*/
|
||||||
|
#define MCU_USB_WAIT_FOR_CONNECTION MCU_USB_WAIT_FOR_CONNECTION_ENABLED
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Maximum time (in milliseconds) to wait for the host to open
|
||||||
|
* the CDC serial port during MCU_USB_enuInit().
|
||||||
|
*
|
||||||
|
* Only applies when MCU_USB_WAIT_FOR_CONNECTION is ENABLED. USB host
|
||||||
|
* enumeration typically takes ~1-2 seconds, so 3000 ms gives a
|
||||||
|
* comfortable margin. Set to 0 for an infinite wait (never times out).
|
||||||
|
* This is separate from TRANSMIT/RECEIVE timeouts because connection
|
||||||
|
* setup is a one-time event with different timing characteristics
|
||||||
|
* than per-byte I/O operations.
|
||||||
|
*/
|
||||||
|
#define MCU_USB_CONNECTION_TIMEOUT_MS 3000U
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* TIMEOUTS */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Maximum time (in milliseconds) to wait for a single transmit
|
||||||
|
* operation to complete before returning STD_NOK / STD_TIMEOUT.
|
||||||
|
*
|
||||||
|
* Applied to each transmit call in MCU_USB_prg.c. Prevents the driver
|
||||||
|
* from blocking forever if the host-side serial port is closed mid-send
|
||||||
|
* or the USB bus becomes unresponsive.
|
||||||
|
*/
|
||||||
|
#define MCU_USB_TRANSMIT_TIMEOUT_MS 1000U
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Maximum time (in milliseconds) to wait for a byte to arrive on
|
||||||
|
* the receive side before returning a timeout result.
|
||||||
|
*
|
||||||
|
* Applied to each blocking receive call. Prevents the driver from hanging
|
||||||
|
* when the host is attached but simply not sending anything.
|
||||||
|
*/
|
||||||
|
#define MCU_USB_RECEIVE_TIMEOUT_MS 1000U
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* BUFFER SIZING */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Size (in bytes) of the software transmit buffer used by the
|
||||||
|
* USB driver to queue outbound data.
|
||||||
|
*
|
||||||
|
* A larger buffer lets the application call MCU_USB_enuSendBuffer with
|
||||||
|
* bigger chunks without having to wait for the USB peripheral to drain,
|
||||||
|
* at the cost of more SRAM. 64 bytes matches the USB Full-Speed bulk
|
||||||
|
* endpoint packet size, which is a convenient minimum for alignment.
|
||||||
|
*/
|
||||||
|
#define MCU_USB_TRANSMIT_BUFFER_SIZE 64U
|
||||||
|
|
||||||
|
#endif /* MCU_USB_CFG_H */
|
||||||
68
src/MCU_USB/inc/MCU_USB.h
Normal file
68
src/MCU_USB/inc/MCU_USB.h
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* File: MCU_USB.h
|
||||||
|
* Component: MCU_USB
|
||||||
|
* Description: Public interface for the MCU USB-CDC driver.
|
||||||
|
* TX: SendByte, SendBuffer (both fire-and-forget via putchar_raw).
|
||||||
|
* RX: background ring buffer drained lazily from the SDK's
|
||||||
|
* stdio layer. ReadByte / bIsRxDataAvailable read from it.
|
||||||
|
*
|
||||||
|
* Layer: MCU (hardware abstraction)
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
#ifndef MCU_USB_H
|
||||||
|
#define MCU_USB_H
|
||||||
|
|
||||||
|
#include "STD_TYPES.h"
|
||||||
|
|
||||||
|
#define MCU_USB_WAIT_FOR_CONNECTION_DISABLED 0U
|
||||||
|
#define MCU_USB_WAIT_FOR_CONNECTION_ENABLED 1U
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* TX PUBLIC API */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
STD_tenuResult MCU_USB_enuInit(void);
|
||||||
|
|
||||||
|
STD_tenuResult MCU_USB_enuSendByte(u8 u8Byte);
|
||||||
|
|
||||||
|
STD_tenuResult MCU_USB_enuSendBuffer(const u8 *pu8Data, u16 u16Length);
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* RX PUBLIC API */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read one byte from the USB RX ring buffer (non-blocking).
|
||||||
|
*
|
||||||
|
* Internally drains any pending bytes from the SDK's stdio layer into
|
||||||
|
* the ring buffer before checking. Returns immediately.
|
||||||
|
*
|
||||||
|
* @param pu8Byte Pointer to store the received byte.
|
||||||
|
* @return STD_OK byte read,
|
||||||
|
* STD_NULL_POINTER_ERROR if pu8Byte is NULL,
|
||||||
|
* STD_NOK if no data available.
|
||||||
|
*/
|
||||||
|
STD_tenuResult MCU_USB_enuReadByte(u8 *pu8Byte);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read up to u16MaxLength bytes from the USB RX ring buffer.
|
||||||
|
*
|
||||||
|
* @param pu8Data Output buffer.
|
||||||
|
* @param u16MaxLength Maximum bytes to read.
|
||||||
|
* @param pu16Read Actual bytes read.
|
||||||
|
* @return STD_OK at least one byte read,
|
||||||
|
* STD_NULL_POINTER_ERROR if pu8Data or pu16Read is NULL,
|
||||||
|
* STD_NOK if no data available.
|
||||||
|
*/
|
||||||
|
STD_tenuResult MCU_USB_enuReadBuffer(u8 *pu8Data, u16 u16MaxLength, u16 *pu16Read);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if USB RX data is available.
|
||||||
|
*
|
||||||
|
* Drains pending bytes from the SDK first, then checks the ring buffer.
|
||||||
|
*
|
||||||
|
* @return STD_TRUE if data available, STD_FALSE if empty.
|
||||||
|
*/
|
||||||
|
STD_tBool MCU_USB_bIsRxDataAvailable(void);
|
||||||
|
|
||||||
|
#endif /* MCU_USB_H */
|
||||||
229
src/MCU_USB/prg/MCU_USB_prg.c
Normal file
229
src/MCU_USB/prg/MCU_USB_prg.c
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* File: MCU_USB_prg.c
|
||||||
|
* Component: MCU_USB
|
||||||
|
* Description: USB-CDC driver. TX via putchar_raw (fire-and-forget).
|
||||||
|
* RX via internal ring buffer, lazily drained from the SDK's
|
||||||
|
* stdio layer on every ReadByte / ReadBuffer / IsDataAvailable
|
||||||
|
* call. No separate RX task or interrupt needed — the SDK's
|
||||||
|
* TinyUSB background task fills stdio internally, and we pull
|
||||||
|
* from stdio into our ring buffer on demand.
|
||||||
|
*
|
||||||
|
* Layer: MCU (hardware abstraction)
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
#include "STD_TYPES.h"
|
||||||
|
|
||||||
|
#include "pico/stdio_usb.h"
|
||||||
|
#include "pico/stdio.h"
|
||||||
|
#include "pico/time.h"
|
||||||
|
|
||||||
|
#include "MCU_USB.h"
|
||||||
|
#include "MCU_USB_priv.h"
|
||||||
|
#include "MCU_USB_cfg.h"
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/* RX RING BUFFER */
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/** @brief RX ring buffer size — must be power of 2. */
|
||||||
|
#define USB_RX_BUFFER_SIZE_BITS 6U
|
||||||
|
#define USB_RX_BUFFER_SIZE (1U << USB_RX_BUFFER_SIZE_BITS)
|
||||||
|
#define USB_RX_BUFFER_MASK (USB_RX_BUFFER_SIZE - 1U)
|
||||||
|
|
||||||
|
static u8 au8RxBuffer[USB_RX_BUFFER_SIZE];
|
||||||
|
static u16 u16RxHead = 0U;
|
||||||
|
static u16 u16RxTail = 0U;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Drain any available bytes from the SDK's stdio into our ring buffer.
|
||||||
|
*
|
||||||
|
* Called lazily from ReadByte, ReadBuffer, and bIsRxDataAvailable.
|
||||||
|
* getchar_timeout_us(0) is non-blocking — returns PICO_ERROR_TIMEOUT (-1)
|
||||||
|
* immediately if no data. We keep pulling until the SDK has nothing left
|
||||||
|
* or our ring buffer is full.
|
||||||
|
*/
|
||||||
|
static void vDrainStdio(void)
|
||||||
|
{
|
||||||
|
s32 s32ByteLoc;
|
||||||
|
u16 u16NextHeadLoc;
|
||||||
|
|
||||||
|
s32ByteLoc = (s32)getchar_timeout_us(0);
|
||||||
|
|
||||||
|
while (s32ByteLoc >= 0)
|
||||||
|
{
|
||||||
|
u16NextHeadLoc = (u16RxHead + 1U) & USB_RX_BUFFER_MASK;
|
||||||
|
|
||||||
|
/* If the ring buffer is full, stop draining (oldest data preserved,
|
||||||
|
* newest data from SDK is lost). Caller should read faster. */
|
||||||
|
if (u16NextHeadLoc == u16RxTail)
|
||||||
|
{
|
||||||
|
/* Buffer full — can't store this byte. Break out. */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
au8RxBuffer[u16RxHead] = (u8)s32ByteLoc;
|
||||||
|
u16RxHead = u16NextHeadLoc;
|
||||||
|
|
||||||
|
s32ByteLoc = (s32)getchar_timeout_us(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================================================================= */
|
||||||
|
/* INIT */
|
||||||
|
/* ========================================================================= */
|
||||||
|
|
||||||
|
STD_tenuResult MCU_USB_enuInit(void)
|
||||||
|
{
|
||||||
|
STD_tenuResult enuResultLoc = STD_OK;
|
||||||
|
STD_tBool bSdkInitSuccess = STD_FALSE;
|
||||||
|
|
||||||
|
bSdkInitSuccess = (stdio_usb_init() != 0) ? STD_TRUE : STD_FALSE;
|
||||||
|
|
||||||
|
if (bSdkInitSuccess == STD_FALSE)
|
||||||
|
{
|
||||||
|
enuResultLoc = STD_NOK;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
#if MCU_USB_WAIT_FOR_CONNECTION == MCU_USB_WAIT_FOR_CONNECTION_ENABLED
|
||||||
|
absolute_time_t absTimeout = make_timeout_time_ms(MCU_USB_CONNECTION_TIMEOUT_MS);
|
||||||
|
STD_tBool bHostOpen = STD_FALSE;
|
||||||
|
STD_tBool bTimeoutReached = STD_FALSE;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
sleep_ms(10);
|
||||||
|
bHostOpen = (stdio_usb_connected() != 0) ? STD_TRUE : STD_FALSE;
|
||||||
|
bTimeoutReached = (time_reached(absTimeout) != 0) ? STD_TRUE : STD_FALSE;
|
||||||
|
} while ((bHostOpen == STD_FALSE) && (bTimeoutReached == STD_FALSE));
|
||||||
|
|
||||||
|
if (bHostOpen == STD_FALSE)
|
||||||
|
{
|
||||||
|
enuResultLoc = STD_NOK;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
return enuResultLoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================================================================= */
|
||||||
|
/* SEND BYTE */
|
||||||
|
/* ========================================================================= */
|
||||||
|
|
||||||
|
STD_tenuResult MCU_USB_enuSendByte(u8 u8Byte)
|
||||||
|
{
|
||||||
|
STD_tenuResult enuResultLoc = STD_OK;
|
||||||
|
|
||||||
|
putchar_raw(u8Byte);
|
||||||
|
|
||||||
|
return enuResultLoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================================================================= */
|
||||||
|
/* SEND BUFFER */
|
||||||
|
/* ========================================================================= */
|
||||||
|
|
||||||
|
STD_tenuResult MCU_USB_enuSendBuffer(const u8 *pu8Data, u16 u16Length)
|
||||||
|
{
|
||||||
|
STD_tenuResult enuResultLoc = STD_OK;
|
||||||
|
u16 u16IndexLoc;
|
||||||
|
|
||||||
|
if (pu8Data == STD_NULL)
|
||||||
|
{
|
||||||
|
enuResultLoc = STD_NULL_POINTER_ERROR;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (u16IndexLoc = 0U; u16IndexLoc < u16Length; u16IndexLoc++)
|
||||||
|
{
|
||||||
|
putchar_raw(pu8Data[u16IndexLoc]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return enuResultLoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================================================================= */
|
||||||
|
/* READ BYTE (NON-BLOCKING) */
|
||||||
|
/* ========================================================================= */
|
||||||
|
|
||||||
|
STD_tenuResult MCU_USB_enuReadByte(u8 *pu8Byte)
|
||||||
|
{
|
||||||
|
STD_tenuResult enuResultLoc = STD_OK;
|
||||||
|
|
||||||
|
if (pu8Byte == STD_NULL)
|
||||||
|
{
|
||||||
|
enuResultLoc = STD_NULL_POINTER_ERROR;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Pull any pending data from SDK into our ring buffer */
|
||||||
|
vDrainStdio();
|
||||||
|
|
||||||
|
if (u16RxHead == u16RxTail)
|
||||||
|
{
|
||||||
|
enuResultLoc = STD_NOK;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
*pu8Byte = au8RxBuffer[u16RxTail];
|
||||||
|
u16RxTail = (u16RxTail + 1U) & USB_RX_BUFFER_MASK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return enuResultLoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================================================================= */
|
||||||
|
/* READ BUFFER (NON-BLOCKING) */
|
||||||
|
/* ========================================================================= */
|
||||||
|
|
||||||
|
STD_tenuResult MCU_USB_enuReadBuffer(u8 *pu8Data, u16 u16MaxLength, u16 *pu16Read)
|
||||||
|
{
|
||||||
|
STD_tenuResult enuResultLoc = STD_OK;
|
||||||
|
|
||||||
|
if ((pu8Data == STD_NULL) || (pu16Read == STD_NULL))
|
||||||
|
{
|
||||||
|
enuResultLoc = STD_NULL_POINTER_ERROR;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
vDrainStdio();
|
||||||
|
|
||||||
|
u16 u16CountLoc = 0U;
|
||||||
|
|
||||||
|
while ((u16RxHead != u16RxTail) && (u16CountLoc < u16MaxLength))
|
||||||
|
{
|
||||||
|
pu8Data[u16CountLoc] = au8RxBuffer[u16RxTail];
|
||||||
|
u16RxTail = (u16RxTail + 1U) & USB_RX_BUFFER_MASK;
|
||||||
|
u16CountLoc++;
|
||||||
|
}
|
||||||
|
|
||||||
|
*pu16Read = u16CountLoc;
|
||||||
|
|
||||||
|
if (u16CountLoc == 0U)
|
||||||
|
{
|
||||||
|
enuResultLoc = STD_NOK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return enuResultLoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================================================================= */
|
||||||
|
/* RX DATA AVAILABLE CHECK */
|
||||||
|
/* ========================================================================= */
|
||||||
|
|
||||||
|
STD_tBool MCU_USB_bIsRxDataAvailable(void)
|
||||||
|
{
|
||||||
|
STD_tBool bResultLoc = STD_FALSE;
|
||||||
|
|
||||||
|
vDrainStdio();
|
||||||
|
|
||||||
|
if (u16RxHead != u16RxTail)
|
||||||
|
{
|
||||||
|
bResultLoc = STD_TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bResultLoc;
|
||||||
|
}
|
||||||
18
src/MCU_USB/prg/MCU_USB_priv.h
Normal file
18
src/MCU_USB/prg/MCU_USB_priv.h
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* File: MCU_USB_priv.h
|
||||||
|
* Component: MCU_USB
|
||||||
|
* Description: Private header for the MCU_USB driver.
|
||||||
|
* Contains internal macros, helper declarations, and any
|
||||||
|
* lower-level definitions that are only used inside this
|
||||||
|
* component itself. Nothing declared here is exposed to
|
||||||
|
* external components.
|
||||||
|
*
|
||||||
|
* Layer: MCU (hardware abstraction) - internal use only
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
#ifndef MCU_USB_PRIV_H
|
||||||
|
#define MCU_USB_PRIV_H
|
||||||
|
|
||||||
|
/* Private declarations, internal macros and helpers will go here */
|
||||||
|
|
||||||
|
#endif /* MCU_USB_PRIV_H */
|
||||||
288
src/STD_TYPES/inc/STD_TYPES.h
Normal file
288
src/STD_TYPES/inc/STD_TYPES.h
Normal file
@ -0,0 +1,288 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* File: STD_TYPES.h
|
||||||
|
* Component: STD_TYPES
|
||||||
|
* Description: Standard type definitions used across all components.
|
||||||
|
* Provides project-wide fixed-width integer typedefs, boolean
|
||||||
|
* types, and common return/status codes so that every module
|
||||||
|
* speaks the same "type language".
|
||||||
|
*
|
||||||
|
* Layer: Library (shared by all layers)
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
#ifndef STD_TYPES_H
|
||||||
|
#define STD_TYPES_H
|
||||||
|
|
||||||
|
/* Standard type definitions will go here */
|
||||||
|
|
||||||
|
/* ************************************************************************** */
|
||||||
|
/* ********************** PUBLIC TYPE DEFINITIONS **************************** */
|
||||||
|
/* ************************************************************************** */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @addtogroup 1-LIB_TYP_Types
|
||||||
|
* @ingroup LIB_TYP
|
||||||
|
* @{
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* ************************************************************************** */
|
||||||
|
/* ************************ BASIC INTEGER TYPES ****************************** */
|
||||||
|
/* ************************************************************************** */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 8-bit unsigned integer type
|
||||||
|
* @details Size: 1 Byte, Range: [0 : 255]
|
||||||
|
*/
|
||||||
|
typedef unsigned char u8;
|
||||||
|
#define STD_u8MIN_VALUE ((u8)0) /**< Minimum value for u8 type */
|
||||||
|
#define STD_u8MAX_VALUE ((u8)0xFF) /**< Maximum value for u8 type */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 16-bit unsigned integer type
|
||||||
|
* @details Size: 2 Bytes, Range: [0 : 65535]
|
||||||
|
*/
|
||||||
|
typedef unsigned short int u16;
|
||||||
|
#define STD_u16MIN_VALUE ((u16)0) /**< Minimum value for u16 type */
|
||||||
|
#define STD_u16MAX_VALUE ((u16)0xFFFFU) /**< Maximum value for u16 type */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 32-bit unsigned integer type
|
||||||
|
* @details Size: 4 Bytes, Range: [0 : 4,294,967,295]
|
||||||
|
*/
|
||||||
|
typedef unsigned long int u32;
|
||||||
|
#define STD_u32MIN_VALUE ((u32)0) /**< Minimum value for u32 type */
|
||||||
|
#define STD_u32MAX_VALUE ((u32)0xFFFFFFFFU) /**< Maximum value for u32 type */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 64-bit unsigned integer type
|
||||||
|
* @details Size: 8 Bytes, Range: [0 : 18,446,744,073,709,551,615]
|
||||||
|
*/
|
||||||
|
typedef unsigned long long u64;
|
||||||
|
#define STD_u64MIN_VALUE ((u64)0) /**< Minimum value for u64 type */
|
||||||
|
#define STD_u64MAX_VALUE ((u64)0xFFFFFFFFFFFFFFFFULL) /**< Maximum value for u64 type */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 8-bit signed integer type
|
||||||
|
* @details Size: 1 Byte, Range: [-128 : 127]
|
||||||
|
*/
|
||||||
|
typedef signed char s8;
|
||||||
|
#define STD_s8MIN_VALUE ((s8)-128) /**< Minimum value for s8 type */
|
||||||
|
#define STD_s8MAX_VALUE ((s8)127) /**< Maximum value for s8 type */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 16-bit signed integer type
|
||||||
|
* @details Size: 2 Bytes, Range: [-32,768 : 32,767]
|
||||||
|
*/
|
||||||
|
typedef signed short int s16;
|
||||||
|
#define STD_s16MIN_VALUE ((s16)-32768) /**< Minimum value for s16 type */
|
||||||
|
#define STD_s16MAX_VALUE ((s16)32767) /**< Maximum value for s16 type */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 32-bit signed integer type
|
||||||
|
* @details Size: 4 Bytes, Range: [-2,147,483,648 : 2,147,483,647]
|
||||||
|
*/
|
||||||
|
typedef signed long int s32;
|
||||||
|
#define STD_s32MIN_VALUE ((s32)-2147483648) /**< Minimum value for s32 type */
|
||||||
|
#define STD_s32MAX_VALUE ((s32)2147483647) /**< Maximum value for s32 type */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 64-bit signed integer type
|
||||||
|
* @details Size: 8 Bytes, Range: [-9,223,372,036,854,775,808 : 9,223,372,036,854,775,807]
|
||||||
|
*/
|
||||||
|
typedef signed long long s64;
|
||||||
|
#define STD_s64MIN_VALUE ((s64)-9223372036854775807LL - 1LL) /**< Minimum value for s64 type */
|
||||||
|
#define STD_s64MAX_VALUE ((s64)9223372036854775807LL) /**< Maximum value for s64 type */
|
||||||
|
|
||||||
|
/* ************************************************************************** */
|
||||||
|
/* ************************* RESULT TYPES *********************************** */
|
||||||
|
/* ************************************************************************** */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Standard Result Type
|
||||||
|
*
|
||||||
|
* Enumeration for function return values indicating operation status
|
||||||
|
*/
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
STD_OK = 0U, /**< Operation completed successfully */
|
||||||
|
STD_INDEX_OUT_OF_RANGE_ERROR, /**< Array index out of bounds */
|
||||||
|
STD_NULL_POINTER_ERROR, /**< Null pointer detected */
|
||||||
|
STD_NOK /**< Operation failed */
|
||||||
|
} STD_tenuResult;
|
||||||
|
|
||||||
|
/* ************************************************************************** */
|
||||||
|
/* ************************ CALLBACK TYPES ********************************** */
|
||||||
|
/* ************************************************************************** */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Callback Function Type
|
||||||
|
* @details Type definition for void callback functions
|
||||||
|
*/
|
||||||
|
typedef void (*STD_tpfCallbackFunc)(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialization Function Type
|
||||||
|
* @details Type definition for initialization functions returning STD_tenuResult
|
||||||
|
*/
|
||||||
|
typedef STD_tenuResult (*STD_tpfInitFunc)(void);
|
||||||
|
|
||||||
|
/* ************************************************************************** */
|
||||||
|
/* ************************ BOOLEAN AND STATE TYPES ************************* */
|
||||||
|
/* ************************************************************************** */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Boolean Type
|
||||||
|
* @details Standard boolean enumeration
|
||||||
|
*/
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
STD_FALSE, /**< False value */
|
||||||
|
STD_TRUE /**< True value */
|
||||||
|
} STD_tBool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief State Type
|
||||||
|
* @details Standard state enumeration
|
||||||
|
*/
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
STD_IDLE, /**< Idle state */
|
||||||
|
STD_BUSY /**< Busy state */
|
||||||
|
} STD_tenuState;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Compare State Type
|
||||||
|
* @details Type for comparison results
|
||||||
|
*/
|
||||||
|
typedef u8 STD_tu8CMPstate;
|
||||||
|
|
||||||
|
/** @} */ // end of 1-LIB_TYP_Types group
|
||||||
|
|
||||||
|
|
||||||
|
/* ************************************************************************** */
|
||||||
|
/* ************************* UTILITY MACROS ********************************* */
|
||||||
|
/* ************************************************************************** */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @addtogroup 2-LIB_TYP_Macros
|
||||||
|
* @ingroup LIB_TYP
|
||||||
|
* @{
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @brief Null pointer definition */
|
||||||
|
#define STD_NULL ((void *)0)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constant qualifier macro
|
||||||
|
* @details Removes const qualifier in unit test builds
|
||||||
|
*/
|
||||||
|
#ifdef UTD
|
||||||
|
# define STD_CONST
|
||||||
|
#else
|
||||||
|
# define STD_CONST const
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Static qualifier macro
|
||||||
|
* @details Removes static qualifier in unit test builds
|
||||||
|
*/
|
||||||
|
#ifdef UTD
|
||||||
|
# define STD_STATIC
|
||||||
|
#else
|
||||||
|
# define STD_STATIC static
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Inline function macro
|
||||||
|
* @details Controls function inlining based on build type
|
||||||
|
*/
|
||||||
|
#ifdef UTD
|
||||||
|
# define STD_INLINE
|
||||||
|
#else
|
||||||
|
# define STD_INLINE __attribute__((always_inline)) inline
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Interrupt function macro
|
||||||
|
* @details Marks functions as interrupts in non-test builds
|
||||||
|
*/
|
||||||
|
#ifdef UTD
|
||||||
|
# define STD_INTERRUPT
|
||||||
|
#else
|
||||||
|
# define STD_INTERRUPT __attribute__((interrupt))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Non-inlined interrupt function macro
|
||||||
|
* @details Marks functions as non-inlined interrupts in non-test builds. This prevents
|
||||||
|
* the compiler from inlining interrupt service routines, which can be important
|
||||||
|
* for debugging and maintaining consistent interrupt latency.
|
||||||
|
*/
|
||||||
|
#ifdef UTD
|
||||||
|
# define STD_INTERRUPT_NO_INLINE
|
||||||
|
#else
|
||||||
|
# define STD_INTERRUPT_NO_INLINE __attribute__((interrupt, noinline))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Weak symbol macro
|
||||||
|
* @details Marks symbols as weak in non-test builds
|
||||||
|
*/
|
||||||
|
#ifdef UTD
|
||||||
|
# define STD_WEAK
|
||||||
|
#else
|
||||||
|
# define STD_WEAK __attribute__((weak))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Packed structure macro
|
||||||
|
* @details Forces packed memory layout
|
||||||
|
*/
|
||||||
|
#define STD_PACKED __attribute__((packed))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 2-byte alignment macro
|
||||||
|
* @details Forces 2-byte alignment for structures and variables.
|
||||||
|
* Required for MLX16 architecture DMA buffers and optimal performance.
|
||||||
|
*/
|
||||||
|
#define STD_ALIGNED_2BYTE __attribute__((aligned(2)))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Switch case fallthrough macro
|
||||||
|
* @details Explicitly indicates intentional fallthrough in switch statements.
|
||||||
|
* Suppresses compiler warnings about implicit fallthrough between cases.
|
||||||
|
* Use this when you intentionally omit a break statement.
|
||||||
|
* @note Only available in GCC 7 and later
|
||||||
|
*/
|
||||||
|
#if __GNUC__ >= 7
|
||||||
|
#define STD_FALLTHROUGH __attribute__((fallthrough))
|
||||||
|
#else
|
||||||
|
#define STD_FALLTHROUGH
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Static assertion macro
|
||||||
|
* @details Provides compile-time assertions for configuration validation.
|
||||||
|
* This macro abstracts the compiler-specific static assert mechanism
|
||||||
|
* allowing for easy portability across different compilers.
|
||||||
|
*
|
||||||
|
* @param condition The condition to assert (must be compile-time constant)
|
||||||
|
* @param message The error message to display if assertion fails
|
||||||
|
*
|
||||||
|
* @note For C11 compliant compilers, uses _Static_assert.
|
||||||
|
* For older compilers, falls back to a compatible implementation.
|
||||||
|
*/
|
||||||
|
#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)
|
||||||
|
/* C11 and later: use standard _Static_assert */
|
||||||
|
#define STD_STATIC_ASSERT(condition, message) _Static_assert(condition, message)
|
||||||
|
#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
|
||||||
|
/* GCC 4.6+: use _Static_assert extension */
|
||||||
|
#define STD_STATIC_ASSERT(condition, message) _Static_assert(condition, message)
|
||||||
|
#else
|
||||||
|
/* Fallback for older compilers: use array size trick */
|
||||||
|
#define STD_STATIC_ASSERT(condition, message) \
|
||||||
|
typedef char static_assertion_##__LINE__[(condition) ? 1 : -1]
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/** @} */ // end of 2-LIB_TYP_Macros group
|
||||||
|
|
||||||
|
#endif /* STD_TYPES_H */
|
||||||
0
src/SYS_ECU/cfg/SYS_ECU_cfg
Normal file
0
src/SYS_ECU/cfg/SYS_ECU_cfg
Normal file
0
src/SYS_ECU/inc/SYS_ECU.h
Normal file
0
src/SYS_ECU/inc/SYS_ECU.h
Normal file
70
src/SYS_ECU/prg/SYS_ECU.c
Normal file
70
src/SYS_ECU/prg/SYS_ECU.c
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* File: SYS_ECU.c
|
||||||
|
* Component: SYS_ECU
|
||||||
|
* Description: System ECU entry point and main application loop.
|
||||||
|
* This is the top-level orchestrator for the firmware: it owns
|
||||||
|
* the initialization sequence (calling each component's enuInit
|
||||||
|
* in dependency order) and the main super-loop scheduler that
|
||||||
|
* dispatches runnables at the configured cadence.
|
||||||
|
*
|
||||||
|
* SYS_ECU never contains application logic itself - it only
|
||||||
|
* calls into the components that do.
|
||||||
|
*
|
||||||
|
* Layer: System (top of the stack)
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
#include "STD_TYPES.h"
|
||||||
|
#include "pico/stdlib.h" /* sleep_ms */
|
||||||
|
|
||||||
|
/* Components initialized and scheduled by SYS_ECU, listed in
|
||||||
|
* dependency order (drivers first, then HAL, then application). */
|
||||||
|
#include "MCU_USB.h"
|
||||||
|
#include "MCU_PIO.h"
|
||||||
|
#include "HAL_COM.h"
|
||||||
|
#include "HAL_LED.h"
|
||||||
|
#include "APP_CLSW.h"
|
||||||
|
|
||||||
|
/* ========================================================================= */
|
||||||
|
/* INITIALIZATION SEQUENCE */
|
||||||
|
/* ========================================================================= */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize all components in dependency order.
|
||||||
|
*
|
||||||
|
* Each component's enuInit only sets up its own internal state.
|
||||||
|
* The call order matters: MCU drivers first (they talk to hardware),
|
||||||
|
* then HAL (depends on drivers), then application (depends on HAL).
|
||||||
|
*/
|
||||||
|
static void SYS_ECU_vInitAll(void)
|
||||||
|
{
|
||||||
|
/* MCU layer - hardware drivers */
|
||||||
|
MCU_USB_enuInit();
|
||||||
|
MCU_PIO_enuInit();
|
||||||
|
|
||||||
|
/* HAL layer - abstractions over hardware */
|
||||||
|
HAL_COM_enuInit();
|
||||||
|
HAL_LED_enuInit();
|
||||||
|
|
||||||
|
/* Application layer */
|
||||||
|
APP_CLSW_enuInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================================================================= */
|
||||||
|
/* MAIN ENTRY */
|
||||||
|
/* ========================================================================= */
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
/* Phase 1: bring up every component in the correct order */
|
||||||
|
SYS_ECU_vInitAll();
|
||||||
|
|
||||||
|
/* Phase 2: super-loop scheduler - dispatches runnables each tick.
|
||||||
|
* 10 ms tick rate gives responsive interactive input while keeping
|
||||||
|
* CPU usage reasonable. Will eventually be replaced by a proper
|
||||||
|
* timer-driven scheduler. */
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
APP_CLSW_vRunnable();
|
||||||
|
sleep_ms(10);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user