stm32学习
七.CAN
7.STM32 CAN外设
标识符过滤器:
每个过滤器的核心由两个32位寄存器组成:R1[31:0]和R2[31:0]

FSCx:位宽设置,置0为16位,置1为32位
FBMx:模式设置,置0为屏蔽模式,置1为列表模式
FFAx:关联模式,置0为FIFO0,置1为FIFO1
FACTx;激活设置,置0,禁用,置1,启用
匹配模式:
若是列表模式,则按设置的ID匹配一个ID
若是屏蔽模式,若屏蔽(mask)为1,则表示该位需严格匹配,为0则不严格匹配
过滤器配置示例:

左移5位或者21位是为了避开后面的RTR、IDE位之类的
CAN的三种测试模式:

CAN的三种工作模式:

位时间特性:

波特率=APB1时钟/分频系数/1位Tq的数量=36MHz/(BRP[9:0]+1)/(1+(TS1[3:0]+1))+(TS2[2:0]+1)
中断:
CAN外设占用4个专用的中断向量
发送中断:发送邮箱空时产生
FIFO0中断:收到一个报文/FIFO0满/FIFO0溢出时产生
FIFO1中断:收到一个报文/FIFO1满/FIFO1溢出时产生
状态改变错误中断:出错/唤醒/进入睡眠时产生
事件标志和中断产生:

时间触发通信:

错误处理和离线恢复:
TEC和REC根据错误的情况增加或者减少
ABOM:置1,开启离线自动恢复,进入离线状态后,就自动开启恢复过程;置0,关闭离线自动恢复,软件必须先请求进入然后再退出初始化模式,随后恢复过程才被开启

8.单个设备的环回测试
初始化代码:
void MyCAN_Init(void)
{
// 使能GPIOA和CAN1外设时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // GPIOA属于APB2总线
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE); // CAN1属于APB1总线
// 配置CAN_TX引脚(PA12)为复用推挽输出模式
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; // PA12对应CAN_TX
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 输出速度50MHz
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 配置CAN_RX引脚(PA11)为上拉输入模式
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; // PA11对应CAN_RX
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 输入模式速度配置无效
GPIO_Init(GPIOA, &GPIO_InitStructure);
// CAN总线基本配置
CAN_InitTypeDef CAN_InitStructure;
CAN_InitStructure.CAN_Mode = CAN_Mode_LoopBack; // 环回模式(测试模式)
CAN_InitStructure.CAN_Prescaler = 48; // 预分频器:36MHz/48=750KHz
// 时间段配置(同步段+传播段+相位缓冲段):
// 同步段tq=1,BS1=2tq,BS2=3tq → 总周期=1+2+3=6tq → 波特率=750KHz/6=125Kbps
CAN_InitStructure.CAN_BS1 = CAN_BS1_2tq; // 同步后采样点位置
CAN_InitStructure.CAN_BS2 = CAN_BS2_3tq; // 信号重新同步时间
CAN_InitStructure.CAN_SJW = CAN_SJW_2tq; // 重新同步跳跃宽度
// 其他模式配置(默认关闭高级功能)
CAN_InitStructure.CAN_NART = DISABLE; // 禁止自动重传
CAN_InitStructure.CAN_TXFP = DISABLE; // 发送优先级由标识符决定
CAN_Init(CAN1, &CAN_InitStructure); // 应用配置到CAN1
// 过滤器配置(允许所有消息通过)
CAN_FilterInitTypeDef CAN_FilterInitStructure;
CAN_FilterInitStructure.CAN_FilterNumber = 0; // 使用过滤器0
CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0000; // 过滤器ID高位
CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000; // 过滤器ID低位
CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x0000; // 过滤器掩码高位
CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000;// 过滤器掩码低位
CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit; // 32位过滤
CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask; // 掩码模式
CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0; // 分配到FIFO0
CAN_FilterInitStructure.CAN_FilterActivation = ENABLE; // 启用过滤器
CAN_FilterInit(&CAN_FilterInitStructure); // 应用过滤器配置
}
发送接收函数:
// CAN数据发送函数
// 参数:ID - 标准帧ID(0-0x7FF),Length - 数据长度(0-8),Data - 数据指针
void MyCAN_Transmit(uint32_t ID, uint8_t Length, uint8_t *Data)
{
CanTxMsg TxMessage; // 定义发送消息结构体
TxMessage.StdId = ID; // 设置标准帧ID
TxMessage.ExtId = ID; // 扩展帧ID(未使用)
TxMessage.IDE = CAN_Id_Standard; // 使用标准帧格式
TxMessage.RTR = CAN_RTR_Data; // 数据帧(非远程请求)
TxMessage.DLC = Length; // 设置数据长度
for (uint8_t i = 0; i < Length; i ++) // 拷贝数据到发送缓冲区
{
TxMessage.Data[i] = Data[i];
}
uint8_t TransmitMailbox = CAN_Transmit(CAN1, &TxMessage); // 发送请求
// 等待发送完成(带超时保护)
uint32_t Timeout = 0;
while (CAN_TransmitStatus(CAN1, TransmitMailbox) != CAN_TxStatus_Ok)
{
Timeout++;
if (Timeout > 100000) // 超时退出(防止死锁)
{
break;
}
}
}
// 检查接收缓冲区状态
// 返回值:1-有新数据,0-无数据
uint8_t MyCAN_ReceiveFlag(void)
{
// 检查FIFO0中待处理消息数量(>0表示有新数据)
if (CAN_MessagePending(CAN1, CAN_FIFO0) > 0)
{
return 1;
}
return 0;
}
// CAN数据接收函数
// 参数:ID - 接收到的帧ID,Length - 数据长度,Data - 接收数据缓冲区
void MyCAN_Receive(uint32_t *ID, uint8_t *Length, uint8_t *Data)
{
CanRxMsg RxMessage; // 定义接收消息结构体
CAN_Receive(CAN1, CAN_FIFO0, &RxMessage); // 从FIFO0读取数据
// 根据帧类型获取ID(支持标准帧和扩展帧)
if (RxMessage.IDE == CAN_Id_Standard)
{
*ID = RxMessage.StdId;
}
else
{
*ID = RxMessage.ExtId;
}
// 处理数据帧(忽略远程帧)
if (RxMessage.RTR == CAN_RTR_Data)
{
*Length = RxMessage.DLC; // 获取数据长度
for (uint8_t i = 0; i < *Length; i ++)
{
Data[i] = RxMessage.Data[i]; // 拷贝接收数据
}
}
else
{
// 此处可添加远程帧处理逻辑
}
}
9.数据帧-遥控帧
// 自定义 CAN 发送函数,用于将 CAN 消息发送出去
void MyCAN_Transmit(CanTxMsg *TxMessage)
{
// 调用 CAN_Transmit 函数尝试将消息发送到 CAN1 总线,并获取使用的发送邮箱编号
uint8_t TransmitMailbox = CAN_Transmit(CAN1, TxMessage);
// 初始化超时计数器
uint32_t Timeout = 0;
// 循环检查消息是否发送成功
while (CAN_TransmitStatus(CAN1, TransmitMailbox) != CAN_TxStatus_Ok)
{
// 超时计数器递增
Timeout ++;
// 如果超时计数器超过 100000,退出循环
if (Timeout > 100000)
{
break;
}
}
}
// 自定义 CAN 接收函数,用于从 CAN1 总线接收消息
void MyCAN_Receive(CanRxMsg *RxMessage)
{
// 调用 CAN_Receive 函数从 CAN1 的 FIFO0 接收消息
CAN_Receive(CAN1, CAN_FIFO0, RxMessage);
}
主函数中的测试程序:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Key.h"
#include "MyCAN.h"
uint8_t KeyNum;
// 定义一个 CAN 发送消息数组,包含多条不同的 CAN 消息
CanTxMsg TxMsgArray[] = {
/* StdId ExtId IDE RTR DLC Data[8] */
{0x555, 0x00000000, CAN_Id_Standard, CAN_RTR_Data, 4, {0x11, 0x22, 0x33, 0x44}},
{0x000, 0x12345678, CAN_Id_Extended, CAN_RTR_Data, 4, {0xAA, 0xBB, 0xCC, 0xDD}},
{0x666, 0x00000000, CAN_Id_Standard, CAN_RTR_Remote, 0, {0x00, 0x00, 0x00, 0x00}},
{0x000, 0x0789ABCD, CAN_Id_Extended, CAN_RTR_Remote, 0, {0x00, 0x00, 0x00, 0x00}},
};
// 定义发送消息数组的索引变量
uint8_t pTxMsgArray = 0;
// 定义 CAN 接收消息结构体变量
CanRxMsg RxMsg;
int main(void)
{
OLED_Init();
Key_Init();
MyCAN_Init();
OLED_ShowString(1, 1, " Rx :");
OLED_ShowString(2, 1, "RxID:");
OLED_ShowString(3, 1, "Leng:");
OLED_ShowString(4, 1, "Data:");
while (1)
{
KeyNum = Key_GetNum();
if (KeyNum == 1)
{
// 调用自定义 CAN 发送函数,发送当前索引对应的 CAN 消息
MyCAN_Transmit(&TxMsgArray[pTxMsgArray]);
// 发送消息数组索引递增
pTxMsgArray ++;
// 如果索引超过数组长度,将索引重置为 0
if (pTxMsgArray >= sizeof(TxMsgArray) / sizeof(CanTxMsg))
{
pTxMsgArray = 0;
}
}
// 如果有 CAN 接收标志
if (MyCAN_ReceiveFlag())
{
// 调用自定义 CAN 接收函数,接收 CAN 消息
MyCAN_Receive(&RxMsg);
// 如果接收到的消息是标准帧
if (RxMsg.IDE == CAN_Id_Standard)
{
// 在 OLED 第一行显示 "Std"
OLED_ShowString(1, 6, "Std");
// 在 OLED 第二行显示标准 ID 的十六进制值
OLED_ShowHexNum(2, 6, RxMsg.StdId, 8);
}
// 如果接收到的消息是扩展帧
else if (RxMsg.IDE == CAN_Id_Extended)
{
// 在 OLED 第一行显示 "Ext"
OLED_ShowString(1, 6, "Ext");
// 在 OLED 第二行显示扩展 ID 的十六进制值
OLED_ShowHexNum(2, 6, RxMsg.ExtId, 8);
}
// 如果接收到的消息是数据帧
if (RxMsg.RTR == CAN_RTR_Data)
{
// 在 OLED 第一行显示 "Data "
OLED_ShowString(1, 10, "Data ");
// 在 OLED 第三行显示数据长度的十六进制值
OLED_ShowHexNum(3, 6, RxMsg.DLC, 1);
// 在 OLED 第四行显示数据的十六进制值
OLED_ShowHexNum(4, 6, RxMsg.Data[0], 2);
OLED_ShowHexNum(4, 9, RxMsg.Data[1], 2);
OLED_ShowHexNum(4, 12, RxMsg.Data[2], 2);
OLED_ShowHexNum(4, 15, RxMsg.Data[3], 2);
}
// 如果接收到的消息是远程帧
else if (RxMsg.RTR == CAN_RTR_Remote)
{
// 在 OLED 第一行显示 "Remote"
OLED_ShowString(1, 10, "Remote");
// 在 OLED 第三行显示数据长度的十六进制值
OLED_ShowHexNum(3, 6, RxMsg.DLC, 1);
// 在 OLED 第四行显示 0x00 的十六进制值
OLED_ShowHexNum(4, 6, 0x00, 2);
OLED_ShowHexNum(4, 9, 0x00, 2);
OLED_ShowHexNum(4, 12, 0x00, 2);
OLED_ShowHexNum(4, 15, 0x00, 2);
}
}
}
}
10.定时发送
// 此函数用于初始化定时器2,设置其工作模式、定时周期、中断等参数
void Timer_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
// 配置定时器2使用内部时钟源。定时器可以使用内部或外部时钟源,这里选择内部时钟
TIM_InternalClockConfig(TIM2);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 1000 - 1;
TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
// 清除定时器2的更新标志位。在初始化时清除标志位,避免误触发中断
TIM_ClearFlag(TIM2, TIM_FLAG_Update);
// 使能定时器2的更新中断。当定时器产生更新事件时,会触发中断服务函数
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
// 配置NVIC(嵌套向量中断控制器)的优先级分组为2。这决定了中断的抢占优先级和子优先级的分配方式
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
// 设置NVIC的中断通道为定时器2中断
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
// 使能定时器2的中断通道
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
// 设置定时器2中断的抢占优先级为2。抢占优先级高的中断可以打断正在执行的低优先级中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
// 设置定时器2中断的子优先级为1。在抢占优先级相同的情况下,子优先级高的中断先执行
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIM2, ENABLE);
}
#include "stm32f10x.h" // 包含STM32F10x系列微控制器的头文件,用于访问硬件寄存器和外设
#include "Delay.h"
#include "OLED.h"
#include "Key.h"
#include "MyCAN.h"
#include "Timer.h"
uint8_t KeyNum;
uint8_t TimingFlag;
uint8_t TriggerFlag;
uint8_t RequestFlag;
// 定义定时发送的CAN消息结构体
CanTxMsg TxMsg_Timing = {
.StdId = 0x100, // 标准标识符
.ExtId = 0x00000000, // 扩展标识符
.IDE = CAN_Id_Standard, // 使用标准标识符
.RTR = CAN_RTR_Data, // 消息类型为数据帧
.DLC = 4, // 数据长度为4字节
.Data = {0x11, 0x22, 0x33, 0x44}
};
// 定义触发发送的CAN消息结构体
CanTxMsg TxMsg_Trigger = {
.StdId = 0x200, // 标准标识符
.ExtId = 0x00000000, // 扩展标识符
.IDE = CAN_Id_Standard, // 使用标准标识符
.RTR = CAN_RTR_Data, // 消息类型为数据帧
.DLC = 4, // 数据长度为4字节
.Data = {0x11, 0x22, 0x33, 0x44}
};
// 定义请求发送的CAN消息结构体
CanTxMsg TxMsg_Request = {
.StdId = 0x300, // 标准标识符
.ExtId = 0x00000000, // 扩展标识符
.IDE = CAN_Id_Standard, // 使用标准标识符
.RTR = CAN_RTR_Data, // 消息类型为数据帧
.DLC = 4, // 数据长度为4字节
.Data = {0x11, 0x22, 0x33, 0x44}
};
// 定义接收CAN消息的结构体
CanRxMsg RxMsg;
int main(void)
{
OLED_Init();
Key_Init();
MyCAN_Init();
Timer_Init();
// 在OLED显示屏上显示标题
OLED_ShowString(1, 1, "Tx");
OLED_ShowString(2, 1, "Tim:");
OLED_ShowString(3, 1, "Tri:");
OLED_ShowString(4, 1, "Req:");
// 主循环
while (1)
{
/*定时发送*/
if (TimingFlag == 1)
{
// 清除定时标志位
TimingFlag = 0;
TxMsg_Timing.Data[0] ++;
TxMsg_Timing.Data[1] ++;
TxMsg_Timing.Data[2] ++;
TxMsg_Timing.Data[3] ++;
MyCAN_Transmit(&TxMsg_Timing);
OLED_ShowHexNum(2, 5, TxMsg_Timing.Data[0], 2);
OLED_ShowHexNum(2, 8, TxMsg_Timing.Data[1], 2);
OLED_ShowHexNum(2, 11, TxMsg_Timing.Data[2], 2);
OLED_ShowHexNum(2, 14, TxMsg_Timing.Data[3], 2);
}
/*触发发送*/
KeyNum = Key_GetNum();
if (KeyNum == 1)
{
// 设置触发标志位
TriggerFlag = 1;
}
if (TriggerFlag == 1)
{
// 清除触发标志位
TriggerFlag = 0;
TxMsg_Trigger.Data[0] ++;
TxMsg_Trigger.Data[1] ++;
TxMsg_Trigger.Data[2] ++;
TxMsg_Trigger.Data[3] ++;
MyCAN_Transmit(&TxMsg_Trigger);
OLED_ShowHexNum(3, 5, TxMsg_Trigger.Data[0], 2);
OLED_ShowHexNum(3, 8, TxMsg_Trigger.Data[1], 2);
OLED_ShowHexNum(3, 11, TxMsg_Trigger.Data[2], 2);
OLED_ShowHexNum(3, 14, TxMsg_Trigger.Data[3], 2);
}
/*请求发送*/
if (MyCAN_ReceiveFlag())
{
// 接收CAN消息
MyCAN_Receive(&RxMsg);
// 判断是否为标准帧、远程帧且标识符为0x300
if (RxMsg.IDE == CAN_Id_Standard &&
RxMsg.RTR == CAN_RTR_Remote &&
RxMsg.StdId == 0x300)
{
// 设置请求标志位
RequestFlag = 1;
}
// 判断是否为标准帧、数据帧且标识符为0x3FF
if (RxMsg.IDE == CAN_Id_Standard &&
RxMsg.RTR == CAN_RTR_Data &&
RxMsg.StdId == 0x3FF)
{
// 设置请求标志位
RequestFlag = 1;
}
}
if (RequestFlag == 1)
{
// 清除请求标志位
RequestFlag = 0;
TxMsg_Request.Data[0] ++;
TxMsg_Request.Data[1] ++;
TxMsg_Request.Data[2] ++;
TxMsg_Request.Data[3] ++;
MyCAN_Transmit(&TxMsg_Request);
OLED_ShowHexNum(4, 5, TxMsg_Request.Data[0], 2);
OLED_ShowHexNum(4, 8, TxMsg_Request.Data[1], 2);
OLED_ShowHexNum(4, 11, TxMsg_Request.Data[2], 2);
OLED_ShowHexNum(4, 14, TxMsg_Request.Data[3], 2);
}
}
}
// 定时器2中断服务函数
void TIM2_IRQHandler(void)
{
// 判断定时器2更新中断标志位是否置位
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
// 设置定时标志位
TimingFlag = 1;
// 清除定时器2更新中断标志位
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
八.stm32基础
1.介绍

Cortex内核分类:

STM32微控制器有多种封装类型,每种封装类型都有其特定的特点和应用场景
常见的STM32封装类型:
- LQFP:LQFP封装是一种常见的平面封装类型,具有四边引脚,适用于大多数通用应用。LQFP封装的引脚数量从32到144不等,适合需要较多引脚的应用
- VFQFPN:VFQFPN封装是一种无引脚封装类型,具有较小的占用空间和较好的散热性能
- BGA:BGA封装是一种高密度封装类型,具有球形引脚阵列,适用于高性能和高密度的应用
LQFP144:

复位电路:

boot启动电路:

Cortex-M内核有硬件调试模块(JTAG和SWD调试接口),该模块可在取指(指令断点)或访问数据(数据断点)时停止。内核停止时,可以查询内核的内部状态和系统的外部状态。完成查询后,可恢复程序执行
F1系列可以通过AFIO_MAPR寄存器的SWJ_CFG[2:0]位来释放部分或者全部SWJ-DP引脚