NIM A paper

Leave a comment

A technical paper by me is published recently. https://doi.org/10.1016/j.nima.2022.166623

It descripts a program, called BoxScore, to replace the CoMPASS for the CEAN digitizer x730 series. The core is the DigitizerClass that can directly control a digitizer and read data from a digitizer. It can build events in a real time.

The development of the BoxScore was around the RAISOR for more convenient beam tuning. There are few objectives for the program

  • real-time events building
  • online particle identification and count rate extraction
  • share information via a data base
  • save data, parameters, and particle selection gates in a CERN root format

Real-time events building is the primary objective. At that time, CoMPASS was unable to build events. But the latest version of CoMPASS can build events now.

After events were built, the PID selection and the corresponding count rate must be able to extracted. This is the most important information during beam tuning.

Next, the count rates for various isotopes was extracted, this information should able to propagate to the tuning operators and also for record saving.

At the end, the beam condition and detail should be able to saved and revisit in the future.

BoxScore is far from perfect, there are many things to be improved. But it laid the foundation for future upgrade.

The program can be download at https://doi.org/10.5281/zenodo.5613251

Cheat sheet on C++

Leave a comment

Check g++ supports which C++ version

gcc -v --help 2> /dev/null | sed -n '/^ -std=([^<][^ ]+)./ {s//\1/p}'

the above command is from this stack overflow.

Pointer

int haha = 1;
//creation of pointer or "address" : Type/Class * XXX
//should view as (Type/Class *) XXX
int * address_of_haha = &haha; //address of a non-pointer (&) 
int kaka = *address_of_haha; //context of an address (*)
//pointer of an array
int jaja[5] = {1, 2, 3, 4, 5};
int * papa = jaja; //papa is the address of jaja[0]
// *papa = papa[0] = jaja[0]
// *(papa+1) = papa[1] = jaja[1]

Clone a pointer

a single object

Class * haha = new Class();
Class * kaka = new Class(*haha);

or

Class * haha = new Class();
Class * kaka = new Class();
*kaka = *haha; // only copy the haha[0], if haha is an array.

to clone a pointer array

Class * haha = new Class();
Class * kaka = NULL;
std::memcpy(kaka, haha, sizeof(haha));

cxxx vs xxx.h

  • cxxx are c++ library using namespace
  • xxx.h are c library using global variable
  • <cmath> defines the relevant symbols under the std namespace; <math.h> defines them globally.

Stack vs Heap

in a very rough sense, stack memory is

  • static memory that memory allocation happened when the contained function is called.
  • the size of the memory is known to the compiler.
  • the function is over, the memory is de-allocated.
  • memory allocation and de-allocation is faster, compare to heap memory.

these are stack memory:

int haha;
int kaka[5];

in a very rough sense, heap memory is opposite to stack. So,

  • dynamic memory, memory allocation happened when the “new” operator is called.
  • the size of the memory is not know to the compiler.
  • need manually to de-allocated.

To allocate a heap memory:

int * haha = new int[5];

Delete a pointer

whenever a pointer array is created using [], use delete []

int * haha = new int [10];
delete [] haha;

int ** kaka = new int *[10];
for(int i = 0; i < 10; i++ ) kaka[i] = new int;
for(int i = 0; i < 10; i++ ) delete kaka[i];
delete [] kaka;
 

new vs malloc

malloc() does not call the constructor. malloc() use free() to de-allocate memory. new use delete to free memory.

int * haha = (int *) malloc (sizeof(int) * 5);
free(haha); // or delete haha;
int * kaka = new int[5];
delete [] kaka;

Fitting Two parallel lines

Leave a comment

In this post, the problem is fitting parallel curves, and it should be solved using clustering. After clustering, the rest is fitting individual cluster.

the post also mentioned another method that find the minimum square and maximum number of points with cut-off threshold. The problem of the method is, I did not know any good numerical minimization method. But now, I can apply the Nelder-Mead method to this problem.


The implementation is straight forward, the tricky part is that, we have 2 quantities need to be optimized, the distance-square d^2 , and the number of points \nu, and how to judge in the case when the d^2 is smaller but \nu is larger and the d^2 is larger but \nu is smaller ?

After few trials, nu larger is more important. It is because, if only seek for minimum d^2, it could only use 1 point and discard all other point due to the threshold. So, in the sorting phase of the Nelder-Mead method, I have set the condition that

If (countPt[i] < totCount ){
   if( countPt[i] < countPt[i+1] ) swap(i,i+1);
}else{
   if( distant[i]/countPt[i] < distant[i+1]/countPt[i+1] ) swap(i, i+1);
}

So far, I did not set the termination and simple run for 1000 iteration. And here is the result.

The generated points are

for ( int i = 0 ; i < n; i++){
  x[i]  = gRandom->Rndm();
  ey[i] = gRandom->Gaus(0, 0.1);
  oy[i] = gRandom->Ineger(2);
  y[i] = x[i] + ey[i] + 1.5*oy[o] -1;
}

The fitting give the slope of 1.0182, and the two y-interceptions are 0.5139 and -1.0178.

Simple agglomerative single-linkage clustering

Leave a comment

The problem is that, there are serval parallel curves and we have to fit the curves. Or, for a set of family of cruve

\displaystyle C_i : f(x) + e + c_i

where f(x) is the function we want to fit, e is the error, c_i is the offset for each line. This is something like this:


The function I am dealing with is something is a quadratic plus some high order fluctuation, f(x) = a x (x-1)  + b O(x).

I tried many ways to “convert” the problem in to ordinary fitting. One way is using the least-square method with a threshold and maximize number of points fitted. The idea is, pick a point and compare the distance from all lines with initial guessing parameters, if the minimum distance is smaller than the threshold, it counted. By maximizing the number of points counted and minimized the total square distance via searching thought the whole parameter space, we can least-squared fit the data. However, searching the whole parameter space could be time consuming. And so far, I did not know any good way to solve this “finding minimum of an complicated function” .


Another way is clustering, since each curve is an obvious cluster in human eyes. However, how to let the computer know is the problem. I tried neural network, but I failed to do the unsupervised learning. Thus, I turn to a more conventional method — Agglomerative single-linkage clustering. This method is well demonstrated in the wiki. A dendrogram is the final product of the algorithm. From the dendrogram, we can cluster the data into many clusters.

I know that the scipy package for python has the fast algorithm, but I prefer C++, and cannot find a simple code. So, I wrote my code and show the result in here.


First, we need a distance metric, a definition of distance. I choose the Euclidean distance, which is

\displaystyle d(\vec{a}, \vec{b}) = |\vec{a} - \vec{b}|

Once we calculated the distance matrix that each element is

\displaystyle D_{ij} = d(\vec{a_i}, \vec{a_j})

Then, the smallest distance pair is found, say, (i,j) = (a, b) . Then, we need to calculated the new distance matrix that show the new distance for the cluster (a,b) to the rest of the points, and how this is done depends on single-linkage, complete-linkage, or average-linkage.

In the matrix sense, the new distance matrix will be 1-row and 1 column smaller. But in the implementation, for simplicity, keep the matrix size to be the same and simply fill the row and column with larger index with nan. in this way, the index or the id of each point with not change, and the cluster id will be the smaller id of the points in the cluster.

For example, ten points

The distance matrix is

I set the distance function be d(\vec{a},\vec{a}) = nan. The closed pair is (4,5), and the distance is 0.090961. And “4” will be the cluster id.

The new distance matrix is

We set row and column 5 to be nan. the new column and row 4 will be using single-linkage. The single-linkage is taking the minimum distances to the cluster. For complete-linkage, the distance to the cluster is the maximum distances. In the below example, the (a,b) formed a pair. The distance of the point c to pair (a,b) could be the minimum, the maximum, or the average of (a,c) and (b,c).

Back to the example, lets take (0,4) and (0, 5). This new distance will be the minimum among the two. min(0.6238, 0.5939) = 0.5939. Same procedure apply for the others.

After 9 iterations, all matrix elements becomes nan and the clustering complete. The sequence of the pair is

The corresponding dendrogram is

From the dendrogram, it is easily to divide the data points into 2 clusters.


The C++ code for CERN root is:

double distant(double x1, double y1, double x2, double y2 ){
  return sqrt( (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2)) ;
}

struct MinPos{
  double min;
  vector<int> pos;
};

MinPos FindMin(double *D, int size){

  MinPos mp;

  mp.min = 1e9;
  for( int i = 0 ; i < size; i++){
    for( int j = i ; j < size; j++){
      if( D[i*size+j] < mp.min ) {
        mp.min = D[i*size+j];
        mp.pos = {i, j};
      }
    }
  }

  if( mp.min == 1e9 ) mp.min = TMath::QuietNaN();

  return mp;
}

double * ReduceDMatrix(double *D, int size, MinPos mp){

  double * Dnew = new double [size*size];
  for( int i = 0 ; i < size; i++){
    for( int j = 0 ; j < size; j++){
      if( i == mp.pos[0] || i == mp.pos[1]) {
        Dnew[i*size+j]= TMath::Min( D[mp.pos[0]*size+j] , D[mp.pos[1]*size+j] );
      }else if( j == mp.pos[0] || j == mp.pos[1]){
        Dnew[i*size+j]= TMath::Min( D[i*size+mp.pos[0]] , D[i*size+mp.pos[1]] );
      }else{
        Dnew[i*size+j] = D[i*size+j];
      }
    }
  }
  for( int i = 0 ; i < 2 ; i++){  
    for( int j = 0 ; j < 2 ; j++){
      Dnew[mp.pos[i]*size+mp.pos[j]] = TMath::QuietNaN();
    }
  }
  for( int j = 0; j < size; j++) Dnew[mp.pos[1] * size + j ] = TMath::QuietNaN();
  for( int i = 0; i < size; i++) Dnew[i * size + mp.pos[1] ] = TMath::QuietNaN();

  return Dnew;

}

int main(){

  ///============== Generate data
  const int n = 10;
  double x[n], y[n], ey[n], oy[n];

  for( int i = 0; i < n; i++){
    x [i] = gRandom->Rndm();
    ey[i] = gRandom->Gaus(0,0.1);
    oy[i] = gRandom->Integer(2);
    y [i] = x[i] + ey[i] + 5* oy[i];    
  }

  ///================ Clustering (agglomerative, single-linkage clustering 
  double * D1 = new double [n*n];
  for( int i = 0 ; i < n; i++){
    for( int j = 0 ; j < n; j++){
      if( i ==j ) {
        D1[i*n+j] = TMath::QuietNaN();
        continue;
      }
      D1[i*n+j] = distant(x[i], y[i], x[j], y[j]);
    }
  }

vector<vector<int>> dendrogram;
  vector<double> branchLength;

  MinPos mp = FindMin(D1, n);
  dendrogram.push_back(mp.pos);
  branchLength.push_back(mp.min/2.);

  double * D2 = D1;
  int count = 2;
  do{
    D2 = ReduceDMatrix(D2, n, mp);
    mp = FindMin(D2, n);
    if( TMath::IsNaN(mp.min) ){
      break;
    }else{
      dendrogram.push_back(mp.pos);
      branchLength.push_back(mp.min/2.);
    }
    count ++;
  }while( !TMath::IsNaN(mp.min) );

  ///================= find the group 1 from the dendrogram.
  int nTree = (int) dendrogram.size() ;
  vector<int> group1;
  for( int i = nTree -1 ; i >= 0 ; i--){
    if( i == nTree-1 ){
      group1.push_back(dendrogram[i][0]);
    }else{
      int nG = (int) group1.size();
      for( int p = 0; p < nG; p++){
        if( group1[p] == dendrogram[i][0] ) {
              group1.push_back(dendrogram[i][1]);
        }
      }
    }
  }

}

Using this code, for two parallel lines,

another data set:

3j, 6j, 9j-symbol for C++

Leave a comment

Since I cannot find a odd c++ code ( < c++11 ) for the calculation of those j-symbol, I made one for myself!

The code just using the formula which you can find in wiki or here. So, the code is not optimized, but for simple calculation, I guess it does not matter. Also, I checked, in my pc, calculate 1000 9-j symbols only take 1 or 2 sec.

The program first build

  1. factorial
  2. Clebsch–Gordan coefficient
  3. 3-j symbol
  4. 6-j symbol
  5. 9-j symbol
#include <stdlib.h>
#include <cmath>
#include <algorithm>

using namespace std;

double factorial(double n){
  if( n < 0 ) return -100.;
  return (n == 1. || n == 0.) ? 1. : factorial(n-1) * n ;
}

double CGcoeff(double J, double m, double J1, double m1, double J2, double m2){
  // (J1,m1) + (J2, m2) = (J, m)

  if( m != m1 + m2 ) return 0;

  double Jmin = abs(J1 - J2);
  double Jmax = J1+J2;

  if( J < Jmin || Jmax < J ) return 0;

  double s0 = (2*J+1.) * factorial(J+J1-J2) * factorial(J-J1+J2) * factorial(J1+J2-J) / factorial(J+J1+J2 + 1.);
  s0 = sqrt(s0);

  double s = factorial(J +m ) * factorial(J -m);
  double s1 = factorial(J1+m1) * factorial(J1-m1);
  double s2 = factorial(J2+m2) * factorial(J2-m2);
  s = sqrt(s * s1 * s2);

  //printf(" s0, s = %f , %f \n", s0, s);

  int kMax = min( min( J1+J2-J, J1 - m1), J2 + m2);

  double CG = 0.;
  for( int k = 0; k <= kMax; k++){
    double k1 = factorial(J1+J2-J-k);
    double k2 = factorial(J1-m1-k);
    double k3 = factorial(J2+m2-k);
    double k4 = factorial(J - J2 + m1 +k);
    double k5 = factorial(J - J1 - m2 +k);
    double temp = pow(-1, k) / (factorial(k) * k1 * k2 * k3 * k4 * k5);
    if( k1 == -100. || k2 == -100. || k3 == -100. || k4 == -100. || k5 == -100. ) temp = 0.;

    //printf(" %d | %f \n", k, temp);
    CG += temp;
  }

  return s0*s*CG;

}

double ThreeJSymbol(double J1, double m1, double J2, double m2, double J3, double m3){

  // ( J1 J2 J3 ) = (-1)^(J1-J2 - m3)/ sqrt(2*J3+1) * CGcoeff(J3, -m3, J1, m1, J2, m2) 
  // ( m1 m2 m3 )

  return pow(-1, J1 - J2 - m3)/sqrt(2*J3+1) * CGcoeff(J3, -m3, J1, m1, J2, m2);

}

double SixJSymbol(double J1, double J2, double J3, double J4, double J5, double J6){

  // coupling of j1, j2, j3 to J-J1
  // J1 = j1
  // J2 = j2
  // J3 = j12 = j1 + j2
  // J4 = j3
  // J5 = J = j1 + j2 + j3
  // J6 = j23 = j2 + j3

  //check triangle condition
  if( J3 < abs(J1 - J2 ) || J1 + J2 < J3 ) return 0; 
  if( J6 < abs(J2 - J4 ) || J2 + J4 < J6 ) return 0;
  if( J5 < abs(J1 - J6 ) || J1 + J6 < J5 ) return 0;
  if( J5 < abs(J3 - J4 ) || J3 + J4 < J5 ) return 0;

  double sixJ = 0;

  for( float m1 = -J1; m1 <= J1 ; m1 = m1 + 1){
    for( float m2 = -J2; m2 <= J2 ; m2 = m2 + 1){
      for( float m3 = -J3; m3 <= J3 ; m3 = m3 + 1){
        for( float m4 = -J4; m4 <= J4 ; m4 = m4 + 1){
          for( float m5 = -J5; m5 <= J5 ; m5 = m5 + 1){
            for( float m6 = -J6; m6 <= J6 ; m6 = m6 + 1){

              double f = (J1 - m1) + (J2 - m2) + (J3 - m3) + (J4 - m4) + (J5 - m5) + (J6 - m6);

              double a1 = ThreeJSymbol( J1, -m1, J2, -m2, J3, -m3); // J3 = j12 
              double a2 = ThreeJSymbol( J1, m1, J5, -m5, J6, m6); // J5 = j1 + (J6 = j23)
              double a3 = ThreeJSymbol( J4, m4, J2, m2, J6, -m6); // J6 = j23
              double a4 = ThreeJSymbol( J4, -m4, J5, m5, J3, m3); // J5 = j3 + j12

              double a = a1 * a2 * a3 * a4;
              //if( a != 0 ) printf("%4.1f %4.1f %4.1f %4.1f %4.1f %4.1f | %f \n", m1, m2, m3, m4, m5, m6, a);

              sixJ += pow(-1, f) * a1 * a2 * a3 * a4;

            }
          }
        }
      }
    }
  }

  return sixJ;
}

double NineJSymbol( double J1, double J2, double J3, double J4, double J5, double J6, double J7, double J8, double J9){

  double gMin = min( min (min( abs(J1 - J2 ), abs(J4 - J5)) , abs( J4 - J6 )) , abs(J7 - J8));
  double gMax = max( max (max( J1+J2, J4+J5), J3+J6), J7+J8);

  //printf(" gMin, gMax = %f %f \n", gMin, gMax);

  double nineJ = 0;
  for( float g = gMin; g <= gMax ; g = g + 1){
    double f = pow(-1, 2*g) * (2*g+1);
    double s1 = SixJSymbol(J1, J4, J7, J8, J9, g);
    if( s1 == 0 ) continue;
    double s2 = SixJSymbol(J2, J5, J8, J4, g, J6);
    if( s2 == 0 ) continue;
    double s3 = SixJSymbol(J3, J6, J9, g, J1, J2);
    if( s3 == 0 ) continue;
    nineJ += f* s1*s2*s3;
  }

  return nineJ;
}

screen capture for fixed area in Mac

Leave a comment

The build-in screen capture shortcut shift-command-4 is cool. But human is just not reliable. For nice presentation, I searched a while, in Mac, there is a terminal command

screencapture -c -R x,y,w,h

 

where x, y, w ,h are x-position, y-position, width and heigh respectively. the option -c is for capture to clipboard.

 

Android program for nuclear physics

Leave a comment

I wrote an android program to get the mass, separation energy of given isotope. The mass is taken from mass16.txt (https://www-nds.iaea.org/amdc/). The program can also so conversion between some kinematics parameters, such as from KE to momentum, to Lorentz beta, to TOF by given length, to Brho value.

The program also can calculate the kinematics of transfer reaction.

I am not sure how to upload to google play store. So I upload the apk file in here.

Here are two screen shots

Screenshot_20180128-203400.png

Screenshot_20180128-203330.png

Qt Script Engine

Leave a comment

In order to use “any” function for fitting algorithm, the Qt script engine is a very powerful tool. And the basic usage is very simple.

QScriptEngine engine;
QString function = "p1*x + pow(x,2)";
double p1 = 3.2;
double x = 5;
engine.globalObject().setProperty("p1", p1);
engine.globalObject().setProperty("x", x);
ans = engine.evaluate(function_str).toNumber();

 

The above code will evaluate the expression p_1 x + x^2 . Although the running time will be a bit slower then hard code, the advantage is that, the expression can be read from a file.

Qt source code for controlling Omron E5CC

2 Comments

These 2 weeks I was fiighting with the manual and try to control the device.

In my application, I need to change the temperature super slowly, not jump from 1 point to the other. I heard that there is an internal function called SP-ramp, that seem to fit my purpose, but I don’t really understand the manual….

The program can control most basic functions, record temperature, set the change rate of SP, limit the output (MV).

However, I don’t know how to get the RUN/STOP and AT (Auto-tune) status, so be careful.

The source code is in Github, feel free to download and modify. The program use Modbus for communication, so, you need to set the device.

https://github.com/TripletESR/Omron_PID

I also made a simplified manual, but I do not post in here for copyright issue. If you want, leave a massage.

Omron E5CC Modbus RTU in QT 101

Leave a comment

The Omron E5CC temperature PID controller is using modbus RTU(Remote Terminal Unit) to connect.

First, you prepare a USB-to-RS-485 cable. Connect it to the E5CC. Notice that the A port of the E5CC, should be connected to  negative of the RS-485. Don’t follow the E5CC manual. Also the programing manual is extremely for expert of modbus, not for beginner.


In Modbus, the idea is that, your PC (master) send signal over the connected devices (slaves). The signal is a Hex, contained few things

DeviceID + Function-Code + Address + Command-in-Hex + CRC-16

The master send a signal, only the device with matching DeviceID will respond to the signal. Thus, there is no “locking” that link the master-slave.

The Function-Code is modbus function code, it tell the type of the signal. The type can be reading, writing, diagnostic, etc. This function is part of the modbus protocol, which is common for all devices.

The Address is the memory address stored in the device. The address should be provided by the device manufacture.

Then the command-in-Hex follow.

At the end, a 4-digit Hec CRC-16 is used for error check. Qt can calculate CRC-16 for modbus.


Read single value

In the Omron E5CC programing manual. Section 4-4-1, we see that the data structure of a read signal. It is read because the Function-Code is 0x03, which is  “Read Holding Registers” in modbus RTU protocal. For example, reading the temperature (PV) of E5CC,

01 03 00 00 00 02 C4 0B

0x01 is deviceID, this can be set in the E5CC.

0x03 is the function code

0x0000 is the address

0x0002 is number of value we are going to read. In modbus, one value contains 4-digit Hex, or 0xHHHH. In E5CC, each parameter is stored as 8-digit Hex, or 0xHHHHHHHH. Thus the E5CC manual tell us to get 2 values for temperature reading.

0xC408 is the CRC-16

The device return

01 03 04 00 00 03 E8 FA 8D

04 is the number of 8-digit Bin (2-digit Hex = 8-digit Bin). Four of 8-digit Bin = 8-digit Hex.

00 00 03 E8 is the return value of the temperature, converted to DEC is 1000, which is 100.0 ºC.


Write single value

Run/Stop command

01 06 00 00 01 01 49 9A

0x06 is the “Write Single Register” function-Code

0x0000 is the address

0x0101 is the value that start the run/stop for E5CC.

When writing single value in E5CC, only address 0x0000 is allowed. Thus, to write the SV (set value, or SP = Set Point) we have to use write multiple value. (stupid….)


Write multiple value

The set the alarm upper and lower values

01 10 01 0A 00 04 08 00 00 03 E8 FF FF FC 18 8D E9

0x10 is the “Write Multiple Coils”

0x010A is the address of the alarm upper value

0x0004  is the 2 times number of value to be written. In our case, we want to write 2 value, thus the value is 0x4. The address of the next value would be 0x010A + 0x0002 = 0x010C. Since all value use 2 memory slots.

0x08 is the length of the input value. There are 2 value, each value is four 4-digit-Hex, so the input is 0x08.

0x000003E8 is the first value

0xFFFFFC18 is the 2nd value

This structure can also be use to write single value. If we want to change the SV (or Set Point) to 100 ºC. The address is 0x0106.

01 10 01 06 00 02 04 00 00 00 64


In Qt, there is an example for modus, call master. We can use it for simple I/O.

Here is a screenshot.

master_1.PNG

First, we go to Tools to setup the Setting

master_2.PNG

In the E5CC, you have to set the communication mod to be modbus. In modbus, the parity, data bits, and stop bits are no used.

Since we use USB, this is serial port. The port number can be found using

const auto infos = QSerialPortInfo::availablePorts();
for (const QSerialPortInfo &info : infos) {
        qDebug("PortName     ="+info.portName())
        qDebug() <<"description  =" << (!info.description().isEmpty() ?  info.description() : blankString);
        qDebug() <<"manufacturer =" << (!info.manufacturer().isEmpty() ? info.manufacturer() : blankString);
        qDebug() <<"serialNumber =" << (!info.serialNumber().isEmpty() ? info.serialNumber() : blankString);
        qDebug() <<"Location     =" << info.systemLocation();
        qDebug() <<"Vendor       =" << (info.vendorIdentifier() ? QString::number(info.vendorIdentifier(), 16) : blankString);
        qDebug() <<"Identifier   =  << (info.productIdentifier() ? QString::number(info.productIdentifier(), 16) : blankString);
}

In my case, it is COM4, so I put COM4 in the port. Then connect.

The read the temperature. We set the Table (at the lower left corner) to be Holding Register. In the Read (left panel), Start Address is 0, and Number of values to be 2. In the Qt application output, we will see:

qt.modbus: (RTU client) Sent Serial PDU: 0x0300000002
qt.modbus.lowlevel: (RTU client) Sent Serial ADU: 0x010300000002c40b
qt.modbus: (RTU client) Send successful: 0x0300000002
qt.modbus.lowlevel: (RTU client) Response buffer: “010304”
qt.modbus: (RTU client) Incomplete ADU received, ignoring
qt.modbus.lowlevel: (RTU client) Response buffer: “0103040000001cfbfa”
qt.modbus: (RTU client) Received ADU: “0103040000001cfbfa”

The PDU = Protocol Data Unit, ADU = Accessing Data Unit = DeviceID + PDU + CRC-16. In the last line, the received PDU is 0x03040000001c, the value is 0x0000 and 0x001c=28, which is 18 ºC.

The display in the GUI is:

master_3.PNG

Now, we try to stop the device.

In the write panel, Start address is 0x0000, and according to the E5CC programing manual, the value consist of two part

AA BB

AA is the command code

BB is the value

From the manual, the command code for RUN/STOP, 0xAA=0x01, and for STOP 0xBB=0x01. Thus, the value is 0x0101. Then we press write.

master_4.PNG

The Qt application output is

qt.modbus: (RTU client) Sent Serial PDU: 0x0600000101
qt.modbus.lowlevel: (RTU client) Sent Serial ADU: 0x010600000101499a
qt.modbus: (RTU client) Send successful: 0x0600000101
qt.modbus.lowlevel: (RTU client) Response buffer: “01”
qt.modbus: (RTU client) Modbus ADU not complete
qt.modbus.lowlevel: (RTU client) Response buffer: “010600000101499a”
qt.modbus: (RTU client) Received ADU: “010600000101499a”

And we can see there is a STOP display on the E5CC.

We can see, although the Holding Register is the same, in read, the function code is 0x03, in write, the function code is 0x06. In Qt manual, the QModBusPdu Class, we can see a list of Function-Code. (https://doc.qt.io/qt-5/qmodbuspdu.html#FunctionCode-enum)


This program is limited 10 address, so the operation is limited. This introduction is the basic, I think user can can study the Qt code to know how to use modbus, how to read and write signal. And people can read this website for another modbus 101.

Older Entries