openblt/Host/Source/interfaces/SRecReader.pas

650 lines
22 KiB
ObjectPascal

unit SRecReader;
//***************************************************************************************
// Project Name: Motorola S-Record Reader
// Description: Class used to read S-Record files. Supports both S19 and S28 records.
// File Name: SRecReader.pas
//
//---------------------------------------------------------------------------------------
// C O P Y R I G H T
//---------------------------------------------------------------------------------------
// Copyright (c) 2011 by Feaser http://www.feaser.com All rights reserved
//
// This software has been carefully tested, but is not guaranteed for any particular
// purpose. The author does not offer any warranties and does not guarantee the accuracy,
// adequacy, or completeness of the software and is not responsible for any errors or
// omissions or the results obtained from use of the software.
//
//---------------------------------------------------------------------------------------
// L I C E N S E
//---------------------------------------------------------------------------------------
// This file is part of OpenBLT. OpenBLT is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option) any later
// version.
//
// OpenBLT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with OpenBLT.
// If not, see <http://www.gnu.org/licenses/>.
//
// A special exception to the GPL is included to allow you to distribute a combined work
// that includes OpenBLT without being obliged to provide the source code for any
// proprietary components. The exception text is included at the bottom of the license
// file <license.html>.
//
//***************************************************************************************
interface
//***************************************************************************************
// Includes
//***************************************************************************************
uses
Windows, Messages, SysUtils, Classes, Forms;
//***************************************************************************************
// Type Definitions
//***************************************************************************************
type
PByte = ^Byte;
TSRecLineType = (ltInvalid, ltS0, ltS1, ltS2, ltS3, ltS7, ltS8, ltS9);
TSRecType = (tpS1, tpS2, tpS3, tpMixed);
type
TSRecData = record
val : Byte;
addr : LongWord;
end;
type
PRegion = ^TRegion;
TRegion = record
addr : LongWord;
size : LongWord;
end;
type
TSRecReader = class(TObject)
private
FFileName : String;
FDataSize : Longword;
FFirstAddr : LongWord;
FLastAddr : LongWord;
FFileType : TSRecType;
FRegions : TList;
FRegionsCnt : Word;
function GetLineType(line: String) : TSRecLineType;
function GetLineSize(line: String; var size: Word) : Boolean;
function GetLineData(line: String; index: Word; var data: TSRecData) : Boolean;
function GetLineAddress(line: String; var address: LongWord) : Boolean;
procedure UpdateRegions(addr: LongWord; size: Word);
public
constructor Create;
destructor Destroy; override;
function SetFileName(fileName : String) : Boolean;
function GetFileName : String;
function GetDataSize : Longword;
function GetFirstAddress : Longword;
function GetLastAddress : Longword;
function GetRegionCount : Word;
procedure GetRegion(index : Word; var addr : Longword; var length : Longword);
procedure GetData(var buffer : array of Byte; addr : Longword; length : Longword);
function GetFileType : TSRecType;
function BlankCheck(addr : Longword; length : Longword) : Boolean;
end;
implementation
//***************************************************************************************
// NAME: Create
// PARAMETER: none
// RETURN VALUE: none
// DESCRIPTION: Class constructore
//
//***************************************************************************************
constructor TSRecReader.Create;
begin
inherited Create;
FRegions := TList.Create;
end; //*** end of Create ***
//***************************************************************************************
// NAME: Destroy
// PARAMETER: none
// RETURN VALUE: none
// DESCRIPTION: Class destructor
//
//***************************************************************************************
destructor TSRecReader.Destroy;
var
index : Word;
region: PRegion;
begin
if FRegions.Count > 0 then
begin
for index := 0 to FRegions.Count-1 do
begin
region := FRegions.Items[index];
Dispose(region);
end;
end;
FRegions.Free;
inherited;
end; //*** end of Destroy ***
//***************************************************************************************
// NAME: UpdateRegions
// PARAMETER: addr: start address of data
// size: length of data
// RETURN VALUE: none
// DESCRIPTION: Processes the data by categorizing it into a region.
//
//***************************************************************************************
procedure TSRecReader.UpdateRegions(addr: LongWord; size: Word);
var
index : Word;
added : Boolean;
region: PRegion;
begin
added := False;
if FRegions.Count > 0 then
begin
// loop through all existing regions
for index := 0 to FRegions.Count-1 do
begin
// set pointer to this region
region := FRegions.Items[index];
// does data fit at the end?
if (region^.addr+region^.size) = addr then
begin
// add at the end of this region
region^.size := region^.size + size;
added := True;
end
// does data fit at the start?
else if region^.addr = (addr+size) then
begin
// add at the start of this region
region^.addr := region^.addr - size;
added := True;
end;
// was data added to a region?
if added then
begin
Break; // no need to continue loop
end;
end;
end;
// data couldn't be added to an existing region?
if not added then
begin
// create a new region and add the data there
New(region);
region^.addr := addr;
region^.size := size;
FRegions.Add(region);
end;
end; //*** end of UpdateRegions ***
//***************************************************************************************
// NAME: GetLineType
// PARAMETER: Line from S-Record
// RETURN VALUE: line type
// DESCRIPTION: Determines what type of S-Record line we're dealing with.
//
//***************************************************************************************
function TSRecReader.GetLineType(line: String) : TSRecLineType;
begin
Result := ltInvalid;
if Pos('S0', UpperCase(line)) > 0 then Result := ltS0;
if Pos('S1', UpperCase(line)) > 0 then Result := ltS1;
if Pos('S2', UpperCase(line)) > 0 then Result := ltS2;
if Pos('S3', UpperCase(line)) > 0 then Result := ltS3;
if Pos('S7', UpperCase(line)) > 0 then Result := ltS7;
if Pos('S8', UpperCase(line)) > 0 then Result := ltS8;
if Pos('S9', UpperCase(line)) > 0 then Result := ltS9;
end; //*** end of GetLineType ***
//***************************************************************************************
// NAME: GetLineSize
// PARAMETER: Line from S-Record
// RETURN VALUE: Number of data bytes
// DESCRIPTION: Obtains the number of databytes in the specified S-Record line.
//
//***************************************************************************************
function TSRecReader.GetLineSize(line: String; var size: Word) : Boolean;
var
unusedBytes : Byte;
begin
Result := false; // init
if GetLineType(line) = ltS1 then
unusedBytes := 3 // 2 for address and 1 for checksum
else if GetLineType(line) = ltS2 then
unusedBytes := 4 // 3 for address and 1 for checksum
else if GetLineType(line) = ltS3 then
unusedBytes := 5 // 4 for address and 1 for checksum
else
Exit; // not a S1 or S2 line
size := StrToInt('$' + Copy(line, 3, 2)) - unusedBytes;
Result := true;
end; //*** end of GetLineSize ***
//***************************************************************************************
// NAME: GetLineData
// PARAMETER: Line from S-Record
// RETURN VALUE: data at a specific index (starts at 0)
// DESCRIPTION: Obtains the data at a specific index in the specified S-Record line.
//
//***************************************************************************************
function TSRecReader.GetLineData(line: String; index: Word; var data: TSRecData) : Boolean;
var
dataOffset : Byte;
lineSize : Word;
lineAddr : LongWord;
begin
Result := false; // init
if GetLineType(line) = ltS1 then
dataOffset := 9 // characters untill we reach actual data
else if GetLineType(line) = ltS2 then
dataOffset := 11 // characters untill we reach actual data
else if GetLineType(line) = ltS3 then
dataOffset := 13 // characters untill we reach actual data
else
Exit; // not a S1, S2 or S3 line
// make sure index parameter is valid
if not GetLineSize(line, lineSize) then
Exit
else
if index > (lineSize-1) then Exit;
// obtain the start address of the line
if not GetLineAddress(line, lineAddr) then
Exit;
data.val := StrToInt('$' + Copy(line, dataOffset+(index*2), 2));
data.addr := lineAddr + index;
Result := true;
end; //*** end of GetLineData ***
//***************************************************************************************
// NAME: GetLineAddress
// PARAMETER: Line from S-Record
// RETURN VALUE: Start address
// DESCRIPTION: Obtains the start address as specified in the S-Record line.
//
//***************************************************************************************
function TSRecReader.GetLineAddress(line: String; var address: LongWord) : Boolean;
var
addrLength : Byte;
begin
Result := false; // init
if GetLineType(line) = ltS1 then
addrLength := 4 // 2 byte address
else if GetLineType(line) = ltS2 then
addrLength := 6 // 3 byte address
else if GetLineType(line) = ltS3 then
addrLength := 8 // 4 byte address
else
Exit; // not a S1, S2 or S3 line
address := StrToInt('$' + Copy(line, 5, addrLength));
Result := true;
end; //*** end of GetLineAddress ***
//***************************************************************************************
// NAME: SetFileName
// PARAMETER: Filename with full path
// RETURN VALUE: True when the S-Record was valid and could be loaded, otherwise False.
// DESCRIPTION: Function verifies that the file is actually a S-Record. If so, then
// the most class properties are set based on the info in the S-Record.
//
//***************************************************************************************
function TSRecReader.SetFileName(fileName : String) : Boolean;
var
SRecFile : TextFile;
Line : string;
ValidSRec : Boolean;
LineSize : Word;
LineAddr : LongWord;
S1RecFound : Boolean;
S2RecFound : Boolean;
S3RecFound : Boolean;
begin
// first reset all the internal properties
FFileName := '';
FDataSize := 0;
FFirstAddr := $ffffffff;
FLastAddr := 0;
FFileType := tpS1;
FRegionsCnt := 0;
FRegions.Clear;
// init locals
S1RecFound := false;
S2RecFound := false;
S3RecFound := false;
Result := false;
ValidSRec := false;
// 1. Verify if file exists
if not FileExists(fileName) then Exit;
// 2. Verify if file contains S-Records
AssignFile(SRecFile, fileName); // get file handle
Reset(SRecFile); // go to start of file
while not Eof(SRecFile) do
begin
ReadLn(SRecFile, Line); // read line from file
if (GetLineType(Line) = ltS1) or (GetLineType(Line) = ltS2) or
(GetLineType(Line) = ltS3) then
begin
ValidSRec := true;
Break; // valid S-Record
end;
end;
CloseFile(SRecFile); // release file
if not ValidSRec then Exit;
// 3. Calculate datasize, first address and last address
AssignFile(SRecFile, fileName); // get file handle
Reset(SRecFile); // go to start of file
while not Eof(SRecFile) do
begin
ReadLn(SRecFile, Line); // read line from file
LineSize := 0; // init
if GetLineSize(Line, LineSize) then
FDataSize := FDataSize + LineSize; // add to previous value
if GetLineAddress(Line, LineAddr) then
begin
if LineAddr < FFirstAddr then
FFirstAddr := LineAddr;
if LineAddr > FLastAddr then
FLastAddr := LineAddr + LineSize -1;
end;
// check line type
if GetLineType(line) = ltS1 then
S1RecFound := true;
if GetLineType(line) = ltS2 then
S2RecFound := true;
if GetLineType(line) = ltS3 then
S3RecFound := true;
end;
CloseFile(SRecFile); // release file
// set file type
if (S1RecFound) and (not S2RecFound) and (not S3RecFound) then
FFileType := tpS1
else if (S2RecFound) and (not S1RecFound) and (not S3RecFound) then
FFileType := tpS2
else if (not S2RecFound) and (not S1RecFound) and (S3RecFound) then
FFileType := tpS3
else
FFileType := tpMixed;
// 4. Determine regions
AssignFile(SRecFile, fileName); // get file handle
Reset(SRecFile); // go to start of file
while not Eof(SRecFile) do
begin
ReadLn(SRecFile, Line); // read line from file
LineSize := 0; // init
if GetLineAddress(Line, LineAddr) then
begin
if GetLineSize(Line, LineSize) then
begin
UpdateRegions(LineAddr, LineSize);
end;
end;
end;
CloseFile(SRecFile); // release file
// set region count
FRegionsCnt := FRegions.Count;
// 5. Verify properties and if ok, set the FFilename property
if (FDataSize <> 0) and (FFirstAddr <> $ffffffff) and (FLastAddr <> 0) then
begin
FFileName := fileName; // set the filename property
Result := true;
end;
end; //*** end of SetFileName ***
//***************************************************************************************
// NAME: GetFileName
// PARAMETER: none
// RETURN VALUE: Filename with full path or '' when invalid.
// DESCRIPTION: S-Record filename that is configured to be read be this class.
//
//***************************************************************************************
function TSRecReader.GetFileName : String;
begin
Result := FFileName;
end; //*** end of GetFileName ***
//***************************************************************************************
// NAME: GetDataSize
// PARAMETER: none
// RETURN VALUE: Size of data.
// DESCRIPTION: Obtains the number of databytes in the S-Record. 0xFF values are
// not included.
//
//***************************************************************************************
function TSRecReader.GetDataSize : Longword;
begin
Result := FDataSize;
end; //*** end of GetDataSize ***
//***************************************************************************************
// NAME: GetFirstAddress
// PARAMETER: none
// RETURN VALUE: First address in S-Record.
// DESCRIPTION: Obtains the first memory address with data in the S-Record.
//
//***************************************************************************************
function TSRecReader.GetFirstAddress : Longword;
begin
Result := FFirstAddr;
end; //*** end of GetFirstAddress ***
//***************************************************************************************
// NAME: GetLastAddress
// PARAMETER: none
// RETURN VALUE: Last address in S-Record.
// DESCRIPTION: Obtains the last memory address with data in the S-Record.
//
//***************************************************************************************
function TSRecReader.GetLastAddress : Longword;
begin
Result := FLastAddr;
end; //*** end of GetLastAddress ***
//***************************************************************************************
// NAME: GetRegionCount
// PARAMETER: none
// RETURN VALUE: Number of address regions
// DESCRIPTION: Obtains the number of address regions found in the S-record.
//
//***************************************************************************************
function TSRecReader.GetRegionCount : Word;
begin
Result := FRegionsCnt;
end; //*** end of GetRegionCount ***
//***************************************************************************************
// NAME: GetRegion
// PARAMETER: index: region index to retrieve info from
// addr: destination for region's start address
// length: destination for region's length
// RETURN VALUE: none
// DESCRIPTION: Obtains the address region info.
//
//***************************************************************************************
procedure TSRecReader.GetRegion(index : Word; var addr : Longword; var length : Longword);
var
region: PRegion;
begin
// initialize return values
addr := 0;
length := 0;
if FRegions.Count > 0 then
begin
if index < FRegions.Count then
begin
// set pointer to this region
region := FRegions.Items[index];
// obtain region info
addr := region^.addr;
length := region^.size;
end;
end;
end; //*** end of GetRegion ***
//***************************************************************************************
// NAME: GetData
// PARAMETER: buffer is a pointer to a byte buffer where the data will be written
// to from memory address addr to addr+length.
// RETURN VALUE: none
// DESCRIPTION: Obtains the data in the S-Record for a memory block that starts at
// addr and goes until addr+length and stores the data in buffer. empty
// data will be filled with 0xFF.
//
//***************************************************************************************
procedure TSRecReader.GetData(var buffer : array of Byte; addr : Longword; length : Longword);
var
data : TSRecData;
line : string;
lineAddr : LongWord;
lineSize : Word;
SRecFile : TextFile;
cnt : Word;
ok2Continue : boolean;
begin
// check parameters
if length = 0 then Exit;
// first init entire buffer to 0xff
for cnt := 0 to length-1 do
begin
buffer[cnt] := $ff;
end;
// go through each line to see if it has data for us
AssignFile(SRecFile, FFileName); // get file handle
Reset(SRecFile); // go to start of file
while not Eof(SRecFile) do
begin
// this can take a while so process messages to not stall the parent app
Application.ProcessMessages;
ReadLn(SRecFile, line); // read line from file
ok2Continue := true; // init
// obtain line properties
if not GetLineAddress(line, lineAddr) then ok2Continue := false;
if not GetLineSize(line, lineSize) then ok2Continue := false;
if ok2Continue then
begin
// process all data on the line
for cnt := 0 to lineSize-1 do
begin
// read data info
if not GetLineData(line, cnt, data) then ok2Continue := false;
if ok2Continue then
begin
// is this one for us?
if (data.addr >= addr) and (data.addr <= (addr+length-1)) then
begin
// store it in the memory buffer
buffer[data.addr-addr] := data.val;
end;
end;
end;
end;
end;
CloseFile(SRecFile); // release file
end; //*** end of GetData ***
//***************************************************************************************
// NAME: GetFileType
// PARAMETER: none
// RETURN VALUE: S-Record file type
// DESCRIPTION: Determines is the file contains just S1 data lines, just S2 data
// lines. or a combination of these two.
//
//***************************************************************************************
function TSRecReader.GetFileType : TSRecType;
begin
Result := FFileType;
end; //*** end of GetFileType ***
//***************************************************************************************
// NAME: BlankCheck
// PARAMETER: checks from addr to addr+length-1.
// RETURN VALUE: true if all bytes are 0xff, false otherwise
// DESCRIPTION: Checks if a memory range in the S-Record file is empty (0xff) or not.
//
//***************************************************************************************
function TSRecReader.BlankCheck(addr : Longword; length : Longword) : Boolean;
var
buffer : array of Byte;
cnt : LongWord;
begin
Result := true;
SetLength(buffer, length); // init size of the dynamic array
GetData(buffer, addr, length); // fill it with the data contents from the S-Record
for cnt := 0 to length-1 do
begin
if buffer[cnt] <> $ff then
begin
Result := false; // memory range is not blank
Break; // no need to continue loop
end;
end;
end; //*** end of BlankCheck ***
end.
//******************************** end of SRecReader.pas ********************************