This is the 11th day of my participation in Gwen Challenge

Two days ago, when I was studying the serial port idle interrupt of STM32F103 microcontroller, I suddenly remembered that Modbus communication is very suitable for idle interrupt to deal with. Take a look at the communication specification in Modbus RTU mode.

It can be seen that in Modbus RTU communication mode, the start and end of data are distinguished by the idle character interval, while STM32F103 MCU has its own serial port idle mode detection.

Under normal circumstances, the detection of a frame of data communicated by Modbus can be judged by time, and it keeps reading whether the length of received data is changed. If the length of received data does not change within a certain period of time, it is considered that a frame of data is finished. Specific implementation can refer to STM8 learning notes -Modbus communication protocol simple transplantation.

Judging whether a frame of data is received by time will lead to the interruption of the microcontroller. STM32F103 microcontroller with its own idle data detection function, this function is completed by the internal hardware of the microcontroller, do not need to process the program, so that the microcontroller has the opportunity to perform more tasks. In order to make the single chip microcomputer processing Modbus RTU communication easier and faster, we can use serial DMA and idle interrupt to achieve this. DMA+ idle interrupt implementation method refer to STM32 MCU serial port idle interrupt +DMA receiving variable length data.

The following direct use of code to illustrate the specific implementation process, first initialize the serial port.

#define CHECK_NONE_ONE_STOP 1 #define CHECK_NONE_TWO_STOP 0 #define CHECK_NONE_TWO_STOP 0 #define CHECK_NONE_TWO_STOP 0 #define CHECK_ODD 0 #define CHECK_ODD 0 // Odd 1 valid 0 invalid void uart2_init(u16 baud  ) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE ); RCC_APB1PeriphClockCmd( RCC_APB1Periph_USART2, ENABLE ); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // Push pull multiplexing mode gPIo_initstructure. GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init( GPIOA, &GPIO_InitStructure ); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Speed = GPIO_Speed_50MHz; // Float input mode gPIo_initstructure. GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init( GPIOA, &GPIO_InitStructure ); NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init( &NVIC_InitStructure ); USART_InitStructure.USART_BaudRate = baud; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; #if(CHECK_EVEN == 1) #if(CHECK_EVEN == 1) #if(CHECK_EVEN == 1) #if(CHECK_EVEN == 1) USART_InitStructure.USART_Parity = USART_Parity_Even; #endif #if(CHECK_ODD == 1) #endif #if(CHECK_ODD == 1) USART_InitStructure.USART_Parity = USART_Parity_Odd; #endif #if(CHECK_NONE_ONE_STOP==1) // Stop bit is one usart_initstructure. USART_StopBits = USART_StopBits_1; #endif #if(CHECK_NONE_TWO_STOP==1) usARt_initstructure. USART_StopBits = USART_StopBits_2; #endif USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_Init( USART2, &USART_InitStructure ); #if (UART2_DMA == 1) USART_ITConfig( USART2, USART_IT_IDLE, ENABLE ); USART_DMACmd(USART2, USART_DMAReq_Rx, ENABLE); // enable DMA to receive uartDMA_Init(); #else USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); #endif USART_Cmd(USART2, ENABLE); // Enable RXNE interrupt and IDLE interrupt. RXNE interrupts when 1 byte is received, and IDLE interrupts when a frame is received. For example, eight RXNE interrupts and one IDLE interrupt are generated when eight bytes are sent to the microcontroller at a time. }Copy the code

In Modbus communication, each byte of data needs to be validated, so macro definitions are used here to initialize serial port data using that validation. Set CHECK_EVEN to 1 if parity is required, CHECK_ODD to 1 if parity is required, and both to 0 if parity bits are not required. When the serial port is initialized, idle interrupt and DMA functions need to be enabled.

Initialize the DMA function of serial port 2

void uartDMA_Init( void ) { DMA_InitTypeDef DMA_IniStructure; RCC_AHBPeriphClockCmd( RCC_AHBPeriph_DMA1, ENABLE ); // Enable DMA clock DMA_DeInit(DMA1_Channel6); //DMA1 channel 6 is USART2_RX dMA_inistructure. DMA_PeripheralBaseAddr = (u32) &usart2 ->DR; //DMA peripheral usART base address dma_inistruct. DMA_MemoryBaseAddr = (u32) dMA_rec_buff; //DMA memory base address dma_inistructure. DMA_DIR = DMA_DIR_PeripheralSRC; Dma_inistructure. DMA_BufferSize = DMA_REC_LEN; Dma_inistruct. DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_PeripheralInc_Disable; DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_MemoryInc_Enable; DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // Carry DMA_IniStructure in bytes.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // The data buffer is moved into dMA_inistructure. DMA_Mode = DMA_Mode_Normal; // Working in normal cache mode dma_inistructure. DMA_Priority = DMA_Priority_Medium; //DMA channel x has medium priority dMA_inistructure. DMA_M2M = DMA_M2M_Disable; //DMA channel x is not set to memory to memory transfer DMA_Init(DMA1_Channel6, &DMA_IniStructure); DMA_Cmd( DMA1_Channel6, ENABLE ); }Copy the code

The reception of serial port 2 corresponds to DMA channel 6, so the DMA channel selects channel 6, then uses the data register of serial port 2 as the peripheral base address and the array DMA_rec_buff as the DMA memory address. The peripheral address does not increase automatically, but the memory address increases automatically. For every byte received by serial port 2, DMA stores the received data into the array DMA_rec_buff and adds one bit to the address pointing to the array, so the data received by serial port 2 is stored in the array DMA_rec_buff.

Note that the array cache must be larger than the maximum number of data received by serial port 2. Otherwise, if the serial port data is long and the array is small, the data in the array will be overwritten and the received data will be incorrect.

Next is the realization of serial interrupt

void USART2_IRQHandler( void ) { u8 tem = 0; If (USART_GetITStatus(USART2, USART_IT_IDLE)! {USART_ReceiveData(USART2); // Read data note: this sentence must be used, otherwise it cannot clear the idle interrupt flag bit. DMA_Cmd( DMA1_Channel6, DISABLE ); Uart2_rec_cnt = dma_rec_len-dMA_getCurrdatacounter (DMA1_Channel6); Copy_data (DMA_rec_buff, uART2_rec_cnT); ReceiveOK_flag = 1; USART_ClearITPendingBit(USART2, USART_IT_IDLE); // Clear the idle interrupt flag myDMA_Enable(DMA1_Channel6); If (USART_GetITStatus(USART2, USART2, USART2, USART2, USART2, USART2) USART_IT_RXNE ) ! = RESET) // Receive interrupt {TEM = USART_ReceiveData(USART2); USART_SendData( USART2, tem ); } #endif }Copy the code

If the serial port is idle, a frame of data has been received. Close the DMA channel and start processing the data. Gets the length of the data received in DMA, then copies the data into the backup array, and sets the receive completion flag bit. Enable DMA for the next data reception.

The main program starts processing the received data when the completion flag is detected. In this way, the serial port can be separated from the data receiving and processing, and the serial port can continue to receive the next data while processing the previous data. The real-time performance of the system is improved.

If you use the DMA receive array to process the data directly, it is possible that the DMA receives the new data while the old data is being processed and stores the new data in the DMA cache array. In this case, the old data will be overwritten by the new data during data processing, causing system exceptions. If Modbus is not so fast and the system has time to process the last batch of data before the next one comes in, it is ok to use DMA cache data directly.

However, the above method of copying data into other data processing is also risky. For example, the first batch of data has not been processed, and the second batch of data has been received. When the serial port is idle and interrupted, the system will still copy the second batch of data to the backup array. The data in the backup array will still be overwritten. But when this happens, it means that the system is receiving data more often than it can process it, and the system itself is at risk and needs to be redesigned.

After receiving the data, you need to call the data handler function in the main program.

int main(void) { u8 j = 0; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); delay_init(); // the delay function initializes LED_Init(); // Initialize the hardware interface connected to the LED uart2_init(9600); While (1) {if(receiveOK_flag) // Start processing data after receiving a frame of data {receiveOK_flag = 0; DisposeReceive(); // handle modbus communication} j++; if(j > 50) { j = 0; LED = ! LED; } delay_ms(10); }}Copy the code

Finally, protocol analysis of Modbus

void DisposeReceive( void ) { u16 CRC16 = 0, CRC16Temp = 0; If (data_backup[0] == SlaveID) // Address = local address Address range: 1-32 {CRC16 = App_Tab_Get_CRC16(data_backup, datalen_backup-2); / / low byte in the high byte CRC check in the back High byte for message last CRC16Temp = ((under-16) (data_backup] [dataLen_backup - 1 < < 8) | data_backup[dataLen_backup - 2] ); if( CRC16 ! = CRC16Temp ) { err = 4; / / CRC check error} StartRegAddr = (under-16) (data_backup [2] < < 8) | data_backup [3]. if( StartRegAddr > (HoldRegStartAddr + HoldRegCount - 1) ) { err = 2; 00-07 channel 1-8} if(err == 0) {switch(data_backup[1]) {case 3: // Read multiple registers {Modbus_03_Slave(); break; } case 6: // Write a single register {Modbus_06_Slave(); break; } case 16: // write multiple registers {Modbus_16_Slave(); break; } default: { err = 1; // Break is not supported; } } } if( err > 0 ) { SendBuf[0] = data_backup[0]; SendBuf[1] = data_backup[1] | 0x80; SendBuf[2] = err; CRC16Temp = App_Tab_Get_CRC16(SendBuf, 3); SendBuf[3] = CRC16Temp & 0xFF; //CRC low SendBuf[4] = (CRC16Temp >> 8); //CRC high uart2_Send(SendBuf, 5); err = 0; Read Hold register 03 Master request message: 0x01 0x03 0x0000 0x0001 0x840A Read hold register 03 master request message: 0x01 0x03 0x0000 0x0001 0x840A Read hold register 03 master request message: 0x01 0x03 0x0000 0x0001 0x840A Slave normal response message: 0x01 0x03 0x02 0x09C4 0xBF87 The 2-byte data read is 0x09C4 */ void Modbus_03_Slave(void) {u16 RegNum = 0; u16 CRC16Temp = 0; u8 i = 0; RegNum = ( u16 )( data_backup[4] << 8 ) | data_backup[5]; If ((StartRegAddr + RegNum) <= (HoldRegStartAddr + HoldRegCount)) // Register address + register number within the specified range <=8 {SendBuf[0] = data_backup[0]; SendBuf[1] = data_backup[1]; SendBuf[2] = RegNum * 2; // Return the number of bytes for(I = 0; i < RegNum; I ++) {SendBuf[3 + I * 2] = HoldReg[StartRegAddr * 2 + I * 2]; SendBuf[4 + i * 2] = HoldReg[StartRegAddr * 2 + i * 2 + 1]; } CRC16Temp = App_Tab_Get_CRC16( SendBuf, RegNum * 2 + 3 ); SendBuf[RegNum * 2 + 3] = CRC16Temp & 0xFF; SendBuf[RegNum * 2 + 4] = (CRC16Temp >> 8); // Low CRC SendBuf[RegNum * 2 + 4] = (CRC16Temp >> 8); //CRC high uart2_Send(SendBuf, RegNum * 2 + 5); } else { err = 3; // The number of registers is not in the specified range}} /* Function Function: Write single hold register 06 Master request message: 0x01 0x06 0x0000 0x1388 0x849C Write register 0 value: 0x1388 Slave normal response message: 0x01 0x06 0x0000 0x1388 0x849C The value of register 0 is 0x1388 */ void Modbus_06_Slave(void) {u16 RegValue = 0; u16 CRC16Temp = 0; RegValue = ( u16 )( data_backup[4] << 8 ) | data_backup[5]; If (RegValue <= HoldMaxValue) // Register value not exceeding 1000 {HoldReg[StartRegAddr * 2] = data_backup[4]; HoldReg[StartRegAddr * 2 + 1] = data_backup[5]; HoldReg[StartRegAddr * 2 + 1] = data_backup[5]; SendBuf[0] = data_backup[0]; SendBuf[1] = data_backup[1]; SendBuf[2] = data_backup[2]; SendBuf[3] = data_backup[3]; SendBuf[4] = data_backup[4]; SendBuf[5] = data_backup[5]; CRC16Temp = App_Tab_Get_CRC16(SendBuf, 6); SendBuf[6] = CRC16Temp & 0xFF; // Low CRC SendBuf[7] = (CRC16Temp >> 8); //CRC high uart2_Send(SendBuf, 8); } else { err = 3; // The register value is not in the specified range}} /* 0x01 0x10 0x7540 0x0002 0x04 0x0000 0x2710 0xB731 Write two hold register values starting from 0x7540 4-byte slave station normal response message: 0x01 0x10 0x7540 0x0002 0x5A10 Write two hold register values starting from 0x7540 */ void Modbus_16_Slave(void) {u16 RegNum = 0; u16 CRC16Temp = 0; u8 i = 0; RegNum = ( u16 )( data_backup[4] << 8 ) | data_backup[5]; If ((StartRegAddr + RegNum) <= (HoldRegStartAddr + HoldRegCount)) {for(I = 0; i < RegNum; HoldReg[StartRegAddr * 2 + I * 2] = data_backup[I * 2 + 7]; HoldReg[StartRegAddr * 2 + I * 2] = data_backup[I * 2 + 7]; HoldReg[StartRegAddr * 2 + 1 + i * 2] = data_backup[i * 2 + 8]; } SendBuf[0] = data_backup[0]; SendBuf[1] = data_backup[1]; SendBuf[2] = data_backup[2]; SendBuf[3] = data_backup[3]; SendBuf[4] = data_backup[4]; SendBuf[5] = data_backup[5]; CRC16Temp = App_Tab_Get_CRC16(SendBuf, 6); SendBuf[6] = CRC16Temp & 0xFF; // Low CRC SendBuf[7] = (CRC16Temp >> 8); //CRC high uart2_Send(SendBuf, 8); } else { err = 3; // The number of registers is not specified}}Copy the code

Modbus implementations only implement the three most common ones here.

The realization principle of each function code is not said here, directly through the code and annotations can be seen.

The complete code for serial port and Modbus is posted below

uart2.h

#ifndef __UART2_H #define __UART2_H #include "sys.h" #define DMA_REC_LEN 50; void uartDMA_Init( void ); void myDMA_Enable( DMA_Channel_TypeDef*DMA_CHx ); void uart2_Send( u8 *buf, u16 len ); void copy_data( u8 *buf, u16 len ); #endifCopy the code

uart2.c

//串口2空闲中断 + DMA数据传输
#include "uart2.h"
#define UART2_DMA  1																				//使用串口2  DMA传输
#define  CHECK_NONE_ONE_STOP    1 //无校验位  1个停止位  1有效  0 无效 
#define  CHECK_NONE_TWO_STOP    0 //无校验位  2个停止位  1有效  0 无效 
#define  CHECK_EVEN    0          //偶数校验   1有效  0 无效 
#define  CHECK_ODD     0          //奇数校验   1有效  0 无效 

u8 dma_rec_buff[DMA_REC_LEN] = {0};
u16 uart2_rec_cnt = 0;																			//串口接收数据长度

u8 data_backup[DMA_REC_LEN] = {0}; 													//数据备份
u16 dataLen_backup = 0;																			//长度备份
_Bool receiveOK_flag = 0;																		//接收完成标志位

/*
空闲中断是什么意思呢?
指的是当总线接收数据时,一旦数据流断了,此时总线没有接收传输,处于空闲状态,IDLE就会置1,产生空闲中断;又有数据发送时,IDLE位就会置0;
注意:置1之后它不会自动清0,也不会因为状态位是1而一直产生中断,它只有0跳变到1时才会产生,也可以理解为上升沿触发。
所以,为确保下次空闲中断正常进行,需要在中断服务函数发送任意数据来清除标志位。
*/

void  uart2_init( u16 baud )
{
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    NVIC_InitTypeDef  NVIC_InitStructure;

    RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE );
    RCC_APB1PeriphClockCmd( RCC_APB1Periph_USART2, ENABLE );

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;												 //推挽复用模式
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init( GPIOA, &GPIO_InitStructure );

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;									 //浮空输入模式
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init( GPIOA, &GPIO_InitStructure );

    NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

    NVIC_Init( &NVIC_InitStructure );

    USART_InitStructure.USART_BaudRate = baud;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No;
		
#if(CHECK_EVEN == 1) 																											 //如果定义了偶校验  数据位长度要改为9位
    USART_InitStructure.USART_WordLength = USART_WordLength_9b;
    USART_InitStructure.USART_Parity = USART_Parity_Even;
#endif

#if(CHECK_ODD == 1) 																											 //如果定义了奇校验  数据位长度要改为9位
    USART_InitStructure.USART_WordLength = USART_WordLength_9b;
    USART_InitStructure.USART_Parity = USART_Parity_Odd;
#endif

#if(CHECK_NONE_ONE_STOP==1)																							   //停止位为 一位
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
#endif

#if(CHECK_NONE_TWO_STOP==1)																								 //停止位为 两位																	
    USART_InitStructure.USART_StopBits = USART_StopBits_2;
#endif		
		
		
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;

    USART_Init( USART2, &USART_InitStructure );
		
#if (UART2_DMA == 1)
    USART_ITConfig( USART2, USART_IT_IDLE, ENABLE );											 //使能串口空闲中断
    USART_DMACmd( USART2, USART_DMAReq_Rx, ENABLE );											 //使能串口2 DMA接收
    uartDMA_Init();																												 //初始化 DMA
#else
    USART_ITConfig( USART2, USART_IT_RXNE, ENABLE );			  							 //使能串口RXNE接收中断
#endif
    USART_Cmd( USART2, ENABLE );																					 //使能串口2
		
//RXNE中断和IDLE中断的区别?
//当接收到1个字节,就会产生RXNE中断,当接收到一帧数据,就会产生IDLE中断。比如给单片机一次性发送了8个字节,就会产生8次RXNE中断,1次IDLE中断。
}

void uartDMA_Init( void )
{
    DMA_InitTypeDef  DMA_IniStructure;

    RCC_AHBPeriphClockCmd( RCC_AHBPeriph_DMA1, ENABLE ); 									 //使能DMA时钟

    DMA_DeInit( DMA1_Channel6 );																			     //DMA1通道6对应 USART2_RX
    DMA_IniStructure.DMA_PeripheralBaseAddr = ( u32 )&USART2->DR;					 //DMA外设usart基地址
    DMA_IniStructure.DMA_MemoryBaseAddr = ( u32 )dma_rec_buff; 						 //DMA内存基地址
    DMA_IniStructure.DMA_DIR = DMA_DIR_PeripheralSRC;											 //数据传输方向,从外设读取发送到内存
    DMA_IniStructure.DMA_BufferSize = DMA_REC_LEN;												 //DMA通道的DMA缓存的大小  也就是 DMA一次传输的字节数
    DMA_IniStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;				 //外设地址寄存器不变
    DMA_IniStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;								 //数据缓冲区地址递增
    DMA_IniStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外设以字节为单位搬运
    DMA_IniStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; 				 //数据缓冲区以字节为单位搬入
    DMA_IniStructure.DMA_Mode = DMA_Mode_Normal;													 //工作在正常缓存模式
    DMA_IniStructure.DMA_Priority = DMA_Priority_Medium;									 //DMA通道 x拥有中优先级
    DMA_IniStructure.DMA_M2M = DMA_M2M_Disable;														 //DMA通道x没有设置为内存到内存传输

    DMA_Init( DMA1_Channel6, &DMA_IniStructure );
    DMA_Cmd( DMA1_Channel6, ENABLE );
}

//重新恢复DMA指针
void myDMA_Enable( DMA_Channel_TypeDef*DMA_CHx )
{
    DMA_Cmd( DMA_CHx, DISABLE );  																				 //关闭DMA1所指示的通道
    DMA_SetCurrDataCounter( DMA_CHx, DMA_REC_LEN );												 //DMA通道的DMA缓存的大小
    DMA_Cmd( DMA_CHx, ENABLE );  																					 //DMA1所指示的通道
}
//发送len个字节
//buf:发送区首地址
//len:发送的字节数
void uart2_Send( u8 *buf, u16 len )
{
    u16 t;
    for( t = 0; t < len; t++ )																						 //循环发送数据
    {
        while( USART_GetFlagStatus( USART2, USART_FLAG_TC ) == RESET );
        USART_SendData( USART2, buf[t] );
    }
    while( USART_GetFlagStatus( USART2, USART_FLAG_TC ) == RESET );
}

//备份接收到的数据
void copy_data( u8 *buf, u16 len )
{
    u16 t;
    dataLen_backup = len;																									 //保存数据长度
    for( t = 0; t < len; t++ )
    {
        data_backup[t] = buf[t];																					 //备份接收到的数据,防止在处理数据过程中接收到新数据,将旧数据覆盖掉。
    }
}
// Modbus RTU 通信协议
//地址码 功能码 寄存器高位 寄存器低位  数据高位  数据低位 CRC高位 CRC低位
//01     03      00          00         03         e8      xx        xx
//利用空闲中断接收串口不定长数据
//串口接收一组数据结束后才会进入中断,在数据发送过程中,已经接收到的数据将会被存入DMA的缓冲区中
void USART2_IRQHandler( void )
{
    u8 tem = 0;
#if (UART2_DMA == 1)			//如果使能了 UART2_DMA 则使用串口空闲中断和 DMA功能接收数据
	if( USART_GetITStatus( USART2, USART_IT_IDLE ) != RESET )							 			  //空闲中断  一帧数据发送完成
    {
        USART_ReceiveData( USART2 );																			 			//读取数据注意:这句必须要,否则不能够清除空闲中断标志位。
        DMA_Cmd( DMA1_Channel6, DISABLE );																 			//关闭 DMA 防止后续数据干扰
        uart2_rec_cnt = DMA_REC_LEN - DMA_GetCurrDataCounter( DMA1_Channel6 );  //DMA接收缓冲区数据长度减去当前 DMA传输通道中剩余单元数量就是已经接收到的数据数量
        //uart2_Send( dma_rec_buff, uart2_rec_cnt );														//发送接收到的数据
			  copy_data( dma_rec_buff, uart2_rec_cnt );														    //备份数据
        receiveOK_flag = 1;																											//置位数据接收完成标志位
        USART_ClearITPendingBit( USART2, USART_IT_IDLE );									 			//清除空闲中断标志位
        myDMA_Enable( DMA1_Channel6 );																		 			//重新恢复 DMA等待下一次接收
    }
#else										//如果未使能 UART2_DMA 则通过常规方式接收数据  每接收到一个字节就会进入一次中断
    if( USART_GetITStatus( USART2, USART_IT_RXNE ) != RESET )						 //接收中断
    {
        tem = USART_ReceiveData( USART2 );
        USART_SendData( USART2, tem );
    }
#endif
}
Copy the code

modbus.h

#ifndef __MODBUS_H #define __MODBUS_H #include "sys.h" #define SlaveID 0x01 void Modbus_03_Slave(void); void Modbus_06_Slave(void); void Modbus_16_Slave(void); #endifCopy the code

modbus.c

#include "modbus.h" #include "crc16.h" #include "uart2.h" #define HoldRegStartAddr 0x0000 Extern u8 data_backup[DMA_REC_LEN]; // extern u8 data_backup[DMA_REC_LEN]; Extern u16 dataLen_backup; extern u16 dataLen_backup; U8 SendBuf[DMA_REC_LEN] = {0}; u16 StartRegAddr = HoldRegStartAddr; u8 err = 0; U8 HoldReg[HoldRegCount*2] = {1, 1, 2, 2, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8}; 0x02 Start address not in the specified range 0x03 Number of registers not in the specified range 0x04 Data verification error */ / process received data // Receive: [Address][Function code][Start ADDRESS high][Start address low][Total Register number high][Total register number Low][CRC low][CRC high] void DisposeReceive(void) {u16 CRC16 = 0, CRC16Temp = 0; If (data_backup[0] == SlaveID) // Address = local address Address range: 1-32 {CRC16 = App_Tab_Get_CRC16(data_backup, datalen_backup-2); / / low byte in the high byte CRC check in the back High byte for message last CRC16Temp = ((under-16) (data_backup] [dataLen_backup - 1 < < 8) | data_backup[dataLen_backup - 2] ); if( CRC16 ! = CRC16Temp ) { err = 4; / / CRC check error} StartRegAddr = (under-16) (data_backup [2] < < 8) | data_backup [3]. if( StartRegAddr > (HoldRegStartAddr + HoldRegCount - 1) ) { err = 2; 00-07 channel 1-8} if(err == 0) {switch(data_backup[1]) {case 3: // Read multiple registers {Modbus_03_Slave(); break; } case 6: // Write a single register {Modbus_06_Slave(); break; } case 16: // write multiple registers {Modbus_16_Slave(); break; } default: { err = 1; // Break is not supported; } } } if( err > 0 ) { SendBuf[0] = data_backup[0]; SendBuf[1] = data_backup[1] | 0x80; SendBuf[2] = err; CRC16Temp = App_Tab_Get_CRC16(SendBuf, 3); SendBuf[3] = CRC16Temp & 0xFF; //CRC low SendBuf[4] = (CRC16Temp >> 8); //CRC high uart2_Send(SendBuf, 5); err = 0; Read Hold register 03 Master request message: 0x01 0x03 0x0000 0x0001 0x840A Read hold register 03 master request message: 0x01 0x03 0x0000 0x0001 0x840A Read hold register 03 master request message: 0x01 0x03 0x0000 0x0001 0x840A Slave normal response message: 0x01 0x03 0x02 0x09C4 0xBF87 The 2-byte data read is 0x09C4 */ void Modbus_03_Slave(void) {u16 RegNum = 0; u16 CRC16Temp = 0; u8 i = 0; RegNum = ( u16 )( data_backup[4] << 8 ) | data_backup[5]; If ((StartRegAddr + RegNum) <= (HoldRegStartAddr + HoldRegCount)) // Register address + register number within the specified range <=8 {SendBuf[0] = data_backup[0]; SendBuf[1] = data_backup[1]; SendBuf[2] = RegNum * 2; // Return the number of bytes for(I = 0; i < RegNum; I ++) {SendBuf[3 + I * 2] = HoldReg[StartRegAddr * 2 + I * 2]; SendBuf[4 + i * 2] = HoldReg[StartRegAddr * 2 + i * 2 + 1]; } CRC16Temp = App_Tab_Get_CRC16( SendBuf, RegNum * 2 + 3 ); SendBuf[RegNum * 2 + 3] = CRC16Temp & 0xFF; SendBuf[RegNum * 2 + 4] = (CRC16Temp >> 8); // Low CRC SendBuf[RegNum * 2 + 4] = (CRC16Temp >> 8); //CRC high uart2_Send(SendBuf, RegNum * 2 + 5); } else { err = 3; // The number of registers is not in the specified range}} /* Function Function: Write single hold register 06 Master request message: 0x01 0x06 0x0000 0x1388 0x849C Write register 0 value: 0x1388 Slave normal response message: 0x01 0x06 0x0000 0x1388 0x849C The value of register 0 is 0x1388 */ void Modbus_06_Slave(void) {u16 RegValue = 0; u16 CRC16Temp = 0; RegValue = ( u16 )( data_backup[4] << 8 ) | data_backup[5]; If (RegValue <= HoldMaxValue) // Register value not exceeding 1000 {HoldReg[StartRegAddr * 2] = data_backup[4]; HoldReg[StartRegAddr * 2 + 1] = data_backup[5]; HoldReg[StartRegAddr * 2 + 1] = data_backup[5]; SendBuf[0] = data_backup[0]; SendBuf[1] = data_backup[1]; SendBuf[2] = data_backup[2]; SendBuf[3] = data_backup[3]; SendBuf[4] = data_backup[4]; SendBuf[5] = data_backup[5]; CRC16Temp = App_Tab_Get_CRC16(SendBuf, 6); SendBuf[6] = CRC16Temp & 0xFF; // Low CRC SendBuf[7] = (CRC16Temp >> 8); //CRC high uart2_Send(SendBuf, 8); } else { err = 3; // The register value is not in the specified range}} /* 0x01 0x10 0x7540 0x0002 0x04 0x0000 0x2710 0xB731 Write two hold register values starting from 0x7540 4-byte slave station normal response message: 0x01 0x10 0x7540 0x0002 0x5A10 Write two hold register values starting from 0x7540 */ void Modbus_16_Slave(void) {u16 RegNum = 0; u16 CRC16Temp = 0; u8 i = 0; RegNum = ( u16 )( data_backup[4] << 8 ) | data_backup[5]; If ((StartRegAddr + RegNum) <= (HoldRegStartAddr + HoldRegCount)) {for(I = 0; i < RegNum; HoldReg[StartRegAddr * 2 + I * 2] = data_backup[I * 2 + 7]; HoldReg[StartRegAddr * 2 + I * 2] = data_backup[I * 2 + 7]; HoldReg[StartRegAddr * 2 + 1 + i * 2] = data_backup[i * 2 + 8]; } SendBuf[0] = data_backup[0]; SendBuf[1] = data_backup[1]; SendBuf[2] = data_backup[2]; SendBuf[3] = data_backup[3]; SendBuf[4] = data_backup[4]; SendBuf[5] = data_backup[5]; CRC16Temp = App_Tab_Get_CRC16(SendBuf, 6); SendBuf[6] = CRC16Temp & 0xFF; // Low CRC SendBuf[7] = (CRC16Temp >> 8); //CRC high uart2_Send(SendBuf, 8); } else { err = 3; // The number of registers is not specified}}Copy the code

The test results

Test Function code 0x03 Read one or more hold register values

Test Function code 0x06 Write a single hold stored value

Test Function code 0x10 Write multiple hold register values

Read all register values again

The register value is changed successfully.