Hvordan du kan bruge Python til at bygge din egen CNC-controller og 3D-printer

Denne artikel diskuterer den proces, jeg brugte til at bygge den første nogensinde implementering af CNC-maskincontroller på ren Python.

Computer numerisk styring (CNC) maskinstyringer implementeres typisk ved hjælp af programmeringssproget C eller C ++. De kører på OS-mindre eller realtidsoperativsystemer med enkle mikrocontrollere.

I denne artikel vil jeg beskrive, hvordan man bygger en CNC-controller - især en 3D-printer - ved hjælp af moderne ARM-kort (Raspberry Pi) med et moderne sprog på højt niveau (Python).

En sådan moderne tilgang åbner en bred vifte af integrationsmuligheder med andre banebrydende teknologier, løsninger og infrastrukturer. Dette gør hele projektudvikleren venlig.

Om projektet

Moderne ARM-kort bruger typisk Linux som et referenceoperativsystem. Dette giver os adgang til hele Linux-infrastrukturen med alle Linux-softwarepakker. Vi kan være vært for en webserver på et kort, bruge Bluetooth-forbindelse, bruge OpenCV til billedgenkendelse og opbygge en klynge af kort blandt andet.

Dette er velkendte opgaver, der kan implementeres på ARM-kort, og de kan være virkelig nyttige til brugerdefinerede CNC-maskiner. For eksempel kan autopositionering ved hjælp af compuvision være meget praktisk for nogle maskiner.

Linux er ikke et realtids-operativsystem. Dette betyder, at vi ikke kan generere impulser med de krævede tidspunkter til at styre stepper motorer direkte fra kortstifterne med kørende software, selv ikke som et kernemodul. Så hvordan kan vi bruge steppers og Linux-funktioner på højt niveau? Vi kan bruge to chips - en mikrokontroller med en klassisk CNC-implementering og et ARM-kort tilsluttet denne mikrocontroller via UART (universal asynkron modtager-sender).

Hvad hvis der ikke er egnede firmwarefunktioner til denne mikrocontroller? Hvad hvis vi har brug for at kontrollere yderligere akser, der ikke er implementeret i mikrocontrolleren? Enhver ændring af den eksisterende C / C ++ firmware kræver masser af udviklingstid og indsats. Lad os se, om vi kan gøre det lettere og endda spare penge på mikrokontroller ved blot at fjerne dem.

PyCNC

PyCNC er en gratis open-source højtydende G-kode tolk og CNC / 3D-printer controller. Det kan køre på forskellige Linux-drevne, ARM-baserede kort, såsom Raspberry Pi, Odroid, Beaglebone og andre. Dette giver dig fleksibiliteten til at vælge ethvert kort og bruge alt, hvad Linux tilbyder. Og du kan gemme hele G-kodens kørselstid på et kort uden behov for en separat mikrocontroller til realtidsdrift.

Valg af Python som det vigtigste programmeringssprog reducerer kodebasen betydeligt sammenlignet med C / C ++ - projekter. Det reducerer også kedelpladen og den microcontroller-specifikke kode og gør projektet tilgængeligt for et bredere publikum.

Hvordan det virker

Projektet bruger DMA (Direct Memory Access) på chiphardwaremodulet. Den kopierer simpelthen GPIO (General Purpose Input Output) -tilstandsbufferen allokeret i RAM til de faktiske GPIO-registre. Denne kopieringsproces synkroniseres af systemuret og fungerer helt uafhængigt af CPU-kernerne. Således genereres en sekvens af impulser til trinmotorens akse i hukommelsen, og derefter sender DMA dem præcist.

Lad os grave dybere ned i koden for at forstå det grundlæggende og hvordan man får adgang til hardwaremoduler fra Python.

GPIO

Et indgangsoutputmodul til almindeligt formål styrer pin-tilstande. Hver pin kan have lav eller høj tilstand. Når vi programmerer mikrokontrolleren, bruger vi normalt SDK (softwareudviklingssæt) definerede variabler til at skrive til den pin. For eksempel for at aktivere en høj tilstand for ben 1 og 3:

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

Hvis du kigger i SDK, finder du erklæringen for denne variabel, og den ser ud som:

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

Det er bare en markør. Det peger ikke på placeringen i RAM, men på adressen til den fysiske processor. Det aktuelle GPIO-modul findes på denne adresse.

For at administrere stifter kan vi skrive og læse data. Raspberry Pi's ARM-processor er ikke en undtagelse, og den har det samme modul. For at kontrollere stifter kan vi skrive / læse data. Vi kan finde adresserne og datastrukturer i den officielle dokumentation for periferiudstyr til processorer.

Når vi kører en proces i brugerens runtime, starter processen i det virtuelle adresseområde. Den faktiske perifere enhed er tilgængelig direkte. Men vi kan stadig få adgang til ægte fysiske adresser med ‘/dev/mem’enheden.

Her er nogle enkle koder i Python, der styrer en pin-tilstand ved hjælp af denne tilgang:

Lad os opdele det linje for linje:

Linje 1–6 : overskrifter, import.

Linje 7 : åben ‘/dev/mem’ enhedens adgang til den fysiske adresse.

Linie 8 : Vi bruger mmap-systemopkaldet til at kortlægge en fil (selvom denne fil repræsenterer fysisk hukommelse) i processens virtuelle hukommelse. Vi specificerer længden og forskydningen af ​​kortområdet. For længden tager vi sidestørrelsen. Og forskydningen er 0x3F200000.

Dokumentationen siger, at bussen adressen 0x7E200000indeholder GPIO registre, og vi har brug for at angive den fysiske adresse. Dokumentationen siger (side 6, afsnit 1.2.3), at 0x7E000000busadressen er tilknyttet den 0x20000000fysiske adresse, men denne dokumentation er til Raspberry 1.

Bemærk, at alle 0x3F000000modulbus- adresser er ens for Raspberry Pi 1–3, men dette kort blev ændret til RPi 2 og 3. Så adressen her er 0x3F200000. For Raspberry Pi 1 skal du ændre det til 0x20200000.

Efter dette kan vi skrive til vores proces virtuelle hukommelse, men det skriver faktisk til GPIO-modulet.

Linje 9 : Luk filhåndtaget, da vi ikke behøver at gemme det.

Linje 11–14 : vi læser og skriver til vores kort med 0x08offset. Ifølge dokumentationen er det GPFSEL2 GPIO Function Select 2-registeret. Og dette register styrer pin-funktioner.

Vi indstiller (ryd alle, og indstil derefter med OR-operatøren) 3 bits med den 3. bit indstillet til 001. Denne værdi betyder, at stiften fungerer som output. Der er mange ben og mulige tilstande til dem. Dette er grunden til, at registeret for tilstande er opdelt i flere registre, hvor hver indeholder tilstande til 10 ben.

Linie 16 og 22 : opsæt 'Ctrl + C' afbrydelsesbehandler.

Linje 17 : uendelig løkke.

Linie 18 : indstil stiften til høj tilstand ved at skrive til GPSET0-registret.

Bemærk, at Raspberry Pi ikke har registre, som PORTA (AVR-mikrocontrollere) har. Vi kan ikke skrive hele GPIO-tilstanden af ​​alle ben. Der er bare indstillede og klare registre, der bruges til at indstille og rydde specificeret med bitvise maskenåle.

Linie 19 og 21 : forsinkelse

Linje 20 : indstil pin til lav tilstand med GPCLR0-registret.

Linie 25 og 26 : skift pin til standard, inputtilstand. Luk hukommelseskortet.

Denne kode skal køres med superbrugerrettigheder. Navngiv filen, ‘gpio.py’ og kør den med ‘sudo python gpio.py’. Hvis du har en LED tilsluttet pin 21, blinker den.

DMA

Direct Memory Access er et specielt modul, der er designet til at kopiere hukommelsesblokke fra et område til et andet. Vi kopierer data fra hukommelsesbufferen til GPIO-modulet. Først og fremmest har vi brug for et solidt område i fysisk RAM, der kopieres.

Der er få mulige løsninger:

  1. Vi kan oprette en simpel kernedriver, der tildeler, låser og rapporterer til os adressen på denne hukommelse.
  2. I nogle implementeringer tildeles virtuel hukommelse og bruges ‘/proc/self/pagemap’til at konvertere adressen til den fysiske. Jeg vil ikke anbefale denne tilgang, især når vi har brug for at tildele stort område. Enhver næsten tildelt hukommelse (endda låst, se kernedokumentationen) kan flyttes til det fysiske område.
  3. Alle Raspberry Pi har en ‘/dev/vcio’enhed, som er en del af den grafiske driver og kan tildele fysisk hukommelse til os. Et officielt eksempel viser, hvordan man gør det. Og vi kan bruge det i stedet for at skabe vores egne.

Selve DMA-modulet er blot et sæt registre, der findes et eller andet sted på en fysisk adresse. Vi kan styre dette modul via disse registre. Dybest set er der kilde-, destinations- og kontrolregistre. Lad os kontrollere nogle enkle koder, der viser, hvordan du bruger DMA-modulerne til at styre 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.

Hvis du fandt denne artikel nyttig, bedes du give mig nogle klapper, så flere mennesker ser den. Tak!

IoT handler hurtigt om prototypering af ideer. For at gøre det muligt udviklede vi DeviceHive, en open source IoT / M2M-platform. DeviceHive giver et solidt fundament og byggesten til at skabe enhver IoT / M2M-løsning, der bygger bro mellem indlejret udvikling, cloudplatforme, big data og klientapps.