本人花费5天时间完成了stm32的基础, 小累, 现在总结一下
总述
mindmap
STM32[STM32]
))片上外设((
((GPIO))
GPIOA
GPIOB
GPIOC
GPIOD
GPIOE
((USART))
USART1
USART2
USART3
("AFIO(查看重映射)")
((I2C))
I2C1
I2C2
((SPI))
SPI1
SPI2
((EXTI))
中断编程
((TIM))
TIM1
TIM2
TIM3
TIM4
((ADC))
ADC1
ADC2
))时钟((
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 |
启动模式 |
其他 |
片上外设名+数字 |
比喻关系
时钟
时钟的介绍和作用
时钟信号是一种电子逻辑信号(电压或电流),它以恒定频率在高低状态之间振荡,并像节拍器一样用于同步数字电路的动作

故: 片上外设在使用前必须打开对应的时钟
1 2 3 4
| RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
|
时钟树

灰色默认关闭, 绿色默认开启
图示代码可以自己配置时钟树, 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){ FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable); FLASH_SetLatency(FLASH_ACR_LATENCY_2); RCC_HSEConfig(RCC_HSE_ON); while(RCC_GetFlagStatus(RCC_FLAG_HSERDY) == RESET); 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); 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的最大输出速率
注意: 越快越耗能
- Low - 2MHz
- Medium - 10MHz
- High - 50MHz
GPIO的默认电压
- GPIO_PULLUP
- GPIO_PULLDOWN
- 不设置
GPIO的初始化
1 2 3 4 5 6 7 8 9 10 11 12
| void My_OnBoardLEDInit(void){ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE) = {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);
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最多传输的数据位
- 9600
- 115200
- 921600
USART的数据位长度
- 8b
- 9b
USART的停止位长度
- 0.5
- 1
- 1.5
- 2
USART的校验方式
- NO 无
- EVEN 偶 会要求数据有偶数个1
- ODD 奇 会要求数据有奇数个1
USART的收发方向
- Tx 发
- Rx 收
- Tx | Rx 收发
USART的硬件流控
硬件流控通过使用特定的硬件信号,在收发双方之间建立一种“握手”机制,确保数据传输的可靠性。
- None
- CTS
- RTS
- 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
| 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);
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; } 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; } 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') { ret = 0; break; } else if(LineSeperator == LINE_SEPERATOR_LF && c == '\n') { ret = 0; break; } else if(i >= 2 && pStrOut[i-2] == '\r' && c == '\n') { ret = 0; break; } if(i == MaxLength) { ret = -2; break; } } } while(Timeout < 0 || GetTick() < expireTime); if(i == MaxLength) { pStrOut[i-1] = '\0'; } else { pStrOut[i] = '\0'; } return ret; }
|
UART的HAL库
1 2 3
| HAL_UART_RxCpltCallback(huartx); 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); 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配置
- 异步收发模式
- 串口中断
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的传输速度(波特率)
- Sm <=100kbps
- Fm <=400kbps
I2C的模式
- I2C
- SMBus_device
- SMBus_host
I2C时钟信号占空比
仅Sm快速模式才可以设置
- 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
| 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_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);
|
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) { while(I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY) == SET); I2C_GenerateSTART(I2Cx, ENABLE); while(I2C_GetFlagStatus(I2Cx, I2C_FLAG_SB) == RESET); I2C_ClearFlag(I2Cx, I2C_FLAG_AF); I2C_SendData(I2Cx, Addr & 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; } } I2C_ReadRegister(I2Cx, I2C_Register_SR1); I2C_ReadRegister(I2Cx, I2C_Register_SR2); 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; } } 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; while(I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY) == SET); I2C_GenerateSTART(I2Cx, ENABLE); while(I2C_GetFlagStatus(I2Cx, I2C_FLAG_SB) == RESET); 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; } } if(Size == 1) { I2C_AcknowledgeConfig(I2Cx, DISABLE); __disable_irq(); I2C_ReadRegister(I2Cx, I2C_Register_SR1); I2C_ReadRegister(I2Cx, I2C_Register_SR2); I2C_GenerateSTOP(I2Cx, ENABLE); __enable_irq(); while(I2C_GetFlagStatus(I2Cx, I2C_FLAG_RXNE) == RESET); pBuffer[0] = I2C_ReceiveData(I2Cx); } else { I2C_AcknowledgeConfig(I2Cx, ENABLE); 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(); } while(I2C_GetFlagStatus(I2Cx, I2C_FLAG_RXNE) == RESET); pBuffer[i] = I2C_ReceiveData(I2Cx); } I2C_AcknowledgeConfig(I2Cx, DISABLE); I2C_GenerateSTOP(I2Cx, ENABLE); __enable_irq(); 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; uint16_t SCL_GPIO_Pin; GPIO_TypeDef *SDA_GPIOx; uint16_t SDA_GPIO_Pin; } SI2C_TypeDef; __weak void My_SI2C_Init(SI2C_TypeDef *SI2C) { 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); } GPIO_WriteBit(SI2C->SDA_GPIOx, SI2C->SDA_GPIO_Pin, Bit_SET); GPIO_WriteBit(SI2C->SCL_GPIOx, SI2C->SCL_GPIO_Pin, Bit_SET); 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); } 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); 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); }
__weak int My_SI2C_SendBytes(SI2C_TypeDef *SI2C, uint8_t Addr, const uint8_t *pData, uint16_t Size) { sda_w(1); scl_w(1); sda_w(0); delay(1); if(SendByte(SI2C, Addr & 0xfe) != 0) { SendStop(SI2C); return -1; } for(uint16_t i=0; i<Size; i++) { if(SendByte(SI2C, pData[i]) != 0) { SendStop(SI2C); return -2; } } SendStop(SI2C); return 0; }
__weak int My_SI2C_ReceiveBytes(SI2C_TypeDef *SI2C, uint8_t Addr, uint8_t *pBuffer, uint16_t Size) { sda_w(1); scl_w(1); sda_w(0); delay(1); if(SendByte(SI2C, Addr | 0x01) != 0) { SendStop(SI2C); return -1; } for(uint16_t i=0; i<Size; i++) { pBuffer[i] = ReceiveByte(SI2C, (i==Size-1) ? 1 : 0); } SendStop(SI2C); return 0; }
static uint8_t SendByte(SI2C_TypeDef *SI2C, uint8_t Byte) { for(int8_t i=7; i>=0; i--) { scl_w(0); sda_w((Byte & (0x01<<i)) ? 1 : 0); delay(2); scl_w(1); delay(2); } scl_w(0); sda_w(1); delay(2); scl_w(1); delay(2); return sda_r; }
static void SendStop(SI2C_TypeDef *SI2C) { scl_w(0); delay(1); sda_w(0); delay(1); scl_w(1); delay(1); sda_w(1); delay(1); }
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); sda_w(1); delay(2); scl_w(1); delay(2); if(sda_r) { ret |= 0x01 << i; } else { } } scl_w(0); if(Ack) { sda_w(0); } else { sda_w(1); } delay(2); return ret; }
|
SPI
简介
SPI的速度比I2C更快, 且通信模式是全双工(可以同时双向传输)
SPI的结构

SPI的重映射

SPI的引脚配置

SPI的时钟信号

SPI的比特位传输顺序
|LSB_first|先传最低有效位|
|MSB_first|先传最高有效位
SPI的数据宽度
- 8 bit
- 16 bit
SPI的通信方向
- 2线全双工
- 2线只读
- 单线接受
- 单线发送
SPI的模式
- Master
- Slaver
SPI的波特率
72 MHz / 64 = 1.125M
SPI的NSS
- 硬件NSS
- 软件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; GPIO_Init(GPIOB, &GPIO_InitStruct); GPIO_InitStruct.GPIO_Mode =GPIO_Mode_IPU; GPIO_InitStruct.GPIO_Pin = GPIO_Pin_4; GPIO_Init(GPIOB, &GPIO_InitStruct); GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD; GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5; GPIO_Init(GPIOB, &GPIO_InitStruct); GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStruct.GPIO_Pin = GPIO_Pin_15; GPIO_Init(GPIOA, &GPIO_InitStruct);
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);
|
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; SPI_Cmd(SPIx, ENABLE); SPI_I2S_SendData(SPIx, pDataTx[0]); for(uint16_t i=0; i<Size-1; i++) { while(SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_TXE) == RESET); SPI_I2S_SendData(SPIx, pDataTx[i+1]); while(SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_RXNE) == RESET); pDataRx[i] = SPI_I2S_ReceiveData(SPIx); } while(SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_RXNE) == RESET); pDataRx[Size-1] = SPI_I2S_ReceiveData(SPIx); 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); EXTI_InitTypeDef EXTI_InitStruct = {0}; EXTI_InitStruct.EXTI_Line = EXTI_Line5; EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising EXTI_InitStruct.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStruct); 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); }else if (EXTI_GetFlagStatus(EXTI_Line6) == SET){ EXTI_ClearFlag(EXTI_Line6); } }
|
TIM
TIM的重映射

时基单元
1 2 3 4 5 6 7 8
| TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct = {0}; TIM_TimeBaseInitStruct.TIM_Prescaler = 71; TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInitStruct.TIM_Period = 999; TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0; TIM_ARRPreloadConfig(TIMx, ENABLE); TIM_TimeBaseInit(TIMx, &TIM_TimeBaseInitStruct); TIM_Cmd(TIMx, ENABLE);
|
TIM_Prescaler
预分频器的值, 72 / (预分频+1)
TIM_CounterMode
计数方式, 一般选UP
TIM_Period
计数周期, 每计数(计数周期+1) * (重复计数次数+1), 输出update事件
TIM_RepetitionCounter
重复计数次数, 一般设为0
TIM_ARRPreloadConfig
最好打开
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); TIM_CCPreloadControl(TIMx, ENABLE); TIM_SetCompare1(TIMx, 23999);
|
TIM_OCMode
输出比较模式, 设置为PWM1, 当cnt ≤ ccr 输出高电压
,当cnt > ccr 输出低电压.
TIM_OCPolarity
输出比较极性, 是否对输出的信号取反.
TIM_OutputState
使能OC的通道
TIM_Pulse
ccr的值
TIM_CtrlPWMOutputs
打开OC的总开关
TIM_CCPreloadControl
开启cc的预加载
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);
|
TIM_Channel
输入捕获的通道(根据引脚)
TIM_ICFilter
输入捕获过滤器
TIM_ICPolarity
捕获上升沿/下降沿
TIM_ICPrescaler
是否分频
TIM_ICSelection
直接或间接输入?
对于第五点, 加入channel2选择间接输入,那么channel2的输入信号实际上是channel1的输入信号
类似的, 1<–>2, 3<–>4可以利用间接输入获取宁一个通道上的信号
从模式器
从模式: 通过TRGI的信号对时基单元进行控制
1 2
| TIM_SelectInputTrigger(TIM1,TIM_TS_TI1FP1); TIM_SelectSlaveMode(TIM1, TIM_SlaveMode_Reset);
|
TIM_SelectInputTrigger
选择TRGI
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); HAL_TIM_Base_Start_IT(&htim2); void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == htim1.Instance) { ... } else if(htim-> Instance == htim2.Instance) { ... } ... }
-------------------------
HAL_TIM_PWM_Start(&htimx,TIM_CHANNEL_y);
__HAL_TIM_SET_COMPARE(&htimx, TIM_CHANNEL_y, z);
|
TIM生成PWM/SPWM(HAL库)
1 2 3 4 5
| HAL_TIM_PWM_Start(&htimx,TIM_CHANNEL_y);
__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){ 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); RCC_ADCCLKConfig(RCC_PCLK2_Div6); 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); ADC_ExternalTrigConvCmd(ADC1, ENABLE); }
|
ADC的使用
1 2 3 4 5 6 7 8 9 10
| ADC_ClearFlag(ADC1, ADC_FLAG_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); 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的常规序列
- 最多存在16个任务(对每个通道的信号转换)
- 可以将TIM3_TRGO(设为update, 1ms一次)作为常规序列的触发器,那么每1ms做一次常规序列
ADC的注入序列
- 最多存在4个任务
- 有单独的结果寄存器,
- 优先级更高
应用
矩阵键盘
4x4键盘
行: 输出引脚(PB12~15)
列: 输入引脚(PA8~11)
工作原理: 循环扫描: 一条行线发送扫描信号, 四条列线扫描按键是否被按下

依次将每条行线设置为低电平, 然后检测列线, 如果按键被按下, 列线会==检测到低电平==, 故行线应设置为OUT__PP, 列线应设置为IPU.如果没有列线会低电平, 那么把该行线重新设置成高电平
OLED
- 通信协议: I2C/ SPI
- 传输速度: 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数据手册