1、环境
开发系列:STM32MP157
开发软件:STM32CubeIDE 1.4.0
例程目的:在M4端实现CAN通讯
2、目的
近日,有客户需要在STM32MP157中的M4端实现CAN通讯,我也是初次在M4端编写CAN通讯代码,上网研究了其他人写的STM32系列单片机案例才把这个例程Demo写好。
3、程序配置
1、CAN引脚选用:
FDCAN1_RX ---->PI9
FDCAN1_TX ----->PH13
2、配置FDCAN1的相关参数
Mode模式选择Classic
如果刚刚第一步配置好的话,GPIO Setting上会出现两个引脚列表。
如果需要配置CAN中断接收的话把这个钩上√
这里非常关键一步就是Parameter Setting
重点!!!!!!!:Norminal xxxx这四个参数的设置直接决定了CAN波特率是多少!!!
CAN波特率的计算方式 :CAN波特率 = 系统时钟/Norminal prescaler/ (Nominal Sync Jump Width+Nominal Time Seg1+Nominal Time Seg1)
数据段也跟仲裁段一样的配置就好了。
Tx Fifo Queue Elmts Nbr为10,该参数设置用于Tx FIFO/Queue的Tx Buffers个数。范围0-32
上一步说到波特率的计算,其中系统时钟是在clock configuration配置,这里值得注意的是我使用的是HSE(外部高速时钟)为24MHZ。
所以波特率为24MHZ/8/(1+3+2) = 500KHZ
我这配置FDCAN模块的时候遇到了HSE被disable了,也就是说无法使用,这样我们需要使用一下设置,如下图,把disable改成Crustal/Ceramic Resonator
完成以上步骤后,可以保存一下.ioc文件,会自动生成初始化代码
4、代码编写
fdcan初始化代码,这些都不需要我们自行编写,只需在ioc文件上选择上保存即可。
fdcan.c
/* FDCAN1 init function */
void MX_FDCAN1_Init(void)
{
hfdcan1.Instance = FDCAN1;
hfdcan1.Init.FrameFormat = FDCAN_FRAME_CLASSIC;
hfdcan1.Init.Mode = FDCAN_MODE_NORMAL;
hfdcan1.Init.AutoRetransmission = DISABLE;
hfdcan1.Init.TransmitPause = DISABLE;
hfdcan1.Init.ProtocolException = DISABLE;
hfdcan1.Init.NominalPrescaler = 8;
hfdcan1.Init.NominalSyncJumpWidth = 1;
hfdcan1.Init.NominalTimeSeg1 = 3;
hfdcan1.Init.NominalTimeSeg2 = 2;
hfdcan1.Init.DataPrescaler = 8;
hfdcan1.Init.DataSyncJumpWidth = 1;
hfdcan1.Init.DataTimeSeg1 = 3;
hfdcan1.Init.DataTimeSeg2 = 2;
hfdcan1.Init.MessageRAMOffset = 0;
hfdcan1.Init.StdFiltersNbr = 0;
hfdcan1.Init.ExtFiltersNbr = 0;
hfdcan1.Init.RxFifo0ElmtsNbr = 10;
hfdcan1.Init.RxFifo0ElmtSize = FDCAN_DATA_BYTES_8;
hfdcan1.Init.RxFifo1ElmtsNbr = 10;
hfdcan1.Init.RxFifo1ElmtSize = FDCAN_DATA_BYTES_8;
hfdcan1.Init.RxBuffersNbr = 10;
hfdcan1.Init.RxBufferSize = FDCAN_DATA_BYTES_8;
hfdcan1.Init.TxEventsNbr = 10;
hfdcan1.Init.TxBuffersNbr = 10;
hfdcan1.Init.TxFifoQueueElmtsNbr = 10;
hfdcan1.Init.TxFifoQueueMode = FDCAN_TX_FIFO_OPERATION;
hfdcan1.Init.TxElmtSize = FDCAN_DATA_BYTES_8;
if (HAL_FDCAN_Init(&hfdcan1) != HAL_OK)
{
Error_Handler();
}
}
void HAL_FDCAN_MspInit(FDCAN_HandleTypeDef* fdcanHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
if(fdcanHandle->Instance==FDCAN1)
{
/* USER CODE BEGIN FDCAN1_MspInit 0 */
/* USER CODE END FDCAN1_MspInit 0 */
if(IS_ENGINEERING_BOOT_MODE())
{
/** Initializes the peripherals clock
*/
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_FDCAN;
PeriphClkInit.FdcanClockSelection = RCC_FDCANCLKSOURCE_HSE;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
{
Error_Handler();
}
}
/* FDCAN1 clock enable */
__HAL_RCC_FDCAN_CLK_ENABLE();
__HAL_RCC_GPIOH_CLK_ENABLE();
__HAL_RCC_GPIOI_CLK_ENABLE();
/**FDCAN1 GPIO Configuration
PH13 ------> FDCAN1_TX
PI9 ------> FDCAN1_RX
*/
GPIO_InitStruct.Pin = GPIO_PIN_13;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF9_FDCAN1;
HAL_GPIO_Init(GPIOH, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_AF;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Alternate = GPIO_AF9_FDCAN1;
HAL_GPIO_Init(GPIOI, &GPIO_InitStruct);
/* FDCAN1 interrupt Init */
HAL_NVIC_SetPriority(FDCAN1_IT0_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(FDCAN1_IT0_IRQn);
/* USER CODE BEGIN FDCAN1_MspInit 1 */
/* USER CODE END FDCAN1_MspInit 1 */
}
}
接下来就是要我们手动编写的函数接口
can接受过滤器配置
更多的配置可以查看一下该stm32mp1xx_hal_fdcan.h文件
void CANconfigure()
{
FDCAN_FilterTypeDef sFilterConfig;
sFilterConfig.IdType = FDCAN_STANDARD_ID;//标准帧 FDCAN_EXTENDED_ID
sFilterConfig.FilterIndex = 0;
sFilterConfig.FilterType = FDCAN_FILTER_DUAL;//双过滤。FDCAN_FILTER_RANGE
sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0;//过滤存储在rx fifo0
sFilterConfig.FilterID1 = 0;//范围id
sFilterConfig.FilterID2 = 0;//范围id
//表示只能接收FilterID1 << id << FilterID2
//如果设置为0的话,表示不做任何的过滤
if (HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig) != HAL_OK)//滤波器初始化
{
Error_Handler();
}
sFilterConfig.IdType = FDCAN_STANDARD_ID;
sFilterConfig.FilterIndex = 0;
sFilterConfig.FilterType = FDCAN_FILTER_DUAL;
sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO1;
sFilterConfig.FilterID1 = 0;
sFilterConfig.FilterID2 = 0;
if (HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig) != HAL_OK)//滤波器初始化
{
Error_Handler();
}
if (HAL_FDCAN_Start(&hfdcan1) != HAL_OK)//开启FDCAN
{
Error_Handler();
}
if(HAL_FDCAN_ActivateNotification(&hfdcan1, FDCAN_IT_RX_FIFO0_NEW_MESSAGE, FDCAN_IT_TX_COMPLETE) != HAL_OK)
{
Error_Handler();
}
}
can发送函数:
FDCAN_HandleTypeDef hfdcan1;//全局变量
FDCAN_TxHeaderTypeDef TxHeader;//全局变量
CAN_Transmit(uint16_t ID, uint8_t *pdata, uint8_t length)
{
TxHeader.Identifier = ID;//帧ID
TxHeader.IdType = FDCAN_STANDARD_ID;//标准ID
TxHeader.TxFrameType = FDCAN_DATA_FRAME;//数据帧
TxHeader.DataLength = FDCAN_DLC_BYTES_8;//数据长度
TxHeader.ErrorStateIndicator = FDCAN_ESI_PASSIVE;//错误指示
TxHeader.BitRateSwitch = FDCAN_BRS_OFF;//关闭可变波特率
TxHeader.FDFormat = FDCAN_CLASSIC_CAN;//传统classsic can
TxHeader.TxEventFifoControl = FDCAN_NO_TX_EVENTS;//发送时间FIFO控制,不存储
TxHeader.MessageMarker = 0; //识别消息状态,范围0到0xFF
if (HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan1, &TxHeader, pdata) != HAL_OK)
{
Error_Handler();
}
}
接收中断回调函数
void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs)
{
printf("fdcan recvice.....\r\n");
/* Get RX message */
if (HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_FIFO0, &RxHeader, (uint8_t*)&can_rx_data.RX_Data[0]) != HAL_OK)
{
Error_Handler();
}
can_rx_data.ID = RxHeader.Identifier;
can_rx_data.length = RxHeader.DataLength>>16;
for (int i=0;i<can_rx_data.length;i++)
{
printf("fdcan recvice%d:%d.....\r\n",i,can_rx_data.RX_Data[i]);
}
}
编写好这几个函数接口之后,编译看是否有错误再稍微作修改。
5、问题及方法
1、判断你的函数接口和配置是否正确,我发现可以是用外部回环模式进行测试,这样可以摆脱外部因素导致了通讯失败。
将FDCAN_FRAME_CLASSIC改成FDCAN_MODE_EXTERNAL_LOOPBACK
void MX_FDCAN1_Init(void)
{
hfdcan1.Instance = FDCAN1;
hfdcan1.Init.FrameFormat = FDCAN_MODE_EXTERNAL_LOOPBACK;
hfdcan1.Init.Mode = FDCAN_MODE_NORMAL;
//省略部分代码
}
2、这个是最最最最奇葩的,搞了我几天时间,就是如果在M4上使用CAN接口有可能需要修改tf-a文件和dtb设备树。
从源码上找到属于自己板子的fdts/stm32mp157xxxx.dtsi文件,修改如下:
因为我使用的是HSE时钟,而原来的是PLL4R时钟,导致时钟不工作,CAN功能异常。
内核设备树DTB修改:
把m_can1屏蔽disabled,分配给m4_m_can1
总结
希望大家遇到像我这类似的问题,能少走点弯路。好好总结。