The code in this post is adapted from the Ch376msc repository, initializes the CH376 via SPI, and allows to use FatFs to access the USB filesystem by using only the CH376’s sector read/write commands.
In a recent embedded project, I need to access data stored on a USB thumb drive using a dsPIC33EP512MC502. As you might have guessed, the PIC that I selected, despite being a very capable device, does not have an integrated USB controller, requiring me to use an external USB module. After some research, I decided to pick the CH376, a 8051-core USB interface chip which is available in SOP-28 and SSOP-20 packages. Three host interface modes are supported, namely UART, SPI and 8-bit parallel (SOP-28 packages only). The CH376 is the successor to the CH375, with added support for SD cards, in addition to USB thumb drives. The CH375 is commonly used to build ISA to USB adapters, which I have covered in my previous article.
This is the pinout for the CH376S (SOP-28) and CH376T (SSOP-20), extracted from the datasheet. As you can see, parallel pins are missing from the CH376T:
The required connections are described in sections “Functions Description” and “SPI Serial Interface” of the datasheet, but will also be summarized here for convenience. To use SPI, ground the WR# and RD# pins (# denotes active-low pins). Although the datasheet also states that PCS#/TXD#/RXD#/A0# should be connected to VCC, these pins already have internal pull-up resistors and hence no external connections for these pins are needed in SPI mode. It is also recommended to operate the CH376 using 5V, as during my tests, I encountered some weird issues when running the CH376 on 3.3V, which did not occur if 5V was used. The CH376 also supports SPI interrupt (when input data is available), either via a dedicated pin, or via the MISO pin, which can be specified during initialization.
This is the circuit diagram that I used:
By using the sample code from this Github repository, I was quickly able to retrieve an SPI response from the CH376 after sending CMD_CHECK_EXIST
command, by setting CKE=CKP=SMP=1
during SPI initialization:
SPI2CON1bits.CKE = 1;
SPI2CON1bits.CKP = 1;
SPI2CON1bits.SMP = 1;
Next, I mounted my drive by sending CMD_DISK_MOUNT
command, which worked well with a USB thumbdrive containing a single FAT16/FAT32 partition, and I was able to read a simple text file from my thumb drive. However, after some experiments, it is clear that support for reading/writing files/directories on the CH376 is very limited, lacking files copying, long file names (LFN) support, among other things. As the CH376 supports raw sector access (via CMD_DISK_READ
and CMD_DISK_WRITE
), I decided to integrate my CH376 library with FatFs, a generic FAT filesystem module, to make it more convenient to manage files on the USB drive.
By default, FatFs comes with sample code to interface with the SD card using the PIC’s integrated SPI controller, (files mm_pic.h, diskio.h and diskio.c), which has to be modified to allow input source selection (either SD card or USB via CH376), and to route data to the CH376 if necessary. To do this, I added setFatFsIsUsb
to diskio.c:
void setFatFsIsUsb(unsigned char isUSB)
{
isUsingCH376 = isUSB;
}
Of course, the FatFS initialization code (among other things) will have to be changed:
DSTATUS disk_initialize(BYTE pdrv) {
if (isUsingCH376)
{
return disk_initialize_usb();
}
else {
return disk_initialize_sd(pdrv);
}
}
To note, functions disk_read()
and disk_write()
will also have to be re-written to allow both SD and USB sources. This is needed because when reading/writing sector data, FatFs supports read/writing an arbitrary number of sectors, whereas CH376 can only support either 1 or 4 sectors. Therefore, a request to read/write, let’s say 3 sectors, will have to be sent as three different requests:
DRESULT disk_read_usb (
BYTE *buff,
DWORD sector,
UINT count
)
{
BYTE *buffPT = buff;
UINT c = 0;
UINT readCnt = 0;
UINT cnt = 0;
if (count == 1 || count == 4)
{
cnt = ch376_sectorRead(sector, count, buffPT);
readCnt += cnt;
buffPT += cnt;
if (cnt != SECTOR_SIZE)
{
SendUARTStr("sctRdE1");
return RES_ERROR;
}
}
else {
for (c = 0; c < count; c++)
{
cnt = ch376_sectorRead(sector + c, 1, buffPT);
readCnt += cnt;
buffPT += cnt;
if (cnt != SECTOR_SIZE)
{
SendUARTStr("sctRdE2");
return RES_ERROR;
}
}
}
return RES_OK;
}
Although CH376 also supports SD cards, which can be accessed in a manner similar to USB drives, by just switching the operating mode (CMD_SET_USB_MODE
), in my experience accessing SD card via the CH376 consumes unneeded extra amount of current. I therefore access the SD card directly using my PIC and only use the CH376 to access the USB thumb drives.
With these changes, FatFs functions can now be used to access files stored on my USB drive using the following code snippet:
setFatFsIsUsb(TRUE);
SPI2STATbits.SPIEN = 1;
ch376_clearvars();
ch376_reset();
FRESULT res = f_mount(&fs, "", 1);
if (res == FR_OK)
{
SendUARTStr("USB Mounted");
}
Once the drive has been successfully mounted via FatFs, you can access the USB filesystem using C-like functions (f_read
, f_write
, f_open
, f_close
, etc.) without having to worry about limited filesystem support on the CH376.
You can download the full source code, together with the CH375 and CH376 datasheets here. My code is adapted from the Ch376msc repository, initializes the CH376 via SPI, and allows you to use FatFs to access the USB filesystem by using only the CH376’s sector read/write commands. Other unused features have been removed for simplicity.
See Also