找回密码
 立即注册
查看: 31|回复: 0

18-通过USB-CDC发送命令读写EEPROM测试程序,STC32G144K246实验箱演示程序

[复制链接]
  • 打卡等级:常住居民I
  • 打卡总天数:78
  • 最近打卡:2026-07-21 13:27:09
已绑定手机

231

主题

168

回帖

1069

积分

版主

积分
1069
发表于 2026-6-29 16:01:43 | 显示全部楼层 |阅读模式
18-通过USB-CDC发送命令读写EEPROM测试程序,STC32G144K246实验箱演示程序
主程序main.C代码如下(汇编版本在附件中):


/*---------------------------------------------------------------------*/
/* --- Web: www.STCAI.com ---------------------------------------------*/
/* --- BBS: www.STCAIMCU.com  -----------------------------------------*/
/* 如果要在程序中使用此代码,请在程序中注明使用了STC的资料及程序        */
/*---------------------------------------------------------------------*/



/*************  本程序功能说明  **************

本例程基于STC32G144K246为主控芯片的实验箱进行编写测试。

使用Keil C251编译器,Memory Model推荐设置XSmall模式,默认定义变量在edata,单时钟存取访问速度快。

edata建议保留1K给堆栈使用,空间不够时可将大数组、不常用变量加xdata关键字定义到xdata空间。

通过USB-CDC对STC内部自带的EEPROM(FLASH)进行读写测试。

对FLASH做扇区擦除、写入、读出的操作,命令指定地址。

默认波特率:  115200,8,N,1.

串口命令设置: (命令字母不区分大小写)
    W 0x000040 1234567890  --> 对0x000040地址写入字符1234567890.
    R 0x000040 10          --> 对0x000040地址读出10个字节数据.

注意:下载时,下载界面"硬件选项"中设置用户EEPROM大小,

并确保串口命令中的地址在EEPROM设置的大小范围之内。

下载时, 选择IRC频率 48MHz,主时钟使用 PLL 90MHz 时钟频率.

******************************************/

#include "..\comm\STC32G144K246.h"
#include "ai_usb.h"

/****************  用户定义宏  ****************/

#define SEL_HPLL1       1
#define SEL_HPLL2       2
#define HPLL_SEL        SEL_HPLL2

#if(HPLL_SEL==SEL_HPLL1)
    #define MAIN_Fosc   120000000L //定义主时钟(超频)
#else
    #define MAIN_Fosc   90000000L  //定义主时钟
#endif

#define Buf_Length      64      //读写EEPROM缓冲长度

/****************  本地变量声明  **************/

u8  temp[Buf_Length];        //EEPROM操作缓冲

u16 Key1_cnt;
bit Key1_Flag;
bit Key1_Function;

/****************  本地函数声明  **************/

void delay_ms(u16 ms);
u8   CheckData(u8 dat);
u32  GetAddress(void);
u8   GetDataLength(void);
void EEPROM_SectorErase(u32 EE_address);
void EEPROM_read_n(u32 EE_address,u8 *DataAddress,u8 length);
u8   EEPROM_write_n(u32 EE_address,u8 *DataAddress,u8 length);
void KeyScan(void);
void HPLL_config(void);

/***************** 主函数 *********************/
void main(void)
{
    u8  i,j;
    u32 addr;
    u8  status;

    EAXFR = 1;      //扩展寄存器(XFR)访问使能
    CKCON = 0;      //提高访问XRAM速度

    P3PU = 0x04;    //使能P3.2口上拉

    HPLL_config();
    usb_init();     //USB CDC 接口配置
    EA = 1;         //允许总中断

    while(1)
    {
        delay_ms(1);

        KeyScan();
        if(Key1_Function)
        {
            Key1_Function = 0;
            printf_usb("STC32G系列单片机EEPROM测试程序,串口命令设置如下:\r\n");    //USB-CDC发送一个字符串
            printf_usb("W 0x000040 1234567890  --> 对0x000040地址写入字符1234567890.\r\n");   //USB-CDC发送一个字符串
            printf_usb("R 0x000040 10          --> 对0x000040地址读出10个字节内容.\r\n"); //USB-CDC发送一个字符串
        }

        if(bUsbOutReady)     //超时计数
        {
              for(i=0; i<OutNumber; i++)    printf_usb("%c",UsbOutBuffer);   //把收到的数据原样返回,用于测试

                status = 0xff;  //状态给一个非0值
                if((OutNumber >= 12) && (UsbOutBuffer[1] == ' ') && (UsbOutBuffer[10] == ' ')) //最短命令为10个字节
                {
                    for(i=0; i<10; i++)
                    {
                        if((UsbOutBuffer >= 'a') && (UsbOutBuffer <= 'z'))    UsbOutBuffer = UsbOutBuffer - 'a' + 'A';  //小写转大写
                    }
                    addr = GetAddress();
                    if(addr < 0x00ffffff)
                    {
                        if(UsbOutBuffer[0] == 'W')    //写入N个字节
                        {
                            j = OutNumber - 11;
                            if(j > Buf_Length)  j = Buf_Length; //越界检测
                            EEPROM_SectorErase(addr);           //擦除扇区
                            i = EEPROM_write_n(addr,&UsbOutBuffer[11],j);      //写N个字节
                            if(i == 0)
                                 printf_usb("已写入%u字节内容!\r\n",j);
                            else printf_usb("写入错误!\r\n");
                            status = 0; //命令正确
                        }

                        else if(UsbOutBuffer[0] == 'R')   //PC请求返回N字节EEPROM数据
                        {
                            j = GetDataLength();
                            if(j > Buf_Length)  j = Buf_Length; //越界检测
                            if(j > 0)
                            {
                                EEPROM_read_n(addr,temp,j);
                                printf_usb("读出%u个字节内容如下:\r\n",j);
                                for(i=0; i<j; i++)  printf_usb("%c",temp);
                                printf_usb("\r\n");
                                status = 0; //命令正确
                            }
                        }
                    }
                }
                if(status != 0)
                {
                    printf_usb("命令错误!\r\n");
                    printf_usb("STC32G系列单片机EEPROM测试程序,串口命令设置如下:\r\n");    //USB-CDC发送一个字符串
                    printf_usb("W 0x000040 1234567890  --> 对0x000040地址写入字符1234567890.\r\n");   //USB-CDC发送一个字符串
                    printf_usb("R 0x000040 10          --> 对0x000040地址读出10个字节内容.\r\n"); //USB-CDC发送一个字符串
                }

            usb_OUT_done();
        }
    }
}

//========================================================================
// 函数: void HPLL_config(void)
// 描述: PLL时钟配置函数。
// 参数: none.
// 返回: none.
// 版本: VER1.0
// 日期: 2025-11-01
// 备注:
//========================================================================
void HPLL_config(void)
{
#if(HPLL_SEL==SEL_HPLL2)
    WTST = 2;           //通过WTST增加等待时钟控制Flash读取速度在33MHz以内, 主时钟:360MHz/2/2=90MHz, 读Flash速度:90MHz/(1+2)=30MHz

    HPLLCR |= 0x80;     //使能HPLL(高速外设默认使用HPLL时钟,切换到HPLL2前要先使能HPLL,切换完再关闭HPLL)

                        //首先需要将HIRC主频调节到48MHz
    HPLL2CR &= ~0x10;   //选择HPLL2输入时钟源为HIRC
//    HPLL2CR |= 0x10;    //选择HPLL2输入时钟源为IRCM
    HPLL2PDIV = 8;      //设置HPLL2输入时钟预分频为8(HPLL输入频率必须为6MHz)
//    HPLL2CR |= 0x00;    //HPLL2=6MHz*52=312MHz
//    HPLL2CR |= 0x01;    //HPLL2=6MHz*54=324MHz
//    HPLL2CR |= 0x02;    //HPLL2=6MHz*56=336MHz
//    HPLL2CR |= 0x03;    //HPLL2=6MHz*58=348MHz
    HPLL2CR |= 0x04;    //HPLL2=6MHz*60=360MHz
//    HPLL2CR |= 0x05;    //HPLL2=6MHz*62=372MHz
//    HPLL2CR |= 0x06;    //HPLL2=6MHz*64=384MHz
//    HPLL2CR |= 0x07;    //HPLL2=6MHz*66=396MHz
//    HPLL2CR |= 0x08;    //HPLL2=6MHz*68=408MHz
//    HPLL2CR |= 0x09;    //HPLL2=6MHz*70=420MHz
//    HPLL2CR |= 0x0a;    //HPLL2=6MHz*72=432MHz
//    HPLL2CR |= 0x0b;    //HPLL2=6MHz*74=444MHz
//    HPLL2CR |= 0x0c;    //HPLL2=6MHz*76=456MHz
//    HPLL2CR |= 0x0d;    //HPLL2=6MHz*78=468MHz
//    HPLL2CR |= 0x0e;    //HPLL2=6MHz*80=480MHz
//    HPLL2CR |= 0x0f;    //HPLL2=6MHz*82=492MHz
    HPLL2CR |= 0x20;    //高速外设时钟选择 HPLL2/2
    HPLL2CR |= 0x80;    //使能HPLL2

    HPLLCR &= ~0x80;    //关闭HPLL(高速外设默认使用HPLL时钟,切换到HPLL2前要先使能HPLL,切换完再关闭HPLL)

    CLKDIV = 2;         //系统时钟 = 主时钟源/2 = HPLL2/2/2 = 90MHz
    CLKSEL = 0x08;      //选择HPLL2/2作为主时钟源

#else
    WTST = 3;           //通过WTST增加等待时钟控制Flash读取速度在33MHz以内, 主时钟:480MHz/2/2=120MHz, 读Flash速度:120MHz/(1+3)=30MHz
                        //首先需要将HIRC主频调节到48MHz
    HPLLCR &= ~0x10;    //选择HPLL输入时钟源为HIRC
//    HPLLCR |= 0x10;     //选择HPLL输入时钟源为IRCM
    HPLLPDIV = 8;       //设置HPLL输入时钟预分频为8(HPLL输入频率必须为6MHz)
//    HPLLCR |= 0x00;     //HPLL=6MHz*52=312MHz
//    HPLLCR |= 0x01;     //HPLL=6MHz*54=324MHz
//    HPLLCR |= 0x02;     //HPLL=6MHz*56=336MHz
//    HPLLCR |= 0x03;     //HPLL=6MHz*58=348MHz
//    HPLLCR |= 0x04;     //HPLL=6MHz*60=360MHz
//    HPLLCR |= 0x05;     //HPLL=6MHz*62=372MHz
//    HPLLCR |= 0x06;     //HPLL=6MHz*64=384MHz
//    HPLLCR |= 0x07;     //HPLL=6MHz*66=396MHz
//    HPLLCR |= 0x08;     //HPLL=6MHz*68=408MHz
//    HPLLCR |= 0x09;     //HPLL=6MHz*70=420MHz
//    HPLLCR |= 0x0a;     //HPLL=6MHz*72=432MHz
//    HPLLCR |= 0x0b;     //HPLL=6MHz*74=444MHz
//    HPLLCR |= 0x0c;     //HPLL=6MHz*76=456MHz
//    HPLLCR |= 0x0d;     //HPLL=6MHz*78=468MHz
    HPLLCR |= 0x0e;     //HPLL=6MHz*80=480MHz
//    HPLLCR |= 0x0f;     //HPLL=6MHz*82=492MHz
    HPLL2CR &= ~0x60;   //高速外设时钟选择 HPLL/2
    HPLLCR |= 0x80;     //使能HPLL

    CLKDIV = 2;         //系统时钟=主时钟源/2 = HPLL/2/2 = 120MHz
    CLKSEL = 0x04;      //选择HPLL/2作为主时钟源
#endif

//    P5M0 |= 0x10; P5M1 &= ~0x10;//设置P5.4口为推挽输出
//    P5SR &= ~0x10;              //设置P5.4口为快速模式
//    HIRCCR |= 0x10;             //输出系统时钟
//    MCLKOCR = 10;               //系统时钟 10 分频到P5.4
}

//========================================================================
// 函数: void delay_ms(unsigned int ms)
// 描述: 延时函数。
// 参数: ms,要延时的ms数.
// 返回: none.
// 版本: VER1.0
// 日期: 2025-11-01
// 备注: 由于芯片使能了Cache功能,软件延时时间可能不太准确
//========================================================================
void delay_ms(u16 ms)
{
    u16 i;
    do{
        i = MAIN_Fosc / 6000;
        while(--i);
    }while(--ms);
}

//========================================================================
// 函数: u8 CheckData(u8 dat)
// 描述: 将字符"0~9,A~F或a~f"转成十六进制.
// 参数: dat: 要检测的字符.
// 返回: 0x00~0x0F为正确. 0xFF为错误.
// 版本: V1.0, 2012-10-22
//========================================================================
u8 CheckData(u8 dat)
{
    if((dat >= '0') && (dat <= '9'))        return (dat-'0');
    if((dat >= 'A') && (dat <= 'F'))        return (dat-'A'+10);
    return 0xff;
}

//========================================================================
// 函数: u32 GetAddress(void)
// 描述: 计算各种输入方式的地址.
// 参数: 无.
// 返回: 24位EEPROM地址.
// 版本: V1.0, 2013-6-6
//========================================================================
u32 GetAddress(void)
{
    u32 address;
    u8  i,j;

    address = 0;
    if((UsbOutBuffer[2] == '0') && (UsbOutBuffer[3] == 'X'))
    {
        for(i=4; i<10; i++)
        {
            j = CheckData(UsbOutBuffer);
            if(j >= 0x10)   return 0xffffffff;   //error
            address = (address << 4) + j;
        }
        return (address);
    }
    return  0xffffffff;  //error
}

/**************** 获取要读出数据的字节数 ****************************/
u8 GetDataLength(void)
{
    u8  i;
    u8  length;

    length = 0;
    for(i=11; i<OutNumber; i++)
    {
        if(CheckData(UsbOutBuffer) >= 10)  break;
        length = length * 10 + CheckData(UsbOutBuffer);
    }
    return (length);
}

//========================================================================

#define IAP_STANDBY()   IAP_CMD = 0     //IAP空闲命令(禁止)
#define IAP_READ()      IAP_CMD = 1     //IAP读出命令
#define IAP_WRITE()     IAP_CMD = 2     //IAP写入命令
#define IAP_ERASE()     IAP_CMD = 3     //IAP擦除命令

#define IAP_ENABLE()    IAP_CONTR = IAP_EN; IAP_TPS = MAIN_Fosc / 1000000
#define IAP_DISABLE()   IAP_CONTR = 0; IAP_CMD = 0; IAP_TRIG = 0; IAP_ADDRE = 0xff; IAP_ADDRH = 0xff; IAP_ADDRL = 0xff

#define IAP_EN          (1<<7)
#define IAP_SWBS        (1<<6)
#define IAP_SWRST       (1<<5)
#define IAP_CMD_FAIL    (1<<4)

//========================================================================
// 函数: void DisableEEPROM(void)
// 描述: 禁止EEPROM.
// 参数: none.
// 返回: none.
// 版本: V1.0, 2014-6-30
//========================================================================
void DisableEEPROM(void)        //禁止访问EEPROM
{
    IAP_CONTR = 0;          //关闭 IAP 功能
    IAP_CMD = 0;            //清除命令寄存器
    IAP_TRIG = 0;           //清除触发寄存器
    IAP_ADDRE = 0xff;       //将地址设置到非 IAP 区域
    IAP_ADDRH = 0xff;       //将地址设置到非 IAP 区域
    IAP_ADDRL = 0xff;
}

//========================================================================
// 函数: void EEPROM_Trig(void)
// 描述: 触发EEPROM操作.
// 参数: none.
// 返回: none.
// 版本: V1.0, 2014-6-30
//========================================================================
void EEPROM_Trig(void)
{
    F0 = EA;    //保存全局中断
    EA = 0;     //禁止中断, 避免触发命令无效
    IAP_TRIG = 0x5A;
    IAP_TRIG = 0xA5;                    //先送5AH,再送A5H到IAP触发寄存器,每次都需要如此
                                        //送完A5H后,IAP命令立即被触发启动
                                        //CPU等待IAP完成后,才会继续执行程序。
    _nop_();   //由于STC32G是多级流水线的指令系统,触发命令后建议加4个NOP,保证IAP_DATA的数据完成准备
    _nop_();
    _nop_();
    _nop_();
    EA = F0;    //恢复全局中断
}

//========================================================================
// 函数: void EEPROM_SectorErase(u32 EE_address)
// 描述: 擦除一个扇区.
// 参数: EE_address:  要擦除的EEPROM的扇区中的一个字节地址.
// 返回: none.
// 版本: V1.0, 2014-6-30
//========================================================================
void EEPROM_SectorErase(u32 EE_address)
{
    IAP_ENABLE();                       //设置等待时间,允许IAP操作,送一次就够
    IAP_ERASE();                        //宏调用, 送扇区擦除命令,命令不需改变时,不需重新送命令
                                        //只有扇区擦除,没有字节擦除,512字节/扇区。
                                        //扇区中任意一个字节地址都是扇区地址。
    IAP_ADDRE = (u8)(EE_address >> 16); //送扇区地址高字节(地址需要改变时才需重新送地址)
    IAP_ADDRH = (u8)(EE_address >> 8);  //送扇区地址中字节(地址需要改变时才需重新送地址)
    IAP_ADDRL = (u8)EE_address;         //送扇区地址低字节(地址需要改变时才需重新送地址)
    EEPROM_Trig();                      //触发EEPROM操作
    DisableEEPROM();                    //禁止EEPROM操作
}

//========================================================================
// 函数: void EEPROM_read_n(u32 EE_address,u8 *DataAddress,u8 lenth)
// 描述: 读N个字节函数.
// 参数: EE_address:  要读出的EEPROM的首地址.
//       DataAddress: 要读出数据的指针.
//       length:      要读出的长度
// 返回: 0: 写入正确.  1: 写入长度为0错误.  2: 写入数据错误.
// 版本: V1.0, 2014-6-30
//========================================================================
void EEPROM_read_n(u32 EE_address,u8 *DataAddress,u8 length)
{
    IAP_ENABLE();                           //设置等待时间,允许IAP操作,送一次就够
    IAP_READ();                             //送字节读命令,命令不需改变时,不需重新送命令
    do
    {
        IAP_ADDRE = (u8)(EE_address >> 16); //送地址高字节(地址需要改变时才需重新送地址)
        IAP_ADDRH = (u8)(EE_address >> 8);  //送地址中字节(地址需要改变时才需重新送地址)
        IAP_ADDRL = (u8)EE_address;         //送地址低字节(地址需要改变时才需重新送地址)
        EEPROM_Trig();                      //触发EEPROM操作
        *DataAddress = IAP_DATA;            //读出的数据送往
        EE_address++;
        DataAddress++;
    }while(--length);

    DisableEEPROM();
}

//========================================================================
// 函数: u8 EEPROM_write_n(u32 EE_address,u8 *DataAddress,u8 length)
// 描述: 写N个字节函数.
// 参数: EE_address:  要写入的EEPROM的首地址.
//       DataAddress: 要写入数据的指针.
//       length:      要写入的长度
// 返回: 0: 写入正确.  1: 写入长度为0错误.  2: 写入数据错误.
// 版本: V1.0, 2014-6-30
//========================================================================
u8 EEPROM_write_n(u32 EE_address,u8 *DataAddress,u8 length)
{
    u8  i;
    u16 j;
    u8  *p;

    if(length == 0) return 1;               //长度为0错误

    IAP_ENABLE();                           //设置等待时间,允许IAP操作,送一次就够
    i = length;
    j = EE_address;
    p = DataAddress;
    IAP_WRITE();                            //宏调用, 送字节写命令
    do
    {
        IAP_ADDRE = (u8)(EE_address >> 16); //送地址高字节(地址需要改变时才需重新送地址)
        IAP_ADDRH = (u8)(EE_address >> 8);  //送地址中字节(地址需要改变时才需重新送地址)
        IAP_ADDRL = (u8)EE_address;         //送地址低字节(地址需要改变时才需重新送地址)
        IAP_DATA  = *DataAddress;           //送数据到IAP_DATA,只有数据改变时才需重新送
        EEPROM_Trig();                      //触发EEPROM操作
        EE_address++;                       //下一个地址
        DataAddress++;                      //下一个数据
    }while(--length);                       //直到结束

    EE_address = j;
    length = i;
    DataAddress = p;
    i = 0;
    IAP_READ();                             //读N个字节并比较
    do
    {
        IAP_ADDRE = (u8)(EE_address >> 16); //送地址高字节(地址需要改变时才需重新送地址)
        IAP_ADDRH = (u8)(EE_address >> 8);  //送地址中字节(地址需要改变时才需重新送地址)
        IAP_ADDRL = (u8)EE_address;         //送地址低字节(地址需要改变时才需重新送地址)
        EEPROM_Trig();                      //触发EEPROM操作
        if(*DataAddress != IAP_DATA)        //读出的数据与源数据比较
        {
            i = 2;
            break;
        }
        EE_address++;
        DataAddress++;
    }while(--length);

    DisableEEPROM();
    return i;
}

//========================================================================
// 函数: void KeyScan(void)
// 描述: 按键扫描函数。
// 参数: none.
// 返回: none.
// 版本: VER1.0
// 日期: 2025-4-1
// 备注:
//========================================================================
void KeyScan(void)
{
        if(!P32)
        {
                if(!Key1_Flag)
                {
                        Key1_cnt++;
                        if(Key1_cnt >= 50)                //50ms防抖
                        {
                                Key1_Flag = 1;                        //设置按键状态,防止重复触发
                                Key1_Function = 1;
                        }
                }
        }
        else
        {
                Key1_cnt = 0;
                Key1_Flag = 0;
        }
}



18-通过USB-CDC发送命令读写EEPROM测试程序.zip

40.67 KB, 下载次数: 7

回复

使用道具 举报 送花

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|深圳国芯人工智能有限公司 ( 粤ICP备2022108929号-2 )

GMT+8, 2026-7-22 10:25 , Processed in 0.070511 second(s), 42 queries .

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表