學習板:STM32F103ZET6
參考:
STM32F103(二十三)通用同步異步收發器(USART)
485通信
- 一、485簡介
- 1、簡述
- 2、芯片簡述
- 3、芯片測驗
- 4、面向原理圖
- 二、手把手寫信號傳遞函式
- 1、寫一個發送一次u8資料的函式
- 2、寫一個發送多個u8資料的函式
- 3、寫一個接收多個資料函式的函式
- 三、例題(F1板子發送——>F4板子接收)
- 1、題
- 2、分析
- 3、F1板子代碼(發送資料)
- 4、F4板子代碼(接收資料并串口列印)
- 5、引腳連接
- 6、測驗
- 四、總結
一、485簡介
1、簡述
485是一種2線、半雙工的通信,傳輸距離可達到1Km以上,通過A線、B線兩線的電壓差來傳遞信號,
485芯片介面電平很低,可以兼容TTL電路,所以可以直接接入到TTL電路,發送信號“1”時兩線電壓差為+(2~6)V,發送信號“0”時兩線電壓差為 -(2 ~6)V,
因為需要根據A線、B線兩端電壓差來確定邏輯“1”和“0”,所以收發雙方都需要接一個匹配電阻,一般為120Ω,
2、芯片簡述
485芯片一般為以下框圖:( SP3485為例)

包括我近期專案選用的一款485芯片:

其中:
RO:接收資料輸出端,485通信程序中,終端信號接收引腳,
RE:接收資料使能,低電平有效,終端的485芯片RE引腳置0,進入接收模式,
DI:發送資料輸入端,485通信程序中,始端信號發送引腳,
DE:發送資料使能,高電平有效,始端的485芯片DE引腳置1,進入發送模式,
A、B:信號傳輸線,準確的講,只是起到導電作用,
VCC、GND:供電,
485通信為半雙工通信,即同一時間只能接識訓發送資料,而接收資料使能RE低電平有效,發送資料使能DE高電平有效,所以這兩個引腳可以共接一個IO口,有以下情況:
①當IO口輸出低電平時,接收資料使能RE有效,進入接收模式
②當IO口輸出高電平時,發送資料使能DE有效,進入發送模式,
3、芯片測驗
以SP485芯片為例,總結:
①發送模式時,DI輸出“1”或“0”時傳輸線A、B上的電平情況
②接收模式時,傳輸線上不同電平時,RO引腳輸出電平情況

上圖是我對SP485的測驗結果,主設備用的F1板子,從設備為F4板子,再詳述一下:
①當主設備設定為發送模式時,DI引腳發送“1”,則傳輸線上:A線高電平、B線低電平…同時從設備為接收模式,當A線為高電平、B線為低電平時,RO引腳輸出高電平,
②當主設備設定為發送模式時,DI引腳發送“0”,則傳輸線上:A線低電平、B線高電平…同時從設備為接收模式,當A線為低電平、B線為高電平時,RO引腳輸出低電平,
所以省略中間程序,我們知道:主設備發送“1”,從設備收到“1”,主設備發送“0”,從設備收到“0”,而485芯片沒有時鐘引腳,所以我們想到用異步串行傳輸USART,主設備USART一次輸出8位的數,從設備一次接收8位的數,只需保證雙方串口的波特率、停止位、校驗位等一致即可
4、面向原理圖

我用的板子上使用的是串口2,USART_TX通過跳線帽與485的DI引腳連接;USART_RX通過跳線帽與485的RO引腳連接;PD7控制RE和DR,之前也總結到過,這里當PD7=0時為接收模式,當PD7=1時,為發送模式,
所以除了硬體連接,其他部分都是USART的代碼
二、手把手寫信號傳遞函式
通過第一部分總結,我們知道485通信可以通過雙方的USART來輸出和接收信號,所以USART收發函式就是485通信的函式,而485模塊只需要設定為接識訓發送模式,雙方設定好后,其實就是兩塊板子之間的USART通信,
1、寫一個發送一次u8資料的函式
void My485_Send_One_Data(u8 data)
{
PDout(7)=1;//發送資料使能
while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);//等待上一次資料發送完成
USART_SendData(USART2,data);
}
上述代碼中,首先PD7輸出高電平,將485設定為發送資料模式,然后等待上一次資料發送完畢后,開啟本次資料傳輸,代碼比較簡單,
2、寫一個發送多個u8資料的函式
void My485_Send_Len_data(u8 data[],u16 len)
{
u16 t=0;
for(t=0;t<len;t++)
{
My485_Send_One_Data(data[t]);
delay_ms(10);//最好延遲,防止對方反應不過來
}
}
這個函式也比較簡單,data[]為傳遞的資料存盤陣列,len為需要傳遞的資料個數,其中My485_Send_One_Data(data[t]);函式是上面寫的那個函式,
3、寫一個接收多個資料函式的函式
發送資料的函式一般都比較好寫,麻煩的是接收資料的函式,因為接收資料函式要用到中斷,所以還需要配置中斷,直接附已經除錯好的代碼,然后再講思想:
void My485_Receive_data(u8 len)
{
NVIC_InitTypeDef NVIC_InitStructure;
PDout(7)=0;//接收資料使能
NUM=0;
RS485_Receive_Num=len;
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ; //搶占優先級 3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子優先級 3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);
}
extern u8 buffer[200];//接收的資料存盤在這個陣列
extern u8 RS485_Receive_Num;//儲存需要接收的資料個數
extern u8 NUM; //統計現在接收到幾個資料
void USART2_IRQHandler(void)
{
u8 temp;
if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) //接收到資料
{
temp=USART_ReceiveData(USART2);
if(RS485_Receive_Num>NUM)
{
buffer[NUM]=temp;
NUM++;
}
if(RS485_Receive_Num<=NUM)
USART_ITConfig(USART2, USART_IT_RXNE, DISABLE);
}
}
在void My485_Receive_data(u8 len)函式中,將485模塊設定為接收模式;將NUM這個全域變數清零,這個變數是用來統計目前已經接收到多少個資料,然后就是NVIC中斷優先級設定和使能中斷
這個接收函式配置好中斷后,USART2就開始檢測是否有資料輸入了,只要有資料輸入,就產生一次中斷進入中斷服務函式,然后接收到資料、存盤資料、清除中斷、等待下一次資料到來,當接收到我們需要接收到的資料個數時,再失能中斷
資料存盤的陣列我采用的是全域陣列,因為資料處理都是在中斷服務函式中處理的,陣列的地址沒法傳遞,
在void My485_Receive_data(u8 len)函式中可以再加一段程式,將儲存資料的buffer[]陣列清零,不過我覺得沒必要,只需要保證通信不會出現例外,規定好接收資料的多少就可以了,
注意理解這個思想就好了,比如后面的例題,我把中斷服務函式的以下代碼放在了主函式中:
if(RS485_Receive_Num<=NUM)
USART_ITConfig(USART2, USART_IT_RXNE, DISABLE);
三、例題(F1板子發送——>F4板子接收)
1、題
F1板子發送一組1~100的100個數,通過雙方485通信,F4接收到資料并通過串口列印在串口監視器上,
2、分析
F1板子發送資料,所以只用到上面寫的第二個函式,需要配置USART2和485模塊,
F4板子接收資料用到上面寫的第三個函式,需要配置USART2和485模塊,同時需要串口列印,所以需要配置USART1(我的兩塊板子都是:485與USART2連接,USART1通過RS232與USB連接)
3、F1板子代碼(發送資料)
485.h代碼:
#ifndef _485_
#define _485_
#include "sys.h"
void My485_Init(u32 bound);
void My485_Send_One_Data(u8 data);
void My485_Send_Len_data(u8 *,u16 );
void My485_Receive_data(u8 len);//引數:讀取資料個數
#endif
485.c代碼:
#include "485.h"
#include "stm32f10x.h"
void My485_Init(u32 bound)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //PA2 TX
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3;
GPIO_Init(GPIOA, &GPIO_InitStructure); //PA3 RX
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_7;
GPIO_Init(GPIOD, &GPIO_InitStructure); //PD7 使能發送、接收
USART_InitStructure.USART_BaudRate=bound;
USART_InitStructure.USART_Mode=USART_Mode_Tx|USART_Mode_Rx;
USART_InitStructure.USART_Parity=USART_Parity_No;
USART_InitStructure.USART_StopBits=USART_StopBits_1;
USART_InitStructure.USART_WordLength=USART_WordLength_8b;
USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
USART_Init(USART2,&USART_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ; //搶占優先級 3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子優先級 3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
USART_Cmd(USART2,ENABLE);
}
extern u8 buffer[200];//接收的資料存盤在這個陣列
extern u8 RS485_Receive_Num;//儲存需要接收的資料個數
extern u8 NUM; //統計現在接收到幾個資料
void USART2_IRQHandler(void)
{
u8 temp;
if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) //接收到資料
{
temp=USART_ReceiveData(USART2);
if(RS485_Receive_Num>NUM)
{
buffer[NUM]=temp;
NUM++;
}
}
}
void My485_Send_One_Data(u8 data)
{
PDout(7)=1;//發送資料使能
while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);//等待上一次資料發送完成
USART_SendData(USART2,data);
}
void My485_Send_Len_data(u8 data[],u16 len)
{
u16 t=0;
for(t=0;t<len;t++)
My485_Send_One_Data(data[t]);
}
void My485_Receive_data(u8 len)
{
NVIC_InitTypeDef NVIC_InitStructure;
PDout(7)=0;//接收資料使能
NUM=0;
RS485_Receive_Num=len;
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ; //搶占優先級 3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子優先級 3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);
}
主函式代碼:
#include "stm32f10x.h"
#include "485.h"
#include "delay.h"
u8 buffer[200];//接收的資料存盤在這個陣列
u8 RS485_Receive_Num=0;//儲存需要接收的資料個數
u8 NUM=0; //統計現在接收到幾個資料
u8 arr[200]; //發送的資料儲存在這個陣列
int main(void)
{
u16 i;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
delay_init();
My485_Init(115200);
for(i=0;i<100;i++)
arr[i]=i+1;//賦初值
delay_ms(1500);
My485_Send_Len_data(arr,100);
while(1)
{
}
}
主函式寫的是按下復位后,隔1.5后發送100個資料
4、F4板子代碼(接收資料并串口列印)
USART1頭檔案代碼(為了串口列印):
#ifndef _USART_
#define _USART_
#include "stm32f4xx.h"
#include "stm32f4xx_conf.h"
void Myusart1_Init(u32);
#endif
USART的.c檔案代碼(為了串口列印):
#include "myusart.h"
#include "stm32f4xx.h"
#include "sys.h"
#include "string.h"
void Myusart1_Init(u32 baud)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;
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); //初始化PA9,PA10
USART_InitStructure.USART_BaudRate=baud;
USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode=USART_Mode_Rx|USART_Mode_Tx;
USART_InitStructure.USART_Parity=USART_Parity_No;
USART_InitStructure.USART_StopBits=USART_StopBits_1;
USART_InitStructure.USART_WordLength=USART_WordLength_8b;
USART_Init(USART1,&USART_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2 ; //搶占優先級 3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //子優先級 3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
USART_Cmd(USART1, ENABLE);
}
485.h代碼:
#ifndef _485_
#define _485_
#include "sys.h"
void My485_Init(u32 bound);
void My485_Send_One_Data(u8 data);
void My485_Send_Len_data(u8 *,u16 );
void My485_Receive_data(u8 len);//引數:讀取資料個數
#endif
485.c代碼:
#include "485.h"
#include "stm32f4xx.h"
void My485_Init(u32 bound)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOG,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource2,GPIO_AF_USART2);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource3,GPIO_AF_USART2);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3;
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);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_8;
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz;
GPIO_Init(GPIOG,&GPIO_InitStructure); //PG8 發送接收使能
USART_InitStructure.USART_BaudRate=bound;
USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode=USART_Mode_Rx|USART_Mode_Tx;
USART_InitStructure.USART_Parity=USART_Parity_No;
USART_InitStructure.USART_StopBits=USART_StopBits_1;
USART_InitStructure.USART_WordLength=USART_WordLength_8b;
USART_Init(USART2,&USART_InitStructure);
USART_Cmd(USART2, ENABLE);
}
extern u8 buffer[200];//接收的資料存盤在這個陣列
extern u8 RS485_Receive_Num;//儲存需要接收的資料個數
extern u8 NUM; //統計現在接收到幾個資料
void USART2_IRQHandler(void)
{
u8 temp;
if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) //接收到資料
{
temp=USART_ReceiveData(USART2);
if(RS485_Receive_Num>NUM)
{
buffer[NUM]=temp;
NUM++;
}
}
}
void My485_Send_One_Data(u8 data)
{
PGout(8)=1;//發送資料使能
while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);//等待上一次資料發送完成
USART_SendData(USART2,data);
}
void My485_Send_Len_data(u8 data[],u16 len)
{
u16 t=0;
for(t=0;t<len;t++)
My485_Send_One_Data(data[t]);
}
void My485_Receive_data(u8 len)
{
NVIC_InitTypeDef NVIC_InitStructure;
PGout(8)=0;//接收資料使能
NUM=0;
RS485_Receive_Num=len;
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ; //搶占優先級 3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子優先級 3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);
}
main.c代碼:
#include "stm32f4xx.h"
#include "myusart.h"
#include "delay.h"
#include "485.h"
u8 buffer[200];//接收的資料存盤在這個陣列
u8 RS485_Receive_Num=0;//儲存需要接收的資料個數
u8 NUM=0; //統計現在接收到幾個資料
u8 arr[200]; //發送的資料儲存在這個陣列
int main(void)
{
u16 i;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
Myusart1_Init(115200);
My485_Init(115200);
delay_init(168);
My485_Receive_data(100);//接收100個資料
while(1)
{
if(RS485_Receive_Num<=NUM)
{
NUM=0;
USART_ITConfig(USART2, USART_IT_RXNE, DISABLE);
for(i=0;i<RS485_Receive_Num;i++)
{
USART_SendData(USART1,buffer[i]);//通過串口監視器顯示
delay_ms(10);//一定要延時,保證每個資料能準確發送到串口
}
}
// USART_SendData(USART1,buffer[45]);//通過串口監視器顯示
}
}
5、引腳連接
①F1板子485端子A線與F4板子485端子A線相連,B線與B線相連,
②保證F1板子與F4板的USART2與485模塊相連,
③保證F4板子USART1與USB輸出相連,
6、測驗
USB連接F4板子并打開串口監視器,同時復位F1、F4板子,察看資料接收情況:

四、總結
485通信,主要還是用到上一篇博客總結的USART通信,雙方485模塊只是提供了長途導線連接介面,明白了USART通信和485模塊的作用,485通信應該很好理解的,
當然,對于大型專案,32的CPU無法滿足計算要求,可以利用485通信在上位機中處理資料:

我用的485模塊與上位機的連接器:

感興趣的朋友可以去嘗試一下,上位機代碼是VS用C#編的,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/351107.html
標籤:其他
上一篇:Arduino與Proteus仿真實體-74C922鍵盤解碼驅動仿真
下一篇:基于物聯網的環境監控系統
