背景介绍

背景

嵌入式在汽车电子领域的应用

BMS:电池管理系统,限制充电放电温度电流大小等,包含限流模块(类似一个线圈)

BCM:车身控制器,对车体上的模块进行控制(一般在发动机左上角)

OBD:车载自诊断系统

关于通信频段:

芯片

国外单片机品牌:ST、德州仪器TI、恩智浦NXP

国内单片机品牌:兆易创新GD、MM、CH、HC

本次使用的芯片:STM32F103C8T6 ,手柄和飞控都用此芯片

单片机的组成:

  • Cortex-M3内核
  • 调试系统
  • 内部总线
  • 外设
  • 存储器
  • 时钟和复位
  • I/O

总线:

  • APB2总线(72MHZ)
  • AHB总线(系统总线)

外设:

  • 片上外设:存在于芯片内部的外设,出厂时由厂商集成在芯片内部的设备 | 例如: ADC,DAC,SPI,IIC,USART等
  • 片外外设: | 例如:蜂鸣器

存储器:

  • 内存(内存):SRAM
  • 外存(磁盘):FLASH(闪存)

| 根据片上flash的大小,对单片机容量进行了划分 |

时钟和复位:

  • 4个时钟源:
    • HSE:外部高速时钟(给予内核以时钟,外接晶振,稳定)
    • HSI:内部高速时钟(给予内核以时钟)
    • LSE:外部低速时钟(给予RTC以时钟,外接晶振,稳定)
    • LSI:内部低速时钟(给予RTC以时钟)
  • 复位:
    • 让程序恢复至最初位置重新运行
    • 冷复位:断点再上电
    • 热复位:上电时按下复位键
    • 复位是可控的、优先级最高的异常

IO口:

  • IO口是单片机和外接进行信息交换的唯一途径

编程方式

地址编程

[√] 寄存器编程(一堆地址按照逻辑集合在一起)

标准库编程(更加简单的编程方式)

HAL库编程(简洁的图形化编程,傻瓜式编程,使用软件STM32cubeMX)

本次使用寄存器编程

下载方式

ISP

ICP:ST-LINK、J-LINK、DAP-LINK(应用最为广泛)

IAP:固件升级技术

项目简介

遥控器:2.4G模块

飞机本体:姿态检测、闭环PID算法

GPIO的概述

区分端口号和管脚号

GPIO工作模式

通用型一般输入输出

GPIO示意图

image-20250113154632306

输入:

  • 上拉输入:默认输入一个高电平信号,TTL 逻辑‘1’,3.3V 左右
  • 下拉输入:默认输入一个低电平信号,TTL 逻辑‘0’,0.2V 左右
  • 浮空输入:默认输入一个极性不确定的TTL电平(电路如果已有初始电平状态则可以使用)
  • 模拟输入:和 ADC(模数转换器)一起使用(模拟信号经过TTL肖特基触发器后可得到方波)

输出:

  • 通用推挽输出:可以输出高低电平
  • 通用开漏输出:只能输出地低电平
  • 复用推挽输出:片上外设输出高低电平
  • 复用开漏输出:片上外设输出低电平

GPIO输出

GPIO寄存器描述

CRH CRL只跟管脚号有关,每个管脚对应四位

  • CRL:端口配置低寄存器
  • CRH:端口配置高寄存器

NOP延迟的实现

阻塞延迟:

  • _nop();

非阻塞延迟:

  • 定时器+状态机

delay.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include "delay.h"

void Delay_us(uint32_t timers)
{
while(timers--)
{
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();
__nop();__nop();
}

}

delay.h

1
2
3
4
5
6
7
8
9
10
#ifndef _DELAY_H
#define _DELAY_H

#include "stm32f10x.h"

#define Delay_ms(X) Delay_us(X*1000)

void Delay_us(uint32_t timers);

#endif

STM32F103VET6 单片机主频最大为 72MHz

T=1/f=1/72000000s=1/72000000*10^6us=1/72us

1us=72*T(72个__nop();)


1 编写LED灯、蜂鸣器代码
2 使用延时函数实现流水或呼吸显示
3 编写按键控制函数,可以达到对灯或蜂鸣器的控制


LED灯、蜂鸣器的配置代码

gpio.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include "gpio.h"

/**********************
功能描述:LED_Config
功能:配置LED引脚
日期:2025年01月13日
硬件:PB9-LED1
PB10-LED2
**********************/
void LED_Config(void)
{
// 启用GPIOB时钟
RCC->APB2ENR |= (0X1 << 3);

// 配置PB9为推挽输出模式
GPIOB->CRH &=~ (0XF << 4);
GPIOB->CRH |= (0X1 << 4);

// 设置PB9为高电平-点亮LED
GPIOB->ODR |= (0X1 << 9);
//GPIOB->ODR &=~ (0X1 << 9);//设置PB9为低电平-熄灭LED
}

/**********************
功能描述:Beep_Config
功能:配置蜂鸣器引脚
日期:2025年01月13日
硬件:蜂鸣器-FMQ-PB10-蜂鸣器
**********************/
void Beep_Config(void)
{
// 启用GPIOB时钟
RCC->APB2ENR |= (0X1 << 3);

// 配置PB10为推挽输出模式
GPIOB->CRH &=~ (0XF << 8); //
GPIOB->CRH |= (0X1 << 8);

// 设置PB10为高电平-开启蜂鸣器
GPIOB->ODR |= (0X1 << 10);
//GPIOB->ODR &=~ (0X1 << 10);//设置PB10为低电平-关闭蜂鸣器
GPIOB->BSRR |= (0X1 << 10);
}

gpio.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifndef _GPIO_H
#define _GPIO_H

#include "stm32f10x.h"


void LED_Config(void);


//x为真时,灯亮 x为假时,灯灭
//:冒号前后分别是亮灯代码和灭灯代码
#define LED1_Ctrl(x) (x)?(GPIOB->ODR |= (0X1 << 9)):(GPIOB->ODR &=~ (0X1 << 9))

#endif

beep.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include "beep.h"
#include "gpio.h"
#include "delay.h"


/**********************
功能描述:Beep_Config
功能:配置蜂鸣器引脚
日期:2025年01月13日
硬件:蜂鸣器-FMQ-PB10-蜂鸣器
**********************/
void Beep_Config(void)
{
// 启用GPIOB时钟
RCC->APB2ENR |= (0X1 << 3); //x1 << 3 将数字 1 左移 3 位,相当于 0x8,表示启用 GPIOB 的时钟。

// 配置PB10为推挽输出模式
GPIOB->CRH &=~ (0XF << 8);
GPIOB->CRH |= (0X1 << 8);

// 设置PB10为高电平-开启蜂鸣器
GPIOB->ODR |= (0X1 << 10);
//GPIOB->ODR &=~ (0X1 << 10);//设置PB10为低电平-关闭蜂鸣器
//<<右边写几就是对应的引脚
GPIOB->BSRR |= (0X1 << 10);
}

void Beep_init(void) {
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}

void Beep_on(void){

GPIO_ResetBits(GPIOB,GPIO_Pin_10);
Delay_us(20);
GPIO_SetBits(GPIOB,GPIO_Pin_10);
Delay_us(20);
GPIO_ResetBits(GPIOB,GPIO_Pin_10);

}

main.c

1
2
3
4
5
6
7
8
9
10
#include "stm32f10x.h"
#include "gpio.h"
#include "delay.h"

int main(void)
{
LED_Config();
LED1_Ctrl(1);//LED亮灯

}

寄存器配置的一般方法

三目运算符

三目运算符概述

1
(X)?(Y):(Z)

当X=1即X为真,执行Y语句

当X=0即X为假,执行Z语句

进行宏定义

类似代码

1
2
3
4
5
6
//逻辑伪代码
#define LED1_Ctrl(x) (x)?(亮灯程序):(灭灯程序)


//LED1的宏定义代码
#define LED1_Ctrl(x) (x)?(GPIOB->ODR |= (0X1 << 9)):(GPIOB->ODR &=~ (0X1 << 9))

在主函数中调用

1
LED1_Ctrl(1); //调用亮灯程序

呼吸灯

使用delay的方式实现:

1
2
3
4
5
6
7
8
9
10
void LED_Breathe(void)
{
for(int i =0 ;i<1000;i++)
{
LED1_Ctrl(1);LED2_Ctrl(1);
Delay_us(i);
LED1_Ctrl(0);LED2_Ctrl(0);
Delay_us(1000-i);
}
}

按键

消除抖动

按键获取键值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
uint8_t KEY_GetVal(void)
{
if(() & ()==0)
{
Delay_ms(20);
if()
{
while(()&())
return 1;
}

}


}

异常与中断

手册的5.1节给出了异常模型

中断数目可配置为1~240

优先级

优先级数字越小,对应的优先级越高

  • 占先优先级
  • 次级优先级

只有占先高的才能打断占先低的,若占先一致且同时抵达,则次级高的先执行

假设此时有三个ISR,A,B,C

占先 次级
A 0 1
B 1 1
C 1 0

外部中断/事件控制器EXIT

9.2节

他有多种选择触发器,根据需求进行选择

请求挂起寄存器EXIT_PR

模拟/数字转换(ADC)

ADC位数越大,采得每份电压值越小,ADC精度越高

逐次逼近型

通道

有16个多路通道。可以把转换组织成两组:规则通道与注入通道


DMA

DMA,全称Direct Memory Access,直接存储器访问,是一种硬件机制,允许数据在内存和外设之间直接传输,无需CPU介入。(可视为数据搬运工,只负责搬运数据,本身无存储空间)

DMA的数据传输方向:

  • 偏上外设 ==> 存储器
  • 存储器 ==> 片上外设
  • 存储器 ==> 存储器

独立数据源:从哪里取出数据

目标数据区:数据放到哪里去

DMA资源:DMA1有7个通道,DMA2有5个通道

DMA2和ADC3都只存在于大容量和互联设备中

DMA可协作CPU搬运外设的数据,但值得注意的是,DMA优先级是低于内核CPU的。

从外设数据寄存器或者从当前外设/存储器地址寄存器指示的存储器地址取数据,第一次传输时的开始地址是DMA_CPARx或DMA_CMARx寄存器指定的外设基地址或存储器单元。

存数据到外设数据寄存器或者当前外设/存储器地址寄存器指示的存储器地址,第一次传输时的开始地址是DMA_CPARx或DMA_CMARx寄存器指定的外设基地址或存储器单元。

执行一次DMA_CNDTRx寄存器的递减操作,该寄存器包含未完成的操作数目。

数据传输的宽度

  • 字(8位)
  • 半字(16位)
  • 全字(32位)

2.4G通信模块

数据链路层:

AT指令集——modbus通讯协议:串行通信协议

网络层:

TCP协议簇、MQTT协议

IIC协议

协议类似USART(串口)、SPI

IIC的特点:串行、同步、半双工

硬件层:

​ SCL时钟线:给主机和从机提供同步时钟,确保通信的稳定性

​ SDA数据线,传输数据

IIC的数据有效性:

SDA的状态只能在SCL为低电平的时候改变

当SCL为高电平时,SDA必须保持稳定(保持高或者低电平不能更改),当SCL为低电平时,才能改变电平状态

读写位在地址后面的原因

确认读还是写数据(读写输出输入有不同的状态)

PID算法

开环和闭环(PID是闭环中用的最多的)

P:比例

I:积分

D:微分


问题的解决:

使用ST-LINK进行代码烧录的情况下

下图为STLINK下载器

image-20250114111658130

在没有进行驱动安装的时候,直接点击keil软件中的load按钮是无法写入的,可能会报一个”Cannot Load Flash Device Description!”的错误日志

1,在keil软件中点击下面的按钮

image-20250113195101494

确保右下角显示为ONLINE在线状态

点击Keil::STM32F1xx_DFP(以使用的具体芯片为准)右侧install,在安装完成之后,会显示为Up to date

image-20250113194829601

由于需要使用ST-LINK下载器(类似于USB的一个设备)下载程序,故还需要安装此下载器的驱动

驱动已上传google云盘:STLINK Utility.zip链接下载

下载并解压完成之后,先进入STLINK Utility这个文件夹,双击setup.exe,安装完成后在点击STLINK驱动文件夹中的ST-LinkUpgrade.exe,此时将STLINK下载器插入usb口(注意不要在STLINK上插开发板),点击Device Connect,就可以显示连接上了STLINK,随后点击升级按钮即可。

在做完上述步骤后,重新启动keil,并连接好下载器、单片机,点击

在keil中编译好,然后点击load即可成功将程序烧录进开发板

image-20250113201651884

使用…烧录代码的情况下