Showing posts with label Python. Show all posts
Showing posts with label Python. Show all posts

Saturday, 22 September 2007

Temperature drop

My extruder controller is working much better after I cured the motor noise problem. The 10 bit SAR ADC also seems to work better than the 16 bit sigma-delta version did. With the 16 bit one there was a lot of noise on the readings, even when the motor wasn't running. I had to average over many samples to get a consistent reading which delayed the response. With the 10 bit ADC I just read it and compare it with the temperature set point value to decide if the heater should be on or off. That gives a temperature swing of about ± 3°C with the heater going on and off every four or five seconds.

The temperature is calculated from the ADC reading and vice versa by the PC with the following Python class :-
from math import *

class Thermistor:
"Class to do the thermistor maths"
def __init__(self, r0, t0, beta, r1, r2):
self.r0 = r0 # stated resistance
self.t0 = t0 + 273.15 # temperature at stated resistance, e.g. 25C
self.beta = beta # stated beta
self.vref = 1.5 * 1.357 / 1.345 # ADC reference, corrected
self.vcc = 3.3 # supply voltage to potential divider
self.vs = r1 * self.vcc / (r1 + r2) # effective bias voltage
self.rs = r1 * r2 / (r1 + r2) # effective bias impedance
self.k = r0 * exp(-beta / self.t0) # constant part of calculation

def temp(self,adc):
"Convert ADC reading into a temperature in Celsius"
v = adc * self.vref / 1024 # convert the ADC value to a voltage
r = self.rs * v / (self.vs - v) # resistance of thermistor
return (self.beta / log(r / self.k)) - 273.15 # temperature

def setting(self, t):
"Convert a temperature into a ADC value"
r = self.r0 * exp(self.beta * (1 / (t + 273.15) - 1 / self.t0)) # resistance of the thermistor
v = self.vs * r / (self.rs + r) # the voltage at the potential divider
return round(v / self.vref * 1024) # the ADC reading
It is instantiated as follows :-
thermistor = Thermistor(10380, 21, 3450, 1790, 2187)
10380 is the resistance of the thermistor measured by my multimeter at a room temperature of 21°C. 3450 is the beta of the thermistor taken from the data sheet. The last two values are the two resistors forming a potential divider with the thermistor wired across the second one, again the values are measured with a multimeter. The fudge factor of 1.357 / 1.345 corrects the MSP430 internal reference voltage so that it agrees with the multimeter.

The result seems to track the temperature measured by a thermocouple to within about 5°C, good enough for me. Just as I had finished checking it, I knocked the thermistor leads with my thermocouple and it fell off. It was stuck to the brass nozzle with JB Weld but I forgot to roughen the surface first. I am now waiting 16 hours for it to set again.

The extruder controller firmware is only about 400 lines of C. As well as temperature control it also controls the DC motor precisely using the shaft encoder and handles the I²C protocol.

I have now completed all the mechanical parts, the electronics and the firmware. I just need to get the RepRap host code to talk to my non standard hardware to complete the machine.

Saturday, 28 April 2007

Ever decreasing circles

I wrote some test code in Python to check that my firmware was working properly. This turned out to be incredibly easy using the socket and struct modules. I have not written a lot of Python before but it never ceases to amaze me how much more productive it is compared to C++. Handling the Ethernet UDP comms required only a few lines of code. Here are extracts from the constructor and destructor :-
from socket import *
from struct import *

class Hydra:
def __init__(self, ip_addr):
# Create sockets
self.sendSock = socket(AF_INET,SOCK_DGRAM)
self.sendAddr = (ip_addr,21000)

self.recvSock = socket(AF_INET,SOCK_DGRAM)
self.recvSock.bind(("",21001))
self.recvSock.settimeout(0.1)

def __del__(self):
# Close sockets
self.recvSock.close()
self.sendSock.close()
Below is the code that sends a command and receives the reply. UDP packets are not guaranteed to arrive at their destination and if they do they are not guaranteed to arrive in the same order they were sent in, although on a small single segment LAN they generally do. Packets are protected by CRCs and checksums so if you do receive a packet you can be pretty sure it is not corrupt.

To make a reliable protocol timeouts, retries and packet sequence numbers are required. I also added a 4 byte magic number at the start of each packet just to make sure a stray packet from a rogue application or the Web could not cause HydraRaptor to lose the plot.
    def do_cmd(self,cmd):
tries = 3
while tries:
self.sendSock.sendto('\x12\x34\x56\x78' + cmd,self.sendAddr)
try:
data,addr = self.recvSock.recvfrom(1024)
return data
except timeout:
print "read failed"
tries = tries - 1
raise 'comms failed'
Here is the code to wait for the machine to be ready and then instruct it to go to to a 3D position.
   def get_status(self):
return unpack('>hhhhBB', self.do_cmd('\x00\x00'))

def goto_xyz(self,pos,delay):
while 1:
a, b, c, steps, seq, ready = self.get_status()
if steps == 0 and ready:
break
seq = (seq + 1) & 255
self.do_cmd(pack('>BBhhhh', self.goto_xyz_cmd, seq, pos[0], pos[1], pos[2], delay))
Notice how easy it is to marshal and unmarshal packets using the struct module. The big endian to little endian conversion is handled simply by putting a > sign at the start of the format string.

Below are some test patterns. The 25 circles in the centre are 1 - 25mm radius and are drawn with the minimum number of line segments to keep within 0.05mm accuracy. This is calculated with the formula
π / acos((r - 0.05) / (r + 0.05))
The outer circle is made with 360 line segments.



Not only can I now drive HydraRaptor from a script but I can also interact directly with it via the command line:
>>> hydra = Hydra("10.0.0.42")
>>> print hydra
At (9944,11943,400) steps = 0
>>> hydra.goto_xyz((0,0,0),1000)
>>> print hydra
At (3310,3976,133) steps = 3976
>>> print hydra
At (0,0,0) steps = 0
>>>
I noticed that although the Z axis is repeatable in the short term the distance to the paper varied by about 0.1mm over a two day period. I expect this is due to the MDF expanding and contracting slightly. I plan to make a tool height sensor mounted on the XY table to make it easy to switch tools. This should also compensate for the wood changes.

The next thing to do is use my existing milling setup to make some motor mounts to make an improved milling machine. To do this I will extend my Python script to allow me to define an outline as a list of lines and arcs and then mill around it.

I will then try to make the RepRap FDM extruder with the improved milling machine.

Monday, 23 April 2007

It speaks, but my PC has its fingers in its ears

I almost got UDP working this evening. I wrote a simple Python test script to send a "get status" command to HydraRaptor. Here it is :-
   from socket import *

# Set the socket parameters
hydra = "10.0.0.42"
inPort = 21000
inAddr = (hydra,inPort)
outPort = 21001
outAddr = ("localhost",outPort)
buf = 1024

# Create sockets
sendSock = socket(AF_INET,SOCK_DGRAM)
recvSock = socket(AF_INET,SOCK_DGRAM)
recvSock.bind(outAddr)
recvSock.settimeout(2.0)

# Send a get status command
cmd = "\x12\x34\x56\x78\x00"
if(sendSock.sendto(cmd,inAddr)):
print "Sending message ....."

# Receive the reply
try:
data,addr = recvSock.recvfrom(buf)
if data:
print " Received message '", data, "'"
except:
print "Read failed"

# Close sockets
recvSock.close()
sendSock.close()
HydraRaptor replies with its x,y,z coordinates and the number of steps remaining. The reply packet arrives at my PC which then sends an ICMP destination unreachable (port unreachable) message back. I have no idea why. netstat -ad shows :
  UDP    shuttle:21001          *:*                                    2412
[Pythonwin.exe]
Which looks to me to show that the port is being listened to. I have tried turning my firewall off but that made no difference. I am sure the problem is at the PC end because Ethereal shows me that the packets are well formed. I have had enough for this evening. Any Python socket experts out there? I have never done sockets in Python before.