PIC16F1947驱动CH376芯片实现SD卡数据存储

在工业数据采集、便携仪表等应用中,经常需要把传感器数据保存到SD卡里,方便后续分析。但直接用单片机读写SD卡,得自己搞定SD卡命令、FAT文件系统,工作量巨大。用CH376文件管理芯片,一切就简单了——它内置了FAT32/FAT16文件系统和SD卡驱动,单片机只需通过串口、SPI或并口发送几条指令,就能像操作电脑文件一样读写SD卡。

今天,就用PIC16F1947通过SPI接口控制CH376,实现SD卡的初始化、文件创建、数据写入和读取。

一、CH376芯片简介

CH376是南京沁恒微电子推出的USB/SD卡文件管理控制芯片。它支持三种通信接口:8位并行总线、SPI串行总线、异步串口。对于PIC16F1947这种只有两个串口的单片机,串口需要接触摸屏和一路RS485,所以我使用的是SPI接口,只需4根线(SCSSCKSDISDO),外加一个中断引脚。

核心功能                                                                               

  • 内置FAT12/FAT16/FAT32文件系统固件
  • 支持SD卡和U
  • 提供文件打开、读写、创建、删除等命令
  • 以字节或扇区为单位读写

二、PIC16F1947CH376SPI模式)

要让CH376工作于SPI从机模式,需要在复位时配置引脚电平。CH376在上电复位时会采样RXDTXDWR#RD#PCS#AO引脚,若它们均为高电平或悬空,则选择SPI接口。

  • 如果使用5V供电,V3引脚必须接一个0.01μF电容到地;如果使用3.3V供电,则V3直接接VCC,此处PIC16F1947可以3.3V供电,所以CH376也使用3.3V供电。
  • 晶振须用12MHz

SD卡插座CH376可以直接连接SD卡,它的SD_CSSD_CKSD_DISD_DO引脚直接接SD卡座对应引脚,无需额外电路。注意SD卡电源也要接3.3V

三、软件设计:PIC16F1947CH376SPI通信

CH376SPI接口支持模式0和模式3CPOL=0, CPHA=0CPOL=1, CPHA=1),数据位顺序为MSB firstSPI时钟频率建议不超过2MHz

3.1 底层SPI读写函数

初始化代码如下:

#include <xc.h>
#include <pic16f1947.h>

// 引脚定义
#define CH376_SCS     RC0   // 片选,低有效
#define CH376_SCK     RC3
#define CH376_SDI     RC4   // MOSI
#define CH376_SDO     RC5   // MISO
#define CH376_INT     RB0   // 中断输入

// 宏定义操作
#define CS_LOW()     RC0 = 0
#define CS_HIGH()    RC0 = 1

// SPI初始化(主模式,时钟Fosc/4,空闲低,上升沿采样)
void SPI_Init(void) {
    SSP1STAT = 0x40;   // CKE=1, 数据在时钟上升沿发送
    SSP1CON1 = 0x20;   // SSPEN=1, 时钟Fosc/4, CKP=0
    TRISC3 = 0;        // SCK 输出
    TRISC4 = 0;        // SDI 输出
    TRISC5 = 1;        // SDO 输入
}

// SPI交换一个字节
unsigned char SPI_Transfer(unsigned char dat) {
    SSP1BUF = dat;
    while(!SSP1STATbits.BF);
    return SSP1BUF;
}

// 向CH376写命令(一个字节)
void CH376_WriteCmd(unsigned char cmd) {
    CS_LOW();
    SPI_Transfer(cmd);
    CS_HIGH();
}

// 向CH376写数据(一个或多个字节)
void CH376_WriteData(unsigned char *buf, unsigned char len) {
    CS_LOW();
    while(len--) {
        SPI_Transfer(*buf++);
    }
    CS_HIGH();
}

// 从CH376读数据
void CH376_ReadData(unsigned char *buf, unsigned char len) {
    CS_LOW();
    while(len--) {
        *buf++ = SPI_Transfer(0xFF);
    }
    CS_HIGH();
}

注意CH376SPI操作中,每发送一个命令后,需要等待芯片完成内部处理(可以查询BZ引脚或延时,或通过中断等待)。一般可以在发送命令后,延时一小段时间再读数据。

3.2 通用命令执行函数

大多数CH376命令需要等待中断(INT变低)。可编写一个函数,发送命令并等待中断,然后获取中断状态。

// 等待CH376中断(超时返回0)
unsigned char CH376_WaitInt(unsigned int timeout_ms) {
    while(timeout_ms--) {
        if(RB0 == 0) return 1;   // 中断发生
        __delay_ms(1);
    }
    return 0;
}

// 执行命令并等待中断,返回中断状态
unsigned char CH376_ExecCmd(unsigned char cmd) {
    CH376_WriteCmd(cmd);
    if(!CH376_WaitInt(500)) return 0xFF;   // 超时
    // 获取中断状态
    CH376_WriteCmd(0x22);   // CMD_GET_STATUS
    unsigned char status = SPI_Transfer(0xFF);
    CS_HIGH();
    return status;
}

四、初始化流程

使用CH376操作SD卡之前,先将其设置为“SD卡主机模式,然后检测并初始化SD卡。

// 初始化CH376并挂载SD卡
unsigned char SD_Init(void) {
    // 1. 复位CH376
    CH376_WriteCmd(0x05);   // CMD_RESET_ALL
    __delay_ms(50);
    
    // 2. 设置USB模式为SD卡主机模式(模式3)
    CH376_WriteCmd(0x15);   // CMD_SET_USB_MODE
    CH376_WriteData(&(unsigned char){0x03}, 1);  // 模式3:SD卡主机
    __delay_us(20);
    // 读取操作状态(可选)
    CH376_WriteCmd(0x22);
    unsigned char status = SPI_Transfer(0xFF);
    CS_HIGH();
    if(status != 0x51) return 0;   // 不是成功
    
    // 3. 检测SD卡是否插入(硬件检测,这里假设已插入)
    // 可选:发送CMD_DISK_CONNECT查询
    
    // 4. 初始化磁盘(挂载)
    status = CH376_ExecCmd(0x31);   // CMD_DISK_MOUNT
    if(status != 0x14) return 0;    // USB_INT_SUCCESS
    
    return 1;  // 成功
}

五、文件写入示例:创建新文件并写入数据

假设我们要创建一个名为“DATA.TXT”的文件,写入“Hello World!”

// 写入字符串到文件
void Write_File(void) {
    unsigned char status;
    
    // 1. 设置文件名
    // 命令:CMD_SET_FILE_NAME (0x2F) 后跟字符串(以0结尾)
    CH376_WriteCmd(0x2F);
    CS_LOW();
    SPI_Transfer('/');      // 根目录
    SPI_Transfer('D');
    SPI_Transfer('A');
    SPI_Transfer('T');
    SPI_Transfer('A');
    SPI_Transfer('.');
    SPI_Transfer('T');
    SPI_Transfer('X');
    SPI_Transfer('T');
    SPI_Transfer(0x00);     // 字符串结束
    CS_HIGH();
    
    // 2. 新建文件(CMD_FILE_CREATE, 0x34)
    status = CH376_ExecCmd(0x34);
    if(status != 0x14) {
        // 错误处理
        return;
    }
    
    // 3. 写入数据:使用字节写命令
    unsigned char data[] = "Hello World!";
    unsigned int len = sizeof(data)-1;
    
    // CMD_BYTE_WRITE (0x3C) 需要先写入长度(2字节,低字节在前)
    CH376_WriteCmd(0x3C);
    CS_LOW();
    SPI_Transfer(len & 0xFF);
    SPI_Transfer(len >> 8);
    CS_HIGH();
    
    // 等待第一次中断(请求数据)
    if(!CH376_WaitInt(500)) return;
    CH376_WriteCmd(0x22);
    unsigned char int_status = SPI_Transfer(0xFF);
    CS_HIGH();
    if(int_status != 0x1E) return;  // USB_INT_DISK_WRITE
    
    // 写入数据块(CMD_WR_REQ_DATA, 0x2D)
    CH376_WriteCmd(0x2D);
    // 首先读取数据块长度(1字节)
    CS_LOW();
    unsigned char block_len = SPI_Transfer(0xFF);
    if(block_len > 0) {
        for(unsigned char i=0; i<block_len; i++) {
            if(i < len) SPI_Transfer(data[i]);
            else SPI_Transfer(0);
        }
    }
    CS_HIGH();
    
    // 继续写(CMD_BYTE_WR_GO, 0x3D)
    status = CH376_ExecCmd(0x3D);
    if(status != 0x14) return;
    
    // 最后关闭文件并更新长度
    CH376_WriteCmd(0x36);   // CMD_FILE_CLOSE
    CS_LOW();
    SPI_Transfer(1);        // 允许更新长度
    CS_HIGH();
    CH376_WaitInt(500);
}

六、读取文件示例

void Read_File(void) {
    unsigned char status;
    // 1. 设置文件名(同上)
    // 2. 打开文件(CMD_FILE_OPEN, 0x32)
    status = CH376_ExecCmd(0x32);
    if(status != 0x14) return;
    
    // 3. 读取文件长度(可选)
    CH376_WriteCmd(0x0C);   // CMD_GET_FILE_SIZE
    CS_LOW();
    SPI_Transfer(0x68);     // 固定输入68H
    CS_HIGH();
    unsigned long file_len;
    CH376_ReadData((unsigned char*)&file_len, 4);  // 小端格式
    
    // 4. 读取数据:一次读64字节
    unsigned char buf[64];
    unsigned short remain = file_len;
    while(remain) {
        unsigned short req = (remain > 64) ? 64 : remain;
        CH376_WriteCmd(0x3A);   // CMD_BYTE_READ
        CS_LOW();
        SPI_Transfer(req & 0xFF);
        SPI_Transfer(req >> 8);
        CS_HIGH();
        
        // 等待中断
        if(!CH376_WaitInt(500)) break;
        CH376_WriteCmd(0x22);
        unsigned char int_st = SPI_Transfer(0xFF);
        CS_HIGH();
        if(int_st != 0x1D) break;  // USB_INT_DISK_READ
        
        // 读数据块
        CH376_WriteCmd(0x2D);   // CMD_WR_REQ_DATA? 其实是读数据用CMD_RD_USB_DATA,这里注意
        // 正确的是:CMD_RD_USB_DATA (0x27)
        CH376_WriteCmd(0x27);
        CS_LOW();
        unsigned char block_len = SPI_Transfer(0xFF);
        for(unsigned char i=0; i<block_len; i++) {
            buf[i] = SPI_Transfer(0xFF);
        }
        CS_HIGH();
        remain -= block_len;
        
        // 继续读
        CH376_ExecCmd(0x3B);   // CMD_BYTE_RD_GO
    }
    // 关闭文件
    CH376_ExecCmd(0x36);  // 带参数0
}

七、常见问题与调试经验

  1. SPI时序:确保SCK空闲时为低电平,上升沿采样,下降沿输出。
  2. 中断处理CH376完成命令后INT变低,单片机读取状态后自动清除中断。如果不用中断引脚,可以轮询CMD_GET_STATUS,但效率低。
  3. 晶振:必须12MHz,否则SD卡通信会失败。
  4. 初始化失败:检查CH376V3电容、电源纹波。上电后延时100ms再发命令。
  5. 文件系统SD卡必须先格式化为FAT32FAT16
  6. 长文件名CH376默认支持短文件名,若需长文件名需额外配置。

八、扩展应用

  • 配置文件读取:从SD卡读取参数,动态修改系统设置。
  • 固件升级:将新固件放在SD卡,单片机读取后写入Flash

九、总结

通过PIC16F1947SPI接口控制CH376,可以快速实现SD卡的数据存储功能,省去自己编写FAT文件系统和SD卡驱动的麻烦。CH376的命令集清晰,开发周期短,非常适合嵌入式数据记录场景。

后续干货不断,咱们一起在单片机的世界里,共同进步。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值