【2026 机器人模块大赛】7 +基于MPS的智能家居六能交互机器人+速度+位置控制(stm32)

一、任务目标

利用STMF103RCT6双安卓口的开发板,发信号给MP6540H控制无刷直流6对级12V的2804的云台电机,通过SPI读取MA600A磁编码角度传感器,通过I2C 显示0.96的OLED。

二、硬件部分

:clipboard: 引脚接线

功能模块 信号名称 STM32F103引脚 外设/模式 备注
电机驱动 MP6540H PWMA PB6 TIM4_CH1 (PWM) A相PWM
PWMB PB7 TIM4_CH2 (PWM) B相PWM
PWMC PB8 TIM4_CH3 (PWM) C相PWM
ENA PB12 GPIO_Output 使能A
ENB PB13 GPIO_Output 使能B
ENC PB14 GPIO_Output 使能C
nSleep PB15 GPIO_Output 睡眠控制
nFault PB0 GPIO_Input 故障检测
磁编码器 MA600A SCLK PA5 SPI1_SCK SPI时钟
CIPO PA6 SPI1_MISO 数据输入
COPI PA7 SPI1_MOSI 数据输出
CS PA4 GPIO_Output 片选
OLED 显示屏 SCL PB10 I2C1_SCL I2C时钟
SDA PB11 I2C1_SDA I2C数据
电流检测 I_U PA0 ADC1_IN0 U相电流
I_V PA1 ADC1_IN1 V相电流
调试串口 TX PA2 USART2_TX 串口输出
RX PA3 USART2_RX 串口输入
板载资源 LED2 PA8 GPIO_Output 板载LED
SW3 PC9 GPIO_Input 按键3
SW4 PC8 GPIO_Input 按键4
USB1 RX PA9 USB_DM USB1接收
TX PA10 USB_DP USB1发送
USB2 DM PA11 USB_DM USB差分负
DP PA12 USB_DP USB差分正
调试接口 SWDIO PA13 SWD ST-Link下载
SWCLK PA14 SWD ST-Link时钟

三、软件部分

:clipboard: STM32F103RCT6 FOC控制系统 - CubeMX配置指南

:clipboard: STM32F103RCT6 FOC控制系统 - CubeMX配置指南

一、新建工程

  1. 打开STM32CubeMX → ACCESS TO MCU SELECTOR
  2. 搜索 STM32F103RCT6 → 选择型号 → Start Project

时钟配置 (Clock Configuration) 配置项

设置值 HSE Crystal/Ceramic Resonator (8MHz) LSE Crystal Oscillator (32.768KHz) PLL Source HSE PLL Multiplier ×9 SYSCLK 72MHz AHB Prescaler /1 APB1 Prescaler /2 (36MHz) APB2 Prescaler /1 (72MHz) LSE = 32.768KHz (可选,用于RTC)

配置TIM1(三相PWM输出)

第一步:打开TIM1配置界面

  1. 在左侧 Timers 下找到 TIM1

  2. 点击进入配置界面

第二步:基础配置

参数 设置值 Clock Source Internal Clock Channel 1 PWM Generation CH1 Channel 2 PWM Generation CH2 Channel 3 PWM Generation CH3

第四步:配置PWM模式

在 PWM Generation Channel 1/2/3 中:

参数 设置值 Mode PWM Mode 1 Pulse 0 Output Compare Polarity High Fast Mode Disable CHxN Polarity High (如果使用互补输出

第五步:配置GPIO引脚

配置完成后,CubeMX会自动分配引脚:

  • PA8 → TIM1_CH1 (PWMA)

  • PA9 → TIM1_CH2 (PWMB)

  • PA10 → TIM1_CH3 (PWMC)

第六步:启用PWM输出

在 NVIC Settings 中:

  • 可选:启用 TIM1 更新中断(用于FOC定时)

:clipboard: 下一步:配置SPI1(MA600A磁编码器)

第一步:打开SPI1配置界面

  1. 在左侧 Connectivity 下找到 SPI1

  2. 点击进入配置界面

第二步:基础配置

参数 设置值 Mode Full Duplex Master Hardware NSS Signal Disable

第三步:配置SPI参数

在 Parameter Settings 中:

Data Size: 16 Bits
First Bit: MSB First
Clock Polarity (CPOL): Low
Clock Phase (CPHA): 1 Edge
Baud Rate Prescaler: 16
NSS Signal Type: Software (GPIO)

第四步:配置GPIO引脚

配置完成后,CubeMX会自动分配引脚:

  • PA5 → SPI1_SCK
  • PA6 → SPI1_MISO (CIPO)
  • PA7 → SPI1_MOSI (COPI)

  • 第五步:手动添加CS引脚

    SPI1的CS引脚需要手动配置:

  1. 在左侧 GPIO 中找到 PA4
  2. 设置为 GPIO_Output
  3. 默认电平设置为 High (CS高电平表示未选中)

:clipboard: 下一步:配置I2C1(OLED显示屏)

第一步:打开I2C1配置界面

  1. 在左侧 Connectivity 下找到 I2C1

  2. 点击进入配置界面

参数 设置值 Mode I2C Slave Address 0x78(OLED默认地址)

第三步:配置I2C参数

在 Parameter Settings 中:

I2C Speed Mode: Fast Mode
Fast Mode Speed (kHz): 400
Addressing Mode: 7-bit
Own Address 1: 0
Dual Address Mode: Disable
General Call Mode: Disable
No Stretch Mode: Disable

第四步:配置GPIO引脚

配置完成后,CubeMX会自动分配引脚:

  • PB6 → I2C1_SCL

  • PB7 → I2C1_SDA

    第五步:验证配置

    配置完成后,I2C1参数:

  • 模式:Fast Mode

  • 速度:400kHz

  • 地址:7位

:clipboard: 下一步:配置ADC1(三相电流检测)

第一步:打开ADC1配置界面

  1. 在左侧 Analog 下找到 ADC1

  2. 点击进入配置界面

第二步:基础配置

在 Mode Settings 中:

  • Scan Conversion Mode : Enable

  • Continuous Conversion Mode : Disable

  • Discontinuous Conversion Mode : Disable

参数 设置值 Channel ADC_CHANNEL_0 (PA0) Sampling Time 55.5 Cycles

然后添加 Rank 2 :

参数 设置值 Channel ADC_CHANNEL_1 (PA1) Sampling Time 55.5 Cycles

然后添加 Rank 3 :

参数 设置值 Channel ADC_CHANNEL_2 (PA2) Sampling Time 55.5 Cycles

:clipboard: 下一步:配置GPIO控制引脚

第一步:打开GPIO配置界面

  1. 在左侧 System Core 下找到 GPIO

  2. 点击进入配置界面

第二步:配置控制引脚

需要配置以下引脚:

引脚 功能 Mode Default Level PB12 ENA GPIO_Output Low PB13 ENB GPIO_Output Low PB14 ENC GPIO_Output Low PB15 nSleep GPIO_Output High PB0 nFault GPIO_Input -

操作步骤:

  1. 找到 PB12 :
  • Mode: GPIO_Output
  • Pull-up/Pull-down: No pull-up and no pull-down
  • Speed: Low
  • Default Level: Low
  1. 找到 PB13 :
  • Mode: GPIO_Output
  • 其他同PB12
  1. 找到 PB14 :
  • Mode: GPIO_Output
  • 其他同PB12
  1. 找到 PB15 :
  • Mode: GPIO_Output
  • Default Level: High (nSleep高电平表示唤醒状态)
  1. 找到 PB0 :
  • Mode: GPIO_Input

  • Pull-up/Pull-down: No pull-up and no pull-down (或根据电路选择)

  • 找到板载LED引脚(通常是PC13)

  • Mode: GPIO_Output

:clipboard: 下一步:配置USART1(调试串口)

第一步:打开USART1配置界面

  1. 在左侧 Connectivity 下找到 USART1

  2. 点击进入配置界面

第二步:基础配置

参数 设置值 Mode Asynchronous Baud Rate 115200 Word Length 8 Bits Stop Bits 1 Parity None Hardware Flow Control None

第三步:配置GPIO引脚

配置完成后,CubeMX会自动分配引脚:

  • PA9 → USART1_TX

  • PA10 → USART1_RX

    第四步:配置中断(可选)

    在 NVIC Settings 中:

  • 勾选 USART1 global interrupt

  • 设置优先级(例如:Preemption Priority: 0, Sub Priority: 1)

修改程序

/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : FOC + Serial CLI + OLED
  ******************************************************************************
  */

#include "main.h"

volatile uint32_t tick_count = 0;

/* ---- Simple string helpers (no libc) ---- */
static int strequal(const char *a, const char *b) { while(*a&&*b){if(*a!=*b)return 0;a++;b++;} return *a==*b; }
static int strprefix(const char *a, const char *b, int n) { for(int i=0;i<n;i++){if(a[i]!=b[i])return 0;} return 1; }
static float myatof(const char *s) { float r=0,d=1;int sign=1; if(*s=='-'){sign=-1;s++;} while(*s>='0'&&*s<='9'){r=r*10+(*s-'0');s++;} if(*s=='.'){s++;while(*s>='0'&&*s<='9'){d*=0.1f;r+=(*s-'0')*d;s++;}} return r*sign; }
static int myatoi(const char *s) { int r=0; while(*s>='0'&&*s<='9'){r=r*10+(*s-'0');s++;} return r; }

/* ---- UART TX (register-direct, no HAL) ---- */
#define U1_BASE 0x40013800  // USART1 base address (APB2)
static void uart_tx(uint8_t c) { while(!(*(volatile uint32_t*)(U1_BASE+0x00) & (1<<7))); *(volatile uint32_t*)(U1_BASE+0x04) = c; }
static void uart_puts(const char *s) { while(*s) uart_tx((uint8_t)*s++); }
static void uart_puti(int32_t n) {
  char buf[12]; int i=0, neg=0;
  if(n<0){neg=1; n=-n;} if(n==0) buf[i++]='0';
  while(n>0){buf[i++]='0'+(n%10); n/=10;}
  if(neg) buf[i++]='-';
  while(i>0) uart_tx((uint8_t)buf[--i]);
}
static void uart_putx(uint32_t n) {
  char h[]="0123456789ABCDEF"; uart_tx('0'); uart_tx('x');
  int s=28; while(s>0 && ((n>>s)&0xF)==0) s-=4;
  while(s>=0){uart_tx(h[(n>>s)&0xF]); s-=4;}
}

/* ---- FOC state ---- */
static float foc_vq     = 0.3f;       // 默认300 (x1000),提高默认电压以便电机能启动
static float foc_speed  = 50.0f;      // 默认500 (x10)
static float foc_angle  = 0.0f;
static int   closed_loop = 0;         // 0=open, 1=closed
static float pid_kp = 0.005f, pid_ki = 0.001f;  // Kp=5(x1000), Ki=1(x1000)
static int   motor_running = 0;
static int   iq_ref = 100;            // 默认电流参考值ADC
static int   pole_pairs = 6;          // 默认极对数

/* ---- sinf/cosf (simple Taylor) ---- */
static float sinf(float x) {
  while(x>3.1415926f) x-=6.2831853f;
  while(x<-3.1415926f) x+=6.2831853f;
  float r=0, p=x, f=1; for(int i=0;i<10;i++){r+=p/f; p*=-x*x; f*=(2*i+2)*(2*i+3);} return r;
}
static float cosf(float x) {
  while(x>3.1415926f) x-=6.2831853f;
  while(x<-3.1415926f) x+=6.2831853f;
  float r=0, p=1, f=1; for(int i=0;i<10;i++){r+=p/f; p*=-x*x; f*=(2*i+1)*(2*i+2);} return r;
}

/* ---- SVPWM ---- */
#define M_SQRT3_DIV2 0.8660254037844386f
#define PWM_MAX 1600

static void svpwm_calc(float va, float vb, float vc, uint16_t *a, uint16_t *b, uint16_t *c) {
  float vmax=va, vmin=va;
  if(vb>vmax) vmax=vb;
  if(vc>vmax) vmax=vc;
  if(vb<vmin) vmin=vb;
  if(vc<vmin) vmin=vc;
  float vo = -0.5f*(vmax+vmin);
  va+=vo; vb+=vo; vc+=vo;
  *a=(uint16_t)((va+0.5f)*(float)PWM_MAX);
  *b=(uint16_t)((vb+0.5f)*(float)PWM_MAX);
  *c=(uint16_t)((vc+0.5f)*(float)PWM_MAX);
}

static void foc_run(void) {
  if(!motor_running) { TIM4->CCR1=0; TIM4->CCR2=0; TIM4->CCR3=0; return; }

  // 更新角度
  foc_angle += foc_speed * 0.001f;
  while(foc_angle >= 6.2831853f) foc_angle -= 6.2831853f;

  // FOC计算
  float vd=0, vq=foc_vq;
  float va = vd*cosf(foc_angle) - vq*sinf(foc_angle);
  float vb = -0.5f*va + M_SQRT3_DIV2*(vd*sinf(foc_angle)+vq*cosf(foc_angle));
  float vc = -va - vb;

  uint16_t pa,pb,pc; 
  svpwm_calc(va,vb,vc,&pa,&pb,&pc);

  // 限制范围
  if(pa>PWM_MAX-1) pa=PWM_MAX-1;
  if(pb>PWM_MAX-1) pb=PWM_MAX-1;
  if(pc>PWM_MAX-1) pc=PWM_MAX-1;

  // 输出PWM
  TIM4->CCR1=pa; 
  TIM4->CCR2=pb; 
  TIM4->CCR3=pc;
}

/* ---- Serial CLI ---- */
static char cmd_buf[64]; static int cmd_idx=0; static int echo_on=0;

static void cmd_help(void) {
  uart_puts("\r\n=== FOC 命令 (默认值) ===\r\n"
    "start       - 启动电机\r\n"
    "stop        - 停止电机\r\n"
    "mode open   - 开环模式 (默认)\r\n"
    "mode closed - 电流闭环模式\r\n"
    "vq <0~1000> - 开环电压 x1000 (默认300, 最大1000)\r\n"
    "iq <0~4095> - 闭环电流ADC参考值 (默认100, 0~4095)\r\n"
    "sp <1~9999> - 电角速度 x10 rad/s (默认500, 最大9999)\r\n"
    "kp <0~999>  - 电流环 Kp x1000 (默认5, 最大999)\r\n"
    "ki <0~999>  - 电流环 Ki x1000 (默认1, 最大999)\r\n"
    "pp <1~50>   - 极对数 (默认6, 1~50)\r\n"
    "stat        - 显示当前状态\r\n"
    "angle       - 读取MA600角度\r\n"
    "spi XXXX    - 16bit帧SPI: 发XXXX, 收rx\r\n"
    "spi2 X Y    - 32bit帧SPI: 发X,Y, 收rx0 rx1\r\n"
    "echo on     - 自动打印 (默认OFF)\r\n"
    "echo off    - 停止自动打印\r\n"
    "help        - 显示帮助\r\n"
    "========================\r\n\r\n");
}

static void cmd_stat(void) {
  uart_puts(closed_loop?"CLOSED":"OPEN");
  uart_puts(" vq="); uart_puti((int)(foc_vq*1000));
  uart_puts(" iq="); uart_puti(iq_ref);
  uart_puts(" sp="); uart_puti((int)(foc_speed*10));
  uart_puts(" pp="); uart_puti(pole_pairs);
  uart_puts(" kp="); uart_puti((int)(pid_kp*1000));
  uart_puts(" ki="); uart_puti((int)(pid_ki*1000));
  uart_puts("\r\nRUN="); uart_puts(motor_running?"YES":"NO");
  uart_puts(" PWM="); uart_puti(TIM4->CCR1); uart_tx(','); uart_puti(TIM4->CCR2); uart_tx(','); uart_puti(TIM4->CCR3);
  uart_puts(" MA="); uart_puti((int)(MA600A_ReadRaw()));
  uart_puts("\r\n");
}

static void cmd_parse(char *cmd) {
  if(strequal(cmd,"help")) cmd_help();
  else if(strequal(cmd,"start")) {
    if(!motor_running){motor_running=1; GPIOB->ODR|=(1<<12)|(1<<13)|(1<<14)|(1<<15); uart_puts("STARTED\r\n");}
    else uart_puts("Already running\r\n");
  }
  else if(strequal(cmd,"stop")) {
    motor_running=0; TIM4->CCR1=0;TIM4->CCR2=0;TIM4->CCR3=0;
    GPIOB->ODR&=~((1<<12)|(1<<13)|(1<<14)|(1<<15)); uart_puts("STOPPED\r\n");
  }
  else if(strprefix(cmd,"mode ",5)) {
    if(strequal(cmd+5,"open")) { closed_loop=0; uart_puts("Mode: OPEN\r\n"); }
    else if(strequal(cmd+5,"closed")) { closed_loop=1; uart_puts("Mode: CLOSED\r\n"); }
    else uart_puts("? mode open/closed\r\n");
  }
  else if(strprefix(cmd,"vq ",3)) { 
    int v=myatoi(cmd+3); 
    if(v<0)v=0;if(v>1000)v=1000; 
    foc_vq=(float)v/1000.0f; 
    uart_puts("vq="); uart_puti(v); uart_puts("\r\n"); 
  }
  else if(strprefix(cmd,"iq ",3)) { 
    int i=myatoi(cmd+3); 
    if(i<0)i=0;if(i>4095)i=4095; 
    iq_ref=(uint16_t)i; 
    uart_puts("iq="); uart_puti(i); uart_puts("\r\n"); 
  }
  else if(strprefix(cmd,"sp ",3)) { 
    int s=myatoi(cmd+3); 
    if(s<1)s=1;if(s>9999)s=9999; 
    foc_speed=(float)s/10.0f; 
    uart_puts("sp="); uart_puti(s); uart_puts("\r\n"); 
  }
  else if(strprefix(cmd,"kp ",3)) { 
    int k=myatoi(cmd+3); 
    if(k<0)k=0;if(k>999)k=999; 
    pid_kp=(float)k/1000.0f; 
    uart_puts("kp="); uart_puti(k); uart_puts("\r\n"); 
  }
  else if(strprefix(cmd,"ki ",3)) { 
    int k=myatoi(cmd+3); 
    if(k<0)k=0;if(k>999)k=999; 
    pid_ki=(float)k/1000.0f; 
    uart_puts("ki="); uart_puti(k); uart_puts("\r\n"); 
  }
  else if(strprefix(cmd,"pp ",3)) { 
    int p=myatoi(cmd+3); 
    if(p<1)p=1;if(p>50)p=50; 
    pole_pairs=(uint8_t)p; 
    uart_puts("pp="); uart_puti(p); uart_puts("\r\n"); 
  }
  else if(strequal(cmd,"stat")) cmd_stat();
  else if(strequal(cmd,"echo on")) { echo_on=1; uart_puts("Echo ON\r\n"); }
  else if(strequal(cmd,"echo off")) { echo_on=0; uart_puts("Echo OFF\r\n"); }
  else if(strequal(cmd,"angle")) {
    uint16_t raw=MA600A_ReadRaw(); float deg=(float)raw*360.0f/65536.0f;
    uart_puts("raw="); uart_putx(raw); uart_puts(" deg="); uart_puti((int)deg); uart_puts("\r\n");
  }
  else if(strprefix(cmd,"spi ",4)) {
    uint16_t tx=(uint16_t)myatoi(cmd+4);
    uint16_t rx=MA600A_SPI_Exchange(tx);
    uart_puts("tx="); uart_putx(tx); uart_puts(" rx="); uart_putx(rx); uart_puts("\r\n");
  }
  else if(strprefix(cmd,"spi2 ",5)) {
    char *p = cmd+5;
    while(*p==' ') p++;
    uint16_t tx0=(uint16_t)myatoi(p);
    while(*p>='0'&&*p<='9') p++;
    while(*p==' ') p++;
    uint16_t tx1=(uint16_t)myatoi(p);
    uint16_t rx0=MA600A_SPI_Exchange(tx0);
    uint16_t rx1=MA600A_SPI_Exchange(tx1);
    uart_puts("tx="); uart_putx(tx0); uart_tx(' '); uart_putx(tx1);
    uart_puts(" rx="); uart_putx(rx0); uart_tx(' '); uart_putx(rx1); uart_puts("\r\n");
  }
  else { uart_puts("? '"); uart_puts(cmd); uart_puts("'  help?\r\n"); }
}

void USART1_IRQHandler(void) {
  if(*(volatile uint32_t*)(U1_BASE+0x00) & (1<<5)) {
    uint8_t c = *(volatile uint32_t*)(U1_BASE+0x04);
    if(c=='\r'||c=='\n') { cmd_buf[cmd_idx]=0; if(cmd_idx>0) cmd_parse(cmd_buf); cmd_idx=0; }
    else if(cmd_idx<63) cmd_buf[cmd_idx++]=c;
  }
}

/* ---- Main ---- */
int main(void) {
  /* HSE->PLLx9->72MHz */
  RCC->CR |= (1<<16); while(!(RCC->CR&(1<<17)));
  *(volatile uint32_t*)0x40022000 |= (1<<1);
  RCC->CFGR = (RCC->CFGR & ~(0xF<<18)) | (0x7<<18) | (1<<16) | (0x4<<8);
  RCC->CR |= (1<<24); while(!(RCC->CR&(1<<25)));
  RCC->CFGR = (RCC->CFGR & ~0x3) | 0x2;
  while((RCC->CFGR & (0x3<<2)) != (0x2<<2));

  *(volatile uint32_t*)0xE000E014 = 72000-1; *(volatile uint32_t*)0xE000E018=0; *(volatile uint32_t*)0xE000E010=7;

  /* GPIO clocks */
  RCC->APB2ENR |= (1<<2)|(1<<3)|(1<<4);

  /* PA8 LED */
  GPIOA->CRH = (GPIOA->CRH & ~0xF) | 0x3;

  /* PA4 CS for MA600A */
  GPIOA->CRL = (GPIOA->CRL & ~(0xF<<16)) | (0x3<<16);
  GPIOA->ODR |= (1<<4);

  /* PC8 key */
  GPIOC->CRH = (GPIOC->CRH & ~(0xF<<0)) | 0x8;
  GPIOC->ODR |= (1<<8);

  /* PB12-15 motor ctrl: output PP */
  GPIOB->CRH = (GPIOB->CRH & ~(0xFFFF<<16)) | (0x3333<<16);
  GPIOB->ODR &= ~((1<<12)|(1<<13)|(1<<14));
  GPIOB->ODR |= (1<<15);

  /* PB0 nFault: input pull-up */
  GPIOB->CRL = (GPIOB->CRL & ~0xF) | 0x8;

  /* TIM4 PWM: PB6/7/8 AF_PP */
  RCC->APB1ENR |= (1<<2);
  GPIOB->CRL = (GPIOB->CRL & ~(0xFF<<24)) | (0xBB<<24);  // PB6,PB7 = AF push-pull 50MHz
  GPIOB->CRH = (GPIOB->CRH & ~0xF) | 0xB;                // PB8 = AF push-pull 50MHz

  TIM4->PSC = 0;                          // 不分频,72MHz时钟
  TIM4->ARR = PWM_MAX-1;                  // 自动重装载值
  TIM4->CCR1 = 0; TIM4->CCR2 = 0; TIM4->CCR3 = 0;  // 初始占空比为0

  // CCMR1: CH1(PB6)和CH2(PB7)配置为PWM模式1,使能预装载
  TIM4->CCMR1 = (6<<4)|(1<<3) | (6<<12)|(1<<11);  // OC1M=110(PWM1), OC1PE=1, OC2M=110, OC2PE=1
  // CCMR2: CH3(PB8)配置为PWM模式1,使能预装载
  TIM4->CCMR2 = (6<<4)|(1<<3);                    // OC3M=110, OC3PE=1

  TIM4->CCER = (1<<0)|(1<<4)|(1<<8);     // 使能CH1,CH2,CH3输出
  TIM4->CR1 |= (1<<7);                   // ARPE=1 自动重装载预装载使能
  TIM4->CR1 |= (1<<0);                   // CEN=1 计数器使能
  TIM4->EGR |= (1<<0);                   // UG=1 产生更新事件

  /* SPI1 */
  RCC->APB2ENR |= (1<<12);
  GPIOA->CRL = (GPIOA->CRL & ~(0xFFF<<20)) | (0xB4B<<20);
  SPI1->CR1 = (1<<11)|(0x5<<3)|(1<<2)|(1<<1)|(1<<9)|(1<<8)|(1<<6);
  volatile uint16_t d=SPI1->DR; (void)d;

  /* I2C2 */
  RCC->APB1ENR |= (1<<22);
  GPIOB->CRH = (GPIOB->CRH & ~(0xFF<<8)) | (0xFF<<8);
  I2C2->CR2=36; I2C2->CCR=180; I2C2->TRISE=37; I2C2->CR1|=(1<<0);

  /* USART1: PA9=TX AF_PP, PA10=RX floating input, 115200 */
  RCC->APB2ENR |= (1<<14);  // Enable USART1 clock (APB2)
  GPIOA->CRH = (GPIOA->CRH & ~(0xFF<<4)) | (0x4B<<4);  // PA9=AF output 50MHz (TX), PA10=floating input (RX)
  /* BRR = fCLK/(16*baudrate) = 72000000/(16*115200) = 39.0625 = 0x271 (integer part in bits 15-4, fraction in bits 3-0) */
  *(volatile uint32_t*)(U1_BASE+0x08) = 0x271;  // 39.0625 -> mantissa=39(0x27), fraction=1
  *(volatile uint32_t*)(U1_BASE+0x0C) = (1<<13)|(1<<5)|(1<<3)|(1<<2);  // UE+RXNEIE+TE+RE
  /* Enable USART1 interrupt in NVIC (IRQ 37) - ISER[1] bit 5 */
  *(volatile uint32_t*)0xE000E104 |= (1<<(37-32));  // ISER[1] for IRQ 37

  int oled_ok = OLED_Init();
  MA600A_Init();

  if(oled_ok==0) OLED_Clear();

  uart_puts("\r\nF103 FOC Controller\r\n");
  cmd_help();

  uint32_t last=0, counter=0;
  uint8_t btn_last=1;
  while(1) {
    uint32_t t = tick_count;
    if(t-last==0) continue;
    last = t;

    /* PC8 button */
    uint8_t btn = !(GPIOC->IDR&(1<<8));
    if(btn_last==1 && btn==0) {
      motor_running = !motor_running;
      if(motor_running) { GPIOB->ODR|=(1<<12)|(1<<13)|(1<<14)|(1<<15); }
      else { TIM4->CCR1=0;TIM4->CCR2=0;TIM4->CCR3=0; GPIOB->ODR&=~((1<<12)|(1<<13)|(1<<14)|(1<<15)); }
    }
    btn_last=btn;

    /* FOC update at 1kHz (from 1ms tick) */
    if(motor_running) foc_run();

    /* OLED + LED every 200ms */
    counter++;
    if(counter>=200) {
      counter=0;
      GPIOA->ODR ^= (1<<8);
      uint16_t raw = MA600A_ReadRaw();
      float ang = (float)raw/65536.0f*360.0f;
      if(oled_ok==0) {
        OLED_ShowString(0,0,"RAW:"); OLED_ShowNum(24,0,raw,5);
        OLED_ShowString(60,0,"H:"); OLED_ShowHex(72,0,raw);
        OLED_ShowString(0,1,"ANG:"); OLED_ShowNum(24,1,(uint32_t)ang,3);
        OLED_ShowString(0,2,"RUN:"); OLED_ShowString(24,2,motor_running?"YES":"NO ");
        OLED_ShowString(0,3,"VQ:"); OLED_ShowNum(24,3,(int)(foc_vq*1000),4);
        OLED_ShowString(0,4,"PMW:"); OLED_ShowNum(24,4,TIM4->CCR1,4);
        OLED_ShowString(0,5,motor_running?"MOTOR:ON ":"MOTOR:OFF");
      }
    }

    /* Echo */
    if(echo_on && t%200==0) cmd_stat();
  }
}

void SysTick_Handler(void) { tick_count++; }

编译程序

cd g:\U3C5\STM32F103RCT6; make clean; make all

下载程序

总结

发现几个现象,每次我下载程序前先清除芯片,然后再下载,成功后按RESET启动,但是也出现很多次程序没有下载成功,让AI改半天也不行,然后无意中发现,重新给板子断电再上电就好了,而且遇见好几次,比如程序改完,灯不闪了,OLED屏不亮了,按RESET不行,AI改几次也不行,这时候可以试试重新断电

演示视频

这个设计的是真优秀

设计很好,stm32FOC控制性价比不太高,建议博主更换为国产IC。

那您帮忙推荐一款适合的性价比高的啊,不要小众的,编程麻烦

这是一个完成度很高的嵌入式底层驱动项目。作者对 STM32 的寄存器操作非常熟练,FOC 算法逻辑清晰。只要解决了下载器连接不稳定的问题,并优化一下代码结构,这将是一个非常有竞争力的参赛作品!