Thank you. Here's what I've got now:
Except as shown (with cli commented out) you now are not atomically setting your pins of interest. You have to protect the read and write 'together'.
Take the route for case 0-
uint8_t tmpC = PORTC;
switch(channel) {
case 0:
OCR1A = registers[4 + level];
if(registers[0] & 1) tmpC |= 1; else tmpC &= ~(1);
if(registers[0] & 2) tmpC |= 1<<1; else tmpC &= ~(1<<1);
PORTC = tmpC;
You read PORTC, an interrupt takes place after the read/before the write, and in that interrupt you are blinking an led on portc-
tmpC = PORTC; //led is pin 7, currently off so bit7 is 0
//interrupt fires- led turns on in isr, so PORTC bit7 is now 1
//return from interrupt
tmpC gets bits0/1 set as needed
PORTC = tmpC; //your original read of bit7 was 0, and are now writing that bit as 0
you just turned off the led, which was not your job to do
Your original code looks no worse (probably better), and since you are getting the instructions sbi/cbi you do not have to worry about interrupt protection at all.
For this avr, you either get instructions emitted to get atomic bit writes, or you interrupt protect the whole read/modify/write sequence. To get these instructions, the requirements are a known address in the range these instructions can handle, and a single known bit position-
*(address_range_for_SBI) |= 1|2|4|8|16|32|64|128 //set bit-> sbi 0xaddress, 0xbitposition
*(address_range_for_CBI) &= ~ 1|2|4|8|16|32|64|128 //clear bit-> cbi 0xaddress, 0xbitposition
the compiler will then emit the sbi/cbi instruction (even though the appearance is read/modify/write).
If you fall outside these requirements, then the compile will end up with read/modify/write and will need interrupt protection surrounding this sequence. If you look at arduino source code for the original avr, they interrupt protect all writes to ddr/port as they use port/pin values that are derived at runtime. They also have single functions you use for these writes, so they all end up in the same function with the required interrupt protection applied. If you want to get it right without regard to what the compiler is emitting for instructions, then you would either surround all pin manipulation with interrupt protection, or would create callable functions to do this work so you have a single place to take care of the interrupt protection (same as arduino).
Any other 'modern' mcu with set/clr/[tgl] registers has the ability to set/clr/[tgl] specific bits in a register, and the compiler has no involvement whether you get this atomicity (as long as you use an assignment, =), since it does not depend on the instruction used.
PORTC.OUTSET = 1<<3; //avr0/1, this is atomically setting pin PC3, no interrupt protection needed regardless of what instruction is used (sts/st)
The big advantage of this is you can now have runtime values for the address and bits, and still get atomicity since it derives from the register used, not the instructions produced-
mypins[2].port->OUTSET = 1<<(mypins[2].pin);
None of this is specific to ports/pins, and requires thought in about every other area when dealing with registers/variables/etc that are potentially shared. Its also easy to get wrong. The stm32 for example has set/reset gpio registers, but it also has all the other gpio registers shared among pins. If you start changing any of these registers in code at more than one interrupt level, then access to these registers will require interrupt protection. Not that common to be changing pin configurations at runtime, but by the time you do it you will have forgotten the potential problem of doing so, and if not initially treated as a potential future problem only the low probabilities will save you from a corrupt register.
edit-
For these avr's, one of the reasons macros are used when dealing with pins is to make sure you get the sbi/cbi instructions produced (meeting compiler requirements to produce the wanted sbi/cbi), which eliminates the need to interrupt protect-
https://godbolt.org/z/rjasvG1K5for the 'modern' mcu such as avr0/, you can come up with any code you want and as long as you use assignment on these set/clr registers, there will be no need for interrupt protection-
https://godbolt.org/z/zs6ce74ce(nothing optimal about this code, and emitted code is not a pleasant sight, but it will work and you will get your pins set without any need to deal with interrupt protection)