Files
OpenEPaperLink/zbs243_shared/cpu/8051/printf.c
2023-03-27 15:32:37 +02:00

800 lines
26 KiB
C

#include "printf.h"
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include "board.h"
#include "screen.h"
#include "uart.h"
#include "zbs243.h"
typedef void (*StrFormatOutputFunc)(uint32_t param /* low byte is data, bits 24..31 is char */) __reentrant;
static __idata __at(0x00) unsigned char R0;
static __idata __at(0x01) unsigned char R1;
static __idata __at(0x02) unsigned char R2;
static __idata __at(0x03) unsigned char R3;
static __idata __at(0x04) unsigned char R4;
static __idata __at(0x05) unsigned char R5;
static __idata __at(0x06) unsigned char R6;
static __idata __at(0x07) unsigned char R7;
static uint8_t __xdata mCvtBuf[18];
// callback must be reentrant and callee_saves
#pragma callee_saves prvPrintFormat
void prvPrintFormat(StrFormatOutputFunc formatF, uint16_t formatD, const char __code *fmt, va_list vl) __reentrant __naked {
// formatF is in DPTR
// sp[0..-1] is return addr
// sp[-2..-3] is formatD
// sp[-4..-5] is fmt
// sp[-6] is vl
__asm__(
" push _R7 \n"
" push DPH \n" // push formatF
" push DPL \n"
" mov _R7, sp \n" // save place on stack where we stashed it so we can call it easily
" push _R4 \n"
" push _R3 \n"
" push _R2 \n"
" push _R1 \n"
" push _R0 \n"
" mov A, #-12 \n"
" add A, sp \n"
" mov R0, A \n"
// R0 now points to pushed params, for large values, we see high bytes first
// to get next byte, we need to DECEREMENT R0
" mov DPH, @R0 \n"
" dec R0 \n"
" mov DPL, @R0 \n"
" dec R0 \n"
" mov _R0, @R0 \n"
" dec R0 \n"
// now format string is in DPTR, and R0 points to the top byte of whatever was in the first param
// main loop: get a byte of the format string
"00001$: \n"
" clr A \n"
" movc A, @A + DPTR \n"
" inc DPTR \n"
// if zero, we're done
" jz 00098$ \n"
// if not '%', print it
" cjne A, #'%', 00097$ \n"
// we got a percent sign - init state for format processing
" mov R4, #0 \n" // bit flags:
// 0x01 = '*' = pointer provided instead of value (integers only)
// 0x02 = '0' = zero-pad (for numbers only)
// 0x04 = have pad-to length
// 0x08 = long
// 0x10 = long long
// 0x20 = signed print requested. also: need to print a negative (used to reuse hex printing for decimal once converted to bcd)
" mov R2, #0 \n" // padLen
// loop for format string ingestion
"00002$: \n"
" clr A \n"
" movc A, @A + DPTR \n"
" inc DPTR \n"
// if zero, we're done
" jz 00098$ \n"
// check for percent sign
" cjne A, #'%', 00003$ \n"
// fallthrough to print it and go read next non-format byte
// print a char in A, go read next format byte
"00097$: \n"
" lcall 00060$ \n"
" sjmp 00001$ \n"
// exit label - placed for easy jumping to
"00098$: \n"
" pop _R0 \n"
" pop _R1 \n"
" pop _R2 \n"
" pop _R3 \n"
" pop _R4 \n"
" pop DPL \n"
" pop DPH \n"
" pop _R7 \n"
" ret \n"
// continue to process format string - handle %c
"00003$: \n"
" cjne A, #'c', 00004$ \n"
" dec R0 \n" // param is pushed as int (16 bits)
" mov A, @R0 \n"
" dec R0 \n"
" sjmp 00097$ \n" // print and go read next non-format byte
// continue to process format string - handle %m
"00004$: \n"
" mov R3, A \n"
" orl A, #0x20 \n"
" cjne A, #'m', 00008$ \n"
// sort out which hexch charset to use
" mov A, R3 \n"
" anl A, #0x20 \n"
" rr A \n"
" mov R1, A \n"
// go, do
" push DPH \n"
" push DPL \n"
" lcall 00090$ \n" // read the short (__xdata) pointer - >DPTR
" mov R4, #8 \n" // byteSel
"00005$: \n"
" push DPH \n"
" push DPL \n"
" mov A, R4 \n"
" dec A \n"
" add A, DPL \n"
" mov DPL, A \n"
" mov A, DPH \n"
" addc A, #0 \n"
" mov DPH, A \n"
" movx A, @DPTR \n"
" mov R2, A \n"
" swap A \n"
" mov R3, #2 \n"
"00006$: \n"
" anl A, #0x0f \n"
" add A, R1 \n"
" mov DPTR, #00099$ \n"
" movc A, @A + DPTR \n"
" lcall 00060$ \n"
" mov A, R2 \n"
" djnz R3, 00006$ \n"
" pop DPL \n"
" pop DPH \n"
" djnz R4, 00007$ \n"
// done with mac addr
"00055$: \n"
" pop DPL \n"
" pop DPH \n"
" sjmp 00001$ \n"
// print colon and contimue mac addr printing
"00007$: \n"
" mov A, #':' \n"
" lcall 00060$ \n"
" sjmp 00005$ \n"
// continue to process format string - handle '*'
"00008$: \n"
" mov A, R3 \n"
" cjne A, #'*', 00009$ \n"
" cjne R2, #0, 00097$ \n" // only valid when no length/padding has been specified yet, else invalid specifier
" mov A, #0x01 \n" //"pointer mode"
"00010$: \n"
" orl A, R4 \n"
" mov R4, A \n"
" sjmp 00002$ \n" // get next format specifier now
// continue to process format string - handle '0'
"00009$: \n"
" cjne A, #'0', 00011$ \n"
" cjne R2, #0, 00011$ \n" // setting "zero pad" is only valid when pad length is zero
" mov A, #0x06 \n" //"have pad length" | "zero-pad"
" sjmp 00010$ \n" // orr A into R4, get next format specifier now
// continue to process format string - handle '1'...'9'
"00011$: \n"
" mov R3, A \n"
" add A, #-'0' \n"
" jnc 00012$ \n" // now 0..9 are valid
" add A, #-10 \n"
" jc 00012$ \n"
" add A, #10 \n" // get it back into 1..9 range
" mov R3, A \n"
" mov A, #10 \n"
" mov B, R2 \n"
" mul AB \n"
" add A, R3 \n"
" mov R2, A \n"
" mov A, #0x04 \n" //"have pad length"
" sjmp 00010$ \n" // orr A into R4, get next format specifier now
// continue to process format string - handle 'l'
"00012$: \n"
" cjne R3, #'l', 00014$ \n"
" mov A, R4 \n"
" anl A, #0x08 \n"
" jz 00013$ \n" // no "long" yet? set that
// have long - set long log
" mov A, #0x10 \n" //"long long"
" sjmp 00010$ \n" // orr A into R4, get next format specifier now
// first 'l' - set long
"00013$: \n"
" mov A, #0x08 \n" //"long"
" sjmp 00010$ \n" // orr A into R4, get next format specifier now
// continue to process format string - handle 's'
"00014$: \n"
" cjne R3, #'s', 00025$ \n"
" mov A, R4 \n"
" anl A, #0x08 \n"
" push DPH \n"
" push DPL \n"
" jnz 00015$ \n"
" lcall 00091$ \n" // get and resolve generic pointer into DPTR
" sjmp 00016$ \n"
"00015$: \n" // get short pointer into DPTR, record that it is to XRAM
" clr PSW.5 \n"
" clr PSW.1 \n"
" lcall 00090$ \n"
"00016$: \n" // pointer to string now in DPTR
// we have the string pointer in {DPTR,PSW}, let's see if we have padding to do
" mov A, R4 \n"
" anl A, #0x04 \n"
" jnz 00018$ \n"
// print string with no length restrictions
"00017$: \n"
" lcall 00095$ \n"
" jz 00055$ \n"
" lcall 00060$ \n"
" sjmp 00017$ \n"
// print string with length restrictions and/or padding
"00018$: \n"
" cjne R2, #0, 00019$ \n" // verify reqested len was not zero
" sjmp 00055$ \n"
"00019$: \n"
" lcall 00095$ \n"
" jz 00020$ \n"
" lcall 00060$ \n"
" djnz R2, 00019$ \n"
// we get here if we ran out of allowable bytes - we're done then
" ljmp 00055$ \n"
// just a trampoline for range issues
"00035$: \n"
" ljmp 00036$ \n"
// we need to pad with spaces
"00020$: \n"
" mov A, #' ' \n"
" lcall 00060$ \n"
" djnz R2, 00020$ \n"
" ljmp 00055$ \n"
// continue to process format string - handle 'x'/'X'
"00025$: \n"
" mov A, R3 \n"
" orl A, #0x20 \n"
" cjne A, #'x', 00035$ \n"
" push DPH \n"
" push DPL \n"
" lcall 00080$ \n" // get pointer to the number in DPTR, length in bytes in B
// save it
"00070$: \n"
" push DPH \n"
" push DPL \n"
// sort out how long it would be if printed, first get a pointer to the highest
" mov A, B \n"
" rl A \n"
" mov R1, A \n"
" rr A \n"
" add A, #0xff \n"
" add A, DPL \n"
" mov DPL, A \n"
" mov A, DPH \n"
" addc A, #0x00 \n"
" mov DPH, A \n"
"00026$: \n"
" lcall 00079$ \n"
" anl A, #0xf0 \n"
" jnz 00028$ \n"
" dec R1 \n"
" lcall 00079$ \n"
" jnz 00028$ \n"
" dec R1 \n"
// dec DPTR
" dec DPL \n"
" mov A, DPL \n"
" cjne A, #0xff, 00027$ \n"
" dec DPH \n"
"00027$: \n"
" djnz B, 00026$ \n"
// we now know how many digits the number is (in R1), except that it has "0" if the number if zero, we cannot have that
"00028$: \n"
" cjne R1, #0, 00029$ \n"
" inc R1 \n"
"00029$: \n" // we now finally have the full length of the digits
// if the number is negative (happens when we're printing decimals)
// the length of it is one more, also in case of zero-padding, we need to print the minus sign here now
" mov A, R4 \n"
" anl A, #0x20 \n"
" jz 00051$ \n"
" inc R1 \n" // the length is one more
" mov A, R4 \n"
" anl A, #02 \n" // if zero-padding, the negative comes now
" jz 00051$ \n"
" mov A, #'-' \n"
" lcall 00060$ \n"
"00051$: \n"
// sort out if we need padding at all and if there is space
" mov A, R4 \n"
" anl A, #0x04 \n"
" jz 00031$ \n" // no padding requested
// padding was requested len is in R2
" mov A, R2 \n"
" clr C \n"
" subb A, R1 \n"
" jc 00031$ \n" // pad-to len < number_len -> no padding needed
" jz 00031$ \n" // pad-to len == number_len -> no padding needed
" mov R2, A \n"
// sort out which character to use -> DPL
" mov A, R4 \n" // fancy way to create space/zero as needed
" anl A, #0x02 \n"
" swap A \n"
" rr A \n"
" add A, #0x20 \n"
" mov DPL, A \n"
// pad!
"00030$: \n"
" mov A, DPL \n"
" lcall 00060$ \n"
" djnz R2, 00030$ \n"
"00031$: \n"
// if the number is negative (happens when we're printing decimals)
// we made the length of it is one more, which we need to undo
// also in case of space-padding, we need to print the minus sign here now
" mov A, R4 \n"
" anl A, #0x20 \n"
" jz 00052$ \n"
" dec R1 \n" // the length is one less than we had increased it to
" mov A, R4 \n"
" anl A, #02 \n" // if space-padding, the negative comes now
" jnz 00052$ \n"
" mov A, #'-' \n"
" lcall 00060$ \n"
"00052$: \n"
// time to print the number itself
// sort out which hexch charset to use -> R2
" mov A, R3 \n"
" anl A, #0x20 \n"
" rr A \n"
" mov R2, A \n"
// re-get the number pointer
" pop DPL \n"
" pop DPH \n"
// currently DPTR points to the number low byte, R1 is now many digits we expect to print, R2 is the charset selection, R4 and R3 are free
// let's calculate how many bytes we expect to process -> R4
" mov A, R1 \n"
" inc A \n"
" clr C \n"
" rrc A \n"
" mov R4, A \n"
// let's repoint DPTR to the first byte we'll print in (remember we print 2 digits per byte)
" dec A \n"
" add A, DPL \n"
" mov DPL, A \n"
" mov A, DPH \n"
" addc A, #0x00 \n"
" mov DPH, A \n"
// decide if we need to print just a nibble of the high byte or the whole thing. Free up R1
" mov A, R1 \n"
" anl A, #0x01 \n"
" jz 00032$ \n"
// we're printing just the low nibble of the first byte - set up for it
" lcall 00079$ \n"
" mov R1, #1 \n"
" sjmp 00033$ \n"
// print loop
"00032$: \n"
" lcall 00079$ \n"
" mov R1, #2 \n"
" mov R3, A \n"
" swap A \n"
"00033$: \n"
" anl A, #0x0f \n"
" add A, R2 \n"
" push DPH \n"
" push DPL \n"
" mov DPTR, #00099$ \n"
" movc A, @A + DPTR \n"
" pop DPL \n"
" pop DPH \n"
" lcall 00060$ \n"
" mov A, R3 \n"
" djnz R1, 00033$ \n"
// dec DPTR
" dec DPL \n"
" mov A, DPL \n"
" cjne A, #0xff, 00034$ \n"
" dec DPH \n"
"00034$: \n"
" djnz R4, 00032$ \n"
// done!
" ljmp 00055$ \n"
// continue to process format string - handle 'd'
"00036$: \n"
" cjne R3, #'d', 00037$ \n"
" mov A, #0x20 \n"
" orl A, R4 \n"
" mov R4, A \n"
" sjmp 00040$ \n"
// continue to process format string - handle 'u'
"00037$: \n"
" cjne R3, #'u', 00038$ \n"
" sjmp 00040$ \n"
// no more format strings exist that we can handle - bail
"00038$: \n"
" ljmp 00001$ \n"
// handle decimal printing
"00040$: \n"
" push DPH \n"
" push DPL \n"
" lcall 00080$ \n" // get pointer to the number in DPTR, length in bytes in B
" push B \n"
// copy the number to the double-dabble storage at proper offset (0 for u64, 4 for u32, 6 for u16)
// we do this so that the dabble area always starts at the same place...
" mov A, #8 \n"
" clr C \n"
" subb A, B \n"
" add A, #_mCvtBuf \n"
" mov R1, A \n"
" clr A \n"
" addc A, #(_mCvtBuf >> 8) \n"
" mov R3, A \n"
"00041$: \n"
" lcall 00079$ \n"
" inc DPTR \n"
" lcall 00086$ \n"
" movx @DPTR, A \n"
" inc DPTR \n"
" lcall 00086$ \n"
" djnz B, 00041$ \n"
// leave DPTR pointing to dabble storage, past the number
" lcall 00086$ \n"
// we now have the top byte of the number in A, good time to check for negatives, if needed
" mov B, A \n"
" mov A, R4 \n"
" anl A, #0x20 \n"
" jz 00050$ \n" // unsigned printing requested
" mov A, B \n"
" anl A, #0x80 \n"
" jnz 00043$ \n" // is negative - we need to invert, 0x20 bit in R1 stays
// positive - 0x20 bit in R1 needs to go
" mov A, R4 \n"
" anl A, #~0x20 \n"
" mov R4, A \n"
" sjmp 00050$ \n"
// we need to negate the number
// but first we need a pointer to it, and its size
"00043$: \n"
" pop B \n"
" push B \n"
" mov A, #8 \n"
" clr C \n"
" subb A, B \n"
" add A, #_mCvtBuf \n"
" mov DPL, A \n"
" clr A \n"
" addc A, #(_mCvtBuf >> 8) \n"
" mov DPH, A \n"
// ok, now we are ready to negate it
" clr C \n"
"00049$: \n"
" movx A, @DPTR \n"
" mov R1, A \n"
" clr A \n"
" subb A, R1 \n"
" movx @DPTR, A \n"
" inc DPTR \n"
" djnz B, 00049$ \n"
// zero out the rest of the storage (10 bytes)
"00050$: \n"
" mov B, #10 \n"
" clr A \n"
"00042$: \n"
" movx @DPTR, A \n"
" inc DPTR \n"
" djnz B, 00042$ \n"
// calculate number of dabble steps
" pop A \n"
" swap A \n"
" rr A \n"
" mov R3, A \n"
// do the thing
"00044$: \n"
// dabble (10 iters for simplicity)
" mov DPTR, #(_mCvtBuf + 8) \n"
" mov B, #10 \n"
"00046$: \n"
" movx A, @DPTR \n"
" mov R1, A \n"
" anl A, #0x0f \n"
" add A,#-0x05 \n"
" mov A, R1 \n"
" jnc 00047$ \n"
" add A, #0x03 \n"
"00047$: \n"
" mov R1, A \n"
" anl A, #0xf0 \n"
" add A,#-0x50 \n"
" mov A, R1 \n"
" jnc 00048$ \n"
" add A, #0x30 \n"
"00048$: \n"
" movx @DPTR, A \n"
" inc DPTR \n"
" djnz B, 00046$ \n"
// double (18 iters for simplicity)
" mov DPTR, #_mCvtBuf \n"
" clr C \n"
" mov B, #18 \n"
"00045$: \n"
" movx A, @DPTR \n"
" rlc A \n"
" movx @DPTR, A \n"
" inc DPTR \n"
" djnz B, 00045$ \n"
" djnz R3, 00044$ \n"
// dabbling is done, print it now using hex routine
" mov DPTR, #(_mCvtBuf + 8) \n"
" mov B, #10 \n"
" clr PSW.5 \n" // it is now for sure in XRAM
" ljmp 00070$ \n"
// read short pointer from param stack
"00090$: \n"
" mov DPH, @R0 \n"
"00093$: \n"
" dec R0 \n"
" mov DPL, @R0 \n"
" dec R0 \n"
" ret \n"
// read and increment pointer of the type provided by 00091$ (in {DPTR,PSW}) into A. clobber nothing
"00095$: \n"
" jb PSW.5, 00066$ \n"
" jb PSW.1, 00067$ \n"
// XRAM
" movx A, @DPTR \n"
" inc DPTR \n"
" ret \n"
// CODE
"00066$: \n"
" clr A \n"
" movc A, @A+DPTR \n"
" inc DPTR \n"
" ret \n"
// IRAM
"00067$: \n"
" mov DPH, R0 \n"
" mov R0, DPL \n"
" mov A, @R0 \n"
" mov R0, DPH \n"
" inc DPL \n"
" ret \n"
// resolve generic pointer on param stack to an pointer in DPTR and flags in PSW.5 and PSW.1
// PSW.5 will be 0 and PSW.1 will be 0 for XRAM (PDATA goes here too)
// PSW.5 will be 1 and PSW.1 will be 0 for CODE
// PSW.5 will be 0 and PSW.1 will be 1 for IRAM
"00091$: \n"
" clr PSW.5 \n"
" clr PSW.1 \n"
" mov A, @R0 \n"
" dec R0 \n"
" jz 00090$ \n" // 0x00: pointer type: xdata
" xrl A, #0x80 \n"
" jz 00094$ \n" // 0x80: pointer type: code
" xrl A, #0xc0 \n"
" jz 00092$ \n" // 0x40: pointer type: idata
// pdata
" mov DPH, _XPAGE \n"
" sjmp 00093$ \n"
// idata
"00092$: \n"
" setb PSW.1 \n"
" sjmp 00093$ \n"
// code
"00094$: \n"
" setb PSW.5 \n"
" sjmp 00090$ \n"
// read the pointer of the type that 00080$ returns (in DPTR) into A. clobber nothing
"00079$: \n"
" jnb PSW.5, 00078$ \n"
" push _R0 \n"
" mov R0, DPL \n"
" mov A, @R0 \n"
" pop _R0 \n"
" ret \n"
"00078$: \n"
" movx A, @DPTR \n"
" ret \n"
// get pointer to a number, might be pushed or might be pointed to, size might vary. return pointer to number's LOW byte in DPTR
"00080$: \n"
" mov A, R4 \n"
" anl A, #0x01 \n"
" jnz 00083$ \n"
// param is itself on stack - now we care about size, but either way, PSW.5 will be 1
" setb PSW.5 \n"
" mov B, #0 \n"
" mov A, R4 \n"
" anl A, #0x18 \n"
" jz 00081$ \n"
" anl A, #0x10 \n"
" jz 00082$ \n"
// long long (8 bytes) \n"
" setb B.2 \n"
" dec R0 \n"
" dec R0 \n"
" dec R0 \n"
" dec R0 \n"
// long (4 bytes)
"00082$: \n"
" setb B.1 \n"
" dec R0 \n"
" dec R0 \n"
// int (2 bytes) \n"
"00081$: \n"
" setb B.0 \n"
" dec R0 \n"
" mov DPL, R0 \n"
" dec R0 \n"
" inc B \n"
" ret \n"
// pointer it on stack itself, number is in xram, but we still need to provide the length
"00083$: \n"
" clr PSW.5 \n" // mark as "in xram"
" mov A, R4 \n"
" anl A, #0x18 \n"
" jz 00084$ \n"
" anl A, #0x10 \n"
" jz 00085$ \n"
// long long
" mov B, #8 \n"
" ljmp 00090$ \n"
// long
"00085$: \n"
" mov B, #4 \n"
" ljmp 00090$ \n"
// int
"00084$: \n"
" mov B, #2 \n"
" ljmp 00090$ \n"
// swap R3:R1 <-> DPH:DPL
"00086$: \n"
" xch A, DPH \n"
" xch A, R3 \n"
" xch A, DPH \n"
" xch A, DPL \n"
" xch A, R1 \n"
" xch A, DPL \n"
" ret \n"
/* putchar func
called via call. char is in A, R7 has pointer to stack as needed
can clobber B, CANNOT clobber DPTR
a mess because...8051
*/
"00060$: \n"
" push DPH \n"
" push DPL \n"
" push _R1 \n"
" push _R0 \n"
" mov _R0, R7 \n"
" mov DPL, @R0 \n"
" dec R0 \n"
" mov DPH, @R0 \n" // DPTR is now func ptr
" dec R0 \n"
" dec R0 \n"
" dec R0 \n"
" dec R0 \n"
" mov _R1, @R0 \n"
" dec R0 \n"
" mov _R0, @R0 \n" // R1:R0 is now "formatD"
" lcall 00061$ \n" // to set ret addr
" pop _R0 \n"
" pop _R1 \n"
" pop DPL \n"
" pop DPH \n"
" ret \n"
"00061$: \n"
" push DPL \n"
" push DPH \n"
" mov DPL, _R0 \n"
" mov DPH, _R1 \n"
" ret \n"
"00099$: \n"
" .ascii \"01234567\" \n"
" .ascii \"89ABCDEF\" \n"
" .ascii \"01234567\" \n"
" .ascii \"89abcdef\" \n");
(void)fmt;
(void)vl;
(void)formatF;
(void)formatD;
}
#pragma callee_saves prPrvPutchar
static void prPrvPutchar(uint32_t data) __reentrant {
char ch = data >> 24;
if (ch == '\n')
uartTx('\r');
uartTx(ch);
}
#pragma callee_saves epdPutchar
static void epdPutchar(uint32_t data) __reentrant {
char ch = data >> 24;
writeCharEPD(ch);
}
void pr(const char __code *fmt, ...) __reentrant {
va_list vl;
va_start(vl, fmt);
prvPrintFormat(prPrvPutchar, 0, fmt, vl);
va_end(vl);
}
void epdpr(const char __code *fmt, ...) __reentrant {
va_list vl;
va_start(vl, fmt);
prvPrintFormat(epdPutchar, 0, fmt, vl);
va_end(vl);
}
#pragma callee_saves prPrvPutS
static void prPrvPutS(uint32_t data) __reentrant {
char __xdata *__idata *strPP = (char __xdata *__idata *)data;
char ch = data >> 24;
*(*strPP)++ = ch;
}
void spr(char __xdata *out, const char __code *fmt, ...) __reentrant {
char __xdata *outStart = out;
va_list vl;
va_start(vl, fmt);
prvPrintFormat(prPrvPutS, (uint16_t)&out, fmt, vl);
va_end(vl);
*out = 0;
}