-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcapitol_3.tex
2160 lines (1628 loc) · 141 KB
/
capitol_3.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
%% Capitol 3
% \part{Programació de perifèrics I}
% \label{part:programacio}
En aquest capítol s'aniran introduint i explicant els perifèrics mes habituals que trobem en els microcontroladors actuals. Cada capítol constarà d'una petita introducció al perifèric en qüestió i un petit exemple amb el codi corresponent i els comentaris adients.
\begin{remark}
El tipus de codi que es veurà als exemples es coneix com {\em Baremetal} que vol dir que no es fa servir cap Sistema Operatiu, que es veurà a la \fullref{part:freertos}. Aquests sistemes es basen en les inicialitzacions necessàries i un bucle sense fi al {\bf main()}\index{main()} on es realitzen les operacions desitjades. S'acostuma a usar aquest mètode en aplicacions senzilles o en aplicacions que corren en microcontroladors poc potents o sense prou memòria com per executar còmodament un \gls{RTOS}.
\end{remark}
\chapter{Consola de Debug}
\label{sub:console}
Un de les principals diferències quan treballem amb sistemes encastats és que no tenim una consola on executem el nostre binari i podem veure quins resultats ha obtingut.
Una millora d'ARM respecte arquitectures anteriors va ser la d'incorporar ja fa temps un mecanisme de debug, a través d'un pin d'output anomenat {\em SWO} que permet enviar dades cap a una consola al nostre PC de desenvolupament.
Aquest pin {\em SWO} forma part del sistema de Debug dels Cortex anomenat ITM ({\em Instrumentation Trace Macrocell}) \cite{ITM}. Això vol dir que la majoria de microcontroladors basats en Cortex que ens trobem, siguin del fabricant que sigui portaran aquesta funcionalitat.\footnote{La majoria de Cortex-M0+ no porten aquest perifèric.}
Per poder fer servir aquesta funcionalitat, cal primer configurar el pin SWO per a que funcioni com a tal (es pot fer servir també com un \gls{GPIO} normal). Tot seguit es configura el dispositiu de trace i el mòdul ITM d'aquest. La configuració es fa a la funció {\bf setupSWOForPrint()}\index{setupSWOForPrint()}.
Un cop configurat el mòdul ITM, la funció {\bf ITM\_SendChar()}\index{ITM\_SendChar()} permet enviar caràcter a caràcter el que vulguem presentar a la consola a l'altre costat.
En el cas de Simplicity, els caràcters rebuts es presenten directament a la consola de la part d'abaix del \gls{IDE} (Figura~\ref{fig:ITM}).
\begin{figure}
\centering
\includegraphics[width=0.85\textwidth, keepaspectratio]{imatges/ITM_Debug.png}
\caption{Captura de pantalla de la Consola del Simplicity Studio}
\label{fig:ITM}
\end{figure}
\chapter{Fent servir printf}
\label{sub:console_example}
Tot i que això és força útil, podem fer servir una funció força coneguda i molt útil com és {\bf printf()}\index{printf()} per fer-nos-ho tot més senzill (a canvi d'alguna cosa, com veurem).
La funció {\bf printf()} és una vella coneguda per qualsevol programador de C (o C++, o PHP, o …). Així que seria genial poder fer servir aquesta funció en el nostre sistema encastat i que la cadena aparegui a la consola del nostre PC. Això ho podem fer de la següent manera. Primer de tot cal saber que la funció {\bf printf()} fa servir la funció de sistema {\bf \_write()} per imprimir la cadena. Per tant, caldrà que implementem aquesta funció per tenir el nostre desitjat {\bf printf()}.
\index{\_write()}\index{ITM\_SendChar()}
\begin{lstlisting}[caption={Funció {\bf \_write()}},style=customc,label=write]
int _write(int file, const char *ptr, int len) {
int x;
for (x = 0; x < len; x++) {
ITM_SendChar(*ptr++);
}
return (len);
}
\end{lstlisting}
Com podem veure al Llistat~\ref{write}, el que fa aquesta funció és anar enviant un a un tots els caràcters de la cadena passada via el paràmetre {\bf ptr} i de longitud {\bf len}.
També cal configurar el mòdul ITM (mòdul de debug) perquè activi la sortida {\em SWO} cridant a la funció {\bf setupSWOForPrint()}\index{setupSWOForPrint()}. Un cop fet això, ja podem fer servir la funció {\bf printf()}\index{printf()} tal com hem fet sempre.
Aquesta explicació la podeu trobar al \href{http://community.silabs.com/t5/Simplicity-Studio-and-Software/how-to-enable-printf-output/td-p/133981}{fòrum de Silicon Labs} (no cal registre) i el codi el teniu en el directori d'instal·lació del Simplicity/developer/sdks/exx32/v4.4.1/kits/common/drivers/ als fitxers:
\begin{itemize}
\item retargetio.c
\item retargetswo.c
\end{itemize}
\section{Problemes d'usar printf}
Com dèiem, poder fer servir el nostre estimat {\bf printf()}\index{printf()} al nostre sistema encastat no ens sortirà gratis. Com que aquesta funció és força complexa i permet moltes possibilitats, incloure-la en un projecte afegirà una bona quantitat de memòria de programa.
En l'exemple que tenim al repositori, les mides són les que es veuen a la Taula \ref{tb:printfsize}. Per tant, podem estimar que afegir {\bf printf()} al nostre projecte afegirà uns 3800 Bytes (3.7 KB) de codi de programa.
\begin{table}[htb]
\caption{Mida de l'executable segons {\bf printf()}}
\centering
\begin{tabular}{|c|c|}
\hline
{\bf Opció } & {\bf Bytes secció .text} \\
\hline
Sense printf() & 956\\
\hline
Amb printf() & 4.748\\
\hline
Amb printf() i punt flotant &13.644\\
\hline
\end{tabular}
\label{tb:printfsize}
\end{table}
Potser no és gaire important aquesta quantitat, però segur que caldrà tenir-la en compte si estem treballant amb microcontroladors que tenen poca memòria FLASH de programa (hi ha Cortex-M0+ amb només 4 KB de \gls{FLASH}!).
També cal tenir en compte que algunes versions de la funció {\bf printf()} no suporten valors en punt flotant. Segons l'eina, caldrà activar aquesta opció en cas que la vulguem fer servir (Figura \ref{fig:SILabsPrintfFloat}. Cal tenir en compte que això incrementarà encara més la quantitat de memòria que necessitarà aquesta funció com es veu a la Taula \ref{tb:printfsize}.
\begin{figure}[htb]
\centering
\includegraphics[width=0.85\textwidth, keepaspectratio]{imatges/SilabsPrintfFloat.png}
\caption{Opció a {\em Simplicity} per activar el punt flotant al {\bf printf()}}
\label{fig:SILabsPrintfFloat}
\end{figure}
Una forma força habitual de disposar dels avantatges del printf
mentre es desenvolupa i treure'l de forma ràpida quan es genera el
binari definitiu es redefinir el \textbf{printf} amb un nom nostre, i
establir una variable condicional de compilació per activar o no el
printf real, tal i com es veu en el llistat Llistat~\ref{ourprintf},
llavors en el nostre codi, enlloc de cridar \textbf{printf} per
mostrar un missatge, haurem de fer servir \textbf{PRINTF}.
\index{printf()}\index{PRINTF()}
\begin{lstlisting}[caption={Redefenir {\bf printf()}},style=customc,label=ourprintf]
#ifdef USE_PRINTF
#define PRINTF(...) printf( __VA_ARGS__)
#else
#define PRINTF(...)
#endif
\end{lstlisting}
\chapter{Gestió de rellotges}
\label{sec:clocks}
En la majoria de microcontroladors moderns la gestió dels rellotges és una qüestió delicada i molt important. Per tal de millorar el consum del dispositiu, és habitual tenir un control i poder decidir si cert perifèric rep el senyal de rellotge o no (més detalls a \fullref{ch:low-power}). En cas que no el rebi, el perifèric romandrà totalment desconnectat i no consumirà energia. En cas que el vulguem fer servir, una de les primeres coses que haurem de fer és activar i proporcionar-li el senyal de rellotge adequat.
Per aquesta tasca de controlar els rellotges, acostuma a existir un perifèric concret que fa tota la gestió, tant de manegar l'entrada de diferents senyals de rellotge com de preparar i enviar aquests senyals als diferents perifèrics. En els microcontroladors tant de SiliconLabs com de ST tenim diferents branques de rellotge per diferents perifèrics i la CPU. En termes generals podem dir que els perifèrics considerats lents (RTC, LEUART, etc.) reben un senyal de rellotge de baixa freqüència, els perifèrics considerats ràpids (USART, SPI, DAC, ADC, Timers, etc.) un senyal de rellotge d'alta freqüència i la CPU i els perifèrics més relacionats (DMA, Interrupcions, etc.) un altre rellotge \cite[94]{EFM32GRM}\cite[152]{STM32F4RM}.
Al llarg dels diferents exemples s'anirà veient com es gestionen els rellotges. En els casos més senzills, tant sols cal activar el rellotge pel perifèric desitjat cridant a la funció {\bf CMU\_ClockEnable()}\index{CMU\_ClockEnable()}. Aquesta funció rep com a paràmetre el perifèric al que se li vol enviar o desactivar el rellotge.
Altres funcions permeten decidir quin senyal rellotge concret es connecta amb quina branca (funció {\bf CMU\_ClockSelectSet()}\index{CMU\_ClockSelectSet()}); dividir un rellotge abans d'entrar a cert perifèric ({\bf CMU\_ClockDivSet()}\index{CMU\_ClockDivSet()}) (Llistat~\ref{clk_mng}).
\index{CMU\_ClockSelectSet()}\index{CMU\_ClockDivSet()}\index{CMU\_ClockEnable()}
\begin{lstlisting}[style=customc,caption={Exemple de configuració del rellotge pel RTC},label=clk_mng]
CMU_ClockSelectSet( cmuClock_LFA, cmuSelect_LFXO ); // El rellotge "Low Frequency Crystal Oscillator" entra al bus LFA
CMU_ClockDivSet( cmuClock_RTC, cmuClkDiv_32768 ); // El rellotge es divideix per 32768 abans d'alimentar el RTC
CMU_ClockEnable(cmuClock_RTC, true); // S'activa el rellotge pel periferic RTC
CMU_ClockEnable(cmuClock_HFLE, true ); // S'activa el rellotge "Low energy clock"
\end{lstlisting}
\section{{\em Systick}}
\label{sec:systick}
Com ja s'ha mencionat breument a \fullref{se:arquitectura}, dins els {\em cores} ARM-M hi ha un {\em timer} simple sense cap relació amb els {\em Timers} perifèrics que veurem més endavant a \fullref{sub:Timers}. Aquest {\em timer} es coneix pel nom de \gls{Systick} i consta tan sols d'un comptador decreixent de 24 bits i un generador d'interrupció en quant arriba a zero \cite[312]{GuideCortexM3M4}. El timer Systick funciona amb el rellotge de la CPU dividit per algun factor configurable. Això caldrà tenir-ho en compte alhora de fer implementacions de baix consum, on en ocasions s'atura aquest rellotge (veure~\fullref{ch:low-power}).
Aquest {\em timer} està pensat perquè els S.O. el puguin fer servir, i com que està integrat dins el {\em core} Cortex, la portabilitat dels S.O. entre diferents fabricants serà molt senzilla (seria més complicat haver de fer un port per cada {\em timer} diferent de cada fabricant).
%%ATENCIO: Trenco manualment CMU\_ClockFreqGet\\(cmuClock\_CORE) pq Latex no ho fa be
L'\href{https://github.com/mariusmm/cursembedded/tree/master/Simplicity/Systick}{exemple al repositori} implementa una funció de {\bf Delay()}\index{Delay()}. El que es fa és configurar el {\em Systick} perquè generi una interrupció cada 1 mil·lisegon (Llistat~\ref{systickconfig}). Com que la funció {\bf CMU\_ClockFreqGet\\(cmuClock\_CORE)}\index{CMU\_ClockFreqGet()} retorna la freqüència de funcionament del rellotge del sistema, al dividir-la per 1000 i configurar el Systick amb aquest valor, generarà una interrupció cada mil·lisegon.
A la ISR corresponent s'incrementa una variable global per tenir un comptatge dels mil·lisegons transcorreguts (veure Llistat~\ref{systickISR}). La variable {\bf msTicks} s'ha definit com a {\bf volatile} pel que s'explicarà a \fullref{sb:volatile}.
\index{SysTick\_Config()}\index{CMU\_ClockFreqGet()}
\begin{lstlisting}[caption={Configuració del {\em Systick}},style=customc,label=systickconfig]
main() {
...
SysTick_Config(CMU_ClockFreqGet(cmuClock_CORE) / 1000);
...
}
\end{lstlisting}
\index{SysTick\_Handler()}
\begin{lstlisting}[caption={ISR del {\em Systick}},style=customc,label=systickISR]
void SysTick_Handler(void)
{
msTicks++; /* increment counter necessary in Delay()*/
}
\end{lstlisting}
Per últim, la funció {\bf Delay()}\index{Delay()} rep com a paràmetre els mil·lisegons a aturar-se i s'espera aquest temps comptant el temps fent servir la variable global que incrementa la ISR (Llistat~\ref{systickDelay}).
\index{Delay()}
\begin{lstlisting}[caption={Funció delay() amb {\em Systick}},style=customc,label=systickDelay]
void Delay(uint32_t dlyTicks)
{
uint32_t curTicks;
curTicks = msTicks;
while ((msTicks - curTicks) < dlyTicks) ;
}
\end{lstlisting}
\chapter{GPIO}
\label{sub:GPIO_2}
\glsreset{GPIO}
Diem \gls{GPIO} al perifèric encarregat de la gestió de l'entrada i sortida de propòsit general. Fent servir aquest perifèric podrem configurar l'entrada o la sortida d'un pin concret del microcontrolador i posar-hi el valor desitjat ('0' o '1') o llegir quin valor hi ha posat algun altre dispositiu.
De forma general, un pin en concret el podrem configurar perquè treballi com a entrada o com a sortida. Si un pin està configurat com a entrada, el valor de voltatge elèctric que tingui a l'entrada del pin, es podrà llegir per part del codi del microcontrolador. De forma inversa, un pin configurat com a sortida posarà el valor elèctric equivalent al valor que el codi escrigui.
Així, si estem treballant a 3.3 Volts d'alimentació i d'entrada i sortida, si a un pin configurat com d'entrada algun altre dispositiu
hi posa un valor proper a 3.3 volts, des de el nostre software o comunament anomenat \textbf{Firmware} (\gls{FW}) en la seva forma anglesa, llegirem que aquest pin té un valor d''1'. En canvi, si el pin està configurat com a sortida, quan posem un '1' des del \gls{FW}, el pin corresponent forçarà un valor de 3.3 volts (com a la Figura~\ref{fig:led}).
\begin{remark}
Quan diem que l'alimentació és de 3.3 Volts estem suposant aquesta tensió d'alimentació, però el rang acceptable va de 1.8 fins a 3.8 Volts i llavors les sortides tindrien el valor d'alimentació \cite[9]{EFM32TG840}. Així, si alimentem el microcontrolador a, posem per cas, 2.8 Volts, un '1' lògic de sortida d'un pin forçarà 2.8 Volts a aquell pin.
\end{remark}
\begin{figure}
\centering
\includegraphics[width=0.85\textwidth, keepaspectratio]{imatges/led.png}
\caption{Esquemàtic mostrant un LED connectat a un pin de GPIO}
\label{fig:led}
\end{figure}
Existeixen diferents formes de configurar un pin segons el fabricant i la tecnologia, la més comuna és el mode {\em push-pull} que permet forçar un valor '1' o '0' segons convingui. Una altra opció que de vegades cal fer servir és el mode {\em open-drain}, que la sortida només pot forçar el valor '0' però no el valor '1'.
En aquest cas, per forçar el valor '1' es fa servir una resistència connectada a 3.3 volts; aquesta mena de resistència s'anomena un {\em \gls{pull-up}}. Aquesta mena de resistències (o el seu complementari, un {\em \gls{pull-down}}) també s'utilitza en la connexió de polsadors o botons, de manera que quan el botó no està polsat, la resistència de {\em pull-up} (o de {\em pull-down}) força el valor corresponent.
\begin{figure}
\centering
\includegraphics[width=0.85\textwidth, keepaspectratio]{imatges/buttons.png}
\caption[Esquemàtic amb resistències de {\em pull-ups}]{Esquemàtic amb resistències de {\em pull-ups} (etiquetades com R101 i R102)}
\label{fig:pullup}
\end{figure}
Com es veu al esquemàtic de la Figura~\ref{fig:pullup}, quan no es prem el botó les resistències etiquetades R101 i R102 fan que la línia estigui a ‘1' lògic ({\em pull-up}). En quan es prem el botó, aquest connecta GND a la línia i per tant passa a tenir el valor lògic ‘0'.
Cal saber també que la quantitat de corrent que un pin individual pot proporcionar està limitada i, en alguns casos, es pot seleccionar la quantitat màxima de corrent que pot donar un pin concret.
\section{Un exemple senzill}
\label{sub:GPIO_2_example}
Farem un codi que llegeixi l'estat dels botons de la placa, i en cas que estiguin pitjats, s'encén o apaga el LED. Veiem el codi el llistat~\ref{gpio_example}.
\begin{lstlisting}[style=customc,caption={Codi d'exemple de GPIO},label=gpio_example]
CMU_ClockEnable(cmuClock_GPIO, true);
GPIO_PinModeSet(gpioPortD, 7, gpioModePushPullDrive, 0); /* LED */
GPIO_PinModeSet(gpioPortD, 8, gpioModeInput, 0); /* Boto 0 */
GPIO_PinModeSet(gpioPortB, 11, gpioModeInput, 0); /* Boto 1 */
/* Infinite loop */
while (1) {
if (GPIO_PinInGet(gpioPortD, 8) == 0) {
GPIO_PinOutClear(gpioPortD, 7);
}
if (GPIO_PinInGet(gpioPortB, 11) == 0) {
GPIO_PinOutSet(gpioPortD, 7);
}
}
\end{lstlisting}
El primer que es fa amb la sentència {\bf CMU\_ClockEnable()}\index{CMU\_ClockEnable()} és alimentar amb un rellotge al perifèric \gls{GPIO}. En la arquitectura Cortex-M de Silicon Labs cal fer això per cada perifèric que fem servir. D'aquesta manera, un perifèric que no necessitem no rep cap rellotge i el seu consum és disminueix dràsticament.
Tot seguit es configuren els 3 pins que utilitzarem:
\begin{itemize}
\item PD7 com sortida per controlar el LED,
\item PD8 i PB11 com entrades connectades als botons 0 i 1.
\end{itemize}
Un cop configurats els pins, dins el bucle infinit es va mirant tota l'estona per {\em polling} el valor dels dos pins d'entrada i canviant el valor de sortida cap al LED segons toqui.
\section{BSP}
\label{sec:BSP}
Podem començar a introduir el concepte de \gls{BSP} que no són més que funcions específiques per la nostra PCB de manera que ens aïllen la implementació de la funcionalitat.
Anem a suposar que canviem de PCB (o de versió) i el LED que volem encendre ja no està connectat al pin D7 si no que està, posem per cas, al E2. Caldria canviar totes les crides que tinguéssim al nostre codi de l'estil vist al Llistat \ref{orig_gpio_bsp} per la del Llistat \ref{change_gpio_bsp} amb tots els errors que això provocar.
\begin{lstlisting}[style=customc, caption=Codi de configuració d'un pin, label=orig_gpio_bsp]
GPIO_PinOutSet(gpioPortD, 7);
\end{lstlisting}
\begin{lstlisting}[style=customc, label=change_gpio_bsp, caption=Codi amb la nova configuració del pin]
GPIO_PinOutSet(gpioPortE, 2);
\end{lstlisting}
Una forma molt habitual de treballar és escriure funcions amb les funcionalitats més comunes i que ens amaguin aquests detalls. Així, pel nostre exemple podríem definir les funcions del Llistat \ref{BSP_example}.
\index{LedInit()}\index{LedOn()}\index{LedOff()}\index{GPIO\_PinOutSet()}\index{GPIO\_PinModeSet()}\index{GPIO\_PinOutClear()}
\begin{lstlisting}[style=customc, label=BSP_example, caption=Exemple de BSP senzill]
LedInit() {
GPIO_PinModeSet(gpioPortD, 7, gpioModePushPullDrive, 0); /* LED */
}
LedOn() {
GPIO_PinOutSet(gpioPortD, 7);
}
LedOff() {
GPIO_PinOutClear(gpioPortD, 7);
}
\end{lstlisting}
Fent servir aquestes funcions enlloc de les crides directes al GPIO ens permetran introduir canvis a la PCB sense haver de canviar gaire el nostre codi.
Habitualment en el BSP s'inclouen les inicialitzacions dels diversos rellotges (veure~{\fullref{sec:clocks}}), les funcions per accedir a recursos propis de la placa com LEDs o botons, configuració de les opcions de {\em Debug} (Veure~{\fullref{sub:console}}), etc.
\section{Manipulant bits individuals}
\label{sec:bits}
Tot i que no és específic dels GPIOs, veurem aquí com manipular bits individuals en C. Sovint ens caldrà posar a valor '0' o '1' un bit individual d'una variable sense canviar el valor de la resta dels bits, veiem aquí les receptes per fer-ho.
\begin{remark}
La numeració de bits en C comença pel 0, així el bit menys significatiu d'una variable serà sempre el 0 i el més significatiu $N-1$, amb $N$ el nombre de bits de la variable. Per situar un 1 a un bit determinat, en C es fa servir l'operador {\bf <{}<}, que desplaça cap a l'esquerra el valor de l'esquerra tants bits com indiqui el valor de la dreta.
\end{remark}
L'exemple per aquesta secció es troba al repositori en el projecte \href{https://github.com/mariusmm/cursembedded/tree/master/Simplicity/Bit_1}{Bit\_1}.
\index{|}
\subsection{Posar a 1 un bit}
Per posar un bit concret a '1' d'una variable tant sols cal fer una OR lògica (símbol {\bf |}) tal com es veu al Llistat~\ref{example_bit}.
En aquest cas, primer es posa a '1' el bit 4 de la variable {\em my\_variable}.
\index{main()}
\begin{lstlisting}[style=customc, label=example_bit, caption=Manipulant un bit concret d'una variable]
void main(void) {
...
uint8_t my_variable;
...
my_variable = 5;
// Set to '1' bit 4
my_variable |= (1 << 4);
printf("my_variable: 0x%02X\r\n", my_variable); // should be 0x15
// Now set to '0' bit 2
my_variable &= ~(1 << 2);
printf("my_variable: 0x%02X\r\n", my_variable); // should be 0x11
// Now we toggle bit 0 twice
my_variable ^= (1 << 0);
printf("my_variable: 0x%02X\r\n", my_variable); // should be 0x10
my_variable ^= (1 << 0);
printf("my_variable: 0x%02X\r\n", my_variable); // should be 0x11
...
if ( (my_variable & 0x10) != 0 ) {
/* the variable my_variable has the 4th bit set */
}
}
\end{lstlisting}
\index{\&}\index{\~{}}
\subsection{Posar a 0 un bit}
Per posar a zero un bit individual, la feina a fer és una mica més estranya, ja que cal fer una AND lògica amb tots els bits a '1' menys el desitjat que haurà d'estar a '0'. Això es pot fer amb la mateixa construcció d'abans i fent l'operació NOT bit a bit (amb el símbol {\bf \~{}}) abans de fer la AND (símbol {\bf \&}).
En el mateix exemple es veu com, després de posar a 1 el bit 4, es posa a 0 el bit 3 amb la operació AND comentada.
\index{\^{}}
\subsection{{\em Toggle} un bit}
Per a fer un {\em toggle} d'un bit individual, el que cal fer és la operació lògica XOR (símbol {\bf \^{}}) amb el bit desitjat al valor '1'.
A l'exemple es fa {\em toggle} dues vegades al bit 0 de la variable.
\subsection{Comparar si un bit està a cert valor}
L'altre necessitat que apareix sovint és la de comprovar el valor d'un bit determinat d'una variable.
L'opció més habitual és fer una AND lògica entre la variable i el bit interessant i comprovar que el resultat és diferent de '0'. Si la comparació dona cert vol dir que el bit en qüestió està a '1', en cas contrari, el bit d'interès té el valor '0'. Es pot veure al final de l'exemple al Llistat~\ref{example_bit}, on es comprova que el bit 4 de la variable sigui valgui '1'.
\begin{remark}
Aquesta mena d'operacions són força propicies per introduir {\em bugs} complicats de detectar. Operar per error un bit que no pertany a aquella mida de variable pot portar a errors molt difícils de detectar i el compilador no donarà cap mena de error o avís.
\end{remark}
\chapter{Controlador d'interrupcions}
\label{ch:IRQ}
Una interrupció (\gls{IRQ}) és un succés que interromp l'execució normal del processador i passa a executar un codi de programa especial pel succés concret. La \gls{ISR} és el codi que es crida per a cada succés o interrupció.
Cada interrupció té assignada una ISR pròpia. Aquesta informació s'acostuma a guardar en una zona de memòria especial, anomenada memòria de vectors d'interrupció.
Les IRQs estan enumerades i tenen prioritats, així habitualment, un valor menor vol dir major prioritat. Aquest valor de IRQ també es fa servir per saber quina posició dels vectors d'interrupció es troba la ISR corresponent.
El controlador d'interrupcions gestiona quines interrupcions rebudes arriben al processador, segons les prioritats i si la interrupció concreta està activada o no.
Veurem un cas amb els GPIO, el codi està disponible \href{https://github.com/mariusmm/cursembedded/tree/master/Simplicity/GPIO_2}{al repositori}. En aquest cas, el que es fa primer és configurar els pins perquè generin una interrupció HW al flanc de baixada (recordem el {\em pull-up} a la PCB, Figura~\ref{fig:pullup}). Tot seguit s'activen les interrupcions corresponents.
\index{NVIC\_EnableIRQ()}\index{GPIO\_IntConfig()}
\begin{lstlisting}[style=customc]
/* Set Interrupt configuration for both buttons */
GPIO_IntConfig(gpioPortD, 8, false, true, true);
GPIO_IntConfig(gpioPortB, 11, false, true, true);
/* Enable interrupts */
NVIC_EnableIRQ(GPIO_EVEN_IRQn);
NVIC_EnableIRQ(GPIO_ODD_IRQn);
\end{lstlisting}
En el cas dels Cortex-M de SiliconLabs, els pins de \gls{GPIO} poden generar només 2 interrupcions, els pins parells la interrupció GPIO\_EVEN\_IRQ i els pins senars la GPIO\_ODD\_IRQ \cite[405]{EFM32GRM}. En els microcontroladors de ST, hi ha una arquitectura diferent i cada pin d'entrada pot generar una IRQ segons el seu índex de manera que el pin PA2, el PB2, el PC2 etc. generen la IRQ EXTI2, però només un d'aquests pins pot generar la IRQ \cite[384]{STM32F4RM}.
En el cos de l'exemple, com que els botons estan connectats al pin D8 i B11 cada un d'ells activarà una de les dues interrupcions.
La \gls{ISR} per la interrupció parell la veiem al Llistat~\ref{gpio_isr}.
\index{GPIO\_EVEN\_IRQHandler()}\index{GPIO\_IntClear()}\index{GPIO\_IntGet()}\index{GPIO\_PinOutClear()}
\begin{lstlisting}[style=customc, label=gpio_isr, caption=Exemple d'ISR per GPIO]
void GPIO_EVEN_IRQHandler(void) {
uint32_t aux;
aux = GPIO_IntGet();
/* clear flags */
GPIO_IntClear(aux);
/* Set LED off */
GPIO_PinOutClear(gpioPortD, 7);
}
\end{lstlisting}
A l'arquitectura Cortex-M el nom de les ISR està fixat en un fitxer de l'entorn de programació, de manera que només cal escriure una funció amb el nom correcte i ja tenim definida la ISR. El fitxer que defineix les ISR depèn de cada model de microcontrolador, en el nostre cas és el fitxer startup\_efm32tg.S.
En el cas de l'arquitectura Cortex, la pròpia ISR ha de netejar el \gls{flag} d'interrupció que l'ha cridat. Això es fa al principi de tot de la \gls{ISR}, llegint quins \glspl{flag} estan actius (funció {\bf GPIO\_IntGet()}\index{GPIO\_IntGet()}) i tot seguit netejant aquests mateixos \glspl{flag} ({\bf GPIO\_IntClear()}\index{GPIO\_IntClear()}).
A continuació, s'encén o s'apaga el LED segons correspongui (a una ISR s'apaga, a l'altre s'encén).
L'altre cosa a destacar d'aquest exemple és el que hi ha dins el bucle infinit, que està buit. I està buit perquè, en aquest exemple, el microcontrolador no té res a fer fins que no hi hagi una interrupció provinent d'un botó.
En una aplicació real, en aquest bucle es podrà posar codi que si s'hagi d'executar contínuament, o instruccions que posin “a dormir” el microcontrolador tot esperant una interrupció, etc. Tot això ho anirem veient més endavant.
\section{Escrivint ISRs en C}
\label{sec:IRQ_example}
Com ja sabem, les \gls{ISR} són les funcions especials que s'executen tant bon punt es dispara una interrupció determinada.
Tradicionalment les adreces a aquestes \glspl{ISR} (anomenats de vegades vectors d'interrupció) s'emmagatzemaven a una zona especial de la memòria del processador. Quan el processador rebia una \gls{IRQ}, com que aquestes van numerades simplement calcula l'offset de la IRQ a la taula de ISRs i executa aquella funció determinada.
En els ARM Cortex amb els que treballem això es fa tal qual acabem d'explicar. En el cas dels Cortex (i la majoria de microcontroladors i processadors) la taula de vectors d'interrupcions es col·loca a partir de la posició 0 de memòria.
El que cal, doncs, és que les nostres eines de compilació posin aquests vectors com toca a cada un dels binaris que generem. En el cas de les eines per Cortex (tant Simplicity com les eines de ST ho fan així), aprofiten un codi d'inicialització proporcionat per ARM anomenat, en el nostre cas startup\_efm32tg.S. Aquest fitxer està escrit en assemblador i, entre d'altres coses, té el codi que es veu a la Figura~\ref{fig:ISR}:
\begin{figure}
\centering
\includegraphics[width=0.85\textwidth, keepaspectratio]{imatges/interruptvectorcortex.png}
\caption{Vectors d'interrupció}
\label{fig:ISR}
\end{figure}
Com es pot veure, aquest codi declara el nom de les ISRs corresponents a cada una de les IRQs possibles al microcontrolador. Més endavant en el mateix fitxer, es posa una funció per defecte per a cada una de les ISR que és només un bucle sense fi. Com que aquesta funció es declara com {\em weak}, nosaltres podrem sobreescriure–la en cas que ho vulguem fer.
En el cas de Cortex, les funcions de ISR no han de ser definides de cap forma especial, més enllà de posar el nom que li correspongui.
En altres arquitectures, com ara AVR d'Atmel, les ISR han de retornar d'una manera diferent a les funcions normals donat que durant la crida a una ISR no es guarden tots els registres com es fa a una crida a una funció normal. Si escrivim la funció en assemblador, enlloc d'una instrucció {\bf RET} ens caldrà escriure una instrucció {\bf RETI}.
Si estem treballant en llenguatge C, com que el compilador posa una instrucció {\bf RET} quan acaba la funció, caldrà indicar-li d'alguna forma que la funció en qüestió és una ISR i que ha d'acabar-la amb una instrucció {\bf RETI}.
Això, en el cas d'AVR es fa com es veu al Llistat \ref{ISR_AVR_1} o \ref{ISR_AVR_2} depenent del tipus de compilador que estiguem fent servir.
\begin{lstlisting}[style=customc, label=ISR_AVR_1, caption=Exemple d'ISR per AVR]
#pragma vector=TIMER0_OVF_vect
__interrupt void MotorPWMBottom() {
// codi
}
\end{lstlisting}
\begin{lstlisting}[style=customc, label=ISR_AVR_2, caption=Exemple d'ISR per AVR]
ISR(PCINT1_vect) {
//codi
}
\end{lstlisting}
\section{Fent servir ISRs}
Com ja hem comentat, una \gls{ISR} s'executa quan es dispara una \gls{IRQ}. Durant l'execució de la ISR el microcontrolador està en un mode d'execució especial, amb les demés IRQs desactivades (depèn de l'arquitectura això pot no ser així).
Donat que la resta d'IRQs poden estar desactivades, és important que el temps que el processador estigui executant una ISR sigui el mínim possible i que el codi, per tant, sigui el més senzill possible.
Si el que hem de fer, a part de tasques molt simples a la ISR, és engegar o controlar un procés més complicat, aquest procés no el farem dins la ISR, si no en un procés a part i comunicarem via \glspl{flag}, cues, semàfors o mecanismes similars la ISR amb el procés. Així minimitzem el temps que el processador està en mode ISR.
\subsection{Ús de variables globals}
\label{sb:volatile}
Com ja hem vist breument a \fullref{sub:memory-mapped} hi ha una paraula reservada que es fa servir quan es fan accessos a memòria i altres usos d'una variable on el compilador no ha d'actuar amb cap optimització. La paraula reservada {\bf volatile} davant la declaració d'una variable indica al compilador que la variable s'hi ha d'accedir tal com diu el codi i no efectuar cap optimització.
\begin{remark}
En el cas de modificar el valor d'una variable global des d'una ISR, cal declar-la com a {\bf volatile}.
\end{remark}
\chapter{Timers}
\label{sub:Timers}
Un {\em \gls{Timer}} (temporitzador) és un dels perifèrics més habituals de trobar en un microcontrolador. Bàsicament consisteix en un comptador que pot generar alguna interrupció quan arriba a un cert llindar o al seu valor límit. Com sempre, cada fabricant el fa segons el seu criteri i, per tant, cada un té característiques diferents.
Normalment els {\em timers} es poden connectar a diferents rellotges disponibles dins el microcontrolador. A més, força sovint els {\em timers} poden dividir prèviament la freqüència del rellotge que l'alimenta per reduir-la encara més. Així, podem tenir un rellotge d'1 MHz alimentant un {\em timer} que abans de que hi entri es divideixi per 8 per tenir un rellotge efectiu de 125 kHz. Amb això, si configurem el {\em timer} perquè compti fins al valor 125000, tindrem que el {\em timer} generarà una interrupció cada segon.
En el cas de Silicon Labs, els Timers tenen múltiples opcions \cite[249]{EFM32GRM}:
\begin{itemize}
\item comptador de 16 bits
\item pre-escalatge del rellotge: el rellotge d'entrada es pot pre-escalar (dividir) per diversos factors (de 2 fins a 1024).
\item diverses fonts de rellotge
\item diverses formes de comptatge (cap a munt, cap avall, amunt i avall, etc.)
\item 3 canals per Timer, per generar diverses interrupcions (per {\em overflow}, per arribar a un llindar, {\em underflow}, etc.)
\end{itemize}
Tot plegat fa que sigui força complicat de configurar, i com ve sent costum, el fabricant ens dona una biblioteca per simplificar-nos una mica la vida.
Els controls que tenim habitualment per un Timer, un cop configurat, són \cite{EMLIB}:
\begin{itemize}
\item engegar i parar el Timer ({\bf TIMER\_Enable()}\index{TIMER\_Enable()} a EMLIB).
\item llegir o configurar el màxim valor pel timer ({\bf TIMER\_TopGet()}\index{TIMER\_TopGet()} / {\bf TIMER\_TopSet()}\index{TIMER\_TopSet()} a EMLIB).
\item llegir o configurar el valor pel compare ({\bf TIMER\_CompareGet()}\index{TIMER\_CompareGet()} / {\bf TIMER\_CompareSet()}\index{TIMER\_CompareSet()} a EMLIB).
\end{itemize}
Els usos que li podem donar a aquest perifèric són variats, els més habituals són els següents::
\begin{itemize}
\item Comptar el temps: es configura per a que generi una interrupció cada segon i ja tenim un rellotge de temps real. Hi ha perifèrics específics per aquesta tasca (\glspl{RTC}) com es veurà a \fullref{sub:RTC}.
\item Fer {\em delays} acurats: de vegades cal que un senyal o una acció passi després d'un cert temps. Amb un Timer ben configurat podem comptar temps petits de l'ordre de microsegons.
\item Comptar polsos externes: segons quins Timers poden comptar segons una entrada externa, i aquesta no cal que sigui un rellotge. Pot ser, per exemple, les pulsacions d'un botó o les transicions d'un senyal.
\item Generar senyals \gls{PWM}: tipus de senyal digital per controlar motors o d'altres dispositius (Veure~{\fullref{sub:PWM}}).
\end{itemize}
\section{Exemple senzill amb un Timer}
\label{sub:Timers_exemple}
El \href{https://github.com/mariusmm/cursembedded/tree/master/Simplicity/Timer_1}{primer exemple} fa servir un Timer per esperar-se 1 segon a canviar d'estat el LED (fer un {\em toggle}) després que es premi el Botó 0.
El que es fa en primer lloc és configurar el Timer0 amb un seguit d'opcions. Les més importants de cara a l'exemple són:
\begin{itemize}
\item .prescale = timerPrescale1024 que configura el divisor de rellotge a 1024.
\item .mode = timerModeUp així el Timer només compta cap amunt.
\item .oneShot = true en aquest cas, un cop arribi al màxim valor el Timer es pararà.
\end{itemize}
Un cop configurat el Timer, s'entra en el bucle infinit.
Dins el bucle infinit, si es polsa el botó 0, es posa el comptador del Timer a 0 i s'engega el comptador (Llistat~\ref{Timer_example_1}).
\index{TIMER\_CounterSet()}\index{TIMER\_Enable()}\index{main()}\index{GPIO\_PinInGet()}
\begin{lstlisting}[style=customc, label=Timer_example_1, caption=Codi d'exemple d'ús d'un Timer ]
void main(void) {
...
if (GPIO_PinInGet(gpioPortD, 8) == 0) {
TIMER_CounterSet(TIMER0, 0);
TIMER_Enable(TIMER0, true);
}
}
\end{lstlisting}
Tot seguit, es comprova si el comptador ha arribat al valor {\bf TOP\_VALUE} usant la funció {\bf TIMER\_CounterGet()}\index{TIMER\_CounterGet()}, si és així es fa {\em toggle} del LED (s'encén si estava apagat o el contrari), s'atura el Timer i es posa el seu comptador a 0 (Llistat~\ref{Timer_example_2}).
\index{main()}\index{TIMER\_CounterGet()}\index{TIMER\_Enable()}\index{TIMER\_CounterSet()}\index{GPIO\_PinOutToggle()}
\begin{lstlisting}[style=customc, label=Timer_example_2, caption=Codi per comprovar si el Timer ha arribat a cert valor]
void main(void) {
...
/* If timer count gets to TOP_VALUE, toggle LED and stop Timer */
if (TIMER_CounterGet(TIMER0) >= TOP_VALUE) {
GPIO_PinOutToggle(gpioPortD, 7);
TIMER_Enable(TIMER0, false);
TIMER_CounterSet(TIMER0, 0);
}
...
}
\end{lstlisting}
Com hem calculat el valor {\bf TOP\_VALUE} (13671)?
\begin{verbatim}
Rellotge d'entrada al Timer: 14.000.000 Hz (14 MHz)
Prescaler: 1024 (Seleccionat al inicialitzar el timer)
Freqüència de treball del Timer = 14.000.000 / 1024 = 13.671,875 Hz
\end{verbatim}
Per tant, en un segon el comptador del Timer haurà comptat fins a 13.671 (o 13.672, no ve d'un tick!).
En aquest exemple hem fet servir el Timer d'una forma força rudimentària, ja que no és gaire habitual fer {\em polling} d'un Timer per transcorre un temps determinat. Es fa servir aquest mètode per implementar funcions tipus {\bf Delay()} simples. A continuació veurem un exemple més complicat basat en interrupcions.
\section{Exemple més complex amb el Timer}
\label{sub:Timers_exemple_2}
Al \href{https://github.com/mariusmm/cursembedded/tree/master/Simplicity/Timer_2}{segon exemple} fem servir interrupcions
per obtenir informació del Timer i així alliberar la CPU.
Primer de tot, cal saber per quines condicions pot generar interrupcions el nostre Timer. En el cas de la família EFM32, cada Timer té només una interrupció (anomenada TIMERn\_IRQn) però tenen els següents esdeveniments que poden generar una interrupció \cite{EFM32GRM}:
\begin{itemize}
\item {\em Overflow}: quan el comptador arriba a {\bf TOP}
\item {\em Underflow}: quan el comptador arriba a 0
\item {\em Compare Match}: quan el comptador arriba a un valor determinat. N'hi ha un per cada canal del Timer.
\end{itemize}
Així doncs, quan estiguem a la \gls{ISR} del Timer si hem activat més d'un esdeveniments a que generi la interrupció haurem de mirar quin esdeveniments ha estat.
El Timer es configura igual que a l'exemple anterior, i a més s'activen les interrupcions per aquest perifèric amb el codi que es veu a \ref{Timer_enableIRQ}.
\index{TIMER\_IntEnable()}\index{NVIC\_EnableIRQ()}
\begin{lstlisting}[style=customc, label=Timer_enableIRQ, caption=Codi corresponent a l'activació de les IRQs del Timer]
/* Enable overflow interrupt */
TIMER_IntEnable(TIMER0, TIMER_IF_OF);
/* Enable IRQ for Timer 0*/
NVIC_EnableIRQ(TIMER0_IRQn);
\end{lstlisting}
Amb això, un cop s'engegui el Timer i quan arribi el comptador a {\bf TOP} llençarà la interrupció {\bf TIMER0\_IRQn}, que executarà la {\bf ISR TIMER0\_IRQHandler()}\index{TIMER0\_IRQHandler()} que es veu al Llistat~\ref{Timer_IRQ} on simplement es netegen els \glspl{flag} d'interrupció i es fa {\em toggle} del \gls{LED}.
\index{TIMER\_IntGet()}\index{TIMER\_IntClear()}
\begin{lstlisting}[style=customc, label=Timer_IRQ, caption=ISR del Timer]
void TIMER0_IRQHandler(void) {
uint32_t flags;
/* Clear flag for TIMER0 */
flags = TIMER_IntGet(TIMER0);
TIMER_IntClear(TIMER0, flags);
/* Toggle LED ON/OFF */
GPIO_PinOutToggle(gpioPortD, 7);
}
\end{lstlisting}
La configuració i la posada en marxa del Timer es fa a la \gls{ISR} del botó 0, de manera similar a com ja havíem fet anteriorment a d'altres exemples: primer es netegen els {\em flags} de la \gls{IRQ}, tot seguit es posa el comptador del Timer a 0, es posa el valor màxim i per últim s'engega el Timer (Llistat~\ref{GPIO_ISR_Timer}).
\index{GPIO\_EVEN\_IRQHandler()}\index{GPIO\_IntGet()}\index{GPIO\_IntClear()}
\index{TIMER\_CounterSet()}\index{TIMER\_TopSet()}\index{TIMER\_Enable()}
\begin{lstlisting}[style=customc, label=GPIO_ISR_Timer, caption=ISR del GPIO per l'exemple del Timer]
void GPIO_EVEN_IRQHandler(void) {
uint32_t flags;
/* clear flags */
flags = GPIO_IntGet();
GPIO_IntClear(flags);
/* Set counter to 0 */
TIMER_CounterSet(TIMER0, 0);
/* Set TIMER Top value */
TIMER_TopSet(TIMER0, TOP_VALUE);
/* Start Timer */
TIMER_Enable(TIMER0, true);
}
\end{lstlisting}
Per últim, fer notar que al bucle infinit final del {\bf main()}\index{main()} no hi ha cap codi, ja que la \gls{CPU} no té res a fer mentre espera la pulsació del botó o que s'exhaureixi el temps, En el tema de baix consum (\fullref{ch:low-power}) es veurà com aprofitar aquest fet per reduir el consum del sistema amb un exemple a \fullref{sub:letimer_example}.
Al diagrama de seqüència de la Figura~\ref{fig:TImer_2seq} explica l'exemple.
\begin{figure}
\centering
\includegraphics[width=0.65\textwidth, keepaspectratio]{imatges/Timer2Seq.png}
\caption{Diagrama de seqüència de l'exemple Timer\_2}
\label{fig:TImer_2seq}
\end{figure}
\chapter{RTC}
\label{sub:RTC}
Un altre perifèric que acostumem a trobar als microcontroladors actuals és una mena de \gls{Timer} una mica especial. Habitualment aquests perifèrics serveixen per tenir un control de temps en segons i/o un calendari, enlloc de temps molts més curts de mil·lisegons o microsegons com els Timers que ja hem vist.
Aquest tipus de perifèrics acostumen a fer servir una entrada de rellotge pròpia de 32,768 kHz (32.768 Hz), que és una freqüència de rellotge molt habitual per aquestes feines. Algunes famílies de microcontroladors poden funcionar amb altres freqüències o generar-la internament per fer el sistema més senzill.
Els RTC varien força de fabricant a fabricant, així els STM32 tenen un RTC complet, on podem guardar dia, mes i any, hora minut i segons i el dispositiu mateix manté la data (dies del mes, anys de traspàs, etc.), fent molt senzill mantenir una data dins el dispositiu (veure Figura~\ref{fig:STRTC}) \cite[799]{STM32F4RM}.
\begin{figure}
\centering
\includegraphics[width=0.85\textwidth, keepaspectratio]{imatges/RTC_STM32.png}
\caption{Registres del RTC de STM32 \cite{ST_AN3371}}
\label{fig:STRTC}
\end{figure}
Per contra, els RTCs de EFM32 són força més senzills, i és, de fet, un Timer de 24 bits de molt baix consum amb una entrada de rellotge pròpia i que es pot triar cada quan generen una interrupció \cite[285]{EFM32GRM}. Si triem fer una interrupció cada segon, podem manegar per SW la gestió de l'hora i el calendari.
\href{https://github.com/mariusmm/cursembedded/tree/master/Simplicity/RTC}{A l'exemple per EFM32} tant sols se selecciona el rellotge de 32 kHz extern (LFXO), el divisor a 32768 per tenir un tic cada segon i es configura per a que generi una interrupció cada 2 segons (veure Llistat~\ref{RTCInit}). En aquest exemple, a la \gls{ISR} només es canvia el LED (s'apaga o encén segons el seu estat actual) (veure Llistat~\ref{RTCISR}).
\index{CMU\_ClockSelectSet()}\index{CMU\_ClockDivSet()}\index{NVIC\_EnableIRQ()}
\index{RTC\_CompareSet()}\index{RTC\_IntEnable()}\index{NVIC\_ClearPendingIRQ()}
\begin{lstlisting}[style=customc, caption={Inicialització del RTC}, label=RTCInit]
void main(void) {
...
CMU_ClockSelectSet( cmuClock_LFA, cmuSelect_LFXO );
CMU_ClockDivSet( cmuClock_RTC, cmuClkDiv_32768 );
...
RTC_CompareSet(0, 2);
/* Enabling Interrupt from RTC */
RTC_IntEnable(RTC_IFC_COMP0);
NVIC_ClearPendingIRQ(RTC_IRQn);
NVIC_EnableIRQ(RTC_IRQn);
...
}
\end{lstlisting}
\index{RTC\_IRQHandler()}\index{RTC\_IntClear()}\index{GPIO\_PinOutToggle()}
\begin{lstlisting}[style=customc,caption={ISR del RTC},label=RTCISR]
void RTC_IRQHandler(void) {
/* Clear interrupt source */
RTC_IntClear(RTC_IFC_COMP0);
GPIO_PinOutToggle(gpioPortD, 7);
}
\end{lstlisting}
Si volguéssim mantenir un rellotge fent servir aquest perifèric podríem configurar-lo perquè generi una interrupció cada segon, i dins la \gls{ISR} mantenir un comptador de segons i actualitzar la data segons això.
\section{RTC externs}
Quan la majoria de microcontroladors no incloïen un RTC intern com els que hem vist, era habitual fer servir un dispositiu \gls{RTC} extern. D'aquests dispositius n'hi ha de tota mena però la majoria tenen les següents característiques:
\begin{itemize}
\item Interface \gls{I2C} (veure \fullref{sub:I2C}) amb el microcontrolador.
\item Necessita un cristall de 32.768 Hz.
\item Un error d'aproximadament un segon a l'any.
\item Actualitza data i hora, calculant anys de traspàs.
\item Capacitat de generar interrupcions segons una alarma programable.
\item Molt baix consum i alimentació separada amb bateria (pila botó).
\item Molts d'ells tenen una petita memòria RAM adreçable per guardar-hi dades persistents.
\end{itemize}
Amb aquesta mena de dispositiu, el microcontrolador es descarrega de gestionar el calendari i només cal accedir als registres del RTC extern per saber l'hora o data del sistema. A la majoria dels casos també és possible programar alguna mena d'alarma, de forma que quan arriba cert temps o data una línia dedicada pot generar una interrupció al microcontrolador. Alguns models també tenen la possibilitat de generar un senyal de forma periòdica per tenir, per exemple, un senyal a 128 Hz.
Habitualment l'alimentació d'aquests dispositius es pot fer per un canal separat de l'alimentació principal i usant una bateria o pila tipus botó. Això permet que el RTC sempre estigui alimentat encara que es perdi l'alimentació principal (per avaria, tall de corrent, canvi de bateries, etc.). Aprofitant que sempre tenen alimentació, força RTCs tenen una zona de memòria RAM per a que el microcontrolador pugui guardar-hi dades persistents. L'accés a aquesta zona de memòria també es fa mitjançant el bus \gls{I2C}, sent molt senzill emmagatzemar-hi dades.
Per últim, cal dir que són dispositius força barats (entre 0.5 € i 3 € per unitat en volums petits), sent una bona opció en cas que el nostre microcontrolador no disposi d'aquesta funcionalitat \cite{RTCDS1}\cite{RTCDS2}\cite{RTCDS3}.
\chapter{PWM}
\label{sub:PWM}
El \gls{PWM} és una tècnica per aconseguir controlar la potència subministrada a un dispositiu mitjançant un senyal digital. Simplificant, fent que un senyal digital (‘1' o ‘0') estigui més o menys estona a ‘1' aconseguim controlar la potència que rep el dispositiu a la sortida.
Aquest tipus de modulació es fa servir per controlar motors simples (motors DC) on enlloc d'enviar un voltatge variable per controlar la velocitat enviem un senyal PWM. Així, si enviem polsos més llargs el motor girarà més ràpid i si enviem polsos més curts el motor girarà més a poc a poc. Com que el voltatge que se li envia sempre es el màxim, la potència del motor és sempre la màxima.
Els dos paràmetres principals d'un senyal PWM són la seva freqüència i el seu \gls{duty cycle} (la durada del pols a ‘1' respecte la durada del pols a ‘0').
\href{https://github.com/mariusmm/cursembedded/tree/master/Simplicity/PWM_1}{A l'exemple} es fa servir el LED de la placa enlloc d'un motor. Posarem una freqüència molt petita, així la podrem veure amb els nostres ulls, i anirem canviant el \gls{duty cycle} amb els dos botons, de manera que podem veure què està passant.
Primer veiem què passa i després veiem com ho fa el codi.
Només programar la placa veiem el LED fent pampallugues força ràpid tota l'estona. Si polsem el botó 1 veurem que el LED va més ràpid, si tornem a polsar el botó 1 encara va més ràpid així fins que al cinquè cop que el polsem el LED es queda encès tota l'estona.
El que estem veient és que el LED va rebent un senyal PWM on el duty cycle cada cop és més gran (més estona encès que apagat) fins que al final és del 100\% (sempre encès). El ritme al que fa pampallugues el LED és (més o menys) la freqüència del PWM, que en aquest exemple és de 13.5~Hz.
\section{Generar PWM}
\label{sub:PWM_example}
La majoria de microcontroladors actuals tenen algun dispositiu HW que permet generar \gls{PWM}. En el cas dels micros de Silicon Labs, el dispositiu que ens permet generar-ne són els \glspl{Timer}.
Un Timer (veure la secció~\fullref{sub:Timers}) no és més que un comptador HW que genera interrupcions o sortides quan el comptador arriba a uns certs valors.
Per generar PWM, el Timer es configura el seu registre {\bf TOP} per a que ens doni la freqüència de funcionament del PWM desitjada. El que farà el Timer és comptar sempre fins a TOP i re-iniciar-se en quan hi arribi. Per generar el \gls{duty cycle} el que es fa es posar el valor desitjat al registre {\bf COMPARE} ({\bf CC}). El que farà el Timer és treure un ‘1' mentre el comptador no arribi a {\bf CC}, llavors posarà un ‘0' a la sortida fins que el comptador arribi a {\bf TOP}, on tornarà a començar el cicle (Figura~\ref{fig:pwm_timer})..
\begin{figure}
\centering
\includegraphics[width=0.85\textwidth, keepaspectratio]{imatges/pwm_timer.png}
\caption{Generació de PWM amb un timer \cite[262]{EFM32GRM}}
\label{fig:pwm_timer}
\end{figure}
En el codi d'exemple (Llistat \ref{Set_Timer_PWM}), el que es fa és configurar el TIMER1 en mode PWM i connectar la sortida del Timer al Pin D7 que és on està el LED connectat.
\index{TIMER\_InitCC()}
\begin{lstlisting}[style=customc, label=Set_Timer_PWM, caption=Configuració del Timer per l'exemple PWM]
/* Set Timer */
TIMER_InitCC(TIMER1, 1, &timerCCInit);
TIMER1->ROUTE |= (TIMER_ROUTE_CC1PEN | TIMER_ROUTE_LOCATION_LOC4);
\end{lstlisting}
A continuació és configuren els dos registres importants per generar PWM (Llistat~\ref {Timer_PWM_Conf}). Com que volem una freqüència baixa pel \gls{PWM} per poder veure'l amb els nostres ulls, al registre {\bf TOP} hi posem {\bf PWM\_FREQ} (4000), que el calculem de la següent manera \cite{EFM32GRM}.
\begin{displaymath}
\text{Freq. de PWM} = \frac{\text{Freq Clk}}{\text{prescaler} * \text{valor TOP}} = \frac{14.000.000 \text{ Hz}}{256 * (4000 +1 )} = 13.67 \text{ Hz}
\end{displaymath}
\index{TIMER\_TopSet()}\index{TIMER\_CompareBufSet()}
\begin{lstlisting}[style=customc, label=Timer_PWM_Conf, caption=Configuració del Timer per l'exemple PWM]
TIMER_TopSet(TIMER1, PWM_FREQ);
TIMER_CompareBufSet(TIMER1, 1, pwm_value);
\end{lstlisting}
La resta del codi és senzill: es preparen les dues interrupcions per cada un dels botons, i quan algun dels dos es prem, la \gls{ISR} modifica el valor del registre {\bf CC} del Timer (un botó augmenta el duty cycle, l'altre el disminueix).
Si veiem el senyal generat amb un oscil·loscopi veiem el que es mostra a la Figura~\ref{fig:DutyCycle1} (estat per defecte).
\begin{figure}
\centering
\includegraphics[width=0.85\textwidth, keepaspectratio]{imatges/DutyCycle1.png}
\caption{PWM amb Duty Cycle al 16\%}
\label{fig:DutyCycle1}
\end{figure}
Veiem que la freqüència real del senyal és de 13.57 Hz (73.70 ms de període) i que el senyal està a ‘1' 12.30 ms i a ‘0' a 61.40 ms.
Segons anem pitjant el botó 1 i anem augmentant el \gls{duty cycle} anem veient com està més estona a '1' el senyal (Figures~\ref{fig:DutyCycle2},
\ref{fig:DutyCycle3} i \ref{fig:DutyCycle4}).
Cal fixar-se que la freqüència no varia, sempre és \~13.5 Hz i el que va variant és l'estona que està a ‘0' o a ‘1' el senyal.
\begin{figure}
\centering
\includegraphics[width=0.85\textwidth, keepaspectratio]{imatges/DutyCycle2.png}
\caption{PWM amb Duty Cycle al 50\%}
\label{fig:DutyCycle2}
\end{figure}
\begin{figure}
\centering
\includegraphics[width=0.85\textwidth, keepaspectratio]{imatges/DutyCycle3.png}
\caption{PWM amb Duty Cycle al 83.3\%}
\label{fig:DutyCycle3}
\end{figure}
\begin{figure}
\centering
\includegraphics[width=0.85\textwidth, keepaspectratio]{imatges/DutyCycle4.png}
\caption{PWM amb Duty Cycle al 100\%}
\label{fig:DutyCycle4}
\end{figure}
Per acabar, es pot provar de canviar la freqüència del \gls{PWM} per a que no es vegi el LED fent pampallugues. Cal augmentar la freqüència a un valor que superi els 100Hz i enlloc de veure el LED encendre's i apagar-se, es veurà com varia la intensitat amb la que llueix.
\section{Controlant un servomotor}
Un servomotor és un dispositiu electromecànic de control senzill. La majoria d'ells son rotatius i permeten controlar l'angle d'actuació del motor amb un senyal digital, normalment un senyal PWM.
Així, el que se sol necessitar és un senyal PWM a una freqüència determinada i un temps actiu entre certs valors que provocaran un moviment proporcional al motor.
En el cas del servo que tinc entre mans, un Parallax Standard Servo (\#900-00005) (veure Figura~\ref{fig:servomotor})
(\href{https://www.parallax.com/sites/default/files/downloads/900-00005-Standard-Servo-Product-Documentation-v2.2.pdf}{enllaç al seu DataSheet}),
cal un PWM a 50 Hz i uns temps mínims de 0.75 ms i màxim de 2.25 ms.
Aquests temps faran que el servo es mogui entre els 0º i els 180º de rotació.
\begin{figure}
\centering
\includegraphics[width=0.40\textwidth, keepaspectratio]{imatges/servomotor.png}
\caption{Fotografia del servomotor Parallax usat}
\label{fig:servomotor}
\end{figure}
Cal aplicar la següent fórmula per saber el valor TOP del nostre TIMER:
\begin{displaymath}
% \text{Freq. de PWM} = \frac{\text{Freq Clk}}{\text{prescaler} * \text{valor TOP}} - 1 = \frac{14.000.000 \text{ Hz}}{256 * (4000 +1 )} = 13.67 \text{ Hz}
TOP = \frac{f_{CPU}}{ f_{PWM} * PRESCALER} - 1
\end{displaymath}
Com que la freqüència d'entrada és 14.000.000 Hz, la freqüència del PWM ha de ser 50 Hz i TOP no pot ser més gran de 65.536 (16 bits) cal que triem el {\it prescaler} acuradament.
De fet, podríem triar un prescaler de 1024 i ens donaria un valor per TOP de 272. Però amb aquest valor perdem molta resolució per generar el PWM, ja que el {\em timer}
només podrà arribar fins a aquest valor. Si triem un valor més petit pel prescaler, augmentem la resolució i la qualitat del senyal del PWM. Així, si triem un valor de pre-escalat de 8 ens
resulta que TOP ha de valor 34999. Aquest valor és el més gran que podem generar d'aquesta forma i que càpiga en 16 bits, ja que el següent pre-escalat ens dona un valor
massa gran.
Així doncs, tenim que per generar un PWM 50 HZ calen 34999 {\em ticks} del {\em timer}.
Com que la fulla d'especificacions del servomotor ens diu que el temps de pols positiu ha d'anar dels 0.75 ms fins els 2.25 ms, cal saber quants {\em ticks} els corresponent.
Si fem una simple regla de tres tenim:
\begin{displaymath}
0 \degree \rightarrow \frac{0.75 \ ms}{20 \ ms} * 34.999 = 1312,4625 \simeq 1312
\end{displaymath}
i
\begin{displaymath}
180 \degree \rightarrow \frac{2.25 \ ms}{20 \ ms} * 34.999 = 3937,3875 \simeq 3937
\end{displaymath}
I ja tenim els dos valors màxims i mínims per controlar correctament el servomotor en qüestió i ens podem muntar una funció que ens passi
de graus de rotació a {\em counts} del PWM per simplificar el codi (Llistat~\ref{degreestopwm}).
\index{degrees\_to\_pwm()}
\begin{lstlisting}[style=customc,caption={Funció que calcula els {\em counts} donat els graus que es vol del servomotor},label=degreestopwm]
#define PWM_FREQ (34999)
#define PWM_0 (1312)
#define PWM_180 (3937)
uint32_t degrees_to_pwm(uint32_t degrees) {
uint32_t ret_value = 0;
if (degrees < 180) {
ret_value = (degree * (PWM_180 - PWM_0) / 180 ) + PWM_0;
} else {
ret_value = PWM_180;
}
return ret_value;
}
\end{lstlisting}
A les figures~\ref{fig:pwm_0_servo} i \ref{fig:pwm_180_servo} es veu el senyal PWM generat pels casos de 0\degree i 180\degree. Es veu que les mesures de l'oscil·loscopi
donen 50 Hz i un temps del pols positiu de 0.5 ms i 2.25 ms.
\begin{figure}
\centering
\includegraphics[width=0.85\textwidth, keepaspectratio]{imatges/Servo_0.png}
\caption{PWM per situar el servomotor a 0 \degree}
\label{fig:pwm_0_servo}
\end{figure}
\begin{figure}
\centering
\includegraphics[width=0.85\textwidth, keepaspectratio]{imatges/Servo_180.png}
\caption{PWM per situar el servomotor a 180 \degree}
\label{fig:pwm_180_servo}
\end{figure}
I posem un codi a les ISR dels dos botons perquè incrementi (decrementi) el valor en graus on volem situar el servo: La resta del codi està al repositori en aquest
\href{https://github.com/mariusmm/cursembedded/tree/master/Simplicity/PWM_2}{enllaç}.
\index{GPIO\_EVEN\_IRQHandler()}
\begin{lstlisting}[style=customc,caption={ISR del botó que incrementa la rotació del servomotor},label=servoirq]
void GPIO_EVEN_IRQHandler(void) {
uint32_t aux;
aux = GPIO_IntGet();
/* clear flags */
GPIO_IntClear(aux);
degree += 30;
if (degree > 180) {
degree = 180;
}
pwm_value = degrees_to_pwm(degree);
TIMER_CompareBufSet(TIMER1, 1, pwm_value);
}
\end{lstlisting}
\begin{remark}
En el meu cas i amb el servomotor que tinc, s’observa que quan està en repòs de tant en tant se sent com el servo fa algun moviment o “crec”.
No n’estic segur, però pot ser que sigui perquè la freqüència del PWM no sigui prou estable i s’escurci un pel i se surti d’especificacions.
En aquest cas millora si s’allarga el període del PWM augmentant una mica el valor de PWM\_FREQ.
\end{remark}
\begin{remark}
També veig que si el mínim el poso a 0.75 ms el servo no arriba a fer 180º de rotació i puc baixar el mínim fins a 0.5ms i que tot segueixi funcionat.
En aquest cas el valor de PWM\_0 és 875.
\end{remark}
\chapter{\em Watchdog}
\label{sec:Watchdog}
En els microcontroladors actuals tenim un perifèric amb un funcionament força peculiar. Quan s'activa el {\em \gls{Watchdog}}, aquest comença a comptar un cert període de temps, i si no “s'alimenta”, reiniciarà tot el sistema \cite[123]{EFM32GRM}\cite[709]{STM32F4RM}.
I per què volem un perifèric que ens reinici el nostre sistema? Doncs per si el nostre \gls{FW} té algun error i es queda penjat (està en un {\em \gls{dead-lock}}, en un bucle sense sortida, etc.), sempre serà millor que el sistema s'iniciï de nou. Imaginem el cas d'un marcapassos (un sistema encastat força crític); què és millor? Que es quedi penjat per un error del \gls{FW} que passa molt poc (si passés sovint s'hauria detectat) o que quan passi aquest error el sistema es reinici i torni a funcionar en menys de, posem, un segon?
I com evitem que si tot va be el {\em watchdog} no ens reiniciï el sistema? Doncs “alimentant-lo” de tant en tant de manera que el comptador intern del {\em Watchdog} torni a zero (Figura~\ref{fig:Watchdog}).
\begin{figure}
\centering
\includegraphics[width=0.85\textwidth, keepaspectratio]{imatges/Watchdog.png}
\caption{Funcionament del {em Watchdog} \cite{EFM_AN0015}}
\label{fig:Watchdog}
\end{figure}
La majoria de fabricants ens donen unes poques funcions per treballar amb el {\em Watchdog} (en el cas de Silicon Labs a la biblioteca EMLIB tenim el mòdul em\_wdog). Habitualment hi ha alguna funció per configurar-lo, una per engegar-lo i una per alimentar-lo. Hi ha fabricants que no permeten deshabilitar el {\em Watchdog} un cop s'ha engegat per assegurar-se que cap error de \gls{FW} provocarà que deixi de funcionar.
\index{WDOG\_Feed()}
\begin{lstlisting}[style=customc]
WDOG_Feed();
\end{lstlisting}
Habitualment es pot triar quina és la freqüència de funcionament del {\em Watchdog}, per tenir més o menys temps abans no reiniciï el sistema; normalment de pocs mil·lisegons fins a algunes desenes de segons.
\section{Exemple}
\label{sub:Watchdog_example}
En l'\href{https://github.com/mariusmm/cursembedded/tree/master/Simplicity/Watchdog}{exemple que hi ha al repositori}
es configura el {\em Watchdog} perquè treballi amb un rellotge intern de 1 kHz i que compti fins a 4097, de manera que si ningú alimenta el {\em Watchdog} en 4 segons, aquest reiniciarà el sistema. L'exemple conté un bucle que va fent blinkar el LED de la placa i una \gls{ISR} que quan es prem el botó 0 alimenta el {\em Watchdog} (Llistat~\ref{WatchdogISR}).
\index{GPIO\_EVEN\_IRQHandler()}\index{GPIO\_IntGet()}\index{GPIO\_IntClear()}\index{WDOG\_Feed()}
\begin{lstlisting}[style=customc,caption={ISR del botó que alimenta el {\em Watchdog}},label=WatchdogISR]
void GPIO_EVEN_IRQHandler(void) {
uint32_t aux;
aux = GPIO_IntGet();
/* clear flags */
GPIO_IntClear(aux);
/* Feed watchdog */
WDOG_Feed();
}
\end{lstlisting}
Si no premem el botó abans no passin 4 segons, el sistema es reiniciarà. I com ho veurem a l'exemple? Doncs perquè el codi el primer que fa és esbrinar per què s'està reiniciant el sistema (Llistat~\ref{Check_boot}). Si ha estat perquè ha entrat el {\em Watchdog}, el LED no blinkarà.
\begin{lstlisting}[style=customc,caption={Codi per detectar la causa del reinici},label=Check_boot]
if (resetCause & RMU_RSTCAUSE_WDOGRST) {
resetbyWatchdog = true;
} else {
resetbyWatchdog = false;
}
\end{lstlisting}
Cal tenir en compte que aquest dispositiu serveix per solucionar possibles fallades totals del sistema, així que cal ser curosos amb el seu ús. Així, si tenim un bucle {\bf for} que pot generar algun problema, no te sentit posar la comanda de {\em touch} al {\bf watchdog} dins del {\bf for}, si no que segurament te sentit fer-ho abans i després del bucle.
\part{Programació de perifèrics II}
\label{part:programacio_2}
\chapter{ADC}
\label{sub:ADC}
%% AMPLIAR
Un perifèric força habitual en els microcontroladors actuals és l'\gls{ADC}.
Aquesta perifèric el que fa és llegir un senyal analògic (un voltatge) i convertir-lo a un valor digital (un número). Hi ha diversos models d'\gls{ADC} amb característiques diferents, però bàsicament les característiques principals d'un ADC són:
\begin{itemize}
\item Resolució: fa referència a quants bits dona la conversió de l'ADC. Un ADC de 16 bits, en principi, dona més detall del senyal d'entrada que un ADC de 8 bits. Actualment el més habitual és trobar ADCs d'entre 8 i 16 bits de resolució.
\item {\em Sampling rate}: és la cadència amb la que l'ADC agafa una nova mostra del senyal i la converteix a digital. Els microcontroladors actuals incorporen ADCs que poden arribar al milió de mostres per segon.
\item Referència: pot ser que es compari el senyal segons una referència determinada i el ADC ens doni el valor respecte a aquesta referència.
\end{itemize}
En els microcontroladors moderns, és habitual que davant de l'ADC hi hagi un multiplexor analògic, de manera que es pugui convertir diverses senyals connectades a diferents pins amb un mateix perifèric.
A l'hora de fer servir un \gls{ADC}, caldrà configurar-lo en els paràmetres de funcionament que necessitem per la nostra senyal.
Com la majoria de perifèrics, l'ADC pot generar una o vàries \glspl{IRQ} segons certes condicions, habitualment quan s'ha acabat la conversió. D'aquesta manera la CPU no cal que faci {\em polling} del registre d'estat per saber si la conversió ha finalitzat.
L'ADC acaba per donar-nos un valor dins el seu rang de treball proporcional al valor de voltatge d'entrada, aquest valor s'acostuma a anomenar {\em counts}. Per convertir aquest valor en {\em counts} al valor de voltatge corresponent, cal aplicar la fórmula \fullref{eq:ADCFormula}:
\begin{equation}
\label{eq:ADCFormula}
V_{ADC} = \frac{{counts} * V_{max} }{2^N-1}
\end{equation}
On $V_{max}$ és el voltatge màxim de l'entrada i $N$ el nombre de bits (resolució) de la conversió de l'ADC.\footnote{Això és vàlid per configuracions tipus {\em single-ended}}
\section{Exemple d'ADC}
\label{sub:ADC_example}
\href{https://github.com/mariusmm/cursembedded/tree/master/Simplicity/ADC_1s}{Per aquest exemple} ens cal per primer cop un petit HW addicional. Farem servir un potenciòmetre que ens donarà una tensió entre Vdd i 0 volts segons el seu recorregut. La sortida d'aquest potenciòmetre la connectarem al pin 16 del connector de Debug de la PCB. Els altres dos pins aniran a 19 i 20 del mateix connector (veure esquemàtic a la Figura~\ref{fig:sch_adc} i fotografia del sistema a Figura~\ref{fig:setup_adc}).
\begin{figure}
\centering
\includegraphics[width=0.85\textwidth, keepaspectratio]{imatges/adc_schematic.png}
\caption{Esquemàtic de la connexió del Potenciòmetre al canal d'\gls{ADC}}
\label{fig:sch_adc}
\end{figure}
\begin{figure}
\centering
\includegraphics[width=0.85\textwidth, keepaspectratio]{imatges/adc_setup.png}
\caption{Fotografia del sistema amb el connexitat correcte}
\label{fig:setup_adc}
\end{figure}
Connectat així el potenciòmetre, quan estigui a un extrem del recorregut tindrem 0V a l'entrada de l'ADC i quan estigui a l'altre extrem hi tindrem \gls{Vdd}.
A l'exemple trobem un codi molt senzill, on simplement s'inicia l'\gls{ADC} amb els paràmetres per defecte i només es canvia el canal d'entrada (el 6) i la tensió de referència (en aquest cas Vdd).
D'aquesta manera, els 12 bits de resolució del ADC serviran per comparar la tensió d'entrada amb els 3.3 V amb els que està alimentat el microcontrolador a la placa.
\begin{remark}