반응형

아두이노와 외부 디바이스 또는 PC 와 RS232 통신을 하기위한 방법을 설명합니다.

제 블로그에서는 가급적 실사용 위주를 중심으로 코드를 올립니다. 큰 틀은 가져가시고 세부적인 부분만 수정하셔도 동작에 지장이 없으실 겁니다. 

하기의 코드는 실제 개발 제품에 사용했던 것으로 PC가 Master 이고 아두이노가 Slave 인 구성에서의 코드입니다. 

아두이노가 Master 가 되는 상황에서 다른 디바이스를 제어할 경우는 코드 구성이 좀 틀립니다. 

또한 하기 코드의 프로토콜에는 별도의 오류 체크를 포함하지 않았지만 통신 오류를 줄이기 위해 Check Sum 이나 CRC 등의 오류 체크를 포함할 수도 있습니다. 

#define     STX             0x02
#define     ETX             0x03
#define     BUFFER_MAX      20              // 수신버퍼 최대 용량 

const int COMM_CTRL        = 3 ;			// MAX485 RX/TX Control

volatile int Comm_Index = 0 ;					// Receive buffer Index Pointer 
volatile boolean stringComplete = false ;   	// 수신 데이타가 다 들어왔는지 확인한다.
volatile boolean serial_start = false ;

char Receive_Buffer[BUFFER_MAX] ;
char Send_Buffer[7] = {STX, 0x30, 0x31, 'I', 'O', 'K', ETX} ;	            // Serial 송신 버퍼

//--------------------------------------------------------------------------------
void setup() 
{
    Serial.begin(9600);         // set serial communication (9600 bps)

    pinMode(COMM_CTRL, OUTPUT) ;  // RS485 통신의 경우 송,수신 전환 단자제어 
    digitalWrite(COMM_CTRL, LOW);    
}
//--------------------------------------------------------------------------------
void loop() 
{
    if(stringComplete == true)      // 통신 패킷이 모두 들어왔다. 
    {
        if(Receive_Buffer[1] == 0x30 && Receive_Buffer[2] == 0x31)    // 자신의 데이타가 맞다. 
        {
            switch(Receive_Buffer[3])   // 수신된 명령어???
            {
                case 'I' : Initialize() ;       // initialize  
                           break ;                            
                case 'P' : function1() ;
                           break ;
                case 'R' : function2() ;
                           break ;
                case 'T' : function3() ;
                           break ;
                case 'S' : function4() ;
                           break ;                           
                default  : Error() ;
                           break ;
            }
            
            Send_Action_Result() ;   // 수신에 대한 응답 메시지 송신
        }
        memset(Receive_Buffer, 0x00, sizeof(Receive_Buffer)) ;  // 수신 버퍼 Clear 
        stringComplete = false ;   // 시리얼 입력에 대한 작업이 종료 되었다. 
    }
}
//--------------------------------------------------------------------------------
void Send_Action_Result(void)
{
  /****************************************
    function : 시리얼 포트로 수신결과에 대한 응답을 보낸다.
  ****************************************/
    int count ;

    digitalWrite(COMM_CTRL, HIGH)  ;    // RS485 통신칩의 송/수신 컨트롤 단자 제어용입니다. 
                                        // 경우에 따라 없을 수도 있습니다. 
    for (count = 0 ; count < 7 ; count++)
    {
        Serial.write(Send_Buffer[count]) ;    // 송신 버퍼의 내용을 시리얼로 전송합니다.
    }

    delay(10) ;		// RS485 통신의 경우 이걸 해 주어야만 컨트롤 단자가 LOW 로 떨어지지 않는다.

    digitalWrite(COMM_CTRL, LOW)  ;
}
//------------------------------------------------------------------------------------
void function1() {} 
void function2() {} 
void function3() {} 
void function4() {} 
//------------------------------------------------------------------------------------
void Error(void)
{
    Send_Buffer[3] = 'N' ;    // NAK 반송  
    Send_Buffer[4] = 'A' ;    
    Send_Buffer[5] = 'K' ;
}
//-------------------------------------------------------------------------------- 
void Initialize(void)
{
    /* 명령어 수행 루틴 작성 */

    Send_Buffer[3] = 'I' ;    // 반송 명령어 
    Send_Buffer[4] = 'O' ;    // OK : 명령을 정상적으로 수행했다. 
    Send_Buffer[5] = 'K' ;
}
//-------------------------------------------------------------------------------- 
void serialEvent()   // 시리얼 포트의 입력 인터럽트 
{
    if (stringComplete == false)
    {
        char inChar = (char)Serial.read() ;

        if (inChar == STX && serial_start == false)      // 시리얼 포트로 데이타가 들어오기 시작 
        {
            serial_start = true ;
            Comm_Index = 0 ;
        }

        Receive_Buffer[Comm_Index] = inChar ;

        if (Receive_Buffer[Comm_Index] == ETX)          // 시리얼 포트로 데이타가 모두 들어왔다.        
        {
            stringComplete = true ;
            serial_start = false ;
        }

        Comm_Index ++ ;

        if (Comm_Index >= BUFFER_MAX) Comm_Index = 0 ;
    }
}
//--------------------------------------------------------------------------------

 

반응형
반응형

장비제어 프로그램 개발시에 시스템의 하드웨어 설정값등을 저장하고 불러 오는 용도로 c++ 개발툴에서는 ini 파일 저장으로 사용하였습니다. 

 

개발언어를 C#으로 변경하고 난후에는 ini 파일보다는 json 이 좀더 좋을듯 하여 코드를 만들어 보았습니다. 

아래 코드는 Newtonsoft.Json 을 사용하였습니다. Nuget을 통해 라이브러리를 추가할 수 있습니다. 

using Newtonsoft.Json.Linq;
			
private bool saveConfigData(string FileName)
{
    bool result = true ;

    var LIVObject = new JObject();
    var jsonLIVParam = new JObject();
        jsonLIVParam.Add("Start Current", 0) ;
        jsonLIVParam.Add("Stop Current", 100) ;
        jsonLIVParam.Add("Step Current", 0.1) ;

    var jarray = new JArray();
        jarray.Add(10);
        jarray.Add(20);
        jarray.Add(100);
    jsonLIVParam.Add("Array", jarray) ;

    LIVObject.Add("LIV Test", jsonLIVParam) ;   

    string st = LIVObject.ToString() ;

    using (System.IO.StreamWriter file = new System.IO.StreamWriter(FileName, false))
    {
        file.WriteLine(st);
    }

    return result ;
}

private bool loadConfigData(string FileName)
{
    bool result = true ;
    int index = 0 ;

    string text = System.IO.File.ReadAllText(FileName);
    JObject jobj = JObject.Parse(text); //문자를 객체화

    var startCurrent = int.Parse(jobj["LIV Test"]["Start Current"].ToString()) ;
    var stopCurrent = int.Parse(jobj["LIV Test"]["Stop Current"].ToString()) ;
    var stopCurrent = float.Parse(jobj["LIV Test"]["Step Current"].ToString()) ;

    var arrayData = jobj["LIV Test"]["Array"] ;
    int[] arrayValue = new int[arrayData.Count()] ;

    foreach(int elememts in arrayData)
    {
        arrayValue[index++] = elememts ;
    }

    return result ;
}

 

위의 코드는 아래와 같은 포맷의 데이타를 저장하고 불러옵니다. 

 

//------------------------------------- JSON Format ---------------------------------------------------
{
  "LIV Test": {
    "Start Current": 0,
    "Stop Current": 100,
    "Step Current": 0.1,
    "Array": [10, 20, 100]
  }
}

반응형
반응형

C 언어에는  공용체를 이용한 Bit Field 기능이 있어서 꽤 유용하게 사용했었는데 C#에는 Bit Field 기능이 따로 없어 C#에서 제공되는 bitArray 를 이용하여 Class로 구현해 보았습니다. 

Bit Field 의 경우 입,출력 제어를 위한 I/O Board  제어용으로  요긴하게 사용이 될수 있습니다.

class Bit_Field
{
    private uint byteCount = 0;
    private BitArray bitArray  ;
    //---------------------------------------------------------------------------
    public Bit_Field(uint ByteCount)
    {
        bitArray = new BitArray((int)ByteCount * 8);
        byteCount = ByteCount;
    }
    //---------------------------------------------------------------------------
    public byte Read(uint index)
    {
        if(index >= byteCount) throw new IndexOutOfRangeException();
        else
        {
            byte[] result = new byte[byteCount];
            bitArray.CopyTo(result, 0);
            return result[index];
        }
    }
    //---------------------------------------------------------------------------
    public void Write(uint index, byte data)
    {
        if(index >= byteCount) throw new IndexOutOfRangeException();
        else
        {
            int offset = (int)index * 8 ;

            for(int loop=0 ; loop < 8 ; loop++)
            {
                bitArray[loop + offset] = (data & 0x01) == 1 ? true : false ; 
                data >>= 1;
            }
        }
    }
    //---------------------------------------------------------------------------
    public void SetAll(bool value)
    {
        bitArray.SetAll(value) ;
    }
    //---------------------------------------------------------------------------
    public bool this[int index]
    {
        set => bitArray[index] = value;
        get => bitArray[index];
    }  
}

 

사용방법은 아래와 같이 하시면 됩니다. 

 

Bit_Field bit_Field = new Bit_Field(2);  // 2* 8 = 16bit 용 데이타 

//----- 특정 비트에 값을 넣고 바이트값 불러올 경우 ------------

bit_Field[0] = true;         // 0  비트 값을 설정 
bit_Field[1] = true;         // 1  비트 값을 설정 
bit_Field[2] = true;         // 2  비트 값을 설정 
bit_Field[15] = true;        // 15 비트 값을 설정 

byte lower  = bit_Field.Read(0);   // 하위  8비트 리드 
byte higher = bit_Field.Read(1);   // 상위  8비트 리드 

결과
lower = 0x07
higher = 0x80


//--------- 바이트값을 bit field 에 넣고 값을 비트로 확인할 경우 
bit_Field.Write(0, 0x12) ;
bit_Field.Write(0, 0x34) ;

bool ch1 = bit_Field[0] ;
bool ch2 = bit_Field[1] ;
bool ch3 = bit_Field[2] ;
bool ch16 = bit_Field[15] ;
반응형
반응형

계측장비에서 측정된 데이타를 Excel 파일로 저장하기 위한 Class 구현 

using Microsoft.Office.Interop.Excel ;    
namespace ExcelControl
{
    public class CExcelControl : IDisposable
    {
        private Microsoft.Office.Interop.Excel.Application? application = null;
        private Workbook? workBook = null;
        private Worksheet? workSheet = null;
        public bool Visible { get; set; } = false ;

        public CExcelControl(bool visible)
        {
            this.Visible = visible ;
        }

        public bool Create()
        {
            bool result = true;

            try
            {
                application = new Microsoft.Office.Interop.Excel.Application();  //Excel 프로그램 실행
                application.Visible = this.Visible ;                             //Excel 화면 띄우기 옵션
                workBook = application.Workbooks.Add();
            }
            catch (Exception)
            {
                result = false;
            }

            return result;
        }

        public void addSheet(string sheetName)
        {
            if (workBook != null)
            {
                workSheet = workBook.Worksheets.Add(After: workBook.Worksheets.Item[workBook.Worksheets.Count]);
                workSheet.Name = sheetName;
            }
        }

        public bool saveExcelSheep(string fileName)
        {
            bool result = true;

            if (workBook != null)
            {
                try
                {
                    workBook.SaveAs(fileName, XlFileFormat.xlWorkbookDefault);
                }
                catch (Exception)
                {
                    result = false;
                }
            }

            return result;
        }

        public void writeSheet(Action<Worksheet> Information, string sheetName)
        {
            if (workBook != null)
            {
                workSheet = workBook.Worksheets.Item[sheetName];
                if (workSheet != null)
                {
                    Information(workSheet);
                }
            }
        }

        public void Dispose()
        {
            workBook!.Close(true);   
            application!.Quit();

            if(workBook != null) Marshal.ReleaseComObject(workBook) ;
            if(application != null) Marshal.ReleaseComObject(application) ;
        }  
    }
}

사용방법

   

private void btnOpen_Click(object sender, EventArgs e)
{
    using(CExcelControl excel = new CExcelControl(true))  // 엑셀 객체 생성
    {
        excel.Create() ;                                  //  Excel 프로그램 실행 
        excel.writeSheet(writeSheetHeader, "Sheet1") ;    // Sheet1 에 데이타 저장 
        excel.saveExcelSheep("d:\test.xlsx") ;            // Excel 파일 저장 
    }
}

private void writeSheetHeader(Worksheet workSheet)
{
    workSheet.Cells[1, 1] = "Data1";
    workSheet.Cells[1, 2] = "Data2";
    workSheet.Cells[1, 3] = "Data3"; 
}
반응형
반응형

고출력 레이저 다이오드 모듈에 사용되는  LD Bar의 NFP(Near Field Pattern) 특성을 측정하기 위하여 OpenCVSharp 을 이용한 클래스를 만들었습니다. 

 

1. public class EmitterInform : LD Bar 에 있는 한개의 Emitter 정보를 저장하기위한 클래스 입니다.

2. public class NFP_Process : Emitter 정보를 찾고 처리하기위한 클래스 입니다. 

  (1) LD Bar 에 있는 각각의 Emitter 정보를 찾는다. 

  (2) 각 Emitter 의 위치 좌표값 정보

  (3) 각 Emitter 의 Intensity (밝기) 정보 

3. Emitter 정보 Search 알고리즘 : 아래 이미지는 테스트 과정에서 임의로 화면에 표시한것입니다. 실제 알고리즘 적용 단계에서는 아래 이미지가 보이지는 않습니다. 

  (1) 카메라로 LD Bar의 광출력 영상을 촬영한다. 흑백 카메라를 사용하는 것이 좋으며 칼라 카메라를 사용했을 경우 영상을 흑백으로 변환하여야 합니다.

LD Bar를 카메라로 찍은 모습

  (2) Cv2.FindContours 함수를 사용하여 각각의 Emitter의 외곽 정보를 찾는다.  

  (3) 이렇게 찾은 개별 Emitter에 대해 Cv2.Moments 함수를 이용하여 Emitter의 중심점 좌표를 찾는다. 

빨간색 점이 각 Emitter의 중심좌표이다.

  (4) Contour 정보와 중심점 좌표를 이용하여 각 Emitter의 Mask 이미지를 생성한다. 

  (5) 생성된 Mask 이미지와 소스 이미지를 이용하여 각각의 Emitter에 대해 평균값을 계산하여 Intensity를 구한다. 

  (6) 저장된 List는 Emitter의 순서가 뒤죽박죽이기 때문에 위치의 X값을 기준으로 Sort를 해야 한다.

저장된 NFP 정보

4. 구현된 클래스

public class EmitterInform     // Emitter 정보를 저장하기 위한 클래스 
{
    public System.Drawing.Point Position { get; set; }     // Emitter 의 위치 좌표
    public double Intensity { get; set; }               // Emitter 의 밝기 

    public EmitterInform(System.Drawing.Point Position, double Intensity)
    {
        this.Position = Position;
        this.Intensity = Intensity;
    }
}
//----------------------------------------------------------------------------------------------------
public class NFP_Process
{
    private Mat sourceImage;
    private int ThresholdRangeMin = 10;
    private int ThresholdRangeMax = 255;

    //----------------------------------------------------------------------------------------------------
    public NFP_Process()
    {
        sourceImage = new Mat();
    }
    //----------------------------------------------------------------------------------------------------
    public void imageLoad(string fileName)
    {
        sourceImage = Cv2.ImRead(fileName, ImreadModes.Grayscale);
    }
    //----------------------------------------------------------------------------------------------------
    public void imageLoad(string fileName, PictureBox pictureBox)
    {
        sourceImage = Cv2.ImRead(fileName, ImreadModes.Grayscale);
        pictureBox.Image = BitmapConverter.ToBitmap(sourceImage);
    }
    //----------------------------------------------------------------------------------------------------
    public void setThresholdRange(int Min, int Max)
    {
        if (Max > Min)
        {
            if (Min > 0 || Min < 256) ThresholdRangeMin = Min;
            if (Max > 0 || Max < 256) ThresholdRangeMax = Max;
        }
    }
    //----------------------------------------------------------------------------------------------------
    public int SearchEmitters(List<EmitterInform> emittersInforms)
    {
        int Count = 0;  // emitter 갯수  
        Mat binaryImage = new Mat();

        Cv2.Threshold(sourceImage, binaryImage, ThresholdRangeMin, ThresholdRangeMax, ThresholdTypes.Binary);   // Source Image를 이진 이미지로 변환한다. 
        Cv2.FindContours(binaryImage, out OpenCvSharp.Point[][] contour, out HierarchyIndex[] hierarchy, RetrievalModes.Tree, ContourApproximationModes.ApproxSimple);  // Emitters 의 외형을 추출한다. 

        emittersInforms.Clear();

        if (contour.Length > 0)     // emitter를 정상적으로 찾았을 경우 
        {
            foreach (var emitter in contour)
            {
                Moments mmt = Cv2.Moments(emitter);     // emitter의 모멘트 정보를 추출

                int cx = (int)(mmt.M10 / mmt.M00);   // emitter 중심점의 X 좌표
                int cy = (int)(mmt.M01 / mmt.M00);   // emitter 중심점의 Y 좌표

                if (cx > 0 && cy > 0)
                {
                    Mat mask = Mat.Zeros(sourceImage.Height, sourceImage.Width, MatType.CV_8UC1);  // 마스크 이미지를 초기화 한다. 
                    Cv2.DrawContours(mask, contour, Count, Scalar.White, 2, LineTypes.Link4, hierarchy);  // Emitter 의 외형을 그린다. 
                    Cv2.FloodFill(mask, new OpenCvSharp.Point(cx, cy), Scalar.White);  // mask 이미지에서 Emitter 외형의 내부를 채운다. 

                    Scalar value = Cv2.Mean(sourceImage, mask);  // 소스 이미지에서 마스킹된 emitter의 Pixel 값들의 평균을 구한다. 

                    emittersInforms.Add(new EmitterInform(new System.Drawing.Point(cx, cy), Math.Round(value.Val0, 3)));  // emitter 정보를 저장한다. 
                    Count++;
                }
            }

            emittersInforms.Sort((InformA, InformB) => InformA.Position.X.CompareTo(InformB.Position.X));     // searching 된 emitter들을 X 좌표값을 기준으로 sorting한다. 
        }

        return Count;
    }
    //----------------------------------------------------------------------------------------------------
}

5. 사용방법 

 

private void button2_Click(object sender, EventArgs e)
{

    NFP_Process nfp_Process = new NFP_Process();    //class 객체 생성

    List<EmitterInform> emitterInforms= new List<EmitterInform>();  // Emitter 의 정보가 저장될 List
    nFP_Process.imageLoad("nfp_image.png");  // 저장된 이미지 loading

    int count = nfp_Process .SearchEmitters(emitterInforms);  // NFP search
    dataGridView1.DataSource = emitterInforms;  // 써칭된 정보를 dataGridView 에 출력 
}

 

6. 기타 

  (1)저장된 영상의 상태에 따라 ThresholdRangeMin, ThresholdRangeMax 값을 조정하면 더 정확한 정보를 추출할 수 있다,

반응형

+ Recent posts