Main Site Documentation

Problem resuming thread upon interrupt


#1

Hi, folks:
When I am writing driver for my TTS module, I have some wired problem. The module has a status pin that will drop to low when it’s ready to take another batch of text. So I decided to put UART communication between MCU and TTS module in a thread. It usually takes seconds to minutes to finish TTS depending on the length of text. So it makes sense to suspend the TTS thread after it sends text to TTS module and resume it from the main thread upon InterruptEdgeLow. That what I planned and it largely worked.

Please have a look at my code attached below. The wired thing is, I have to add an extra


ttsThread.Suspend();

at the beginning of ttsThread, otherwise the first UART text would not be sent to TTS module/executed by TTS module. I am not sure what the problem is. The way it is works, I mean having an extra suspend() at the beginning is not too big a deal but I still want to figure this out.

Does it have anything to do with how interrupt is handled by NTMF? When TTS module first powers up, the status pin will remain high until the module is ready Will this low edge register into interrupt queue and then resumes the first suspend()?

Thanks in advance. The following is my code, all that matters is the ttsThread, interrupt event handler.

using System;
using System.Threading;
using System.IO.Ports;
using System.Text;

using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;

using GHIElectronics.NETMF.FEZ;

namespace TTS_Module
{
    public class Program
    {
        private static byte[] ttsTalkback = new byte[10];
        private static SerialPort tts;
        private static OutputPort apSD = new OutputPort((Cpu.Pin)FEZ_Pin.Digital.Di22, false);
        private static OutputPort ttsReset = new OutputPort((Cpu.Pin)FEZ_Pin.Digital.Di24, true);
        private static InterruptPort ttsRdy = new InterruptPort((Cpu.Pin)FEZ_Pin.Interrupt.Di32, true, Port.ResistorMode.PullDown, Port.InterruptMode.InterruptEdgeLow);
        private static Thread ttsThread = new Thread(ttsThreadMethod);

        public static void Main()
        {
            tts = new SerialPort("COM1", 9600, Parity.None, 8, StopBits.One);
            tts.Open();
            tts.DataReceived += new SerialDataReceivedEventHandler(tts_DataReceived);
            ttsRdy.OnInterrupt += new NativeEventHandler(ttsRdy_OnInterrupt);

            //start the text to speech thread, while doing other staff in main thread.
            ttsThread.Start();
            
            /* do your staff here. 
            while (true)
            {
                // Sleep for 500 milliseconds
                Thread.Sleep(500);

                // 
                
            }
            */
            Thread.Sleep(Timeout.Infinite);
        }

        private static void ttsThreadMethod()
        {
            apSD.Write(true); // turning on power amplifer actually has nothing to do with TTS IC but somehow the following thread.Suspend is necessary, otherwise the immediate following text/command will not be excuted.
            ttsThread.Suspend();

            Synthesis("[h2][g2]"); // configure for proper English TTS,[h?]: treatment of word, letter by letter or normal; [g?]: numbers to be read in Chinese or English
            ttsThread.Suspend();

            Synthesis("[m17]Hi, My name is John.[p200][m20]and my name is Catherine[m17]"); // profile selection, [m?] I prefer [m17] so I set it back to John at the end.
            ttsThread.Suspend();

            Synthesis("[f0]How do you do? [p200][f1]How do you do?"); // demo for use of tag "[f?]",word by word or normal
            ttsThread.Suspend();

            Synthesis("I can read words letter by letter like cat is spelled as[h1]cat[h2]"); // demo for use of tag "[h?]" :automatic, letter by letter or nornal
            ttsThread.Suspend();

            Synthesis("I can also say numbers digit by digit like [n1]123 or as a whole number[p200][n0]123[n2]"); // demo for use of tag "[n?]", treatment of numbers: value or number by number
            ttsThread.Suspend();

            Synthesis("I can talk [s0] very very slow [s5]Or [p50] normal. Or [p50][s7] faster,[s8][p50] faster,or[s10][p50]even faster![s5]"); // demo for use of tag "[s?]" speed of speech, 0-10
            ttsThread.Suspend();

            Synthesis("[v1]I can also whisper,[v4]or louder![v7]louder and [v10] even louder.[v5]"); // demo for use of tag "[v?]" volume of speech, 0-10
            ttsThread.Suspend();

            Synthesis("I can change my pitch too. like [t0]That's enough![t9]That's enough![[t10][s1]OK oK.[t5][s5]"); // demo for use of tag "[v?]" tone of speech
            ttsThread.Suspend();

            Synthesis("That's all about text control.[p200]Next we will listen to some recorded tones.First,[x0]sound101 through sound140[x1]"); // use of recorded message or not
            ttsThread.Suspend();
            Synthesis("sound101,sound102,sound103,sound104,sound105,sound106,sound107,sound108,sound109,sound110,sound111,sound112,sound113,sound114,sound115,sound116,sound117,sound118,sound119,sound120,sound121,sound122,sound123,sound124,sound125,sound126,sound127,sound128,sound129,sound130,sound131,sound132,sound133,sound134,sound135,sound136,sound137,sound138,sound139,sound140");
            ttsThread.Suspend();

            Synthesis("OK, more recorded tones. [x0]sound201 through sound240[x1]");
            ttsThread.Suspend();

            Synthesis("sound201,sound202,sound203,sound204,sound205,sound206,sound207,sound208,sound209,sound210,sound211,sound212,sound213,sound214,sound215,sound216,sound217,sound218,sound219,sound220,sound221,sound222,sound223,sound224,sound225,sound226,sound227,sound228,sound229,sound230,sound231,sound232,sound233,sound234,sound235,sound236,sound237,sound238,sound239,sound240");
            ttsThread.Suspend();

            Synthesis("Well, even more recorded tones. 90 alarm tones,sound301,sound302,sound303,sound304,sound305,sound306,sound307,sound308,sound309,sound310,sound311,sound312,sound313,sound314,sound315,sound316,sound317,sound318,sound319,sound320,sound321,sound322,sound323,sound324,sound325,sound326,sound327,sound328,sound329,sound330,sound331,sound332,sound333,sound334,sound335,sound336,sound337,sound338,sound339,sound340,sound341,sound342,sound343,sound344,sound345,sound346,sound347,sound348,sound349,sound350,sound351,sound352,sound353,sound354,sound355,sound356,sound357,sound358,sound359,sound360,sound361,sound362,sound363,sound364,sound365,sound366,sound367,sound368,sound369,sound370,sound371,sound372,sound373,sound374,sound375,sound376,sound377,sound378,sound379,sound380,sound381,sound382,sound383,sound384,sound385,sound386,sound387,sound388,sound389,sound390");
            ttsThread.Suspend();

            Synthesis("Hold on, 30 more tones: sound401,sound402,sound403,sound404,sound405,sound406,sound407,sound408,sound409,sound410,sound411,sound412,sound413,sound414,sound415,sound416,sound417,sound418,sound419,sound420,sound421,sound422,sound423,sound424,sound425,sound426,sound427,sound428,sound429,sound430");
            ttsThread.Suspend();

            Synthesis("ATT. Festival"); // two recorded messages stored in external SPI flash, read by ATT natural language and Festival reference engine, named as ATT and Festival respectively.


        }

        public static void Synthesis(String MSG)
        {
            byte[] Stream = new byte[MSG.Length + 5]; // if this function is frequently called, consider define Stream as static. The way it is involves frequent GC activation which can slow down system.
            Stream[0] = 0xFD; // frame head
            Stream[1] = (byte)((MSG.Length + 2) >> 8); // length, high byte
            Stream[2] = (byte)((MSG.Length + 2) & 0xFF); // length, low byte
            Stream[3] = 0x01; // tts synthesis command
            Stream[4] = 0x01; // encoding
            Array.Copy(Encoding.UTF8.GetBytes(MSG), 0, Stream, 5, MSG.Length); // Move payload MSG into data region of TTS frame
            tts.Write(Stream, 0, Stream.Length);
        }

        public static void issueCMD(byte cmd)
        {
            byte[] Stream = new byte[4];
            Stream[0] = 0xFD;
            Stream[1] = 0x00;
            Stream[2] = 0x01;
            Stream[3] = cmd;
            tts.Write(Stream, 0, 4);
        }


        private static void tts_DataReceived(object Sender, SerialDataReceivedEventArgs e)
        {
            tts.Read(ttsTalkback,0,tts.BytesToRead);
        }

        private static void ttsRdy_OnInterrupt(uint port, uint state, DateTime Time )
        {
            if (ttsThread.ThreadState == ThreadState.Suspended)
                ttsThread.Resume(); 
        }
    }
}

#2

I would avoid using Thread.Suspend/Resume. Maybe you could rather use an AutoResetEvent which you wait on in your sending thread and signal from the interrupt. Waiting on an event will effectively suspend the thread until the event is signaled.

Is it possible that your module does not accept data until it actually drives the signal low, so the first data you send is being ignored because the device is not ready for it? If that is the case, then it makes sense that the thread is suspended until the device indicates that it is read and then you resume the thread to start sending the data.


#3

@ taylorza -

Hi, taylorza, you are right that the module does not accept data when the status pin is high when the module is first powered up but does accept data later on even in the middle of synthesis, in that situation, it will discard unfinished text and start on the next text.

It takes about 280ms to initialize itself. So I tried to wait 200ms before starting the ttsThread in main, it didn’t solve the problem.

I will look into the AutoResetEvent, I guess the advantage is that the cost on MCU is lower.

Cheers.


#4

@ Jerome - The problem with Suspend/Resume is that as your code gets more complex you have a higher risk of deadlocks, not so much in the approach you have used, but using an Event is the “correct” way to handle thread synchronization.

I could not tell you if there is a significant power consumption difference between Suspend/Resume and putting a thread to sleep using an Event, I would need to look closer at the implementation differences to even try wage a guess.


#5

Here is a blog on Thread.Suspend (et.al.) being obsolete. It could explain why you need the extra Suspend():
http://msmvps.com/blogs/peterritchie/archive/2006/10/13/2700_System.Threading.Thread.Suspend_280029002700-is-obsolete_3A00_-2700_Thread.Suspend-has-been-deprecated_2E00__2E00__2E00.aspx

As Taylorza said, AutoResetEvent is an appropriate replacement. ManualResetEvent could also be used, but would not fit as elegantly in your particular logic. Essentially, declare an AutoResetEvent with false as the constructor parameter, do an AutoResetEvent.WaitOne() where you are currently doing Thread.Suspend(), and do an AutoResetEvent.Set() in ttsRdy_OnInterrupt(…).


private AutoResetEvent foo = new AutoResetEvent(false);
.
.
.
apSD.Write(true);
foo.WaitOne();
 
Synthesis("[h2][g2]");
foo.WaitOne();
.
.
.
private static void ttsRdy_OnInterrupt(uint port, uint state, DateTime Time )
{
    foo.Set();
}


#6

@ Gregg -
@ taylorza -

Hi, Gregg, taylorza,

Thanks for the info. Using AutoResetEvent does seem a better way. I copied Gregg’s code into mine, the wired problem persisted. I will hook the module onto my oscilloscope later on to see what is actually going on.


#7

@ Jerome - Interesting. What TTS module do you use?


#8

@ Architect -

A module I built. It does not have a name yet. I am still working on some minor issues. This problem described in this thread is the thing that is preventing me from releasing it.


#9

@ Jerome - Very interesting. I have my own TTS module as well. Hope you will resolve your issue and looking forward to see your module.


#10

It has been a while. I want to update folks who participated in the discussion. The problem turned out to be related to hardware. I recently added a high pass filter to get rid of most power up noise into the latest revision. And as a byproduct, the problem described in the original post disappeared. I am still not sure what exactly caused it in the last revision, but now everything seems working properly.