> 文章列表 > STM32+收发器实现CAN和485总线

STM32+收发器实现CAN和485总线

STM32+收发器实现CAN和485总线

RS485总线是一种常见的(Recommended Standard)串行总线标准(485是它的标识号),采用平衡发送与差分接收的方式,因此具有抑制共模干扰的能力。CAN是控制器局域网络(Controller Area Network, CAN)的简称,是一种能够实现分布式实时控制的串行通信网络,属于CSMA(多路载波侦听)/CD(冲突检测)+AMP(基于消息优先级的冲突检测)总线并具备错误检测能力。

CAN总线起初用于实现汽车内ECU(Electronic Control Unit)之间可靠的通信,后因其简单实用可靠等特点,而广泛应用于工业自动化、船舶、医疗等其它领域。串口是工业自动化系统中非常重要的通讯方式,在工业自动化通讯系统始终占据非常重要的地位。因为485具有通信距离远,易布线等优点,常用于工业自动化智能终端,后因其技术特点,大部分的仪器仪表以及众多一主多从通信架构的产品都采用了这种通信方式。

本篇文章介绍了使用STM32+MAX485实现485通信以及使用STM32+TJA1004实现CAN通信的方法,并补充说明了CAN通信采样点和负载率的公式计算说明。

文章目录

概念说明

实现原理

嵌入式程序 

485通信

CAN通信

CAN参数补充说明


概念说明

  • 总线:总线(Bus)是一组能为多个部件分时共享的公共信息传送线路。分时和共享是总线的两个特点。计算机组成中总线分为地址总线、数据总线以及控制总线,他们挂接的计算机各功能部件(CPU,内存,显卡,硬盘),而我们文章提到的总线是各独立的智能硬件之间产品的串行通信方式,如果您能把多个产品(网关+n个终端)理解成一个智能产品系统,那么就不能理解为什么称这种通信方式为总线。
  • 差分信号:单端信号指的是用一个线传输的信号,以地为参考点,这样测量信号的精确值依赖系统内地的一致性,信号源和信号接收器距离越远,他们局部地的电压值之间有差异的可能性就越大。差分信号指的是用两根线传输信号,信号值是两个导体间的电压差,假如两条信号都收到同样的(同向、等幅度)的干扰信号,由于接收端是对接收的两条线信号进行减法处理,因此干扰信号会被基本抵消。
  • CSMA/CD:CSMA协议要求站点在发送数据之前先监听信道。如果信道空闲,站点就可以发送数据;如果信道忙,则站点不能发送数据。在早期的CSMA传输方式中,由于信道传播时延的存在,即使通信双方的站点,都没有侦听到载波信号,在发送数据时仍可能会发生冲突。如果发生冲突,信道上可以检测到超过发送站点本身发送的载波信号幅度的电磁波,由此判断出冲突的存在。一旦检测到冲突,发送站点就立即停止发送,并向总线上发一串阻塞信号,用以通知总线上通信的对方站点,快速地终止被破坏的帧,然后进行新一轮的总线竞争。
  • 收发器:收发器的主要功能是将CAN或UART控制器的TTL收发信号转换成CAN总线的单工差分信号,本例中485的收发器为MAX485,它的输入信号除了TX/RX,还有一个控制方向的引脚来控制当前芯片工作在收还是发模式,CAN的收发器为TJA1004。整体总线通信架构如下图:


实现原理

嵌入式程序跑在STM32平台上,正确配置UART以及CAN控制器的通信参数(速率帧格式等),正确驱动收发器(部分486收发器需要控制方向,CAN控制器有唤醒帧功能,需要进行正确操作)。然后嵌入式程序即可通过控制器进行数据的收发,下图是实现原理:


嵌入式程序 

485/CAN通信的嵌入式程序比较简单,基本分为两个部分:

  1. 控制器的初始化
  2. 收发器操作与数据的收发

流程架构图如下:


485通信

串口控制器的初始化包括:

  1. 开启串口控制器以及相应IO口时钟
  2. 初始化IO口以及配置串口中断
  3. 配置串口通信参数

下面是相关的接口代码(已经增加中文注释帮助理解),代码支持使用不同串口控制器来实现484通信:

//时钟初始化
void uart_RCC(USART_TypeDef* USARTx,uint32_t RCC_APB_Tx,uint32_t RCC_APB_Rx)
{//相对应串口时钟if(USARTx == USART1)RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);else if(USARTx == USART2)RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);else if(USARTx == USART3)RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);else if(USARTx == UART4)RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART4, ENABLE);	//相对串口的TxRx GPIO时钟RCC_APB2PeriphClockCmd(RCC_APB_Tx|RCC_APB_Rx,ENABLE);
}
//GPIO初始化
void uart_GPIO(GPIO_TypeDef* TX_GpioPort,uint16_t TX_GpioPin,GPIO_TypeDef* RX_GpioPort,uint16_t RX_GpioPin,GPIO_TypeDef* Dir_GpioPort,uint16_t Dir_GpioPin)
{GPIO_InitTypeDef GPIO_InitStructure;//USARTx_TXGPIO_InitStructure.GPIO_Pin = TX_GpioPin;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽GPIO_Init(TX_GpioPort, &GPIO_InitStructure);//USARTx_RXGPIO_InitStructure.GPIO_Pin = RX_GpioPin;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//浮空输入GPIO_Init(RX_GpioPort, &GPIO_InitStructure);//directionGPIO_InitStructure.GPIO_Pin = Dir_GpioPin;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出GPIO_Init(Dir_GpioPort, &GPIO_InitStructure);
}
//串口中断配置
void uart_NVIC(uint8_t NVIC_IRQChannel,uint8_t NVIC_IRQChannelPreemptionPriority,uint8_t NVIC_IRQChannelSubPriority)
{NVIC_InitTypeDef NVIC_InitStructure;//设置NVIC优先级分组为2NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);//串口接收中断打开 NVIC_InitStructure.NVIC_IRQChannel = NVIC_IRQChannel;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = NVIC_IRQChannelPreemptionPriority;NVIC_InitStructure.NVIC_IRQChannelSubPriority = NVIC_IRQChannelSubPriority;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);	
}
//
///华丽的分割线
///
//串口初始化
uint8 u8_drv_uart_Init(USART_TypeDef* USARTx)
{if(USARTx == USART1){uart_RCC(USART1,D_UART1_TX_RCC,D_UART1_RX_RCC);	//重映射//RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO,ENABLE);//开启端口B和复用时钟功能//GPIO_PinRemapConfig(GPIO_Remap_USART1,ENABLE);//使能端口重映射RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启端口B和复用时钟功能uart_GPIO(D_UART1_TX_PORT,D_UART1_TX_PIN,D_UART1_RX_PORT,D_UART1_RX_PIN);v_drv_uart_NVIC(USART1_IRQn,D_USART1_IRQn_PreemptionPriority,D_USART1_IRQn_SubPriority);}else if(USARTx == USART2){uart_RCC(USART2,D_UART2_TX_RCC,D_UART2_RX_RCC);	uart_GPIO(D_UART2_TX_PORT,D_UART2_TX_PIN,D_UART2_RX_PORT,D_UART2_RX_PIN);v_drv_uart_NVIC(USART2_IRQn,D_USART2_IRQn_PreemptionPriority,D_USART2_IRQn_SubPriority);}else if(USARTx == USART3){uart_RCC(USART3,D_UART3_TX_RCC,D_UART3_RX_RCC);	uart_GPIO(D_UART3_TX_PORT,D_UART3_TX_PIN,D_UART3_RX_PORT,D_UART3_RX_PIN);v_drv_uart_NVIC(USART3_IRQn,D_USART3_IRQn_PreemptionPriority,D_USART3_IRQn_SubPriority);}else if(USARTx == UART4){uart_RCC(UART4,D_UART4_TX_RCC,D_UART4_RX_RCC);	uart_GPIO(D_UART4_TX_PORT,D_UART4_TX_PIN,D_UART4_RX_PORT,D_UART4_RX_PIN);uart_NVIC(UART4_IRQn,D_USART4_IRQn_PreemptionPriority,D_USART4_IRQn_SubPriority);}else{}
}
//串口发送
void uart_Send(USART_TypeDef* USARTx,char *pc_data,uint8_t u8_len)
{uint8_t i = 0;GPIO_SetBits(Dir_GpioPort, Dir_GpioPin);//收发器发送模式for(i=0;i<u8_len;i++){USART_SendData(USARTx, *pc_data); while(USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET){} pc_data++;}GPIO_ResetBits(Dir_GpioPort, Dir_GpioPin);//收发器接收模式
}

下面是利用485总线发送“Hello world”以及接收中断函数,中断函数只提供了UART1,其余串口逻辑一致。代码如下:

//485通信初始化接口
uint8_t RS485_InitSerial(USART_TypeDef* USARTx,tsUart *psUart)
{u8_drv_uart_Init(USARTx);v_drv_uart_Configuration(USARTx, psUart->u32Uartbaud, psUart->u8WordLength, psUart->u8StopBits, psUart->u8Parity);uart_Send(USARTx,"Hello world",11);return 1;
}
//串口1的接收中断
void USART1_IRQHandler(void)
{unsigned char Temp=0;if(USART_GetFlagStatus(USART1, USART_FLAG_ORE) != RESET)  {   //OverRun Error interruptUSART_ReceiveData(USART1); }   if(USART_GetFlagStatus(USART1, USART_FLAG_NE) != RESET) {//Noise Error interrupt }   if(USART_GetFlagStatus(USART1, USART_FLAG_FE) != RESET) {//Framing Error interrupt }   if(USART_GetFlagStatus(USART1, USART_FLAG_PE) != RESET) {//Parity Error interrupt }///if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET){Temp=USART_ReceiveData(USART1);//添加处理字符的代码USART_ClearITPendingBit(USART1,USART_IT_RXNE);}
}

CAN通信

CAN控制器的初始化同样包括了中断,IO口初始化,以及发送帧的实现,下面是这部分代码,以提供了中文注释帮助大家理解:

//延时函数
void _NOP_(int x)
{int j=0;for(j=0;j<x;j++){;	}
}
//中断初始化函数
static void CAN_NVIC_Configuration(uint32 canx)
{NVIC_InitTypeDef NVIC_InitStructure;/* Configure the NVIC Preemption Priority Bits */  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);#ifdef  VECT_TAB_RAM  /* Set the Vector Table base location at 0x20000000 */ NVIC_SetVectorTable(NVIC_VectTab_RAM, 0x0); #else  /* VECT_TAB_FLASH  *//* Set the Vector Table base location at 0x08000000 */ NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x0);   #endifNVIC_InitStructure.NVIC_IRQChannel = can[canx].rx0_irq;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;//0;//1;if (canx == 0)NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;elseNVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);NVIC_InitStructure.NVIC_IRQChannel = can[canx].sce_irq;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);#ifdef CAN_TX_INT_ENABLE	NVIC_InitStructure.NVIC_IRQChannel = can[canx].tx_irq;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);
#endif
}
//CAN IO口初始化
tatic void CAN_PinInit(uint32 canx)
{GPIO_InitTypeDef  GPIO_InitStructure;/* Configure CAN pin: RX */RCC_APB2PeriphClockCmd(can[canx].gpio_rx.rccx | RCC_APB2Periph_AFIO, ENABLE);GPIO_InitStructure.GPIO_Pin = can[canx].gpio_rx.index;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(can[canx].gpio_rx.channel, &GPIO_InitStructure);/* Configure CAN pin: TX */RCC_APB2PeriphClockCmd(can[canx].gpio_tx.rccx | RCC_APB2Periph_AFIO, ENABLE);GPIO_InitStructure.GPIO_Pin = can[canx].gpio_tx.index;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//GPIO_Mode_AF_PP;//GPIO_Mode_Out_PP;GPIO_Init(can[canx].gpio_tx.channel, &GPIO_InitStructure);/* Configure CAN pin: STB */RCC_APB2PeriphClockCmd(can[canx].gpio_stb.rccx | RCC_APB2Periph_AFIO, ENABLE);GPIO_InitStructure.GPIO_Pin = can[canx].gpio_stb.index;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;GPIO_Init(can[canx].gpio_stb.channel, &GPIO_InitStructure);
}
//CAN控制器配置
static int CAN_Configuration(uint32 canx)
{CAN_InitTypeDef        CAN_InitStructure;CAN_FilterInitTypeDef  CAN_FilterInitStructure;if(canx==0){RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);}else{RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN2, ENABLE);}// CAN register init CAN_DeInit(can[canx].canx);CAN_StructInit(&CAN_InitStructure);// CAN cell init CAN_InitStructure.CAN_TTCM=DISABLE;//禁止时间触发通信模式CAN_InitStructure.CAN_ABOM=ENABLE;CAN_InitStructure.CAN_AWUM=DISABLE;//睡眠模式通过清除sleep位唤醒CAN_InitStructure.CAN_NART=DISABLE;//报文自动重传CAN_InitStructure.CAN_RFLM=DISABLE;//接收溢出时,FIFO未锁定CAN_InitStructure.CAN_TXFP=DISABLE;//发送的优先级由标识符的大小决定CAN_InitStructure.CAN_Mode=CAN_Mode_Normal;//正常模式//设置CAN的通信速率为50kbps,采样点百分之八十CAN_InitStructure.CAN_SJW=CAN_SJW_1tq;CAN_InitStructure.CAN_BS1=CAN_BS1_14tq;CAN_InitStructure.CAN_BS2=CAN_BS2_3tq;CAN_InitStructure.CAN_Prescaler=4;if (CAN_Init(can[canx].canx, &CAN_InitStructure) == 0)return -1;// CAN filter init if (canx == 0)CAN_FilterInitStructure.CAN_FilterNumber=0;elseCAN_FilterInitStructure.CAN_FilterNumber=14;CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdMask;CAN_FilterInitStructure.CAN_FilterScale=CAN_FilterScale_32bit;//CAN_FilterScale_16bit; //32bitCAN_FilterInitStructure.CAN_FilterIdHigh = 0x0000;CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000;CAN_FilterInitStructure.CAN_FilterMaskIdHigh=0x0000;CAN_FilterInitStructure.CAN_FilterMaskIdLow=0x0000;CAN_FilterInitStructure.CAN_FilterFIFOAssignment=CAN_FIFO0;CAN_FilterInitStructure.CAN_FilterActivation=ENABLE;   //时能过滤器CAN_FilterInit(&CAN_FilterInitStructure);CAN_ITConfig(can[canx].canx, CAN_IT_FMP0, ENABLE);CAN_ITConfig(can[canx].canx, CAN_IT_ERR, ENABLE);
#ifdef CAN_TX_INT_ENABLE		CAN_ITConfig(can[canx].canx, CAN_IT_TME, ENABLE);
#endifcan[canx].state |= INIT_COMPLETE;return 0;
} 
//CAN数据帧实现
static unsigned char _CAN_SendData(uint32 canx, uint32 StdId, uint8 DLC, uint8 * Data)
{uint16 i;uint16 count = 0;CanTxMsg TxMessage;unsigned char TransmitMailbox;if((DLC-0)==0){return 1;}TxMessage.StdId=StdId; 	//标准标识符TxMessage.ExtId=StdId; TxMessage.RTR=CAN_RTR_DATA;TxMessage.IDE=CAN_ID_EXT/*CAN_ID_STD*/;TxMessage.DLC=DLC; 		//数据长度if(DLC>8){TxMessage.Data[0]=0xFF;}else{for(i=0;i<DLC;i++){TxMessage.Data[i]=Data[i];}}
#ifdef CAN_TX_INT_ENABLE
waiting:if (canx == 0) {if (sem_value_can1 > 0) {sem_value_can1--;} else {_NOP_(1);goto waiting;}} 
#ifdef STM32F10X_CL		else {if (sem_value_can2 > 0) {sem_value_can2--;} else {_NOP_(1);goto waiting;}}
#endifTransmitMailbox=CAN_Transmit(can[canx].canx,&TxMessage);
#elsecount = 0;
retry:		TransmitMailbox=CAN_Transmit(can[canx].canx,&TxMessage);if (TransmitMailbox == CAN_TxStatus_NoMailBox) {count++;if (count > MAXTRYCOUNT){	//can_init(canx);goto out;}goto retry;}i = 0xFFF;do {_NOP_(1);} while((CAN_TransmitStatus(can[canx].canx, TransmitMailbox) != CANTXOK) && (--i));count = 0;if (i <= 0x01){count++;if (count > MAXTRYCOUNT){	//can_init(canx);goto out;}goto retry;}//return 0;elsereturn 1;
out:return 0;
#endif
}

下面的代码按定义参数正确配置了CAN控制器,并封装了供上层调用的发送接口,我还附上了CAN接收中断代码:

static uint8 u8CanSndId[2];
static struct can_info can[CAN_NUM] = {{RCC_APB1Periph_CAN1,19, 20, 21/*CAN1_TX_IRQn, CAN1_RX0_IRQn, CAN1_RX1_IRQn*/,22/*CAN1_SCE_IRQn*/, {RCC_APB2Periph_GPIOA, GPIOA, GPIO_Pin_11},{RCC_APB2Periph_GPIOA, GPIOA, GPIO_Pin_12}, {RCC_APB2Periph_GPIOA, GPIOA, GPIO_Pin_8},CAN1,},{RCC_APB1Periph_CAN2,63, 64, 65/*CAN2_TX_IRQn, CAN2_RX0_IRQn, CAN2_RX1_IRQn*/,66/*CAN2_SCE_IRQn*/,{RCC_APB2Periph_GPIOB, GPIOB, GPIO_Pin_12},{RCC_APB2Periph_GPIOB, GPIOB, GPIO_Pin_13},{RCC_APB2Periph_GPIOB, GPIOB, GPIO_Pin_11},CAN2,},};//can通信参数//CAN初始化
void can_init(uint32 canx)
{	canx -= 1;CAN_PinInit(canx);									//Configurate the GPIOCAN_NVIC_Configuration(canx);						//Configurate the NVICCAN_Configuration(canx);
}
//CAN发送数据
unsigned char CAN_SendData(uint32 canx, uint16 addr, uint16 Length, uint8 *Data)
{unsigned int SendNumber=0;unsigned int i=0;unsigned int res=0;uint32 ExtId;SendNumber=Length/8;canx -= 1;//---------------------------u8CanSndId[canx]++;if(u8CanSndId[canx] >= 255){u8CanSndId[canx] = 1;}//---------------------------ExtId =(0x10000000 | addr) + (u8CanSndId[canx]<<16);res=_CAN_SendData(canx, ExtId, 8, Data);if(res!=1){return 0;}for(i=1; i<SendNumber; i++){//---------------------------u8CanSndId[canx]++;if(u8CanSndId[canx] >= 255){u8CanSndId[canx] = 1;}//---------------------------//ExtId = addr;ExtId = addr + (u8CanSndId[canx]<<16);res=_CAN_SendData(canx, ExtId,8,Data+8*i);
#ifndef CAN_TX_INT_ENABLE	if(res!=1){return 8*i;}
#endif}//---------------------------u8CanSndId[canx]++;if(u8CanSndId[canx] >= 255){u8CanSndId[canx] = 1;}//---------------------------//ExtId = addr;ExtId = addr + (u8CanSndId[canx]<<16);res=_CAN_SendData(canx, ExtId, Length-(SendNumber*8), Data+8*i);
#ifndef CAN_TX_INT_ENABLE	if(res!=1){return 8*i;}
#endifreturn Length;
}
//CAN1接收中断
void CAN1_RX0_IRQHandler(void)
{uint8 i=0;uint8 u8QueueIndex = 0;uint16 u16Address = 0;uint32 u32CanRevId = 0;CanRxMsg RxMessage;CAN_Receive(CAN1,CAN_FIFO0, &RxMessage);s_app_can_paraD1.u8Mode = 1;s_app_can_paraD1.u32WaitReceiveFinishCount = sDeviceConfig.sCan1.u32WaitReceiveFinishCount;	//-------------------------------------------------------------u32CanRevId = RxMessage.ExtId;//-------------------------------------------------------------u8QueueIndex = u8_app_can_FindQueueIndexD1(u32CanRevId);if(u8QueueIndex == 0){return;}	else if(u8QueueIndex>=CANDQUEUENUM-1){return;}else if(u8QueueIndex == 0xff){	return;}//-------------------------------------------------------------for(i=0;i<RxMessage.DLC;i++){ //CAN接收处理函数(RxMessage.Data[i],u8QueueIndex);}s_app_can_paraD1.u8Mode = 0;
}

CAN参数补充说明

  • CAN采样点:CAN发送的每一位数据(0或者1)维持的时间由由几段时间构成:同步段(SS),物理延时段(TSEG1),误差补偿段(TSEG2),实时同步补偿段(SJW)。采样点是接收机采样确定电平高低的位置,在TSEG1段之后,大部分情况为百分之80%,车厂会提相应要求。采样点位置如下图:
  • CAN速率与采样点配置:如上代码所示CAN速率是由几个时间段参数计算而成,ST提供了工具帮助我们生成针对不同速率与采样点位置的参数,软件截图如下:
  •  CAN总线负载率:总线负载率=总线每秒上传输的实际bit的总时间/1s *100%。原理非常简单,波特率的定义就是每秒CAN总线上可以传输多少CAN数据bit,总线负载率自然就是总线实际传输的bit数量比上总线可以承载的最大bit数了。CAN FD由于支持速率可变,总线占用时间的计算就稍微麻烦一些,需要分开计算:(\\frac{bits_{datarate}*T_{data}+bits_{normalrate}*T_{normal}}{T})*100%

十六宿舍 原创作品,转载必须标注原文链接。

©2023 Yang Li. All rights reserved.

欢迎关注 『十六宿舍』,大家喜欢的话,给个👍,更多关于嵌入式相关技术的内容持续更新中。