I've made a Python program which reads the file and then sends/receives the data towards/from the microcontroller and everything worked well until I added a menu to display short instructions.
Since the UART communication has to run in a separate thread I used threading
and StringVar()
to access the data from the main thread.
To demonstrate the problem I've made a short example which has nothing to do with microcontrollers.
The steps to reproduce the problem:
- Click the
Next screen
radio button to open the second screen (at the initial screen the menu works well) - Click
Help > Instructions
After (or sometimes even before) closing the message box the program will crash with:
TclStackFree: incorrect freePtr. Call out of sequence?
This application has requested the Runtime to terminate it in an unusual way.
Please contact the application's support team for more information.
Note:
In the original program where there are more UI elements the program always crashes before showing the message box and after removing even more UI elements the program doesn't crash every time - that's why I've left some "redundant" labels. When some more labels are added the program will crash every time.
I have narrowed the cause of the crash to:
displayVal.set()
in checkForData()
function. By removing that instruction everything works well.
Moreover, after removing displayVal.trace("w", displayVal_trace)
the program will not crash anymore but opening the menu will still temporarily freeze the working thread.
I thought after displayVal.set()
Tkinter is trying to update the label but can't because of showing the menu - however, the problem remained even after I removed label_data = Label(up2, textvariable = displayVal)
.
Here is the stripped down code tested with Python 2.7:
from Tkinter import *
import tkMessageBox
import threading
import time
threadRun = True
checkDelay = 0.5
def checkForData():
global threadRun, checkDelay
print "Simulating thread for receiving messages from UART"
while threadRun == True:
print time.time()
displayVal.set(time.time()) # <-- if removed the menu works OK (no crash)
time.sleep(checkDelay)
print "No more receiving of messages"
def listenForData():
t = threading.Thread(target=checkForData)
t.daemon = False
t.start()
def stopListening():
global threadRun, checkDelay
threadRun = False
time.sleep(checkDelay + 0.1)
def exit_handler():
print "Exiting..."
stopListening()
root.destroy()
root = Tk()
right = int((root.winfo_screenwidth() - 600) / 2)
down = int(root.winfo_screenheight() / 3 - 400 / 2)
root.geometry("600x400+%d+%d" % (right, down))
root.resizable(width = False, height = False)
root.protocol("WM_DELETE_WINDOW", exit_handler)
displayVal = StringVar()
displayVal.set("nothing")
def setupView():
global masterframe
masterframe = Frame()
masterframe.pack(fill=BOTH, expand=True)
selectPort() # the 1st screen for selecting COM ports
def selectPort():
global masterframe, radioVar
# remove everything from the frame
for child in masterframe.winfo_children():
child.destroy()
radioVar = StringVar()
l1 = Label(masterframe, text = "Select...")
l1.pack(pady=(50, 20))
# this would be a list of detected COM ports
lst = ["Next screen"]
if len(lst) > 0:
for n in lst:
r1 = Radiobutton(masterframe, text=n, variable=radioVar, value=n)
r1.config(command = next_screen)
r1.pack()
def mainScreen():
global masterframe, term, status
# remove previous screen from the frame
for child in masterframe.winfo_children():
child.destroy()
up1 = Frame(masterframe)
up1.pack(side=TOP)
up2 = Frame(masterframe)
up2.pack()
terminal = Frame(masterframe)
terminal.pack()
down = Frame(masterframe)
down.pack(side=BOTTOM, fill=BOTH)
label_top = Label(up1, text="Something")
label_top.pack(pady=5)
label_data = Label(up2, textvariable = displayVal)
label_data.pack(pady=(10, 0))
term = Text(terminal, height=10, width=35, bg="white")
term.pack()
term.tag_config("red", foreground="red")
term.tag_config("blue", foreground="blue")
term.insert(END, "The file has been read\n", "red")
term.insert(END, "File contents:\n")
term.insert(END, data)
status = Label(down, text="Status...", bd=1, relief=SUNKEN, anchor=W, bg="green")
status.pack(fill=X)
displayVal.trace("w", displayVal_trace) # <-- if removed only temporary freeze but no crash
def displayVal_trace(name, index, mode):
global term
if(displayVal.get() != "NOTHING"):
term.insert(END, "\nReceived: ", "blue")
term.insert(END, displayVal.get())
term.see(END)
def next_screen():
listenForData()
mainScreen()
def stop():
stopListening()
def instructions():
tkMessageBox.showinfo("Help", "This is help")
main_menu = Menu(root)
root.config(menu = main_menu)
help_menu = Menu(main_menu)
main_menu.add_cascade(label="Help", menu=help_menu)
help_menu.add_command(label="Instructions", command=instructions)
data = [[1], [2], [3]] # this would be the data read from the file
b1 = data[0][0]
b2 = data[1][0]
b3 = data[2][0]
print "Read from file:", b1, b2, b3
setupView()
root.mainloop()