18-通过USB-CDC发送命令读写EEPROM测试程序,STC32G144K246实验箱演示程序
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缓冲长度
/****************本地变量声明**************/
u8temp; //EEPROM操作缓冲
u16 Key1_cnt;
bit Key1_Flag;
bit Key1_Function;
/****************本地函数声明**************/
void delay_ms(u16 ms);
u8 CheckData(u8 dat);
u32GetAddress(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)
{
u8i,j;
u32 addr;
u8status;
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 == ' ') && (UsbOutBuffer == ' ')) //最短命令为10个字节
{
for(i=0; i<10; i++)
{
if((UsbOutBuffer >= 'a') && (UsbOutBuffer <= 'z')) UsbOutBuffer = UsbOutBuffer - 'a' + 'A';//小写转大写
}
addr = GetAddress();
if(addr < 0x00ffffff)
{
if(UsbOutBuffer == 'W') //写入N个字节
{
j = OutNumber - 11;
if(j > Buf_Length)j = Buf_Length; //越界检测
EEPROM_SectorErase(addr); //擦除扇区
i = EEPROM_write_n(addr,&UsbOutBuffer,j); //写N个字节
if(i == 0)
printf_usb("已写入%u字节内容!\r\n",j);
else printf_usb("写入错误!\r\n");
status = 0; //命令正确
}
else if(UsbOutBuffer == '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;
u8i,j;
address = 0;
if((UsbOutBuffer == '0') && (UsbOutBuffer == 'X'))
{
for(i=4; i<10; i++)
{
j = CheckData(UsbOutBuffer);
if(j >= 0x10) return 0xffffffff; //error
address = (address << 4) + j;
}
return (address);
}
return0xffffffff;//error
}
/**************** 获取要读出数据的字节数 ****************************/
u8 GetDataLength(void)
{
u8i;
u8length;
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)
{
u8i;
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;
}
}
页:
[1]