一.ADC(相电压、电流采集,总线电压,旋钮等)

1.普通单次转换ADC

已跑通,很简单,掌握几个核心函数就行

void knobs_task(void) {
    osDelay(500);


    while(1) {
    HAL_ADC_Start(&hadc2);
    HAL_ADC_PollForConversion(&hadc2, 1000);
    adc2_value= HAL_ADC_GetValue(&hadc2); // 读取有效值
     usb_printf("%lu\r\n", adc2_value);
        osDelay(100);
    }
}

void knobs_init(void) {
    HAL_ADCEx_Calibration_Start(&hadc2, ADC_SINGLE_ENDED);
    knobsTaskHandle=osThreadNew(knobs_task, NULL, &knobsTask_attributes);

}

2.普通连续转换ADC

出现问题并未跑通,十分疑惑,把OE位置于0就可以进行正常读取,或者打开Low Power Auto Wait也可以解决。已有人遇到过相同的问题,但并未找到问题的根本

32ADC单通道连续模式只能采样一次问题,adc值不更新( CUBMX+HAL)_循环读取内部adc的值不变化-CSDN博客

void knobs_task(void) {
    HAL_ADC_Start(&hadc2);
    HAL_ADC_PollForConversion(&hadc2, 1000);//1000和HAL_MAX_DELAY都试过没有变换
    osDelay(500);
    while(1) {


    adc2_value= HAL_ADC_GetValue(&hadc2); // 读取有效值
     usb_printf("%lu\r\n", adc2_value);
        osDelay(100);

    }
}

void knobs_init(void) {
    HAL_ADCEx_Calibration_Start(&hadc2, ADC_SINGLE_ENDED);
    knobsTaskHandle=osThreadNew(knobs_task, NULL, &knobsTask_attributes);

}

3.DMA连续多通道ADC转换!!

要点:

1.SCAN模式打开,各RANK的通道设置正确,连续转换,打开DMA,DMA中设置循环,通道随便选?

2.DATA大小设置的要和代码接收的要一致(word->uint32_t,byte->uint8_t)

3.DMA Continuous Requests要打开!KK视频中并未提到这一点,若不打开会一直为零

或者获取一个值后不进行更新。

4.Clock Prescaler要设置改一点,默认参数(4分频)可能对于ADC处于超频状态,若不改此参数上述操作正确,会出现数据打印慢、不定时打印的情况

  uint32_t dma_adc2_buffer[4];

  HAL_ADC_Start_DMA(&hadc2, (uint32_t*)dma_adc2_buffer, sizeof(dma_adc2_buffer)/sizeof(uint32_t));

  while(1){

}

二、SPI通讯(编码器读取电机角度)

1.径向磁铁安装注意还是要大一点的比较好,本次使用的偏小为直径3mm

2.CPOL和CPHA的选择,根据芯片手册来,看他的SCLK初始是高还是低(看时序图可知MT6701为初始低电平),然后看他说建议上升沿还是下降沿采集,CUBEMX中描述有些许差别,为第一个跳变沿或第二个跳变沿开始隔一个开始采集,大同小异,由此开始配置CUBEMX

为什么这里选择8bits?磁编码器发送的数据不是24bit(14位数据+4位状态+6位CRC)吗?因为我们选择uint8_t,后刚好24位可以被uint8_t txData[3];接收完,然后我们将uint8_t txData[0]和uint8_t txData[1]的前六位进行运算即可。还有另一种方式就是选16bits然后一次接16bit(按8bit分割)在右移两位去掉两位状态即为纯角度。

HAL_StatusTypeDef spiStatus = HAL_SPI_TransmitReceive(&hspi1, (uint8_t *)&txData, (uint8_t *)&rawData, 1, HAL_MAX_DELAY);//第二种方式读取,不知道为什么HAL_SPI_TransmitReceive就是用不了,需要改成纯Receive,而且MT6701就没有MISO线,发送什么?
而且这里的(uint8_t *)我也不是很理解,16位数据直接右移两位不就行了吗?这样分割是为了什么?

下面的uint16_t Size参数是什么意思?为什么填1也可以?接受的数据不是3个8bit吗?

/**
  * @brief  Receive an amount of data in blocking mode.
  * @param  hspi pointer to a SPI_HandleTypeDef structure that contains
  *               the configuration information for SPI module.
  * @param  pData pointer to data buffer
  * @param  Size amount of data to be received
  * @param  Timeout Timeout duration
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout)

附上修改后的MT6701磁编码器代码,十分稳定

//
// Created by 29569 on 25-4-27.
//

#include "mt6701_spi.h"
#include "arm_math.h"
#include "cmsis_os2.h"
#include "spi.h"
#include "stm32g4xx_hal_spi.h"
#include "FreeRTOS.h"
#include "task.h"
#include "usbd_cdc_if.h"


osThreadId_t mt6701_spiTaskHandle;
const osThreadAttr_t mt6701_spiTask_attributes = {
    .name = "mt6701_spiTask",
    .priority = (osPriority_t) osPriorityNormal,
    .stack_size = 512*4
  };

#define MT6701_CS_Enable() HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET)
#define MT6701_CS_Disable() HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET)


int rotationCount = 0;  // 旋转过的圈数
int rotationCount_Last; // 上一次循环时转过的圈数

uint16_t MT6701_GetRawData(void)
{
    uint16_t rawData;
    uint16_t timeOut = 200;

    while (HAL_SPI_GetState(&hspi1) != HAL_SPI_STATE_READY)
    {
        if (timeOut-- == 0)
        {
            return 0; // 在超时时直接返回,避免继续执行后续代码
        }
    }

    MT6701_CS_Enable();

    HAL_StatusTypeDef spiStatus = HAL_SPI_Receive(&hspi1, (uint8_t *)&rawData, 2, 200);
    if (spiStatus != HAL_OK)
    {
        MT6701_CS_Disable();
        rawData = 0;
        return 0; // 在SPI传输错误时直接返回,避免继续执行后续代码
    }

    MT6701_CS_Disable();

    return rawData >> 2; // 取高14位的角度数据
}
float MT6701_GetRawAngle(void)
{
    uint16_t rawData = MT6701_GetRawData();
    return (float)rawData / 16384.0f * 2*PI;
}

// 获得转过的总角度,有圈数累加
float MT6701_GetFullAngle(void)
{
    static float angle_Last = 0.0f;     // 上次的轴角度,范围0~6.28
    float angle = MT6701_GetRawAngle(); // 当前角度,范围0~6.28
    float deltaAngle = angle - angle_Last;

    // 计算旋转的总圈数
    // 通过判断角度变化是否大于80%的一圈(0.8f*6.28318530718f)来判断是否发生了溢出,如果发生了,则将full_rotations增加1(如果d_angle小于0)或减少1(如果d_angle大于0)。

    if (fabsf(deltaAngle) > (0.8f * 6.28318530718f))
    {
        rotationCount += (deltaAngle > 0) ? -1 : 1; // 圈数计算
        rotationCount_Last = rotationCount;
    }

    angle_Last = angle;
    return rotationCount * 6.28318530718f + angle_Last; // 转过的圈数 * 2pi + 未转满一圈的角度值
}

// 计算转速
float MT6701_GetVelocity(void)
{
    static float full_Angle_Last = 0.0f; // 记录上次转过的总角度
    float full_Angle = MT6701_GetFullAngle();
    float delta_Angle = (rotationCount - rotationCount_Last) * 2*PI + (full_Angle - full_Angle_Last);
    float vel = delta_Angle * 1000.0f; // Ts = 1ms

    // 更新变量值
    full_Angle_Last = full_Angle;
    return vel;
}






void mt6701_spi_task(void) {

    osDelay(500);

    while (1) {


        usb_printf("SPI:%f\r\n", (float)MT6701_GetVelocity());
        //usb_printf("TEST剩余栈:%d,%d\r\n", (int)uxTaskGetStackHighWaterMark(NULL), (int)xPortGetFreeHeapSize());

        osDelay(5);



    }
}


void mt6701_spi_init(void) {

    mt6701_spiTaskHandle=osThreadNew(mt6701_spi_task, NULL, &mt6701_spiTask_attributes);

}

STM32 HAL库spi读取mt6701角度值_mt6701 spi-CSDN博客

STM32CubeMX学习笔记(1)--SPI接口使用(读取MT6701位置传感器)1.MT6701简介 1.1 IIC - 掘金(未实现功能)

三、FreeRTOS(底层系统框架)

freertos基本概念、任务创建和删除就不多说了,注意的是Linux和windows都为通用操作系统(general-purpose OS)。

freertos的堆是在全局区域里创建的,并非占用系统堆。

成分

描述

系统堆HEAP

当我们使用malloc函数申请内存时,就是从这里申请的,它必须由程序员提前定义好大小,如果空间不足,malloc会申请失败。目前我了解到的,它就这一个作用。

系统栈STACK

用来存储临时变量、函数的参数等等,当我们进行函数嵌套时,进入函数前,是要进行保存现场的工作的,等执行完函数跳回到原来位置时,需要恢复现场,而保存现场所使用的内存,就是从系统栈中获取的,如果系统栈不足,就会出现常说的栈溢出,导致程序跑飞。与系统堆不同的是,系统栈可以不提前规定大小,不影响程序运行。

全局区

用来存储全局变量、静态变量

freertos堆大小,分配方法由下图参数决定

堆和栈的分配方式:

FreeRTOS提供了多种堆管理方案,可以在FreeRTOSConfig.h文件中选择使用哪种方案。

heap_1:只支持静态分配,即在程序开始时就分配好所有任务的TCB和栈空间,不支持动态创建和删除任务。

heap_2:支持动态分配,即在程序运行时可以创建和删除任务,但不支持内存回收,即删除任务后不会释放其占用的内存空间。

heap_3:支持动态分配和内存回收,使用标准C库的malloc和free函数来管理堆空间,但可能存在内存碎片问题,即堆空间被分割成很多小块,导致无法分配足够大的连续空间。

heap_4:支持动态分配和内存回收,并且可以合并相邻的空闲块,减少内存碎片问题,但需要更多的代码空间和执行时间。

heap_5:在heap_4的基础上增加了多个堆区域的支持,可以将不同大小或者不同属性的内存区域作为堆来使用,提高了内存利用率。

注意若选择heap_5则创建线程需要在osKernelInitialize();之后,否则会卡在申请空间。

若想要查看堆栈剩余情况,可使用以下函数打印出情况

 usb_printf("TEST最小剩余栈%d\r\n",(int)uxTaskGetStackHighWaterMark(NULL));//查看栈最小时候的值
 usb_printf("TEST剩余栈%d\r\n",(int)xPortGetFreeHeapSize());//查看栈的值

configUSE_NEWLIB_REENTRANT参数和printf、sprintf之类有关,关掉的话就没有前述函数的库(NEWLIB)无法进行打印输出

堆栈溢出检测(CHECK_FOR_STACK_OVERFLOW)需要写回调处理函数,懒得写固暂时不开

【FreeRTOS】FreeRTOS学习笔记 ---- 堆和栈,第1个FreeRTOS程序,创建任务函数及任务管理_freertos的堆栈-CSDN博客

STM32CubeMX FreeRTOS堆栈分配、调试技巧-腾讯云开发者社区-腾讯云

FreeRTOS记录(一、熟悉开发环境以及CubeMX下FreeRTOS配置) - 知乎

四、PWM(最终三相电压输出)

1.center aligned mode 1 2 3的区别,简单来说区别就是中断产生的时机不同,mode3为高点低点都产生中断

PWM模式1:无论是向上计数还是向下计数,只要CNTCCRx,PWM输出低电平(CCR值越高占空比越高)。

PWM模式2:无论是向上计数还是向下计数。只要CNTCCRx,PWM输出高电平(CCR值越高占空比越低)。

STM32三种对齐计数模式及其中断回调函数——用CubeMX工具_stm32 timer三种中心对齐计数模式区别-CSDN博客

STM32Cube的PWM控制基础篇(三)定时器的PWM设置详解_ch idle state-CSDN博客(PWM里各个参数的含义此文章中都有解释)

STM32CubeMX学习笔记(6)--定时器触发ADC采样1.PWM mode1和PWM mode2的区别 总结: P - 掘金(又是他)

其他疑惑

1.这个栈大小怎么知道多少合适?少了会导致各种奇奇怪怪的问题,在连续转换ADC实验时发现大了的话就无法连上串口,已经控制变量进行过实验

2.为什么ADC的值要用ADC:%lu,%lu,%lu,%lu才能打印?而%f不行?

3.为什么这边最小栈为128,但是我LED闪烁的栈分配了8还是能运行?

4.为什么栈大小同为12000,堆和栈的分配方式设置为heap_5就USB打印不出来了,USB灯也不闪了,Heap_4就可以