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,不是反接 二、採集線過長,要考慮使用屏蔽線