基于CH347--USB转SPI芯片操作FLASH

发布于:2022-10-13 ⋅ 阅读:(589) ⋅ 点赞:(0)

USB转SPI芯片简介

        高速USB转接芯片CH347是一款集成480Mbps高速USB接口、JTAG接口、SPI接口、I2C接口、异步UART串口、GPIO接口等多种硬件接口的转换芯片。

接口示意图:

CH347-SPI接口特点

  • USB传输采用USB2.0高速(480Mbps)
  • 工作在 Host/Master主机模式;
  • 内置硬件DMA,支持批量数据的快速发送和读取;
  • 支持SPI模式0/1/2/3,支持传输频率配置,传输频率可达60MHz;
  • 硬件信号:SCS0、SCS1、SCK、MISO和MOSI;
  • 传输位序:MSB/LSB;
  • 数据结构:8位/16位传输;
  • 提供计算机端驱动程序和USB转SPI函数库,支持二次开发;

使用芯片准备工作

选择CH347工作模式

CH347芯片在复位时,会根据DTR1(CFG0)和RTS1(CFG1)引脚的电平状态配置其工作模式,各工作模式及功能说明如下:

工作模式

模式说明

CFG0

CFG1

Mode0

480Mbps高速USB转双UART(Baudrate最高9Mbps)

1

1

Mode1

480Mbps高速USB转UART+SPI+I2C(厂商驱动模式)

0

1

Mode2

480Mbps高速USB转UART+SPI+I2C(系统HID驱动模式)

1

0

Mode3

480Mbps高速USB转UART+JTAG(厂商驱动模式)

0

0

CH347可使用SPI的模式有两种,其区别在Mode1需要安装厂商驱动,Mode3可以使用系统内置HID驱动无需额外安装,只需在编程时调用CH347动态库进行软件编程即可,此处我们使用Mode1来进行操作。

驱动安装

window驱动安装

从WCH官网下载CH347转SPI/I2C/JTAG/GPIO驱动:CH341PAR.EXE - 南京沁恒微电子股份有限公司

驱动下载后进行一次安装,后续即可实现系统“免驱”效果无需二次安装。未插入设备时安装会显示“驱动预安装成功”,此时驱动已经正常安装,硬件即插即用。

Windows驱动通过微软数字签名认证,支持32/64位 Windows 11/10/8.1/8/7/VISTA/XP/2000,SERVER 2019/2016/2012/2008/2003等系统,无需担心Windows不同系统兼容性问题。

官方同时提供驱动资源包CH341PAR.ZIP - 南京沁恒微电子股份有限公司,可将驱动安装文件打包至成熟产品一齐发布,且支持无界面安装操作,可通过软件编程调用命令行操作,只需执行“SETUP /S”命令即可静默驱动安装。

点击安装之后,等待弹出安装成功窗口后点击确定即可。

 

Linux驱动安装

联系WCH技术支持获取到CH347-Linux驱动,然后进行安装

1、执行make编译驱动;

2、执行make load动态加载驱动,或执行make install后可实现重新启动自动检测硬件并加载驱动;

3、插入设备可查看到生成前缀为ch34x_pis的设备节点。

使用USB操作FLASH

本次操作CH347开发板板载FLASH:W25Q16JVSSIQ。

除此之外,CH347也可操作常见AT25/26、GD25等FLASH

调用函数

WCH提供了一套公用的库函数接口,即Windows&Linux平台接口函数名称与参数一致,其库函数接口特性如下:

操作SPI、I2C、GPIO等的接口在任何工作模式下都可使用同一API,在进行软件编写时,只需调用接口完成代码操作逻辑而不用关注当前硬件工作模式。提供插拔检测函数可动态监测设备插拔信息,更方便进行设备管理。

具体详细内容可参考官方开发手册:CH347EVT.ZIP - 南京沁恒微电子股份有限公司 【目录:CH347EVT\EVT\PUB\CH347应用开发手册.PDF】

/***************插拔监测函数************/
BOOL    WINAPI  CH347SetDeviceNotify(                                           // 设定设备事件通知程序
                                     ULONG                  iIndex,             // 指定设备序号,0对应第一个设备
                                     PCHAR                  iDeviceID,          // 可选参数,指向字符串,指定被监控的设备的ID,字符串以\0终止
                                     mPCH347_NOTIFY_ROUTINE iNotifyRoutine );   // 指定设备事件回调程序,为NULL则取消事件通知,否则在检测到事件时调用该程序
/***************SPI接口函数通用于Mode1/2********************/
// SPI控制器初始化
BOOL    WINAPI  CH347SPI_Init(ULONG iIndex,mSpiCfgS *SpiCfg);

//获取SPI控制器配置信息
BOOL    WINAPI  CH347SPI_GetCfg(ULONG iIndex,mSpiCfgS *SpiCfg);

//设置片选状态,使用前需先调用CH347SPI_Init对CS进行设置
BOOL    WINAPI  CH347SPI_ChangeCS(ULONG         iIndex,         // 指定设备序号   
                                  UCHAR         iStatus);       // 0=撤消片选,1=设置片选

//设置SPI片选
BOOL    WINAPI  CH347SPI_SetChipSelect(ULONG            iIndex,            // 指定设备序号
                                       USHORT           iEnableSelect,     // 低八位为CS1,高八位为CS2; 字节值为1=设置CS,为0=忽略此CS设置
                                       USHORT           iChipSelect,       // 低八位为CS1,高八位为CS2;片选输出,0=撤消片选,1=设置片选
                                       ULONG            iIsAutoDeativeCS,  // 低16位为CS1,高16位为CS2;操作完成后是否自动撤消片选
                                       ULONG            iActiveDelay,      // 低16位为CS1,高16位为CS2;设置片选后执行读写操作的延时时间,单位us
                                       ULONG            iDelayDeactive);   // 低16位为CS1,高16位为CS2;撤消片选后执行读写操作的延时时间,单位us

//SPI4写数据
BOOL    WINAPI  CH347SPI_Write(ULONG            iIndex,          // 指定设备序号  
                               ULONG            iChipSelect,     // 片选控制, 位7为0则忽略片选控制, 位7为1进行片选操作
                               ULONG            iLength,         // 准备传输的数据字节数  
                               ULONG            iWriteStep,      // 准备读取的单个块的长度
                               PVOID            ioBuffer);       // 指向一个缓冲区,放置准备从MOSI写出的数据

//SPI4读数据.无需先写数据,效率较CH347SPI_WriteRead高很多
BOOL    WINAPI  CH347SPI_Read(ULONG         iIndex,           // 指定设备序号 
                              ULONG         iChipSelect,      // 片选控制, 位7为0则忽略片选控制, 位7为1进行片选操作
                              ULONG         oLength,          // 准备发出的字节数
                              PULONG        iLength,          // 准备读入的数据字节数 
                              PVOID         ioBuffer);        // 指向一个缓冲区,放置准备从DOUT写出的数据,返回后是从DIN读入的数据

// 处理SPI数据流,4线接口
BOOL    WINAPI  CH347SPI_WriteRead(ULONG            iIndex,       // 指定设备序号
                                   ULONG            iChipSelect,  // 片选控制, 位7为0则忽略片选控制, 位7为1则操作片选
                                   ULONG            iLength,      // 准备传输的数据字节数
                                   PVOID            ioBuffer );   // 指向一个缓冲区,放置准备从DOUT写出的数据,返回后是从DIN读入的数据

// 处理SPI数据流,4线接口
BOOL    WINAPI  CH347StreamSPI4(ULONG           iIndex,       // 指定设备序号
                                ULONG           iChipSelect,  // 片选控制, 位7为0则忽略片选控制, 位7为1则参数有效
                                ULONG           iLength,      // 准备传输的数据字节数
                                PVOID           ioBuffer );   // 指向一个缓冲区,放置准备从DOUT写出的数据,返回后是从DIN读入的数据

 操作流程

 

代码示例

Windows例程

可参考官方开发资料:CH347EVT.ZIP - 南京沁恒微电子股份有限公司 【目录:CH347EVT\EVT\TOOLS\CH347Demo】

界面读写示例如下:

Linux例程

 可参考如下代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#include "CH347LIB.h"

#define CMD_FLASH_SECTOR_ERASE 0x20
#define CMD_FLASH_BYTE_PROG    0x02
#define CMD_FLASH_READ         0x03
#define CMD_FLASH_RDSR         0x05
#define CMD_FLASH_WREN         0x06

#define CMD_FLASH_JEDEC_ID     0x9F

#define SPI_FLASH_PerWritePageSize 256

#ifndef CH34x_DEBUG
#define CH34x_DEBUG
#endif

#ifdef CH34x_DEBUG
#define dbg( format, arg...)    printf( format "\n", ##arg );
#endif
#define err( format, arg... )   \
    printf( "error %d: " format "\n", __LINE__, ##arg )

int mindex = -1;
BOOL FlashDevIsOpened = false;
static struct timeval t1, t2;

BOOL CH347_SPI_Init()
{
    BOOL ret = false;
    mSpiCfgS spiDev = { 0 };    

    // Init the SPI arg
    spiDev.iMode = 3;
    spiDev.iClock = 1;
    spiDev.iByteOrder = 1;
    spiDev.iSpiOutDefaultData = 0xFF;

    // Init CH347 SPI 
    ret = CH347SPI_Init(mindex, &spiDev);
    if (!ret) {
        err("Failed to init device");
        return false;
    }

    return true;
}

ULONG EndSwitch(ULONG dwVal)
{
    ULONG SV;
    ((PUCHAR)&SV)[0] = ((PUCHAR)&dwVal)[3];
    ((PUCHAR)&SV)[1] = ((PUCHAR)&dwVal)[2];
    ((PUCHAR)&SV)[2] = ((PUCHAR)&dwVal)[1];
    ((PUCHAR)&SV)[3] = ((PUCHAR)&dwVal)[0];
    return SV;
}

BOOL FLASH_IC_Check()
{
    unsigned int count;
    unsigned int Flash_ID = 0;
    unsigned int dat = 0;
    unsigned int iLength = 0;

    UCHAR mBuffer[16] = { 0 };
    memset(mBuffer+1, 0xFF, 3);
    mBuffer[0] = CMD_FLASH_JEDEC_ID;
    iLength = 3;
    if (CH347SPI_WriteRead(mindex, 0x80, iLength + 1, mBuffer) == false)
        return (0xFFFFFFFF);
    else
    {
        mBuffer[0] = 0;
        memcpy(&dat, mBuffer, 4);
    }

    Flash_ID = EndSwitch(dat);
    printf("  Flash_ID: %X\n", Flash_ID);
}

unsigned int FLASH_RD_Block(unsigned int address, UCHAR *pbuf, unsigned int len)
{
    /* W25系列FLASH、SST系列FLASH */
    ULONG iLen = 0;
    UCHAR DBuf[8192] = {0};

    DBuf[0] = CMD_FLASH_READ;
    DBuf[1] = (UCHAR)(address >> 16);
    DBuf[2] = (UCHAR)(address >> 8);
    DBuf[3] = (UCHAR)(address);

    iLen = len;
    if (!CH347SPI_Read(mindex, 0x80, 4, &iLen, DBuf))
    {
        printf("FLASH_RD_Block %ld bytes failure.", iLen);
        return 0;
    }
    else
    {
        memcpy(pbuf, DBuf, len);
        return len;
    }
}

// FLASH字节读
BOOL FlashBlockRead()
{
    double UseT;
    ULONG DataLen, FlashAddr = 0, i;
    UCHAR DBuf[8192] = {0};
    CHAR FmtStr[512] = "", FmtStr1[8 * 1024 * 3 + 16] = "";

    if (!FlashDevIsOpened)
    {
        printf("请先打开设备");
        return false;
    }

    //获取FLASH读的起始地址
    FlashAddr = 0x00;
    //获取FLASH读的字节数,十六进制
    DataLen = 0x500;

    gettimeofday(&t1, NULL);
    DataLen = FLASH_RD_Block(FlashAddr, DBuf, DataLen);
    gettimeofday(&t2, NULL);

    int data_sec = t2.tv_sec - t1.tv_sec;
    int data_usec = t2.tv_usec - t1.tv_usec;

    UseT = ((float)data_sec + (float)data_usec / 1000000);

    if (DataLen < 1)
    {
        printf(">>Flash读:从[%lX]地址开始读入%ld字节...失败.\n", FlashAddr, DataLen);
    }
    else
    {
        printf(">>Flash读:从[%lX]地址开始读入%ld字节...成功.用时%.3fS\n", FlashAddr, DataLen, UseT);
        { //显示FLASH数据,16进制显示
            for (i = 0; i < DataLen; i++)
                sprintf(&FmtStr1[strlen(FmtStr1)], "%02X ", DBuf[i]);
            printf("Read: \n%s\n", FmtStr1);
        }
    }
    return true;
}

BOOL FLASH_WriteEnable()
{
    ULONG iLen = 0;
    UCHAR DBuf[128] = {0};

    DBuf[0] = CMD_FLASH_WREN;
    iLen = 0;
    return CH347SPI_WriteRead(mindex, 0x80, iLen + 1, DBuf);
}

BOOL CH34xFlash_Wait()
{
    ULONG mLen, iChipselect;
    UCHAR mWrBuf[3];
    UCHAR status;
    mLen = 3;
    iChipselect = 0x80;
    mWrBuf[0] = CMD_FLASH_RDSR;
    do{
        mWrBuf[0] = CMD_FLASH_RDSR;
        if( CH347StreamSPI4( mindex, iChipselect, mLen, mWrBuf ) == false )
            return false;       
        status = mWrBuf[1];
    }while( status & 1 );   
    return true;
}

BOOL CH34xSectorErase(ULONG StartAddr )
{
    ULONG mLen, iChipselect;
    UCHAR mWrBuf[4];
    if( FLASH_WriteEnable(mindex) == false )
        return false;
    mWrBuf[0] = CMD_FLASH_SECTOR_ERASE;
    mWrBuf[1] = (UCHAR)( StartAddr >> 16 & 0xff );
    mWrBuf[2] = (UCHAR)( StartAddr >> 8 & 0xf0 );
    mWrBuf[3] = 0x00;
    mLen = 4;   
    iChipselect = 0x80;
    if( CH347StreamSPI4( mindex, iChipselect, mLen, mWrBuf ) == false )
        return false;

    if( CH34xFlash_Wait() == false )
        return false;
    return true;        
}

BOOL W25XXX_WR_Page(PUCHAR pBuf, ULONG address, ULONG len)
{
    ULONG iChipselect = 0x80;
    UCHAR mWrBuf[8192];

    if( !FLASH_WriteEnable() )
            return false;
        
    mWrBuf[0] = CMD_FLASH_BYTE_PROG;
    mWrBuf[1] = (UCHAR)(address >> 16);
    mWrBuf[2] = (UCHAR)(address >> 8);
    mWrBuf[3] = (UCHAR)address;
    memcpy(&mWrBuf[4], pBuf, len);

    if( CH347SPI_Write( mindex, iChipselect, len+4, SPI_FLASH_PerWritePageSize+4,mWrBuf) == false )
        return false;
    memset( mWrBuf, 0, sizeof( UCHAR ) * len );
    if( !CH34xFlash_Wait() )
        return false;
}

BOOL FlashBlockWrite()
{
    ULONG i = 0;
    ULONG DataLen, FlashAddr, BeginAddr, NumOfPage, NumOfSingle;
    UCHAR DBuf[8 * 1024 + 16] = {0};
    UCHAR FmtStr[8 * 1024 * 3 + 16] = "", ValStr[16] = "";
    PUCHAR pbuf;
    double BT, UseT;

    //获取写FLASH的起始地址,十六进制
    FlashAddr = 0x00;
    BeginAddr = FlashAddr;
    //获取写FLASH的字节数,十六进制
    DataLen = 0;

    for (i = 0; i < 1280; i++)
    {
        DBuf[i] = 0x55;
        DataLen++;
    }

    pbuf = DBuf;

    NumOfPage = DataLen / SPI_FLASH_PerWritePageSize;
    NumOfSingle = DataLen % SPI_FLASH_PerWritePageSize;

    gettimeofday(&t1, NULL);
    if (NumOfPage == 0)
    {
        W25XXX_WR_Page(DBuf, FlashAddr, DataLen);
    } else 
    {
        while (NumOfPage--)
        {
            W25XXX_WR_Page(pbuf, FlashAddr, SPI_FLASH_PerWritePageSize);
            pbuf += SPI_FLASH_PerWritePageSize;
            FlashAddr += SPI_FLASH_PerWritePageSize;
        }
        if (NumOfSingle)
            W25XXX_WR_Page(pbuf, FlashAddr, NumOfSingle);
    }
    gettimeofday(&t2, NULL);

    int data_sec = t2.tv_sec - t1.tv_sec;
    int data_usec = t2.tv_usec - t1.tv_usec;

    UseT = ((float)data_sec + (float)data_usec / 1000000);

    if (DataLen < 1)
    {
        printf(">>Flash写:从[%lX]地址开始写入%ld字节...失败\n", BeginAddr, DataLen);
    }
    else
    {
        printf(">>Flash写:从[%lX]地址开始写入%ld字节...成功.用时%.3fS\n", BeginAddr, DataLen, UseT / 1000);
    }
    return true;
}

int main()
{
    BOOL ret = false;

    // Open the device
    mindex = CH347OpenDevice(0);
    // mindex = open("/dev/hidraw1", O_RDWR);
    if (mindex < 0) {
        printf("Failed to open device.\n");
        return -1;
    }

    FlashDevIsOpened = true;
    // Init the SPI controler    
    ret = CH347_SPI_Init();
    if (!ret) {
        err("Failed to init CH347 SPI.");
        exit(-1);
    }

    // Read the Flash ID
    ret = FLASH_IC_Check();
    if (!ret) {
        err("Failed to find flash");
        exit(-1);
    }

    // Read the Flash data
    ret = FlashBlockRead();
    if (!ret) {
        err("Failed to read flash");
        exit(-1);
    }

    // Erase the flash data
    ret = CH34xSectorErase(0x00);
    if (!ret) {
        err("Failed to erase flash");
        exit(-1);
    }

    // Write the flash data
    ret = FlashBlockWrite();
    if (!ret) {
        err("Failed to write flash");
        exit(-1);
    }

    // Check the flash data
   ret = FlashBlockRead();
    if (!ret) {
        err("Failed to read flash");
        exit(-1);
    }

    // Close the CH347 Device
    if (CH347CloseDevice(mindex))
    {
        FlashDevIsOpened = false;
        printf("Close device succesed\n");
    }

    return 0;
}

执行截图:


网站公告

今日签到

点亮在社区的每一天
去签到