在調試STM32F407的串口Modbus通訊之前,也使用過Modbus通訊,只不過都是在PLC或則昆侖通態的觸摸屏上使用直接調用現成的庫里面的模塊,驅動就可以,相對於STM32來,使用PLC庫里面的模塊和觸摸屏驅動都是初始化后配參數就可以了,但是用32寫的時候比較麻煩了一些。由於STM32沒有RS485通訊端口只能用UART端口轉成RS485加了一個485芯片,在調試Modbus通信按照plc和觸摸屏的經驗慢慢琢磨也是挺有意思的。當最后你都調通的時候心里那種成就感是對於一個理工男來說是非常爽的!不啰嗦了下面介紹Modbus通信流程以及功能碼:
1、數據目的:讀輸入繼電器 1區 功能碼:02 通信格式: 設備地址 功能碼 起始地址 讀數據位個數 CRC校驗
例: 01 02 00 00 00 01 B9 CA
例 :01 02 00 00 00 01 B9 CA
返回數據:01 02 01 01 60 48 //返回01表示繼電器1狀態
01 02 01 00 A1 88 //返回00表示繼電器0狀態
2、數據目的:讀輸出繼電器 0區 功能碼:01 通信格式: 設備地址 功能碼 起始地址 讀數據位個數 CRC校驗
例: 01 01 00 00 00 01 FD CA
例:01 01 00 00 00 01 FD CA
返回數據:01 01 01 01 90 48 //返回01表示繼電器1狀態
01 01 01 00 51 88 //返回00表示繼電器0狀態
3、數據目的:寫輸出繼電器 0區 功能碼:05 通信格式: 設備地址 功能碼 起始地址 數據 CRC校驗
例: 01 05 00 00 00 00 CD CA
例:01 05 00 00 00 00 CD CA //寫繼電器0狀態
例:01 05 00 00 FF FFCC 7A //寫繼電器1狀態
4、數據目的:讀3區16位寄存器數據 3區 功能碼:04 通信格式: 設備地址 功能碼 起始地址 數據位數 CRC校驗
例: 01 04 00 01 00 00 60 0A
例:01 04 00 01 00 01 60 0A //寫繼電器0狀態
返回數據:01 04 02 00 05 79 33 //返回02帶邊數據位數 00 05代表返回的數據
5、數據目的:讀4區16位寄存器數據 4區 功能碼:03 通信格式: 設備地址 功能碼 起始地址 數據位數 CRC校驗
例: 01 03 00 01 00 00 D5 CA
例:01 03 00 01 00 01 D5 CA //寫繼電器0狀態
返回數據:01 03 02 00 05 78 47 //返回02帶邊數據位數 00 05代表返回的數據
6、數據目的:寫4區16位寄存器數據 4區 功能碼:06 /10 通信格式: 設備地址 功能碼 起始地址 數據 CRC校驗
例: 01 06 00 01 00 00 9C 6B
例:01 06 00 01 0E 03 9C 6B //寫00 01數據0E 03
啰嗦了半天功能碼,接下來寫一下Modbus的在STM32中的流程:
1、發送需要讀取從站的地址以及功能碼
2、發送完成后開始200ms定時中斷等待從站數據是否返回(定時器中斷優先級低於串口中斷)若無數據返回重復1步驟
3、有數據返回開啟2ms中斷,接收間隔超過2ms代表數據接收完成。
4、解析數據,返回數據;
extern u8 RS485_BUFF[100]; u8 Rcv_Len; /* Private define ------------------------------------------------------------*/ /* Private macro -------------------------------------------------------------*/ /* Private variables ---------------------------------------------------------*/ extern u8 Uart0_send_buff[]; extern u8 Uart0_rev_buff[]; extern vs8 Uart0_send_counter; extern vu8 Uart0_rev_count;//com0串口接收計數器 extern vu8 Uart0_rev_comflag; extern vu8 Crc_counter;//com0校驗計數器 extern vu8 *Uart0_send_pointer ;//com0串口發送指針 extern vu8 Uart_send_flag;//從機響應超時標志 /******************************************************************************* * 函 數 名 : RS485_Init * 函數功能 : USART2初始化函數 * 輸 入 : bound:波特率 * 輸 出 : 無 *******************************************************************************/ void RS485_Init(u32 bound) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOG,ENABLE); //使能GPIOA\G時鍾 RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);//使能USART2時鍾 //串口2引腳復用映射 GPIO_PinAFConfig(GPIOA,GPIO_PinSource2,GPIO_AF_USART2); //GPIOA2復用為USART2 GPIO_PinAFConfig(GPIOA,GPIO_PinSource3,GPIO_AF_USART2); //GPIOA3復用為USART2 //USART2 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3; //GPIOA2與GPIOA3 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//復用功能 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //速度100MHz GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽復用輸出 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉 GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化PA2,PA3 //PG8推挽輸出,485模式控制 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //GPIOG8 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//輸出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //速度100MHz GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽輸出 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉 GPIO_Init(GPIOG,&GPIO_InitStructure); //初始化PG8 //USART2 初始化設置 USART_InitStructure.USART_BaudRate = bound;//波特率設置 USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字長為8位數據格式 USART_InitStructure.USART_StopBits = USART_StopBits_1;//一個停止位 USART_InitStructure.USART_Parity = USART_Parity_No;//無奇偶校驗位 USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//無硬件數據流控制 USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收發模式 USART_Init(USART2, &USART_InitStructure); //初始化串口2 USART_Cmd(USART2, ENABLE); //使能串口 2 USART_ClearFlag(USART2, USART_FLAG_TC); USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//開啟接受中斷 //Usart2 NVIC 配置 NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;//搶占優先級3 NVIC_InitStructure.NVIC_IRQChannelSubPriority =1; //子優先級2 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能 NVIC_Init(&NVIC_InitStructure); //根據指定的參數初始化VIC寄存器、 RS485_TX_EN=0; //默認為接收模式 } /******************************************************************************* * 函 數 名 : USART2_IRQHandler * 函數功能 : USART2中斷函數 * 輸 入 : 無 * 輸 出 : 無 *******************************************************************************/ void USART2_IRQHandler(void) { if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) { if(Uart_send_flag==1) { Uart_send_flag=0;//標志沒有超時 TIM_Cmd(TIM2, DISABLE); TIM_DeInit( TIM2);//復位TIM2定時器 } if(Uart0_rev_comflag != 1) { if(Uart0_rev_count < 100) { Uart0_rev_buff[Uart0_rev_count++]= USART_ReceiveData(USART2); TIM_DeInit( TIM2);//復位TIM2定時器 TIM2_Init(2,8400); } else { USART_ReceiveData(USART2);//如果接收計數器大於100,放棄接收的數據,防止同總線上有其它數據長大於100的信息導致本接收緩存溢出 } } else { USART_ReceiveData(USART2);//如果接收沒有處理,放棄接收的數據 } } if(USART_GetITStatus(USART2, USART_IT_TXE) != RESET) { Uart0_send_counter--; if(Uart0_send_counter>0) { /* Write one byte to the transmit data register */ USART_SendData(USART2, *Uart0_send_pointer++); } else { delay_ms(1); RS485_TX_EN=0; // s8 i ; USART_ITConfig(USART2, USART_IT_TXE, DISABLE); Uart_send_flag = 1;// 數據發送完成 TIM_DeInit( TIM2);//復位TIM2定時器 TIM2_Init(20,8400); } } }
extern u8 RS485_BUFF[100]; //UART2 外設485數據緩存區 extern u8 mag_nav_cmd[50]; //磁導航modbus指令 extern u8 inertia_cmd[50]; //慣性導航modbus指令 extern u8 readrfid_cmd[50]; //讀取ID卡modbus指令 extern u8 Uart0_send_buff[]; extern u8 Uart0_rev_buff[]; extern vs8 Uart0_send_counter; extern vu8 Uart0_rev_count;//com0串口接收計數器 extern vu8 Uart0_rev_comflag; extern vu8 Crc_counter;//com0校驗計數器 extern vu8 *Uart0_send_pointer ;//com0串口發送指針 extern vu8 Uart_send_flag;//從機響應超時標志 void Modbus_Function_3(uint8_t device_id,uint8_t reg_H_addr,uint8_t reg_L_addr,uint8_t red_num_H,uint8_t red_num_L); u8 time_state; extern u8 sent_data[20];
/******************************************************************************* * 函 數 名 : TIM2_Init * 函數功能 : TIM2初始化函數 * 輸 入 : per:重裝載值 psc:分頻系數 * 輸 出 : 無 *******************************************************************************/ void TIM2_Init(u32 per,u16 psc) { TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //TIM初始化結構體 NVIC_InitTypeDef NVIC_InitStructure; //優先級結構體 //APB1 42M //APB1_TIM2 84M RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); //使能TIM2時鍾 TIM_TimeBaseInitStructure.TIM_Period=(per-1)*10; //自動裝載症//// TIM_TimeBaseInitStructure.TIM_Prescaler=psc-1; //分頻系數////分頻系數和計數值決定定時器值 TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1; TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //設置向上計數模式 TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure); // 初始化定時器TIM2 TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); //開啟定時器中斷 TIM_ClearITPendingBit(TIM2,TIM_IT_Update); //清楚定時器標志位 NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; //定時器中斷通道 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;//搶占優先級 NVIC_InitStructure.NVIC_IRQChannelSubPriority =2; //子優先級 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能 NVIC_Init(&NVIC_InitStructure); TIM_Cmd(TIM2,ENABLE); //使能定時器 } /******************************************************************************* * 函 數 名 : TIM2_IRQHandler * 函數功能 : TIM2中斷函數 * 輸 入 : 無 * 輸 出 : 無 *******************************************************************************/ void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) { u8 i; /* TIM IT DISABLE [使能TIM2溢出中斷]*/ TIM_ITConfig(TIM2, TIM_IT_Update, DISABLE); /* TIM2 DISABLE counter */ TIM_Cmd(TIM2, DISABLE); TIM_DeInit( TIM2);//復位TIM2定時器 if(Uart_send_flag==1) { Uart_send_flag=3;//說明超時 } else { Uart0_rev_comflag = 1;//接收完成 for(i=0;i<Uart0_rev_count+1;i++) { RS485_BUFF[i]= Uart0_rev_buff[i]; } Crc_counter = Uart0_rev_count;//crc校驗計數器賦值 Uart0_rev_count = 0;//接收計數器清零 } } TIM_ClearITPendingBit(TIM2, TIM_IT_Update); }