Problems of RS-485 communication in embedded development
RS-485 description
RS-485 is generally referred to as 485 bus, which is one of the most commonly used industrial buses. It generally adopts 2-wire half-duplex mode and uses differential mode to send and receive information. The highest speed can reach 10M BPS.
interface chip
When the microcontroller uses the 485 bus, it uses the UART or USART interface to complete the signal input and output through the RS-485 transceiver. Commonly used chips are MAX485, MAX3485, SP3485 and so on.
hardware connection
The hardware is very simple. RO and DI are connected to the UART interface of the single-chip microcomputer, which are data sending and receiving pins. RE and DE are connected and connected to the GPIO of the single-chip microcomputer to control the direction of data flow, whether it is input or output.
CubeMX settings
See the CubeMX tutorial here: Embedded Development – CubeMX Getting Started Tutorial
Connect to UART2, the specific settings are as follows:
These parameters need to be set according to your setting requirements, asynchronous mode, baud rate, number of bits, parity, stop bits, other defaults That's it. Enable interrupts to facilitate receiving data.
UART does not require a high baud rate, and the error is within 5%. Therefore, the on-chip RC oscillator can be used for the crystal oscillator. Of course, it is recommended to use an external quartz crystal oscillator. The frequency is more accurate, and more importantly, the reliability is high.
code writing
pin definition
#define MAX485_OUT() HAL_GPIO_WritePin(CTL_485_PORT,CTL_485_PIN, GPIO_PIN_SET)
#define MAX485_IN() HAL_GPIO_WritePin(CTL_485_PORT,CTL_485_PIN, GPIO_PIN_RESET)
enable serial port
HAL_UART_Receive_IT(&huart2, uart2_state_typedef.data, 1); //开启串口,接收到的数据放到uart2_state_typedef.data,每次接收1个字节
__HAL_UART_ENABLE_IT(&huart2, UART_IT_RXNE); //启动RXNE中断
__HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE); //启动IDLE中断
RXNE interrupt is used to receive data, receive 1 byte each time, and open this interrupt again in the interrupt
IDLE is used to judge the end of the frame, 485 8 bytes per frame, an IDLE interrupt will be generated after the bus is idle , Entering this interrupt means that a frame is over.
interrupt function
void USART2_IRQHandler(void)
{
/* USER CODE BEGIN USART2_IRQn 0 */
/* USER CODE END USART2_IRQn 0 */
HAL_UART_IRQHandler(&huart2);
/* USER CODE BEGIN USART2_IRQn 1 */
//RS485接口
//收到1个字节的数据
if(__HAL_UART_GET_FLAG(&huart2, UART_FLAG_RXNE))
{
__HAL_UART_ENABLE_IT(&huart2, UART_IT_RXNE); //启动RXNE中断
uart2_state_typedef.data[uart2_state_typedef.len] = huart2.Instance->RDR;
uart2_state_typedef.len++;
}
//总线空闲时,会发生一次IDLE中断,此时意味着数据接收完成
//不同的内核,清除IDLEIE的方式不同,请查阅手册
if(__HAL_UART_GET_FLAG(&huart2, UART_FLAG_IDLE))
{
huart2.Instance->ICR |= USART_ICR_IDLECF; //向USART_CR1的IDLECF位写1,以清除IDLEIF标志,否则会一直进IDLEIE中断
}
}
send data
void modbus_send(void) //发送数据到串口,数据需要事先在modbus_send_array中准备好
{
MAX485_OUT(); //转换为输出模式
delay_us(100); //延时,以等待接口芯片切换完成
HAL_UART_Transmit(MODBUS_PORT, modbus_send_array,8, 100); //从串口2发送数组命令
MAX485_IN(); //转换为输入模式
delay_us(10);
}
Receive data
u8 modbus_receive(u16 timeout) //发送指令后,读取伺服回传的数据,超时单位为ms
{
u8 ret = 255;
u8 i = 0;
while(1)
{
HAL_Delay(1);
i++;
if(i>timeout)
break;
if(uart2_state_typedef.state == UART_RECEIVE_OK)
{
uart2_state_typedef.state = 0;
ret = 0;
huart2.Instance->ICR |= (USART_ICR_EOBCF|USART_ICR_TCCF|USART_ICR_FECF|USART_ICR_PECF);//eobf txe tc fe pe
return ret;
break;
}
}
return ret;
}
Then you can send and receive.
There is a problem, an extra number was charged
So in the serial port interrupt, data reception, and IDLE, respectively trigger a level signal for observation. As shown below:
Waveform on the data line
The waveform of the first row is the sending pin
The waveform of the second row is the receiving pin
The waveform of the third row is the direction control pin
The fourth row is the receiving interrupt, and there is a pulse every time
the fifth row is the IDLE interrupt, the next step There is a pulse.
The figure below is enlarged, the waveform of a frame of data,
pay attention to the pulse in the red circle, the data has not been received yet, but it has already entered the interrupt and started receiving data once, which is an extra received character.
problem analysis
This pulse is where the pulse occurs. It is 5ms after the data transmission is completed and the direction of 485 is switched to reception. Obviously at that time, another RXNE interrupt is entered. The reason for the interrupt is also very simple. Because of the following figure Low level transition.
That is to say, when the data direction of the 485 changes from receiving to sending, the receiving port will detect a low level, which is considered as the start bit of the serial port receiving data, but there is no subsequent high level end bit, so The data received is definitely wrong, and the FE bit of the ISR register of UART2 also points out this point, as shown in the figure below
problem solved
Knowing the problem, you also know how to solve it. Modify the sending function slightly. After the sending is completed, clear the RXNE flag of the ISR register to solve the problem.
void modbus_send(void) //发送读寄存器的指令到串口
{
u32 i=0;
HAL_UART_AbortReceive_IT(&huart2);
__HAL_UART_DISABLE_IT(&huart2, UART_IT_RXNE); //禁用RXNE中断
MAX485_OUT();
delay_us(100);
HAL_UART_Transmit(MODBUS_PORT, modbus_send_array,8, 100); //从串口2发送数组命令
huart2.Instance->RQR |= USART_RQR_RXFRQ; //清除485方向切换导致的RXNE标识
huart2.Instance->ICR |= USART_ICR_IDLECF; //清除空闲标识
huart2.Instance->ICR |= USART_ICR_TCCF; //清除发送完成标识
__HAL_UART_ENABLE_IT(&huart2, UART_IT_RXNE); //启动RXNE中断
MAX485_IN();
uart2_state_typedef.len = 0;
uart2_state_typedef.state = UART_READY;
delay_us(10);
}
Entered 8 receive interrupts, received 8 characters, and entered an IDLE interrupt, indicating that the frame is over and the work is normal.