initial commit
This commit is contained in:
commit
db5a6e1246
9 changed files with 496 additions and 0 deletions
98
abstractlcd.nim
Normal file
98
abstractlcd.nim
Normal file
|
@ -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)
|
28
config.nims
Normal file
28
config.nims
Normal file
|
@ -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"
|
78
gpio.nim
Normal file
78
gpio.nim
Normal file
|
@ -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)
|
39
index.html
Normal file
39
index.html
Normal file
|
@ -0,0 +1,39 @@
|
|||
<!DOCTYPE html>
|
||||
<canvas id="lcd"></canvas>
|
||||
<script>
|
||||
const canvas = document.getElementById("lcd");
|
||||
const ctx = canvas.getContext("2d");
|
||||
const img = ctx.createImageData(128, 64);
|
||||
const scale = 3;
|
||||
|
||||
canvas.width = 128 * scale;
|
||||
canvas.height = 64 * scale;
|
||||
ctx.imageSmoothingEnabled = false;
|
||||
ctx.scale(scale, scale);
|
||||
|
||||
function draw(data) {
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
if (data[i] == "1") {
|
||||
img.data[i*4 + 0] = 255;
|
||||
img.data[i*4 + 1] = 255;
|
||||
img.data[i*4 + 2] = 255;
|
||||
img.data[i*4 + 3] = 255;
|
||||
} else {
|
||||
img.data[i*4 + 0] = 0;
|
||||
img.data[i*4 + 1] = 100;
|
||||
img.data[i*4 + 2] = 200;
|
||||
img.data[i*4 + 3] = 255;
|
||||
}
|
||||
}
|
||||
ctx.putImageData(img, 0, 0);
|
||||
ctx.drawImage(canvas, 0, 0);
|
||||
}
|
||||
|
||||
var ws = new WebSocket("ws://localhost:8080/ws");
|
||||
ws.onmessage = function(event) {
|
||||
draw(event.data);
|
||||
}
|
||||
ws.onerror = function(event) {
|
||||
console.log("WebSocket error!");
|
||||
}
|
||||
</script>
|
140
lcd.nim
Normal file
140
lcd.nim
Normal file
|
@ -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()
|
9
main.nim
Normal file
9
main.nim
Normal file
|
@ -0,0 +1,9 @@
|
|||
import abstractlcd
|
||||
import piclock
|
||||
|
||||
var frame: LcdFrame
|
||||
|
||||
var lcd = init()
|
||||
while true:
|
||||
frame = drawTime()
|
||||
lcd = lcd.send(frame)
|
1
nim.cfg
Normal file
1
nim.cfg
Normal file
|
@ -0,0 +1 @@
|
|||
threads:on
|
23
piclock.nim
Normal file
23
piclock.nim
Normal file
|
@ -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..<image.height:
|
||||
for x in 0..<image.width:
|
||||
let rgbx = image.unsafe[x, y]
|
||||
result[x][y] = rgbx.a > 0
|
||||
|
||||
proc drawTime*(): LcdFrame =
|
||||
let dt = now()
|
||||
result = dt.format("HH:mm:ss").drawText()
|
80
wsclock.nim
Normal file
80
wsclock.nim
Normal file
|
@ -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)
|
Loading…
Add table
Reference in a new issue