STM32单片机(七). USART串口、IIC和CAN通信

在简单的学习过了STM32中的简单外设以及中断系统后,在本章节中开始介绍STM32芯片中各个通信接口的配置。在计算机中,按数据传输方式可分为串行通信以及并行通信;按数据同步方式可分为异步通信和同步通信;按数据传输方向课分为单工、半双工和全双工通信。
串行通信: 在一条数据线上,将数据按照二进制位依次传输,传输一位数据占据一个固定的时间长度。适用于计算机之间、计算机与外设之间的远距离通信,其具备占用传输线数量少、长距离传输时成本低的优点,但数据传输控制相比于并行通信复杂。
并行通信: 在多条数据线上,一个数据的多位同时进行传输,通常是8位、16位或32位数据一起进行传输。其具备控制简单、传输速度快的特点,但由于其占用数据线过多,长距离传输数据时成本较高,且接受设备出同时接收数据时容易出现错位即抗干扰能力弱。
异步通信: 通信双方(发送和接收端)使用各自的时钟控制数据的发送和接收,但为了双方的收发协调,时钟需要尽可能的一致。其具备实现容易、成本低的特点,但通信过程中每个字符需要添加2~3位数据作为起止位,各帧之间需有间隔导致了传输效率不高。
同步通信: 通信时需要建立发送方时钟对接收方时钟的直接控制,使得双方达到完全的同步。传输数据的位之间距离为“位间隔”的整数倍,且传送的字符间不留间隙,保持位同步关系。
单工通信: 数据的传输仅能够沿着一个方向,不能反向传输。
半双工通信: 数据的传输可以沿着两个方向,但需要分时进行。
全双工通信: 数据的传输可以同时双向的进行。
通信速率: 衡量通信性能的参数,以波特率来表示,代表着每秒钟传输二进制编码的位数,单位为:位/秒。

1、USART串口通信

1.1 USART介绍

串口通信(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的内部结构框图如下所示:
STM32单片机(七). USART串口、IIC和CAN通信_第1张图片
引脚功能: 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等。

1.2 串口通信配置步骤

在对STM32F1的USART进行配置时,需要使用到USART库,包含stm32f10x_usart.c和stm32f10x_usart.h文件。

  1. 使能串口时钟和GPIO端口时钟;
    STM32F103ZET6芯片中共有5个串口通信资源,其中串口1挂载在APB1总线上,串口2~5挂载在APB2总线上。使用串口时,分别使能对应总线上的串口以及GPIO的时钟即可。调用函数:RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOx, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_USARTx, ENABLE);

  2. 配置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);
  1. 初始化串口参数;
    调用函数:void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);
    其中USART_InitTypeDef是一个结构体类型,其中的成员变量如下:
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;
  1. 使能串口;
    调用串口函数:void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState);
  2. 设置串口中断类型并使能;
    调用函数:void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState);
    其中USARTx选择串口,USART_IT选择中断类型,NewState选择使能或失能。串口中断的类型很多,需要依据具有的场景进行选择,例如在串口接收数据时,产生中断:USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
    串口发送数据时:USART_ITConfig(USART1, USART_IT_TC, ENABLE);
  3. 设置串口中断优先级,使能串口中断通道;
    对串口中断进行了配置,需要初始化NVIC。
  4. 编写串口中断服务函数。
    使用串口开启了中断功能,就需要在中断服务函数(USARTx_IRQHandler)判断由串口产生的中断是那种类型,然后实现相应的功能。判断中断类型,调用库函数: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);

1.3 应用示例

使用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);
	}
}

2、I2C与EEPROM通信

2.1 I2C通信及配置步骤

I2C(Inter-Intergrated Circuit)是PHILIPS公司开发的串行总线,用于连接微控制器及其外围设备,属于同步通信。其具有控制方式简单,通信速率较高的优点。I2C总线由两根双向信号线组成,一根是数据线SDA,一根是时钟线SCL。使用I2C通信设备的连接如下图所示:
STM32单片机(七). USART串口、IIC和CAN通信_第2张图片
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时钟信号为高电平的过程中,数据线上的数据需要保持稳定;在时钟信号为低电平时,数据线上允许有高/低电平的变化。
STM32单片机(七). USART串口、IIC和CAN通信_第3张图片

(2) 起始和停止信号;
SCL时钟线为高电平是,SDA数据线由高电平向低电平的变化表示起始信号;SCL为高电平是,SDA有低电平向高电平的变化表示终止信号。
STM32单片机(七). USART串口、IIC和CAN通信_第4张图片

(3) 应答信号;
当主机发送器件传输一个字节的数据后,其尾部需跟一个校验位,检验位是接收端通过控制SDA来实现,可以提示发送端数据已被接收完成。校验位就是数据或地址传输过程中的响应,包括“应答(ACK)”和“非应答(NACK)”两种信号。作为数据接收端,设备接收到数据后,如果要继续接收数据,则需要向对方发送ACK信号即特定的低电平脉冲;若不要继续接收数据,则需要向对方发送NACK信号即特定的高电平脉冲。
STM32单片机(七). USART串口、IIC和CAN通信_第5张图片

(4) 总线寻址方式;
I2C总线寻址方式按照从机地址位数可分为:7位寻址和10位寻址。
在这里插入图片描述

D7~D0组成从机的地址,D0为数据传输方向位,置0时表示主机向从机写数据,置1时表示主机由从机读取数据。
(5) 数据传输。
I2C总线上传输的数据比较广泛,包含有地址信号以及数据信号,在一个起始信号发出后,紧接着需要传输一个从机地址,每一次数据的传输由主机产生的终止信号结束。在总线的一次数据传输中,有下以下的方式:
A. 主机向从机发送数据,数据传输方向在整个过程中不变;
B. 主机在第一个字节后,变为从从机读取数据;
C. 传输过程中,需要改变数据流动方向时,起始信号和从机地址被重复产生一次,但两次读/写方向位相反。

2.2 EEPROM(AT24C02)

AT24C02是一个2K位串行CMOS,内部含有256个8位字节,有一个16字节页写缓冲器。AT24C02通过I2C总线进行操作,拥有写保护功能,芯片内部存储的数据可以保证在掉电的情况下不丢失,一般用来存储一些比较重要的数据。

STM32单片机(七). USART串口、IIC和CAN通信_第6张图片
AT24C02芯片的地址有7位,高4位为1010,低3位由A0/A1/A2信号线的高/低电平决定。I2C通信中传输地址或数据都是以字节为单位的,在传输地址时,芯片的地址占据7位,还有1位用来选择读/写方向。
在这里插入图片描述

2.3 应用示例

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);
	}
}

3、CAN通信

3.1 CAN介绍

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负载,高效的管理大量传入消息,并按需要的优先级实现消息发送。

3.2 CAN通信配置步骤

使用库函数对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();

3.3 应用示例

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);
	}
}

你可能感兴趣的:(单片机学习与开发,STM32,IIC,USART串口,CAN通信)