From db5a6e12465bc8a0682d1f61ff439350e817c60f Mon Sep 17 00:00:00 2001 From: Rupus Reinefjord Date: Sat, 25 Feb 2023 17:19:22 +0100 Subject: [PATCH] initial commit --- abstractlcd.nim | 98 +++++++++++++++++++++++++++++++++ config.nims | 28 ++++++++++ gpio.nim | 78 +++++++++++++++++++++++++++ index.html | 39 ++++++++++++++ lcd.nim | 140 ++++++++++++++++++++++++++++++++++++++++++++++++ main.nim | 9 ++++ nim.cfg | 1 + piclock.nim | 23 ++++++++ wsclock.nim | 80 +++++++++++++++++++++++++++ 9 files changed, 496 insertions(+) create mode 100644 abstractlcd.nim create mode 100644 config.nims create mode 100644 gpio.nim create mode 100644 index.html create mode 100644 lcd.nim create mode 100644 main.nim create mode 100644 nim.cfg create mode 100644 piclock.nim create mode 100644 wsclock.nim diff --git a/abstractlcd.nim b/abstractlcd.nim new file mode 100644 index 0000000..c1c8502 --- /dev/null +++ b/abstractlcd.nim @@ -0,0 +1,98 @@ +import std/[bitops, enumerate] +import lcd + +type + LcdFrame* = array[0..127, array[0..63, bool]] + ChipDiff = array[0..7, array[0..63, ref[uint8]]] + FrameDiff = tuple + cs1: ChipDiff + cs2: ChipDiff + ChipState = ref object + address: int + page: int + on: bool + LcdState = tuple + cs1: ChipState + cs2: ChipState + frame: LcdFrame + +func diff(frame, newFrame: LcdFrame): FrameDiff = + proc set(cd: var ChipDiff; colMin, colMax: int) = + for colIdx in colMin..colMax: + let oldCol = frame[colIdx] + let newCol = newFrame[colIdx] + + for pageIdx in 0..7: + let colStartIdx = pageIdx * 8 + let colEndIdx = colStartIdx + 7 + let oldColData = oldCol[colStartIdx..colEndIdx] + let newColData = newCol[colStartIdx..colEndIdx] + + if oldColData != newColData: + var byte: uint8 + for i, b in enumerate(newColData): + if b: + byte.setBit(i) + var dataRef: ref[uint8] + new dataRef + dataRef[] = byte + cd[pageIdx][colIdx mod 64] = dataRef + + result.cs1.set(0, 63) + result.cs2.set(64, 127) + +proc send(cs: ChipState, cd: ChipDiff) = + for pageIdx in 0..7: + for colIdx in 0..63: + if cd[pageIdx][colIdx] == nil: + continue + if cs.page != pageIdx: + lcdSetPage(pageIdx) + if cs.address != colIdx: + lcdSetAddress(colIdx) + lcdWriteData(cd[pageIdx][colIdx][]) + if cs.address == 63: + if cs.page == 7: + cs.page = 0 + else: + cs.page += 1 + cs.address = 0 + lcdSetPage(cs.page) + lcdSetAddress(cs.address) + else: + cs.address += 1 + +proc send*(state: LcdState, newFrame: LcdFrame): LcdState = + let frameDiff = state.frame.diff(newFrame) + + # TODO: check this inside the loop to not do this if no data will be sent? + if not state.cs1.on: + lcdSetChip1(true) + state.cs1.on = true + + if state.cs2.on: + lcdSetChip2(false) + state.cs2.on = false + + state.cs1.send(frameDiff.cs1) + + lcdSetChip1(false) + state.cs1.on = false + lcdSetChip2(true) + state.cs2.on = true + + state.cs2.send(frameDiff.cs2) + + result = (cs1: state.cs1, + cs2: state.cs2, + frame: newFrame) + +proc init*: LcdState = + lcdSetChip1(true) + lcdSetChip2(true) + lcdReset() + lcdOn() + lcdClear() + lcdSetDisplayStartLine(0) + result.cs1 = ChipState(address: 0, page: 0, on: true) + result.cs2 = ChipState(address: 0, page: 0, on: true) diff --git a/config.nims b/config.nims new file mode 100644 index 0000000..32fd853 --- /dev/null +++ b/config.nims @@ -0,0 +1,28 @@ +proc piOpts() = + --cpu:arm + --os:linux + --arm.linux.gcc.exe:"arm-linux-musleabihf-gcc" + --arm.linux.gcc.linkerexe:"arm-linux-musleabihf-gcc" + +task debug, "build project in debug mode": + --define:debug + #--debugger:native + piOpts() + setCommand "c", "main.nim" + +task release, "build project in release mode": + --define:release + piOpts() + setCommand "c", "main.nim" + +task upload, "upload to rpizero": + exec "scp main root@10.0.1.98:" + +task x86, "build project for x86, debug": + --define:debug + setCommand "cpp", "piclock.nim" + +task ws, "build webserver": + --gc:arc + --threads:on + setCommand "c", "wsclock.nim" diff --git a/gpio.nim b/gpio.nim new file mode 100644 index 0000000..42f171c --- /dev/null +++ b/gpio.nim @@ -0,0 +1,78 @@ +import std/[bitops, memfiles] + +type + Pin* = range[0 .. 53] + +const + gpioStart = 0x20200000 + offsetGpfsel0 = 0x00000000 + offsetGpfsel1 = 0x00000004 + offsetGpfsel2 = 0x00000008 + offsetGpfsel3 = 0x0000000C + offsetGpfsel4 = 0x00000010 + offsetGpfsel5 = 0x00000014 + offsetGpset0 = 0x0000001C + offsetGpset1 = 0x00000020 + offsetGpclr0 = 0x00000028 + offsetGpclr1 = 0x0000002C + offsetGplev0 = 0x00000034 + offsetGplev1 = 0x00000038 + offsetGpioEnd = 0x000000A0 + +func ptrFromOffset(p: pointer, offset: int): ptr[uint32] = + cast[ptr uint32](cast[ByteAddress](p) + offset) + +let + mm*: MemFile = open("/dev/mem", mode = fmReadWrite, + offset = gpioStart, mappedSize = offsetGpioEnd) + gpfsel0* = ptrFromOffset(mm.mem, offsetGpfsel0) + gpfsel1* = ptrFromOffset(mm.mem, offsetGpfsel1) + gpfsel2* = ptrFromOffset(mm.mem, offsetGpfsel2) + gpfsel3* = ptrFromOffset(mm.mem, offsetGpfsel3) + gpfsel4* = ptrFromOffset(mm.mem, offsetGpfsel4) + gpfsel5* = ptrFromOffset(mm.mem, offsetGpfsel5) + gpset0* = ptrFromOffset(mm.mem, offsetGpset0) + gpset1* = ptrFromOffset(mm.mem, offsetGpset1) + gpclr0* = ptrFromOffset(mm.mem, offsetGpclr0) + gpclr1* = ptrFromOffset(mm.mem, offsetGpclr1) + gplev0* = ptrFromOffset(mm.mem, offsetGplev0) + gplev1* = ptrFromOffset(mm.mem, offsetGplev1) + +proc gpset*(pin: Pin) = + let pinOffset = (1 shl (pin mod 32)).uint32 + if pin < 32: + gpset0[] = pinOffset + else: + gpset1[] = pinOffset + +proc gpclr*(pin: Pin) = + let pinOffset = (1 shl (pin mod 32)).uint32 + if pin < 32: + gpclr0[] = pinOffset + else: + gpclr1[] = pinOffset + +proc gpfsel(pin: Pin): ptr[uint32] = + case pin + of 0..9: gpfsel0 + of 10..19: gpfsel1 + of 20..29: gpfsel2 + of 30..39: gpfsel3 + of 40..49: gpfsel4 + of 50..53: gpfsel5 + +proc setInputPin*(gpfsel: ptr[uint32], pin: Pin) = + let pinOffset = (0b111 shl ((pin mod 10) * 3)).uint32 + gpfsel[].clearMask(pinOffset) + +proc setOutputPin*(gpfsel: ptr[uint32], pin: Pin) = + let pinOffset = (0b001 shl ((pin mod 10) * 3)).uint32 + gpfsel[].setMask(pinOffset) + +proc setInputPins*(pins: varargs[Pin]) = + for pin in pins: + setInputPin(pin.gpfsel, pin) + +proc setOutputPins*(pins: varargs[Pin]) = + for pin in pins: + setOutputPin(pin.gpfsel, pin) diff --git a/index.html b/index.html new file mode 100644 index 0000000..c1af74f --- /dev/null +++ b/index.html @@ -0,0 +1,39 @@ + + + diff --git a/lcd.nim b/lcd.nim new file mode 100644 index 0000000..4c048c5 --- /dev/null +++ b/lcd.nim @@ -0,0 +1,140 @@ +import std/[bitops, os] +import gpio + +type + BusyState* = enum + ready, busy + OnOffState* = enum + on, off + ResetState* = enum + normal, reset + LcdStatus* = tuple + busy: BusyState + onoff: OnOffState + reset: ResetState + + # pin mapping +const # gpio # lcd + pinRS*: Pin = 8 # 4 + pinRW*: Pin = 9 # 5 + pinDB0*: Pin = 14 # 7 + pinDB1*: Pin = 15 # 8 + pinDB2*: Pin = 2 # 9 + pinDB3*: Pin = 3 # 10 + pinDB4*: Pin = 4 # 11 + pinDB5*: Pin = 5 # 12 + pinDB6*: Pin = 6 # 13 + pinDB7*: Pin = 7 # 14 + pinE*: Pin = 10 # 6 + pinCS1*: Pin = 11 # 15 + pinCS2*: Pin = 12 # 16 + pinRST*: Pin = 13 # 17 + cmdPins* = [pinDB0, pinDB1, pinDB2, pinDB3, pinDB4, pinDB5, pinDB6, pinDB7, pinRW, pinRS] + dataPins* = [pinDB0, pinDB1, pinDB2, pinDB3, pinDB4, pinDB5, pinDB6, pinDB7] + +proc lcdSetChip1*(on: bool) = + setOutputPins(pinCS1) + if on: + gpset pinCS1 + else: + gpclr pinCS1 + +proc lcdSetChip2*(on: bool) = + setOutputPins(pinCS2) + if on: + gpset pinCS2 + else: + gpclr pinCS2 + +proc lcdWrite* = + setOutputPins(pinE) + gpclr pinE + + gpset pinE + sleep(1) + gpclr pinE + +proc lcdReset* = + setOutputPins(pinRST) + gpset pinRST + gpclr pinRST + sleep(1) + gpset pinRST + +proc lcdWriteInstruction*(cmd: range[0..1023]) = + setOutputPins(cmdPins) + for i in 0..<10: + if (cmd and (1 shl i)) != 0: + gpset cmdPins[i] + else: + gpclr cmdPins[i] + lcdWrite() + +proc lcdOn* = + lcdWriteInstruction(0x3f) + +proc lcdOff* = + lcdWriteInstruction(0x3e) + +proc lcdSetAddress*(address: range[0..63]) = + lcdWriteInstruction(0x40 + address) + +proc lcdSetPage*(page: range[0..7]) = + lcdWriteInstruction(0xb8 + page) + +proc lcdSetDisplayStartLine*(line: range[0..63]) = + lcdWriteInstruction(0xc0 + line) + +proc lcdReadStatus*: uint32 = + setInputPins(dataPins) + setOutputPins(pinE, pinRW, pinRS) + gpclr pinE + gpset pinRW + gpclr pinRS + + gpset pinE + sleep(1) + result = gplev0[] + gpclr pinE + +proc lcdWriteData*(data: uint8) = + lcdWriteInstruction(0x200 + data) + +proc lcdReadData*: uint32 = + setInputPins(dataPins) + setOutputPins(pinE, pinRW, pinRS) + gpclr pinE + gpset pinRW + gpset pinRS + + gpset pinE + sleep(1) + result = gplev0[] + gpclr pinE + +proc lcdStatus*: LcdStatus = + let data = lcdReadStatus() + result = (busy: if data.testBit(pinDB7.int): busy else: ready, + onoff: if data.testBit(pinDB5.int): off else: on, + reset: if data.testBit(pinDB4.int): reset else: normal) + +proc lcdData*: uint8 = + let data = lcdReadData() + for pin in dataPins: + if data.testBit(pin.int): + result.setBit(pin.int) + +proc lcdClearChip = + lcdSetPage(0) + lcdSetAddress(0) + for page in 0..7: + lcdSetPage(page) + for address in 0..63: + lcdWriteData(0) + lcdSetPage(0) + lcdSetAddress(0) + +proc lcdClear* = + lcdSetChip1(true) + lcdSetChip2(true) + lcdClearChip() diff --git a/main.nim b/main.nim new file mode 100644 index 0000000..41f9033 --- /dev/null +++ b/main.nim @@ -0,0 +1,9 @@ +import abstractlcd +import piclock + +var frame: LcdFrame + +var lcd = init() +while true: + frame = drawTime() + lcd = lcd.send(frame) diff --git a/nim.cfg b/nim.cfg new file mode 100644 index 0000000..b81c897 --- /dev/null +++ b/nim.cfg @@ -0,0 +1 @@ +threads:on diff --git a/piclock.nim b/piclock.nim new file mode 100644 index 0000000..286f28e --- /dev/null +++ b/piclock.nim @@ -0,0 +1,23 @@ +import std/[random, times] +import pixie +import abstractlcd + +let font = readFont("scientifica.ttf") +font.size = 11 + +proc drawSomethingRandom*: LcdFrame = + for x in 0..127: + for y in 0..63: + result[x][y] = rand(1) == 1 + +proc drawText*(text: string): LcdFrame = + let image = newImage(128, 64) + image.fillText(font, text, translate(vec2(40, 25))) + for y in 0.. 0 + +proc drawTime*(): LcdFrame = + let dt = now() + result = dt.format("HH:mm:ss").drawText() diff --git a/wsclock.nim b/wsclock.nim new file mode 100644 index 0000000..7b1e9fe --- /dev/null +++ b/wsclock.nim @@ -0,0 +1,80 @@ +import std/[locks, os, sets, strformat, strutils] +import mummy, mummy/routers +import piclock + +var + L: Lock + serverThread: Thread[Server] + clockThread: Thread[void] + clients: HashSet[WebSocket] + data: string + +initLock(L) + +proc toData(frame: LcdFrame): string = + var data: seq[string] + for y in 0..63: + for x in 0..127: + if frame[x][y]: + data.add("1") + else: + data.add("0") + result = data.join() + +proc indexHandler(request: Request) = + var headers: HttpHeaders + headers["Content-Type"] = "text/html" + let indexFile = open("index.html").readAll() + request.respond(200, headers, indexFile) + +proc upgradeHandler(request: Request) = + let websocket = request.upgradeToWebSocket() + {.gcsafe.}: + withLock L: + websocket.send(data) + +proc websocketHandler(websocket: WebSocket, + event: WebSocketEvent, + message: Message) = + case event: + of OpenEvent: + echo "Client connected: ", websocket + {.gcsafe.}: + withLock L: + clients.incl(websocket) + of MessageEvent: + echo message.kind, ": ", message.data + of ErrorEvent: + discard + of CloseEvent: + echo "Client disconnected: ", websocket + {.gcsafe.}: + withLock L: + clients.excl(websocket) + +proc serverProc(server: Server) = + let address = "0.0.0.0" + echo fmt"Serving on http://{address}:8080" + {.gcsafe.}: + server.serve(Port(8080), address = address) + +proc clockProc = + while true: + {.gcsafe.}: + withLock L: + data = drawTime().toData() + for c in clients: + c.send(data) + sleep(250) + +var router: Router +router.get("/", indexHandler) +router.get("/ws", upgradeHandler) + +let server = newServer(router, websocketHandler) + +createThread(serverThread, serverProc, server) +createThread(clockThread, clockProc) + +joinThread(serverThread) +joinThread(clockThread)