USB to UART bridges are cheap and readily available, but have really lousy timing characteristics. Newark sells an "Embedded Pi" board which has an STM32F ARM processor that you can write bare-metal code on. That chip has three UARTs on it, and I think they can go pretty fast; if you were to use one to communicate with the Raspberry Pi that would leave two available for other purposes. Disclaimer: I've bought one of these boards, but have as yet simply used the Raspberry Pi itself to handle by I/O needs directly.
If you want lots of slower UARTs, the STM32F on the Embedded Pi board could probably handle a fair number, especially if you're willing to write some Arm assembly language. If there are two groups of 16 I/O pins available on a single board it might be possible to have 16 simultaneous software UARTs all working at once at a pretty decent baud rate (have a periodic interrupt at 3x or 5x the baud rate which stores 16-bit latched values from the receive port to a buffer, and outputs 16-bit precomputed values from a buffer to the transmit port; if you do this, then provided the average servicing time for the software UARTs isn't too great, it won't matter if there is an occasional worst-case hit (e.g. all sixteen ports receiving a byte simultaneously).
This approach can actually work out remarkably efficiently for receiving, since the "common case" code doesn't even have to look at individual UARTs. Suppose that you're sampling data at 5x, and the last 47 bytes of the buffer are duplicated immediately preceding it. Assuming data is written to the buffer in ascending order, you can then check whether any byte has been fully received on any of the 16 channels by simply saying:
bytes_ready = (armed_flag & data[rxptr] & ~data[rxptr-47] & ~data[rxptr-46] & ~data[rxptr-45] & ~data[rx_ptr-44]);
If bytes_ready
is zero, no data has been received. Otherwise, if e.g. bit 2 of bytes_ready
is set, that means that a received data byte may be found in bit 2 of data[rx_ptr-40], data[rx_ptr-35], data[rx_ptr-30], etc. Once one grabs the data, clear bit 2 of armed_flag and arrange for it to get re-set after about 44 samples.
This approach will require a bit of work on those samples where a byte of data is fully received (and potentially a lot of work if all 16 channels have a byte of data arrive at once) but on most samples the amount of work will be very slight. If one had 64 I/O pins, one could handle up to 32 UARTs using this approach without adding any extra work to the "common" case.