-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathREADME.html
685 lines (443 loc) · 56.6 KB
/
README.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
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<meta charset="utf-8"/>
<meta name="papoon_usb" content=""Not Insane" USB library for STM32F103xx MCUs"/>
</head>
<body>
<hr />
<p><a name="description"></a>
<strong>papoon_usb</strong> is a lightweight, efficient, cleanly-designed library for the USB Device peripheral in STMF103xx (and similar) MCUs. Its compiled binary size is approximately half that of the bloated, indirection-filled, spaghetti-code USB libraries on GitHub from ST and others; its performance boost is likely similar. Best of all, the code is “Not Insane” <a href="#not_insane">(1)</a> — or at least is as sane as reasonably possible given the craziness of the USB architecture and ST’s hardware implementation of it.</p>
<h2 id="branamecontentsacontents"><br> <a name="contents"></a>
Contents</h2>
<ul>
<li><a href="#license">License</a></li>
<li><a href="#how_to_use_papoon_usb">How to use papoon_usb</a>
<ul>
<li><a href="#tldnr_too_long_did_not_read">TL;DNR (“Too Long, Did Not Read”)</a>
<ul>
<li><a href="#simple_example">Simple example</a></li>
<li><a href="#code_description">Code description</a></li>
</ul></li>
<li><a href="#nle_want_to_read_more">NLE;WTRM (“Not Long Enough, Want To Read More”)</a>
<ul>
<li><a href="#interrupts_polling_callbacks">Interrupts, polling, callbacks</a></li>
<li><a href="#usb_class_implementations">USB class implementations</a></li>
</ul></li>
</ul></li>
<li><a href="#example_client_applications">Example client applications</a></li>
<li><a href="#implementation_of_papoon_usb">Implementation of papoon_usb</a>
<ul>
<li><a href="#cplusplus">C++</a></li>
<li><a href="#regbits_github">regbits</a></li>
</ul></li>
<li><a href="#motivation_for_papoon_usb">Motivation for papoon_usb</a></li>
<li><a href="#insanity">Insanity</a>
<ul>
<li><a href="#usb_standard">USB standard</a></li>
<li><a href="#st_hardware_and_documentation">ST hardware and documentation</a></li>
<li><a href="#st_software">ST software</a></li>
</ul></li>
<li><a href="#further_development">Further development</a></li>
</ul>
<h2 id="branamelicensealicense"><br> <a name="license"></a>
License</h2>
<p>papoon_usb: “Not Insane” USB library for STM32F103xx MCUs for STM MCUs</p>
<p>Copyright (C) 2019,2020 Mark R. Rubin</p>
<p>This file is part of papoon_usb.</p>
<p>The papoon_usb program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.</p>
<p>The papoon_usb program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.</p>
<p>You should have received a copy of the <a href="LICENSE.txt">GNU General Public License</a> along with the papoon_usb program. If not, see <a href="https://www.gnu.org/licenses/gpl.html">https://www.gnu.org/licenses/gpl.html</a></p>
<h2 id="branamehow_to_use_papoon_usbahowtousepapoon_usb"><br> <a name="how_to_use_papoon_usb"></a>
How to use papoon_usb</h2>
<p><a name="tldnr_too_long_did_not_read"></a></p>
<h3 id="tldnrtoolongdidnotread">TL;DNR (“Too Long, Did Not Read”)</h3>
<p>Simple example <a name="simple_example"></a> of client application, using USB CDC-ACM class (source available in <a href="examples/blue_pill/example.cxx">example.cxx</a>):</p>
<pre><code>#include <core_cm3.hxx>
#include <stm32f103xb.hxx>
#include <usb_dev_cdc_acm.hxx>
#include <usb_mcu_init.hxx>
using namespace stm32f10_12357_xx;
UsbDevCdcAcm usb_dev;
uint8_t recv_buf[UsbDevCdcAcm::CDC_OUT_DATA_SIZE],
send_buf[UsbDevCdcAcm::CDC_IN_DATA_SIZE ];
int main()
{
usb_dev.serial_number_init();
usb_mcu_init();
usb_dev.init();
while (usb_dev.device_state() != UsbDev::DeviceState::CONFIGURED)
usb_dev.poll();
while (true) {
usb_dev.poll();
uint16_t recv_len,
send_len;
if (recv_len = usb_dev.recv(UsbDevCdcAcm::CDC_ENDPOINT_OUT, recv_buf)) {
// process data received from host -- populate send_buf and set send_len
while (!usb_dev.send(UsbDevCdcAcm::CDC_ENDPOINT_IN, send_buf, send_len))
usb_dev.poll();
}
}
}
</code></pre>
<p>That’s it. Where’s <code>usbd_desc.c</code> (which doesn’t contain any USB descriptors), <code>usbd_cdc_interface.c</code>, <code>stm32f1xx_it.c</code>, ad nauseum — all of which other libraries think application client coders should be required to implement/modify? (See <a href="#st_software">ST software</a>, below.) Sorry: They’re not needed here.</p>
<br>
<br>
Code <a name="code_description"></a> snippets from the example, with brief additional information following each:
<br>
<br>
<pre><code>#include <core_cm3.hxx>
#include <stm32f103xb.hxx>
</code></pre>
<p><code>regbits</code> system for type-safe STM32F103 (and generic) direct “bare metal” register-level programming (see <a href="#regbits_github">regbits</a>, below)</p>
<br>
<pre><code>#include <usb_dev_cdc_acm.hxx>
</code></pre>
<p>USB CDC-ACM class (see <a href="#usb_class_implementations">USB class implementations</a> below)</p>
<br>
<pre><code>using namespace stm32f10_12357_xx;
</code></pre>
<p>see <a href="#cplusplus">C++</a>, below</p>
<br>
<pre><code>UsbDevCdcAcm usb_dev;
</code></pre>
<p>instantiate USB class (C++ derived class) object</p>
<br>
<pre><code>uint8_t recv_buf[UsbDevCdcAcm::CDC_OUT_DATA_SIZE],
send_buf[UsbDevCdcAcm::CDC_IN_DATA_SIZE ];
</code></pre>
<p>Data buffers. Note “IN” and “OUT” are host-centric, as per USB standard nomenclature.</p>
<br>
<pre><code>int main()
{
</code></pre>
<p>The application.</p>
<br>
<pre><code> usb_dev.serial_number_init();
</code></pre>
<p>Optional, to use STM32F103xx “Unique Device ID”. Must be done before MCU clock initialization — see <a href="#cplusplus">C++</a> and <a href="#unique_id">STM hardware and documentation</a>, below.</p>
<br>
<pre><code> usb_mcu_init();
</code></pre>
<p>Initialize CPU clocks, USB peripheral, etc. See
<a href="usb/usb_mcu_init.cxx">usb_mcu_init.cxx</a> for example implementation.</p>
<br>
<pre><code> usb_dev.init();
</code></pre>
<p>Initialize library (and USB class) (see <a href="#cplusplus">C++</a>, below).</p>
<br>
<pre><code> while (usb_dev.device_state() != UsbDev::DeviceState::CONFIGURED)
usb_dev.poll();
</code></pre>
<p>Optional — <code>UsbDev::recv()</code> and <code>UsbDev::send()</code> will return 0 and false respectively until USB enumeration of device by host has been completed, but this explicitly waits until the status is known. Also see <a href="#interrupts_polling_callbacks">Interrupts, polling, callbacks</a>, below, regarding related timing and performance issues.</p>
<br>
<pre><code> while (true) {
usb_dev.poll();
</code></pre>
<p>See <a href="#interrupts_polling_callbacks">Interrupts, polling, callbacks</a>, below.</p>
<br>
<pre><code> uint16_t recv_len,
send_len;
if (recv_len = usb_dev.recv(UsbDevCdcAcm::CDC_ENDPOINT_OUT, recv_buf)) {
</code></pre>
<p>Non-blocking read. Returns number of bytes of bytes received or 0 if none</p>
<br>
<pre><code> // process data received from host -- populate send_buf and set send_len
while (!usb_dev.send(UsbDevCdcAcm::CDC_ENDPOINT_IN, send_buf, send_len))
usb_dev.poll();
}
</code></pre>
<p>Wait for send ready (i.e. previous send completed), and queue USB “IN” transaction. (See <a href="#interrupts_polling_callbacks">Interrupts, polling, callbacks</a>, below)</p>
<br>
<pre><code> }
}
</code></pre>
<p>End main loop, end <code>main()</code> function.</p>
<p><br> <a name="nle_want_to_read_more"></a></p>
<h3 id="nlewtrmnotlongenoughwanttoreadmore">NLE;WTRM (“Not Long Enough, Want To Read More”)</h3>
<p><a name="interrupts_polling_callbacks"></a></p>
<h4 id="interruptspollingcallbacks">Interrupts, polling, callbacks</h4>
<p>papoon_usb can be used in any of four modes chosen from a 2x2 configuration matrix:</p>
<pre><code> endpoint queries endpoint callbacks
------------------ --------------------
polled | poll+query poll+callback
interrupt driven | interrupt+quey interrupt+callback
</code></pre>
<p>These are controlled by defining (or not) one or both of two macros: <code>USB_DEV_INTERRUPT_DRIVEN</code> and <code>USB_DEV_ENDPOINT_CALLBACKS</code>.</p>
<p>In <code>USB_DEV_INTERRUPT_DRIVEN</code> mode, client code must implement an ARM NVIC interrupt handler:</p>
<pre><code>extern "C" void USB_LP_CAN1_RX0_IRQHandler()
{
usb_dev.interrupt_handler();
}
</code></pre>
<p>and enable it via code such as:</p>
<pre><code> arm::nvic->iser.set(arm::NvicIrqn::USB_LP_CAN1_RX0);
</code></pre>
<p>(using C++ with <code>regbits</code>’s <a href="arm/core_cm3.hxx">core_cm3.hxx</a>) or:</p>
<pre><code> NVIC_EnableIRQ(USB_LP_CAN1_RX0_IRQn);
</code></pre>
<p>(using C with <a href="arm/core_cm3.h">core_cm3.h</a>).</p>
<p>In non-<code>USB_DEV_INTERRUPT_DRIVEN</code> (i.e. polled) mode, client application code must call <code>UsbDev::poll()</code> (via an instantiated object, e.g. <code>usb_dev.poll()</code>). The frequency with which this must be done depends on the USB device class (implemented in the C++ class derived from <code>UsbDev</code>) being used. In general, once USB device enumeration has been completed there should be no particular timing requirements as papoon_usb configures the STM32F103xx USB peripheral to cause the host to wait (repeatedly attempting to transfer data until confirmed) until the “OUT” (standard USB host-centric nomenclature) data has been retrieved by the client application calling <code>UsbDev::recv()</code>. Likewise, client code can send “IN” data to the host at any time, checking the return value of <code>UsbDev::send()</code> to see if the previous send (if any) has completed and the new data has been successfully queued for transfer.</p>
<p>To the contrary, before and during enumeration <code>poll()</code> must be called at a very high rate due to the speed of the USB 2.0 “full speed” protocol. It is best to do this in as tight a loop as possible, optimally:</p>
<pre><code> while (usb_dev.device_state() != UsbDev::DeviceState::CONFIGURED)
usb_dev.poll();
</code></pre>
<p>although it might be acceptable to do some minimal additional processing within the loop. Note that given the latency involved in ARM interrupts this is probably faster than relying on them, although interrupts are sufficiently fast for (most? all?) USB host controllers and their host driver software implementations.</p>
<p>If the <code>USB_DEV_ENDPOINT_CALLBACKS</code> macro is defined, papoon_usb will call any functions registered via <code>UsbDev::register_recv_callback()</code> and <code>UsbDev::register_send_callback()</code> when data has been received on the registered endpoint or it is available to send data, respectively. C++ coders note that these must be global or namespace-scope functions (see <a href="#cplusplus">C++</a>, below), not object instance methods (no <code>std::bind</code> available). Note that enabling both polling and callbacks is not particularly useful: <code>UsbDev::poll()</code> returns a <code>uint16_t</code> with bits set indicating “ready” endpoints (which can be extracted using the <code>UsbDev::poll_recv_ready()</code> and <code>UsbDev::poll_recv_ready()</code> convenience functions), and explicitly executing the appropriate code either inline or by calling a “callback” function in the application’s main loop is as efficient as allowing <code>UsbDev</code> to call the callback implicitly. The choice is a matter of the application developer’s taste.</p>
<p>Regardless the polled-vs-interrupt-driven and callbacks-vs-direct configuration chosen, all the above methods use papoon_usb’s <code>UsbDev::send()</code> and <code>UsbDev::recv()</code> methods to marshall data between application code <code>uint8_t*</code> buffers and the internal STM32F103xx USB peripheral’s “PMA” memory. Data copying is done via CPU or DMA, controlled by defining (or not) the <code>USB_DEV_DMA_PMA</code> compilation macro. Testing has shown little or no performance benefit from using DMA in this use-case (as opposed to memory-to-memory copies in normal memory) but the code and option to use it has been retained regardless (see <a href="#further_development">Further development</a>, below).</p>
<p>Overall performance can, however, be increased by applications directly accessing PMA memory, eliminating the buffer copying overhead. This could consist of the application directly generating data to send to the host in PMA memory, directly reading/parsing received data, or using the STM32F103xx DMA engine to transfer data between another peripheral and the USB PMA memory. A classic example of the latter would be implementing a bidirectional USB-to-serial hardware bridge using the papoon_usb and the STM32F103xx USART peripheral.</p>
<p>A number of <code>UsbDev</code> class methods are provided for these use-cases, including non-buffer-copying <code>send()</code> and <code>recv()</code> methods, <code>recv_lnth()</code> and <code>recv_done()</code> (for status checking), <code>read()</code> and <code>writ()</code> (single <code>uint16_t</code> data copies), and <code>send_buf()</code> and <code>recv_buf()</code> (for obtaining raw memory addresses). Note that extreme care must be used when using these — memory overwrites will almost certainly cause fatal application crashes, and careful attention must be paid to <code>uint8_t</code>, <code>uint16_t</code>, and <code>uint32_t</code> memory alignment and endian-ness. See the documentation in <a href="usb/usb_dev.hxx">usb_dev.hxx</a> for further descriptions and information.</p>
<p>Finally, if only the above direct-access mehods are being used and the normal buffer-copying <code>send()</code> and <code>recv()</code> methods are not needed, the <code>USB_DEV_NO_BUFFER_RECV_SEND</code> macro can be defined to prevent their compilation and save space in the compiled application binary.</p>
<p><a name="usb_class_implementations"></a></p>
<h4 id="usbclassimplementations">USB class implementations</h4>
<p>It is very easy to implement USB device classes (C++ classes derived from UsbDev) in papoon_usb … or at least as easy as is possible given the excessive complexity of device classes in the USB standards.</p>
<p>This repository contains sample implementations for the following USB device classes:</p>
<ul>
<li>CDC/ACM (Communication Device Class, Abstract Control Model)</li>
<li>HID mouse (Human Interface Device Class, mouse)</li>
<li>MIDI</li>
<li>“simple” (a minimal custom USB device class)</li>
</ul>
<p>See code implementing these in:</p>
<ul>
<li><a href="usb/usb_dev_cdc_acm.cxx">usb_dev_cdc_acm.cxx</a></li>
<li><a href="usb/usb_dev_hid_mouse.cxx">usb_dev_hid_mouse.cxx</a></li>
<li><a href="usb/usb_dev_midi.cxx">usb_dev_midi.cxx</a></li>
<li><a href="usb/usb_dev_simple.cxx">usb_dev_simple.cxx</a></li>
</ul>
<p>and corresponding <code>.hxx</code> files. Note that <a href="usb/usb_dev_hid_mouse.cxx">usb_dev_hid_mouse.cxx</a> is derived from an intermediate <code>UsbDevHid</code> class in <a href="usb/usb_dev_hid.hxx">usb_dev_hid.hxx</a> and <a href="usb/usb_dev_hid.cxx">usb_dev_hid.cxx</a> for future use in implementing e.g. an HID keyboard class.</p>
<p>A USB device class is implemented by deriving a new C++ class from the <code>UsbDev</code> base class, defining several <code>UsbDev</code> class methods and member variables (see <a href="#static_polymorphism">static polymorphism</a>, below), plus any USB class-specific member variables. These include:</p>
<ul>
<li>The standard-required USB device descriptor <code>const uint8_t UsbDev::_DEVICE_DESC[]</code></li>
<li>The standard-required USB configuration descriptor <code>const uint8_t UsbDev::_CONFIG_DESC[]</code>, including all necessary sub-descriptors. Note that the <code>bLength</code> field needs to be set at runtime by the class' <code>init()</code> method.</li>
<li>A <code>const uint8_t *UsbDev::_STRING_DESCS[]</code> array. This should include at minimum the <code>UsbDev::_LANGUAGE_ID_STRING_DESC[]</code>, accessed via <code>UsbDev:: language_id_string_desc()</code>, plus any other string descriptors referenced by index in the <code>_DEVICE_DESC</code> and/or other descriptors.</li>
<li>A <code>void DerivedClass::init()</code> method (which overrides <code>UsbDev::init()</code>) and must, at minimum, set the <code>UsbDev::CONFIG_DESC_SIZE_NDX</code> element of the <code>_CONFIG_DESC</code> to its correct value (i.e. <code>sizeof(_CONFIG_DESC)</code>) and call the base class' <code>UsbDev::init()</code>. Note that although common to all <code>UsbDev</code>-derived classes, this <code>sizeof()</code> initialization cannot be done in <code>UsbDev::init()</code> due to source file scope issues (see <a href="#static_polymorphism">static polymorphism</a>, below).</li>
<li>Implement the base class <code>bool UsbDev::device_class_setup()</code> method. This method only needs to handle USB class-specific “setup” requests, accessed via the base class' <code>UsbDev::SetupPacket* _setup_packet</code> member object. The method should return <code>true</code> is it has actually executed any <code>setup</code> request(s), otherwise <code>false</code>, but needs to be implemented (returning a default value of <code>false</code>) regardless.</li>
<li><code>void UsbDev::set_configuration()</code> and <code>void UsbDev::set_interface()</code> which perform USB class-specific actions if required.</li>
</ul>
<p>Again, see the provided example USB class implementation files for use as templates for creating a new USB class.</p>
<h2 id="branameexample_client_applicationsaexampleclientapplications"><br> <a name="example_client_applications"></a>
Example client applications</h2>
<p>Several example client applications are provided as templates for using papoon_usb in the <a href="examples/blue_pill">examples/blue_pill</a> directory. As the name suggests, the are written for the ubiquitous “Blue Pill” STM32F103xx development board which has an application-controlled LED connected to the MCU’s GPIO PA13 port (active low). Code driving the LED, along with the GPIO initialization in <a href="usb/usb_mcu_init.cxx">usb_mcu_init.cxx</a>, can be easily removed or modified for other hardware environments.</p>
<p>The examples use USB classes (C++ classes derived from <code>UsbDev</code>) found in the <a href="usb">usb</a> directory. Some examples work in conjunction with host software found in the <a href="examples/linux">examples/linux</a> directory. This software variously relies on <code>libusb</code> (<a href="https://libusb.info/">project site</a>, <a href="https://github.com/libusb/libusb">github</a>), or the Linux USB CDC-ACM driver <a name="linux_usb_cdc_acm_driver"></a> which creates a <code>/dev/ttyACM0</code> *NIX device special file for reading/writing data. The latter may require using other OS-specific mechanisms. <code>libusb</code> is fairly OS-agnostic.</p>
<p>Makefiles (<a href="examples/linux/Makefile">linux</a>, <a href="examples/linux/Makefile">blue_pill</a>) are provided. Note that the latter is a stub only (written to test this repository’s completeness) and needs to be replaced with something matching the user’s own ARM/STM embedded development toolchain environment.</p>
<p>The examples include:</p>
<h5 id="cdc-acmecho">CDC-ACM echo</h5>
<p>STM32F103 USB device echoes back text sent to it via the USB CDC-ACM class protocol (see <a href="#usb_standard">USB insanity</a>, below). Text is prepended with a four-character hexadecimal sequence number, and a two-character hex index showing when a single DOWN (USB host-centric nomenclature) transfer has been split into two UP echoes due to the additional 8 bytes of sequence/index/spaces. See <a href="#linux_usb_cdc_acm_driver">above</a> regarding host USB CDC-ACM drivers.</p>
<p>Implemented in the <a href="examples/blue_pill/usb_cdc_acm_echo.cxx">usb_cdc_acm_echo.cxx</a> and <a href="examples/blue_pill/usb_echo.cxx">usb_echo.cxx</a>
client application example source files.</p>
<h5 id="simpleecho">Simple echo</h5>
<p>STM32F103 USB device echoes back text sent to it via the a simple custom USB class (see <a href="#usb_standard">USB insanity</a>, below), implemented in <a href="usb/usb_dev_simple.cxx">usb_dev_simple.cxx</a> Text is prepended with a four-character hexadecimal sequence number, and a two-character hex index showing when a single DOWN (USB host-centric nomenclature) transfer is split into two UP echoes due to the additional 8 bytes of sequence/index/spaces.</p>
<p>Implemented in <a href="examples/blue_pill/usb_simple_echo.cxx">usb_simple_echo.cxx</a>, <a href="examples/blue_pill/usb_echo.cxx">usb_echo.cxx</a> client application source files. See <a href="examples/linux/stdin.cxx">linux/stdin.cxx</a> for the simple USB class' <code>libusb</code>-based host side user-space “driver”.</p>
<h5 id="simplerandomtest">Simple random test</h5>
<p>This is a “stress test” demonstration in which both the STM32F103 USB device and the host simultaneously send random data of random length (up to the max 64 byte USB full-speed endpoint data packet limit) to each other using the “simple” custom USB class protocol (<a href="usb/usb_dev_simple.cxx">usb_dev_simple.cxx</a>). Implemented in the <a href="examples/blue_pill/usb_simple_randomtest.cxx">usb_simple_randomtest.cxx</a>, <a href="examples/blue_pill/usb_randomtest.cxx">usb_randomtest.cxx</a> client application source files and the <a href="examples/linux/simple_randomtest.cxx">linux/simple_randomtest.cxx</a> <code>libusb</code>-based host-side source file.</p>
<h5 id="cdc-acmrandomtest">CDC-ACM random test</h5>
<p>Similar to the “simple” randomtest above, but using the standard USB CDC-ACM class (<a href="usb/usb_dev_cdc_acm.cxx">usb_dev_cdc_acm.cxx</a>) protocol. Note that CDC-ACM uses USB <code>BULK</code> endpoints compared to “simple”’s <code>INTERRUPT</code>, so performance — both throughput and latency — differ greatly between the two. Implemented in the <a href="examples/blue_pill/usb_cdc_acm_randomtest.cxx">usb_cdc_acm_randomtest.cxx</a>, <a href="examples/blue_pill/usb_randomtest.cxx">usb_randomtest.cxx</a> client application source files and the <a href="examples/linux/tty_randomtest.cxx">linux/tty_randomtest.cxx</a> <code>libusb</code>-based host-side source file.</p>
<h5 id="hidmouse">HID mouse</h5>
<p>This USB HID demo jitters the host computer’s mouse pointer in very small circles (intentionally small, as otherwise regaining control of the host desktop environment would be difficult). Implemented in the <a href="examples/blue_pill/mouse.cxx">mouse.cxx</a> client application source file and the <a href="usb/usb_dev_hid_mouse.cxx">usb_dev_hid_mouse.cxx</a> and <a href="usb/usb_dev_hid.cxx">usb_dev_hid.cxx</a> <code>UsbDev</code> C++ derived class sources. See <a href="#usb_standard">USB insanity</a>, below, regarding USB HID class standard.</p>
<h5 id="midi">MIDI</h5>
<p>This USB MIDI demo sends a looped C major ascending scale to the host computer. (“By pressing down a special key it plays a little melody.”) (Not really; code starts immediately and runs indefinitely.) The demo does not implement “MIDI IN” to the STM32F103 device. Source files: <a href="examples/blue_pill/midi.cxx">midi.cxx</a> and <a href="usb/usb_dev_midi.cxx">usb_dev_midi.cxx</a>. See <a href="#usb_standard">USB insanity</a>, below, regarding USB MIDI class standard.</p>
<h2 id="branameimplementation_of_papoon_usbaimplementationofpapoon_usb"><br> <a name="implementation_of_papoon_usb"></a>
Implementation of papoon_usb</h2>
<p><a name="cplusplus"></a></p>
<h4 id="c">C++</h4>
<p>Everyone will hate the papoon_usb code.</p>
<p>C coders will hate it because … C++. Although note that due to papoon_usb’s simplistic use of C++, C wrappers can easily be created for access from pure C code. See
<a href="usb/usb_dev.h">usb_dev.h</a>,
<a href="usb/usb_dev_c.inl">usb_dev_c.inl</a>,
<a href="usb/usb_dev_cdc_acm_c.cxx">usb_dev_cdc_acm_c.cxx</a>,
<a href="examples/blue_pill/usb_echo_c.c">usb_echo_c.c</a>,
<a href="examples/blue_pill/usb_cdc_acm_echo_c.c">usb_cdc_acm_echo_c.c</a>,
and the <code>usb_cdc_acm_echo_c.elf</code> target in
<a href="examples/blue_pill/Makefile">examples/blue_pill/Makefile</a>.</p>
<p>C++ coders will hate papoon_usb because …</p>
<ul>
<li>“That’s not C++!! That’s ‘object based’, not 'object-oriented” programming!"</li>
<li>“init() methods?? <strong>init()</strong> methods!!! That’s not RAII!! Why isn’t that code in the constructor??”</li>
<li>“Global scope objects!!?? Why don’t you use the Singleton Pattern??”</li>
<li>“And where are the rest of the design patterns?”</li>
<li>“Static polymorphism?? Where are the virtual methods?? Why aren’t you using dynamic_cast?”</li>
</ul>
<p>… and many, many more.</p>
<p>Well, there are reasons for why the code was written the way it was, not that those reasons are likely to sway believers in rigid C++ design philosophies.</p>
<p>Not “object-oriented”, only "object-based? Analysis accepted. This is purposeful use of C++ as what some call “a better C”.</p>
<p>The init() methods? Again intentional. MCU pre-<code>main()</code> startup code is complex enough without requiring that it call runtime constructors for static objects, thus this code’s use of only <code>constexpr</code> constructors. More important is the fact that much of the object initialization <strong>can’t</strong> take place before the MCU itself is configured (clock sources and speeds, peripheral enabling and resetting, etc), and likewise this application-specific MCU initialization does not belong in the generic pre-<code>main()</code> <code>init()</code> (aka <code>start()</code>) function. Also see <a href="#unique_id">below</a> for the reason behind the <code>UsbDev::serial_number_init()</code> method (this is where the practicalities of the real world diverge from what’s taught in CompSci 201).</p>
<p>Global objects vs the Singleton Pattern? The underlying hardware is inherently and unavoidably made up of singletons in the form of hardware subsystems. There is no need to dynamically construct singleton pointers, and in fact the entire software architecture contains no dynamic memory allocation at all (unthinkable!!). I contend this is a rational design in the face of the limited capabilities (20 KB RAM, 64 to 128 KB non-volatile flash memory, 72 MHz max clock speed) of the STM32F103xx chips.</p>
<p>Virtual methods vs static polymorphism? <a name="static_polymorphism"></a> Again, due to memory and processor constraints, avoiding vtable indirection is very desirable. The technique of defining base class methods in derived class implementation (<code>.cxx</code>) files (is this a design pattern?) works perfectly well given that only a single derived class will ever be used in any given application executable binary.</p>
<p>But please feel free to re-architect the code to fit other design philosophies. (Respect the GPL open-source <a href="#license">License</a>, above.) Report back on binary executable code size and runtime performance, and if comparable (and I can understand the code) I’ll consider using your version.</p>
<p><br> <a name="regbits_github"></a></p>
<h4 id="regbits">regbits</h4>
<p>papoon_usb is written using the <a href="https://github.com/thanks4opensource/regbits">regbits</a> C++ templates for type-safe bit manipulation, and in particular the <a href="https://github.com/thanks4opensource/regbits_stm">regbits_stm</a> implementation for STM32F103xb, all necessary source files for which are explicitly/directly included in this repository for convenience (see <a href="regbits/regbits.hxx">regbits.hxx</a>, <a href="regbits/stm32f103xb.hxx">stm32f103xb.hxx</a>).</p>
<p>Briefly, regbits allows easy (and safe) direct “bare metal” programming of MCU registers:</p>
<pre><code>rcc->apb1enr |= Rcc::Apb1enr::TIM2EN; // set single bit
rcc->cfgr /= Rcc::Cfgr::HPRE_DIV_64; // set bit field, analogous to
// RCC->CFGR = (RCC->CFGR & ~HPRE_MASK)
| HPRE_DIV_64);
</code></pre>
<p>but this will not compile:</p>
<pre><code>rcc->apb1enr |= Rcc::Apb2enr::ADC1EN; // note mismatch, APB1 vs APB2
</code></pre>
<p>See the regbits <a href="https://github.com/thanks4opensource/regbits/blob/master/README.md">README.md</a> and regbits_stm <a href="https://github.com/thanks4opensource/regbits_stm/blob/master/README.md">README.md</a> documentation for (much) more detail.</p>
<h2 id="branamemotivation_for_papoon_usbamotivationforpapoon_usb"><br> <a name="motivation_for_papoon_usb"></a>
Motivation for papoon_usb</h2>
<h4 id="background">Background</h4>
<p>“papoon_usb” was written due to ultimate frustration with the open source STM32F103xx USB libraries found online.</p>
<p>The vast majority of those libraries were either copies of one out of two generations of ST provided ones, or were heavily based on them. There were two that were not, and were encouragingly fairly small and cleanly written. Unfortunately they did not work (I don’t know how the authors could claim they did), and my attempts at fixing them failed due to lack of understanding of the USB protocols.</p>
<p>Alternately, the USB components of <a href="https://github.com/libopencm3/libopencm3">libopencm3</a>, the source code for which is somewhat clean and understandable, also did not successfully run when compiled. At that point I decided to start a completely fresh codebase.</p>
<p>Given prior experience with <a href="#ST_HAL_and_LL_libraries">STM HAL (and LL) libraries</a> — by coincidence implementing USB on STM32L476 MCUs — I wanted to avoid them at all cost. (Note that the HAL/STM32L476 success was pure hacking: I extracted a minimal subset of HAL necessary to compile and link, including some small additions such as a <code>malloc()</code> stub and a replacement for HAL’s <code>systick</code> handler. The code was still huge and incomprehensible.) See <a href="#ST_HAL_and_LL_libraries">STM HAL (and LL) libraries</a> for more, highly-opinionated, criticisms.</p>
<p>I decided instead to use as a starting point a GitHub repository I later discovered was a copy of the older ST-provided “STM32_USB-FS-Device_Lib” source code distribution. Despite the fact the code was functional (after some small fixes), in retrospect this was a very bad decision. The code is, hands down, the second worst I have ever seen in my long career as a software developer. (The worst was a many thousands line long Commodore Basic program 100% comprised of spaghetti code <code>GOTO</code> statements. That the ST code is even in the same league is a significant although dubious accomplishment.) Again, see <a href="#st_software">ST software</a> for additional complaints.</p>
<p>Finally, in theory none of this analysis/hacking/porting should ever have been necessary. One would think a company the size of ST could document their hardware well enough that a competent programmer could write code to utilize it (and in fact would be interested in doing so, if for no other reason than to increase sales). Not so — if interested see <a href="#st_hardware_and_documentation">STM hardware and documentation</a> below for details. Also note that the STM32F103 USB subsystem documentation is better than many other ST attempts, and that evaluation of whether or not I’m a “competent programmer” is left to the reader.</p>
<p><a name="not_insane"></a></p>
<h4 id="funnyname">Funny name</h4>
<p>I have long thought that the “cuter” the name of a software component, the lower the quality. That said, given the huge amount of time I invested in writing this library (primarily spent decrypting the ST documentation and example code, reverse-engineering the hardware, and dealing with the byzantine USB standards themselves) — efforts that never should have been necessary had a reasonable implementation been available — I chose the name and am including the “insane” accusations.</p>
<p>Please feel free to make your own decisions regarding both the code quality the accusations' merits.</p>
<p>(1) (footnote from repository description <a href="#description">above</a>) “Papoon” and “Not Insane” are obscure, inside joke, references. My apologies.</p>
<h2 id="branameinsanityainsanity"><br> <a name="insanity"></a>
Insanity</h2>
<p>The following is highly opinionated editorial opinion, perusal of which is not in the least necessary for using or understanding papoon_usb. Those easily offended by extremely critical remarks directed toward well established and respected hardware and software organizations should likely skip reading it. Regardless, I stand by (and attempt to justify) the statements expressed below.</p>
<p><a name="usb_standard"></a></p>
<h3 id="usbstandard">USB standard</h3>
<p>The USB standard(s) is/are poorly conceived and (insanely) overly complex.</p>
<p>Period.</p>
<p>As evidence supporting this opinion, search online for the many websites, books, libraries, and blogs attempting to implement and explain USB. (The oft-cited <a href="http://www.usbmadesimple.co.uk/">USB Made Simple</a> and <a href="https://www.beyondlogic.org/usbnutshell/usb1.shtml">USB in a NutShell</a> websites are excellent attempts, but even combined are not fully sufficient.)
Also browse the source code for many USB drivers and libraries (possibly including this one) for obvious examples of their authors not fully understanding the systems they’re trying to interface with (i.e. code which has all the hallmarks of “this somehow works, don’t touch it”).</p>
<p>The official standards documents at <a href="https://www.usb.org/">USB-IF</a> also largely fail. Overly formal “standarese”, with few and far between actual illustrative examples, they most frequently describe data fields by name and function but either omit or bury the actual numeric values required for those fields. (The <code>GET_DESCRIPTOR</code> <code>bRreqest</code> <code>wValue</code> contains the “Descriptor Type and
Descriptor Index”. Fine. What are those values and their respective meanings? Often such information is contained in completely separate documents, if at all.) (Note that <a href="file:///usr/local/doc/software/usb/www.usbmadesimple.co.uk/ums_4.htm">USB Made Simple - Part 4</a> has a succinct table listing the values/meanings.) Note also that the USB-IF document
<a href="https://www.usb.org/sites/default/files/midi10.pdf">Universal Serial Bus Device Class Definition for MIDI Devices</a> is better than most of their others in this regard.</p>
<p>Beyond this, and more importantly, the problem isn’t as much the complexity of the standard(s) <em>per se</em> but the lack of hierarchical layering in their design. Okay: Maybe the planet-sized mass of metadata configuration implemented in the <a href="https://www.usb.org/document-library/hid-usage-tables-112">HID Usage Tables</a> is of value to someone (e.g. a standard API for “Voice Mail Controls” among much similar minutia). Maybe the multiplexed “virtual MIDI cables” (over and above the multiple channels supported by basic MIDI itself) are a useful capability. I think these (and hundreds of others) could have been implemented more simply, but that’s immaterial. (See the size and obfuscation of the absolutely required USB descriptors in <a href="usb/usb_dev_hid_mouse.cxx">usb_dev_hid_mouse.cxx</a> and <a href="usb/usb_dev_midi.cxx">usb_dev_midi.cxx</a>.)</p>
<p>The problem is that all of this complexity is necessary to implement <strong>all</strong> USB classes and clients … even in the (most frequent) situations where none of it is needed.</p>
<p>Compare, for example, this repository’s <a href="usb/usb_dev_simple.cxx">usb_dev_simple.cxx</a> custom USB class with the implementation of the standard USB CDC-ACM class in <a href="usb/usb_dev_cdc_acm.cxx">usb_dev_cdc_acm.cxx</a>. The former should be the “Hello, world” of USB classes — two endpoints, one IN and one OUT, no configuration.</p>
<p>But such a USB class not in the standards, so everyone (as is widely documented) uses CDC-ACM because host-side drivers for it are included in most/all operating systems. Yes, when USB was originally developed, a USB-to-serial bridge device was probably one of the first hardware/software implementations created. But instead of defining all the necessary metadata and out-of-band communications on top of basic communications, it was baked into the fundamental design of the class. So now, many years later, communications between an embedded MCU and a host computer <strong>must</strong> implement baud rate and total/stop/parity bit control even when none of it is relevant to the data being communicated.</p>
<p>Inconsistencies and strange design decisions abound in USB, above and beyond the lack of layering. The basic design sends device description to the host via a small “Device Descriptor” and a large “Configuration Descriptor” containing multiple sub-descriptors (plus optional standalone “String Descriptors”). This is already two mechanisms devoted to the same task – the host retrieves descriptors by type and index, but also by parsing the Configuration Descriptor for its components. (Possibly this was done for efficiency.) But the mechanism is then extended by various USB classes such as HID which <strong>do</strong> define new “top-level” descriptor types — which could have been included in the aggregate Configuration Descriptor</p>
<p>Maybe some of this obfuscation was intentional. USB was designed by Intel at the height of the “Wintel” monopoly over the computer industry. (Hmm. Let’s see if this repository stays up given the current ownership of GitHub. ;) ). Implementing the desired “plug-and-play” capability would naturally fall to proprietary drivers integrated into a monolithic operating system. Maybe a design which was difficult for others to implement could have been considered “a good thing”. ;)</p>
<p>Finally, possibly the most confusing aspect of the very fragile USB enumeration process is the “Set Address” command. The <a href="https://www.usb.org/document-library/usb-20-specification">Universal Serial Bus
Specification Revision 2.0 April 27, 2000</a> (linked download on page may have URL certificate problems) states:</p>
<p><em>9.2.6.3 Set Address Processing</em></p>
<p><em>After the reset/resume recovery interval, if a device receives a SetAddress() request, the device must be able to complete processing of the request and be able to successfully complete the Status stage of the request within 50 ms. In the case of the SetAddress() request, the Status stage successfully completes when the device sends the zero-length Status packet or when the device sees the
ACK in response to the Status stage data packet.</em></p>
<p><em>After successful completion of the Status stage, the device is allowed a SetAddress() recovery interval of 2 ms. At the end of this interval, the device must be able to accept Setup packets addressed to the new address. Also, at the end of the recovery interval, the device must not respond to tokens sent to the
old address (unless, of course, the old and new address is the same).</em></p>
<p>Any possible mistakes in ST’s documentation are not the responsibility of of the USB-IF organization, but <a href="https://www.st.com/content/ccc/resource/technical/document/reference_manual/59/b9/ba/7f/11/af/43/d5/CD00171190.pdf/files/CD00171190.pdf/jcr:content/translations/en.CD00171190.pdf">RM0008 Reference manual STM32F101xx, STM32F102xx, STM32F103xx, STM32F105xx and
STM32F107xx advanced ARM ® -based 32-bit MCUs</a> states:</p>
<p><em>During USB enumeration process, the host assigns a unique address to this device, which must be written in the ADD[6:0] bits of the USB_DADDR register,
and configures any other necessary endpoint.</em></p>
<p>Depending on one’s level of clairvoyance, these descriptions may or may not contain sufficient information for implementing USB device enumeration. The fact, glossed over in the USB-IF standard and completely omitted in ST’s documentation, is that setting the device’s address <strong>must</strong> be delayed until the next transfer on the bus occurs. (Trust me. I spent many fine debugging hours discovering this fact.) It is these types of crucial details that make USB so difficult.</p>
<p>And, in this particular case, none of it is necessary. USB is not a “bus” (pedanticism: “not a <em>multidrop</em> bus”). It is a multilevel star topology, tree structured, directed acyclic graph network. Any given USB cable is uniquely connected to exactly one upstream hub (root or secondary) and one downstream device. There is no reason for the hub to communicate anything on the cable other than information intended for that device, so why is an “address” needed? The address <strong>is</strong> part of the higher-level device description scheme and thus is needed “above” the hub, but the hub could strip it out for communication to the device, or the device could simply ignore it. Again, this may be an ST hardware implementation issue, but getting it right was absolutely necessary to get papoon_usb to function on the STM32F103xx MCU.</p>
<br>
So why, given all the vitriol expressed above, do I use USB? Simply because there's no other alternative for getting data in and out of a host modern computer. Ethernet's physical layer electrical requirements make it infrequently supported on low-end MCUs. WiFi and Bluetooth took pages from USB's playbook and require similar kinds of unwanted complexity ("Are you a heart rate monitor? Use this Bluetooth profile." No, I just want to send and receive some data, thank you.) And devices like USB-to-serial converters bring one right back to USB (they just hide the details) while adding their own processing and latency issues.
<p>You can’t fight city hall.</p>
<p><br> <a name="st_hardware_and_documentation"></a></p>
<h3 id="sthardwareanddocumentation">ST hardware and documentation</h3>
<p>As per the example described above, ST’s documentation for the USB peripheral, while actually better than many of their other attempts, leaves a lot to be desired. In general the impression given is that users are not intended to have much need for the documentation nor develop much software at the register level it describes, and instead use ST-provided libraries and tools (see <a href="#st_software">ST software</a>, below, for problems in that area).</p>
<p>The USB peripheral in the STM32F10x series MCUs is somewhat primitive and lacking in features/capabilities. Casual research seems to indicate that it was carried over from the smaller STM8 8-bit MCUs. Supporting evidence for these claims is the fact that ST started using a vastly improved USB peripheral in MCUs following the STM32F10x series.</p>
<p><a name="toggle-only-bits-hardware"></a>
By far the largest problem is the (mis-)design of the “USB endpoint n register (USB_EPnR), n=[0..7]”. These 8 registers contain bits that must be accessed and controlled via 4 different methods:</p>
<ol>
<li>A <code>SETUP</code> bit that is read-only</li>
<li><code>CTR_RX</code> and <code>CTR_TX</code> bits that can be read, and cleared to a value of 0, but not set to a value of 1.</li>
<li>Normal read-write bits: <code>EP_TYPE</code>, <code>EP_KIND</code>, and <code>EA</code> bits.</li>
<li>“Toggle-only” bits that can only be flipped from 1 to 0 or vice versa by writing a 1 value to them (writing 0 has no effect). These include the <code>DTOG_RX</code>, <code>STAT_RX</code>, <code>DTOG_TX</code>, and <code>STAT_TX</code> bits.</li>
</ol>
<p>Of these, type number 4 is the most problematic. <a href="https://www.st.com/content/ccc/resource/technical/document/reference_manual/59/b9/ba/7f/11/af/43/d5/CD00171190.pdf/files/CD00171190.pdf/jcr:content/translations/en.CD00171190.pdf">RM0008</a> specifically states:</p>
<p><em>Read-modify-write cycles on these registers should be avoided because between the read and the write operations some bits could be set by the hardware and the
next write would modify them before the CPU has the time to detect the change.</em></p>
<p>This is a logical inconsistency. Several of the “toggle-only” bits need to be set to specific values at various stages of the USB device enumeration process. Their toggle-only access requirement does nothing to change the basic race condition (where the hardware changes them between software read and write) that the documentation warns against. I got involved in a pedantic argument about this when I asked for clarification on the ST support forum — the responder stated that flipping controlling the bits was not “read-modify-write”. That’s literally true, but to get them to a desired state requires reading the register’s bits, deciding whether they need to be flipped (XOR’d) or not, and writing something back. Same problem.</p>
<p>As evidence of how poorly understood these bits are, see <a href="#toggle-only-bits-software">below</a> for how ST’s own libraries attempt to handle them. My own speculation is that the hardware designers (if they thought through the issue at all) felt that software would know <em>a priori</em> what state the bits were in at any given protocol phase, and could then toggle them (or not) as desired to achieve their desired state (see <a href="#write-toggle-only-bits">below</a>). But note that this <strong>still</strong> doesn’t change the existence of the race condition risk.</p>
<p>As the Beatles sang: <a href="https://youtu.be/S-rB0pHI9fU?t=35">“Very strange.”</a></p>
<p><a name="unique_id"></a>
Other problems in addition to the above USB peripheral ones include the fact that the STM32F103xx’s “Device electronic signature”, “Unique device ID register (96 bits)” (see <a href="https://www.st.com/content/ccc/resource/technical/document/reference_manual/59/b9/ba/7f/11/af/43/d5/CD00171190.pdf/files/CD00171190.pdf/jcr:content/translations/en.CD00171190.pdf">RM0008</a>, section 30.2) reads <code>0xffffffffffffffffffffffff</code> instead of its correct value when the MCU is clocked at the 72 (or 48) MHz required for USB peripheral operation. Any corroboration or information regarding this is welcome (it is possible that my testing has been done on faulty and/or counterfeit chips).</p>
<p><br> <a name="st_software"></a></p>
<h3 id="stsoftware">ST software</h3>
<p><a name="ST_HAL_and_LL_libraries"></a></p>
<h4 id="sthalandlllibraries">ST HAL (and LL) libraries</h4>
<p>I’ve written it before, both in this README.md, similar ones in other repositories, and in emails and online forums: ST’s HAL and LL libraries (and their inclusion in the “CubeMX”, etc. IDEs) are bloated, inefficient, convoluted disasters masquerading as official, vendor-vetted software.</p>
<p>ST (and other hardware manufactures) <strong>should</strong> distribute code that falls into one of two categories: Either simple, direct, tutorial-like examples that allow developers to understand and better use the hardware, or code that is so fast, efficient, feature-rich and easy for client code to use that no one would need anything else (or to modify them for their own needs). “papoon_usb” may not be perfect (and likely is not), but I contend that it succeeds better on <strong>both</strong> counts than ST’s offerings.</p>
<p>I invite interested readers to trace through a function call graph of the USB CDC-ACM device code contained in the
<a href="https://www.st.com/en/embedded-software/stm32cubef1.html">STM32CubeF1</a> distribution, starting at <code>Projects/STM3210C_EVAL/Applications/USB_Device/CDC_Standalone/Src/main.c</code>. I attempted to do so(not for the first time) for this README.md and once again gave up after documenting 2000+ lines worth of program flow.</p>
<p>In all fairness, the HAL code does do more than the <a href="examples/blue_pill/usb_cdc_acm_echo.cxx">usb_cdc_acm_echo.cxx</a> demo in this repository. (It does implement a classic bidirectional interface between USB and a serial port, and it does use DMA.) But the hallmark of the code is the levels of indirection it contains. Every function calls through multiple software layers (“Projects”, “Middlewares”, and “Drivers”, the latter both HAL and LL). And not in a linear, hierarchical arrangement — each level freely calls up and down the layers, including cyclical loops through them</p>
<p>Contrast with papoon_usb, where the client application accesses public member functions of the <code>UsbDev</code> base class, which, if necessary, invokes derived class (USB class) functionality. Not to mention that the code (a fraction of the HAL/LL size) is contained in a vastly smaller number of files and directories.</p>
<p>Is the ST code either a good tutorial or an efficient implementation? I contend it’s neither.</p>
<h4 id="stm32_usb-fs-device_lib">STM32_USB-FS-Device_Lib</h4>
<p>As “insane” as the current ST Cube/HAL/LL code is, the older “STM32_USB-FS-Device_Lib” is much worse. Again I invite readers to see for themselves, but basically the code defies attempts at static analysis. It is based on a totally unnecessary state machine, driven by a state variable whose value can only be determined at runtime. And tracing USB code at runtime is nearly impossible — the timing requirements are so strict that any attempts to follow it with debugging breakpoints cause it to fail. I ended up implementing a complex runtime tracing system which kept a large buffer of encoded debug state information and code locations, and standalone programs to parse and display the resultant dumps in readable form. All of this is in addition to the code’s fondness for similarly named preprocessor macros and C functions which call each other for no discernable design reason, variables that store values for deferred use later on (these were the first things I removed — they turned out to be unneeded), and many other mis-features. But as mentioned above, this codebase was the only working one I had to start with.</p>
<p><a name="toggle-only-bits-software"></a></p>
<h4 id="endpointregisterstoggle-onlybits">Endpoint registers “toggle-only” bits</h4>
<p>As a final example, both of the above codebases contain almost identical code to handle the “toggle-only” bits in the STM32F103xx USB peripheral as described <a href="#toggle-only-bits-hardware">above</a>. I speculate that the latter code was copied from the former, in an example of the “it works, don’t touch it” mentality.</p>
<p>From STM32_USB-FS-Device_Lib’s Libraries/STM32_USB-FS-Device_Driver/inc/usb_regs.h:</p>
<pre><code>#define _SetEPTxStatus(bEpNum,wState) {\
register uint16_t _wRegVal; \
_wRegVal = _GetENDPOINT(bEpNum) & EPTX_DTOGMASK;\
/* toggle first bit ? */ \
if((EPTX_DTOG1 & wState)!= 0) \
_wRegVal ^= EPTX_DTOG1; \
/* toggle second bit ? */ \
if((EPTX_DTOG2 & wState)!= 0) \
_wRegVal ^= EPTX_DTOG2; \
_SetENDPOINT(bEpNum, (_wRegVal | EP_CTR_RX|EP_CTR_TX)); \
} /* _SetEPTxStatus */
</code></pre>
<p>From Cube F1’s Drivers/STM32F1xx_HAL_Driver/Inc/stm32f1xx_hal_pcd.h:</p>
<pre><code>#define PCD_SET_EP_TX_STATUS(USBx, bEpNum, wState) { register uint16_t _wRegVal;\
\
_wRegVal = PCD_GET_ENDPOINT((USBx), (bEpNum)) & USB_EPTX_DTOGMASK;\
/* toggle first bit ? */ \
if((USB_EPTX_DTOG1 & (wState))!= 0U)\
{ \
_wRegVal ^= USB_EPTX_DTOG1; \
} \
/* toggle second bit ? */ \
if((USB_EPTX_DTOG2 & (wState))!= 0U) \
{ \
_wRegVal ^= USB_EPTX_DTOG2; \
} \
PCD_SET_ENDPOINT((USBx), (bEpNum), (_wRegVal | USB_EP_CTR_RX|USB_EP_CTR_TX));\
} /* PCD_SET_EP_TX_STATUS */
</code></pre>
<p>Contrast both of the above with papoon_usb’s <a href="regbits/stm32f103xb.hxx">stm32f103xb.hxx</a>:</p>
<pre><code>void stat_tx(
const mskd_t tx_stat)
volatile
{
// don't modify read/write bits, clear clear-only bits, or
// toggle other toggle-only bits
Reg<uint32_t, Epr> current = *this;
// clear bits which should not be toggled, cleared, or written
// must use mskd_t's with all bits set
current.clr( Epr::DTOG_TX_DATA1
| Epr::CTR_TX
| Epr::SETUP
| Epr::STAT_RX_VALID
| Epr::DTOG_RX_DATA1
| Epr::CTR_RX );
// XOR new stat bits
current.flp(tx_stat);
// write back to register, toggling stat bits to desired value
*this = current;
}
</code></pre>
<p>A single read, a single <code>&=</code> (note that the <code>|</code> operands are C++ constexpr; they collapse to a single value at compile time), a single <code>^</code>, and a final write. No branching. (Also: This is a C++ inline method, so is as efficient as a C macro.) This code has been shown to work, but I welcome any contrary evidence that the hardware “prefers” the bits be toggled one at a time.</p>
<h2 id="branamefurther_developmentafurtherdevelopment"><br> <a name="further_development"></a>
Further development</h2>
<h4 id="papoon_usb">papoon_usb</h4>
<ul>
<li>Implement USB standard GET_CONFIGURATION and GET_INTERFACE.</li>
<li>Try removing cached <code>ctr_tx</code> and <code>ctr_stp</code> in <code>UsbDev::ctr()</code> and rely on bits in <code>usb->EPRN[0]</code> register maintaining correct state.</li>
<li><a name="write-toggle-only-bits"></a> Try writing endpoint registers' “toggle-only” bits without first reading them for their current vs. desired state, instead trusting that their value is deterministically known at given points in the enumeration process.</li>
<li>Further investigate performance of CPU vs DMA copies to/from PMA memory.</li>
<li>Test USB class with multiple configurations in device descriptor.</li>
<li>Test USB class with multiple interfaces in configuration descriptor (aka “composite” device).</li>
<li>Implement endpoint double-buffering.</li>
</ul>
<p><a name="regbits_future_work"></a></p>
<h4 id="regbits">regbits</h4>
<ul>
<li>Implement direct, write-only, methods for manipulating “toggle only” bits in EPRN[N] registers (rely on register being in known state at fixed points during enumeration process).</li>
<li>Investigate why <a href="regbits/regbits.hxx">regbits.hxx</a> <code>constexpr</code> semantics require C++17 compilation (<code>-std=c++17</code> option) when using the <code>gcc-arm-none-eabi-9-2019-q4-major</code> compiler at <a href="https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads">arm Developer</a> vs C++11 (<code>-std=c++11</code>) being sufficient for the <code>gcc-arm-none-eabi-8-2018-q4-major</code> and earlier releases.</li>
</ul>
<h4 id="libusbandlinux">libusb and Linux</h4>
<ul>
<li>Find way to fully reset (cause re-enumeration as per power-up/-cycle) attached STM32F103xx USB peripheral from software as alternative to shorting USB D+ line to ground as per USB hardware standard.</li>
<li>Find why <code>simple_randomtest.elf</code> can simultaneously perform IN and OUT transfers at max USB 2.0 full speed 1 KHz (endpoint descriptor <code>bInterval</code>==1) if IN and OUT endpoints in <code>usb_dev_simple.cxx</code> have different endpoint numbers, but IN is slower if numbers same (bidirectional endpoint).</li>
<li>Change endpoint descriptors in <code>usb_dev_simple.cxx</code> from <code>INTERRUPT</code> to <code>BULK</code> and compare performance, both total bandwidth and max latency.</li>
</ul>
</body>
</html>