目录
[七、SPI_NOR FLASH](#七、SPI_NOR FLASH)
[1.Cube MX配置](#1.Cube MX配置)
简介:
本系列使用硬件:
1.核心板:【立创·天空星STM32F407VxT6】开发板
2.控制板:STM32天空星_无刷电机拓展板
3.电机:1806无刷云台电机
学习资料:
跳转目录:
【CubeMX-HAL库】STM32F407---无刷电机学习笔记
【CubeMX-HAL库】STM32F407---无刷电机基础知识
【CubeMX-HAL库】STM32F407---无刷电机开环控制
【CubeMX-HAL库】STM32F407---无刷电机闭环控制
【CubeMX-HAL库】STM32F407---无刷电机电流闭环控制
【CubeMX-HAL库】STM32F407---无刷电机SVPWM控制
【CubeMX-HAL库】软件、硬件SPI+DMA驱动TFT彩屏(LVGL)
后续继续补充......
其他笔记跳转链接:【CubeMX-HAL库】STM32H743---学习笔记
一、工程创建
本实验通过Cube MX配置使用Keil5编写程序代码。
①打开Cube MX创建新工程,在搜索框输入STM32F407ZET6选择对应芯片。
②在系统核心配置中选择RCC->打开外部时钟源HSE和LSE。
③在DEBUG栏中使能SW引脚。
④将时钟频率设置为168MHz。
⑤设置文件路径及工程名,配置生成Keil-MDK文件。
⑥ 选择复制必要的文件,并且'.c/.h'独立分开后点击"GENERATE CODE"生成代码。
⑦打开生成的Keil工程,可以先将编码设置为UTF-8格式(LVGL中字库大部分为UTF-8编码,防止之后乱码),进入魔术棒勾选使用LIB库,选择对应的下载器并勾复位并运行,然后编译工程,顺便将部件框都拖到习惯的位置,编译成功后即可下载程序。
二、板载LED
通过原理图可知核心板上LED接在PB2引脚,高电平点亮。
![](https://i-blog.csdnimg.cn/direct/8ac54ba3ea03401fbe01a1894116713d.png)
![](https://i-blog.csdnimg.cn/direct/af99305e06ce4354a46a7cf1ce00a28c.png)
cpp
#define LED_OFF HAL_GPIO_WritePin(LED_GPIO_Port,LED_Pin,GPIO_PIN_SET)
#define LED_ON HAL_GPIO_WritePin(LED_GPIO_Port,LED_Pin,GPIO_PIN_RESET)
#define LED HAL_GPIO_ReadPin(LED_GPIO_Port,LED_Pin)
#define LED_TOG HAL_GPIO_TogglePin(LED_GPIO_Port,LED_Pin)
三、用户按键
由原理图可知,按键为PA0,拨动开关引脚分别为PD8,PD9,PD14。
![](https://i-blog.csdnimg.cn/direct/b7093a07214d48fdaa1ad1e858fe20ab.png)
CubeMX设置对应IO,并配置相应上拉下拉。
![](https://i-blog.csdnimg.cn/direct/4cf96cfcb8d64c1988427de45ae50966.png)
cpp
#define KEY_R HAL_GPIO_ReadPin(KEY_R_GPIO_Port,KEY_R_Pin)
#define KEY_D HAL_GPIO_ReadPin(KEY_D_GPIO_Port,KEY_D_Pin)
#define KEY_L HAL_GPIO_ReadPin(KEY_L_GPIO_Port,KEY_L_Pin)
#define KEY_UP HAL_GPIO_ReadPin(KEY_WKUP_GPIO_Port,KEY_WKUP_Pin)
uint8_t key_scan(uint8_t mode)
{
static uint8_t key = 1;
if(mode)key = 1;
if(key == 1 && (KEY_R == 0 || KEY_D == 0 || KEY_L == 0 || KEY_UP == 1))
{
key = 0;
HAL_Delay(2);
if(KEY_R == 0) return 1;
else if(KEY_D == 0) return 2;
else if(KEY_L == 0) return 3;
else if(KEY_UP == 1) return 4;
}
else if(KEY_R == 1 && KEY_D == 1 && KEY_L == 1 && KEY_UP == 0)
key = 1;
return 0;
}
四、蜂鸣器
由原理图可知无源蜂鸣器在PB1,刚好在ITM3_CH4通道可使用PWM驱动。
![](https://i-blog.csdnimg.cn/direct/e8d49db61d544c7081da70f48f99cd90.png)
CubeMX配置TIM3的CH4通道,使用2KHz频率驱动蜂鸣器。
![](https://i-blog.csdnimg.cn/direct/40f27f3fb3494e0e8f0f38c0bc56848a.png)
cpp
#define BEEP_Init HAL_TIM_PWM_Start_IT(&htim3,TIM_CHANNEL_4)//2KHz NO Source BEEP
#define BEEP_ON TIM3->CCR4 = 50
#define BEEP_OFF TIM3->CCR4 = 100
1.完整IO控制代码
cpp
#ifndef __key_H__
#define __key_H__
#include "main.h"
#define BEEP_Init HAL_TIM_PWM_Start_IT(&htim3,TIM_CHANNEL_4)//2KHz NO Source BEEP
#define BEEP_ON TIM3->CCR4 = 50
#define BEEP_OFF TIM3->CCR4 = 100
#define LED_OFF HAL_GPIO_WritePin(LED_GPIO_Port,LED_Pin,GPIO_PIN_SET)
#define LED_ON HAL_GPIO_WritePin(LED_GPIO_Port,LED_Pin,GPIO_PIN_RESET)
#define LED HAL_GPIO_ReadPin(LED_GPIO_Port,LED_Pin)
#define LED_TOG HAL_GPIO_TogglePin(LED_GPIO_Port,LED_Pin)
#define KEY_R HAL_GPIO_ReadPin(KEY_R_GPIO_Port,KEY_R_Pin)
#define KEY_D HAL_GPIO_ReadPin(KEY_D_GPIO_Port,KEY_D_Pin)
#define KEY_L HAL_GPIO_ReadPin(KEY_L_GPIO_Port,KEY_L_Pin)
#define KEY_UP HAL_GPIO_ReadPin(KEY_WKUP_GPIO_Port,KEY_WKUP_Pin)
uint8_t key_scan(uint8_t mode)
{
static uint8_t key = 1;
if(mode)key = 1;
if(key == 1 && (KEY_R == 0 || KEY_D == 0 || KEY_L == 0 || KEY_UP == 1))
{
key = 0;
HAL_Delay(2);
if(KEY_R == 0) return 1;
else if(KEY_D == 0) return 2;
else if(KEY_L == 0) return 3;
else if(KEY_UP == 1) return 4;
}
else if(KEY_R == 1 && KEY_D == 1 && KEY_L == 1 && KEY_UP == 0)
key = 1;
return 0;
}
#endif
五、TFT彩屏驱动
屏幕使用SPI+DMA驱动,背光引脚暂未使用调光设置没开启PWM。
详细代码介绍可转【CubeMX-HAL库】软件、硬件SPI+DMA驱动TFT彩屏(LVGL)
![](https://i-blog.csdnimg.cn/direct/d395dc17b6c74825b03226100772feb1.png)
![](https://i-blog.csdnimg.cn/direct/5f1b817d75af4d608b66965d50897637.png)
![](https://i-blog.csdnimg.cn/direct/2e31e0eea07841f4b2994a45bd92b74b.png)
六、ADC多通道
1.通道确认
由原理图可知,我们本次需要采集的ADC主要有两个三项电流部分、NTC控制板温度、芯片内部温度。
![](https://i-blog.csdnimg.cn/direct/505fbdb918744e39aaed8969622e983d.png)
![](https://i-blog.csdnimg.cn/direct/193555c4160f466395fa5b674d497189.png)
![](https://i-blog.csdnimg.cn/direct/f9bb1830eb0b4a01ad1d5a80795ff2b2.png)
![](https://i-blog.csdnimg.cn/direct/6020c3c1dc314ca7b38d1a137adeec88.png)
2.CubeMX配置
①开启对应的ADC通道
![](https://i-blog.csdnimg.cn/direct/9ea304f19da44084bf11c3c8566f673f.png)
②选择规则组通道
![](https://i-blog.csdnimg.cn/direct/8129bb335d89421aba89c00872f585d5.png)
③开启DMA
![](https://i-blog.csdnimg.cn/direct/7067f76dfc7046f985928288008a64cd.png)
④开启ADC中断
可进行DMA采集一定次数之后,在中断中进行滤波。
![](https://i-blog.csdnimg.cn/direct/fd46b44bf5bf432e93396c4149a96245.png)
3.KEIL配置
①内部温度传感器
![](https://i-blog.csdnimg.cn/direct/89aab1986adb44e68b3fe93eb507afab.png)
![](https://i-blog.csdnimg.cn/direct/822158691fba4b12bd3343c2c0286370.png)
cpp
#define Vsense 0.76f //温度传感器在25℃时的电压值
#define Avg_Slope 0.0025f //温度与Vsense曲线的平均斜率
float ADC_Get_MCU_Temperature(void)//获取内部温度传感器温度
{
float adc_vol,temp;
adc_vol = ADC_T * 3.3f / 4096;
temp = (adc_vol - Vsense) / Avg_Slope + 25;
return temp;
}
②NTC热敏电阻
本次NTC使用10K ±1%精度的电阻,
![](https://i-blog.csdnimg.cn/direct/0e360b40cc404dd0878bad08b21fecf2.png)
![](https://i-blog.csdnimg.cn/direct/a3d0ecfac06542df8035619355789d1a.png)
由原理图中的NTC电路,推算采集的电压值,然后在反推当前NTC的阻值。(下面3.3V改为5V)
![](https://i-blog.csdnimg.cn/direct/bba0e827065848999cd4839e676c9aba.png)
(当10K在下,NTC在上时计算如下:)
![](https://i-blog.csdnimg.cn/direct/d634afacc2624b88b7272fd413508152.png)
查表法计算NTC温度
cpp
#define data0 28017
#define data1 26826
#define data2 25697
#define data3 24629
#define data4 23618
#define data5 22660
#define data6 21752
#define data7 20892
#define data8 20075
#define data9 19299
#define data10 18560
#define data11 18482
#define data12 18149
#define data13 17632
#define data14 16992
#define data15 16280
#define data16 15535
#define data17 14787
#define data18 14055
#define data19 13354
#define data20 12690
#define data21 12068
#define data22 11490
#define data23 10954
#define data24 10458
#define data25 10000
#define data26 9576
#define data27 9184
#define data28 8819
#define data29 8478
#define data30 8160
#define data31 7861
#define data32 7579
#define data33 7311
#define data34 7056
#define data35 6813
#define data36 6581
#define data37 6357
#define data38 6142
#define data39 5934
#define data40 5734
#define data41 5541
#define data42 5353
#define data43 5173
#define data44 4998
#define data45 4829
#define data46 4665
#define data47 4507
#define data48 4355
#define data49 4208
#define data50 4065
#define data51 3927
#define data52 3794
#define data53 3664
#define data54 3538
#define data55 3415
#define data56 3294
#define data57 3175
#define data58 3058
#define data59 2941
#define data60 2825
#define data61 2776
#define data62 2718
#define data63 2652
#define data64 2582
#define data65 2508
#define data66 2432
#define data67 2356
#define data68 2280
#define data69 2207
#define data70 2135
#define data71 2066
#define data72 2000
#define data73 1938
#define data74 1879
#define data75 1823
#define data76 1770
#define data77 1720
#define data78 1673
#define data79 1628
#define data80 1586
#define data81 1546
#define data82 1508
#define data83 1471
#define data84 1435
#define data85 1401
#define data86 1367
#define data87 1334
#define data88 1301
#define data89 1268
#define data90 1236
#define data91 1204
#define data92 1171
#define data93 1139
#define data94 1107
#define data95 1074
#define data96 1042
#define data97 1010
const uint16_t NTC_Table[98]={
data0,data1,data2,data3,data4,data5,data6,data7,data8,data9,
data10,data11,data12,data13,data14,data15,data16,data17,data18,data19,
data20,data21,data22,data23,data24,data25,data26,data27,data28,data29,
data30,data31,data32,data33,data34,data35,data36,data37,data38,data39,
data40,data41,data42,data43,data44,data45,data46,data47,data48,data49,
data50,data51,data52,data53,data54,data55,data56,data57,data58,data59,
data60,data61,data62,data63,data64,data65,data66,data67,data68,data69,
data70,data71,data72,data73,data74,data75,data76,data77,data78,data79,
data80,data81,data82,data83,data84,data85,data86,data87,data88,data89,
data90,data91,data92,data93,data94,data95,data96,data97,
};
uint16_t NTC_Get_Temp_Array(void)//NTC温度查表计算(放大了10倍)
{
float t;
unsigned int dat,max,min,mid,da,j;
t=ADC_NTC;
t=t/4096;
t=t*3300;//计算mV电压
t=t/(5-t/1000);
dat=t*10;
da=dat;
max=97;
min=0;
while(1)
{
mid=(max+min)/2;
if(NTC_Table[mid]<da)
max=mid;
else
min=mid;
if((max-min)<=1)
break;
}
if(max==min)
da=min*10;
else
{
j=(NTC_Table[min]-NTC_Table[max])/10;
j=(NTC_Table[min]-da)/j;
da=j;
da=10*min+da; //采集的温度放大了10倍
}
return da;
}
公式法计算NTC温度
cpp
#include "math.h"
/*
Rt = Rp *exp(B*(1/T1-1/T2))
Rt 是热敏电阻在T1温度下的阻值;
Rp是热敏电阻在T2常温下的标称阻值;
exp是e的n次方,e是自然常数,就是自然对数的底数,近似等于2.7182818;
B值是热敏电阻的重要参数,教程中用到的热敏电阻B值为3380;
这里T1和T2指的是开尔文温度,T2是常温25℃,即(273.15+25)K
T1就是所求的温度
*/
#define Rp 10000.0f/* 10K */
#define T2 (273.15f + 25.0f)/* T2 */
#define Bx 3380.0f/* B */
#define Ka 273.15f
/**
* @brief 计算温度值
* @note 计算温度分为两步:
1.根据ADC采集到的值计算当前对应的Rt
2.根据Rt计算对应的温度值
* @param para: 温度采集对应ADC通道的值(已滤波)
* @retval 温度值
*/
float NTC_Get_Temp_Count(uint16_t ADC_VALUE)//计算温度值
{
float Rt;
float temp;
/*
NTC在上,分压电阻在下时:
Rt = 5.0 * 10000 / VTEMP - 10000,
其中VTEMP就是温度检测通道采集回来的电压值,VTEMP = ADC值* 3.3/4096
由此我们可以计算出当前Rt的值:
Rt = 5.0f * 10000.0f / (para * 3.3f / 4096.0f ) - 10000.0f;
NTC在下,分压电阻在上时:
Rt = (10k * VTEMP) / (3.3 - VTEMP)
Rt = 10000.0f * (para * 3.3f / 4096.0f) / (5.0f - (para * 3.3f / 4096.0f));
*/
Rt = 10000.0f * (ADC_VALUE * 3.3f / 4096.0f) / (5.0f - (ADC_VALUE * 3.3f / 4096.0f));/*根据当前ADC值计算出Rt的值*/
/*根据当前Rt的值来计算对应温度值:Rt = Rp *exp(B*(1/T1-1/T2))*/
temp = Rt / Rp;/* 解出exp(B*(1/T1-1/T2)) ,即temp = exp(B*(1/T1-1/T2)) */
temp = log(temp);/* 解出B*(1/T1-1/T2) ,即temp = B*(1/T1-1/T2) */
temp /= Bx;/* 解出1/T1-1/T2 ,即temp = 1/T1-1/T2 */
temp += (1.0f / T2);/* 解出1/T1 ,即temp = 1/T1 */
temp = 1.0f / (temp);/* 解出T1 ,即temp = T1 */
temp -= Ka;/* 计算T1对应的摄氏度 */
return temp;/* 返回温度值 */
}
实际效果
![](https://i-blog.csdnimg.cn/direct/a9962b49296f4bd389b354c24a830797.png)
③INA240A2电流传感器
4.完整ADC代码
①BSP_ADC.c
②BSP_ADC.h
七、SPI_NOR FLASH
![](https://i-blog.csdnimg.cn/direct/de6fd7c7d5fa4de18872c41721bb8da0.png)
八、SDIO_SD卡
![](https://i-blog.csdnimg.cn/direct/556b50eace0142a6a4bedc5cae0eb01d.png)
九、I2C_AS5600编码器
1.Cube MX配置
![](https://i-blog.csdnimg.cn/direct/23280127b3af433da6829043b16b0c46.png)
快速模式
![](https://i-blog.csdnimg.cn/direct/fb29ed7574d84a5497491e2f2a5bd0dc.png)
2.KEIL配置
①AS5600.c
cpp
#include "AS5600.h"
uint16_t AS5600_1_ReadRaw(void)//获取原始角度寄存器
{
uint8_t data[2]={AS5600_RAW_ANGLE_H,0x00};
HAL_I2C_Master_Transmit(&hi2c1,AS5600_ADDRESS_W,data,1,Time_Out);
HAL_I2C_Master_Receive(&hi2c1,AS5600_ADDRESS_R,data,2,Time_Out);
// I2C_Start();
// I2C_SendByte(AS5600_ADDRESS_W);
// I2C_RecviveAck();
// I2C_SendByte(AS5600_RAW_ANGLE_H);
// I2C_RecviveAck();
//
// I2C_Start();
// I2C_SendByte(AS5600_ADDRESS_R);
// I2C_RecviveAck();
// Data_H = I2C_RecviveData();
// I2C_RecviveAck();
//
// I2C_Start();
// I2C_SendByte(AS5600_ADDRESS_R);
// I2C_RecviveAck();
// Data_L = I2C_RecviveData();
// I2C_SendAck(1);
// I2C_Stop();
return (data[0] << 8) | data[1];
}
float AS5600_1_GetAngle_0_2PI(void)//读取角度(0-2PI)
{
float Angle = 0.0;
Angle = AS5600_1_ReadRaw() * _2PI / 4096;
// Angle = (Angle/4096) * 360;
return Angle;
}
float AS5600_1_Full_Angle = 0.0;
float AS5600_1_Last_Angle = 0.0;
float AS5600_1_GetAngle_Cycles(void)//读取圈数
{
float Now_Angle = 0.0;
float Angle = AS5600_1_GetAngle_0_2PI();
Now_Angle = Angle - AS5600_1_Last_Angle;
if(fabs(Now_Angle) > (0.8f*2*PI))
{
AS5600_1_Full_Angle = AS5600_1_Full_Angle + ((Now_Angle > 0) ? -1 :1);
}
AS5600_1_Last_Angle = Angle;
return (AS5600_1_Full_Angle * 2 * PI + AS5600_1_Last_Angle);
}
uint16_t AS5600_2_ReadRaw(void)//获取原始角度寄存器
{
uint8_t data[2]={AS5600_RAW_ANGLE_H,0x00};
HAL_I2C_Master_Transmit(&hi2c2,AS5600_ADDRESS_W,data,1,Time_Out);
HAL_I2C_Master_Receive(&hi2c2,AS5600_ADDRESS_R,data,2,Time_Out);
return (data[0] << 8) | data[1];
}
float AS5600_2_GetAngle_0_2PI(void)//读取角度(0-2PI)
{
float Angle = 0.0;
Angle = AS5600_2_ReadRaw() * _2PI / 4096;
return Angle;
}
float AS5600_2_Full_Angle = 0.0;
float AS5600_2_Last_Angle = 0.0;
float AS5600_2_GetAngle_Cycles(void)//读取圈数
{
float Now_Angle = 0.0;
float Angle = AS5600_2_GetAngle_0_2PI();
Now_Angle = Angle - AS5600_2_Last_Angle;
if(fabs(Now_Angle) > (0.8f*2*PI))
{
AS5600_2_Full_Angle = AS5600_2_Full_Angle + ((Now_Angle > 0) ? -1 :1);
}
AS5600_2_Last_Angle = Angle;
return (AS5600_2_Full_Angle * 2 * PI + AS5600_2_Last_Angle);
}
float Last_Vel_ts = 0.0;
float Vel_Last_Angle = 0.0;
float AS5600_2_GetVelocity(void)
{
float dt = 0.0;
float Vel_ts = SysTick -> VAL;
if(Vel_ts < Last_Vel_ts) dt = (Last_Vel_ts - Vel_ts)/9*1e-6f;
else dt = (0xFFFFFF - Vel_ts + Last_Vel_ts)/9*1e-6f;
if(dt < 0.0001f) dt = 10000;
float Vel_Angle = AS5600_2_GetAngle_Cycles();
float dv = Vel_Angle - Vel_Last_Angle;
float velocity = (Vel_Angle - Vel_Last_Angle)/dt;
Last_Vel_ts = Vel_ts;
Vel_Last_Angle = Vel_Angle;
return velocity;
}
②AS5600.h
cpp
#ifndef __AS5600_H
#define __AS5600_H
#include "main.h"
#include <stdio.h>
#include <math.h>
#include "i2c.h"
#define Time_Out 100//超时时间
#define AS5600_ADDRESS_W 0X6C//加上读写位(1位1/0)
#define AS5600_ADDRESS_R 0X6D
#define AS5600_RAW_ANGLE_H 0X0C//原始角度寄存器[11:8]共12位分辨率
#define AS5600_RAW_ANGLE_L 0X0D//原始角度寄存器[7:0]
#define PI 3.14159265359f
#define _2PI 6.28318530718f
uint16_t AS5600_1_ReadRaw(void);//获取原始角度寄存器
float AS5600_1_GetAngle_0_2PI(void);//读取角度(0-2PI)
float AS5600_1_GetAngle_Cycles(void);//读取圈数
uint16_t AS5600_2_ReadRaw(void);//获取原始角度寄存器
float AS5600_2_GetAngle_0_2PI(void);//读取角度(0-2PI)
float AS5600_2_GetAngle_Cycles(void);//读取圈数
float AS5600_2_GetVelocity(void);
#endif
3.演示效果
![](https://i-blog.csdnimg.cn/direct/0b4df1a17caa421891a61f12b43a57c5.gif)