嵌入式相关

Keil MDK 工程建立

在建立工程之前,建议大家在电脑的某个目录下面建立一个文件夹,后面所建立的工程都可以放在这个文件夹下面,工程路径切勿带有 中文字符 !这里我们建立一个文件夹为 STM32-LED

点击 MDK 菜单:Project –> New Uvision Project ,然后将目录定位到刚才建立的文件夹 STM32-LED之下,在这个目录下面建立子文件夹 Project (名称随意)。

img

工程命名为 Led(名称随意),点击保存。

img

img

3.接下来会出现一个选择 CPU 的界面,就是选择我们的芯片型号。这里我使用的芯片是 STM32F103C8T6,所以如下选择。

STMicroelectronics -> STM32F1 -> Series -> STM32F103 -> STM32F103C8T6,最后点击OK即可。

也可以在 Search 框中直接搜索选择

img

img

4.点击 OK,MDK 会弹出 Manage Run-Time Environment 窗口。在这个界面,我们可以添加自己需要的组件,从而方便构建开发环境。我们暂时用不上所以直接点击 Cancel 。

img

随后出现下图界面:

img

工程目录下的 LED.uvprojx 是工程文件,Listings 和 Objects文件夹是 MDK 自动生成的文件夹,用于存放编译过程产生的中间文件。我习惯把这两个文件夹删除,然后重新新建一个 OBJ 文件夹,用来存放编译中间文件。当然,不删除这两个文件夹也是没有关系的,个人习惯而已。

5.接下来,我们在 Project 工程目录下面,新建 4 个文件夹 CORE, OBJ 以及STM32F10X_FWLIB。

CORE 用来存放核心文件和启动文件,OBJ 是用来存放编译过程文件以及 hex 文件

STM32F10X_FWLIB文件夹顾名思义用来存放 ST 官方提供的库函数源码文件

User存放实验的驱动文件

已有的 Project目录除了用来放工程文件外,还用来存放主函数文件 main.c,以及其他包括 system_stm32f10x.c 等等。

img

6.下面我们要将官方的固件库包里的源码文件复制到我们的工程目录文件夹下面。打开官方固件库包,定位到我们之前准备好的固件库包的目录下面,将目录下面的 src,inc 文件夹 copy 到我们刚才建立的 STM32F10X_FWLIB 文件夹下面。src 存放的是固件库的.c 文件,inc 存放的是对应的.h 文件。

img

7.下面我们要将固件库包里面相关的启动文件复制到我们的工程目录 CORE 之下。打开官方固件库包,将文件 core_cm3.c和 core_cm3.h 文件复制到 CORE 下 面 去 。 startup_stm32f10x_hd.s 文件复制到 CORE 下面。

img

8.将三个文件 stm32f10x.h,system_stm32f10x.c,system_stm32f10x.h,复制到我们的 Project目录之下。

然后将下面的 4 个文件main.c,stm32f10x_conf.h,stm32f10x_it.c,stm32f10x_it.h 复制到 Project 目录下面。

img

9.创建System文件夹存放system.c system.h systick.c systick.h usart.c usart.h文档

img

10.前面 8 个步骤,我们将需要的固件库相关文件复制到了我们的工程目录下面,下面我们将这些文件加入我们的工程中去。右键点击 Target1,选择 Manage Components。

img

\11. Project Targets一栏,我们将Target名字修改为 LED,然后在Groups一栏删掉一个Source Group1,建立三个 Groups:USER,CORE,FWLIB。然后点击 OK,可以看到我们的 Target名字以及 Groups 情况。

img

点击OK

img

12.下面我们往 Group 里面添加我们需要的文件。我们按照步骤 10 的方法, 右键点击点击 LED,选择选择 Manage Components.然后选择需要添加文件的 Group,这里第一步我们选 择 FWLIB , 然 后 点 击 右 边 的 Add Files, 定 位 到 我 们 刚 才 建 立 的 目 录

STM32F10x_FWLib/src 下面,将里面所有的文件选中(Ctrl+A),然后点击 Add,然后 Close.可以看到 Files 列表下面包含我们添加的文件。这里需要说明一下,对于我们写代码,如果我们只用到了其中的某个外设,我们就可以不用添加没有用到的外设的库文件。这里我们全部添加进来是为了后面方便,不用每次添加,当然这样的坏处是工程太大,编译起来速度慢,用户可以自行选择。

img

13.用同样的方法,将 Groups 定位到 CORE 和 USER 下面,添加需要的文件。这里我们的 CORE 下面需要添加的文件为 core_cm3.c,startup_stm32f10x_hd.s ( 注意,默认添加的时候文件类型为.c, 也就是添加 startup_stm32f10x_hd.s 启动文件的时候,你需要选择文件类型为 为 All files 才能看得到这个文件),USER 目录下面需要添加的文件为 main.c,stm32f10x_it.c,system_stm32f10x.c,SYSTEM添加system.c system.h systick.c systick.h usart.c usart.h.

这样我们需要添加的文件已经添加到我们的工程中了,最后点击 OK,回到工程主界面。

img

img

img

img

最后效果图如下:

img

14.接下来我们要编译工程,在编译之前我们首先要选择编译中间文件编译后存放目录。方法是点击魔术棒,然后选择“Output”选项下面的“Select folder for objects…”,然后选择目录为我们上面新建的 OBJ 目录。这里大家注意,如果我们不设置 Output 路径,那么默认的编译中间文件存放目录就是 MDK 自动生成的 Objects 目录和 Listings 目录。

img

15.添加头文件路径

img

img

16.接下来,我们再来编译工程,可以看到又报了很多同样的错误。为什么呢?这是因为 3.5 版本的库函数在配置和选择外设的时候通过宏定义来选择的,所以我们需要配置一个全局的宏定义变量。按照步骤 16,定位到 c/c++界面,然后填写

“STM32F10X_HD,USE_STDPERIPH_DRIVER”到 Define 输入框里面。这里解释一下,如果你用的是中容量那么 STM32F10X_HD 修改为 STM32F10X_MD,小容量修改为 STM32F10X_LD. 然后点击 OK。

img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 启动文件以及宏定义与芯片对应关系

startup_stm32f10x_cl.s
——互联型的器件,包括:STM32F105xx,STM32F107xx

startup_stm32f10x_hd.s
——大容量器件,包括:STM32F101xx,STM32F102xx,STM32F103xx

startup_stm32f10x_hd_vl.s
——大容量器件,包括:STM32F100xx

startup_stm32f10x_ld.s
——小容量器件,包括:STM32F101xx,STM32F102xx,STM32F103xx

startup_stm32f10x_ld_vl.s
——小容量器件,包括:STM32F100xx

startup_stm32f10x_md.s
——中容量器件,包括:STM32F101xx,STM32F102xx,STM32F103xx

startup_stm32f10x_md_vl.s
——中容量器件,包括:STM32F100xx

17.这样一个工程模版建立完毕。下面还需要配置,让编译之后能够生成 hex 文件。同样点击魔术棒,进入配置菜单,选择 Output。然后勾上下三个选项。 其中 Create HEX file 是编译生成 hex 文件,Browser Information 是可以查看变量和函数定义。

img

程序编译流程

预处理 –> 编译 –> 汇编 –> 链接

GPIO

LED电路分析

LED电路分析

1. GPIO功能描述

每个GPI/O端口有两个32位配置寄存器(GPIOx_CRL,GPIOx_CRH),两个32位数据寄存器(GPIOx_IDR和GPIOx_ODR),一个32位置位/复位寄存器(GPIOx_BSRR),一个16位复位寄存器(GPIOx_BRR)和一个32位锁定寄存器(GPIOx_LCKR)。一个GPIO端口就有7个寄存器进行控制。

根据数据手册中列出的每个I/O端口的特定硬件特征, GPIO端口的每个位可以由软件分别配置

成多种模式。

​ ─ 输入浮空

​ ─ 输入上拉

​ ─ 输入下拉

​ ─ 模拟输入

​ ─ 开漏输出

​ ─ 推挽式输出

​ ─ 推挽式复用功能

​ ─ 开漏复用功能

2. 特殊引脚

img

以上3个引脚需要使用时,需要按一下方式配置(SWJ-CFG[2:0] = 010);

img

4. 配置GPIO

寄存器开发:直接开发方式,控制更为高效;开发效率低,开发难度大;

库函数开发:实际上也是对寄存器进行开发,各种函数对寄存器控制进行包装,程序可移植强;控制效率更低

GPIO输出

GPIO输出配置步骤

1, 开启GPIO端口时钟

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

2,初始化GPIO引脚配置

GPIO_Init(GPIOB, &GPIO_InitStruct);

3,初始化 PB0 状态

GPIO_ResetBits(GPIOB, GPIO_Pin_0); //置0

GPIO_SetBits(GPIOB, GPIO_Pin_0); //置1

GPIO输入

模拟输入:专门接收模拟信号(连续变化的,有一定范围变化值)

浮空输入:外部电路不需要默认电平的数字电路信号输入(内部没有电阻)

上拉输入:外部电路需要默认高电平的数字电路信号输入(内部自带上拉电阻)

下拉输入:外部电路需要默认低电平的数字电路信号输入(内部自带下拉电阻)

img

GPIO输入的配置步骤

1, 开启GPIO端口时钟

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

2,初始化GPIO引脚配置

GPIO_Init(GPIOA, &GPIO_InitStruct);

3,读取GPIO电平状态

uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);

uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);

image-20210919120453210

USART(通用同步异步收发器)

串行通信/并行通信

串行通信:数据按一定的排列顺序逐位通信;

并行通信:数据按一定的排列顺序同时通信;

单工、半双工、全双工

单工:单向通信;收音机,电视机

半双工:数据在同时只能流向一个方向;对讲机

数据线:

串行通行:1

并行通信:数据位数

全双工:数据在同时交互;手机

数据线:

串行通行:2

并行通信:数据位数 * 2

通行方式讲解

参数

同步异步

行半双工和全双工通信。异步通信,发送方不需要理会接收方是否在线

波特率

波特(Baud)即调制速率,指的是有效数据讯号调制载波的速率,即单位时间内载波调制状态变化的次数。波特率即指一个单位时间内传输符号的个数。确保通信的时钟一致的(时钟对齐的桥梁)。

数据字长度:8B/9B

8B:没有校验位,则选择8B;

9B:有校验位,则选择9B;

检测标志

l 接收缓冲器满:提醒用户及时转移数据

l 发送缓冲器空:提醒用户数据发送完毕

l 传输结束标志:提醒用户通信结束

校验:奇校验、偶校验、无校验

l 偶校验:校验位使得一帧中的7或8个LSB数据以及校验位中’1’的个数为偶数。 例如:数据=00110101,有4个’1’,如果选择偶校验(在USART_CR1中的PS=0),校验位将是’0’。

l 奇校验:此校验位使得一帧中的7或8个LSB数据以及校验位中’1’的个数为奇数。 例如:数据=00110101,有4个’1’,如果选择奇校验(在USART_CR1中的PS=1),校验位将是’1’。

中断 NVIC

STM32中断机制(NVIC)

中断在硬件中的作用:非常大的作用;提高CPU运行效率,伪多线程操作;作为系统紧急措施;

STM32使用中断时需要注意

① 中断服务函数时间不适宜过长;

② 中断服务函数中,尽量不使用延时,必要时尽量短(10ms以下)

③ 中断服务函数中不要使用 printf (占用时间长)

STM32中断优先级机制

抢占优先级:(霸道)在一个中断响应中,高抢占优先级的可以中断低抢占优先级的中断;主要优先级

响应优先级:(文明)两个中断在抢占优先级相同的情况下,同时发生中断,高响应优先级优先响应;次要优先级

优先级相同时,谁先触发

抢占优先级和响应优先级是占用相同的寄存器位(4位)

中断优先级分组

中断优先分组,中途更改的危害

配置步骤

  1. NVIC 优先级分组写在主函数,且之后不再重新分组(习惯)
1
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);  
  1. 初始化
1
2
3
4
5
// 初始化 NVIC
NVIC_Init(&NVIC_InitStruct);

// 开启中断请求的使能
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
  1. 中断服务函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 在“startup_stm32f10x_md.s”文件中定义了中断服务函数,是以 IRQHandler 结尾
// 在自己的代码中编写如下函数
void USART1_IRQHandler()
{
// 1. 判断中断类型
if (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET)
{

// 2.处理中断
// ...

// 3.清除中断标志位
USART_ClearFlag(USART1, USART_FLAG_RXNE);
}
}

C 相关

宏定义

宏定义的本质是替换

所有的宏定义都会将宏替换成定义的内容;

#define N 3 之后程序中出现N都会替换成3

代参宏:

#define Y(n) ((N+1)*n) (参数为n)

Y(3) —> ((N+1)*3)

Y(5+1) –> ((N+1)*5+1)

Y((5+1)) –> ((N+1)*(5+1))

宏定义的运用

使用宏定义传参时注意,表达式不具引用透明性,如 i++,这回造成意料之外的结果。

表达式具有引用透明性: i + 1,没有问题。

1
2
3
4
5
#define SQUARE(n) ((n)*(n))

int x=1;
int y1 = SQUARE(x + 1); //ok
int y2 = SQUARE(x++); //undefined behaviour
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#define MIN(x, y) x > y ? y : x

int main()
{
int i = 3, j = 5, k;

k = MIN(i++, j++);

printf("i=%d j=%d k=%d", i, j, k);

return 0;
}

// 输出:i=5 j=6 k=4

| 和 || 区别

|:位或(&:位与) 处理二进制数据(对数据)

||:逻辑或(&&:逻辑与)处理逻辑关系(对关系)

变量

静态变量

定义:静态变量的生命周期跟程序一样,某一些编译器自动赋值为0,不一定全文件可用;

建议手动进行赋值,

与局部变量的差异性:

(1)生命周期不一样;

(2)赋值;

(3)存储区域

静态变量:读写区

局部变量:栈区

全局变量:读写区

全局变量

定义:全局变量的生命周期跟程序一样,某一些编译器自动赋值为0,一定是全文件可用,但是到了其他文件中就不可以使用了;

外部引用(extern)

(1)外部引用变量:实现全局变量在其他文件中使用;

(2)外部引用编译器:实现混合编程(汇编语言、C语言、C++语言…)

头文件定义变量?

不可以的,因为在多个文件同时引用该头文件时,会出现多重定义;

头文件的引用:实际是复制

C 指针

1. 函数的形参使用指针类型的作用

(1) 改变实参的值(多个返回值)

(2) 结构体或者数组

2. 函数形参与实参的关系

形参:函数的输入参数;(函数内部)

实参:函数调用时,给定的值;(函数外部)

形参 = 实参;(内存地址时不一样的,内存大小根据定义的类型)

3. 指针的操作

赋值: &取地址:获取变量、常量的地址

(解引用)取值: * 不带偏移

​ [ ] 带偏移

运算 +,- 地址偏移(偏移的大小看数据类型*偏移量)

DHT11 温湿度传感器

DHT11

DHT11 数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器。它应用专用的数

字模块采集技术和温湿度传感技术,确保产品具有枀高的可靠性与卓越的长期稳定性。传感器包括一

个电容式感湿元件和一个 NTC 测温元件,并与一个高性能 8 位单片机相连接。

应用范围:

暖通空调、除湿器、农业、冷链仓储、测试及检测设备、消费品、汽车、自动控制、数据记录器、气象站、家电、湿度调节器、医疗、其他相关湿度检测控制。

img

即,要求程序不能频繁测量,至少间隔2s以上

单总线串行通信:

单总线即只有一根数据线,系统中的数据交换、控制均由单总线完成。设备(主机或从机)通过一个漏极开路(OD)或三态端口连至该数据线,以允许设备在不发送数据时能够释放总线,而让其它设备使用总线;单总线通常要求外接一个约 4.7kΩ 的上拉电阻,当总线闲置时,其状态为高电平。

数据位定义:

一次传送 40 位(5字节)数据,高位先出

数据格式:

8bit 湿度整数数据 + 8bit 湿度小数数据 + 8bit 温度整数数据 + 8bit 温度小数数据 + 8bit 校验位。

注:其中湿度小数部分为 0。

校验位数据定义:

“8bit 湿度整数数据 + 8bit 湿度小数数据 + 8bit 温度整数数据 + 8bit 温度小数数据”结果的末 8 位等于8bit 校验位。

img

STM32发送一次开始信号后,DHT11 从低功耗模式转换到高速模式,待STM32开始信号结束后,DHT11 发送响应信号,送出 40bit 的数据。信号发送如图所示。

img

img

imgimg

OLED

1. OLED技术特点

(1) OLED 器件的核心层厚度很薄,厚度可以小于 1mm,为液晶的 1/3。

(2) OLED 器件为全固态机构,无真空,液体物质,抗震性好,可以适应巨大的加速度,振动等恶劣环境。

(3) 主动发光的特性使 OLED 几乎没有视角限制,视角一般可达到 170 度,具有较宽的视角,从侧面也不会失真。

(4) OLED 显示屏的响应速度超过 TFT—LCD 液晶屏。TFT—LCD 的响应时间大约使几十毫秒,现在做得最好的 TFT—LCD 响应时间也只有 12 毫秒。而 OLED 显示屏的响应时间大约是几微秒到几十微秒。

(5) OLED 低温特性好,在零下 40 摄氏度都能正常显示,目前航天服上也使用OLED 作为显示屏。而 TFT—LCD 的响应速度随温度发生变化,低温下,其响应速度变慢,因此,液晶在低温下显示效果不好。

(6) OLED 采用有机发光原理,所需材料很少,制作上比采用液体发光的液晶工序少,液晶显示屏少 3 道工序,成本大幅降低。

(7) OLED 采用的二极管会自行发光,因此不需要背面光源,发光转化效率高,能耗比液晶低,OLED 能够在不同材质的基板上制造,厂家甚至可以将电路印刷在弹性材料上——做成能弯曲的柔软显示器。

(8) 低电压直流驱动,5V 以下,用电池就能点亮。高亮度,可达 300 明流以上。

2. OLED引脚

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
GND

VCC

SCL -->D0(18) -->SCLK -->PA5

SDA -->D1(19) -->SDIN -->PA7

RES -->RES#(14) -->RES# -->PA3

DC -->D/C#(15) -->D/C# -->PA6

CS -->CS#(13) -->CS# -->PA4

E -->GND

R/W# -->GND

img

1
2
3
4
5
BS0 --> GND

BS1 --> GND

BS2 --> GND

所以我们的OLED选择4线的SPI通信

img

3. OLED通信控制

4线的SPI通信

4线串行接口由串行时钟SCLK、串行数据SDIN、D/ C#、CS#组成。在4线SPI模式下,D0作为SCLK,D1作为SDIN。对于未使用的数据引脚,D2应该保持开路状态。从D3到D7、E和R/W# (WR#)#可连接到外部接地。

img

写命令时: D/C#–>GND; CS#–>GND;

写数据时: D/C#–>VCC; CS#–>GND;

SDIN按D7, D6,…的顺序在SCLK的每一个上升边上移位到一个8位移位寄存器中。D - D/ c#每8个时钟采样一次,并将移位寄存器中的数据字节写入图形显示数据寄存器 (GDDRAM)或同一时钟中的命令寄存器。在串行模式下,只允许写操作;

img

4. 程序移植步骤

(1) 读需要移植的工程文件,了解工程架构

(2) 复制关键的文件,到我们的工程中

(3) 修改GPIO及其外设资源

(4) 修改一下基础通信(SPI)

(5) 调试程序(处理错误,实现效果)

SPI通信方式

其他

项目开发的流程

① 分析需求:功能点、使用的场景、技术角度;

② 选择设备或环境;

③ 基础的架构(技术方案);

④ 模块化实现;

⑤ 各模块的联合调试;

⑥ 系统整体调试;

⑦ 交由测试人员测试;

⑧ 产品生产;