Ako môžete pomocou Pythonu zostaviť svoj vlastný CNC radič a 3D tlačiareň

Tento článok pojednáva o procese, ktorý som použil na zostavenie vôbec prvej implementácie radiča CNC stroja na čistom Pythone.

Kontroléry strojov s numerickým riadením (CNC) sa zvyčajne implementujú pomocou programovacieho jazyka C alebo C ++. Pracujú na operačných systémoch bez OS alebo v reálnom čase s jednoduchými mikrokontrolérmi.

V tomto článku popíšem, ako zostaviť CNC radič - predovšetkým 3D tlačiareň - pomocou moderných dosiek ARM (Raspberry Pi) s moderným jazykom vysokej úrovne (Python).

Takýto moderný prístup otvára širokú škálu možností integrácie s ďalšími špičkovými technológiami, riešeniami a infraštruktúrami. Celý projekt je tak priateľský k vývojárom.

O projekte

Moderné dosky ARM zvyčajne používajú Linux ako referenčný operačný systém. Toto nám umožňuje prístup k celej linuxovej infraštruktúre so všetkými softvérovými balíčkami pre Linux. Môžeme hostiť webový server na doske, používať pripojenie Bluetooth, používať OpenCV na rozpoznávanie obrázkov a okrem iného vytvárať zhluk dosiek.

Toto sú dobre známe úlohy, ktoré je možné implementovať na doskách ARM, a môžu byť skutočne užitočné pre zákazkové CNC stroje. Napríklad automatické polohovanie pomocou výpočtovej techniky môže byť pre niektoré stroje veľmi užitočné.

Linux nie je operačný systém v reálnom čase. To znamená, že nemôžeme generovať impulzy s požadovaným časovaním na riadenie krokových motorov priamo z pinov dosky pomocou spusteného softvéru, dokonca ani ako modul jadra. Ako teda môžeme používať steppery a funkcie Linuxu na vysokej úrovni? Môžeme použiť dva čipy - jeden mikrokontrolér s klasickou CNC implementáciou a dosku ARM pripojenú k tomuto mikrokontroléru cez UART (univerzálny asynchrónny prijímač-vysielač).

Čo ak pre tento mikrokontrolér neexistujú žiadne vhodné funkcie firmvéru? Čo ak potrebujeme ovládať ďalšie osi, ktoré nie sú implementované v mikrokontroléri? Akékoľvek úpravy existujúceho firmvéru C / C ++ si budú vyžadovať dostatok času a úsilia na vývoj. Uvidíme, či to môžeme uľahčiť a dokonca ušetriť peniaze na mikrokontroléroch ich jednoduchým odstránením.

PyCNC

PyCNC je bezplatný vysoko výkonný tlmočník G-kódu s otvoreným zdrojovým kódom a radič CNC / 3D tlačiarne. Môže bežať na rôznych doskách založených na Linuxe a ARM, napríklad Raspberry Pi, Odroid, Beaglebone a ďalších. To vám dáva flexibilitu vybrať si ktorúkoľvek dosku a využiť všetko, čo Linux ponúka. A môžete ponechať celý runtime G-kódu na jednej doske bez potreby samostatného mikrokontroléra pre prevádzku v reálnom čase.

Výber Pythonu ako hlavného programovacieho jazyka významne znižuje kódovú základňu v porovnaní s projektmi C / C ++. Znižuje tiež kód špecifický pre štítky a mikrokontroléry a sprístupňuje projekt širšiemu publiku.

Ako to funguje

Projekt využíva DMA (Direct Memory Access) na hardvérovom module čipu. Jednoducho skopíruje vyrovnávaciu pamäť stavov GPIO (vstup na všeobecný účel) pridelený v RAM do skutočných registrov GPIO. Tento proces kopírovania je synchronizovaný systémovými hodinami a funguje úplne nezávisle od jadier CPU. V pamäti sa teda vygeneruje sekvencia impulzov pre os krokového motora a potom ich DMA presne vyšle.

Poďme hlbšie do kódu, aby sme pochopili základné informácie a prístup k hardvérovým modulom z Pythonu.

GPIO

Všeobecné vstupné a výstupné moduly riadia stavy pinov. Každý kolík môže mať nízky alebo vysoký stav. Keď programujeme mikrokontrolér, zvyčajne na zápis na tento pin používame premenné definované v SDK (softvérová vývojová sada). Napríklad na povolenie vysokého stavu pre piny 1 a 3:

PORTA = (1 << PIN1) | (1 << PIN3)

Ak sa pozriete na SDK, nájdete deklaráciu tejto premennej a bude vyzerať podobne ako:

#define PORTA (*(volatile uint8_t *)(0x12345678))

Je to iba ukazovateľ. Nesmeruje to na umiestnenie v RAM, ale na adresu fyzického procesora. Aktuálny modul GPIO sa nachádza na tejto adrese.

Na správu pinov môžeme písať a čítať údaje. Procesor ARM Raspberry Pi nie je výnimkou, má rovnaký modul. Na kontrolu pinov môžeme zapisovať / čítať dáta. Adresy a dátové štruktúry nájdeme v oficiálnej dokumentácii k procesorovým perifériám.

Keď spustíme proces za behu používateľa, proces sa spustí vo virtuálnom adresnom priestore. Skutočná periféria je prístupná priamo. Stále však môžeme pristupovať k skutočným fyzickým adresám pomocou ‘/dev/mem’zariadenia.

Tu je jednoduchý kód v Pythone, ktorý pomocou tohto prístupu riadi stav pinu:

Poďme to rozdeliť riadok po riadku:

Riadky 1–6 : hlavičky, import.

Riadok 7 : otvorený ‘/dev/mem’ prístup zariadenia k fyzickej adrese.

Riadok 8 : Systémovým volaním mmap používame na mapovanie súboru (aj keď v našom prípade tento súbor predstavuje fyzickú pamäť) do virtuálnej pamäte procesu. Zadáme dĺžku a posunutie oblasti mapy. Pre dĺžku zoberieme veľkosť stránky. A offset je 0x3F200000.

Dokumentácia hovorí, že adresa zbernice0x7E200000 obsahuje registre GPIO a musíme uviesť fyzickú adresu. Dokumentácia hovorí (strana 6, odsek 1.2.3), že 0x7E000000adresa zbernice je namapovaná na 0x20000000fyzickú adresu, ale táto dokumentácia je určená pre Raspberry 1.

Upozorňujeme, že všetky adresy zbernice modulov sú rovnaké pre Raspberry Pi 1–3, ale táto mapa bola zmenená na 0x3F000000pre RPi 2 a 3. Takže táto adresa je 0x3F200000. Pre Raspberry Pi 1 to zmeňte na 0x20200000.

Potom môžeme zapisovať do virtuálnej pamäte nášho procesu, ale v skutočnosti sa zapíše do modulu GPIO.

Riadok 9 : Zatvorte popisovač súborov, pretože ho nemusíme ukladať.

Riadky 11–14 : čítame a zapisujeme na našu mapu s 0x08posunom. Podľa dokumentácie ide o register GPFSEL2 GPIO Function Select 2. A tento register ovláda funkcie pinov.

Nastavili sme (vymazať všetko, potom sme nastavili operátorom OR) 3 bity s 3. bitom nastaveným na 001. Táto hodnota znamená, že pin funguje ako výstup. Existuje pre ne veľa pinov a možných režimov. Preto je register pre režimy rozdelený do niekoľkých registrov, kde každý obsahuje režimy pre 10 pinov.

Riadky 16 a 22 : nastavenie obslužného programu prerušenia 'Ctrl + C'.

17. riadok : nekonečná slučka.

Riadok 18 : nastavte pin na vysoký stav zápisom do registra GPSET0.

Upozorňujeme, že Raspberry Pi nemá registre, aké majú PORTA (mikrokontroléry AVR). Nemôžeme napísať celý stav GPIO všetkých pinov. Existujú iba registre nastavenia a vymazania, ktoré sa používajú na nastavovanie a vymazanie špecifikované pomocou pinov bitovej masky.

Riadky 19 a 21 : meškanie

Riadok 20 : nastavte pin na nízky stav pomocou registra GPCLR0.

Riadky 25 a 26 : prepnite prepínač na predvolené nastavenie, vstupný stav. Zatvorte pamäťovú mapu.

Tento kód by mal byť spustený s oprávneniami superužívateľa. Pomenujte súbor ‘gpio.py’ a spustite ho pomocou ‘sudo python gpio.py’. Pokiaľ máte k pinu 21 pripojenú LED, bude blikať.

DMA

Direct Memory Access je špeciálny modul, ktorý je určený na kopírovanie blokov pamäte z jednej oblasti do druhej. Skopírujeme údaje z vyrovnávacej pamäte do modulu GPIO. Najskôr potrebujeme pevnú oblasť vo fyzickej pamäti RAM, ktorá sa bude kopírovať.

Existuje niekoľko možných riešení:

  1. Môžeme vytvoriť jednoduchý ovládač jadra, ktorý nám pridelí, uzamkne a ohlási adresu tejto pamäte.
  2. V niektorých implementáciách je pridelená virtuálna pamäť, ktorá sa používa ‘/proc/self/pagemap’na prevod adresy na fyzickú. Neodporúčal by som tento prístup, zvlášť keď potrebujeme prideliť veľkú plochu. Akákoľvek virtuálne pridelená pamäť (dokonca uzamknutá, pozri dokumentáciu k jadru) možno presunúť do fyzickej oblasti.
  3. Všetky Raspberry Pi majú ‘/dev/vcio’zariadenie, ktoré je súčasťou grafického ovládača a dokáže nám prideliť fyzickú pamäť. Oficiálny príklad ukazuje, ako na to. A môžeme ho použiť namiesto vytvárania vlastných.

Samotný modul DMA je iba sada registrov, ktoré sú umiestnené niekde na fyzickej adrese. Tento modul môžeme ovládať cez tieto registre. V zásade existujú zdrojové, cieľové a riadiace registre. Pozrime sa na jednoduchý kód, ktorý ukazuje, ako používať moduly DMA na správu GPIO.

Since additional code is required to allocate physical memory with ‘/dev/vcio’, we will use a file with an existing CMA PhysicalMemory class implementation. We will also use the PhysicalMemory class, which performs the trick with memap from the previous sample.

Let’s break it down line by line:

Lines 1–3: headers, imports.

Lines 5–6: constants with the channel DMA number and GPIO pin that we will use.

Lines 8–15: initialize the specified GPIO pin as an output, and light it up for a half second for visual control. In fact, it’s the same thing we did in the previous example, written in a more pythonic way.

Line 17: allocates 64 bytes in physical memory.

Line 18: creates special structures — control blocks for the DMA module. The following lines break the structure of this block. Each field has a length of 32 bit.

Line 19: transfers information flags. You can find a full description of each flag on page 50 of the official documentation.

Line 20: source address. This address must be a bus address, so we call get_bus_address(). The DMA control block must be aligned by 32 bytes, but the size of this block is 24 bytes. So we have 8 bytes, which we use as storage.

Line 21: destination address. In our case, it’s the address of the SET register of the GPIO module.

Line 22: transmission length — 4 bytes.

Line 23: stride. We do not use this feature, set 0.

Line 24: address of the next control block, in our case, next 32 bytes.

Line 25: padding. But since we used this address as a data source, put a bit, which should trigger GPIO.

Line 26: padding.

Lines 28–37: fill in the second DMA control block. The difference is that we write to CLEAR GPIO register and set our first block as a next control block to loop the transmission.

Lines 38–39: write control blocks to physical memory.

Line 41: get the DMA module object with the selected channel.

Lines 42–43: reset the DMA module.

Line 44: specify the address of the first block.

Line 45: run the DMA module.

Lines 49–52: clean up. Stop the DMA module and switch the GPIO pin to the default state.

Let’s connect the oscilloscope to the specified pin and run this application (do not forget about sudo privileges). We will observe ~1.5 MHz square pulses:

DMA challenges

There are several things that you should take into consideration before building a real CNC machine.

First, the size of the DMA buffer can be hundreds of megabytes.

Second, the DMA module is designed for a fast data copying. If several DMA channels are working, we can go beyond the memory bandwidth, and buffer will be copied with delays that can cause jitters in the output pulses. So, it’s better to have some synchronization mechanism.

To overcome this, I created a special design for control blocks:

The oscillogram at the top of the image shows the desired GPIO states. The blocks below represent the DMA control blocks that generate this waveform. “Delay 1” specifies the pulse length, and “Delay 2” is the pause length between pulses. With this approach, the buffer size depends only on the number of pulses.

For example, for a machine with 200mm travel length and 400 pulses per mm, each pulse would take 128 bytes (4 control blocks per 32 bytes), and the total size will be ~9.8MB. We would have more than one axis, but most of the pulses would occur at the same time. And it would be dozens of megabytes, not hundreds.

I solved the second challenge, related to synchronization, by introducing temporary delays through the control blocks. The DMA module has a special feature: it can wait for a special ready signal from the module where it writes data. The most suitable module for us is the PWM (pulse width modulation) module, which will also help us with synchronization.

The PWM module can serialize the data and send it with fixed speed. In this mode, it generates a ready signal for the FIFO (first in, first out) buffer of the PWM module. So, let’s write data to the PWM module and use it only for synchronization.

Basically, we would need to enable a special flag in the perceptual mapping of the transfer information flag, and then run the PWM module with the desired frequency. The implementation is quite long — you can study it yourself.

Instead, let’s create some simple code that can use the existing module to generate precise pulses.

import rpgpio
PIN=21PINMASK = 1 << PINPULSE_LENGTH_US = 1000PULSE_DELAY_US = 1000DELAY_US = 2000 g = rpgpio.GPIO()g.init(PIN, rpgpio.GPIO.MODE_OUTPUT) dma = rpgpio.DMAGPIO()for i in range(1, 6): for i in range(0, i): dma.add_pulse(PINMASK, PULSE_LENGTH_US) dma.add_delay(PULSE_DELAY_US) dma.add_delay(DELAY_US)dma.run(True) raw_input(“Press Enter to stop”)dma.stop()g.init(PIN, rpgpio.GPIO.MODE_INPUT_NOPULL)

The code is pretty simple, and there is no need to break it down. If you run this code and connect an oscilloscope, you will see:

And now we can create real G-code interpreter and control stepper motors. But wait! It is already implemented here. You can use this project, as it’s distributed under the MIT license.

Hardware

The Python project can be adopted for your purposes. But in order to inspire you, I will describe the original hardware implementation of this project — a 3D printer. It basically contains the following components:

  1. Raspberry Pi 3
  2. RAMPSv1.4 board
  3. 4 A4988 or DRV8825 module
  4. RepRap Prusa i3 frame with equipment (end-stops, motors, heaters, and sensors)
  5. 12V 15A power supply unit
  6. LM2596S DC-DC step down converter module
  7. MAX4420 chip
  8. ADS1115 analog to digital converter module
  9. UDMA133 IDE ribbon cable
  10. Acrylic glass
  11. PCB stands
  12. Set of connectors with 2.54mm step

The 40-pin IDE ribbon cable is suitable for the Raspberry Pi 40 pins connector, but the opposite end requires some work. Cut off the existing connector from the opposite end and crimp connectors to the cable wires.

The RAMPSv1.4 board was originally designed for connection to the Arduino Mega connector, so there is no easy way to connect this board to the Raspberry Pi. The following method allows you to simplify the boards connection. You will need to connect less than 40 wires.

I hope this connection diagram is fairly simple and easily duplicated. It’s better to connect some pins (2nd extruder, servos) for future use, even if they are not currently needed.

You might be wondering — why do we need the MAX4420 chip? The Raspberry Pi pins provide 3.3V for the GPIO outputs, and the pins can provide very small current. It’s not enough to switch the MOSFET (Metal Oxide Semiconductor Field Effect Transistor) gate. In addition, one of the MOSFETs works under the 10A load of a bed heater. As a result, with a direct connection to a Raspberry Pi, this transistor will overheat. Therefore, it is better to connect a special MOSFET driver between the highly loaded MOSFET and Raspberry Pi. It can switch the MOSFET in a an efficient way and reduce its heating.

The ADS1115 is an Analog to Digital Converter (ADC). Since Raspberry Pi doesn’t have an embedded ADC module, I used an external one to measure the temperature from the 100k Ohm thermistors. The RAMPSv1.4 module already has a voltage divider for the thermistors. The LM2596S step down converter must be adjusted to a 5V output, and it is used to power the Raspberry Pi board itself.

Now it can be mounted on the 3D printer frame and the RAMPSv1.4 board should be connected to the equipped frame.

That’s it. The 3D printer is assembled, and you can copy the source code to the Raspberry Pi and run it. sudo ./pycnc will run it in an interactive G-Code shell. sudo ./pycnc filename.gcode will run a G Code file. Check the ready config for Slic3r.

And in this video, you can see how it actually works.

Ak sa vám tento článok zdal užitočný, dajte mi prosím nejaké tlieskanie, aby ho videlo viac ľudí. Vďaka!

IoT je predovšetkým o prototypovaní nápadov rýchlo. Aby to bolo možné, vyvinuli sme DeviceHive, open source platformu IoT / M2M. DeviceHive poskytuje pevný základ a stavebné kamene na vytvorenie ľubovoľného riešenia IoT / M2M, čím preklenuje priepasť medzi integrovaným vývojom, cloudovými platformami, big data a klientskými aplikáciami.