I have been fiddling with the Arm Cortex-M7 at work and I thought I should write a blog post about some of the tips/tricks and common pitfalls for bringing up the MPU ( memory protection unit ).
The main purpose of the MPU is to provide the ability to explicitly define attributes and behaviors when accessing memory in your application. It can isolate different areas of memory on your chip, restricting different cores to only be able to access certain regions of your memory map and sits between the CPU and memory subsystems to enforce rules on memory accesses. These memory accesses can also be given Read/Write/Execute permissions, i.e. allow you to make sure that no code executes from any region that you don't want it to and make sure certain regions aren't readable or writeable. You can define caching behavior as well, for the purpose of correctly defining memory access patterns between different cores and making sure the data the application sees isn't corrupted or stale.
I recommend first reading through the Armv7-M Architecture Reference Manual. My bare minimum recommended reading is chapter A2, A3, B1, B2, and B3. Reread B3.5 before starting your bringup. You should begin MPU configuration with a good foundation of how the system memory map works and that on a multicore chip, they share a unified memory map between all cores.
You can download and read the manual here - https://developer.arm.com/documentation/ddi0403/ee/
Let's begin.
1. The most important thing to do before even thinking about bringing up the MPU is organizing your memory map. On a multicore chip, you have different cores accessing different regions of RAM, as well as Flash memory. Typically, your linker script and build system will collect the Data, Code, stack/heap allocations, etc. for each core and stitch them together, and then each core will begin their execution at their respective locations in the unified memory map that Arm Cortex M7 devices use, from 0x00000000 to 0xFFFFFFFF.
I say one must organize their memory map because of specific rules for defining an "MPU region" - this basically being a starting address and a size for the region, where all accesses in that region will have to adhere to the specific rules for caching as well as access permissions defined for that region. Each core has their own on-chip MPU, and each core will have to configure their own MPU correctly, keeping in mind the overall system as well as what memory itself (the core) should be allowed to touch, as well as what memory in the overall memory map should shared between cores, what memory is prone to being touched by other bus masters such as DMA, etc.
The two rules for defining an MPU region are the following
1. The size of the region must be a power of 2, with a minimum of 32 bytes and a maximum of 4GB. This means that an mpu region can only be sized like 64 KB, 32MB, etc; always being a power of 2.
2. The base address, or where the mpu region starts, must be a multiple of ts own size. For example, a 32 MB MPU region can only sit on addresses that are a multiple of 32 MB, such as 0x00000000, 0x200000000, etc.
In order to effectively configure the MPU for a multicore chip, you must first define what RAM allocations each core will have. For a seamless MPU bringup, it is typical for RAM allocations to be in powers of 2, so that the MPU region for that core can obey rule #1, and the entire RAM allocation for the core can be done easily with one mpu region. Moreover, the core's base address must be aligned to a multiple of its own size, to obey rule #2.
With this in mind, is extremely helpful to create an excel sheet with the default system memory map and define how you want to setup your memory for all your cores. Doing this properly and thoroughly will set you up for success later on turning the MPU on. For each core, define its power of 2 size, and then place it at a base address that is a multiple of that size. You can do funky mpu configurations with subregions, but that should be a last resort as it typically complicates your process a lot.
After you have wrote down all your RAM allocations per core as well as where they will be in memory on paper, you will want to edit your linker script so that this memory allocation can take effect. Before turning on your mpu, organize your memory and then run your application as normal to make sure that all your cores are behaving as expected and there aren't weird things happening with your app. If you have done this properly, there should be no change in application behavior with the MPU off.
Next is to actually define your mpu regions and turn it on. The goal is to enable to MPU and to set the PRIVDEFENA bit off, so that any access to a region of memory without an explicit MPU region will MemManage Fault.
When defining your MPU regions per core, these are the most important registers.
MPU_RASR -> Region Attribute and Size register. This is where you will write the power of 2 size for that specific region number, as well as define all the memory attributes for that region.
MPU_RBAR -> this is where you will write the base address the mpu region starts.
MPU_CTRL -> this is how you will enable your mpu, as well as write PRIVDEFENA which will basically turn your mpu into "allow non-defined memory accesses through" mode or "be very strict in enforcing rules" mode.
1. It is always good to start with the default memory attributes for each memory region. This way, the behavior stays the same as what the core would use without the MPU turned on, and then you can change up the access/caching behavior to match whatever use case you need. For the default memory attributes, remember the following.
- The code region of the system memory map uses Write back, no write allocate caching.
- The SRAM/RAM region uses Write Back, Write Allocate caching behavior.
- Peripheral memory uses Device memory configurations.
Carefully read B3.5.9 of the reference manual to learn how to define your memory attributes correctly for that region. Also refer to B3.1 to find out what the default memory attributes are for each region of the system address map.
To facilitate the actual writing of code, it is good to define in a header file all the necessary #defines to place bits correctly to configure the mpu and reuse as much code as you can. For example, for the MPU_RASR, make a macro that takes a number and correctly shifts the bits over to write into the correct bit position for that register. Remember, if you use one file to configure the mpu for all your cores, you will have to make sure that one file works for all different cores, which will inevitably have different requirements for accessing memory.
The following below are some general tips/tricks for defining mpu regions.
- Shared memory between cores should be marked Non-Cacheable. They should have their own mpu region with Non-Cachebale attributes for all cores that use that shared memory.
- Memory that is touched by other bus masters, such as DMA, should be marked non-cacheable.
- The peripheral region should be configured as device memory.
- Make sure you either are aware of the cache configuration before you initialize your MPU, or you clean and invalidate your caches before turning on your mpu, which will affect memory access behaviors. You can end up with weird bugs that manifest in weird ways ( such as failed stack canary checks) when initially turning on your mpu.
The following are some general tips/tricks for debugging a misconfigured MPU.
- Put while(1)s on all your reset paths that are triggered via exceptions or other app failures so that when you do reach a MemManageFault, you just spin in a loop and you can JTAG in and inspect the stack frame to find out exactly what went wrong.
- Disable all watchdogs during debugging so that you don't run into watchdog failures that aren't a symptom of a wrong mpu configuration.
- When you have a valid exception stack frame on a memmange fault, you can read stack frame pointer + 0x18 to find out exactly what instruction caused the fault. Use arm-none-eabi --numeric-sort on your elf files to find out what that instruction points to and figure out the root cause for your fault.
- Thoroughly read the Armv7 exception model in section B1.5 of the Reference manual to understand what gets placed into the stack on exceptions to debug these faults.
- Triple check to make sure that your mpu is being configured and the right bits are being written to isolate your application failures to application level bugs (like a core is accessing memory without a mpu region) instead of the hardware just being misconfigured.
There are many more intricate details and peculiarities about the MPU that you find when bringing up the MPU, but some of those details are just better learned when experienced. I hope this article gives you a good foundation of what to expect when turning the MPU on :)
[ ... ]james@hamesjan~/interests❯▾
recent
So I did my first assembly for the claw machine!.
FYI this is for my claw machine project. See it here - https://github.com/hamesjan/clawedtronics
Basically, I got it working ( kinda ).
I've learned a lot and I'm really excited to iterate on the PCB I had built. I wrote down some notes for what I have to fix - Ive written it here
Power decision circuit needed between USB and Power Supply power. Having both will cause back driving that blows stuff up
0.1 nf Capacitors are too small. Also there is order for 100nf capacitors in wrong size and 0.1u in right size, just use right size for all
Screw terminals for 2 are flathead screws. Dont have a flathead. Either change them to philips.
Also, It might be nice to just find the right 9x9 connector so it's an easy connection.
Buttons are the wrong footprint. Also Non inverting buffer is wrong, too small too.
Weird bug with the bootloader. Not sure what is happening with ESP but I can’t get into the app sometimes.
DONT Solder while power is on!!!! Also, get a power switch so I don’t have to keep unplugging and replugging.
Pull up EN and GPIO0
USB C has the wrong footprint, wrong holes so it's hard to fit.
Break out GPIOs use too small pitch, just standard breakout pins like esp32.
During assembly I blew up 1 motor driver, a resistor, and other stuff. I basically have no sure idea what happened - a couple of suspect reasons but I'm not experienced in electronics enough to say for sure. Think I might have to get an oScope and really apply my little EE knowledge to figure out what went wrong before I order the next revision. I'm kinda bummed this is the only way to iterate on HW, where I have to spend a bunch of money to buy parts, pcbs, etc. But I'm glad I got setup with assembly tools so I can not have to pay for those things now :)
[ ... ]I'm currently in the process of building out my second ever PCB, first ever done by myself. I'm building a claw machine - mostly for fun and to learn how to better design pcbs and get experience with electrical engineering. It's been fun! I especially love KiCad, it is so freaking simple and intuitive and the fact that it is open source gives me hope in humanity.
I wanted to write this to point out one of the pain points I had during pcb development. Maybe this is just one of those first time learning curve things but yea....
So the issue I had was that the separation between PCB design software, part vendors, and the fab shop. I would design my PCB, have to change my design because I find the footprints/symbols in my design don't match the cheapest thing the vendors offer (Digikey), and then I would have to revise my pcb design after I submit it to fab, and then yea you get the gist. There is just a disconnect between the components a beginner designer like me works with and the enormous catalog of DigiKey, as well as the fab's rules. Now that I'm writing this, I learned that nailing down the components first, and then doing pcb design/layout seems to be the path to doing as little revisions as possible. Maybe I'm just being lazy and I just don't want to do revisions lol.
I think in a perfect world, there would be a set list of components that a fab has, and I can just export all footprints/symbols for those components into KiCAD under a library, and then have those things automatically map to DigiKey MFGPNs so I don't have to keep going back and forth between the three things. It would be so nice if I could just start in KiCAD, have all my components chosen for me ( basic passive components, etc.) and then just have them be the cheapest thing that vendors offer; or the vendor can partner with the fabs to set up this beginner workflow so PCB design doesn't seem so intimidating.
This probably already exists and I just don't know about it.
Anyways, I'm excited. I just spent like ```$400 (im testing this code block functionality lol)``` on the following
- all the smd components
- PCB and stencil
- soldering iron, solder, flux, fan, wire, wick, helping hands, magnifying glass, tweezers, etc.
It's gonna be like Christmas once all my components get here.
Edit: Oh brother JLPCB just contacted me they need to do a 4 wire Kelvin Test??? WTF is that.... Them boys charging $70 for that just cuz I have a couple of holes with size 0.15mm... Time to revise again >:(
[ ... ]I decided to start this blog because I wanted to create a space just for me. I've been an avid journaler for more than 10 years now and I really enjoy going back and reading through them because 1. it helps me remember minute details of my life in a vivid way 2. Its funny to see how I have changed/ haven't changed. I wanted to create a space on the web where its all mine and I can customize it however and I can just put as many cool things as I want because its like the ultimate version of my own cozy nook in the internet.
So just to give an overview of where Im at in life: I'm currently writing this in a quaint coffee shop in redwood city. I've been living in the SF bay area for 5 months now - working as an intern at Tesla on the Firmware platforms team. I really enjoy the work, its very fast-paced and startup-like, but there are development practices that one would only see in a huge company like itself. Its cool to work on both sides - a place where scalability really really matters but is kind of scrappy; it seems like the best place for me since I dream of starting my own thing one day but I want to really get years of experience learning best practices first. During my free time, I've been just doing random stuff around the city - yesterday I went to Pacifica pier to go crabbing with my roommates. Its funny, we were out there for hours and between the 5 of us we caught one crab that barely missed the 5 3/4 inch size requirement so we didn't even get to take it home and grub on it. Meanwhile, there's these people at the end of the pier that are having a fiesta - blaring corridos and drinking beer and smoking cigarettes - they are catching huge crabs, 2 - 3 at a time on their snare, and just leaving with buckets of them. We met this one guy who was from Mississippi and had traveled all the way west to catch crabs because his wife back home really liked them. Anyways - I'm looking forward to making this website as cool as possible in the future. I want to make really cool projects and post them here, and just stake out my own corner of the internet.
[ ... ]