Tổng Hợp

Modbus (Phần 2) – Chế độ Slave

[Series Hướng dẫn lập trình Modbus cho vi điều khiển]

Lâu lâu mình lại bận bịu vài tháng, thậm chí cả năm mới quay lại viết lách.
Phần 1 thì đã có từ lâu rồi và rất nhiều bạn đang hóng phần 2 để dò xét đúng sai ?

Phần này mình sẽ giới thiệu cho các bạn 1 thư viện miễn phí và đầy đủ, đó là: FreeModbus, các bạn truy cập link dưới để tải về:
https://sourceforge.net/projects/freemodbus.berlios/

Trước kia khi mình viết phần 1 thì vẫn còn trang chủ freemodbus.org để truy cập và tìm tài liệu, nhưng giờ trang đấy đã bị điều hướng sang 1 trang khác và mình không tìm thấy mục tải cũng như tài liệu. có vẻ tên miền đó đã bị mua lại. Nhưng không sao, các bạn tải trên link trên là được.

Hình 1. Thư viện modbus khi tải về và giải nén

Trong mục “demo” sẽ có vị dụ demo về cách sử dụng modbus cho các nền tảng khác nhau như ATMEGA, STM32, MPS430, hệ điều hành window, hệ điều hành linux…vân vân và mây mây.
Thư mục “modbus” chứa tất cả code modbus ở đó:

Hình 2. Phân loại các file c theo chức năng của modbus

+ Thư mục “ascii” chức các file *.c phục vụ chạy modbus – ascii
+ Thư mục “rtu” chứa các file *.c phục chạy modbus – rtu
+ Thư mục “tcp” chức các file *.c phục vụ chạy modbus tcp
+ Thư mục “functions” chứa các file *.c thực hiện các lệnh trong modbus ví dụ: Read coils, Write Coils, ReadHolding, WriteHolding ..v.v..
+ Thư mục “include” chứa tất cả các file *.h, nếu bạn sử dụng keilC thì trỏ đường dẫn đến thư mục này

Nếu sử dụng modbus – ascii thì không cần thiết thêm các file thuộc thư mục rtu hoặc tcp vào project, nếu bạn muốn thêm thì cũng chả sao.

Hình 3. Các file được thêm vào project để phục vụ modbus

Trong đống file trên bạn sẽ cần sửa 2 file rất quan trọng, đó là file “portserial.c” và file “porttimer.c”, bởi vì 2 file này là 2 file dùng để giao tiếp giữa thư viện modbus và con chip mà bạn đang lập trình. Ứng với mỗi chip khác nhau thì 2 file trên sẽ có nội dung khác nhau. Do đó bạn sẽ phải tự viết 2 file này . Đừng lo, mình sẽ hướng dẫn. Phần này mình sẽ chạy ở chế độ Slave nên các file sẽ chỉnh sửa để chạy theo kiểu slave.

portserial.c

portserial.c là file giúp thư viện modbus điều khiển được việc giao tiếp UART/USART trên chip của bạn. Bạn cần phải sửa hết nội dung của file này, cụ thể là các hàm sau:

+ void vMBPortSerialEnable

Hàm này dùng để bật/tắt ngắt USART. Hàm này rất quan trọng, bởi vì nó điều phối hoạt động gửi/ nhận dữ liệu cho modbus. Trên đường truyền modbus thì hoạt động gửi / nhận sẽ diễn ra luân phiên. Hàm này sẽ được dùng thường xuyên để bật/ tắt 1 trong 2 chức năng truyền và nhận.
Và 1 điều RẤT RẤT quan trọng về hàm này là bạn phải set cờ ngắt truyền lên mức 1 để đảm bảo chương trình sẽ nhảy vào ngắt truyền ngay lập tức khi Enable chức năng truyền lên. Vì sao ? vì hoạt động truyền dữ liệu được điều khiển hoàn toàn bởi ngắt truyền, nếu lần ngắt đầu tiên không hoạt động thì sẽ không diễn ra việc gửi dữ liệu. Và modbus đương nhiên không hoạt động. Bạn vẫn đang khó hiểu phải không ? Hãy tưởng tượng bạn có 1 mảng dữ liệu cần gửi đi, cách thông thường là bạn gửi từng byte trong mảng đấy và chờ từng byte trong mảng đấy gửi xong rồi mới gửi byte mới và tiếp tục cho đến hết. Như vậy bạn sẽ mất thời gian ở việc chờ gửi byte, và nếu số lượng byte lớn thì việc chờ sẽ càng lâu, mà việc chờ sẽ khiến chương trình chạy chậm lại. Rất là không hợp lý. Do đó các nhà phát triển ứng dụng FreeModbus sử dụng ngắt truyền để việc gửi dữ liệu diễn ra tự động, không phải chờ, chương trình không bị chạy chậm lại. Bằng cách: mỗi lần ngắt truyền sẽ gửi 1 byte, sau khi byte đó được gửi xong, hệ thống sẽ tạo ra ngắt, và ngắt đó lại gửi byte khác cho đến hết. Cho nên tôi mới lưu ý quan trọng là : SAU khi Enable chức năng truyền thì phải đảm bảo có ngắt truyền diễn ra, vì không có phát ngắt truyền đó thì việc truyền dữ liệu sẽ không thể diễn ra được.

Ví dụ về đoạn code trên stm32 như sau:

void
 vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable ) // Hàm mở Serial
 {
     ENTER_CRITICAL_SECTION(  );    
     // Enable/Disable RX
    if (xRxEnable == TRUE)
        USART_ITConfig(EMB_COM, USART_IT_RXNE, ENABLE);
    else USART_ITConfig(EMB_COM, USART_IT_RXNE, DISABLE);

    // Enable/Disable TX
    if (xTxEnable == TRUE)
        USART_ITConfig(EMB_COM, USART_IT_TXE, ENABLE);
    else USART_ITConfig(EMB_COM, USART_IT_TXE, DISABLE);

    EXIT_CRITICAL_SECTION(  );
}
 //

Ở STM32, câu lệnh USART_ITconfig(EMB_COM, USART_IT_TXE, ENABLE) sẽ mở chức năng ngắt truyền và đồng thời sét cờ ngắt để nhảy vào hàm ngắt truyền luôn.
Còn lệnh USART_ITconfig(EMG_COM, USART_IT_RXNE, ENABLE) là mở chức năng ngắt nhận.

+ BOOL xMBPortSerialInit

Hàm xMBPortSerialInit dùng để khởi tạo chức năng USART cho chip, cái này thì ai cũng biết rồi, cái này tùy bạn cấu hình thôi, miễn là bật được USART lên để thằng FreeModbus sử dụng được.

+ BOOL xMBPortSerialPutByte( CHAR ucByte )

hàm xMBPortSerialPutByte dùng để gửi 1 byte qua USART, cái này mỗi chip 1 khác nên bạn tự viết. Nhưng phải nhớ 1 điều rằng: KHÔNG ĐƯỢC CHỜ GỬI BYTE XONG
Tại vì đã dùng ngắt để tối ưu chỗ chờ gửi byte rồi
Ví dụ với STM32:

BOOL xMBPortSerialPutByte( CHAR ucByte ) // Hàm gửi đi 1 byte
 {
     USART_SendData(EMB_COM,ucByte); // Chỉ gửi, không chờ gửi xong
     return TRUE;
 }

+ BOOL xMBPortSerialGetByte

Hàm xMBPortSerialGetByte dùng để đọc 1 byte nhận về từ USART, bạn tự sửa theo chip của bạn. Hàm này thì dễ rồi, từng làm với USART thì ai cũng biết rồi.
Ví dụ với STM32:

BOOL xMBPortSerialGetByte( CHAR * pucByte ) // Hàm nhận về 1 byte
 {
     *pucByte = USART_ReceiveData(EMB_COM);
     return TRUE;
 }
 //

Hàm ngắt nhận dữ liệu

Bạn cần viết 1 hàm ngắt nhận dữ liệu từ USART, bạn nào chưa hiểu hàm ngắt nhận là gì thì vui lòng học USART trước nha. Trong hàm ngắt nhận USART thì bạn gọi hàm pxMBFrameCBByteReceived( ) vào.
Ví dụ:

void EMB_RECEIVED_ISR(void) // Hàm ngắt nhận USART
 {
         pxMBFrameCBByteReceived(  );
  }

Hàm ngắt truyền dữ liệu

Tiếp theo, bạn cần viết 1 hàm ngắt truyền USART, hàm ngắt truyền sẽ xảy ra khi gửi xong 1 byte qua USART, hoặc xảy ra khi vừa ENABLE chức năng ngắt truyền. Cái này thì bạn tự viết theo chip mà bạn đang sử dụng. Miễn là trong hàm ngắt truyền bạn gọi hàm pxMBFrameCBTransmitterEmpty( ).
Ví dụ:

 void EMB_TRANSMITTED_ISR(void) // Hàm ngắt truyền USART
 {
         pxMBFrameCBTransmitterEmpty(  );
  } 

Thế đó, file portserial.c này sửa có vậy thôi, Thật ra bạn đọc chỗ trên này chỉ để hiểu thôi, chứ bạn có thể lên internet tìm file đó, vì trên internet có gần hết rồi, chỉ cần tìm từ khóa “portserial freemodbus với chip xxx”. xxx là tên chip của bạn.
Hoặc là bạn vào thư mục “demo” mà bạn có sau khi giải nén thư viện freemodbus. Tìm trong đống demo đó khả năng cao sẽ có file cho đúng loại chip mà bạn cần. Tiếp theo mình sẽ hướng dẫn cách sửa file “porttimer.c”

porttimer.c

file này để làm gì ?
File này dùng để cấu hình 1 timer mà freemodbus dùng để xác định khi nào nhận được đầy đủ 1 lệnh (command) qua serial.
Vậy, Tại sao lại có thể phát hiện đã nhận hết lệnh qua USART bằng TIMER ? WTH?
Hãy nhìn bức ảnh dưới này, thể hiện các lệnh được gửi nối tiếp nhau qua serial.

Xem Thêm :   Những bài thơ chúc mừng sinh nhật hay hài hước và bá đạo nhất

Xem thêm :  Bánh cuốn cao bằng – món ngon “khó cưỡng” miền sơn cước

Hình 4. Các lệnh được gửi lần lượt qua serial

Xem hình 4. Các lệnh command1, command2, command3, command4 được gửi lần lượt qua serial. Khoảng thời gian giữa các command gọi là T2, còn khoảng thời gian giữa các byte cùng 1 command là T1. Dễ thấy rằng thời gian rảnh rỗi giữa các lệnh lớn hơn thời gian rảnh rỗi giữa 2 byte cùng 1 lệnh. Do đó freemodbus sử dụng timer để phát hiện khi nào nhận được byte cuối cùng của 1 lệnh nào đó.
Cụ thể, khi có ngắt nhận được 1 byte nào đó, thì bật timer lên, nếu có lần ngắt nhận byte tiếp theo trước khi có ngắt timer thì chứng tỏ byte mới nhận chưa phải byte cuối cùng. Còn nếu có ngắt timer xảy ra mà chưa có ngắt nhận byte tiếp thì chứng tỏ byte này là byte cuối cùng của command.
Để hoạt động đúng thì thời gian từ lúc bật timer đến lúc xảy ra ngắt timer không được lớn hơn T2.

Sau đây là phần hướng dẫn cấu hình, chúng ta sẽ cần phải sửa hàm cấu hình timer, để đạt được thời gian tràn hợp lý nhất

+ BOOL xMBPortTimersInit( USHORT TimerFrq )

Hàm xMBPortTimersInit dùng để khởi tạo Timer, hàm này mình đã sửa lại nên mình để tham số đầu vào là tần số Timer cho tiện. Vậy, nên để tần số ngắt timer bao nhiêu ? Trước tiên ta cần biết thời gian để gửi 1 byte qua serial là bao nhiêu?
Tôi gọi thời gian để gửi 1 byte qua serial (TB) = 8/ Baudrate (giây)

Ví dụ Baudrate = 19200 (bps) thì TB = 8/19200 = 416.6 (micro giây)
Ta đã tính được TB rồi, vậy câu hỏi bây giờ là cấu hình tần số ngắt của timer là bao nhiêu ?

Freemodbus khuyến cáo chúng ta nên để khoảng thời gian từ lúc bật timer cho đến khi timer tràn (Ti) = 3.5 * TB.

Vậy ở đây bạn cấu hình làm sao cho Ti = 3.5* TB là được. TB thì bạn biết lấy đâu ra rồi đó.
Ví dụ, code cho STM32:

BOOL xMBPortTimersInit( USHORT TimerFrq ) // khởi tạo Timer có tần số ngắt 
 {
   RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE);
/* Time base configuration */
   TIM_TimeBaseStructure.TIM_Prescaler = ((SystemCoreClock/2)/TimerFrq)-1;     // frequency = Usart_baudrate/3.5
   TIM_TimeBaseStructure.TIM_Period = 10 - 1;    // 
   TIM_TimeBaseStructure.TIM_ClockDivision = 0;
   TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
   TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure);
 //  TIM_ITConfig(TIM5,TIM_IT_Update,ENABLE);
 //  TIM_Cmd(TIM5, ENABLE);
   NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
   NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn;
   NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
   NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
   NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
   NVIC_Init(&NVIC_InitStructure);   
return TRUE;
}
 //

+ void vMBPortTimersEnable( void )

Hàm vMBPortTimersEnable dùng để bật chức năng ngắt timer, bạn viết làm sao để khi gọi hàm này thì chức năng ngắt timer được bật là được. Mỗi khi bật chức năng ngắt timer thì bạn nhớ reset couter trước để đảm bảo đúng thời gian ngắt.
ví dụ với STM32:

void vMBPortTimersEnable( void )
{
    TIM_ITConfig(TIM5,TIM_IT_Update,ENABLE);
    TIM_Cmd(TIM5, ENABLE);
}

void vMBPortTimersDisable( void )

Hàm vMBPortTimersDisable dùng để tắt tính năng ngắt timer, bạn viết làm sao cho khi gọi hàm này thì timer sẽ không thể ngắt được nữa là được.
Ví dụ với STM32:

void vMBPortTimersDisable( void )
 {
     TIM_Cmd(TIM5, DISABLE);
     TIM_ITConfig(TIM5,TIM_IT_Update,DISABLE);
 }  

Hàm ngắt timer

Cấu hình timer từ nãy rồi, vậy thì phải có hàm ngắt timer chứ nhỉ, trong hàm ngắt timer của bạn phải gọi hàm pxMBPortCBTimerExpired( ) mới được.
Ví dụ:

void Modbus_Timer_ISR(void)
{
     pxMBPortCBTimerExpired(  ) ;
    ClearTimerFlag();
} 

Vậy là xong phần sửa 2 file “portserial.c” và “porttimer.c”, chúc mừng các bạn đã đọc đến đây. Nghĩ thôi cũng thấy oải rồi. Nhưng chương trình vẫn còn tiếp !

ModbusUser.c

Tiếp theo bạn cần tạo 1 file ModbusUser.c vào project của bạn và copy đoạn code bên dưới này vào. Phần code này là do mình viết để sử dụng được thư viện modbus. LƯU Ý RẰNG: FILE này chỉ cần khi sử dụng modbus ở chế độ SLAVE. Tức cài đặt thiết bị này là Slave.

/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"
#include "port.h"
#include "mbconfig.h"
#include <string.h>
#include <stdarg.h>
#include "mbutils.h"

/* Important Note
 * If test modbus with ModbusPoll, ModbusSlave Software. You need tick to check box "PLC Address (Base1)"
 * and the following REG_START_ADDRESS is not < 1
*/



/* ----------------------- Static variables ---------------------------------*/

 USHORT   usRegInputStart = REG_INPUT_START;
 USHORT   usRegInputBuf[REG_INPUT_NREGS];							// Mảng 16bit lưu giá trị của Register Input (Read Only)

 USHORT   usRegHoldingStart = REG_HOLDING_START;
 USHORT   usRegHoldingBuf[REG_HOLDING_NREGS];					// Mảng 16bit lưu giá trị của Register Holding (Read/Write)

 USHORT		usCoilStart = REG_COIL_START;
 UCHAR 	usCoilBuf[(REG_COIL_NREGS/8)+1];									// Mảng 1 bit lưu giá trị của Output Coil  (Read/Write )

 USHORT 	usDescreteStart = REG_DESCRETES_INPUT_START;
 UCHAR 	usDescreteBuf[(REG_DESCRETES_INPUT_NREGS / 8)+1];		// Mảng 1bit  lưu giá trị của Descretes Input (Read Only)

static UCHAR Vendor[3] = "MCL";

/* ------------------------ Function defination --------------------------------*/



/*
 * UseModbus()
 * This function Ininitialize Modbus Protocol and Enable it
 * Define Use ASCII or RTU in "mbconfig.h"
*/
eMBErrorCode UseModbus(void)
{
	#if USE_INIT_DEBUG > 0
	S_DBG("rnInit HMI Modbus");
	#endif
	
		eMBErrorCode    eStatus;
		/* Initialize Protocol Stack. */

		
		#if MB_ASCII_ENABLED > 0
		/* ------- Initialize modbus ASCII------ */
		if( ( eStatus = eMBInit( MB_ASCII, 0x01, (UCHAR)0, 115200, MB_PAR_NONE ) ) != MB_ENOERR ) // 0xAA = 170
		{
			#ifdef USE_DEBUG_MB
			ShowMBErrStatus(eStatus);
			#endif
			return eStatus;
		}
		#endif
	
		
		// Lưu ý đối với Modbus RTU : Dùng modbus RTU dễ bị nhiễu, nhận sai dữ liệu, vì vậy phần cứng phải ổn định
		// Nên sử dụng các chân Hardware handshake controll để điều khiển luồng dữ liệu
		#if MB_RTU_ENABLED >0 
		/* ---------- Initialize Modbus RTU -------- */
		if( ( eStatus = eMBInit( MB_RTU, 0x01, (UCHAR)0, 921600, MB_PAR_NONE ) ) != MB_ENOERR ) // Slave Address = 0x01
		{
			#ifdef USE_DEBUG_MB
			ShowMBErrStatus(eStatus);
			#endif
			return eStatus;
		}
		#endif
		
		/* ----- Enable Modbus ---- */
		if( ( eStatus = eMBEnable(  ) ) != MB_ENOERR )
		{
			#ifdef USE_DEBUG_MB
			ShowMBErrStatus(eStatus);
			#endif
			return eStatus;
		}
		
		/* ---- Set Slave ID ---- */
		if ((eStatus = eMBSetSlaveID(0x01,TRUE,Vendor,sizeof(Vendor))) != MB_ENOERR)
		{
			#ifdef USE_DEBUG_MB
			ShowMBErrStatus(eStatus);
			#endif
			return eStatus;
		}
		return eStatus;
}

//


//

#ifdef USE_DEBUG_MB

void ShowMBErrStatus(		eMBErrorCode    eStatus)
{
		switch(eStatus)
		{
			case MB_ENOERR:
				usart_puts("MB: No error");
				break;
			case MB_ENOREG:
				usart_puts("MB: illegal reg address");
				break;
			case MB_EINVAL:
				usart_puts("MB: illegal argument");
				break;
			case MB_EPORTERR:
				usart_puts("MB: porting layer error");
				break;
			case MB_ENORES:
				usart_puts("MB: insufficient resources");
				break;
			case MB_EIO:
				usart_puts("MB: I/O error");
				break;
			case MB_EILLSTATE:
				usart_puts("MB: protocol stack in illegal state");
				break;
			case MB_ETIMEDOUT:
				usart_puts("MB: timeout error occurred");
				break;

			default: 
				usart_puts("Err: Unknown");
				break;
		}
}
#endif
//


#define REGION_MB_REG_PROCESS
#ifdef REGION_MB_REG_PROCESS
eMBErrorCode
eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{
    eMBErrorCode    eStatus = MB_ENOERR;
    int             iRegIndex;

    if( ( usAddress >= REG_INPUT_START )
        && ( usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS ) )
    {
        iRegIndex = ( int )( usAddress - usRegInputStart );
        while( usNRegs > 0 )
        {
            *pucRegBuffer++ = ( unsigned char )( usRegInputBuf[iRegIndex] >> 8 );
            *pucRegBuffer++ = ( unsigned char )( usRegInputBuf[iRegIndex] & 0xFF );
            iRegIndex++;
            usNRegs--;
        }
    }
    else
    {
        eStatus = MB_ENOREG;
    }

    return eStatus;
}
//



eMBErrorCode
eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode )
{
    eMBErrorCode    eStatus = MB_ENOERR;
    int             iRegIndex;

    if( ( usAddress >= REG_HOLDING_START ) &&
        ( usAddress + usNRegs <= REG_HOLDING_START + REG_HOLDING_NREGS ) )
    {
        iRegIndex = ( int )( usAddress - usRegHoldingStart );
        switch ( eMode )
        {
            /* Pass current register values to the protocol stack. */
        case MB_REG_READ:
            while( usNRegs > 0 )
            {
                *pucRegBuffer++ = ( UCHAR )( usRegHoldingBuf[iRegIndex] >> 8 );
                *pucRegBuffer++ = ( UCHAR )( usRegHoldingBuf[iRegIndex] & 0xFF );
                iRegIndex++;
                usNRegs--;
            }
            break;

            /* Update current register values with new values from the
             * protocol stack. */
        case MB_REG_WRITE:
            while( usNRegs > 0 )
            {
                usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8;
                usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;
                iRegIndex++;
                usNRegs--;
            }
        }
    }
    else
    {
        eStatus = MB_ENOREG;
    }
    return eStatus;
}
//




/*
 * Hàm eMBRegCoilsCB
 * Viết bởi facebook.com/microcontroler, copy từ hàm eMBRegHoldingCB rồi chỉnh sửa
 * do cùng đặc điểm là có khả năng cả ghi cả đọc
 * Hàm này tác động trực tiếp vào mảng dữ liệu usCoilBuf[]
*/

eMBErrorCode
eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode )
{
   eMBErrorCode    eStatus = MB_ENOERR;
   int             iRegIndex;

   if( ( usAddress >= REG_COIL_START ) &&
       ( usAddress + usNCoils <= REG_COIL_START + REG_COIL_NREGS ) )
   {
       iRegIndex = ( int )( usAddress - usCoilStart );
       switch ( eMode )
       {
           /* Pass current register values to the protocol stack. */
       case MB_REG_READ:
           while( usNCoils > 0 )
           {
							UCHAR ucResult = xMBUtilGetBits( usCoilBuf, iRegIndex, 1 );
						 
							xMBUtilSetBits( pucRegBuffer, iRegIndex - ( usAddress - REG_COIL_START ), 1, ucResult );

//               *pucRegBuffer++ = ( UCHAR ) ( usCoilBuf[iRegIndex] >> 8 );
//               *pucRegBuffer++ = ( UCHAR ) ( usCoilBuf[iRegIndex] & 0xFF );
               iRegIndex++;
               usNCoils--;
           }
           break;

           /* Update current register values with new values from the
            * protocol stack. */
       case MB_REG_WRITE:
           while( usNCoils > 0 )
           {
								UCHAR ucResult = xMBUtilGetBits( pucRegBuffer, iRegIndex - ( usAddress - REG_COIL_START ), 1 );
								xMBUtilSetBits( usCoilBuf, iRegIndex, 1, ucResult );
//               usCoilBuf[iRegIndex] = *pucRegBuffer++ << 8;
//               usCoilBuf[iRegIndex] |= *pucRegBuffer++;
               iRegIndex++;
               usNCoils--;
           }
       }
   }
   else
   {
       eStatus = MB_ENOREG;
   }
   return eStatus;	 
}
//



/*
 * Hàm eMBRegDiscreteCB
 * Hàm này thao tác với chức năng đọc Descretes Input
 * Write: facebook.com/microcontroler
 * Copy từ hàm eMBRegInputCB do cùng đặc điểm là thanh ghi chỉ đọc
 * Hàm này tác động trực tiếp vào mảng usDescreteBuf[]
*/

eMBErrorCode
eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
   eMBErrorCode    eStatus = MB_ENOERR;
   int             iRegIndex;

//   usRegInputBuf[0] = (GPIOD->ODR & 0xF000);
//   usRegInputBuf[0] = usRegInputBuf[0] >> 12;
//   usRegInputBuf[1] = usRegInputBuf[0];

   if( ( usAddress >= REG_DESCRETES_INPUT_START )
       && ( usAddress + usNDiscrete <= REG_DESCRETES_INPUT_START + REG_DESCRETES_INPUT_NREGS) )
   {
       iRegIndex = ( int )( usAddress - usDescreteStart );
       while( usNDiscrete > 0 )
       {
						UCHAR ucResult = xMBUtilGetBits( usDescreteBuf, iRegIndex, 1 );
						 
						xMBUtilSetBits( pucRegBuffer, iRegIndex - ( usAddress - usDescreteStart ), 1, ucResult );
//           *pucRegBuffer++ = ( unsigned char )( usDescreteBuf[iRegIndex] >> 8 );
//           *pucRegBuffer++ = ( unsigned char )( usDescreteBuf[iRegIndex] & 0xFF );
           iRegIndex++;
           usNDiscrete--;
       }
   }
   else
   {
       eStatus = MB_ENOREG;
   }
   return eStatus;
}


//
#endif
//


#define REGION_ACCESS_MB_BIT_REG
#ifdef REGION_ACCESS_MB_BIT_REG
/*
 * MBSetCoil
 * Hàm này dùng để ghi giá trị 1 coil nào đó (giá trị 0|1)
 * Tham số bitOfset có nghĩa là vị trí của bit đó
 * Tham số thứ 2 là giá trị đặt cho Coil (0-1)
 * Hàm trả về :
 * 	ACCESS_NO_ERR: Nếu không có lỗi gì
 * 	ACCESS_ADDR_ERR: Nếu truy cập vào 1 địa chỉ thanh ghi mà không được cấp phát bộ nhớ
 *				Ví dụ: #define REC_COIL_START   30 -> Tức là vùng nhớ Coil được truy cập từ địa chỉ 30 trở lên
 *					Nếu địa chỉ < 30 thì Hàm sẽ trả về lỗi ACCESS_ADDR_ERR
 *					Nếu địa chỉ > REC_COIL_START + REC_COIL_NREGS-1 thì hàm cũng trả về lỗi này
 * Write: facebook.com/microcontroler
 * Gmail: 
*/
eMBAccessDataCode MBSetCoil(USHORT bitOfset, char value)
{
	UCHAR ByteAddr=0, BitAddr=0;
	UCHAR Mybyte=0;
	
	/* ---------- Check Address Correction -------------*/
	
	#if REG_COIL_START -1 >0
	if (bitOfset < REG_COIL_START ) return ACCESS_ADDR_ERR;
	#endif
	if (bitOfset > (REG_COIL_START + REG_COIL_NREGS -1)) return ACCESS_ADDR_ERR;
	
	USHORT DeltaBitOffset = bitOfset - REG_COIL_START;
	
	ByteAddr = DeltaBitOffset/8;
	BitAddr = DeltaBitOffset %8;

	
	Mybyte = 1;
	Mybyte = Mybyte << (BitAddr);
	if (value == 1)
	{
			usCoilBuf[ByteAddr]|= Mybyte;
	}
	else
	{
			Mybyte = ~ Mybyte;
			usCoilBuf[ByteAddr] &= Mybyte;
	}
	
	return ACCESS_NO_ERR;
}
//


/*
 * MBGetCoil
 * Hàm này dùng để lấy giá trị của 1 coil trong thanh ghi
 * bitOfset là địa chỉ bit
 * GetValue là 1 con trỏ trả về giá trị của Bit cần biết
 * Hàm trả về :
 * 	ACCESS_NO_ERR: Nếu không có lỗi gì
 * 	ACCESS_ADDR_ERR: Nếu truy cập vào 1 địa chỉ thanh ghi mà không được cấp phát bộ nhớ
 *				Ví dụ: #define REC_COIL_START   30 -> Tức là vùng nhớ Coil được truy cập từ địa chỉ 30 trở lên
 *					Nếu địa chỉ < 30 thì Hàm sẽ trả về lỗi ACCESS_ADDR_ERR
 *					Nếu địa chỉ > REC_COIL_START + REC_COIL_NREGS-1 thì hàm cũng trả về lỗi này
 * Write: facebook.com/microcontroler
 * Gmail: 
*/
eMBAccessDataCode MBGetCoil(USHORT bitOfset, UCHAR* GetValue)
{
	UCHAR ByteAddr=0, BitAddr=0;
	UCHAR Result  = 0;
	
	/* ---------- Check Address Correction -------------*/
	#if REG_COIL_START - 1 >0
	if (bitOfset < REG_COIL_START ) return ACCESS_ADDR_ERR;
	#endif
	if (bitOfset > (REG_COIL_START + REG_COIL_NREGS -1)) return ACCESS_ADDR_ERR;
	
	
	USHORT DeltaBitOffset = bitOfset - REG_COIL_START;
	
	ByteAddr = DeltaBitOffset/8;
	BitAddr = DeltaBitOffset %8;
	
	Result = usCoilBuf[ByteAddr] >> BitAddr;
	Result &= (UCHAR)1;
	
	*GetValue = Result;
	
	return ACCESS_NO_ERR;
}
//


/*
 * MBSetDescretesInputbit
 * Hàm này dùng để ghi 1 bit vào Vùng nhớ Descretes Input
 * bitOfset là vị trí bit cần set
 * value là giá trị cần set cho bit
 * Hàm trả về :
 * 	ACCESS_NO_ERR: Nếu không có lỗi gì
 * 	ACCESS_ADDR_ERR: Nếu truy cập vào 1 địa chỉ thanh ghi mà không được cấp phát bộ nhớ
 *				Ví dụ: #define REG_DESCRETES_INPUT_START   30 -> Tức là vùng nhớ DescretesInput được truy cập từ địa chỉ 30 trở lên
 *					Nếu địa chỉ < 30 thì Hàm sẽ trả về lỗi ACCESS_ADDR_ERR
 *					Nếu địa chỉ > REG_DESCRETES_INPUT_START + REG_DESCRETES_INPUT_NREGS-1 thì hàm cũng trả về lỗi này

 * Write: facebook.com/microcontroler
*/
eMBAccessDataCode MBSetDescretesInputbit(USHORT bitOfset, char value)
{
	UCHAR ByteAddr=0, BitAddr=0;
	UCHAR Mybyte;
	/* ---------- Check Address Correction -------------*/
	#if REG_DESCRETES_INPUT_START - 1 >0
	if (bitOfset < (REG_DESCRETES_INPUT_START )) return ACCESS_ADDR_ERR;
	#endif
	if (bitOfset > (REG_DESCRETES_INPUT_START + REG_DESCRETES_INPUT_NREGS -1)) return ACCESS_ADDR_ERR;
	
	
	USHORT DeltaBitOffset = bitOfset  - REG_COIL_START;
	
	ByteAddr = DeltaBitOffset/8;
	BitAddr = DeltaBitOffset %8;


	Mybyte = 1;
	Mybyte = Mybyte << (BitAddr);
	if (value == 1)
	{
			usDescreteBuf[ByteAddr]|= Mybyte;
	}
	else
	{
			Mybyte = ~ Mybyte;
			usDescreteBuf[ByteAddr] &= Mybyte;
	}
	
	return ACCESS_NO_ERR;

}
//

/*
 * MBGetDescretesInputbit
 * Hàm này dùng để lấy giá trị của 1 bit trong thanh ghi DescretesInput (thanh ghi Bit, Master chỉ đọc)
 * bitOfset là vị trí bit cần set
 * GetValue là con trỏ nhận về giá trị của bit cần lấy.
 * Hàm trả về :
 * 	ACCESS_NO_ERR: Nếu không có lỗi gì
 * 	ACCESS_ADDR_ERR: Nếu truy cập vào 1 địa chỉ thanh ghi mà không được cấp phát bộ nhớ
 *				Ví dụ: #define REG_DESCRETES_INPUT_START   30 -> Tức là vùng nhớ DescretesInput được truy cập từ địa chỉ 30 trở lên
 *					Nếu địa chỉ < 30 thì Hàm sẽ trả về lỗi ACCESS_ADDR_ERR
 *					Nếu địa chỉ > REG_DESCRETES_INPUT_START + REG_DESCRETES_INPUT_NREGS-1 thì hàm cũng trả về lỗi này

 * Write: facebook.com/microcontroler
*/
eMBAccessDataCode MBGetDescretesInputbit(USHORT bitOfset, UCHAR* GetValue)
{
	UCHAR ByteAddr=0, BitAddr=0;
	UCHAR Result  = 0;
	
	/* ---------- Check Address Correction -------------*/
	#if REG_DESCRETES_INPUT_START - 1 > 0
	if (bitOfset < REG_DESCRETES_INPUT_START ) return ACCESS_ADDR_ERR;
	#endif
	if (bitOfset > (REG_DESCRETES_INPUT_START + REG_DESCRETES_INPUT_NREGS -1)) return ACCESS_ADDR_ERR;
	
	
	USHORT DeltaBitOffset = bitOfset - REG_DESCRETES_INPUT_START;
	
	ByteAddr = DeltaBitOffset/8;
	BitAddr = DeltaBitOffset %8;
	
	Result = usDescreteBuf[ByteAddr] >> BitAddr;
	Result &= (UCHAR)1;
	
	*GetValue = Result;
	
	return ACCESS_NO_ERR;
}
//
#endif
//


#define REGION_ACCESS_INPUT_HOLDING_REGISTER
#ifdef REGION_ACCESS_INPUT_HOLDING_REGISTER
/*
 *	Write: facebook.com/microcontroler
 *	Hàm này dùng để trích xuất dữ liệu 16bit từ các thanh ghi modbus
 * 	Ví dụ cần lấy dữ liệu 16 bit tại địa chỉ 45 của thanh ghi REG INPUT
 *	MBGetData16Bits(REG_INPUT, 45, &Value);
* Hàm trả về giá trị
* 	ACCESS_NO_ERR: Nếu không có lỗi gì
* 	ACCESS_ADDR_ERR: Nếu truy cập vào 1 địa chỉ thanh ghi mà không được cấp phát bộ nhớ
*				Ví dụ: #define REG_HOLDING_START       30 -> Tức là vùng nhớ Holding được truy cập từ địa chỉ 30 trở lên
*					Nếu địa chỉ < 30 thì Hàm sẽ trả về lỗi ACCESS_ADDR_ERR
*		ACCESS_WRONG_DATA_TYPE: Nghĩa là loại dữ liệu bị sai, hàm này chỉ được truy cập vào 2 thanh ghi dữ liệu là
*				REG_INPUT và REG_HOLDING
*/
eMBAccessDataCode MBGetData16Bits(MB_Data_Type DataType, USHORT Address, USHORT* Value)
{
	USHORT BeginAddress;
	USHORT LastAddress16;
	
	switch (DataType)
	{
		case 	REG_INPUT:
			
			/* ---------Check Address Correction ------------ */
			BeginAddress = REG_INPUT_START;
			if (Address < (BeginAddress -1)) return ACCESS_ADDR_ERR; 
			LastAddress16 = REG_INPUT_START + REG_INPUT_NREGS -1;
			if (Address > LastAddress16) return ACCESS_ADDR_ERR;

			/* --------Assign Data ------------*/
			*Value = usRegInputBuf[Address - BeginAddress ];
		
			break;
		
		case REG_HOLDING:
			/* ------ Check Address correction --------*/
			BeginAddress = REG_HOLDING_START;
			if (Address < (BeginAddress -1)) return ACCESS_ADDR_ERR;
			LastAddress16 = REG_HOLDING_START + REG_HOLDING_NREGS -1;
			if (Address > LastAddress16) return ACCESS_ADDR_ERR;
		
			/* -------- Assign Data -------------*/
			*Value = usRegHoldingBuf[Address - BeginAddress];
		
			break;
		
		default:
			return ACCESS_WRONG_DATA_TYPE;

	}
	return ACCESS_NO_ERR;
}
//

/*
 * Write: facebook.com/microcontroler
 *	Hàm này dùng để trích xuất dữ liệu 32bit từ các thanh ghi modbus
 * 	Ví dụ cần lấy dữ liệu 32 bit tại địa chỉ 45 của thanh ghi REG HOLDING
 *	MBGetData32Bits(REG_HOLDING, 45, &Value);
* Hàm trả về giá trị
* 	ACCESS_NO_ERR: Nếu không có lỗi gì
* 	ACCESS_ADDR_ERR: Nếu truy cập vào 1 địa chỉ thanh ghi mà không được cấp phát bộ nhớ
*				Ví dụ: #define REG_HOLDING_START       30 -> Tức là vùng nhớ Holding được truy cập từ địa chỉ 30 trở lên
*					Nếu địa chỉ < 30 thì Hàm sẽ trả về lỗi ACCESS_ADDR_ERR
*		ACCESS_WRONG_DATA_TYPE: Nghĩa là loại dữ liệu bị sai, hàm này chỉ được truy cập vào 2 thanh ghi dữ liệu là
*				REG_INPUT và REG_HOLDING
*/
eMBAccessDataCode MBGetData32Bits(MB_Data_Type DataType, USHORT Address, ULONG* Value)
{
	USHORT BeginAddress;
	USHORT LastAddress32;
	
	USHORT HightData = 0;
	USHORT LowData = 0;
	ULONG Result = 0;
	switch (DataType)
	{
		case 	REG_INPUT:
			
			/*  --- Check Address Correction --- */
			BeginAddress = REG_INPUT_START;
			if (Address < (BeginAddress -1)) return ACCESS_ADDR_ERR; 
			LastAddress32 = REG_INPUT_START + REG_INPUT_NREGS -2;
			if (Address > LastAddress32) return ACCESS_ADDR_ERR;

		
			/* -------- Assign Value to Reg ----- */
			LowData = usRegInputBuf[Address - BeginAddress ];
			HightData = usRegInputBuf[Address - BeginAddress + 1];
		
			break;
		case REG_HOLDING:
			
			/*  --- Check Address Correction --- */
			BeginAddress = REG_HOLDING_START;
			if (Address < (BeginAddress -1)) return ACCESS_ADDR_ERR;
			LastAddress32 = REG_HOLDING_START + REG_HOLDING_NREGS -2;
			if (Address > LastAddress32) return ACCESS_ADDR_ERR;
			
			/* -------- Assign Value to Reg ----- */
			LowData = usRegHoldingBuf[Address - BeginAddress ];
			HightData = usRegHoldingBuf[Address - BeginAddress + 1];
		
			break;
		default:
			return ACCESS_WRONG_DATA_TYPE;

	}
	Result = ((ULONG)HightData)<<16;
	Result += LowData;
	
	*Value = Result;
	
	return ACCESS_NO_ERR;
}
//

/*
*	Write: facebook.com/microcontroler
*	Hàm này dùng để thay đổi dữ liệu 16bit của thanh ghi Modbus
* Ví dụ muốn gán giá trị 325 cho thanh ghi Holding tại địa chỉ 170 thì thực hiện như sau:
* MBSetData16Bits(REG_HOLDING, 170, 325);
* Hàm trả về giá trị
* 	ACCESS_NO_ERR: Nếu không có lỗi gì
* 	ACCESS_ADDR_ERR: Nếu truy cập vào 1 địa chỉ thanh ghi mà không được cấp phát bộ nhớ
*				Ví dụ: #define REG_HOLDING_START       30 -> Tức là vùng nhớ Holding được truy cập từ địa chỉ 30 trở lên
*					Nếu địa chỉ < 30 thì Hàm sẽ trả về lỗi ACCESS_ADDR_ERR
*		ACCESS_WRONG_DATA_TYPE: Nghĩa là loại dữ liệu bị sai, hàm này chỉ được truy cập vào 2 thanh ghi dữ liệu là
*				REG_INPUT và REG_HOLDING
*/
eMBAccessDataCode MBSetData16Bits(MB_Data_Type DataType, USHORT Address, USHORT Value)
{
	USHORT BeginAddress;
	USHORT LastAddress16;
	
	switch (DataType)
	{
		case 	REG_INPUT:
			BeginAddress = REG_INPUT_START;
			if (Address < (BeginAddress -1 )) return ACCESS_ADDR_ERR; 
			LastAddress16 = REG_INPUT_START + REG_INPUT_NREGS -1;
		
			if (Address > LastAddress16) return ACCESS_ADDR_ERR;

			usRegInputBuf[Address - BeginAddress ] = Value;
		
			break;
		case REG_HOLDING:
			BeginAddress = REG_HOLDING_START;
			if (Address <( BeginAddress -1)) return ACCESS_ADDR_ERR;
			LastAddress16 = REG_HOLDING_START + REG_HOLDING_NREGS -1;
		
			if (Address > LastAddress16) return ACCESS_ADDR_ERR;
		
			usRegHoldingBuf[Address - BeginAddress ] = Value;
			break;
		default:
			return ACCESS_WRONG_DATA_TYPE;

	}
	return ACCESS_NO_ERR;
}
//


/*
*	Write: facebook.com/microcontroler
*	hàm này dùng để thay đổi dữ liệu 32bit của thanh ghi Modbus
* Ví dụ: Cần ghi giá trị 0x554433 vào địa chỉ 100 của thanh ghi HOLDING
* MBSetData32Bits(REG_HOLDING, 100, 0x554433);
* Hàm trả về giá trị
* 	ACCESS_NO_ERR: Nếu không có lỗi gì
* 	ACCESS_ADDR_ERR: Nếu truy cập vào 1 địa chỉ thanh ghi mà không được cấp phát bộ nhớ
*				Ví dụ: #define REG_HOLDING_START       30 -> Tức là vùng nhớ Holding được truy cập từ địa chỉ 30 trở lên
*					Nếu địa chỉ < 30 thì Hàm sẽ trả về lỗi ACCESS_ADDR_ERR
*		ACCESS_WRONG_DATA_TYPE: Nghĩa là loại dữ liệu bị sai, hàm này chỉ được truy cập vào 2 thanh ghi dữ liệu là
*				REG_INPUT và REG_HOLDING
*/
eMBAccessDataCode MBSetData32Bits(MB_Data_Type DataType, USHORT Address, ULONG Value)
{
	USHORT BeginAddress;
	USHORT LastAddress32;
	
	USHORT HightData = (Value & 0xFFFF0000)>>16;
	USHORT LowData = (Value & 0x0000FFFF);
	
	switch (DataType)
	{
		case 	REG_INPUT:
			
			/*  --- Check Address Correction --- */
			BeginAddress = REG_INPUT_START;
			if (Address < (BeginAddress -1)) return ACCESS_ADDR_ERR; 
			LastAddress32 = REG_INPUT_START + REG_INPUT_NREGS -2;
			if (Address > LastAddress32) return ACCESS_ADDR_ERR;

		
			/* -------- Assign Value to Reg ----- */
		usRegInputBuf[Address - BeginAddress ] = LowData;   // Origin: HightData
		usRegInputBuf[Address - BeginAddress + 1] = HightData;	// Origin: LowData
		
			break;
		case REG_HOLDING:
			
			/*  --- Check Address Correction --- */
			BeginAddress = REG_HOLDING_START;
			if (Address < (BeginAddress -1)) return ACCESS_ADDR_ERR;
			LastAddress32 = REG_HOLDING_START + REG_HOLDING_NREGS -2;
			if (Address > LastAddress32) return ACCESS_ADDR_ERR;
			
			/* -------- Assign Value to Reg ----- */
			usRegHoldingBuf[Address - BeginAddress ] = LowData;
			usRegHoldingBuf[Address - BeginAddress + 1] = HightData;
		
			break;
		default:
			return ACCESS_WRONG_DATA_TYPE;
	}
	return ACCESS_NO_ERR;
}
//


/*
*	Write: facebook.com/microcontroler
*	hàm này dùng để thay đổi dữ liệu float của thanh ghi Modbus
* Ví dụ: Cần ghi giá trị 324.434 vào địa chỉ 100 của thanh ghi HOLDING
* MBSetFloatData(REG_HOLDING, 100, 324.434);
* Hàm trả về giá trị
* 	ACCESS_NO_ERR: Nếu không có lỗi gì
* 	ACCESS_ADDR_ERR: Nếu truy cập vào 1 địa chỉ thanh ghi mà không được cấp phát bộ nhớ
*				Ví dụ: #define REG_HOLDING_START       30 -> Tức là vùng nhớ Holding được truy cập từ địa chỉ 30 trở lên
*					Nếu địa chỉ < 30 thì Hàm sẽ trả về lỗi ACCESS_ADDR_ERR
*		ACCESS_WRONG_DATA_TYPE: Nghĩa là loại dữ liệu bị sai, hàm này chỉ được truy cập vào 2 thanh ghi dữ liệu là
*				REG_INPUT và REG_HOLDING
*/

typedef union _data {
  float f;
  char  s[4];
	uint32_t U32;
} myData;


eMBAccessDataCode MBSetFloatData(MB_Data_Type DataType, USHORT Address, float Value)
{
	USHORT BeginAddress;
	USHORT LastAddress32;

	myData q;
	q.f = Value;
	
	USHORT *HightData;
	USHORT *LowData;
	
	HightData = (USHORT*)q.s +1;
	LowData = (USHORT *)q.s ;
	
	switch (DataType)
	{
		case 	REG_INPUT:
			
			/*  --- Check Address Correction --- */
			BeginAddress = REG_INPUT_START;
			if (Address < (BeginAddress -1)) return ACCESS_ADDR_ERR; 
			LastAddress32 = REG_INPUT_START + REG_INPUT_NREGS -2;
			if (Address > LastAddress32) return ACCESS_ADDR_ERR;

		
			/* -------- Assign Value to Reg ----- */
		usRegInputBuf[Address - BeginAddress ] = *LowData;   // Origin: HightData
		usRegInputBuf[Address - BeginAddress + 1] = *HightData;	// Origin: LowData
		
			break;
		case REG_HOLDING:
			
			/*  --- Check Address Correction --- */
			BeginAddress = REG_HOLDING_START;
			if (Address < (BeginAddress -1)) return ACCESS_ADDR_ERR;
			LastAddress32 = REG_HOLDING_START + REG_HOLDING_NREGS -2;
			if (Address > LastAddress32) return ACCESS_ADDR_ERR;
			
			/* -------- Assign Value to Reg ----- */
			usRegHoldingBuf[Address - BeginAddress ] = *LowData;
			usRegHoldingBuf[Address - BeginAddress + 1] = *HightData;
		
			break;
		default:
			return ACCESS_WRONG_DATA_TYPE;
	}
	return ACCESS_NO_ERR;
}
//


/*
 * Write: 
 *	Hàm này dùng để trích xuất dữ liệu float từ thanh ghi lưu trữ dữ liệu
 * 	Ví dụ cần lấy dữ liệu float  tại địa chỉ 45 của thanh ghi REG HOLDING
 *	MBGetFloatData(REG_HOLDING, 45, &Value);
* Hàm trả về giá trị
* 	ACCESS_NO_ERR: Nếu không có lỗi gì
* 	ACCESS_ADDR_ERR: Nếu truy cập vào 1 địa chỉ thanh ghi mà không được cấp phát bộ nhớ
*				Ví dụ: #define REG_HOLDING_START       30 -> Tức là vùng nhớ Holding được truy cập từ địa chỉ 30 trở lên
*					Nếu địa chỉ < 30 thì Hàm sẽ trả về lỗi ACCESS_ADDR_ERR
*		ACCESS_WRONG_DATA_TYPE: Nghĩa là loại dữ liệu bị sai, hàm này chỉ được truy cập vào 2 thanh ghi dữ liệu là
*				REG_INPUT và REG_HOLDING
*/
#include <math.h>
#include <float.h>

eMBAccessDataCode MBGetFloatData(MB_Data_Type DataType, USHORT Address, float* Value)
{
	USHORT BeginAddress;
	USHORT LastAddress32;
	char *P;

	
	USHORT HightData = 0;
	USHORT LowData = 0;

	myData  Result;
	
	switch (DataType)
	{
		case 	REG_INPUT:
			
			/*  --- Check Address Correction --- */
			BeginAddress = REG_INPUT_START;
			if (Address < (BeginAddress -1)) return ACCESS_ADDR_ERR; 
			LastAddress32 = REG_INPUT_START + REG_INPUT_NREGS -2;
			if (Address > LastAddress32) return ACCESS_ADDR_ERR;

		
			/* -------- Assign Value to Reg ----- */
			LowData = usRegInputBuf[Address - BeginAddress ];
			HightData = usRegInputBuf[Address - BeginAddress + 1];
		
			break;
		case REG_HOLDING:
			
			/*  --- Check Address Correction --- */
			BeginAddress = REG_HOLDING_START;
			if (Address < (BeginAddress -1)) return ACCESS_ADDR_ERR;
			LastAddress32 = REG_HOLDING_START + REG_HOLDING_NREGS -2;
			if (Address > LastAddress32) return ACCESS_ADDR_ERR;
			
			/* -------- Assign Value to Reg ----- */
			LowData = usRegHoldingBuf[Address - BeginAddress ];
			HightData = usRegHoldingBuf[Address - BeginAddress + 1];
		
			break;
		default:
			return ACCESS_WRONG_DATA_TYPE;
	}

	
	P = (char*)&LowData;
	Result.s[0] = *P;
	Result.s[1] = *(P +1);
	
	P = (char*)&HightData;
	Result.s[2] = *P;
	Result.s[3] = *(P + 1);
	
	*Value = Result.f;
	float val = Result.f;
		switch(fpclassify(*Value)) {
				case FP_INFINITE:  
					*Value = 0; 
					break;//return "Inf";
				case FP_NAN:       
					*Value = 0; 
				break;//return "NaN";
				case FP_NORMAL:    
					break;//return "normal";
				case FP_SUBNORMAL: 
					*Value = 0; 
					break;//return "subnormal";
				case FP_ZERO:       
					break;//return "zero";
				default:
					*Value = 0;
					break;//return "unknown";
		}
	
	if (val > (float)10000.0)
		*Value = 0;
	if (val <(float)-10000.0)
		*Value = 0;
	if ((val > (float)0)&&(val < (float)0.0000001))	*Value = 0;
	
	return ACCESS_NO_ERR;
}
//

#endif
//

File ModbusUser.c cung cấp cho bạn các hàm thao tác lên dữ liệu modbus, và hàm Khởi tạo tổng quan cho Modbus. Ngoài ra trên file này còn khai báo 1 số thanh ghi dùng chung đã nhắc đến ở phần 1. Cụ thể là:

USHORT   usRegInputStart = REG_INPUT_START;
USHORT   usRegInputBuf[REG_INPUT_NREGS];                            // Mảng 16bit lưu giá trị của Register Input (Read Only)

USHORT   usRegHoldingStart = REG_HOLDING_START;
USHORT   usRegHoldingBuf[REG_HOLDING_NREGS];                    // Mảng 16bit lưu giá trị của Register Holding (Read/Write)

USHORT        usCoilStart = REG_COIL_START;
UCHAR     usCoilBuf[(REG_COIL_NREGS/8)+1];                                    // Mảng 1 bit lưu giá trị của Output Coil  (Read/Write )

USHORT     usDescreteStart = REG_DESCRETES_INPUT_START;
UCHAR     usDescreteBuf[(REG_DESCRETES_INPUT_NREGS / 8)+1];       // Mảng 1bit  lưu giá trị của Descretes Input (Read Only)

+ usRegInputBuf: Là thanh ghi dùng chung 16 bit (mình gọi là REG_INPUT)
+ usRegHoldingBuf: là thanh ghi chung 16 bit (mình gọi là HOLDING)
+ usCoilBuf: là thanh ghi dùng chung 1bit (còn gọi là thanh ghi COIL)
+ usDescreteBuf: là thanh ghi dùng chung 1 bit chỉ đọc (bởi master) gọi là thanh ghi (DESCRETES_INPUT).

Xem Thêm :   Em Ngọc bán xe máy cũ giá rẻ ở Dĩ An Bình Dương 0965256317 – 0325368742 trả góp ship toàn quốc

Xem thêm :  Tuổi tỵ là con gì ? người tuổi tỵ sinh năm bao nhiêu ? người tuổi tỵ sinh năm bao nhiêu

Các bạn lưu ý rằng file này chỉ dùng cho chế độ SLAVE. và việc khai báo các thanh ghi này chỉ khai báo trên slave.

Giải thích các hàm trong ModbusUser.c

eMBErrorCode UseModbus(void)

Hàm này để khởi tạo sử dụng Modbus, Hiện tại hàm này đang hỗ trợ khởi tạo sử dụng Modbus ASCII hoặc modbus RTU. Phần cấu hình này mình sẽ hướng dẫn sau.

eMBAccessDataCode MBSetCoil(USHORT bitOfset, char value)

Hàm MBSetCoil dùng để đặt 1 giá trị cho 1 coil của thanh ghi chung COIL tại địa chỉ bitOfset, địa chỉ này phải > 0, value là giá trị đặt cho Coil đó, có 2 giá trị cho phép đó là 0 hoặc 1.
Hàm này trả về kiểu giá trị eMBAccessDataCode, kiểu giá trị này có cấu trúc như sau:

typedef enum
{
    ACCESS_ADDR_ERR, // Lỗi địa chỉ truy cập
    ACCESS_NO_ERR,  // Không hề có lỗi
    ACCESS_WRONG_DATA_TYPE // Lỗi sai kiểu dữ liệu modbus
} eMBAccessDataCode;

Ví dụ cách dùng:
MBSetCoil(1, 1); // Set giá trị 1 cho thanh ghi chung COIL tại địa chỉ 1
MBSetCoil(2,0); // Set giá trị 0 cho thanh ghi chung COIL tại địa chỉ 2
hoặc:
eMBAccessDataCode stt = MBSetCoil(200, 1);
if (stt == ACCESS_NO_ERR)
{
println(“Set coil successful”);
}

eMBAccessDataCode MBGetCoil(USHORT bitOfset, UCHAR* GetValue)

Hàm MBGetCoil dùng để lấy 1 byte từ thanh ghi chung COIL. ví dụ:

char Coil2 = 0;
eMBAccessDataCode stt = MBGetCoil(2, &Coil2);
if( stt ==  ACCESS_NO_ERR )
{
   printf("Gia tri cua coil2 la:%d", Coil2);
}

eMBAccessDataCode MBSetDescretesInputbit(USHORT bitOfset, char value)

Hàm SetDescretesInputbit dùng để set 1 giá trị cho thanh ghi chung DescretesInput tại địa chỉ bitOfset.
Ví dụ set giá trị 1 tại địa chỉ 120:

eMBAccessDataCode stt = MBSetDescretesInputbit (120, 1);
 if( stt ==  ACCESS_NO_ERR )
 {
    printf("Set DescretesInputbit at address: 120 successful");
 }

eMBAccessDataCode MBGetDescretesInputbit(USHORT bitOfset, UCHAR*
GetValue)

Hàm MBGetDescretesInputbit dùng để
lấy giá trị của thanh ghi chung DescretesInput tại địa chỉa bitOfset.
Ví dụ lấy giá trị tại địa chỉ 10:

char DesInput = 0;
eMBAccessDataCode stt = MBGetDescretesInputbit (10, & DesInput);
if( stt ==  ACCESS_NO_ERR )
{
   printf("Gia tri cua DesInput la: %d", DesInput);
}

eMBAccessDataCode MBGetData16Bits(MB_Data_Type DataType, USHORT Address,
USHORT* Value)

Hàm GetData16Bits dùng để lấy ra
giá trị 16bit của thanh ghi chung HOLDING hoặc thanh ghi INPUT tại địa chỉ
Address, và giá trị được truyền ra tham số con trỏ *Value

Xem Thêm :   900 Củ Khoai là giá miếng đất 1970m2 có 200 thổ cư mặt tiền nhựa rộng 45m ở Tân Châu Tây Ninh bán rẻ

Xem thêm :  Cây nổ gai là cây gì, có tác dụng gì? – Baonongsan.com

Ví dụ lấy giá trị thanh ghi
HOLDING tại địa chỉ 85:

Uint16_t Data = 0;
eMBAccessDataCode stt = MBGetData16Bits(REG_HOLDING,  85, &Data);
if (stt == ACCESS_NO_ERR)
{
   printf(“Data = %d”, Data);
}

Ví dụ lấy giá trị thanh ghi INPUT
tại địa chỉ 12:

int16_t DataIP = 0;
eMBAccessDataCode stt = MBGetData16Bits(REG_INPUT,  12, &DataIP);
if (stt == ACCESS_NO_ERR)
{
   printf(“DataIP = %d”, DataIP);
}

eMBAccessDataCode MBSetData16Bits(MB_Data_Type DataType, USHORT Address,
USHORT Value)

Hàm MBSetData16Bits dùng để set
giá trị cho thanh ghi chung HOLDING hoặc thanh ghi chung INPUT tại địa chỉ
Address, với giá trị Value.
Ví dụ set giá trị  3000 cho thanh ghi
HOLDING tại địa chỉ 20:

eMBAccessDataCode stt = MBSetData16Bits(REG_HOLDING, 20, 3000);
if (stt == ACCESS_NO_ERR)
{
   printf(“Set value successful”);
}

Ví dụ set giá trị  1500 cho thanh ghi INPUT tại địa chỉ 15:

eMBAccessDataCode stt = MBSetData16Bits(REG_INPUT, 15, 1500);
if (stt == ACCESS_NO_ERR)
{
   printf(“Set value successful”);
}

eMBAccessDataCode MBGetData32Bits(MB_Data_Type DataType, USHORT Address,
ULONG* Value)

Hàm MBGetData32Bits dùng để lấy
giá trị 32bit từ thanh ghi chung HOLDING hoặc INPUT tại địa chỉ Address, và dữ
liệu được truyền ra tham số con trỏ *Value.
Ví dụ lấy dữ liệu 32bit từ thanh ghi HOLDING tại địa chỉ 50:

Uint32_t Data32 = 0;
eMBAccessDataCode stt = MBGetData32Bits(REG_HOLDING,  50, & Data32);
if (stt == ACCESS_NO_ERR)
{
   printf(“Data32 = %ld”, Data32);
}

Ví dụ lấy dữ liệu 32bit từ thanh
ghi INPUT tại địa chỉ 200:

Uint32_t Data32 = 0;
eMBAccessDataCode stt = MBGetData32Bits(REG_INPUT,  200, &Data32);
if (stt == ACCESS_NO_ERR)
{
   printf(“Data32 = %ld”, Data32);
}

eMBAccessDataCode MBSetData32Bits(MB_Data_Type DataType, USHORT Address,
ULONG Value)

Hàm MBSetData32Bits dùng để set
giá trị 32 bit(Value) cho thanh ghi chung HOLDING hoặc INPUT tại địa chỉ
Address.

Ví dụ set giá trị 22333444555 cho
thanh ghi HOLDING tại địa chỉ 100:

eMBAccessDataCode stt = MBSetData32Bits(REG_HOLDING, 100, 22333444555);
if (stt == ACCESS_NO_ERR)
{
   printf(“Set value successful”);
}

Ví dụ set giá trị -11300876432 cho
thanh ghi INPUT tại địa chỉ 1:

eMBAccessDataCode stt = MBSetData32Bits(REG_INPUT, 1, -11300876432);
if (stt == ACCESS_NO_ERR)
{
   printf(“Set value successful”);
}

eMBAccessDataCode MBSetFloatData(MB_Data_Type DataType, USHORT Address,
float Value)

Hàm MBSetFloatData dùng để set 1
giá trị kiểu thực (float) cho thanh ghi chung HOLDING hoặc INPUT tại địa chỉ
Address.

Ví dụ set giá trị 20.04 cho thanh
ghi HOLDING tại địa chỉ 24:

eMBAccessDataCode stt = MBSetFloatData (REG_HOLDING, 24, 20.04);
if (stt == ACCESS_NO_ERR)
{
   printf(“Set value successful”);
}

Ví dụ set giá trị 30.5 cho thanh
ghi INPUT tại địa chỉ 25:

eMBAccessDataCode stt = MBSetFloatData (REG_INPUT, 25, 30.5);
if (stt == ACCESS_NO_ERR)
{
   printf(“Set value successful”);
}

eMBAccessDataCode MBGetFloatData(MB_Data_Type DataType, USHORT
Address, float* Value)

Hàm MBGetFloatData dùng để lấy dữ
liệu kiểu thực (float ) từ thanh ghi chung HOLDING hoặc INPUT tại địa chỉ
Address.

Ví dụ lấy dữ liệu tại địa chỉ 10 của
thanh ghi HOLDING:

float FData = 0;
eMBAccessDataCode stt = MBGetFloatData (REG_HOLDING,  10, & FData);
if (stt == ACCESS_NO_ERR)
{
   printf(“Float Data = %f”, FData);
}

Ví dụ lấy dữ liệu tại địa chỉ 44 của
thanh ghi INPUT:

float FData = 0; 
eMBAccessDataCode stt = MBGetFloatData (REG_INPUT,  44, & FData); 
if (stt == ACCESS_NO_ERR) 
{    
   printf(“Float Data = %f”, FData); 
}

Được rồi, dài quá rồi, đến đây bạn đã có đầy đủ file để chạy được modbus. Nhưng vẫn chưa build được, còn 1 số ý mình sẽ trình bày ở phần 3. Đoạn code trên khá dài, nhưng mình không muốn để link vì sợ sau này link die. các bạn cố gắng copy rồi tạo thành file nhé.

Good luck !

Bình chọn

Share this:

Like this:

Số lượt thích

Đang tải…

Xem thêm bài viết thuộc chuyên mục: Kiến Thức Chung

Xem thêm bài viết thuộc chuyên mục: Tổng Hợp

Related Articles

Back to top button