国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

STM32硬件庫(非HAL庫)實現(xiàn)MODBUS RTU協(xié)議的03,06功能碼(讀以及與單個發(fā)送)

這篇具有很好參考價值的文章主要介紹了STM32硬件庫(非HAL庫)實現(xiàn)MODBUS RTU協(xié)議的03,06功能碼(讀以及與單個發(fā)送)。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

本人軟件工程專業(yè),關于硬件只有408的基礎,后續(xù)學習發(fā)現(xiàn)一些博主所講以及b站上所給的教程并不是很清晰,故編寫此文檔供大家查看。

如果我說的地方哪里有問題,希望大家可以給出意見!(●ˇ?ˇ●)


參考文檔:

Modbus通訊協(xié)議常用功能碼解釋_modbus功能碼_Lee139499的博客-CSDN博客


目錄

一.什么是MODBUS RTU

1.關于MODBUS中的功能碼

?2.MODBUS RTU中的數(shù)據(jù)幀結構

?編輯

二.代碼上的實現(xiàn)

1.初始化定時器和USART

2.設置定時器為輸入捕獲模式

3.在USART接收中斷中記錄定時器值

4.自定義文件,針對于MODBUS協(xié)議對數(shù)據(jù)進行處理

三.使用軟件


一.什么是MODBUS RTU

?????????MODBUS是一種單主站的主/從通訊模式。Modbus網(wǎng)絡上只有一個主站,主站在Modbus網(wǎng)絡上沒有地址,從站的地址范圍為0-247,其中0為廣播地址,從站的實際地址范圍為1-247。

????????通信由主機發(fā)起,一問一答式,從機無法主動向主機發(fā)送數(shù)據(jù)。

? ? ? ? 傳輸過程中,兩個字節(jié)之間的相鄰時間不得大于3.5個字符的時間,否則視為一幀數(shù)據(jù)傳輸結束。

1.關于MODBUS中的功能碼

常用的就是01、02、03、04、05、06、15、16,具體描述見下圖:

  • 串口modbus協(xié)議06功能碼,stm32,嵌入式硬件,學習

?2.MODBUS RTU中的數(shù)據(jù)幀結構

  • 串口modbus協(xié)議06功能碼,stm32,嵌入式硬件,學習

????????地址:設備的 MODBUS 地址,用于標識通信中的從設備。

????????功能碼:表示對從設備執(zhí)行的操作,例如讀取保持寄存器、寫單個寄存器等。

????????數(shù)據(jù)(2字節(jié)):傳輸?shù)臄?shù)據(jù),由兩個字節(jié)組成。具體數(shù)據(jù)內(nèi)容可能根據(jù)功能碼不同而有所變化。????????

????????CRC校驗(2字節(jié)):用于驗證數(shù)據(jù)的完整性,由兩個字節(jié)組成。該校驗值是在數(shù)據(jù)幀中的所有字段(包括地址、功能碼和數(shù)據(jù))被計算后得到的。

串口modbus協(xié)議06功能碼,stm32,嵌入式硬件,學習


二.代碼上的實現(xiàn)

????????此項目中,我使用的是STM32F103C8T6開發(fā)板,串口使用USART。

????????因為我只需要實現(xiàn)了03,06功能碼,所以代碼部分只有針對這兩個功能碼的實現(xiàn)。

????????那么,根據(jù)該協(xié)議,我們需要使用定時器來實現(xiàn)判斷兩個字節(jié)之間的相鄰時間,確保數(shù)據(jù)傳輸?shù)臅r間間隔不得大于設定好的時間。

????????在串口USART中判斷兩個字節(jié)之間的相鄰時間,以確保數(shù)據(jù)幀傳輸不超過設定的時間閾值。我們使用一個定時器來記錄兩個字節(jié)之間的時間,并在定時器中斷中進行判斷。

? ? ? ? 步驟如下:

1.初始化定時器和USART

??????首先,你需要初始化定時器和USART,確保它們已經(jīng)配置正確。

void Serial_Init(void){

    // 1.開啟時鐘(USART與GPIO)
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);// USART
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);// GPIO
    // 2.GPIO初始化(TX——復用輸出,RX——輸入)
    GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; 
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; 
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
    // 3.配置USART
    USART_InitTypeDef USART_InitStructure = {
        .USART_BaudRate = 9600,// 波特率
        .USART_HardwareFlowControl = USART_HardwareFlowControl_None, // 硬件流控制
        .USART_Mode = USART_Mode_Tx | USART_Mode_Rx,// 指定發(fā)送功能 如果又要發(fā)送也要接收 可以采用 A | B 的格式
        .USART_Parity = USART_Parity_No,// 校驗位
        .USART_StopBits = USART_StopBits_1,// 停止位
        .USART_WordLength = USART_WordLength_8b
    };
    USART_Init(USART1,&USART_InitStructure);

    // 開啟中斷
    USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
    // 配置NVIC
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    // 初始化NVIC的USART1通道
    NVIC_InitTypeDef NVIC_InitStructure = {
        .NVIC_IRQChannel = USART1_IRQn,
        .NVIC_IRQChannelCmd = ENABLE,
        .NVIC_IRQChannelPreemptionPriority = 1,
        .NVIC_IRQChannelSubPriority = 1
    };
    NVIC_Init(&NVIC_InitStructure);

    // 4.開啟USART(或配置中斷)
    USART_Cmd(USART1,ENABLE);
}

????????以及定時器的相關配置:

void Timer_Init(uint16_t arr,uint16_t psc){

	//RCC內(nèi)部時鐘 ON
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
	
	//時鐘源選擇
	TIM_InternalClockConfig(TIM3);
	
	//配置時機單元
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;						// 不分頻
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;					// 向上計數(shù)
	TIM_TimeBaseInitStructure.TIM_Period = arr ;									// 因為預分頻器和計數(shù)器都有1個數(shù)的偏差,所以這里要再減去一個1
	TIM_TimeBaseInitStructure.TIM_Prescaler = psc ;									// Tout = ((arr+1)*(psc+1))/Tclk ;
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;	
	TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);
	
	
	
	TIM_ClearFlag(TIM3,TIM_IT_Update);
	//配置輸出中斷控制
	TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);
	
	
	//配置NVIC
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);									// 優(yōu)先級分組
	
	NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM_Channel_3;									// 中斷通道
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;									// 制特定中斷通道的使能狀態(tài)
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;						// 搶占優(yōu)先級
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;								// 響應優(yōu)先級
	NVIC_Init(&NVIC_InitStructure); 
	


	//啟動定時器
	TIM_Cmd(TIM3,ENABLE);
}

2.設置定時器為輸入捕獲模式

????????將定時器設置為輸入捕獲模式,以便在USART接收到一個字節(jié)時記錄定時器的值。

// 配置定時器通道為輸入捕獲模式
void configure_input_capture() {
    // 配置輸入捕獲通道 CHx 為輸入捕獲模式
    TIM3->CCMR1 |= TIM_CCMR1_CC1S_0; // 將CC1S位設置為01,選擇輸入捕獲通道1為TI1
    // 配置輸入捕獲通道 CHx 的觸發(fā)邊沿或狀態(tài)變化條件
    TIM3->CCER |= TIM_CCER_CC1P; // 設置捕獲邊沿為下降沿觸發(fā),如果需要上升沿觸發(fā),可以選擇設置為TIM_CCER_CC1NP
    // 使能捕獲通道 CHx
    TIM3->CCER |= TIM_CCER_CC1E;
}

3.在USART接收中斷中記錄定時器值

????????在USART接收中斷中,記錄定時器的當前值,并在接收到字節(jié)時啟動或重置定時器。

// 定時器中的變量定義:
volatile uint32_t last_capture_time = 0;
const uint32_t max_frame_time = 4000; // 設定的最大幀傳輸時間,單位為定時器計數(shù)值

// 串口中的變量定義:
uint8_t Serial_RxPacket[100] = {0};
uint16_t Serial_RxLength = 0;
uint8_t Serial_RxFlag;
uint8_t clearBufferFlag = 0;

????????以下為定時器的中斷配置:

void TIM3_IRQHandler(void){

	if(TIM_GetITStatus(TIM3,TIM_IT_CC3) != RESET){	 // 輸入捕獲中斷觸發(fā),計算兩個捕獲之間的時間間隔
		uint32_t current_capture_time = TIM_GetCapture1(TIM3);
        uint32_t time_interval = current_capture_time - last_capture_time;

		if (time_interval > max_frame_time) {
            // 超過設定的最大幀傳輸時間,認為一幀數(shù)據(jù)傳輸結束
            // 處理完整的數(shù)據(jù)幀
			Serial_RxFlag = 1;
        }

        // 重置定時器捕獲時間
        last_capture_time = current_capture_time;

		TIM_ClearITPendingBit(TIM3,TIM_IT_CC3);
		
	}	

}

????????以下為串口USART的中斷配置:

void USART1_IRQHandler(void){

	if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
	{
        // 接收到一個字節(jié)數(shù)據(jù),記錄定時器的當前值
        last_capture_time = TIM_GetCapture3(TIM3);
        Serial_RxPacket[Serial_RxLength++] = USART_ReceiveData(USART1);
        Serial_RxFlag = 1;
        USART_ClearITPendingBit(USART1,USART_IT_RXNE);// 清除標志位
        
	}

}

????????在定時器輸入捕獲中斷中判斷兩個字節(jié)之間的時間: 當定時器捕獲到第二個字節(jié)時,計算兩個捕獲之間的時間間隔。如果這個時間間隔超過設定的閾值,則視為一幀數(shù)據(jù)傳輸結束。

4.自定義文件,針對于MODBUS協(xié)議對數(shù)據(jù)進行處理

#include "stm32f10x.h"
#include "Timer.h"
#include "Serial.h"
uint8_t Serial_TxPacket[100] = {0};                        // 發(fā)送內(nèi)容
extern uint8_t Serial_RxPacket[100];                       // 接收內(nèi)容
extern uint16_t Serial_RxLength;

extern uint16_t modbus_io[100];                            // modbus寄存器內(nèi)數(shù)據(jù)
// uint16_t modbus_id = 0X01;                              // id號
uint16_t modbus_function;                                  // 功能碼
uint16_t modbus_check;                                     // 校驗位
uint16_t modbus_packege_times = 0;                         // 總包計數(shù)
uint16_t CRC_check_result;                                 // CRC校驗的結果



uint16_t calculate_crc16(const uint8_t *data, size_t len) {
    // printf("%d\n",len);
    
    // 初始化crc為0xFFFF
    uint16_t crc = 0xFFFF;

    // 循環(huán)處理每個數(shù)據(jù)字節(jié)
    for (size_t i = 0; i < len; i++) {
        // 將每個數(shù)據(jù)字節(jié)與crc進行異或操作
        crc ^= data[i];

        // 對crc的每一位進行處理:如果最低位為1,則右移一位并執(zhí)行異或0xA001操作(即0x8005按位顛倒后的結果)
        for (int j = 0; j < 8; j++) {
            if (crc & 0x0001) {
                crc = (crc >> 1) ^ 0xA001;
            } 
            // 如果最低位為0,則僅將crc右移一位
            else {
                crc = crc >> 1;
            }
        }
    }
    return crc;
}


void Data_Funcion_3(void){
    Serial_TxPacket[0] = Serial_RxPacket[0];                // ID
    Serial_TxPacket[1] = Serial_RxPacket[1];                // 功能碼
    // 字節(jié)長度,根據(jù)接收的內(nèi)容4,5位來判斷
    Serial_TxPacket[2] = (Serial_RxPacket[4] << 8 | Serial_RxPacket[5]) * 2;


    for(modbus_packege_times = 0;modbus_packege_times<Serial_TxPacket[2];modbus_packege_times+=2)
    {
        Serial_TxPacket[3+modbus_packege_times] = modbus_io[modbus_packege_times / 2] >> 8;
        Serial_TxPacket[4+modbus_packege_times] = modbus_io[modbus_packege_times / 2];
    }         
    // 校驗碼
    CRC_check_result = calculate_crc16(Serial_TxPacket,Serial_TxPacket[2] + 3);
    Serial_TxPacket[3+modbus_packege_times] = (CRC_check_result) & 0xFF;
    Serial_TxPacket[4+modbus_packege_times] = (CRC_check_result>>8) & 0xFF;


      

    Serial_SendArray(Serial_TxPacket,5+modbus_packege_times);
    return ;
}


void Data_Funcion_6(void){
    // Serial_TxPacket[0] = Serial_RxPacket[0];             // ID
    // Serial_TxPacket[1] = Serial_RxPacket[1];             // 功能碼
    modbus_io[Serial_RxPacket[3] - 1] = Serial_RxPacket[4];         
    modbus_io[Serial_RxPacket[3]] = Serial_RxPacket[5];
    
    Serial_SendArray(Serial_RxPacket,Serial_RxLength);
    return ;
}


void Data_Resolve(void){

    // 需增加校驗位計算
    modbus_check = calculate_crc16(Serial_RxPacket,Serial_RxLength-2);
    
    if(modbus_check != 0)                                  // 校驗是否通過
    {   
        Serial_TxPacket[0] = 0x01;                         // 預設id
        if(Serial_RxPacket[0] == Serial_TxPacket[0]){      // 確認id號是否一致

            modbus_function = Serial_RxPacket[1];
            switch(modbus_function)
            { 
                case 3 :                                   // 根據(jù)03功能碼,主機要求從機反饋內(nèi)容 
                    Data_Funcion_3();
                    break;
                case 6 :
                    Data_Funcion_6();
                    break;
                // case 16 :
                //     Serial_SendArray(Serial_TxPacket,Serial_RxLength);
                //     break;
                default :
                    break;
            }

        }

    }
    Serial_RxFlag = 0;
    Serial_RxLength = 0;
}




三.使用軟件

????????使用Keil給板子上程序后,我這邊使用了Modbus Pull和Modbus Slave做實驗,網(wǎng)上可以查到并且下載。串口modbus協(xié)議06功能碼,stm32,嵌入式硬件,學習

????????我們?nèi)绻褂冒遄赢斪鲝臋C的話,那么只需要使用到Pull就可以了,不需要使用到Slave。

?????????工程代碼:

github上的項目工程

? ? ? ? 目前我只做了這兩個功能碼,如果這邊有什么錯誤的地方還請大佬們給出指點(????)~文章來源地址http://www.zghlxwxcb.cn/news/detail-779626.html

到了這里,關于STM32硬件庫(非HAL庫)實現(xiàn)MODBUS RTU協(xié)議的03,06功能碼(讀以及與單個發(fā)送)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如若轉載,請注明出處: 如若內(nèi)容造成侵權/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領支付寶紅包贊助服務器費用

相關文章

  • Modbus-RTU協(xié)議C#實現(xiàn)

    1、安裝依賴包 System.IO.Ports 2、讀協(xié)議 3、寫協(xié)議 4、CRC16校驗

    2024年02月15日
    瀏覽(22)
  • 【正點原子STM32】RS485串行通信標準(串口基礎協(xié)議 和 MODBUS協(xié)議、總線連接、通信電路、通信波形圖、RS485相關HAL庫驅動、RS485配置步驟、)

    【正點原子STM32】RS485串行通信標準(串口基礎協(xié)議 和 MODBUS協(xié)議、總線連接、通信電路、通信波形圖、RS485相關HAL庫驅動、RS485配置步驟、)

    一、RS485介紹 二、RS485相關HAL庫驅動介紹 三、RS485配置步驟 四、編程實戰(zhàn) 五、總結 串口、UART、TTL、RS232、RS422和RS485之間的關系可以如此理解: 串口 :是一個廣義術語,通常指的是采用串行通信協(xié)議的接口,它可以包括多種具體的物理接口標準和邏輯電平標準。 UART (通用

    2024年04月13日
    瀏覽(29)
  • STM32實現(xiàn)基于RS485的簡單的Modbus協(xié)議

    STM32實現(xiàn)基于RS485的簡單的Modbus協(xié)議

    我這里用STM32實現(xiàn),其實可以搬移到其他MCU,之前有項目使用STM32實現(xiàn)Modbus協(xié)議 這個場景比較正常,很多時候都能碰到 這里主要是Modbus和變頻器通信 最常見的是使用Modbus實現(xiàn)傳感器數(shù)據(jù)的采集,我記得之前用過一些傳感器都是Modbus協(xié)議 這就需要MCU實現(xiàn)Modbus協(xié)議,不過實際使

    2024年02月08日
    瀏覽(31)
  • DGIOT-Modbus-RTU控制指令05、06的配置與下發(fā)

    DGIOT-Modbus-RTU控制指令05、06的配置與下發(fā)

    [小 迪 導 讀]:伴隨工業(yè)物聯(lián)網(wǎng)在實際應用中普及,Modbus-RTU作為行業(yè)內(nèi)的標準化通訊協(xié)議。在為物聯(lián)網(wǎng)起到采集作用的同時,設備的控制也是一個密不可分的環(huán)節(jié)。 場景解析:在使用Modbus對設備進行采集后,可以通過自動控制和手動控制來實現(xiàn)動環(huán)或者設備的運行狀態(tài)調(diào)節(jié)。

    2024年02月09日
    瀏覽(12)
  • Profibus-DP轉modbus RTU網(wǎng)關modbus rtu協(xié)議

    Profibus-DP轉modbus RTU網(wǎng)關modbus rtu協(xié)議

    捷米JM-DPM-RTU網(wǎng)關在Profibus總線側實現(xiàn)主站功能,在Modbus串口側實現(xiàn)從站功能??蓪rofibusDP協(xié)議的設備(如:E+H流量計、倍福編碼器等)接入到Modbus網(wǎng)絡中;通過增加DP/PA耦合器,也可將Profibus PA從站接入Modbus網(wǎng)絡。在Modbus串口側提供RS485和RS232兩種電平接口。 捷米JM-DPM-RTU網(wǎng)關

    2024年02月10日
    瀏覽(21)
  • C# ModBus協(xié)議(RTU )詳細指南

    C# ModBus協(xié)議(RTU )詳細指南

    ModBus協(xié)議:官方的解釋是Modbus協(xié)議是一種通信協(xié)議,用于在自動化設備之間進行數(shù)據(jù)傳輸。它最初是由Modicon公司于1979年開發(fā)的,現(xiàn)在已成為工業(yè)界的一種通用協(xié)議。Modbus協(xié)議有多種變體,包括 Modbus-RTU、Modbus-TCP和Modbus-ASCII 等,其中Modbus-RTU是最常用的變體之一。Modbus協(xié)議基于

    2024年02月04日
    瀏覽(21)
  • modbus-tcp-rtu協(xié)議圖表

    MODBUS TCP 讀寄存器 請求 序號 意義 所占字節(jié) 字節(jié)存放格式 1 事務處理標識 2個字節(jié) 高字節(jié)在前 2 協(xié)議標識 2個字節(jié) 高字節(jié)在前 3 長度 2個字節(jié) 高字節(jié)在前 4 單元標識 1個字節(jié) 0x00-0xff 5 功能碼 1個字節(jié) 0x03 6 起始寄存器地址 2個字節(jié) 高字節(jié)在前 7 寄存器個數(shù) 2個字節(jié) 高字節(jié)在前

    2024年01月23日
    瀏覽(20)
  • Modbus-RTU功能碼

    Modbus-RTU功能碼

    以下圖片中的幀解析都不含站號和校驗碼 在一個遠程設備中,使用該功能碼讀取線圈的 1 至 2000 連續(xù)狀態(tài)。請求 PDU (功能碼-地址-數(shù)據(jù))詳細說明了起始地址,即指定的第一個線圈地址和線圈編號。從零開始尋址線圈。因此尋址線圈 1-16 為 0-15(PLC地址一般也是這樣,寄存器

    2024年02月04日
    瀏覽(22)
  • MODBUS RTU 通信協(xié)議 CRC16校驗算法

    MODBUS RTU 通信協(xié)議 CRC16校驗算法

    CRC校驗碼是一個2個字節(jié)(16位二進制)的數(shù)。 發(fā)送端:發(fā)送的數(shù)據(jù)計算CRC校驗碼----發(fā)送:數(shù)據(jù)+CRC校驗碼 接收端:收到數(shù)據(jù)后重新計算CRC校驗碼,然后和接收到數(shù)據(jù)中的CRC校驗碼進行比較,判斷是否相等。 如果不相等:數(shù)據(jù)傳輸過程中出錯,給出錯誤應答。 CRC16 校驗源碼

    2024年02月16日
    瀏覽(26)
  • 嵌入式 STM32 通訊協(xié)議--MODBUS

    嵌入式 STM32 通訊協(xié)議--MODBUS

    目錄 一、自定義通信協(xié)議 1、協(xié)議介紹 2、網(wǎng)絡協(xié)議 3、自定義的通信協(xié)議? 二、MODBUS通信協(xié)議 1、概述 2、MODBUS幀結構? 協(xié)議描述 3、MODBUS數(shù)據(jù)模型 ? 4、MODBUS事務處理的定義 5、MODBUS功能碼? 6、功能碼定義? ?7、MODBUS數(shù)據(jù)鏈路層 8、MODBUS地址規(guī)則? 9、MODBUS幀描述 10、MODBUS兩種

    2024年02月11日
    瀏覽(40)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領取紅包,優(yōu)惠每天領

二維碼1

領取紅包

二維碼2

領紅包