在簡單的學習過了STM32中的簡單外設以及中斷系統後,在本章節中開始介紹STM32晶片中各個通訊介面的設定。在計算機中,按資料傳輸方式可分為序列通訊以及並行通訊;按資料同步方式可分為非同步通訊和同步通訊;按資料傳輸方向課分為單工、半雙工和全雙工通訊。
序列通訊: 在一條傳輸線上,將資料按照二進位制位依次傳輸,傳輸一位資料佔據一個固定的時間長度。適用於計算機之間、計算機與外設之間的遠距離通訊,其具備佔用傳輸線數量少、長距離傳輸時成本低的優點,但資料傳輸控制相比於並行通訊複雜。
並行通訊: 在多條傳輸線上,一個資料的多位同時進行傳輸,通常是8位元、16位元或32位元資料一起進行傳輸。其具備控制簡單、傳輸速度快的特點,但由於其佔用傳輸線過多,長距離傳輸資料時成本較高,且接受裝置出同時接收資料時容易出現錯位即抗干擾能力弱。
非同步通訊: 通訊雙方(傳送和接收端)使用各自的時鐘控制資料的傳送和接收,但為了雙方的收發協調,時鐘需要儘可能的一致。其具備實現容易、成本低的特點,但通訊過程中每個字元需要新增2~3位資料作為起止位,各幀之間需有間隔導致了傳輸效率不高。
同步通訊: 通訊時需要建立傳送方時鐘對接收方時鐘的直接控制,使得雙方達到完全的同步。傳輸資料的位之間距離為「位間隔」的整數倍,且傳送的字元間不留間隙,保持位同步關係。
單工通訊: 資料的傳輸僅能夠沿著一個方向,不能反向傳輸。
半雙工通訊: 資料的傳輸可以沿著兩個方向,但需要分時進行。
全雙工通訊: 資料的傳輸可以同時雙向的進行。
通訊速率: 衡量通訊效能的引數,以波特率來表示,代表著每秒鐘傳輸二進位制編碼的位數,單位為:位/秒。
串列埠通訊(Serial communication)是外設和計算機之間經過傳輸線、地線以及電源線進行資料傳輸的一種通訊方式,屬於序列通訊。串列埠是常用的介面標準,規定了電氣標準,但沒有規定介面外掛電纜以及使用的協定。
A. 介面標準
串列埠通訊的介面標準包含了RS-232C、ES-232、RS-422A、RS-485等等,常用的是RS-232C標準,定義了資料終端裝置(DTE)與資料通訊裝置(DCE)之間的物理介面標準。而在RS-232C標準下又有DB25(25針聯結器)和DB9(9針聯結器)之分,DB9的管腳說明如下表中所示:
管腳序號 | 管腳定義 | 功能 | 訊號方向 |
---|---|---|---|
1 | PGND | 保護接地 | |
2 | TXD | 傳送資料(序列輸出) | DTE→DCE |
3 | RXD | 接收資料(序列輸入) | DTE←DCE |
4 | RTS | 傳送請求 | DTE→DCE |
5 | CTS | 傳送允許 | DTE←DCE |
6 | DSR | 資料建立就緒 | DTE←DCE |
7 | SGND | 訊號接地 | |
8 | DCD | 載波檢測 | DTE←DCE |
9 | DTR | 資料終端準備就緒 | DTE→DCE |
10 | RI | 振鈴指示 | DTE←DCEddd |
B. 通訊協定
RS23的通訊協定遵循96-N-8-1格式。其中「96」表示通訊波特率為9600;「N」表示無校驗位;「8」表述資料位數為8位元;「1」表示有1位停止位。
在STM32中的USART(通用同步非同步收發器),可以與外部裝置進行全雙工資料交換,UART(通用非同步收發器)是在其基礎上剪裁掉了同步通訊功能。USART在STM32中應用最多的是對printf重定向,通過輸出函數將資訊列印到串列埠助手上顯示出來。USART的內部結構框圖如下所示:
引腳功能: TX(傳送資料輸出引腳),RX(接收資料輸入引腳),SW_RX(資料接收引腳,內部引腳),nRTS(請求以傳送,n表示低電平有效,僅適用於硬體流控制),nCTS(清除以傳送,n表示低電平有效,僅適用於硬體流控制)。
資料暫存器: USART_DR,低9位有效,第9位資料是否有效取決於USART控制暫存器1(USART_CR1)的M位設定,當M位為0時表示8位元資料字長,M位為1時表示9位資料字長,通常情況下使用8位元資料字長。
控制器: USART中有專門控制傳送的傳送器、控制接收的接收器,以及喚醒單元、中斷控制等。
波特率生成: 波特率越大,傳輸速度越快。
計算公式: 波特率= USART的時脈頻率 / (16*USARTDIV)
其中USARTDIV是一個存放在波特率暫存器(USART_BRR)的一個無符號定點數。串列埠通訊常用的波特率為4800、9600、115200等。
在對STM32F1的USART進行設定時,需要使用到USART庫,包含stm32f10x_usart.c和stm32f10x_usart.h檔案。
使能串列埠時鐘和GPIO埠時鐘;
STM32F103ZET6晶片中共有5個串列埠通訊資源,其中串列埠1掛載在APB1匯流排上,串列埠2~5掛載在APB2匯流排上。使用串列埠時,分別使能對應匯流排上的串列埠以及GPIO的時鐘即可。呼叫函數:RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOx, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_USARTx, ENABLE);
設定GPIO埠,設定串列埠引腳為複用功能;
使用引腳的USART功能需要將GPIO設定為複用功能,並將串列埠的Tx引腳設定為複用推輓輸出,Rx引腳設定為浮空輸入,呼叫函數:
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);
typedef struct
{
uint32_t USART_BaudRate; //波特率,4800 / 9600 / 115200
uint16_t USART_WordLength; //字長,8 / 9
uint16_t USART_StopBits; //停止位,0.5 / 1 / 1.5 / 2
uint16_t USART_Parity; //校驗位 USART_Parity_NO(無校驗)、USART_Parity_Even(偶校驗)以及USART_Parity_Odd(奇校驗)
uint16_t USART_Mode; //USART模式 USART_Mode_Rx / USART_Mode_Tx
uint16_t USART_HardwareFlowControl; //硬體流控制 只有在硬體流模式下有效,常用無硬體流模式 USART_HardwareFlowControl_None
} USART_InitTypeDef;
void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState);
void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState);
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
USART_ITConfig(USART1, USART_IT_TC, ENABLE);
ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT);
uint16_t USART_ReceiveData(USART_TYpeDef* USARTx);
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG);
使用STM32的USART1實現微控制器與電腦端串列埠助手之間的通訊,詳細的程式碼如下:
usart.h
#ifndef _usart_H
#define _usart_H
#include "system.h"
void USART1_Init(u32 bound);
#endif
usart.c
#include "usart.h"
int fputc(int ch,FILE *p)
{
USART_SendData(USART1, (u8)ch);
while(USART_GetFlagStatus(USART1,USART_FLAG_TC) != SET);
return ch;
}
void USART1_Init(u32 bound)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
// 設定GPIO埠
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;//TX
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;//RX
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA,&GPIO_InitStructure);
//USART1 初始化設定
USART_InitStructure.USART_BaudRate = bound;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
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(USART1, &USART_InitStructure);
USART_Cmd(USART1, ENABLE);
USART_ClearFlag(USART1, USART_FLAG_TC);
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
//Usart1 NVIC 設定
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;
NVIC_InitStructure.NVIC_IRQChannelSubPriority =3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void USART1_IRQHandler(void)
{
u8 r;
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
r =USART_ReceiveData(USART1);
USART_SendData(USART1,r);
while(USART_GetFlagStatus(USART1,USART_FLAG_TC) != SET);
}
USART_ClearFlag(USART1,USART_FLAG_TC);
}
main.c
#include "system.h"
#include "led.h"
#include "SysTick.h"
#include "usart.h"
int main()
{
u8 i=0;
SysTick_Init(72);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
LED_Init();
USART1_Init(9600);
while(1)
{
i++;
if(i%20==0)
{
led1=!led1;
}
delay_ms(10);
}
}
I2C(Inter-Intergrated Circuit)是PHILIPS公司開發的序列匯流排,用於連線微控制器及其外圍裝置,屬於同步通訊。其具有控制方式簡單,通訊速率較高的優點。I2C匯流排由兩根雙向訊號線組成,一根是傳輸線SDA,一根是時鐘線SCL。使用I2C通訊裝置的連線如下圖所示:
I2C的物理層具備如下特點:
(1) 支援多裝置,匯流排上可連線多個I2C裝置,支援多主機和多從機裝置;
(2) I2C匯流排只是用SDA和SCL兩根線路,SDA傳輸資料,SCL用於資料收發同步時鐘;
(3) 連線在I2C匯流排上的裝置都擁有一個獨一無二的地址,主機可使用改地址對不同裝置之間進行存取;
(4) I2C匯流排通過上拉電阻,當I2C裝置空閒時,輸出高阻態,當所有裝置都空閒時,由上拉電阻將匯流排拉昇為高電平;
(5) 多主機裝置同時使用I2C匯流排時,為了防止資料衝突,會採用仲裁方式決定佔用情況;
(6) 三種傳輸模式:標準模式(速率100kbit/s)、快速模式(400kbit/s)以及高速模式(3.4Mbit/s);
(7) 連線到相同I2C匯流排上的裝置數量受到匯流排最大電容400pF的限制。
I2C的協定層包含了:
(1) 資料有效性規定;
I2C匯流排在傳輸資料時,SCL時鐘訊號為高電平的過程中,傳輸線上的資料需要保持穩定;在時鐘訊號為低電平時,傳輸線上允許有高/低電平的變化。
(2) 起始和停止訊號;
SCL時鐘線為高電平是,SDA傳輸線由高電平向低電平的變化表示起始訊號;SCL為高電平是,SDA有低電平向高電平的變化表示終止訊號。
(3) 應答訊號;
當主機傳送器件傳輸一個位元組的資料後,其尾部需跟一個校驗位,檢驗位是接收端通過控制SDA來實現,可以提示傳送端資料已被接收完成。校驗位就是資料或地址傳輸過程中的響應,包括「應答(ACK)」和「非應答(NACK)」兩種訊號。作為資料接收端,裝置接收到資料後,如果要繼續接收資料,則需要向對方傳送ACK訊號即特定的低電平脈衝;若不要繼續接收資料,則需要向對方傳送NACK訊號即特定的高電平脈衝。
(4) 匯流排定址方式;
I2C匯流排定址方式按照從機地址位數可分為:7位定址和10位定址。
D7~D0組成從機的地址,D0為資料傳輸方向位,置0時表示主機向從機寫資料,置1時表示主機由從機讀取資料。
(5) 資料傳輸。
I2C匯流排上傳輸的資料比較廣泛,包含有地址訊號以及資料訊號,在一個起始訊號發出後,緊接著需要傳輸一個從機地址,每一次資料的傳輸由主機產生的終止訊號結束。在匯流排的一次資料傳輸中,有下以下的方式:
A. 主機向從機傳送資料,資料傳輸方向在整個過程中不變;
B. 主機在第一個位元組後,變為從從機讀取資料;
C. 傳輸過程中,需要改變資料流動方向時,起始訊號和從機地址被重複產生一次,但兩次讀/寫方向位相反。
AT24C02是一個2K位序列CMOS,內部含有256個8位元位元組,有一個16位元組頁寫緩衝器。AT24C02通過I2C匯流排進行操作,擁有防寫功能,晶片內部儲存的資料可以保證在掉電的情況下不丟失,一般用來儲存一些比較重要的資料。
AT24C02晶片的地址有7位,高4位元為1010,低3位由A0/A1/A2訊號線的高/低電平決定。I2C通訊中傳輸地址或資料都是以位元組為單位的,在傳輸地址時,晶片的地址佔據7位,還有1位用來選擇讀/寫方向。
24cxx.h
#ifndef _24cxx_H
#define _24cxx_H
#include "system.h"
#include "iic.h"
#define AT24C01 127
#define AT24C02 255
#define AT24C04 511
#define AT24C08 1023
#define AT24C16 2047
#define AT24C32 4095
#define AT24C64 8191
#define AT24C128 16383
#define AT24C256 32767
#define EE_TYPE AT24C02
void AT24CXX_Init(void);
u8 AT24CXX_ReadOneByte(u16 ReadAddr);
void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite);
void AT24CXX_WritelenByte(u16 WriteAddr,u8 DataToWrite,u8 len);
u32 AT24CXX_readLenByte(u16 ReadAddr,u8 Len);
u8 AT24CXX_Check(void);
void AT24CXX_read(u16 ReadAddr,u8 *pBuffer,u16 NumToRead);
void AT24CXX_write(u16 WriteAddr,u8 *pBuffer,u16 NumToWrite);
#endif
24cxx.c
#include "24cxx.h"
#include "SysTick.h"
void AT24CXX_Init(void)
{
IIC_Init();
}
u8 AT24CXX_ReadOneByte(u16 ReadAddr) //從從機AT24Cxx讀取一個位元組資料
{
u8 temp=0;
IIC_start();
if(EE_TYPE>AT24C16)
{
IIC_send_byte(0xA0); //傳送寫命令
IIC_wait_ack(); //等待應答
IIC_send_byte(ReadAddr>>8); //傳送高地址
}
else
{
IIC_send_byte(0xA0+((ReadAddr/256)<<1)); //傳送器件地址,寫資料
}
IIC_wait_ack();
IIC_send_byte(ReadAddr%256); //傳送低地址
IIC_wait_ack();
IIC_start();
IIC_send_byte(0xA1); //進入接收模式
IIC_wait_ack();
temp=IIC_read_byte(0);
IIC_stop();
return temp;
}
//在AT24Cxx指定地址寫入資料
void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite)
{
IIC_start();
if(EE_TYPE>AT24C16)
{
IIC_send_byte(0xA0); //傳送寫命令
IIC_wait_ack(); //等待應答
IIC_send_byte(WriteAddr>>8); //傳送高地址
}
else
{
IIC_send_byte(0xA0+((WriteAddr/256)<<1)); //傳送器件地址,寫資料
}
IIC_wait_ack();
IIC_send_byte(WriteAddr%256);
IIC_wait_ack();
IIC_send_byte(DataToWrite);
IIC_wait_ack();
IIC_stop();
delay_ms(10);
}
//向AT24CXX指定位置寫入長度為len的資料
void AT24CXX_WritelenByte(u16 WriteAddr,u8 DataToWrite,u8 len)
{
u8 t;
for(t=0;t<len;t++)
{
AT24CXX_WriteOneByte(WriteAddr+t,(DataToWrite>>(8*t))&0xff);
}
}
//從ATC24XX中指定地址開始讀長度為len的資料
u32 AT24CXX_readLenByte(u16 ReadAddr,u8 Len)
{
u8 t;
u32 temp=0;
for(t=0;t<Len;t++)
{
temp<<=8;
temp+=AT24CXX_ReadOneByte(ReadAddr+Len-t-1);
}
return temp;
}
//檢查ATC24XX工作是否正常,0正常,1不正常
u8 AT24CXX_Check(void)
{
u8 temp;
temp=AT24CXX_ReadOneByte(255);
if (temp==0x36)
return 0;
else
{
AT24CXX_WriteOneByte(255,0x36);
temp=AT24CXX_ReadOneByte(255);
if (temp==0x36)
return 0;
}
return 1;
}
//從指定地址開始讀出指定額資料,pBuffer存放資料的陣列首地址,NumToRead需要讀出資料的個數
void AT24CXX_read(u16 ReadAddr,u8 *pBuffer,u16 NumToRead)
{
while(NumToRead)
{
*pBuffer++=AT24CXX_ReadOneByte(ReadAddr++);
NumToRead--;
}
}
//指定地址寫入指定個數的資料
void AT24CXX_write(u16 WriteAddr,u8 *pBuffer,u16 NumToWrite)
{
while(NumToWrite--)
{
AT24CXX_WriteOneByte(WriteAddr,*pBuffer);
WriteAddr++;
pBuffer++;
}
}
iic.h
#ifndef _iic_H
#define _iic_H
#include "system.h"
/*IIC_SCL時鐘埠、引腳定義*/
#define IIC_SCL_PORT GPIOB
#define IIC_SCL_PIN (GPIO_Pin_10)
#define IIC_SCL_PORT_RCC RCC_APB2Periph_GPIOB
/*IIC_SDA資料埠、引腳定義*/
#define IIC_SDA_PORT GPIOB
#define IIC_SDA_PIN (GPIO_Pin_11)
#define IIC_SDA_PORT_RCC RCC_APB2Periph_GPIOB
/*IO操作*/
#define IIC_SCL PBout(10)//SCL
#define IIC_SDA PBout(11)//SDA
#define READ_SDA PBin(11)//ÊäÈëSDA
//IIC操作函數
u8 IIC_read_byte(u8 ack);
void IIC_send_byte(u8 byte);
void IIC_noack(void);
void IIC_ack(void);
u8 IIC_wait_ack(void);
void IIC_stop(void);//Í£Ö¹ÐźÅ
void IIC_start(void);
void SDA_IN(void);
void SDA_OUT(void);
void IIC_Init(void);
#endif
iic.c
#include "iic.h"
#include "SysTick.h"
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(IIC_SCL_PORT_RCC|IIC_SDA_PORT_RCC,ENABLE);
GPIO_InitStructure.GPIO_Pin=IIC_SCL_PIN;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_Init(IIC_SCL_PORT,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin=IIC_SDA_PIN;
GPIO_Init(IIC_SDA_PORT,&GPIO_InitStructure);
IIC_SCL=1;
IIC_SDA=1;
}
/*模擬IIC工作時序*/
void SDA_OUT(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin=IIC_SDA_PIN;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_Init(IIC_SDA_PORT,&GPIO_InitStructure);
}
void SDA_IN(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin=IIC_SDA_PIN;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
GPIO_Init(IIC_SDA_PORT,&GPIO_InitStructure);
}
void IIC_start(void) //起始訊號
{
SDA_OUT();
IIC_SDA=1;
IIC_SCL=1;
delay_us(5);
IIC_SDA=0;
delay_us(6);
IIC_SCL=0;
}
void IIC_stop(void) //終止訊號
{
SDA_OUT();
IIC_SCL=0;
IIC_SDA=0;
IIC_SCL=1;
delay_us(6);
IIC_SDA=1;
delay_us(6);
}
//等待應答訊號的到來,1接收應答失敗,0接收應答成功
u8 IIC_wait_ack(void)
{
u8 tempTime=0;
IIC_SDA=1;
delay_us(1);
SDA_IN(); //SDA設定輸入
IIC_SCL=1;
delay_us(1);
while(READ_SDA)
{
tempTime++;
if(tempTime>250) //接收應答失敗
{
IIC_stop();
return 1;
}
}
IIC_SCL=0;
return 0;
}
//產生應答訊號
void IIC_ack(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=0;
delay_us(2);
IIC_SCL=1;
delay_us(5);
IIC_SCL=0;
}
//產生非應答訊號
void IIC_noack(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=1;
delay_us(2);
IIC_SCL=1;
delay_us(5);
IIC_SCL=0;
}
//傳送單位元組資訊
void IIC_send_byte(u8 byte)
{
u8 t;
SDA_OUT();
IIC_SCL=0; //開始資料傳輸
for(t=0;t<8;t++)
{
if((byte&0x80)>0) //每一次傳送最高位
IIC_SDA=1;
else
IIC_SDA=0;
byte<<=1; //傳送位左移到最高位
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
delay_us(2);
}
}
//讀取單位元組資訊
u8 IIC_read_byte(u8 ack)
{
u8 i,receive=0;
SDA_IN(); //設定為輸入
for(i=0;i<8;i++)
{
IIC_SCL=0;
delay_us(2);
IIC_SCL=1;
receive<<=1;
if(READ_SDA)
receive++;
delay_us(1);
}
//讀取完成後,傳送應答資訊
if(!ack)
{
IIC_noack();
}
else
{
IIC_ack();
}
return receive;
}
main.c
#include "system.h"
#include "led.h"
#include "SysTick.h"
#include "usart.h"
#include "24cxx.h"
#include "key.h"
int main()
{
u8 i=0;
u8 key;
u8 k=0;
SysTick_Init(72);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
LED_Init();
USART1_Init(9600);
KEY_Init();
AT24CXX_Init();
while(AT24CXX_Check())
{
printf("AT24C02檢測不正常\r\n");
delay_ms(500);
}
printf("AT24C02檢測正常\r\n");
while(1)
{
key=KEY_Scan(0);
if(key==KEY_UP)
{
k++;
if(k>255)
{
k=255;
}
AT24CXX_WriteOneByte(0,k);
printf("寫入的資料為:%d\r\n",k);
}
if(key==KEY_DOWN)
{
k=AT24CXX_ReadOneByte(0);
printf("讀取的資料為:%d\r\n",k);
}
i++;
if(i%20==0)
{
led1=!led1;
}
delay_ms(10);
}
}
CAN(Controller Area Network),控制器區域網路,是ISO國際標準化序列通訊協定。為了減少線束的數量、通過多個LAN進行大量資料的高速通訊的實際需要,1986年德國電氣商博世公司開發了面向汽車的CAN通訊協定。CAN是國際上應用最廣泛的現場匯流排之一,為分散式系統實現各節點之間實時、可靠的資料通訊提供了有利的技術支援。
CAN通訊只需要兩個訊號線,CAN_H和CAN_L,CAN控制器根據兩根訊號線上的電位差來判斷匯流排電平。匯流排電平被分為顯性電平和隱性電平,傳送方通過使匯流排電平發生變化,從而將資訊傳送給接收方。
CAN通訊具備以下特點:
1、 多主控制;
匯流排空閒時,所有單元均可發生訊息,當兩個以上的單元同時發生訊息時,依據識別符號(ID)決定優先順序。ID並不是表示傳送的目的地址,而是表示存取匯流排訊息的優先順序。兩個以上的單元開始傳送訊息時,對訊息ID的每個位進行逐個仲裁比較。仲裁獲勝的單元繼續傳送訊息,仲裁失敗的單元停止傳送進行接收工作。
2、 系統柔軟性;
與匯流排相連的單元沒有類似「地址」的資訊,因此在匯流排上增加單元時,匯流排上的其它單元軟硬體及應用層不需要被改變。
3、 通訊速度快、距離遠;
最高1Mbps(距離小於40m),最遠10km(速率低於5Kbps)。
4、 錯誤檢測、錯誤通知和錯誤恢復功能;
匯流排上的所有單元都可檢測錯誤,出現錯誤的單元會立即通知其它單元,正在傳送訊息的單元檢測出錯誤後,會強制結束當前的傳送。強制結束傳送的單元會不斷重新傳送此訊息直到成功傳送為止。
5、 故障封閉功能;
可以判斷出錯的型別是匯流排上暫時的資料錯誤(外部噪聲)或持續的資料錯誤(單元內部故障、驅動器故障、斷線等)。當匯流排持續資料錯誤時,可將引起此故障的單元從匯流排上隔離。
6、 可連線節點多;
可以同時連線多個單元裝置,連線總數理論上無限制,但實際上可連線的單元數受到匯流排時延和電氣負載的限制。降低通訊速度,可連線的單元數增加;提高通訊速度,可連線單元數減少。
對於CAN通訊的兩種標準:ISO11898(高速)可以組建一個高速、短距離的閉環網路,該標準下匯流排最大長度40m,通訊速率最高1Mbps,匯流排兩端需各有一個120歐姆的電阻作為阻抗匹配功能,以減少回波反射。ISO11519-2(低速)可以組建一個低速、遠距離的開環網路,該標準下匯流排最大長度1km,通訊速率最高125kbps,兩根匯流排獨立不形成閉環,兩根匯流排上各自串聯一個2.2千歐的電阻。
在IS01898標準下,顯性電平對應邏輯0,CAN_H和CAN_L壓差為2.5V左右;隱性電平對應邏輯1,CAN_H和CAN_L壓差為0V。在匯流排上顯性電平具有優先權,只要有一個單元輸出顯性電平,匯流排上即為顯性電平。隱性電平具有包容性,只有所有的單元都輸出隱性電平,匯流排上才為隱性電平。
CAN匯流排和CAN控制器之間需要一個CAN收發器將訊號進行轉換,通訊主要通過5種型別的幀進行,分別為資料框、遙控幀、錯誤幀、過載幀以及幀間隔。其中資料框由7個段構成,分別為幀起始(資料框的開始段)、仲裁段(幀優先順序的段)、控制段(資料的位元組數及保留位的段)、資料段(資料內容,0~8位元組資料)、CRC段(檢查幀錯誤的段)、ACK段(確認正常接收段)以及幀結束段(資料框結束的段)。
在STM32F1晶片中自帶bxCAN控制器(Basic Extended CAN),基本擴充套件CAN,支援2.0A和B版本的CAN協定。目的是以最小的CPU負載,高效的管理大量傳入訊息,並按需要的優先順序實現訊息傳送。
使用庫函數對CAN進行設定需要使用到庫檔案stm32f10x_can.h和stm32f10x_can.c,詳細的步驟如下:
1、 使能CAN時鐘,將對應的引腳複用對映為CAN功能;
CAN1和CAN2都是掛載在APB1匯流排上的裝置,其傳送和接收引腳對應不同的IO口,因此在使能時鐘後,還需要使能對應IO口的時鐘,並將其設定為複用功能。設定程式碼如下:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, EANBLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, EANBLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉輸入模式
GPIO_Init(GPIOA, & GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //複用推輓輸出
GPIO_InitStructure.GPIO_Speede = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, & GPIO_InitStructure);
2、 設定CAN工作模式、波特率等;
呼叫函數:uint8_t CAN_Init(CAN_TypeDef* CANx, CAN_InitTypeDef* CAN_InitStruct);
引數CAN_InitStruct為結構體指標變數,其成員變數包含CAN工作模式以及波特率初始化的變數:
typedef struct
{
uint16_t CAN_prescaler; //設定CAN外設時鐘分頻
uint8_t CAN_Mode; //正常模式(CAN_Mode_Normal)、迴環模式(CAN_Mode_LoopBack)、靜默模式(CAN_Mode_Silent)以及迴環靜默模式(CAN_Mode_Silent_LoopBack)
uint8_t CAN_SJW; //設定CAN重新同步時單次可增加或縮短的最大長度,可設定為1-4tp(CAN_SJW_1/2/3/4tp)
uint8_t CAN_BS1; //設定位時序BS1長度,CAN_BS1_1/2/3/…/16tp
uint8_t CAN_BS2; //設定位時序BS2長度,CAN_BS1_1/2/3/…/8tp
FunctionalState CAN_TTCM; //是否使用時間觸發功能
FunctionalState CAN_ABOM; //是否使用自動離線管理
FunctionalState CAN_AWUM; //是否使用自動喚醒功能
FunctionalState CAN_NART; //是否使用自動重傳功能
FunctionalState CAN_RFLM; //是否使用鎖定接收FIFO
FunctionalState CAN_TXFP; //設定傳送報文的優先順序判斷方法,使能時以報文存入傳送郵箱的先後順序傳送,失能時按照報文ID優先順序傳送
}
CAN的波特率 = Fpclk1 / ((CAN_BS1+CAN_BS2+1)*CAN_Prescaler)
3、 設定CAN篩選器(過濾器);
呼叫函數:void CAN_FilterInit(CAN_FilterInitTypeDef* CAN_FilterInitStruct);
其中結構體CAN_FilterInitTypeDef的成員變數為:
typeder struct
{
uint16_t CAN_FilterIdHigh; //儲存需要篩選的ID高16位元
uint16_t CAN_FilterIdLow; //儲存需要篩選的ID低16位元
uint16_t CAN_FilterMaskIdHigh; //儲存需要篩選ID或掩碼
uint16_t CAN_FilterMaskIdLow; //儲存需要篩選ID或掩碼
uint16_t CAN_FilterFIFOAssignment; //設定報文被儲存到那一個接收FIFO,FIFO
0或FIFO1
uint8_t CAN_FilterNumber; //設定篩選器編號,0~27
uint8_t CAN_FilterMode; //設定篩選器工作模式,列表模式(CAN_FilterMode_IdList)和掩碼模式(CAN_FilterMode_IdMask)
uint8_t CAN_FilterScale; //設定篩選器位寬,CAN_FilterScale_32bit及CAN_FilterScale_64bit
}
4、 選擇CAN中斷型別,開啟中斷;
呼叫函數:void CAN_ITConfig(CAN_TypeDef* CANx, uint32_t CAN_IT, FunctionalState NewState);
5、 CAN傳送和接收訊息;
傳送訊息函數:uint8_t CAN_Transmit(CAN_TypeDef* CANx, CanTxMsg* TxMessage);
其中結構體變數CanTxMsg包含的成員變數為:
typedef struct
{
uint32_t StdId; //報文11位標準識別符號,0~0x7FF
uint32_t ExtId; //報文29位擴充套件識別符號,0~0x1FFFFFFF
uint8_t IDE; //擴充套件標誌位,CAN_ID_STD(標準幀)和CAN_ID_EXT(擴充套件幀)
uint8_t RTR; //報文型別標誌位,CAN_RTR_Data(資料框)和CAN_RTR_Remote(遙控幀)
uint8_t DLC; //資料框的長度,0~8,當報文是遙控幀是DLC=0
uint8_t Data[8]; //資料框中資料段的資料
}
接收訊息函數:void CAN_Receive(CAN_TypeDef* CANx, uint8_t FIFONumber, CanRxMsg* RxMessage);
其中結構體變數CanRxMsg包含的成員變數為:
typedef struct
{
uint32_t StdId; //報文11位標準識別符號,0~0x7FF
uint32_t ExtId; //報文29位擴充套件識別符號,0~0x1FFFFFFF
uint8_t IDE; //擴充套件標誌位,CAN_ID_STD(標準幀)和CAN_ID_EXT(擴充套件幀)
uint8_t RTR; //報文型別標誌位,CAN_RTR_Data(資料框)和CAN_RTR_Remote(遙控幀)
uint8_t DLC; //資料框的長度,0~8,當報文是遙控幀是DLC=0
uint8_t Data[8]; //資料框中資料段的資料
uint8_t FMI; //儲存篩選器編號,表示經過哪一個篩選器儲存進接收FIFO的
}
6、 CAN狀態的獲取
呼叫函數:CAN_TransmitStatus(); CAN_MessagePending(); CAN_GetFlagStatus();
STM32使用按鍵切換CAN1的通訊模式(資料傳送、接收),將資料通過串列埠列印出,詳細的程式碼模組如下:
can.h
#ifndef _can_H
#define _can_H
#include "system.h"
#define CAN_RX0_INT_ENABLE 0 //使能中斷
void CAN_Mode_Init(u8 tsjw,u8 tbs2,u8 tbs1,u16 brp,u8 mode); //CAN初始化
u8 CAN_Send_Msg(u8* msg,u8 len); //傳送資料
u8 CAN_Receive_Msg(u8 *buf); //接收資料
#endif
can.c
#include "can.h"
#include "usart.h"
void CAN_Mode_Init(u8 tsjw,u8 tbs2,u8 tbs1,u16 brp,u8 mode)
{
GPIO_InitTypeDef GPIO_InitStructure;
CAN_InitTypeDef CAN_InitStructure;
CAN_FilterInitTypeDef CAN_FilterInitStructure;
#if CAN_RX0_INT_ENABLE
NVIC_InitTypeDef NVIC_InitStructure;
#endif
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
CAN_InitStructure.CAN_TTCM=DISABLE;
CAN_InitStructure.CAN_ABOM=DISABLE;
CAN_InitStructure.CAN_AWUM=DISABLE;
CAN_InitStructure.CAN_NART=ENABLE;
CAN_InitStructure.CAN_RFLM=DISABLE;
CAN_InitStructure.CAN_TXFP=DISABLE;
CAN_InitStructure.CAN_Mode= mode;
CAN_InitStructure.CAN_SJW=tsjw;
CAN_InitStructure.CAN_BS1=tbs1;
CAN_InitStructure.CAN_BS2=tbs2;
CAN_InitStructure.CAN_Prescaler=brp;
CAN_Init(CAN1, &CAN_InitStructure);
//ÅäÖùýÂËÆ÷
CAN_FilterInitStructure.CAN_FilterNumber=0;
CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdMask;
CAN_FilterInitStructure.CAN_FilterScale=CAN_FilterScale_32bit;
CAN_FilterInitStructure.CAN_FilterIdHigh=0x0000;
CAN_FilterInitStructure.CAN_FilterIdLow=0x0000;
CAN_FilterInitStructure.CAN_FilterMaskIdHigh=0x0000;
CAN_FilterInitStructure.CAN_FilterMaskIdLow=0x0000;
CAN_FilterInitStructure.CAN_FilterFIFOAssignment=CAN_Filter_FIFO0;
CAN_FilterInitStructure.CAN_FilterActivation=ENABLE;
CAN_FilterInit(&CAN_FilterInitStructure);
#if CAN_RX0_INT_ENABLE
CAN_ITConfig(CAN1,CAN_IT_FMP0,ENABLE);
NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
#endif
}
#if CAN_RX0_INT_ENABLE
void USB_LP_CAN1_RX0_IRQHandler(void)
{
CanRxMsg RxMessage;
int i=0;
CAN_Receive(CAN1, 0, &RxMessage);
for(i=0;i<8;i++)
printf("rxbuf[%d]:%d\r\n",i,RxMessage.Data[i]);
}
#endif
u8 CAN_Send_Msg(u8* msg,u8 len)
{
u8 mbox;
u16 i=0;
CanTxMsg TxMessage;
TxMessage.StdId=0x12;
TxMessage.ExtId=0x12;
TxMessage.IDE=0;
TxMessage.RTR=0;
TxMessage.DLC=len;
for(i=0;i<len;i++)
TxMessage.Data[i]=msg[i];
mbox= CAN_Transmit(CAN1, &TxMessage);
i=0;
while((CAN_TransmitStatus(CAN1, mbox)==CAN_TxStatus_Failed)&&(i<0XFFF)) i++;
if(i>=0XFFF) return 1;
return 0;
}
u8 CAN_Receive_Msg(u8 *buf)
{
u32 i;
CanRxMsg RxMessage;
if( CAN_MessagePending(CAN1,CAN_FIFO0)==0)return 0;
CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);
for(i=0;i<RxMessage.DLC;i++)
buf[i]=RxMessage.Data[i];
return RxMessage.DLC;
}
main.c
#include "system.h"
#include "SysTick.h"
#include "led.h"
#include "usart.h"
#include "key.h"
#include "can.h"
int main()
{
u8 i=0,j=0;
u8 key;
u8 mode=0;
u8 res;
u8 tbuf[8],char_buf[8];
u8 rbuf[8];
SysTick_Init(72);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
LED_Init();
USART1_Init(9600);
KEY_Init();
CAN_Mode_Init(CAN_SJW_1tq,CAN_BS2_8tq,CAN_BS1_9tq,4,CAN_Mode_Normal);/
while(1)
{
key=KEY_Scan(0);
if(key==KEY_UP)
{
mode=!mode;
CAN_Mode_Init(CAN_SJW_1tq,CAN_BS2_8tq,CAN_BS1_9tq,4,mode);
if(mode==0)
{
printf("Normal Mode\r\n");
}
else
{
printf("LoopBack Mode\r\n");
}
}
if(key==KEY_DOWN)
{
for(j=0;j<8;j++)
{
tbuf[j]=j;
char_buf[j]=tbuf[j]+0x30;
}
res=CAN_Send_Msg(tbuf,8);
if(res)
{
printf("Send Failed!\r\n");
}
else
{
printf("傳送資料:%s\r\n",char_buf);
}
}
res=CAN_Receive_Msg(rbuf);
if(res)
{
for(j=0;j<res;j++)
{
char_buf[j]=rbuf[j]+0x30;
}
printf("接收資料:%s\r\n",char_buf);
}
i++;
if (i%20==0)
{
led1=!led1;
}
delay_ms(10);
}
}