Source of useful inline ASM macros for PIC32? - Page 1
Forum section: electronics / general

author: Chris C
author_rank: Frequent Contributor
timestamp: July 13, 2015, 11:09:49 pm
content: I'm currently working with 16-bit PICs. Often I find it necessary to ensure that modifications to memory contents are made atomically (in a single uninterruptable cycle), especially when working with registers (like digital ports). When optimizations are turned on, the default MPLAB C compiler typically emits instructions that perform modifications atomically, so no problem. But when I have to attach a debugger, I usually have to turn optimizations off; causing it to emit non-atomic modifications that sometimes cause erroneous results.My solution was to write a series of inline ASM macros that I could use when atomic modifications are necessary, regardless of compiler settings. Plus a few others, which I wrote or found elsewhere, that have been indispensable.Later this week, I'm getting my first PIC32, and migrating a lot of code to it - including these macros. I'm not very good at ASM, and avoid it when possible; though I'm sure I can muddle through it. But it occurs to me such macros should be a fairly common requirement, and that someone, somewhere, has surely done this already.Does anyone have any useful code or references to share? All of my 16-bit ASM macros follow, as an example of what I'm looking for PIC32 equivalents of. (And as a help to anyone who might need them on 16-bit PICs.)Code:// Atomically clear bits in value at addr, based on the passed mask.ZINLINE void MultiBitClear16(volatile UI16* addr, UI16 mask) { asm volatile("and.w %,[%],[%]" : : "r"(addr), "r"(~mask)); };ZINLINE void MultiBitClear8(volatile UI8* addr, UI8 mask) { asm volatile("and.b %,[%],[%]" : : "r"(addr), "r"(~mask)); };// Atomically set bits in value at addr, based on the passed mask.ZINLINE void MultiBitSet16(volatile UI16* addr, UI16 mask) { asm volatile("ior.w %,[%],[%]" : : "r"(addr), "r"(mask)); };ZINLINE void MultiBitSet8(volatile UI8* addr, UI8 mask) { asm volatile("ior.b %,[%],[%]" : : "r"(addr), "r"(mask)); };// Atomically toggle bits in value at addr, based on the passed mask.ZINLINE void MultiBitToggle16(volatile UI16* addr, UI16 mask) { asm volatile("xor.w %,[%],[%]" : : "r"(addr), "r"(mask)); };ZINLINE void MultiBitToggle8(volatile UI8* addr, UI8 mask) { asm volatile("xor.b %,[%],[%]" : : "r"(addr), "r"(mask)); };// Write passed single-bit value (val) at bit number (bit) in value at addr.ZINLINE void BitWrite16(volatile UI16* addr, UNATIVE bit, UNATIVE val) { asm volatile("CP %0,#0 \n BSW.Z [%1],%2" : : "r"(val), "r"(addr), "r"(bit)); }; // ...because asm allows doing it without a conditional jump (but no 8-bit equivalent available for BitClear8)// Atomically Add/Sub/Inc/Dec value at passed address.#define AtomicAdd16(addr,value) asm volatile("add.w %,[%],[%]" : : "r"(value),"r"(addr))ZINLINE void AtomicSub16(void* addr, I16 value) { value=-value;AtomicAdd16(addr,value); };#define AtomicInc16(addr) asm volatile("inc.w [%],[%]" : : "r"(addr))#define AtomicDec16(addr) asm volatile("dec.w [%],[%]" : : "r"(addr))// Halt CPU. If ICD is attached, it is activated at the halt point. Undocumented opcode, there is no official way to do this.#define HaltCpu() __asm__ volatile (".pword 0xDA4000")// Put the CPU into basic sleep, it will be awakened if an interrupt occurs.#define IdleCpu() __asm__ volatile ("pwrsav #1")// Get index of first/last set bit.ZINLINE UNATIVE GetLowBit16(volatile UI16 in) { UNATIVE out; asm volatile("ff1r %,%" : "=r"(out) : "r"(in)); return out; };ZINLINE UNATIVE GetHighBit16(volatile UI16 in) { UNATIVE out; asm volatile("ff1l %,%" : "=r"(out) : "r"(in)); if (out) out=17-out; return out; };// Atomic locks.typedef volatile UI16 ATOMICLOCK; // depending on underlying implementation, this could be a structure, or non-zero at init; so init only by calling AtomicLock onceZINLINE BOOLEAN AtomicLock(ATOMICLOCK* lock) {asm volatile("btsts.z [%0],#0" : : "r"(lock)); // at indirect address, save bit #0 in carry flag, then set bitreturn CpuZero;};#define AtomicUnlock(lock) *lock=0#define AtomicIsLocked(lock) ((*lock) != 0)#define AtomicIsUnlocked(lock) ((*lock) == 0)

author: andersm
author_rank: Super Contributor
timestamp: July 14, 2015, 01:11:42 am
content: The MIPS architecture used by the PIC32 is a load-store architecture. Values are loaded from and stored to memory, but operations are always performed in registers. To perform atomic operations you have to disable interrupts or use the LL/SC (Load Linked Word and Store Conditional Word) instructions, which aborts the store if atomicity is not guaranteed. I recommend downloading the architecture manuals from ImgTec's website. The XC32 compiler has built-in functions for dealing with interrupts and for performing atomic operations, see the XC32 documentation and GCC documentation.For peripheral registers, Microchip have implemented set, clear and invert registers, which atomically operate on bits within the register.

author: Chris C
author_rank: Frequent Contributor
timestamp: July 14, 2015, 02:00:04 am
content: Wow. I must admit I'm going into this upgrade mostly blind, and didn't anticipate such architectural differences.No worries though. Your descriptions are clear. Knowing exactly what my options are, and where to research them further, you've saved me many hours. Thank you, !