Looking for an economic substitute for the MCP2221A

BobaMosfet

Joined Jul 1, 2009
2,211
I recently designed a circuit that needs to be interfaced to a PC via a USB port. For that purpose, I've used the MCP2221A chip, which is quite effective and easy to implement. The thing is that said chip has become the most expensive component (by far) in the design. Are there any other more economic alternatives out there that could be used instead?
You can use an FT232RL... Got it from ArrowNAC in 2014 for $3.65 low qty to play with. OEM: FTDI.
1739978581919.png
 

Thread Starter

cmartinez

Joined Jan 17, 2007
8,759
So I ordered several CH340C chips to test them, and here are my observations:
  • The CH340C cannot generate custom baud rates. I had to change the MCU I've been using because of this. But fortunately, the price of the new MCU is similar to the old one and its pinout is identical, and its architecture is quite similar. So I had to make no changes to the circuit (other than the ones necessary to use the CH340C) and had to make very small tweaks to the firmware.
  • The MCU can be programmed even with the CH340C completely connected to the circuit, so the use of an in-circuit jumper is not necessary as was the case with the MCP2221A.
  • A little bit more current (about 1 µA) is being consumed by the device, which is negligible for my purposes.
  • The CH340C is a cheap-o chip that is easily saturated and overrun while working at 115200 bauds. So a delay routine had to be inserted while transmitting characters to avoid errors. This could've been improved by the use of the RTS, CTS and DTR control lines, but it would've only complicated things in both hardware and software. A delay routine also had to be implemented at the computer software because the CH340C was also processing (translating from USB to UART) the received characters way too slowly and was being overrun.
Overall, I would say that switching from the MCP2221A to the CH340C chip makes the hell of a lot economic sense when one's device manufacturing cost is critical. But for more complex and sophisticated (and therefore more lucrative) projects I think I'm going to keep using the former.

Thanks to all that participated in this thread, especially Jon who brought my attention to the CH340 line of chips. My problem has been solved.
 

nsaspook

Joined Aug 27, 2009
16,320
Didn't expect to see such bad speed limitations on the CH340C. 115200 baud is pretty much a standard speed for USB to UART serial converters so you would think that should work without issues on the chip or OS driver side.
What's the actual serial clock speed (checked with a scope or something) when set to 115200? If it's off by more than a few percent that can cause framing/overrun errors in back to back characters without delays between characters.

The MCP2221A has decent internal buffers.
448-Byte Buffer to Handle Data Throughput at Any Supported UART Baud Rate: - 64-byte transmit - 384-byte receive

"CH340 has built-in separate transmit-receive buffer" but states no size.

Is your controllers serial routines interrupt driven or polled? Without a sizable internal chip buffer, polling or slow interrupts can drop characters if there is a tiny (2 -> 8 byte) FIFO on the actual uart at 115200.
 
Last edited:

Thread Starter

cmartinez

Joined Jan 17, 2007
8,759
Didn't expect to see such bad speed limitations on the CH340C. 115200 baud is pretty much a standard speed for USB to UART serial converters so you would think that should work without issues on the chip or OS driver side.
What's the actual serial clock speed (checked with a scope or something) when set to 115200? If it's off by more than a few percent that can cause framing/overrun errors in back to back characters without delays between characters.

The MCP2221A has decent internal buffers.
448-Byte Buffer to Handle Data Throughput at Any Supported UART Baud Rate: - 64-byte transmit - 384-byte receive

"CH340 has built-in separate transmit-receive buffer" but states no size.

Is your controllers serial routines interrupt driven or polled? Without a sizable internal chip buffer, polling or slow interrupts can drop characters if there is a tiny (2 -> 8 byte) FIFO on the actual uart at 115200.
You're right, the CH340 datasheet sucks and doesn't give any details as to its buffer size.

But let me give you an example. When I tried to transmit a string of 50 characters from the MCU to the computer via the CH340, at least 5 or 6 of those characters would be received with errors (reported as "?" within the received string), with two or three characters missing too. The way I was transmitting said characters from the MCU was simply to wait until the MCU's transmit buffer was empty before sending the next one. That was never an issue with the MCP2221A. But the only way to make things work with the CH340C was to include a delay of 15ms between characters, which is huge considering a transmission speed of 115200 bauds. Even a delay of 10ms will report an error every 75 characters or so, if they're all transmitted in a row.

To be fair, the MCP2221A also gets choked when receiving characters from the USB and then translating them to UART. I had to include a 10ms delay routine in the computer software to get rid of all the glitches. All this comes from my refusal of using the proper UART control lines to implement a robust hardware protocol. It's just that I want to keep my design's pin count as low as possible ... I'm kinda cheap that way ...
 

Attachments

Last edited:

Thread Starter

cmartinez

Joined Jan 17, 2007
8,759
And here's another thing worth mentioning. The CH340C datasheet says that it runs internally at 12 MHz, and that an error rate of up to 1.2% can be expected. For my MCU, I adjusted its internal oscillator to tune the baud rate, minimizing its rate of error to less than 0.05%
 

nsaspook

Joined Aug 27, 2009
16,320
You're right, the CH340 datasheet sucks and doesn't give any details as to its buffer size.

But let me give you an example. When I tried to transmit a string of 50 characters from the MCU to the computer via the CH340, at least 5 or 6 of those characters would be received with errors (reported as "?" within the received string), with two or three characters missing too. The way I was transmitting said characters from the MCU was simply to wait until the MCU's transmit buffer was empty before sending the next one. That was never an issue with the MCP2221A. But the only way to make things work with the CH340C was to include a delay of 15ms between characters, which is huge considering a transmission speed of 115200 bauds. Even a delay of 10ms will report an error every 75 characters or so, if they're all transmitted in a row.

To be fair, the MCP2221A also gets choked when receiving characters from the USB and then translating them to UART. I had to include a 10ms delay routine in the computer software to get rid of all the glitches. All this comes from my refusal of using the proper UART control lines to implement a robust hardware protocol. It's just that I want to keep my design's pin count as low as possible ... I'm kinda cheap that way ...
Is your serial interfaces on the uC polled or interrupt driven without software buffers or interrupt driven with ring-buffers?
 

Thread Starter

cmartinez

Joined Jan 17, 2007
8,759
What micro are you using?
I was using the PIC16LF15323, but I had to switch to the PCI16LF1823 because the latter can run Timer1 using an external oscillator as a source, independent of the internal system oscillator. What this means is that keeping track of time using a 32.758 kHz external crystal is a piece of cake while the MCU runs at 32 MHz so as to be able to communicate through the UART at 115200 bauds.
 
Last edited:

Thread Starter

cmartinez

Joined Jan 17, 2007
8,759
Is your serial interfaces on the uC polled or interrupt driven without software buffers or interrupt driven with ring-buffers?
In this project, the UART is being polled for both transmission and reception. The MCU I'm using has a meager two-byte buffer in it. You're going to have to enlighten me as to what "ring-buffers" means.
 

nsaspook

Joined Aug 27, 2009
16,320
In this project, the UART is being polled for both transmission and reception. The MCU I'm using has a meager two-byte buffer in it. You're going to have to enlighten me as to what "ring-buffers" means.
Polling, OK.
1740854377213.png
1740854419716.png
https://users.ece.utexas.edu/~valvano/mspm0/ebook/Ch8_SerialCommunication.htm
http://www.simplyembedded.org/tutorials/msp430-uart/

That's a major part of the problem at 115200 unless it's done very fast (at the max FOSC possible for the controller) in a dedicated time when only serial data is expected on the RX side and correctly on the TX side by making sure the shift register is idle/empty (TRMT bit set) before pushing another character into the shift-register from the UART register (buffer). I think that's right for most PIC 8-bit controllers but double check for each device to be sure.

Examples:
https://onlinedocs.microchip.com/ox...UID-FD5172BC-F1D8-431D-9F08-800C5BF4C055.html
https://onlinedocs.microchip.com/ox...UID-6DBE5F62-8C0A-4B29-A556-AE2694C676F2.html

"Ring-buffers" (circular buffer) just means a buffer that goes round (start and finish of data) in the buffer space allocated instead of having a fixed "linear" array index start and a varying index end of data in buffer memory. For a receive interrupt routine using a ring-buffer to store the incoming data usually minimizes interrupt time with well written (short and fast as possible) code. For TX interrupt driven routines, a linear array type buffer is usually all that's needed and can work with received data if simplicity is needed.

http://www.simplyembedded.org/tutorials/interrupt-free-ring-buffer/

https://www.allaboutcircuits.com/te...ouble-buffering-technique-interrupt-friendly/
 
Last edited:

Thread Starter

cmartinez

Joined Jan 17, 2007
8,759
Polling, OK.
View attachment 343529
View attachment 343530
https://users.ece.utexas.edu/~valvano/mspm0/ebook/Ch8_SerialCommunication.htm

That's a major part of the problem at 115200 unless it's done very fast (at the max FOSC possible for the controller) in a dedicated time when only serial data is expected on the RX side and correctly on the TX side by making sure the shift register is idle/empty (TRMT bit set) before pushing another character into the shift-register from the UART register (buffer). I think that's right for most PIC 8-bit controllers but double check for each device to be sure.

Examples:
https://onlinedocs.microchip.com/ox...UID-FD5172BC-F1D8-431D-9F08-800C5BF4C055.html
https://onlinedocs.microchip.com/ox...UID-6DBE5F62-8C0A-4B29-A556-AE2694C676F2.html

"Ring-buffers" (circular buffer) just means a buffer that goes round (start and finish of data) in the buffer space allocated instead of having a fixed "linear" array index start and a varying index end of data in buffer memory. For a receive interrupt routine using a ring-buffer to store the incoming data usually minimizes interrupt time with well written (short and fast as possible) code. For TX interrupt driven routines, a linear array type buffer is usually all that's needed and can work with received data if simplicity is needed.

http://www.simplyembedded.org/tutorials/interrupt-free-ring-buffer/

https://www.allaboutcircuits.com/te...ouble-buffering-technique-interrupt-friendly/
Excellent explanation, thanks for putting in the time. I've used said "ring buffer" technique in the past in different projects and it's quite practical, and efficient code-wise. My personal term for it is "rollover buffer"

And yes, all of my Tx routines check for the state of the transmission buffer before pushing characters for processing, so that's not an issue at least on the MCU side. But the CH340C seems to be not as fast as the MCU, and that's what was causing the glitches, which have already been taken care of. Albeit at a cost. But in this particular project the required delay between characters is not an issue since data transmission will be done every couple of seconds, and then only about 50 characters at a time.
 

Thread Starter

cmartinez

Joined Jan 17, 2007
8,759
And by the way. Look at all the gymnastics :eek: I had to make to test the CH340C on version 1.0 of my circuit, which was designed to use the MCP2221A:

7c5f8d89-f948-45c4-aa59-f75889914fcb.jpg
 

panic mode

Joined Oct 10, 2011
4,974
just out of curiosity, what kind of savings we are talking about? unless this is produced in sufficiently high volume, it will not be worth it...
 

nsaspook

Joined Aug 27, 2009
16,320
Excellent explanation, thanks for putting in the time. I've used said "ring buffer" technique in the past in different projects and it's quite practical, and efficient code-wise. My personal term for it is "rollover buffer"

And yes, all of my Tx routines check for the state of the transmission buffer before pushing characters for processing, so that's not an issue at least on the MCU side. But the CH340C seems to be not as fast as the MCU, and that's what was causing the glitches, which have already been taken care of. Albeit at a cost. But in this particular project the required delay between characters is not an issue since data transmission will be done every couple of seconds, and then only about 50 characters at a time.
Just to be sure. For the Tx side. So, are you checking buffer and/or the transmit Shift Register empty flag (the TRMT bit)? On a typical system with there are both and it's possible that while the buffer indicates clear for data, the Tx TSR is still busy. If the TSR is still busy then a write to the buffer register can (by eratta or configuration for things like 9-bit transmission) corrupt the data currently being shifted out even if the documentation says the TSR will wait until the STOP bit is transmitted before being load with data from the TXREG.

Just checking the TRMT bit before pushing the next character will add a small delay that can (YMMV) fix TX corruption errors at higher speeds on some devices.

1740856963919.png


Example TSR related errata for a controller:
Module: UART (Transmit Interrupt)
When using UTXISEL<1:0> = 01 (interrupt when
the last character is shifted out of the Transmit
Shift register) and the final character is being
shifted out through the Transmit Shift Register
(TSR), the TX interrupt may occur before the final
bit is shifted out.
Work around
If it is critical that the interrupt processing occurs
only when all transmit operations are complete,
after which the following work around can be
implemented:
Hold off the interrupt routine processing by adding
a loop at the beginning of the routine that polls the
Transmit Shift register empty bit, as shown in
With a FIFO and code that polls or uses interrupts driven by the UART (Transmit Interrupt), this can cause corruption of the last character from the buffer without the Transmit Shift register empty bit workaround.
 

Thread Starter

cmartinez

Joined Jan 17, 2007
8,759
just out of curiosity, what kind of savings we are talking about? unless this is produced in sufficiently high volume, it will not be worth it...
This small change saved me about a couple of thousand bucks for the first batch. And it's gonna keep on saving me a bundle in the near future too.

EDIT: that was an understatement, the real figure is more in the vicinity of three thou
 
Last edited:

Thread Starter

cmartinez

Joined Jan 17, 2007
8,759
Just to be sure. For the Tx side. So, are you checking buffer and/or the transmit Shift Register empty flag (the TRMT bit)? On a typical system with there are both and it's possible that while the buffer indicates clear for data, the Tx TSR is still busy. If the TSR is still busy then a write to the buffer register can (by eratta or configuration for things like 9-bit transmission) corrupt the data currently being shifted out even if the documentation says the TSR will wait until the STOP bit is transmitted before being load with data from the TXREG.

Just checking the TRMT bit before pushing the next character will add a small delay that can (YMMV) fix TX corruption errors at higher speeds on some devices.

View attachment 343534


Example TSR related errata for a controller:


With a FIFO and code that polls or uses interrupts driven by the UART (Transmit Interrupt), this can cause corruption of the last character from the buffer without the Transmit Shift register empty bit workaround.
Since my program does not make use of UART interrupts, it only polls the state of the transmission buffer after sending a character through the UART. Here's my code:

Code:
;*****************************************************************************************
;                            Transmit W through the serial port
;                                       Exits bank 0
;*****************************************************************************************
Transmit_W:
     banksel TXSTA                ;bank 3
Tx_Loop:   
      btfss TXSTA, TRMT           ;loop until TXSTA is set, that is, until the transmit
     goto Tx_Loop                 ;buffer is empty
     movwf TXREG                  ;transmit W throught the UART

     call Delay_0.5ms@32MHz       ;delay 1.5ms, for the benefit of the CH340C, this
     call Delay_0.5ms@32MHz       ;because the chip has not been installed with data flow
     call Delay_0.5ms@32MHz       ;control in its hardware (CTS, RTS, DTR, etc)

     movlb 0x00                   ;select memory bank 0 before leaving
    return

Yeah, I know. Using three calls for the same delay instead of a single one is rather clumsy. But the device's code is actually quite short and there's no need for this sort of optimization. Also, I don't think that calling said delay before or after sending the character makes any difference. I added the delay after the transmit instruction just because it was a late addition to the code.
 

nsaspook

Joined Aug 27, 2009
16,320
Since my program does not make use of UART interrupts, it only polls the state of the transmission buffer after sending a character through the UART. Here's my code:

Code:
;*****************************************************************************************
;                            Transmit W through the serial port
;                                       Exits bank 0
;*****************************************************************************************
Transmit_W:
     banksel TXSTA                ;bank 3
Tx_Loop:
      btfss TXSTA, TRMT           ;loop until TXSTA is set, that is, until the transmit
     goto Tx_Loop                 ;buffer is empty
     movwf TXREG                  ;transmit W throught the UART

     call Delay_0.5ms@32MHz       ;delay 1.5ms, for the benefit of the CH340C, this
     call Delay_0.5ms@32MHz       ;because the chip has not been installed with data flow
     call Delay_0.5ms@32MHz       ;control in its hardware (CTS, RTS, DTR, etc)

     movlb 0x00                   ;select memory bank 0 before leaving
    return

Yeah, I know. Using three calls for the same delay instead of a single one is rather clumsy. But the device's code is actually quite short and there's no need for this sort of optimization. Also, I don't think that calling said delay before or after sending the character makes any difference. I added the delay after the transmit instruction just because it was a late addition to the code.
The usage of interrupts doesn't matter as polling will have the same defect in operation if the TSR interaction is buggy in the controllers hardware UART implementation. It's shouldn't make any difference if the hardware didn't have bugs. That's the point of checking the TSR empty flag, there are bugs in some devices. This is one workaround for those bugs.

It's just a guess if there are issues with transmitting back to back characters without artificial delays.
 
Last edited:
Top