From 73f22f5fce957b040cfa64cc447d90f13129e678 Mon Sep 17 00:00:00 2001 From: Hexa Dust Date: Mon, 20 Oct 2025 20:08:01 +0100 Subject: [PATCH] initial commit --- README.md | 18 ++++ oled-to-hex | 58 +++++++++++++ oled.mac | 198 ++++++++++++++++++++++++++++++++++++++++++ oled_compute.png | Bin 0 -> 1123 bytes oled_cpm.png | Bin 0 -> 983 bytes oled_tog_hxd.png | Bin 0 -> 616 bytes oled_tog_hxd_grad.png | Bin 0 -> 1028 bytes oled_zilog.png | Bin 0 -> 858 bytes 8 files changed, 274 insertions(+) create mode 100644 README.md create mode 100755 oled-to-hex create mode 100644 oled.mac create mode 100644 oled_compute.png create mode 100644 oled_cpm.png create mode 100644 oled_tog_hxd.png create mode 100644 oled_tog_hxd_grad.png create mode 100644 oled_zilog.png diff --git a/README.md b/README.md new file mode 100644 index 0000000..5bfc2d4 --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# RC2014 - Quazar OLED board + +This project provides MACRO-80 assembler code for `oled` program to drive OLED display for RC2014 by Quazar. It provides a Rust "script" to convert 128x32 black and white image to hex output suitable for the `oled` program. + +## Compiling + +``` +m80 =oled.mac/z +l80 oled,oled/n/e +``` + +## Running + +``` +oled +``` + +Now paste your image hex data as generated by `oled-to-hex`. diff --git a/oled-to-hex b/oled-to-hex new file mode 100755 index 0000000..ba7cc68 --- /dev/null +++ b/oled-to-hex @@ -0,0 +1,58 @@ +#!/usr/bin/env -S denim +/* Cargo.toml +[package] +name = "oled-to-hex" +version = "0.1.0" +authors = ["Anonymous"] +edition = "2021" + +[dependencies] +image = { version = "0.25.8", default-features = false, features = ["png"] } +*/ + +use image::GenericImageView; +use image::ImageReader; +use image::Pixel; +use std::env; +use std::error::Error; +use std::io::{Error as IoError, ErrorKind}; + +/// Example script description +fn main() -> Result<(), Box> { + let file = env::args().skip(1).next().ok_or(IoError::new( + ErrorKind::NotFound, + "expected PNG 128x32 file path", + ))?; + println!("Loading {}", file); + + let img = ImageReader::open(file)?.decode()?; + let (w, h) = img.dimensions(); + println!("Loaded file {}x{}", w, h); + if w != 128 && h != 32 { + return Err(Into::into(IoError::new( + ErrorKind::InvalidData, + "bad immage dimensions", + ))); + } + + for row in 0..4 { + for column in 0..128 { + let mut byte = 0u8; + for bit in 0..8 { + byte = byte >> 1; + let x = column; + let y = row * 8 + bit; + let pixel = img.get_pixel(x, y); + let luma = pixel.to_luma()[0]; + if luma > 128 { + byte = byte | 128; + } + } + print!("{:02X}", byte); + } + } + + Ok(()) +} + +// vim: ft=rust diff --git a/oled.mac b/oled.mac new file mode 100644 index 0000000..6554eb5 --- /dev/null +++ b/oled.mac @@ -0,0 +1,198 @@ +.Z80 +ASEG ; absolute addressing +ORG 100h ; skip CP/M + +CR EQU 0Dh ; carriage return +LF EQU 0Ah ; new line +BDOS EQU 0005h ; BDOS entry point +PSTR EQU 09h ; print string +CRED EQU 01h ; read console + +OLED EQU 50h ; base addr of OLED card +ORST EQU 04h ; reset command (H) +OBUF EQU 02h ; chip select (L), buffer (H) +ODB EQU 01h ; display byte (H) +OCMD EQU 00h ; command (L) + +; 4 rows of display +OROWA EQU 0B0h ; select top row +OROWB EQU 0B1h ; select second row +OROWC EQU 0B2h ; select third row +OROWD EQU 0B3h ; select bottom row + +OCOL0 EQU 04h ; left most column +OSELC EQU 10h ; select column command +OCOLL EQU 80h ; column count: 128 columns + +; 128 columns from 4 to 131 +; 1Xh, 0Yh - XY = 04h to 83h + +; MAIN +CALL OINIT ; init OLED +CALL OCLS ; clear screen +CALL PROMPT ; print prompt + +CALL OCSTART ; go to start +CALL READLINE ; read row A +CALL READLINE ; read row B +CALL READLINE ; read row C +CALL READLINE ; read row D + +RET ; exit to CP/M + + +READLINE: + LD D,OCOLL ; number of columns +READLINE1: + PUSH DE + CALL READHEX ; read hex input from console + CALL ODATA ; write to OLED + POP DE + DEC D ; count donw columns + JP NZ,READLINE1 ; next column + RET + +OINIT: + CALL ORESET ; reset OLED + LD HL,OINITSEQ ; commands to send +OINITLOOP: + LD A,(HL) ; load command byte to A + CP 0FFh ; check if we are done + RET Z ; marker reached + CALL OCOMMAND ; send command from A + INC HL ; next command + JR OINITLOOP ; send next command + +ORESET: + LD C,OLED ; base I/O address + LD B,ORST OR OBUF ; RST H, /CS H, DB L + OUT (C),A ; write any A, to (C) + B + REPT 4 + NOP ; delay + ENDM + LD B,OBUF ; clear RST + OUT (C),A ; send + REPT 20 + NOP ; long delay + ENDM + RET + +OCLS: + CALL OCSTART ; set start + CALL OCLSROW ; clear row A + CALL OCLSROW ; clear row B + CALL OCLSROW ; clear row C + CALL OCLSROW ; clear row D + RET + +; set cursor to start +OCSTART: + LD A,OSELC ; select column + CALL OCOMMAND + LD A,OCOL0 ; column 0 + CALL OCOMMAND + LD A,OROWA ; select first row + CALL OCOMMAND + RET + +OCLSROW: + ;XOR A ; display byte to write (0) + LD A,0FFh ; turn on all pixels + LD D,OCOLL ; count columns in D +OCLSROW1: + CALL ODATA ; write A to displey + DEC D ; next column + JR NZ,OCLSROW1 + RET + +; A = command to send to OLED +OCOMMAND: + LD C,OLED ; base I/O + LD B,OCMD ; send command + OUT (C),A ; send + REPT 4 + NOP + ENDM + LD B,OCMD OR OBUF + OUT (C),A ; send + REPT 10 + NOP + ENDM + RET + +; Write to OLED memory +; A = graphic byte to dispaly memory +ODATA: + LD C,OLED ; base I/O + LD B,ODB ; select Data Byte mode + OUT (C),A ; output byte + REPT 4 + NOP + ENDM + LD B,OBUF OR ODB + OUT (C),A ; output byte + NOP + NOP + RET + +; Read byte from console +READ: + LD C,CRED ; read from console + CALL BDOS ; call CP/M + RET ; A=CHAR + +; Reads 4 bit value (0-F) +READNIB: + CALL READ + LD B,41h ; compare with 'A' (10) + CP B + JP NC,RNB1 ; A>=B + SUB 30h ; sub '0' + RET +RNB1: + SUB 41h-10 ; sub 'A'-10 + AND 0Fh ; clear to 4 bits so 'a-f' also work + RET + +; Reads two bytes (0-F) from console +; A = byte read +READHEX: + CALL READNIB ; read MSD nibble + SLA A ; shift 4 bits + SLA A + SLA A + SLA A + LD B,A ; B=A + PUSH BC ; save B + CALL READNIB ; read LSD nibble + POP BC ; restore B + OR B ; OR B to A + RET + +PROMPT: + LD C,PSTR ; load function number + LD DE,PROMPTMSG ; load string addr + CALL BDOS ; call CP/M + RET + +PROMPTMSG: + DB 'OLED:>', CR, LF,'$' ; string terminated with $ + +; Initialization sequence +; 081h,080h - set contrast (080h full) +; 020h,000h - set horizontal window wrapping +; 021h,004h,083h - window full width +; 022h,000h,003h - window full height +OINITSEQ: + DB 0AEh,0D5h,0A0h,0A8h + DB 01Fh,0D3h,000h,0ADh + DB 08Eh,0D8h,005h,0A1h + DB 0C8h,0DAh,012h,091h + DB 03Fh,03Fh,03Fh,03Fh + DB 081h,070h,0D9h,0D2h + DB 0DBh,034h,0A6h,0A4h + DB 0AFh,020h,000h,021h + DB 004h,083h,000h,003h + DB 0FFh ; end marker + +END diff --git a/oled_compute.png b/oled_compute.png new file mode 100644 index 0000000000000000000000000000000000000000..9baf787e72789c23ccb7cf90ef5e141b46e04254 GIT binary patch literal 1123 zcmV-p1f2VcP)F$lbS|No!WnvmdR253u9S53^SEtJ;`wDI-v z_4Tju>-BoIX&a^ueJh`BMkIDyV@A&;T23ReqonXxrjX!5bZpE)1 zpX|IN4A5bi4@jo3vR*;1F$(*P62y{j%(RnO&wML-Dl+X1z-YN*$m$-}7OKfo`=0FK zJASXqdbwi^P(eV3+{{5X5Dpnc4$cq}(pmDo{P;Q2pV>UKj`3lB%;;l#k1&7_@UDR< zzk_(+lUebt^lYSmv#;B47Ke^Ajxa!nVQdCa$yZ20q-RW^kRACmipIXXrvJ>2UFP)u z9Aki!EP%zqkgJer!7~}ZFG6pmZ-qHlLDHH0VZ%`d=rUGK!-zNwYGnqny{dR{mhdwT zqkY9#UQd?v3i7*)?I;68LC%8VlNmY95RJNVutOPk79t*t*%w52Qli*&fy&M}$^eMS znWP(X81mFK%xD_l>MKRbdO0T97g5AGGYqi8VAYtaeUC6eCFq$W(EylTIn})$saz#1 zDk8KinjXFtXO z><&KDI~fu%=!yxV#~mLO^-iG9nPna|d-!&T^T?lJ05+H@N>mKs5gR2X+K0hLM6-28 z^B$oYb3|>BF;rGuEA8Ye16Wd6->o>aiv%^qslwbn58F%Tx1#$FJEHhLHUlVRReEBE zG(^0jb)?sdoN80}cf|mTalDOcMme4QZ~wmzY5kY53YuqoKifZJc2&MQD`Y;ZS)$Sg zQ1^_VnO$uCQ3habAY%ab4q$r58bcmiHzZ~=0G=1=XADx6SN0$?tR!_t<_ZH~f2EJzgdU;Tle0lETCcPu6_Y>70KOM{Bq;jJg3E3Vh%k?QHgKu?J(HU)hDvg)ZK?v6 znLWRT0a$TVu1Xfz3i9!cihdSsyrO2kAJn8M$lA!t?4W#6NcVF}H4a_-t}=j6 zD)xBw`wRmp{XTf?oxwBH?0Fd@nO`^?R0_H)=|B8`2L_0GWH!)6fm2s2OsX$7)+TU0 zLO8o3X8zqu{;=yU7+{78*kFgf)+Z5}gk8~aCSbj}G6O`9Rg7s)AbM8k)muQNSFSL? p?(~lXo4R#m=TzPUZlZt0_7im+OC@xm9_x8{^@^5x-eMJ7QoF8X0 zKy&;!j%Q*TAnpD4^*#iSdlGII^qv&k1-sAL@SEZ*I=THI05vv^d~?mRqFHFq{m57WV5R$43NUlG--$-1 z)hD07V+Y6tjJkD)&Td2LP~RX-0ebz4@6`0tDBYch3iLc#xM4cBKO<@s%DJh4C*X)gH|Zr?kNi0qT(Ly zR?5AoXb?spvjd<&XJF6nGE|y?f@uge~yYvo&QQB zAlXxPfYIjPnLs(8B_rC589-L6KD(S{H+i)nRi<3ge+mQGk)j5Rx|Icko$1*p3bsg@ zf*-|%#&@(x{u=jpB_&HBlZecWM}Ya|DPk)R0`gUrNBTSiotT1483VK+QDB+b>X*|9 zY;|sq{G91wo^sgu8C^8!^iOT|e{m!ceUJoB}0QNdPNb zWwVq4GD#i*!p>~8Uno1rr!#=M14OaEd>6oUx5^f5g|l)@Cs<$@dDgaP6?9Kws~HQ; z?D+W9c*_}pnb{1GUS`+a%*=lj6SFijG8r~QSo-tfcL8?hSt^fas#wp>IRR1`%C8@eop6~zw002ovPDHLk FV1ibE&pQAB literal 0 HcmV?d00001 diff --git a/oled_tog_hxd.png b/oled_tog_hxd.png new file mode 100644 index 0000000000000000000000000000000000000000..afa404b6e1c2742c2ffb9786a2c91faac15ef67c GIT binary patch literal 616 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 literal 0 HcmV?d00001