【STM32】RS485 Modbus協議 採集傳感器數據

1、硬件編程

一、傳感器:爲液壓傳感器,12vDC,RS485數據輸出,採用Modbus協議通訊
二、電路:根據傳感器屬性,電路主要是兩部分,通訊電路和電源電源
(1)485電路:因爲485是半雙工通訊,須要控制收發,因此索性在把電路設計成自動收發電路在這裏插入圖片描述
接跳線帽W一、W2即便用RS485收發,不接就是普通串口收發。RE和DE是收發使能,選擇485模式:3_TXD常高,使能接受;當發送數據時,數據的起始位(降低沿)將3_TXD引腳電平拉低,將順便使能發送。傳感器接3_A、3_B。


數組

(2)開關電路:主要是爲了控制傳感器開關,以下降功耗
在這裏插入圖片描述
緩存

2、測試485電路函數

一、485電路測試程序
對於單片機來講,其實仍然是串口通訊,只不過通過485芯片實現了RS232電平→RS485電平。
(1)初始化串口3,並在初始化時開啓串口接收中斷

測試

void MX_USART3_UART_Init(void)
{

  huart3.Instance = USART3;
  huart3.Init.BaudRate = 9600;
  huart3.Init.WordLength = UART_WORDLENGTH_8B;
  huart3.Init.StopBits = UART_STOPBITS_1;
  huart3.Init.Parity = UART_PARITY_NONE;
  huart3.Init.Mode = UART_MODE_TX_RX;
  huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart3.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart3) != HAL_OK)
  {
    Error_Handler();
  }
  Usart3RecIT();//開啓串口3接收中斷
  start_capture();//發送請求幀
}

(2)接收中斷回調函數中保存串口數據ui

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{

	if(huart->Instance == USART3){//RS485設備
		HAL_UART_Receive_IT(&huart3, &uart3Data, 1);
        uart3WriteByte(uart3Data);//接收到的數據寫入緩存
		uart3DataFlg = 1;
	}	
}

(3)緩存數據:環形隊列,不囉嗦spa

#define SENSOR_485_DATA_BUFFER_MAX_LENGTH 60
typedef struct {
	uint16_t   front;
	uint16_t   rear;
	uint8_t*   buffer;
	uint32_t   maxSize;
}Buffer_t;
static Buffer_t sensor485Buffer;
uint8_t  sensor485DataBuffer[SENSOR_485_DATA_BUFFER_MAX_LENGTH];//緩存數組

void uart3WriteByte(uint8_t data)
{
	Buffer_Puts(&sensor485Buffer,&data,1);//入隊
}

bool Buffer_Puts(Buffer_t* buffer, uint8_t* data, uint16_t length)
{
	if (buffer->maxSize - Buffer_Size(buffer) <= length)//隊滿
		return false;
	for (uint16_t i = 0; i<length; ++i)//隊列未滿
	{
		buffer->rear = (buffer->rear + 1) % buffer->maxSize;
		buffer->buffer[buffer->rear] = data[i];//進隊
	}
	return true;
}

(4)這個接口,是爲了向傳感器發送指令,請求傳感器數據,指令須要查看傳感器指令定義,屬於協議那部分,先測試設計

void start_capture(){
    uint8_t TxData[10]= "1111111111";
	HAL_UART_Transmit(&huart3,TxData,10,0xffff);
    HAL_Delay(100);

二、在Keil中Debug,用串口助手向單片機發送數據,查看數組sensor485DataBuffer接收到了,接收是能夠了
在這裏插入圖片描述
code

三、可是我遇到了問題,上位機沒有接受到2-(4)發送的「1111111111」,單片機發送出現了問題blog

(1)分別檢查TX、R1五、R16都有信號出來
在這裏插入圖片描述
(2)第一反應是485芯片發送使能沒有成功,檢查RE和DE引腳,果真一直是低電平,說明三極管一直導通,沒有阻塞過
在這裏插入圖片描述
(3)無奈之下,乾脆用反相器替換了三極管
在這裏插入圖片描述
(4)但願能用吧,抓緊寫軟件了





3、加入Modbus協議

一、協議原理
(1)以上測試說明:鏈路層硬件協議√;
(2)可是問題來了,只有硬件協議能夠和傳感器通訊嗎? 固然不行,傳感器又不是電腦,它沒有上位機:你點一下發送就把數據發出去了。這個時候須要單片機來告知它發送。因此咱們還須要:鏈路層軟件協議Modbus協議√;
(3)咱們選取Modbus協議中對咱們編程有幫助的幾點:


  • 以幀的形式通訊,有ASCII和RTU兩種模式,幀中的地址、功能碼等都是一個或多個字節,每一個字節是一個8位串口數據
    在這裏插入圖片描述

  • 若是一個串口鏈接多個4856設備,能夠經過地址區分不一樣485設備,固然串口資源充足也能夠掛載在多個串口上

  • RTU模式經過兩幀數據的時間間隔,區分先後兩幀數據,若是串口數據間隔大於3.5個字節,那麼就認爲一幀數據結束了,在9600bps/s波特率下,傳輸3.5個字節時間大概爲4ms;ASCII模式讀回車換行就好了

  • 若是要讀取傳感器狀態、數據,都須要發送請求幀

  • 數據須要CRC校驗

  • 具體指令規則需查看傳感器指令文檔

二、 根據傳感器指令文檔編程
在這裏插入圖片描述
(1)打開傳感器開關

void open_sensor(){
	//控制電源開關的引腳拉高
}

(2)發送讀取命令

void start_read(){
    uint8_t TxData[10]= "010300000001840A";
	HAL_UART_Transmit(&huart3,TxData,10,0xffff);
    HAL_Delay(100); 
}

(3)讀取數據

  • 在串口接收中斷中接收數據,若是下一個串口數據在4ms以內到來,那麼從新計時,定時器中斷不會生效;若是沒來,則認爲接收完一幀數據,程序會進入定時器中斷,這個很關鍵
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
		if(huart->Instance == USART3){
			  HAL_TIM_Base_Stop_IT(&htim5);
			  HAL_UART_Receive_IT(&huart3, &uart3Data, 1);
              uart3WriteByte(uart3Data);
              HAL_TIM_Base_Start_IT(&htim5);//開始計時4ms,即modbus設備在9600波特率下傳輸一幀數據的時間間隔
			  uart3DataFlg = 1;
		}
}
  • 在定時器中斷中進行處理數據
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if(htim==(&htim5))
  {
		//TIM_ClearITPendingBit(TIM5,TIM_IT_Update); //標準庫須要清除TIMx更新中斷標誌,HAL庫不須要,直接寫業務邏輯就行
		 HAL_TIM_Base_Stop_IT(&htim5);
		 Modbus_Work();//數據處理函數
		 Uart3BufClear();
		 RS485_RX_CNT++;
  }
}

(4)數據CRC校驗

  • CRC原理就不囉嗦了
  • 不一樣種類的CRC校驗的多項式並不統一,這裏是0xA001,文檔中會說明
int16_t CRC_16( int8_t *vptr, int8_t len)
{
    uint16_t MODBUSCRC = 0xffff;
    uint16_t POLYNOMIAL = 0xa001;
    uint8_t i, j;

    for (i = 0; i < len; i++)
    {
        MODBUSCRC ^= vptr[i] ;
        for (j = 0; j < 8; j++)
        {
            if ((MODBUSCRC & 0x0001) != 0)
            {
                MODBUSCRC >>= 1;
                MODBUSCRC ^= POLYNOMIAL;
            }
            else
            {
                MODBUSCRC >>= 1;
            }
        }
    }
    return MODBUSCRC;
}

(5)數據解析

int Modbus_Work(void)
{
	double depth;
	uint16_t  depth_high;
	uint16_t  depth_low;
	int len = strlen((char*)(sensor485DataBuffer+1));
	if(sensor485DataBuffer[1] == 0x01)//從機地址正確則進行換算
	{
		if((CRC_16((int8_t *)(sensor485DataBuffer+1),len))==0x0000){//CRC校驗
			depth_high = sensor485DataBuffer[3];
			depth_low = sensor485DataBuffer[4];
			depth = (double)((depth_high << 8) + depth_low)/1000;
		}
	}memset(sensor485DataBuffer,0,len+1);
	return depth;//這就是水深值了
}

4、注意事項 一、RS485通訊,必定要A接A,B接B,不是反接 二、採集線過長,要考慮使用屏蔽線

相關文章
相關標籤/搜索