编者的话
Flash 编程与烧写,原本应该是开发的最后一步,当所有程序都做好了,在线编译运行正常,才会通过 Flash 编程,生成二进制的可执行文件 LDR,再通过 JTAG 仿真器将 LDR 文件烧写到 Flash 中,上电 BOOT 实现脱机运行的功能。
我们为何把这个章节安排在第二个阶段,是因为 21489 的第二种编程方式,首选就需要烧写程序,所以在这个文档里,先把烧写程序的方法给用户做详细的阐述,也好顺利的进入第三个阶段。
ADI 的 DSP,通过 Flash 编程生成的二进制可执行文件尾椎为 LDR,但实际上他的数据格式仍然是通过用户自己选择,而组合成的 BIN,HEX 等常规数据。所以大家不要被 LDR 这个名字所疑惑,仅仅只是 ADI 取的名字罢了。打个比方就是,ADI 拿一个叫做 LDR 的瓶子来装BOOT 数据,而这些数据归根到底,依然是我们在其他嵌入式处理器开发中所熟知的 BIN,HEX 等等,ADI 也无法别出心裁的整出一个独特的数据格式来。
21489 的这块板上,我们做了 NORFLASH 和 SPIFLASH 两种,下面也会对每一种 Flash 做详细的注解,如何来生成这两种 flash 可用的 LDR 文件,又如何来通过 AD-HP530ICE 仿真器去烧写相应的 LDR 文件到 FLASH 中,实现脱机。
Flash 编程和烧写前所需要做的准备工作
硬件准备
仿真器:AD-HP530ICE
开发板:ADSP-21489EVB
硬件设计原理图
软件准备
Visual DSP++5.1.2
硬件链接
仿真器和开发板通过 JTAG 连接,开发板上电,仿真器上电。
软件链接
新建 21489 的 session,具体可参见前面文章中的详细说明,此处不再讲解。
NORFLASH 编程
此章将详细讲解如何使用 VDSP 软件来进行 NORFLASH 编程,生成 boot 用的 LDR 文件。我们以按键控制 LED 灯的程序来做例程讲解。
- 把工程拖到 VDSP 软件中来。
- 在工程名上按鼠标右键,选择“Project Options…”
- 根据芯片的实际版本,为工程选择一个芯片版本,将“Type”选为“Loader File”。我们现在用的 21489 都是 0.2 版,所以就选择 0.2。
- 按下图为生成的 LDR 文件选择格式,设置完成后点“确定”按钮,完成 LDR 文件的配置。ADSP21489_prom.dxe 文件位于 FlashDriver 文件夹里的 NORFLASH 文件夹下。
NORFLASH 生成 LDR,所以在 Boot Type 里选择 Parallel port;Format 我选择的是 Binary,一直用这个,没啥因为所以;Width 选择 8-bit,因为 Flash 就是 8bit 的。
Kernel file 这个就很关键了,必须要需要添加这个 kernel,这个 kernel 是 ADI 公司提供的,我只是把它单独拧出来放到我的 Flash Driver 文件夹里,方便调用。
- 选择“ ReBuild all“按钮全编译工程。
- 编译完成后,会看到生成文件提示。该文件默认生成地址为当前工程的 Debug 文件夹下。
烧写
- 选择 Tools 里的 Flash Programmer。特别注意,一定要链接好 session,才有此选项!
- 为 NorFlash 加载一个“.dxe”格式的驱动文件,这个文件在 “Flash Driver”文件夹下。
这个是 Flash 的烧写驱动,每一个型号的 Flash 都需要专门对应自己的驱动,ADI 提供了一个驱动源码,如果用户的 Flash 型号与原厂提供的这个不符,则需要对驱动进行修改。我们开发板使用的就是ADI原厂的这个Flash型号,所以就可以直接用这个Driver,不用做任何修改。在这里 OP 也建议大家都用原厂提供的这个型号,否则自己改 Flash烧写驱动,还是一件挺麻烦的事情。
- 找到“ ADSP21489_FlashDriver.dxe”文件。
- 按下图选择选项,然后点“ Data”后面的按钮,找到 ADSP21489_PBLED 工程下 Debug 文件夹下刚才生成的“ ADSP21489_PBLED.ldr”文件。
- 烧写过程中的读条,请静心等待。
- 完成烧写。
- 断开链接,完整 Flash 编程和烧写得工作。
-
将 BOOT 开关 SW2 和 SW3 分别拨到 OFF 和 ON,设置成 NORFLASH 启动
-
拔掉电源插头,重新上电,并打开电源开关,按下按键,相应的 LED 灯亮,验证完成。
驱动程序的源代码
main.c
#include <cdef21489.h>
#include <def21489.h>
#include <stdlib.h> // malloc includes
#include “s29al016.h” // flash-S29AL016D includes
#include “Services_Sharc.h” // system services buffers
#include “util.h” // library struct includes
#include “Errors.h” // error type includes
#define FLASH_START_ADDR 0x4000000
static char *pEzKitTitle = “ADSP-21489-CORE_FlashDriver”;
#define BUFFER_SIZE 0x400
//Flash Programmer commands
typedef enum
{
NO_COMMAND, // 0
GET_CODES, // 1
RESET, // 2
WRITE, // 3
FILL, // 4
ERASE_ALL, // 5
ERASE_SECT, // 6
READ, // 7
GET_SECTNUM, // 8
GET_SECSTARTEND,// 9
}enProgCmds;
//----- g l o b a l s -----//
char *AFP_Title ;
char *AFP_Description; // Device Description: AMD S29AL016D
char *AFP_DeviceCompany; // Device Company
char *AFP_DrvVersion = “1.01.0”; // Driver Version
char *AFP_BuildDate = DATE; // Driver Build Date
enProgCmds AFP_Command = NO_COMMAND; // command sent down from the GUI
int AFP_ManCode = -1;
int AFP_DevCode = -1;
unsigned long AFP_Offset = 0x0; // offset into flash
int *AFP_Buffer; // buffer used to read and write flash
long AFP_Size = BUFFER_SIZE; // buffer size
long AFP_Count = -1; // count of locations to be read or written
long AFP_Stride = -1; // stride used when reading or writing
int AFP_NumSectors = NUM_SECTORS; // number of sectors in the flash device
int AFP_Sector = -1; // sector number
int AFP_Error = 0; // contains last error encountered
bool AFP_Verify = FALSE; // verify writes or not
unsigned long AFP_StartOff = 0x0; // sector start offset
unsigned long AFP_EndOff = 0x0; // sector end offset
int AFP_FlashWidth = 0x8; // width of the flash device
int *AFP_SectorInfo;
bool bExit = FALSE; //exit flag
//----- c o n s t a n t d e f i n i t i o n s -----//
// structure for flash sector information
typedef struct _SECTORLOCATION
{
unsigned long ulStartOff;
unsigned long ulEndOff;
}SECTORLOCATION;
SECTORLOCATION SectorInfo[NUM_SECTORS];
//----- f u n c t i o n p r o t o t y p e s -----//
ERROR_CODE AllocateAFPBuffer(void);
ERROR_CODE GetSectorMap(void);
ERROR_CODE GetFlashInfo(void);
ERROR_CODE ProcessCommand(void);
ERROR_CODE SetupForFlash(void);
ERROR_CODE FillData( unsigned long ulStart, long lCount, long lStride, int *pnData );
ERROR_CODE ReadData( unsigned long ulStart, long lCount, long lStride, int *pnData );
ERROR_CODE WriteData( unsigned long ulStart, long lCount, long lStride, int *pnData );
void FreeAFPBuffer(void);
//------------- m a i n ( ) ----------------//
int main(void)
{
// open the device
AFP_Error = ADIS29AL016DEntryPoint.adi_pdd_Open( NULL, // DevMgr handle
0, // pdd entry point
NULL, // device instance
NULL, // client handle callback identifier
ADI_DEV_DIRECTION_BIDIRECTIONAL,// data direction for this device
NULL, // DevMgr handle for this device
NULL, // handle to DmaMgr for this device
NULL, // handle to deferred callback service
NULL ); // client’s callback function
// allocate AFP_Buffer
if (( AFP_Error = AllocateAFPBuffer()) != NO_ERR)
return FALSE;
// get sector map
if (( AFP_Error = GetSectorMap())!= NO_ERR)
return FALSE;
// setup the device so the DSP can access it
if (SetupForFlash() != NO_ERR)
return FALSE;
// get flash manufacturer & device codes, title & desc
if (( AFP_Error = GetFlashInfo()) != NO_ERR)
return FALSE;
// command processing loop
while ( !bExit )
{
// the plug-in will set a breakpoint at "AFP_BreakReady" so it knows
// when we are ready for a new command because the DSP will halt
//
// the jump is used so that the label will be part of the debug
// information in the driver image otherwise it may be left out
// since the label is not referenced anywhere
asm("AFP_BreakReady:");
asm("nop;");
if ( FALSE )
asm("jump AFP_BreakReady;");
// Make a call to the ProcessCommand
AFP_Error = ProcessCommand();
}
// Clear the AFP_Buffer
FreeAFPBuffer();
// Close the Device
AFP_Error = ADIS29AL016DEntryPoint.adi_pdd_Close(NULL);
return TRUE;
}
//----------- P r o c e s s C o m m a n d ( ) ----------//
//
// PURPOSE
// Process each command sent by the GUI based on the value in
// the AFP_Command.
//
// RETURN VALUE
// ERROR_CODE - value if any error occurs during Opcode scan
// NO_ERR - otherwise
//
// CHANGES
// 9-28-2005 Created
ERROR_CODE ProcessCommand()
{
ERROR_CODE ErrorCode = NO_ERR; //return error code
COMMAND_STRUCT CmdStruct;
// switch on the command and fill command structure.
switch ( AFP_Command )
{
// erase all
case ERASE_ALL:
CmdStruct.SEraseAll.ulFlashStartAddr = FLASH_START_ADDR; //FlashStartAddress
ErrorCode = (ERROR_CODE) ADIS29AL016DEntryPoint.adi_pdd_Control(NULL, CNTRL_ERASE_ALL, &CmdStruct );
break;
// erase sector
case ERASE_SECT:
CmdStruct.SEraseSect.nSectorNum = AFP_Sector; // Sector Number to erase
CmdStruct.SEraseSect.ulFlashStartAddr = FLASH_START_ADDR; // FlashStartAddress
ErrorCode = (ERROR_CODE) ADIS29AL016DEntryPoint.adi_pdd_Control(NULL, CNTRL_ERASE_SECT, &CmdStruct);
break;
// fill
case FILL:
ErrorCode = FillData( AFP_Offset, AFP_Count, AFP_Stride, AFP_Buffer );
break;
// get manufacturer and device codes
case GET_CODES:
CmdStruct.SGetCodes.pManCode = (unsigned long *)&AFP_ManCode; // Manufacturer Code
CmdStruct.SGetCodes.pDevCode = (unsigned long *)&AFP_DevCode; // Device Code
CmdStruct.SGetCodes.ulFlashStartAddr = FLASH_START_ADDR;
ErrorCode = (ERROR_CODE) ADIS29AL016DEntryPoint.adi_pdd_Control(NULL, CNTRL_GET_CODES, &CmdStruct);
break;
// get sector number based on address
case GET_SECTNUM:
CmdStruct.SGetSectNum.ulOffset = AFP_Offset; // offset from the base address
CmdStruct.SGetSectNum.pSectorNum = (unsigned long *)&AFP_Sector; //Sector Number
ErrorCode = (ERROR_CODE) ADIS29AL016DEntryPoint.adi_pdd_Control(NULL, CNTRL_GET_SECTNUM, &CmdStruct);
break;
// get sector number start and end offset
case GET_SECSTARTEND:
CmdStruct.SSectStartEnd.nSectorNum = AFP_Sector; // Sector Number
CmdStruct.SSectStartEnd.pStartOffset = &AFP_StartOff;// sector start address
CmdStruct.SSectStartEnd.pEndOffset = &AFP_EndOff; // sector end address
ErrorCode = (ERROR_CODE) ADIS29AL016DEntryPoint.adi_pdd_Control(NULL, CNTRL_GET_SECSTARTEND, &CmdStruct );
break;
// read
case READ:
ErrorCode = ReadData( AFP_Offset, AFP_Count, AFP_Stride, AFP_Buffer );
break;
// reset
case RESET:
CmdStruct.SReset.ulFlashStartAddr = FLASH_START_ADDR; //Flash start address
ErrorCode = (ERROR_CODE) ADIS29AL016DEntryPoint.adi_pdd_Control(NULL, CNTRL_RESET, &CmdStruct);
break;
// write
case WRITE:
ErrorCode = WriteData( AFP_Offset, AFP_Count, AFP_Stride, AFP_Buffer );
break;
// no command or unknown command do nothing
case NO_COMMAND:
default:
// set our error
ErrorCode = UNKNOWN_COMMAND;
break;
}
// clear the command
AFP_Command = NO_COMMAND;
return(ErrorCode);
}
//----------- A l l o c a t e A F P B u f f e r ( ) ----------//
//
// PURPOSE
// Allocate memory for the AFP_Buffer
//
// RETURN VALUE
// ERROR_CODE - value if any error occurs
// NO_ERR - otherwise
//
// CHANGES
// 9-28-2005 Created
ERROR_CODE AllocateAFPBuffer()
{
ERROR_CODE ErrorCode = NO_ERR; //return error code
// by making AFP_Buffer as big as possible the plug-in can send and
// receive more data at a time making the data transfer quicker
//
// by allocating it on the heap the compiler does not create an
// initialized array therefore making the driver image smaller
// and faster to load
//
// The linker description file (LDF) could be modified so that
// the heap is larger, therefore allowing the BUFFER_SIZE to increase.
AFP_Buffer = (int *)malloc(BUFFER_SIZE);
// AFP_Buffer will be NULL if we could not allocate storage for the
// buffer
if ( AFP_Buffer == NULL )
{
// tell GUI that our buffer was not initialized
ErrorCode = BUFFER_IS_NULL;
}
return(ErrorCode);
}
//----------- F r e e A F P B u f f e r ( ) ----------//
//
// PURPOSE
// Free the AFP_Buffer
//
// RETURN VALUE
// ERROR_CODE - value if any error occurs
// NO_ERR - otherwise
//
// CHANGES
// 9-28-2005 Created
void FreeAFPBuffer()
{
// free the buffer if we were able to allocate one
if ( AFP_Buffer )
free( AFP_Buffer );
}
//----------- S e t u p F o r F l a s h ( ) ----------//
//
// PURPOSE
// Perform necessary setup for the processor to talk to the
// flash such as external memory interface registers, etc.
//
// RETURN VALUE
// ERROR_CODE - value if any error occurs during Opcode scan
// NO_ERR - otherwise
ERROR_CODE SetupForFlash()
{
// setup EPCTL to use bank 2 (MS1) core accesses.
*pEPCTL = (((*pEPCTL) & (~B1SD)) | EPBRCORE);
//*pPMCTL = (PLLM16 | INDIV | DIVEN | SDCKR3);
*pPMCTL = (PLLM16|PLLD4|DIVEN);
// setup for max waitstates
// NOTE: The PKDIS bit is set which makes a 1 to 1 mapping, each 8 bit byte
/// maps to an address.
*pAMICTL1 = ( PKDIS | WS31 | HC1 | HC2 | RHC1 | RHC2 | IC7 | AMIEN | AMIFLSH);
return NO_ERR;
}
//----------- G e t S e c t o r M a p ( ) ----------//
//
// PURPOSE
// Get the start and end offset for each sector in the flash.
//
// RETURN VALUE
// ERROR_CODE - value if any error occurs
// NO_ERR - otherwise
//
// CHANGES
// 9-28-2005 Created
ERROR_CODE GetSectorMap()
{
ERROR_CODE ErrorCode = NO_ERR; //return error code
GET_SECTSTARTEND_STRUCT SSectStartEnd; //structure for GetSectStartEnd
int i; //index
//initiate sector information structures
for( i=0;i<AFP_NumSectors; i++)
{
SSectStartEnd.nSectorNum = i;
SSectStartEnd.pStartOffset = &SectorInfo[i].ulStartOff;
SSectStartEnd.pEndOffset = &SectorInfo[i].ulEndOff;
ErrorCode = (ERROR_CODE) ADIS29AL016DEntryPoint.adi_pdd_Control(NULL, CNTRL_GET_SECSTARTEND, (COMMAND_STRUCT *)&SSectStartEnd );
}
AFP_SectorInfo = (int*)&SectorInfo[0];
return(ErrorCode);
}
//----------- G e t F l a s h I n f o ( ) ----------//
//
// PURPOSE
// Get the manufacturer code and device code
//
// RETURN VALUE
// ERROR_CODE - value if any error occurs
// NO_ERR - otherwise
//
// CHANGES
// 9-28-2005 Created
ERROR_CODE GetFlashInfo()
{
ERROR_CODE ErrorCode = NO_ERR; //return error code
static GET_CODES_STRUCT SGetCodes; //structure for GetCodes
COMMAND_STRUCT CmdStruct;
//setup code so that flash programmer can just read memory instead of call GetCodes().
CmdStruct.SGetCodes.pManCode = (unsigned long *)&AFP_ManCode;
CmdStruct.SGetCodes.pDevCode = (unsigned long *)&AFP_DevCode;
CmdStruct.SGetCodes.ulFlashStartAddr = FLASH_START_ADDR;
AFP_Error = ADIS29AL016DEntryPoint.adi_pdd_Control(NULL, CNTRL_GET_CODES, &CmdStruct );
AFP_Error = ADIS29AL016DEntryPoint.adi_pdd_Control(NULL, CNTRL_GET_DESC, &CmdStruct );
AFP_Title = pEzKitTitle;
AFP_Description = CmdStruct.SGetDesc.pDesc;
AFP_DeviceCompany = CmdStruct.SGetDesc.pFlashCompany;
return(ErrorCode);
}
//----------- F i l l D a t a ( ) ----------//
//
// PURPOSE
// Fill flash device with a value.
//
// INPUTS
// unsigned long ulStart - address in flash to start the writes at
// long lCount - number of elements to write, in this case bytes
// long lStride - number of locations to skip between writes
// int *pnData - pointer to data buffer
//
// RETURN VALUE
// ERROR_CODE - value if any error occurs during fill
// NO_ERR - otherwise
//
// CHANGES
// 9-28-2005 Created
ERROR_CODE FillData( unsigned long ulStart, long lCount, long lStride, int *pnData )
{
long i = 0; // loop counter
ERROR_CODE ErrorCode = NO_ERR; // tells whether we had an error while filling
bool bVerifyError = FALSE; // lets us know if there was a verify error
unsigned long ulStartAddr; // current address to fill
unsigned long ulSector = 0; // sector number to verify address
int nCompare = 0; // value that we use to verify flash
ADI_DEV_1D_BUFFER WriteBuff; // buffer pointer
ADI_DEV_1D_BUFFER ReadBuff; // buffer pointer
ulStartAddr = FLASH_START_ADDR + ulStart;
COMMAND_STRUCT CmdStruct; //structure for GetSectStartEnd
// verify writes if the user wants to
if( AFP_Verify == TRUE )
{
// fill the value
for (i = 0; ( ( i < lCount ) && ( ErrorCode == NO_ERR ) ); i++, ulStartAddr += ( lStride ) )
{
// check to see that the address is within a valid sector
CmdStruct.SGetSectNum.ulOffset = ulStartAddr;
CmdStruct.SGetSectNum.pSectorNum = &ulSector;
ErrorCode = (ERROR_CODE) ADIS29AL016DEntryPoint.adi_pdd_Control(NULL, CNTRL_GET_SECTNUM, &CmdStruct );
if( NO_ERR == ErrorCode )
{
// unlock the flash, do the write, and wait for completion
WriteBuff.Data = (void *)&pnData[0];
WriteBuff.pAdditionalInfo = (void *)&ulStartAddr;
ErrorCode = (ERROR_CODE) ADIS29AL016DEntryPoint.adi_pdd_Write(NULL, ADI_DEV_1D, (ADI_DEV_BUFFER *)&WriteBuff);
ReadBuff.Data = (void *)&nCompare;
ReadBuff.pAdditionalInfo = (void *)&ulStartAddr ;
ErrorCode = (ERROR_CODE) ADIS29AL016DEntryPoint.adi_pdd_Read(NULL, ADI_DEV_1D, (ADI_DEV_BUFFER *)&ReadBuff);
//if( nCompare != ( pnData[0] & 0x0000FFFF ) )
if( ( nCompare & 0xFF ) != (pnData[0] & 0xFF) )
{
bVerifyError = TRUE;
break;
}
}
else
{
return ErrorCode;
}
}
// return appropriate error code if there was a verification error
if( bVerifyError == TRUE )
return VERIFY_WRITE;
}
// user did not want to verify writes
else
{
// fill the value
for (i = 0; ( ( i < lCount ) && ( ErrorCode == NO_ERR ) ); i++, ulStartAddr += ( lStride ))
{
// check to see that the address is within a valid sector
CmdStruct.SGetSectNum.ulOffset = ulStartAddr;
CmdStruct.SGetSectNum.pSectorNum = &ulSector;
ErrorCode = (ERROR_CODE) ADIS29AL016DEntryPoint.adi_pdd_Control(NULL, CNTRL_GET_SECTNUM, &CmdStruct );
if( NO_ERR == ErrorCode )
{
// unlock the flash, do the write, and wait for completion
WriteBuff.Data = (void *)&pnData[0];
WriteBuff.pAdditionalInfo = (void *)&ulStartAddr;
ErrorCode = (ERROR_CODE) ADIS29AL016DEntryPoint.adi_pdd_Write(NULL, ADI_DEV_1D, (ADI_DEV_BUFFER *)&WriteBuff);
}
else
{
return ErrorCode;
}
}
}
// return the appropriate error code
return ErrorCode;
}
//----------- W r i t e D a t a ( ) ----------//
//
// PURPOSE
// Write a buffer to flash device.
//
// INPUTS
// unsigned long ulStart - address in flash to start the writes at
// long lCount - number of elements to write, in this case bytes
// long lStride - number of locations to skip between writes
// int *pnData - pointer to data buffer
//
// RETURN VALUE
// ERROR_CODE - value if any error occurs during writing
// NO_ERR - otherwise
//
// CHANGES
// 9-28-2005 Created
ERROR_CODE WriteData( unsigned long ulStart, long lCount, long lStride, int *pnData )
{
long i = 0; // loop counter
ERROR_CODE ErrorCode = NO_ERR; // tells whether there was an error trying to write
int nCompare = 0; // value that we use to verify flash
bool bVerifyError = FALSE; // lets us know if there was a verify error
unsigned long ulAbsoluteAddr; // current address to write
unsigned long ulSector = 0; // sector number to verify address
ADI_DEV_1D_BUFFER WriteBuff; // buffer pointer
ADI_DEV_1D_BUFFER ReadBuff; // buffer pointer
ulAbsoluteAddr = FLASH_START_ADDR + ulStart;
COMMAND_STRUCT CmdStruct; //structure for GetSectStartEnd
// if the user wants to verify then do it
if( AFP_Verify == TRUE )
{
// write the buffer up to BUFFER_SIZE items
for (i = 0; ( i < lCount ) && ( ErrorCode == NO_ERR ); i++, ulAbsoluteAddr += lStride)
{
// check to see that the address is within a valid sector
CmdStruct.SGetSectNum.ulOffset = ulAbsoluteAddr;
CmdStruct.SGetSectNum.pSectorNum = &ulSector;
ErrorCode = (ERROR_CODE) ADIS29AL016DEntryPoint.adi_pdd_Control(NULL, CNTRL_GET_SECTNUM, &CmdStruct );
if( NO_ERR == ErrorCode )
{
// unlock the flash, do the write, increase shift, and wait for completion
WriteBuff.Data = (void *)&pnData[i];
WriteBuff.pAdditionalInfo = (void *)&ulAbsoluteAddr ;
ErrorCode = (ERROR_CODE) ADIS29AL016DEntryPoint.adi_pdd_Write(NULL, ADI_DEV_1D, (ADI_DEV_BUFFER *)&WriteBuff);
ReadBuff.Data = (void *)&nCompare;
ReadBuff.pAdditionalInfo = (void *)&ulAbsoluteAddr ;
ErrorCode = (ERROR_CODE) ADIS29AL016DEntryPoint.adi_pdd_Read(NULL, ADI_DEV_1D, (ADI_DEV_BUFFER *)&ReadBuff);
if( ( nCompare & 0xFF ) != (pnData[i] & 0xFF) )
{
bVerifyError = TRUE;
break;
}
}
else
{
return ErrorCode;
}
}
// return appropriate error code if there was a verification error
if( bVerifyError == TRUE )
return VERIFY_WRITE;
}
// the user does not want to verify
else
{
// write the buffer up to BUFFER_SIZE items
for (i = 0; ( i < lCount ) && ( ErrorCode == NO_ERR ); i++, ulAbsoluteAddr += lStride)
{
// check to see that the address is within a valid sector
CmdStruct.SGetSectNum.ulOffset = ulAbsoluteAddr;
CmdStruct.SGetSectNum.pSectorNum = &ulSector;
ErrorCode = (ERROR_CODE) ADIS29AL016DEntryPoint.adi_pdd_Control(NULL, CNTRL_GET_SECTNUM, &CmdStruct );
if( NO_ERR == ErrorCode )
{
// unlock the flash, do the write, increase shift, and wait for completion
WriteBuff.Data = (void *)&pnData[i] ;
WriteBuff.pAdditionalInfo = (void *)&ulAbsoluteAddr ;
ErrorCode = (ERROR_CODE) ADIS29AL016DEntryPoint.adi_pdd_Write(NULL, ADI_DEV_1D, (ADI_DEV_BUFFER *)&WriteBuff);
}
else
{
return ErrorCode;
}
}
}
// return the appropriate error code
return ErrorCode;
}
//----------- R e a d D a t a ( ) ----------//
//
// PURPOSE
// Read a buffer from flash device.
//
// INPUTS
// unsigned long ulStart - address in flash to start the reads at
// long lCount - number of elements to read, in this case bytes
// long lStride - number of locations to skip between reads
// int *pnData - pointer to data buffer to fill
//
// RETURN VALUE
// ERROR_CODE - value if any error occurs during reading
// NO_ERR - otherwise
//
// CHANGES
// 9-28-2005 Created
ERROR_CODE ReadData( unsigned long ulStart, long lCount, long lStride, int *pnData )
{
long i = 0; // loop counter
ERROR_CODE ErrorCode = NO_ERR; // tells whether there was an error trying to read
unsigned long ulAbsoluteAddr; // current address to read
unsigned long ulSector = 0; // sector number to verify address
unsigned long ulMask =0xffff;
ADI_DEV_1D_BUFFER ReadBuff; // buffer pointer
COMMAND_STRUCT CmdStruct; //structure for GetSectStartEnd
ulAbsoluteAddr = FLASH_START_ADDR + ulStart;
// read the buffer up to BUFFER_SIZE items
for (i = 0; (i < lCount) && (i < BUFFER_SIZE); i++, ulAbsoluteAddr += lStride)
{
// check to see that the address is within a valid sector
CmdStruct.SGetSectNum.ulOffset = ulAbsoluteAddr;
CmdStruct.SGetSectNum.pSectorNum = &ulSector;
ErrorCode = (ERROR_CODE) ADIS29AL016DEntryPoint.adi_pdd_Control(NULL, CNTRL_GET_SECTNUM, &CmdStruct );
if( NO_ERR == ErrorCode )
{
ReadBuff.Data = (void *)&pnData[i];
ReadBuff.pAdditionalInfo = (void *)&ulAbsoluteAddr ;
ErrorCode = (ERROR_CODE) ADIS29AL016DEntryPoint.adi_pdd_Read(NULL, ADI_DEV_1D, (ADI_DEV_BUFFER *)&ReadBuff);
}
else
{
return ErrorCode;
}
}
// return the appropriate error code
return ErrorCode;
}
flash.c
//----- I n c l u d e s -----//
#include <cdef21489.h>
#include <def21489.h>
#include “s29al016.h”
#include “util.h”
#include “Errors.h”
//---- c o n s t a n t d e f i n i t i o n s -----//
static char *pFlashDesc = “S29AL016D70”;
static char *pDeviceCompany = “SPANSION”;
//---- F u n c t i o n P r o t o t y p e s ----//
// (for Helper Functions) //
static ERROR_CODE EraseFlash(unsigned long ulStartAddr);
static ERROR_CODE EraseBlock( int nBlock, unsigned long ulStartAddr );
static ERROR_CODE GetCodes(int *pnManCode, int *pnDevCode, unsigned long ulStartAddr);
static ERROR_CODE GetSectorNumber( unsigned long ulAddr, int *pnSector );
static ERROR_CODE GetSectorStartEnd( unsigned long *ulStartOff, unsigned long *ulEndOff, int nSector );
static ERROR_CODE PollToggleBit( const unsigned long ulOffset, const unsigned short usValue );
static ERROR_CODE ReadFlash(const unsigned long ulOffset, unsigned short *pusValue );
static ERROR_CODE ResetFlash(unsigned long ulStartAddr);
static ERROR_CODE WriteFlash(const unsigned long ulOffset, const unsigned short usValue );
static unsigned long GetFlashStartAddress( unsigned long ulAddr);
// Open a device
static u32 adi_pdd_Open(
ADI_DEV_MANAGER_HANDLE ManagerHandle, // device manager handle
u32 DeviceNumber, // device number
ADI_DEV_DEVICE_HANDLE DeviceHandle, // device handle
ADI_DEV_PDD_HANDLE *pPDDHandle, // pointer to PDD handle location
ADI_DEV_DIRECTION Direction, // data direction
void *pEnterCriticalArg, // enter critical region parameter
ADI_DMA_MANAGER_HANDLE DMAHandle, // handle to the DMA manager
ADI_DCB_HANDLE DCBHandle, // callback handle
ADI_DCB_CALLBACK_FN DMCallback // device manager callback function
);
// Close a device
static u32 adi_pdd_Close(
ADI_DEV_PDD_HANDLE PDDHandle
);
// Read from a device
static u32 adi_pdd_Read( ADI_DEV_PDD_HANDLE PDDHandle,
ADI_DEV_BUFFER_TYPE BufferType,
ADI_DEV_BUFFER *pBuffer);
// Write to a device
static u32 adi_pdd_Write( ADI_DEV_PDD_HANDLE PDDHandle,
ADI_DEV_BUFFER_TYPE BufferType,
ADI_DEV_BUFFER *pBuffer);
// Control the device
static u32 adi_pdd_Control( ADI_DEV_PDD_HANDLE PDDHandle,
u32 Command,
void *pArg);
ADI_DEV_PDD_ENTRY_POINT ADIS29AL016DEntryPoint =
{
adi_pdd_Open,
adi_pdd_Close,
adi_pdd_Read,
adi_pdd_Write,
adi_pdd_Control
};
// ---- P h y s i c a l D e v i c e D r i v e A P I f u n c t i o n s ----//
//----------- a d i _ p d d _ C l o s e ( ) ----------//
//
// PURPOSE
// This function opens the S29AL016D flash device for use.
//
// INPUTS
// ManagerHandle - device manager handle
// DeviceNumber - device number
// DeviceHandle - device handle
// PDDHandle - This is the handle used to identify the device
// Direction - data direction
// *pEnterCriticalArg - enter critical region parameter
// DMAHandle - handle to the DMA manager
// DCBHandle - callback handle
// DMCallback - device manager callback function
//
// RETURN VALUE
// Result
u32 adi_pdd_Open( // Open a device
ADI_DEV_MANAGER_HANDLE ManagerHandle, // device manager handle
u32 DeviceNumber, // device number
ADI_DEV_DEVICE_HANDLE DeviceHandle, // device handle
ADI_DEV_PDD_HANDLE *pPDDHandle, // pointer to PDD handle location
ADI_DEV_DIRECTION Direction, // data direction
void *pEnterCriticalArg, // enter critical region parameter
ADI_DMA_MANAGER_HANDLE DMAHandle, // handle to the DMA manager
ADI_DCB_HANDLE DCBHandle, // callback handle
ADI_DCB_CALLBACK_FN DMCallback ) // device manager callback function
{
// check for errors if required
#ifdef ADI_S29AL016D_ERROR_CHECKING_ENABLED
if (DeviceNumber > 0) // check the device number
return (ADI_DEV_RESULT_BAD_DEVICE_NUMBER);
if (Direction != ADI_DEV_DIRECTION_BIDIRECTIONAL) // check the direction
return (ADI_DEV_RESULT_DIRECTION_NOT_SUPPORTED);
#endif
return (NO_ERR);
}
//----------- a d i _ p d d _ C l o s e ( ) ----------//
//
// PURPOSE
// This function closes the S29AL016D flash device.
//
// INPUTS
// PDDHandle - This is the handle used to identify the device
//
// RETURN VALUE
// Result
u32 adi_pdd_Close(ADI_DEV_PDD_HANDLE PDDHandle) // PDD handle
{
return (NO_ERR);
}
//----------- a d i _ p d d _ R e a d ( ) ----------//
//
// PURPOSE
// Provides buffers to the S29AL016D flash device for reception
// of inbound data.
//
// INPUTS
// PDDHandle - This is the handle used to identify the device
// BufferType - This argument identifies the type of buffer: one-
// dimentional, two-dimensional or circular
// *pBuffer - The is the address of the buffer or first buffer in
// a chain of buffers
//
// RETURN VALUE
// Result
u32 adi_pdd_Read( ADI_DEV_PDD_HANDLE PDDHandle,
ADI_DEV_BUFFER_TYPE BufferType,
ADI_DEV_BUFFER *pBuffer)
{
ADI_DEV_1D_BUFFER *pBuff1D;
unsigned short *pusValue;
unsigned long *pulAbsoluteAddr;
u32 Result;
// cast our buffer to a 1D buffer
pBuff1D = (ADI_DEV_1D_BUFFER*)pBuffer;
// cast our data buffer
pusValue = (unsigned short *)pBuff1D->Data;
// cast our offset
pulAbsoluteAddr = (unsigned long *)pBuff1D->pAdditionalInfo;
Result = ReadFlash( *pulAbsoluteAddr, pusValue );
return(Result);
}
//----------- a d i _ p d d _ W r i t e ( ) ----------//
//
// PURPOSE
// Provides buffers to the S29AL016D flash device for transmission
// of outbound data.
//
// INPUTS
// PDDHandle - This is the handle used to identify the device
// BufferType - This argument identifies the type of buffer: one-
// dimentional, two-dimensional or circular
// *pBuffer - The is the address of the buffer or first buffer in
// a chain of buffers
//
// RETURN VALUE
// Result
u32 adi_pdd_Write( ADI_DEV_PDD_HANDLE PDDHandle,
ADI_DEV_BUFFER_TYPE BufferType,
ADI_DEV_BUFFER *pBuffer)
{
ADI_DEV_1D_BUFFER *pBuff1D; // buffer pointer
short *psValue; // stores the value to be written to flash
unsigned long *pulAbsoluteAddr; // the absolute address to write
unsigned long ulFlashStartAddr; // flash start address
u32 Result; // error code returned
// cast our buffer to a 1D buffer
pBuff1D = (ADI_DEV_1D_BUFFER*)pBuffer;
// cast our data buffer
psValue = (short *)pBuff1D->Data;
// cast our offset
pulAbsoluteAddr = (unsigned long *)pBuff1D->pAdditionalInfo;
// get flash start address from absolute address
ulFlashStartAddr = *pulAbsoluteAddr;
ulFlashStartAddr &= 0xFFFF0000;
WriteFlash( ulFlashStartAddr, 0x00 );
WriteFlash( ulFlashStartAddr+0xaaa, 0xaa );
WriteFlash( ulFlashStartAddr+0x555, 0x55 );
WriteFlash( ulFlashStartAddr+0xaaa, 0xa0 );
// program our actual value now
Result = WriteFlash( *pulAbsoluteAddr, *psValue);
// make sure the write was successful
Result = PollToggleBit(*pulAbsoluteAddr, *psValue & 0xff);
return(Result);
}
//----------- a d i _ p d d _ C o n t r o l ( ) ----------//
//
// PURPOSE
// This function sets or detects a configuration parameter
// for the S29AL016D flash device.
//
// INPUTS
// PDDHandle - This is the handle used to identify the device
// Command - This is the command identifier
// *pArg - The is the address of command-specific parameter
//
// RETURN VALUE
// Result
u32 adi_pdd_Control( ADI_DEV_PDD_HANDLE PDDHandle,
u32 Command,
void *pArg)
{
ERROR_CODE ErrorCode = NO_ERR;
COMMAND_STRUCT *pCmdStruct = (COMMAND_STRUCT *)pArg;
// switch on the command
switch ( Command )
{
// erase all
case CNTRL_ERASE_ALL:
ErrorCode = EraseFlash(pCmdStruct->SEraseAll.ulFlashStartAddr);
break;
// erase sector
case CNTRL_ERASE_SECT:
ErrorCode = EraseBlock( pCmdStruct->SEraseSect.nSectorNum, pCmdStruct->SEraseSect.ulFlashStartAddr );
break;
// get manufacturer and device codes
case CNTRL_GET_CODES:
ErrorCode = GetCodes((int *)pCmdStruct->SGetCodes.pManCode, (int *)pCmdStruct->SGetCodes.pDevCode, (unsigned long)pCmdStruct->SGetCodes.ulFlashStartAddr);
break;
case CNTRL_GET_DESC:
//Filling the contents with data
//pCmdStruct->SGetDesc.pTitle = pEzKitTitle;
pCmdStruct->SGetDesc.pDesc = pFlashDesc;
pCmdStruct->SGetDesc.pFlashCompany = pDeviceCompany;
break;
// get sector number based on address
case CNTRL_GET_SECTNUM:
ErrorCode = GetSectorNumber( pCmdStruct->SGetSectNum.ulOffset, (int *)pCmdStruct->SGetSectNum.pSectorNum );
break;
// get sector number start and end offset
case CNTRL_GET_SECSTARTEND:
ErrorCode = GetSectorStartEnd( pCmdStruct->SSectStartEnd.pStartOffset, pCmdStruct->SSectStartEnd.pEndOffset, pCmdStruct->SSectStartEnd.nSectorNum );
break;
// reset
case CNTRL_RESET:
ErrorCode = ResetFlash(pCmdStruct->SReset.ulFlashStartAddr);
break;
// no command or unknown command do nothing
default:
// set our error
ErrorCode = UNKNOWN_COMMAND;
break;
}
// return
return(ErrorCode);
}
//----- H e l p e r F u n c t i o n s ----//
//----------- R e s e t F l a s h ( ) ----------//
//
// PURPOSE
// Sends a “reset” command to the flash.
//
// INPUTS
// unsigned long ulStartAddr - flash start address
//
// RETURN VALUE
// ERROR_CODE - value if any error occurs
// NO_ERR - otherwise
ERROR_CODE ResetFlash(unsigned long ulAddr)
{
unsigned long ulFlashStartAddr; //flash start address
// get flash start address from absolute address
// The ulAddr should ideally be pointing to the flash start
// address. However we just verify it here again.
ulFlashStartAddr = GetFlashStartAddress(ulAddr);
// send the reset command to the flash
// return to standard operation mode
WriteFlash( ulFlashStartAddr + 0xaaa , 0xF0 );
// WriteFlash( ulFlashStartAddr + 0x555 , 0xF0 );
// reset should be complete
return NO_ERR;
}
//----------- E r a s e F l a s h ( ) ----------//
//
// PURPOSE
// Sends an “erase all” command to the flash.
//
// INPUTS
// unsigned long ulStartAddr - flash start address
//
// RETURN VALUE
// ERROR_CODE - value if any error occurs
// NO_ERR - otherwise
ERROR_CODE EraseFlash(unsigned long ulAddr)
{
ERROR_CODE ErrorCode = NO_ERR; // tells us if there was an error erasing flash
unsigned long ulFlashStartAddr; // flash start address
// get flash start address from absolute address
// The ulAddr should ideally be pointing to the flash start
// address. However we just verify it here again.
ulFlashStartAddr = GetFlashStartAddress(ulAddr);
// erase contents in Main Flash Array
WriteFlash( ulFlashStartAddr + 0xaaa, 0xaa );
WriteFlash( ulFlashStartAddr + 0x555, 0x55 );
WriteFlash( ulFlashStartAddr + 0xaaa, 0x80 );
WriteFlash( ulFlashStartAddr + 0xaaa, 0xaa );
WriteFlash( ulFlashStartAddr + 0x555, 0x55 );
WriteFlash( ulFlashStartAddr + 0xaaa, 0x10 );
// poll until the command has completed
ErrorCode = PollToggleBit(ulFlashStartAddr, 0xFF);
// erase should be complete
return ErrorCode;
}
//----------- E r a s e B l o c k ( ) ----------//
//
// PURPOSE
// Sends an “erase block” command to the flash.
//
// INPUTS
// int nBlock - block to erase
// unsigned long ulStartAddr - flash start address
//
// RETURN VALUE
// ERROR_CODE - value if any error occurs
// NO_ERR - otherwise
ERROR_CODE EraseBlock( int nBlock, unsigned long ulAddr )
{
ERROR_CODE ErrorCode = NO_ERR; //tells us if there was an error erasing flash
unsigned long ulSectStart = 0x0; //stores the sector start offset
unsigned long ulSectEnd = 0x0; //stores the sector end offset(however we do not use it here)
unsigned long ulFlashStartAddr; //flash start address
// get flash start address from absolute address
// The ulAddr should ideally be pointing to the flash start
// address. However we just verify it here again.
ulFlashStartAddr = GetFlashStartAddress(ulAddr);
// Get the sector start offset
// we get the end offset too however we do not actually use it for Erase sector
GetSectorStartEnd( &ulSectStart, &ulSectEnd, nBlock );
// send the erase block command to the flash
WriteFlash( (ulFlashStartAddr + ulSectStart+ 0xaaa), 0xaa );
WriteFlash( (ulFlashStartAddr + ulSectStart+ 0x555), 0x55 );
WriteFlash( (ulFlashStartAddr + ulSectStart+ 0xaaa), 0x80 );
WriteFlash( (ulFlashStartAddr + ulSectStart+ 0xaaa), 0xaa );
WriteFlash( (ulFlashStartAddr + ulSectStart+ 0x555), 0x55 );
WriteFlash( (ulFlashStartAddr + ulSectStart), 0x30 );
// poll until the command has completed
ErrorCode = PollToggleBit(ulFlashStartAddr + ulSectStart, 0xFF);
// block erase should be complete
return ErrorCode;
}
//----------- P o l l T o g g l e B i t ( ) ----------//
//
// PURPOSE
// Polls the toggle bit in the flash to see when the operation
// is complete.
//
// INPUTS
// unsigned long ulAddr - address in flash
//
// RETURN VALUE
// ERROR_CODE - value if any error occurs
// NO_ERR - otherwise
ERROR_CODE PollToggleBit( const unsigned long ulOffset, const unsigned short usValue )
{
bool bError = TRUE; // flag to indicate error
bool bPass = FALSE; // flag indicating passing
int nTimeOut = 0x1FFFFF; // timeout after a while
ERROR_CODE ErrorCode = NO_ERR; // flag to indicate error
unsigned short nReadVal = 0;
while( bError && !bPass )
{
// read the data
ReadFlash( ulOffset, &nReadVal );
// see if the data read == data written
if( (nReadVal & 0xFF) != usValue )
{
// check DQ5 bit for error
if( (nReadVal & 0x20) == 0x20 )
bError = FALSE;
}
else
bPass = TRUE;
}
// if we didn't pass yet then make sure DQ7 was
// not changing simultaneously with DQ5
if( !bPass )
{
ReadFlash( ulOffset, &nReadVal );
// see if the data read == data written
if( (nReadVal & 0xFF) == usValue )
bPass = TRUE;
}
if( !bPass )
ErrorCode = POLL_TIMEOUT;
// we can return
return ErrorCode;
}
//----------- G e t C o d e s ( ) ----------//
//
// PURPOSE
// Sends an “auto select” command to the flash which will allow
// us to get the manufacturer and device codes.
//
// INPUTS
// int *pnManCode - pointer to manufacture code
// int *pnDevCode - pointer to device code
// unsigned long ulStartAddr - flash start address
//
// RETURN VALUE
// ERROR_CODE - value if any error occurs
// NO_ERR - otherwise
ERROR_CODE GetCodes(int *pnManCode, int *pnDevCode, unsigned long ulAddr)
{
unsigned long ulFlashStartAddr; //flash start address
// get flash start address from absolute address
// The ulAddr should ideally be pointing to the flash start
// address. However we just verify it here again.
ulFlashStartAddr = GetFlashStartAddress(ulAddr);
// send the auto select command to the flash
// return to standard operation mode
WriteFlash( ulFlashStartAddr + 0xaaa, 0xAA );
WriteFlash( ulFlashStartAddr + 0x555, 0x55 );
WriteFlash( ulFlashStartAddr + 0xaaa, 0x90 );
// read the manufacturer code
ReadFlash( ulFlashStartAddr , (unsigned short *)pnManCode );
*pnManCode &= 0x00FF;
ReadFlash( ulFlashStartAddr + 0x02, (unsigned short *)pnDevCode );
*pnDevCode &= 0x00FF;
// we need to issue another command to get the part out
// of auto select mode so issue a reset which just puts
// the device back in read mode
ResetFlash(ulAddr);
// ok
return NO_ERR;
}
//----------- G e t S e c t o r N u m b e r ( ) ----------//
//
// PURPOSE
// Gets a sector number based on the offset.
//
// INPUTS
// unsigned long ulAddr - absolute address
// int *pnSector - pointer to sector number
//
// RETURN VALUE
// ERROR_CODE - value if any error occurs
// NO_ERR - otherwise
ERROR_CODE GetSectorNumber( unsigned long ulAddr, int *pnSector )
{
int nSector = 0;
// get offset from absolute address
unsigned long ulMask = 0x3fffff; //offset mask
unsigned long ulOffset= ulAddr & ulMask; //offset
// determine the sector
nSector = ulOffset & 0xffff0000;
nSector = ulOffset >> 16;
nSector = nSector & 0x000f;
// if it is a valid sector, set it
if ( (nSector >= 0) && (nSector < NUM_SECTORS) )
*pnSector = nSector;
// else it is an invalid sector
else
return INVALID_SECTOR;
// ok
return NO_ERR;
}
//----------- G e t S e c t o r S t a r t E n d ( ) ----------//
//
// PURPOSE
// Gets a sector start and end address based on the sector number.
//
// INPUTS
// unsigned long *ulStartOff - pointer to the start offset
// unsigned long *ulEndOff - pointer to the end offset
// int nSector - sector number
//
// RETURN VALUE
// ERROR_CODE - value if any error occurs
// NO_ERR - otherwise
ERROR_CODE GetSectorStartEnd( unsigned long *ulStartOff, unsigned long *ulEndOff, int nSector )
{
// The blocks in the flash memory are asymmetrically arranged
// Thus we use block sizes to determine the block addresses
unsigned long ulBlkSize = 0x10000; // Block size 0
// block is in main flash
if( ( nSector < NUM_SECTORS ))
{
*ulStartOff =(nSector * ulBlkSize );
*ulEndOff = ( (*ulStartOff) + ulBlkSize ) - 1;
}
// no such sector
else
return INVALID_SECTOR;
// ok
return NO_ERR;
}
//----------- G e t F l a s h S t a r t A d d r e s s ( ) ----------//
//
// PURPOSE
// Gets flash start address from an absolute address.
//
// INPUTS
// unsigned long ulAddr - absolute address
//
// RETURN VALUE
// unsigned long - Flash start address
unsigned long GetFlashStartAddress( unsigned long ulAddr)
{
ERROR_CODE ErrorCode = NO_ERR; //tells us if there was an error erasing flash
unsigned long ulFlashStartAddr; //flash start address
unsigned long ulSectStartAddr; //sector start address
unsigned long ulSectEndAddr; //sector end address
unsigned long ulMask; //offset mask
// get flash start address from absolute address
GetSectorStartEnd( &ulSectStartAddr, &ulSectEndAddr, (NUM_SECTORS-1));
ulMask = ~(ulSectEndAddr);
ulFlashStartAddr = ulAddr & ulMask;
return(ulFlashStartAddr);
}
//----------- R e a d F l a s h ( ) ----------//
//
// PURPOSE
// Reads a value from an address in flash.
//
// INPUTS
// unsigned long ulAddr - the address to read from
// int pnValue - pointer to store value read from flash
//
// RETURN VALUE
// ERROR_CODE - value if any error occurs
// NO_ERR - otherwise
ERROR_CODE ReadFlash( const unsigned long ulAddr, unsigned short *pusValue )
{
// set our flash address to where we want to read
volatile unsigned short *pFlashAddr = (volatile unsigned short *)(ulAddr);
// read the value
*pusValue = (unsigned short)*pFlashAddr;
// ok
return NO_ERR;
}
//----------- W r i t e F l a s h ( ) ----------//
//
// PURPOSE
// Write a value to an address in flash.
//
// INPUTS
// unsigned long ulAddr - address to write to
// unsigned short nValue - value to write
//
// RETURN VALUE
// ERROR_CODE - value if any error occurs
// NO_ERR - otherwise
ERROR_CODE WriteFlash( const unsigned long ulAddr, const unsigned short usValue )
{文章来源:https://www.toymoban.com/news/detail-424129.html
// set the address
volatile unsigned short *pFlashAddr = (volatile unsigned short *)(ulAddr);
*pFlashAddr = usValue;
// ok
return NO_ERR;
}文章来源地址https://www.toymoban.com/news/detail-424129.html
到了这里,关于ADSP-21489的开发详解:Norflash的硬件设计及程序烧写详解(含源代码)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!