Link to home
Start Free TrialLog in
Avatar of judico
judico

asked on

Questions to drichards regarding the managed code for the multimeter (Continued)

This is a continuation of the following question:

https://www.experts-exchange.com/questions/21106354/Questions-to-drichards-regarding-the-managed-code-for-the-multimeter.html

In the above link the obtainment of the data from the device was achieved. However, some details need to be ironed out. The response of the device to changes of the external voltage is quite slow -- on the order of half a minute. What might be the reason for the slow response? Also, the format in which the data arrive is strange -- seems that an eight bit byte should correspond to each one of the four channels of the PIC. It appears, however, that only the data from the first channel consist of 8 bits, the second appears to be of 6 bits while channels 3 and 4 seem to be of 7 bits. What might be the reason for that?
Avatar of judico
judico

ASKER

This is the program of the PIC, if this may help:

' PicBasic Pro program to display 4-channel 10-bit
' A/D conversion on serial terminal
@ DEVICE MCLR-OFF, INTRC_OSC_NOCLKOUT, WDT_OFF, BOD_OFF, PWRT_ON, PROTECT_ON
DEFINE OSCCAL_1K 1  ' Calibrate internal oscillator
DEFINE ADC_BITS 10  ' Set number of bits in result
DEFINE ADC_CLOCK 3  ' Set clock source (3=rc)
DEFINE AD_SAMPLEUS 100  ' Set sampling time in US
N2400 CON 16780
AD VAR WORD[41  ' Create adval to store result
X VAR BYTE  ' Holds each A/D channel result
TRISIO = %11011111
ANSEL = 801111111  ' All A/D Frc osc
CMCON = 7  ' Analog comparators off
ADCON0.7 = 1  ' Right justify
PAUSE 500  ' Wait 1/2 second to initialize

MAIN :
FOR X = 0 TO 3  ' 4-channel loop
ADCIN X,AD[X]   'Read A/D input
WHILE ADCONO.1 = 1  ' Wait until conversion complete
WEND
@ NOP
NEXT X  ' CLose loop
PAUSE 250  ' Pause 250mS
FOR X = 0 TO 3  'Get 4-channel results
AD[X] = AD[X]  &  $03FF  ' Mask out upper 6-bits of 10-bit results
SEROUT2 GPI0.5,N2400, [DEC X," ",  DEC AD[X] ,13,10]
NEXT X
GOT0 MAIN  ' Loop
END  ' END Program
Avatar of judico

ASKER

I was able to resolve the problem with the sluggishness of device's response by setting the timer values on the order of 1 - 300ms, say:

aTimer = new System::Timers::Timer(200);

Obviously the timer value has to be closer to the sampling time of the PIC -- as seen above DEFINE AD_SAMPLEUS 100  ' Set sampling time in US. Larger times seem to get the program out of sync with the sampling rate of the device. Thus, the above code line is only to set the synchronization with the sampling time of the device and cannot be used to set how often the program will call the device -- one should find some other way to set the frequency of calling the device. So far I don't see how this can be done since the above timer is also connected with the frequency of invalidating the application of the OnPaint method.

As for the format of the received string, it is strange and I don't understand why it should be so, but the parsing I found through trial and error seems to work.
It may be helpful to debug output the contents of the data buffer each time you read the serial port.  If you do not read each sample as it comes in (hence the apparent timing issue) bytes will be lost or the data buffer will contain data from multiple samples and your program must be smart enough to decipher the individual samples.

If you're losing data, there's not much you can do except read faster.  I suspect that you are not losing data but are just reading buffers that contain multiple samples.  If we knew the data format precisely we could deal with that.  As it is, you'll just have to read fast enough tokeep up with the sample rate.
Avatar of judico

ASKER

I have another dll which I use in VB.NET and seems to be working well there. Probably it will not be a bad idea to implement it in the current VC++.NET program and compare its outcome with the outcome from the code you sent me. Unfortunately, I don't know how to implement that dll here in the VC++.NET program. IN VB.NET I install it as a new object in the toolbox and then write the following:

I open the port in the constructor and set the DTR:

Public Sub New()
        MyBase.New()

        'This call is required by the Windows Form Designer.
        InitializeComponent()

        'Add any initialization after the InitializeComponent() call
       
        RS232Comms1.OpenComms()
        RS232Comms1.SetDTR()
   
    End Sub


while in the following subroutine the byte itself is obtained, converted into integer and then parsed:


 Public Sub rS232Comms1_DataRxEvent(ByVal sender As System.Object, ByVal e As RS232.RxEventArgs) Handles RS232Comms1.DataRxEvent

        Dim LengthOfString As Integer
        Dim buffer() As Byte = e.data
        Dim oEncoder As New System.Text.ASCIIEncoding
        Dim oEnc As System.Text.Encoding = oEncoder.GetEncoding(1252)
        Dim byteValueString As String

and so on ...

   End Sub

How can I accomplish this in VC++.NET?
>> How can I accomplish this in VC++.NET?

Should work pretty much the same way.  Drop int into the toolbox and then put it on the form.   These two lines:

        RS232Comms1.OpenComms()
        RS232Comms1.SetDTR()

will go in the constructor and look the same except with ';' at the end.

You should also be able to add an event handler though the property page.
Avatar of judico

ASKER

Couldn't make it to work with

        RS232Comms1.OpenComms();
        RS232Comms1.SetDTR();

in the constructor. The only way it works is when, after putting RS232Comms1 into the form and then exchanging all m_hComm in your code by RS232Comms (not RS232Comms1). Also,

// rS232Comms1
                    //
                    this->rS232Comms1->BaudRate = RS232::RS232Comms::eBAUDRATE::CBR_2400;
                    this->rS232Comms1->DataBits = RS232::RS232Comms::eDATABITS::DATA7;
                    this->rS232Comms1->StopBit = RS232::RS232Comms::eSTOPBIT::TWOSTOPBITS;
                    //

has to be commented out. After all that is done the program seems to work exactly the way your code works (in this case the RS232Comms1 object has to be installed, though). I thought there might be a simpler way to achieve this by avoiding the use of MultiMeter.h (in your code it's MeterLib.h), MultiMeter.cpp and Parameters.h and instead just using the RS232Comms1.dll.
My bad.  C++ will be a pointer, so it's:

RS232Comms1->OpenCOmms(); // Might be called RS232Comms rather than RS232Comms1?  Check the code it puts in the form.
RS232Comms1->SetDTR();
Avatar of judico

ASKER

Still can't make it to work. I put the above lines in the constructor of the temperature.h file:

 public __gc class temperature : public System::Windows::Forms::Form
     {    
     public:
          temperature(void)
          {          
               InitializeComponent();         

                     rS232Comms->OpenComms();
                     rS232Comms->SetDTR();

etc .

          }
       }



commented out the calls to the methods in MultiMeter.h, namely, calls to ReadString(), ConfigureSerialInterface() and Close(). Then placed the following in temperature.h:



private:
          void OnTimedEvent(Object* source, System::Timers::ElapsedEventArgs* e)
                    {
         try
               {
double dMeterReading;

DWORD dwRead = 0;
   
BYTE *pBuf = NULL;

    unsigned char __pin *buf = &m_mgdBuf[0];
    dwRead = ReadPort(buf, 27);
    buf = NULL; // We're done with pinned pointer

    if ( dwRead > 0 )
    {
                dMeterReading = System::Text::Encoding::ASCII->GetString(m_mgdBuf, 0, dwRead);
    }
    else
    {
                    dMeterReading = S"";
    }
         }

etc.

                }



as well as:



int ReadPort(unsigned char *buf, int buflen)
{
      FlushFileBuffers( RS232Comms ); // This is where it appears  
      //
    DWORD dwRead = 0;
      if ( ReadFile( RS232Comms, buf, buflen, &dwRead, NULL ) == FALSE ) dwRead = 0;
    return dwRead;
}


So this all is in temperature.h. However, obviously I'm missing something -- probably the  event handler which I don't know how to write. In VB.NET the event handler is a separate subroutine. How is this done in C++? Also, in VB.NET the data-byte 'e.data' seems to be placed in the buffer automatically after opening the port, just by declaring Dim buffer() As Byte = e.data.
What in heck is RS232Comms?  I think you are mixing paradigms here.  As I understand it fro your posts, RS232Comms is a serial port component much like the MultiMeter class I wrote.  You should only use one or the other, I think.  Can't really tell, though.  You should be able to use the MultiMeter class directly and bag RS232Comms or bag MultiMeter and use RS232Comms.

Or is RS232COmms something you wrote?  Or maybe you can zip up your whole code and I can see what's going on?
Avatar of judico

ASKER

This is something I don't understand as well. Obviouosly I'm mixing up something -- the component itself appears as rS232Comms1 (notice the small case 'r'). In the program an RS232Comms appears as well (I didn't write any of this). Probably the best thing is to send you the RS232.dll component to take a look at it. There is also an accompanying help file with a sample code in C#.  Please, tell me what e-mail address to send it to.
n_a@comcast.net
ASKER CERTIFIED SOLUTION
Avatar of drichards
drichards

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of judico

ASKER

Thank you very much for the help. There were two crucial steps in solving the problem:

1) These enums had to be undefined:

#undef CBR_2400
#undef TWOSTOPBITS

2) The handler for the DataRx event had to be properly set. It turned out it was not enough to just write:

private: System::Void rS232Comms1_DataRxEvent(System::Object *  sender, RS232::RxEventArgs *  e)
                {
                    textBox1->Text = e->data->Length.ToString();
                    textBox2->Text = System::Text::ASCIIEncoding::ASCII->GetString(e->data);
                }

In addition to the above code the handler in property sheet for the component had also to be set. I wonder if there’s any other way to set the handler through code directly in the method as in VB.NET:

Public Sub rS232Comms1_DataRxEvent(ByVal sender As System.Object, ByVal e As RS232.RxEventArgs) Handles RS232Comms1.DataRxEvent

(and not setting it up through the property sheet – of course, the property sheet adds code anyway).

Further, I enabled the timer1 object from the toolbox, set it to 100ms and placed in its tick method the code which opens the port:

private: System::Void timer1_Tick(System::Object *  sender, System::EventArgs *  e)
           {                  
                   try
                              {
                   rS232Comms1->OpenComms();
                   rS232Comms1->SetDTR();
                              }
                      catch (Exception *exception)
                              {
                              }                            
               }


and left empty

private:
          void OnTimedEvent(Object* source, System::Timers::ElapsedEventArgs* e)
                                 {                           
                            }

Processing of the data, as you suggested, occurs in:

private: System::Void rS232Comms1_DataRxEvent(System::Object *  sender, RS232::RxEventArgs *  e)
                                  {            
                                                try
                                                {
                textBox1->Text = e->data->Length.ToString();
                    textBox2->Text = System::Text::ASCIIEncoding::ASCII->GetString(e->data);
                                                }
                    catch (Exception *exception)
                                          {
                                          }

      dMeterReadingString = System::Text::ASCIIEncoding::ASCII->GetString(e->data);
                        

<Some Code ...>


                                             Invalidate();

             }

At that, again the problem with the initial zero appeared but this time it is due to the erratic data obtained after the first call. This was taken care of by starting the plot loop from value i = 2.

Otherwise, the outcome is practically the same as with your code. Especially, the output string consists again of the strange number of 27 characters as when using your code – so, it depends on how the PIC has been programmed and has nothing to do with the VC++ code.

Unfortunately, for some reason I’m not able to close the port and stop the timer when a dedicated button2 is depressed.

private: System::Void button2_Click(System::Object *  sender, System::EventArgs *  e)
             {

ExitCounter += 1;
                   if ( ExitCounter > 0 )
                   {
                   }
                   else
                   {
                         rS232Comms1->CloseComms();                   
                   }
                  
                 timer1->Enabled = false;
             }

These are minor problems, however, and I have to put some more work to fix them, if need be (if you have any idea how to fix these problems please let me know). Probably, however, I will prefer to use your solution which works pretty well and doesn’t need additional objects to accompany it. The main thing is that I was able to understand, with your help, in general, the principle of working with an object such as the rs232comms.dll.
>> I wonder if there’s any other way to set the handler through code directly in the method as in VB.NET:

Yes, all setting the handler in the property sheet is add the handler stub and a line in 'InitializeComponent':

    this->rS232Comms1->DataRxEvent += new RS232::DataReceivedHandler(this, rS232Comms1_DataRxEvent);

You can add this line manually if you want.  You have to be a little careful about manually adding code in 'InitializeCoponent', however, because sometimes the designer gets confused and you can't use the visual designer any more (unless you correct the problem!).  The line also can be placed somewhere other than InitializeComponent, but you will not get events from RS232 until this line is executed.  Best just to use the designer property sheet unless you have special requirements.
>> Unfortunately, for some reason I’m not able to close the port and stop the timer when a dedicated button2 is depressed

This is probably because you did not disable Timer1 and the serial port is reopened.  You should disable Timer1 after the first pass - it is not necessary to execute the timer more than once.  You can always re-enable the timer later if you need to reopen the serial port after closing it.
Avatar of judico

ASKER

OK, now I understand that I should use the designer property sheet to set up handlers of events.

I continue to have problems with closing the port, however. For instance, I don't understand where that X in the upper left corner of the form is created. I'd like to close the port and stop the timer accompanying the disappearance of the form upon clicking on that X. This I need because after I close that form, I get into the form with the initial logo from where I may want to enter again the form which calls the device. If the port hasn't been closed the subsequent entering the form which calls the device doesn't begin the data acquisition and hangs.
Add a handler for the form Closed or Closing event and close the serial port there.
Avatar of judico

ASKER

I added a handler. closed the serial port there and it seemed to work well. However, for unknown reason all of a sudden when rebuilding the program it started giving me the error:

error result returned from 'resgen.exe'.

What happened beats me.
Avatar of judico

ASKER

I thought I could fix that by re-installing the Visual Studio .NET (I had to do that once when using VB.NET -- the rs232comms.dll began disappearing for no apparent reason). That didn't do any good in this cse. Interestingly, as long as you don't rebuild the project the program works despite the error:

error result returned from 'resgen.exe'.

Also, I copied entirely the code from the problematic project into another project. The other project compiles fine -- no 'error result returned from 'resgen.exe'' is returned. However, the new project consisting of exactly the same code yields different results. How is that possible?
 
No idea.  Go Microsoft.

What sort of different results do you get?
Avatar of judico

ASKER

One of the projects somehow gets past the initial errors when calling the device (the first 2-3 times the device is called the obtained result is gibberish) and  the plotting begins with a non-zero value. Unfortunately, that is the code which gives me the above error. The same code pasted into another project which compiles well, regards the gibberish as data and the first couple of points are always zero. I have provided to ignore the first 3 points and now it appears to work fine. However, on top of everything the numerical results the two programs with identical code give are different. Say, one of the programs gives the value 33.6 as a numerical result while the other program with the exactly same code gives 22.9. I don't know what this is all about.
Avatar of judico

ASKER

Please take a look at this question:

https://www.experts-exchange.com/questions/21111147/Invalidate-only-part-of-the-form-in-managed-Visual-C-NET.html

I'd like to invalidate only part of the screen to avoid the flickering due to repainting of the screen. I remember you mentioned something in this respect before and told me to look into the documentation of the language. I tried but couldn't get much help.