第一次提交
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
待添加adc的bsp支持,应该提供阻塞/IT/DMA的量测接口
|
||||
|
||||
是否需要bsp_adc?由于功能非常简单,似乎可以直接使用HAL的接口,没有必要多此一举进行封装?
|
||||
|
||||
回调函数.
|
||||
+12
@@ -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数据接收回调函数设置为任务的接口,通过这种方式,可以进一步提高整个系统的实时性,同时保证高优先级的任务一定按时执行。
|
||||
@@ -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
|
||||
|
||||
@@ -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; // 返回信号量,同时增加索引
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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`值,防止对其他需要精确定时的任务产生影响。
|
||||
@@ -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)
|
||||
;
|
||||
}
|
||||
@@ -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_ */
|
||||
@@ -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;
|
||||
```
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
@@ -0,0 +1,23 @@
|
||||
|
||||
可以作为io接口,也可以处理外部中断.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
使用示例
|
||||
|
||||
```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, ...);
|
||||
```
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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`参数. 这个功能只在一条总线上挂载多个主机的时候有用.
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
```
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
# bsp pwm
|
||||
|
||||
同一定时器下的pwm通道更改周期时请注意该定时器下是否有其他pwm示例,如果有请注意不要影响其他pwm通道的周期。
|
||||
使用pwm dma传输中断时注意占空比的设置,设为0将不会进入中断函数
|
||||
默认tim psc已在cubemx设置好,详情可参考cubemx配置文件
|
||||
@@ -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); // 直接调用接收完成的回调函数
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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 提醒用户)
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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传输中断**后会被调用(说明收到了完整的一包数据),随后在里面根据中断来源,调用拥有产生了该中断的模块的协议解析函数进行数据解包;后者在串口传输出错的时候会被调用,重新开启接收。
|
||||
@@ -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); // 发送
|
||||
}
|
||||
@@ -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发送数据
|
||||
@@ -0,0 +1,5 @@
|
||||
# bsp usb
|
||||
|
||||
简单写点,有待优化. 目前仅支持虚拟串口通信,暂未开发其他内容.
|
||||
|
||||
注意,为了增加发送完成和接收完成回调,对Inc/usbd_xxxx.h四个文件做了修改,对Src/usbxxx.c也进行了修改。
|
||||
Reference in New Issue
Block a user