Я в одной из предыдущих статей писал — я, фактически, безработный. Юридически — нет, во-первых я пенсионер, но вполне мог бы и работать. Во-вторых, вроде как и работаю в одной маленькой фирме из двух человек, но последний год у нас с контрактами напряженка. С голоду не умираем, пенсии вполне достаточно на жизнь, но ведь развлекаться как-то надо?
Время от времени от скуки публикую статьи-обзоры на сайте шопоголиков, администрация сайта даже денег довольно-таки регулярно за это дает. Очень хорошая отмазка для супруги — нет, я не шопоголик, это я, вроде как, при деле. И вот здесь взялся публиковать статейки — если на том сайте такие публиковать, только минусов нахватаешь — типа ты что, слишком умный, что ли? — а вот на тебе минус и не балуй. И в следующий раз пиши про какую-нибудь мыльницу.
В одной из предыдущих своих статей я рассказывал о дисплее на базе адресуемых светодиодов. Сейчас расскажу, как проектировались последние варианты плат для них.
Самих светодиодов было довольно-таки много — 1296 штук, плюс в плате должны быть вырезы. Дисплей, размещенный в окне, не должен полностью заслонять свет. А дисплей, предназначенный для замораживания, если его сделать сплошным, и порвать при заморозке может. Или компоненты оторвать. А может и еще что — специально обученный товарищ рассказывал, что случается при заморозке печатных плат во льду, но у меня в одно ухо влетело, во второе — вылетело.
Плата здесь приведена только для примера — реальная структура была чуть сложнее и были еще компоненты, которые надо было втолкать на эту же плату — но по сравнению с матрицей светодиодов это были совсем мелочи и окончательная разводка была сделана вручную.
Сразу предупреждаю, что я не большой специалист ни в Python, ни в KiCAD — для меня это лишь инструменты, которыми я пользуюсь время от времени. Наверное, я вообще во всем по жизни чайник — но зато медный и со свистком.
Первое дело — конечно, схема. Ее нужно нарисовать так, чтобы компоненты, расположенные рядом, имели и соответствующие номера.
Готовим форму платы — у нее множество отверстий, руками делать вспотеешь. Эта процедура вообще сделана по сермяге. Нарисовал пустую плату с одним только контуром и сделал генерацию более сложного контура по образу и подобию. Потом уже обнаружил, что в KiCAD есть библиотека pcbnew, предназначенная для разработки своих скриптов, но переделывать уже не хотелось, работает — не трогай.
Итак, создаем пустую плату и запускаем
вот этот скрипт:
panel_rows = 36
panel_lines = 36
deltaX = 10.0
deltaY = 10.0
holeX = 7.0
holeY = 7.0
hole_cut = 1.0
panel_gap = 2
origin=[0,0]
pcb_name = 'habr_dummy.kicad_pcb'
pcb_name2 = 'habr_edges.kicad_pcb'
def Create_edges():
try:
pcb_file = open(pcb_name, 'r')
#pcb_data = pcb_file.read()
lineList = pcb_file.readlines()
#pos = pcb_data.find(')')
pcb_file.close()
except IOError:
print("Cannot open file '%s'." % pcb_name)
return
panel_size=[0,0]
panel_size[0]= deltaX * panel_rows - panel_gap
panel_size[1]= deltaY * panel_lines - panel_gap
#print(panel_size)
corners=[]
corners.append([origin[0] - panel_size[0]/2, origin[1] - panel_size[1]/2])
corners.append([origin[0] + panel_size[0]/2, origin[1] - panel_size[1]/2])
corners.append([origin[0] + panel_size[0]/2, origin[1] + panel_size[1]/2])
corners.append([origin[0] - panel_size[0]/2, origin[1] + panel_size[1]/2])
#print(corners)
holes=[]
for x in range(panel_rows-1):
for y in range(panel_lines-1):
hole_orig =[0,0]
hole_orig [0] = origin[0] - (deltaX * (panel_rows-1))/2 + x*deltaX + deltaX/2
hole_orig [1] = origin[1] - (deltaY * (panel_lines-1))/2 + y*deltaY + deltaY/2
hole_corners=[]
hole_corners.append([hole_orig[0] - holeX/2 + hole_cut, hole_orig[1] - holeY/2]) # left down
hole_corners.append([hole_orig[0] + holeX/2 - hole_cut, hole_orig[1] - holeY/2 ])
hole_corners.append([hole_orig[0] + holeX/2, hole_orig[1] - holeY/2 + hole_cut])
hole_corners.append([hole_orig[0] + holeX/2 , hole_orig[1] + holeY/2 - hole_cut])
hole_corners.append([hole_orig[0] + holeX/2 - hole_cut, hole_orig[1] + holeY/2])
hole_corners.append([hole_orig[0] - holeX/2 + hole_cut, hole_orig[1] + holeY/2])
hole_corners.append([hole_orig[0] - holeX/2, hole_orig[1] + holeY/2 - hole_cut])
hole_corners.append([hole_orig[0] - holeX/2, hole_orig[1] - holeY/2 + hole_cut])
holes.append(hole_corners)
pos = len(lineList)-1
# build board edge
for i in range(len(corners)):
k = i+1
if k==len(corners):
k=0;
new_line = ' (gr_line (start '
new_line += str(corners[i][0]) + ' '
new_line += str(corners[i][1]) + ') (end '
new_line += str(corners[k][0]) + ' '
new_line += str(corners[k][1])
new_line += ') (layer Edge.Cuts) (width 0.05))\n'
#print(new_line)
lineList.insert(pos, new_line)
pos += 1
# build holes
for hole in holes:
for i in range(len(hole)):
k = i+1
if k==len(hole):
k=0;
new_line = ' (gr_line (start '
new_line += str(hole[i][0]) + ' '
new_line += str(hole[i][1]) + ') (end '
new_line += str(hole[k][0]) + ' '
new_line += str(hole[k][1])
new_line += ') (layer Edge.Cuts) (width 0.05))\n'
#print(new_line)
lineList.insert(pos, new_line)
pos += 1
lineList.insert(pos, '\n')
try:
pcb_file = open(pcb_name2, 'w')
for line in lineList:
#pcb_file.write(line.decode('utf-8'))
pcb_file.write(line)
pcb_file.close()
except IOError:
print("Cannot write file '%s'." % pcb_name2)
Create_edges()
Сразу сделана хорошая часть работы:
Монтажных отверстий всего 6, но уж поставим их тоже сразу. Эти же отверстия будут служить и для подвода напряжения.
Код
import pcbnew
panel_rows = 36
panel_lines = 36
deltaX = 10.0
deltaY = 10.0
pcb_name2 = 'habr_edges.kicad_pcb'
def MountHoles(pcb):
libpath = "/usr/share/kicad/modules/MountingHole.pretty"
for i in range (2):
x = panel_rows/4 * deltaX + deltaX/2
if i==0:
x = -x
for j in range (3):
y = panel_lines/3 * deltaY
if j==1:
y = 0
elif j==2:
y = -y
print(x,y)
footprint = pcbnew.FootprintLoad(libpath, "MountingHole_3.2mm_M3_DIN965_Pad")
pcb.Add(footprint)
footprint.SetPosition(pcbnew.wxPoint(pcbnew.Millimeter2iu(x),pcbnew.Millimeter2iu(y)))
footprint.Reference().SetVisible(False)
def Convert():
print("start")
pcb = pcbnew.LoadBoard(pcb_name2)
MountHoles(pcb)
pcb.Save(pcb_name2)
print("created!")
Convert()
Вырезы около монтажных отверстий получаются слишком большими — поправим их в конце, не все же автоматизировать, что-то и руками сделать надо.
Теперь на получившуюся плату загружаем список компонентов.
Мой компьютер после этого практически прекращает шевелиться — я его покупал несколько лет назад и основным критерием было отсутствие любых вентиляторов и шума соответственно. И должна быть возможность подключения двух, а лучше трех дисплеев. Цель была достигнута — и, кроме всего, системный блок потребляет всего 1 ампер от 12-Вольтового источника питания. Есть задачи, когда его быстродействия начинает не хватать, но их не так уж и много.
Размещаем светодиоды
Код
import pcbnew
panel_rows = 36
panel_lines = 36
deltaX = 10.0
deltaY = 10.0
holeX = 7.0
holeY = 7.0
hole_cut = 1.0
panel_gap = 2
origin=[0,0]
pcb_name = 'habr_populated.kicad_pcb'
pcb_name2 = 'habr_layout.kicad_pcb'
def LED_placement(pcb):
led_position=[]
for y in range (panel_lines):
line_pos=[]
for x in range (panel_rows):
diode_ref = y*panel_rows + x +1
# Find the component
c = pcb.FindModuleByReference("D"+str(diode_ref))
# Place it somewhere
pos = [0.0,0.0]
rot =0;
pos[1] = origin[1] + (deltaY * (panel_lines-1))/2 - y*deltaY
if y%2==0:
pos[0] = origin[0] - (deltaX * (panel_rows-1))/2 + x*deltaX
#rot = (-45+180)*10
rot = (270+180)*10
else:
pos[0] = origin[0] + (deltaX * (panel_rows-1))/2 - x*deltaX
#rot = -45 *10
rot = 270 *10
line_pos.append(pos)
c.SetPosition(pcbnew.wxPointMM(pos[0], pos[1]))
# Rotate it (angle in 1/10 degreee)
c.SetOrientation(rot)
c.Reference().SetVisible(False)
led_position.append(line_pos)
return led_position
def Convert():
print("start")
pcb = pcbnew.LoadBoard(pcb_name)
led_position = LED_placement(pcb)
pcb.Save(pcb_name2)
print("created!")
Convert()
теперь конденсаторы
Код
def Cap_placement(pcb):
cap_position=[]
for y in range (panel_lines-1):
line_pos=[]
for x in range (panel_rows):
cap_ref = y*panel_rows + x +1
# Find the component
c = pcb.FindModuleByReference("C"+str(cap_ref))
# Place it somewhere
pos = [0.0,0.0]
rot =0;
pos[1] = origin[1] + (deltaY * (panel_lines-1))/2 - y*deltaY - deltaY/2
if y%2==0:
pos[0] = origin[0] - (deltaX * (panel_rows-1))/2 + x*deltaX
rot = (270+180)*10
else:
pos[0] = origin[0] + (deltaX * (panel_rows-1))/2 - x*deltaX
rot = 270 *10
line_pos.append(pos)
c.SetPosition(pcbnew.wxPointMM(pos[0], pos[1]))
# Rotate it (angle in 1/10 degreee)
c.SetOrientation(rot)
c.Reference().SetVisible(False)
cap_position.append(line_pos)
return cap_position
На стороне компонентов у нас будет положительное напряжение питания, на обратной — земля.
Проведем короткие проводочки от светодиодов и конденсаторов и установим проходные отверстия.
Код
def AddTrack(pcb, track, netCode, width, layer):
for i in range (len(track)-1):
t = pcbnew.TRACK(pcb)
pcb.Add(t)
t.SetStart(pcbnew.wxPoint(track[i][0], track[i][1]))
t.SetEnd(pcbnew.wxPoint(track[i+1][0], track[i+1][1]))
t.SetWidth(pcbnew.Millimeter2iu(width))
t.SetNetCode(netCode)
t.SetLayer(layer)
def AddVia(pcb, pos, netCode, dia, drill):
v = pcbnew.VIA(pcb)
pcb.Add(v)
v.SetViaType(pcbnew.VIA_THROUGH)
v.SetWidth(pcbnew.Millimeter2iu(dia))
v.SetNetCode(netCode)
v.SetPosition(pcbnew.wxPoint(pos[0],pos[1]))
#v.SetLayerPair(0,31)
v.SetDrill(pcbnew.Millimeter2iu(drill))
def LedGroundViaTrace(pcb, led_position, netCode):
#ground vias
for y in range (panel_lines):
for x in range (panel_rows):
v = pcbnew.VIA(pcb)
pcb.Add(v)
v.SetViaType(pcbnew.VIA_THROUGH)
v.SetWidth(pcbnew.Millimeter2iu(0.8)) # 1mm
v.SetNetCode(netCode)
pos = led_position[y][x]
x0=pos[0]
if y%2==0:
y0 = pos[1]-1.5-0.65
else:
y0 = pos[1]+1.5+0.65
v.SetPosition(pcbnew.wxPointMM(x0,y0))
v.SetLayerPair(0,31)
v.SetDrill(pcbnew.Millimeter2iu(0.4))
#line to via
for y in range (panel_lines):
for x in range (panel_rows):
pos = led_position[y][x]
if y%2==0:
x0=pos[0]+1
y0 = pos[1]-0.65
y1=y0-0.5
x1=pos[0]
y2=y1-1
else:
x0=pos[0]-1
y0 = pos[1]+0.65
y1=y0+0.5
x1=pos[0]
y2=y1+1
t = pcbnew.TRACK(pcb)
pcb.Add(t)
t.SetStart(pcbnew.wxPointMM(x0,y0))
t.SetEnd(pcbnew.wxPointMM(x0, y1))
t.SetWidth(pcbnew.Millimeter2iu(0.25))
t.SetNetCode(netCode)
t = pcbnew.TRACK(pcb)
pcb.Add(t)
t.SetStart(pcbnew.wxPointMM(x0,y1))
t.SetEnd(pcbnew.wxPointMM(x1, y2))
t.SetWidth(pcbnew.Millimeter2iu(0.25))
t.SetNetCode(netCode)
def CapGroundViaTrace(pcb, cap_position, netCode):
#ground vias
for y in range (panel_lines-1):
for x in range (panel_rows):
v = pcbnew.VIA(pcb)
pcb.Add(v)
v.SetViaType(pcbnew.VIA_THROUGH)
v.SetWidth(pcbnew.Millimeter2iu(0.8)) # 1mm
v.SetNetCode(netCode)
pos = cap_position[y][x]
x0=pos[0];
if y%2==0:
y0 = pos[1]-1.5
else:
y0 = pos[1]+1.5
v.SetPosition(pcbnew.wxPointMM(x0,y0))
v.SetLayerPair(0,31)
v.SetDrill(pcbnew.Millimeter2iu(0.4))
#line to via
for y in range (panel_lines-1):
for x in range (panel_rows):
t = pcbnew.TRACK(pcb)
pcb.Add(t)
pos = cap_position[y][x]
if y%2==0:
y0 = pos[1]-0.485
y1 = pos[1]-1.5
else:
y0 = pos[1]+0.485
y1 = pos[1]+1.5
t.SetStart(pcbnew.wxPointMM(pos[0],y0))
t.SetEnd(pcbnew.wxPointMM(pos[0], y1))
t.SetWidth(pcbnew.Millimeter2iu(0.25))
t.SetNetCode(netCode)
По сути дела остались только линии данных. Не забываем, что в сумме ток у нас приличный и линии тока лучше не разрывать, поэтому линия данных идет не по прямой, а по другой стороне платы.
Код
def DataLines(pcb):
for y in range (panel_lines):
for x in range (panel_rows-1):
diode_ref = "D"+str(y*panel_rows+x+1)
diode = pcb.FindModuleByReference(diode_ref)
for pad in diode.Pads():
if pad.GetPadName()=='1':
#PIN1 DOUT
netCode = pad.GetNet().GetNet()
x0 = pad.GetPosition().x
y0 = pad.GetPosition().y
if y%2==0:
x1 = x0 + pcbnew.Millimeter2iu(0.8)
x2 = x1 + pcbnew.Millimeter2iu(0.65)
y1 = y0 - pcbnew.Millimeter2iu(0.65)
x3 = x2 + pcbnew.Millimeter2iu(5.1)
x4 = x3 + pcbnew.Millimeter2iu(0.65)
y2 = y1 - pcbnew.Millimeter2iu(0.65)
x5 = x4 + pcbnew.Millimeter2iu(0.8)
else:
x1 = x0 - pcbnew.Millimeter2iu(0.8)
x2 = x1 - pcbnew.Millimeter2iu(0.65)
y1 = y0 + pcbnew.Millimeter2iu(0.65)
x3 = x2 - pcbnew.Millimeter2iu(5.1)
x4 = x3 - pcbnew.Millimeter2iu(0.65)
y2 = y1 + pcbnew.Millimeter2iu(0.65)
x5 = x4 - pcbnew.Millimeter2iu(0.8)
# top
track=[]
track.append([x0, y0])
track.append([x1, y0])
track.append([x2, y1])
AddTrack(pcb, track, netCode, 0.25, 0)
AddVia(pcb, [x2, y1], netCode, 0.8, 0.4)
#bottom
track=[]
track.append([x2, y1])
track.append([x3, y1])
AddTrack(pcb, track, netCode, 0.25, 31)
AddVia(pcb, [x3, y1], netCode, 0.8, 0.4)
# top
track=[]
track.append([x3, y1])
track.append([x4, y2])
track.append([x5, y2])
AddTrack(pcb, track, netCode, 0.25, 0)
def CrossLines(pcb):
for y in range (panel_lines-1): #panel_rows
diode_ref = "D"+str((y+1)*panel_rows )
diode = pcb.FindModuleByReference(diode_ref)
for pad in diode.Pads():
if pad.GetPadName()=='1':
#PIN1 DOUT
netCode = pad.GetNet().GetNet()
x0 = pad.GetPosition().x
y0 = pad.GetPosition().y
if y%2==0:
x1 = x0 + pcbnew.Millimeter2iu(0.8)
x2 = x1 + pcbnew.Millimeter2iu(0.65)
y1 = y0 - pcbnew.Millimeter2iu(0.65)
y2 = y1 - pcbnew.Millimeter2iu(deltaY - 1.3)
y3 = y2 - pcbnew.Millimeter2iu(0.65)
else:
x1 = x0 - pcbnew.Millimeter2iu(0.8)
x2 = x1 - pcbnew.Millimeter2iu(0.65)
y1 = y0 - pcbnew.Millimeter2iu(0.65)
y2 = y1 - pcbnew.Millimeter2iu(deltaY - 1.3)
y3 = y2 - pcbnew.Millimeter2iu(0.65)
# top
track=[]
track.append([x0, y0])
track.append([x1, y0])
track.append([x2, y1])
track.append([x2, y2])
track.append([x1, y3])
track.append([x0, y3])
AddTrack(pcb, track, netCode, 0.25, 0)
Полигоны, конечно, и ручкам сделать несложно, да уж ладно, раз взялись писать подпрограммы, то доделаем и это
Код
def DrawPolygons(pcb):
# grownd plane DrawPolygons(pcb)
plane_size=[0,0]
plane_size[0]= deltaX * panel_rows/2 - panel_gap/2 - 0.5
plane_size[1]= deltaY * panel_lines/2 - panel_gap/2 - 0.5
Contour=[]
Contour.append( [pcbnew.Millimeter2iu(plane_size[0]), pcbnew.Millimeter2iu(plane_size[1]) ])
Contour.append( [pcbnew.Millimeter2iu(plane_size[0]), -pcbnew.Millimeter2iu(plane_size[1]) ])
Contour.append( [-pcbnew.Millimeter2iu(plane_size[0]), -pcbnew.Millimeter2iu(plane_size[1]) ])
Contour.append( [-pcbnew.Millimeter2iu(plane_size[0]), pcbnew.Millimeter2iu(plane_size[1]) ])
nets = pcb.GetNetsByName()
net = nets.find("GND").value()[1]
netCode = net.GetNet()
newarea = pcb.InsertArea(netCode, pcbnew.B_Cu, pcbnew.B_Cu, Contour[0][0], Contour[0][1], pcbnew.ZONE_CONTAINER.DIAGONAL_EDGE)
newoutline = newarea.Outline()
for i in range (1,4):
newoutline.Append(Contour[i][0],Contour[i][1]);
poly_set = pcbnew.SHAPE_POLY_SET()
poly_set.NewOutline()
for i in range (0,4):
poly_set.Append(Contour[i][0],Contour[i][1])
newarea.SetFilledPolysList(poly_set)
nets = pcb.GetNetsByName()
net = nets.find("+5V").value()[1]
netCode = net.GetNet()
newarea = pcb.InsertArea(netCode, pcbnew.F_Cu, pcbnew.F_Cu, Contour[0][0], Contour[0][1], pcbnew.ZONE_CONTAINER.DIAGONAL_EDGE)
newoutline = newarea.Outline()
for i in range (1,4):
newoutline.Append(Contour[i][0],Contour[i][1]);
poly_set = pcbnew.SHAPE_POLY_SET()
poly_set.NewOutline()
for i in range (0,4):
poly_set.Append(Contour[i][0],Contour[i][1])
newarea.SetFilledPolysList(poly_set)
И суммарная процедура теперь выглядит так
Код
def Convert():
print("start")
pcb = pcbnew.LoadBoard(pcb_name)
led_position = LED_placement(pcb)
cap_position = Cap_placement(pcb)
nets = pcb.GetNetsByName()
net = nets.find("GND").value()[1]
netCode = net.GetNet()
CapGroundViaTrace(pcb, cap_position, netCode)
LedGroundViaTrace(pcb, led_position, netCode)
DataLines(pcb)
CrossLines(pcb)
DrawPolygons(pcb)
pcb.Save(pcb_name2)
print("created!")
Convert()
Когда все функции написаны, уже легко менять размеры, формы, количество пикселей, добавлять перламутровые пуговицы и потакать любым прихотям заказчика, если плата делается на заказ. Хотя, наверно, заказчику лучше рассказать, как употел разработчик этой платы рисуя бесконечные соединения, а то ведь не оценит.
В заключение на жизнь пожаловаться, что ли. Хотя Остап Бендер советовал с этим обращаться во всемирную лигу сексуальных реформ (смех смехом, а она действительно существовала в то время).
Отвечайте нам, а то,
Если вы не отзовётесь,
мы напишем в «Спортлото» ©
В Финляндии вовсю кричат о нехватке рабочей силы, особенно квалифицированной в области IT. Но о чем крик — я не понимаю.
Мой случай несколько не входит в общие рамки — пенсионер по инвалидности мало кому нужен. Да, в офис приходить каждый день мне сложно. Но ведь сейчас все топят за дистанционную работу.
А тут-то мне равных немного — мало у кого дома есть столько оборудования, как у меня. И опыт работы в фирмах, круче которых, наверно, только яйца вкрутую, и то не факт.
И знакомых полно — руководителей среднего звена. Но они работают в крупных фирмах и кадровиков не могут убедить никак, всем нужны молодые работники на постоянную работу.
А пенсионера со странными требованиями (например, зарплата не должна быть больше определенного уровня), всерьез никто не воспринимает. Типа, иди, кури. А я ищу не денег, а просто применения сил — потому, наверно, и не хотят связываться.
Остаются мелкие фирмы, хозяева которых рисковать с персоналом не могут и берут хороших знакомых, если они идут, конечно. У мелких фирм, как правило, и зарплата мелкая.
Но в такой мелкой фирме я и так работаю — мой друг, владелец и тоже пенсионер, и я. Но последний год у нас контрактов нет.
В нашем переулке, в двух соседних от меня домах, живут инженеры по электронике, были далеко не последними специалистами. С коллапсом Nokia многие фирмы, которые работали с ней, тоже позакрывали свои подразделения. Много народа остолось без работы, и они тоже.
Ладно, один из них не молодой, а второй через какое-то время тяжело заболел. Но у меня есть и несколько знакомых инженеров, в самом расцвете сил — но перебиваются случайными заработками. Я с ними сталкивался как раз, когда у них была такая временная работа, большинство из них очень квалифицированные специалисты.