example: add gdrom_iso9660

This combines my iso9660 parsing code, with all of the prior gdrom packet
interface / command code.

The example, on real Dreamcast hardware, displays the first 2048 bytes [1] of every
file in the root directory on the serial console.

[1] or the size of the file, whichever is smaller
This commit is contained in:
Zack Buhman 2024-02-27 00:28:39 +08:00
parent c45cb293f0
commit 10d17d3c98
16 changed files with 462 additions and 116 deletions

View File

@ -160,12 +160,18 @@ sh7091/sh7091.hpp: regs/sh7091.csv regs/gen/sh7091.py
sh7091/sh7091_bits.hpp: regs/sh7091_bits.csv regs/gen/core_bits.py
python regs/gen/core_bits.py $< > $@
gdrom/gdrom.hpp: regs/gdrom.csv regs/gen/gdrom.py
python regs/gen/gdrom.py $< gdrom > $@
gdrom/gdrom_bits.hpp: regs/gdrom_bits.csv regs/gen/core_bits.py
python regs/gen/core_bits.py $< gdrom > $@
gdrom/command_packet_format.hpp: regs/gdrom_command_packet_format.csv regs/gen/gdrom_command_packet_format.py regs/gen/generic_sparse_struct.py
python regs/gen/gdrom_command_packet_format.py $< gdrom_command_packet_format > $@
iso9660/%.hpp: iso9660/%.csv iso9660/byte_position.py
python iso9660/byte_position.py $< > $@
clean:
find -P \
-regextype posix-egrep \

View File

@ -357,3 +357,10 @@ GDROM_TEST_OBJ = \
example/gdrom_test.elf: LDSCRIPT = $(LIB)/alt.lds
example/gdrom_test.elf: $(START_OBJ) $(GDROM_TEST_OBJ)
GDROM_ISO9660_OBJ = \
example/gdrom_iso9660.o \
sh7091/serial.o
example/gdrom_iso9660.elf: LDSCRIPT = $(LIB)/alt.lds
example/gdrom_iso9660.elf: $(START_OBJ) $(GDROM_ISO9660_OBJ)

213
example/gdrom_iso9660.cpp Normal file
View File

@ -0,0 +1,213 @@
#include <cstdint>
#include "memorymap.hpp"
#include "systembus.hpp"
#include "sh7091/serial.hpp"
#include "gdrom/gdrom.hpp"
#include "gdrom/gdrom_bits.hpp"
#include "gdrom/command_packet_format.hpp"
#include "gdrom/toc.hpp"
#include "iso9660/primary_volume_descriptor.hpp"
#include "iso9660/directory_record.hpp"
void pio_data(const uint8_t * data)
{
while ((gdrom::status::bsy(gdrom_if.status) | gdrom::status::drq(gdrom_if.status)) != 0);
gdrom_if.features = gdrom::features::dma::disable;
gdrom_if.drive_select = gdrom::drive_select::drive_select
| gdrom::drive_select::lun(0);
gdrom_if.command = gdrom::command::code::packet_command;
while (gdrom::status::drq(gdrom_if.status) == 0);
const uint16_t * buf = reinterpret_cast<const uint16_t *>(&data[0]);
for (int i = 0; i < 6; i++) {
gdrom_if.data = buf[i];
}
while (gdrom::status::bsy(gdrom_if.status) != 0);
}
void read_data(uint16_t * buf, const uint32_t length)
{
serial::string("read_data drq interrupt_reason: ");
serial::integer<uint8_t>(gdrom::status::drq(gdrom_if.status), ' ');
serial::integer<uint8_t>(gdrom_if.interrupt_reason);
for (uint32_t i = 0; i < (length / 2); i++) {
buf[i] = gdrom_if.data;
}
}
uint32_t toc__get_data_track_fad()
{
auto packet = gdrom_command_packet_format::get_toc(0, // single-density
0x0198 // maximum toc length
);
serial::string("get_toc\n");
pio_data(packet._data());
serial::string("byte_count: ");
serial::integer<uint16_t>(gdrom_if.byte_count());
uint16_t buf[gdrom_if.byte_count() / 2];
read_data(buf, gdrom_if.byte_count());
serial::string("status: ");
serial::integer<uint8_t>(gdrom_if.status);
auto toc = reinterpret_cast<const struct gdrom_toc::toc *>(buf);
for (int i = 0; i < 99; i++) {
if (toc->track[i].fad() == 0xffffff)
break;
serial::string("track ");
serial::integer<uint8_t>(i);
serial::integer<uint32_t>(toc->track[i].fad());
}
// assume track 1 is the correct track
return toc->track[1].fad();
}
uint32_t cd_read(uint16_t * buf,
const uint32_t starting_address,
const uint32_t transfer_length)
{
const uint8_t data_select = 0b0010; // data
const uint8_t expected_data_type = 0b100; // XA mode 2 form 1
const uint8_t parameter_type = 0b0; // FAD specified
const uint8_t data = (data_select << 4) | (expected_data_type << 1) | (parameter_type << 0);
auto packet = gdrom_command_packet_format::cd_read(data,
starting_address,
transfer_length);
auto arst = packet._data();
serial::string("cd_read\n");
serial::string("starting_address: ");
serial::integer<uint32_t>(starting_address);
pio_data(packet._data());
uint32_t length = 0;
while ((gdrom::status::drq(gdrom_if.status)) != 0) {
const uint32_t byte_count = gdrom_if.byte_count();
length += byte_count;
read_data(buf, byte_count);
}
serial::string("status: ");
serial::integer<uint8_t>(gdrom_if.status);
serial::string("read length: ");
serial::integer<uint32_t>(length);
return length;
}
void main()
{
// gdrom unlock undocumented register
g1_if.GDUNLOCK = 0x1fffff;
// Without this read from system_boot_rom, the read value of
// gdrom_if.status is always 0xff
for(uint32_t i = 0; i < 0x200000 / 4; i++) {
(void)system_boot_rom[i];
}
const uint32_t fad = toc__get_data_track_fad();
serial::character('\n');
const uint32_t primary_volume_descriptor = fad + 16;
uint16_t buf[2048 / 2];
const uint32_t length0 = cd_read(buf,
primary_volume_descriptor,
1 // one sector; 2048 bytes
);
serial::character('\n');
auto pvd = reinterpret_cast<const iso9660::primary_volume_descriptor *>(&buf[0]);
auto root_dr = reinterpret_cast<const iso9660::directory_record *>(&pvd->directory_record_for_root_directory[0]);
serial::string("primary volume descriptor:\n");
serial::string(" standard_identifier: ");
serial::string(pvd->standard_identifier, 5);
serial::character('\n');
serial::string(" root directory record:\n");
serial::string(" location of extent: ");
serial::integer<uint32_t>(root_dr->location_of_extent.get());
serial::string(" data length: ");
serial::integer<uint32_t>(root_dr->data_length.get());
serial::character('\n');
/*
for (int i = 0; i < 2048; i++) {
serial::hexlify(buf8[i]);
serial::character(' ');
if ((i % 16) == 15)
serial::character('\n');
}
*/
serial::character('\n');
const uint32_t root_directory_extent = root_dr->location_of_extent.get();
const uint32_t length1 = cd_read(buf,
root_directory_extent + 150, // 150?
1 // one sector; 2048 bytes
);
serial::character('\n');
auto buf8 = reinterpret_cast<const uint8_t *>(buf);
uint32_t offset = 0;
while (true) {
serial::string("directory entry offset: ");
serial::integer<uint32_t>(offset);
auto dr = reinterpret_cast<const iso9660::directory_record *>(&buf8[offset]);
if (dr->length_of_directory_record == 0)
break;
serial::string(" length_of_directory_record: ");
serial::integer<uint8_t>(dr->length_of_directory_record);
serial::string(" length_of_file_identifier: ");
serial::integer<uint8_t>(dr->length_of_file_identifier);
serial::string(" file_identifier: ");
serial::string(dr->file_identifier, dr->length_of_file_identifier);
serial::character('\n');
if (dr->file_flags == 0) {
serial::string(" location_of_extent: ");
serial::integer<uint32_t>(dr->location_of_extent.get());
serial::string(" data_length: ");
serial::integer<uint32_t>(dr->data_length.get());
if (dr->file_identifier[0] != '1') {
const uint32_t extent = dr->location_of_extent.get();
uint16_t buf2[2048 / 2];
const uint32_t length1 = cd_read(buf2,
extent + 150, // 150?
1 // one sector; 2048 bytes
);
auto file = reinterpret_cast<const uint8_t *>(&buf2[0]);
serial::string("---begin file content---\n");
serial::string(file, dr->data_length.get());
serial::string("---end file content---\n");
}
}
offset += dr->length_of_directory_record;
}
serial::integer<uint32_t>(offset);
while (1);
}
/*
for (int i = 0; i < 12; i++) {
serial::hexlify(arst[i]);
serial::character(' ');
}
serial::character('\n');
*/

View File

@ -1,8 +1,9 @@
#include "gdrom/gdrom.hpp"
#include "gdrom/gdrom_bits.hpp"
#include <cstdint>
#include "memorymap.hpp"
#include "systembus.hpp"
#include "gdrom/gdrom.hpp"
#include "gdrom/gdrom_bits.hpp"
#include "sh7091/serial.hpp"
void test_unit()
@ -172,7 +173,7 @@ void get_toc()
41 00 2f 7c (lead-out information)
*/
void cd_read()
void cd_read2()
{
// CD-ROM XA mode 2 form 1
@ -182,7 +183,7 @@ void cd_read()
const uint8_t data[12] = {
0x31, // CD_READ
0x31, // CD_READ2
(data_select << 4) | (expected_data_type << 1) | (parameter_type << 0),
0x00, // starting address (msb)
@ -238,7 +239,7 @@ void main()
req_mode();
get_toc();
cd_read();
cd_read2();
while (1);
}

View File

@ -7,15 +7,15 @@ namespace gdrom_command_packet_format {
template <>
constexpr void byte_order<2>(uint8_t * buf, const uint32_t n)
{
buf[0] = n >> 8;
buf[1] = n >> 0;
buf[0] = (n >> 8) & 0xff;
buf[1] = (n >> 0) & 0xff;
}
template <>
constexpr void byte_order<3>(uint8_t * buf, const uint32_t n)
{
buf[0] = n >> 16;
buf[1] = n >> 8;
buf[1] = n >> 0;
buf[0] = (n >> 16) & 0xff;
buf[1] = (n >> 8) & 0xff;
buf[2] = (n >> 0) & 0xff;
}
}

View File

@ -22,11 +22,11 @@ struct gdrom_if_reg {
reg8 sector_count;
};
const reg8 _pad4[3];
reg8 sector_number;
const reg8 sector_number;
const reg8 _pad5[3];
reg8 byte_control_low;
reg8 byte_count_low;
const reg8 _pad6[3];
reg8 byte_control_high;
reg8 byte_count_high;
const reg8 _pad7[3];
reg8 drive_select;
const reg8 _pad8[3];
@ -34,6 +34,11 @@ struct gdrom_if_reg {
const reg8 status;
reg8 command;
};
uint16_t byte_count() const
{
return (byte_count_high << 8) | (byte_count_low << 0);
}
};
static_assert((offsetof (struct gdrom_if_reg, alternate_status)) == 24);
@ -44,8 +49,8 @@ static_assert((offsetof (struct gdrom_if_reg, features)) == 132);
static_assert((offsetof (struct gdrom_if_reg, interrupt_reason)) == 136);
static_assert((offsetof (struct gdrom_if_reg, sector_count)) == 136);
static_assert((offsetof (struct gdrom_if_reg, sector_number)) == 140);
static_assert((offsetof (struct gdrom_if_reg, byte_control_low)) == 144);
static_assert((offsetof (struct gdrom_if_reg, byte_control_high)) == 148);
static_assert((offsetof (struct gdrom_if_reg, byte_count_low)) == 144);
static_assert((offsetof (struct gdrom_if_reg, byte_count_high)) == 148);
static_assert((offsetof (struct gdrom_if_reg, drive_select)) == 152);
static_assert((offsetof (struct gdrom_if_reg, status)) == 156);
static_assert((offsetof (struct gdrom_if_reg, command)) == 156);

89
gdrom/toc.hpp Normal file
View File

@ -0,0 +1,89 @@
#include <cstdint>
namespace gdrom_toc {
struct track {
const uint8_t _control_adr;
const uint8_t _fad[3];
uint32_t fad() const
{
return (_fad[0] << 16) | (_fad[1] << 8) | (_fad[2] << 0);
}
uint8_t control() const
{
return (_control_adr >> 4) & 0xf;
}
uint8_t adr() const
{
return (_control_adr >> 0) & 0xf;
}
};
static_assert((sizeof (track)) == 4);
struct start_track {
const uint8_t _control_adr;
const uint8_t start_track_number;
const uint8_t _zero[2];
uint8_t control() const
{
return (_control_adr >> 4) & 0xf;
}
uint8_t adr() const
{
return (_control_adr >> 0) & 0xf;
}
};
static_assert((sizeof (start_track)) == 4);
struct end_track {
const uint8_t _control_adr;
const uint8_t end_track_number;
const uint8_t _zero[2];
uint8_t control() const
{
return (_control_adr >> 4) & 0xf;
}
uint8_t adr() const
{
return (_control_adr >> 0) & 0xf;
}
};
static_assert((sizeof (end_track)) == 4);
struct lead_out {
const uint8_t _control_adr;
const uint8_t _fad[3];
uint32_t fad() const
{
return (_fad[0] << 16) | (_fad[1] << 8) | (_fad[2] << 0);
}
uint8_t control() const
{
return (_control_adr >> 4) & 0xf;
}
uint8_t adr() const
{
return (_control_adr >> 0) & 0xf;
}
};
static_assert((sizeof (lead_out)) == 4);
struct toc {
struct track track[99];
struct start_track start_track;
struct end_track end_track;
struct lead_out lead_out;
};
static_assert((sizeof (toc)) == 408);
}

View File

@ -1,6 +1,6 @@
import sys
from dataclasses import dataclass
from os.path import splitext
from os import path
from csv_input import read_input
from generate import renderer
@ -89,6 +89,8 @@ def header():
yield ""
def render_fields(input_name, fields):
yield "namespace iso9660 {"
yield f"struct {input_name} {{"
for field in fields:
field_size = (field.end - field.start) + 1
@ -108,9 +110,12 @@ def render_fields(input_name, fields):
for field in fields:
yield f"static_assert((offsetof (struct {input_name}, {field.name})) == {field.start - 1});"
yield "}" # namespace
if __name__ == "__main__":
input_file = sys.argv[1]
input_name, _ = splitext(input_file)
input_name0, _ = path.splitext(input_file)
_, input_name = path.split(input_name0)
rows = read_input(input_file)
fields = list(parse(rows))
render, out = renderer()

View File

@ -5,7 +5,8 @@
#include "uint_le_be.hpp"
struct directory_record {
namespace iso9660 {
struct directory_record {
const uint8_t length_of_directory_record;
const uint8_t extended_attribute_record_length;
const uint32_le_be location_of_extent;
@ -17,16 +18,16 @@ struct directory_record {
const uint16_le_be volume_sequence_number;
const uint8_t length_of_file_identifier;
const uint8_t file_identifier[];
};
static_assert((offsetof (struct directory_record, length_of_directory_ecord)) == 0);
static_assert((offsetof (struct directory_record, extended_attribute_record_length)) == 1);
static_assert((offsetof (struct directory_record, location_of_extent)) == 2);
static_assert((offsetof (struct directory_record, data_length)) == 10);
static_assert((offsetof (struct directory_record, recording_date_and_time)) == 18);
static_assert((offsetof (struct directory_record, file_flags)) == 25);
static_assert((offsetof (struct directory_record, file_unit_size)) == 26);
static_assert((offsetof (struct directory_record, interleave_gap_size)) == 27);
static_assert((offsetof (struct directory_record, volume_sequence_number)) == 28);
static_assert((offsetof (struct directory_record, length_of_file_identifier)) == 32);
static_assert((offsetof (struct directory_record, file_identifier)) == 33);
};
static_assert((offsetof (struct directory_record, length_of_directory_record)) == 0);
static_assert((offsetof (struct directory_record, extended_attribute_record_length)) == 1);
static_assert((offsetof (struct directory_record, location_of_extent)) == 2);
static_assert((offsetof (struct directory_record, data_length)) == 10);
static_assert((offsetof (struct directory_record, recording_date_and_time)) == 18);
static_assert((offsetof (struct directory_record, file_flags)) == 25);
static_assert((offsetof (struct directory_record, file_unit_size)) == 26);
static_assert((offsetof (struct directory_record, interleave_gap_size)) == 27);
static_assert((offsetof (struct directory_record, volume_sequence_number)) == 28);
static_assert((offsetof (struct directory_record, length_of_file_identifier)) == 32);
static_assert((offsetof (struct directory_record, file_identifier)) == 33);
}

View File

@ -5,7 +5,8 @@
#include "uint_le_be.hpp"
struct primary_volume_descriptor {
namespace iso9660 {
struct primary_volume_descriptor {
const uint8_t volume_descriptor_type;
const uint8_t standard_identifier[5];
const uint8_t volume_descriptor_version;
@ -39,38 +40,38 @@ struct primary_volume_descriptor {
const uint8_t _res4;
const uint8_t application_use[512];
const uint8_t _res5[653];
};
static_assert((offsetof (struct primary_volume_descriptor, volume_descriptor_type)) == 0);
static_assert((offsetof (struct primary_volume_descriptor, standard_identifier)) == 1);
static_assert((offsetof (struct primary_volume_descriptor, volume_descriptor_version)) == 6);
static_assert((offsetof (struct primary_volume_descriptor, _res1)) == 7);
static_assert((offsetof (struct primary_volume_descriptor, system_identifier)) == 8);
static_assert((offsetof (struct primary_volume_descriptor, volume_identifier)) == 40);
static_assert((offsetof (struct primary_volume_descriptor, _res2)) == 72);
static_assert((offsetof (struct primary_volume_descriptor, volume_space_size)) == 80);
static_assert((offsetof (struct primary_volume_descriptor, _res3)) == 88);
static_assert((offsetof (struct primary_volume_descriptor, volume_set_size)) == 120);
static_assert((offsetof (struct primary_volume_descriptor, volume_sequence_number)) == 124);
static_assert((offsetof (struct primary_volume_descriptor, logical_block_size)) == 128);
static_assert((offsetof (struct primary_volume_descriptor, path_table_size)) == 132);
static_assert((offsetof (struct primary_volume_descriptor, location_of_occurrence_of_type_l_path_table)) == 140);
static_assert((offsetof (struct primary_volume_descriptor, location_of_optional_occurence_of_type_l_path_table)) == 144);
static_assert((offsetof (struct primary_volume_descriptor, location_of_occurence_of_type_m_path_table)) == 148);
static_assert((offsetof (struct primary_volume_descriptor, location_of_optional_occurence_of_type_m_path_table)) == 152);
static_assert((offsetof (struct primary_volume_descriptor, directory_record_for_root_directory)) == 156);
static_assert((offsetof (struct primary_volume_descriptor, volume_set_identifier)) == 190);
static_assert((offsetof (struct primary_volume_descriptor, publisher_identifier)) == 318);
static_assert((offsetof (struct primary_volume_descriptor, data_preparer_identifier)) == 446);
static_assert((offsetof (struct primary_volume_descriptor, application_identifier)) == 574);
static_assert((offsetof (struct primary_volume_descriptor, copyright_file_identifier)) == 702);
static_assert((offsetof (struct primary_volume_descriptor, abstract_file_identifier)) == 739);
static_assert((offsetof (struct primary_volume_descriptor, bibliographic_file_identifier)) == 776);
static_assert((offsetof (struct primary_volume_descriptor, volume_creation_date_and_time)) == 813);
static_assert((offsetof (struct primary_volume_descriptor, volume_modification_date_and_time)) == 830);
static_assert((offsetof (struct primary_volume_descriptor, volume_expiration_date_and_time)) == 847);
static_assert((offsetof (struct primary_volume_descriptor, volume_effective_date_and_time)) == 864);
static_assert((offsetof (struct primary_volume_descriptor, file_structure_version)) == 881);
static_assert((offsetof (struct primary_volume_descriptor, _res4)) == 882);
static_assert((offsetof (struct primary_volume_descriptor, application_use)) == 883);
static_assert((offsetof (struct primary_volume_descriptor, _res5)) == 1395);
};
static_assert((offsetof (struct primary_volume_descriptor, volume_descriptor_type)) == 0);
static_assert((offsetof (struct primary_volume_descriptor, standard_identifier)) == 1);
static_assert((offsetof (struct primary_volume_descriptor, volume_descriptor_version)) == 6);
static_assert((offsetof (struct primary_volume_descriptor, _res1)) == 7);
static_assert((offsetof (struct primary_volume_descriptor, system_identifier)) == 8);
static_assert((offsetof (struct primary_volume_descriptor, volume_identifier)) == 40);
static_assert((offsetof (struct primary_volume_descriptor, _res2)) == 72);
static_assert((offsetof (struct primary_volume_descriptor, volume_space_size)) == 80);
static_assert((offsetof (struct primary_volume_descriptor, _res3)) == 88);
static_assert((offsetof (struct primary_volume_descriptor, volume_set_size)) == 120);
static_assert((offsetof (struct primary_volume_descriptor, volume_sequence_number)) == 124);
static_assert((offsetof (struct primary_volume_descriptor, logical_block_size)) == 128);
static_assert((offsetof (struct primary_volume_descriptor, path_table_size)) == 132);
static_assert((offsetof (struct primary_volume_descriptor, location_of_occurrence_of_type_l_path_table)) == 140);
static_assert((offsetof (struct primary_volume_descriptor, location_of_optional_occurence_of_type_l_path_table)) == 144);
static_assert((offsetof (struct primary_volume_descriptor, location_of_occurence_of_type_m_path_table)) == 148);
static_assert((offsetof (struct primary_volume_descriptor, location_of_optional_occurence_of_type_m_path_table)) == 152);
static_assert((offsetof (struct primary_volume_descriptor, directory_record_for_root_directory)) == 156);
static_assert((offsetof (struct primary_volume_descriptor, volume_set_identifier)) == 190);
static_assert((offsetof (struct primary_volume_descriptor, publisher_identifier)) == 318);
static_assert((offsetof (struct primary_volume_descriptor, data_preparer_identifier)) == 446);
static_assert((offsetof (struct primary_volume_descriptor, application_identifier)) == 574);
static_assert((offsetof (struct primary_volume_descriptor, copyright_file_identifier)) == 702);
static_assert((offsetof (struct primary_volume_descriptor, abstract_file_identifier)) == 739);
static_assert((offsetof (struct primary_volume_descriptor, bibliographic_file_identifier)) == 776);
static_assert((offsetof (struct primary_volume_descriptor, volume_creation_date_and_time)) == 813);
static_assert((offsetof (struct primary_volume_descriptor, volume_modification_date_and_time)) == 830);
static_assert((offsetof (struct primary_volume_descriptor, volume_expiration_date_and_time)) == 847);
static_assert((offsetof (struct primary_volume_descriptor, volume_effective_date_and_time)) == 864);
static_assert((offsetof (struct primary_volume_descriptor, file_structure_version)) == 881);
static_assert((offsetof (struct primary_volume_descriptor, _res4)) == 882);
static_assert((offsetof (struct primary_volume_descriptor, application_use)) == 883);
static_assert((offsetof (struct primary_volume_descriptor, _res5)) == 1395);
}

View File

@ -2,7 +2,6 @@
#include <cstdint>
#include <bit>
#include <iostream>
template <typename T>
struct uint_le_be {

View File

@ -6,9 +6,9 @@
"0084","1","features","W",
"0088","1","interrupt_reason","R",
"0088","1","sector_count","W",
"008C","1","sector_number","RW",
"0090","1","byte_control_low","RW",
"0094","1","byte_control_high","RW",
"008C","1","sector_number","R",
"0090","1","byte_count_low","RW",
"0094","1","byte_count_high","RW",
"0098","1","drive_select","RW",
"009C","1","status","R",
"009C","1","command","W",

1 address size name r/w description
6 0084 1 features W
7 0088 1 interrupt_reason R
8 0088 1 sector_count W
9 008C 1 sector_number RW R
10 0090 1 byte_control_low byte_count_low RW
11 0094 1 byte_control_high byte_count_high RW
12 0098 1 drive_select RW
13 009C 1 status R
14 009C 1 command W

Binary file not shown.

View File

@ -40,6 +40,12 @@ def group_by_address(rows):
return list(sorted(_groups.items(), key=lambda kv: kv[0]))
def byte_count():
yield "uint16_t byte_count() const"
yield "{"
yield "return (byte_count_high << 8) | (byte_count_low << 0);"
yield "}"
def render_groups(groups):
next_address = 0
reserved_num = 0
@ -67,6 +73,9 @@ def render_groups(groups):
next_address = address + group[0].size
yield ""
yield from byte_count()
yield "};"

View File

@ -47,6 +47,14 @@ void string(const char * s)
}
}
void string(const uint8_t * s, uint32_t len)
{
while (len > 0) {
character(*s++);
len--;
}
}
void hexlify(const uint8_t n)
{
constexpr uint32_t length = 2;

View File

@ -6,6 +6,8 @@ void character(const char c);
void string(const char * s);
void string(const uint8_t * s, uint32_t len);
void hexlify(const uint8_t n);
template <typename T>