Create a PCB programmatically (instead of using a GUI) (like OpenSCAD for 3d modelling)
Install Haskell and Kicad :
$ sudo apt install haskell-platform kicad
Create a new haskell project :
$ mkdir led
$ cd led
$ cabal sandbox init
$ cabal init
Fill in all the informations needed for the project. Then add Hpcb as a dependency in led.cabal. Example below :
-- Initial led.cabal generated by cabal init. For further documentation,
-- see http://haskell.org/cabal/users-guide/
name: led
version: 0.1.0.0
-- synopsis:
-- description:
license: MIT
license-file: LICENSE
author: iemxblog
maintainer: iemxblog@gmail.com
-- copyright:
-- category:
build-type: Simple
extra-source-files: ChangeLog.md
cabal-version: >=1.10
executable led
main-is: Main.hs
-- other-modules:
-- other-extensions:
build-depends:
base >=4.9 && <4.10,
hpcb
-- hs-source-dirs:
default-language: Haskell2010
Install Hpcb like this (it will be installed in the sandbox) :
cabal install --only-dependencies
Edit Main.hs and copy/paste the following code :
module Main (
main
) where
import Hpcb
import Data.Monoid
outline :: Circuit
outline =
rectangle w h
# translate (V2 (w/2-2.54) (h/2-2.54*1.5))
# layer EdgeCuts
where (w, h) = (4*2.54, 3.5*2.54)
ledBoard :: Circuit
ledBoard = (
pinHeaderFromNets "JP1" ["VCC", "GND"]
<> led_805 "D1" "RED" # rotate 180 # translate (V2 (2.54*2) (-1.27))
<> r805 "R1" "330" # rotate 180 # translate (V2 (2.54*2) 2.54)
<> outline
)
# connect (net "VCC") (pinName "D1" "A")
# connect (pinName "D1" "K") (pin "R1" 1)
# connect (net "GND") (pin "R1" 2)
main :: IO ()
main = runCircuit ledBoard
Then produce the board file like this :
cabal build
./dist/build/led/led > led.kicad_pcb
Then open it with Kicad like this :
pcbnew led.kicad_pcb
And here is the result :
- Places components, translates, rotates them (and composes translations and rotations)
- Makes electrical connections (ratsnest, but not routing)
- Outputs a Kicad PCB file
- Routing
- Flipping components (putting them on the back side of a board)
4 functions are available to make connections : connect, net, pin, and pinName.
Here are some examples :
Create a resistor named "R1", of value "10k" and connect its pin number 1 to net "GND".
resistor = r805 "R1" "10k" # connect (net "GND") (pin "R1" 1)
Create an LED named "D1", of value "RED", and connect its cathode named "K" to net "GND"
led = led_805 "D1" "RED" # connect (net "GND") (pinName "D1" "K")
Create an ATMega328p named "U1", and connect all its VCC pins to net "VCC", and all its GND pins to net "GND".
mcu =
atmega328p_au "U1"
# connect (net "VCC") (pinName "VCC")
# connect (net "VCC") (pinName "AVCC")
# connect (net "GND") (pinName "U1" "GND")
Create 2 leds, an connect their 2 anodes together.
leds =
( led_805 "D1" "RED"
<> led_805 "D2" "GREEN"
)
# connect (pinName "D1" "A") (pinName "D2" "A")
leds =
led_805 "D1" "RED"
<> led_805 "D2" "GREEN" # connect (pinName "D1" "A") (pinName "D2" "A")
This example will return an error saying that it cannot find component D1. It is normal because operator # has a higher precedence than <>, so "pinName" looks only in the circuit returned by this function :
led_805 "D2" "GREEN"
And there is no component named "D1".
In this circuit, pin 1 of resistor R1 won't be connected to pin "SDA" of the atmega328p_au. The reason is that the SDA net associated with pin ADC4 of component U1 is overwritten on the following line.
mcu =
atmega328p_au "U1"
# connect (net "SDA") (pinName "U1" "ADC4")
# connect (net "ADC4") (pinName "U1" "ADC4") -- net SDA is overwritten by net ADC4
r =
r805 "R1" "1k"
# connect (net "VCC") (pin "R1" 1)
# connect (net "SDA") (pin "R1" 2) -- This pin won't be connected to pin ADC4 of component U1, because its net has been overwritten
circuit = mcu <> r
Should overwriting be forbidden in a future version ? Or should we find another solution to this problem, that will cause errors not seen by users ? If you want to give some feedback about this, send me an email at iemxblog@gmail.com.
Documentation Tests New footprints Flipping components Routing
In the Kicad file format, a footprint has a location and an orientation. So when we apply a transformation to a component, we have to keep track of its orientation.
The coordinate system used in Hpcb is inpired by homogeneous coordinates. Here are the coordinates of some footprint (a column vector) :
(x)
(y)
(o)
(1)
"x" and "y" are the coordinates of the footprint. "o" is its orientation. Here is how we make a translation :
(x') (1 0 0 tx) (x)
(y') = (0 1 0 ty) (y)
(o') (0 0 1 0 ) (o)
(1 ) (0 0 0 1 ) (1)
Here is how we make a rotation :
(x') (cos a sin a 0 0) (x)
(y') = (-sin a cos a 0 0) (y)
(o') (0 0 1 a) (o)
(1 ) (0 0 0 1) (1)
Let's take an example. This is Kicad file format's representation of a resitor (805 package) :
(module Resistors_SMD:R_0805 (layer F.Cu) (tedit 58AADA8F) (tstamp 59B14E88)
(at -6.35 2.54)
(descr "Resistor SMD 0805, reflow soldering, Vishay (see dcrcw.pdf)")
(tags "resistor 0805")
(attr smd)
(fp_text reference REF** (at 0 -1.65) (layer F.SilkS)
(effects (font (size 1 1) (thickness 0.15)))
)
(fp_text value R_0805 (at 0 1.75) (layer F.Fab)
(effects (font (size 1 1) (thickness 0.15)))
)
(fp_text user %R (at 0 -1.65) (layer F.Fab)
(effects (font (size 1 1) (thickness 0.15)))
)
(fp_line (start -1 0.62) (end -1 -0.62) (layer F.Fab) (width 0.1))
(fp_line (start 1 0.62) (end -1 0.62) (layer F.Fab) (width 0.1))
(fp_line (start 1 -0.62) (end 1 0.62) (layer F.Fab) (width 0.1))
(fp_line (start -1 -0.62) (end 1 -0.62) (layer F.Fab) (width 0.1))
(fp_line (start 0.6 0.88) (end -0.6 0.88) (layer F.SilkS) (width 0.12))
(fp_line (start -0.6 -0.88) (end 0.6 -0.88) (layer F.SilkS) (width 0.12))
(fp_line (start -1.55 -0.9) (end 1.55 -0.9) (layer F.CrtYd) (width 0.05))
(fp_line (start -1.55 -0.9) (end -1.55 0.9) (layer F.CrtYd) (width 0.05))
(fp_line (start 1.55 0.9) (end 1.55 -0.9) (layer F.CrtYd) (width 0.05))
(fp_line (start 1.55 0.9) (end -1.55 0.9) (layer F.CrtYd) (width 0.05))
(pad 1 smd rect (at -0.95 0) (size 0.7 1.3) (layers F.Cu F.Paste F.Mask))
(pad 2 smd rect (at 0.95 0) (size 0.7 1.3) (layers F.Cu F.Paste F.Mask))
(model Resistors_SMD.3dshapes/R_0805.wrl
(at (xyz 0 0 0))
(scale (xyz 1 1 1))
(rotate (xyz 0 0 0))
)
)
We just have to translate it using Hpcb functions, like fpText, fpRectangle, fpLine, pad, etc. And here is the result :
-- | SMD Resistor, 805 package (2012 metric)
r805 :: String -- ^ Reference
-> String -- ^ Value
-> Circuit
r805 ref val = footprint ref "R_805" $
fpText "reference" ref defaultEffects # translate (V2 0 (-1.65)) # layer FSilkS
<> fpText "value" val defaultEffects # translate (V2 0 1.65) # layer FFab
<> fpRectangle 2.0 1.25 # layer FFab # width 0.1
<> fpRectangle 3.2 2.0 # layer FCrtYd # width 0.05
<> (
fpLine (V2 0.6 0.875) (V2 (-0.6) 0.875)
<> fpLine (V2 (-0.6) (-0.875)) (V2 0.6 (-0.875))
) # layer FSilkS # width 0.15
<> (
pad 1 SMD Rect (V2 0.7 1.3) (newNet ref 1) # translate (V2 (-0.95) 0)
<> pad 2 SMD Rect (V2 0.7 1.3) (newNet ref 2) # translate (V2 0.95 0)
) # layers [FCu, FPaste, FMask]
Let's take an example : we will create a new component named lm358n from the existing footprint soic_8. We give the value "LM358N" to the component, and we just use the function "names" to assign names to pins, and that's all. A pin can have multiple names, as you can see for pin 4, which has 2 names.
lm358n :: String
-> Circuit
lm358n ref = soic_8 ref "LM358N" # names ref [
(1, ["OUTA"]),
(2, ["-INA"]),
(3, ["+INA"]),
(4, ["GND", "V-"]),
(5, ["+INB"]),
(6, ["-INB"]),
(7, ["OUTB"]),
(8, ["V+"])
]
And then we will be able to use "connect" and "pinName" functions to connect those pins.
SKiDL is a module that extends Python with the ability to design electronic circuits.
PCBmodE is a printed circuit board design Python script that creates an SVG from JSON input files, and then creates Gerber and Excellon files for manufacturing.