第一次提交

This commit is contained in:
2026-05-29 17:36:34 +08:00
commit 5b3333125d
1318 changed files with 722705 additions and 0 deletions
View File
View File
+5
View File
@@ -0,0 +1,5 @@
待添加adc的bsp支持,应该提供阻塞/IT/DMA的量测接口
是否需要bsp_adc?由于功能非常简单,似乎可以直接使用HAL的接口,没有必要多此一举进行封装?
回调函数.
+12
View File
@@ -0,0 +1,12 @@
BSP
这是BSP层的说明文档。
> TODO:
> 1. 增加硬件和软件CRC支持,统一不同crc顺序和计算码的调用方式
bsp的功能是提供对片上(MCU中)外设的封装,即单片机芯片内部拥有的功能的封装。**在开发板pcb上集成的模块(蜂鸣器,加热电阻,wifi等)应该放在module层而不是这里**。
bsp应该提供几种接口。包括初始化接口,一般命名为`XXXRegister()`(对于只有一个instance的可以叫`XXXInit()`,但建议统一风格都叫register);调用此模块实现的必要功能,如通信型外设(CubeMX下的connectivity)提供接收和发送的接口,以及接收完成、发送完成(若有必要,或有发送队列需求)的数据回调函数。
- bsp_tools.h中提供了将bsp数据接收回调函数设置为任务的接口,通过这种方式,可以进一步提高整个系统的实时性,同时保证高优先级的任务一定按时执行。
+24
View File
@@ -0,0 +1,24 @@
#ifndef BSP_INIT_h
#define BSP_INIT_h
#include "bsp_init.h"
#include "bsp_log.h"
#include "bsp_dwt.h"
//#include "bsp_usb.h"
/**
* @brief bsp层初始化统一入口,这里仅初始化必须的bsp组件,其他组件的初始化在各自的模块中进行
* 需在实时系统启动前调用,目前由RobotoInit()调用
*
* @note 其他实例型的外设如CAN和串口会在注册实例的时候自动初始化,不注册不初始化
*/
//
void BSPInit()
{
DWT_Init(480);
BSPLogInit();
}
#endif // !BSP_INIT_h
+56
View File
@@ -0,0 +1,56 @@
#include <stdarg.h>
#include "cmsis_os.h"
#include "bsp_log.h"
#include "bsp_tools.h"
#define MX_SIG_LIST_SIZE 32 // 不可修改,最大信号量数量
typedef struct
{
void *callback;
uint32_t sig;
void *ins;
} CallbackTask_t;
// 递增记录全局信号量的数组,最大32个信号量
static uint8_t sig_idx = 0;
static uint32_t tmp_sig = 1; // tmp_sing << sig_idx从而生成对应信号量
static osThreadId cbkid_list[MX_SIG_LIST_SIZE];
static CallbackTask_t cbkinfo_list[MX_SIG_LIST_SIZE];
// 死循环任务,执行cbk函数指针,每次执行完毕后等待sig信号
__attribute__((noreturn)) static void CallbackTaskBase(void const *cbk)
{
void (*cbk_func)(void const *) = (void (*)(void const *))((CallbackTask_t const *)cbk)->callback;
void const *ins = ((CallbackTask_t const *)cbk)->ins;
uint32_t sig = ((CallbackTask_t const *)cbk)->sig;
for (;;)
{
cbk_func(ins);
osSignalWait(sig, osWaitForever);
}
}
uint32_t CreateCallbackTask(char *name, void *cbk, void *ins, osPriority priority)
{
if (sig_idx >= MX_SIG_LIST_SIZE)
while (1)
LOGERROR("[rtos:cbk_register] CreateCallbackTask: sig_idx >= MX_SIG_LIST_SIZE");
cbkinfo_list[sig_idx].callback = cbk;
cbkinfo_list[sig_idx].sig = tmp_sig << sig_idx;
cbkinfo_list[sig_idx].ins = ins;
osThreadDef_t threadDef;
threadDef.name = name;
threadDef.pthread = &CallbackTaskBase;
threadDef.tpriority = priority;
threadDef.instances = 0;
threadDef.stacksize = 128;
cbkid_list[sig_idx] = osThreadCreate(&threadDef, (void *)&cbkinfo_list[sig_idx]);
return cbkinfo_list[sig_idx++].sig; // 返回信号量,同时增加索引
}
+17
View File
@@ -0,0 +1,17 @@
#include "cmsis_os.h"
#include "bsp_log.h"
/**
* @brief 创建一个新的任务,该任务收到osSignalSet信号时会被唤醒,否则保持挂起状态
*
* @note 注册完成后,只需要将module提供的回调函数替换成osSignalSet即可
* 推荐将处理较为复杂的回调函数注册为任务,如视觉长包解析/多机通信/双板通信/裁判系统通信等
*
* @param name 任务名称,注意以'\0'结尾
* @param cbk 回调函数指针
* @param ins 回调函数的参数,bsp为实例指针
* @param priority 任务优先级,决定了在中断结束后能否保证立刻执行
* @return uint32_t 信号量,用于唤醒该任务
*/
uint32_t CreateCallbackTask(char *name, void *cbk, void *ins, osPriority priority);
+395
View File
@@ -0,0 +1,395 @@
#include "bsp_can.h"
#include "main.h"
#include "memory.h"
#include "stdlib.h"
#include "bsp_dwt.h"
#include "bsp_log.h"
/* can instance ptrs storage, used for recv callback */
// 在CAN产生接收中断会遍历数组,选出hcan和rxid与发生中断的实例相同的那个,调用其回调函数
// @todo: 后续为每个CAN总线单独添加一个can_instance指针数组,提高回调查找的性能
static CANInstance *can_instance[CAN_MX_REGISTER_CNT] = {NULL};
static uint8_t idx; // 全局CAN实例索引,每次有新的模块注册会自增
/* ----------------two static function called by CANRegister()-------------------- */
/**
* @brief 添加过滤器以实现对特定id的报文的接收,会被CANRegister()调用
* 给CAN添加过滤器后,BxCAN会根据接收到的报文的id进行消息过滤,符合规则的id会被填入FIFO触发中断
* 对于FDCAN,设置使用特定ID模式过滤。
*
* @note f407的bxCAN有28个过滤器,这里将其配置为前14个过滤器给CAN1使用,后14个被CAN2使用
* 初始化时,奇数id的模块会被分配到FIFO0,偶数id的模块会被分配到FIFO1
* 注册到CAN1的模块使用过滤器0-13,CAN2使用过滤器14-27
* FDCAN的消息RAM是所有FDCAN外设共用的。
* H723系列FDCAN过滤器数量完全在CubeMX中自定义,因此先做一次检查,再添加即可。
*
* @attention 你不需要完全理解这个函数的作用,因为它主要是用于初始化,在开发过程中不需要关心底层的实现
* 享受开发的乐趣吧!如果你真的想知道这个函数在干什么,请联系作者或自己查阅资料(请直接查阅官方的reference manual)
* FDCAN的教程较少,但是添加FDCAN的人已经发了一篇CSDN讲解了,可以参考一下
*
* @param _instance can instance owned by specific module
*/
static void CANAddFilter(CANInstance *_instance)
{
#ifdef FDCAN
static uint8_t can1_filter_idx = 0, can2_filter_idx = 0 , can3_filter_idx = 0;
//检查是否超出过滤器设定数量上限
if(can1_filter_idx > hfdcan1.Init.StdFiltersNbr || can2_filter_idx>hfdcan2.Init.StdFiltersNbr || can3_filter_idx > hfdcan3.Init.StdFiltersNbr)
{
while(1)
{
//报错
}
}
uint8_t *filter_idx_p;
if(_instance->can_handle==&hfdcan1)
{
filter_idx_p=&can1_filter_idx;
}
else if(_instance->can_handle==&hfdcan2)
{
filter_idx_p=&can2_filter_idx;
}
else if(_instance->can_handle==&hfdcan3)
{
filter_idx_p=&can3_filter_idx;
}
else
{
while(1)
{
//报错
}
}
FDCAN_FilterTypeDef fdcan_filter_conf;
fdcan_filter_conf.FilterIndex=(*filter_idx_p)++;
//使用单个ID模式
fdcan_filter_conf.FilterType=FDCAN_FILTER_DUAL;
fdcan_filter_conf.FilterConfig=(_instance->tx_id & 1) ? FDCAN_FILTER_TO_RXFIFO0 : FDCAN_FILTER_TO_RXFIFO1;//奇数id的模块会被分配到FIFO0,偶数id的模块会被分配到FIFO1
fdcan_filter_conf.FilterID1=_instance->rx_id;
fdcan_filter_conf.FilterID2=_instance->rx_id;
fdcan_filter_conf.IdType=FDCAN_STANDARD_ID;
fdcan_filter_conf.IsCalibrationMsg=0;
//fdcan_filter_conf.RxBufferIndex=0;
HAL_FDCAN_ConfigFilter(_instance->can_handle, &fdcan_filter_conf);
#else
CAN_FilterTypeDef can_filter_conf;
static uint8_t can1_filter_idx = 0, can2_filter_idx = 14; // 0-13给can1用,14-27给can2用
can_filter_conf.FilterMode = CAN_FILTERMODE_IDLIST; // 使用id list模式,即只有将rxid添加到过滤器中才会接收到,其他报文会被过滤
can_filter_conf.FilterScale = CAN_FILTERSCALE_16BIT; // 使用16位id模式,即只有低16位有效
can_filter_conf.FilterFIFOAssignment = (_instance->tx_id & 1) ? CAN_RX_FIFO0 : CAN_RX_FIFO1; // 奇数id的模块会被分配到FIFO0,偶数id的模块会被分配到FIFO1
can_filter_conf.SlaveStartFilterBank = 14; // 从第14个过滤器开始配置从机过滤器(在STM32的BxCAN控制器中CAN2是CAN1的从机)
can_filter_conf.FilterIdLow = _instance->rx_id << 5; // 过滤器寄存器的低16位,因为使用STDID,所以只有低11位有效,高5位要填0
can_filter_conf.FilterBank = _instance->can_handle == &hcan1 ? (can1_filter_idx++) : (can2_filter_idx++); // 根据can_handle判断是CAN1还是CAN2,然后自增
can_filter_conf.FilterActivation = CAN_FILTER_ENABLE; // 启用过滤器
HAL_CAN_ConfigFilter(_instance->can_handle, &can_filter_conf);
#endif
}
/**
* @brief 在第一个CAN实例初始化的时候会自动调用此函数,启动CAN服务
*
* @note 此函数会启动CAN1并开启中断
* FDCAN的情况下,我们采用FIFO接收方式(而不是buffer),FIFO和buffer还有queue接收方式请自行查阅H723手册
* FDCAN比bxCAN多了一个全局过滤器,这里配置为全部拒绝,只接受指定ID。
*
*/
void CANServiceInit()
{
#ifdef FDCAN
//可能不需要这么多中断
uint32_t FDCAN_RXActiveITs = FDCAN_IT_RX_FIFO0_NEW_MESSAGE|FDCAN_IT_RX_FIFO0_FULL\
|FDCAN_IT_RX_FIFO0_WATERMARK|FDCAN_IT_RX_FIFO0_MESSAGE_LOST \
|FDCAN_IT_RX_FIFO1_NEW_MESSAGE| FDCAN_IT_RX_FIFO1_FULL\
|FDCAN_IT_RX_FIFO1_WATERMARK|FDCAN_IT_RX_FIFO1_MESSAGE_LOST;
//HAL_FDCAN_ConfigClockCalibration()
HAL_FDCAN_ConfigRxFifoOverwrite(&hfdcan1,FDCAN_RX_FIFO0,FDCAN_RX_FIFO_OVERWRITE);
HAL_FDCAN_ConfigRxFifoOverwrite(&hfdcan1,FDCAN_RX_FIFO1,FDCAN_RX_FIFO_OVERWRITE);
HAL_FDCAN_ConfigGlobalFilter(&hfdcan1, FDCAN_REJECT, FDCAN_REJECT, FDCAN_REJECT_REMOTE, FDCAN_REJECT_REMOTE);//全局过滤器设置
HAL_FDCAN_Start(&hfdcan1);
HAL_FDCAN_ActivateNotification(&hfdcan1,FDCAN_RXActiveITs, 0);
HAL_FDCAN_ConfigRxFifoOverwrite(&hfdcan2,FDCAN_RX_FIFO0,FDCAN_RX_FIFO_OVERWRITE);
HAL_FDCAN_ConfigRxFifoOverwrite(&hfdcan2,FDCAN_RX_FIFO1,FDCAN_RX_FIFO_OVERWRITE);
HAL_FDCAN_ConfigGlobalFilter(&hfdcan2, FDCAN_REJECT, FDCAN_REJECT, FDCAN_REJECT_REMOTE, FDCAN_REJECT_REMOTE);
HAL_FDCAN_Start(&hfdcan2);
HAL_FDCAN_ActivateNotification(&hfdcan2,FDCAN_RXActiveITs, 0);
HAL_FDCAN_ConfigRxFifoOverwrite(&hfdcan3,FDCAN_RX_FIFO0,FDCAN_RX_FIFO_OVERWRITE);
HAL_FDCAN_ConfigRxFifoOverwrite(&hfdcan3,FDCAN_RX_FIFO1,FDCAN_RX_FIFO_OVERWRITE);
HAL_FDCAN_ConfigGlobalFilter(&hfdcan3, FDCAN_REJECT, FDCAN_REJECT, FDCAN_REJECT_REMOTE, FDCAN_REJECT_REMOTE);
HAL_FDCAN_Start(&hfdcan3);
HAL_FDCAN_ActivateNotification(&hfdcan3,FDCAN_RXActiveITs, 0);
#else
HAL_CAN_Start(&hcan1);
HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING);
HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO1_MSG_PENDING);
HAL_CAN_Start(&hcan2);
HAL_CAN_ActivateNotification(&hcan2, CAN_IT_RX_FIFO0_MSG_PENDING);
HAL_CAN_ActivateNotification(&hcan2, CAN_IT_RX_FIFO1_MSG_PENDING);
#endif
}
/* ----------------------- two extern callable function -----------------------*/
CANInstance *CANRegister(CAN_Init_Config_s *config)
{
if (!idx)
{
CANServiceInit(); // 第一次注册,先进行硬件初始化
LOGINFO("[bsp_can] CAN Service Init");
}
if (idx >= CAN_MX_REGISTER_CNT) // 超过最大实例数
{
while (1)
{
LOGERROR("[bsp_can] CAN instance exceeded MAX num, consider balance the load of CAN bus");
}
}
for (size_t i = 0; i < idx; i++)
{ // 重复注册 | id重复
if (can_instance[i]->rx_id == config->rx_id && can_instance[i]->can_handle == config->can_handle)
{
while (1)
{
LOGERROR("[}bsp_can] CAN id crash ,tx [%d] or rx [%d] already registered", &config->tx_id, &config->rx_id);
}
}
}
CANInstance *instance = (CANInstance *)malloc(sizeof(CANInstance)); // 分配空间
memset(instance, 0, sizeof(CANInstance)); // 分配的空间未必是0,所以要先清空
// 进行发送报文的配置
#ifdef FDCAN
instance->txconf.Identifier = config->tx_id; // 发送id
instance->txconf.IdType = FDCAN_STANDARD_ID; // 使用标准id,扩展id则使用CAN_ID_EXT(目前没有需求)
instance->txconf.TxFrameType = FDCAN_DATA_FRAME, // 发送数据帧
instance->txconf.DataLength = FDCAN_DLC_BYTES_8, // 数据长度为8字节
instance->txconf.ErrorStateIndicator = FDCAN_ESI_ACTIVE, // 兼容CAN2.0,错误状态指示器设为主动
instance->txconf.BitRateSwitch = FDCAN_BRS_OFF, // 兼容CAN2.0禁用位速率切换
instance->txconf.FDFormat = FDCAN_CLASSIC_CAN, // 使用经典CAN格式
instance->txconf.TxEventFifoControl = FDCAN_NO_TX_EVENTS, // 不需要,禁用事件FIFO
instance->txconf.MessageMarker = 0; // 不使用消息标记
#else
instance->txconf.StdId = config->tx_id; // 发送id
instance->txconf.IDE = CAN_ID_STD; // 使用标准id,扩展id则使用CAN_ID_EXT(目前没有需求)
instance->txconf.RTR = CAN_RTR_DATA; // 发送数据帧
instance->txconf.DLC = 0x08; // 默认发送长度为8
#endif
// 设置回调函数和接收发送id
instance->can_handle = config->can_handle;
instance->tx_id = config->tx_id; // 好像没用,可以删掉
instance->rx_id = config->rx_id;
instance->can_module_callback = config->can_module_callback;
instance->id = config->id;
CANAddFilter(instance); // 添加CAN过滤器规则
can_instance[idx++] = instance; // 将实例保存到can_instance中
return instance; // 返回can实例指针
}
/* @todo 目前似乎封装过度,应该添加一个指向tx_buff的指针,tx_buff不应该由CAN instance保存 */
/* 如果让CANinstance保存txbuff,会增加一次复制的开销 */
uint8_t CANTransmit(CANInstance *_instance, float timeout)
{
static uint32_t busy_count;
static volatile float wait_time __attribute__((unused)); // for cancel warning
float dwt_start = DWT_GetTimeline_ms();
#ifdef FDCAN
while(HAL_FDCAN_GetTxFifoFreeLevel(_instance->can_handle)==0)
#else
while (HAL_CAN_GetTxMailboxesFreeLevel(_instance->can_handle) == 0) // 等待邮箱空闲
#endif
{
if (DWT_GetTimeline_ms() - dwt_start > timeout) // 超时
{
LOGWARNING("[bsp_can] CAN MAILbox full! failed to add msg to mailbox. Cnt [%d]", busy_count);
busy_count++;
return 0;
}
}
wait_time = DWT_GetTimeline_ms() - dwt_start;
#ifdef FDCAN
if (HAL_FDCAN_AddMessageToTxFifoQ(_instance->can_handle, &_instance->txconf, _instance->tx_buff))
#else
// tx_mailbox会保存实际填入了这一帧消息的邮箱,但是知道是哪个邮箱发的似乎也没啥用
if (HAL_CAN_AddTxMessage(_instance->can_handle, &_instance->txconf, _instance->tx_buff, &_instance->tx_mailbox))
#endif
{
LOGWARNING("[bsp_can] CAN bus BUSY! cnt:%d", busy_count);
busy_count++;
return 0;
}
return 1; // 发送成功
}
void CANSetDLC(CANInstance *_instance, uint8_t length)
{
// 发送长度错误!检查调用参数是否出错,或出现野指针/越界访问
if (length > 8 || length == 0) // 安全检查
while (1)
{
LOGERROR("[bsp_can] CAN DLC error! check your code or wild pointer");
}
_instance->txconf.DataLength = length;
}
/* -----------------------belows are callback definitions--------------------------*/
//对于FDCAN,回调函数和处理方式完全不同,因此直接用两套逻辑处理
#ifdef FDCAN
/**
* @brief 此函数会被下面两个函数调用,用于处理FIFO0和FIFO1溢出中断(说明收到了新的数据)
* 所有的实例都会被遍历,找到can_handle和rx_id相等的实例时,调用该实例的回调函数
*
* @param _fdhcan
* @param fifox passed to HAL_CAN_GetRxMessage() to get mesg from a specific fifo
*/
static void FDCANFIFOxCallback(FDCAN_HandleTypeDef *_hfdcan, uint32_t fifox)
{
static FDCAN_RxHeaderTypeDef rxconf; // 同上
static uint16_t DataLength = 0;
static uint8_t fdcan_rx_buff[8];
while (HAL_FDCAN_GetRxFifoFillLevel(_hfdcan, fifox)) // FIFO不为空,有可能在其他中断时有多帧数据进入
{
HAL_FDCAN_GetRxMessage(_hfdcan, fifox, &rxconf, fdcan_rx_buff); // 从FIFO中获取数据
//解析数据长度,@Todo 此处在用新版本重新生成后可能得修改,DataLength可能不需要右移,具体情况具体看 !
if(((rxconf.DataLength >> 16) & 0xF)>=0 && ((rxconf.DataLength >> 16) & 0xF)<=8)
{
DataLength=(rxconf.DataLength >> 16) & 0xF; // 保存接收到的数据长度
}
else
{
DataLength=0;
}
if(rxconf.RxFrameType==FDCAN_DATA_FRAME && rxconf.IdType==FDCAN_STANDARD_ID)
{
for (size_t i = 0; i < idx; ++i)
{
// 两者相等说明这是要找的实例
if (_hfdcan == can_instance[i]->can_handle && rxconf.Identifier == can_instance[i]->rx_id)
{
if (can_instance[i]->can_module_callback != NULL) // 回调函数不为空就调用
{
can_instance[i]->rx_len = DataLength; // 保存接收到的数据长度
memcpy(can_instance[i]->rx_buff, fdcan_rx_buff, can_instance[i]->rx_len); // 消息拷贝到对应实例
can_instance[i]->can_module_callback(can_instance[i]); // 触发回调进行数据解析和处理
}
return;
}
}
}
}
}
void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs)
{
/* 检查Rx FIFO 0中是否有消息丢失 */
if ((RxFifo0ITs & FDCAN_IT_RX_FIFO0_MESSAGE_LOST) != 0)
{
//报错
}
/* 检查是否有新消息写入Rx FIFO 0或到达一定阈值 */
if ((RxFifo0ITs & FDCAN_IT_RX_FIFO0_NEW_MESSAGE)||(RxFifo0ITs & FDCAN_IT_RX_FIFO0_FULL)||(RxFifo0ITs & FDCAN_IT_RX_FIFO0_WATERMARK))
{
FDCANFIFOxCallback(hfdcan, FDCAN_RX_FIFO0); // 调用我们自己写的函数来处理消息
}
}
void HAL_FDCAN_RxFifo1Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo1ITs)
{
/* 检查Rx FIFO 1中是否有消息丢失 */
if ((RxFifo1ITs & FDCAN_IT_RX_FIFO1_MESSAGE_LOST) != 0)
{
//报错
}
/* 检查是否有新消息写入Rx FIFO 1或到达一定阈值 */
if ((RxFifo1ITs & FDCAN_IT_RX_FIFO1_NEW_MESSAGE)||(RxFifo1ITs & FDCAN_IT_RX_FIFO1_FULL)||(RxFifo1ITs & FDCAN_IT_RX_FIFO1_WATERMARK))
{
FDCANFIFOxCallback(hfdcan, FDCAN_RX_FIFO1); // 调用我们自己写的函数来处理消息
}
}
#else
/**
* @brief 此函数会被下面两个函数调用,用于处理FIFO0和FIFO1溢出中断(说明收到了新的数据)
* 所有的实例都会被遍历,找到can_handle和rx_id相等的实例时,调用该实例的回调函数
*
* @param _hcan
* @param fifox passed to HAL_CAN_GetRxMessage() to get mesg from a specific fifo
*/
static void CANFIFOxCallback(CAN_HandleTypeDef *_hcan, uint32_t fifox)
{
static CAN_RxHeaderTypeDef rxconf; // 同上
uint8_t can_rx_buff[8];
while (HAL_CAN_GetRxFifoFillLevel(_hcan, fifox)) // FIFO不为空,有可能在其他中断时有多帧数据进入
{
HAL_CAN_GetRxMessage(_hcan, fifox, &rxconf, can_rx_buff); // 从FIFO中获取数据
for (size_t i = 0; i < idx; ++i)
{ // 两者相等说明这是要找的实例
if (_hcan == can_instance[i]->can_handle && rxconf.StdId == can_instance[i]->rx_id)
{
if (can_instance[i]->can_module_callback != NULL) // 回调函数不为空就调用
{
can_instance[i]->rx_len = rxconf.DLC; // 保存接收到的数据长度
memcpy(can_instance[i]->rx_buff, can_rx_buff, rxconf.DLC); // 消息拷贝到对应实例
can_instance[i]->can_module_callback(can_instance[i]); // 触发回调进行数据解析和处理
}
return;
}
}
}
}
/**
* @brief 注意,STM32的两个CAN设备共享两个FIFO
* 下面两个函数是HAL库中的回调函数,他们被HAL声明为__weak,这里对他们进行重载(重写)
* 当FIFO0或FIFO1溢出时会调用这两个函数
*/
// 下面的函数会调用CANFIFOxCallback()来进一步处理来自特定CAN设备的消息
/**
* @brief rx fifo callback. Once FIFO_0 is full,this func would be called
*
* @param hcan CAN handle indicate which device the oddest mesg in FIFO_0 comes from
*/
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
CANFIFOxCallback(hcan, CAN_RX_FIFO0); // 调用我们自己写的函数来处理消息
}
/**
* @brief rx fifo callback. Once FIFO_1 is full,this func would be called
*
* @param hcan CAN handle indicate which device the oddest mesg in FIFO_1 comes from
*/
void HAL_CAN_RxFifo1MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
CANFIFOxCallback(hcan, CAN_RX_FIFO1); // 调用我们自己写的函数来处理消息
}
#endif
+103
View File
@@ -0,0 +1,103 @@
#ifndef BSP_CAN_H
#define BSP_CAN_H
//在此选择CAN类型,两者只能选择一个!!!
#define FDCAN //G系列和H7系列使用FDCAN
//#define BXCAN //F系列使用BxCAN
//CAN类型宏定义检查,有错误停止编译
#if !defined(FDCAN) && !defined(BXCAN)
#error "Neither FDCAN nor BXCAN is defined. Please define one of them."
#elif defined(FDCAN) && defined(BXCAN)
#error "Both FDCAN and BXCAN are defined. Please define only one."
#endif
#include <stdint.h>
#ifdef FDCAN
#include "fdcan.h"
#define hcan1 hfdcan1
#define hcan2 hfdcan2
#define hcan3 hfdcan3
#define CAN_MX_REGISTER_CNT 16 // 这个数量取决于CAN总线的负载
#define MX_CAN_FILTER_CNT (3 * 14) // 最多可以使用的CAN过滤器数量,目前远不会用到这么多
#define DEVICE_CAN_CNT 3 //H723VG有3个FDCAN
#endif
#ifdef BXCAN
#include "can.h"
// 最多能够支持的CAN设备数
#define CAN_MX_REGISTER_CNT 16 // 这个数量取决于CAN总线的负载
#define MX_CAN_FILTER_CNT (2 * 14) // 最多可以使用的CAN过滤器数量,目前远不会用到这么多
#define DEVICE_CAN_CNT 2 // 根据板子设定,F407IG有CAN1,CAN2,因此为2;F334只有一个,则设为1
// 如果只有1个CAN,还需要把bsp_can.c中所有的hcan2变量改为hcan1(别担心,主要是总线和FIFO的负载均衡,不影响功能)
#endif
/* can instance typedef, every module registered to CAN should have this variable */
#pragma pack(1)
typedef struct _
{
#ifdef FDCAN
FDCAN_HandleTypeDef *can_handle; // can句柄
FDCAN_TxHeaderTypeDef txconf; // CAN报文发送配置
#else
CAN_HandleTypeDef *can_handle; // can句柄
CAN_TxHeaderTypeDef txconf; // CAN报文发送配置
#endif
uint32_t tx_id; // 发送id
uint32_t tx_mailbox; // CAN消息填入的邮箱号
uint8_t tx_buff[8]; // 发送缓存,发送消息长度可以通过CANSetDLC()设定,最大为8
uint8_t rx_buff[8]; // 接收缓存,最大消息长度为8
uint32_t rx_id; // 接收id
uint8_t rx_len; // 接收长度,可能为0-8
// 接收的回调函数,用于解析接收到的数据
void (*can_module_callback)(struct _ *); // callback needs an instance to tell among registered ones
void *id; // 使用can外设的模块指针(即id指向的模块拥有此can实例,是父子关系)
} CANInstance;
#pragma pack()
/* CAN实例初始化结构体,将此结构体指针传入注册函数 */
typedef struct
{
#ifdef FDCAN
FDCAN_HandleTypeDef *can_handle; // can句柄
#else
CAN_HandleTypeDef *can_handle; // can句柄
#endif
uint32_t tx_id; // 发送id
uint32_t rx_id; // 接收id
void (*can_module_callback)(CANInstance *); // 处理接收数据的回调函数
void *id; // 拥有can实例的模块地址,用于区分不同的模块(如果有需要的话),如果不需要可以不传入
} CAN_Init_Config_s;
/**
* @brief Register a module to CAN service,remember to call this before using a CAN device
* 注册(初始化)一个can实例,需要传入初始化配置的指针.
* @param config init config
* @return CANInstance* can instance owned by module
*/
CANInstance *CANRegister(CAN_Init_Config_s *config);
/**
* @brief 修改CAN发送报文的数据帧长度;注意最大长度为8,在没有进行修改的时候,默认长度为8
*
* @param _instance 要修改长度的can实例
* @param length 设定长度
*/
void CANSetDLC(CANInstance *_instance, uint8_t length);
/**
* @brief transmit mesg through CAN device,通过can实例发送消息
* 发送前需要向CAN实例的tx_buff写入发送数据
*
* @attention 超时时间不应该超过调用此函数的任务的周期,否则会导致任务阻塞
*
* @param timeout 超时时间,单位为ms;后续改为us,获得更精确的控制
* @param _instance* can instance owned by module
*/
uint8_t CANTransmit(CANInstance *_instance,float timeout);
#endif
+114
View File
@@ -0,0 +1,114 @@
# bsp_can
<p align='right'>neozng1@hnu.edu.cn</p>
# 请注意使用CAN设备的时候务必保证总线只接入了2个终端电阻!开发板一般都有一个,6020电机、c620/c610电调、LK电机也都有终端电阻,注意把多于2个的全部断开(通过拨码)
## 使用说明
若你希望新增一个基于CAN的module,首先在该模块下应该有一个包含`can_instance`指针的module结构体(或当功能简单的时候,可以是单独存在的`can_instance`,但不推荐这样做)。
## 代码结构
.h文件内包括了外部接口和类型定义,以及模块对应的宏。c文件内为私有函数和外部接口的定义。
## 类型定义
```c
#define MX_REGISTER_DEVICE_CNT 12 // maximum number of device can be registered to CAN service, this number depends on the load of CAN bus.
#define MX_CAN_FILTER_CNT (4 * 14) // temporarily useless
#define DEVICE_CAN_CNT 2 // CAN1,CAN2
/* can instance typedef, every module registered to CAN should have this variable */
typedef struct _
{
CAN_HandleTypeDef *can_handle; // can句柄
CAN_TxHeaderTypeDef txconf; // CAN报文发送配置
uint32_t tx_id; // 发送id
uint32_t tx_mailbox; // CAN消息填入的邮箱号
uint8_t tx_buff[8]; // 发送缓存,发送消息长度可以通过CANSetDLC()设定,最大为8
uint8_t rx_buff[8]; // 接收缓存,最大消息长度为8
uint32_t rx_id; // 接收id
uint8_t rx_len; // 接收长度,可能为0-8
// 接收的回调函数,用于解析接收到的数据
void (*can_module_callback)(struct _ *); // callback needs an instance to tell among registered ones
void *id; // 使用can外设的模块指针(即id指向的模块拥有此can实例,是父子关系)
} CANInstance;
typedef struct
{
CAN_HandleTypeDef* can_handle;
uint32_t tx_id;
uint32_t rx_id;
void (*can_module_callback)(can_instance*);
void* id;
} can_instance_config;
typedef void (*can_callback)(can_instance*);
```
- `MX_REGISTER_DEVICE_CNT`是最大的CAN设备注册数量,当每个设备的发送频率都较高时,设备过多会产生总线拥塞从而出现丢包和数据错误的情况。
- `MX_CAN_FILTER_CNT`是最大的CAN接收过滤器数量,两个CAN共享标号0~27共28个过滤器。这部分内容比较繁杂,暂时不用理解,有兴趣自行参考MCU的数据手册。当前为简单起见,每个过滤器只设置一组规则用于控制一个id的过滤。
- `DEVICE_CAN_CNT`是MCU拥有的CAN硬件数量。
- `can_instance`是一个CAN实例。注意,CAN作为一个总线设备,一条总线上可以挂载多个设备,因此多个设备可以共享同一个CAN硬件。其成员变量包括发送id,发送邮箱(不需要管,只是一个32位变量,CAN收发器会自动设置其值),发送buff以及接收buff,还有接收id和接收协议解析回调函数。**由于目前使用的设备每个数据帧的长度都是8,因此收发buff长度暂时固定为8**。定义该结构体的时候使用了一个技巧,使得在结构体内部可以用结构体自身的指针作为成员,即`can_module_callback`的定义。
- `can_instance_config`是用于初始化CAN实例的结构,在调用CAN实例的初始化函数时传入(下面介绍函数时详细介绍)。
- `can_module_callback()`是模块提供给CAN接收中断回调函数使用的协议解析函数指针。对于每个需要CAN的模块,需要定义一个这样的函数用于解包数据。
- 每个使用CAN外设的module,都需要在其内部定义一个`can_instance*`。
## 外部接口
```c
void CANRegister(can_instance* instance, can_instance_config config);
void CANSetDLC(CANInstance *_instance, uint8_t length); // 设置发送帧的数据长度
uint8_t CANTransmit(can_instance* _instance, uint8_t timeout);
```
`CANRegister`是用于初始化CAN实例的接口,module层的模块对象(也应当为一个结构体)内要包含一个`usart_instance`。调用时传入实例指针,以及用于初始化的config。`CANRegister`应当在module的初始化函数内被调用,推荐config采用以下的方式定义,更加直观明了:
```c
can_instance_config config={.can_handle=&hcan1,
.tx_id=0x005,
.rx_id=0x200,
can_module_callback=MotorCallback}
```
`CANTransmit()`是通过模块通过其拥有的CAN实例发送数据的接口,调用时传入对应的instance。在发送之前,应当给instance内的`send_buff`赋值。
## 私有函数和变量
在.c文件内设为static的函数和变量
```c
static can_instance *instance[MX_REGISTER_DEVICE_CNT]={NULL};
```
这是bsp层管理所有CAN实例的入口。
```c
static void CANServiceInit()
static void CANAddFilter(can_instance *_instance)
static void CANFIFOxCallback(CAN_HandleTypeDef *_hcan, uint32_t fifox)
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
void HAL_CAN_RxFifo1MsgPendingCallback(CAN_HandleTypeDef *hcan)
```
- `CANServiceInit()`会被`CANRegister()`调用,对CAN外设进行硬件初始化并开启接收中断和消息提醒。
- `CANAddFilter()`在每次使用`CANRegister()`的时候被调用,用于给当前注册的实例添加过滤器规则并设定处理对应`rx_id`的接收FIFO。过滤器的作用是减小CAN收发器的压力,只接收符合过滤器规则的报文(否则不会产生接收中断)。
- `HAL_CAN_RxFifo0MsgPendingCallback()`和`HAL_CAN_RxFifo1MsgPendingCallback()`都是对HAL的CAN回调函数的重定义(原本的callback是`__week`修饰的弱定义),当发生FIFO0或FIFO1有新消息到达的时候,对应的callback会被调用。`CANFIFOxCallback()`随后被前两者调用,并根据接收id和硬件中断来源(哪一个CAN硬件,CAN1还是CAN2)调用对应的instance的回调函数进行协议解析。
- 当有一个模块注册了多个can实例时,通过`CANInstance.id`,使用强制类型转换将其转换成对应模块的实例指针,就可以对不同的模块实例进行回调处理了。
## 注意事项
由于CAN总线自带发送检测,如果总线上没有挂载目标设备(接收id和发送报文相同的设备),那么CAN邮箱会被占满而无法发送。在`CANTransmit()`中会对CAN邮箱是否已满进行`while(1)`检查。当超出`timeout`之后函数会返回零,说明发送失败。
由于卡在`while(1)`处不断检查邮箱是否空闲,调用`CANTransmit()`的任务可能无法按时挂起,导致任务定时不精确。建议在没有连接CAN进行调试时,按需注释掉有关CAN发送的代码部分,或设定一个较小的`timeout`值,防止对其他需要精确定时的任务产生影响。
+134
View File
@@ -0,0 +1,134 @@
/**
******************************************************************************
* @file bsp_dwt.c
* @author Wang Hongxi
* @author modified by Neo with annotation
* @version V1.1.0
* @date 2022/3/8
* @brief
*/
#include "bsp_dwt.h"
#include "cmsis_os.h"
static DWT_Time_t SysTime;
static uint32_t CPU_FREQ_Hz, CPU_FREQ_Hz_ms, CPU_FREQ_Hz_us;
static uint32_t CYCCNT_RountCount;
static uint32_t CYCCNT_LAST;
static uint64_t CYCCNT64;
/**
* @brief 私有函数,用于检查DWT CYCCNT寄存器是否溢出,并更新CYCCNT_RountCount
* @attention 此函数假设两次调用之间的时间间隔不超过一次溢出
*
* @todo 更好的方案是为dwt的时间更新单独设置一个任务?
* 不过,使用dwt的初衷是定时不被中断/任务等因素影响,因此该实现仍然有其存在的意义
*
*/
static void DWT_CNT_Update(void)
{
static volatile uint8_t bit_locker = 0;
if (!bit_locker)
{
bit_locker = 1;
volatile uint32_t cnt_now = DWT->CYCCNT;
if (cnt_now < CYCCNT_LAST)
CYCCNT_RountCount++;
CYCCNT_LAST = DWT->CYCCNT;
bit_locker = 0;
}
}
void DWT_Init(uint32_t CPU_Freq_mHz)
{
/* 使能DWT外设 */
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
/* DWT CYCCNT寄存器计数清0 */
DWT->CYCCNT = (uint32_t)0u;
/* 使能Cortex-M DWT CYCCNT寄存器 */
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
CPU_FREQ_Hz = CPU_Freq_mHz * 1000000;
CPU_FREQ_Hz_ms = CPU_FREQ_Hz / 1000;
CPU_FREQ_Hz_us = CPU_FREQ_Hz / 1000000;
CYCCNT_RountCount = 0;
DWT_CNT_Update();
}
float DWT_GetDeltaT(uint32_t *cnt_last)
{
volatile uint32_t cnt_now = DWT->CYCCNT;
float dt = ((uint32_t)(cnt_now - *cnt_last)) / ((float)(CPU_FREQ_Hz));
*cnt_last = cnt_now;
DWT_CNT_Update();
return dt;
}
double DWT_GetDeltaT64(uint32_t *cnt_last)
{
volatile uint32_t cnt_now = DWT->CYCCNT;
double dt = ((uint32_t)(cnt_now - *cnt_last)) / ((double)(CPU_FREQ_Hz));
*cnt_last = cnt_now;
DWT_CNT_Update();
return dt;
}
void DWT_SysTimeUpdate(void)
{
volatile uint32_t cnt_now = DWT->CYCCNT;
static uint64_t CNT_TEMP1, CNT_TEMP2, CNT_TEMP3;
DWT_CNT_Update();
CYCCNT64 = (uint64_t)CYCCNT_RountCount * (uint64_t)UINT32_MAX + (uint64_t)cnt_now;
CNT_TEMP1 = CYCCNT64 / CPU_FREQ_Hz;
CNT_TEMP2 = CYCCNT64 - CNT_TEMP1 * CPU_FREQ_Hz;
SysTime.s = CNT_TEMP1;
SysTime.ms = CNT_TEMP2 / CPU_FREQ_Hz_ms;
CNT_TEMP3 = CNT_TEMP2 - SysTime.ms * CPU_FREQ_Hz_ms;
SysTime.us = CNT_TEMP3 / CPU_FREQ_Hz_us;
}
float DWT_GetTimeline_s(void)
{
DWT_SysTimeUpdate();
float DWT_Timelinef32 = SysTime.s + SysTime.ms * 0.001f + SysTime.us * 0.000001f;
return DWT_Timelinef32;
}
float DWT_GetTimeline_ms(void)
{
DWT_SysTimeUpdate();
float DWT_Timelinef32 = SysTime.s * 1000 + SysTime.ms + SysTime.us * 0.001f;
return DWT_Timelinef32;
}
uint64_t DWT_GetTimeline_us(void)
{
DWT_SysTimeUpdate();
uint64_t DWT_Timelinef32 = SysTime.s * 1000000 + SysTime.ms * 1000 + SysTime.us;
return DWT_Timelinef32;
}
void DWT_Delay(float Delay)
{
uint32_t tickstart = DWT->CYCCNT;
float wait = Delay;
while ((DWT->CYCCNT - tickstart) < wait * (float)CPU_FREQ_Hz)
;
}
+101
View File
@@ -0,0 +1,101 @@
/**
******************************************************************************
* @file bsp_dwt.h
* @author Wang Hongxi
* @author modified by NeoZng
* @version V1.2.0
* @date 2022/3/8
* @brief
******************************************************************************
* @attention
*
******************************************************************************
*/
#ifndef _BSP_DWT_H
#define _BSP_DWT_H
#include "main.h"
#include "stdint.h"
#include "bsp_log.h"
typedef struct
{
uint32_t s;
uint16_t ms;
uint16_t us;
} DWT_Time_t;
/**
* @brief 该宏用于计算代码段执行时间,单位为秒/s,返回值为float类型
* 首先需要创建一个float类型的变量,用于存储时间间隔
* 计算得到的时间间隔同时还会通过RTT打印到日志终端,你也可以将你的dt变量添加到查看
*/
#define TIME_ELAPSE(dt, code) \
do \
{ \
float tstart = DWT_GetTimeline_s(); \
code; \
dt = DWT_GetTimeline_s() - tstart; \
LOGINFO("[DWT] " #dt " = %f s\r\n", dt); \
} while (0)
/**
* @brief 初始化DWT,传入参数为CPU频率,单位MHz
*
* @param CPU_Freq_mHz c板为168MHz,A板为180MHz
*/
void DWT_Init(uint32_t CPU_Freq_mHz);
/**
* @brief 获取两次调用之间的时间间隔,单位为秒/s
*
* @param cnt_last 上一次调用的时间戳
* @return float 时间间隔,单位为秒/s
*/
float DWT_GetDeltaT(uint32_t *cnt_last);
/**
* @brief 获取两次调用之间的时间间隔,单位为秒/s,高精度
*
* @param cnt_last 上一次调用的时间戳
* @return double 时间间隔,单位为秒/s
*/
double DWT_GetDeltaT64(uint32_t *cnt_last);
/**
* @brief 获取当前时间,单位为秒/s,即初始化后的时间
*
* @return float 时间轴
*/
float DWT_GetTimeline_s(void);
/**
* @brief 获取当前时间,单位为毫秒/ms,即初始化后的时间
*
* @return float
*/
float DWT_GetTimeline_ms(void);
/**
* @brief 获取当前时间,单位为微秒/us,即初始化后的时间
*
* @return uint64_t
*/
uint64_t DWT_GetTimeline_us(void);
/**
* @brief DWT延时函数,单位为秒/s
* @attention 该函数不受中断是否开启的影响,可以在临界区和关闭中断时使用
* @note 禁止在__disable_irq()和__enable_irq()之间使用HAL_Delay()函数,应使用本函数
*
* @param Delay 延时时间,单位为秒/s
*/
void DWT_Delay(float Delay);
/**
* @brief DWT更新时间轴函数,会被三个timeline函数调用
* @attention 如果长时间不调用timeline函数,则需要手动调用该函数更新时间轴,否则CYCCNT溢出后定时和时间轴不准确
*/
void DWT_SysTimeUpdate(void);
#endif /* BSP_DWT_H_ */
+54
View File
@@ -0,0 +1,54 @@
# bsp_dwt
DWT是stm32内部的一个"隐藏资源",他的用途是给下载器提供准确的定时,从而为调试信息加上时间戳.并在固定的时间间隔将调试数据发送到你的xxlink上.
## 常用功能
### 计算两次进入同一个函数的时间间隔
```c
static uint32_t cnt;
float deltaT;
deltaT=DWT_GetDeltaT(&cnt);
```
### 计算执行某部分代码的耗时
```c
float start,end;
start=DWT_DetTimeline_ms();
// some proc to go...
for(uint8_t i=0;i<10;i++)
foo();
end = DWT_DetTimeline_ms()-start;
```
我们还提供了一个宏用于调试计时:
```c
#define TIME_ELAPSE(dt, code) \
do \
{ \
float tstart = DWT_GetTimeline_s(); \
code; \
dt = DWT_GetTimeline_s() - tstart; \
LOGINFO("[DWT] " #dt " = %f s\r\n", dt); \
} while (0)
```
传入一个float类型的变量,并将你要执行的代码写入第二个参数:
```c
static float my_func_dt;
TIME_ELAPSE(my_func_dt,
Function1(vara);
Function2(some, var);
Function3(your,param);
// something more
);
// my_func_dt can be used for other purpose then;
```
+289
View File
@@ -0,0 +1,289 @@
#include "bsp_flash.h"
#include "main.h"
#include "string.h"
static uint32_t ger_sector(uint32_t address);
/**
* @brief erase flash
* @param[in] address: flash address
* @param[in] len: page num
* @retval none
*/
/**
* @brief 擦除flash
* @param[in] address: flash 地址
* @param[in] len: 页数量
* @retval none
*/
void flash_erase_address(uint32_t address, uint16_t len)
{
FLASH_EraseInitTypeDef flash_erase;
uint32_t error;
flash_erase.Sector = ger_sector(address);
flash_erase.TypeErase = FLASH_TYPEERASE_SECTORS;
flash_erase.VoltageRange = FLASH_VOLTAGE_RANGE_3;
flash_erase.NbSectors = len;
HAL_FLASH_Unlock();
HAL_FLASHEx_Erase(&flash_erase, &error);
HAL_FLASH_Lock();
}
/**
* @brief write data to one page of flash
* @param[in] start_address: flash address
* @param[in] buf: data point
* @param[in] len: data num
* @retval success 0, fail -1
*/
/**
* @brief 往一页flash写数据
* @param[in] start_address: flash 地址
* @param[in] buf: 数据指针
* @param[in] len: 数据长度
* @retval success 0, fail -1
*/
int8_t flash_write_single_address(uint32_t start_address, uint32_t *buf, uint32_t len)
{
static uint32_t uw_address;
static uint32_t end_address;
static uint32_t *data_buf;
static uint32_t data_len;
HAL_FLASH_Unlock();
uw_address = start_address;
end_address = get_next_flash_address(start_address);
data_buf = buf;
data_len = 0;
while (uw_address <= end_address)
{
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD,uw_address, *data_buf) == HAL_OK)
{
uw_address += 4;
data_buf++;
data_len++;
if (data_len == len)
{
break;
}
}
else
{
HAL_FLASH_Lock();
return -1;
}
}
HAL_FLASH_Lock();
return 0;
}
/**
* @brief write data to some pages of flash
* @param[in] start_address: flash start address
* @param[in] end_address: flash end address
* @param[in] buf: data point
* @param[in] len: data num
* @retval success 0, fail -1
*/
/**
* @brief 往几页flash写数据
* @param[in] start_address: flash 开始地址
* @param[in] end_address: flash 结束地址
* @param[in] buf: 数据指针
* @param[in] len: 数据长度
* @retval success 0, fail -1
*/
int8_t flash_write_muli_address(uint32_t start_address, uint32_t end_address, uint32_t *buf, uint32_t len)
{
uint32_t uw_address = 0;
uint32_t *data_buf;
uint32_t data_len;
HAL_FLASH_Unlock();
uw_address = start_address;
data_buf = buf;
data_len = 0;
while (uw_address <= end_address)
{
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD,uw_address, *data_buf) == HAL_OK)
{
uw_address += 4;
data_buf++;
data_len++;
if (data_len == len)
{
break;
}
}
else
{
HAL_FLASH_Lock();
return -1;
}
}
HAL_FLASH_Lock();
return 0;
}
/**
* @brief read data for flash
* @param[in] address: flash address
* @param[out] buf: data point
* @param[in] len: data num
* @retval none
*/
/**
* @brief 从flash读数据
* @param[in] start_address: flash 地址
* @param[out] buf: 数据指针
* @param[in] len: 数据长度
* @retval none
*/
void flash_read(uint32_t address, uint32_t *buf, uint32_t len)
{
memcpy(buf, (void*)address, len *4);
}
/**
* @brief get the sector number of flash
* @param[in] address: flash address
* @retval sector number
*/
/**
* @brief 获取flash的sector号
* @param[in] address: flash 地址
* @retval sector号
*/
static uint32_t ger_sector(uint32_t address)
{
uint32_t sector = 0;
if ((address < ADDR_FLASH_SECTOR_1) && (address >= ADDR_FLASH_SECTOR_0))
{
sector = FLASH_SECTOR_0;
}
else if ((address < ADDR_FLASH_SECTOR_2) && (address >= ADDR_FLASH_SECTOR_1))
{
sector = FLASH_SECTOR_1;
}
else if ((address < ADDR_FLASH_SECTOR_3) && (address >= ADDR_FLASH_SECTOR_2))
{
sector = FLASH_SECTOR_2;
}
else if ((address < ADDR_FLASH_SECTOR_4) && (address >= ADDR_FLASH_SECTOR_3))
{
sector = FLASH_SECTOR_3;
}
else if ((address < ADDR_FLASH_SECTOR_5) && (address >= ADDR_FLASH_SECTOR_4))
{
sector = FLASH_SECTOR_4;
}
else if ((address < ADDR_FLASH_SECTOR_6) && (address >= ADDR_FLASH_SECTOR_5))
{
sector = FLASH_SECTOR_5;
}
else if ((address < ADDR_FLASH_SECTOR_7) && (address >= ADDR_FLASH_SECTOR_6))
{
sector = FLASH_SECTOR_6;
}
else if ((address < ADDR_FLASH_SECTOR_8) && (address >= ADDR_FLASH_SECTOR_7))
{
sector = FLASH_SECTOR_7;
}
else if ((address < ADDR_FLASH_SECTOR_9) && (address >= ADDR_FLASH_SECTOR_8))
{
sector = FLASH_SECTOR_8;
}
else if ((address < ADDR_FLASH_SECTOR_10) && (address >= ADDR_FLASH_SECTOR_9))
{
sector = FLASH_SECTOR_9;
}
else if ((address < ADDR_FLASH_SECTOR_11) && (address >= ADDR_FLASH_SECTOR_10))
{
sector = FLASH_SECTOR_10;
}
else if ((address < ADDR_FLASH_SECTOR_12) && (address >= ADDR_FLASH_SECTOR_11))
{
sector = FLASH_SECTOR_11;
}
else
{
sector = FLASH_SECTOR_11;
}
return sector;
}
/**
* @brief get the next page flash address
* @param[in] address: flash address
* @retval next page flash address
*/
/**
* @brief 获取下一页flash地址
* @param[in] address: flash 地址
* @retval 下一页flash地址
*/
uint32_t get_next_flash_address(uint32_t address)
{
uint32_t sector = 0;
if ((address < ADDR_FLASH_SECTOR_1) && (address >= ADDR_FLASH_SECTOR_0))
{
sector = ADDR_FLASH_SECTOR_1;
}
else if ((address < ADDR_FLASH_SECTOR_2) && (address >= ADDR_FLASH_SECTOR_1))
{
sector = ADDR_FLASH_SECTOR_2;
}
else if ((address < ADDR_FLASH_SECTOR_3) && (address >= ADDR_FLASH_SECTOR_2))
{
sector = ADDR_FLASH_SECTOR_3;
}
else if ((address < ADDR_FLASH_SECTOR_4) && (address >= ADDR_FLASH_SECTOR_3))
{
sector = ADDR_FLASH_SECTOR_4;
}
else if ((address < ADDR_FLASH_SECTOR_5) && (address >= ADDR_FLASH_SECTOR_4))
{
sector = ADDR_FLASH_SECTOR_5;
}
else if ((address < ADDR_FLASH_SECTOR_6) && (address >= ADDR_FLASH_SECTOR_5))
{
sector = ADDR_FLASH_SECTOR_6;
}
else if ((address < ADDR_FLASH_SECTOR_7) && (address >= ADDR_FLASH_SECTOR_6))
{
sector = ADDR_FLASH_SECTOR_7;
}
else if ((address < ADDR_FLASH_SECTOR_8) && (address >= ADDR_FLASH_SECTOR_7))
{
sector = ADDR_FLASH_SECTOR_8;
}
else if ((address < ADDR_FLASH_SECTOR_9) && (address >= ADDR_FLASH_SECTOR_8))
{
sector = ADDR_FLASH_SECTOR_9;
}
else if ((address < ADDR_FLASH_SECTOR_10) && (address >= ADDR_FLASH_SECTOR_9))
{
sector = ADDR_FLASH_SECTOR_10;
}
else if ((address < ADDR_FLASH_SECTOR_11) && (address >= ADDR_FLASH_SECTOR_10))
{
sector = ADDR_FLASH_SECTOR_11;
}
else /*(address < FLASH_END_ADDR) && (address >= ADDR_FLASH_SECTOR_23))*/
{
sector = FLASH_END_ADDR;
}
return sector;
}
+80
View File
@@ -0,0 +1,80 @@
#ifndef _BSP_FLASH_H
#define _BSP_FLASH_H
#include "main.h"
/* Base address of the Flash sectors */
#define ADDR_FLASH_SECTOR_0 ((uint32_t)0x08000000) /* Base address of Sector 0, 16 Kbytes */
#define ADDR_FLASH_SECTOR_1 ((uint32_t)0x08004000) /* Base address of Sector 1, 16 Kbytes */
#define ADDR_FLASH_SECTOR_2 ((uint32_t)0x08008000) /* Base address of Sector 2, 16 Kbytes */
#define ADDR_FLASH_SECTOR_3 ((uint32_t)0x0800C000) /* Base address of Sector 3, 16 Kbytes */
#define ADDR_FLASH_SECTOR_4 ((uint32_t)0x08010000) /* Base address of Sector 4, 64 Kbytes */
#define ADDR_FLASH_SECTOR_5 ((uint32_t)0x08020000) /* Base address of Sector 5, 128 Kbytes */
#define ADDR_FLASH_SECTOR_6 ((uint32_t)0x08040000) /* Base address of Sector 6, 128 Kbytes */
#define ADDR_FLASH_SECTOR_7 ((uint32_t)0x08060000) /* Base address of Sector 7, 128 Kbytes */
#define ADDR_FLASH_SECTOR_8 ((uint32_t)0x08080000) /* Base address of Sector 8, 128 Kbytes */
#define ADDR_FLASH_SECTOR_9 ((uint32_t)0x080A0000) /* Base address of Sector 9, 128 Kbytes */
#define ADDR_FLASH_SECTOR_10 ((uint32_t)0x080C0000) /* Base address of Sector 10, 128 Kbytes */
#define ADDR_FLASH_SECTOR_11 ((uint32_t)0x080E0000) /* Base address of Sector 11, 128 Kbytes */
#define FLASH_END_ADDR ((uint32_t)0x08100000) /* Base address of Sector 23, 128 Kbytes */
#define ADDR_FLASH_SECTOR_12 ((uint32_t)0x08100000) /* Base address of Sector 12, 16 Kbytes */
#define ADDR_FLASH_SECTOR_13 ((uint32_t)0x08104000) /* Base address of Sector 13, 16 Kbytes */
#define ADDR_FLASH_SECTOR_14 ((uint32_t)0x08108000) /* Base address of Sector 14, 16 Kbytes */
#define ADDR_FLASH_SECTOR_15 ((uint32_t)0x0810C000) /* Base address of Sector 15, 16 Kbytes */
#define ADDR_FLASH_SECTOR_16 ((uint32_t)0x08110000) /* Base address of Sector 16, 64 Kbytes */
#define ADDR_FLASH_SECTOR_17 ((uint32_t)0x08120000) /* Base address of Sector 17, 128 Kbytes */
#define ADDR_FLASH_SECTOR_18 ((uint32_t)0x08140000) /* Base address of Sector 18, 128 Kbytes */
#define ADDR_FLASH_SECTOR_19 ((uint32_t)0x08160000) /* Base address of Sector 19, 128 Kbytes */
#define ADDR_FLASH_SECTOR_20 ((uint32_t)0x08180000) /* Base address of Sector 20, 128 Kbytes */
#define ADDR_FLASH_SECTOR_21 ((uint32_t)0x081A0000) /* Base address of Sector 21, 128 Kbytes */
#define ADDR_FLASH_SECTOR_22 ((uint32_t)0x081C0000) /* Base address of Sector 22, 128 Kbytes */
#define ADDR_FLASH_SECTOR_23 ((uint32_t)0x081E0000) /* Base address of Sector 23, 128 Kbytes */
/**
* @brief erase flash
* @param[in] address: flash address
* @param[in] len: page num
* @retval none
*/
void flash_erase_address(uint32_t address, uint16_t len);
/**
* @brief write data to one page of flash
* @param[in] start_address: flash address
* @param[in] buf: data point
* @param[in] len: data num
* @retval success 0, fail -1
*/
int8_t flash_write_single_address(uint32_t start_address, uint32_t *buf, uint32_t len);
/**
* @brief write data to some pages of flash
* @param[in] start_address: flash start address
* @param[in] end_address: flash end address
* @param[in] buf: data point
* @param[in] len: data num
* @retval success 0, fail -1
*/
int8_t flash_write_muli_address(uint32_t start_address, uint32_t end_address, uint32_t *buf, uint32_t len);
/**
* @brief read data for flash
* @param[in] address: flash address
* @param[out] buf: data point
* @param[in] len: data num
* @retval none
*/
void flash_read(uint32_t address, uint32_t *buf, uint32_t len);
/**
* @brief get the next page flash address
* @param[in] address: flash address
* @retval next page flash address
*/
uint32_t get_next_flash_address(uint32_t address);
#endif
View File
+66
View File
@@ -0,0 +1,66 @@
#include "bsp_gpio.h"
#include "memory.h"
#include "stdlib.h"
static uint8_t idx;
static GPIOInstance *gpio_instance[GPIO_MX_DEVICE_NUM] = {NULL};
/**
* @brief EXTI中断回调函数,根据GPIO_Pin找到对应的GPIOInstance,并调用模块回调函数(如果有)
* @note 如何判断具体是哪一个GPIO的引脚连接到这个EXTI中断线上?
* 一个EXTI中断线只能连接一个GPIO引脚,因此可以通过GPIO_Pin来判断,PinX对应EXTIX
* 一个Pin号只会对应一个EXTI,详情见gpio.md
* @param GPIO_Pin 发生中断的GPIO_Pin
*/
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
// 如有必要,可以根据pinstate和HAL_GPIO_ReadPin来判断是上升沿还是下降沿/rise&fall等
GPIOInstance *gpio;
for (size_t i = 0; i < idx; i++)
{
gpio = gpio_instance[i];
if (gpio->GPIO_Pin == GPIO_Pin && gpio->gpio_model_callback != NULL)
{
gpio->gpio_model_callback(gpio);
return;
}
}
}
GPIOInstance *GPIORegister(GPIO_Init_Config_s *GPIO_config)
{
GPIOInstance *ins = (GPIOInstance *)malloc(sizeof(GPIOInstance));
memset(ins, 0, sizeof(GPIOInstance));
ins->GPIOx = GPIO_config->GPIOx;
ins->GPIO_Pin = GPIO_config->GPIO_Pin;
ins->pin_state = GPIO_config->pin_state;
ins->exti_mode = GPIO_config->exti_mode;
ins->id = GPIO_config->id;
ins->gpio_model_callback = GPIO_config->gpio_model_callback;
gpio_instance[idx++] = ins;
return ins;
}
// ----------------- GPIO API -----------------
// 都是对HAL的形式上的封装,后续考虑增加GPIO state变量,可以直接读取state
void GPIOToggel(GPIOInstance *_instance)
{
HAL_GPIO_TogglePin(_instance->GPIOx, _instance->GPIO_Pin);
}
void GPIOSet(GPIOInstance *_instance)
{
HAL_GPIO_WritePin(_instance->GPIOx, _instance->GPIO_Pin, GPIO_PIN_SET);
}
void GPIOReset(GPIOInstance *_instance)
{
HAL_GPIO_WritePin(_instance->GPIOx, _instance->GPIO_Pin, GPIO_PIN_RESET);
}
GPIO_PinState GPIORead(GPIOInstance *_instance)
{
return HAL_GPIO_ReadPin(_instance->GPIOx, _instance->GPIO_Pin);
}
+87
View File
@@ -0,0 +1,87 @@
#include "gpio.h"
#include "stdint.h"
#define GPIO_MX_DEVICE_NUM 10
/**
* @brief 用于判断中断来源,注意和CUBEMX中配置一致
*
*/
typedef enum
{
GPIO_EXTI_MODE_RISING,
GPIO_EXTI_MODE_FALLING,
GPIO_EXTI_MODE_RISING_FALLING,
GPIO_EXTI_MODE_NONE,
} GPIO_EXTI_MODE_e;
/**
* @brief GPIO实例结构体定义
*
*/
typedef struct tmpgpio
{
GPIO_TypeDef *GPIOx; // GPIOA,GPIOB,GPIOC...
GPIO_PinState pin_state; // 引脚状态,Set,Reset;not frequently used
GPIO_EXTI_MODE_e exti_mode; // 外部中断模式 not frequently used
uint16_t GPIO_Pin; // 引脚号,
// 这些引脚是stm32f4xx_hal_gpio.h中定义的宏!!! 一定要注意
// 随便取个名字当临时声明
void (*gpio_model_callback)(struct tmpgpio *); // exti中断回调函数
void *id; // 区分不同的GPIO实例
} GPIOInstance;
/**
* @brief GPIO初始化配置结构体定义
*
*/
typedef struct
{
GPIO_TypeDef *GPIOx; // GPIOA,GPIOB,GPIOC...
GPIO_PinState pin_state; // 引脚状态,Set,Reset not frequently used
GPIO_EXTI_MODE_e exti_mode; // 外部中断模式 not frequently used
uint16_t GPIO_Pin; // 引脚号,@note 这里的引脚号是GPIO_PIN_0,GPIO_PIN_1...
// 这些引脚是stm32f4xx_hal_gpio.h中定义的宏!!! 一定要注意
void (*gpio_model_callback)(GPIOInstance *); // exti中断回调函数
void *id; // 区分不同的GPIO实例
} GPIO_Init_Config_s;
/**
* @brief 注册GPIO实例
*
* @param GPIO_config
* @return GPIOInstance*
*/
GPIOInstance *GPIORegister(GPIO_Init_Config_s *GPIO_config);
/**
* @brief GPIO API,切换GPIO电平
*
* @param _instance
*/
void GPIOToggel(GPIOInstance *_instance);
/**
* @brief 设置GPIO电平
*
* @param _instance
*/
void GPIOSet(GPIOInstance *_instance);
/**
* @brief 复位GPIO电平
*
* @param _instance
*/
void GPIOReset(GPIOInstance *_instance);
/**
* @brief 读取GPIO电平
*
* @param _instance
* @return GPIO_PinState
*/
GPIO_PinState GPIORead(GPIOInstance *_instance);
+23
View File
@@ -0,0 +1,23 @@
可以作为io接口,也可以处理外部中断.
![image-20230202151939109](../../.assets/image-20230202151939109.png)
![img](../../.assets/00937839b59a4c039ee8ecb8a5136e3c.png)
使用示例
```c
//在app层只需要设置前三个,callback由module自动设置
GPIO_Init_Config_s gpio_init = {
.exti_mode = GPIO_EXTI_MODE_FALLING, // 注意和CUBEMX的配置一致
.GPIO_Pin = GPIO_PIN_6, // GPIO引脚
.GPIOx = GPIOG, // GPIO外设
.gpio_model_callback = NULL, // EXTI回调函数
},
GPIOInstance* test_example = GPIORegister(&gpio_init);
GPIOSet(test_example);
// GPIOxxx(test_exmaple, ...);
```
+151
View File
@@ -0,0 +1,151 @@
#include "bsp_iic.h"
#include "memory.h"
#include "stdlib.h"
static uint8_t idx = 0; // 配合中断以及初始化
static IICInstance *iic_instance[IIC_DEVICE_CNT] = {NULL};
IICInstance *IICRegister(IIC_Init_Config_s *conf)
{
if (idx >= MX_IIC_SLAVE_CNT) // 超过最大实例数
while (1) // 酌情增加允许的实例上限,也有可能是内存泄漏
;
// 申请到的空间未必是0, 所以需要手动初始化
IICInstance *instance = (IICInstance *)malloc(sizeof(IICInstance));
instance = (IICInstance *)malloc(sizeof(IICInstance));
memset(instance, 0, sizeof(IICInstance));
instance->dev_address = conf->dev_address << 1; // 地址左移一位,最低位为读写位
instance->callback = conf->callback;
instance->work_mode = conf->work_mode;
instance->handle = conf->handle;
instance->id = conf->id;
iic_instance[idx++] = instance;
return instance;
}
void IICSetMode(IICInstance *iic, IIC_Work_Mode_e mode)
{ // HAL自带重入保护,不需要手动终止或等待传输完成
if (iic->work_mode != mode)
{
iic->work_mode = mode; // 如果不同才需要修改
}
}
void IICTransmit(IICInstance *iic, uint8_t *data, uint16_t size, IIC_Seq_Mode_e seq_mode)
{
if (seq_mode != IIC_SEQ_RELEASE && seq_mode != IIC_SEQ_HOLDON)
while (1)
; // 未知传输模式, 程序停止
// 根据不同的工作模式进行不同的传输
switch (iic->work_mode)
{
case IIC_BLOCK_MODE:
if (seq_mode != IIC_SEQ_RELEASE)
while (1)
; // 阻塞模式下不支持HOLD ON模式!!!只能传输完成后立刻释放总线
HAL_I2C_Master_Transmit(iic->handle, iic->dev_address, data, size, 100); // 默认超时时间100ms
break;
case IIC_IT_MODE:
if (seq_mode == IIC_SEQ_RELEASE)
HAL_I2C_Master_Seq_Transmit_IT(iic->handle, iic->dev_address, data, size, I2C_OTHER_AND_LAST_FRAME);
else if (seq_mode == IIC_SEQ_HOLDON)
HAL_I2C_Master_Seq_Transmit_IT(iic->handle, iic->dev_address, data, size, I2C_OTHER_FRAME);
break;
case IIC_DMA_MODE:
if (seq_mode == IIC_SEQ_RELEASE)
HAL_I2C_Master_Seq_Transmit_DMA(iic->handle, iic->dev_address, data, size, I2C_OTHER_AND_LAST_FRAME);
else if (seq_mode == IIC_SEQ_HOLDON)
HAL_I2C_Master_Seq_Transmit_DMA(iic->handle, iic->dev_address, data, size, I2C_OTHER_FRAME);
break;
default:
while (1)
; // 未知传输模式, 程序停止
}
}
void IICReceive(IICInstance *iic, uint8_t *data, uint16_t size, IIC_Seq_Mode_e seq_mode)
{
if (seq_mode != IIC_SEQ_RELEASE && seq_mode != IIC_SEQ_HOLDON)
while (1)
; // 未知传输模式, 程序停止,请检查指针越界
// 初始化接收缓冲区地址以及接受长度, 用于中断回调函数
iic->rx_buffer = data;
iic->rx_len = size;
switch (iic->work_mode)
{
case IIC_BLOCK_MODE:
if (seq_mode != IIC_SEQ_RELEASE)
while (1)
; // 阻塞模式下不支持HOLD ON模式!!!
HAL_I2C_Master_Receive(iic->handle, iic->dev_address, data, size, 100); // 默认超时时间100ms
break;
case IIC_IT_MODE:
if (seq_mode == IIC_SEQ_RELEASE)
HAL_I2C_Master_Seq_Receive_IT(iic->handle, iic->dev_address, data, size, I2C_OTHER_AND_LAST_FRAME);
else if (seq_mode == IIC_SEQ_HOLDON)
HAL_I2C_Master_Seq_Receive_IT(iic->handle, iic->dev_address, data, size, I2C_OTHER_FRAME);
break;
case IIC_DMA_MODE:
if (seq_mode == IIC_SEQ_RELEASE)
HAL_I2C_Master_Seq_Receive_DMA(iic->handle, iic->dev_address, data, size, I2C_OTHER_AND_LAST_FRAME);
else if (seq_mode == IIC_SEQ_HOLDON)
HAL_I2C_Master_Seq_Receive_DMA(iic->handle, iic->dev_address, data, size, I2C_OTHER_FRAME);
break;
default:
while (1)
; // 未知传输模式, 程序停止
break;
}
}
void IICAccessMem(IICInstance *iic, uint16_t mem_addr, uint8_t *data, uint16_t size, IIC_Mem_Mode_e mem_mode, uint8_t mem8bit_flag)
{
uint16_t bit_flag = mem8bit_flag ? I2C_MEMADD_SIZE_8BIT : I2C_MEMADD_SIZE_16BIT;
if (mem_mode == IIC_WRITE_MEM)
{
HAL_I2C_Mem_Write(iic->handle, iic->dev_address, mem_addr, bit_flag, data, size, 100);
}
else if (mem_mode == IIC_READ_MEM)
{
HAL_I2C_Mem_Read(iic->handle, iic->dev_address, mem_addr, bit_flag, data, size, 100);
}
else
{
while (1)
; // 未知模式, 程序停止
}
}
/**
* @brief IIC接收完成回调函数
*
* @param hi2c handle
*/
void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef *hi2c)
{
// 如果是当前i2c硬件发出的complete,且dev_address和之前发起接收的地址相同,同时回到函数不为空, 则调用回调函数
for (uint8_t i = 0; i < idx; i++)
{
if (iic_instance[i]->handle == hi2c && hi2c->Devaddress == iic_instance[i]->dev_address)
{
if (iic_instance[i]->callback != NULL) // 回调函数不为空
iic_instance[i]->callback(iic_instance[i]);
return;
}
}
}
/**
* @brief 内存访问回调函数,仅做形式上的封装,仍然使用HAL_I2C_MasterRxCpltCallback
*
* @param hi2c handle
*/
void HAL_I2C_MemRxCpltCallback(I2C_HandleTypeDef *hi2c)
{
HAL_I2C_MasterRxCpltCallback(hi2c);
}
+106
View File
@@ -0,0 +1,106 @@
#include "i2c.h"
#include "stdint.h"
#define IIC_DEVICE_CNT 1 // MC02只有1个I2C接口可用,
#define MX_IIC_SLAVE_CNT 8 // 最大从机数目,根据需要修改
/* i2c 工作模式枚举 */
typedef enum
{
// 基本工作模式
IIC_BLOCK_MODE = 0, // 阻塞模式
IIC_IT_MODE, // 中断模式
IIC_DMA_MODE, // DMA模式
} IIC_Work_Mode_e;
/* I2C MEM工作模式枚举,这两种方法都是阻塞 */
typedef enum
{
IIC_READ_MEM = 0, // 读取从机内部的寄存器or内存
IIC_WRITE_MEM, // 写入从机内部的寄存器or内存
} IIC_Mem_Mode_e;
/* Seq传输工作模式枚举,注意HOLD_ON要在IT或DMA下使用 */
// 必须以IIC_RELEASE为最后一次传输,否则会导致总线占有权无法释放
typedef enum
{
IIC_SEQ_RELEASE, // 完成传输后释放总线占有权,这是默认的传输方式
IIC_SEQ_HOLDON = 0, // 保持总线占有权不释放,只支持IT和DMA模式
} IIC_Seq_Mode_e;
/* i2c实例 */
typedef struct iic_temp_s
{
I2C_HandleTypeDef *handle; // i2c handle
uint8_t dev_address; // 暂时只支持7位地址(还有一位是读写位)
IIC_Work_Mode_e work_mode; // 工作模式
uint8_t *rx_buffer; // 接收缓冲区指针
uint8_t rx_len; // 接收长度
void (*callback)(struct iic_temp_s *); // 接收完成后的回调函数
void *id; // 用于标识i2c instance
} IICInstance;
/* I2C 初始化结构体配置 */
typedef struct
{
I2C_HandleTypeDef *handle; // i2c handle
uint8_t dev_address; // 暂时只支持7位地址(还有一位是读写位),注意不需要左移
IIC_Work_Mode_e work_mode; // 工作模式
void (*callback)(IICInstance *); // 接收完成后的回调函数
void *id; // 用于标识i2c instance
} IIC_Init_Config_s;
/**
* @brief IIC初始化
*
* @param conf 初始化配置
* @return IICInstance*
*/
IICInstance *IICRegister(IIC_Init_Config_s *conf);
/**
* @brief IIC设置工作模式
*
* @param iic 要设置的iic实例
* @param mode 工作模式
*/
void IICSetMode(IICInstance *iic, IIC_Work_Mode_e mode);
/**
* @brief IIC发送数据
*
* @param iic iic实例
* @param data 待发送的数据首地址指针
* @param size 发送长度
* @param mode 序列传输模式
* @note 注意,如果发送结构体,那么该结构体在声明时务必使用#pragma pack(1)进行对齐,并在声明结束后使用#pragma pack()恢复对齐
*
*/
void IICTransmit(IICInstance *iic, uint8_t *data, uint16_t size, IIC_Seq_Mode_e mode);
/**
* @brief IIC接收数据
*
* @param iic iic实例
* @param data 接收数据的首地址指针
* @param size 接收长度
* @param mode 序列传输模式
* @note 注意,如果直接将接收数据memcpy到目标结构体或通过强制类型转换进行逐字节写入,
* 那么该结构体在声明时务必使用#pragma pack(1)进行对齐,并在声明结束后使用#pragma pack()恢复对齐
*/
void IICReceive(IICInstance *iic, uint8_t *data, uint16_t size, IIC_Seq_Mode_e mode);
/**
* @brief IIC读取从机寄存器(内存),只支持阻塞模式,超时默认为1ms
*
* @param iic iic实例
* @param mem_addr 要读取的从机内存地址,目前只支持8位地址
* @param data 要读取或写入的数据首地址指针
* @param size 要读取或写入的数据长度
* @param mode 写入或读取模式: IIC_READ_MEM or IIC_WRITE_MEM
* @param mem8bit_flag 从机内存地址是否为8位
*/
void IICAccessMem(IICInstance *iic, uint16_t mem_addr, uint8_t *data, uint16_t size, IIC_Mem_Mode_e mode, uint8_t mem8bit_flag);
+19
View File
@@ -0,0 +1,19 @@
# bsp iic
> 预计增加模拟iic
## 注意事项
使用时写入地址,不需要左移!!!
cubemx未配置dma时请勿使用dma传输,其行为是未定义的。
## 总线机制详解
关于I2C的序列传输,Restart condition和总线仲裁,请看:
https://blog.csdn.net/NeoZng/article/details/128496694
https://blog.csdn.net/NeoZng/article/details/128486366
使用序列通信则在单次通信后不会释放总线,继续占用直到调用传输函数时传入`IIC_RELEASE`参数. 这个功能只在一条总线上挂载多个主机的时候有用.
+34
View File
@@ -0,0 +1,34 @@
#include "bsp_log.h"
#include "SEGGER_RTT.h"
#include "SEGGER_RTT_Conf.h"
#include <stdio.h>
void BSPLogInit()
{
SEGGER_RTT_Init();
}
int PrintLog(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
int n = SEGGER_RTT_vprintf(BUFFER_INDEX, fmt, &args); // 一次可以开启多个buffer(多个终端),我们只用一个
va_end(args);
return n;
}
void Float2Str(char *str, float va)
{
int flag = va < 0;
int head = (int)va;
int point = (int)((va - head) * 1000);
head = abs(head);
point = abs(point);
if (flag)
sprintf(str, "-%d.%d", head, point);
else
sprintf(str, "%d.%d", head, point);
}
+72
View File
@@ -0,0 +1,72 @@
#ifndef _BSP_LOG_H
#define _BSP_LOG_H
#include "SEGGER_RTT.h"
#include "SEGGER_RTT_Conf.h"
#include <stdio.h>
#define BUFFER_INDEX 0
/**
* @brief 日志系统初始化
*
*/
void BSPLogInit();
/**
* @brief 日志功能原型,供下面的LOGI,LOGW,LOGE等使用
*
*/
#define LOG_PROTO(type, color, format, ...) \
SEGGER_RTT_printf(BUFFER_INDEX, " %s%s" format "\r\n%s", \
color, \
type, \
##__VA_ARGS__, \
RTT_CTRL_RESET)
/*----------------------------------------下面是日志输出的接口-------------------------------------------------*/
/* 清屏 */
#define LOG_CLEAR() SEGGER_RTT_WriteString(0, " " RTT_CTRL_CLEAR)
/* 无颜色日志输出 */
#define LOG(format, ...) LOG_PROTO("", "", format, ##__VA_ARGS__)
/**
* 有颜色格式日志输出,建议使用这些宏来输出日志
* @attention 注意这些接口不支持浮点格式化输出,若有需要,请使用Float2Str()函数进行转换后再打印
* @note 在release版本上车使用时,与makefile中添加的宏DISABLE_LOG_SYSTEM一起使用,可以关闭日志系统
*/
#if DISABLE_LOG_SYSTEM
#define LOGINFO(format, ...)
#define LOGWARNING(format, ...)
#define LOGERROR(format, ...)
#else
// information level
#define LOGINFO(format, ...) LOG_PROTO("I:", RTT_CTRL_TEXT_BRIGHT_GREEN, format, ##__VA_ARGS__)
// warning level
#define LOGWARNING(format, ...) LOG_PROTO("W:", RTT_CTRL_TEXT_BRIGHT_YELLOW, format, ##__VA_ARGS__)
// error level
#define LOGERROR(format, ...) LOG_PROTO("E:", RTT_CTRL_TEXT_BRIGHT_RED, format, ##__VA_ARGS__)
#endif // DISABLE_LOG_SYSTEM
/**
* @brief 通过segger RTT打印日志,支持格式化输出,格式化输出的实现参考printf.
* @attention !! 此函数不支持浮点格式化,若有需要,请使用Float2Str()函数进行转换后再打印 !!
*
* @param fmt 格式字符串
* @param ... 参数列表
* @return int 打印的log字符数
*/
int PrintLog(const char *fmt, ...);
/**
* @brief 利用sprintf(),将float转换为字符串进行打印
* @attention 浮点数需要转换为字符串后才能通过RTT打印
*
* @param str 转换后的字符串
* @param va 待转换的float
*/
void Float2Str(char *str, float va);
#endif
+50
View File
@@ -0,0 +1,50 @@
# bsp_log
<p align='right'>neozng1@hnu.edu.cn</p>
## 使用说明
bsp_log是基于segger RTT实现的日志打印模块。
推荐使用`bsp_log.h`中提供了三级日志:
```c
#define LOGINFO(format,...)
#define LOGWARNING(format,...)
#define LOGERROR(format,...)
```
分别用于输出不同等级的日志。注意RTT不支持直接使用`%f`进行浮点格式化,要使用`void Float2Str(char *str, float va);`转化成字符串之后再发送。
**若想启用RTT,必须通过`launch.json`的`debug-jlink`启动调试(不论使用什么调试器)。** 按照`VSCode+Ozone环境配置`完成配置之后的cmsis dap和daplink是可以支持Jlink全家桶的。
另外,若你使用的是cmsis-dap和daplink,**请在 *jlink* 调试任务启动之后再打开`log`任务。**(均在项目文件夹下的.vsocde/task.json中,有注释自行查看)。否则可能出线RTT viewer无法连接客户端的情况。
在ozone中查看log输出,直接打开console调试任务台和terminal调试中断便可看到调试输出。
> 由于ozone版本的原因,可能出现日志不换行或没有颜色。
## 自定义输出
你也可以自定义输出格式,详见Segger RTT的文档。
```c
int printf_log(const char *fmt, ...);
void Float2Str(char *str, float va); // 输出浮点需要先用此函数进行转换
```
调用第一个函数,可以通过jlink或dap-link向调试器连接的上位机发送信息,格式和printf相同,示例如下:
```c
printf_log("Hello World!\n");
printf_log("Motor %d met some problem, error code %d!\n",3,1);
```
第二个函数可以将浮点类型转换成字符串以方便发送:
```c
float current_feedback=114.514;
char* str_buff[64];
Float2Str(str_buff,current_feedback);
printf_log("Motor %d met some problem, error code %d!\n",3,1);
```
+107
View File
@@ -0,0 +1,107 @@
#include "bsp_pwm.h"
#include "stdlib.h"
#include "memory.h"
// 配合中断以及初始化
static uint8_t idx;
static PWMInstance *pwm_instance[PWM_DEVICE_CNT] = {NULL}; // 所有的pwm instance保存于此,用于callback时判断中断来源
static uint32_t PWMSelectTclk(TIM_HandleTypeDef *htim );
/**
* @brief pwm dma传输完成回调函数
*
* @param htim 发生中断的定时器句柄
*/
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
{
for (uint8_t i = 0; i < idx; i++)
{ // 来自同一个定时器的中断且通道相同
if (pwm_instance[i]->htim == htim && htim->Channel == (1<<(pwm_instance[i]->channel/4)))
{
if (pwm_instance[i]->callback) // 如果有回调函数
pwm_instance[i]->callback(pwm_instance[i]);
return; // 一次只能有一个通道的中断,所以直接返回
}
}
}
PWMInstance *PWMRegister(PWM_Init_Config_s *config)
{
if (idx >= PWM_DEVICE_CNT) // 超过最大实例数,考虑增加或查看是否有内存泄漏
while (1)
;
PWMInstance *pwm = (PWMInstance *)malloc(sizeof(PWMInstance));
memset(pwm, 0, sizeof(PWMInstance));
pwm->htim = config->htim;
pwm->channel = config->channel;
pwm->period = config->period;
pwm->dutyratio = config->dutyratio;
pwm->callback = config->callback;
pwm->id = config->id;
pwm->tclk = PWMSelectTclk(pwm->htim);
// 启动PWM
HAL_TIM_PWM_Start(pwm->htim, pwm->channel);
PWMSetPeriod(pwm, pwm->period);
PWMSetDutyRatio(pwm, pwm->dutyratio);
pwm_instance[idx++] = pwm;
return pwm;
}
/* 只是对HAL的函数进行了形式上的封装 */
void PWMStart(PWMInstance *pwm)
{
HAL_TIM_PWM_Start(pwm->htim, pwm->channel);
}
/* 只是对HAL的函数进行了形式上的封装 */
void PWMStop(PWMInstance *pwm)
{
HAL_TIM_PWM_Stop(pwm->htim, pwm->channel);
}
/*
* @brief 设置pwm周期
*
* @param pwm pwm实例
* @param period 周期 单位 s
*/
void PWMSetPeriod(PWMInstance *pwm, float period)
{
__HAL_TIM_SetAutoreload(pwm->htim, period*((pwm->tclk)/(pwm->htim->Init.Prescaler+1)));
}
/*
* @brief 设置pwm占空比
*
* @param pwm pwm实例
* @param dutyratio 占空比 0~1
*/
void PWMSetDutyRatio(PWMInstance *pwm, float dutyratio)
{
__HAL_TIM_SetCompare(pwm->htim, pwm->channel, dutyratio * (pwm->htim->Instance->ARR));
}
/* 只是对HAL的函数进行了形式上的封装 */
void PWMStartDMA(PWMInstance *pwm, uint32_t *pData, uint32_t Size)
{
HAL_TIM_PWM_Start_DMA(pwm->htim, pwm->channel, pData, Size);
}
// 设置pwm对应定时器时钟源频率
static uint32_t PWMSelectTclk(TIM_HandleTypeDef *htim)
{
uintptr_t tclk_temp = ((uintptr_t)((htim)->Instance));
// 判断是否为 APB1 定时器 (TIM2~TIM7, TIM12~TIM14)
if ((tclk_temp >= (APB1PERIPH_BASE + 0x0000UL)) &&
(tclk_temp <= (APB1PERIPH_BASE + 0x83FFUL))) // 根据芯片参考手册调整范围
{
return (HAL_RCC_GetPCLK1Freq() * ((RCC->D1CFGR & RCC_D1CFGR_HPRE) == 0 ? 1 : 2));
}
// 判断是否为 APB2 定时器 (TIM1, TIM8)
else if ((tclk_temp >= (APB2PERIPH_BASE + 0x0000UL)) &&
(tclk_temp <= (APB2PERIPH_BASE + 0x03FFUL))) // 根据芯片参考手册调整范围
{
return (HAL_RCC_GetPCLK2Freq() * ((RCC->D1CFGR & RCC_D1CFGR_HPRE) == 0 ? 1 : 2));
}
return 0;
}
+91
View File
@@ -0,0 +1,91 @@
/**
* @file bsp_pwm.h
* @author your name (you@domain.com)
* @brief
* @version 0.1
* @date 2023-02-14
*
* @copyright Copyright (c) 2023
*
*/
#ifndef BSP_PWM_H
#define BSP_PWM_H
#include "tim.h"
#include "stdint.h"
#include "stm32h7xx_hal_rcc.h"
#include "stm32h723xx.h"
#define PWM_DEVICE_CNT 16 // 最大支持的PWM实例数量
/* pwm实例结构体 */
typedef struct pwm_ins_temp
{
TIM_HandleTypeDef *htim; // TIM句柄
uint32_t channel; // 通道
uint32_t tclk; // 时钟频率
float period; // 周期
float dutyratio; // 占空比
void (*callback)(struct pwm_ins_temp *); // DMA传输完成回调函数
void *id; // 实例ID
} PWMInstance;
typedef struct
{
TIM_HandleTypeDef *htim; // TIM句柄
uint32_t channel; // 通道
float period; // 周期
float dutyratio; // 占空比
void (*callback)(PWMInstance*); // DMA传输完成回调函数
void *id; // 实例ID
} PWM_Init_Config_s;
/**
* @brief 注册一个pwm实例
*
* @param config 初始化配置
* @return PWMInstance*
*/
PWMInstance *PWMRegister(PWM_Init_Config_s *config);
/**
* @brief 启动pwm
*
* @param pwm pwm实例
*/
void PWMStart(PWMInstance *pwm);
/**
* @brief 设置pwm占空比
*
* @param pwm pwm实例
* @param dutyratio 占空比 0~1
*/
void PWMSetDutyRatio(PWMInstance *pwm, float dutyratio);
/**
* @brief 停止pwm
*
* @param pwm pwm实例
*/
void PWMStop(PWMInstance *pwm);
/**
* @brief 设置pwm周期
*
* @param pwm pwm实例
* @param period 周期 单位 s
*/
void PWMSetPeriod(PWMInstance *pwm, float period);
/**
* @brief 启动pwm dma传输
*
* @param pwm pwm实例
* @param pData 数据首地址指针,注意数据的位数必须和CubeMX配置的DMA传输位数(字长)一致
* @param Size 数据长度
* @note 如果使用此函数,则需要在CubeMX中配置DMA传输位数为对应位数
* 例如:使用16位数据,则需要配置DMA传输位数为16位(half word),配置错误会导致指针越界或数据错误
*/
void PWMStartDMA(PWMInstance *pwm, uint32_t *pData, uint32_t Size);
#endif // BSP_PWM_H
+5
View File
@@ -0,0 +1,5 @@
# bsp pwm
同一定时器下的pwm通道更改周期时请注意该定时器下是否有其他pwm示例,如果有请注意不要影响其他pwm通道的周期。
使用pwm dma传输中断时注意占空比的设置,设为0将不会进入中断函数
默认tim psc已在cubemx设置好,详情可参考cubemx配置文件
+185
View File
@@ -0,0 +1,185 @@
#include "bsp_spi.h"
#include "memory.h"
#include "stdlib.h"
/* 所有的spi instance保存于此,用于callback时判断中断来源*/
static SPIInstance *spi_instance[SPI_DEVICE_CNT] = {NULL};
static uint8_t idx = 0; // 配合中断以及初始化
uint8_t SPIDeviceOnGoing[SPI_DEVICE_CNT] = {1}; // 用于判断当前spi是否正在传输,防止多个模块同时使用一个spi总线 (0: 正在传输, 1: 未传输)
SPIInstance *SPIRegister(SPI_Init_Config_s *conf)
{
if (idx >= MX_SPI_BUS_SLAVE_CNT) // 超过最大实例数
while (1)
;
SPIInstance *instance = (SPIInstance *)malloc(sizeof(SPIInstance));
memset(instance, 0, sizeof(SPIInstance));
instance->spi_handle = conf->spi_handle;
instance->GPIOx = conf->GPIOx;
instance->cs_pin = conf->cs_pin;
instance->spi_work_mode = conf->spi_work_mode;
instance->callback = conf->callback;
instance->id = conf->id;
if (instance->spi_handle->Instance == SPI1)
{
instance->cs_pin_state = &SPIDeviceOnGoing[0];
}
else if (instance->spi_handle->Instance == SPI2)
{
instance->cs_pin_state = &SPIDeviceOnGoing[1];
}
else
{
while (1)
;
}
spi_instance[idx++] = instance;
return instance;
}
void SPITransmit(SPIInstance *spi_ins, uint8_t *ptr_data, uint8_t len)
{
// 拉低片选,开始传输(选中从机)
HAL_GPIO_WritePin(spi_ins->GPIOx, spi_ins->cs_pin, GPIO_PIN_RESET);
switch (spi_ins->spi_work_mode)
{
case SPI_DMA_MODE:
HAL_SPI_Transmit_DMA(spi_ins->spi_handle, ptr_data, len);
break;
case SPI_IT_MODE:
HAL_SPI_Transmit_IT(spi_ins->spi_handle, ptr_data, len);
break;
case SPI_BLOCK_MODE:
HAL_SPI_Transmit(spi_ins->spi_handle, ptr_data, len, 1000); // 默认50ms超时
// 阻塞模式不会调用回调函数,传输完成后直接拉高片选结束
HAL_GPIO_WritePin(spi_ins->GPIOx, spi_ins->cs_pin, GPIO_PIN_SET);
break;
default:
while (1)
; // error mode! 请查看是否正确设置模式,或出现指针越界导致模式被异常修改的情况
break;
}
}
void SPIRecv(SPIInstance *spi_ins, uint8_t *ptr_data, uint8_t len)
{
// 用于稍后回调使用
spi_ins->rx_size = len;
spi_ins->rx_buffer = ptr_data;
// 拉低片选,开始传输
HAL_GPIO_WritePin(spi_ins->GPIOx, spi_ins->cs_pin, GPIO_PIN_RESET);
switch (spi_ins->spi_work_mode)
{
case SPI_DMA_MODE:
HAL_SPI_Receive_DMA(spi_ins->spi_handle, ptr_data, len);
break;
case SPI_IT_MODE:
HAL_SPI_Receive_IT(spi_ins->spi_handle, ptr_data, len);
break;
case SPI_BLOCK_MODE:
HAL_SPI_Receive(spi_ins->spi_handle, ptr_data, len, 1000);
// 阻塞模式不会调用回调函数,传输完成后直接拉高片选结束
HAL_GPIO_WritePin(spi_ins->GPIOx, spi_ins->cs_pin, GPIO_PIN_SET);
break;
default:
while (1)
; // error mode! 请查看是否正确设置模式,或出现指针越界导致模式被异常修改的情况
break;
}
}
void SPITransRecv(SPIInstance *spi_ins, uint8_t *ptr_data_rx, uint8_t *ptr_data_tx, uint8_t len)
{
// 用于稍后回调使用,请保证ptr_data_rx在回调函数被调用之前仍然在作用域内,否则析构之后的行为是未定义的!!!
spi_ins->rx_size = len;
spi_ins->rx_buffer = ptr_data_rx;
// 等待上一次传输完成
if (spi_ins->spi_handle->Instance == SPI1)
{
while (!SPIDeviceOnGoing[0])
{
};
}
else if (spi_ins->spi_handle->Instance == SPI2)
{
while (!SPIDeviceOnGoing[1])
{
};
}
// 拉低片选,开始传输
HAL_GPIO_WritePin(spi_ins->GPIOx, spi_ins->cs_pin, GPIO_PIN_RESET);
*spi_ins->cs_pin_state =
spi_ins->CS_State =
HAL_GPIO_ReadPin(spi_ins->GPIOx, spi_ins->cs_pin);
switch (spi_ins->spi_work_mode)
{
case SPI_DMA_MODE:
HAL_SPI_TransmitReceive_DMA(spi_ins->spi_handle, ptr_data_tx, ptr_data_rx, len);
break;
case SPI_IT_MODE:
HAL_SPI_TransmitReceive_IT(spi_ins->spi_handle, ptr_data_tx, ptr_data_rx, len);
break;
case SPI_BLOCK_MODE:
HAL_SPI_TransmitReceive(spi_ins->spi_handle, ptr_data_tx, ptr_data_rx, len, 1000); // 默认50ms超时
// 阻塞模式不会调用回调函数,传输完成后直接拉高片选结束
HAL_GPIO_WritePin(spi_ins->GPIOx, spi_ins->cs_pin, GPIO_PIN_SET);
*spi_ins->cs_pin_state =
spi_ins->CS_State =
HAL_GPIO_ReadPin(spi_ins->GPIOx, spi_ins->cs_pin);
break;
default:
while (1)
; // error mode! 请查看是否正确设置模式,或出现指针越界导致模式被异常修改的情况
break;
}
}
void SPISetMode(SPIInstance *spi_ins, SPI_TXRX_MODE_e spi_mode)
{
if (spi_mode != SPI_DMA_MODE && spi_mode != SPI_IT_MODE && spi_mode != SPI_BLOCK_MODE)
while (1)
; // error mode! 请查看是否正确设置模式,或出现指针越界导致模式被异常修改的情况
if (spi_ins->spi_work_mode != spi_mode)
{
spi_ins->spi_work_mode = spi_mode;
}
}
/**
* @brief 当SPI接收完成,将会调用此回调函数,可以进行协议解析或其他必须的数据处理等
*
* @param hspi spi handle
*/
void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)
{
for (size_t i = 0; i < idx; i++)
{
// 如果是当前spi硬件发出的complete,且cs_pin为低电平(说明正在传输),则尝试调用回调函数
if (spi_instance[i]->spi_handle == hspi && // 显然同一时间一条总线只能有一个从机在接收数据
HAL_GPIO_ReadPin(spi_instance[i]->GPIOx, spi_instance[i]->cs_pin) == GPIO_PIN_RESET)
{
// 先拉高片选,结束传输,在判断是否有回调函数,如果有则调用回调函数
HAL_GPIO_WritePin(spi_instance[i]->GPIOx, spi_instance[i]->cs_pin, GPIO_PIN_SET);
*spi_instance[i]->cs_pin_state =
spi_instance[i]->CS_State =
HAL_GPIO_ReadPin(spi_instance[i]->GPIOx, spi_instance[i]->cs_pin);
// @todo 后续添加holdon模式,由用户自行决定何时释放片选,允许进行连续传输
if (spi_instance[i]->callback != NULL) // 回调函数不为空, 则调用回调函数
spi_instance[i]->callback(spi_instance[i]);
return;
}
}
}
/**
* @brief 和RxCpltCallback共用解析即可,这里只是形式上封装一下,不用重复写
* 这是对HAL库的__weak函数的重写,传输使用IT或DMA模式,在传输完成时会调用此函数
* @param hspi spi handle
*/
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{
HAL_SPI_RxCpltCallback(hspi); // 直接调用接收完成的回调函数
}
+98
View File
@@ -0,0 +1,98 @@
#include "spi.h"
#include "stdint.h"
#include "gpio.h"
/* 根据开发板引出的spi引脚以及CubeMX中的初始化配置设定 */
#define SPI_DEVICE_CNT 2 // C型开发板引出两路spi,分别连接BMI088/作为扩展IO在8pin牛角座引出
#define MX_SPI_BUS_SLAVE_CNT 4 // 单个spi总线上挂载的从机数目
/* spi transmit recv mode enumerate*/
typedef enum
{
SPI_BLOCK_MODE = 0, // 默认使用阻塞模式
SPI_IT_MODE,
SPI_DMA_MODE,
} SPI_TXRX_MODE_e;
/* SPI实例结构体定义 */
typedef struct spi_ins_temp
{
SPI_HandleTypeDef *spi_handle; // SPI外设handle
GPIO_TypeDef *GPIOx; // 片选信号对应的GPIO,如GPIOA,GPIOB等等
uint16_t cs_pin; // 片选信号对应的引脚号,GPIO_PIN_1,GPIO_PIN_2等等
SPI_TXRX_MODE_e spi_work_mode; // 传输工作模式
uint8_t rx_size; // 本次接收的数据长度
uint8_t *rx_buffer; // 本次接收的数据缓冲区
uint8_t CS_State; // 片选信号状态,用于中断模式下的片选控制
uint8_t * cs_pin_state; // 片选信号状态,用于中断模式下的片选控制
void (*callback)(struct spi_ins_temp *); // 接收回调函数
void *id; // 模块指针
} SPIInstance;
/* 接收回调函数定义,包含SPI的module按照此格式构建回调函数 */
typedef void (*spi_rx_callback)(SPIInstance *);
// @todo: 这里可以将GPIO_TypeDef *GPIOx; uint16_t cs_pin合并为bsp_gpio以简化代码实现
/* SPI初始化配置,其实基本和SPIIstance一模一样,为了代码风格统一因此再次定义 */
typedef struct
{
SPI_HandleTypeDef *spi_handle; // SPI外设handle
GPIO_TypeDef *GPIOx; // 片选信号对应的GPIO,如GPIOA,GPIOB等等
uint16_t cs_pin; // 片选信号对应的引脚号,GPIO_PIN_1,GPIO_PIN_2等等
SPI_TXRX_MODE_e spi_work_mode; // 传输工作模式
spi_rx_callback callback; // 接收回调函数
void *id; // 模块指针
} SPI_Init_Config_s;
/**
* @brief 注册一个spi instance
*
* @param conf 传入spi配置
* @return SPIInstance* 返回一个spi实例指针,之后通过该指针操作spi外设
*/
SPIInstance *SPIRegister(SPI_Init_Config_s *conf);
/**
* @brief 通过spi向对应从机发送数据
* @todo 后续加入阻塞模式下的timeout参数
*
* @param spi_ins spi实例指针
* @param ptr_data 要发送的数据
* @param len 待发送的数据长度
*/
void SPITransmit(SPIInstance *spi_ins, uint8_t *ptr_data, uint8_t len);
/**
* @brief 通过spi从从机获取数据
* @attention 特别注意:请保证ptr_data在回调函数被调用之前仍然在作用域内,否则析构之后的行为是未定义的!!!
*
* @param spi_ins spi实例指针
* @param ptr_data 接受数据buffer的首地址
* @param len 待接收的长度
*/
void SPIRecv(SPIInstance *spi_ins, uint8_t *ptr_data, uint8_t len);
/**
* @brief 通过spi利用移位寄存器同时收发数据
* @todo 后续加入阻塞模式下的timeout参数
* @attention 特别注意:请保证ptr_data_rx在回调函数被调用之前仍然在作用域内,否则析构之后的行为是未定义的!!!
*
* @param spi_ins spi实例指针
* @param ptr_data_rx 接收数据地址
* @param ptr_data_tx 发送数据地址
* @param len 接收&发送的长度
*/
void SPITransRecv(SPIInstance *spi_ins, uint8_t *ptr_data_rx, uint8_t *ptr_data_tx, uint8_t len);
/**
* @brief 设定spi收发的工作模式
*
* @param spi_ins spi实例指针
* @param spi_mode 工作模式,包括阻塞模式(block),中断模式(IT),DMA模式.详见SPI_TXRX_MODE_e的定义
*
* @todo 是否直接将mode作为transmit/recv的参数,而不是作为spi实例的属性?两者各有优劣
*/
void SPISetMode(SPIInstance *spi_ins, SPI_TXRX_MODE_e spi_mode);
+9
View File
@@ -0,0 +1,9 @@
# bsp spi
> 预计增加模拟spi
初始化传入参数中的GPIOx(GPIOA,GPIOB,...)和cs_pin(GPIO_PIN_1,GPIO_PIN_2, ...)都是HAL库内建的宏,在CubeMX初始化的时候若有给gpio分配标签则填入对应名字即可,否则填入原本的宏。
注意,如果你没有在CubeMX中为spi分配dma通道,请不要使用dma模式
(后续添加安全检查,通过判断hspi的dma handler是否为空来选择模式,如果为空,则自动将DMA转为IT模式以继续传输,并通过log warning 提醒用户)
+139
View File
@@ -0,0 +1,139 @@
/**
* @file bsp_usart.c
* @author neozng
* @brief 串口bsp层的实现
* @version beta
* @date 2022-11-01
*
* @copyright Copyright (c) 2022
*
*/
#include "bsp_usart.h"
#include "bsp_log.h"
#include "stdlib.h"
#include "memory.h"
/* usart service instance, modules' info would be recoreded here using USARTRegister() */
/* usart服务实例,所有注册了usart的模块信息会被保存在这里 */
static uint8_t idx;
static USARTInstance *usart_instance[DEVICE_USART_CNT] = {NULL};
/**
* @brief 启动串口服务,会在每个实例注册之后自动启用接收,当前实现为DMA接收,后续可能添加IT和BLOCKING接收
*
* @todo 串口服务会在每个实例注册之后自动启用接收,当前实现为DMA接收,后续可能添加IT和BLOCKING接收
* 可能还要将此函数修改为extern,使得module可以控制串口的启停
*
* @param _instance instance owned by module,模块拥有的串口实例
*/
void USARTServiceInit(USARTInstance *_instance)
{
HAL_UARTEx_ReceiveToIdle_DMA(_instance->usart_handle, _instance->recv_buff, _instance->recv_buff_size);
// 关闭dma half transfer中断防止两次进入HAL_UARTEx_RxEventCallback()
// 这是HAL库的一个设计失误,发生DMA传输完成/半完成以及串口IDLE中断都会触发HAL_UARTEx_RxEventCallback()
// 我们只希望处理第一种和第三种情况,因此直接关闭DMA半传输中断
__HAL_DMA_DISABLE_IT(_instance->usart_handle->hdmarx, DMA_IT_HT);
}
USARTInstance *USARTRegister(USART_Init_Config_s *init_config)
{
if (idx >= DEVICE_USART_CNT) // 超过最大实例数
while (1)
LOGERROR("[bsp_usart] USART exceed max instance count!");
for (uint8_t i = 0; i < idx; i++) // 检查是否已经注册过
if (usart_instance[i]->usart_handle == init_config->usart_handle)
while (1)
LOGERROR("[bsp_usart] USART instance already registered!");
USARTInstance *instance = (USARTInstance *)malloc(sizeof(USARTInstance));
memset(instance, 0, sizeof(USARTInstance));
instance->usart_handle = init_config->usart_handle;
instance->recv_buff_size = init_config->recv_buff_size;
instance->module_callback = init_config->module_callback;
usart_instance[idx++] = instance;
USARTServiceInit(instance);
return instance;
}
/* @todo 当前仅进行了形式上的封装,后续要进一步考虑是否将module的行为与bsp完全分离 */
void USARTSend(USARTInstance *_instance, uint8_t *send_buf, uint16_t send_size, USART_TRANSFER_MODE mode)
{
switch (mode)
{
case USART_TRANSFER_BLOCKING:
HAL_UART_Transmit(_instance->usart_handle, send_buf, send_size, 100);
break;
case USART_TRANSFER_IT:
HAL_UART_Transmit_IT(_instance->usart_handle, send_buf, send_size);
break;
case USART_TRANSFER_DMA:
HAL_UART_Transmit_DMA(_instance->usart_handle, send_buf, send_size);
break;
default:
while (1)
; // illegal mode! check your code context! 检查定义instance的代码上下文,可能出现指针越界
break;
}
}
/* 串口发送时,gstate会被设为BUSY_TX */
uint8_t USARTIsReady(USARTInstance *_instance)
{
if (_instance->usart_handle->gState | HAL_UART_STATE_BUSY_TX)
return 0;
else
return 1;
}
/**
* @brief 每次dma/idle中断发生时,都会调用此函数.对于每个uart实例会调用对应的回调进行进一步的处理
* 例如:视觉协议解析/遥控器解析/裁判系统解析
*
* @note 通过__HAL_DMA_DISABLE_IT(huart->hdmarx,DMA_IT_HT)关闭dma half transfer中断防止两次进入HAL_UARTEx_RxEventCallback()
* 这是HAL库的一个设计失误,发生DMA传输完成/半完成以及串口IDLE中断都会触发HAL_UARTEx_RxEventCallback()
* 我们只希望处理,因此直接关闭DMA半传输中断第一种和第三种情况
*
* @param huart 发生中断的串口
* @param Size 此次接收到的总数居量,暂时没用
*/
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
for (uint8_t i = 0; i < idx; ++i)
{ // find the instance which is being handled
if (huart == usart_instance[i]->usart_handle)
{ // call the callback function if it is not NULL
if (usart_instance[i]->module_callback != NULL)
{
usart_instance[i]->module_callback();
memset(usart_instance[i]->recv_buff, 0, Size); // 接收结束后清空buffer,对于变长数据是必要的
}
HAL_UARTEx_ReceiveToIdle_DMA(usart_instance[i]->usart_handle, usart_instance[i]->recv_buff, usart_instance[i]->recv_buff_size);
__HAL_DMA_DISABLE_IT(usart_instance[i]->usart_handle->hdmarx, DMA_IT_HT);
return; // break the loop
}
}
}
/**
* @brief 当串口发送/接收出现错误时,会调用此函数,此时这个函数要做的就是重新启动接收
*
* @note 最常见的错误:奇偶校验/溢出/帧错误
*
* @param huart 发生错误的串口
*/
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
for (uint8_t i = 0; i < idx; ++i)
{
if (huart == usart_instance[i]->usart_handle)
{
HAL_UARTEx_ReceiveToIdle_DMA(usart_instance[i]->usart_handle, usart_instance[i]->recv_buff, usart_instance[i]->recv_buff_size);
__HAL_DMA_DISABLE_IT(usart_instance[i]->usart_handle->hdmarx, DMA_IT_HT);
LOGWARNING("[bsp_usart] USART error callback triggered, instance idx [%d]", i);
return;
}
}
}
+75
View File
@@ -0,0 +1,75 @@
#ifndef BSP_RC_H
#define BSP_RC_H
#include <stdint.h>
#include "main.h"
#define DEVICE_USART_CNT 8 // MC02串口数量8个,其中
#define USART_RXBUFF_LIMIT 256 // 如果协议需要更大的buff,请修改这里
// 模块回调函数,用于解析协议
typedef void (*usart_module_callback)();
/* 发送模式枚举 */
typedef enum
{
USART_TRANSFER_NONE=0,
USART_TRANSFER_BLOCKING,
USART_TRANSFER_IT,
USART_TRANSFER_DMA,
} USART_TRANSFER_MODE;
// 串口实例结构体,每个module都要包含一个实例.
// 由于串口是独占的点对点通信,所以不需要考虑多个module同时使用一个串口的情况,因此不用加入id;当然也可以选择加入,这样在bsp层可以访问到module的其他信息
typedef struct
{
uint8_t recv_buff[USART_RXBUFF_LIMIT]; // 预先定义的最大buff大小,如果太小请修改USART_RXBUFF_LIMIT
uint8_t recv_buff_size; // 模块接收一包数据的大小
UART_HandleTypeDef *usart_handle; // 实例对应的usart_handle
usart_module_callback module_callback; // 解析收到的数据的回调函数
} USARTInstance;
/* usart 初始化配置结构体 */
typedef struct
{
uint8_t recv_buff_size; // 模块接收一包数据的大小
UART_HandleTypeDef *usart_handle; // 实例对应的usart_handle
usart_module_callback module_callback; // 解析收到的数据的回调函数
} USART_Init_Config_s;
/**
* @brief 注册一个串口实例,返回一个串口实例指针
*
* @param init_config 传入串口初始化结构体
*/
USARTInstance *USARTRegister(USART_Init_Config_s *init_config);
/**
* @brief 启动串口服务,需要传入一个usart实例.一般用于lost callback的情况(使用串口的模块daemon)
*
* @param _instance
*/
void USARTServiceInit(USARTInstance *_instance);
/**
* @brief 通过调用该函数可以发送一帧数据,需要传入一个usart实例,发送buff以及这一帧的长度
* @note 在短时间内连续调用此接口,若采用IT/DMA会导致上一次的发送未完成而新的发送取消.
* @note 若希望连续使用DMA/IT进行发送,请配合USARTIsReady()使用,或自行为你的module实现一个发送队列和任务.
* @todo 是否考虑为USARTInstance增加发送队列以进行连续发送?
*
* @param _instance 串口实例
* @param send_buf 待发送数据的buffer
* @param send_size how many bytes to send
*/
void USARTSend(USARTInstance *_instance, uint8_t *send_buf, uint16_t send_size,USART_TRANSFER_MODE mode);
/**
* @brief 判断串口是否准备好,用于连续或异步的IT/DMA发送
*
* @param _instance 要判断的串口实例
* @return uint8_t ready 1, busy 0
*/
uint8_t USARTIsReady(USARTInstance *_instance);
#endif
+80
View File
@@ -0,0 +1,80 @@
# bsp_usart
<p align='right'>neozng1@hnu.edu.cn</p>
> TODO: 增加发送队列以解决短时间内调用`USARTSend()`发生丢包的问题,目前仅支持DMA。还需要提供阻塞和IT模式以供选择,参考bspiic和spi进行实现。
> 可以直接在发送函数的参数列表添加发送模式选择,或增加instance成员变量,并提供设置模式接口,两者各有优劣
## 使用说明
若你需要构建新的基于串口的module,首先需要拥有一个`usart_instance`的指针用于操作串口对象。
需要在串口实例下设定接收的数据包的长度,实例对应的串口硬件(通过`UART_HandleTypeDef`指定,如`&huart1`),解析接收数据对应的回调函数这三个参数。然后,调用`USARTRegister()`并传入配置好的`usart_instance`指针即可。
若要发送数据,调用`USARTSend()`。注意buffsize务必小于你创建的buff的大小,否则造成指针越界后果未知。
串口硬件收到数据时,会将其存入`usart_instance.recv_buff[]`中,当收到完整一包数据,会调用设定的回调函数`module_callback`(即你注册时提供的解析函数)。在此函数中,你可以通过`usart_instance.recv_buff[]`访问串口收到的数据。
## 代码结构
.h文件内包括了外部接口和类型定义,以及模块对应的宏。c文件内为私有函数和外部接口的定义。
## 类型定义
```c
#define DEVICE_USART_CNT 3 // C板至多分配3个串口
#define USART_RXBUFF_LIMIT 128 // if your protocol needs bigger buff, modify here
typedef void (*usart_module_callback)();
/* usart_instance struct,each app would have one instance */
typedef struct
{
uint8_t recv_buff[USART_RXBUFF_LIMIT]; // 预先定义的最大buff大小,如果太小请修改USART_RXBUFF_LIMIT
uint8_t recv_buff_size; // 模块接收一包数据的大小
UART_HandleTypeDef *usart_handle; // 实例对应的usart_handle
usart_module_callback module_callback; // 解析收到的数据的回调函数
} usart_instance;
```
- `DEVICE_USART_CNT`是开发板上可用的串口数量。
- `USART_RXBUFF_LIMIT`是串口单次接收的数据长度上限,暂时设为128,如果需要更大的buffer容量,修改该值。
- `usart_module_callback()`是模块提供给串口接收中断回调函数使用的协议解析函数指针。对于每个需要串口的模块,需要定义一个这样的函数用于解包数据。
- 每定义一个`usart_instance`,就代表一个串口的**实例**(对象)。一个串口实例内有接收buffer,单个数据包的大小,该串口对应的`HAL handle`(代表其使用的串口硬件具体是哪一个)以及用于解包数据的回调函数。
## 外部接口
```c
void USARTRegister(usart_instance *_instance);
void USARTSend(usart_instance *_instance, uint8_t *send_buf, uint16_t send_size);
```
- `USARTRegister`是用于初始化串口对象的接口,module层的模块对象(也应当为一个结构体)内要包含一个`usart_instance`。
**在调用该函数之前,需要先对其成员变量`*usart_handle`,`module_callback()`以及`recv_buff_size`进行赋值。**
- `USARTSend()`是通过模块通过其拥有的串口对象发送数据的接口,调用时传入的参数为串口实例指针,发送缓存以及此次要发送的数据长度(8-bit\*n)。
## 私有函数和变量
在.c文件内设为static的函数和变量
```c
static usart_instance *instance[DEVICE_USART_CNT];
```
这是bsp层管理所有串口实例的入口。
```c
static void USARTServiceInit(usart_instance *_instance)
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
```
- `USARTServiceInit()`会被`USARTRegister()`调用,开启接收中断
- `HAL_UARTEx_RxEventCallback()`和`HAL_UART_ErrorCallback()`都是对HAL的回调函数的重定义(原本的callback是`__week`修饰的弱定义),前者在发生**IDLE中断**或**单次DMA传输中断**后会被调用(说明收到了完整的一包数据),随后在里面根据中断来源,调用拥有产生了该中断的模块的协议解析函数进行数据解包;后者在串口传输出错的时候会被调用,重新开启接收。
+31
View File
@@ -0,0 +1,31 @@
/**
* @file bsp_usb.c
* @author your name (you@domain.com)
* @brief usb是单例bsp,因此不保存实例
* @version 0.1
* @date 2023-02-09
*
* @copyright Copyright (c) 2023
*
*/
#include "bsp_usb.h"
#include "bsp_log.h"
#include "bsp_dwt.h"
static uint8_t *bsp_usb_rx_buffer; // 接收到的数据会被放在这里,buffer size为2048
// 注意usb单个数据包(Full speed模式下)最大为64byte,超出可能会出现丢包情况
uint8_t *USBInit(USB_Init_Config_s usb_conf)
{
// usb的软件复位(模拟拔插)在usbd_conf.c中的HAL_PCD_MspInit()中
bsp_usb_rx_buffer = CDCInitRxbufferNcallback(usb_conf.tx_cbk, usb_conf.rx_cbk); // 获取接收数据指针
// usb的接收回调函数会在这里被设置,并将数据保存在bsp_usb_rx_buffer中
LOGINFO("USB init success");
return bsp_usb_rx_buffer;
}
void USBTransmit(uint8_t *buffer, uint16_t len)
{
CDC_Transmit_HS(buffer, len); // 发送
}
+30
View File
@@ -0,0 +1,30 @@
/**
* @file bsp_usb.h
* @author your name (you@domain.com)
* @brief 提供对usb vpc(virtal com port)的操作接口,hid和msf考虑后续添加
* @attention 这一版usb修改了usbd_cdc_if.c中的CDC_Receive_FS函数,若使用cube生成后会被覆盖.后续需要由usbcdciftemplate创建一套自己的模板
* @version 0.1
* @date 2023-02-09
*
* @copyright Copyright (c) 2023
*
*/
#pragma once
#include "usb_device.h"
#include "usbd_cdc.h"
#include "usbd_conf.h"
#include "usbd_desc.h"
#include "usbd_cdc_if.h"
typedef struct
{
USBCallback tx_cbk;
USBCallback rx_cbk;
} USB_Init_Config_s;
/* @note 虚拟串口的波特率/校验位/数据位等动态可变,取决于上位机的设定 */
/* 使用时不需要关心这些设置(作为从机) */
uint8_t *USBInit(USB_Init_Config_s usb_conf); // bsp初始化时调用会重新枚举设备
void USBTransmit(uint8_t *buffer, uint16_t len); // 通过usb发送数据
+5
View File
@@ -0,0 +1,5 @@
# bsp usb
简单写点,有待优化. 目前仅支持虚拟串口通信,暂未开发其他内容.
注意,为了增加发送完成和接收完成回调,对Inc/usbd_xxxx.h四个文件做了修改,对Src/usbxxx.c也进行了修改。