Friday, December 25, 2009

Flash Loader for 8-Pin ZiLOG MCUs

Serial Port Flash Loader for 8-Pin Z8F Zilog Encore(XP)

One major problem for the ZiLOG 8-pin mcu's is that it needs an expensive usb smart cable in order for the program to be 'flashed'/'burned' to their memory. This application aims to address this problem, by using a common serial port for flash loading (reading and erasing as well) instead of using an expensive tool.



For now,it's only tested with 8-pin Z8F042A. Hopefully in the future, it can also support other ZiLOG MCUs, not only these 8-pins (those 20- and 28-pins should be easier to program).

download: Flash Loader for 8-Pin Z8F.rar

forum link for project progress: Serial Port Flash Loader for 8-Pin Zilog MCUs

update(123009):
already tested with 8-pin z8f0423 and 28-pin z8f082a (yes, soic-28 also)

update(010110):
Win32 Executable verion: Flash Loader for 8-Pin Z8F (WIN32 Executable).rar
it only requires msvcp90.dll - most Win32 OS already have this; if not yet installed, it can be downloaded from Microsoft.

Wednesday, December 16, 2009

Python for PIC MCUs

Python (pseudo) Compiler for PIC12/PIC16 Microcontrollers

It uses pyastra (python assembler translator) and gpasm assembler.
The PyQt GUI has a QScintilla-based editor for easy editing of the python scripts to be compiled.

main.py : (compatible with Portable Eric 4 Python IDE)
 #################################  
# Python (pseudo) Compiler for PIC12 and PIC16 devices
# using pyastra and gpasm
# PyQt GUI by yus
#################################

import sys, os
from PyQt4.QtGui import *
from PyQt4.QtCore import *
from PyQt4.Qsci import QsciScintilla, QsciLexerPython

class ScriptEditor(QsciScintilla):
def __init__(self):
QsciScintilla.__init__(self)
self.filename = None
self.filedialog = QFileDialog()
# font
font = QFont()
font.setPointSize(9.5)

# Choose python lexer
lexer = QsciLexerPython()
lexer.setDefaultFont(font)
self.setLexer(lexer)

# Folding visual : we will use boxes
self.setFolding(QsciScintilla.BoxedTreeFoldStyle)
# Braces matching
self.setBraceMatching(QsciScintilla.SloppyBraceMatch)
# Editing line color
self.setCaretLineVisible(True)
self.setCaretLineBackgroundColor(QColor(200, 240, 200))
# line numbers
self.setMarginWidth(0, QFontMetrics(font).width( "00000" ) )

def open(self):
self.filename = self.filedialog.getOpenFileName(None,
'Open Python Script', '.\\',
'python script(*.py);;text file(*.txt);;All files (*)', QString())
try:
f = open(self.filename)
self.setText(f.read())
f.close()
except:
print 'unable to open script.'

def save(self):
if self.filename == None:
self.save_as()
else:
try:
f = open(self.filename, "w")
f.write(str(self.text()))
f.close()
except:
print 'file not save.'

def save_as(self):
self.filename = self.filedialog.getSaveFileName(None,
'Save Python Script', '.\\',
'python script(*.py);;;text file(*.txt);;All files (*)', QString())
if self.filename != None:
self.save()

def get_filename(self):
return self.filename
class AppWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.setWindowTitle('PIC12-PIC16 Python Compiler ( PyQt, PyAsTra and GPASM ) - yus ')
#self.setMinimumSize(700, 320)
#self.move(20, 20)

self.editor = ScriptEditor()
self.open_btn = QPushButton('Open')
self.save_btn = QPushButton('Save As')
self.device_label = QLabel('Select Device:')
self.status_info = QLabel('Select or create a python script first')
self.device_cbox = QComboBox()
self.compile_btn = QPushButton('Compile Script')
self.compile_btn.setEnabled(False) # initially disabled until a script is opened

self.editor_area = QDockWidget('(pic script here)')
self.editor_area.setWidget(self.editor)
self.addDockWidget(Qt.TopDockWidgetArea, self.editor_area)

self.output_info = QTextEdit()
self.output_info.setReadOnly(True) # read only information
self.output_info.setTextColor(Qt.darkBlue)
self.output_info_widget = QDockWidget('Output Information')
self.output_info_widget.setWidget(self.output_info)
self.addDockWidget(Qt.BottomDockWidgetArea, self.output_info_widget)

file_tbar = QToolBar()
file_tbar.addWidget(self.open_btn)
file_tbar.addWidget(self.save_btn)
compile_tbar = QToolBar()
compile_tbar.addWidget(self.device_label)
compile_tbar.addWidget(self.device_cbox)
compile_tbar.addWidget(self.compile_btn)

self.addToolBar(file_tbar)
self.addToolBar(compile_tbar)

self.status = QStatusBar()
self.status.addWidget(QLabel('\t')) # dummy widget
self.status.addWidget(self.status_info, 1)
self.setStatusBar(self.status)

self.update_device_list()
self.connect(self.save_btn, SIGNAL('clicked()'), self.save_script)
self.connect(self.open_btn, SIGNAL('clicked()'), self.open_script)
self.connect(self.compile_btn, SIGNAL('clicked()'), self.compile_script)

def open_script(self):
self.editor.open()
fname = str(self.editor.get_filename())
self.editor_area.setWindowTitle(fname)
if fname != 'None':
self.compile_btn.setEnabled(True)
def save_script(self):
self.editor.save_as()
fname = str(self.editor.get_filename())
self.editor_area.setWindowTitle(fname)
if fname != 'None':
self.compile_btn.setEnabled(True)
def compile_script(self):
self.editor.save() # save changes in the script before compiling
script = str(self.editor.get_filename())
device = str(self.device_cbox.currentText())
# PyAstra (python assymbly translator)
pyastra_command = '.\\pyastra_console.py -p'+device[3:] + ' -S --compile ' + script
msg = ' from pyastra console :\n'
try: # clean/delete previous output files
for ext in ('asm', 'hex', 'lst', 'cod'):
os.remove(script[:-2] + ext)
except:
pass #print 'one or more files not found'
try:
msg += os.popen(pyastra_command).read() # execute pyastra console
self.output_info.setText(msg) #pyastra.py info
except:
self.output_info.append('Error occured while translating the python script')
if msg.find('Program memory usage')>0:
# assemble the generated asm file using gpasm.exe
self.output_info.append('---------------\nExecuting gpasm.exe....')
file_asm = '%s'%self.editor.get_filename()
file_asm = file_asm[:file_asm.find('.py')] + '.asm'
#self.output_info.append( os.popen('.\\gpasm\\gpasm -v').read() ) # show gpasm version
msg = os.popen('.\\gpasm\\gpasm -I .\\gpasm\\header '+file_asm).read()
self.output_info.append(msg)
self.output_info.append('Finished.')
self.status_info.setText('Done')
else:
self.status_info.setText('Please verify the script')

def update_device_list(self):
for root, dirs, files in os.walk('.\\pyastra\\ports\\pic14\\procs'):
for name in files:
device = str(name)
if device[:1]=='1' and device.find('.pyc')<0 and device.find('i')<0:
device = device[:device.find('.')]
self.device_cbox.addItem('pic'+device)
self.device_cbox.setCurrentIndex(127) # initially set to PIC16F876A

if __name__ == '__main__':
app = QApplication(sys.argv)
form = AppWindow()
form.show()
sys.exit(app.exec_())




Complete Eric4 project : Python for PIC.rar
* already includes pyastra and gpasm

note: Python programming language is really NOT intended for platform/devices with very limited resources, such a microcontroller with a very small memory. For now, C language is still the widely used in microcontroller programming.

forum link: Python Compiler for PIC MCUs

Sunday, December 6, 2009

USB-based Oscilloscope (beta)

This is NOT considered as an oscilloscope yet. It's just a preparation of making a real PIC18F USB-based oscilloscope. For my initial testing, I used my PIC18F and PyUSB demo, same hardware and firmware for the 18F2550. The only difference is in the GUI, instead of PyQt QDial, I use PyQwt PlotCurve widget.

the Python script: (compatible with my Portable Eric 4 Python IDE (v2))
 #################################  
# USB-based oscillpscope (beta)
# using pyUSB and PyQt/PyQwt
#################################

import sys, usb
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4.Qwt5 import *
from PyQt4.Qwt5.anynumpy import *

class UsbPic:
def __init__(self, vendor_id, product_id):
busses = usb.busses() # enumerate busses
self.handle = None
for bus in busses:
devices = bus.devices
for dev in devices:
if dev.idVendor==vendor_id and dev.idProduct==product_id: # device matches
self.dev = dev
self.conf = self.dev.configurations[0]
self.intf = self.conf.interfaces[0][0]
self.endpoints = []
for endpoint in self.intf.endpoints:
self.endpoints.append(endpoint)
return

def open(self):
if self.handle:
self.handle = None
try:
self.handle = self.dev.open()
self.handle.detachKernelDriver(0)
self.handle.detachKernelDriver(1)
self.handle.setConfiguration(self.conf)
self.handle.claimInterface(self.intf)
self.handle.setAltInterface(self.intf)
return True
except:
return False

def write(self, ep, buff, timeout = 100):
try:
return self.handle.interruptWrite(ep, buff, timeout) #return bytes written
except:
return 0
def read(self, ep, size, timeout = 100):
try:
return self.handle.interruptRead(ep, size, timeout) # return data read
except:
return []
def getDeviceName(self):
return self.handle.getString(2, 40)

class AmplitudevsTime(QwtPlot):
def __init__(self):
QwtPlot.__init__(self)
self.setTitle("<font size=1 color=darkblue>Potentiometer Position ( 8-bit ADC value )</font>")
self.setCanvasBackground(Qt.black)
#grid
grid = QwtPlotGrid()
#grid.enableXMin(True)
#grid.enableYMin(True)
grid.setMajPen(QPen(Qt.darkGreen, 0, Qt.DotLine))
grid.setMinPen(QPen(Qt.darkGreen, 0 , Qt.DotLine))
grid.attach(self)
# x-axis
self.setAxisTitle(QwtPlot.xBottom, "<font size=1 color=darkred>time (seconds)</font>")
self.timerange = arange(0.0, 60, 0.2) #60 seconds, 200 ms interval
self.amplitudes = zeros(len(self.timerange), Float)
# curve
self.amplitude_plot = QwtPlotCurve('Amplitude')
self.setAxisScale(QwtPlot.yLeft, 0, 255) #amplitude range : 0 to 255
self.setAxisScale(QwtPlot.xBottom, 0, 60) #time range: 0 to 60 seconds
self.amplitude_plot.setPen(QPen(Qt.yellow))
self.amplitude_plot.attach(self)

def updatePlot(self, new_value=0):
# shift amplitude array left and assign new value to z[n-1].
self.amplitudes = concatenate((self.amplitudes[1:], self.amplitudes[:1]), 1)
self.amplitudes[-1] = new_value
self.amplitude_plot.setData(self.timerange, self.amplitudes)
self.replot()

class MyForm(QDialog):
def __init__(self, parent = None):
super(MyForm, self).__init__(parent)
self.setWindowTitle("USB-based Oscilloscope (Beta) - pYUSb + PIC18F2550")
self.setMinimumSize(560, 300)
# create widgets/controls
self.connect_btn = QPushButton('Connect')
self.toggle1_btn = QPushButton('Toggle LED1')
self.toggle2_btn = QPushButton('Toggle LED2')
self.status_label = QLabel('press "Connect" button')
self.update_timer = QTimer()

self.display = AmplitudevsTime()

layout = QGridLayout()
layout.addWidget(self.display, 0, 0, 10, 15)
layout.addWidget(self.toggle1_btn, 2, 15)
layout.addWidget(self.toggle2_btn, 2, 16)
layout.addWidget(self.connect_btn, 7, 15)
layout.addWidget(self.status_label, 4, 15, 2, 2)
self.setLayout(layout)
# widgets initial condition
self.toggle1_btn.setEnabled(False)
self.toggle2_btn.setEnabled(False)
# signals
self.connect(self.connect_btn, SIGNAL("clicked()"), self.DeviceConnect)
self.connect(self.toggle1_btn, SIGNAL("clicked()"), self.toggleLED1)
self.connect(self.toggle2_btn, SIGNAL("clicked()"), self.toggleLED2)
self.connect(self.update_timer, SIGNAL("timeout()"), self.updateDisplay)

def DeviceConnect(self):
self.device = UsbPic(0x04d8, 0x0204) # Microchip Vendor ID and Product ID
if self.device.open():
self.toggle1_btn.setEnabled(True)
self.toggle2_btn.setEnabled(True)
self.update_timer.start(200) # update every 200ms
self.status_label.setText('Connected to:\n %s' %self.device.getDeviceName())
else:
self.toggle1_btn.setEnabled(False)
self.toggle2_btn.setEnabled(False)
self.update_timer.stop()
self.status_label.setText('Warning:\n No Device Found!')
def toggleLED1(self):
self.device.write(1, [0x80], 1000)
def toggleLED2(self):
self.device.write(1, [0x82], 1000)
def updateDisplay(self):
self.device.write(1, [0x81])
byteread = self.device.read(0x81, 64)
if len(byteread)>1:
self.display.updatePlot(byteread[1])

if __name__ == "__main__":
app = QApplication(sys.argv)
form = MyForm()
form.show()
sys.exit(app.exec_())

Right now, my problem is on the 18F2550 side. I still don't know how to use both the USB and ADC interrupts together. My first modification on PIC's firmware was no success. When I enabled the ADC interrupt routine, the whole program response slows down. I still have to read properly the datasheet(plus application notes), and ask for help of the 'masters'. What I'm currently doing on the code is reading a single byte of ADC value every 200ms (very slow!). From what I've understand, the PIC can (it should) send 64 bytes for every USB interrupt read request. I don't know how fast it is, but it will surely improve the PIC18F USB-based oscilloscope.

Thursday, December 3, 2009

Portable Eric 4 Python IDE (v2)


After my first portable Eric4 IDE, here comes another portable Development Environement for Python. It's still Eric4, but this time it's now equipped with SciPy scientific tool for python (open-source software for mathematics, science, and engineering). Together with NumPy and matplotlib, scipy turns Python into a powerful language for numerical computing.

Aside from matplotlib, I also added PyQwt. They both share the same purpose - plotting graphs in python. Although matplotlib graphs are more detailed than of PyQwt, I still prefer PyQwt because it can be easily integrated to PyQt GUIs, and it has also faster response(ideal for creating GUIs). Here is a good comparison between matplotlib vs PyQwt. Also, a possibility of embedding matplotlib plots into PyQt.

I also included pySerial, pyParallel and PyUSB. These three are great for learning PC interfacing. I wasn't able to test the pyparallel module, since my machine doesn't have any LPT ports. PySerial works fine, same with previous portable IDE. For PyUSB to work, LibUsb-Win32 has still need to be installed on the host machine.

download link: Eric4 IDE Python 2.6.rar
size: ~62MB RAR file (~250MB uncompressed EXE file), tested only on WinXP
view "readme.txt" (inside the RAR file) for more information.


some scripts run on this IDE:
Ruby and Python Programming Language Thread
DSP 0.01: A tutorial