本人花费5天时间完成了stm32的基础, 小累, 现在总结一下

总述

STM32介绍

st 公司名
m32 微处理器32位
F103 产品系列
C8T6 规格型号

产品系列

G 通用
F 主流
H 高性能
L 低功耗
W 无线

规格型号

字母 代表 补充
C 引脚数 T=36 C=48
8 Flash容量 4 = 16kb, 8 = 64kb
T 封装类型 T=LQFP
6 温度范围 6 = -40℃ ~85℃

引脚

VDD 供电(3.3v)
VSS 地(0v)
NRST 复位
VBAT 备用电池
BOOTO 启动模式
其他 片上外设名+数字

比喻关系

stm32 人体
时钟 心脏
片上外设 器官

时钟

时钟的介绍和作用

时钟信号是一种电子逻辑信号(电压或电流),它以恒定频率在高低状态之间振荡,并像节拍器一样用于同步数字电路的动作

故: 片上外设在使用前必须打开对应的时钟

1
2
3
4
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
// RCC - Reset and Clock Control
// APBx - APB时钟线
// PeriphClockCmd - 外设时钟开关

时钟树

灰色默认关闭, 绿色默认开启
图示代码可以自己配置时钟树, stm32的startup文件里有自己的时钟树配置(可以注释掉)

|树根|HSI HSE|
|-|-|
|树干|SYSCLK HCLK|
|树枝|PCLK1 PCLK2|
|树叶|片上外设…|

树根

  • HSE(High SpeedExternal) HSI(High SpeedInternal) 通常外接时钟频率更稳定

背景: HSE使用外部晶体或谐振器作为时钟源。这些外部元件的频率精度和稳定性通常非常高,因为它们是由石英等材料制成的,具有精确的物理特性。
相比之下,HSI是内部RC振荡器,其频率受到温度、电压和制造工艺变化的影响,因此稳定性较差。

分频器, 锁相环, 复用器
|分频器|DIV|频率除法|
|-|-|
|锁相环|PLL|频率乘法|
|复用器|MUX|频率选择|

树干

  • SYSCLK$\leq$72MHz
  • HCLK $\leq$72MHz
    SYSCLK的三种来源
HSE 4~16MHz
HSI 8MHz
PLL $\leq$72MHz

树枝

---
title: 树枝
---
flowchart LR
    i1("SYSCLK(<= 72MHz>)")
    i2("HCLK(<= 72MHz>)")
    i3("PCLK1(<= 36MHz>)")
    i4("PCLK2(<= 72MHz>)")
    i1-->|AHB|i2
    i2-->|APB1|i3
    i2-->|APB2|i4

树叶

1
2
3
4
5
6
7
8
9
// 开启片上外设
RCC_APB2PeriphClockCmd(外设名字, ENABLE);
RCC_APB1PeriphClockCmd(外设名字, ENABLE);
RCC_AHBPeriphClockCmd(外设名字, ENABLE);

// 复位片上外设
RCC_APB2PeriphResetCmd(外设名字, ENABLE);
RCC_APB2PeriphResetCmd(外设名字, DISABLE);

实战

自己配置时钟树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void My_SystemCLK_Init(void){
// 0. 指令预取
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
FLASH_SetLatency(FLASH_ACR_LATENCY_2); // SYS <= 24MHz 0, SYS <= 48MHz 1 , SYS <= 72MHz 2
// 1. 打开HSE并等待
RCC_HSEConfig(RCC_HSE_ON);
while(RCC_GetFlagStatus(RCC_FLAG_HSERDY) == RESET);
// 2. 配置SYSCLK
RCC_PLLCmd(ENABLE);
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
while(RCC_GetSYSCLKSource() != RCC_SYSCLKSource_PLLCLK);
// 3. 配置HCLK, PCLK1, PCLK2
RCC_HCLKConfig(RCC_SYSCLK_Div1);
RCC_PCLK1Config(RCC_HCLK_Div2);
RCC_PCLK2Config(RCC_HCLK_Div1);
}

利用CubeMX配置时钟树

将SYSCLK调到最大频率,剩下按需分频

片上外设

GPIO

简介

一个stm32的传输信号的通道

GPIO的引脚

名称 引脚
GPIOA PA(0~15)
GPIOB PB(0~15)
GPIOC PC(13~15)
GPIOD PD(0~1)

GPIO的工作模式

输出

输出模式名称

中文 英文
通用输出推挽 OUT_PP
通用输出开漏 OUT_OD
复用输出推挽 AF_PP
复用输出开漏 AF_OD

输出模式解释

名称 解释
通用 CPU写值
复用 其他外设写值
推挽 0- 低电压 1- 高电压
开漏 0- 低电压 1- 高阻抗
输入
中文 英文 解释
输入上拉 IPU 默认为1
输入下拉 IPD 默认为0
输入浮空 IN_FLOATING 电信号不稳定
模拟模式 AIN 用于ADC的模拟信号输入

GPIO的最大输出速率

注意: 越快越耗能

  1. Low - 2MHz
  2. Medium - 10MHz
  3. High - 50MHz

GPIO的默认电压

  1. GPIO_PULLUP
  2. GPIO_PULLDOWN
  3. 不设置

GPIO的初始化

1
2
3
4
5
6
7
8
9
10
11
12
void My_OnBoardLEDInit(void){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE) = {0}; // 注意要初始化为0

GPIO_InitTypeDef GPIO_InitStruct;

GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;

GPIO_Init(GPIOC, &GPIO_InitStruct);
GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_RESET);
}

GPIO的读写操作

1
2
3
4
GPIO_WriteBit(GPIOx, GPIO_Pin, BitValue); // 写
GPIO_ReadInputDataBit(GPIOx, GPIO_Pin); // 读
GPIO_ReadOutputDataBit(GPIOx, GPIO_Pin); // 读

GPIO的HAL库

1
2
3
4
5
6
7
8
9
HAL_GPIO_Init(GPIOx, *GPIO_IniStruct); // 初始化引脚
HAL_GPIO_DeInit(GPIOx, *GPIO_IniStruct); // 重置引脚

HAL_GPIO_ReadPin(GPIOx, GPIO_Pin); // 读
HAL_GPIO_WritePin(GPIOx, GPIO_Pin, GPIO_PinState); // 写
// GPIO_Pin_SET代表1, GPIO_Pin_RESET代表0

HAL_GPIO_TogglePin(GPIOx, GPIO_Pin); // 取反引脚电压
HAL_GPIO_LockPin(GPIOx, GPIO_Pin); // 锁定引脚电压

USART

简介

一个stm32发送接收数据的通道
前提: 收发双方的通信协议应该一致

连线方式:

stm32 usart
vcc vcc
gnd gnd
Rxd Txd
Txd Rxd

USART的波特率

波特率: 1s最多传输的数据位

  1. 9600
  2. 115200
  3. 921600

USART的数据位长度

  1. 8b
  2. 9b

USART的停止位长度

  1. 0.5
  2. 1
  3. 1.5
  4. 2

USART的校验方式

  1. NO 无
  2. EVEN 偶 会要求数据有偶数个1
  3. ODD 奇 会要求数据有奇数个1

USART的收发方向

  1. Tx 发
  2. Rx 收
  3. Tx | Rx 收发

USART的硬件流控

硬件流控通过使用特定的硬件信号,在收发双方之间建立一种“握手”机制,确保数据传输的可靠性。

  1. None
  2. CTS
  3. RTS
  4. CTS | RTS

USART的标志位

PE(parity error) 奇偶校验错误
FE(frame error) 帧格式错误
NE(noise error) 噪声错误
ORE(overrun error) 过载错误

USART的引脚

查看重映射表 --> stm32使用手册的8.3

USART的引脚配置

查看外设引脚配置 --> stm32使用手册的8.1.11

USART的初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 开启时钟,USART总开关
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
USART_Cmd(USART1, ENABLE);
// 配置参数
USART_InitTypeDef USART_InitStruct = {0};
USART_InitStruct.USART_BaudRate = 115200;
USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_InitStruct.USART_Parity = USART_Parity_No;
USART_InitStruct.USART_WordLength = USART_WordLength_8b;
USART_InitStruct.USART_StopBits = USART_StopBits_1;
USART_Init(USART1, &USART_InitStruct);
// IO配置
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
GPIO_Init(GPIOA, &GPIO_InitStruct);

USART的常用封装函数

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
void My_USART_SendByte(USART_TypeDef *USARTx, const uint8_t Data)
{
My_USART_SendBytes(USARTx, &Data, 1);
}

__weak void My_USART_SendBytes(USART_TypeDef *USARTx, const uint8_t *pData, uint16_t Size)
{
if(Size == 0) return;
for(uint16_t i=0; i < Size; i++)
{
while(USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET);
USART_SendData(USARTx, pData[i]);
}
while(USART_GetFlagStatus(USARTx, USART_FLAG_TC) == RESET);
}
void My_USART_SendChar(USART_TypeDef *USARTx, const char C)
{
My_USART_SendBytes(USARTx, (const uint8_t *)&C, 1);
}
void My_USART_SendString(USART_TypeDef *USARTx, const char *Str)
{
My_USART_SendBytes(USARTx, (const uint8_t *)Str, strlen(Str));
}
void My_USART_Printf(USART_TypeDef *USARTx, const char *Format, ...)
{
char format_buffer[128];
va_list argptr;

__va_start(argptr, Format);

vsprintf(format_buffer, Format, argptr);

__va_end(argptr);

My_USART_SendString(USARTx, format_buffer);
}
uint8_t My_USART_ReceiveByte(USART_TypeDef *USARTx)
{
while(USART_GetFlagStatus(USARTx, USART_FLAG_RXNE) == RESET);

return USART_ReceiveData(USARTx);
}
__weak uint16_t My_USART_ReceiveBytes(USART_TypeDef *USARTx, uint8_t *pDataOut, uint16_t Size, int Timeout)
{
uint32_t expireTime;

Delay_Init();

if(Timeout >= 0)
{
expireTime = GetTick() + Timeout; // 计算过期时间,过期时间 = 当前时间+Timeout
}

uint16_t i = 0;

do
{
if(USART_GetFlagStatus(USARTx, USART_FLAG_RXNE) == SET)
{
pDataOut[i++] = USART_ReceiveData(USARTx);

if(i==Size) break;
}
}
while(Timeout < 0 || GetTick() < expireTime); // 判断是否超时

return i;
}
int My_USART_ReceiveLine(USART_TypeDef *USARTx, char *pStrOut, uint16_t MaxLength, uint16_t LineSeperator, int Timeout)
{
// 如果最大长度都不足以装下行分隔符
// 就直接返回失败
if(MaxLength < 2 || ((LineSeperator == LINE_SEPERATOR_CRLF) && (MaxLength < 1)))
{
return -2;
}
int ret = -1;
uint32_t expireTime;
Delay_Init(); // 要用到单片机当前时间,所以初始化延迟函数
if(Timeout >= 0)
{
expireTime = GetTick() + Timeout; // 计算过期时间,过期时间 = 当前时间+Timeout
}

uint16_t i = 0;
do
{
if(USART_GetFlagStatus(USARTx, USART_FLAG_RXNE) == SET)
{
char c = (char)USART_ReceiveData(USARTx);
pStrOut[i++] = c;

if(LineSeperator == LINE_SEPERATOR_CR && c == '\r') // \r
{
ret = 0;
break;
}
else if(LineSeperator == LINE_SEPERATOR_LF && c == '\n') // \n
{
ret = 0;
break;
}
else if(i >= 2 && pStrOut[i-2] == '\r' && c == '\n') // \r\n
{
ret = 0;
break;
}

if(i == MaxLength) // 超过最大长度
{
ret = -2;
break;
}
}
}
while(Timeout < 0 || GetTick() < expireTime); // 判断是否超时

// 在字符串末尾增加'\0'
if(i == MaxLength)
{
pStrOut[i-1] = '\0';
}
else
{
pStrOut[i] = '\0';
}

return ret;
}

UART的HAL库

1
2
3
HAL_UART_RxCpltCallback(huartx); // 当接受到UART数据时就会触发
HAL_UART_Receive_IT(huartx, *pData, Size); //
memset(*ptr, value, size) // 设置一段内存的值

printf的重定向

1
2
3
4
5
#include <stdio.h>
int fputc(int ch, FILE *f){
HAL_UART_Transmit(&huart1, (uint_8*)&ch, 1, 10);//huart1需要根据你的配置修改
return ch;
}

可变参数宏格式化日志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#ifdef USER_MAIN_DEBUG
#define user_main_printf(format, ...) printf( format "\r\n", ##__VA_ARGS__)
#define user_main_info(format, ...) printf("[\tmain]info:" format "\r\n", ##
__VA_ARGS__)
#define user_main_debug(format, ...) printf("[\tmain]debug:" format "\r\n", ##
__VA_ARGS__)
#define user_main_error(format, ...) printf("[\tmain]error:" format "\r\n",##
__VA_ARGS__)
#else
#define user_main_printf(format, ...)
#define user_main_info(format, ...)
#define user_main_debug(format, ...)
#define user_main_error(format, ...)
#endif

USART的HAL配置

  1. 异步收发模式
  2. 串口中断

AFIO

简介

开启重映射

使用

1
2
3
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); // 开启时钟
GPIO_PinRemapConfig(GPIO_Remap_USART1, ENABLE); // 开启引脚重映射

I2C

简介

使用I2C是因为可以一个master对多个slave, 而USART是一对一

I2C电路结构

SCL: 时钟线 主–>从
SDA: 数据线 主<->从

I2C数据帧结构


字节1: Co/DC/....
Co: 1可以命令和数据间切换, 0不可以切换命令和数据
DC: 高位数据, 低位命令
起始位: 拉低电压
ACK: 应答信号
结束位: 拉高电压

I2C引脚配置

I2C重映射

I2C的传输速度(波特率)

  1. Sm <=100kbps
  2. Fm <=400kbps

I2C的模式

  1. I2C
  2. SMBus_device
  3. SMBus_host

I2C时钟信号占空比

仅Sm快速模式才可以设置

  1. 2
  2. 16/9

I2C初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
   // 开启时钟, 复位I2C, 闭合I2C总开关
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1, ENABLE);
RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1, DISABLE); I2C_Cmd(I2C1, ENABLE);
// 重映射
GPIO_PinRemapConfig(GPIO_Remap_I2C1, ENABLE);
// 配置引脚
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOB, &GPIO_InitStruct);
// 配置I2C参数
I2C_InitTypeDef I2C_InitStruct;
I2C_InitStruct.I2C_ClockSpeed = 400000;
I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStruct.I2C_Mode =I2C_Mode_I2C;
I2C_Init(I2C1, &I2C_InitStruct);

I2C的标志位

BUSY 总线忙
SB 起始位成功
AF 应答失败
ADDR 寻址成功
TxE 发送寄存器为空
BTF 发送寄存器和移位寄存器为空
ACK 发送ACK(对于正在接受的字节)
STOP 发送停止位

I2C的操作

1
2
3
4
5
6
I2C_GetFlagStatus(I2Cx, I2C_FLAG); // 得到标志位状态
I2C_GenerateSTART(I2Cx, ENABLE); // 产生开始位
I2C_GenerateSTOP(I2Cx, ENABLE); // 产生停止位
I2C_ClearFlag(I2Cx, I2C_FLAG); // 清理标志位
I2C_SendData(I2Cx, data); // 发送1个字节

I2C的常用封装函数

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
weak int My_I2C_SendBytes(I2C_TypeDef *I2Cx, uint8_t Addr, const uint8_t *pData, uint16_t Size)
{
// #1. 等待总线空闲
while(I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY) == SET);
// #2. 发送起始位
I2C_GenerateSTART(I2Cx, ENABLE);
while(I2C_GetFlagStatus(I2Cx, I2C_FLAG_SB) == RESET);
// #3. 寻址阶段
I2C_ClearFlag(I2Cx, I2C_FLAG_AF);
I2C_SendData(I2Cx, Addr & 0xfe); // 0xfe代表数据发送
while(1)
{
if(I2C_GetFlagStatus(I2Cx, I2C_FLAG_ADDR) == SET)
{
break;
}
if(I2C_GetFlagStatus(I2Cx, I2C_FLAG_AF) == SET)
{
I2C_GenerateSTOP(I2Cx, ENABLE);
return -1; // 寻址失败
}
}
// 清除ADDR
I2C_ReadRegister(I2Cx, I2C_Register_SR1);
I2C_ReadRegister(I2Cx, I2C_Register_SR2);
// #4. 发送数据
for(uint16_t i=0; i<Size; i++)
{
while(1)
{
if(I2C_GetFlagStatus(I2Cx, I2C_FLAG_AF) == SET)
{
I2C_GenerateSTOP(I2Cx, ENABLE);
return -2; // 数据被拒收
}
if(I2C_GetFlagStatus(I2Cx, I2C_FLAG_TXE) == SET)
{
break;
}
}
I2C_SendData(I2Cx, pData[i]);
}
while(1)
{
if(I2C_GetFlagStatus(I2Cx, I2C_FLAG_AF) == SET)
{
I2C_GenerateSTOP(I2Cx, ENABLE);
return -2; // 数据被拒收
}

if(I2C_GetFlagStatus(I2Cx, I2C_FLAG_BTF) == SET)
{
break;
}
}
// #5. 发送停止位
I2C_GenerateSTOP(I2Cx, ENABLE);
return 0; // 成功
}

__weak int My_I2C_ReceiveBytes(I2C_TypeDef *I2Cx, uint8_t Addr, uint8_t *pBuffer, uint16_t Size)
{
if(Size == 0) return 0;
// #1. 等待总线空闲
while(I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY) == SET);
// #2. 发送起始位
I2C_GenerateSTART(I2Cx, ENABLE);
while(I2C_GetFlagStatus(I2Cx, I2C_FLAG_SB) == RESET);
// #3. 寻址阶段
I2C_ClearFlag(I2Cx, I2C_FLAG_AF);
I2C_SendData(I2Cx, Addr | 0x01);
while(1)
{
if(I2C_GetFlagStatus(I2Cx, I2C_FLAG_ADDR) == SET)
{
break;
}
if(I2C_GetFlagStatus(I2Cx, I2C_FLAG_AF) == SET)
{
I2C_GenerateSTOP(I2Cx, ENABLE);
return -1; // 寻址失败
}
}
// #4. 数据读取
if(Size == 1)
{
// 向ACK写0
I2C_AcknowledgeConfig(I2Cx, DISABLE);

// 清除ADDR
__disable_irq();
I2C_ReadRegister(I2Cx, I2C_Register_SR1);
I2C_ReadRegister(I2Cx, I2C_Register_SR2);

// 发送停止位
I2C_GenerateSTOP(I2Cx, ENABLE);
__enable_irq();
// 等待RxNE置位
while(I2C_GetFlagStatus(I2Cx, I2C_FLAG_RXNE) == RESET);
// 读取数据
pBuffer[0] = I2C_ReceiveData(I2Cx);
}
else
{
// 向ACK写1
I2C_AcknowledgeConfig(I2Cx, ENABLE);

// 清除ADDR
I2C_ReadRegister(I2Cx, I2C_Register_SR1);
I2C_ReadRegister(I2Cx, I2C_Register_SR2);

for(uint16_t i=0; i<Size-1; i++)
{
if(i==Size-2)
{
__disable_irq();
}
// 等待RxNE置位
while(I2C_GetFlagStatus(I2Cx, I2C_FLAG_RXNE) == RESET);
// 读取
pBuffer[i] = I2C_ReceiveData(I2Cx);
}

// 向ACK写0
I2C_AcknowledgeConfig(I2Cx, DISABLE);
// 发送停止位
I2C_GenerateSTOP(I2Cx, ENABLE);

__enable_irq();

// 等待RxNE置位
while(I2C_GetFlagStatus(I2Cx, I2C_FLAG_RXNE) == RESET);
pBuffer[Size-1] = I2C_ReceiveData(I2Cx);
}

return 0;
}

软件I2C

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
typedef struct
{
GPIO_TypeDef *SCL_GPIOx; // SCL引脚的组编号
uint16_t SCL_GPIO_Pin; // SCL引脚的引脚编号

GPIO_TypeDef *SDA_GPIOx; // SDA引脚的组编号
uint16_t SDA_GPIO_Pin; // SDA引脚的引脚编号

} SI2C_TypeDef;
__weak void My_SI2C_Init(SI2C_TypeDef *SI2C)
{
// #1. 使能SCL引脚的时钟
if(SI2C->SCL_GPIOx == GPIOA)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
}
else if(SI2C->SCL_GPIOx == GPIOB)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
}
else if(SI2C->SCL_GPIOx == GPIOC)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
}
else if(SI2C->SCL_GPIOx == GPIOD)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);
}

// #2. 对SCL和SDA写1
GPIO_WriteBit(SI2C->SDA_GPIOx, SI2C->SDA_GPIO_Pin, Bit_SET);
GPIO_WriteBit(SI2C->SCL_GPIOx, SI2C->SCL_GPIO_Pin, Bit_SET);

// #2. 使能SDA引脚的时钟
if(SI2C->SDA_GPIOx == GPIOA)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
}
else if(SI2C->SDA_GPIOx == GPIOB)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
}
else if(SI2C->SDA_GPIOx == GPIOC)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
}
else if(SI2C->SDA_GPIOx == GPIOD)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);
}

// #3. 初始化SCL引脚为输出开漏
GPIO_InitTypeDef GPIO_InitStruct;

GPIO_InitStruct.GPIO_Pin = SI2C->SCL_GPIO_Pin;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(SI2C->SCL_GPIOx, &GPIO_InitStruct);

// #4. 初始化SDA引脚为输出开漏

GPIO_InitStruct.GPIO_Pin = SI2C->SDA_GPIO_Pin;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(SI2C->SDA_GPIOx, &GPIO_InitStruct);
}

//
// @简介:通过软件I2C向从机写入多个字节
//
// @参数 I2Cx:填写要操作的I2C的名称,可以是I2C1或I2C2
// @参数 Addr:填写从机的地址,左对齐 - A6 A5 A4 A3 A2 A1 A0 0
// @参数 pData:要发送的数据(数组)
// @参数 Size:要发送的数据的数量,以字节为单位
//
// @返回值:0 - 发送成功, -1 - 寻址失败, -2 - 数据被拒收
//
__weak int My_SI2C_SendBytes(SI2C_TypeDef *SI2C, uint8_t Addr, const uint8_t *pData, uint16_t Size)
{
sda_w(1);
scl_w(1);

// #1. 发送起始位
sda_w(0);
delay(1);

// #2. 发送从机地址+RW
if(SendByte(SI2C, Addr & 0xfe) != 0)
{
SendStop(SI2C);
return -1; // 寻址失败
}

// #3. 发送数据
for(uint16_t i=0; i<Size; i++)
{
if(SendByte(SI2C, pData[i]) != 0)
{
SendStop(SI2C);
return -2; // 数据被拒收
}
}

// #4. 发送停止位
SendStop(SI2C);

return 0;
}

//
// @简介:通过软件I2C从从机读多个字节
//
// @参数 I2Cx:填写要操作的I2C的名称,可以是I2C1或I2C2
// @参数 Addr:填写从机的地址,左对齐 - A6 A5 A4 A3 A2 A1 A0 0
// @参数 pBuffer:接收缓冲区(数组)
// @参数 Size:要读取的数据的数量,以字节为单位
//
// @返回值:0 - 发送成功, -1 - 寻址失败
//
__weak int My_SI2C_ReceiveBytes(SI2C_TypeDef *SI2C, uint8_t Addr, uint8_t *pBuffer, uint16_t Size)
{
sda_w(1);
scl_w(1);

// #1. 发送起始位
sda_w(0);
delay(1);

// #2. 发送从机地址+RW
if(SendByte(SI2C, Addr | 0x01) != 0)
{
SendStop(SI2C);
return -1; // 寻址失败
}

// #3. 接收
for(uint16_t i=0; i<Size; i++)
{
pBuffer[i] = ReceiveByte(SI2C, (i==Size-1) ? 1 : 0);
}

// #4. 发送停止位
SendStop(SI2C);

return 0;
}

//
// @简介:发送一个字节
//
// @返回值:0-ACK,其它-NAK
//
static uint8_t SendByte(SI2C_TypeDef *SI2C, uint8_t Byte)
{
for(int8_t i=7; i>=0; i--)
{
scl_w(0); // 将SCL拉低
sda_w((Byte & (0x01<<i)) ? 1 : 0); // 变SDA的电压
delay(2); // 延迟1/2周期

scl_w(1); // 将SCL拉高
delay(2); // 延迟1/2周期
}

// 读取ACK
scl_w(0); // 将SCL拉低
sda_w(1); // 将SDA释放
delay(2); // 延迟1/4周期

scl_w(1); // 将SCL拉高
delay(2); // 延迟1/4周期

return sda_r;
}

//
// @简介:发送停止位
//
static void SendStop(SI2C_TypeDef *SI2C)
{
scl_w(0); // scl拉低
delay(1); // 延迟1/4周期
sda_w(0); // sda拉低
delay(1); // 延迟1/4周期
scl_w(1); // scl拉高
delay(1); // 延迟1/4周期
sda_w(1); // sda拉高
delay(1); // 延迟1/4周期
}


//
// @简介:从从机读取一个字节的数据
// @参数 Ack:0 - 回NAK,1 - 回ACK
// @返回值:读取到的数据
//
static uint8_t ReceiveByte(SI2C_TypeDef *SI2C, uint8_t Ack)
{
uint8_t ret = 0;

for(int8_t i=7; i>=0; i--)
{
scl_w(0); // scl拉低
sda_w(1); // 释放SDA
delay(2); // 延迟1/2周期
scl_w(1); // scl拉高
delay(2); // 延迟1/2周期

if(sda_r) // 如果读到的比特位为1
{
ret |= 0x01 << i; // 写入比特位
}
else // 如果读到的比特位为0
{
// 什么也不干
}
}

// 回复ACK或NAK

scl_w(0); // scl拉低

if(Ack)
{
sda_w(0); // sda拉低
}
else
{
sda_w(1); // sda拉高
}

delay(2); // 延迟1/2周期

return ret;
}

SPI

简介

SPI的速度比I2C更快, 且通信模式是全双工(可以同时双向传输)

SPI的结构

SPI的重映射

SPI的引脚配置

SPI的时钟信号

SPI的比特位传输顺序

|LSB_first|先传最低有效位|
|MSB_first|先传最高有效位

SPI的数据宽度

  1. 8 bit
  2. 16 bit

SPI的通信方向

  1. 2线全双工
  2. 2线只读
  3. 单线接受
  4. 单线发送

SPI的模式

  1. Master
  2. Slaver

SPI的波特率

72 MHz / 64 = 1.125M

SPI的NSS

  1. 硬件NSS
  2. 软件NSS 用电脑写值

SPI的初始化

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
// 开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_SPI1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 初始化引脚
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz; // SCK
GPIO_Init(GPIOB, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode =GPIO_Mode_IPU;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_4;
GPIO_Init(GPIOB, &GPIO_InitStruct); // MISO
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5;
GPIO_Init(GPIOB, &GPIO_InitStruct); // MOSI
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_15;
GPIO_Init(GPIOA, &GPIO_InitStruct); // 普通IO

// 初始化SPI
SPI_InitTypeDef SPI_InitStruct = {0};
SPI_InitStruct.SPI_Mode = SPI_Mode_Master;
SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStruct.SPI_CPOL = SPI_CPOL_High;
SPI_InitStruct.SPI_CPHA = SPI_CPHA_2Edge;
SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_64;
SPI_InitStruct.SPI_NSS = SPI_NSS_Soft;

SPI_Init(SPI1, &SPI_InitStruct);

SPI_NSSInternalSoftwareConfig(SPI1, SPI_NSSInternalSoft_Set); // 给NSS接高电压

SPI的封装函数

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
void My_SPI_MasterTransmitReceive(SPI_TypeDef *SPIx, const uint8_t *pDataTx, uint8_t *pDataRx, uint16_t Size)
{
if(Size == 0) return;

// #1. 闭合总开关
SPI_Cmd(SPIx, ENABLE);

// #2. 写入第一个字节
SPI_I2S_SendData(SPIx, pDataTx[0]);

// #3. 读写Size-1个字节
for(uint16_t i=0; i<Size-1; i++)
{
// 向TDR写数据
while(SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_TXE) == RESET);

SPI_I2S_SendData(SPIx, pDataTx[i+1]);

// 从RDR读数据
while(SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_RXNE) == RESET);

pDataRx[i] = SPI_I2S_ReceiveData(SPIx);
}

// #4. 读取最后一个字节
while(SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_RXNE) == RESET);

pDataRx[Size-1] = SPI_I2S_ReceiveData(SPIx);

// #5. 断开总开关
SPI_Cmd(SPIx, DISABLE);
}

EXTI

简介

检测GPIO引脚的电压变化(上升沿/下降沿), 产生中断

线

EXTI线(共16+4条)对GPIOx的电压变化的捕获,从而产生中断

注意EXTI线5只能处理PA5, PB5, PC5, PD5…

EXTI的初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void My_EXTIInit(){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
GPIO_EXTILineConfig(GPIO_PortSource, GPIO_PinSource); // 分配线的IO引脚
EXTI_InitTypeDef EXTI_InitStruct = {0};
EXTI_InitStruct.EXTI_Line = EXTI_Line5; // EXTI线5
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt; // 中断事件
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising // 检测上升沿/下降沿/双边沿
EXTI_InitStruct.EXTI_LineCmd = ENABLE; // 线的闭合或断开
EXTI_Init(&EXTI_InitStruct); // 初始化一条EXTI线

// NVIC的配置
NVIC_PriorityGroupConfig(NVIC_PriorityGroup2);
NVIC_InitTypeDef NVIC_InitStruct = {0};
NVIC_InitStruct.NVIC_IRQChannel = EXTI_9_5_IRQn; // 中断通道
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0; // 优先级
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0; // 子优先级
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; // 开关
}

编写中断函数

1
2
3
4
5
6
7
8
9
void EXTI_9_5_IRQHandler(){
if (EXTI_GetFlagStatus(EXTI_Line5) == SET){
EXTI_ClearFlag(EXTI_Line5);
// code
}else if (EXTI_GetFlagStatus(EXTI_Line6) == SET){
EXTI_ClearFlag(EXTI_Line6);
// code
}
}

TIM

TIM的重映射

时基单元

1
2
3
4
5
6
7
8
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct = {0};
TIM_TimeBaseInitStruct.TIM_Prescaler = 71; // 1MHZ
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period = 999; // 1KHz
TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;
TIM_ARRPreloadConfig(TIMx, ENABLE); // 打开ARR预加载
TIM_TimeBaseInit(TIMx, &TIM_TimeBaseInitStruct);
TIM_Cmd(TIMx, ENABLE); // 时基单元打开
  1. TIM_Prescaler预分频器的值, 72 / (预分频+1)
  2. TIM_CounterMode计数方式, 一般选UP
  3. TIM_Period计数周期, 每计数(计数周期+1) * (重复计数次数+1), 输出update事件
  4. TIM_RepetitionCounter重复计数次数, 一般设为0
  5. TIM_ARRPreloadConfig最好打开
  6. TIM_Cmd时基单元总开关

输出比较

1
2
3
4
5
6
7
8
9
TIM_OCInitTypeDef TIM_OCInitStruct = {0};
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1; //
TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStruct.TIM_OutputState = ENABLE;
TIM_OCInitStruct.TIM_Pulse = 0;
TIM_OC1Init(TIMx, &TIM_OCInitStruct);
TIM_CtrlPWMOutputs(TIMx, ENABLE); // 打开OC总开关
TIM_CCPreloadControl(TIMx, ENABLE); // 使能cc预加载
TIM_SetCompare1(TIMx, 23999);
  1. TIM_OCMode输出比较模式, 设置为PWM1, 当cnt \leq ccr 输出高电压
    ,当cnt > ccr 输出低电压.
  2. TIM_OCPolarity输出比较极性, 是否对输出的信号取反.
  3. TIM_OutputState使能OC的通道
  4. TIM_Pulseccr的值
  5. TIM_CtrlPWMOutputs打开OC的总开关
  6. TIM_CCPreloadControl 开启cc的预加载
  7. TIM_SetCompare1 设置cc1的值, cc/cnt就是占空比(ccr是寄存器)

输入捕获

1
2
3
4
5
6
7
8
9
10
11
TIM_ICInitTypeDef TIM_ICInitStruct = {0};
TIM_ICInitStruct.TIM_Channel = TIM_Channel_1;
TIM_ICInitStruct.TIM_ICFilter = 0;
TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM_ICInitStruct.TIM_ICPrescaler = TIM_ICPSC_DIV1; // 分频器
TIM_ICInitStruct.TIM_ICSelection = TIM_ICSelection_DirectTI; // 直接?间接
TIM_ICInit(TIM4, &TIM_ICInitStruct);
TIM_ICInitStruct.TIM_Channel = TIM_Channel_2;
TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Falling;
TIM_ICInitStruct.TIM_ICSelection = TIM_ICSelection_IndirectTI;
TIM_ICInit(TIM4, &TIM_ICInitStruct);
  1. TIM_Channel输入捕获的通道(根据引脚)
  2. TIM_ICFilter输入捕获过滤器
  3. TIM_ICPolarity捕获上升沿/下降沿
  4. TIM_ICPrescaler是否分频
  5. TIM_ICSelection直接或间接输入?

对于第五点, 加入channel2选择间接输入,那么channel2的输入信号实际上是channel1的输入信号
类似的, 1<–>2, 3<–>4可以利用间接输入获取宁一个通道上的信号

从模式器

从模式: 通过TRGI的信号对时基单元进行控制

1
2
TIM_SelectInputTrigger(TIM1,TIM_TS_TI1FP1); // 选择 TRGI的来源
TIM_SelectSlaveMode(TIM1, TIM_SlaveMode_Reset); // 复位模式
  1. TIM_SelectInputTrigger选择TRGI
  2. TIM_SelectSlaveMode选择从模式
TRGI输入来源 解释
TIM_TS_ITRx 其他定时器的TRGO
TIM_TS_TI1F_ED 输入捕获通道1滤波边沿检测
TIM_TS_TI1FP1 输入捕获通道1滤波1
TIM_TS_TI1FP2 输入捕获通道2滤波2
从模式 解释
Reset TRGI上升沿,清零cnt
Gated TRGI高电压, 关闭时基单元;低电压, 开启时基单元
Trigger TRGI上升沿, 开启时基单元
Externall TRGI作为时基单元的时钟来源
主模式 解释
Enable 时基单元打开, TRGO输出1; 反之TRGO输出0
Update 每次update, TRGO输出脉冲

TIM的中断(HAL库)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
HAL_TIM_Base_Start_IT(&htim1); // 使能TIM1的中断
HAL_TIM_Base_Start_IT(&htim2); // 使能TIM2的中断
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if (htim->Instance == htim1.Instance) {
...//定时器1中断业务
}
else if(htim-> Instance == htim2.Instance) {
...//定时器2中断业务
}
...
}

-------------------------
// 使能timx的通道y
HAL_TIM_PWM_Start(&htimx,TIM_CHANNEL_y);
// 修改timx的通道y的pwm的ccr为z,即修改占空比
__HAL_TIM_SET_COMPARE(&htimx, TIM_CHANNEL_y, z);

TIM生成PWM/SPWM(HAL库)

1
2
3
4
5
// 使能timx的通道y
HAL_TIM_PWM_Start(&htimx,TIM_CHANNEL_y);
// 修改timx的通道y的pwm比较值为z,即修改占空比
__HAL_TIM_SET_COMPARE(&htimx, TIM_CHANNEL_y, z);

ADC

模拟信号转换成数字信号
ADC1, ADC2都是12位逐次逼近型(精度是3.3v / (2^12)

ADC的初始化

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
void My_ADC_Init(void){
// 开启时钟并闭合ADC总开关
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
ADC_Cmd(ADC1, ENABLE);

// 初始化引脚
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AIN; // 模拟输入
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
GPIO_Init(GPIOA, &GPIO_InitStruct);

// 配置ADC时钟信号
RCC_ADCCLKConfig(RCC_PCLK2_Div6); // 12MHz

// 配置ADC基础参数
ADC_InitTypeDef ADC_InitStruct = {0};

ADC_InitStruct.ADC_ContinuousConvMode = DISABLE; // 连续模式
ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right; // 右对齐
ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // 软件启动
ADC_InitStruct.ADC_Mode = ADC_Mode_Independent; // 独立模式
ADC_InitStruct.ADC_NbrOfChannel = 1; // 常规通道数
ADC_InitStruct.ADC_ScanConvMode = DISABLE; // 关闭扫描模式

ADC_Init(ADC1, &ADC_InitStruct);

// 配置常规序列
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_13Cycles5); // 10.24
ADC_ExternalTrigConvCmd(ADC1, ENABLE); // 总开关
}


ADC的使用

1
2
3
4
5
6
7
8
9
10
ADC_ClearFlag(ADC1, ADC_FLAG_EOC); // 清空EOC标志位
ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 通过软件启动发送脉冲
while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);
uint16_t dr = ADC_GetConversionValue(ADC1); // 获得转换结果
float voltage = dr * (3.3f / 4095); // 电压 = dr * (3.3 / 2 ^ 12 - 1)
if (voltage > 1.5){ //
GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_SET); // 光线弱,灭
}else{
GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_RESET); // 光线强,亮
}

ADC的多路复用

1个ADC同时转换多路信号

ADC的常规序列

  1. 最多存在16个任务(对每个通道的信号转换)
  2. 可以将TIM3_TRGO(设为update, 1ms一次)作为常规序列的触发器,那么每1ms做一次常规序列

ADC的注入序列

  1. 最多存在4个任务
  2. 有单独的结果寄存器,
  3. 优先级更高

应用

矩阵键盘

4x4键盘
行: 输出引脚(PB12~15)
列: 输入引脚(PA8~11)
工作原理: 循环扫描: 一条行线发送扫描信号, 四条列线扫描按键是否被按下

依次将每条行线设置为低电平, 然后检测列线, 如果按键被按下, 列线会==检测到低电平==, 故行线应设置为OUT__PP, 列线应设置为IPU.如果没有列线会低电平, 那么把该行线重新设置成高电平

OLED

  1. 通信协议: I2C/ SPI
  2. 传输速度: SPI > I2C

四针脚

通信协议是: I2C

SCL Out_OD
SDA Out_OD
GND GND
VCC VCC

七针脚

通信协议是: 4线SPI

OLED STM32
VCC 3.3V
GND GND
DO SCL
DI SDA(先传高位)
RES reset
DC 置高发送数据, 置低发送命令
CS 片选,接低电平,代表始终选中芯片

OLED介绍

像素点 : 128 x 64
存储器(GDDRAM) : 128 x 8Bytes, 也就是每8行为一个字节
写命令: 配置页地址, 列地址
写数据: …

命令表

SSD1306数据手册