这段串口处理函数,要慢慢读,我加了注释:
先大致看懂,细节慢慢理解:
else if((RX2_Buffer == 'W') && (RX2_Cnt >= 12) && (RX2_Buffer == ' ')) //写入N个字节
{
//检测要写入的空间是否为空
j = RX2_Cnt - 11;//要写入的字节数
for(i=0; i<j; i++)tmp = 0xff; //循环初始化,典型的数组遍历或缓冲区填充操作
/*将临时数组 tmp 的第 i 个元素赋值为 0xFF(即二进制 11111111)。
常见用途:
初始化:将缓冲区填充为固定值(如全1),可能用于后续校验或清空旧数据。
测试写入:模拟写入操作,检测硬件(如Flash、EEPROM)是否正常响应。
协议填充:某些通信协议要求空闲位或填充位为高电平(对应 0xFF)。存储设备(如Flash)的擦除或写入前准备。
而函数u8 SPI_Read_Compare(u32 addr, u8 *buffer, u16 size)的功能是
读出n个字节,跟指定的数据进行比较, 错误返回1,正确返回0
*/
i = SPI_Read_Compare(Flash_addr,tmp,j);
if(i > 0) //SPI_Read_Compare和全部是FF的数组对比,全部是FF返回0
{
PrintString2("要写入的地址为非空,不能写入,请先擦掉!\r\n");
}
else
{
SPI_Write_Nbytes(Flash_addr,&RX2_Buffer,j); //写N个字节
//在C语言中,数组前面加&表示取整个数组的地址
//&数组名的类型是指向整个数组的指针,而不是一个指向指针常量的指针
i = SPI_Read_Compare(Flash_addr,&RX2_Buffer,j); //比较写入的数据
if(i == 0)
{
PrintString2("已写入");
if(j >= 100) {UART2_TxByte((u8)(j/100+'0')); j = j % 100;}
if(j >= 10) {UART2_TxByte((u8)(j/10+'0')); j = j % 10;}
UART2_TxByte((u8)(j%10+'0'));
PrintString2("字节内容!\r\n");
}
else PrintString2("写入错误!\r\n");
}
F0 = 1;
}
else if((RX2_Buffer == 'R') && (RX2_Cnt >= 12) && (RX2_Buffer == ' ')) //读出N个字节
{
j = GetDataLength();
if((j > 0) && (j < EE_BUF_LENGTH))
{
SPI_Read_Nbytes(Flash_addr,tmp,j);
PrintString2("读出");
if(j>=100)UART2_TxByte((u8)(j/100+'0'));
UART2_TxByte((u8)(j%100/10+'0'));
UART2_TxByte((u8)(j%10+'0'));
PrintString2("个字节内容如下:\r\n");
for(i=0; i<j; i++)UART2_TxByte(tmp);
UART2_TxByte(0x0d);
UART2_TxByte(0x0a);
F0 = 1;
}
}
}
}
if(!F0) PrintString2("命令错误!\r\n");
主要注意细节:
我们先给F0 软件置0 ,等我们执行一段语句命令后,F0还能成功保持是0,就 执行if(!F0) PrintString2("命令错误!\r\n"); 打印“命令错误!”,不成功保持0,就说明我们这段语句正常执行了。本身这段语句读、写的时候不断检查有没有写错,读错,有错的话就让F0等于0 ----- 如果正确顺利的写入和读出了就让F0 = 1;
函数u8 SPI_Read_Compare(u32 addr, u8 *buffer, u16 size)比较重要:
我们仔细看:
我已经详细标了注释:
/************************************************************************
读出n个字节,跟指定的数据进行比较, 错误返回1,正确返回0
buffer先装填好要比较数组的值,长度也是u16 size
************************************************************************/
u8 SPI_Read_Compare(u32 addr, u8 *buffer, u16 size)
{
u8j;
if(size == 0) return 2;
if(!B_FlashOK)return 2;
while(CheckFlashBusy() > 0); //Flash忙检测
j = 0;
SPI_CE_Low(); //enable device
SPI_WriteByte(SFC_READ); //read command
SPI_WriteByte(((u8 *)&addr)); //设置起始地址
SPI_WriteByte(((u8 *)&addr));
SPI_WriteByte(((u8 *)&addr));
do
{
if(*buffer != SPI_ReadByte()) //receive byte and store at buffer
//接收字节并和存储在缓冲区中的buffer每个组员相比较
//如果按位读出来的和buffer相应组员相等,什么都不干
//不相等,j=1;跳出循环,返回 1,否则返回 0
{
j = 1;
break;
}
buffer++;
}while(--size); //read until no_bytes is reached
//读取直到没有更多的字节可以读取”或“读取直到达到文件末尾”。
SPI_CE_High(); //disable device
return j;
}
接着看 ---除扇区, 一个扇区4KB
我们也在函数上注释:
/************************************************
擦除扇区, 一个扇区4KB
入口参数: 无
出口参数: 无
************************************************/
void FlashSectorErase(u32 addr)
{
if(B_FlashOK)
{
FlashWriteEnable(); //使能Flash写命令
SPI_CE_Low();
if(B_FlashOK == 1)
{
SPI_WriteByte(SFC_SECTORER1); //发送#define SFC_SECTORER1 0xD7即PM25LV040 扇区擦除指令
}
else
{
SPI_WriteByte(SFC_SECTORER2); //发送#define SFC_SECTORER2 0x20 即W25Xxx 扇区擦除指令
}
SPI_WriteByte(((u8 *)&addr)); //设置起始地址
SPI_WriteByte(((u8 *)&addr));
SPI_WriteByte(((u8 *)&addr));
// (u8 *)&addr的意思是将addr的地址转换为unsigned char类型的指针。在C语言中,(type *)&variable是一种类型
//转换语法,它将variable的地址转换为type类型的指针。因此,(u8 *)&addr将addr的地址转换为unsigned char类型的指针
SPI_CE_High();
}
}
SPI 写一个字节
/************************************************************************/
void SPI_WriteByte(u8 out)
{
SPDAT = out;
while(SPIF == 0) ;
SPIF = 1; //清SPIF标志
WCOL = 1; //清WCOL标志
}
硬件写,程序很简单:写 等 清标志
好了,轮到SPI初始化:
/************************************************************************/
void SPI_init(void)
{
SSIG = 1; //忽略 SS 引脚功能,使用 MSTR 确定器件是主机还是从机
SPEN = 1; //使能 SPI 功能
DORD = 0; //先发送/接收数据的高位( MSB)
MSTR = 1; //设置主机模式
CPOL = 1; //SCLK 空闲时为高电平,SCLK 的前时钟沿为下降沿,后时钟沿为上升沿
CPHA = 1; //数据在 SCLK 前时钟沿驱动,后时钟沿采样
//采用漏极开路+上拉电阻方式驱动,需要调低SPI频率才能正常与Flash通信
SPCTL = (SPCTL & ~3) | 2; //SPI 时钟频率选择, 0: 4T, 1: 8T,2: 16T,3: 2T
// spctl 的最低两位设置为 10,即选择1/16倍时钟频率(1/16T)
/*
1. 位操作解释:
(SPCTL & ~3):这一步是清除 spctl 变量的最低两位。~3 是对3取反,即二进制的 11111111111111111111111111111100(假设是32位整数)。与 spctl 进行按位与操作,会清除最低两位。
| 2:这一步是将结果与2进行按位或操作。2的二进制是 10,所以这一步是将 spctl 的最低两位设置为 10。
2. 时钟频率选择:
0:1/4倍时钟频率(1/4T)
1:1/8倍时钟频率(1/8T)
2:1/16倍时钟频率(1/16T)
3:1/2倍时钟频率(1/2T)
这行代码将 spctl 的最低两位设置为 10,即选择1/16倍时钟频率(1/16T)。
通过位操作可以高效地配置硬件寄存器。
*/
P_SW1 = (P_SW1 & ~(3<<2)) | (1<<2); //IO口切换. 0: P1.2 /P5.4 P1.3 P1.4 P1.5, 1: P2.2 P2.3 P2.4 P2.5, 2: P5.4 P4.0 P4.1 P4.3, 3: P3.5 P3.4 P3.3 P3.2
/*
代码解释
位操作:
3<<2:将数字3左移2位,得到二进制值1100,即十进制的12。
~(3<<2):对1100取反,得到0011(假设是4位寄存器)。
p_sw1 & ~(3<<2):将p_sw1寄存器的第3位和第2位清零。
(1<<2):将数字1左移2位,得到二进制值0100,即十进制的4。
(p_sw1 & ~(3<<2)) | (1<<2):将p_sw1寄存器的第3位和第2位设置为01。
实际是 1 对应的 SPI功能脚切换为 P2.2 P2.3 P2.4 P2.5
*/
HSCLKDIV = 0x01; //高速时钟1分频,默认2分频
P_PM25LV040_SCK = 0; // set clock to low initial state
P_PM25LV040_SI = 1;
SPIF = 1; //清SPIF标志
WCOL = 1; //清WCOL标志
}
已经注释的很清楚了
/************************************************************************/
void SPI_WriteByte(u8 out)
{
SPDAT = out;
while(SPIF == 0) ;
SPIF = 1; //清SPIF标志
WCOL = 1; //清WCOL标志
}
/************************************************************************/
u8 SPI_ReadByte(void)
{
SPDAT = 0xff;
while(SPIF == 0) ;
SPIF = 1; //清SPIF标志
WCOL = 1; //清WCOL标志
return (SPDAT);
}
这个SPI写一个字节,读一个字节,因为是硬件读写,程序很简单
检测Flash是否准备就绪,B_FlashOK = 0就出错,B_FlashOK = 1 为PM25LVxx B_FlashOK = 2W25X4x系列B_FlashOK = 3 W25X8x系列
/************************************************
检测Flash是否准备就绪
入口参数: 无
出口参数:
0 : 没有检测到正确的Flash
1 : Flash准备就绪
************************************************/
void FlashCheckID(void)
{
SPI_CE_Low();
SPI_WriteByte(SFC_RDID); //发送读取ID命令
SPI_WriteByte(0x00); //空读3个字节
SPI_WriteByte(0x00);
SPI_WriteByte(0x00);
PM25LV040_ID1 = SPI_ReadByte(); //读取制造商ID1
PM25LV040_ID= SPI_ReadByte(); //读取设备ID
PM25LV040_ID2 = SPI_ReadByte(); //读取制造商ID2
SPI_CE_High();
// UART2_TxByte(PM25LV040_ID1);
// UART2_TxByte(PM25LV040_ID);
// UART2_TxByte(PM25LV040_ID2);
if((PM25LV040_ID1 == 0x9d) && (PM25LV040_ID2 == 0x7f))B_FlashOK = 1;//检测是否为PM25LVxx系列的Flash
else if(PM25LV040_ID == 0x12)B_FlashOK = 2; //检测是否为W25X4x系列的Flash
else if(PM25LV040_ID == 0x13)B_FlashOK = 3; //检测是否为W25X8x系列的Flash
else B_FlashOK = 0;
}
下面这2个函数,最好理解:
/************************************************
检测Flash的忙状态
入口参数: 无
出口参数:
0 : Flash处于空闲状态
1 : Flash处于忙状态
************************************************/
u8 CheckFlashBusy(void)
{
u8dat;
SPI_CE_Low();
SPI_
WriteByte(SFC_RDSR); //发送读取状态命令
dat = SPI_ReadByte(); //读取状态
SPI_CE_High();
return (dat); //状态值的Bit0即为忙标志
}
/************************************************
使能Flash写命令
入口参数: 无
出口参数: 无
************************************************/
void FlashWriteEnable(void)
{
while(CheckFlashBusy() > 0); //Flash忙检测
SPI_CE_Low();
SPI_WriteByte(SFC_WREN); //发送写使能命令
SPI_CE_High();
}
到此为止,所有的子函数都准备后了,下面看看主函数:
/********************* 主函数 *************************/
void main(void)
{
WTST = 0;//设置程序指令延时参数,赋值为0可将CPU执行指令的速度设置为最快
EAXFR = 1; //扩展寄存器(XFR)访问使能
CKCON = 0; //提高访问XRAM速度
P0M1 = 0x30; P0M0 = 0x30; //设置P0.4、P0.5为漏极开路(实验箱加了上拉电阻到3.3V)
P1M1 = 0x32; P1M0 = 0x32; //设置P1.1、P1.4、P1.5为漏极开路(实验箱加了上拉电阻到3.3V), P1.1在PWM当DAC电路通过电阻串联到P2.3
P2M1 = 0x00; P2M0 = 0x00; //设置为准双向口
P3M1 = 0x50; P3M0 = 0x50; //设置P3.4、P3.6为漏极开路(实验箱加了上拉电阻到3.3V)
P4M1 = 0x3c; P4M0 = 0x3c; //设置P4.2~P4.5为漏极开路(实验箱加了上拉电阻到3.3V)
P5M1 = 0x0c; P5M0 = 0x0c; //设置P5.2、P5.3为漏极开路(实验箱加了上拉电阻到3.3V)
P6M1 = 0xff; P6M0 = 0xff; //设置为漏极开路(实验箱加了上拉电阻到3.3V)
P7M1 = 0x00; P7M0 = 0x00; //设置为准双向口
delay_ms(10);
UART2_config(2); // 选择波特率, 2: 使用Timer2做波特率, 其它值: 无效.
EA = 1; //打开总中断
PrintString2("命令设置:\r\n");
PrintString2("E 0x001234 --> 扇区擦掉十六进制地址\r\n");
PrintString2("W 0x001234 1234567890 --> 写入操作十六进制地址写入内容\r\n");
PrintString2("R 0x001234 10 --> 读出操作十六进制地址读出字节\r\n");
PrintString2("C --> 如果检测不到PM25LV040/W25X40CL/W25Q80BV, 发送C强制允许操作.\r\n\r\n");
SPI_init();
FlashCheckID();
FlashCheckID();
if(!B_FlashOK)PrintString2("未检测到PM25LV040/W25X40CL/W25Q80BV!\r\n");
else
{
if(B_FlashOK == 1)
{
PrintString2("检测到PM25LV040!\r\n");
}
else if(B_FlashOK == 2)
{
PrintString2("检测到W25X40CL!\r\n");
}
else if(B_FlashOK == 3)
{
PrintString2("检测到W25Q80BV!\r\n");
}
PrintString2("制造商ID1 = 0x");
UART2_TxByte(Hex2Ascii(PM25LV040_ID1 >> 4));
UART2_TxByte(Hex2Ascii(PM25LV040_ID1));
PrintString2("\r\n ID2 = 0x");
UART2_TxByte(Hex2Ascii(PM25LV040_ID2 >> 4));
UART2_TxByte(Hex2Ascii(PM25LV040_ID2));
PrintString2("\r\n 设备ID = 0x");
UART2_TxByte(Hex2Ascii(PM25LV040_ID >> 4));
UART2_TxByte(Hex2Ascii(PM25LV040_ID));
PrintString2("\r\n");
}
while(1)
{
delay_ms(1);
if(RX2_TimeOut > 0)
{
if(--RX2_TimeOut == 0)//超时,则串口接收结束
{
if(RX2_Cnt > 0)
{
RX2_Check(); //串口2处理数据
}
RX2_Cnt = 0;
}
}
}
}
还有3个子函数没有研究: