文章目錄
- 前言
- 一、串口通信基本知識
- 1串口通訊
- 2通訊方式
- 3波特率
- 4同步通信與異步通信
- 5無線串口模塊
- 二、無線串口模塊配置
- 三、單片機配置
- 1引腳設定
- 2串口初始化
- 3資料收發
- 四、利用傳輸協議收發
- 1向上位機發送資料
- 2單片機之間相互傳輸
- 單片機發送
- 單片機接收
前言
? 最近學習了利用無線串口模塊進行單片機與電腦上位機,或者進行單片機與單片機之間的通信,在這里分享一下串口通信的作業原理和通信協議的相關學習筆記,以MK60為例,
一、串口通信基本知識
1串口通訊
串口是一種按位(bit)進行發送和接收位元組的通訊方式,即在傳輸中將1位元組(Byte)拆分成8位,用一個埠一位一位地進行傳輸,
2通訊方式
由于在資料傳輸時占用了一個埠,所以串口的資料線最少只需要一根,使用一根傳輸線可以實作單工模式(Simplex Communication)通信或者半雙工模式(Half Duplex)通信,使用兩根傳輸線可以實作全雙工模式(Full Duplex)通信,
單工模式通信的一方固定為發送端,另一方固定為接收端,資訊單向傳輸,
半雙工模式通信中資訊傳輸方向是可以改變的,即傳輸的兩個設備在傳輸中負責發送還是接收是不固定的,半雙工模式通信中任一時刻的通信方向是唯一的,當一方為發射端時另一方為接收端,不可以同時進行收發,
全雙工模式通信中兩根傳輸線各自負責一個方向的傳輸,可以實作收發同時進行,
3波特率
波特率(Baud)就是每秒鐘傳輸的資料位數,它是對傳輸速率的一種度量,
通常使用的波特率有 600、900、1200、1800、2400、4800、9600、19200、38400、57600、115200 等,
4同步通信與異步通信
同步通信
進行資料傳輸時,發送和接收雙方要保持完全的同步,要求接收和發送設備必須使用同一時鐘,同步通信速度快但是對于軟硬體要求較高,
異步通信
異步通信在發送字符時,所發送的字符之間的時隙可以是任意的,傳輸雙方按照一定的協議,使傳輸資料的每一幀包含“開始位”和“停止位”(或包含“校驗位”),每一幀的資料傳輸總是從“開始位”開始,傳輸到“停止位”結束,異步通信的實作相對簡單,但是傳輸效率相對同步通信較低,在本文中所使用的的都是異步通信方式,
在單片機中使用異步收發器(Universal Asynchronous Receiver/Transmitters,UART)功能進行實作,
5無線串口模塊
? 無線串口模塊本身作用相當于有線串口通信中的傳輸線,單片機上的從機和電腦上用的主機端,一般使用的是全雙工通信,常見有四個介面,除去VCC和GND的電源介面外,另外兩個介面分別為RXD:接收資料,TXD:發送資料,與單片機之間的串口介面交叉相連(如圖),
|
|
二、無線串口模塊配置
? 第一次使用串口模塊之前需要事先配置好串口模塊,購買串口模塊時商家一般都會提供相關資料,這里主要提一下配置中需要注意的幾個點:
| 1.電腦埠查看 |
|---|
| 先注意安裝驅動,配置時的埠號可以 右鍵***此電腦—>管理—>設備管理器—>埠*** 查看, |
| 2.主從地址設定 |
| 設定口模塊的主從機的地址必須相同,并且注意不要和其他的無線串口使用同一地址, |
| 3.波特率設定 |
| 無線串口模塊主從機的波特率以及電腦上位機和單片機設定的波特率必須相同,否則無法正常通信,波特率設定并不是越大越好,根據實際自己能通信的波特率而定,不推薦太大, |
| 4.從機設定模式 |
| 配置從機時需要注意將SET引腳和VCC引腳短接以進入配置模式, |
三、單片機配置
1引腳設定
單片機的串口可以使用的引腳是固定的,可以查閱資料手冊,下面列舉的是K60的串口通道,
/********************************** UART ***************************************/
// 模塊通道 埠 可選范圍 建議
#define UART0_RX PTA15 //PTB16 //PTA1、PTA15、PTB16、PTD6 PTA1不要用(與Jtag沖突)
#define UART0_TX PTA14 //PTB17 //PTA2、PTA14、PTB17、PTD7 PTA2不要用(與Jtag沖突)
#define UART1_RX PTE1 //PTC3、PTE1
#define UART1_TX PTE0 //PTC4、PTE0
#define UART2_RX PTD2 //PTD2
#define UART2_TX PTD3 //PTD3
#define UART3_RX PTE5 //PTB10、PTC16、PTE5
#define UART3_TX PTE4 //PTB11、PTC17、PTE4
#define UART4_RX PTE25 //PTC14、PTE25
#define UART4_TX PTE24 //PTC15、PTE24
#define UART5_RX PTE9 //PTD8、PTE9
#define UART5_TX PTE8 //PTD9、PTE8
UART0 和 UART1 時鐘源為內核時鐘,UART2~UART5 的時鐘源為外設時鐘(總線時鐘),在后面的示例中所使用的的都是UART3通道,從機的TX介面連接到PTE5,RX介面連接到PTE4,可以自行根據實際情況修改,
2串口初始化
K60的串口在初始化之后就可以直接接收發送了,但在實際的使用中接收資料時容易丟失資料,需要設定串口資料接收的中斷,當資料需要接收時就進入中斷,
#define UARTn UART3 //使用UART3通道
#define BAUD 57600 //設定波特率為57600
#define UARTn_RX_TX_IRQ UART3_RX_TX_IRQn
#define UARTn_RX_TX_VECTORn UART3_RX_TX_VECTORn
void UART_IRQHandler(void);//中斷服務函式宣告
void uart_rx_irq_en(UARTn_e uratn)
{
UART_C2_REG(UARTN[uratn]) |= UART_C2_RIE_MASK; //使能UART接收中斷
enable_irq((IRQn_t)((uratn << 1) + UART0_RX_TX_IRQn)); //使能IRQ中斷
}
void uart_init()
{
uart_init (UARTn, BAUD); //初始化串口UARTn,波特率為BAUD
NVIC_SetPriority(UARTn_RX_TX_IRQn,1); //配置串口中斷優先級為1
set_vector_handler(UARTn_RX_TX_VECTORn,UART_IRQHandler); //配置串口中斷服務函式UART_IRQHandler
uart_rx_irq_en(UARTn); //使能中斷
}
3資料收發
在進行大量資料的收發之前,先進行簡單資料的收發來測驗,以下程式中實作在上位機中發送一個字符,單片機接收到后再將原資料處理發送回上位機,
void uart_getchar (UARTn_e uratn, char *ch)//接收一個字符
{
while (!(UART_S1_REG(UARTN[uratn]) & UART_S1_RDRF_MASK)); //等待接收滿了
*ch = UART_D_REG(UARTN[uratn]); // 獲取接收到的8位資料
}
void uart_putchar (UARTn_e uratn, char ch)//發送一個
{
while(!(UART_S1_REG(UARTN[uratn]) & UART_S1_TDRE_MASK)); //等待發送緩沖區空
UART_D_REG(UARTN[uratn]) = (uint8)ch; //發送資料
}
int main() //單片機主函式
{
char ch;
uart_init();//串口初始化
while(1)
{
uart_getchar(UARTn,&ch); //接收一個字符儲存到變數ch
ch++;//示例處理,轉入'A'輸出'B'
uart_putchar(UARTn,ch); //發送字符ch,長資料接收需要在中斷中
}
}
程式效果:
? 在上位機發送字符’A’,單片機回傳字符’B’
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-vkzflyO2-1608480123544)(C:\Users\yejiahang\AppData\Roaming\Typora\typora-user-images\image-20201216231640759.png)]
四、利用傳輸協議收發
1向上位機發送資料
? 在實際使用當中,僅僅單字符傳輸是不夠的,所以這里就要用到傳輸協議了,顧名思義,協議就是傳輸雙方規定好固定傳輸格式,從而使接收到多個資料時不會混淆,并且當產生傳輸資料出錯時可以檢測到甚至糾錯,傳輸協議可以按照自己的上位機或者自己設計,
? 這是我所使用的匿名上位機的傳輸協議格式:
| “AAAA”+“02”+Pb+X[0]+X[1]+···+X[n] |
|---|
AAAA
標志位,標志一幀資料的開始
02
協議代碼, 表示此協議為02號協議
Pb
校驗位,將n個資料求和之后得出的校驗位,檢驗是否有誤
X[n]
資料 ,所要傳輸的n個資料
下面為示例協議的發送函式,注意:由于傳輸的資料為16位,而串口一次傳8位資料,所以單個資料被拆成兩個進行傳輸,
unsigned short int pst[10]={0};
void pstInit(void)//所要發送的資料設定,這里傳輸三姿態角作為示例
{
pst[0]=pitch;
pst[1]=roll;
pst[2]=yaw;
}
/*
*uartn 串口通道
*pst 所要發送的資料
*x 協議代碼 此處為2
*/
void Data_SendMc(UARTn_e uartn,unsigned short int *pst,uint8 x)
{
unsigned char _cnt=0; unsigned char sum = 0;
unsigned char data_to_send[23]; //發送快取
data_to_send[_cnt++]=0xAA;
data_to_send[_cnt++]=0xAA;
data_to_send[_cnt++]=x;
data_to_send[_cnt++]=0;
data_to_send[_cnt++]=(unsigned char)(pst[0]>>8); //高8位
data_to_send[_cnt++]=(unsigned char)pst[0]; //低8位
data_to_send[_cnt++]=(unsigned char)(pst[1]>>8);
data_to_send[_cnt++]=(unsigned char)pst[1];
data_to_send[_cnt++]=(unsigned char)(pst[2]>>8);
data_to_send[_cnt++]=(unsigned char)pst[2];
data_to_send[_cnt++]=(unsigned char)(pst[3]>>8);
data_to_send[_cnt++]=(unsigned char)pst[3];
data_to_send[_cnt++]=(unsigned char)(pst[4]>>8);
data_to_send[_cnt++]=(unsigned char)pst[4];
data_to_send[_cnt++]=(unsigned char)(pst[5]>>8);
data_to_send[_cnt++]=(unsigned char)pst[5];
data_to_send[_cnt++]=(unsigned char)(pst[6]>>8);
data_to_send[_cnt++]=(unsigned char)pst[6];
data_to_send[_cnt++]=(unsigned char)(pst[7]>>8);
data_to_send[_cnt++]=(unsigned char)pst[7];
data_to_send[_cnt++]=(unsigned char)(pst[8]>>8);
data_to_send[_cnt++]=(unsigned char)pst[8];
data_to_send[3] = _cnt-4;
sum = 0;
for(unsigned char i=0;i<_cnt;i++)//校驗位計算
sum += data_to_send[i];
data_to_send[_cnt++] = sum;
for(unsigned char i=0;i<_cnt;i++)
uart_putchar (uartn,data_to_send[i] );//資料發送
}
接收資料接收部分將區分單片機發送資料與電腦發送的資料,以十六進制數AAAA為標志位的資料是由單片機發送,以字串“AAAA”為標志位的是由上位機發送資料,
2單片機之間相互傳輸
下面是我在單片機之間傳輸資料時使用的格式:
| (X1,X2,······,Xn,Pb) |
|---|
’('
開始位 , 標志一幀資料的開始
Xn
資料 , 所要傳輸的n個資料
Pb
校驗位 , 將n個資料求和之后得出的校驗位,檢驗是否有誤
’)'
停止位, 標志一幀資料的結束
單片機發送
#define START '(' //開始位設定
#define FINISH ')' //結束位設定
#define SP ',' //以逗號分隔
unsigned short int pst[10]={0};
void pstInt(void)//所要發送的資料設定,同向上位機發送
{
pst[0]=pitch;
pst[1]=roll;
pst[2]=yaw;
}
/*
*uartn 串口通道
*pst 所要發送的資料
*x 發送資料個數 示例中為3
*/
void Data_SendMcu(UARTn_e uartn,unsigned short int *pst,uint8 x)
{
unsigned char _cnt=0; unsigned char sum = 0;
unsigned char data_to_send[45]; //發送快取
data_to_send[_cnt++]=START;
for(unsigned char i=0;i<x;i++)
{
data_to_send[_cnt++]=(unsigned char)(pst[i]>>8); //高8位
data_to_send[_cnt++]=(unsigned char)pst[i]; //低8位
data_to_send[_cnt++]=SP;
}
sum = 0;
for(unsigned char i=0;i<_cnt;i++)//校驗位計算
sum += data_to_send[i];
data_to_send[_cnt++]=0;
data_to_send[_cnt++] = sum;
data_to_send[_cnt++] =FINISH;
for(unsigned char i=0;i<_cnt;i++)
uart_putchar (uartn,data_to_send[i] );//資料發送
}
與單片機的發送和與上位機發送類似,由于不受上位機協議限制,我們可以自己設定發送資料的個數,即每一幀傳輸資料長度可以不一樣,不需要用0占位,
單片機接收
typedef enum
{
UART_GET_WAIT, //等待接收狀態
UART_GET_ING, //正在接收資料
UART_GET_T, //接收成功
UART_GET_F, //接收失敗(校驗失敗)
}UartGetStatusNode;
typedef struct
{
unsigned short int data_to_Get[45]; //接收快取
unsigned char n; //上一分隔符位置
unsigned char nn; //當前接收位置
unsigned char t; //接收到資料個數
unsigned short int prt[10]; //接收資料快取
unsigned short int UartGetCh[10]; //可信資料
}UartInfoNode;
UartGetStatusNode UartGetStatus=UART_GET_WAIT; //接收狀態機
UartInfoNode UartInfo; //資料結構體變數
void Data_GetInit(void)//結構體初始化
{
memset(UartInfo.data_to_Get,0,45);
UartInfo.n=0;
UartInfo.nn=0;
UartInfo.t=0;
memset(UartInfo.prt,0,10);
}
void Data_GetMcu(UARTn_e uartn,char ch)
{
switch(UartGetStatus)
{
case UART_GET_WAIT:
if(ch=='(') //找到開始位
{
UartInfo.data_to_Get[UartInfo.nn++]=ch;
UartInfo.n=UartInfo.nn-1;
UartGetStatus=UART_GET_ING;
}
break;
case UART_GET_ING:
if(ch=='(')
UartGetStatus=UART_GET_F;//資料位數錯誤,接收失敗
else if(ch==',') //找到分隔位
{
UartInfo.data_to_Get[UartInfo.nn++]=ch;
if(UartInfo.nn==UartInfo.n+4)
UartInfo.prt[UartInfo.t++]= UartInfo.data_to_Get[UartInfo.nn-3]<<8+UartInfo.data_to_Get[UartInfo.nn-2];//存入待校驗資料
else
UartGetStatus=UART_GET_F;//資料位數錯誤,接收失敗
UartInfo.n=UartInfo.nn-1;
}
else if(ch==')')//找到結束位
{
UartInfo.data_to_Get[UartInfo.nn++]=ch;
for(unsigned char i=0;i<UartInfo.nn-3;i++)//進行校驗
sum +=UartInfo.data_to_Get[i];
if(sum==UartInfo.data_to_Get[UartInfo.nn-3]<<8+UartInfo.data_to_Get[UartInfo.nn-2])
UartGetStatus=UART_GET_T;//校驗通過,資料可信
else
UartGetStatus=UART_GET_F;//檢驗未通過,資料不可信
}
else
UartInfo.data_to_Get[UartInfo.nn++]=ch;
if(UartInfo.nn>44)
UartGetStatus=UART_GET_F;
break;
case UART_GET_T:
for(unsigned char i=0;i<UartInfo.t;i++) //接收成功,將快取資料存入UartGetCh
UartInfo.UartGetCh[i]=UartInfo.prt[i];
Data_GetInit();
UartGetStatus=UART_GET_WAIT;
sum=0;
break;
case UART_GET_F: //接收失敗,重新查找
Data_GetInit();
UartGetStatus=UART_GET_WAIT;
sum=0;
break;
default:
break;
}
}
void UART_IRQHandler(void)
{
if(UART_S1_REG(UARTN[UARTn]) & UART_S1_RDRF_MASK) //接收資料暫存器滿
uart_getchar (UARTn, &ch);
Data_GetMcu(UARTn,ch);//接收處理
if(UART_S1_REG(UARTN[UARTn]) & UART_S1_TDRE_MASK ) //發送資料暫存器空
{
Data_SendMcu(UARTn,pst,x);
}
}
int main()
{
uart_init();
Data_GetInit();
while(1)
{
//對UartInfo.UartGetCh進行處理
}
}
當接收到的資料校驗失敗的時候,這一幀的資料直接放棄,等待下一次的接收,只有當校驗通過的時候才會將快取資料存入UartGetCh陣列當中,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/238551.html
標籤:其他
上一篇:JS資料型別檢測typeof、instanceof、constructor、Object.prototype.toString.call(val)的區別
