本实验通过 KEY_UP 按键选择 CAN 的工作模式(正常模式/环回模式),然后通过 KEY0 控制数据发送,并通过查询的办法,将接收到的数据通过串口打印出来。如果是环回模式,用一个开发板即可测试。如果是正常模式,就需要 2 个探索者 STM32F4 开发板,并且 将他们的 CAN 接口对接起来,然后一个开发板发送数据,另外一个开发板将接收到的数据通过串口打印出来。
?CAN 的初始化配置步骤:
1)配置相关引脚的复用功能(AF9), 使能CAN 时钟。
//使能相关时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);//使能 PORTA 时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);//使能 CAN1 时钟
//初始化 GPIO
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11| GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化 PA11,PA12
//引脚复用映射配置
GPIO_PinAFConfig(GPIOA,GPIO_PinSource11,GPIO_AF_CAN1);//PA11 复用为 CAN1
GPIO_PinAFConfig(GPIOA,GPIO_PinSource12,GPIO_AF_CAN1);//PA12 复用为 CAN1
?2)设置 CAN 工作模式及波特率等。
CAN_Init()用来初始化 CAN 的工作模式以及波特率,CAN_Init() 函数体中,在初始化之前,会设置 CAN_MCR 寄存器的 INRQ 为 1 让其进入初始化模式,然后初始化 CAN_MCR 寄存器和 CRN_BTR 寄存器之后,会设置 CAN_MCR 寄存器的 INRQ 为 0 让其退出初始化模式。所以我们在调用这个函数的前后不需要再进行初始化模式设置。CAN_Init()函数的定义:
uint8_t CAN_Init(CAN_TypeDef* CANx, CAN_InitTypeDef* CAN_InitStruct);
第一个参数就是 CAN 标号,这里我们的芯片只有一个 CAN,所以就是 CAN1。
第二个参数是 CAN 初始化结构体指针,结构体类型是 CAN_InitTypeDef,结构体的定义:
?typedef struct
{
uint16_t CAN_Prescaler;
uint8_t CAN_Mode;
uint8_t CAN_SJW;
uint8_t CAN_BS1;
uint8_t CAN_BS2;
FunctionalState CAN_TTCM;
FunctionalState CAN_ABOM;
FunctionalState CAN_AWUM;
FunctionalState CAN_NART;
FunctionalState CAN_RFLM;
FunctionalState CAN_TXFP;
} CAN_InitTypeDef;
初始化实例为:?
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= CAN_Mode_LoopBack; //模式设置 1,回环模式;
CAN_InitStructure.CAN_SJW=CAN_SJW_1tq;//重新同步跳跃宽度为个时间单位
CAN_InitStructure.CAN_BS1=CAN_BS1_8tq; //时间段 1 占用 8 个时间单位
CAN_InitStructure.CAN_BS2=CAN_BS2_7tq;//时间段 2 占用 7 个时间单位
CAN_InitStructure.CAN_Prescaler=5; //分频系数(Fdiv)
CAN_Init(CAN1, &CAN_InitStructure); // 初始化 CAN1
3)设置滤波器。
函数 CAN_FilterInit ()用来初始化 CAN 的滤波器相关参数,CAN_Init() 函数体中,在初始化之前,会设置 CAN_FMR 寄存器的 INRQ 为 INIT 让其进入初始化模式, 然后初始化 CAN 滤波器相关的寄存器之后,会设置 CAN_FMR 寄存器的 FINIT 为 0 让其退出初始化模式。所以我们在调用这个函数的前后不需要再进行初始化模式设置。CAN_FilterInit ()函数的定义:
void CAN_FilterInit(CAN_FilterInitTypeDef* CAN_FilterInitStruct);
?CAN_FilterInitTypeDef类型定义:
typedef struct
{
uint16_t CAN_FilterIdHigh;
uint16_t CAN_FilterIdLow;
uint16_t CAN_FilterMaskIdHigh;
uint16_t CAN_FilterMaskIdLow;
uint16_t CAN_FilterFIFOAssignment;
uint8_t CAN_FilterNumber;
uint8_t CAN_FilterMode;
uint8_t CAN_FilterScale;
FunctionalState CAN_FilterActivation;
} CAN_FilterInitTypeDef;
第 1 个至第 4 个是用来设置过滤器的 32 位 id 以及 32 位 mask id, 分别通过 2 个 16 位来组合的。
第 5 个成员变量 CAN_FilterFIFOAssignment 用来设置 FIFO 和过滤器的关联关系,我们的实验 是关联的过滤器 0 到 FIFO0,值为 CAN_Filter_FIFO0。
第 6 个成员变量 CAN_FilterNumber 用来设置初始化的过滤器组,取值范围为 0~13。
第 7 个成员变量 FilterMode 用来设置过滤器组的模式,取值为标识符列表模式 CAN_FilterMode_IdList 和标识符屏蔽位模式 CAN_FilterMode_IdMask。
第 8 个成员变量 FilterScale 用来设置过滤器的位宽为 2 个 16 位 CAN_FilterScale_16bit 还是 1 个 32 位 CAN_FilterScale_32bit。
第 9 个成员变量 CAN_FilterActivation 就很明了了,用来激活该过滤器。
CAN_FilterInitStructure.CAN_FilterNumber=0; //过滤器 0
CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdMask;
CAN_FilterInitStructure.CAN_FilterScale=CAN_FilterScale_32bit; //32 位
CAN_FilterInitStructure.CAN_FilterIdHigh=0x0000;32 位 ID
CAN_FilterInitStructure.CAN_FilterIdLow=0x0000;
CAN_FilterInitStructure.CAN_FilterMaskIdHigh=0x0000;//32 位 MASK
CAN_FilterInitStructure.CAN_FilterMaskIdLow=0x0000;
CAN_FilterInitStructure.CAN_FilterFIFOAssignment=CAN_Filter_FIFO0;// FIFO0
CAN_FilterInitStructure.CAN_FilterActivation=ENABLE; //激活过滤器 0
CAN_FilterInit(&CAN_FilterInitStructure);//滤波器初始化
4)发送接受消息
发送消息的函数是:
uint8_t CAN_Transmit(CAN_TypeDef* CANx, CanTxMsg* TxMessage);
第一个参数是 CAN 标号,我们使用 CAN1。第二个参数是相关消息结构体 CanTxMsg 指针类型,CanTxMsg 结构体的成员变量用来设置标准标识符,扩展标识符,消息类型和消息帧长度等信息。 接受消息的函数是:
void CAN_Receive(CAN_TypeDef* CANx, uint8_t FIFONumber, CanRxMsg* RxMessage);
前面两个参数,CAN 标号和 FIFO 号。第二个参数 RxMessage 是用来存放接受到的消息信息。结构体 CanRxMsg 和结构体 CanTxMsg 比较接近,分别用来定义发送消息和描述接受消息。
5)CAN 状态获取 对于 CAN 发送消息的状态,挂起消息数目等等之类的传输状态信息,库函数提供了一些 列的函数,包括 CAN_TransmitStatus()函数,CAN_MessagePending()函数,CAN_GetFlagStatus() 函数等等,可以根据需要来调用。
源码讲解:
打开 can.c 文件,此部分代码总共 3 个函数,首先是:CAN_Mode_Init 函数。该函数用于 CAN 的初始化, 该函数带有 5 个参数,可以设置 CAN 通信的波特率和工作模式等。
第二个函数,Can_Send_Msg 函数。该函数用于 CAN 报文的发送,主要是设置标识符 ID 等信息,写入数据长度和数据,并请求发送,实现一次报文的发送。
//can 发送一组数据(固定格式:ID 为 0X12,标准帧,数据帧)
//len:数据长度(最大为 8) msg:数据指针,最大为 8 个字节.
//返回值:0,成功; 其他,失败;
u8 CAN1_Send_Msg(u8* msg,u8 len)
{
u8 mbox;
u16 i=0;
CanTxMsg TxMessage;
TxMessage.StdId=0x12; // 标准标识符为 0
TxMessage.ExtId=0x12; // 设置扩展标示符(29 位)
TxMessage.IDE=0; // 使用扩展标识符
TxMessage.RTR=0; // 消息类型为数据帧,一帧 8 位
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;
}
第三个函数,Can_Receive_Msg 函数。用来接受数据并且将接受到的数据存放到 buf 中。
//can 口接收数据查询
//buf:数据缓存区;
//返回值:0,无数据被收到; 其他,接收的数据长度;
u8 CAN1_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; //返回接受到的数据长度
}
can.c 里面,还包含了中断接收的配置,通过 can.h 的 CAN1_RX0_INT_ENABLE 宏定义, 来配置是否使能中断接收,本章我们不开启中断接收的。
can.h 头文件中,CAN1_RX0_INT_ENABLE 用于设置是否使能中断接收,本章我们不用中 断接收,故设置为 0。
main函数
int main(void)
{
u8 key;
u8 i=0,t=0;
u8 cnt=0;
u8 canbuf[8];
u8 res;
u8 mode=1;//CAN工作模式;0,普通模式;1,环回模式
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
delay_init(168); //初始化延时函数
uart_init(115200); //初始化串口波特率为115200
LED_Init(); //初始化LED
KEY_Init(); //按键初始化
CAN1_Mode_Init(CAN_SJW_1tq,CAN_BS2_6tq,CAN_BS1_7tq,6,CAN_Mode_LoopBack);//CAN初始化环回模式,波特率500Kbps
while(1)
{
key=KEY_Scan(0);
if(key==KEY0_PRES)//KEY0按下,发送一次数据
{
for(i=0;i<8;i++)
{
canbuf[i]=cnt+i;//填充发送缓冲区
}
printf("发送的数据:\r\n");
printf("%d %d %d %d\r\n",canbuf[0],canbuf[1],canbuf[2],canbuf[3]); //显示数据
printf("%d %d %d %d\r\n",canbuf[4],canbuf[5],canbuf[6],canbuf[7]); //显示数据
res=CAN1_Send_Msg(canbuf,8);//发送8个字节
if(res)printf("Failed\r\n"); //提示发送失败
else printf("OK \r\n"); //提示发送成功
}else if(key==WKUP_PRES)//WK_UP按下,改变CAN的工作模式
{
mode=!mode;
CAN1_Mode_Init(CAN_SJW_1tq,CAN_BS2_6tq,CAN_BS1_7tq,6,mode); //CAN普通模式初始化,普通模式,波特率500Kbps
if(mode==0)//普通模式,需要2个开发板
{
printf("Nnormal Mode \r\n");
}else //回环模式,一个开发板就可以测试了.
{
printf("LoopBack Mode\r\n");
}
}
key=CAN1_Receive_Msg(canbuf);
if(key)//接收到有数据
{
printf("接受的数据:\r\n");
printf("%d %d %d %d\r\n",canbuf[0],canbuf[1],canbuf[2],canbuf[3]); //显示数据
printf("%d %d %d %d\r\n",canbuf[4],canbuf[5],canbuf[6],canbuf[7]); //显示数据
}
t++;
delay_ms(10);
if(t==20)
{
LED0=!LED0;//提示系统正在运行
t=0;
cnt++;
//printf("%d\r\n",cnt); //显示数据
}
}
}
运行视频
|