181 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			181 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*!
 | |
|  * @file modbus.c
 | |
|  * @authors Simon Donnet-Monay & Remi Heredero
 | |
|  * @date 14 march 2023
 | |
|  * @brief 
 | |
|  */
 | |
| 
 | |
| #include "modbus.h"
 | |
| #include "crc.h"
 | |
| 
 | |
| // Modbus functions
 | |
| #define READ_INPUT_REGISTERS    0x04 // Modbus function for read input register
 | |
| #define READ_HOLDING_REGISTERS  0x03 // Modbus function for read holding register
 | |
| #define WRITE_SINGLE_REGISTER   0x06 // Modbus function for write a single register
 | |
| 
 | |
| // Modbus data model
 | |
| uint8_t modbusAddress;
 | |
| uint16_t input_registers[2];
 | |
| uint16_t holding_registers[2];
 | |
| 
 | |
| 
 | |
| // Modbus error codes
 | |
| #define ILLEGAL_FUNCTION		1
 | |
| #define ILLEGAL_DATA_ADDRESS	2
 | |
| #define ILLEGAL_DATA_VALUE		3
 | |
| #define SLAVE_DEVICE_FAILURE	4
 | |
| 
 | |
| /**
 | |
|  * Buffers for serial receive and send operations 
 | |
|  * which are more than one byte long
 | |
|  **/
 | |
| uint8_t rx_buf[256];
 | |
| uint8_t tx_buf[256];
 | |
| 
 | |
| // Current position pointer for storing receive position
 | |
| uint8_t recPtr = 0;
 | |
| 
 | |
| /**
 | |
|  * End of MODBUS frame. 
 | |
|  */
 | |
| void modbus_timer(void) {
 | |
| 	INTCONbits.TMR0IF = 0;          // Reset flag of the timer0 interrupt 
 | |
|     recPtr = 0;                     // Reset position of the char in the frame
 | |
|     TMR0_StopTimer();               // Stop timer who detect the end of the frame
 | |
|     modbus_analyse_and_answer();    // Run analyse of this frame
 | |
| }
 | |
| 
 | |
| extern uint16_t measure_voltage();
 | |
| 
 | |
| /**
 | |
|  * @brief Analyse the received frame and build an answer
 | |
|  * @return The error code if the frame isn't valid (TODO)
 | |
|  */
 | |
| uint8_t modbus_analyse_and_answer(void) {
 | |
|     // Init lenght of the answer frame at 0
 | |
|     uint16_t length = 0;
 | |
|     
 | |
|     // Check if the received frame is for this device
 | |
|     if(rx_buf[0] == modbusAddress){
 | |
|         tx_buf[0] = rx_buf[0];  // Copy the address on the tx buffer
 | |
|         tx_buf[1] = rx_buf[1];  // Copy the function number on the tx buffer
 | |
|         
 | |
|         // Init the addresse Register local variable
 | |
|         uint16_t adresseRegister = ((uint16_t)rx_buf[2] << 8) | rx_buf[3];
 | |
|         
 | |
|         switch(rx_buf[1]){  // Check the function from rx buffer
 | |
|             
 | |
|             // In case of the function is to read input register: 
 | |
|             case READ_INPUT_REGISTERS: 
 | |
|                 // Define length as the number of register we want read
 | |
|                 length = ((uint16_t)rx_buf[4] << 8) | rx_buf[5];
 | |
|                 
 | |
|                 // Write this length on the tx buffer for the answer
 | |
|                 tx_buf[2] = (uint8_t)(length*2);
 | |
|                 
 | |
|                 // For each register, write the value on the tx buffer (register on 16bits)
 | |
|                 for(uint16_t i = 0; i < length; i++){   // Data
 | |
|                     tx_buf[i*2+4] = input_registers[adresseRegister+i];
 | |
|                     tx_buf[i*2+3] = (input_registers[adresseRegister+i] >> 8);
 | |
|                 }
 | |
|                 
 | |
|                 // Transform length as the number of bytes on tx register
 | |
|                 length*=2;  // 2 bytes by register
 | |
|                 length+=3;  // + address + function + length
 | |
|                 break;
 | |
|             
 | |
|                 
 | |
|             // In case of the function is to read holding register
 | |
|             case READ_HOLDING_REGISTERS:
 | |
|                 // Define length as the number of register we want read
 | |
|                 length = ((uint16_t)rx_buf[4] << 8) | rx_buf[5];
 | |
|                 
 | |
|                 // Write this length on the tx buffer for the answer
 | |
|                 tx_buf[2] = (uint8_t)(length*2);
 | |
|                 
 | |
|                 // For each register, write the value on the tx buffer (register on 16bits))
 | |
|                 for(uint16_t i = 0; i < length; i++){
 | |
|                     tx_buf[i*2+4] = holding_registers[adresseRegister+i];
 | |
|                     tx_buf[i*2+3] = (holding_registers[adresseRegister+i] >> 8);
 | |
|                 }
 | |
|                 // Transform length as the number of bytes on tx register
 | |
|                 length*=2;  // 2 bytes by register
 | |
|                 length+=3;  // + address + function + length
 | |
|                 break;
 | |
|                 
 | |
|             
 | |
|             // In case of the funciton is to write a single register
 | |
|             case WRITE_SINGLE_REGISTER:
 | |
|                 
 | |
|                 // Write the value on rx buffer on the holding register define by the adress register we define before
 | |
|                 holding_registers[adresseRegister] = ((uint16_t)rx_buf[4] << 8) | rx_buf[5];
 | |
|                 
 | |
|                 // Copy data on the tx buffer
 | |
|                 for (uint8_t i = 2; i <= 5; i++) {
 | |
|                     tx_buf[i] = rx_buf[i];
 | |
|                     length = i+1;
 | |
|                 }
 | |
|                 break;
 | |
|         }
 | |
|         
 | |
|     }
 | |
|       
 | |
|     // Clear address on rx buffer (for validate we treat the data)
 | |
|     rx_buf[0] = 0;
 | |
|     
 | |
|     // Send the answer frame
 | |
|     modbus_send(length);
 | |
|     
 | |
|     // TODO return error code
 | |
|   
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Record a char when it's received on the modbus
 | |
|  */
 | |
| void modbus_char_recvd(void) {
 | |
|     //! Record the received char on the rx buffer and move position of the record pointer for the next char
 | |
| 	rx_buf[recPtr++] = RCREG1;
 | |
|     
 | |
|     //! Reload and start the timer0 for restart to count the time between char
 | |
|     TMR0_Reload();
 | |
|     TMR0_StartTimer();
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Create the CRC and send the tx buffer
 | |
|  * @param length lenght of the frame without the CRC
 | |
|  */
 | |
| void modbus_send(uint8_t length) {
 | |
|     // Create the CRC
 | |
| 	uint16_t crc = CRC16(tx_buf, length);
 | |
| 	
 | |
|     // Write CRC on the tx buffer
 | |
|     tx_buf[length] = crc;
 | |
|     tx_buf[length+1] = crc >> 8;
 | |
| 
 | |
| 	length += 2; //! add 2 CRC bytes for total size
 | |
| 
 | |
| 	// Send each byte of the frame on the tx buffer
 | |
|     for (uint8_t i = 0; i < length; i++){
 | |
|           EUSART1_Write(tx_buf[i]);
 | |
|       }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Initialize the modbus with adress and handler function
 | |
|  * @param address The adress of this device on modbus protocole
 | |
|  */
 | |
| void modbus_init(uint8_t address) {
 | |
| 	// Init the modbus adress
 | |
|     modbusAddress = address;
 | |
|     
 | |
|     // Save the modbus adress in the dedicated register
 | |
|     holding_registers[1] = address;
 | |
|     
 | |
|     // Set the handler for the character detection
 | |
|     EUSART1_SetRxInterruptHandler(modbus_char_recvd);
 | |
|     
 | |
|     // Set the handler for the detection of end frame
 | |
|     TMR0_SetInterruptHandler(modbus_timer);
 | |
| } |