For my first amazing feat, I am going to make the interface as originally planned: i.e. as a USB CDC to SP0256 'bridge'.
The original motivation of this mini project was to make a USB CDC to SP0256 'bridge' so I could record individual phoneme signals. An application on the PC would send the phoneme sequence over serial and then also record the output. As mentioned, I found a collection of recorded phonemes, but I'm going to carry on with this. Maybe I'll get some better recordings. Anyway, I have future plans beyond the original bridge, but this is a good first step towards those.
Of HALs and Hacks and Heaps
I've covered this before in other projects, but I'm not a big fan of the STM32 HAL libraries. I still use them anyway for projects like these because they are convenient, but they strike me as bloated. For example, after configuring the chip and generating the project and doing a build:
debug 'optimize for debug'
arm-none-eabi-size "BluePillSP0256AL2.elf" text data bss dec hex filename 36140 1156 13576 50872 c6b8 BluePillSP0256AL2.elf
so, 36 K flash (out of 64K) is consumed, and something 14.5 K ram (out of 24K) is used. Yikes! Even doing a release build:
release, 'minimize size'
arm-none-eabi-size "BluePillSP0256AL2.elf" text data bss dec hex filename 30856 1148 13552 45556 b1f4 BluePillSP0256AL2.elf
doesn't improve it much. But c'est la vie. For things like USB the HAL library is pretty much the only option unless maybe you went with an alternative library. The USB peripheral is a beast.
The HAL also has quirks, so I have a set of hacks which I use that make USB CDC and UART work the way I want them to. Bit since the code is generated, those hacks get overwritten. I have a batch file that re-applies them after every time I re-generate the code. You will regenerate often in the beginning, because you'll change your mind about peripherals, etc. The batch file makes it tolerable.
The last hack is an enhancement -- I have my own heap (i.e. malloc) implementation. I needed this a while back for a library that required realloc(), and the one that comes with FreeRTOS does not provide that. Additionally, I added some debugging enhancements that let me do a 'heapwalk' of the blocks and also to fill blocks with a pattern so they are easier to visually inspect. The last bit of legerdemain with the heap is using a nifty feature of the gcc linker. You can tell the linker to redirect symbols to a 'wrapper' function that you must provide. This trick allows me to re-direct calls to malloc() that are even in pre-compiled code (e.g. libc) into my implementation. This is important, because otherwise you will have two heaps: the one you conscientiously use that you provide, and the default implementation that is in libc. I am less fond of the libc implementation because it will 'grow' the heap as needed upwards into the stack. There isn't a hard limit on the arena size (well, up until crashing -- that's a hard limit!).
Some folks have asked for my heap implementation. It's in the source (in the github project in the links section). It goes where FreeRTOS normally places heap_4.c:
the 'fixup.bat' does the deletion of heap_4.c and replaces it with heap_x.c. The linker stuff is documented in main.c near the top.
OK with all that setup, I generally start with a common design where the 'default' task (which apparently cannot be deleted via the tool, so I just work with it) handles things like the LEDs (of which this board has just one on the board itself) and periodically collecting debug statistics like heap usage, per-task stack usage, and anything else I might be interested in such as circular buffer usage. I find these useful for tuning in the final build. The release build conditionally omits that diagnostic code.
The other common thing I do is implement a 'monitor' task. This is a serial console through which I can issue commands to see what's going on and do configuration stuff. I typically do that over USB CDC, since that's the way I usually use the board, however I've found that complicates debugging, because when you hit a breakpoint the USB state machine halts and then you generally lose your connection on the PC and require to cycle power. I have since found it easier (when possible) to use a physical uart and an external uart-to-usb bridge (e.g. FTDI). That way the PC can stay connected to the USB on the separate bridge while the board is halted. This is fairly easy for me to do because part of my 'hacks' is to abstract UART and USB CDC into a common 'stream' interface. Then stuff that needs to use that IO can 'bind' to the relevant stream interface object and deal with them in a consistent way. As such, switching between physical UART and USB CDC is just a one-line change where the interface binding is performed.
The monitor task uses a common component of mine called 'command processor'. It handles the low(er) data IO from the stream interface, and performs a looking of commands that are stored in a table. The command entry provides some help text, and also the function pointer to the handler for the command. Since this project is just starting, the set of commands is currently minimal:
- set -- set a persistent setting or list all the settings
- persist -- save settings to flash
- depersist -- read the settings from flash
- reboot -- restart the board as if you pressed 'reset'
- dump -- dump memory contents
- diag -- print some of the diagnostic statistics I mentioned earlier
- help -- get help on a specific command, or list all commands
I generally like to get a board brought up to this stage before proceeding with the project-specific stuff. And so I did!
Implementing the SP0256 interface processor.