Figure 1 Modbus Debug Wizard
As shown in the figure: our USB to 485 module virtualizes COM5, baud rate 9600, no parity bit, 8 data bits, 1 stop bit, and the device address is assumed to be 1.
When writing registers, if we want to write 01 to a register address of 0000, click "Write", and the sending instruction will appear: 01 06 00 00 00 01 48 0A. Let's analyze this frame of data, where 01 is the device address, 06 is the function code, representing the function of writing registers, followed by 00 00, which indicates the address of the register to be written, 00 01 is the data to be written, and 48 0A is the CRC check code, which is automatically calculated by the software. According to the Modbus protocol, when writing registers, after the slave successfully completes the operation of the instruction, it will directly return the instruction sent by the host, and our debugging wizard will receive a frame of data like this: 01 06 00 00 00 01 48 0A.
Suppose we want to read registers starting from register address 0002, and the number of registers to be read is 2. Click "Read", and the send instruction will appear: 01 03 00 02 00 02 65 CB. 01 is the device address, 03 is the function code, representing the function of writing registers, 00 02 is the starting address of reading registers, and the latter 00 02 means to read the values of 2 registers, and 65 CB is the CRC check. The received data is: 01 03 04 00 00 00 00 FA 33. 01 is the device address, 03 is the function code, 04 represents that the number of data bytes read later is 4, 00 00 00 00 is the data inside the registers with addresses 00 02 and 00 03, and FA 33 is the CRC check.
It seems to be getting clearer and clearer. The so-called Modbus communication protocol is nothing more than the host sending different instructions, and the slave performs different operations according to the judgment of the instructions. Since our development board does not have as many corresponding functions as the Modbus function code, we define an array regGroup[5] in the program, which is equivalent to 5 registers. In addition, we define a sixth register to control the buzzer. By sending different instructions, we change the data of the register group or change the switch state of the buzzer. In the Modbus protocol, the address and value of the register are 16 bits, that is, 2 bytes. We default the high byte to 0x00, and the low byte is the value corresponding to the array regGroup. The addresses 0x0000 to 0x0004 correspond to the elements in the regGroup array. When we write, we display the numbers on our LCD1602 liquid crystal. For the address 0x0005, write 0x00, the buzzer will not sound, and write any other number, the buzzer will alarm. The main work of our microcontroller is to parse the data received by the serial port to perform different operations, which is mainly in the RS485.C file.
/***********************RS485.c file program source code****************************/
#include
#include
sbit RS485_DIR = P1^7; //RS485 direction selection pin
bit flagOnceTxd = 0; // Single transmission completion flag, that is, one byte is sent
bit cmdArrived = 0; //Command arrival flag, that is, receiving the command sent by the host computer
unsigned char cntRxd = 0;
unsigned char pdata bufRxd[40]; //Serial port receive buffer
unsigned char regGroup[5]; //Modbus register group, address is 0x00~0x04
extern bit flagBuzzOn;
extern void LcdShowStr(unsigned char x, unsigned char y, const unsigned char *str);
extern unsigned int GetCRC16(unsigned char *ptr, unsigned char len);
void ConfigUART(unsigned int baud) //Serial port configuration function, baud is the baud rate
{
RS485_DIR = 0; //RS485 is set to receive direction
SCON = 0x50; //Configure the serial port to mode 1
TMOD &= 0x0F; // Clear the control bit of T1
TMOD |= 0x20; //Configure T1 to mode 2
TH1 = 256 - (11059200/12/32) / baud; //Calculate T1 reload value
TL1 = TH1; //initial value equals reload value
ET1 = 0; //Disable T1 interrupt
ES = 1; // Enable serial port interrupt
TR1 = 1; //Start T1
}
unsigned char UartRead(unsigned char *buf, unsigned char len) //Serial port data reading function, data receiving pointer buf, read data length len, return value is the actual read data length
{
unsigned char i;
if (len > cntRxd) //When the read length is greater than the received data length,
{
len = cntRxd; //The read length is set to the actual received data length
}
for (i=0; i { *buf = bufRxd[i]; buf++; } cntRxd = 0; // Clear the receive counter return len; //Return the actual read length } void DelayX10us(unsigned char t) //Software delay function, delay time (t*10)us { do { _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); } while (--t); } void UartWrite(unsigned char *buf, unsigned char len) //Serial port data writing function, that is, serial port sending function, data pointer buf to be sent, data length len { RS485_DIR = 1; //RS485 is set to send while (len--) //send data { flagOnceTxd = 0; SBUF = *buf; buf++; while (!flagOnceTxd); } DelayX10us(5); //Wait for the last stop bit to complete, the delay time is determined by the baud rate RS485_DIR = 0; // RS485 is set to receive } void UartDriver() //Serial port driver function, detects received commands and executes corresponding actions { unsigned char i; unsigned char cnt; unsigned char len; unsigned char buf[30]; unsigned char str[4]; unsigned int crc; unsigned char crch, crcl; if (cmdArrived) //When a command arrives, read and process the command { cmdArrived = 0; len = UartRead(buf, sizeof(buf)); //Read the received command into the buffer if (buf[0] == 0x01) // Check the address to decide whether to respond to the command. In this case, the local address is 0x01 { crc = GetCRC16(buf, len-2); //Calculate CRC check value crch = crc >> 8; crcl = crc & 0xFF; if ((buf[len-2] == crch) && (buf[len-1] == crcl)) //Judge whether the CRC check is correct { switch (buf[1]) //Execute operation according to function code { case 0x03: //Read one or consecutive registers if ((buf[2] == 0x00) && (buf[3] <= 0x05)) //Register address supports 0x0000~0x0005 { if (buf[3] <= 0x04) { i = buf[3]; //Extract register address cnt = buf[5]; //Extract the number of registers to be read buf[2] = cnt*2; //The number of bytes of data to read is the number of registers*2, because the register defined by Modbus is 16 bits len = 3; while (cnt--) { buf[len++] = 0x00; //fill the high byte of the register with 0 buf[len++] = regGroup[i++]; //low byte } } else //Address 0x05 is the buzzer status { buf[2] = 2; //Number of bytes to read buf[3] = 0x00; buf[4] = flagBuzzOn; len = 5; } break; } else //When the register address is not supported, return an error code { buf[1] = 0x83; //Function code highest position 1 buf[2] = 0x02; //Set the exception code to 02-invalid address len = 3; break; } case 0x06: //Write a single register if ((buf[2] == 0x00) && (buf[3] <= 0x05)) //Register address supports 0x0000~0x0005 { if (buf[3] <= 0x04) { i = buf[3]; //Extract register address regGroup[i] = buf[5]; //Save register data cnt = regGroup[i] >> 4; //Display on LCD if (cnt >= 0xA) str[0] = cnt - 0xA + 'A'; else str[0] = cnt + '0'; cnt = regGroup[i] & 0x0F; if (cnt >= 0xA) str[1] = cnt - 0xA + 'A'; else str[1] = cnt + '0'; str[2] = ''; LcdShowStr(i*3, 0, str); } else //Address 0x05 is the buzzer status { flagBuzzOn = (bit)buf[5]; //Convert register value to buzzer on/off } len -= 2; //length -2 to recalculate CRC and return to the original frame break; } else //When the register address is not supported, return an error code { buf[1] = 0x86; //Function code highest position 1 buf[2] = 0x02; //Set the exception code to 02-invalid address len = 3; break; } default: //Other unsupported function codes buf[1] |= 0x80; //Function code highest position 1 buf[2] = 0x01; //Set the exception code to 01-invalid function len = 3; break; } crc = GetCRC16(buf, len); //Calculate CRC check value buf[len++] = crc >> 8; //CRC high byte buf[len++] = crc & 0xFF; //CRC low byte UartWrite(buf, len); //Send response frame } } } } [page] void UartRxMonitor(unsigned char ms) //Serial port receiving monitoring function { static unsigned char cntbkp = 0; static unsigned char idletmr = 0; if (cntRxd > 0) //When the receive counter is greater than zero, monitor the bus idle time { if (cntbkp != cntRxd) //Receive counter changes, that is, when data is just received, clear the idle timer { cntbkp = cntRxd; idletmr = 0; } else { if (idletmr < 5) //The receive counter has not changed, that is, when the bus is idle, the accumulated idle time { idletmr += ms; if (idletmr >= 5) //If the idle time exceeds 4 bytes of transmission time, it is considered that a frame of command has been received { cmdArrived = 1; //Set command arrival flag } } } } else { cntbkp = 0; } } void InterruptUART() interrupt 4 //UART interrupt service function { if (RI) // Byte received { RI = 0; //Manually clear the receive interrupt flag if (cntRxd < sizeof(bufRxd)) //When the receiving buffer is not exhausted, { bufRxd[cntRxd++] = SBUF; //Save the received byte and increment the counter } } if (TI) //bytes sent { TI = 0; //Manually clear the transmit interrupt flag flagOnceTxd = 1; //Set the single transmission completion flag } } /***********************lcd1602.c file program source code****************************/ #include #define LCD1602_DB P0 sbit LCD1602_RS = P1^0; sbit LCD1602_RW = P1^1; sbit LCD1602_E = P1^5; void LcdWaitReady() //Wait for the LCD to be ready { unsigned char sta; LCD1602_DB = 0xFF; LCD1602_RS = 0; LCD1602_RW = 1; do { LCD1602_E = 1; sta = LCD1602_DB; //Read status word LCD1602_E = 0; } while (sta & 0x80); //bit7 is equal to 1, indicating that the LCD is busy. Repeat the test until it is equal to 0. } void LcdWriteCmd(unsigned char cmd) //Write command function { LcdWaitReady(); LCD1602_RS = 0; LCD1602_RW = 0; LCD1602_DB = cmd; LCD1602_E = 1; LCD1602_E = 0; } void LcdWriteDat(unsigned char dat) //Write data function { LcdWaitReady(); LCD1602_RS = 1; LCD1602_RW = 0; LCD1602_DB = dat; LCD1602_E = 1; LCD1602_E = 0; } void LcdShowStr(unsigned char x, unsigned char y, const unsigned char *str) //Display string, screen starting coordinates (x, y), string pointer str { unsigned char addr; //Calculate the display RAM address from the input display coordinates if (y == 0) addr = 0x00 + x; //The first line of characters starts at 0x00 else addr = 0x40 + x; //The second line of characters starts at 0x40 //Write the string continuously from the starting display RAM address LcdWriteCmd(addr | 0x80); //Write the starting address while (*str != '') //Continuously write string data until the end character is detected { LcdWriteDat(*str); str++; } } void LcdInit() //LCD initialization function { LcdWriteCmd(0x38); //16*2 display, 5*7 dot matrix, 8-bit data interface LcdWriteCmd(0x0C); //Display on, cursor off LcdWriteCmd(0x06); //The text remains unchanged, the address is automatically increased by 1 LcdWriteCmd(0x01); //Clear screen } Regarding the CRC verification algorithm, if you are not specifically studying the verification algorithm itself, you don’t need to study the details of this program. The document directly provides us with the function, and we can call it directly. /***********************CRC16.c file program source code****************************/ unsigned int GetCRC16(unsigned char *ptr, unsigned char len) { unsigned int index; unsigned char crch = 0xFF; //high CRC byte unsigned char crcl = 0xFF; //low CRC byte unsigned char code TabH[] = { //CRC high byte value table 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40 } ; unsigned char code TabL[] = { //CRC low byte value table 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C, 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80, 0x40 } ; while (len--) //Calculate the CRC of the specified length { index = crch ^ *ptr++; crch = crcl ^ TabH[index]; crcl = TabL[index]; } return ((crch<<8) | crcl); } /***********************main.c file program source code****************************/ void ConfigTimer0(unsigned int ms); extern void LcdInit(); extern void ConfigUART(unsigned int baud); extern void UartRxMonitor(unsigned char ms); extern void UartDriver(); void main () { EA = 1; // Enable general interrupt ConfigTimer0(1); //Configure T0 timing 1ms ConfigUART(9600); //Configure the baud rate to 9600 LcdInit(); //Initialize LCD while(1) { UartDriver(); } } void ConfigTimer0(unsigned int ms) //T0 configuration function { unsigned long tmp; tmp = 11059200 / 12; //Timer counting frequency tmp = (tmp * ms) / 1000; //Calculate the required count value tmp = 65536 - tmp; //Calculate the timer reload value tmp = tmp + 34; //Correct the error caused by interrupt response delay T0RH = (unsigned char)(tmp >> 8); //Timer reload value is split into high and low bytes T0RL = (unsigned char)tmp; TMOD &= 0xF0; // Clear the control bit of T0 TMOD |= 0x01; //Configure T0 to mode 1 TH0 = T0RH; //Load T0 reload value TL0 = T0RL; ET0 = 1; // Enable T0 interrupt TR0 = 1; //Start T0 } void InterruptTimer0() interrupt 1 //T0 interrupt service function { TH0 = T0RH; //Timer reloads the reload value TL0 = T0RL; if (flagBuzzOn) //Buzzer on or off BUZZ = ~BUZZ; else BUZZ = 1; UartRxMonitor(1); //Serial port receiving monitoring }
Previous article:Basic Concepts of A/D and D/A
Next article:Interface circuit for 16-bit microprocessor
Recommended ReadingLatest update time:2024-11-16 16:44
- Popular Resources
- Popular amplifiers
Professor at Beihang University, dedicated to promoting microcontrollers and embedded systems for over 20 years.
- Innolux's intelligent steer-by-wire solution makes cars smarter and safer
- 8051 MCU - Parity Check
- How to efficiently balance the sensitivity of tactile sensing interfaces
- What should I do if the servo motor shakes? What causes the servo motor to shake quickly?
- 【Brushless Motor】Analysis of three-phase BLDC motor and sharing of two popular development boards
- Midea Industrial Technology's subsidiaries Clou Electronics and Hekang New Energy jointly appeared at the Munich Battery Energy Storage Exhibition and Solar Energy Exhibition
- Guoxin Sichen | Application of ferroelectric memory PB85RS2MC in power battery management, with a capacity of 2M
- Analysis of common faults of frequency converter
- In a head-on competition with Qualcomm, what kind of cockpit products has Intel come up with?
- Dalian Rongke's all-vanadium liquid flow battery energy storage equipment industrialization project has entered the sprint stage before production
- Allegro MicroSystems Introduces Advanced Magnetic and Inductive Position Sensing Solutions at Electronica 2024
- Car key in the left hand, liveness detection radar in the right hand, UWB is imperative for cars!
- After a decade of rapid development, domestic CIS has entered the market
- Aegis Dagger Battery + Thor EM-i Super Hybrid, Geely New Energy has thrown out two "king bombs"
- A brief discussion on functional safety - fault, error, and failure
- In the smart car 2.0 cycle, these core industry chains are facing major opportunities!
- The United States and Japan are developing new batteries. CATL faces challenges? How should China's new energy battery industry respond?
- Murata launches high-precision 6-axis inertial sensor for automobiles
- Ford patents pre-charge alarm to help save costs and respond to emergencies
- New real-time microcontroller system from Texas Instruments enables smarter processing in automotive and industrial applications