diff options
| author | spicyjpeg <thatspicyjpeg@gmail.com> | 2023-07-03 08:13:23 +0200 |
|---|---|---|
| committer | spicyjpeg <thatspicyjpeg@gmail.com> | 2023-07-03 08:13:23 +0200 |
| commit | 06e65bea3a778b2dae5af77a7935ae3868ddd4d3 (patch) | |
| tree | 3af954ebead7540c9478d0a026ca2e59f3cf7b64 /examples | |
| parent | 472cf1c254ea5be5aff8a6c7067cbed2d467d85b (diff) | |
| download | psn00bsdk-06e65bea3a778b2dae5af77a7935ae3868ddd4d3.tar.gz | |
Fix setjmp.h, FntSort(), examples, rewrite system/timer
Diffstat (limited to 'examples')
| -rw-r--r-- | examples/sound/cdstream/main.c | 52 | ||||
| -rw-r--r-- | examples/sound/cdstream/stream.c | 76 | ||||
| -rw-r--r-- | examples/sound/cdstream/stream.h | 67 | ||||
| -rw-r--r-- | examples/system/timer/main.c | 255 |
4 files changed, 255 insertions, 195 deletions
diff --git a/examples/sound/cdstream/main.c b/examples/sound/cdstream/main.c index fbca65e..2bd2142 100644 --- a/examples/sound/cdstream/main.c +++ b/examples/sound/cdstream/main.c @@ -159,13 +159,13 @@ typedef struct { (((uint32_t) (x) & 0xff000000) >> 24) \ ) -/* Interrupt callbacks */ +/* Helper functions */ #define DUMMY_BLOCK_ADDR 0x1000 #define STREAM_BUFFER_ADDR 0x1010 typedef struct { - int start_lba, stream_length; + int start_lba, stream_length, sample_rate; volatile int next_sector; volatile size_t refill_length; @@ -175,13 +175,11 @@ static Stream_Context stream_ctx; static StreamReadContext read_ctx; void cd_read_handler(CdlIntrResult event, uint8_t *payload) { - // Mark the data as valid. + // Mark the data that has just been read as valid. if (event != CdlDiskError) Stream_Feed(&stream_ctx, read_ctx.refill_length * 2048); } -/* Helper functions */ - // This isn't actually required for this example, however it is necessary if the // stream buffers are going to be allocated into a region of SPU RAM that was // previously used (to make sure the IRQ is not going to be triggered by any @@ -254,14 +252,12 @@ void setup_stream(const CdlLOC *pos) { int num_chunks = (SWAP_ENDIAN(vag->size) + vag->interleave - 1) / vag->interleave; - config.spu_address = STREAM_BUFFER_ADDR; - config.channel_mask = 0; - config.interleave = vag->interleave; - config.buffer_size = RAM_BUFFER_SIZE; - config.refill_threshold = 0; - config.sample_rate = SWAP_ENDIAN(vag->sample_rate); - config.refill_callback = (void *) 0; - config.underrun_callback = (void *) 0; + __builtin_memset(&config, 0, sizeof(Stream_Config)); + + config.spu_address = STREAM_BUFFER_ADDR; + config.interleave = vag->interleave; + config.buffer_size = RAM_BUFFER_SIZE; + config.sample_rate = SWAP_ENDIAN(vag->sample_rate); // Use the first N channels of the SPU and pan them left/right in pairs // (this assumes the stream contains one or more stereo tracks). @@ -277,6 +273,7 @@ void setup_stream(const CdlLOC *pos) { read_ctx.start_lba = CdPosToInt(pos) + 1; read_ctx.stream_length = (num_channels * num_chunks * vag->interleave + 2047) / 2048; + read_ctx.sample_rate = config.sample_rate; read_ctx.next_sector = 0; read_ctx.refill_length = 0; @@ -315,10 +312,9 @@ int main(int argc, const char* argv[]) { Stream_Start(&stream_ctx, false); int sectors_per_chunk = (stream_ctx.chunk_size + 2047) / 2048; - int vag_sample_rate = getSPUSampleRate(stream_ctx.config.sample_rate); bool paused = false; - int sample_rate = vag_sample_rate; + int sample_rate = read_ctx.sample_rate; uint16_t last_buttons = 0xffff; @@ -326,12 +322,12 @@ int main(int argc, const char* argv[]) { bool buffering = feed_stream(); FntPrint(-1, "PLAYING SPU STREAM\n\n"); - FntPrint(-1, "BUFFER: %d (%d)\n", stream_ctx.db_active, stream_ctx.chunk_counter); + FntPrint(-1, "BUFFER: %d\n", stream_ctx.db_active); FntPrint(-1, "STATUS: %s\n\n", buffering ? "READING" : "IDLE"); FntPrint(-1, "BUFFERED: %d/%d\n", stream_ctx.buffer.length, stream_ctx.config.buffer_size); FntPrint(-1, "POSITION: %d/%d\n", read_ctx.next_sector, read_ctx.stream_length); - FntPrint(-1, "SMP RATE: %5d HZ\n\n", (sample_rate * 44100) >> 12); + FntPrint(-1, "SMP RATE: %5d HZ\n\n", sample_rate); FntPrint(-1, "[START] %s\n", paused ? "RESUME" : "PAUSE"); FntPrint(-1, "[LEFT/RIGHT] SEEK\n"); @@ -371,16 +367,18 @@ int main(int argc, const char* argv[]) { if ((last_buttons & PAD_CIRCLE) && !(pad->btn & PAD_CIRCLE)) read_ctx.next_sector = 0; - if (!(pad->btn & PAD_DOWN) && (sample_rate > 0x400)) - sample_rate -= 0x40; - if (!(pad->btn & PAD_UP) && (sample_rate < 0x2000)) - sample_rate += 0x40; - if ((last_buttons & PAD_CROSS) && !(pad->btn & PAD_CROSS)) - sample_rate = vag_sample_rate; - - // Only set the sample rate registers if necessary. - if (pad->btn != 0xffff) - Stream_SetSampleRate(&stream_ctx, (sample_rate * 44100) >> 12); + if (!(pad->btn & PAD_DOWN) && (sample_rate > 11000)) { + sample_rate -= 100; + Stream_SetSampleRate(&stream_ctx, sample_rate); + } + if (!(pad->btn & PAD_UP) && (sample_rate < 88200)) { + sample_rate += 100; + Stream_SetSampleRate(&stream_ctx, sample_rate); + } + if ((last_buttons & PAD_CROSS) && !(pad->btn & PAD_CROSS)) { + sample_rate = read_ctx.sample_rate; + Stream_SetSampleRate(&stream_ctx, sample_rate); + } last_buttons = pad->btn; } diff --git a/examples/sound/cdstream/stream.c b/examples/sound/cdstream/stream.c index aaf5703..624b6e1 100644 --- a/examples/sound/cdstream/stream.c +++ b/examples/sound/cdstream/stream.c @@ -7,8 +7,8 @@ #include <stddef.h> #include <stdbool.h> #include <stdlib.h> -#include <string.h> #include <assert.h> +#include <psxgpu.h> #include <psxspu.h> #include <psxetc.h> #include <psxapi.h> @@ -27,10 +27,20 @@ #define _min(x, y) (((x) < (y)) ? (x) : (y)) -/* Interrupt handlers */ +/* Private utilities */ static volatile Stream_Context *_active_ctx = (void *) 0; +static Stream_Time _default_timer_function(void) { + return VSync(-1); +} + +static int _get_default_timer_rate(void) { + return (GetVideoMode() == MODE_PAL) ? 50 : 60; +} + +/* Interrupt handlers */ + static void _spu_irq_handler(void) { Stream_Context *ctx = _active_ctx; @@ -59,7 +69,9 @@ static void _spu_irq_handler(void) { // once the buffer's length is below the refill threshold. ctx->db_active ^= 1; ctx->buffering = true; - ctx->chunk_counter++; + + ctx->play_time += ctx->samples_per_chunk; + ctx->last_updated = ctx->config.timer_function(); size_t tail = ctx->buffer.tail; uint8_t *ptr = &ctx->buffer.data[ctx->buffer.tail]; @@ -82,12 +94,16 @@ static void _spu_irq_handler(void) { uint32_t address = ctx->config.spu_address + (ctx->db_active ? ctx->chunk_size : 0); + int sample_rate = ctx->new_sample_rate; + ctx->config.sample_rate = sample_rate; + SPU_IRQ_ADDR = getSPUAddr(address); for (uint32_t ch = 0, mask = ctx->config.channel_mask; mask; ch++, mask >>= 1) { if (!(mask & 1)) continue; + SPU_CH_FREQ (ch) = getSPUSampleRate(sample_rate); SPU_CH_LOOP_ADDR(ch) = getSPUAddr(address + offset); offset += ctx->config.interleave; @@ -110,8 +126,8 @@ static void _spu_dma_handler(void) { /* Public API */ void Stream_Init(Stream_Context *ctx, const Stream_Config *config) { - memset(ctx, 0, sizeof(Stream_Context)); - memcpy(&(ctx->config), config, sizeof(Stream_Config)); + __builtin_memset(ctx, 0, sizeof(Stream_Context)); + __builtin_memcpy(&(ctx->config), config, sizeof(Stream_Config)); ctx->num_channels = 0; for (uint32_t mask = config->channel_mask; mask; mask >>= 1) { @@ -121,8 +137,15 @@ void Stream_Init(Stream_Context *ctx, const Stream_Config *config) { assert(ctx->num_channels); - ctx->chunk_size = ctx->config.interleave * ctx->num_channels; - ctx->buffer.data = malloc(config->buffer_size); + if (!ctx->config.timer_function) { + ctx->config.timer_rate = _get_default_timer_rate(); + ctx->config.timer_function = &_default_timer_function; + } + + ctx->chunk_size = ctx->config.interleave * ctx->num_channels; + ctx->samples_per_chunk = ctx->config.interleave / 16 * 28; + ctx->new_sample_rate = ctx->config.sample_rate; + ctx->buffer.data = malloc(config->buffer_size); assert(ctx->buffer.data); @@ -160,6 +183,9 @@ bool Stream_Start(Stream_Context *ctx, bool resume) { uint32_t address = ctx->config.spu_address + (ctx->db_active ? ctx->chunk_size : 0); + int sample_rate = ctx->new_sample_rate; + ctx->config.sample_rate = sample_rate; + SpuSetKey(0, ctx->config.channel_mask); for (uint32_t ch = 0, mask = ctx->config.channel_mask; mask; ch++, mask >>= 1) { @@ -167,7 +193,7 @@ bool Stream_Start(Stream_Context *ctx, bool resume) { continue; SPU_CH_ADDR (ch) = getSPUAddr(address); - SPU_CH_FREQ (ch) = getSPUSampleRate(ctx->config.sample_rate); + SPU_CH_FREQ (ch) = getSPUSampleRate(sample_rate); SPU_CH_ADSR1(ch) = 0x00ff; SPU_CH_ADSR2(ch) = 0x0000; @@ -197,26 +223,40 @@ bool Stream_Stop(void) { SpuSetKey(1, ctx->config.channel_mask); - _active_ctx = (void *) 0; + ctx->last_stopped = ctx->config.timer_function(); + _active_ctx = (void *) 0; + return true; } void Stream_SetSampleRate(Stream_Context *ctx, int value) { - ctx->config.sample_rate = value; - - if (!Stream_IsActive(ctx)) - return; - - for (uint32_t ch = 0, mask = ctx->config.channel_mask; mask; ch++, mask >>= 1) { - if (mask & 1) - SPU_CH_FREQ(ch) = getSPUSampleRate(value); - } + ctx->new_sample_rate = value; } bool Stream_IsActive(const Stream_Context *ctx) { return (ctx == _active_ctx); } +uint32_t Stream_GetSamplesPlayed(const Stream_Context *ctx) { + // Calculate the time elapsed from the last update (or since the stream was + // stopped) and use the value to estimate how many samples have been played + // since then. + Stream_Time delta; + + if (ctx == _active_ctx) + delta = ctx->config.timer_function() - ctx->last_updated; + else + delta = ctx->last_stopped - ctx->last_updated; + + return ctx->play_time + ( + (delta * ctx->config.sample_rate) / ctx->config.timer_rate + ); +} + +void Stream_ResetSamplesPlayed(Stream_Context *ctx) { + ctx->play_time = 0; +} + size_t Stream_GetRefillLength(const Stream_Context *ctx) { int unbuf_total = (int) ctx->config.buffer_size - (int) ctx->buffer.length; diff --git a/examples/sound/cdstream/stream.h b/examples/sound/cdstream/stream.h index aa384ed..ab28f50 100644 --- a/examples/sound/cdstream/stream.h +++ b/examples/sound/cdstream/stream.h @@ -32,7 +32,9 @@ /* Type definitions */ -typedef uint32_t *(*Stream_Callback)(void); +typedef uint32_t Stream_Time; +typedef void (*Stream_Callback)(void); +typedef Stream_Time (*Stream_TimerFunction)(void); /** * @brief Stream initialization settings structure. @@ -45,20 +47,24 @@ typedef uint32_t *(*Stream_Callback)(void); * cannot be used. The channel mask is a bitfield whose bits represent which SPU * channels the stream is going to use: for instance, a value of 0b1101 will * assign the first channel of the stream to SPU channel 0, the second channel - * to SPU channel 2 and the third channel to SPU channel 3. The sample rate is - * in Hertz, while the interleave and buffer size are in bytes. - * - * The refill threshold, refill callback and underrun callback are optional. If - * provided, the callbacks will be invoked by the SPU IRQ handler once the - * FIFO's length goes below the specified threshold and once it reaches zero, - * respectively. + * to SPU channel 2 and the third channel to SPU channel 3. The sample rate and + * optional timer rate are in Hertz, while the interleave and buffer size are in + * bytes. + * + * The refill threshold, refill callback, underrun callback and timer function + * are optional. If provided, the callbacks will be invoked by the SPU IRQ + * handler once the FIFO's length goes below the specified threshold and once it + * reaches zero, respectively. The timer function will be used to improve the + * accuracy of Stream_GetSamplesPlayed(); if not provided, VSync(-1) will be + * used by default. */ typedef struct { uint32_t spu_address, channel_mask; size_t interleave, buffer_size, refill_threshold; - int sample_rate; + int sample_rate, timer_rate; - Stream_Callback refill_callback, underrun_callback; + Stream_Callback refill_callback, underrun_callback; + Stream_TimerFunction timer_function; } Stream_Config; typedef struct { @@ -71,20 +77,20 @@ typedef struct { * * @details This structure represents a single audio stream. An arbitrary number * of streams may be created concurrently, however only one can be active at a - * time (as the SPU only provides a single interrupt). With the exception of the - * chunk counter, most fields are only used internally and shall not be accessed - * directly. + * time (as the SPU only provides a single interrupt). All fields are only used + * internally and shall not be accessed directly. */ typedef struct { Stream_Config config; volatile Stream_Buffer buffer; void *old_irq_handler, *old_dma_handler; - size_t chunk_size; + size_t chunk_size, samples_per_chunk; uint8_t num_channels; - volatile uint8_t db_active, buffering, callback_issued; - volatile uint32_t chunk_counter; + volatile uint8_t db_active, buffering, callback_issued; + volatile Stream_Time last_updated, last_stopped, play_time; + volatile int new_sample_rate; } Stream_Context; /* Public API */ @@ -146,9 +152,9 @@ bool Stream_Stop(void); /** * @brief Changes the sampling rate of a stream. * - * @details If the stream is currently active the pitch of the SPU channels - * assigned to it is changed to match the new value, otherwise the new sampling - * rate will be applied once the stream is started. + * @details If the stream is currently active the change will apply on the next + * FIFO data pull, otherwise the new sampling rate will be applied once the + * stream is started or resumed. * * @param ctx * @param value @@ -164,6 +170,29 @@ void Stream_SetSampleRate(Stream_Context *ctx, int value); bool Stream_IsActive(const Stream_Context *ctx); /** + * @brief Returns an estimate of how many audio samples have been played so far. + * + * @details The returned value can be divided by the stream's sampling rate to + * obtain the total playback time in seconds. The value is interpolated over + * time using VSync(-1) as a time reference by default. If greater accuracy is + * required, a custom timer function can be provided instead by setting the + * appropriate fields in the configuration object before calling Stream_Init(). + * + * @param ctx + * @return Number of audio samples + * + * @see Stream_ResetSamplesPlayed() + */ +uint32_t Stream_GetSamplesPlayed(const Stream_Context *ctx); + +/** + * @brief Resets the audio sample counter returned by Stream_GetSamplesPlayed(). + * + * @param ctx + */ +void Stream_ResetSamplesPlayed(Stream_Context *ctx); + +/** * @brief Returns how many bytes in a stream's FIFO are currently empty and can * be filled. * diff --git a/examples/system/timer/main.c b/examples/system/timer/main.c index eb62712..c424e84 100644 --- a/examples/system/timer/main.c +++ b/examples/system/timer/main.c @@ -1,156 +1,149 @@ -#include <sys/types.h> -#include <stdio.h> +/* + * PSn00bSDK hardware timer example + * (C) 2023 spicyjpeg - MPL licensed + */ + +#include <stdint.h> #include <psxgpu.h> -#include <psxapi.h> #include <psxetc.h> +#include <psxapi.h> #include <hwregs_c.h> -/* OT and Packet Buffer sizes */ -#define OT_LEN 256 -#define PACKET_LEN 1024 +/* Display/GPU context utilities */ -/* Screen resolution */ -#define SCREEN_XRES 320 -#define SCREEN_YRES 240 +#define SCREEN_XRES 320 +#define SCREEN_YRES 240 -/* Screen center position */ -#define CENTERX SCREEN_XRES>>1 -#define CENTERY SCREEN_YRES>>1 +#define BGCOLOR_R 48 +#define BGCOLOR_G 24 +#define BGCOLOR_B 0 +typedef struct { + DISPENV disp; + DRAWENV draw; +} Framebuffer; -/* Double buffer structure */ typedef struct { - DISPENV disp; /* Display environment */ - DRAWENV draw; /* Drawing environment */ -} DB; + Framebuffer db[2]; + int db_active; +} RenderContext; + +void init_context(RenderContext *ctx) { + Framebuffer *db; + + ResetGraph(0); + ctx->db_active = 0; + + db = &(ctx->db[0]); + SetDefDispEnv(&(db->disp), 0, 0, SCREEN_XRES, SCREEN_YRES); + SetDefDrawEnv(&(db->draw), SCREEN_XRES, 0, SCREEN_XRES, SCREEN_YRES); + setRGB0(&(db->draw), BGCOLOR_R, BGCOLOR_G, BGCOLOR_B); + db->draw.isbg = 1; + db->draw.dtd = 1; + + db = &(ctx->db[1]); + SetDefDispEnv(&(db->disp), SCREEN_XRES, 0, SCREEN_XRES, SCREEN_YRES); + SetDefDrawEnv(&(db->draw), 0, 0, SCREEN_XRES, SCREEN_YRES); + setRGB0(&(db->draw), BGCOLOR_R, BGCOLOR_G, BGCOLOR_B); + db->draw.isbg = 1; + db->draw.dtd = 1; + + PutDrawEnv(&(db->draw)); + //PutDispEnv(&(db->disp)); + + // Create a text stream at the top of the screen. + FntLoad(960, 0); + FntOpen(8, 16, 304, 208, 2, 512); +} + +void display(RenderContext *ctx) { + Framebuffer *db; + + DrawSync(0); + VSync(0); + ctx->db_active ^= 1; -/* Double buffer variables */ -DB db[2]; -int db_active = 0; + db = &(ctx->db[ctx->db_active]); + PutDrawEnv(&(db->draw)); + PutDispEnv(&(db->disp)); + SetDispMask(1); +} + +/* Interrupt handlers */ +typedef struct { + int irq_count, last_irq_count, irqs_per_sec; +} TimerState; -/* Function declarations */ -void init(); -void display(); +static volatile TimerState timer_state[3]; +static void timer0_handler(void) { + timer_state[0].irq_count++; +} -volatile int timer_calls = 0; +static void timer1_handler(void) { + timer_state[1].irq_count++; +} -void timer_func() -{ - timer_calls++; +static void timer2_handler(void) { + timer_state[2].irq_count++; } -volatile int vsync_count = 0; -volatile int tick_count = 0; -volatile int tick_value = 0; - -void vsync_func() -{ - vsync_count++; - if( vsync_count > 60 ) - { - tick_value = timer_calls-tick_count; - tick_count = timer_calls; - vsync_count = 0; +static void vblank_handler(void) { + int refresh_rate = (GetVideoMode() == MODE_PAL) ? 50 : 60; + + // Only update once per second (every 50 or 60 vblanks). + if (VSync(-1) % refresh_rate) + return; + + for (int i = 0; i < 3; i++) { + TimerState *state = &timer_state[i]; + + int count = state->irq_count; + state->irqs_per_sec = count - state->last_irq_count; + state->last_irq_count = count; } } -/* Main function */ -int main() { - - int counter; - - /* Init graphics and GTE */ - init(); - - +/* Main */ + +static RenderContext ctx; + +int main(int argc, const char* argv[]) { + init_context(&ctx); + + // Set up the timers and register callbacks for their interrupts. + TIMER_CTRL(0) = 0x0160; // Dotclock input, repeated IRQ on overflow + TIMER_CTRL(1) = 0x0160; // Hblank input, repeated IRQ on overflow + TIMER_CTRL(2) = 0x0260; // CLK/8 input, repeated IRQ on overflow + + __builtin_memset(timer_state, 0, sizeof(timer_state)); + EnterCriticalSection(); - //SetRCnt(RCntCNT2, 0xF040, RCntMdINTR); - - // NTSC clock base - counter = 4304000/560; - - // PAL clock base - //counter = 5163000/560; - - SetRCnt(RCntCNT2, counter, RCntMdINTR); - TIMER_CTRL(2) = 0x1E58; - InterruptCallback(6, timer_func); - StartRCnt(RCntCNT2); - ChangeClearRCnt(2, 0); + InterruptCallback(IRQ_TIMER0, &timer0_handler); + InterruptCallback(IRQ_TIMER1, &timer1_handler); + InterruptCallback(IRQ_TIMER2, &timer2_handler); + VSyncCallback(&vblank_handler); ExitCriticalSection(); - - VSyncCallback(vsync_func); - - /* Main loop */ - while( 1 ) { - - FntPrint(-1, "TIMER COUNT=%d\n", timer_calls); - FntPrint(-1, "TICKS/SEC=%d\n", tick_value); - - /* Swap buffers and draw text */ - display(); - + + while (1) { + FntPrint(-1, "HARDWARE TIMER EXAMPLE\n\n"); + + for (int i = 0; i < 3; i++) { + TimerState *state = &timer_state[i]; + + FntPrint(-1, "TIMER %d:\n", i); + FntPrint(-1, " VALUE: %d\n", TIMER_VALUE(i)); + FntPrint(-1, " IRQS: %d\n", state->irq_count); + FntPrint(-1, " IRQS/S: %d\n\n", state->irqs_per_sec); + } + + FntPrint(-1, "VBLANK COUNTER:\n"); + FntPrint(-1, " VALUE: %d\n\n", VSync(-1)); + + FntFlush(-1); + display(&ctx); } - - return 0; - -} -void init() { - - /* Reset the GPU, also installs a VSync event handler */ - ResetGraph( 0 ); - //SetVideoMode(MODE_PAL); - - /* Set display and draw environment areas */ - /* (display and draw areas must be separate, otherwise hello flicker) */ - SetDefDispEnv( &db[0].disp, 0, 0, SCREEN_XRES, SCREEN_YRES ); - SetDefDrawEnv( &db[0].draw, SCREEN_XRES, 0, SCREEN_XRES, SCREEN_YRES ); - - /* Enable draw area clear and dither processing */ - setRGB0( &db[0].draw, 63, 0, 127 ); - db[0].draw.isbg = 1; - db[0].draw.dtd = 1; - - - /* Define the second set of display/draw environments */ - SetDefDispEnv( &db[1].disp, SCREEN_XRES, 0, SCREEN_XRES, SCREEN_YRES ); - SetDefDrawEnv( &db[1].draw, 0, 0, SCREEN_XRES, SCREEN_YRES ); - - //db[0].disp.screen.y = 24; - //db[1].disp.screen.y = 24; - - setRGB0( &db[1].draw, 63, 0, 127 ); - db[1].draw.isbg = 1; - db[1].draw.dtd = 1; - - - /* Apply the drawing environment of the first double buffer */ - PutDrawEnv( &db[0].draw ); - - FntLoad(960, 0); - FntOpen(0, 8, 320, 216, 0, 100); - + return 0; } - -void display() { - - FntFlush(-1); - - /* Wait for GPU to finish drawing and vertical retrace */ - DrawSync( 0 ); - VSync( 0 ); - - /* Swap buffers */ - db_active ^= 1; - - /* Apply display/drawing environments */ - PutDrawEnv( &db[db_active].draw ); - PutDispEnv( &db[db_active].disp ); - - /* Enable display */ - SetDispMask( 1 ); - -}
\ No newline at end of file |
