{"id":18694458,"url":"https://github.com/luickk/minimalroboticsplatform","last_synced_at":"2025-04-12T07:10:45.501Z","repository":{"id":50333302,"uuid":"518785779","full_name":"luickk/MinimalRoboticsPlatform","owner":"luickk","description":"MRP is a minimal microkernel that supports the most fundamental robotic domains. It's thought for highly integrated robotics development.  ","archived":false,"fork":false,"pushed_at":"2023-12-06T12:19:19.000Z","size":6713,"stargazers_count":11,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-10T20:25:24.328Z","etag":null,"topics":["arm","bare-metal","embedded","raspberry-pi-3","robotics","zig"],"latest_commit_sha":null,"homepage":"","language":"Zig","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/luickk.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-07-28T09:39:59.000Z","updated_at":"2025-03-29T02:08:57.000Z","dependencies_parsed_at":"2023-12-06T13:40:54.882Z","dependency_job_id":null,"html_url":"https://github.com/luickk/MinimalRoboticsPlatform","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/luickk%2FMinimalRoboticsPlatform","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/luickk%2FMinimalRoboticsPlatform/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/luickk%2FMinimalRoboticsPlatform/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/luickk%2FMinimalRoboticsPlatform/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/luickk","download_url":"https://codeload.github.com/luickk/MinimalRoboticsPlatform/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248530575,"owners_count":21119600,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["arm","bare-metal","embedded","raspberry-pi-3","robotics","zig"],"created_at":"2024-11-07T11:10:53.087Z","updated_at":"2025-04-12T07:10:45.473Z","avatar_url":"https://github.com/luickk.png","language":"Zig","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Embedded Robotics Kernel\n\n## Goal\n\nThe goal is to build a minimalistic robotic platform for embedded projects. The idea is to enable applications to run on this kernel with std support as well as a kernel-provided, robotics-specific toolset. Such a toolset includes communication, control, state handling, and other critical robotic domains. This would enable an ultra-light, simplistic and highly integrated robotic platform.\nThe whole project is designed to support multiple boards, as for example a Raspberry Pi or a NVIDIA Jetson Nano. To begin with, basic kernel features are implemented on a virtual machine (qemu virt armv7).\nThe end product is meant to be a compromise between a Real Time Operating system and a Microcontroller, to offer the best of both worlds on a modern Soc.\n\nThe idea is that the kernel and its drivers are fixed and generically usable across Arm Socs. The actual user implementation of the projects is meant to happen in `src/environment/*yourCustomEnvironmentName*` where every new dir is a new env. Every environment is built from separately compiled user/ kernel privileged apps. The apps can talk to each other with a variety of kernel provided interfaces, such as topics, services, actions and so on. Since every app is compiled on its own and completely isolated by the kernel, regarding their communications, which is compile time defined, maximum static runtime safety and security should be given.\nThanks to Zigs lazy compilation, driver handlers can be implemented and not be used or replaced, depending on the choice of board.\n\nThis project is aiming to build an experience that gives the end user (developer) as much guidance and form as necessary, to build a safe and secure platform, with as much freedom as possible. This is achieved by reducing complex runtime defined communications and allocations to an absolute minimum, whilst also being flexible enough to be used across a number of boards.\n\n## Why not Rust?\n\nI began this project in Rust but decided to switch to Zig (equally modern). Here is why.\nThe prime argument for Rust is safety, which is also important for embedded development but has a different nature. The thing is that I very rarely (wrote) saw embedded code that really made use (at least to an extent to which it would be relevant) of Rusts safety. This is due to the fact that embedded code is mostly procedural and linear and not overly complex (opposing to higher level code). Zig on the other hand, is a real improvement compared to Rust because it does not try to solve the problem through abstraction but concepts and rules. I really tried rust at the start of this project. That lead me to this conclusion.\n\nThe Rust code can still be found in the separate [rust branch](https://github.com/luickk/rust-rtos/tree/rust_code) and includes a proper Cargo build process(without making use of an external build tools) for the Raspberry, as well as basic serial(with print! macro implementation) and interrupt controller utils.\n\n## Finding the perfect Board \n\nIn order to first boot the kernel on a physical board, I'm searching for the best board. Number one priority is simplicity. The raspberry has a relatively complex multi bootstage process. That is not ideal, includes a file system on an SD Card in is pretty ugly in general.\nThe Jetson Nano has a similarly complex boot process. \n\nThe Rock Pi on the other hand offer eMMC storage that can be flashed with the Maskrom directly from another device. The Rock Pi eMMC is quite elegant because it does, a) not require a file system, and b) is loaded directly by the arm cores(and not from the GPU as with the raspberry).\n\n## Compatibility\n\nCurrently, there is support for the important Arm SOC elements such as the generic timer, interrupt controller as well as the Raspberries BCM2835 secondary interrupt controller and system timer. The project can be configured with ROM relocation and without, so most Arm SOC boards should be compatible at the moment.\n\n## Allocation Policy\n\nMemory allocation is an extremely powerful and basic functionality that can be very dangerous depending on when and how it's used.\nFor that reason the kernels allocations are only permitted at kernel boot/init time. There is no realloc, neither for userspace apps nor for the kernel. Alternatively, there are reserved memory buffers for every feature. I don't yet have a perfect solution for dealing with an out off memory event though.\n\nThere is an app allocation available in user space so that a considered decision can be made and an allocator still be used if the app is not important.\n\n### Allocation Projection\n\n// todo =\u003e alloc projection\nSince there are no allocations at actual runtime (after the kernel init), a projection of the required (allocated) memory can be made, including spare reserves. That would not only give indications for Ram compatibility, but would allow for predictions about the risk for running out of memory in case of black swan events.\n\n## Kernel wise features\n\n### Topics\n\nA way to share data streams with other processes, similar to pipes but optimized for sensor data and data distribution/ access over many processes.\n\nHow many topics and in which configuration must be setup at compiletime in the `envConfig.zig` of the project. Note that Topics are just another type of state and are implemented in StatusControl. Each Topic can be configured in its buffer type, size, identifier and so on. In the runtime phase of the platform, every topic then behaves according to its configuration and can be addressed through its fixed id.\n\nThere are two ways to communicate over a Topic, one is through SysCalls and the other is through direct mapped memory, which is very effective but less easy to use. Also, currently both ways of communicating on a Topic must not be mixed so only either one of both can be used.\n\n#### What kind of data is it for?\n\nTopics can be used for all kinds of statically sized data. Depending on the amount of data per time unit, there a re different methods of retrievals. \n- `fn userSysCallInterface.waitForTopicUpdate(comptime name: []const u8)` (which leverages a semaphore) can be used to wait for data in a separate thread\n\nUses sys-calls as interface. Pushes/reads n units of the latest(depending on the buffer type) data\n- `fn userSysCallInterface.popFromTopic(comptime name: []const u8, ret_buff: []u8)` \n- `fn userSysCallInterface.pushToTopic(comptime name: []const u8, data: []u8)`\n\nUses direct mapped memory to read/write to a Topic. Is also bound to all preconfigured parameters including the buffer type.\n- `fn ShareMemTopicsInterface.read(self: *SharedMemTopicsInterface, comptime name: []const u8, ret_buff: []u8)` \n- `fn ShareMemTopicsInterface.write(self: *SharedMemTopicsInterface, comptime name: []const u8, data: []u8)`\n\n### Status Control\n\nA way to centrally communicate state and adapt the system appropriatly. \nSince the status of a sensor, service, io device, or more abstract concepts is not just a tool but one of the most important control aspects in a robotic system, this funcitonality is deeply integrated and not just meant for state sharing but also as a state machine at the heart of the system. \nThe idea is that you can setup tasks which are only scheduled if a certain state is matching. // =\u003e todo\n\n- `fn userSysCallInterface.updateStatus(comptime name: []const u8, value: anytype) !void`\n- `fn userSysCallInterface.readStatus(comptime T: type, comptime name: []const u8) !T`\n\nStatuses (including topics) have to be predefined in envConfig.zig for each environment are id'ed by their name. Since all the parameters and names are compile-time that allows for safe compile time type/ name checking.\nThe usage of names makes the code very readable and easy to understand.\n\n### Setup Routines\n\nAre routines that run at kernel start, but before scheduler init. Meant for for driver inititation and run in kernel space.\n\n### Kernel Threads\n\nThreads that run in kernel space, for example to handle IO that requires kernel level access or other drivers.\n\n## User Apps\n\nThats where the actual development is meant to happen. The user apps run, as the name suggests, in userspace and are compiled separately.\nCommunication between all tasks is meant to happen through StatusControl(Topics, Statuses...) and Actions.\n\n### Actions\n\nActions are similar to apps separately compiled programs with the key difference that they are only executed(scheduled) if commanded to do so. Whilst they can run in parallel (be conventionally scheduled) their main purpose is to offer a way to start a task quickly (or roughly the time it takes to interrupt and return).\n\nIf the action needs to be executed even faster, a direct (in current scheduler context) jump is inevitable (a conventional function call, for example).\n\n## Project Structure\n\nThe project aims to give as much guidance to the developer as possible, that also applies to where to put which component of the kernel. In general the projects layout looks like that:\n\n```bash\n├── build.zig\n├── src/\n│    ├── appLib/\n│    │    ├── ..\n│    │    └── \u003e everything that is linked with userspace apps\n│    ├── arm/\n│    │    ├── ..\n│    │    └── \u003e all the \"drivers\" required for the arm soc. linked with the kernel\n│    ├── boards/\n│    │    ├── * contains drivers and board configuration files (qemuVirt.zig, raspi3b.zig..). Which board is compiled can be selected in build.zig, the respective configuration file is then selected and linked. *\n│    │    ├── drivers/\n│    │    │    ├── \u003e everything that is board specific, timer, irq, io code\n│    │    │    ├── bootInit/\n│    │    │    │    ├── \u003e board specific startup code that sets up the correct el, exc. vec. table,.. and and calls the bootloader entry fn. linked witht the bootloader\n│    │    │    │    ├── qemuVirt_boot.S\n│    │    │    │    └── ..\n│    │    │    ├── interruptController/\n│    │    │    │    ├── \u003e board specific drivers for additional(to the arm gic) interrupt controllers, linked with the kernel\n│    │    │    │    ├── bcm2835InterruptController.zig\n│    │    │    │    └── ..\n│    │    │    └── timer/\n│    │    │        ├── \u003e board specific drivers for additional(to the arm gt) timer, linked with the kernel\n│    │    │        ├── bcm2835Timer.zig\n│    │    │        └── ..\n│    │    ├── qemuVirt.zig\n│    │    └── ..\n│    ├── bootloader/\n│    │    ├── bins/ \n│    │    │    └── \u003e the kernels binary (non elf format) is saved here because it is embedded by the bootloader (usings Zigs `@embedFile`) and cannot be placed outside the package path\n│    │    ├── ..\n│    │    └── \u003e contains everything required to make the bootloader boot the kernel\n│    ├── configTemplates/\n│    │    ├── ..\n│    │    └── \u003e contains all the templates for different configurations. E.g. the board or env. configuration\n│    ├── environments/\n│    │    ├── \u003e actual development space. Every environment is a set of userspace apps and kernel threads. Only one environment at a time can be compiled. Which environment is compiled can be selected in the build.zig.\n│    │    ├── basicKernelFunctionalityTest/\n│    │    │    ├── \u003e environment for basic kernel integration tests. In the envConfig.zig everthing environment can be configured. E.g. how many topics, with which buffer type they operate and so on..\n│    │    │    ├── envConfig.zig\n│    │    │    ├── kernelThreads/\n│    │    │    │    ├── \u003e all kernel threads required by the board. E.g. a handler for the additional(or secondary) interrupt controller. linked with the kernel! \n│    │    │    │    ├── threads.zig\n│    │    │    │    └── ..\n│    │    │    ├── setupRoutines/\n│    │    │    │    ├── \u003e setup routines called on kernel entry. E.g. the init of additional interrupt controller handler. linked with the kernel!\n│    │    │    │    ├── routines.zig\n│    │    │    │    └── ..\n│    │    │    └── userApps/\n│    │    │        ├── \u003e actual userspace with all the userspace apps. every app is build seperately\n│    │    │        ├── _semaphoreTest/ \u003e (apps starting with underscore and not compiled..)\n│    │    │        │    ├── linker.ld\n│    │    │        │    └── main.zig\n│    │    │        └── mutexTest/\n│    │    │            ├── linker.ld\n│    │    │            └── main.zig\n│    │    ├── ..\n│    ├── kernel/\n│    │    ├── \u003e actual kernel space\n│    │    ├── bins/\n│    │    │    ├── ..\n│    │    │    └── \u003e app binaries are saved here because they are embedded by the kernel (user Zigs `@embedFile`) and cannot be placed outside the package path/\n│    │    ├── exc_vec.S\n│    │    ├── kernel.zig\n│    │    ├── ..\n│    │    ├── sharedKernelServices/\n│    │    │    ├── SysCallsTopicsInterface.zig\n│    │    │    ├── ..\n│    │    │    └── \u003e all services that have to be accessed over from the drivers for exampled., linked with the kernel\n│    ├── kpi/\n│    │    ├── secondaryInterruptControllerKpi.zig\n│    │    ├── ..\n│    │    └── \u003e kernel programming interface for drivers. e.g. the timer or secondary irq handler driver. inited in the board configuration file\n│    ├── periph/\n│    │    ├── pl011.zig\n│    │    ├── ..\n│    │    └── \u003e all the peripheral devices code\n│    ├── sharedServices/\n│    │    ├── Topic.zig\n│    │    ├── ..\n│    │    └── \u003e code thats so basic that it is linked with both the kernel and the userspace\n│    └── utils\n│        └── utils.zig\n```\n\n# Kernel details\n\n## Bootloader and kernel separation\n\nBecause it simplifies linking and building the kernel as a whole. Linking both the kernel and bootloader is difficult(and error-prone) because it requires the linker to link symbols with VMA offsets that are not supported in size and causes more issues when it comes to relocation of the kernel. \nBoth the bootloader and kernel are compiled\u0026linked separately, then their binaries are concatenated(all in build.zig). The bootloader then prepares the exception vectors, mmu, memory drivers and relocates the kernel code.\n\nThe bootloader is really custom and does a few things differently. One of the primary goals is to keep non static memory allocations to an absolute minimum. This is also true for the stack/ paging tables, which have to be loaded at runtime. At the moment both, bootloader stack and page tables are allocated on the ram, to be more specific in the specified userspace section. This allows to boot from rom(non writable memory...) whilst still supporting boot from ram.\n\n## MMU\n\nI wrote a mmu \"composer\" with which one can simply configure and then generate/ write the pagetables. The page table generation supports 3 lvls and 4-16k granule. 64k is also possible but another level has to be added to the `TransLvl` enum in `src/board/boardConfig.zig` and it's not fully tested yet.\nIdeally I wanted the page tables to be generated at comptime, but in order to have multiple translation levels, the mmu needs absolute physical addresses, which cannot be known at compile time(only relative addresses). Alternatively the memory can be statically reserved and written at runtime, which is not an option for the bootloader though because it is possibly located in rom, and cannot write to statically reserved memory, leaving the only option, allocating the bootloader page table on the ram (together with the stack). The kernel on the other hand could reserve at least the kernel space page tables, since they are static in size, but for consistency reasons kernel and userspace have linker-reserved memory.\n\n### Addresses\n\nThe Arm mmu is really powerful and complex in order to be flexible. For this project the mmu is not required to be flexible, but safe and simple. For an embedded robotics platform it's neither required to have a lot of storage, nor to control the granularity in an extremely fine scope since most of the memory is static anyways.\n\nAdditionally devices as for example the Raspberry Pi forbid Lvl 0 translation at all since it's 512gb at 4k granule which is unnecessary for such a device.\n\nWith those constraints in place, this project only supports translation tables beginning at lvl 1, which is also why, `vaStart` is `0xFFFFFF8000000000`, since that's the lowest possible virtual address in lvl 1.\n\n### Qemu Testing\n\nIn order to test the bootloader/ kernel, qemu offers `-kernel` but that includes a number of abstractions that are not wanted since I want to keep the development at least somewhat close to a real board. Instead, the booloader (which includes the kernel) is loaded with `-device loader`.\n\n## Implementations\n\n### CPU\n#### Interrupt controller\n\nThe Raspberry ships with the BCM2835, which is based on the Arm A53 but does not adapt its interrupt controller. More about the BCM2835s ic can be found [here](https://www.raspberrypi.org/app/uploads/2012/02/BCM2835-ARM-Peripherals.pdf)(p109) and [here](https://xinu.cs.mu.edu/index.php/BCM2835_Interrupt_Controller). The [linux driver implementation](https://github.com/torvalds/linux/blob/master/drivers/irqchip/irq-bcm2835.c) comments are also worth looking at.\n\n\n#### MMU\n\nThe best lecture to understand the MMU is probably the [official Arm documentation](https://developer.arm.com/documentation/100940/0101), which does a very good job of explaining the concepts of the mmu.\nSince this project requires multiple applications running at the same time, virtual memory is indispensable for safety and performance.\n\n## Installation\n\n### Dependencies:\n\n- zig (last tested version 0.10.1)\n- qemu (for testing)\n\n### Run\n\n- `zig build qemu`\nBuilds and runs the project. The environment and board as well as all the other parameters for the build can be configured in build.zig\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fluickk%2Fminimalroboticsplatform","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fluickk%2Fminimalroboticsplatform","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fluickk%2Fminimalroboticsplatform/lists"}