commit db5a6e12465bc8a0682d1f61ff439350e817c60f Author: Rupus Reinefjord Date: Sat Feb 25 17:19:22 2023 +0100 initial commit 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)