If you press both buttons in a row at the same time, your scanning method by switching the COL outputs creates a kind of soft short circuit. While one COL is high and the other becomes low at ROW0 you have VCC/2 (+ a little from R3 and R5). It's at random what the MCU would read there (no, not random, but hard to predict).
Try to set PORTB to all bits low and scan with the DDRB to switch between output(active low) and input(inactive open drain). This way you avoid ambigous analog levels on the ROW lines and wasted 5mA cross current.
By the way, you don't need R3 and R4 if you activate the internal pullup's of the ROW input port (PORTC-bits high).
Reduce R5 to 10k or lower or you may receive false int low on EMI spikes.
A good solution is using a timer interrupt, read the rows there and and change the active COL for the next int cycle. If you have done all COLs, you can calculate the pressed buttons and set a (volatile) bool flag to inform your main loop. In this polling mode you don't need the int input and you don't waste cpu time on delay().
However, pressing more than one button in such a matrix may produce additional buttons looking pressed, it's the price you pay using a matrix. Just discard results with more than one button pressed.
Some years ago I did the same with an atTiny24 using sleep mode to run it from a coin cell. Well, this is Pascal code with assembly sections and the comments are German, but you may extract some useful ideas out of it.
//-------------------------------------------------------------------------
// IR-Fernbedienung
//-------------------------------------------------------------------------
// Copyright (c) 2015 E42 Elektronik
// Version 1, 25.07.15.JH
//-------------------------------------------------------------------------
// MPU: ATTiny24
// Compiler: ELab, Version 4
//-------------------------------------------------------------------------
// Kommentare:
// - Der Prozessor arbeitet mit internem Takt von 8MHz und
// ClkDiv8-Option per Fuses. Daher effektiv 1MHz Systemtakt beim
// Booten.
//-------------------------------------------------------------------------
// Aenderungen:
//-------------------------------------------------------------------------
program IRControl;
{$TYPEDCONST OFF}
{$NOSHADOW}
Device = Tiny24, VCC=3;
Define_Fuses
// Konfiguration fuer E-Lab Programmer
OverRide_Fuses;
Supply = 3.0, 30; // 3.@30mA
LockBits0 = [LOCKBIT1,LOCKBIT2];
FuseBits0 = [CKSEL3,CKSEL2,CKSEL0,SUT0,CKDIV8];
FuseBits1 = [BODLEVEL1]; //2.7V BOD
ProgMode = SPI;
ProgFuses = True;
ProgLock = True;
ProgFlash = True;
Import; // Keine Erweiterungsmodule
From System Import ; // Keine weiteren Laufzeitbibliotheken
Define
ProcClock = 1000000; {Hertz}
StackSize = $001C, iData; // Schachtelungstiefe von Aufrufen
FrameSize = $0018, iData; // Platz fuer lokale Variablen
///////////////////////////////////////////////////////////////////////////
IMPLEMENTATION
///////////////////////////////////////////////////////////////////////////
const
IR_TX_BIT = 0;
IRBurstLenght = 11;
cMinButtonNo = 1;
cMaxButtonNo = 21;
cBurdaIdent = $F1;
cKeyF1 = $49; // Button 4
cKeyF2 = $48; // Button 5
cKeyF3 = $56; // Button 6
cKeyReset = $16; // Button 21
cKeyPreflushInc = $42; // Button 13
cKeyPreflushDec = $41; // Button 15
cKeyFlushInc = $45; // Button 16
cKeyFlushDec = $44; // Button 18
cKeyRangeInc = $58; // Button 7
cKeyRangeDec = $18; // Button 9
cKeyFanInc = $59;
cKeyFanDec = $4D;
cKeyFlush = $1D; // Button 16
cKeyFlushStop = $1E; // Button 17
cKeyNone = $00;
cKeyTable : array [1..21] of byte =
(cKeyNone, cKeyNone, cKeyNone,
cKeyF1, cKeyF2, cKeyF3,
cKeyRangeInc, cKeyNone, cKeyRangeDec,
cKeyPreflushInc, cKeyNone, cKeyPreflushDec,
cKeyFlushInc, cKeyNone, cKeyFlushDec,
cKeyFlush, cKeyFlushStop, cKeyNone,
cKeyNone, cKeyNone, cKeyReset);
(*
Port-Layout
PA0: 13 N/A (URef)
PA1: 12 Matrix pin 3
PA2: 11 Matrix pin 4
PA3: 10 Matrix pin 5
PA4: 09 Matrix pin 6
PA5: 08 Matrix pin 7
PA6: 07 Matrix pin 8
PA7: 06 Matrix pin 11
PB0: 02 out, IR-LED
PB1: 03 out, Visible LED
PB2: 05 Matrix pin 12
*)
(*
Button Matrix nach Tiny24-Pins
| A1 A2 A3 A4 A5 A6 A7 B2
----------------------------------
A1 | . / / R- T- S- / RS
A2 | . . / R+ T+ S+ / /
A3 | . . . / / / / /
A4 | 9 7 8 . / / F1 /
A5 | 12 10 11 . . / F2 W+
A6 | 15 13 14 . . . F3 W-
A7 | 3 2 1 4 5 6 . /
B2 | 21 19 20 18 16 17 . .
*)
{$PDATA}
var
Q_IRTransmit [@PortB, 0] : bit; // PB0: IR-TX
E_IRTransmit [@DDRB, 0] : bit;
Q_DiagLed [@PortB, 1] : bit; // PB1: visible LED
E_DiagLed [@DDRB, 1] : bit;
// Q_DiagLed [@PortA, 0] : bit; // PB1: visible LED
// E_DiagLed [@DDRA, 0] : bit;
Q_Row0 [@PortB, 2] : bit; // PB2: Matrix Row 0
E_Row0 [@DDRB, 2] : bit;
{$IDATA}
var
LastButton : byte;
BurstClocks : byte;
ClockCounter : word;
TxCode : byte;
procedure Troedel;
begin
end;
function ReadRow (BitMask: byte): byte;
var
A: byte;
begin
DDRA := BitMask;
PortA := not BitMask;
Troedel;
Troedel;
A := PinA;
DDRA := $00;
PortA := $FF;
Return (A);
end;
function GetActiveButton: byte;
var
A: byte;
begin
Q_Row0 := True;
E_Row0 := False;
DDRA := $00;
PortA := $FF;
Q_Row0 := False; // row B2 to 0
E_Row0 := True;
Troedel;
Troedel;
A := PinA;
Q_Row0 := True;
E_Row0 := False;
if (A and $02) = 0 then Return(21); endif;
if (A and $04) = 0 then Return(19); endif;
if (A and $08) = 0 then Return(20); endif;
if (A and $10) = 0 then Return(18); endif;
if (A and $20) = 0 then Return(16); endif;
if (A and $40) = 0 then Return(17); endif;
A := ReadRow ($80); // row A7 to 0
if (A and $02) = 0 then Return(3); endif;
if (A and $04) = 0 then Return(2); endif;
if (A and $08) = 0 then Return(1); endif;
if (A and $10) = 0 then Return(4); endif;
if (A and $20) = 0 then Return(5); endif;
if (A and $40) = 0 then Return(6); endif;
A := ReadRow ($40); // row A6 to 0
if (A and $02) = 0 then Return(15); endif;
if (A and $04) = 0 then Return(13); endif;
if (A and $08) = 0 then Return(14); endif;
A := ReadRow ($20); // row A5 to 0
if (A and $02) = 0 then Return(12); endif;
if (A and $04) = 0 then Return(10); endif;
if (A and $08) = 0 then Return(11); endif;
A := ReadRow ($10); // row A4 to 0
if (A and $02) = 0 then Return(9); endif;
if (A and $04) = 0 then Return(7); endif;
if (A and $08) = 0 then Return(8); endif;
Return (0);
end;
// IR-Led Tastverhaeltnis An zu Aus
procedure TroedelOn;
begin
asm;
PUSH _ACCA
LDI _ACCA, 18
ton_loop:
DEC _ACCA
BRNE ton_loop
NOP
POP _ACCA
endasm;
end;
procedure TroedelOff;
begin
asm;
PUSH _ACCA
LDI _ACCA, 40
toff_loop:
DEC _ACCA
BRNE toff_loop
POP _ACCA
endasm;
end;
// 38kHz Burst
procedure OnPeriod;
begin
asm;
LDS _ACCCLO, IRControl.ClockCounter
LDS _ACCCHI, IRControl.ClockCounter+1
LDS _ACCB, IRControl.BurstClocks
CLR _ACCA
ADD _ACCCLO, _ACCB
ADC _ACCCHI, _ACCA
STS IRControl.ClockCounter, _ACCCLO
STS IRControl.ClockCounter+1, _ACCCHI
irb_loop1_on:
LDI _ACCA, IRControl.IRBurstLenght
irb_loop2_on:
CBI PortB, IRControl.IR_TX_BIT
RCALL IRControl.TroedelOn
SBI PortB, IRControl.IR_TX_BIT
RCALL IRControl.TroedelOff
DEC _ACCA
BRNE irb_loop2_on
DEC _ACCB
BRNE irb_loop1_on
endasm;
end;
procedure OffPeriod;
begin
asm;
LDS _ACCCLO, IRControl.ClockCounter
LDS _ACCCHI, IRControl.ClockCounter+1
LDS _ACCB, IRControl.BurstClocks
CLR _ACCA
ADD _ACCCLO, _ACCB
ADC _ACCCHI, _ACCA
STS IRControl.ClockCounter, _ACCCLO
STS IRControl.ClockCounter+1, _ACCCHI
irb_loop1_off:
LDI _ACCA, IRControl.IRBurstLenght
irb_loop2_off:
SBI PortB, IRControl.IR_TX_BIT
RCALL IRControl.TroedelOn
SBI PortB, IRControl.IR_TX_BIT
RCALL IRControl.TroedelOff
DEC _ACCA
BRNE irb_loop2_off
DEC _ACCB
BRNE irb_loop1_off
endasm;
end;
procedure AddByte;
begin
asm;
LDI _ACCALO, 8 ; 8 bits
LDS _ACCAHI, IRControl.TxCode
ab_loop:
LDI _ACCA, 2 ; on time
STS IRControl.BurstClocks, _ACCA
RCALL IRControl.OnPeriod
ROR _ACCAHI
BRCS ab_odd
LDI _ACCA, 2 ; short pause for zero
RJMP ab_put
ab_odd:
LDI _ACCA, 6 ; long pause for one
ab_put:
STS IRControl.BurstClocks, _ACCA
RCALL IRControl.OffPeriod
DEC _ACCALO
BRNE ab_loop
endasm;
end;
procedure SendButtonCode (KeyCode: byte; IsRepeated: boolean);
begin
Q_IRTransmit := True;
E_IRTransmit := True;
ClockCounter := 0;
DisableInts;
// SynPulse
BurstClocks := 31;
OnPeriod;
if IsRepeated then
BurstClocks := 8;
OffPeriod;
BurstClocks := 2;
OnPeriod;
BurstClocks := 12;
OffPeriod;
else
BurstClocks := 16;
OffPeriod;
endif;
// Data
TxCode := cBurdaIdent;
AddByte;
TxCode := cBurdaIdent xor $FF;
AddByte;
TxCode := KeyCode;
AddByte;
TxCode := KeyCode xor $FF;
AddByte;
// EOT
BurstClocks := 2;
OnPeriod;
Q_IRTransmit := True;
E_IRTransmit := False;
EnableInts;
// Gap
while ClockCounter < 372 do
BurstClocks := 1;
OffPeriod;
endwhile;
end;
//--------------------------------------------------------------------
// hier passiert nichts, ausser dass die Hauptschleife danach unten weiterlaeuft
interrupt WatchDogOvr;
begin
end;
procedure InitPorts;
begin
PRR := $0F; // Timer1 off, Timer0 off, SIO off, ADC off
LastButton := 0;
Q_DiagLed := True;
E_DiagLed := True;
end;
// der Watchdog ist das primaere Zeitmesssystem, alle 64ms weckt er den
// Prozessor auf um eine neue Messung vorzunehmen und zu bewerten.
// clear WDIF und WDE, enable WDIE,
// C4 = 0.25s int rate
// C3 = 0.125s int rate
// C2 = 64ms int rate
procedure InitWDogInt;
begin
asm;
CLI
WDR
IN _ACCA, WDTCR
ORI _ACCA, 018h
OUT WDTCR, _ACCA
LDI _ACCA, 0C2h
OUT WDTCR, _ACCA
SEI
endasm;
end;
procedure EnterIdleSpeed;
begin
CLKPR := $80; // enable clock divider change
// CLKPR := $04; // set System Clock [4=500kHz, 5=250kHz]
CLKPR := $00; // set fast System Clock [0=8MHz]
end;
// Takt auf 8MHz hochschalten
procedure EnterFullSpeed;
begin
CLKPR := $80; // enable clock divider change
CLKPR := $00; // set fast System Clock [0=8MHz]
end;
// laeuft mit langsamem Takt
procedure IdleHandler;
var
B: byte;
KeyCode: byte;
begin
Q_DiagLed := False;
repeat
B := GetActiveButton;
Keycode := cKeyNone;
if (B >= cMinButtonNo) and (B <= cMaxButtonNo) then
KeyCode := cKeyTable[B];
if Keycode <> cKeyNone then
EnterFullSpeed;
SendButtonCode (KeyCode, LastButton = B);
EnterIdleSpeed;
endif;
endif;
LastButton := B;
until (B = 0) or (Keycode = cKeyNone);
Q_DiagLed := True;
// disable BOD-detect during sleep
MCUCR := $84;
MCUCR := $80;
// nun CPU abschalten, bis sie vom Hund geweckt wird..
MCUCR := $30; // power down enabled
asm;
SLEEP
endasm;
end;
begin
InitPorts; // Definition Eingaenge/Ausgaene
EnterIdleSpeed; // Auf niedrigen CPU-Stromverbrauch trimmen
MDelay (100); // Warten bis Versorgungsspannung stabil ist
InitWDogInt; // Wachhund als Zeitbasis einrichten
EnableInts; // Zeitbasis erlauben
loop
IdleHandler;
endloop;
end IRControl.
```
volatile <type> varname;
\$\endgroup\$