基于UART串口的stm32 modbusRTU实现(软件方式)详解

1. 实现思路

 此程序中, 串口通信方式: 115200-n-8-1, modbus协议要求帧与帧之间的间隔必须大于3.5个字符时间间隙作为帧与帧之间的分割. 

字符时间计算公式: interval_time = character_interval * 8 / baud_speed * 10 ^ 6 (微秒)

1.1 设置定时器, 超时时间为interval_time.

1.2 设置stm32的uart串口接收数据中断, 每次读取数据都重置定时器计数为0

RDR中断启用标记

1.3 定时器超时后, 说明此时modbus帧已经传输结束, 在定时器超时函数中处理响应.

2.编程

2.1 设置定时器超时

使用的波特率为115200bit/s, 则字符间隔时间大约为2431微妙.

在cubemx中选择一个定时器设置超时时间为2431微妙.

这里我们使用的时钟是16MHz, 定时器prescaler设置为16, 每隔1us触发一次计时器计时, 共计时2430次. 

2.2 设置UART接收字符中断

  • cubemx配置好程序初始化后, 在CR1寄存器中启用RXNEIE标记位, 启用RDR可读中断
  • 在USART中断入口判断RDR寄存器是否可读, 可读则将数据加入全局缓冲区, 并修改缓冲区长度, 修改定时器计数寄存器 CNT = 0
  • //main.c文件中修改
    HAL_TIM_Base_Start_IT(&htim14);//启动定时器
    huart1.Instance->CR1 |= 1 << 5;//enable reception interruption
    
    
    //stm32_it.c文件中修改UART1中断入口函数USART1_IRQHandler
    if(huart1.Instance->ISR & 1 << 5)
    {
    //	uint8_t data = huart1.Instance->RDR;
    //	while( (huart1.Instance->ISR & 1 << 7) == 0);
    //	huart1.Instance->TDR = data;
    	uart_buf[uart_buf_len++] = huart1.Instance->RDR;;
    	TIM14->CNT = 0;
    }
  • 当定时器中断触发后, 说明上一个帧已经接收完成, 在定时器中断超时函数处理帧.
  • 2.3定时器中断函数实现modbusRTU协议解析

    2.3.1 crc校验

    modbus协议使用crc16-modbus标准校验.

    此处通过直接计算方法生成crc.

    #define POLYNOMIAL 0xa001
    
    uint16_t generate_crc(uint8_t* buf, uint8_t len)
    {
    	uint16_t crc = 0xffff;
    	uint8_t i, j;
    	for(i = 0; i < len; i++)
    	{
    		crc ^= buf[i];
    		for(j = 0; j < 8; j++)
    		{
    			uint8_t tmp = crc & 0x0001;
    			crc >>= 1;
    			if( tmp == 1)
    				crc ^= POLYNOMIAL;
    		}
    	}
    	return crc;
    
    }
  • Tx:106-01 03 00 00 00 0A C5 CD
  • 这是从modbuspoll中获取到的一个帧序列, 可以通过此序列进行crc校验, 后两个字节为校验码.

    低地址在前, 高地址在后. CD C5 为其crc值 

    2.3.2 判断是否解析帧

    首先判断帧中的请求地址是否为此设备, 如果不是, 将第二个字节的高位置1, 重新生成crc, 发送给主机.

    uint8_t check_receive_packet()
    {
    	
    	if( uart_buf[0] != local_address) return 3;
    	uint16_t crc = generate_crc(uart_buf, uart_buf_size - 2);
    	if(crc != (uart_buf[uart_buf_size - 1] << 8 | uart_buf[uart_buf_size - 2]) )
    			return 8;
    	return 0;
    
    }

    2.3.3 帧解析

    if(uart_buf_size > 0)
    		{
    			uint8_t i = 0;
    			
    			if( (ret = check_receive_packet()) )
    			{
    				send_buf[i++] = uart_buf[0];
    				send_buf[i++] = uart_buf[1] | 0x80;
    				send_buf[i++] = ret; //exception code
    				crc = generate_crc(send_buf, i);
    				send_buf[i++] = crc & 0xff;
    				send_buf[i++] = crc >> 8;
    				send_data(send_buf, i);
    				uart_buf_size = 0;
    				processed = 1;
    				return;
    			}
    			uint8_t function_code = uart_buf[1];
    			uint16_t reg_addr = uart_buf[2] << 8 | uart_buf[3];
    			//uint16_t val;
    			switch(function_code)
    			{
    				case 0x03:
    					if( reg_addr == 0x01)
    					{
    						send_buf[i++] = uart_buf[0];
    						send_buf[i++] = uart_buf[1];
    						send_buf[i++] = 2;
    						send_buf[i++] = (uint8_t)(g_device_id >> 8);
    						send_buf[i++] = (uint8_t)g_device_id;
    						crc = generate_crc(send_buf, i);
    						send_buf[i++] = crc & 0xff;
    						send_buf[i++] = crc >> 8;
    						send_data(send_buf, i);
    					}else if( reg_addr == 0x02)
    					{
    						send_buf[i++] = uart_buf[0];
    						send_buf[i++] = uart_buf[1];
    						send_buf[i++] = sizeof(uint8_t) * 2;
    						send_buf[i++] = g_temperature_int;
    						send_buf[i++] = g_temperature_dot;
    						crc = generate_crc(send_buf, i);
    						send_buf[i++] = crc & 0xff;
    						send_buf[i++] = crc >> 8;
    						send_data(send_buf, i);
    					} else if( reg_addr == 0x03)
    					{
    						send_buf[i++] = uart_buf[0];
    						send_buf[i++] = uart_buf[1];
    						send_buf[i++] = sizeof(uint8_t) * 2;
    						send_buf[i++] = g_humidity_int;
    						send_buf[i++] = g_humidity_dot;
    						crc = generate_crc(send_buf, i);
    						send_buf[i++] = crc & 0xff;
    						send_buf[i++] = crc >> 8;
    						send_data(send_buf, i);
    					} else if( reg_addr == 0x04)
    					{
    						send_buf[i++] = uart_buf[0];
    						send_buf[i++] = uart_buf[1];
    						send_buf[i++] = sizeof(uint8_t) * 2;
    						
    						uint8_t _int = (int)g_humidity_start;
    						uint8_t x = g_humidity_start - _int;
    						uint8_t _dot = (int)(x * 100); //keep 2 decimals
    						
    						send_buf[i++] = _int;
    						send_buf[i++] = _dot;
    						crc = generate_crc(send_buf, i);
    						send_buf[i++] = crc & 0xff;
    						send_buf[i++] = crc >> 8;
    						send_data(send_buf, i);
    					}
    					else if(reg_addr == 0x05)
    					{
    						send_buf[i++] = uart_buf[0];
    						send_buf[i++] = uart_buf[1];
    						send_buf[i++] = sizeof(uint8_t) * 2;
    						
    						uint8_t _int = (int)g_temperature_start;
    						uint8_t x = g_temperature_start - _int;
    						uint8_t _dot = (int)(x * 100); //keep 2 decimals
    						
    						send_buf[i++] = _int;
    						send_buf[i++] = _dot;
    						crc = generate_crc(send_buf, i);
    						send_buf[i++] = crc & 0xff;
    						send_buf[i++] = crc >> 8;
    						send_data(send_buf, i);
    					}
    					
    					break;
    				case 0x06:
    					 i = 1;
    					//val = uart_buf[4] << 8 | uart_buf[5];
    					if( reg_addr == 0x04)
    					{
    						//register_val = val;
    						//send_data(uart_buf, uart_buf_len);
    						uint8_t _dot = uart_buf[4];
    						uint8_t _int = uart_buf[5];
    						g_humidity_start = uint2float(_int, _dot);
    						
    					}else if(reg_addr == 0x05)
    					{
    						uint8_t _dot = uart_buf[4];
    						uint8_t _int = uart_buf[5];
    						g_temperature_start = uint2float(_int, _dot);
    					}
    					send_data(uart_buf, uart_buf_size);
    					break;
    				}
    			
    				if(i == 0)
    				{
    					while(i < uart_buf_size)
    					{
    						fputc(uart_buf[i], (FILE*)100);
    						i++;
    					}
    				}
    				
    				processed = 1;
    				uart_buf_size = 0;
    			}

    物联沃分享整理
    物联沃-IOTWORD物联网 » 基于UART串口的stm32 modbusRTU实现(软件方式)详解

    发表评论