0前言
本文主要目的是,总结去年设计stm32-CAN板子过程中遇到的问题,分为keil嵌入式软件和嘉立创EDA设计两个部分。
1 STM32F1 CAN功能
keil expected a “}“
问题在于,PCB使用芯片为stm32f103c8t6,下载程序时选择device默认此型号。但是,野火例程采用stm32f103zg,因此与头文件的冲突。
stm32f4和stm32f1的时钟树不同
stm32f4和stm32f1的时钟树不同,不能共用。因此,在初始化外设时,需要明确两者的不同。
stm32f10x_MD型号含义
在keil的魔法棒中,MD指代不同的器件密度:比如stm32f103c8t6是MD,而stm32f407ZET6是HD。理解两者不同,帮助确认preprocessor symbol命名。
两个stm32例程基本一样,一个不能执行
存在两个stm32例程:
- 自己设计的stm32板子能够运行点灯程序,但是没有can程序;
- 有can的例程,但是下载程度后,板子一点反应没有
考虑对比来定位问题:
- 对比两个程序差异,逐步比较
- 比较程序option配置的差别,观察到正确运行的例程中,flash download中的reset and run没有选中,无法执行的例程是选中的。尝试关掉了选中,原先无法执行的错误例程又可以执行了。但是,原先能够连接的sw device出现st link connetion error。此这个问题可以直接断开st-link解决。
- 板子是多少的频率,option就写多少的频率.
LED无法闪烁
延时太快,比如选择1ms,这个时候人眼看不出来led的亮灭变化。
移植CAN例程,can收发器没有反应
从如下方面定位问题:
- 注意波特率的配置、板子晶振、时钟树结构、can初始化配置
- CAN收发器没有电压供应。设计的板子包含三个输入电源,只有一个5v输入。忘记接入5V电源输入
理想测试流程是:
- 测试CAN收发器、stm32的电气特性,确保正确的电压供应
- 测试LED闪烁功能,确保main程序正常运行。
- 测试CAN回环功能,确保软件层面CAN控制器没有问题。此外涉及到CAN外设的正常初始化
- 测试CAN发送功能,此时需要使用CAN-BUS逻辑分析仪。注意CAN协议存在ACK信号,因为没有其它CAN设备回应CAN帧时,会产生两种可能现象:1,无法接收CAN信号;2,自动重发功能会导致CAN总线一直出现CAN帧。
整个过程中,遇到两个奇怪现象:
- 测试中间,板子的led全部无法使用,猜想板子被烧掉。后来观察到,梦源的排线接口地线接到板子的3V3接口上,导致板子没有压差。
- 有接收的逻辑信号,发送的信号却没有。不明原因
stm32f103cbt8的can发送pending异常
自设的STM32板子,LED正常点亮,但是,在测试CAN的回环模式时,发送状态为CAN_TxStatus_Pending ,而非正常的CAN_TxStatus_Ok 。
从以下几个方面定位问题:
- 测试CAN收发器的5V电压,正常
- 检查CAN初始化配置,can接收引脚的设置IPU。猜想此外问题
- 对比CAN stm32f1和stm32f4的初始化程序,观察到时钟和引脚映射等方面的差别
- 采用cubeMX软件,查看CAN初始化程序特点
stm32f1
typedef enum
{ GPIO_Mode_AIN = 0x0,
GPIO_Mode_IN_FLOATING = 0x04,
GPIO_Mode_IPD = 0x28,
GPIO_Mode_IPU = 0x48,
GPIO_Mode_Out_OD = 0x14,
GPIO_Mode_Out_PP = 0x10,
GPIO_Mode_AF_OD = 0x1C,
GPIO_Mode_AF_PP = 0x18
}GPIOMode_TypeDef;
此贴指出,GPIO_Remap1_CAN1、GPIO_Remap2_CAN1、alternate function 、重映射和复用的关系。
此贴同样指出了这个问题。
重新配置CAN引脚和映射关系,发送正常。
此问题提醒我,检查代码的过程中,盲点是非常难以发现的。整个过程中,也观察过GPIO复用逻辑,没有怀疑过选择PA11和PA12作为CAN外设的GPIO复用接口可行性。套用野火等开源资源时,需要耐心对比参考例程与自己设计板子之间的差异。同时,设计PCB时,有意识在板子添加丝印,不用每次打开软件查看。
keil-flash timeout:stm32f051
部分帖子提醒,可能是芯片锁了,需要使用st-link utility。但是显示无法连接上,一起报can’t connect错误。这篇文章提醒,可能是重置模式问题,需要修改模式为software system reset。同时,此帖提到,这种情况适用于st-link能够连接的情况,符合我的情况。奇怪之处在于,device命名不是stm32f10x系列,而是stm32f05x/f030x8。
结果是,程序可以正常烧录,但是led灯没有点亮。尝试烧录到另一个板子中,能够正常工作,说明这个板子本身的硬件问题。
st-link utility能够下载程序,但是这个板子无法用梦源的逻辑分析仪分析出PB12的GPIO口的电平变化。同时keil仍然无法下载程序,并且产生新的debug session错误,如下图。结果表明是当时正在连接st-link utility所致。其次,我断开SWDIO的电源和地后,想要确认这个电压是没问题,用之间锂电池时显示正常3.3v电压。但是出现了intenal command error错误,重新接回线后,恢复正常。
这篇文章提醒,电源和晶振可能存在问题。因为万用表测量过节点电压为3.3v,晶振可能是潜在的问题。考虑到,此次修改了重置电路,可能造成潜在影响。图一为正常运行板子的重置走线。图二是改动后的重置电路走线。
此贴提醒,可能是晶振或者焊锡膏问题。为了判断晶是否正常工作,尝试使用stm32内部晶振,或者使用示波器检查晶振是否起振。
移植别人的例程,尝试开启内部时钟,程序没有反应。此贴提醒,在此基础上添加了flash相关的代码,程序可以正确运行了。
void SystemInit(void)
{
RCC_DeInit();//将外设 RCC寄存器重设为缺省值
RCC_HSICmd(ENABLE);//使能HSI
while(RCC_GetFlagStatus(RCC_FLAG_HSIRDY) == RESET);//等待HSI使能成功
//加上这两句才能到64M
// FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable); //FLASH半周期访问
// FLASH_SetLatency(FLASH_Latency_2); //设置代码延时值
/* Enable Prefetch Buffer */
FLASH->ACR |= FLASH_ACR_PRFTBE;
/* Flash 2 wait state */
FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2;
RCC_HCLKConfig(RCC_SYSCLK_Div1);
RCC_PCLK1Config(RCC_HCLK_Div2);
RCC_PCLK2Config(RCC_HCLK_Div1);
//设置 PLL 时钟源及倍频系数
RCC_PLLConfig(RCC_PLLSource_HSI_Div2, RCC_PLLMul_10);//使能或者失能 PLL,这个参数可以取:ENABLE或者DISABLE
//此处不能太高,我刚开始设置50多,系统都无法运行。
RCC_PLLCmd(ENABLE);//如果PLL被用于系统时钟,那么它不能被失能
//等待指定的 RCC 标志位设置成功 等待PLL初始化成功
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
//设置系统时钟(SYSCLK) 设置PLL为系统时钟源
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);//选择想要的系统时钟
//等待PLL成功用作于系统时钟的时钟源
// 0x00:HSI 作为系统时钟
// 0x04:HSE作为系统时钟
// 0x08:PLL作为系统时钟
while(RCC_GetSYSCLKSource() != 0x08);//需与被选择的系统时钟对应起来,RCC_SYSCLKSource_PLL
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH. */
}
此贴提醒,修改时钟涉及到flash外设,所以需要添加stm32f10x_flash.c文件。在切换了内部时钟程序后,keil仍然出现flash timeout问题。通过st-link utility下载程序后,板子没有反应,猜想晶振没有问题。但是,万用表分别测量晶振两端的电压,显示为0.025V,非常小。但是实际理想晶振值为0.5VDD,说明晶振没有正常起振,晶振是没有正常工作的。
晶振起振条件如下几种情况:
- 此贴提醒,晶振起振的影响因素和正常起振下的特点。
- 此贴提醒,虽然stm32程序没有使用外部晶振,但是晶振无法起振仍然会影响实际芯片的初始化流程等。
- 此贴提醒,焊锡膏的化学作用会影响晶振起振,所以需要清理板子。
- 我的板子能够正常连接st-link,但是无法通过keil下载程序,只能通过st utility下载程序。但是下载的程序无法运行。这个情况本身很是奇怪。
- 考虑到板子下载程序过程中,右下角显示为erase,然后就会跳出flash timeout。如果问题一起无法解决,可以考虑了解flash下载流程,并反推板子问题可能出现在哪儿。keil下载程序具体过程4可以提供相关流程说明。
由于晶振仍然无法起振,于是直接卸下晶振,但是板子仍然能够正常下载程序,这意味着,绿板的flash timeout问题不是晶振造成。
最终意识到,自己搞错了用的stm32器件型号。检查淘宝网页,购买的型号是stm32f051。st-link utility能够直接下载程序,是因为这个软件能够直接识别器件类型,但是keil上需要在option中配置,所以无法直接下载。这也可以解释为什么无法进行erase操作,因为keil的下载已经搞错device了。事实上,前期就注意到utility识别出的device family与实际不符,但是没有深究。
此外器件型号和上次的CAN引脚映射都是细节问题,需要在搭建工程时细致。整个定位问题的过程中,到是学习了许多方面的技巧。
担心电路设计存在不同:考虑到使用的芯片是stm32f051,查看了其引脚图后,除了PF7和PF6不是电源引脚外,其它基本引脚没有什么不同。普通引脚接上电容也没有什么问题。因此,可以直接在这个芯片上运行stm32f051相关的程序就行,电路没有明显问题。
直接用cubeMx生成了stm32f051的keil工程,直接下载成功。cubeMx的图形化配置界面确实好用,内部晶振的操作简单,不需要修改stm32的系统文件。
补充:CAN-bus总线现场布线与接口设计及电缆与连接器选择
CAN程序又无法运行!
原来CAN程序能够正常运行,但是经过一天程序又无法工作,LED能够正常闪烁。程序原本没有问题,突然又有问题了
CAN收发器需要5V供电,在下载程序过程中板子只有3.3v供电。所以在下载程序时,can收发器没有电源供应来工作。当接上5Vmicro-USB口后,电路能够正确收发。
自己设计的板子都有许多特点:比如这个板子为了扩展CAN的螺钉端口,于是采用了2*3的排针设计。但是由于焊接过程中只焊接了一排,另一个排没有焊接,导致CAN的通信链路出现断路。所以任何板子的任何器件都可能出现短路和断路问题,需要了解自己板子设计中的缺陷。
后面又遇到一次无法发送的问题。原因是定位问题时,怀疑ECanTool的某个端口出现问题,所以调整了螺钉端口接线为CAN1。这导致即使CAN正常发送,CAN2端口仍然没有任何反应。
这说明,在测试程序设计过程中,任何调整都会引发连锁反应。纯LED的亮灭状态无法帮助判断程序所有问题。
CAN狂发CAN帧
修改野火的CAN通信例程,stm32f407间隔发送CAN帧,但是ECAN却收到好几万的数据帧。
LED1_ON;
CAN_SetMsg(&TxMessage);
CAN_Transmit(CANx, &TxMessage);
can_delay(0x0fffffff0);//等待发送完毕,可使用CAN_TransmitStatus查看状态
LED1_OFF;
can_delay(0x0fffffff0);
//
测试代码如上,delay函数时间较长。此外,逻辑分析仪没有检测led1的电平变化。 下午使用野火例程发送CAN帧,ECAN无法接收CAN帧。这一会儿疯狂发,一会儿没有动静,让人困惑。原因是,霸天虎板子的电源接在旁边同学的插座上,但是刚刚发现被拨了。
这种细节问题,往往是在检查流程时,突然意识到的。主要利益于前期记录了类似问题,所以会检查所有电路环节的电压是否正常。在设计PCB电路时,也有必要针对不同的电压区域,提供相应的电源LED。
霸天虎板子使用PB12和PB13做为CAN引脚,板子上的螺钉端子直接接上ECAN。另一方面,PB12和PB13的排针连接另一个板子的排针,接入另一个板子的CAN收发器。其中,转换板子的杜邦线不够稳固,需要用手指顶住。通过霸天虎板子按键触发 CAN帧发送,现象如下:
- 螺钉端子都连接的情况下,ECAN上位机能够同时正常接收CAN帧;
- 断开螺钉端子的情况下,ECAN会成千上万得接收帧帧。换言之,收发器自己发送了许多的帧。
尝试修改了霸天虎的代码,按键按一次,CAN帧的数据值就增加1:只要断开螺钉端子的连接,排针端就会一直重复发送刚才发送的;接上螺钉端子后,排针端CAN发送又恢复正常
上图可以看到,当CAN1表示的螺钉端子断开连接时,数据为1A。而按下按键后,CAN2就会一直接收1A数据的帧。这意味碰上,软件端没有问题,这个1A 数据的帧是底层自动重发的结果。联想到CAN外设初始化过程中,需要配置自动重发功能,如下图
/*CAN单元初始化*/
CAN_InitStructure.CAN_TTCM=DISABLE; //MCR-TTCM 关闭时间触发通信模式使能
CAN_InitStructure.CAN_ABOM=ENABLE; //MCR-ABOM 自动离线管理
CAN_InitStructure.CAN_AWUM=ENABLE; //MCR-AWUM 使用自动唤醒模式
这里 CAN_InitStructure.CAN_NART=ENABLE; //MCR-NART 禁止报文自动重传 DISABLE-自动重传
CAN_InitStructure.CAN_RFLM=DISABLE; //MCR-RFLM 接收FIFO 锁定模式 DISABLE-溢出时新报文会覆盖原有报文
CAN_InitStructure.CAN_TXFP=DISABLE; //MCR-TXFP 发送FIFO优先级 DISABLE-优先级取决于报文标示符
CAN_InitStructure.CAN_Mode = CAN_Mode_Normal; //回环工作模式
CAN_InitStructure.CAN_SJW=CAN_SJW_2tq; //BTR-SJW 重新同步跳跃宽度 2个时间单元
当关掉报文自动重发后,CAN1和CAN2都能正常接收CAN帧。问题是,为什么CAN会自动重发?。选择判断CAN_TransmitStatus的状态,只有发送成功的情况下,才发送下一帧的数据。但是自动重发的情况依然会发生。此帖提醒,CAN发送失败的原因是ACK域,CAN收发器在此阶段没有检测到显性信号。猜想原因是,CAN1是霸天虎板子的逻辑信号直接通往螺钉端子,所以这个信号比较稳定。此时ECAN的逻辑分析仪也能够发送回来ACK信号。但是CAN2的逻辑信号经过杜邦线,路径比较长,很有可能影响到了ACK信号的接收。之所以接收CAN1时,CAN2也能够接收CAN帧,就是CAN1路径的ACK信号帮助CAN2实现了发送成功。
现在的CAN波特率是1M/bit,选择较小的波特率5K/bit,则ACK信号CAN2路径也许能够收到。结果是,仍然不行,CAN2干脆完全没有反应;重新换回1Mbps的波特率后,CAN2又能够接收CAN帧。猜想是板子的连接性问题,所以暂时搁置。
本来这个板子是用来做CAN总线的扩展板,所以主要目的是把STM32F051芯片拆卸下来,使用STM32F103芯片。不过现在手头没有这个芯片,考虑使用原来的芯片熟悉CAN的收发流程和相关概念,等待淘宝购买的芯片回来再拆卸。此帖提醒,可以用波特率和每帧100bit的CAN帧来预估1s能够发送多少CAN帧。
Keil no target connected
原本下载器很少出现问题,但是准备测试CAN的中断功能时,keil突然出现no target connected的错误。通过常规的断开ST-LINK的micro-usb接口、按紧四根杜邦线后,问题仍然出现。
联想到,最近经常碰到电气连接的问题,比如板子电源供应或者CAN收发器的电压供应等。因此,怀疑这个no target问题定位在板子的电气连接上。考虑到这个st-link是直接拿实验室的,最近几天又频繁在两个板子之间切换下载程序,杜邦线的连接性出现问题的可能性比较高。尝试更换st-link的四根杜邦线,结果显示连接成功。
CAN发送失败与CAN筛选器配置
选择ECANTool向CAN板子发送CAN帧时,ECANTOOL一直显示发送失败错误。本次CAN测试目的是验证CAN的中断功能,只要CAN板子能够接收ECANTOOL的CAN帧,并且能够在中断程序中翻转LED电平就行。问题是,ECANTOOL现在还无法成功发送CAN帧。
此帖提醒我,是否CAN高和CAN低接反。果然如此, 这说明在测试过程中,接线的活儿容易出现这个映射问题。接下来问题是,为什么CAN无法进入中断?
检查初始化代码过程中,意识到曾经使能关掉CAN筛选器,此操作是尝试解决CAN-Pending问题。
static void CAN_Filter_Config(void)
{
CAN_FilterInitTypeDef CAN_FilterInitStructure;
/*CAN筛选器初始化*/
CAN_FilterInitStructure.CAN_FilterNumber=0; //筛选器组0
CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdMask; //工作在掩码模式
CAN_FilterInitStructure.CAN_FilterScale=CAN_FilterScale_32bit; //筛选器位宽为单个32位。
/* 使能筛选器,按照标志的内容进行比对筛选,扩展ID不是如下的就抛弃掉,是的话,会存入FIFO0。 */
CAN_FilterInitStructure.CAN_FilterIdHigh= ((((u32)0x1314<<3)|CAN_ID_EXT|CAN_RTR_DATA)&0xFFFF0000)>>16; //要筛选的ID高位
CAN_FilterInitStructure.CAN_FilterIdLow= (((u32)0x1314<<3)|CAN_ID_EXT|CAN_RTR_DATA)&0xFFFF; //要筛选的ID低位
CAN_FilterInitStructure.CAN_FilterMaskIdHigh= 0xFFFF; //筛选器高16位每位必须匹配
CAN_FilterInitStructure.CAN_FilterMaskIdLow= 0xFFFF; //筛选器低16位每位必须匹配
CAN_FilterInitStructure.CAN_FilterFIFOAssignment=CAN_Filter_FIFO0 ; //筛选器被关联到FIFO0
CAN_FilterInitStructure.CAN_FilterActivation=DISABLE; //使能筛选器
CAN_FilterInit(&CAN_FilterInitStructure);
/*CAN通信中断使能*/
CAN_ITConfig(CANx, CAN_IT_FMP0, ENABLE);
}
从CAN的结构中可能看到,CAN帧需要经过筛选器才能最终被FIFO处理。CAN外设有两个FIFO,筛选器的初始化决定使用那个FIFO。此贴提醒,关闭筛选器会影响CAN发送。
目前来看,程序流程不清楚导致大量问题的发生。无论是PA11、PA12重映射,LED的测试现象,还是没有打开FIFO,这些都不是真正的问题。重要的是,在整个开发过程中细心,防患于末然。
下午用板子进行测试时,又出现CAN发送失败的情况。原因是,板子现在是处于下载口的电供应状态,所以只有一路电源供应。当我将3.7V的锂电池包接收电路板之后,电路开始能够正常接收CAN帧。新的板子收发器工作电压是3V3,所以能够工作。
CAN无法进入中断
联想到,筛选器的初始化影响CAN发送回ACK信号,筛选掩码的配置可能影响CAN接收实际帧内容和中断位置置1。
static void CAN_Filter_Config(void)
{
CAN_FilterInitTypeDef CAN_FilterInitStructure;
/*CAN筛选器初始化*/
CAN_FilterInitStructure.CAN_FilterNumber=0; //筛选器组0
CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdMask; //工作在掩码模式
CAN_FilterInitStructure.CAN_FilterScale=CAN_FilterScale_32bit; //筛选器位宽为单个32位。
/* 使能筛选器,按照标志的内容进行比对筛选,扩展ID不是如下的就抛弃掉,是的话,会存入FIFO0。 */
CAN_FilterInitStructure.CAN_FilterIdHigh= ((((u32)0x1314<<3)|CAN_ID_EXT|CAN_RTR_DATA)&0xFFFF0000)>>16; //要筛选的ID高位
CAN_FilterInitStructure.CAN_FilterIdLow= (((u32)0x1314<<3)|CAN_ID_EXT|CAN_RTR_DATA)&0xFFFF; //要筛选的ID低位
CAN_FilterInitStructure.CAN_FilterMaskIdHigh= 0xFFFF; //筛选器高16位每位必须匹配
CAN_FilterInitStructure.CAN_FilterMaskIdLow= 0xFFFF; //筛选器低16位每位必须匹配
CAN_FilterInitStructure.CAN_FilterFIFOAssignment=CAN_Filter_FIFO0 ; //筛选器被关联到FIFO0
CAN_FilterInitStructure.CAN_FilterActivation=ENABLE; //使能筛选器
CAN_FilterInit(&CAN_FilterInitStructure);
/*CAN通信中断使能*/
CAN_ITConfig(CANx, CAN_IT_FMP0, ENABLE);
}
如上图代码,野火的例程实际添加了0x1314这样的ID。此前,所有的CAN测试都是发送代码,没有使用筛选器的功能。现在从另一个板子接收CAN帧,这段初始化代码的功能就体现出来。
在配置筛选器为32位宽的掩码模式后,掩码全部设置为0x00,示意CAN收发器全部接收。问题是,led仍然没有任何变化,尝试keil的调试模式定位问题。进入keil调试模式打断点,程序如果进入中断就会在中断程序断点处暂停,结果是CAN没有在断点处暂停。
尝试查看FIFO的状态标志位是否正常,再进一步考虑中断为什么没有产生。测试代码如下,在通过ECAN发送CAN帧后,如果CAN的FIFO0正常接收到CAN帧,相关的状态标志位是置1的。此时程序能够进入if里面的条件语句,LED1也能够翻转。通过修改中断逻辑,先接收CAN,程序正常进入中断。
if(CAN_GetFlagStatus(CANx,CAN_FLAG_FMP0)==SET||CAN_GetFlagStatus(CANx,CAN_FLAG_FF0)==SET)
{
LED1_TOGGLE;
can_delay(0x2dc6c0);
}
void USB_LP_CAN1_RX0_IRQHandler(void)
{
/*从邮箱中读出报文*/
CAN_Receive(CANx, CAN_FIFO0, &RxMessage);
if(RxMessage.Data[0]==0x11){//
CAN_FIFORelease(CANx,CAN_FIFO0);
CAN_SetMsg(&TxMessage);
Txmail=CAN_Transmit(CANx,&TxMessage);
while(CAN_TransmitStatus(CANx,Txmail)!=CAN_TxStatus_Ok)
{}
}
}
CubeMx的HAL库CAN总线收发
因为需要频繁更改多个STM32F103工程文件代码,标准库文件配置又过于繁琐。于是选择cubeMX初始化stm32工程,从而减少许多无效的初始化工作,也不用再过分依赖野火的代码。但是,HAL资源并不多,CAN出现配置问题无法解决。ChatGPT能够提供一些HAL初始化的示例,可以帮助解决部分问题。
告诉ChatGPT创建CAN发送函数,说明了CANID和CAN的数据。虽然不知道这些代码是否能够执行,但是STM32很多初始化函数很繁琐的,现在完全可以让chatgpt代劳。后期可以迁移到HAL库,不过前期主要熟悉库函数比较好。