Papoon is written in C++. This would not be my first choice, but beggars cannot be choosers. When I try to build the first example distributed with papoon, it pulls in the standard crt0.o file that is part of g++. This thinks that, since there is a main() function, this is going to run as some kind of program in a linux system, and expects things like _exit(), _sbrk(), and other standard linux system calls to be provided.
What I decided to do, was to rename main() to something else. I chose cxx_main(). Once this is done, the C++ compiler does not get the itch to drag in the standard startup code.
The next trick is to learn how to call C++ from C (and vice versa). The way to set up a function that can be called from C is like so:
extern "C" void papoon_main ( void ) { cxx_main(); }Now I can call "papoon_main()" from my C code and it in turn calls cxx_main() for me. Of course I could just make cxx_main() C callable in this way, but I like to have as many layers of isolation and protection as possible between my nice C code and this angry world of C++. In turn, I would like to like to call my C printf function from C++. This can be done by simply adding a prototype like this to the C++ code.
extern "C" void printf ( const char *, ... );
If you are curious, you may wonder just what this extern "C" business does. I peek at a disassembly of the generated C++ code and I see the following labels on the code for cxx_main(). Similar and even more wild and crazy labels (such as _ZN17stm32f10_12357_xx6UsbDev18serial_number_initEv) are generated by C++ for other functions.
_Z8cxx_mainv:Who would have guessed? These things are hidden from the typical C++ programmer, who lives happily in a higher level world. The embedded programmer ends up having to be aware of details like this. But the extern "C" construct handles all this for us, forcing C++ to use the sane function names we might expect.
From previous experience with other C++ embedded situations (namely libmaple for the STM32F103), I know that C++ can generate lists of initializer (and destructor) functions that it expects the startup code to call before calling main. I made an attempt to check for these sorts of things, and did not find any generated by the papoon code, but I won't say that I am fully confident.
example.cxx usb_dev_cdc_acm.cxx usb_dev.cxx usb_mcu_init.cxxI work up a Makefile and keep typing "make" and copying header files until suddenly it stops complaining and just compiles everything. The minimum set of header files thus obtained are these 8:
bin_to_hex.hxx regbits.hxx stm32f103xb_tim.hxx usb_dev.hxx core_cm3.hxx stm32f103xb.hxx usb_dev_cdc_acm.hxx usb_mcu_init.hxxAll told, 7070 lines of C++ code, which is certainly non-trivial. From my code, I just call:
papoon_main ();
Nov 4 11:00:05 trona kernel: usb 3-1.2: new full-speed USB device number 11 using ehci-pci Nov 4 11:00:06 trona kernel: usb 3-1.2: device descriptor read/64, error -32 Nov 4 11:00:06 trona kernel: usb 3-1.2: device descriptor read/64, error -32 Nov 4 11:00:06 trona kernel: usb 3-1.2: new full-speed USB device number 12 using ehci-pci Nov 4 11:00:06 trona kernel: usb 3-1.2: device descriptor read/64, error -32 Nov 4 11:00:06 trona kernel: usb 3-1.2: device descriptor read/64, error -32 Nov 4 11:00:06 trona kernel: usb 3-1-port2: attempt power cycle Nov 4 11:00:07 trona kernel: usb 3-1.2: new full-speed USB device number 13 using ehci-pci Nov 4 11:00:07 trona kernel: usb 3-1.2: device not accepting address 13, error -32 Nov 4 11:00:07 trona kernel: usb 3-1.2: new full-speed USB device number 14 using ehci-pci Nov 4 11:00:08 trona kernel: usb 3-1.2: device not accepting address 14, error -32So, it doesn't work. As an experiment I grab a factory fresh blue pill, connect it to a USB cable and plug it in. I get exactly the same messages.
static const uint32_t PERIPH_BASE = 0x40000000U; static const uint32_t APB1PERIPH_BASE = PERIPH_BASE , APB2PERIPH_BASE = PERIPH_BASE + 0x00010000U, AHBPERIPH_BASE = PERIPH_BASE + 0x00020000U;The above code may well create initialized data. For a system like the STM32F103, these values will be in ROM, but will need to be copied into RAM. Maybe. These particular values, being constants, could simply be left in ROM, but without some experimenting I don't really know how the compiler handles this.
Whatever the case, I know full well that there are two things that I am not yet doing that I should be doing to launch even C code, never mind C++, namely:
STM32 usb_papoon demo starting 37 bytes of BSS cleared 86 bytes of Data initializedAha! So there definitely was a need to do this, and better yet, now I see this in my linux logs when I plug in the board:
Nov 4 12:35:42 trona kernel: usb 3-1.2: new full-speed USB device number 15 using ehci-pci Nov 4 12:35:47 trona kernel: usb 3-1.2: New USB device found, idVendor=0483, idProduct=5740, bcdDevice= 2.00 Nov 4 12:35:47 trona kernel: usb 3-1.2: New USB device strings: Mfr=1, Product=2, SerialNumber=3 Nov 4 12:35:47 trona kernel: usb 3-1.2: Product: STM32 Virtual COM Port Nov 4 12:35:47 trona kernel: usb 3-1.2: Manufacturer: STMicroelectronics Nov 4 12:35:47 trona kernel: usb 3-1.2: SerialNumber: 8717472853518850066fff48 Nov 4 12:35:47 trona mtp-probe[53888]: checking bus 3, device 15: "/sys/devices/pci0000:00/0000:00:1a.0/usb3/3-1/3-1.2" Nov 4 12:35:47 trona mtp-probe[53888]: bus: 3, device: 15 was not an MTP device Nov 4 12:35:47 trona kernel: cdc_acm 3-1.2:1.0: ttyACM0: USB ACM device Nov 4 12:35:47 trona kernel: usbcore: registered new interface driver cdc_acm Nov 4 12:35:47 trona kernel: cdc_acm: USB Abstract Control Model driver for USB modems and ISDN adaptersThere is one more code change I need to do before poking at this with picocom. The file "example.cxx" has these lines:
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(); }The comment tells me that I should copy the recv_buf to the send_buf, or do something. As it is, it will receive data, but never respond. So I make it look like this:
if (recv_len = usb_dev.recv(UsbDevCdcAcm::CDC_ENDPOINT_OUT, recv_buf)) { printf ( "Papoon recv %d\n", recv_len ); // process data received from host -- populate send_buf and set send_len for ( i=0; iI flash the new code. I need to unplug and replug the make linux realize that something has happened (here is where the disconnect circuit in the Maple board would be handy). Now I can use "picocom /dev/ttyACN0" and it just works!. I can even use Ctrl-A, Ctrl-X to exit picocom. The pill doesn't know anything happened (or doesn't seem to) and if I start picocom again, I am right back in business!
What is interesting is that usb_recv gets single characters in the pill. No waiting for full lines to accumulate or anything of the sort.
Conclusion
The main thing (believe it or not) I wanted to get out of this was to find out if an unmodified blue pill with the 10K resistor on USBDP would behave correctly. Based on this test, yes it does! I don't need to fuss around under my microscope adding a new resistor. This would have been a challenge (perhaps requiring me to order a supply of 1.5K surface mount resistors). Now I cannot say that a blue pill with the improper 10K resistor will work properly on all systems, but it certainly does on mine, at least on the port I am testing with (one on the front of my computer.I try it on a different port. This one is the hub built into my monitor and connected to a USB on the back of my computer. It works fine there also.
Of course there was much more learned here, but we needed to know if a "stock" blue pill with the wrong 10K resistor would work. If not, we would have been trying to debug new code on faulty hardware.
Feedback? Questions? Drop me a line!Tom's Computer Info / tom@mmto.org