From 5a9790daf171fa87b1721cb204ce241ab78eef65 Mon Sep 17 00:00:00 2001 From: Zack Buhman Date: Sun, 17 Dec 2023 02:03:42 +0800 Subject: [PATCH] examples: add font example --- common.mk | 2 +- example/example.mk | 13 ++ example/font.cpp | 261 +++++++++++++++++++++++++++++++++++++++++ holly/core_bits.hpp | 50 +++++++- holly/ta_parameter.hpp | 95 ++++++++++++++- regs/core_bits.csv | 8 +- regs/core_bits.ods | Bin 22199 -> 22275 bytes sperrypc.data | Bin 0 -> 768 bytes sperrypc.hpp | 5 + 9 files changed, 427 insertions(+), 7 deletions(-) create mode 100644 example/font.cpp create mode 100644 sperrypc.data create mode 100644 sperrypc.hpp diff --git a/common.mk b/common.mk index 1380afc..3145c60 100644 --- a/common.mk +++ b/common.mk @@ -2,7 +2,7 @@ MAKEFILE_PATH := $(abspath $(lastword $(MAKEFILE_LIST))) DIR := $(dir $(MAKEFILE_PATH)) LIB ?= . -OPT ?= -Os +OPT ?= -O3 DEBUG ?= -g -gdwarf-4 GENERATED ?= diff --git a/example/example.mk b/example/example.mk index 62fc5b8..da693a3 100644 --- a/example/example.mk +++ b/example/example.mk @@ -33,6 +33,19 @@ MACAW_TWIDDLE_OBJ = \ example/macaw_twiddle.elf: LDSCRIPT = $(LIB)/alt.lds example/macaw_twiddle.elf: $(START_OBJ) $(MACAW_TWIDDLE_OBJ) +FONT_OBJ = \ + example/font.o \ + vga.o \ + holly/core.o \ + holly/region_array.o \ + holly/background.o \ + holly/ta_fifo_polygon_converter.o \ + serial.o \ + sperrypc.data.o + +example/font.elf: LDSCRIPT = $(LIB)/alt.lds +example/font.elf: $(START_OBJ) $(FONT_OBJ) + MACAW_MULTIPASS_OBJ = \ example/macaw_multipass.o \ vga.o \ diff --git a/example/font.cpp b/example/font.cpp new file mode 100644 index 0000000..316dc3a --- /dev/null +++ b/example/font.cpp @@ -0,0 +1,261 @@ +#include + +#include "align.hpp" + +#include "vga.hpp" +#include "holly.hpp" +#include "holly/core.hpp" +#include "holly/core_bits.hpp" +#include "holly/ta_parameter.hpp" +#include "holly/ta_fifo_polygon_converter.hpp" +#include "holly/texture_memory_alloc.hpp" +#include "memorymap.hpp" +#include "holly/background.hpp" +#include "holly/region_array.hpp" +#include "holly/ta_bits.hpp" +#include "twiddle.hpp" +#include "serial.hpp" + +#include "sperrypc.hpp" + +struct vertex { + float x; + float y; + float z; + float u; + float v; +}; + +/* +// screen space coordinates +const struct vertex quad_verticies[4] = { + { 0.f, 64.f, 0.01f, 0.f, 1.f }, + { 0.f, 0.f, 0.01f, 0.f, 0.f }, + { 64.f, 0.f, 0.01f, 1.f, 0.f }, + { 64.f, 64.f, 0.01f, 1.f, 1.f, }, +}; + +uint32_t transform(uint32_t * ta_parameter_buf) +{ + auto parameter = ta_parameter_writer(ta_parameter_buf); + uint32_t texture_address = (offsetof (struct texture_memory_alloc, texture)); + constexpr uint32_t base_color = 0xffffffff; + auto sprite = global_sprite(base_color); + sprite.parameter_control_word = para_control::para_type::sprite + | para_control::list_type::opaque + | obj_control::col_type::packed_color + | obj_control::texture + | obj_control::_16bit_uv; + sprite.tsp_instruction_word = tsp_instruction_word::src_alpha_instr::one + | tsp_instruction_word::dst_alpha_instr::zero + | tsp_instruction_word::fog_control::no_fog + | tsp_instruction_word::texture_u_size::_8 // 8px + | tsp_instruction_word::texture_v_size::_8; // 8px + sprite.texture_control_word = texture_control_word::pixel_format::_565 + | texture_control_word::scan_order::twiddled + | texture_control_word::texture_address(texture_address / 8); + parameter.append() = sprite; + + parameter.append() = + vertex_sprite_type_1(quad_verticies[0].x, + quad_verticies[0].y, + quad_verticies[0].z, + quad_verticies[1].x, + quad_verticies[1].y, + quad_verticies[1].z, + quad_verticies[2].x, + quad_verticies[2].y, + quad_verticies[2].z, + quad_verticies[3].x, + quad_verticies[3].y, + uv_16bit(quad_verticies[0].u, quad_verticies[0].v), + uv_16bit(quad_verticies[1].u, quad_verticies[1].v), + uv_16bit(quad_verticies[2].u, quad_verticies[2].v)); + // curiously, there is no `dz` in vertex_sprite_type_1 + // curiously, there is no `du_dv` in vertex_sprite_type_1 + + parameter.append() = global_end_of_list(); + + return parameter.offset; +} +*/ + +const struct vertex strip_vertices[4] = { + // [ position ] [ uv coordinates ] + { -0.5f, 0.5f, 0.f, 0.f, 1.f, }, + { -0.5f, -0.5f, 0.f, 0.f, 0.f, }, + { 0.5f, 0.5f, 0.f, 1.f, 1.f, }, + { 0.5f, -0.5f, 0.f, 1.f, 0.f, }, +}; +constexpr uint32_t strip_length = (sizeof (strip_vertices)) / (sizeof (struct vertex)); + +uint32_t transform(uint32_t * ta_parameter_buf, const char * s, const uint32_t len) +{ + auto parameter = ta_parameter_writer(ta_parameter_buf); + uint32_t texture_address = (offsetof (struct texture_memory_alloc, texture)); + + for (uint32_t string_ix = 0; string_ix < len; string_ix++) { + auto polygon = global_polygon_type_0(texture_address); + polygon.parameter_control_word = para_control::para_type::polygon_or_modifier_volume + | para_control::list_type::opaque + | obj_control::col_type::packed_color + | obj_control::texture; + + polygon.tsp_instruction_word = tsp_instruction_word::src_alpha_instr::one + | tsp_instruction_word::dst_alpha_instr::zero + | tsp_instruction_word::fog_control::no_fog + | tsp_instruction_word::texture_u_size::_8 // 8px + | tsp_instruction_word::texture_v_size::_8; // 8px + + polygon.texture_control_word = texture_control_word::pixel_format::_8bpp_palette + | texture_control_word::scan_order::twiddled + | texture_control_word::texture_address((texture_address + 8 * 8 * (s[string_ix] - ' ')) / 8); + parameter.append() = polygon; + + for (uint32_t i = 0; i < strip_length; i++) { + bool end_of_strip = i == strip_length - 1; + + float x = strip_vertices[i].x; + float y = strip_vertices[i].y; + float z = strip_vertices[i].z; + + x *= 32.f; + y *= 32.f; + x += 64.f + 32 * string_ix; + y += 240.f; + z = 1.f / (z + 10.f); + + parameter.append() = + vertex_polygon_type_3(x, y, z, + strip_vertices[i].u, + strip_vertices[i].v, + 0x00000000, // base_color + end_of_strip); + } + } + + parameter.append() = global_end_of_list(); + + return parameter.offset; +} + + +void init_texture_memory(const struct opb_size& opb_size) +{ + volatile texture_memory_alloc * mem = reinterpret_cast(texture_memory); + + background_parameter(mem->background); + + region_array2(mem->region_array, + (offsetof (struct texture_memory_alloc, object_list)), + 640 / 32, // width + 480 / 32, // height + opb_size + ); +} + +inline void inflate_character(const uint8_t * src, const uint8_t c) +{ + uint8_t character_index = c - ' '; + + uint8_t temp[8 * 8]; + for (uint32_t y = 0; y < 8; y++) { + uint8_t row = src[y + 8 * character_index]; + for (uint32_t x = 0; x < 8; x++) { + uint8_t px = (row >> (7 - x)) & 1; + //serial::character((px == 1) ? 'X' : '_'); + //uint16_t rgb565 = px ? 0xffff : 0; + uint16_t palette_index = px ? 2 : 1; + + temp[y * 8 + x] = palette_index; + } + //serial::character('\n'); + } + + auto mem = reinterpret_cast(0xa400'0000); + auto texture = reinterpret_cast(mem->texture); + + uint32_t offset = 8 * 8 * character_index; + union { + uint8_t u8[8 * 8]; + uint32_t u32[8 * 8 / 4]; + } temp2; + + //twiddle::texture(&texture[offset], temp, 8, 8); + twiddle::texture(temp2.u8, temp, 8, 8); + for (uint32_t i = 0; i < 8 * 8 / 4; i++) { + texture[(offset / 4) + i] = temp2.u32[i]; + } +} + +void inflate_font(const uint8_t * src) +{ + for (uint8_t ix = 0x20; ix < 0x7f; ix++) { + inflate_character(src, ix); + } +} + +void palette_data() +{ + holly.PAL_RAM_CTRL = pal_ram_ctrl::pixel_format::rgb565; + + holly.PALETTE_RAM[1] = (15) << 11; + holly.PALETTE_RAM[2] = (15 << 11) | (30 << 5); +} + +uint32_t _ta_parameter_buf[((32 * 10 * 17) + 32) / 4]; + +void main() +{ + vga(); + + auto src = reinterpret_cast(&_binary_sperrypc_data_start); + inflate_font(src); + palette_data(); + + // The address of `ta_parameter_buf` must be a multiple of 32 bytes. + // This is mandatory for ch2-dma to the ta fifo polygon converter. + uint32_t * ta_parameter_buf = align_32byte(_ta_parameter_buf); + + constexpr uint32_t ta_alloc = ta_alloc_ctrl::pt_opb::no_list + | ta_alloc_ctrl::tm_opb::no_list + | ta_alloc_ctrl::t_opb::no_list + | ta_alloc_ctrl::om_opb::no_list + | ta_alloc_ctrl::o_opb::_16x4byte; + + constexpr struct opb_size opb_size = { .opaque = 16 * 4 + , .opaque_modifier = 0 + , .translucent = 0 + , .translucent_modifier = 0 + , .punch_through = 0 + }; + + constexpr uint32_t tiles = (640 / 32) * (320 / 32); + + holly.SOFTRESET = softreset::pipeline_soft_reset + | softreset::ta_soft_reset; + holly.SOFTRESET = 0; + + core_init(); + init_texture_memory(opb_size); + + uint32_t frame_ix = 0; + constexpr uint32_t num_frames = 1; + + const char ana[18] = "A from ana i know"; + + while (true) { + ta_polygon_converter_init(opb_size.total() * tiles, ta_alloc); + uint32_t ta_parameter_size = transform(ta_parameter_buf, ana, 17); + ta_polygon_converter_transfer(ta_parameter_buf, ta_parameter_size); + ta_wait_opaque_list(); + + core_start_render(frame_ix, num_frames); + + v_sync_out(); + v_sync_in(); + core_wait_end_of_render_video(frame_ix, num_frames); + + frame_ix++; + } +} diff --git a/holly/core_bits.hpp b/holly/core_bits.hpp index 3b25c11..d288e96 100644 --- a/holly/core_bits.hpp +++ b/holly/core_bits.hpp @@ -51,6 +51,8 @@ namespace fb_r_ctrl { namespace vclk_div { constexpr uint32_t pclk_vclk_2 = 0 << 23; constexpr uint32_t pclk_vclk_1 = 1 << 23; + + constexpr uint32_t bit_mask = 0x1 << 23; } constexpr uint32_t fb_strip_buf_en = 1 << 22; @@ -63,6 +65,8 @@ namespace fb_r_ctrl { constexpr uint32_t _0565_rgb_16bit = 1 << 2; constexpr uint32_t _888_rgb_24bit_packed = 2 << 2; constexpr uint32_t _0888_rgb_32bit = 3 << 2; + + constexpr uint32_t bit_mask = 0x3 << 2; } constexpr uint32_t fb_line_double = 1 << 1; @@ -82,6 +86,8 @@ namespace fb_w_ctrl { constexpr uint32_t _888_rgb_24bit_packed = 4 << 0; constexpr uint32_t _0888_krgb_32bit = 5 << 0; constexpr uint32_t _8888_argb_32bit = 6 << 0; + + constexpr uint32_t bit_mask = 0x7 << 0; } } @@ -125,6 +131,8 @@ namespace fpu_shad_scale { namespace simple_shadow_enable { constexpr uint32_t parameter_selection_volume_mode = 0 << 8; constexpr uint32_t intensity_volume_mode = 1 << 8; + + constexpr uint32_t bit_mask = 0x1 << 8; } constexpr uint32_t scale_factor_for_shadows(uint32_t num) { return (num & 0xff) << 0; } @@ -138,6 +146,8 @@ namespace fpu_param_cfg { namespace region_header_type { constexpr uint32_t type_1 = 0 << 21; constexpr uint32_t type_2 = 1 << 21; + + constexpr uint32_t bit_mask = 0x1 << 21; } constexpr uint32_t tsp_parameter_burst_threshold(uint32_t num) { return (num & 0x3f) << 14; } @@ -150,16 +160,22 @@ namespace half_offset { namespace tsp_texel_sampling_position { constexpr uint32_t top_left = 1 << 2; constexpr uint32_t center = 1 << 2; + + constexpr uint32_t bit_mask = 0x1 << 2; } namespace tsp_pixel_sampling_position { constexpr uint32_t top_left = 1 << 1; constexpr uint32_t center = 1 << 1; + + constexpr uint32_t bit_mask = 0x1 << 1; } namespace fpu_pixel_sampling_position { constexpr uint32_t top_left = 1 << 0; constexpr uint32_t center = 1 << 0; + + constexpr uint32_t bit_mask = 0x1 << 0; } } @@ -204,12 +220,16 @@ namespace sdram_arb_cfg { constexpr uint32_t isp_pointer_data = 0x9 << 18; constexpr uint32_t isp_parameters = 0xa << 18; constexpr uint32_t crt_controller = 0xb << 18; + + constexpr uint32_t bit_mask = 0xf << 18; } namespace arbiter_priority_control { constexpr uint32_t priority_arbitration_only = 0x0 << 16; constexpr uint32_t override_value_field = 0x1 << 16; constexpr uint32_t round_robin_counter = 0x2 << 16; + + constexpr uint32_t bit_mask = 0x3 << 16; } constexpr uint32_t arbiter_crt_page_break_latency_count_value(uint32_t num) { return (num & 0xff) << 8; } @@ -272,6 +292,8 @@ namespace spg_hblank_int { constexpr uint32_t output_equal_line_comp_val = 0x0 << 12; constexpr uint32_t output_every_line_comp_val = 0x1 << 12; constexpr uint32_t output_every_line = 0x2 << 12; + + constexpr uint32_t bit_mask = 0x3 << 12; } constexpr uint32_t line_comp_val(uint32_t num) { return (num & 0x3ff) << 0; } @@ -286,11 +308,15 @@ namespace spg_control { namespace csync_on_h { constexpr uint32_t hsync = 0 << 9; constexpr uint32_t csync = 1 << 9; + + constexpr uint32_t bit_mask = 0x1 << 9; } namespace sync_direction { constexpr uint32_t input = 0 << 8; constexpr uint32_t output = 1 << 8; + + constexpr uint32_t bit_mask = 0x1 << 8; } constexpr uint32_t pal = 1 << 7; @@ -302,16 +328,22 @@ namespace spg_control { namespace mcsync_pol { constexpr uint32_t active_low = 0 << 2; constexpr uint32_t active_high = 1 << 2; + + constexpr uint32_t bit_mask = 0x1 << 2; } namespace mvsync_pol { constexpr uint32_t active_low = 0 << 1; constexpr uint32_t active_high = 1 << 1; + + constexpr uint32_t bit_mask = 0x1 << 1; } namespace mhsync_pol { constexpr uint32_t active_low = 0 << 0; constexpr uint32_t active_high = 1 << 0; + + constexpr uint32_t bit_mask = 0x1 << 0; } } @@ -341,11 +373,15 @@ namespace text_control { namespace code_book_endian { constexpr uint32_t little_endian = 0 << 17; constexpr uint32_t big_endian = 1 << 17; + + constexpr uint32_t bit_mask = 0x1 << 17; } namespace index_endian { constexpr uint32_t little_endian = 0 << 16; constexpr uint32_t big_endian = 1 << 16; + + constexpr uint32_t bit_mask = 0x1 << 16; } constexpr uint32_t bank_bit(uint32_t num) { return (num & 0x1f) << 8; } @@ -367,6 +403,8 @@ namespace vo_control { constexpr uint32_t field_1_when_hsync_becomes_active_in_the_middle_of_the_vsync_active_edge = 0x6 << 4; constexpr uint32_t field_2_when_hsync_becomes_active_in_the_middle_of_the_vsync_active_edge = 0x7 << 4; constexpr uint32_t inverted_at_the_active_edge_of_vsync = 0x8 << 4; + + constexpr uint32_t bit_mask = 0xf << 4; } constexpr uint32_t blank_video = 1 << 3; @@ -374,16 +412,22 @@ namespace vo_control { namespace blank_pol { constexpr uint32_t active_low = 0 << 2; constexpr uint32_t active_high = 1 << 2; + + constexpr uint32_t bit_mask = 0x1 << 2; } namespace vsync_pol { constexpr uint32_t active_low = 0 << 1; constexpr uint32_t active_high = 1 << 1; + + constexpr uint32_t bit_mask = 0x1 << 1; } namespace hsync_pol { constexpr uint32_t active_low = 0 << 0; constexpr uint32_t active_high = 1 << 0; + + constexpr uint32_t bit_mask = 0x1 << 0; } } @@ -400,6 +444,8 @@ namespace scaler_ctl { namespace field_select { constexpr uint32_t field_1 = 0 << 18; constexpr uint32_t field_2 = 1 << 18; + + constexpr uint32_t bit_mask = 0x1 << 18; } constexpr uint32_t interlace = 1 << 17; @@ -407,12 +453,14 @@ namespace scaler_ctl { constexpr uint32_t vertical_scale_factor(uint32_t num) { return (num & 0xffff) << 0; } } -namespace pal_ram_ctl { +namespace pal_ram_ctrl { namespace pixel_format { constexpr uint32_t argb1555 = 0 << 0; constexpr uint32_t rgb565 = 1 << 0; constexpr uint32_t argb4444 = 2 << 0; constexpr uint32_t argb8888 = 3 << 0; + + constexpr uint32_t bit_mask = 0x3 << 0; } } diff --git a/holly/ta_parameter.hpp b/holly/ta_parameter.hpp index b0d2daf..a397542 100644 --- a/holly/ta_parameter.hpp +++ b/holly/ta_parameter.hpp @@ -131,6 +131,36 @@ static_assert((offsetof (struct vertex_polygon_type_0, _res1)) == 0x14); static_assert((offsetof (struct vertex_polygon_type_0, base_color)) == 0x18); static_assert((offsetof (struct vertex_polygon_type_0, _res2)) == 0x1c); +struct vertex_polygon_type_4 { + uint32_t parameter_control_word; + float x; + float y; + float z; + uint32_t uv; + uint32_t _res0; + uint32_t base_color; + uint32_t offset_color; + + vertex_polygon_type_4(const float x, + const float y, + const float z, + const uint32_t uv, + const uint32_t base_color, + const bool end_of_strip + ) + : parameter_control_word( para_control::para_type::vertex_parameter + | (end_of_strip ? para_control::end_of_strip : 0) + ) + , x(x) + , y(y) + , z(z) + , uv(uv) + , _res0(0) + , base_color(base_color) + , offset_color(0) + { } +}; + struct global_polygon_type_0 { uint32_t parameter_control_word; uint32_t isp_tsp_instruction_word; @@ -218,7 +248,8 @@ struct global_sprite { global_sprite(const uint32_t base_color) : parameter_control_word( para_control::para_type::sprite - | para_control::list_type::opaque ) + | para_control::list_type::opaque + | obj_control::col_type::packed_color ) , isp_tsp_instruction_word( isp_tsp_instruction_word::depth_compare_mode::always | isp_tsp_instruction_word::culling_mode::no_culling ) , tsp_instruction_word( tsp_instruction_word::src_alpha_instr::one @@ -284,6 +315,59 @@ struct vertex_sprite_type_0 { static_assert((sizeof (vertex_sprite_type_0)) == 64); +struct vertex_sprite_type_1 { + uint32_t parameter_control_word; + float ax; + float ay; + float az; + float bx; + float by; + float bz; + float cx; + + float cy; + float cz; + float dx; + float dy; + float _res0; + uint32_t au_av; + uint32_t bu_bv; + uint32_t cu_cv; + + vertex_sprite_type_1(const float ax, + const float ay, + const float az, + const float bx, + const float by, + const float bz, + const float cx, + const float cy, + const float cz, + const float dx, + const float dy, + const uint32_t au_av, + const uint32_t bu_bv, + const uint32_t cu_cv) + : parameter_control_word(para_control::para_type::vertex_parameter) + , ax(ax) + , ay(ay) + , az(az) + , bx(bx) + , by(by) + , bz(bz) + , cx(cx) + , cy(cy) + , dx(dx) + , dy(dy) + , _res0(0) + , au_av(au_av) + , bu_bv(bu_bv) + , cu_cv(cu_cv) + {} +}; + +static_assert((sizeof (vertex_sprite_type_1)) == 64); + struct global_end_of_list { uint32_t parameter_control_word; uint32_t _res0; @@ -347,3 +431,12 @@ union ta_parameter { }; static_assert((sizeof (ta_parameter)) == 32); */ + +uint32_t uv_16bit(float u, float v) +{ + uint32_t * ui = (reinterpret_cast(&u)); + uint32_t * vi = (reinterpret_cast(&v)); + uint32_t u_half = ((*ui) >> 16) & 0xffff; + uint32_t v_half = ((*vi) >> 16) & 0xffff; + return (u_half << 16) | (v_half << 0); +} diff --git a/regs/core_bits.csv b/regs/core_bits.csv index d2a82c0..dec7246 100644 --- a/regs/core_bits.csv +++ b/regs/core_bits.csv @@ -237,10 +237,10 @@ "SCALER_CTL",,16,"horizontal_scaling_enable",1,,,,,,, "SCALER_CTL",,"15-0","vertical_scale_factor",,"0xffff",,,,,, ,,,,,,,,,,, -"PAL_RAM_CTL","pixel_format","1-0","argb1555",0,,,,,,, -"PAL_RAM_CTL","pixel_format","1-0","rgb565",1,,,,,,, -"PAL_RAM_CTL","pixel_format","1-0","argb4444",2,,,,,,, -"PAL_RAM_CTL","pixel_format","1-0","argb8888",3,,,,,,, +"PAL_RAM_CTRL","pixel_format","1-0","argb1555",0,,,,,,, +"PAL_RAM_CTRL","pixel_format","1-0","rgb565",1,,,,,,, +"PAL_RAM_CTRL","pixel_format","1-0","argb4444",2,,,,,,, +"PAL_RAM_CTRL","pixel_format","1-0","argb8888",3,,,,,,, ,,,,,,,,,,, "SPG_STATUS",,13,"vsync",,,,,,,, "SPG_STATUS",,12,"hsync",,,,,,,, diff --git a/regs/core_bits.ods b/regs/core_bits.ods index f60fffd632e485f94178dab38b2a7ba79291605e..986407c356fceceac40c63c1e09c0e075d6c9b85 100644 GIT binary patch delta 10673 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 j-?M-}GdNI&1|`W~cK`F-kp3a>16tD{h7z diff --git a/sperrypc.data b/sperrypc.data new file mode 100644 index 0000000000000000000000000000000000000000..e46ee5e7fffa104c5b05e75cf1d39439d01aa969 GIT binary patch literal 768 zcmX|9J&W8h5PcZ2sBobog^Ls_RJcf?B854`AqKH5O9;nRDUT&Z$}5+{n8REZQkhh# zQ6=qBX|Y2npS57ewRNf1DVdC-tiQiWt!t>wJDjlKt`il;GkW>5hKW#yqKg z+ekwDCX~q-OnGe7Pm)KWomjwDz9jYexz#wj?*HmK>vo;L_-@Cx6Mw)Z+MSIT<+A{6 zk{|2(J*O@NnM5#(=wJKf%zAW)5mU}%=3JZ28viK!8!*T;!JyN-0aU@1GC<1V?n@D{ z3Nc~+y{^kLLr(X{J^AcU)cUNe^+b;4ruIKDkM)zDM@1yG50R=3p~Ji^>skut(!0LQ z)hchQx=t8)E#41!&fZzmV6ch-mAu`8eqXIq!4!i@?9W-wrJU8zndjhfQYq9GLm4yg z!?~n=0Npm{oIic$OX17WF&!wWA~IP8*mf)b*Pmrsn78q@p6Uc>I|{JXGr+rAhkhQ+ znfcnTC6DwE?>F0P|0)12UmV|?#{1qq03#pPsB?PF)0uQdYkA)hr{x>DpbmX0^A&#q Da^Z + +extern uint32_t _binary_sperrypc_data_start __asm("_binary_sperrypc_data_start"); +extern uint32_t _binary_sperrypc_data_end __asm("_binary_sperrypc_data_end"); +extern uint32_t _binary_sperrypc_data_size __asm("_binary_sperrypc_data_size");