找回密码
 立即注册
首页 业界区 安全 STM32基础

STM32基础

庞悦 昨天 15:13
STM32基础入门

GPIO

1. 功能概述


  • GPIO是STM32中最基本的外设,用于控制引脚的电平(输出)或读取引脚状态(输入)。
  • 每个GPIO引脚可独立配置为9种模式之一
    输入一般都是用来检测外部信号的,推挽模式和开漏模式也属于输出模式的其中一种,这两个又叫复用推挽和复用开漏,就是STM32芯片引脚上不仅写了PB10,还写了别的东西。
    模式功能浮空输入不接上拉电阻,也不接下拉电阻,完全处于浮空状态,无信号输入时引脚状态不确定。用来检测微弱信号的变化。上拉输入内接上拉电阻,无外接信号时引脚为默认高电平。用来检测外部信号变为低电平(下降沿触发中断)。下拉输入内接下拉电阻,无外接信号时引脚为默认低电平。用来检测外部信号变为高电平(上升沿触发中断)。模拟输入用于接收模拟信号,要与ADC配合使用。用来测量模拟信号的变化。推挽输出可以输出高电平和低电平。不能使用总线。模拟输出输出模拟信号,与ADC配合使用。开漏输出只能输出低电平和高阻态,输出高电平时要外接上拉电阻。推挽模式具有推挽输出的特性,可用于将GPIO引脚用作特定外设的功能。如USART。开漏模式具有开漏输出的特性,可用于将GPIO引脚用作特定外设的功能。如I2C总线通信。
2. GPIO寄存器


  • 2个配置寄存器(GPIOx_CRL、GPIOx_CRH):每一组GPIO都有16个引脚,0-7号引脚在CRL中,8-15号引脚在CRH中。
  • 2个数据寄存器(GPIOx_IDR、GPIOx_ODR)
  • 置位/复位寄存器(GPIOx_BSRR)、复位寄存器(GPIOx_BRR)、锁定寄存器(GPIOx_LCKR)
3. 寄存器开发流程


  • 开启GPIO时钟(RCC_APB2ENR)
  • 配置GPIO模式(CRL/CRH)
  • 业务逻辑,比如点亮一个灯:输出数据(ODR)
  1. // created by lwpigking
  2. #include "stm32f10x.h"
  3. int main(void) {
  4.         // 1.时钟配置
  5.         RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
  6.         // 2.GPIO工作模式的配置
  7.         GPIOA->CRL &= ~GPIO_CRL_CNF0; // CNF: 00(推挽输出模式)
  8.         GPIOA->CRL |= GPIO_CRL_MODE0; // MODE: 11(输出模式,最大速度50MHZ)
  9.         // 3.输出低电平
  10.         GPIOA->ODR &= ~GPIO_ODR_ODR0; // PA0引脚输出低电平
  11.         while(1) {}
  12. }
复制代码
详解:->是用于访问指向结构体的指针所指向的成员。stm32的源码中对于各个寄存器都有宏定义,比如GPIO_ODR_ODR0,通过|=可以将某一位置1(1|x=1, 0|x得看x),通过&=可以将某一位置0(1=0,0&x=0,1&x得看x)。
4. 实际应用


  • 输出模式:驱动LED、继电器、蜂鸣器、电机驱动等。
  • 输入模式:读取按键、开关状态、外部传感器信号。
  • 复用模式:用于USART、I2C、SPI等外设的引脚功能。
中断系统

1. 功能概述


  • NVIC:嵌套向量中断控制器,管理所有中断的优先级和使能。
  • EXTI:支持外部/事件控制器,用于响应GPIO引脚上的边沿信号(上升沿、下降沿)。
  • 支持中断嵌套,优先级分为抢占优先级和响应优先级。
2. EXTI寄存器

3.寄存器开发流程


  • 开启GPIO和AFIO时钟
  • 配置GPIO为输入模式
  • 配置AFIO_EXTICR,选择中断引脚
  • 配置EXTI触发方式(上升沿/下降沿)
  • 使能EXTI中断线
  • 配置NVIC优先级并使能中断
  • 编写中断服务函数(ISR),并清除中断标志
  1. #include "key.h"
  2. void Key_Init() {
  3.     // 1. 配置时钟
  4.     RCC->APB2ENR |= RCC_APB2ENR_IOPFEN;
  5.     RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;
  6.     // 2. GPIO工作模式配置 PF10:CNF - 10  MODE - 00
  7.     GPIOF->CRH &= ~GPIO_CRH_MODE10;
  8.     GPIOF->CRH |= GPIO_CRH_CNF10_1;
  9.     GPIOF->CRH &= ~GPIO_CRH_CNF10_0;
  10.     GPIOF->ODR &= ~GPIO_ODR_ODR10;
  11.     // 3. AFIO配置引脚复用选择
  12.     AFIO->EXTICR[2] |= AFIO_EXTICR3_EXTI10_PF;
  13.     // 4. 配置EXTI
  14.     EXTI->RTSR |= EXTI_RTSR_TR10; // 上升沿触发
  15.     EXTI->IMR |= EXTI_IMR_MR10; // 开放中断请求
  16.     // 5.配置NVIC
  17.     NVIC_SetPriorityGrouping(3); // 全部都是抢占优先级
  18.     NVIC_SetPriority(EXTI15_10_IRQn, 3);
  19.     NVIC_EnableIRQ(EXTI15_10_IRQn);
  20. }
  21. // 中断服务程序
  22. void EXTI15_10_IRQHandler(void) {
  23.     // 先清除中断挂起标志位
  24.     EXTI->PR |= EXTI_PR_PR10;
  25.     // 延时防抖
  26.     Delay_ms(10);
  27.     // 判断如果依然保持高电平,就反转LED1的状态
  28.     if ((GPIOF->IDR & GPIO_IDR_IDR10) != 0) {
  29.         LED_Toggle(LED1);
  30.     }
  31.    
  32. }
复制代码
4.实际应用


  • 按键检测、紧急停止、外部传感器触发
  • 实时响应外部时间,避免轮询浪费CPU资源
USART

1. 功能概述


  • 用于异步串行通信,常用波特率:9600、115200
  • 支持全双工、半双工、单工模式
  • 可配置数据位、停止位、校验位
2. USART寄存器

1. 状态寄存器 (USART_SR) - USART1->SR

这是一个只读寄存器(某些位可由特定操作清除),用于反映 USART 的当前状态。在发送或接收数据前,必须查询这个寄存器的相应位
名称功能描述TXE发送数据寄存器空 (Transmit data register empty) - 0: 数据尚未从 TDR 转移到移位寄存器,未准备好发送新数据 - 1: TDR 寄存器为空,可以写入新的待发送数据 (发送时查询此位)TC发送完成 (Transmission Complete) - 0: 发送尚未完成 - 1: 发送已完成(包括停止位) (可通过软件序列或读 SR 写 DR 清除)RXNE接收数据寄存器非空 (Read data register not empty) - 0: 未收到数据或数据未读走 - 1: 接收到的数据已存在于 RDR 中,可以读取 (接收时查询此位,通过读 USART1->DR 清除)IDLE空闲总线检测 (IDLE line detected) - 0: 未检测到空闲总线 - 1: 检测到空闲总线(收到一帧完整数据后,总线持续高电平) (通过读 SR 再读 DR 清除)PE奇偶校验错误 (Parity error) - 0: 无奇偶校验错误 - 1: 检测到奇偶校验错误最常用的位是 TXE (发送) 和 RXNE (接收)。
2. 数据寄存器 (USART_DR) - USART1->DR

这是一个可读可写的寄存器,但它对应两个不同的物理寄存器:

  • 当您向该寄存器写数据时,实际上是写入发送数据寄存器 (TDR)
  • 当您从该寄存器读数据时,实际上是从接收数据寄存器 (RDR) 读取。
功能包含要发送或刚接收到的数据。数据的有效位数取决于 USART_CR1 中的 M 位(8位或9位)。操作:

  • 发送:USART1->DR = Data;
  • 接收:Data = USART1->DR;
3. 波特率寄存器 (USART_BRR) - USART1->BRR

用于设置 USART 的通信波特率。
计算公式:
波特率 = fCK / (16 * USARTDIV)
其中 USARTDIV 是一个无符号定点数,存储在 USART_BRR 寄存器中。
名称功能DIV_Mantissa[11:0]USARTDIV 的整数部分DIV_Fraction[3:0]USARTDIV 的小数部分如何计算并设置?
例如,系统时钟 fCK = 72MHz,目标波特率 BaudRate = 115200。
<ol>计算 USARTDIV: USARTDIV = 72MHz / (16 * 115200) = 39.0625
分离整数和小数部分:

  • DIV_Mantissa = integer(39.0625) = 39 = 0x27
  • DIV_Fraction = fractional(0.0625) * 16 = 1 = 0x1
组合: USART1->BRR = (39 CR1

这是最重要的控制寄存器,用于使能 USART 和其主要功能。
名称功能描述UEUSART 使能 (USART enable) - 0: 禁用 USART - 1: 使能 USART必须置1)M字长 (Word length) - 0: 1 起始位,8 数据位,n 停止位 - 1: 1 起始位,9 数据位,n 停止位PCE奇偶校验控制使能 (Parity control enable) - 0: 禁止奇偶校验 - 1: 使能奇偶校验PS奇偶校验选择 (Parity selection) - 0: 偶校验 - 1: 奇校验 (需 PCE=1)PEIEPE 中断使能 (PE interrupt enable)TXEIETXE 中断使能 (TXE interrupt enable)TCIETC 中断使能 (TC interrupt enable)RXNEIERXNE 中断使能 (RXNEIE interrupt enable)TE发送器使能 (Transmitter enable) - 1: 使能发送功能发送必须置1)RE接收器使能 (Receiver enable) - 1: 使能接收功能接收必须置1最基本配置: UE | TE | RE (使能USART、发送器、接收器)
3. 寄存器开发流程


  • 开启USART和GPIO时钟
  • 配置GPIO位复用推挽输出(TX)和浮空输入(RX)
  • 配置波特率(BRR)
  • 配置数据帧格式(数据位、停止位、校验位)
  • 使能USART和发送/接收器
  • 发送/接收数据(轮询方式或中断方式)
  1. #include "usart.h"
  2. void USART_Init(void) {
  3.     // 1. 配置时钟
  4.     RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
  5.     RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
  6.     // 2. GPIO工作模式
  7.     // PA9(复用推挽输出)CNF:10 MODE:11
  8.     GPIOA->CRH |= GPIO_CRH_MODE9;
  9.     GPIOA->CRH |= GPIO_CRH_CNF9_1;
  10.     GPIOA->CRH &= ~GPIO_CRH_CNF9_0;
  11.     // PA10(浮空输入) MODE:00 CNF:01
  12.     GPIOA->CRH &= ~GPIO_CRH_MODE10;
  13.     GPIOA->CRH &= ~ GPIO_CRH_CNF10_1;
  14.     GPIOA->CRH |= GPIO_CRH_CNF10_0;
  15.     // 3. 串口配置
  16.     USART1->BRR = 0x271; // 波特率 115200
  17.     USART1->CR1 |= USART_CR1_TE; // 接收使能
  18.     USART1->CR1 |= USART_CR1_RE; // 发送使能
  19.     USART1->CR1 |= USART_CR1_UE; // USART使能
  20.     // 其他配置默认即可
  21. }
  22. void USART_SendChar(uint8_t ch) {
  23.     // 判断SR里TXE是否为空
  24.     while((USART1->SR & USART_SR_TXE) == 0) {}
  25.     // 向DR写入新的数据
  26.     USART1->DR = ch;
  27. }
  28. uint8_t USART_ReceiveChar(void) {
  29.     while((USART1->SR & USART_SR_RXNE) == 0) {
  30.         // 判断空闲帧
  31.         if (USART1->SR & USART_SR_IDLE) {
  32.             return 0;
  33.         }
  34.     }
  35.     // 读取已经接收到的数据,等待接收下一个数据
  36.     return USART1->DR;
  37. }
  38. void USART_SendString(uint8_t *str, uint8_t size) {
  39.     for (uint8_t i = 0; i < size; i++) {
  40.         USART_SendChar(str[i]);
  41.     }
  42. }
  43. void USART_ReceiveString(uint8_t buffer[], uint8_t *size) {
  44.     // 定义一个变量,用来保存已经接收到的字符个数
  45.     uint8_t i = 0;
  46.     while((USART1->SR & USART_SR_IDLE) == 0) {
  47.         buffer[i] = USART_ReceiveChar();
  48.         i++;
  49.     }
  50.     // 清除IDLE
  51.     // USART1->SR;
  52.     USART1->DR;
  53.     *size = i-1;
  54. }
  55. uint8_t buffer[100] = {0};
  56. uint8_t size = 0;
  57. int main(void) {
  58.     USART_Init();
  59.     while(1) {
  60.         USART_ReceiveString(buffer, &size);
  61.         USART_SendString(buffer, size);
  62.     }
  63. }
复制代码
  1. #include "usart.h"
  2. void USART_Init(void) {
  3.     // 1. 配置时钟
  4.     RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
  5.     RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
  6.     // 2. GPIO工作模式
  7.     // PA9(复用推挽输出)CNF:10 MODE:11
  8.     GPIOA->CRH |= GPIO_CRH_MODE9;
  9.     GPIOA->CRH |= GPIO_CRH_CNF9_1;
  10.     GPIOA->CRH &= ~GPIO_CRH_CNF9_0;
  11.     // PA10(浮空输入) MODE:00 CNF:01
  12.     GPIOA->CRH &= ~GPIO_CRH_MODE10;
  13.     GPIOA->CRH &= ~ GPIO_CRH_CNF10_1;
  14.     GPIOA->CRH |= GPIO_CRH_CNF10_0;
  15.     // 3. 串口配置
  16.     USART1->BRR = 0x271; // 波特率 115200
  17.     USART1->CR1 |= USART_CR1_TE; // 接收使能
  18.     USART1->CR1 |= USART_CR1_RE; // 发送使能
  19.     USART1->CR1 |= USART_CR1_UE; // USART使能
  20.     // 4. 开启中断使能
  21.     USART1->CR1 |= USART_CR1_IDLEIE;
  22.     USART1->CR1 |= USART_CR1_RXNEIE;
  23.     // NVIC
  24.     NVIC_SetPriorityGrouping(3);
  25.     NVIC_SetPriority(USART1_IRQn, 3);
  26.     NVIC_EnableIRQ(USART1_IRQn);
  27. }
  28. void USART_SendChar(uint8_t ch) {
  29.     // 判断SR里TXE是否为空
  30.     while((USART1->SR & USART_SR_TXE) == 0) {}
  31.     // 向DR写入新的数据
  32.     USART1->DR = ch;
  33. }
  34. void USART_SendString(uint8_t *str, uint8_t size) {
  35.     for (uint8_t i = 0; i < size; i++) {
  36.         USART_SendChar(str[i]);
  37.     }
  38. }
  39. void USART1_IRQHandler(void) {
  40.     // 判断中断类型
  41.     if (USART1->SR & USART_SR_RXNE) { // 接收完成一个字符
  42.         buffer[size] = USART1->DR;
  43.         size++;
  44.     } else if (USART1->SR & USART_SR_IDLE) { // 字符串整体接收完成
  45.         // 清除IDLE标志位
  46.         USART1->DR;
  47.         // 直接发送字符串到电脑
  48.         // USART_SendString(buffer, size);
  49.         // 清除size
  50.         // size = 0;
  51.         isOver = 1;
  52.     }
  53. }
  54. uint8_t buffer[100] = {0};
  55. uint8_t size = 0;
  56. uint8_t isOver = 0;
  57. int main(void) {
  58.         USART_Init();
  59.         while(1) {
  60.                 if (isOver)
  61.                 {
  62.                         USART_SendString(buffer, size);
  63.                         isOver = 0;
  64.                         size = 0;
  65.                 }       
  66.         }
  67. }
复制代码
4. 实际应用


  • 打印调试信息 (printf):最常用的功能,重定向 printf 到 USART,方便监控程序状态和变量值。
  • 与上位机通信:与 PC 软件(如串口助手、自定义的上位机)进行数据交换,发送传感器数据,接收控制命令。
  • 模块控制:与 GPS、蓝牙 (HC-05/06)、Wi-Fi (ESP8266/ESP32)、GSM 等模块进行 AT 指令通信。
  • 嵌入式系统间通信:两个 MCU 之间进行简单的数据交换。
  • 工业控制:支持 Modbus RTU 等工业串行协议,与 PLC、变频器等设备通信。
I2C

1. I2C协议回顾

I2C (Inter-Integrated Circuit) 是一种同步、半双工、串行的通信总线,I2C需要两根线,分别是SDA(Serial Data)数据线和SCL(Serial Clock)时钟线。SCL(只有主设备控制)用于控制时钟信号,设备在其每个上升沿读取SDA信号线上的电平。
关键特性:

  • 多主多从:总线上可以连接多个主设备和从设备。
  • 地址寻址:每个从设备都有一个唯一的 7 位或 10 位地址。
  • 低速通信:标准模式 (100 kbps),快速模式 (400 kbps),高速模式 (3.4 Mbps)。
  • 上拉电阻:设备空闲时,输出高阻态,当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平。
基本信号:

  • 起始条件 (S):SCL 为高电平时,SDA 从高到低的跳变。(先拉高SCL,准备好SDA从1到0即为START)
  • 停止条件 (P):SCL 为高电平时,SDA 从低到高的跳变。(先拉高SCL,准备好SDA从0到1即为STOP)
  • 从机地址与读写位:第一个字节的前7位组成了从机地址。第8位代表主机读(高电平)或主机写(低电平),它决定了后续数据的发送方和接收方。
  • 数据有效性:在 SCL 高电平期间,SDA 必须保持稳定。数据只能在 SCL 低电平时改变。
  • 应答 (ACK):发送方(可以是主或从)发送完 8 位数据后,接收方需要将 SDA 拉低,表示应答。(先拉低SDA,拉低的时候SCL要保持低电平,再拉高SCL)
  • 非应答 (NACK):SDA 保持高电平,表示非应答。(先拉高SDA,再拉高SCL)
2. 软件模拟I2C
  1. #define ACK 0
  2. #define NACK 1
  3. // STM32控制SCL和SDA的输出高低电平
  4. #define SCL_HIGH (GPIOB->ODR |= GPIO_ODR_ODR10)
  5. #define SCL_LOW (GPIOB->ODR &= ~GPIO_ODR_ODR10)
  6. #define SDA_HIGH (GPIOB->ODR |= GPIO_ODR_ODR11)
  7. #define SDA_LOW (GPIOB->ODR &= ~GPIO_ODR_ODR11)
  8. // STM32从EEPROM中读取,STM32作为从设备
  9. #define READ_SDA (GPIOB->IDR & GPIO_IDR_IDR11)
  10. // 基本延迟
  11. #define I2C_DELAY Delay_us(10)
  12. void I2C_Init(void) {
  13.     // 1.配置时钟
  14.     RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
  15.     // 2.工作模式:通用开漏输出
  16.     GPIOB->CRH |= (GPIO_CRH_MODE10 | GPIO_CRH_MODE11);
  17.     GPIOB->CRH &= ~(GPIO_CRH_CNF10_1 | GPIO_CRH_CNF11_1);
  18.     GPIOB->CRH |= (GPIO_CRH_CNF10_0 | GPIO_CRH_CNF11_0);
  19. }
  20. void I2C_Start(void) {
  21.     // SCL先拉高保持不变,SDA从高电平到低电平
  22.     SCL_HIGH;
  23.     SDA_HIGH;
  24.     I2C_DELAY; // SDA拉高保持一段时间
  25.     SDA_LOW;
  26.     I2C_DELAY; // SDA拉低保持一段时间
  27. }
  28. void I2C_Stop(void) {
  29.     // SCL先拉高保持不变,SDA从低电平到高电平
  30.     SCL_HIGH;
  31.     SDA_LOW;
  32.     I2C_DELAY; // SDA拉低保持一段时间
  33.     SDA_HIGH;
  34.     I2C_DELAY; // SDA拉高保持一段时间
  35. }
  36. void I2C_Ack(void) {
  37.     // SCL先拉低,SDA拉高,做好准备
  38.     SCL_LOW;
  39.     SDA_HIGH;
  40.     I2C_DELAY;
  41.     // SDA拉低,保持不变,SCL拉高开始采样SDA
  42.     SDA_LOW;
  43.     I2C_DELAY;
  44.     SCL_HIGH;
  45.     I2C_DELAY;
  46.     // 采样结束,SDA不变,SCL拉低
  47.     SCL_LOW;
  48.     I2C_DELAY;
  49.     // SDA拉高,释放数据总线
  50.     SDA_HIGH;
  51.     I2C_DELAY;
  52. }
  53. void I2C_Nack(void) {
  54.     SCL_LOW;
  55.     SDA_HIGH;
  56.     I2C_DELAY;
  57.     SCL_HIGH;
  58.     I2C_DELAY;
  59.    
  60.     SCL_LOW;
  61.     I2C_DELAY;
  62. }
  63. uint8_t I2C_Wait4Ack(void) {
  64.     // SCL拉低,SDA拉高,释放数据总线
  65.     SCL_LOW;
  66.     SDA_HIGH;
  67.     I2C_DELAY;
  68.     // 拉高SCL,开始数据采样
  69.     SCL_HIGH;
  70.     I2C_DELAY;
  71.     // 读取SDA数据线上的电平
  72.     uint16_t ack = READ_SDA;
  73.     // 拉低SCL结束采样
  74.     SCL_LOW;
  75.     I2C_DELAY;
  76.     return ack ? NACK : ACK;
  77. }
  78. void I2C_SendByte(uint8_t byte) {
  79.     for (uint8_t i = 0; i < 8; i++)
  80.     {
  81.         // SCL为低时不进行采样,此时SDA可以根据byte的数据进行一位位的传输(SDA根据byte进行翻转)
  82.         // SCL、SDA拉低,等待数据翻转
  83.         SCL_LOW;
  84.         SDA_LOW;      
  85.         I2C_DELAY;
  86.         // 字节最高位,向SDA写入数据
  87.         // 该位为1就拉高SDA,为0就拉低SDA
  88.         if (byte & 0x80) {
  89.             SDA_HIGH;
  90.         } else {
  91.             SDA_LOW;
  92.         }
  93.         I2C_DELAY;
  94.         // 翻转完毕,准备拉高SCL进行采样
  95.         SCL_HIGH;
  96.         I2C_DELAY;
  97.         // SCL拉低,采样结束
  98.         SCL_LOW;
  99.         I2C_DELAY;
  100.         byte <<= 1; // 继续采样byte的下一位
  101.     }
  102. }
  103. // 主机从从设备接受一个字节的数据,数据总线不归主设备管
  104. uint8_t I2C_ReadByte(void) {
  105.     uint8_t data = 0;
  106.     for (uint8_t i = 0; i < 8; i++)
  107.     {
  108.         // 拉低SCL,等待数据翻转
  109.         SCL_LOW;
  110.         I2C_DELAY;
  111.         // 拉高SCL,采样
  112.         SCL_HIGH;
  113.         I2C_DELAY;
  114.         data <<= 1; // 先左移,新存入的位永远在最低位
  115.         // 开始读数据
  116.         if (READ_SDA) {
  117.             data |= 0x01; // 先存入最低位,然后每次都左移1位
  118.         }
  119.         
  120.         // 拉低SCL,结束采样
  121.         SCL_LOW;
  122.         I2C_DELAY;
  123.     }
  124.     return data;
  125.    
  126. }
复制代码
5. 实际应用


  • 传感器读取:与加速度计、陀螺仪、磁力计、温湿度传感器 、气压传感器等通信。
  • EEPROM 存储:扩展非易失性存储空间,存储设备参数、校准数据等。
  • RTC 时钟:与实时时钟芯片 通信,获取和设置时间。
  • IO 扩展:使用 IO 扩展芯片来增加 GPIO 数量。
  • 音频编解码器:配置音频芯片的参数。
  • 触摸屏控制器:与电阻式或电容式触摸屏控制器通信。

来源:豆瓜网用户自行投稿发布,如果侵权,请联系站长删除

相关推荐

您需要登录后才可以回帖 登录 | 立即注册