STM32&&Cube&&Clion

本文介绍了如何在Clion中配置STM32开发环境,涉及GCC编译器优化、Printf重定向、Keil中C和C++的使用、HAL库处理UART和FDCAN通信,以及SPI和QSPI的基本原理和区别。
摘要由CSDN通过智能技术生成

STM32&&Cube&&Clion

  • 过往嵌入式中的一些笔记,一些有的没的,之前一直存在Notion中,但是每次登录要挂一下梯子很是麻烦,想了想还是放在CSDN中了,内容删减了一些,权当是一个纪念

在Clion中使用STM32

配置CLion用于STM32开发【优雅の嵌入式开发】

当前使用的gcc:gcc-arm-none-eabi-10.3-2021.10 可能会导致编译文件体积变大

Printf的重定向

Keil文件中,使用C

//头文件.h
#include <stdio.h>
int fputc(int ch, FILE *f);
// 用户定义.c
int fputc(int ch, FILE *f)
{
    uint8_t temp[1] = {ch};
    HAL_UART_Transmit(&huartx, temp, 1, 2);//huartx
    return ch;
}

Keil文件中,使用C++

// ------------------------------
// 在使用C++时 printf失效 这样使用
// 在usart.h中添加
#include <stdio.h>
#pragma import(__use_no_semihosting)
struct __FILE
{  
  int handle;
};
int fputc(int ch, FILE* f);
// ------------------------------
// 在usart.c中添加
FILE __stdout;
FILE __stdin;

void _sys_exit(int x)
{
  x = x;
}

int fputc(int ch, FILE* f)
{
  uint8_t temp[1] = { ch };
  HAL_UART_Transmit(&huartx, temp, 1, 2);//huartx 开启串口几就是用串口几
  return ch;
}

在Clion中,需要添加重定向文件,添加后会报错,需要注销掉报错的文件

// -----------------------------.c文件-----------------------------
#include <_ansi.h>
#include <_syslist.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/times.h>
#include <retarget.h>
#include <stdint.h>

#if !defined(OS_USE_SEMIHOSTING)

#define STDIN_FILENO  0
#define STDOUT_FILENO 1
#define STDERR_FILENO 2

UART_HandleTypeDef *gHuart;

void RetargetInit(UART_HandleTypeDef *huart)
{
    gHuart = huart;

    /* Disable I/O buffering for STDOUT stream, so that
     * chars are sent out as soon as they are printed. */
    setvbuf(stdout, NULL, _IONBF, 0);
}

int _isatty(int fd)
{
    if (fd >= STDIN_FILENO && fd <= STDERR_FILENO)
        return 1;

    errno = EBADF;
    return 0;
}

int _write(int fd, char *ptr, int len)
{
    HAL_StatusTypeDef hstatus;

    if (fd == STDOUT_FILENO || fd == STDERR_FILENO)
    {
        hstatus = HAL_UART_Transmit(gHuart, (uint8_t *) ptr, len, HAL_MAX_DELAY);
        if (hstatus == HAL_OK)
            return len;
        else
            return EIO;
    }
    errno = EBADF;
    return -1;
}

int _close(int fd)
{
    if (fd >= STDIN_FILENO && fd <= STDERR_FILENO)
        return 0;

    errno = EBADF;
    return -1;
}

int _lseek(int fd, int ptr, int dir)
{
    (void) fd;
    (void) ptr;
    (void) dir;

    errno = EBADF;
    return -1;
}

int _read(int fd, char *ptr, int len)
{
    HAL_StatusTypeDef hstatus;

    if (fd == STDIN_FILENO)
    {
        hstatus = HAL_UART_Receive(gHuart, (uint8_t *) ptr, 1, HAL_MAX_DELAY);
        if (hstatus == HAL_OK)
            return 1;
        else
            return EIO;
    }
    errno = EBADF;
    return -1;
}

int _fstat(int fd, struct stat *st)
{
    if (fd >= STDIN_FILENO && fd <= STDERR_FILENO)
    {
        st->st_mode = S_IFCHR;
        return 0;
    }

    errno = EBADF;
    return 0;
}

#endif //#if !defined(OS_USE_SEMIHOSTING)

// -----------------------------.h文件-----------------------------
#ifndef _RETARGET_H__
#define _RETARGET_H__

// -----------根据具体情况进行修改--------------------
#include "stm32f1xx_hal.h"
// -------------------------------------------------
#include <sys/stat.h>
#include <stdio.h>
#ifdef __cplusplus
extern "C" {
#endif
    
    void RetargetInit(UART_HandleTypeDef* huart);

int _isatty(int fd);

int _write(int fd, char *ptr, int len);

int _close(int fd);

int _lseek(int fd, int ptr, int dir);

int _read(int fd, char *ptr, int len);

int _fstat(int fd, struct stat* st);
#ifdef __cplusplus
}

#endif

#endif //#ifndef _RETARGET_H__

串口中断

串口空闲中断+DMA

stm32: 串口空闲中断的实现(HAL库)_串口的hal空闲中断函数_哈搭石的博客-CSDN博客

  • 在CubeMX中需要打开串口中断,以及DMA中断
    在这里插入图片描述
    在这里插入图片描述

  • 由于HAL库本身的函数中没有自带的空闲中断,所以自己创建一个文件进行使用,由于这个之后要插入到总的中断中只能使用C文件

    // ----------------------------IdleUart.h------------------------
    
    #ifndef IDLEUART_H
    #define IDLEUART_H
    
    #include "main.h"
    
    // 设置最大接受数据 可以自定义
    #define MAX_UART_BUF 50
    
    typedef struct {
        uint8_t buf[MAX_UART_BUF];
        uint8_t len;
        UART_HandleTypeDef *huart;
    } UartBuf_t;
    
    #ifdef __cplusplus
    extern "C" {
    #endif
    
    // 初始化函数
    void IdleUartInit(UartBuf_t *huart);
    
    // 回调函数,需要插入对应串口的中断中间
    void HAL_UART_IdleCpltCallback(UartBuf_t *huart);
    
    #ifdef __cplusplus
    }
    #endif
    
    // 外部声明
    extern UartBuf_t uartxBuf;
    
    #endif 
    
    // ----------------------------IdleUart.c------------------------
    
    #include "usart.h"
    #include "idleUart.h"
    #include "retarget.h"
    
    // 声明函数,只需要定义所需要的串口
    UartBuf_t uartxBuf = {
            .huart = &huartx,
    };
    
    // 初始化函数
    void IdleUartInit(UartBuf_t *huart) {
    		// 使能空闲中断
        __HAL_UART_ENABLE_IT(huart->huart, UART_IT_IDLE);
    		// 清空空闲中断标志位
        __HAL_UART_CLEAR_IDLEFLAG(huart->huart);
    		// 使能DMA传输
        HAL_UART_Receive_DMA(huart->huart, (uint8_t *) huart->buf, MAX_UART_BUF);
    }
    
    // 回调函数,这是一个总的函数,但是还是需要插入到每一个对应的串口的中断响应中
    void HAL_UART_IdleCpltCallback(UartBuf_t *huart) {
    		
    		// 关闭MDA
        HAL_UART_DMAStop(huart->huart); 
    		// 清空空闲中断标志位
        __HAL_UART_CLEAR_IDLEFLAG(huart->huart);
    		// 获取本次传输的数据长度
        huart->len = MAX_UART_BUF - __HAL_DMA_GET_COUNTER(huart->huart->hdmarx);
    
        if (huart->huart->Instance == USARTX) {
            // do somthing ...
        }
    		// 开启DMA
        HAL_UART_Receive_DMA(huart->huart, (uint8_t *) huart->buf, MAX_UART_BUF);
    }
    
  • 在中断文件stm32xxxx_it.c 找到对应函数的中断文件,加入。

    void USARTX_IRQHandler(void)
    {
      /* USER CODE BEGIN USART3_IRQn 0 */
      HAL_UART_IdleCpltCallback(&uartxBuf);
      /* USER CODE END USART3_IRQn 0 */
      HAL_UART_IRQHandler(&huartx);
      /* USER CODE BEGIN USART3_IRQn 1 */
    
      /* USER CODE END USART3_IRQn 1 */
    }
    

FDCAN

  • 负载率计算,最好使负载率在30%以下(长期稳定)

    CAN总线网络“负载率”计算

  • 在应对有数据回传时(每次只发送单个数据)需要打开自动重新发送

    在这里插入图片描述

    因为CAN 的模块在发送数据的时候,也同时会对总线进行监听,假设两个节点同时发送数据,A节点发送的前3 个位是100,B节点为101,在前2 个位发送完毕的时候,两个节点都会认为自己发送成功,但是当发送到第三个位的时候,B 节点会失去仲裁,因为0 的优先级高于1的优先级,B 节点监听到总线上不是1,因此失去仲裁,等待重新发送数据。CAN 节点在仲裁丢失后,根据CAN2.0B 协议规范,会自动重发。如果高优先级的报文一直占用着总线,则其他低优先级的报文将无法获得仲裁,但是会尝试重新发送。只有当高优先级报文不再占用总线时,低优先级的报文才可能发送成功。否则低优先级报文会出现“假饿死”状态。(例如在CAN_ NM 的逻辑环网络管理中,利用T_ max,T_ type 等定时器来防止报文假饿死)

    如果在某一时刻CAN总线上的多个单元同时向总线发送数据,优先级高的继续发送,那么怎样保证优先级低的数据不丢失呢? 如果整个can 网络都是自己管理的,那么可以通过应用层协议来调整每个节点的发送时间,无论优先级高低,如果发送时间过长(长短程度这个根据您的项目来把握) 则暂停发送,让其他节点发送。或者每个节点都在某个特定时间触发发送,以便每个节点都有机会。总的意思就是一定要做时间管理。如果项目不是像你说的优先级高占用带宽那么严重,就采用(非实时信息空闲时候) 轮询+ (实时信息) 主动发送的方式管理网络。

FDCAN的使用

// -------------------------.h-------------------------
#ifndef _FDCAN_BASE_H_
#define _FDCAN_BASE_H_

#include <cstdint>
#include "fdcan.h"

// 基础控制ID
#define CAN_CONTROL_ID_BASE    0xfff

class FDCANBase {
public:
    explicit FDCANBase(FDCAN_HandleTypeDef *_fdcan) : fdcan(_fdcan) {};
		
		// filter init
    void Init();        

    void Send(uint8_t *_data, uint16_t _id);

    uint8_t txData[8];
private:
    FDCAN_HandleTypeDef *fdcan;
    FDCAN_TxHeaderTypeDef txHeader;
};

#endif //_FDCAN_BASE_H_

// -------------------------.c-------------------------
#include "fdcan_base.h"
#include <cstring>

void FDCANBase::Init() {
    // filter init
    FDCAN_FilterTypeDef can_filter;

    HAL_FDCAN_ActivateNotification(fdcan, FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0);

    can_filter.IdType = FDCAN_STANDARD_ID;
    can_filter.FilterIndex = 0;
		// 设置滤波器
		// #define FDCAN_FILTER_RANGE         ((uint32_t)0x00000000U) /*!< Range filter from FilterID1 to FilterID2                        */
		// #define FDCAN_FILTER_DUAL          ((uint32_t)0x00000001U) /*!< Dual ID filter for FilterID1 or FilterID2                       */
		// #define FDCAN_FILTER_MASK          ((uint32_t)0x00000002U) /*!< Classic filter: FilterID1 = filter, FilterID2 = mask            */
		// #define FDCAN_FILTER_RANGE_NO_EIDM ((uint32_t)0x00000003U) /*!< Range filter from FilterID1 to FilterID2, EIDM mask not applied */

    can_filter.FilterType = FDCAN_FILTER_MASK;
    can_filter.FilterConfig = FDCAN_FILTER_TO_RXFIFO0;
    can_filter.FilterID1 = 0x000;                               // 双电子掩码 全通过
    can_filter.FilterID2 = 0x7ff;
    if (HAL_FDCAN_ConfigFilter(&hfdcan1, &can_filter) != HAL_OK) {
        Error_Handler();
    }

    // tx init
    txHeader.Identifier = CAN_CONTROL_ID_BASE;
    txHeader.IdType = FDCAN_STANDARD_ID;
    txHeader.TxFrameType = FDCAN_DATA_FRAME;
    txHeader.DataLength = FDCAN_DLC_BYTES_8;
    txHeader.ErrorStateIndicator = FDCAN_ESI_ACTIVE;
    txHeader.BitRateSwitch = FDCAN_BRS_OFF;
    txHeader.FDFormat = FDCAN_CLASSIC_CAN;
    txHeader.TxEventFifoControl = FDCAN_NO_TX_EVENTS;

    // rx init
    HAL_FDCAN_Start(fdcan);
}

// can send
void FDCANBase::Send(uint8_t *_data, uint16_t _id) {
    txHeader.Identifier = _id;
    memcpy(txData, _data, 8);
    while (HAL_FDCAN_AddMessageToTxFifoQ(fdcan, &txHeader, txData) != HAL_OK);
}

QSPI/SPI

基础——SPI与QSPI的异同,QSPI的具体协议是什么,QSPI有什么用_qspi和spi接口的区别-CSDN博客

SPI协议其实是包括:Standard SPI、Dual SPI和Queued SPI三种协议接口,分别对应3-wire, 4-wire, 6-wire。

  • 通常我们说的SPI就是Standard SPI,有4根信号线,分别为CLK、CS、MOSI和MISO。数据线工作在全双工
  • Dual SPI,它只是针对SPI Flash而言,不是针对所有SPI外设。对于SPI Flash,全双工并不常用,因此扩展了mosi和miso的用法,让它们工作在半双工,用以加倍数据传输。也就是对于Dual SPI Flash,可以发送一个命令字节进入dual mode,这样mosi变成SIO0(serial io 0),mosi变成SIO1(serial io 1),这样一个时钟周期内就能传输2个bit数据,加倍了数据传输
  • 类似的,还可以扩展,与也是针对SPI Flash,Qual SPI Flash增加了两根I/O线(SIO2,SIO3),目的是一个时钟内传输4个bit。
  • 18
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值