一.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的堆是在全局区域里创建的,并非占用系统堆。
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:无论是向上计数还是向下计数,只要CNT
⩽CCRx
,PWM输出低电平(CCR值越高占空比越高)。
PWM模式2:无论是向上计数还是向下计数。只要CNT
⩽CCRx
,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就可以
评论区