meowzor post draft
This commit is contained in:
parent
db846103a7
commit
a6a6f347ba
9 changed files with 831 additions and 5 deletions
12
.pre-commit-config.yaml
Normal file
12
.pre-commit-config.yaml
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# See https://pre-commit.com for more information
|
||||||
|
# See https://pre-commit.com/hooks.html for more hooks
|
||||||
|
repos:
|
||||||
|
- repo: local
|
||||||
|
hooks:
|
||||||
|
- id: no-spicy-exif
|
||||||
|
name: Ban spicy exif data
|
||||||
|
description: Ensures that there is no sensitive exif data committed
|
||||||
|
language: system
|
||||||
|
entry: exiftool -all= --icc_profile:all -overwrite_original
|
||||||
|
exclude_types: ["svg"]
|
||||||
|
types: ["image"]
|
||||||
490
content/posts/i-built-a-meowzor/hw-arch-diagram.svg
Normal file
490
content/posts/i-built-a-meowzor/hw-arch-diagram.svg
Normal file
|
|
@ -0,0 +1,490 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Vectornator (http://vectornator.io/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
height="699.95825"
|
||||||
|
stroke-miterlimit="10"
|
||||||
|
style="clip-rule:evenodd;fill-rule:nonzero;stroke-linecap:round;stroke-linejoin:round"
|
||||||
|
version="1.1"
|
||||||
|
viewBox="0 0 523.88452 699.95825"
|
||||||
|
width="523.88452"
|
||||||
|
xml:space="preserve"
|
||||||
|
id="svg125"
|
||||||
|
sodipodi:docname="hw-arch-diagram.svg"
|
||||||
|
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:vectornator="http://vectornator.io"><sodipodi:namedview
|
||||||
|
id="namedview127"
|
||||||
|
pagecolor="#505050"
|
||||||
|
bordercolor="#ffffff"
|
||||||
|
borderopacity="1"
|
||||||
|
inkscape:showpageshadow="0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pagecheckerboard="1"
|
||||||
|
inkscape:deskcolor="#505050"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="1.2967172"
|
||||||
|
inkscape:cx="248.31938"
|
||||||
|
inkscape:cy="353.97079"
|
||||||
|
inkscape:window-width="3840"
|
||||||
|
inkscape:window-height="2081"
|
||||||
|
inkscape:window-x="2560"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg125" />
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<g
|
||||||
|
id="Layer-1"
|
||||||
|
vectornator:layerName="Layer 1"
|
||||||
|
transform="translate(-58.05774,-41.984034)">
|
||||||
|
<path
|
||||||
|
d="M 60,100 H 580 V 620 H 60 Z"
|
||||||
|
fill="none"
|
||||||
|
opacity="1"
|
||||||
|
stroke="#ffffff"
|
||||||
|
stroke-linecap="butt"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="3.88452"
|
||||||
|
id="path4" />
|
||||||
|
<g
|
||||||
|
opacity="1"
|
||||||
|
id="g10">
|
||||||
|
<path
|
||||||
|
d="M 123.959,46.8464 V 140"
|
||||||
|
fill="none"
|
||||||
|
opacity="1"
|
||||||
|
stroke="#ffffff"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="3.88452"
|
||||||
|
id="path6" />
|
||||||
|
<path
|
||||||
|
d="M 107.917,71.7105 123.959,43.9261 140,71.7105"
|
||||||
|
fill="none"
|
||||||
|
opacity="1"
|
||||||
|
stroke="#ffffff"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="3.88452"
|
||||||
|
id="path8" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
opacity="1"
|
||||||
|
id="g16">
|
||||||
|
<path
|
||||||
|
d="M 423.959,653.154 V 560"
|
||||||
|
fill="none"
|
||||||
|
opacity="1"
|
||||||
|
stroke="#ffffff"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="3.88452"
|
||||||
|
id="path12" />
|
||||||
|
<path
|
||||||
|
d="M 440,628.29 423.959,656.074 407.917,628.29"
|
||||||
|
fill="none"
|
||||||
|
opacity="1"
|
||||||
|
stroke="#ffffff"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="3.88452"
|
||||||
|
id="path14" />
|
||||||
|
</g>
|
||||||
|
<text
|
||||||
|
fill="#ffffff"
|
||||||
|
font-family="Helvetica"
|
||||||
|
font-size="16px"
|
||||||
|
opacity="1"
|
||||||
|
stroke="none"
|
||||||
|
text-anchor="start"
|
||||||
|
transform="translate(146.273,71.4)"
|
||||||
|
vectornator:width="93.7266"
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
id="text20"> <tspan
|
||||||
|
x="0"
|
||||||
|
y="16"
|
||||||
|
id="tspan18">protobuf/http</tspan></text>
|
||||||
|
<path
|
||||||
|
d="M 80,120 H 480 V 220 H 80 Z"
|
||||||
|
fill="none"
|
||||||
|
opacity="1"
|
||||||
|
stroke="#ffffff"
|
||||||
|
stroke-dasharray="10, 10"
|
||||||
|
stroke-linecap="butt"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
id="path22" />
|
||||||
|
<text
|
||||||
|
fill="#ffffff"
|
||||||
|
font-family="Helvetica"
|
||||||
|
font-size="16px"
|
||||||
|
opacity="1"
|
||||||
|
stroke="none"
|
||||||
|
text-anchor="start"
|
||||||
|
transform="translate(340,180)"
|
||||||
|
vectornator:width="120"
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
id="text26"> <tspan
|
||||||
|
x="0"
|
||||||
|
y="16"
|
||||||
|
id="tspan24">HPS Linux</tspan></text>
|
||||||
|
<path
|
||||||
|
d="M 80,300 H 480 V 600 H 80 Z"
|
||||||
|
fill="none"
|
||||||
|
opacity="1"
|
||||||
|
stroke="#ffffff"
|
||||||
|
stroke-dasharray="10, 10"
|
||||||
|
stroke-linecap="butt"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
id="path28" />
|
||||||
|
<path
|
||||||
|
d="M 100,460 H 380 V 560 H 100 Z"
|
||||||
|
fill="none"
|
||||||
|
opacity="1"
|
||||||
|
stroke="#ffffff"
|
||||||
|
stroke-linecap="butt"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="3.88452"
|
||||||
|
id="path30" />
|
||||||
|
<path
|
||||||
|
d="m 400,460 h 60 v 100 h -60 z"
|
||||||
|
fill="none"
|
||||||
|
opacity="1"
|
||||||
|
stroke="#ffffff"
|
||||||
|
stroke-linecap="butt"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="3.88452"
|
||||||
|
id="path32" />
|
||||||
|
<text
|
||||||
|
fill="#ffffff"
|
||||||
|
font-family="Helvetica"
|
||||||
|
font-size="16px"
|
||||||
|
opacity="1"
|
||||||
|
stroke="none"
|
||||||
|
text-anchor="start"
|
||||||
|
transform="translate(215.828,500.3)"
|
||||||
|
vectornator:width="48.3438"
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
id="text36"> <tspan
|
||||||
|
x="0"
|
||||||
|
y="16"
|
||||||
|
id="tspan34">Nios II</tspan></text>
|
||||||
|
<path
|
||||||
|
d="M 80,253.57 H 480 V 260 H 80 Z"
|
||||||
|
fill="#ffffff"
|
||||||
|
fill-rule="nonzero"
|
||||||
|
opacity="1"
|
||||||
|
stroke="#ffffff"
|
||||||
|
stroke-linecap="butt"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="3.88452"
|
||||||
|
id="path38" />
|
||||||
|
<path
|
||||||
|
d="m 100,140 h 200 v 60 H 100 Z"
|
||||||
|
fill="none"
|
||||||
|
opacity="1"
|
||||||
|
stroke="#ffffff"
|
||||||
|
stroke-linecap="butt"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="3.88452"
|
||||||
|
id="path40" />
|
||||||
|
<text
|
||||||
|
fill="#ffffff"
|
||||||
|
font-family="Helvetica"
|
||||||
|
font-size="16px"
|
||||||
|
opacity="1"
|
||||||
|
stroke="none"
|
||||||
|
text-anchor="start"
|
||||||
|
transform="translate(109.807,155.7)"
|
||||||
|
vectornator:width="180.387"
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
id="text44"> <tspan
|
||||||
|
x="0"
|
||||||
|
y="16"
|
||||||
|
id="tspan42">meowzor-control</tspan></text>
|
||||||
|
<text
|
||||||
|
fill="#ffffff"
|
||||||
|
font-family="Helvetica"
|
||||||
|
font-size="16px"
|
||||||
|
opacity="1"
|
||||||
|
stroke="none"
|
||||||
|
text-anchor="start"
|
||||||
|
transform="translate(484.863,245.12)"
|
||||||
|
vectornator:width="95.1367"
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
id="text50"> <tspan
|
||||||
|
x="0"
|
||||||
|
y="16"
|
||||||
|
id="tspan46">hps2fpga </tspan> <tspan
|
||||||
|
x="0"
|
||||||
|
y="36"
|
||||||
|
id="tspan48">bridge</tspan></text>
|
||||||
|
<path
|
||||||
|
d="m 140,200 v 60"
|
||||||
|
fill="none"
|
||||||
|
opacity="1"
|
||||||
|
stroke="#ffffff"
|
||||||
|
stroke-linecap="butt"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="3.88452"
|
||||||
|
id="path52" />
|
||||||
|
<g
|
||||||
|
opacity="1"
|
||||||
|
id="g60">
|
||||||
|
<path
|
||||||
|
d="m 100,340 h 120 v 60 H 100 Z"
|
||||||
|
fill="none"
|
||||||
|
opacity="1"
|
||||||
|
stroke="#ffffff"
|
||||||
|
stroke-linecap="butt"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="3.88452"
|
||||||
|
id="path54" />
|
||||||
|
<text
|
||||||
|
fill="#ffffff"
|
||||||
|
font-family="Helvetica"
|
||||||
|
font-size="16px"
|
||||||
|
opacity="1"
|
||||||
|
stroke="none"
|
||||||
|
text-anchor="start"
|
||||||
|
transform="translate(130.934,360.3)"
|
||||||
|
vectornator:width="58.1328"
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
id="text58"> <tspan
|
||||||
|
x="0"
|
||||||
|
y="16"
|
||||||
|
id="tspan56">mailbox</tspan></text>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
opacity="1"
|
||||||
|
id="g70">
|
||||||
|
<path
|
||||||
|
d="m 260,340 h 120 v 60 H 260 Z"
|
||||||
|
fill="none"
|
||||||
|
opacity="1"
|
||||||
|
stroke="#ffffff"
|
||||||
|
stroke-linecap="butt"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="3.88452"
|
||||||
|
id="path62" />
|
||||||
|
<text
|
||||||
|
fill="#ffffff"
|
||||||
|
font-family="Helvetica"
|
||||||
|
font-size="16px"
|
||||||
|
opacity="1"
|
||||||
|
stroke="none"
|
||||||
|
text-anchor="start"
|
||||||
|
transform="translate(289.609,351.1)"
|
||||||
|
vectornator:width="60.7812"
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
id="text68"> <tspan
|
||||||
|
x="0"
|
||||||
|
y="16"
|
||||||
|
id="tspan64">shared</tspan> <tspan
|
||||||
|
x="0"
|
||||||
|
y="36"
|
||||||
|
id="tspan66">memory</tspan></text>
|
||||||
|
</g>
|
||||||
|
<path
|
||||||
|
d="m 180,260 v 80"
|
||||||
|
fill="none"
|
||||||
|
opacity="1"
|
||||||
|
stroke="#ffffff"
|
||||||
|
stroke-linecap="butt"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="3.88452"
|
||||||
|
id="path72" />
|
||||||
|
<path
|
||||||
|
d="m 320,260 v 80"
|
||||||
|
fill="none"
|
||||||
|
opacity="1"
|
||||||
|
stroke="#ffffff"
|
||||||
|
stroke-linecap="butt"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="3.88452"
|
||||||
|
id="path74" />
|
||||||
|
<text
|
||||||
|
fill="#ffffff"
|
||||||
|
font-family="Helvetica"
|
||||||
|
font-size="16px"
|
||||||
|
opacity="1"
|
||||||
|
stroke="none"
|
||||||
|
text-anchor="start"
|
||||||
|
transform="translate(414.719,500.3)"
|
||||||
|
vectornator:width="30.5625"
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
id="text78"> <tspan
|
||||||
|
x="0"
|
||||||
|
y="16"
|
||||||
|
id="tspan76">PIO</tspan></text>
|
||||||
|
<path
|
||||||
|
d="m 380,500 h 20"
|
||||||
|
fill="none"
|
||||||
|
opacity="1"
|
||||||
|
stroke="#ffffff"
|
||||||
|
stroke-linecap="butt"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="3.88452"
|
||||||
|
id="path80" />
|
||||||
|
<path
|
||||||
|
d="m 180,400 v 60"
|
||||||
|
fill="none"
|
||||||
|
opacity="1"
|
||||||
|
stroke="#ffffff"
|
||||||
|
stroke-linecap="butt"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="3.88452"
|
||||||
|
id="path82" />
|
||||||
|
<path
|
||||||
|
d="m 320,400 v 60"
|
||||||
|
fill="none"
|
||||||
|
opacity="1"
|
||||||
|
stroke="#ffffff"
|
||||||
|
stroke-linecap="butt"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="3.88452"
|
||||||
|
id="path84" />
|
||||||
|
<text
|
||||||
|
fill="#ffffff"
|
||||||
|
font-family="Helvetica"
|
||||||
|
font-size="16px"
|
||||||
|
opacity="1"
|
||||||
|
stroke="none"
|
||||||
|
text-anchor="start"
|
||||||
|
transform="translate(188.078,310)"
|
||||||
|
vectornator:width="51.9219"
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
id="text88"> <tspan
|
||||||
|
x="0"
|
||||||
|
y="16"
|
||||||
|
id="tspan86">sender</tspan></text>
|
||||||
|
<text
|
||||||
|
fill="#ffffff"
|
||||||
|
font-family="Helvetica"
|
||||||
|
font-size="16px"
|
||||||
|
opacity="1"
|
||||||
|
stroke="none"
|
||||||
|
text-anchor="start"
|
||||||
|
transform="translate(188.078,420)"
|
||||||
|
vectornator:width="59.9062"
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
id="text92"> <tspan
|
||||||
|
x="0"
|
||||||
|
y="16"
|
||||||
|
id="tspan90">receiver</tspan></text>
|
||||||
|
<text
|
||||||
|
fill="#ffffff"
|
||||||
|
font-family="Helvetica"
|
||||||
|
font-size="16px"
|
||||||
|
opacity="1"
|
||||||
|
stroke="none"
|
||||||
|
text-anchor="start"
|
||||||
|
transform="translate(413.438,320)"
|
||||||
|
vectornator:width="46.5625"
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
id="text98"> <tspan
|
||||||
|
x="0"
|
||||||
|
y="16"
|
||||||
|
id="tspan94">FPGA </tspan> <tspan
|
||||||
|
x="0"
|
||||||
|
y="36"
|
||||||
|
id="tspan96">fabric</tspan></text>
|
||||||
|
<text
|
||||||
|
fill="#ffffff"
|
||||||
|
font-family="Helvetica"
|
||||||
|
font-size="16px"
|
||||||
|
opacity="1"
|
||||||
|
stroke="none"
|
||||||
|
text-anchor="start"
|
||||||
|
transform="translate(490.742,600)"
|
||||||
|
vectornator:width="81.2578"
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
id="text102"> <tspan
|
||||||
|
x="0"
|
||||||
|
y="16"
|
||||||
|
id="tspan100">FPGA SoC</tspan></text>
|
||||||
|
<path
|
||||||
|
d="m 380,660 h 100 v 80 H 380 Z"
|
||||||
|
fill="none"
|
||||||
|
opacity="1"
|
||||||
|
stroke="#ffffff"
|
||||||
|
stroke-linecap="butt"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="3.88452"
|
||||||
|
id="path104" />
|
||||||
|
<path
|
||||||
|
d="m 500,660 h 80 v 80 h -80 z"
|
||||||
|
fill="none"
|
||||||
|
opacity="1"
|
||||||
|
stroke="#ffffff"
|
||||||
|
stroke-linecap="butt"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="3.88452"
|
||||||
|
id="path106" />
|
||||||
|
<text
|
||||||
|
fill="#ffffff"
|
||||||
|
font-family="Helvetica"
|
||||||
|
font-size="16px"
|
||||||
|
opacity="1"
|
||||||
|
stroke="none"
|
||||||
|
text-anchor="start"
|
||||||
|
transform="translate(511.816,681.1)"
|
||||||
|
vectornator:width="56.3672"
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
id="text112"> <tspan
|
||||||
|
x="0"
|
||||||
|
y="16"
|
||||||
|
id="tspan108">stepper</tspan> <tspan
|
||||||
|
x="0"
|
||||||
|
y="36"
|
||||||
|
id="tspan110">motors</tspan></text>
|
||||||
|
<text
|
||||||
|
fill="#ffffff"
|
||||||
|
font-family="Helvetica"
|
||||||
|
font-size="16px"
|
||||||
|
opacity="1"
|
||||||
|
stroke="none"
|
||||||
|
text-anchor="start"
|
||||||
|
transform="translate(393.375,671.9)"
|
||||||
|
vectornator:width="73.25"
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
id="text120"> <tspan
|
||||||
|
x="0"
|
||||||
|
y="16"
|
||||||
|
id="tspan114">TMC2209</tspan> <tspan
|
||||||
|
x="0"
|
||||||
|
y="36"
|
||||||
|
id="tspan116">stepper</tspan> <tspan
|
||||||
|
x="0"
|
||||||
|
y="56"
|
||||||
|
id="tspan118">drivers</tspan></text>
|
||||||
|
<path
|
||||||
|
d="m 480,700 h 20"
|
||||||
|
fill="none"
|
||||||
|
opacity="1"
|
||||||
|
stroke="#ffffff"
|
||||||
|
stroke-linecap="butt"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="3.88452"
|
||||||
|
id="path122" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 9.9 KiB |
315
content/posts/i-built-a-meowzor/index.md
Normal file
315
content/posts/i-built-a-meowzor/index.md
Normal file
|
|
@ -0,0 +1,315 @@
|
||||||
|
+++
|
||||||
|
date = "2023-04-20"
|
||||||
|
draft = true
|
||||||
|
path = "/blog/i-built-a-meowzor"
|
||||||
|
tags = []
|
||||||
|
title = "I built a Meowzor robot for class"
|
||||||
|
+++
|
||||||
|
|
||||||
|
As part of the requirements for the third year design studio class for Computer
|
||||||
|
Engineering at UBC, I worked on a group project using a FPGA, electronics, and
|
||||||
|
other pieces. We had to pick a project idea and then build it in a 4 month term
|
||||||
|
as a group of 3 or 4 (we were 3). The group I was in built a robot called
|
||||||
|
Meowzor that points a laser in front of a cat, using an object detection model
|
||||||
|
to find where the cat is and commanding the laser robot accordingly.
|
||||||
|
|
||||||
|
You can see our presentation video here:
|
||||||
|
|
||||||
|
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/JyZmC07ff0Q" title="YouTube video player" frameborder="0" allow="clipboard-write; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||||
|
|
||||||
|
This is the robot itself:
|
||||||
|
|
||||||
|
{% image(name="meowzor-board.jpg", colocated=true) %}
|
||||||
|
Piece of plywood with a DE1-SoC development board on it next to a breadboard
|
||||||
|
with stepper drivers and a 3d printed assembly with a rotation/pitch stage.
|
||||||
|
{% end %}
|
||||||
|
|
||||||
|
The piece I worked was all the robotics parts: the boundary is at the API layer
|
||||||
|
between the robot and the cat-detection service, accepting move-to commands
|
||||||
|
over HTTP. This meant that I wore a lot of hats, from Rust and embedded linux
|
||||||
|
to FPGA dev, electronics and mechanical engineering. It also meant that I had
|
||||||
|
to make decisions to avoid as much hard complexity as possible in order to ship
|
||||||
|
this much stuff on time.
|
||||||
|
|
||||||
|
This is the architecture diagram of the hardware components:
|
||||||
|
|
||||||
|
{% image(name="hw-arch-diagram.svg", colocated=true, process=false) %}
|
||||||
|
Architecture diagram of the hardware. First commands come in via protobuf HTTP
|
||||||
|
API, then they're processed by the meowzor-control daemon, which is attached to
|
||||||
|
a Nios II microcontroller with shared memory and a mailbox. The Nios has a PIO
|
||||||
|
connected to the stepper drivers.
|
||||||
|
{% end %}
|
||||||
|
|
||||||
|
We use stepper motors because they are precise, pretty fast, cheap, and easy to
|
||||||
|
integrate with.
|
||||||
|
|
||||||
|
The overall design is that `meowzor-control` receives position requests,
|
||||||
|
generates step schedules, then gives them to the Nios II firmware to execute.
|
||||||
|
|
||||||
|
## Hardware
|
||||||
|
|
||||||
|
In my program we use a DE1-SoC Intel Cyclone V FPGA development board for
|
||||||
|
pretty much everything, and everyone has one. This board has these relevant
|
||||||
|
features:
|
||||||
|
|
||||||
|
* Cortex-A9 dual core hard processor
|
||||||
|
* 1GB DDR3 memory
|
||||||
|
* More FPGA than you need
|
||||||
|
* Ethernet
|
||||||
|
* GPIOs attached to the FPGA fabric
|
||||||
|
|
||||||
|
My goal was to make the hardware and firmware as simple as possible because
|
||||||
|
they suck to work on, with the most basic FPGA system taking 6 minutes to
|
||||||
|
synthesize with Intel Quartus. The less time I am waiting for a compiler, the
|
||||||
|
more sane I will be. Firmware sucks slightly less to work on, but is also a
|
||||||
|
[wreck of a mess][quartus-bad] due to Intel tools, and the Nios II only can run
|
||||||
|
C/C++ due to the LLVM port being dead.
|
||||||
|
|
||||||
|
RISC-V would be viable if it weren't for the requirement to integrate nicely
|
||||||
|
with the Intel tools. It looks like there's [some movement on making this
|
||||||
|
work][riscv-demos], but I didn't want to take the risk on it. Intel itself has
|
||||||
|
[made a RISC-V Nios processor][nios-v], but I was under the (apparently false?)
|
||||||
|
impression that it was not available except in Quartus Pro licenses which we
|
||||||
|
don't have.
|
||||||
|
|
||||||
|
[riscv-demos]: https://github.com/ARIES-Embedded/riscv-on-max10
|
||||||
|
[nios-v]: https://www.intel.com/content/www/us/en/products/details/fpga/nios-processor/v.html
|
||||||
|
|
||||||
|
[quartus-bad]: https://jade.fyi/blog/quartus-elf2hex-and-misery/
|
||||||
|
|
||||||
|
Thus, what I did with the hardware is to try to shove everything nontrivial
|
||||||
|
up-stack as much as possible. I used a soft core, the Nios II, to generate step
|
||||||
|
signals, since it is easy to integrate with Quartus Platform Designer (Qsys) and
|
||||||
|
thereby spend less energy on hardware. Platform Designer deals with all the annoying pieces of
|
||||||
|
putting together a computer system such as address decoding and other kinds of
|
||||||
|
wiring things up.
|
||||||
|
|
||||||
|
{% image(name="qsys.png", colocated=true) %}
|
||||||
|
The main view of Platform Designer, listing components and showing connections
|
||||||
|
between them, as well as memory mappings. There are two general groups of
|
||||||
|
things: the hard processor and the soft core, which have distinct memory maps.
|
||||||
|
The hard processor is attached to the shared memory and a mailbox component,
|
||||||
|
and the soft core is attached to its own private memory, the shared memory, the
|
||||||
|
mailbox, a timer, and a programmable IO block.
|
||||||
|
{% end %}
|
||||||
|
|
||||||
|
In the end basically all of the hardware is just the reference design from the
|
||||||
|
DE1-SoC materials and various integration in Platform Designer, which is a win
|
||||||
|
because I mostly didn't have to debug it.
|
||||||
|
|
||||||
|
## Firmware
|
||||||
|
|
||||||
|
A mailbox in hardware is an inter-processor communication primitive: it takes a
|
||||||
|
memory address and a command from the sending processor and interrupts the receiving processor when
|
||||||
|
something new is received. The receiving processor can then take the address
|
||||||
|
out of the mailbox, copy the memory out, then empty it for the next item.
|
||||||
|
Optionally the sending processor may be interrupted to inform it of it being
|
||||||
|
empty.
|
||||||
|
|
||||||
|
In the case of the Intel IP, the mailbox has a capacity of one item, and the
|
||||||
|
interrupt to the receiving processor doesn't work, at least in my version of
|
||||||
|
Quartus, so I just polled it (I may have screwed it up but I had bigger fish to
|
||||||
|
fry than to spend more time debugging it; I have 100MHz to work with, so it
|
||||||
|
does not matter one bit).
|
||||||
|
|
||||||
|
Mailboxes require that the receiving processor be able to read some kind of
|
||||||
|
memory in common with the sending processor, since they only send one pointer.
|
||||||
|
In this case I implemented it as shared memory, since I didn't want to write a
|
||||||
|
Linux driver to deal with finding the physical pages I wanted to receive from
|
||||||
|
or something like that.
|
||||||
|
|
||||||
|
The firmware for the Nios II accepts structures like this in shared memory when
|
||||||
|
signaled by the mailbox:
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef struct {
|
||||||
|
CmdKind kind;
|
||||||
|
Direction directions[N_MOTORS];
|
||||||
|
uint16_t _pad;
|
||||||
|
uint32_t delays[N_DELAYS];
|
||||||
|
} Cmd;
|
||||||
|
```
|
||||||
|
|
||||||
|
The delays are sent interleaved, with the motor ID in the top bit of them. As
|
||||||
|
soon as the mailbox receives a `Cmd`, the main thread copies it into a
|
||||||
|
(ring-buffer-based) queue in private memory and empties the mailbox. A timer
|
||||||
|
ISR checks the queue and looks at the top item. If the item is done, it
|
||||||
|
dequeues it. If not, it gets the next step in it, sets the direction, emits a
|
||||||
|
step pulse, then sets the timer to the time of the next step.
|
||||||
|
|
||||||
|
## Software
|
||||||
|
|
||||||
|
### `meowzor-control`
|
||||||
|
|
||||||
|
### NixOS port
|
||||||
|
|
||||||
|
The port is [available here](https://github.com/lf-/de1-soc-nixos).
|
||||||
|
|
||||||
|
Well, this is sure burying a lede. I ported NixOS to the board and it was a
|
||||||
|
*really really good idea* and saved me a load of time. It took about a week of
|
||||||
|
work on and off. The motivation for the NixOS port was that the board vendor
|
||||||
|
had last released a port of Ubuntu 18.04 and I would literally rather port a
|
||||||
|
new OS than deal with old OS versions and lack of config management and
|
||||||
|
inability to fix the image.
|
||||||
|
|
||||||
|
Then, there was no question of which OS I wanted to port: I wanted something I
|
||||||
|
could rip everything out of easily and patch anything arbitrarily if needed.
|
||||||
|
Theoretically I could have used Yocto but I looked at it briefly and it looked
|
||||||
|
kinda like Nix But With Extra Ways To Have Incremental Build Mistakes, and more
|
||||||
|
importantly I didn't want to learn it.
|
||||||
|
|
||||||
|
I read through the [Cyclone V GSRD][gsrd] (Golden Software Reference Design),
|
||||||
|
which is an Intel port of Yocto to another board based on the Cyclone V, which
|
||||||
|
should be very close to what I needed.
|
||||||
|
|
||||||
|
[gsrd]: https://www.rocketboards.org/foswiki/Documentation/CycloneVSoCGSRD
|
||||||
|
|
||||||
|
#### Background: Cyclone V SoC boot process
|
||||||
|
|
||||||
|
The Cyclone V SoC can boot in several different ways depending on how the
|
||||||
|
`BSEL` pins are configured. This is broken out to an unpopulated DIP switch on
|
||||||
|
the bottom of the DE1-SoC board, and the configured state is to boot off of an
|
||||||
|
SD card.
|
||||||
|
|
||||||
|
When reset is deasserted, a boot ROM on the hard processor performs early
|
||||||
|
hardware initialization, bringing up the CPU, and chain loading from some
|
||||||
|
storage, in this case, a partition of a specific MBR type on the SD card.
|
||||||
|
|
||||||
|
See the [Cyclone V Hard Processor System Technical Reference Manual][hps-trm],
|
||||||
|
appendix A, for more details on the early boot process.
|
||||||
|
|
||||||
|
[hps-trm]: https://www.intel.com/content/www/us/en/docs/programmable/683126/21-2/hard-processor-system-technical-reference.html
|
||||||
|
|
||||||
|
This partition contains the U-Boot SPL (second phase loader), which brings up
|
||||||
|
the DDR3 main memory, serial port, and some other hardware, before chain
|
||||||
|
loading into U-Boot itself.
|
||||||
|
|
||||||
|
The FPGA fabric configuration port is accessed in various ways depending on
|
||||||
|
how the `MSEL` mode selection pins are set. On the DE1-SoC board, they are
|
||||||
|
exposed as a DIP switch set on the bottom of the board. Note that surprisingly,
|
||||||
|
`MSEL[3:0] = 4'b0000`, which you want, means *all the switches set to
|
||||||
|
ON*. This setting corresponds to FPPx16 with no encryption (fast parallel
|
||||||
|
programming), which is what works with U-Boot.
|
||||||
|
|
||||||
|
From U-Boot, the FPGA configuration image may be loaded into the
|
||||||
|
configuration port. This can also be done through Linux at runtime using the
|
||||||
|
[FPGA Region][fpga-region-dt] device tree entry.
|
||||||
|
|
||||||
|
[fpga-region-dt]: https://elixir.bootlin.com/linux/latest/source/Documentation/devicetree/bindings/fpga/fpga-region.txt
|
||||||
|
|
||||||
|
U-Boot will then chain load Linux, which NixOS supports well, so the boot
|
||||||
|
process is very standard from there.
|
||||||
|
|
||||||
|
To get U-Boot to do so automatically, you need a snippet like the one below in
|
||||||
|
your U-Boot Kconfig file. This will load a script if present, then enable the
|
||||||
|
FPGA bridge, and boot Linux through the standard `extlinux.conf` mechanism.
|
||||||
|
|
||||||
|
```
|
||||||
|
CONFIG_USE_BOOTCOMMAND=y
|
||||||
|
CONFIG_BOOTCOMMAND="if ext4load mmc 0:2 ${scriptaddr} /boot/u-boot.scr; then source ${scriptaddr}; fi; bridge enable; run distro_bootcmd"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### The port
|
||||||
|
|
||||||
|
The first order of business was to get a kernel that worked. I looked at the
|
||||||
|
GSRD, found the config I was supposed to use, then manually built a new kernel
|
||||||
|
with the checkout of the kernel sources.
|
||||||
|
|
||||||
|
I then put that kernel into the minimal image from the board vendor and
|
||||||
|
confirmed it booted. Success! Next, to build it with Nix.
|
||||||
|
|
||||||
|
Building a kernel with a custom config looks something like this in Nix ([full
|
||||||
|
version][kernel.nix]):
|
||||||
|
|
||||||
|
[kernel.nix]: https://github.com/lf-/de1-soc-nixos/blob/main/kernel.nix
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{ stdenv, buildLinux, linuxKernel, ... } @ args:
|
||||||
|
let base = buildLinux { ... };
|
||||||
|
in linuxKernel.manualConfig {
|
||||||
|
inherit stdenv;
|
||||||
|
inherit (base) src version;
|
||||||
|
configfile = ./socfpga_kconfig;
|
||||||
|
allowImportFromDerivation = true;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
It complained at build time about some missing options required by systemd, so
|
||||||
|
I added those manually to the Kconfig in my checkout and copied it back.
|
||||||
|
|
||||||
|
Next, U-Boot. This was not a fun time but not because of Nix. I built U-Boot
|
||||||
|
per the instructions on the GSRD guide, but using `socfpga_de1_soc_defconfig`
|
||||||
|
instead of the one for the different board. I replaced the U-Boot in the same
|
||||||
|
vendor image, and it would start, flash the transmit LED briefly, and not emit
|
||||||
|
anything over the serial port. Concerning.
|
||||||
|
|
||||||
|
After googling it a lot I wound up finding a forum thread about getting U-Boot
|
||||||
|
to work on the DE1-SoC, in which someone posted [a device tree patch][forum] to
|
||||||
|
set the clock frequency of the UART. I applied this patch to my U-Boot
|
||||||
|
development tree and, suddenly, console!! Rejoicing ensued before immediately
|
||||||
|
sending the patch upstream so this never happens to anyone else.
|
||||||
|
|
||||||
|
[forum]: https://forum.rocketboards.org/t/cyclonev-programming-fpga-from-u-boot/2230/14
|
||||||
|
|
||||||
|
At this point I knew both my U-Boot and Linux kernel worked, so it was time to
|
||||||
|
build a NixOS SD card image. This was one of the reasons I was excited to use
|
||||||
|
NixOS for this project: if an SD card fails, I can just make a new image in 30
|
||||||
|
seconds; the system image is totally disposable.
|
||||||
|
|
||||||
|
NixOS already has a [SD card image builder][sdimage-upstream] sort of
|
||||||
|
supporting U-Boot, but it does such support by leaving a gap to put U-Boot in
|
||||||
|
at the start of the disk after the fact. That wasn't quite satisfying enough
|
||||||
|
for me because setting partition types and dd'ing things is effort and also I
|
||||||
|
want to flash the image directly out of Nix.
|
||||||
|
|
||||||
|
I hacked this image builder up to [generate the correct partition
|
||||||
|
table directly][sdimage-hacked] and also copy the U-Boot SPL image into place.
|
||||||
|
|
||||||
|
[sdimage-upstream]: https://github.com/nixos/nixpkgs/blob/0c67f190b188ba25fc087bfae33eedcc5235a762/nixos/modules/installer/sd-card/sd-image.nix
|
||||||
|
|
||||||
|
[sdimage-hacked]: https://github.com/lf-/de1-soc-nixos/blob/aa4ee306ab5a63e2e838d4ca7d219165c9695c31/sd-image.nix#L184-L192
|
||||||
|
|
||||||
|
At this point I was pretty confident that my NixOS system was going to just
|
||||||
|
work when I booted it, since every part of the early boot was tested, so I just
|
||||||
|
had a go and it worked. For ten seconds. Until it reset itself.
|
||||||
|
|
||||||
|
I was suspicious of power issues or some horrible crime being done to the
|
||||||
|
hardware, so I removed the pieces surrounding the problematic time at boot such
|
||||||
|
as resizing the root partition. This changed nothing and eventually I noticed
|
||||||
|
it seemed to be based on *time* that the system was up. I got out a stop watch
|
||||||
|
and it was a round number. Immediately I put two and two together and realized
|
||||||
|
that Linux must not be correctly configured to pet the watchdog.
|
||||||
|
|
||||||
|
A quick comparison of the device trees used by the GSRD with the quite old ones
|
||||||
|
used by the upstream DE1-SoC port in U-Boot yielded some slightly different
|
||||||
|
watchdog configurations, so I just had a go and made them the same, added the
|
||||||
|
patch to my U-Boot Nix build, and rebuilt the image:
|
||||||
|
|
||||||
|
```patch
|
||||||
|
---
|
||||||
|
arch/arm/dts/socfpga_cyclone5_de1_soc.dts | 4 ----
|
||||||
|
1 file changed, 4 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/arch/arm/dts/socfpga_cyclone5_de1_soc.dts b/arch/arm/dts/socfpga_cyclone5_de1_soc.dts
|
||||||
|
index b71496bfb5..1cef1c2e8a 100644
|
||||||
|
--- a/arch/arm/dts/socfpga_cyclone5_de1_soc.dts
|
||||||
|
+++ b/arch/arm/dts/socfpga_cyclone5_de1_soc.dts
|
||||||
|
@@ -78,7 +78,3 @@
|
||||||
|
clock-frequency = <100000000>;
|
||||||
|
u-boot,dm-pre-reloc;
|
||||||
|
};
|
||||||
|
-
|
||||||
|
-&watchdog0 {
|
||||||
|
- status = "disabled";
|
||||||
|
-};
|
||||||
|
--
|
||||||
|
```
|
||||||
|
|
||||||
|
..... and it works:
|
||||||
|
|
||||||
|
|
||||||
|
{% image(name="it-boots.png", colocated=true) %}
|
||||||
|
Screenshot of a terminal showing the NixOS 23.05 prerelease NixOS booted to the
|
||||||
|
login prompt on an armv7l-linux. Some lines above show "socfpga-dwmac" related
|
||||||
|
ethernet messages.
|
||||||
|
{% end %}
|
||||||
BIN
content/posts/i-built-a-meowzor/it-boots.png
Normal file
BIN
content/posts/i-built-a-meowzor/it-boots.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 80 KiB |
BIN
content/posts/i-built-a-meowzor/meowzor-board.jpg
Normal file
BIN
content/posts/i-built-a-meowzor/meowzor-board.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.3 MiB |
BIN
content/posts/i-built-a-meowzor/qsys.png
Normal file
BIN
content/posts/i-built-a-meowzor/qsys.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 312 KiB |
|
|
@ -61,6 +61,7 @@
|
||||||
cabal-install
|
cabal-install
|
||||||
] ++ (with pkgs; [
|
] ++ (with pkgs; [
|
||||||
sqlite
|
sqlite
|
||||||
|
pre-commit
|
||||||
]);
|
]);
|
||||||
# Change the prompt to show that you are in a devShell
|
# Change the prompt to show that you are in a devShell
|
||||||
# shellHook = "export PS1='\\e[1;34mdev > \\e[0m'";
|
# shellHook = "export PS1='\\e[1;34mdev > \\e[0m'";
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,28 @@
|
||||||
{%- import "macros/colocated_asset.html" as colocated_asset -%}
|
{%- import "macros/colocated_asset.html" as colocated_asset -%}
|
||||||
|
|
||||||
{%- macro image(name, alt, colocated, height) -%}
|
{%- macro image(name, alt, colocated, height, process) -%}
|
||||||
{%- set name_sanitized = name | replace(from=".", to="-") | replace(from="/", to="-") -%}
|
{%- set name_sanitized = name | replace(from=".", to="-") | replace(from="/", to="-") -%}
|
||||||
{%- set image_id = "image" ~ name_sanitized -%}
|
{%- set image_id = "image" ~ name_sanitized -%}
|
||||||
|
|
||||||
{%- if colocated == true -%}
|
{%- if colocated == true -%}
|
||||||
{%- set image_path = colocated_asset::colocated_asset(path=name) -%}
|
{%- set image_path = colocated_asset::colocated_asset(path=name) -%}
|
||||||
{%- set image_url = name -%}
|
{%- set image_url = colocated_asset::colocated_asset(path=name, get_url=true) -%}
|
||||||
{%- else -%}
|
{%- else -%}
|
||||||
{%- set image_path = "/static/images/" ~ name -%}
|
{%- set image_path = "/static/images/" ~ name -%}
|
||||||
{%- set image_url = get_url(path=image_path) -%}
|
{%- set image_url = get_url(path=image_path) -%}
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
|
|
||||||
|
{%- if process -%}
|
||||||
{%- set image = resize_image(path=image_path, width=800, height=height, op="fit") -%}
|
{%- set image = resize_image(path=image_path, width=800, height=height, op="fit") -%}
|
||||||
|
{# because tera doesn't have object literals we need to deconstruct the object here so the other case is compatible #}
|
||||||
|
{%- set processed_image_url = image.url -%}
|
||||||
|
{%- else -%}
|
||||||
|
{%- set image = colocated_asset::colocated_asset(path=image_path, get_url=true) | trim -%}
|
||||||
|
{%- set processed_image_url = image_url -%}
|
||||||
|
{%- endif -%}
|
||||||
<div class="image">
|
<div class="image">
|
||||||
<a href="{{ image_url }}">
|
<a href="{{ image_url }}">
|
||||||
<img src="{{ image.url }}"
|
<img src="{{ processed_image_url }}"
|
||||||
alt="{{ alt }}"
|
alt="{{ alt }}"
|
||||||
title="{{ alt }}"
|
title="{{ alt }}"
|
||||||
{% if label %}
|
{% if label %}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
name=name,
|
name=name,
|
||||||
colocated=colocated | default(value=false),
|
colocated=colocated | default(value=false),
|
||||||
alt=alt | default(value=body),
|
alt=alt | default(value=body),
|
||||||
height=height | default(value=600)
|
height=height | default(value=600),
|
||||||
|
process=process | default(value=true)
|
||||||
) }}
|
) }}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue