all FX functions are now safe wrapped and I added the crate to the docs also it is added to the prelude
This commit is contained in:
parent
3f847e86a6
commit
522844499e
1272 changed files with 1371 additions and 61826 deletions
121
Tools/Arduboy-Python-Utilities/LICENSE
Normal file
121
Tools/Arduboy-Python-Utilities/LICENSE
Normal file
|
@ -0,0 +1,121 @@
|
|||
Creative Commons Legal Code
|
||||
|
||||
CC0 1.0 Universal
|
||||
|
||||
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
||||
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
||||
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
||||
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
||||
HEREUNDER.
|
||||
|
||||
Statement of Purpose
|
||||
|
||||
The laws of most jurisdictions throughout the world automatically confer
|
||||
exclusive Copyright and Related Rights (defined below) upon the creator
|
||||
and subsequent owner(s) (each and all, an "owner") of an original work of
|
||||
authorship and/or a database (each, a "Work").
|
||||
|
||||
Certain owners wish to permanently relinquish those rights to a Work for
|
||||
the purpose of contributing to a commons of creative, cultural and
|
||||
scientific works ("Commons") that the public can reliably and without fear
|
||||
of later claims of infringement build upon, modify, incorporate in other
|
||||
works, reuse and redistribute as freely as possible in any form whatsoever
|
||||
and for any purposes, including without limitation commercial purposes.
|
||||
These owners may contribute to the Commons to promote the ideal of a free
|
||||
culture and the further production of creative, cultural and scientific
|
||||
works, or to gain reputation or greater distribution for their Work in
|
||||
part through the use and efforts of others.
|
||||
|
||||
For these and/or other purposes and motivations, and without any
|
||||
expectation of additional consideration or compensation, the person
|
||||
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
|
||||
is an owner of Copyright and Related Rights in the Work, voluntarily
|
||||
elects to apply CC0 to the Work and publicly distribute the Work under its
|
||||
terms, with knowledge of his or her Copyright and Related Rights in the
|
||||
Work and the meaning and intended legal effect of CC0 on those rights.
|
||||
|
||||
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||
protected by copyright and related or neighboring rights ("Copyright and
|
||||
Related Rights"). Copyright and Related Rights include, but are not
|
||||
limited to, the following:
|
||||
|
||||
i. the right to reproduce, adapt, distribute, perform, display,
|
||||
communicate, and translate a Work;
|
||||
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||
iii. publicity and privacy rights pertaining to a person's image or
|
||||
likeness depicted in a Work;
|
||||
iv. rights protecting against unfair competition in regards to a Work,
|
||||
subject to the limitations in paragraph 4(a), below;
|
||||
v. rights protecting the extraction, dissemination, use and reuse of data
|
||||
in a Work;
|
||||
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||
European Parliament and of the Council of 11 March 1996 on the legal
|
||||
protection of databases, and under any national implementation
|
||||
thereof, including any amended or successor version of such
|
||||
directive); and
|
||||
vii. other similar, equivalent or corresponding rights throughout the
|
||||
world based on applicable law or treaty, and any national
|
||||
implementations thereof.
|
||||
|
||||
2. Waiver. To the greatest extent permitted by, but not in contravention
|
||||
of, applicable law, Affirmer hereby overtly, fully, permanently,
|
||||
irrevocably and unconditionally waives, abandons, and surrenders all of
|
||||
Affirmer's Copyright and Related Rights and associated claims and causes
|
||||
of action, whether now known or unknown (including existing as well as
|
||||
future claims and causes of action), in the Work (i) in all territories
|
||||
worldwide, (ii) for the maximum duration provided by applicable law or
|
||||
treaty (including future time extensions), (iii) in any current or future
|
||||
medium and for any number of copies, and (iv) for any purpose whatsoever,
|
||||
including without limitation commercial, advertising or promotional
|
||||
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
|
||||
member of the public at large and to the detriment of Affirmer's heirs and
|
||||
successors, fully intending that such Waiver shall not be subject to
|
||||
revocation, rescission, cancellation, termination, or any other legal or
|
||||
equitable action to disrupt the quiet enjoyment of the Work by the public
|
||||
as contemplated by Affirmer's express Statement of Purpose.
|
||||
|
||||
3. Public License Fallback. Should any part of the Waiver for any reason
|
||||
be judged legally invalid or ineffective under applicable law, then the
|
||||
Waiver shall be preserved to the maximum extent permitted taking into
|
||||
account Affirmer's express Statement of Purpose. In addition, to the
|
||||
extent the Waiver is so judged Affirmer hereby grants to each affected
|
||||
person a royalty-free, non transferable, non sublicensable, non exclusive,
|
||||
irrevocable and unconditional license to exercise Affirmer's Copyright and
|
||||
Related Rights in the Work (i) in all territories worldwide, (ii) for the
|
||||
maximum duration provided by applicable law or treaty (including future
|
||||
time extensions), (iii) in any current or future medium and for any number
|
||||
of copies, and (iv) for any purpose whatsoever, including without
|
||||
limitation commercial, advertising or promotional purposes (the
|
||||
"License"). The License shall be deemed effective as of the date CC0 was
|
||||
applied by Affirmer to the Work. Should any part of the License for any
|
||||
reason be judged legally invalid or ineffective under applicable law, such
|
||||
partial invalidity or ineffectiveness shall not invalidate the remainder
|
||||
of the License, and in such case Affirmer hereby affirms that he or she
|
||||
will not (i) exercise any of his or her remaining Copyright and Related
|
||||
Rights in the Work or (ii) assert any associated claims and causes of
|
||||
action with respect to the Work, in either case contrary to Affirmer's
|
||||
express Statement of Purpose.
|
||||
|
||||
4. Limitations and Disclaimers.
|
||||
|
||||
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||
surrendered, licensed or otherwise affected by this document.
|
||||
b. Affirmer offers the Work as-is and makes no representations or
|
||||
warranties of any kind concerning the Work, express, implied,
|
||||
statutory or otherwise, including without limitation warranties of
|
||||
title, merchantability, fitness for a particular purpose, non
|
||||
infringement, or the absence of latent or other defects, accuracy, or
|
||||
the present or absence of errors, whether or not discoverable, all to
|
||||
the greatest extent permissible under applicable law.
|
||||
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||
that may apply to the Work or any use thereof, including without
|
||||
limitation any person's Copyright and Related Rights in the Work.
|
||||
Further, Affirmer disclaims responsibility for obtaining any necessary
|
||||
consents, permissions or other rights required for any use of the
|
||||
Work.
|
||||
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||
party to this document and has no duty or obligation with respect to
|
||||
this CC0 or use of the Work.
|
390
Tools/Arduboy-Python-Utilities/fxdata-build.py
Normal file
390
Tools/Arduboy-Python-Utilities/fxdata-build.py
Normal file
|
@ -0,0 +1,390 @@
|
|||
#FX data build tool version 1.15 by Mr.Blinky May 2021 - Mar.2023
|
||||
|
||||
VERSION = '1.15'
|
||||
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import platform
|
||||
|
||||
constants = [
|
||||
#normal bitmap modes
|
||||
("dbmNormal", 0x00),
|
||||
("dbmOverwrite", 0x00),
|
||||
("dbmWhite", 0x01),
|
||||
("dbmReverse", 0x08),
|
||||
("dbmBlack", 0x0D),
|
||||
("dbmInvert", 0x02),
|
||||
#masked bitmap modes for frame
|
||||
("dbmMasked", 0x10),
|
||||
("dbmMasked_dbmWhite", 0x11),
|
||||
("dbmMasked_dbmReverse", 0x18),
|
||||
("dbmMasked_dbmBlack", 0x1D),
|
||||
("dbmMasked_dbmInvert", 0x12),
|
||||
#bitmap modes for last bitmap in a frame
|
||||
("dbmNormal_end", 0x40),
|
||||
("dbmOverwrite_end", 0x40),
|
||||
("dbmWhite_end", 0x41),
|
||||
("dbmReverse_end", 0x48),
|
||||
("dbmBlack_end", 0x4D),
|
||||
("dbmInvert_end", 0x42),
|
||||
#masked bitmap modes for last bitmap in a frame
|
||||
("dbmMasked_end", 0x50),
|
||||
("dbmMasked_dbmWhite_end", 0x51),
|
||||
("dbmMasked_dbmReverse_end", 0x58),
|
||||
("dbmMasked_dbmBlack_end", 0x5D),
|
||||
("dbmMasked_dbmInvert_end", 0x52),
|
||||
#bitmap modes for last bitmap of the last frame
|
||||
("dbmNormal_last", 0x80),
|
||||
("dbmOverwrite_last", 0x80),
|
||||
("dbmWhite_last", 0x81),
|
||||
("dbmReverse_last", 0x88),
|
||||
("dbmBlack_last", 0x8D),
|
||||
("dbmInvert_last", 0x82),
|
||||
#masked bitmap modes for last bitmap in a frame
|
||||
("dbmMasked_last", 0x90),
|
||||
("dbmMasked_dbmWhite_last", 0x91),
|
||||
("dbmMasked_dbmReverse_last", 0x98),
|
||||
("dbmMasked_dbmBlack_last", 0x9D),
|
||||
("dbmMasked_dbmInvert_last", 0x92),
|
||||
]
|
||||
|
||||
def print(s):
|
||||
sys.stdout.write(s + '\n')
|
||||
sys.stdout.flush()
|
||||
|
||||
print('FX data build tool version {} by Mr.Blinky May 2021 - Jan 2023\nUsing Python version {}'.format(VERSION,platform.python_version()))
|
||||
|
||||
bytes = bytearray()
|
||||
symbols = []
|
||||
header = []
|
||||
label = ''
|
||||
indent =''
|
||||
blkcom = False
|
||||
namespace = False
|
||||
include = False
|
||||
try:
|
||||
toolspath = os.path.dirname(os.path.abspath(sys.argv[0]))
|
||||
sys.path.insert(0, toolspath)
|
||||
from PIL import Image
|
||||
except Exception as e:
|
||||
sys.stderr.write(str(e) + "\n")
|
||||
sys.stderr.write("PILlow python module not found or wrong version.\n")
|
||||
sys.stderr.write("Make sure the correct module is installed or placed at {}\n".format(toolspath))
|
||||
sys.exit(-1)
|
||||
|
||||
def rawData(filename):
|
||||
global path
|
||||
with open(path + filename,"rb") as file:
|
||||
bytes = bytearray(file.read())
|
||||
file.close()
|
||||
return bytes
|
||||
|
||||
def includeFile(filename):
|
||||
global path
|
||||
print("Including file {}".format(path + filename))
|
||||
with open(path + filename,"r") as file:
|
||||
lines = file.readlines()
|
||||
file.close()
|
||||
return lines
|
||||
|
||||
def imageData(filename):
|
||||
global path, symbols
|
||||
filename = path + filename
|
||||
|
||||
## parse filename ## FILENAME_[WxH]_[S].[EXT]"
|
||||
spriteWidth = 0
|
||||
spriteHeight = 0
|
||||
spacing = 0
|
||||
elements = os.path.basename(os.path.splitext(filename)[0]).split("_")
|
||||
lastElement = len(elements)-1
|
||||
#get width and height from filename
|
||||
i = lastElement
|
||||
while i > 0:
|
||||
subElements = list(filter(None,elements[i].split('x')))
|
||||
if len(subElements) == 2 and subElements[0].isnumeric() and subElements[1].isnumeric():
|
||||
spriteWidth = int(subElements[0])
|
||||
spriteHeight = int(subElements[1])
|
||||
if i < lastElement and elements[i+1].isnumeric():
|
||||
spacing = int(elements[i+1])
|
||||
break
|
||||
else: i -= 1
|
||||
|
||||
#load image
|
||||
img = Image.open(filename).convert("RGBA")
|
||||
pixels = list(img.getdata())
|
||||
#check for transparency
|
||||
transparency = False
|
||||
for i in pixels:
|
||||
if i[3] < 255:
|
||||
transparency = True
|
||||
break
|
||||
|
||||
# check for multiple frames/tiles
|
||||
if spriteWidth > 0:
|
||||
hframes = (img.size[0] - spacing) // (spriteWidth + spacing)
|
||||
else:
|
||||
spriteWidth = img.size[0] - 2 * spacing
|
||||
hframes = 1
|
||||
if spriteHeight > 0:
|
||||
vframes = (img.size[1] - spacing) // (spriteHeight + spacing)
|
||||
else:
|
||||
spriteHeight = img.size[1] - 2* spacing
|
||||
vframes = 1
|
||||
|
||||
#create byte array for bin file
|
||||
size = (spriteHeight+7) // 8 * spriteWidth * hframes * vframes
|
||||
if transparency:
|
||||
size += size
|
||||
bytes = bytearray([spriteWidth >> 8, spriteWidth & 0xFF, spriteHeight >> 8, spriteHeight & 0xFF])
|
||||
bytes += bytearray(size)
|
||||
i = 4
|
||||
b = 0
|
||||
m = 0
|
||||
fy = spacing
|
||||
frames = 0
|
||||
for v in range(vframes):
|
||||
fx = spacing
|
||||
for h in range(hframes):
|
||||
for y in range (0,spriteHeight,8):
|
||||
line = " "
|
||||
for x in range (0,spriteWidth):
|
||||
for p in range (0,8):
|
||||
b = b >> 1
|
||||
m = m >> 1
|
||||
if (y + p) < spriteHeight: #for heights that are not a multiple of 8 pixels
|
||||
if pixels[(fy + y + p) * img.size[0] + fx + x][1] > 64:
|
||||
b |= 0x80 #white pixel
|
||||
if pixels[(fy + y + p) * img.size[0] + fx + x][3] > 64:
|
||||
m |= 0x80 #opaque pixel
|
||||
else:
|
||||
b &= 0x7F #for transparent pixel clear possible white pixel
|
||||
bytes[i] = b
|
||||
i += 1
|
||||
if transparency:
|
||||
bytes[i] = m
|
||||
i += 1
|
||||
frames += 1
|
||||
fx += spriteWidth + spacing
|
||||
fy += spriteHeight + spacing
|
||||
label = symbols[-1][0]
|
||||
if label.upper() == label:
|
||||
writeHeader('{}constexpr uint16_t {}_WIDTH = {};'.format(indent,label,spriteWidth))
|
||||
writeHeader('{}constexpr uint16_t {}HEIGHT = {};'.format(indent,label,spriteHeight))
|
||||
if frames > 1: writeHeader('{}constexpr uint8_t {}_FRAMES = {};'.format(indent,label,frames))
|
||||
elif '_' in label:
|
||||
writeHeader('{}constexpr uint16_t {}_width = {};'.format(indent,label,spriteWidth))
|
||||
writeHeader('{}constexpr uint16_t {}_height = {};'.format(indent,label,spriteHeight))
|
||||
if frames > 1: writeHeader('{}constexpr uint8_t {}_frames = {};'.format(indent,label,frames))
|
||||
else:
|
||||
writeHeader('{}constexpr uint16_t {}Width = {};'.format(indent,label,spriteWidth))
|
||||
writeHeader('{}constexpr uint16_t {}Height = {};'.format(indent,label,spriteHeight))
|
||||
if frames > 255: writeHeader('{}constexpr uint16_t {}Frames = {};'.format(indent,label,frames))
|
||||
elif frames > 1: writeHeader('{}constexpr uint8_t {}Frames = {};'.format(indent,label,frames))
|
||||
writeHeader('')
|
||||
return bytes
|
||||
|
||||
def addLabel(label,length):
|
||||
global symbols
|
||||
symbols.append((label,length))
|
||||
writeHeader('{}constexpr uint24_t {} = 0x{:06X};'.format(indent,label,length))
|
||||
|
||||
def writeHeader(s):
|
||||
global header
|
||||
header.append(s)
|
||||
|
||||
################################################################################
|
||||
|
||||
if (len(sys.argv) != 2) or (os.path.isfile(sys.argv[1]) != True) :
|
||||
sys.stderr.write("FX data script file not found.\n")
|
||||
sys.exit(-1)
|
||||
|
||||
filename = os.path.abspath(sys.argv[1])
|
||||
datafilename = os.path.splitext(filename)[0] + '-data.bin'
|
||||
savefilename = os.path.splitext(filename)[0] + '-save.bin'
|
||||
devfilename = os.path.splitext(filename)[0] + '.bin'
|
||||
headerfilename = os.path.splitext(filename)[0] + '.h'
|
||||
path = os.path.dirname(filename) + os.sep
|
||||
saveStart = -1
|
||||
|
||||
with open(filename,"r") as file:
|
||||
lines = file.readlines()
|
||||
file.close()
|
||||
|
||||
print("Building FX data using {}".format(filename))
|
||||
lineNr = 0
|
||||
while lineNr < len(lines):
|
||||
parts = [p for p in re.split("([ ,]|[\\'].*[\\'])", lines[lineNr]) if p.strip() and p != ',']
|
||||
for i in range (len(parts)):
|
||||
part = parts[i]
|
||||
#strip unwanted chars
|
||||
if part[:1] == '\t' : part = part[1:]
|
||||
if part[:1] == '{' : part = part[1:]
|
||||
if part[-1:] == '\n': part = part[:-1]
|
||||
if part[-1:] == ';' : part = part[:-1]
|
||||
if part[-1:] == '}' : part = part[:-1]
|
||||
if part[-1:] == ';' : part = part[:-1]
|
||||
if part[-1:] == '.' : part = part[:-1]
|
||||
if part[-1:] == ',' : part = part[:-1]
|
||||
if part[-2:] == '[]': part = part[:-2]
|
||||
#handle comments
|
||||
if blkcom == True:
|
||||
p = part.find('*/',2)
|
||||
if p >= 0:
|
||||
part = part[p+2:]
|
||||
blkcom = False
|
||||
else:
|
||||
if part[:2] == '//':
|
||||
break
|
||||
elif part[:2] == '/*':
|
||||
p = part.find('*/',2)
|
||||
if p >= 0: part = part[p+2:]
|
||||
else: blkcom = True;
|
||||
#handle types
|
||||
elif part == '=' : pass
|
||||
elif part == 'const' : pass
|
||||
elif part == 'PROGMEM' : pass
|
||||
elif part == 'align' : t = 0
|
||||
elif part == 'int8_t' : t = 1
|
||||
elif part == 'uint8_t' : t = 1
|
||||
elif part == 'int16_t' : t = 2
|
||||
elif part == 'uint16_t': t = 2
|
||||
elif part == 'int24_t' : t = 3
|
||||
elif part == 'uint24_t': t = 3
|
||||
elif part == 'int32_t' : t = 4
|
||||
elif part == 'uint32_t': t = 4
|
||||
elif part == 'image_t' : t = 5
|
||||
elif part == 'raw_t' : t = 6
|
||||
elif part == 'String' : t = 7
|
||||
elif part == 'string' : t = 7
|
||||
elif part == 'include' : include = True
|
||||
elif part == 'datasection' : pass
|
||||
elif part == 'savesection' : saveStart = len(bytes)
|
||||
#handle namespace
|
||||
elif part == 'namespace':
|
||||
namespace = True
|
||||
elif namespace == True:
|
||||
namespace = False
|
||||
writeHeader("namespace {}\n{{".format(part))
|
||||
indent += ' '
|
||||
elif part == 'namespace_end':
|
||||
indent = indent[:-2]
|
||||
writeHeader('}\n')
|
||||
namespace = False
|
||||
#handle strings
|
||||
elif (part[:1] == "'") or (part[:1] == '"'):
|
||||
if part[:1] == "'": part = part[1:part.rfind("'")]
|
||||
else: part = part[1:part.rfind('"')]
|
||||
#handle include
|
||||
if include == True:
|
||||
lines[lineNr+1:lineNr+1] = includeFile(part)
|
||||
include = False
|
||||
elif t == 1: bytes += part.encode('utf-8').decode('unicode_escape').encode('utf-8')
|
||||
elif t == 5: bytes += imageData(part)
|
||||
elif t == 6: bytes += rawData(part)
|
||||
elif t == 7: bytes += part.encode('utf-8').decode('unicode_escape').encode('utf-8') + b'\x00'
|
||||
else:
|
||||
sys.stderr.write('ERROR in line {}: unsupported string for type\n'.format(lineNr))
|
||||
sys.exit(-1)
|
||||
#handle values
|
||||
elif part[:1].isnumeric() or (part[:1] == '-' and part[1:2].isnumeric()):
|
||||
n = int(part,0)
|
||||
if t == 4: bytes.append((n >> 24) & 0xFF)
|
||||
if t >= 3: bytes.append((n >> 16) & 0xFF)
|
||||
if t >= 2: bytes.append((n >> 8) & 0xFF)
|
||||
if t >= 1: bytes.append((n >> 0) & 0xFF)
|
||||
#handle align
|
||||
if t == 0:
|
||||
align = len(bytes) % n
|
||||
if align: bytes += b'\xFF' * (n - align)
|
||||
#handle labels
|
||||
elif part[:1].isalpha():
|
||||
for j in range(len(part)):
|
||||
if part[j] == '=':
|
||||
addLabel(label,len(bytes))
|
||||
label = ''
|
||||
part = part[j+1:]
|
||||
parts.insert(i+1,part)
|
||||
break
|
||||
elif part[j].isalnum() or part[j] == '_':
|
||||
label += part[j]
|
||||
else:
|
||||
sys.stderr.write('ERROR in line {}: Bad label: {}\n'.format(lineNr,label))
|
||||
sys.exit(-1)
|
||||
if (label != '') and (i < len(parts) - 1) and (parts[i+1][:1] == '='):
|
||||
addLabel(label,len(bytes))
|
||||
label = ''
|
||||
#handle included constants
|
||||
if label != '':
|
||||
for symbol in constants:
|
||||
if symbol[0] == label:
|
||||
if t == 4: bytes.append((symbol[1] >> 24) & 0xFF)
|
||||
if t >= 3: bytes.append((symbol[1] >> 16) & 0xFF)
|
||||
if t >= 2: bytes.append((symbol[1] >> 8) & 0xFF)
|
||||
if t >= 1: bytes.append((symbol[1] >> 0) & 0xFF)
|
||||
label = ''
|
||||
break
|
||||
#handle symbol values
|
||||
if label != '':
|
||||
for symbol in symbols:
|
||||
if symbol[0] == label:
|
||||
if t == 4: bytes.append((symbol[1] >> 24) & 0xFF)
|
||||
if t >= 3: bytes.append((symbol[1] >> 16) & 0xFF)
|
||||
if t >= 2: bytes.append((symbol[1] >> 8) & 0xFF)
|
||||
if t >= 1: bytes.append((symbol[1] >> 0) & 0xFF)
|
||||
label = ''
|
||||
break
|
||||
if label != '':
|
||||
sys.stderr.write('ERROR in line {}: Undefined symbol: {}\n'.format(lineNr,label))
|
||||
sys.exit(-1)
|
||||
elif len(part) > 0:
|
||||
sys.stderr.write('ERROR unable to parse {} in element: {}\n'.format(part,str(parts)))
|
||||
sys.exit(-1)
|
||||
lineNr += 1
|
||||
|
||||
if saveStart >= 0:
|
||||
dataSize = saveStart
|
||||
dataPages = (dataSize + 255) // 256
|
||||
saveSize = len(bytes) - saveStart
|
||||
savePages = (saveSize + 4095) // 4096 * 16
|
||||
else:
|
||||
dataSize = len(bytes)
|
||||
dataPages = (dataSize + 255) // 256
|
||||
saveSize = 0
|
||||
savePages = 0
|
||||
savePadding = 0
|
||||
dataPadding = dataPages * 256 - dataSize
|
||||
savePadding = savePages * 256 - saveSize
|
||||
|
||||
print("Saving FX data header file {}".format(headerfilename))
|
||||
with open(headerfilename,"w") as file:
|
||||
file.write('#pragma once\n\n')
|
||||
file.write('/**** FX data header generated by fxdata-build.py tool version {} ****/\n\n'.format(VERSION))
|
||||
file.write('using uint24_t = __uint24;\n\n')
|
||||
file.write('// Initialize FX hardware using FX::begin(FX_DATA_PAGE); in the setup() function.\n\n')
|
||||
file.write('constexpr uint16_t FX_DATA_PAGE = 0x{:04x};\n'.format(65536 - dataPages - savePages))
|
||||
file.write('constexpr uint24_t FX_DATA_BYTES = {};\n\n'.format(dataSize))
|
||||
if saveSize > 0:
|
||||
file.write('constexpr uint16_t FX_SAVE_PAGE = 0x{:04x};\n'.format(65536 - savePages))
|
||||
file.write('constexpr uint24_t FX_SAVE_BYTES = {};\n\n'.format(saveSize))
|
||||
for line in header:
|
||||
file.write(line + '\n')
|
||||
file.close()
|
||||
|
||||
print("Saving {} bytes FX data to {}".format(dataSize,datafilename))
|
||||
with open(datafilename,"wb") as file:
|
||||
file.write(bytes[0:dataSize])
|
||||
file.close()
|
||||
if saveSize > 0:
|
||||
print("Saving {} bytes FX savedata to {}".format(saveSize,savefilename))
|
||||
with open(savefilename,"wb") as file:
|
||||
file.write(bytes[saveStart:len(bytes)])
|
||||
file.close()
|
||||
print("Saving FX development data to {}".format(devfilename))
|
||||
with open(devfilename,"wb") as file:
|
||||
file.write(bytes[0:dataSize])
|
||||
if dataPadding > 0: file.write(b'\xFF' * dataPadding)
|
||||
if saveSize > 0:
|
||||
file.write(bytes[saveStart:len(bytes)])
|
||||
if savePadding > 0: file.write(b'\xFF' * savePadding)
|
||||
file.close()
|
229
Tools/Arduboy-Python-Utilities/fxdata-upload.py
Normal file
229
Tools/Arduboy-Python-Utilities/fxdata-upload.py
Normal file
|
@ -0,0 +1,229 @@
|
|||
|
||||
VERSION = '1.20'
|
||||
title ="Arduboy FX data uploader v" + VERSION + " by Mr.Blinky Feb.2022-Mar.2023"
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
|
||||
try:
|
||||
toolspath = os.path.dirname(os.path.abspath(sys.argv[0]))
|
||||
sys.path.insert(0, toolspath)
|
||||
from serial.tools.list_ports import comports
|
||||
from serial import Serial
|
||||
except:
|
||||
sys.stderr.write("pySerial python module not found or wrong version.\n")
|
||||
sys.stderr.write("Make sure the correct module is installed or placed at {}\n".format(toolspath))
|
||||
sys.exit(-1)
|
||||
|
||||
compatibledevices = [
|
||||
#Arduboy Leonardo
|
||||
"VID:PID=2341:0036", "VID:PID=2341:8036",
|
||||
"VID:PID=2A03:0036", "VID:PID=2A03:8036",
|
||||
#Arduboy Micro
|
||||
"VID:PID=2341:0037", "VID:PID=2341:8037",
|
||||
"VID:PID=2A03:0037", "VID:PID=2A03:8037",
|
||||
#Genuino Micro
|
||||
"VID:PID=2341:0237", "VID:PID=2341:8237",
|
||||
#Sparkfun Pro Micro 5V
|
||||
"VID:PID=1B4F:9205", "VID:PID=1B4F:9206",
|
||||
#Adafruit ItsyBitsy 5V
|
||||
"VID:PID=239A:000E", "VID:PID=239A:800E",
|
||||
]
|
||||
|
||||
manufacturers = {
|
||||
0x01 : "Spansion",
|
||||
0x14 : "Cypress",
|
||||
0x1C : "EON",
|
||||
0x1F : "Adesto(Atmel)",
|
||||
0x20 : "Micron",
|
||||
0x37 : "AMIC",
|
||||
0x9D : "ISSI",
|
||||
0xC2 : "General Plus",
|
||||
0xC8 : "Giga Device",
|
||||
0xBF : "Microchip",
|
||||
0xEF : "Winbond"
|
||||
}
|
||||
|
||||
PAGESIZE = 256
|
||||
BLOCKSIZE = 65536
|
||||
PAGES_PER_BLOCK = BLOCKSIZE // PAGESIZE
|
||||
MAX_PAGES = 65536
|
||||
bootloader_active = False
|
||||
|
||||
def print(s):
|
||||
sys.stdout.write(s + '\n')
|
||||
sys.stdout.flush()
|
||||
|
||||
def getComPort(verbose):
|
||||
global bootloader_active
|
||||
devicelist = list(comports())
|
||||
for device in devicelist:
|
||||
for vidpid in compatibledevices:
|
||||
if vidpid in device[2]:
|
||||
port=device[0]
|
||||
bootloader_active = (compatibledevices.index(vidpid) & 1) == 0
|
||||
if verbose : sys.stdout.write("Found {} at port {} ".format(device[1],port))
|
||||
return port
|
||||
if verbose : print("Arduboy not found.")
|
||||
|
||||
def bootloaderStart():
|
||||
global bootloader
|
||||
## find and connect to Arduboy in bootloader mode ##
|
||||
port = getComPort(True)
|
||||
if port is None : sys.exit(-1)
|
||||
if not bootloader_active:
|
||||
print("Selecting bootloader mode...")
|
||||
try:
|
||||
bootloader = Serial(port,1200)
|
||||
time.sleep(0.1)
|
||||
bootloader.close()
|
||||
time.sleep(0.5)
|
||||
except:
|
||||
sys.stderr.write("COM port not available.\n")
|
||||
sys.exit(-1)
|
||||
#wait for disconnect and reconnect in bootloader mode
|
||||
while getComPort(False) == port :
|
||||
time.sleep(0.1)
|
||||
if bootloader_active: break
|
||||
while getComPort(False) is None : time.sleep(0.1)
|
||||
port = getComPort(True)
|
||||
|
||||
sys.stdout.write("Opening port ...")
|
||||
sys.stdout.flush()
|
||||
for retries in range(20):
|
||||
try:
|
||||
time.sleep(0.1)
|
||||
bootloader = Serial(port,57600)
|
||||
break
|
||||
except:
|
||||
if retries == 19:
|
||||
print(" Failed!")
|
||||
sys.exit(-1)
|
||||
sys.stdout.write(".")
|
||||
sys.stdout.flush()
|
||||
time.sleep(0.4)
|
||||
print("\r")
|
||||
|
||||
def getVersion():
|
||||
bootloader.write(b"V")
|
||||
return int(bootloader.read(2))
|
||||
|
||||
def getJedecID():
|
||||
bootloader.write(b"j")
|
||||
jedec_id = bootloader.read(3)
|
||||
time.sleep(0.5)
|
||||
bootloader.write(b"j")
|
||||
jedec_id2 = bootloader.read(3)
|
||||
if jedec_id2 != jedec_id or jedec_id == b'\x00\x00\x00' or jedec_id == b'\xFF\xFF\xFF':
|
||||
sys.stderr.write("No FX flash chip detected.\n")
|
||||
sys.exit(-1)
|
||||
return bytearray(jedec_id)
|
||||
|
||||
def bootloaderExit():
|
||||
global bootloader
|
||||
bootloader.write(b"E")
|
||||
bootloader.read(1)
|
||||
|
||||
################################################################################
|
||||
|
||||
def writeFlash(pagenumber, flashdata):
|
||||
bootloaderStart()
|
||||
|
||||
#check version
|
||||
if getVersion() < 13:
|
||||
sys.stderr.write("Bootloader has no flash cart support\nWrite aborted!\n")
|
||||
sys.exit(-1)
|
||||
|
||||
## detect flash cart ##
|
||||
jedec_id = getJedecID()
|
||||
if jedec_id[0] in manufacturers.keys():
|
||||
manufacturer = manufacturers[jedec_id[0]]
|
||||
else:
|
||||
manufacturer = "unknown"
|
||||
capacity = 1 << jedec_id[2]
|
||||
print("Detected FX flash chip with ID {:02X}{:02X}{:02X} size {}KB".format(jedec_id[0],jedec_id[1],jedec_id[2],capacity // 1024))
|
||||
|
||||
oldtime=time.time()
|
||||
# when starting partially in a block, preserve the beginning of old block data
|
||||
if pagenumber % PAGES_PER_BLOCK:
|
||||
blocklen = pagenumber % PAGES_PER_BLOCK * PAGESIZE
|
||||
blockaddr = pagenumber // PAGES_PER_BLOCK * PAGES_PER_BLOCK
|
||||
#read partial block data start
|
||||
bootloader.write(bytearray([ord("A"), blockaddr >> 8, blockaddr & 0xFF]))
|
||||
bootloader.read(1)
|
||||
bootloader.write(bytearray([ord("g"), (blocklen >> 8) & 0xFF, blocklen & 0xFF,ord("C")]))
|
||||
flashdata = bootloader.read(blocklen) + flashdata
|
||||
pagenumber = blockaddr
|
||||
|
||||
# when ending partially in a block, preserve the ending of old block data
|
||||
if len(flashdata) % BLOCKSIZE:
|
||||
blocklen = BLOCKSIZE - len(flashdata) % BLOCKSIZE
|
||||
blockaddr = pagenumber + len(flashdata) // PAGESIZE
|
||||
#read partial block data end
|
||||
bootloader.write(bytearray([ord("A"), blockaddr >> 8, blockaddr & 0xFF]))
|
||||
bootloader.read(1)
|
||||
bootloader.write(bytearray([ord("g"), (blocklen >> 8) & 0xFF, blocklen & 0xFF,ord("C")]))
|
||||
flashdata += bootloader.read(blocklen)
|
||||
|
||||
## write to flash cart ##
|
||||
blocks = len(flashdata) // BLOCKSIZE
|
||||
for block in range (blocks):
|
||||
if (block & 1 == 0) or verifyAfterWrite:
|
||||
bootloader.write(b"x\xC2") #RGB LED RED, buttons disabled
|
||||
else:
|
||||
bootloader.write(b"x\xC0") #RGB LED OFF, buttons disabled
|
||||
bootloader.read(1)
|
||||
sys.stdout.write("\rWriting block {}/{} ".format(block + 1,blocks))
|
||||
sys.stdout.flush()
|
||||
blockaddr = pagenumber + block * BLOCKSIZE // PAGESIZE
|
||||
blocklen = BLOCKSIZE
|
||||
#write block
|
||||
bootloader.write(bytearray([ord("A"), blockaddr >> 8, blockaddr & 0xFF]))
|
||||
bootloader.read(1)
|
||||
bootloader.write(bytearray([ord("B"), (blocklen >> 8) & 0xFF, blocklen & 0xFF,ord("C")]))
|
||||
bootloader.write(flashdata[block * BLOCKSIZE : block * BLOCKSIZE + blocklen])
|
||||
bootloader.read(1)
|
||||
if verifyAfterWrite:
|
||||
sys.stdout.write("\rVerifying block {}/{}".format(block + 1,blocks))
|
||||
sys.stdout.flush()
|
||||
bootloader.write(b"x\xC1") #RGB BLUE RED, buttons disabled
|
||||
bootloader.read(1)
|
||||
bootloader.write(bytearray([ord("A"), blockaddr >> 8, blockaddr & 0xFF]))
|
||||
bootloader.read(1)
|
||||
bootloader.write(bytearray([ord("g"), (blocklen >> 8) & 0xFF, blocklen & 0xFF,ord("C")]))
|
||||
if bootloader.read(blocklen) != flashdata[block * BLOCKSIZE : block * BLOCKSIZE + blocklen]:
|
||||
sys.stderr.write(" verify failed!\n\nWrite aborted.")
|
||||
bootloader.write(b"x\x40")#RGB LED off, buttons enabled
|
||||
bootloader.read(1)
|
||||
sys.exit(-1)
|
||||
|
||||
#write complete
|
||||
bootloader.write(b"x\x44")#RGB LED GREEN, buttons enabled
|
||||
bootloader.read(1)
|
||||
time.sleep(0.5)
|
||||
bootloader.write(b"x\x40")#RGB LED off, buttons enabled
|
||||
bootloader.read(1)
|
||||
bootloader.close()
|
||||
print("\rFX Data uploaded successfully")
|
||||
|
||||
################################################################################
|
||||
|
||||
print(title)
|
||||
|
||||
if (len(sys.argv) != 2) or (os.path.isfile(sys.argv[1]) != True) :
|
||||
sys.stderr.write("FX data file not found.\n")
|
||||
|
||||
filename = os.path.abspath(sys.argv[1])
|
||||
|
||||
verifyAfterWrite = True
|
||||
|
||||
print('Uploading FX data from file "{}"'.format(filename))
|
||||
f = open(filename,"rb")
|
||||
programdata = bytearray(f.read())
|
||||
f.close()
|
||||
if len(programdata) % PAGESIZE:
|
||||
programdata += b'\xFF' * (PAGESIZE - (len(programdata) % PAGESIZE))
|
||||
programpage = MAX_PAGES - (len(programdata) // PAGESIZE)
|
||||
|
||||
writeFlash(programpage, programdata)
|
Loading…
Add table
Add a link
Reference in a new issue