# ATMEL mikrokontrolleri >  Iesācēja blēņas ar 4x4 plēves klavieri

## Jurkins

Labs vakars.
Savā vecuma marazmā  ::  pievērsos assemblerim. Gribu uztaisīt reālu projektiņu. Protams, var paņemt arduino un viss štokos, bet es negribu (zanudnijs esmu). Vajadzīgi kaut kādi taustiņi, un tad nu sāku ar klavieri. Pilns nets ar copy/paste, bet izglītošanās nolūkos mēģinu "izgudrot divriteni". Uztapa variants ar PCINT pārtraukumu. Rezultātā kods strādā, attiecīgajā reģistrā ierakstās taustiņa numurs (no 1 līdz 16). Bet radās jautājums par parādību - kontaktu drebēšanu. Vai šajā gadījumā tā radīs kaut kādas problēmas? Un vēl esmu strupceļā - PCINT vajadzētu nostrādāt uz līmeņa maiņu. Tas nozīmē, ja es nospiežu taustiņu, šim vajadzētu iziet visas kolonas un notīties no pārtraukuma. Ja taustiņš tiek atlaist, tad pārtraukums notiek, bet kolonas skanētas netiek. Bet es skatos ar osci, ka turot taustiņu nospiestu, klaviere visu laiku tiek skanēta. It kā "kontaktu drebēšana" nevarētu būt vainīga. Jebšu šitas "divritenis"ir galīgi garām? 



```
;====================================================================
; DEFINITIONS
;====================================================================
.include "C:\Program Files (x86)\Labcenter Electronics\Proteus 8 Professional\Tools\AVRASM\appnotes\m328Pdef.inc"

.def tmp = r16
.def tmp1 = r17
.def tmp2 = r18
.def tmp3 = r20
.def btn_num = r19                                                                                                 ;nospiestā taustiņa numurs
.equ btn_row_0 = PINB0
.equ btn_row_1 = PINB1
.equ btn_row_2 = PINB2
.equ btn_row_3 = PINB3
.equ btn_col_0 = PORTB4
.equ btn_col_1 = PORTB5
.equ btn_col_2 = PORTB6
.equ btn_col_3 = PORTB7
.equ row_count = 4 

;====================================================================
; RESET and INTERRUPT VECTORS
;====================================================================

      ; Reset Vector
      rjmp  Start
.org 0x0006
      rjmp btn_push                                ;PCINT0 pārtraukuma vektors

;====================================================================
; CODE SEGMENT
;====================================================================

Start:
      ldi tmp3, row_count                                                                         ;klavieres rindu skaits, šoreiz 4
      ldi tmp, (1<<PIND0)|(1<<PIND1)|(1<<PIND2)|(1<<PIND3)|(1<<PIND4)        
      out DDRD, tmp                                                                                ;PORTD0...4 pataisam par izejām LEDiem (kontrolei)
      ldi tmp, (1<<btn_col_0)|(1<<btn_col_1)|(1<<btn_col_2)|(1<<btn_col_3)
      out DDRB, tmp                                                                                ;PORTB4...7 pataisam par izejām - klavieres kolonas
      ;clr tmp
      ;out PORTB, tmp
      ldi tmp, (1<<btn_row_0)|(1<<btn_row_1)|(1<<btn_row_2)|(1<<btn_row_3)
      out PORTB, tmp                                                                              ;uzliekam pull-up klavieres rindām
      ldi tmp, (1<<PCIE0)
      sts PCICR, tmp                                                                               ;atļaujam PCIE0 pārtraukumu
      ldi tmp, (1<<PCINT0)|(1<<PCINT1)|(1<<PCINT2)|(1<<PCINT3)
      sts PCMSK0, tmp                                                                            ;pārtraukums strādās uz izmaiņām PCINT0...3
      sei                                                                                               ;atļaujam pārtraukumus
      
Loop:
      rjmp  Loop

btn_push:
;      clr btn_num
      in tmp2, PinB              ;ielasam reģistrā klavieres rindas                            
      ori tmp2, 0xf0             ;uzliekam masku - apskatam tikai PB0...3 bitus
      cpi tmp2, 0xff             ;ja visi "1", tad poga ir palaista vaļā, un
      breq no_btn               ;lecam uz pārtraukuma beigām
      clr btn_num               ;ja ir kāda "0", tad iztīram taustiņa numura reģistru
      clr tmp2                    
      ser tmp1                   ;saliekam reģistrā, kuru izmantojam kolonu skanēšanai vieniniekus
next_col:                        ;meklējam, kurā kolonā nospiestais taustiņš
      sec                          ;C karogā ierakstam "1"
      tst btn_num              ;ja taustiņa numurs ir nulle,
      brne not_first_col       ;tad pārlecam
      clc                           ;ja nē, tad C karogā ierakstam "0"
not_first_col:          
      ror tmp1                   ;šiftojam kolonu skanēšanas reģistru
      out PORTB, tmp1        ;rakstam iekš PORTB izejām
      add btn_num, tmp3     ;pieskaitam taustiņa numuram 4
      in tmp2, PinB              ;nolasam klavieres rindas 
      ori tmp2, 0xf0             ;uzliekam masku
      cpi tmp2, 0xff             ;salīdzinam ar 255
      breq next_col             ;ja visi "1", tad lecam atpakaļ pārbaudīt nākamo kolonu
      sub btn_num, tmp3     ;atskaitam no taustiņa numura 4
next_row:                       ;meklējam, kurā rindā nospiestais taustiņš
      inc btn_num              ;pieskaitam taustiņa numuram "1"
      lsr tmp2                    ;šiftojam 
      brcs next_row           ;ja C karogā nav "0", tad lecam atpakaļ
no_btn:
      ldi tmp1, 0x0f    
      out PORTB, tmp1       ;nometam visas kolonas uz "0"
      out PORTD, btn_num  ;iededzam nospiestā taustiņa numuram atbilstošos LEDus
      reti
;====================================================================
```

----------


## M_J

Labvakar!
Beidzot kāds pievērsies assemblerim! Citādi, galīgi nav ar ko apspriest AVR programmēšanu šajā valodā. Jāatzīstas pašreiz īsti nav laika iedziļināties kodā, jo jāsteidz cits darbs, bet uzreiz varu teikt ka ar kontaktu drebēšanu visticamāk vajadzēs cīnīties. Neesmu tieši ar plēves klavierēm ņēmies, bet visādām podziņām un slēdžīšiem kontaktu drebēšana ir aktuāla. Jāsaka gan, ka viss, kas notiek ar klaviatūru ir tik lēns process, ka tam pārtraukumus neizmantoju, parasti šādas lēnās lietas apskatu reizes 40 sekundē un, ja kaut kas izmainās, piefiksēju jauno vērtību un uzmetu skaitītāju. Ja skaitītājs noskaita līdz nullei un jaunā vērtība joprojām ir aktuāla, tad pieņemu, ka taustiņš tiešām ir izmainījis savu stāvokli. Par pārtraukuma atkārtošanos šajā gadījumā, man uzkrita, ka pārtraukuma laikā kaut kas tiek rakstīts reģistrā PORTB, tas ir turpat, kur PINB izmaiņas izsauc pārtraukumu. Vai tas nevarētu izsaukt atkārtotu pārtraukumu? Bet atkārtošos - neesmu iedziļinājies kodā, vienkārši tas iekrita acīs. Otra lieta, kam šajā gadījumā nav nozīmes, bet ja galvenā programmas cilpa ir garāka un sarežģītāka, kas noteikti jāņem vērā - sākot apstrādāt pārtraukumu jāsaglabā statusa reģistrs SREG un izejot no pārtraukuma jāatliek atpakaļ.

----------


## Jurkins

Patiesībā, mana pēdējā tikšanās ar kaut ko līdzīgu bija ... ui cik sen. Automātiskais numura noteicējs, jeb AONs, kā šo sauca. Mikrokontroliera lomu gan spēlēja Z80. Tur klaviatūras "apprasīšana" notikās izmantojot vienīgo ārējo pārtraukumu kaut kādas ... nezin cik (netceros vairs) reizes sekundē. Vienkārši ieraudzīju piemēru ar šo PCINT un rakstīju (mācīšanās nolūkos) pa savam.
Jā, kad biju uzrakstījis postu, jau iedomājos par šo variantu, ka iekš pārtraukuma kaut kas notiek ar to portu, tad tas varētu būt iemesls. Tikai, vai tad ieejot pārtraukumā, pārtraukumi neaizliedzas? Jāmācā matčasķ  :: 
Taisnība, par to statusa reģistru zinības apguvu, vienkārši šeit neierakstīju tās rindiņas. 
p.s. bet assemblers ir foršs  ::

----------


## M_J

Ieejot pārtraukumā pārtraukumi aizliedzas, bet ar RETI atkal atļaujas. Ieejot pārtraukumā, tev nometas tie karogi kas izsauc pārtraukumu, bet ja tu pārtraukumā kaut ko sadari, kas izsauc jaunu pārtraukumu, tad tiklīdz pārtraukumi tiek atļauti, tā pārtraukums notiek. Lai tā nenotiktu, tad tad pārtraukumā pēc darbībām ar PORTB drošības labad būtu jānomet tas pārtraukuma karogs ierakstot tur vieninieku, kā teikts šeit:
• Bit 1 – PCIF1: Pin Change Interrupt Flag 1
When a logic change on any PCINT[14:8] pin triggers an interrupt request, PCIF1 becomes set (one). If the I-bit
in SREG and the PCIE1 bit in PCICR are set (one), the MCU will jump to the corresponding Interrupt Vector.
The flag is cleared when the interrupt routine is executed. Alternatively, the flag can be cleared by writing a
logical one to it.

----------


## M_J

Atvainojos, es par PCIF1, bet vajadzēja par PCIF0, bet būtība jau tā pati.

----------


## Jurkins

Vo, paldies. Nepadomāju, ka tas pārtraukums paliek atmiņā. Mēģināšu. Droši vien jau, ka šis klavieres apprasīšanas veids paliks tikai mācīšanās nolūkos, bet jātiek galā.

----------


## next

Atbalstu ideju ka klavieres skaneeshanu nevajag taisiit ar paartraukumiem (to vienmeer maz un svariigaakaam lietaam vajadziigi).
Labaak pievienot citam cikliskam procesam - piemeeram dinamiskaas indikaacijas regjeneraacijai (arii porti tad mazaak vajadziigi jo ar tiem pashiem klavieres matricu var adreseet).

----------


## next

> Jāsaka gan, ka viss, kas notiek ar klaviatūru ir tik lēns process, ka tam pārtraukumus neizmantoju, parasti šādas lēnās lietas apskatu reizes 40 sekundē un, ja kaut kas izmainās, piefiksēju jauno vērtību un uzmetu skaitītāju. Ja skaitītājs noskaita līdz nullei un jaunā vērtība joprojām ir aktuāla, tad pieņemu, ka taustiņš tiešām ir izmainījis savu stāvokli


 Mans "viena pirksta" klavieres algoritms taads:
Pogai ir viens stabils staavoklis - atlaista.
Ja konstateeta nospieshana tad uzsaakam taimautu kura laikaa atlaishana tiek ignoreeta bet nospiests staavoklis taimauta laika skaitiishanu uzsaak no jauna.
Parasti taada pieeja tiek kritizeeta sakot ka impulsveidiigs trauceejums var tikt uztverts kaa pogas nospieshana.
Man domaat tomeer trauceejumus sheemtehniski jaanoveersh un programmistam tikai ar kontaktu drebeli jaaciinaas.

----------


## Jurkins

Oi, atmiņa pievīla. Toč, manam vienīgajam kontaktam ar assembleri AONam klaviatūras apprasīšana notika kopā ar dinamisko indikāciju. Pārtraukums uz klavieri bija ZX Spectrum (ja kāds vēl tādu atceras).
Nu jā, bet kas to nospiešanu konstatē? Vai iekš galvenā programmas cikla katrā caurgājienā vienmēr tiek pārbaudīts visu pogu stāvoklis? Un nospiešanas gadījumā apakšprogramma? Un vai tad gadījumā, ja nepieciešami kādi svarīgi aprēķini, nu nezinu, tipa enkodera apkalpošana un pozīcijas aprēķināšana, vai tāda pogu apprasīšana nav jāaizliedz?

----------


## M_J

Katrā galvenās programmas cikla caurgājienā to galīgi nav nepieciešamas darīt. Es to daru šādi. Uzstādu kādu no taimeru pārtraukumiem ar tādu periodu, ka tas realizēsies 40 reizes sekundē. Pašā taimera pārtraukumā neko īpašu nedaru, tikai uzmetu pazīmi, ka tāds pārtraukums bijis. Galvenajā programmas ciklā, pamanot šo pazīmi, eju izpildīt apakšprogrammu, kurā apkopotas visas tās lietas, kas jādara 40 reizes sekundē. Pabeidzot šo apakšprogrammu nometu arī taimera uzmesto pazīmi. Un līdz nākošajam taimera pārtraukumam, kas šo pazīmi atkal uzmetīs, galvenais cikls griežas pa mazo apli. Pārtraukumos bez īpašas vajadzības vispār cenšos neko lieku nedarīt, jo pārtraukuma laikā citi pārtraukumi tiek aizliegti un atmelis būtībā ir kurls un akls un var nogulēt kaut ko svarīgu. Var jau arī pārtraukuma laikā piespiedu kārtā atļaut pārtraukumus, bet ar to ir jābūt ļoti uzmanīgam, var iebraukt dziļās auzās. ir gadījies.

----------


## Jurkins

Nu tad principā es varu kaut vai šajā PCINT pārtraukumā ierakstīt pazīmi un, savukārt, to kodu pārvietot uz apakšprogrammu. OK, paldies par padomiem, paburšos izglītošanās nolūkos.
Tā īsti vēl neesmu iebraucis ar tiem skaitītājiem. Ja man vajadzēs vairākus PWM, vai man nepietrūks skaitītāju, ja viena pārtraukumu izmantošu šādam mērķim. Tas nav jautājums, pagaidām vienkārši pārdomas (citādi mans CPU neatbalsta multithreading  :: ). Pie šiem lietām ķeršos vēlāk

----------


## M_J

Tas ir atkarīgs no tā, kā organizē PWM. Vienkāršākais ir vienkārši iedarbināt attiecīgā skaitītāja attiecīgo PWM kanālu. Tad ir jāskatās, cik PWM kanālu attiecīgajam atmelim ir. Vecākiem to bija pamaz, jaunākiem ir vairāk. Piemēram Atmega128 bija divi 8 bitu taimeri, katram viens PWM kanāls, un divi 16 bitu taimeri, tagad nepateikšu, cik katram no tiem ir PWM kanālu, jo tos es nevarēju izmantot tāpēc, ka ja izmanto taimeri priekš PWM, tad no kaut kā ir arī jāatsakās, un to nevarēju. Katrā ziņā PWM kanālu man pietrūka. Bet, man tos PWM kanālus vajadzēja ar zemu frekvenci, kaut kur ap 1kHz. Un tad, izmantojot vienu no 16 bitu OUTPUT COMPARE pārtraukumiem, programmistiski noorganizēju 8 PWM kanālus. Bet vispār, pašreiz ar kāru aci skatos un taisos pamēģināt STM32 procesorus. Izskatās, ka tie pa visiem parametriem bez maz pa kārtu iekabina atmeļiem. Pie tās pašas cenas. Arī PWM kanālu tiem vesela jūra.

----------


## Jurkins

Nu ja, man tas viss vēl priekšā apgūstams. 
Ievēroju, ka arduino līnijas LCD displejs modulītim pogas uzsēdinātas uz analogās ieejas caur rezistoru dalītājiem. Netā ir piemēri, kur 3x4 taustiņu klavieres tā tiek realizētas. No vienas puses ļoti ieekonomējas in/out līnijas, bet kaut kā ...

----------


## ezis666

Man arī patīk ASM, vismaz var skaidri saprast, kas notiks, un kā uzrakstīsi tā arī būs. es arī parasti pogas aptaujāju cilpā, laiku pa laikam, kad poga nospiesta, tad citas nav aktīvas, bet feināk ir caur rezistoru dalītāju un ar ADC pamērīt.

----------


## JDat

Uhh... Kur tie laiki... AVR ASM. Kas attiecas uz pretenzijām pret interrupt lietošanu skenēšanā. Piemēram: Procesors guļ ziemas (PCINT gadījumā: vasaras) miegā un gandrīz neko netērē. Visi stabiņi ir LOW. Visas rindiņas High ar pullup palīdzību. Tiklīdz notiek izmaiņa no High uz low, tā noskenējam matricu un atceramies stāvokli. Pagaidam debounce laiku. debounce laikā interrupt nočeko taimeri, ja laiks pagājis skenē vēlreiz, ja laiks nav pagājis iet gulēt. Ja poga nospiesta, izdaram ko vajag un guļam tālāk līdz ar to  nekāda vajadzība regulāri skenēt. Viss! Katram uzdevumam savs risinājums.

Hifilītiķis stāstīja stāstu: ir košerīgā dithering mikrene, bet jākontrolē ar MCU. Kas notiek? MCU guļ ziemas miegā un gaida vienīgās pogas nospiešanu. Kad poga nospiežas, tad uzliek jaunu uzstādījumus mikrenei un guļ tālāk. Kāpēc? Lai neradītu tokšņus barošanas ķedēs. Pārslēgšanas brīdī sīkums, bet kad bauda mūziku (vīns, kamīns utt), tad visam jābūt tip-top. Hipotētiski. Morāle: cik garš, tik resns.


Kas attiecas uz konrētu problēmu. Jāizlasa datašits. Manā gadījumā (esmu bremze) pat vairākas reizes, kamēr atceras, pamana un saprot mazāko niansi ko ražotājs ir ierakstījis datu lapā. Piemēram. Savulaik biju pārliecināts ka AVRam ir tikai viens PCIN, bet, pateicoties Jurkina tēmai, izlasīju vēlreiz un... Bāc! Ir veseli 3 PCIN, katrs savam portam... Tad vēl nianse par to ka INT0 un INT1 var uzlikt kā vajag (High, low, rise, fall) bet... Kas ir ar PCINT (visiem)? Uz ko šamie reaģēs? Retoriski...

----------


## Jurkins

PCINT reaģē uz abām frontēm, un tur neko nevar pamainīt.
Jā, ja jāceļ no gulēšanas, tad bez pārtraukuma neiztikt.
Jāpamēģina būs ar to ADC arī. Redzesloka paplašināšanai  :: 

edit:
M_J bija taisnība. Pārtraukuma beigās ierakstot iekš PCIF0 "1", noskanē vienreiz un notinas no pārtraukuma.

----------


## Jurkins

Radās tāds (muļķīgs) jautājums - piem. siemens s7-200 ir 256 taimeri un 256 skaitītāji. Nez vai tie ir dzelziskie taimeri vai softiskie?

----------


## M_J

Manuprāt būtu pilnīgs idiotisms taisīt 256 dzelziskus taimerus, kad to pašu var dabūt gatavu ar dažiem klikšķiem pa klaviatūru. Cik rāda mana nelielā pieredze ar rūpnieciskajiem kontrolieriem, nekāda dižā ātrdabība tiem viņu PLC taimeriem nav, piemēram Comap es būtu varējis norealizēt vienu lietu, ja taimera izšķiršanas spēja būtu kaut vai 0.01 sekunde. Bet kas to deva, tādas lietas jātaisa pašam uz Atmel. Nezinu, kā ir ar Siemens, bet tā ir firma, kas joprojām nebeidz mani pārsteigt. Sliktā nozīmē.

----------


## Jurkins

s7, ja pareizi atceros, bija trīs veidu taimeri - ar takti 1ms, 10ms un  100ms. It kā veselais saprāts saka, ka softiskie, bet pagaidām nevaru  izgudrot, kā uzrakstīt 10 neatkarīgus skaitītājus. Laikam par ātri gribu  visu saprast  ::

----------


## JDat

viena softiskā timer tikšķis ir vien dzelziskā timer overflow vai irq kad sasniegts noteikts skaits. Tādā veidā ar ātru MCu var uztaisīt kaut tūkstoti softisko timeru. Pie tam, ja visu pareizi noslēpj C bibliotēkās...

----------


## Jurkins

Laikam tad sanāk, ka es tā uzrakstīju savam skaļuma regulatoram. Negribēju izmantot aiztures. Gan ne asmā un tur man tikai divi "taimeri" sanāca. Laikam tad esmu uz pareizā ceļa.

----------


## JDat

Neliela teorija pa (bez?)tēmu. Varbūt noder. ASM ir patīkams ar to ka vari visu kotrolēt un uztaisīt visu kā Tev patīk. Realizēt to, kas ar C nav realizēts vai standarta gadījumā paņem pārāk daudz resursu (atmiņa, CPU cikli utt). Iprovizācijas šovs "Spiediens" tā teikt...

----------


## M_J

Jā, ar 1ms izšķiršanas spēju jau kaut ko var izdarīt, bet, ja būtu darīšana, piemēram, ar auto motora vadību, tad mēģinot izmantot tādas precizitātes taimeri aizdedzes apsteidzes vadīšanai, kļūda būtu šaušalīga. Bet tam jau arī tie kontrolieri nav domāti, kaut reizēm rodas vajadzība pēc kaut kā ātra un precīza. Runājot par vairākiem neatkarīgiem skaitītājiem, izmantojot vienu taimeri - es organizēju notikumu rindu. Katru notikumu raksturo laika moments kurā šim notikumam jānotiek un kaut kādas darbības, kas šajā laika momentā jāizdara. Man ir atmelis ar 16 bitu taimeri. Vispār ar atmeļa 16 bitu aparātisko taimeri ir par īsu, tāpēc parasti pieorganizēju tam vēl vienu programmistisku baitu. Tātad notikuma laika momentu man raksturos 3 baiti. Vēl 2 baiti norādīs apakšprogrammas adresi, kura man ir jāizpilda šajā laika momentā, vēl 3 baitus saistībā ar notikumu norezervēju citiem datiem saistībā ar paredzēto notikumu. Tātad 1 notikumu man apraksta 8 baiti. Ja gribu ieplānot 8 šādus notikumus, rezervēju atmiņā 64 baitus, ja vairāk, tad attiecīgi lielāku atmiņu. Kad gaidāmo notikumu rinda tiek papildināta ar jaunu notikumu, tiek skatīta cauri jau uzsādīto notikumu rinda, un atrasta vieta jaunajam notikumam, tā lai rindā esošie notikumi būtu sakārtoti laika ziņā pieaugošā secībā. Taimera OUTPUT COMPARE pārtraukums ir uzstādīts uz laika ziņā tuvāk esošo notikumu. Pienākot šim laika momentam, tiek izpildīta attiecīgajam notikumam piesaistītā apakšprogramma un taimera pārtraukums tiek uzstādīts uz nākošo rindas elementu. Tādejādi galvenā programma visu laiku kārto un papildina rindu ar jauniem elementiem, savukārt pārtraukumi izpilda šos elementus un izmet izpildītos no rindas.

----------


## Jurkins

Paldies, šito tagad centīšos sagremot. Liekas, ka uz to pusi tikai primitīvā un juceklīgā veidā es darījos.

----------

