
Я в одной из предыдущих статей писал — я, фактически, безработный. Юридически — нет, во-первых я пенсионер, но вполне мог бы и работать. Во-вторых, вроде как и работаю в одной маленькой фирме из двух человек, но последний год у нас с контрактами напряженка. С голоду не умираем, пенсии вполне достаточно на жизнь, но ведь развлекаться как-то надо?
Время от времени от скуки публикую статьи-обзоры на сайте шопоголиков, администрация сайта даже денег довольно-таки регулярно за это дает. Очень хорошая отмазка для супруги — нет, я не шопоголик, это я, вроде как, при деле. И вот здесь взялся публиковать статейки — если на том сайте такие публиковать, только минусов нахватаешь — типа ты что, слишком умный, что ли? — а вот на тебе минус и не балуй. И в следующий раз пиши про какую-нибудь мыльницу.
В одной из предыдущих своих статей я рассказывал о дисплее на базе адресуемых светодиодов. Сейчас расскажу, как проектировались последние варианты плат для них.
Самих светодиодов было довольно-таки много — 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 многие фирмы, которые работали с ней, тоже позакрывали свои подразделения. Много народа осталось без работы, и они тоже.
Ладно, один из них не молодой, а второй через какое-то время тяжело заболел. Но у меня есть и несколько знакомых инженеров, в самом расцвете сил — но перебиваются случайными заработками. Я с ними сталкивался как раз, когда у них была такая временная работа, большинство из них очень квалифицированные специалисты.
