-
-
Notifications
You must be signed in to change notification settings - Fork 14
/
expander.html
1447 lines (1385 loc) · 86.1 KB
/
expander.html
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="generator" content="rustdoc">
<title>NuttX GPIO Expander for PineDio Stack BL604</title>
<!-- Begin scripts/articles/*-header.html: Article Header for Custom Markdown files processed by rustdoc, like chip8.md -->
<meta property="og:title"
content="NuttX GPIO Expander for PineDio Stack BL604"
data-rh="true">
<meta property="og:description"
content="PineDio Stack BL604 RISC-V Board has an interesting problem on Apache NuttX RTOS... Too many GPIOs! Let's fix this with a GPIO Expander"
data-rh="true">
<meta property="og:image"
content="https://lupyuen.github.io/images/expander-title.jpg">
<meta property="og:type"
content="article" data-rh="true">
<link rel="canonical" href="https://lupyuen.org/articles/expander.html" />
<!-- End scripts/articles/*-header.html -->
<!-- Begin scripts/rustdoc-header.html: Header for Custom Markdown files processed by rustdoc, like chip8.md -->
<link rel="alternate" type="application/rss+xml" title="RSS Feed for lupyuen" href="/rss.xml" />
<link rel="stylesheet" type="text/css" href="../normalize.css">
<link rel="stylesheet" type="text/css" href="../rustdoc.css" id="mainThemeStyle">
<link rel="stylesheet" type="text/css" href="../dark.css">
<link rel="stylesheet" type="text/css" href="../light.css" id="themeStyle">
<link rel="stylesheet" type="text/css" href="../prism.css">
<script src="../storage.js"></script><noscript>
<link rel="stylesheet" href="../noscript.css"></noscript>
<link rel="shortcut icon" href="../favicon.ico">
<style type="text/css">
#crate-search {
background-image: url("../down-arrow.svg");
}
</style>
<!-- End scripts/rustdoc-header.html -->
</head>
<body class="rustdoc">
<!--[if lte IE 8]>
<div class="warning">
This old browser is unsupported and will most likely display funky
things.
</div>
<![endif]-->
<!-- Begin scripts/rustdoc-before.html: Pre-HTML for Custom Markdown files processed by rustdoc, like chip8.md -->
<!-- Begin Theme Picker -->
<div class="theme-picker" style="left: 0"><button id="theme-picker" aria-label="Pick another theme!"><img src="../brush.svg"
width="18" alt="Pick another theme!"></button>
<div id="theme-choices"></div>
</div>
<!-- Theme Picker -->
<!-- End scripts/rustdoc-before.html -->
<h1 class="title">NuttX GPIO Expander for PineDio Stack BL604</h1>
<nav id="rustdoc"><ul>
<li><a href="#bl602-evb-limitations" title="BL602 EVB Limitations">1 BL602 EVB Limitations</a><ul>
<li><a href="#pin-definitions" title="Pin Definitions">1.1 Pin Definitions</a><ul></ul></li></ul></li>
<li><a href="#overcome-the-limitations" title="Overcome The Limitations">2 Overcome The Limitations</a><ul></ul></li>
<li><a href="#gpio-expander" title="GPIO Expander">3 GPIO Expander</a><ul>
<li><a href="#gpio-operations" title="GPIO Operations">3.1 GPIO Operations</a><ul></ul></li></ul></li>
<li><a href="#gpio-interrupt" title="GPIO Interrupt">4 GPIO Interrupt</a><ul>
<li><a href="#bl602-evb-interrupt" title="BL602 EVB Interrupt">4.1 BL602 EVB Interrupt</a><ul></ul></li>
<li><a href="#attach-interrupt-callback" title="Attach Interrupt Callback">4.2 Attach Interrupt Callback</a><ul></ul></li>
<li><a href="#gpio-command" title="GPIO Command">4.3 GPIO Command</a><ul></ul></li>
<li><a href="#other-callers" title="Other Callers">4.4 Other Callers</a><ul></ul></li></ul></li>
<li><a href="#load-gpio-expander" title="Load GPIO Expander">5 Load GPIO Expander</a><ul></ul></li>
<li><a href="#validate-gpio" title="Validate GPIO">6 Validate GPIO</a><ul>
<li><a href="#pin-functions" title="Pin Functions">6.1 Pin Functions</a><ul></ul></li></ul></li>
<li><a href="#test-gpio-expander" title="Test GPIO Expander">7 Test GPIO Expander</a><ul>
<li><a href="#test-touch-panel" title="Test Touch Panel">7.1 Test Touch Panel</a><ul></ul></li>
<li><a href="#test-push-button" title="Test Push Button">7.2 Test Push Button</a><ul></ul></li>
<li><a href="#test-lorawan" title="Test LoRaWAN">7.3 Test LoRaWAN</a><ul></ul></li></ul></li>
<li><a href="#whats-next" title="What’s Next">8 What’s Next</a><ul></ul></li>
<li><a href="#notes" title="Notes">9 Notes</a><ul></ul></li>
<li><a href="#appendix-validate-pin-function" title="Appendix: Validate Pin Function">10 Appendix: Validate Pin Function</a><ul>
<li><a href="#validate-at-compile-time" title="Validate at Compile-Time">10.1 Validate at Compile-Time</a><ul></ul></li>
<li><a href="#validate-at-startup" title="Validate at Startup">10.2 Validate at Startup</a><ul></ul></li></ul></li>
<li><a href="#appendix-initialise-gpio-expander" title="Appendix: Initialise GPIO Expander">11 Appendix: Initialise GPIO Expander</a><ul></ul></li>
<li><a href="#appendix-set-gpio-direction" title="Appendix: Set GPIO Direction">12 Appendix: Set GPIO Direction</a><ul></ul></li>
<li><a href="#appendix-set-gpio-option" title="Appendix: Set GPIO Option">13 Appendix: Set GPIO Option</a><ul></ul></li>
<li><a href="#appendix-write-gpio" title="Appendix: Write GPIO">14 Appendix: Write GPIO</a><ul></ul></li>
<li><a href="#appendix-read-gpio" title="Appendix: Read GPIO">15 Appendix: Read GPIO</a><ul></ul></li>
<li><a href="#appendix-attach-gpio-interrupt" title="Appendix: Attach GPIO Interrupt">16 Appendix: Attach GPIO Interrupt</a><ul></ul></li>
<li><a href="#appendix-detach-gpio-interrupt" title="Appendix: Detach GPIO Interrupt">17 Appendix: Detach GPIO Interrupt</a><ul></ul></li>
<li><a href="#appendix-handle-gpio-interrupt" title="Appendix: Handle GPIO Interrupt">18 Appendix: Handle GPIO Interrupt</a><ul></ul></li></ul></nav><p>📝 <em>3 May 2022</em></p>
<p><img src="https://lupyuen.github.io/images/expander-title.jpg" alt="NuttX GPIO Expander for PineDio Stack BL604" /></p>
<p><a href="https://lupyuen.github.io/articles/pinedio2"><strong>PineDio Stack BL604</strong></a> (Pine64’s newest RISC-V board) has an interesting problem on <a href="https://lupyuen.github.io/articles/nuttx"><strong>Apache NuttX RTOS</strong></a>…</p>
<p><strong>Too Many GPIOs!</strong></p>
<p>Let’s fix this with a <strong>GPIO Expander</strong>.</p>
<p><em>Why too many GPIOs?</em></p>
<p>All <strong>23 GPIOs</strong> on PineDio Stack BL604 are wired up…</p>
<ul>
<li><a href="https://lupyuen.github.io/articles/pinedio2#appendix-gpio-assignment"><strong>“PineDio Stack GPIO Assignment”</strong></a></li>
</ul>
<p>And we need easy access to all GPIOs as our devs create <strong>NuttX Drivers and Apps</strong> for PineDio Stack.</p>
<p>(See pic below)</p>
<p><em>NuttX can’t handle 23 GPIOs?</em></p>
<p>Well it gets messy. Without GPIO Expander, BL604 on NuttX supports one <strong>GPIO Input</strong>, one <strong>GPIO Output</strong> and one <strong>GPIO Interrupt</strong>.</p>
<p>And they are <strong>named sequentially</strong> (Input first, then Output, then Interrupt)…</p>
<ul>
<li>
<p><strong>/dev/gpio0</strong>: GPIO Input</p>
</li>
<li>
<p><strong>/dev/gpio1</strong>: GPIO Output</p>
</li>
<li>
<p><strong>/dev/gpio2</strong>: GPIO Interrupt</p>
</li>
</ul>
<p>(See pic above)</p>
<p><em>This looks OK?</em></p>
<p>Until we realise that they map to <strong>totally different GPIO Pins</strong> on PineDio Stack!</p>
<div><table><thead><tr><th>GPIO Device</th><th style="text-align: center">BL604 GPIO Pin</th><th>Function</th></tr></thead><tbody>
<tr><td><strong>/dev/gpio0</strong></td><td style="text-align: center">GPIO Pin <strong><code>10</code></strong></td><td>SX1262 Busy</td></tr>
<tr><td><strong>/dev/gpio1</strong></td><td style="text-align: center">GPIO Pin <strong><code>15</code></strong></td><td>SX1262 Chip Select</td></tr>
<tr><td><strong>/dev/gpio2</strong></td><td style="text-align: center">GPIO Pin <strong><code>19</code></strong></td><td>SX1262 Interrupt</td></tr>
</tbody></table>
</div>
<p>Extend this to <strong>23 GPIOs</strong> and we have a mapping disaster!</p>
<p>Let’s simplify this setup and map GPIO Pins 0 to 22 as “<strong>/dev/gpio0</strong>” to “<strong>/dev/gpio22</strong>”. We’ll do this with a <strong>GPIO Expander</strong>.</p>
<p>(See pic above)</p>
<p><em>What’s a GPIO Expander?</em></p>
<p>NuttX lets us create <strong>I/O Expander Drivers</strong> that will manage many GPIOs…</p>
<ul>
<li><a href="https://github.com/apache/nuttx/blob/master/include/nuttx/ioexpander/ioexpander.h"><strong>NuttX I/O Expander Driver Interface</strong></a></li>
</ul>
<p>Well BL604 looks like a <strong>Big Bag o’ GPIOs</strong>. Why not create a <strong>GPIO Expander</strong> that will manage all 23 GPIOs?</p>
<ul>
<li><a href="https://github.com/lupyuen/bl602_expander"><strong>BL602 / BL604 GPIO Expander</strong></a></li>
</ul>
<p>(Other microcontrollers might also need a GPIO Expander… Like <a href="https://github.com/openwch/ch32v307"><strong>CH32V307</strong></a>, which has 80 GPIOs!)</p>
<p><em>So we’re just renumbering GPIOs?</em></p>
<p>Above and beyond that, our BL604 GPIO Expander serves other functions…</p>
<ul>
<li>
<p>Attach and detach <strong>GPIO Interrupt Callbacks</strong></p>
</li>
<li>
<p><strong>Validate GPIO Pin Numbers</strong> at startup</p>
</li>
<li>
<p>But skip the GPIOs reserved for <strong>UART, I2C and SPI</strong></p>
<p>(That’s why we have GPIO gaps in the pic above)</p>
</li>
</ul>
<p>Let’s dive in!</p>
<blockquote>
<p><img src="https://lupyuen.github.io/images/expander-pinedio1a.png" alt="All 23 GPIOs on PineDio Stack BL604 are wired up" /></p>
</blockquote>
<blockquote>
<p><a href="https://lupyuen.github.io/articles/pinedio2#appendix-gpio-assignment">(Source)</a></p>
</blockquote>
<h1 id="bl602-evb-limitations"><a class="doc-anchor" href="#bl602-evb-limitations">§</a>1 BL602 EVB Limitations</h1>
<p><em>What’s this BL602 EVB?</em></p>
<p>In NuttX, <strong>BL602 EVB</strong> (“Evaluation Board”) provides the <strong>Board-Specific Functions</strong> for PineDio Stack and other BL602 / BL604 boards…</p>
<ul>
<li><strong>NuttX BL602 EVB:</strong> <a href="https://github.com/lupyuen/nuttx/tree/pinedio/boards/risc-v/bl602/bl602evb/src"><strong>boards/risc-v/bl602/bl602evb</strong></a></li>
</ul>
<p><em>What’s inside BL602 EVB?</em></p>
<p>The important parts of BL602 EVB are…</p>
<ul>
<li>
<p><strong>Pin Definitions:</strong> <a href="https://github.com/lupyuen/nuttx/blob/pinedio/boards/risc-v/bl602/bl602evb/include/board.h"><strong>board.h</strong></a></p>
<p>Defines the pins for the GPIO, UART, I2C, SPI and PWM ports.</p>
</li>
<li>
<p><strong>Bring-Up:</strong> <a href="https://github.com/lupyuen/nuttx/blob/pinedio/boards/risc-v/bl602/bl602evb/src/bl602_bringup.c"><strong>bl602_bringup.c</strong></a></p>
<p>Starts the NuttX Drivers and the GPIO / UART / I2C / SPI / PWM ports.</p>
</li>
<li>
<p><strong>EVB GPIO Driver:</strong> <a href="https://github.com/lupyuen/nuttx/blob/pinedio/boards/risc-v/bl602/bl602evb/src/bl602_gpio.c"><strong>bl602_gpio.c</strong></a></p>
<p>Implements the GPIO Input, Output and Interrupt ports.</p>
<p>Calls the <a href="https://github.com/lupyuen/nuttx/blob/pinedio/arch/risc-v/src/bl602/bl602_gpio.c"><strong>BL602 GPIO Driver</strong></a>.</p>
</li>
</ul>
<p>In a while we’ll study the <strong>limitations of BL602 EVB</strong>, to understand why we created the BL602 GPIO Expander.</p>
<p><em>Wait… Where’s the rest of the BL602 stuff?</em></p>
<p>The <strong>Architecture-Specific Functions</strong> for BL602 and BL604 are located at…</p>
<ul>
<li><strong>NuttX BL602:</strong> <a href="https://github.com/lupyuen/nuttx/tree/pinedio/arch/risc-v/src/bl602"><strong>arch/risc-v/src/bl602</strong></a></li>
</ul>
<p>This includes the low-level drivers for GPIO, UART, I2C, SPI, PWM, …</p>
<p>We’re hunky dory with these drivers, though we’ve made tiny mods like for <a href="https://lupyuen.github.io/articles/pinedio2#spi-device-table"><strong>SPI Device Table</strong></a>.</p>
<p><img src="https://lupyuen.github.io/images/expander-title1a.png" alt="BL602 EVB always maps sequentially the GPIO Pins" /></p>
<h2 id="pin-definitions"><a class="doc-anchor" href="#pin-definitions">§</a>1.1 Pin Definitions</h2>
<p>In BL602 EVB, this is how we <strong>define the pins</strong> for GPIO / UART / I2C / SPI / PWM: <a href="https://github.com/lupyuen/nuttx/blob/pinedio/boards/risc-v/bl602/bl602evb/include/board.h#L38-L59">board.h</a></p>
<div class="example-wrap"><pre class="language-c"><code>#define BOARD_NGPIOIN 1 // Number of GPIO Input pins
#define BOARD_NGPIOOUT 1 // Number of GPIO Output pins
#define BOARD_NGPIOINT 1 // Number of GPIO Interrupt pins
// GPIO Input: GPIO 10
#define BOARD_GPIO_IN1 (GPIO_PIN10 | GPIO_INPUT | GPIO_FLOAT | GPIO_FUNC_SWGPIO)
// GPIO Output: GPIO 15
#define BOARD_GPIO_OUT1 (GPIO_PIN15 | GPIO_OUTPUT | GPIO_PULLUP | GPIO_FUNC_SWGPIO)
// GPIO Interrupt: GPIO 19
#define BOARD_GPIO_INT1 (GPIO_PIN19 | GPIO_INPUT | GPIO_FLOAT | GPIO_FUNC_SWGPIO)</code></pre></div>
<p><a href="https://github.com/lupyuen/nuttx/blob/pinedio/boards/risc-v/bl602/bl602evb/include/board.h#L61-L145">(See the UART / I2C / SPI / PWM Pins)</a></p>
<p>A couple of issues…</p>
<ul>
<li>
<p>BL602 EVB strangely limits us to <strong>one GPIO Input, one GPIO Output and one GPIO Interrupt</strong></p>
</li>
<li>
<p>We could extend this GPIO Limit, but we’ll have to <strong>modify the EVB GPIO Driver</strong>, which sounds odd</p>
<p><a href="https://github.com/lupyuen/nuttx/blob/pinedio/boards/risc-v/bl602/bl602evb/src/bl602_gpio.c#L106-L137">(See this)</a></p>
</li>
<li>
<p>BL602 EVB always <strong>maps sequentially</strong> the GPIO Pins like so: GPIO Input, then GPIO Output, then GPIO Interrupt (pic above)…</p>
<p><strong>/dev/gpio0</strong>: GPIO Input <em>(GPIO 10)</em></p>
<p><strong>/dev/gpio1</strong>: GPIO Output <em>(GPIO 15)</em></p>
<p><strong>/dev/gpio2</strong>: GPIO Interrupt <em>(GPIO 19)</em></p>
<p><a href="https://github.com/lupyuen/nuttx/blob/pinedio/boards/risc-v/bl602/bl602evb/src/bl602_gpio.c#L550-L604">(See this)</a></p>
</li>
<li>
<p>Which becomes super confusing when we <strong>map all 23 GPIOs</strong> on PineDio Stack.</p>
<p>(Especially when our new devs are now creating NuttX Drivers and Apps for PineDio Stack)</p>
</li>
<li>
<p>What happens if we <strong>reuse the GPIOs</strong> by mistake? BL602 EVB will silently allow this. Which ain’t right!</p>
<div class="example-wrap"><pre class="language-c"><code>// GPIO Input: GPIO 10
#define BOARD_GPIO_IN1 (GPIO_PIN10 | GPIO_INPUT | GPIO_FLOAT | GPIO_FUNC_SWGPIO)
// GPIO Output: Also GPIO 10 (Oops!)
#define BOARD_GPIO_OUT1 (GPIO_PIN10 | GPIO_OUTPUT | GPIO_PULLUP | GPIO_FUNC_SWGPIO)</code></pre></div></li>
</ul>
<p>Thus we see that <strong>BL602 EVB is somewhat limited</strong>…</p>
<p>BL602 EVB works great for 3 GPIOs, but <strong>doesn’t scale well</strong> beyond that.</p>
<p>Let’s make this better.</p>
<p><em>Shouldn’t the pins be defined in Kconfig / menuconfig?</em></p>
<p>Perhaps. NuttX on ESP32 defines the pins in <strong>Kconfig and menuconfig.</strong> <a href="https://github.com/apache/nuttx/blob/master/arch/xtensa/src/esp32/Kconfig#L938-L984">(See this)</a></p>
<p>But for now, let’s keep the Pin Definitions in <a href="https://github.com/lupyuen/nuttx/blob/pinedio/boards/risc-v/bl602/bl602evb/include/board.h#L38-L59"><strong>board.h</strong></a>.</p>
<p><img src="https://lupyuen.github.io/images/expander-title2a.jpg" alt="Overcome The Limitations" /></p>
<h1 id="overcome-the-limitations"><a class="doc-anchor" href="#overcome-the-limitations">§</a>2 Overcome The Limitations</h1>
<p>We plan to make BL602 EVB <strong>work great with PineDio Stack</strong>…</p>
<ul>
<li>
<p><strong>Support 23 GPIOs</strong> with any mix of GPIO Inputs / Outputs / Interrupts</p>
<p>(Perfect for PineDio Stack’s SPI Display, I2C Touch Panel, SX1262 Transceiver, Accelerometer, Push Button, …)</p>
</li>
<li>
<p>Renumber the GPIOs as “<strong>/dev/gpio0</strong>” to “<strong>/dev/gpio22</strong>”</p>
<p>(“<strong>/dev/gpioN</strong>” will simply map to <strong>GPIO Pin N</strong>)</p>
</li>
<li>
<p>Allow <strong>gaps in the GPIO Numbering</strong> (pic above)</p>
<p>(We skip the GPIOs reserved for UART, I2C, SPI and PWM)</p>
</li>
<li>
<p>Keep the <strong>Pin Definitions</strong></p>
<p>(Original BL602 EVB will still build OK for plain old BL602)</p>
</li>
<li>
<p><strong>Validate the GPIOs</strong> at startup</p>
<p>(No more reusing GPIOs by mistake!)</p>
</li>
</ul>
<p>We make this happen by extending BL602 EVB with an (optional) <strong>GPIO Expander</strong>.</p>
<p><em>Why not make an EVB for PineDio Stack?</em></p>
<p>Yes we could create a new <strong>EVB for PineDio Stack</strong>.</p>
<p>(And do away with BL602 EVB altogether)</p>
<p>But we’ll save that for later because it might lead to <strong>fragmentation of BL602 / BL604 Support</strong> in NuttX.</p>
<p>(Let’s do the <strong>bare minimum</strong> that will make NuttX decently usable on PineDio Stack!)</p>
<p><img src="https://lupyuen.github.io/images/expander-code1a.png" alt="NuttX I/O Expander Driver Interface" /></p>
<h1 id="gpio-expander"><a class="doc-anchor" href="#gpio-expander">§</a>3 GPIO Expander</h1>
<p><em>So our GPIO Expander works like a NuttX I/O Expander?</em></p>
<p>Yep, NuttX lets us create <strong>I/O Expander Drivers</strong> that will manage many Input, Output and Interrupt GPIOs…</p>
<ul>
<li><a href="https://github.com/apache/nuttx/blob/master/include/nuttx/ioexpander/ioexpander.h"><strong>NuttX I/O Expander Driver Interface</strong></a></li>
</ul>
<p>I/O Expanders will support reading and writing to GPIOs, also attaching and detaching Interrupt Callbacks. (Pic above)</p>
<p><em>Isn’t an I/O Expander Driver supposed to be Platform-Independent?</em></p>
<p>Yeah, we’re borrowing (misappropriating?) this NuttX Abstraction
because it meets our needs for PineDio Stack.</p>
<p>Other RISC-V microcontrollers might also need a GPIO Expander… Like <a href="https://github.com/openwch/ch32v307"><strong>CH32V307</strong></a>, which has 80 GPIOs!</p>
<p><em>Great! How will we get started on GPIO Expander?</em></p>
<p>NuttX helpfully provides a <strong>Skeleton Driver</strong> for I/O Expander (pic below)…</p>
<ul>
<li><a href="https://github.com/apache/nuttx/blob/master/drivers/ioexpander/skeleton.c"><strong>Skeleton Driver for I/O Expander</strong></a></li>
</ul>
<p>Let’s flesh out the Skeleton Driver for our GPIO Expander.</p>
<p><img src="https://lupyuen.github.io/images/expander-code4a.png" alt="Skeleton Driver for I/O Expander" /></p>
<h2 id="gpio-operations"><a class="doc-anchor" href="#gpio-operations">§</a>3.1 GPIO Operations</h2>
<p>Our GPIO Expander supports these <strong>GPIO Operations</strong>…</p>
<ul>
<li>
<p>Set <strong>GPIO Direction</strong></p>
<p>(Input or Output)</p>
</li>
<li>
<p>Set <strong>GPIO Interrupt Options</strong></p>
<p>(Trigger by Rising or Falling Edge)</p>
</li>
<li>
<p>Read a <strong>GPIO Input</strong></p>
</li>
<li>
<p>Write to a <strong>GPIO Output</strong></p>
</li>
<li>
<p>Attach / Detach a <strong>GPIO Interrupt Callback</strong></p>
</li>
</ul>
<p>We define the GPIO Operations like so: <a href="https://github.com/lupyuen/bl602_expander/blob/main/bl602_expander.c#L141-L159">bl602_expander.c</a></p>
<div class="example-wrap"><pre class="language-c"><code>// GPIO Expander Operations
static const struct ioexpander_ops_s g_bl602_expander_ops = {
bl602_expander_direction, // Set GPIO Direction
bl602_expander_option, // Set GPIO Interrupt Options
bl602_expander_writepin, // Write to GPIO Output
bl602_expander_readpin, // Read from GPIO Input
bl602_expander_readbuf, // (Read Buffer Not Implemented)
...
bl602_expander_attach, // Attach GPIO Interrupt Callback
bl602_expander_detach // Detach GPIO Interrupt Callback
};</code></pre></div>
<p>The <strong>implementation of the GPIO Operations</strong> is explained in the Appendix…</p>
<ul>
<li>
<p><a href="https://lupyuen.github.io/articles/expander#appendix-initialise-gpio-expander"><strong>“Initialise GPIO Expander”</strong></a></p>
</li>
<li>
<p><a href="https://lupyuen.github.io/articles/expander#appendix-set-gpio-direction"><strong>“Set GPIO Direction”</strong></a></p>
</li>
<li>
<p><a href="https://lupyuen.github.io/articles/expander#appendix-set-gpio-option"><strong>“Set GPIO Option”</strong></a></p>
</li>
<li>
<p><a href="https://lupyuen.github.io/articles/expander#appendix-write-gpio"><strong>“Write GPIO”</strong></a></p>
</li>
<li>
<p><a href="https://lupyuen.github.io/articles/expander#appendix-read-gpio"><strong>“Read GPIO”</strong></a></p>
</li>
<li>
<p><a href="https://lupyuen.github.io/articles/expander#appendix-attach-gpio-interrupt"><strong>“Attach GPIO Interrupt”</strong></a></p>
</li>
<li>
<p><a href="https://lupyuen.github.io/articles/expander#appendix-detach-gpio-interrupt"><strong>“Detach GPIO Interrupt”</strong></a></p>
</li>
<li>
<p><a href="https://lupyuen.github.io/articles/expander#appendix-handle-gpio-interrupt"><strong>“Handle GPIO Interrupt”</strong></a></p>
</li>
</ul>
<p><em>Existing NuttX Drivers call <a href="https://github.com/lupyuen/nuttx/blob/pinedio/arch/risc-v/src/bl602/bl602_gpio.c#L218-L230"><strong>bl602_gpioread</strong></a> and <a href="https://github.com/lupyuen/nuttx/blob/pinedio/arch/risc-v/src/bl602/bl602_gpio.c#L197-L216"><strong>bl602_gpiowrite</strong></a> to read and write BL602 GPIOs. Will they still work?</em></p>
<p>Yep the <strong>BL602 GPIO Functions</strong> like <a href="https://github.com/lupyuen/nuttx/blob/pinedio/arch/risc-v/src/bl602/bl602_gpio.c#L218-L230"><strong>bl602_gpioread</strong></a> and <a href="https://github.com/lupyuen/nuttx/blob/pinedio/arch/risc-v/src/bl602/bl602_gpio.c#L197-L216"><strong>bl602_gpiowrite</strong></a> will work fine with GPIO Expander.</p>
<p>The <strong>NuttX GPIO Functions</strong> like <code>open()</code> and <code>ioctl()</code> will also work with GPIO Expander.</p>
<p>(That’s because they call the <a href="https://github.com/lupyuen/nuttx/blob/pinedio/drivers/ioexpander/gpio_lower_half.c"><strong>GPIO Lower Half Driver</strong></a>, which is integrated with our GPIO Expander)</p>
<p>Let’s look at GPIO Interrupts, which are more complicated…</p>
<p><img src="https://lupyuen.github.io/images/expander-code5a.png" alt="GPIO Operations" /></p>
<h1 id="gpio-interrupt"><a class="doc-anchor" href="#gpio-interrupt">§</a>4 GPIO Interrupt</h1>
<p><em>BL602 EVB works OK with GPIO Interrupts?</em></p>
<p>As noted (eloquently) by Robert Lipe, it’s <strong>difficult to attach a GPIO Interrupt Callback</strong> with BL602 EVB…</p>
<ul>
<li><a href="https://www.robertlipe.com/buttons-on-bl602-nuttx/"><strong>“Buttons on BL602 NuttX”</strong></a></li>
</ul>
<blockquote>
<p><img src="https://lupyuen.github.io/images/expander-button.jpg" alt="As noted (eloquently) by Robert Lipe, attaching a BL602 GPIO Interrupt Callback is hard (because our stars are misaligned)" /></p>
</blockquote>
<blockquote>
<p><a href="https://www.robertlipe.com/buttons-on-bl602-nuttx/">(Source)</a></p>
</blockquote>
<p>Let’s find out why…</p>
<p>(Perhaps our stars were misaligned 😂)</p>
<h2 id="bl602-evb-interrupt"><a class="doc-anchor" href="#bl602-evb-interrupt">§</a>4.1 BL602 EVB Interrupt</h2>
<p><em>Anything peculiar about GPIO Interrupts on BL602 and BL604?</em></p>
<p><strong>GPIO Interrupt Handling</strong> gets tricky for BL602 and BL604…</p>
<p>All GPIO Interrupts are multiplexed into <strong>One Single GPIO IRQ!</strong></p>
<p><a href="https://github.com/lupyuen/nuttx/blob/pinedio/boards/risc-v/bl602/bl602evb/src/bl602_gpio.c#L477-L505">(<strong>BL602_IRQ_GPIO_INT0</strong> is the common GPIO IRQ)</a></p>
<p>BL602 EVB <strong>demultiplexes the GPIO IRQ</strong> and calls the GPIO Interrupt Callbacks.</p>
<p><img src="https://lupyuen.github.io/images/expander-code2a.png" alt="Attaching a GPIO Interrupt with BL602 EVB" /></p>
<p><a href="https://github.com/lupyuen/nuttx/blob/pinedio/boards/risc-v/bl602/bl602evb/src/bl602_gpio.c#L477-L505">(Source)</a></p>
<p><em>So we call BL602 EVB to attach our own GPIO Interrupt Callback?</em></p>
<p>Sadly we can’t. BL602 EVB <strong>doesn’t expose a Public Function</strong> that we may call to attach our Interrupt Callback.</p>
<p>(<strong>gpint_attach</strong> is a Private Function, as shown above)</p>
<p>We could call <a href="https://lupyuen.github.io/articles/sx1262#handle-dio1-interrupt"><strong><code>ioctl()</code></strong></a>, but that would be extremely awkward in the Kernel Space.</p>
<p><em>Which means we need to implement this in our GPIO Expander?</em></p>
<p>Exactly! Our <strong>GPIO Expander</strong> shall take over these duties from BL602 EVB…</p>
<ul>
<li>
<p>Handle the <strong>GPIO IRQ Interrupt</strong></p>
</li>
<li>
<p><strong>Demultiplex</strong> the IRQ</p>
</li>
<li>
<p>Call the right <strong>GPIO Interrupt Callback</strong></p>
</li>
</ul>
<p>More about the implementation in a moment. Let’s talk about calling the GPIO Expander…</p>
<h2 id="attach-interrupt-callback"><a class="doc-anchor" href="#attach-interrupt-callback">§</a>4.2 Attach Interrupt Callback</h2>
<p><em>How do we attach a GPIO Interrupt Callback?</em></p>
<p>Because GPIO Expander implements the I/O Expander Interface, we may call <a href="https://github.com/lupyuen/nuttx/blob/pinedio/include/nuttx/ioexpander/ioexpander.h#L235-L257"><strong>IOEP_ATTACH</strong></a> to attach an Interrupt Callback.</p>
<p>Let’s attach an Interrupt Callback that will be called when we press the <strong>Push Button</strong> (GPIO 12) on PineDio Stack: <a href="https://github.com/lupyuen/nuttx/blob/2982b3a99057c5935ca9150b9f0f1da3565c6061/boards/risc-v/bl602/bl602evb/src/bl602_bringup.c#L696-L704">bl602_bringup.c</a></p>
<div class="example-wrap"><pre class="language-c"><code>#include <nuttx/ioexpander/gpio.h>
#include <nuttx/ioexpander/bl602_expander.h>
...
// Get the Push Button Pinset and GPIO Pin Number
gpio_pinset_t pinset = BOARD_BUTTON_INT;
uint8_t gpio_pin = (pinset & GPIO_PIN_MASK) >> GPIO_PIN_SHIFT;</code></pre></div>
<p><a href="https://github.com/lupyuen/nuttx/blob/pinedio/boards/risc-v/bl602/bl602evb/include/board.h#L143-L145">(<strong>BOARD_BUTTON_INT</strong> is defined in board.h)</a></p>
<p>First we get the <strong>GPIO Pin Number</strong> for the Push Button.</p>
<p>Then we configure our GPIO Expander to trigger the GPIO Interrupt on the <strong>Falling Edge</strong> (High to Low)…</p>
<div class="example-wrap"><pre class="language-c"><code>// Configure GPIO interrupt to be triggered on falling edge
DEBUGASSERT(bl602_expander != NULL);
IOEXP_SETOPTION(
bl602_expander, // BL602 GPIO Expander
gpio_pin, // GPIO Pin
IOEXPANDER_OPTION_INTCFG, // Configure interrupt trigger
(FAR void *) IOEXPANDER_VAL_FALLING // Trigger on falling edge
);</code></pre></div>
<p><a href="https://github.com/lupyuen/nuttx/blob/pinedio/include/nuttx/ioexpander/ioexpander.h#L91-L110">(<strong>IOEXP_SETOPTION</strong> comes from the I/O Expander)</a></p>
<p>Finally we call GPIO Expander to <strong>attach our Interrupt Callback</strong>…</p>
<div class="example-wrap"><pre class="language-c"><code>// Attach our GPIO interrupt callback
void *handle = IOEP_ATTACH(
bl602_expander, // BL602 GPIO Expander
(ioe_pinset_t) 1 << gpio_pin, // GPIO Pin converted to Pinset
button_isr_handler, // GPIO Interrupt Callback
NULL // TODO: Set the callback argument
);
DEBUGASSERT(handle != NULL);</code></pre></div>
<p><a href="https://github.com/lupyuen/nuttx/blob/pinedio/include/nuttx/ioexpander/ioexpander.h#L235-L257">(<strong>IOEP_ATTACH</strong> comes from the I/O Expander)</a></p>
<p>The <strong>Interrupt Callback</strong> is defined as…</p>
<div class="example-wrap"><pre class="language-c"><code>// Our GPIO Interrupt Callback
static int button_isr_handler(FAR struct ioexpander_dev_s *dev, ioe_pinset_t pinset, FAR void *arg) {
gpioinfo("Button Pressed\n");
return 0;
}</code></pre></div>
<p><a href="https://github.com/lupyuen/nuttx/blob/2982b3a99057c5935ca9150b9f0f1da3565c6061/boards/risc-v/bl602/bl602evb/src/bl602_bringup.c#L1038-L1044">(Source)</a></p>
<p>Note that the Interrupt Callback runs in the <strong>BL602 Interrupt Context</strong>.</p>
<p>Be careful!</p>
<h2 id="gpio-command"><a class="doc-anchor" href="#gpio-command">§</a>4.3 GPIO Command</h2>
<p>Another way to test the Push Button Interrupt is to use the <strong>GPIO Command</strong>.</p>
<p>(This only works if we don’t call <strong>IOEP_ATTACH</strong> to attach the Interrupt Callback)</p>
<p>Enter this in the NuttX Shell…</p>
<div class="example-wrap"><pre class="language-bash"><code>gpio -t 8 -w 1 /dev/gpio12</code></pre></div>
<p>Which says…</p>
<ul>
<li>
<p>Configure the GPIO for <strong>Rising Edge Interrupt</strong></p>
</li>
<li>
<p>And wait 5 seconds for <strong>Signal 1</strong></p>
</li>
</ul>
<p>Quickly press the <strong>Push Button</strong> on PineDio Stack. We should see…</p>
<div class="example-wrap"><pre class="language-text"><code>Interrupt pin: Value=1
Verify: Value=1</code></pre></div>
<p><a href="https://github.com/lupyuen/bl602_expander#test-push-button">(See the Complete Log)</a></p>
<p>If we don’t press the button <strong>within 5 seconds</strong>, the GPIO Command reports an Interrupt Timeout…</p>
<div class="example-wrap"><pre class="language-text"><code>Interrupt pin: Value=1
[Five second timeout with no signal]</code></pre></div><h2 id="other-callers"><a class="doc-anchor" href="#other-callers">§</a>4.4 Other Callers</h2>
<p><em>Who else is calling GPIO Expander to handle interrupts?</em></p>
<p>The <strong>CST816S Driver</strong> for PineDio Stack’s Touch Panel calls GPIO Expander to attach an Interrupt Callback (that’s called when the screen is touched)…</p>
<ul>
<li><a href="https://lupyuen.github.io/articles/touch#initialise-driver"><strong>“Initialise CST816S Driver”</strong></a></li>
</ul>
<p>The <strong>Semtech SX1262 LoRa Transceiver</strong> on PineDio Stack triggers a GPIO Interrupt (on pin DIO1) when a LoRa packet is transmitted or received…</p>
<ul>
<li><a href="https://lupyuen.github.io/articles/sx1262#handle-dio1-interrupt"><strong>“Handle DIO1 Interrupt”</strong></a></li>
</ul>
<p>This code calls <strong><code>ioctl()</code></strong> in the User Space (instead of Kernel Space), so it works OK with GPIO Expander without modification.</p>
<p>(That’s because <strong><code>ioctl()</code></strong> calls the <a href="https://github.com/lupyuen/nuttx/blob/pinedio/drivers/ioexpander/gpio_lower_half.c"><strong>GPIO Lower Half Driver</strong></a>, which is integrated with our GPIO Expander)</p>
<h1 id="load-gpio-expander"><a class="doc-anchor" href="#load-gpio-expander">§</a>5 Load GPIO Expander</h1>
<p>Here’s how we <strong>load our GPIO Expander</strong> at startup: <a href="https://github.com/lupyuen/nuttx/blob/pinedio/boards/risc-v/bl602/bl602evb/src/bl602_bringup.c#L742-L768">bl602_bringup.c</a></p>
<div class="example-wrap"><pre class="language-c"><code>#ifdef CONFIG_IOEXPANDER_BL602_EXPANDER
#include <nuttx/ioexpander/gpio.h>
#include <nuttx/ioexpander/bl602_expander.h>
// Global Instance of GPIO Expander
FAR struct ioexpander_dev_s *bl602_expander = NULL;
#endif // CONFIG_IOEXPANDER_BL602_EXPANDER
...
int bl602_bringup(void) {
...
// Existing Code
#if defined(CONFIG_DEV_GPIO) && !defined(CONFIG_GPIO_LOWER_HALF)
ret = bl602_gpio_initialize();
if (ret < 0) {
syslog(LOG_ERR, "Failed to initialize GPIO Driver: %d\n", ret);
return ret;
}
#endif
// New Code
#ifdef CONFIG_IOEXPANDER_BL602_EXPANDER
// Must load BL602 GPIO Expander before other drivers
bl602_expander = bl602_expander_initialize(
bl602_gpio_inputs, sizeof(bl602_gpio_inputs) / sizeof(bl602_gpio_inputs[0]),
bl602_gpio_outputs, sizeof(bl602_gpio_outputs) / sizeof(bl602_gpio_outputs[0]),
bl602_gpio_interrupts, sizeof(bl602_gpio_interrupts) / sizeof(bl602_gpio_interrupts[0]),
bl602_other_pins, sizeof(bl602_other_pins) / sizeof(bl602_other_pins[0]));
if (bl602_expander == NULL) {
syslog(LOG_ERR, "Failed to initialize GPIO Expander\n");
return -ENOMEM;
}
#endif // CONFIG_IOEXPANDER_BL602_EXPANDER</code></pre></div>
<p>(We’ll talk about <strong>bl602_gpio_*</strong> in the next chapter)</p>
<p>We must load the GPIO Expander <strong>before other drivers</strong> (like CST816S Touch Panel), because GPIO Expander provides GPIO functions for the drivers.</p>
<p>We need to <strong>disable the BL602 EVB GPIO Driver</strong>, because GPIO Expander needs the <a href="https://github.com/lupyuen/nuttx/blob/pinedio/drivers/ioexpander/gpio_lower_half.c"><strong>GPIO Lower Half Driver</strong></a> (which can’t coexist with BL602 EVB GPIO)…</p>
<div class="example-wrap"><pre class="language-c"><code>// Added CONFIG_GPIO_LOWER_HALF below
#if defined(CONFIG_DEV_GPIO) && !defined(CONFIG_GPIO_LOWER_HALF)
ret = bl602_gpio_initialize();</code></pre></div>
<p><a href="https://github.com/lupyuen/nuttx/blob/pinedio/boards/risc-v/bl602/bl602evb/src/bl602_bringup.c#L646-L653">(Source)</a></p>
<p>Check the following in menuconfig…</p>
<ul>
<li>
<p>Enable “<strong>BL602 GPIO Expander</strong>” under “Device Drivers → IO Expander/GPIO Support → Enable IO Expander Support”</p>
</li>
<li>
<p>Set “<strong>Number Of Pins</strong>” to 23</p>
</li>
<li>
<p>Enable “<strong>GPIO Lower Half</strong>”</p>
</li>
</ul>
<p><a href="https://github.com/lupyuen/bl602_expander#install-driver">(Full instrunctions are here)</a></p>
<p><img src="https://lupyuen.github.io/images/expander-code3a.png" alt="Tracking all 23 GPIOs used by PineDio Stack can get challenging" /></p>
<p><a href="https://github.com/lupyuen/nuttx/blob/pinedio/boards/risc-v/bl602/bl602evb/include/board.h#L61-L145">(Source)</a></p>
<h1 id="validate-gpio"><a class="doc-anchor" href="#validate-gpio">§</a>6 Validate GPIO</h1>
<p><em>Managing 23 GPIOs sounds mighty challenging?</em></p>
<p>Indeed! Tracking all 23 GPIOs used by PineDio Stack can get challenging… We might <strong>reuse the GPIOs</strong> by mistake!</p>
<p>Thankfully our GPIO Expander can help: It <strong>validates the GPIOs</strong> at startup.</p>
<p>Here are the <strong>GPIOs currently defined</strong> for PineDio Stack (more to come)…</p>
<ul>
<li><a href="https://github.com/lupyuen/nuttx/blob/pinedio/boards/risc-v/bl602/bl602evb/include/board.h#L61-L145">boards/risc-v/bl602/bl602evb/include/board.h</a></li>
</ul>
<p>At startup, GPIO Expander verifies that the GPIO, UART, I2C, SPI and PWM Ports <strong>don’t reuse the same GPIO</strong>.</p>
<p>If a GPIO is reused like so…</p>
<div class="example-wrap"><pre class="language-c"><code>// SPI CLK: GPIO 11
#define BOARD_SPI_CLK (GPIO_PIN11 | GPIO_INPUT | GPIO_PULLUP | GPIO_FUNC_SPI)
...
// Push Button Interrupt: Also GPIO 11 (Oops!)
#define BOARD_BUTTON_INT (GPIO_PIN11 | GPIO_INPUT | GPIO_FLOAT | GPIO_FUNC_SWGPIO)</code></pre></div>
<p>Then GPIO Expander will <strong>halt with an error</strong> at startup…</p>
<div class="example-wrap"><pre class="language-text"><code>bl602_expander_initialize: ERROR:
GPIO pin 11 is already in use</code></pre></div>
<p><em>Awesome! How do we enable this GPIO Validation?</em></p>
<p>To enable GPIO Validation, we <strong>add all GPIOs</strong> to the arrays <strong>bl602_gpio_inputs</strong>, <strong>bl602_gpio_outputs</strong>, <strong>bl602_gpio_interrupts</strong> and <strong>bl602_other_pins</strong>: <a href="https://github.com/lupyuen/nuttx/blob/pinedio/boards/risc-v/bl602/bl602evb/src/bl602_bringup.c#L126-L222">bl602_bringup.c</a></p>
<div class="example-wrap"><pre class="language-c"><code>#ifdef CONFIG_IOEXPANDER_BL602_EXPANDER
// GPIO Input Pins for BL602 GPIO Expander
static const gpio_pinset_t bl602_gpio_inputs[] =
{
#ifdef BOARD_SX1262_BUSY
BOARD_SX1262_BUSY,
#endif // BOARD_SX1262_BUSY
// Omitted: Other GPIO Input Pins
...
};
// GPIO Output Pins for BL602 GPIO Expander
static const gpio_pinset_t bl602_gpio_outputs[] =
{
#ifdef BOARD_LCD_CS
BOARD_LCD_CS,
#endif // BOARD_LCD_CS
// Omitted: Other GPIO Output Pins
...
};
// GPIO Interrupt Pins for BL602 GPIO Expander
static const gpio_pinset_t bl602_gpio_interrupts[] =
{
#ifdef BOARD_TOUCH_INT
BOARD_TOUCH_INT,
#endif // BOARD_TOUCH_INT
// Omitted: Other GPIO Interrupt Pins
...
};
// Other Pins for BL602 GPIO Expander (For Validation Only)
static const gpio_pinset_t bl602_other_pins[] =
{
#ifdef BOARD_UART_0_RX_PIN
BOARD_UART_0_RX_PIN,
#endif // BOARD_UART_0_RX_PIN
// Omitted: Other UART, I2C, SPI and PWM Pins
...
};
#endif // CONFIG_IOEXPANDER_BL602_EXPANDER</code></pre></div>
<p>At startup, we <strong>pass the pins to GPIO Expander</strong> during initialisation…</p>
<div class="example-wrap"><pre class="language-c"><code>// Initialise GPIO Expander at startup
bl602_expander = bl602_expander_initialize(
// BL602 Pinsets for GPIO Inputs and number of pins
bl602_gpio_inputs,
sizeof(bl602_gpio_inputs) / sizeof(bl602_gpio_inputs[0]),
// BL602 Pinsets for GPIO Outputs and number of pins
bl602_gpio_outputs,
sizeof(bl602_gpio_outputs) / sizeof(bl602_gpio_outputs[0]),
// BL602 Pinsets for GPIO Interrupts and number of pins
bl602_gpio_interrupts,
sizeof(bl602_gpio_interrupts) / sizeof(bl602_gpio_interrupts[0]),
// BL602 Pinsets for Other Pins (UART, I2C, SPI, PWM) and number of pins
bl602_other_pins,
sizeof(bl602_other_pins) / sizeof(bl602_other_pins[0]));</code></pre></div>
<p>GPIO Expander verifies that the <strong>GPIOs are not reused</strong>…</p>
<div class="example-wrap"><pre class="language-c"><code>FAR struct ioexpander_dev_s *bl602_expander_initialize(
// BL602 Pinsets for GPIO Inputs and number of pins
const gpio_pinset_t *gpio_inputs, uint8_t gpio_input_count,
// BL602 Pinsets for GPIO Outputs and number of pins
const gpio_pinset_t *gpio_outputs, uint8_t gpio_output_count,
// BL602 Pinsets for GPIO Interrupts and number of pins
const gpio_pinset_t *gpio_interrupts, uint8_t gpio_interrupt_count,
// BL602 Pinsets for Other Pins (UART, I2C, SPI, PWM) and number of pins
const gpio_pinset_t *other_pins, uint8_t other_pin_count) {
...
// Mark the GPIOs in use. CONFIG_IOEXPANDER_NPINS is 23
bool gpio_is_used[CONFIG_IOEXPANDER_NPINS];
memset(gpio_is_used, 0, sizeof(gpio_is_used));
// Validate the GPIO Inputs
for (i = 0; i < gpio_input_count; i++) {
// Get GPIO Pinset and GPIO Pin Number
gpio_pinset_t pinset = gpio_inputs[i];
uint8_t gpio_pin = (pinset & GPIO_PIN_MASK) >> GPIO_PIN_SHIFT;
// Check that the GPIO is not in use
if (gpio_is_used[gpio_pin]) {
gpioerr("ERROR: GPIO pin %d is already in use\n", gpio_pin);
return NULL;
}
gpio_is_used[gpio_pin] = true;
}
// Omitted: Validate the GPIO Outputs, GPIO Interrupts and Other Pins</code></pre></div>
<p><a href="https://github.com/lupyuen/bl602_expander/blob/main/bl602_expander.c#L958-L1123">(Source)</a></p>
<p>Let’s talk about something else we might validate at startup: Pin Functions.</p>
<p><a href="https://lupyuen.github.io/articles/expander#appendix-initialise-gpio-expander">(More about GPIO Expander initialisation)</a></p>
<p><strong>TODO:</strong> Validate that GPIO Inputs have <code>GPIO_INPUT</code>, GPIO Outputs have <code>GPIO_OUTPUT</code>, GPIO Interrupts have <code>GPIO_INPUT</code>. All GPIO Inputs / Outputs / Interrupts must have <code>GPIO_FUNC_SWGPIO</code>. All Other Pins must have either <code>GPIO_FUNC_UART</code>, <code>GPIO_FUNC_I2C</code>, <code>GPIO_FUNC_SPI</code> or <code>GPIO_FUNC_PWM</code>.</p>
<p><img src="https://lupyuen.github.io/images/bl602-pins1a.png" alt="Pin Functions" /></p>
<p><a href="https://github.com/bouffalolab/bl_docs/blob/main/BL602_RM/en/BL602_BL604_RM_1.2_en.pdf">(From BL602 Reference Manual)</a></p>
<h2 id="pin-functions"><a class="doc-anchor" href="#pin-functions">§</a>6.1 Pin Functions</h2>
<p><em>We’re selecting a GPIO Pin for a UART / I2C / SPI / PWM Port…</em></p>
<p><em>Which pin can we use?</em></p>
<p>The <strong>Pin Functions</strong> for each GPIO Pin are documented here…</p>
<ul>
<li><a href="https://github.com/bouffalolab/bl_docs/blob/main/BL602_RM/en/BL602_BL604_RM_1.2_en.pdf"><strong>“BL602 Reference Manual”</strong></a>, Table 3.1 “Pin Description” (Page 26)</li>
</ul>
<p>In NuttX, we set the <strong>Pin Definitions</strong> at…</p>
<ul>
<li><a href="https://github.com/lupyuen/nuttx/blob/pinedio/boards/risc-v/bl602/bl602evb/include/board.h#L61-L145">boards/risc-v/bl602/bl602evb/include/board.h</a></li>
</ul>
<p><em>Let’s say we’re selecting a pin for SPI MISO?</em></p>
<p>According to the pic above, <strong>SPI MISO</strong> must be either GPIO 0, 4, 8, 12, 16 or 20.</p>
<p><a href="https://lupyuen.github.io/articles/spi2#appendix-miso-and-mosi-are-swapped">(<strong>Beware:</strong> MISO and MOSI are swapped)</a></p>
<p>So this <strong>MISO Pin Definition</strong> is OK…</p>
<div class="example-wrap"><pre class="language-c"><code>// GPIO 0 for MISO is OK
#define BOARD_SPI_MISO (GPIO_PIN0 | GPIO_INPUT | GPIO_PULLUP | GPIO_FUNC_SPI)</code></pre></div>
<p><a href="https://github.com/lupyuen/nuttx/blob/pinedio/boards/risc-v/bl602/bl602evb/include/board.h#L104">(Source)</a></p>
<p>But this MISO Pin Definition is no-no…</p>
<div class="example-wrap"><pre class="language-c"><code>// GPIO 3 for MISO is NOT OK (Oops!)
#define BOARD_SPI_MISO (GPIO_PIN3 | GPIO_INPUT | GPIO_PULLUP | GPIO_FUNC_SPI)</code></pre></div>
<p><em>8 possible pins for MISO? Wow that’s a lot of choices!</em></p>
<p>BL602 / BL604 gives us incredible flexibility in selecting the pins…</p>
<p>But we might <strong>pick the wrong pin</strong> by mistake!</p>
<p>(Looks like an extreme form of STM32’s Alternate Pin Functions)</p>
<p><em>Is there a way to prevent such mistakes?</em></p>
<p>We have some ideas for <strong>validating the Pin Functions</strong> at compile-time or at startup…</p>
<ul>
<li><a href="https://lupyuen.github.io/articles/expander#appendix-validate-pin-function"><strong>“Validate Pin Function”</strong></a></li>
</ul>
<p>But for now, be <strong>very careful when selecting pins</strong>!</p>
<h1 id="test-gpio-expander"><a class="doc-anchor" href="#test-gpio-expander">§</a>7 Test GPIO Expander</h1>
<p><em>How shall we test our GPIO Expander on PineDio Stack?</em></p>
<p>We’ll test with 3 features that are shipped with PineDio Stack…</p>
<ul>
<li>
<p><strong>CST816S Touch Panel</strong></p>
<p>(Which triggers a GPIO Interrupt when touched)</p>
</li>
<li>
<p><strong>Push Button</strong></p>
<p>(Which also triggers a GPIO Interrupt when pushed)</p>
</li>
<li>
<p><strong>LoRaWAN with Semtech SX1262 Transceiver</strong></p>
<p>(Which uses GPIO Input, Output and Interrupt)</p>
</li>
</ul>
<p>Follow these steps to <strong>build, flash and run</strong> NuttX on PineDio Stack…</p>
<ul>
<li>
<p><a href="https://lupyuen.github.io/articles/pinedio2#build-nuttx"><strong>“Build NuttX”</strong></a></p>
</li>
<li>
<p><a href="https://lupyuen.github.io/articles/pinedio2#flash-pinedio-stack"><strong>“Flash PineDio Stack”</strong></a></p>
</li>
<li>
<p><a href="https://lupyuen.github.io/articles/pinedio2#boot-pinedio-stack"><strong>“Boot PineDio Stack”</strong></a></p>
</li>
</ul>
<p>In the NuttX Shell, enter this command to <strong>list the NuttX Devices</strong>…</p>
<div class="example-wrap"><pre class="language-bash"><code>ls /dev</code></pre></div>
<p>We should see <strong>more than 3 GPIOs</strong>…</p>
<div class="example-wrap"><pre class="language-text"><code>/dev:
gpio10
gpio12
gpio14
gpio15
gpio19
gpio20
gpio21
gpio3
gpio9</code></pre></div>
<p><a href="https://github.com/lupyuen/bl602_expander#test-touch-panel">(See the Complete Log)</a></p>
<p>Which means that our <strong>GPIO Expander is active</strong>.</p>
<p>We’re ready to test GPIO Expander!</p>
<p><img src="https://lupyuen.github.io/images/touch-title.jpg" alt="Touch Panel Calibration for Pine64 PineDio Stack BL604 RISC-V Board" /></p>
<p><a href="https://lupyuen.github.io/articles/touch">(Source)</a></p>
<h2 id="test-touch-panel"><a class="doc-anchor" href="#test-touch-panel">§</a>7.1 Test Touch Panel</h2>
<p>At startup, we should see…</p>
<div class="example-wrap"><pre class="language-text"><code>gpio_pin_register: Registering /dev/gpio9
bl602_expander_option: Falling edge: pin=9
bl602_expander_attach: Attach callback for gpio=9
cst816s_register: Driver registered</code></pre></div>
<p><a href="https://github.com/lupyuen/bl602_expander#test-touch-panel">(See the Complete Log)</a></p>
<p>Which says that our NuttX Driver for <a href="https://lupyuen.github.io/articles/touch"><strong>CST816S Touch Panel</strong></a> has called GPIO Expander to configure GPIO 9 for <strong>Falling Edge Trigger</strong>. (High to Low)</p>
<p>And the driver has called GPIO Expander to attach an <strong>Interrupt Callback</strong> for GPIO 9.</p>
<p>In the NuttX Shell, enter this command to start the <a href="https://github.com/lupyuen/lvgltest-nuttx"><strong>LVGL Test App</strong></a>…</p>
<div class="example-wrap"><pre class="language-bash"><code>lvgltest</code></pre></div>
<p>When prompted to calibrate the screen, <strong>tap the 4 corners</strong> of the screen. (Pic above)</p>
<p>We should see…</p>
<div class="example-wrap"><pre class="language-text"><code>bl602_expander_interrupt: Interrupt!
bl602_expander_interrupt: Call gpio=9
cst816s_get_touch_data: DOWN: id=0, touch=0, x=190, y=18
cst816s_get_touch_data: id: 0
cst816s_get_touch_data: flags: 19
cst816s_get_touch_data: x: 190
cst816s_get_touch_data: y: 18</code></pre></div>
<p><a href="https://github.com/lupyuen/bl602_expander#test-touch-panel">(See the Complete Log)</a></p>
<p>Which says that our <strong>Interrupt Callback</strong> for GPIO 9 has been triggered.</p>
<p>GPIO Expander handles the interrupt and <strong>calls the Touch Panel Driver</strong>. (Which fetches the Touch Data later)</p>
<p>Yep GPIO Expander works great with PineDio Stack’s Touch Panel!</p>
<p><a href="https://lupyuen.github.io/articles/pinedio2#nuttx-apps">(More about the LVGL Test App)</a></p>
<p><a href="https://lupyuen.github.io/articles/touch">(More about the CST816S Touch Panel)</a></p>
<h2 id="test-push-button"><a class="doc-anchor" href="#test-push-button">§</a>7.2 Test Push Button</h2>
<p>Earlier we spoke about running the <strong>GPIO Command</strong> to test the <strong>Push Button Interrupt</strong> (GPIO 12)…</p>
<ul>
<li><a href="https://lupyuen.github.io/articles/expander#gpio-command"><strong>“GPIO Command”</strong></a></li>
</ul>
<p>(Assuming that we don’t call <strong>IOEP_ATTACH</strong> in NuttX)</p>
<p>The GPIO Command starts by calling GPIO Expander to configure GPIO 12 for <strong>Rising Edge Trigger</strong>. (Low to High)</p>
<div class="example-wrap"><pre class="language-text"><code>nsh> gpio -t 8 -w 1 /dev/gpio12
bl602_expander_option: Rising edge: pin=12
bl602_expander_readpin: pin=12, value=1
Interrupt pin: Value=1
bl602_expander_attach: Attach callback for gpio=12</code></pre></div>
<p><a href="https://github.com/lupyuen/bl602_expander#test-push-button">(See the Complete Log)</a></p>
<p>Then it calls GPIO Expander to <strong>read GPIO 12</strong>. And attach an <strong>Interrupt Callback</strong> for GPIO 12.</p>
<p>When we press the Push Button, GPIO Expander <strong>handles the interrupt</strong>…</p>
<div class="example-wrap"><pre class="language-text"><code>bl602_expander_interrupt: Interrupt!
bl602_expander_interrupt: Call gpio=12</code></pre></div>
<p>And <strong>calls the Interrupt Callback</strong> for GPIO 12.</p>
<p>Finally the GPIO Command calls GPIO Expander to <strong>detach the Interrupt Callback</strong>…</p>
<div class="example-wrap"><pre class="language-text"><code>bl602_expander_detach: Detach callback for gpio=12
bl602_expander_readpin: pin=12, value=1
Verify: Value=1</code></pre></div>
<p>And read the GPIO Input one last time.</p>
<h2 id="test-lorawan"><a class="doc-anchor" href="#test-lorawan">§</a>7.3 Test LoRaWAN</h2>
<p><strong>LoRaWAN</strong> is the Ultimate Test for GPIO Expander. It depends on <strong>3 GPIOs</strong> connected to the Semtech SX1262 LoRa Transceiver…</p>
<ul>
<li>
<p><strong>SX1262 BUSY</strong> at <strong>/dev/gpio10</strong></p>
<p><strong>GPIO Input</strong> that tells us whether SX1262 is busy</p>
<p>(BUSY is High when SX1262 is busy)</p>
</li>
<li>
<p><strong>SX1262 Chip Select</strong> at <strong>/dev/gpio15</strong></p>
<p><strong>GPIO Output</strong> to select or deselect SX1262 on the SPI Bus</p>
<p>(Chip Select is Low when SX1262 is selected)</p>
</li>
<li>
<p><strong>SX1262 DIO1</strong> at <strong>/dev/gpio19</strong></p>
<p><strong>GPIO Interrupt</strong> for SX1262 to signal that a LoRa Packet has been transmitted or received</p>
<p>(DIO1 shifts from Low to High when that happens)</p>
</li>
</ul>
<p>In the NuttX Shell, enter this command to start the <a href="https://github.com/lupyuen/lorawan_test"><strong>LoRaWAN Test App</strong></a>…</p>
<div class="example-wrap"><pre class="language-bash"><code>lorawan_test</code></pre></div>
<p>Our LoRaWAN App calls GPIO Expander to attach an <strong>Interrupt Callback</strong> for GPIO 19…</p>
<div class="example-wrap"><pre class="language-text"><code>init_gpio: change DIO1 to Trigger GPIO Interrupt on Rising Edge
###### =========== MLME-Request ============ ######
###### MLME_JOIN ######
###### ===================================== ######</code></pre></div>
<p><a href="https://github.com/lupyuen/bl602_expander#test-lorawan">(See the Complete Log)</a></p>
<p>And sends a <strong>Join LoRaWAN Network</strong> request to our LoRaWAN Gateway (ChipStack).</p>
<p>(Which calls GPIO Expander on <strong>GPIO 10</strong> to check if the LoRa Transceiver is busy, and <strong>GPIO 15</strong> to activate the SPI Bus)</p>
<p>After sending the request, the LoRa Transceiver <strong>triggers an interrupt</strong> on GPIO 19…</p>
<div class="example-wrap"><pre class="language-text"><code>DIO1 add event
RadioOnDioIrq
RadioIrqProcess
IRQ_TX_DONE</code></pre></div>
<p>Which is handled by GPIO Expander and our LoRaWAN App.</p>
<p>Eventually our app receives the <strong>Join Network Response</strong> from our LoRaWAN Gateway…</p>
<div class="example-wrap"><pre class="language-text"><code>###### =========== MLME-Confirm ============ ######
STATUS : OK
###### =========== JOINED ============ ######
OTAA
DevAddr : 014C9548
DATA RATE : DR_2</code></pre></div>
<p>And sends a <strong>LoRaWAN Data Packet</strong> <em>(“Hi NuttX”)</em> to the gateway…</p>
<div class="example-wrap"><pre class="language-text"><code>###### =========== MCPS-Confirm ============ ######
STATUS : OK
###### ===== UPLINK FRAME 1 ===== ######
CLASS : A
TX PORT : 1
TX DATA : UNCONFIRMED
48 69 20 4E 75 74 74 58 00
DATA RATE : DR_3
U/L FREQ : 923400000
TX POWER : 0
CHANNEL MASK: 0003</code></pre></div>
<p>The data packet appears on our LoRaWAN Gateway.</p>
<p>Congratulations we have successfully tested GPIO Input, Output and Interrupt with GPIO Expander!</p>
<p><a href="https://lupyuen.github.io/articles/lorawan3">(More about the LoRaWAN Test App)</a></p>
<h1 id="whats-next"><a class="doc-anchor" href="#whats-next">§</a>8 What’s Next</h1>
<p>Now that we’ve fixed the GPIO problem with GPIO Expander, I hope it’s a lot easier to create <strong>NuttX Drivers and Apps</strong> on PineDio Stack.</p>
<p>Lemme know what you’re building with PineDio Stack!</p>
<p>Many Thanks to my <a href="https://lupyuen.github.io/articles/sponsor"><strong>GitHub Sponsors</strong></a> for supporting my work! This article wouldn’t have been possible without your support.</p>
<ul>
<li>
<p><a href="https://lupyuen.github.io/articles/sponsor"><strong>Sponsor me a coffee</strong></a></p>
</li>
<li>
<p><a href="https://www.reddit.com/r/RISCV/comments/uglc7r/nuttx_gpio_expander_for_pinedio_stack_bl604/"><strong>Discuss this article on Reddit</strong></a></p>
</li>
<li>
<p><a href="https://lupyuen.github.io/articles/book"><strong>Read “The RISC-V BL602 / BL604 Book”</strong></a></p>
</li>
<li>
<p><a href="https://lupyuen.github.io"><strong>Check out my articles</strong></a></p>
</li>
<li>
<p><a href="https://lupyuen.github.io/rss.xml"><strong>RSS Feed</strong></a></p>
</li>
</ul>
<p><em>Got a question, comment or suggestion? Create an Issue or submit a Pull Request here…</em></p>
<p><a href="https://github.com/lupyuen/lupyuen.github.io/blob/master/src/expander.md"><strong><code>lupyuen.github.io/src/expander.md</code></strong></a></p>
<h1 id="notes"><a class="doc-anchor" href="#notes">§</a>9 Notes</h1>
<ol>
<li>This article is the expanded version of <a href="https://twitter.com/MisterTechBlog/status/1518352162966802432"><strong>this Twitter Thread</strong></a></li>
</ol>
<p><img src="https://lupyuen.github.io/images/bl602-pins1a.png" alt="Pin Functions" /></p>
<p><a href="https://github.com/bouffalolab/bl_docs/blob/main/BL602_RM/en/BL602_BL604_RM_1.2_en.pdf">(From BL602 Reference Manual)</a></p>
<h1 id="appendix-validate-pin-function"><a class="doc-anchor" href="#appendix-validate-pin-function">§</a>10 Appendix: Validate Pin Function</h1>
<p>In NuttX, we set the <strong>Pin Definitions</strong> at…</p>
<ul>
<li><a href="https://github.com/lupyuen/nuttx/blob/pinedio/boards/risc-v/bl602/bl602evb/include/board.h#L61-L145">boards/risc-v/bl602/bl602evb/include/board.h</a></li>
</ul>
<p>BL602 / BL604 gives us incredible flexibility in <strong>selecting the GPIO Pins</strong> for the UART, I2C, SPI and PWM Ports…</p>
<ul>
<li><a href="https://lupyuen.github.io/articles/expander#pin-functions"><strong>“Pin Functions”</strong></a></li>
</ul>
<p>(8 possible pins for SPI MISO! Pic above)</p>
<p>But we might <strong>pick the wrong pin</strong> by mistake!</p>
<p>For example, this <strong>MISO Pin Definition</strong> is OK…</p>
<div class="example-wrap"><pre class="language-c"><code>// GPIO 0 for MISO is OK
#define BOARD_SPI_MISO (GPIO_PIN0 | GPIO_INPUT | GPIO_PULLUP | GPIO_FUNC_SPI)</code></pre></div>
<p><a href="https://github.com/lupyuen/nuttx/blob/pinedio/boards/risc-v/bl602/bl602evb/include/board.h#L104">(Source)</a></p>
<p>But this MISO Pin Definition is no-no…</p>
<div class="example-wrap"><pre class="language-c"><code>// GPIO 3 for MISO is NOT OK (Oops!)
#define BOARD_SPI_MISO (GPIO_PIN3 | GPIO_INPUT | GPIO_PULLUP | GPIO_FUNC_SPI)</code></pre></div>
<p><em>Is there a way to prevent such mistakes?</em></p>
<p>We have some ideas for <strong>validating the Pin Functions</strong> at compile-time or at startup…</p>
<h2 id="validate-at-compile-time"><a class="doc-anchor" href="#validate-at-compile-time">§</a>10.1 Validate at Compile-Time</h2>
<p><em>Can we validate the Pin Functions at compile-time?</em></p>
<p>Possibly. We can enumerate <strong>all valid combinations</strong> of Pin Functions and Pin Numbers…</p>
<div class="example-wrap"><pre class="language-c"><code>// SPI MISO can be either GPIO 0, 4, 8, 12, 16 or 20
#define SPI_MISO_PIN0 (GPIO_PIN0 | GPIO_INPUT | GPIO_PULLUP | GPIO_FUNC_SPI)
#define SPI_MISO_PIN4 (GPIO_PIN4 | GPIO_INPUT | GPIO_PULLUP | GPIO_FUNC_SPI)
#define SPI_MISO_PIN8 (GPIO_PIN8 | GPIO_INPUT | GPIO_PULLUP | GPIO_FUNC_SPI)
#define SPI_MISO_PIN12 (GPIO_PIN12 | GPIO_INPUT | GPIO_PULLUP | GPIO_FUNC_SPI)
#define SPI_MISO_PIN16 (GPIO_PIN16 | GPIO_INPUT | GPIO_PULLUP | GPIO_FUNC_SPI)
#define SPI_MISO_PIN20 (GPIO_PIN20 | GPIO_INPUT | GPIO_PULLUP | GPIO_FUNC_SPI)</code></pre></div>
<p>And we select the desired combination for each pin…</p>
<div class="example-wrap"><pre class="language-c"><code>// Select GPIO 0 as MISO
#define BOARD_SPI_MISO SPI_MISO_PIN0</code></pre></div>
<p><em>What happens if we pick the wrong pin?</em></p>
<p>This is disallowed…</p>
<div class="example-wrap"><pre class="language-c"><code>// Select GPIO 3 as MISO... Not possible!
#define BOARD_SPI_MISO SPI_MISO_PIN3</code></pre></div>
<p>Because <strong>SPI_MISO_PIN3</strong> doesn’t exist!</p>
<p>But to check whether the <strong>Pin Numbers are unique</strong>, we would still need GPIO Expander to do this at runtime.</p>
<p><em>Shouldn’t the pins be defined in Kconfig / menuconfig?</em></p>
<p>Perhaps. NuttX on ESP32 uses <strong>Kconfig / menuconfig</strong> to define the pins. <a href="https://github.com/apache/nuttx/blob/master/arch/xtensa/src/esp32/Kconfig#L938-L984">(See this)</a></p>
<p>Then we would need GPIO Expander to validate the Pin Functions at runtime.</p>
<p><a href="https://mastodon.social/@Ralim/108201458447291513"><strong>@Ralim</strong></a> has an interesting suggestion…</p>
<blockquote>
<p>If each pin can only be used once, could we flip the arrignment matrix and instead have it always have an entry for each pin, which is either a selected value or hi-z by default; then use kconfig rules to prevent collisions ?</p>
</blockquote>
<p>Which begs the question: Shouldn’t we do the same for NuttX on ESP32? What about other NuttX platforms? 🤔</p>
<h2 id="validate-at-startup"><a class="doc-anchor" href="#validate-at-startup">§</a>10.2 Validate at Startup</h2>
<p><em>What about validating the pins at startup?</em></p>
<p>During initialisation, GPIO Expander could validate that the UART / I2C / SPI / PWM Pin Functions are correctly assigned to the GPIO Pin Numbers.</p>
<p>So it would verify that SPI MISO (from the Pin Definitions) must be either GPIO 0, 4, 8, 12, 16 or 20.</p>
<p>Any other GPIO Pin for SPI MISO will be disallowed by our GPIO Expander. (And fail at startup)</p>
<p><em>But the Pin Definitions only tell us the Function Group (like SPI), not the specific Pin Function (like MISO)?</em></p>
<p>Yeah we might have to make the Pin Functions position-dependent. So SPI Pins will always be listed in this sequence: CS, MOSI, MISO, then CLK.</p>
<p>Here’s how <strong>bl602_other_pins</strong> might look in <a href="https://github.com/lupyuen/nuttx/blob/pinedio/boards/risc-v/bl602/bl602evb/src/bl602_bringup.c#L172-L222">bl602_bringup.c</a></p>
<div class="example-wrap"><pre class="language-c"><code>/* Other Pins for BL602 GPIO Expander (For Validation Only) */
static const gpio_pinset_t bl602_other_pins[] =
{
#ifdef BOARD_UART_0_RX_PIN
RX_TX
(
BOARD_UART_0_RX_PIN,
BOARD_UART_0_TX_PIN
),
#endif /* BOARD_UART_0_RX_PIN */
#ifdef BOARD_UART_1_RX_PIN
RX_TX
(
BOARD_UART_1_RX_PIN,
BOARD_UART_1_TX_PIN
),
#endif /* BOARD_UART_1_RX_PIN */
#ifdef BOARD_PWM_CH0_PIN
CH(
BOARD_PWM_CH0_PIN
),
#endif /* BOARD_PWM_CH0_PIN */
...
#ifdef BOARD_I2C_SCL
SCL_SDA
(
BOARD_I2C_SCL,
BOARD_I2C_SDA
),
#endif /* BOARD_I2C_SCL */
#ifdef BOARD_SPI_CS
CS_MOSI_MISO_CLK
(
BOARD_SPI_CS,
BOARD_SPI_MOSI,
BOARD_SPI_MISO,
BOARD_SPI_CLK
),
#endif /* BOARD_SPI_CS */
};</code></pre></div>
<p>The macros are simple passthroughs…</p>
<div class="example-wrap"><pre class="language-c"><code>#define CH(ch) ch
#define RX_TX(rx, tx) rx, tx
#define SCL_SDA(scl, sda) scl, sda
#define CS_MOSI_MISO_CLK(cs, mosi, miso, clk) cs, mosi, miso, clk</code></pre></div>
<p>At startup, GPIO Expander iterates through the pins and discovers that <strong>BOARD_SPI_MISO</strong> is the third pin (MISO) of the SPI Function Group. So it verifies that it’s either GPIO 0, 4, 8, 12, 16 or 20.</p>
<p>Which is your preferred way to validate the Pin Functions? Lemme know! 🙏</p>
<h1 id="appendix-initialise-gpio-expander"><a class="doc-anchor" href="#appendix-initialise-gpio-expander">§</a>11 Appendix: Initialise GPIO Expander</h1>
<p>At startup, our GPIO Expander does the following initialisation…</p>
<ul>
<li>
<p>Attach the GPIO Expander <strong>Interrupt Handler</strong> to the GPIO IRQ</p>
</li>
<li>
<p>Configure the <strong>GPIO Input, Output and Interrupt Pins</strong> by calling <a href="https://github.com/lupyuen/nuttx/blob/pinedio/arch/risc-v/src/bl602/bl602_gpio.c#L58-L140"><strong>bl602_configgpio</strong></a></p>
</li>
<li>
<p>Register the GPIOs as “<strong>/dev/gpioN</strong>” by calling <a href="https://github.com/lupyuen/nuttx/blob/pinedio/drivers/ioexpander/gpio_lower_half.c#L370-L443"><strong>gpio_lower_half</strong></a></p>
</li>
<li>
<p>Validate the GPIOs and <strong>prevent reuse of GPIOs</strong></p>
</li>
</ul>
<p>Here’s the code: <a href="https://github.com/lupyuen/bl602_expander/blob/main/bl602_expander.c#L956-L1121">bl602_expander.c</a></p>
<div class="example-wrap"><pre class="language-c"><code>// Initialise the BL602 GPIO Expander
FAR struct ioexpander_dev_s *bl602_expander_initialize(
// BL602 Pinsets for GPIO Input and number of pins
const gpio_pinset_t *gpio_inputs,
uint8_t gpio_input_count,
// BL602 Pinsets for GPIO Output and number of pins
const gpio_pinset_t *gpio_outputs,
uint8_t gpio_output_count,
// BL602 Pinsets for GPIO Interrupts and number of pins
const gpio_pinset_t *gpio_interrupts,
uint8_t gpio_interrupt_count,
// BL602 Pinsets for Other Pins (UART, I2C, SPI, UART) and number of pins
const gpio_pinset_t *other_pins,
uint8_t other_pin_count)
{
DEBUGASSERT(gpio_input_count + gpio_output_count + gpio_interrupt_count +
other_pin_count <= CONFIG_IOEXPANDER_NPINS);
/* Use the one-and-only I/O Expander driver instance */
FAR struct bl602_expander_dev_s *priv = &g_bl602_expander_dev;
/* Initialize the device state structure */
priv->dev.ops = &g_bl602_expander_ops;
nxsem_init(&priv->exclsem, 0, 1);</code></pre></div>