返回

stm32标准库学习笔记

目录

基础

图1

STM32 命名规则

STM32 [系列] [子系列/特性] [引脚数] [Flash容量] [封装] [温度范围] [可选后缀]

总线名称 功能描述 访问属性 地址范围 特性说明
I-Code 取指令 只读 0x0000 0000 – 0x1FFF FFFF 高速取指
D-Code 读常量 只读 0x0000 0000 – 0x1FFF FFFF 并行读常量,不阻塞指令流
System 读写变量、外设 读写 其他所有区域 通用数据通路

System 下属总线

总线类型 特性 频率(相对于 CPU) 典型外设
AHB 高性能、高带宽外设 最高(= CPU 频率) GPIO, DMA, FMC, CRC
APB2 高速外设 高(常为 AHB/2) ADC, 高级定时器, USART1
APB1 低速外设 低(常为 AHB/4) I2C, UART2-5, 普通定时器, CAN

GPIO 寄存器

寄存器缩写 全称 偏移地址 位宽 访问属性 功能描述
CRL Port Configuration Register Low 0x00 32-bit 读写 配置引脚 0~7 的模式和速度
CRH Port Configuration Register High 0x04 32-bit 读写 配置引脚 8~15 的模式和速度
IDR Port Input Data Register 0x08 32-bit 只读 读取所有 16 个引脚的当前输入电平
ODR Port Output Data Register 0x0C 32-bit 读写 设置/读取所有 16 个引脚的输出电平
BSRR Port Bit Set/Reset Register 0x10 32-bit 写入 原子操作:低16位复位引脚,高16位置位引脚(写1有效,写0无效)
BRR Port Bit Reset Register 0x14 16-bit 写入 仅复位(清零)引脚(STM32F1 系列存在,F4 及以后通常合并到 BSRR)
LCKR Port Configuration Lock Register 0x18 32-bit 读写 锁定 GPIO 配置(防止误改,需特定操作序列激活锁定)

volatile 关键字

这个变量的值可能会在程序不知情的情况下被改变,因此每次访问都必须从内存中重新读取,不能使用缓存值,也不能被编译器优化掉。
即外部会修改该值,每次访问都要重新读取,而非使用编程时优化

创建工程

copy:

1
2
3
\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\arm  
\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x
\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\CoreSupport

GPIO输出(点灯)

1
2
3
4
5
//使用到的函数
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);//可以使用或运算来控制多个GPIO口
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);

注意添加给的Delay.h,Delay.c文件

示例工程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#include "stm32f10x.h"                  // Device header
#include <Delay.h>
int main(void) {
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//GPIO口都占用APB2,所以要对APB2的RCC使能
    
    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA,&GPIO_InitStruct);
    GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_SET);
    while(1) {
        GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_SET);
        Delay_ms(500);
        GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_RESET);
        Delay_ms(500);
    }
}
模式类别 模式宏定义 说明
输入模式 GPIO_Mode_IN_FLOATING 浮空输入(无上拉/下拉)
GPIO_Mode_IPU 上拉输入(Internal Pull-Up)
GPIO_Mode_IPD 下拉输入(Internal Pull-Down)
GPIO_Mode_AIN 模拟输入(STM32F1 中用于 ADC/DAC;无专用“模拟输入”模式,复用此 GPIO 模式)
输出模式 GPIO_Mode_Out_OD 开漏输出(Open-Drain)
GPIO_Mode_Out_PP 推挽输出(Push-Pull)
需配合 GPIO_Speed 设置输出速度(如 2MHz、10MHz、50MHz)
复用功能 GPIO_Mode_AF_OD 复用开漏输出(用于 I²C 等外设)
GPIO_Mode_AF_PP 复用推挽输出(用于 USART、SPI 等外设)
引脚用于外设功能(如 USART_TX、SPI_SCK),而非普通 GPIO
模拟模式 GPIO_Mode_AIN 模拟输入(用于 ADC 或 DAC)

GPIO 输入(按键控制)

 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
void Key_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
}

uint8_t Key_GetNum(void)
{
    uint8_t KeyNum = 0;
    if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0)
    {
        Delay_ms(20);
        while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0);
        Delay_ms(20);
        KeyNum = 1;
    }
    if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_10) == 0)
    {
        Delay_ms(20);
        while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_10) == 0);
        Delay_ms(20);
        KeyNum = 2;
    }
    
    return KeyNum;
}

OLED 屏幕

导入oled.h,oled.c
写到这里发现OLED商家漏发了qwq

中断

EXIT中断

触发方式:上升沿,下降沿,双边沿,软件触发
每个中断/事件线都对应有一个边沿检测器,可以实现输入信号的上升沿检测和下降沿的检测。
EXTI可分为两大部分功能,一个是产生中断,另一个是产生事件(仅能触发外设)
注:相同的PIN不能同时触发中断(如GPIOA_Pin_0与GPIOB_Pin_0)

需要软件做判断、通信、状态更新? → 选 中断模式。
只需启动一个硬件动作(如采样、计数、传输)? → 选 事件模式。
追求低功耗或高实时性? → 优先考虑 事件模式。

EXTI有20个中断/事件线,每个GPIO都可以被设置为输入线,占用EXTI0至EXTI15, 还有另外七根用于特定的外设事件
图1

 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

typedef enum {
    EXTI_Mode_Interrupt = 0x00,  // 中断模式:触发后向 NVIC 请求中断
    EXTI_Mode_Event     = 0x04   // 事件模式:触发后产生脉冲信号(用于 DMA 或其他外设触发,不进 CPU 中断)
} EXTIMode_TypeDef;

typedef enum {
    EXTI_Trigger_Rising         = 0x08,  // 上升沿触发
    EXTI_Trigger_Falling        = 0x0C,  // 下降沿触发
    EXTI_Trigger_Rising_Falling = 0x10   // 双边沿触发(上升沿 + 下降沿)
} EXTITrigger_TypeDef;

#include "stm32f10x.h"

void EXTI0_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    EXTI_InitTypeDef EXTI_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    // 1. 使能 GPIOA 和 AFIO 时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);

    // 2. 配置 PA0 为浮空输入(或上拉/下拉,根据需求)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 浮空输入
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    // 3. 配置 EXTI0 的中断线,将 PA0 映射到 EXTI0
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);

    // 4. 配置 EXTI0:上升沿触发、使能中断
    EXTI_InitStructure.EXTI_Line = EXTI_Line0;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;     // 中断模式(非事件模式)
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;  // 上升沿触发
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure);

    // 5. 配置 NVIC:使能 EXTI0 中断,设置优先级
    NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x01; // 抢占优先级
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01;        // 子优先级
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

// 6. 编写中断服务函数(必须放在 stm32f10x_it.c 中)
void EXTI0_IRQHandler(void)
{
    if (EXTI_GetITStatus(EXTI_Line0) != RESET)
    {
        // 在这里处理中断事件
        // 例如:翻转 LED、发送数据等

        // 清除中断标志位(必须!)
        EXTI_ClearITPendingBit(EXTI_Line0);
    }
}

// 主函数中调用初始化
int main(void)
{
    // 系统时钟等初始化(略)

    EXTI0_Config(); // 初始化 EXTI0 中断

    while (1)
    {
        // 主循环
    }
}

事件模式

当 PA0 引脚检测到上升沿时,自动触发 ADC1 对通道 0(PA0 本身)进行一次采样,并将结果通过 DMA 存储到内存中,整个过程无需 CPU 干预(即不进入中断)。

 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
#include "stm32f10x.h"

// 定义存储 ADC 结果的缓冲区
__IO uint16_t ADC_ConvertedValue;

void ADC_DMA_EventMode_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    ADC_InitTypeDef ADC_InitStruct;
    DMA_InitTypeDef DMA_InitStruct;
    EXTI_InitTypeDef EXTI_InitStruct;

    // 1. 使能时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); // 用于 EXTI 映射

    // 2. 配置 PA0 为模拟输入(ADC)和 EXTI 触发源
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AIN; // 模拟输入
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    // 3. 配置 EXTI0 为事件模式、上升沿触发
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0); // PA0 -> EXTI0

    EXTI_InitStruct.EXTI_Line = EXTI_Line0;
    EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Event;        // ⚠️ 事件模式!
    EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising; // 上升沿
    EXTI_InitStruct.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStruct);

    // 4. 配置 DMA1 Channel1(ADC1 对应 DMA1 Channel1)
    DMA_DeInit(DMA1_Channel1);
    DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;      // ADC 数据寄存器
    DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)&ADC_ConvertedValue;
    DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC;                   // 外设到内存
    DMA_InitStruct.DMA_BufferSize = 1;
    DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Disable;
    DMA_InitStruct.DMA_PeripheralDataSize = DMA_MemoryDataSize_HalfWord;
    DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
    DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;
    DMA_InitStruct.DMA_Priority = DMA_Priority_High;
    DMA_InitStruct.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(DMA1_Channel1, &DMA_InitStruct);
    DMA_Cmd(DMA1_Channel1, ENABLE);

    // 5. 配置 ADC1
    ADC_InitStruct.ADC_Mode = ADC_Mode_Independent;
    ADC_InitStruct.ADC_ScanConvMode = DISABLE;
    ADC_InitStruct.ADC_ContinuousConvMode = DISABLE; // 单次转换
    ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_Ext_IT11_TIM8_TRGO; 
    // ⚠️ 注意:在 STM32F1 中,EXTI0~15 对应的 ADC 外部触发源是:
    // EXTI11 对应 ADC_ExternalTrigConv_Ext_IT11_TIM8_TRGO
    // 但实际 EXTI0~15 的事件会映射到 "EXTI11" 触发源!这是 STM32F1 的特殊设计。
    // 参考手册 RM0008 Section 11.10.3: "EXTI lines 0 to 15 are mapped to ADC1 external trigger"
    // 所以这里必须选 ADC_ExternalTrigConv_Ext_IT11_TIM8_TRGO

    ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;
    ADC_InitStruct.ADC_NbrOfChannel = 1;
    ADC_Init(ADC1, &ADC_InitStruct);

    // 设置 ADC 通道 0(PA0)的采样时间
    ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);

    // 使能 ADC 外部触发
    ADC_ExternalTrigConvCmd(ADC1, ENABLE);

    // 使能 ADC 和 DMA
    ADC_DMACmd(ADC1, ENABLE);
    ADC_Cmd(ADC1, ENABLE);

    // 校准 ADC(推荐)
    ADC_ResetCalibration(ADC1);
    while(ADC_GetResetCalibrationStatus(ADC1));
    ADC_StartCalibration(ADC1);
    while(ADC_GetCalibrationStatus(ADC1));
}

int main(void)
{
    // 系统初始化(如时钟)
    SystemInit();

    // 初始化 ADC + DMA + EXTI 事件模式
    ADC_DMA_EventMode_Init();

    // 主循环:CPU 可以做其他事,或进入低功耗
    while (1)
    {
        // 此时,只要 PA0 出现上升沿,
        // 就会自动触发 ADC 采样,并通过 DMA 存入 ADC_ConvertedValue
        // 无需任何中断!

        // 例如:每隔 1 秒读取一次最新采样值(非实时,仅演示)
        // 可通过调试器观察 ADC_ConvertedValue 的变化
        for (volatile int i = 0; i < 1000000; i++);
    }
}

TIM 中断

寄存器缩写 全称 位宽 功能描述
SYST_CSR Control and Status Register 32-bit 控制使能、中断使能、时钟源选择、标志位(如 COUNTFLAG)
SYST_RVR Reload Value Register 24-bit 设置重装载值(有效位为低24位,值为0时定时器不工作)
SYST_CVR Current Value Register 24-bit 读取当前计数值;写任意值可清零(写操作会清空计数器,读操作返回当前值)

使用标准库函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 默认使用 AHB 时钟(不分频)
// 配置 SysTick:每 1ms 中断一次(假设系统时钟 72MHz)
if (SysTick_Config(SystemCoreClock / 1000)) {
    // 配置失败(返回非 0)
    while (1);
}

//it.c
volatile uint32_t tick = 0;
void SysTick_Handler(void)
{
    tick++; // 每 1ms 自增
    // 可在此添加周期性任务:如 LED 闪烁、状态检查等
}

手动配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// 配置 SysTick 为 1us 延时(系统时钟 72MHz)
void SysTick_Delay_us(uint32_t us)
{
    // 重装载值 = us * (SystemCoreClock / 1000000)
    SysTick->LOAD = us * (SystemCoreClock / 1000000) - 1;
    SysTick->VAL = 0; // 清空当前值
    SysTick->CTRL = SysTick_CTRL_ENABLE_Msk; // 使能,使用 AHB 时钟,无中断

    while ((SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk) == 0);
    SysTick->CTRL = 0; // 关闭
}

void Delay_us(uint32_t us) {
    SysTick_Delay_us(us);
}

PWM

 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
#include "stm32f10x.h"

// PWM 参数
#define PWM_FREQ_HZ        100          // 100 Hz
#define SYS_TICK_US        100          // SysTick 中断间隔:100 微秒
#define PWM_PERIOD_US      (1000000 / PWM_FREQ_HZ)  // 周期 = 10,000 μs (10ms)

volatile uint32_t pwm_counter = 0;      // 当前计数值
volatile uint32_t pwm_duty = 30;        // 占空比 0~100(30%)
volatile uint32_t pwm_period_ticks = PWM_PERIOD_US / SYS_TICK_US; // 总周期 tick 数 = 100
volatile uint32_t pwm_high_ticks = 0;   // 高电平持续 tick 数

void PWM_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;

    // 1. 使能 GPIOA 时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    // 2. 配置 PA0 为推挽输出
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    // 3. 初始化 PWM 参数
    pwm_period_ticks = PWM_PERIOD_US / SYS_TICK_US; // 10000 / 100 = 100
    pwm_high_ticks = (pwm_period_ticks * pwm_duty) / 100; // 初始 30%

    // 4. 配置 SysTick:每 100us 中断一次
    // 系统主频假设为 72MHz
    if (SysTick_Config(SystemCoreClock / (1000000 / SYS_TICK_US))) {
        // SysTick_Config 的参数是重装载值 = 系统时钟频率 / 中断频率
        // 72MHz / (10^6 / 100) = 72MHz / 10000 = 7200
        while (1); // 配置失败
    }
}

// 在 main.c 或其他地方提供一个设置占空比的函数
void PWM_SetDuty(uint32_t duty)
{
    if (duty > 100) duty = 100;
    pwm_duty = duty;
    pwm_high_ticks = (pwm_period_ticks * pwm_duty) / 100;
}

// SysTick 中断服务函数
void SysTick_Handler(void)
{
    // 更新计数器
    pwm_counter++;

    // 判断是否到达周期终点
    if (pwm_counter >= pwm_period_ticks) {
        pwm_counter = 0; // 重置周期
        GPIO_SetBits(GPIOA, GPIO_Pin_0); // 周期开始:拉高(可选,也可在 else 拉高)
    }

    // 判断是否超过高电平时间
    if (pwm_counter < pwm_high_ticks) {
        GPIO_SetBits(GPIOA, GPIO_Pin_0);   // 输出高
    } else {
        GPIO_ResetBits(GPIOA, GPIO_Pin_0); // 输出低
    }
}

// 主函数
int main(void)
{
    SystemInit(); // 初始化系统时钟(72MHz)

    PWM_Init();

    // 示例:动态改变占空比(可选)
    uint32_t step = 0;
    while (1)
    {
        // 每 500ms 改变一次占空比(演示用)
        for (volatile int i = 0; i < 5000000; i++);

        PWM_SetDuty(step);
        step += 10;
        if (step > 100) step = 0;
    }
}

输出比较(输出PWM)

比较cnt与ccr的值,当为pwm1模式时,cnt<=crr -> 高电平;当为pwm2时,cnt<=crr 输出低电平
占空比: ccr/(arr+1)
频率f= f(sys) / ((ARR+1)*(CCR+1)) 时间: t = 1/f

 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
1.配置gpioa口IPU
2.配置TIM
3.配置TIM_OC

void PWM_Init(void)
{
    /*开启时钟*/
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);            //开启TIM2的时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);           //开启GPIOA的时钟
    
    /*GPIO初始化*/
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);                          //将PA1引脚初始化为复用推挽输出  
                                                                    //受外设控制的引脚,均需要配置为复用模式
    
    /*配置时钟源*/
    TIM_InternalClockConfig(TIM2);      //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
    
    /*时基单元初始化*/
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;              //定义结构体变量
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
    TIM_TimeBaseInitStructure.TIM_Period = 20000 - 1;               //计数周期,即ARR的值
    TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;               //预分频器,即PSC的值
    TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;            //重复计数器,高级定时器才会用到
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);             //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
    
    /*输出比较初始化*/ 
    TIM_OCInitTypeDef TIM_OCInitStructure;                          //定义结构体变量
    TIM_OCStructInit(&TIM_OCInitStructure);                         //结构体初始化,若结构体没有完整赋值
                                                                    //则最好执行此函数,给结构体所有成员都赋一个默认值
                                                                    //避免结构体初值不确定的问题
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;               //输出比较模式,选择PWM模式1
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;       //输出极性,选择为高,若选择极性为低,则输出高低电平取反
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;   //输出使能
    TIM_OCInitStructure.TIM_Pulse = 0;                              //初始的CCR值
    TIM_OC2Init(TIM2, &TIM_OCInitStructure);                        //将结构体变量交给TIM_OC2Init,配置TIM2的输出比较通道2
    
    /*TIM使能*/
    TIM_Cmd(TIM2, ENABLE);          //使能TIM2,定时器开始运行
}

void PWM_SetCompare2(uint16_t Compare)
{
    TIM_SetCompare2(TIM2, Compare);     //设置CCR2的值
}

输入捕获

TIM 输入捕获(Input Capture) 是 STM32 定时器(TIM)的一个重要功能,用于精确测量外部信号的时间特性,比如: 脉冲宽度(高电平/低电平持续时间) 信号周期 频率 占空比 编码器信号边沿时间等

为什么不适用EXTI配置中断

1.EXTI不知道开始时间,不知道时间戳 2.EXTI依赖系统时间

测量方法

1.测频法(一段时间的上升沿次数,适合高频)f = N/T 2.测周期法(两个上升沿内,以fc为频率进行计次,得到N.适合低频)f = fc/N 其实N就是CNT 3.中界频率sqrt(fc/T)

  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
1.配置gpio口
2.配置TIM
3.配置
void IC_Init(void)
{
    /*开启时钟*/
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);            //开启TIM3的时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);           //开启GPIOA的时钟
    
    /*GPIO初始化*/
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);                          //将PA6引脚初始化为上拉输入
    
    /*配置时钟源*/
    TIM_InternalClockConfig(TIM3);      //选择TIM3为内部时钟,若不调用此函数,TIM默认也为内部时钟
    
    /*时基单元初始化*/
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;              //定义结构体变量
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
    TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1;               //计数周期,即ARR的值   满量程计数
    TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;               //预分频器,即PSC的值   f(sys)/ PSC = fc
    TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;            //重复计数器,高级定时器才会用到
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);             //将结构体变量交给TIM_TimeBaseInit,配置TIM3的时基单元
    
    /*输入捕获初始化*/
    TIM_ICInitTypeDef TIM_ICInitStructure;                          //定义结构体变量
    TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;                //选择配置定时器通道1
    TIM_ICInitStructure.TIM_ICFilter = 0xF;                         //输入滤波器参数,可以过滤信号抖动(范围0x0-0xF)
    TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;     //极性,选择为上升沿触发捕获
    TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;           //捕获预分频,选择不分频,每次信号都触发捕获
    TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //输入信号交叉,选择直通,不交叉
    TIM_ICInit(TIM3, &TIM_ICInitStructure);                         //将结构体变量交给TIM_ICInit,配置TIM3的输入捕获通道
    
    /*选择触发源及从模式*/
    TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);                    //触发源选择TI1FP1
    TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);                 //从模式选择复位
                                                                    //即TI1产生上升沿时,会触发CNT归零
    
    /*TIM使能*/
    TIM_Cmd(TIM3, ENABLE);          //使能TIM3,定时器开始运行
}

uint32_t IC_GetFreq(void)
{
    return 1000000 / (TIM_GetCapture1(TIM3) + 1);       //测周法得到频率fx = fc / N,这里不执行+1的操作也可
}



//PWMI
void IC_Init(void)
{
    /*开启时钟*/
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);            //开启TIM3的时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);           //开启GPIOA的时钟
    
    /*GPIO初始化*/
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);                          //将PA6引脚初始化为上拉输入
    
    /*配置时钟源*/
    TIM_InternalClockConfig(TIM3);      //选择TIM3为内部时钟,若不调用此函数,TIM默认也为内部时钟
    
    /*时基单元初始化*/
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;              //定义结构体变量
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
    TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1;               //计数周期,即ARR的值
    TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;               //预分频器,即PSC的值
    TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;            //重复计数器,高级定时器才会用到
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);             //将结构体变量交给TIM_TimeBaseInit,配置TIM3的时基单元
    
    /*PWMI模式初始化*/
    TIM_ICInitTypeDef TIM_ICInitStructure;                          //定义结构体变量
    TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;                //选择配置定时器通道1
    TIM_ICInitStructure.TIM_ICFilter = 0xF;                         //输入滤波器参数,可以过滤信号抖动
    TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;     //极性,选择为上升沿触发捕获
    TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;           //捕获预分频,选择不分频,每次信号都触发捕获
    TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //输入信号交叉,选择直通,不交叉
    TIM_PWMIConfig(TIM3, &TIM_ICInitStructure);                     //将结构体变量交给TIM_PWMIConfig,配置TIM3的输入捕获通道
                                                                    //此函数同时会把另一个通道配置为相反的配置,实现PWMI模式

    /*选择触发源及从模式*/
    TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);                    //触发源选择TI1FP1
    TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);                 //从模式选择复位
                                                                    //即TI1产生上升沿时,会触发CNT归零
    
    /*TIM使能*/
    TIM_Cmd(TIM3, ENABLE);          //使能TIM3,定时器开始运行
}

uint32_t IC_GetFreq(void)
{
    return 1000000 / (TIM_GetCapture1(TIM3) + 1);       //测周法得到频率fx = fc / N,这里不执行+1的操作也可
}

uint32_t IC_GetDuty(void)
{
    return (TIM_GetCapture2(TIM3) + 1) * 100 / (TIM_GetCapture1(TIM3) + 1); //占空比Duty = CCR2 / CCR1 * 100,这里不执行+1的操作也可
}

占空比

高电平 5V,等效电平 = 5V * 钻占空比
占空比越大,模拟电压越趋近于高电平

USART—串口通讯

图1

  1. USART_BaudRate:波特率设置。一般设置为2400、9600、19200、115200。标准库函数会根据设定值计算得到USARTDIV值,从而设置USART_BRR寄存器值。
  2. USART_WordLength: 数据帧字长,可选8位或9位。它设定USART_CR1寄存器的M位的值。如果没有使能奇偶校验控制,一般使用8数据位;如果使能了奇偶校验则一般设置为9数据位。
  3. USART_StopBits: 停止位设置,可选0.5个、1个、1.5个和2个停止位,它设定USART_CR2寄存器的STOP[1:0]位的值,一般我们选择1个停止位。
  4. USART_Parity: 奇偶校验控制选择,可选USART_Parity_No(无校验)、USART_Parity_Even(偶校验)以及USART_Parity_Odd(奇校验),它设定USART_CR1寄存器的PCE位和PS位的值。
  5. USART_Mode: USART模式选择,有USART_Mode_Rx和USART_Mode_Tx,允许使用逻辑或运算选择两个,它设定USART_CR1寄存器的RE位和TE位。
  6. USART_HardwareFlowControl: 硬件流控制选择,只有在硬件流控制模式才有效,可选有使能RTS、使能CTS、同时使能RTS和CTS、不使能硬件流。

当使用同步模式时需要配置SCLK引脚输出脉冲的属性,标准库使用一个时钟初始化结构体USART_ClockInitTypeDef来设置,该结构体内容也只有在同步模式才需要设置。

STM32F103系列控制器USART支持奇偶校验。当使用校验位时,串口传输的长度将是8位的数据帧加上1位的校验位总共9位, 此时USART_CR1寄存器的M位需要设置为1,即9数据位。将USART_CR1寄存器的PCE位置1就可以启动奇偶校验控制, 奇偶校验由硬件自动完成。启动了奇偶校验控制之后,在发送数据帧时会自动添加校验位,接收数据时自动验证校验位。 接收数据时如果出现奇偶校验位验证失败,会见USART_SR寄存器的PE位置1,并可以产生奇偶校验中断。

USART可配置的中断事件

图1

  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
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
#include "usart.h"
#include <stdio.h> // 若使用 printf 重定向

// 发送一个字节(轮询方式)
void USART1_SendByte(uint8_t data)
{
    // 等待发送数据寄存器为空
    while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
    USART_SendData(USART1, (uint8_t)data);
}

// 发送字符串
void USART1_SendString(char* str)
{
    while (*str) {
        USART1_SendByte(*str++);
    }
}

// 重定向 printf 到 USART1(可选)
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif

PUTCHAR_PROTOTYPE
{
    USART1_SendByte((uint8_t)ch);
    return ch;
}

// USART1 初始化
void USART1_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    USART_InitTypeDef USART_InitStruct;
    NVIC_InitTypeDef NVIC_InitStruct;

    // 1. 使能时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);

    // 2. 配置 PA9 (TX) 为复用推挽输出
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    // 3. 配置 PA10 (RX) 为浮空输入
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    // 4. 配置 USART1
    USART_InitStruct.USART_BaudRate = 115200;
    USART_InitStruct.USART_WordLength = USART_WordLength_8b;
    USART_InitStruct.USART_StopBits = USART_StopBits_1;
    USART_InitStruct.USART_Parity = USART_Parity_No;
    USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
    USART_Init(USART1, &USART_InitStruct);

    // 5. 使能 USART1 接收中断
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // 接收非空中断

    // 6. 配置 NVIC
    NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStruct);

    // 7. 使能 USART1
    USART_Cmd(USART1, ENABLE);
}

//  stm32f10x_it.c
#include "stm32f10x_it.h"
#include "usart.h"

// USART1 中断服务函数
void USART1_IRQHandler(void)
{
    uint8_t rx_data;

    // 检查是否是接收中断
    if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
    {
        rx_data = USART_ReceiveData(USART1); // 读取接收到的数据(同时清除 RXNE 标志)

        // 回发接收到的数据(Echo)
        USART1_SendByte(rx_data);

        // 可选:处理特定命令
        // if (rx_data == 'R') { ... }
    }
}

//main.c
#include "stm32f10x.h"
#include "usart.h"

int main(void)
{
    SystemInit(); // 初始化系统时钟(72MHz)

    USART1_Init();

    // 可选:发送启动信息
    USART1_SendString("STM32 USART Echo Ready!\r\n");

    while (1)
    {
        // 主循环可做其他任务
        // 所有串口通信由中断处理
    }
}




#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>

char Serial_RxPacket[100];              //定义接收数据包数组,数据包格式"@MSG\r\n"
uint8_t Serial_RxFlag;                  //定义接收数据包标志位

/**
  * 函    数:串口初始化
  * 参    数:无
  * 返 回 值:无
  */
void Serial_Init(void)
{
    /*开启时钟*/
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);  //开启USART1的时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);   //开启GPIOA的时钟
    
    /*GPIO初始化*/
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);                  //将PA9引脚初始化为复用推挽输出
    
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);                  //将PA10引脚初始化为上拉输入
    
    /*USART初始化*/
    USART_InitTypeDef USART_InitStructure;                  //定义结构体变量
    USART_InitStructure.USART_BaudRate = 9600;              //波特率
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //硬件流控制,不需要
    USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; //模式,发送模式和接收模式均选择
    USART_InitStructure.USART_Parity = USART_Parity_No;     //奇偶校验,不需要
    USART_InitStructure.USART_StopBits = USART_StopBits_1;  //停止位,选择1位
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;     //字长,选择8位
    USART_Init(USART1, &USART_InitStructure);               //将结构体变量交给USART_Init,配置USART1
    
    /*中断输出配置*/
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);          //开启串口接收数据的中断
    
    /*NVIC中断分组*/
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);         //配置NVIC为分组2
    
    /*NVIC配置*/
    NVIC_InitTypeDef NVIC_InitStructure;                    //定义结构体变量
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;       //选择配置NVIC的USART1线
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;         //指定NVIC线路使能
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;       //指定NVIC线路的抢占优先级为1
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;      //指定NVIC线路的响应优先级为1
    NVIC_Init(&NVIC_InitStructure);                         //将结构体变量交给NVIC_Init,配置NVIC外设
    
    /*USART使能*/
    USART_Cmd(USART1, ENABLE);                              //使能USART1,串口开始运行
}

/**
  * 函    数:串口发送一个字节
  * 参    数:Byte 要发送的一个字节
  * 返 回 值:无
  */
void Serial_SendByte(uint8_t Byte)
{
    USART_SendData(USART1, Byte);       //将字节数据写入数据寄存器,写入后USART自动生成时序波形
    while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);   //等待发送完成
    /*下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位*/
}

/**
  * 函    数:串口发送一个数组
  * 参    数:Array 要发送数组的首地址
  * 参    数:Length 要发送数组的长度
  * 返 回 值:无
  */
void Serial_SendArray(uint8_t *Array, uint16_t Length)
{
    uint16_t i;
    for (i = 0; i < Length; i ++)       //遍历数组
    {
        Serial_SendByte(Array[i]);      //依次调用Serial_SendByte发送每个字节数据
    }
}

/**
  * 函    数:串口发送一个字符串
  * 参    数:String 要发送字符串的首地址
  * 返 回 值:无
  */
void Serial_SendString(char *String)
{
    uint8_t i;
    for (i = 0; String[i] != '\0'; i ++)//遍历字符数组(字符串),遇到字符串结束标志位后停止
    {
        Serial_SendByte(String[i]);     //依次调用Serial_SendByte发送每个字节数据
    }
}

/**
  * 函    数:次方函数(内部使用)
  * 返 回 值:返回值等于X的Y次方
  */
uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{
    uint32_t Result = 1;    //设置结果初值为1
    while (Y --)            //执行Y次
    {
        Result *= X;        //将X累乘到结果
    }
    return Result;
}

/**
  * 函    数:串口发送数字
  * 参    数:Number 要发送的数字,范围:0~4294967295
  * 参    数:Length 要发送数字的长度,范围:0~10
  * 返 回 值:无
  */
void Serial_SendNumber(uint32_t Number, uint8_t Length)
{
    uint8_t i;
    for (i = 0; i < Length; i ++)       //根据数字长度遍历数字的每一位
    {
        Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0');    //依次调用Serial_SendByte发送每位数字
    }
}

/**
  * 函    数:使用printf需要重定向的底层函数
  * 参    数:保持原始格式即可,无需变动
  * 返 回 值:保持原始格式即可,无需变动
  */
int fputc(int ch, FILE *f)
{
    Serial_SendByte(ch);            //将printf的底层重定向到自己的发送字节函数
    return ch;
}

/**
  * 函    数:自己封装的prinf函数
  * 参    数:format 格式化字符串
  * 参    数:... 可变的参数列表
  * 返 回 值:无
  */
void Serial_Printf(char *format, ...)
{
    char String[100];               //定义字符数组
    va_list arg;                    //定义可变参数列表数据类型的变量arg
    va_start(arg, format);          //从format开始,接收参数列表到arg变量
    vsprintf(String, format, arg);  //使用vsprintf打印格式化字符串和参数列表到字符数组中
    va_end(arg);                    //结束变量arg
    Serial_SendString(String);      //串口发送字符数组(字符串)
}

/**
  * 函    数:USART1中断函数
  * 参    数:无
  * 返 回 值:无
  * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
  *           函数名为预留的指定名称,可以从启动文件复制
  *           请确保函数名正确,不能有任何差异,否则中断函数将不能进入
  */
void USART1_IRQHandler(void)
{
    static uint8_t RxState = 0;     //定义表示当前状态机状态的静态变量
    static uint8_t pRxPacket = 0;   //定义表示当前接收数据位置的静态变量
    if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)    //判断是否是USART1的接收事件触发的中断
    {
        uint8_t RxData = USART_ReceiveData(USART1);         //读取数据寄存器,存放在接收的数据变量
        
        /*使用状态机的思路,依次处理数据包的不同部分*/
        
        /*当前状态为0,接收数据包包头*/
        if (RxState == 0)
        {
            if (RxData == '@' && Serial_RxFlag == 0)        //如果数据确实是包头,并且上一个数据包已处理完毕
            {
                RxState = 1;            //置下一个状态
                pRxPacket = 0;          //数据包的位置归零
            }
        }
        /*当前状态为1,接收数据包数据,同时判断是否接收到了第一个包尾*/
        else if (RxState == 1)
        {
            if (RxData == '\r')         //如果收到第一个包尾
            {
                RxState = 2;            //置下一个状态
            }
            else                        //接收到了正常的数据
            {
                Serial_RxPacket[pRxPacket] = RxData;        //将数据存入数据包数组的指定位置
                pRxPacket ++;           //数据包的位置自增
            }
        }
        /*当前状态为2,接收数据包第二个包尾*/
        else if (RxState == 2)
        {
            if (RxData == '\n')         //如果收到第二个包尾
            {
                RxState = 0;            //状态归0
                Serial_RxPacket[pRxPacket] = '\0';          //将收到的字符数据包添加一个字符串结束标志
                Serial_RxFlag = 1;      //接收数据包标志位置1,成功接收一个数据包
            }
        }
        
        USART_ClearITPendingBit(USART1, USART_IT_RXNE);     //清除标志位
    }
}

DMA中断

从外设到存储器,从存储器到外设,从存储器到存储器。 具体的方向DMA_CCR位4 DIR配置:0表示从外设到存储器,1表示从存储器到外设。 这里面涉及到的外设地址由DMA_CPAR配置,存储器地址由DMA_CMAR配置。 当我们使用从外设到存储器传输时,以ADC采集为例。DMA外设寄存器的地址对应的就是ADC数据寄存器的地址, DMA存储器的地址就是我们自定义的变量(用来接收存储AD采集的数据)的地址。方向我们设置外设为源地址。

 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
typedef struct
{
    uint32_t DMA_PeripheralBaseAddr;   // 外设地址
    uint32_t DMA_MemoryBaseAddr;       // 存储器地址
    uint32_t DMA_DIR;                  // 传输方向
    uint32_t DMA_BufferSize;           // 传输数目
    uint32_t DMA_PeripheralInc;        // 外设地址增量模式
    uint32_t DMA_MemoryInc;            // 存储器地址增量模式
    uint32_t DMA_PeripheralDataSize;   // 外设数据宽度
    uint32_t DMA_MemoryDataSize;       // 存储器数据宽度
    uint32_t DMA_Mode;                 // 模式选择
    uint32_t DMA_Priority;             // 通道优先级
    uint32_t DMA_M2M;                  // 存储器到存储器模式
} DMA_InitTypeDef;


1) DMA_PeripheralBaseAddr 外设地址,设定DMA_CPAR寄存器的值;一般设置为外设的数据寄存器地址,如果是存储器到存储器模式则设置为其中一个存储器地址。

2) DMA_Memory0BaseAddr 存储器地址,设定DMA_CMAR寄存器值;一般设置为我们自定义存储区的首地址。

3) DMA_DIR 传输方向选择,可选外设到存储器、存储器到外设。它设定DMA_CCR寄存器的DIR[1:0]位的值。这里并没有存储器到存储器的方向选择,当使用存储器到存储器时,只需要把其中一个存储器当作外设使用即可。

4) DMA_BufferSize 设定待传输数据数目,初始化设定DMA_CNDTR寄存器的值

5) DMA_PeripheralInc 如果配置为DMA_PeripheralInc_Enable,使能外设地址自动递增功能,它设定DMA_CCR寄存器的PINC位的值;一般外设都是只有一个数据寄存器,所以一般不会使能该位。

6) DMA_MemoryInc 如果配置为DMA_MemoryInc_Enable,使能存储器地址自动递增功能,它设定DMA_CCR寄存器的MINC位的值;我们自定义的存储区一般都是存放多个数据的,所以要使能存储器地址自动递增功能。

7) DMA_PeripheralDataSize 外设数据宽度,可选字节(8)、半字(16)和字(32),它设定DMA_CCR寄存器的PSIZE[1:0]位的值。

8) DMA_MemoryDataSize 存储器数据宽度,可选字节(8)、半字(16)和字(32),它设定DMA_CCR寄存器的MSIZE[1:0]位的值。当外设和存储器之间传数据时,两边的数据宽度应该设置为一致大小。

9) DMA_Mode DMA传输模式选择,可选一次传输或者循环传输,它设定DMA_CCR寄存器的CIRC位的值。例程我们的ADC采集是持续循环进行的,所以使用循环传输模式。

10) DMA_Priority 软件设置通道的优先级,有4个可选优先级分别为非常高、高、中和低,它设定DMA_CCR寄存器的PL[1:0]位的值。DMA通道优先级只有在多个DMA通道同时使用时才有意义,如果是单个通道,优先级可以随便设置。

11) DMA_M2M 存储器到存储器模式,使用存储器到存储器时用到,设定DMA_CCR的位14 MEN2MEN即可启动存储器到存储器模式

void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size)
{
    MyDMA_Size = Size;                  //将Size写入到全局变量,记住参数Size
    
    /*开启时钟*/
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);                      //开启DMA的时钟
    
    /*DMA初始化*/
    DMA_InitTypeDef DMA_InitStructure;                                      //定义结构体变量
    DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA;                       //外设基地址,给定形参AddrA
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外设数据宽度,选择字节
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;         //外设地址自增,选择使能
    DMA_InitStructure.DMA_MemoryBaseAddr = AddrB;                           //存储器基地址,给定形参AddrB
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;         //存储器数据宽度,选择字节
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;                 //存储器地址自增,选择使能
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;                      //数据传输方向,选择由外设到存储器
    DMA_InitStructure.DMA_BufferSize = Size;                                //转运的数据大小(转运次数)
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;                           //模式,选择正常模式
    DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;                             //存储器到存储器,选择使能
    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;                   //优先级,选择中等
    DMA_Init(DMA1_Channel1, &DMA_InitStructure);                            //将结构体变量交给DMA_Init,配置DMA1的通道1
    
    /*DMA使能*/
    DMA_Cmd(DMA1_Channel1, DISABLE);    //这里先不给使能,初始化后不会立刻工作,等后续调用Transfer后,再开始
}

/**
  * 函    数:启动DMA数据转运
  * 参    数:无
  * 返 回 值:无
  */
void MyDMA_Transfer(void)
{
    DMA_Cmd(DMA1_Channel1, DISABLE);                    //DMA失能,在写入传输计数器之前,需要DMA暂停工作
    DMA_SetCurrDataCounter(DMA1_Channel1, MyDMA_Size);  //写入传输计数器,指定将要转运的次数
    DMA_Cmd(DMA1_Channel1, ENABLE);                     //DMA使能,开始工作
    
    while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);  //等待DMA工作完成
    DMA_ClearFlag(DMA1_FLAG_TC1);                       //清除工作完成标志位
}

I2C

SCK 1时读取,0时不读取。SDA下降沿开始,上升沿停止,其他时候传数据

硬件IIC

  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
#include "stm32f10x.h"
#include "stm32f10x_i2c.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_gpio.h"
#include "misc.h"

// EEPROM 地址(AT24C02,A0-A2=0,写地址为 0xA0,读地址为 0xA1)
#define EEPROM_ADDR    0xA0

// 函数声明
void I2C1_Init(void);
void I2C_EE_ByteWrite(uint8_t device_addr, uint16_t mem_addr, uint8_t data);
uint8_t I2C_EE_ByteRead(uint8_t device_addr, uint16_t mem_addr);
void I2C_EE_PageWrite(uint8_t device_addr, uint16_t mem_addr, uint8_t* pBuffer, uint16_t NumByteToWrite);
void I2C_EE_BufferRead(uint8_t device_addr, uint16_t mem_addr, uint8_t* pBuffer, uint16_t NumByteToRead);
void Delay_ms(uint32_t ms);

int main(void)
{
    uint8_t write_data = 0x55;
    uint8_t read_data = 0;

    // 系统时钟配置(使用内部8MHz,不配置PLL,默认系统时钟为8MHz)
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);

    I2C1_Init();

    // 写入一个字节到地址 0x10
    I2C_EE_ByteWrite(EEPROM_ADDR, 0x10, write_data);
    Delay_ms(10); // 等待 EEPROM 内部写入完成(最大 5ms,保险起见 10ms)

    // 从地址 0x10 读取一个字节
    read_data = I2C_EE_ByteRead(EEPROM_ADDR, 0x10);

    // 此时 read_data 应为 0x55

    while (1)
    {
        // 可添加 LED 闪烁等调试
    }
}

// I2C1 初始化(硬件 I2C)
void I2C1_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    I2C_InitTypeDef I2C_InitStructure;
    // 配置 PB6 (SCL), PB7 (SDA) 为复用开漏输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; // 复用开漏
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    // I2C1 配置
    I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
    I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; // 标准模式下使用 2
    I2C_InitStructure.I2C_OwnAddress1 = 0x00; // 主机模式,不需要自身地址
    I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
    I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
    I2C_InitStructure.I2C_ClockSpeed = 100000; // 100kHz

    I2C_Init(I2C1, &I2C_InitStructure);
    I2C_Cmd(I2C1, ENABLE);
}

// 向 EEPROM 写入一个字节
void I2C_EE_ByteWrite(uint8_t device_addr, uint16_t mem_addr, uint8_t data)
{
    // 等待总线空闲
    while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));
    // 发送起始条件
    I2C_GenerateSTART(I2C1, ENABLE);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
    // 发送设备地址(写)
    I2C_Send7bitAddress(I2C1, device_addr, I2C_Direction_Transmitter);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
    // 发送内存地址(AT24C02 地址为 8 位)
    I2C_SendData(I2C1, (uint8_t)mem_addr);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
    // 发送数据
    I2C_SendData(I2C1, data);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
    // 发送停止条件
    I2C_GenerateSTOP(I2C1, ENABLE);
}

// 从 EEPROM 读取一个字节
uint8_t I2C_EE_ByteRead(uint8_t device_addr, uint16_t mem_addr)
{
    uint8_t data = 0;
    // 第一步:写入要读取的地址
    while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));
    I2C_GenerateSTART(I2C1, ENABLE);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
    I2C_Send7bitAddress(I2C1, device_addr, I2C_Direction_Transmitter);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
    I2C_SendData(I2C1, (uint8_t)mem_addr);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
    // 第二步:重新起始,切换到读模式
    I2C_GenerateSTART(I2C1, ENABLE);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
    I2C_Send7bitAddress(I2C1, device_addr, I2C_Direction_Receiver);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
    // 关闭 ACK(单字节读取)
    I2C_AcknowledgeConfig(I2C1, DISABLE);
    // 发送 STOP 条件(在接收前或后,根据芯片要求)
    I2C_GenerateSTOP(I2C1, ENABLE);
    // 读取数据
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED));
    data = I2C_ReceiveData(I2C1);
    // 重新使能 ACK(为后续通信准备)
    I2C_AcknowledgeConfig(I2C1, ENABLE);
    return data;
}

// 简单延时函数(基于系统时钟 8MHz)
void Delay_ms(uint32_t ms)
{
    uint32_t i, j;
    for (i = 0; i < ms; i++)
        for (j = 0; j < 8000; j++);
}

软件IIC

5us = 1/8000000 * 2 * 2

  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
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
#ifndef __SOFT_I2C_H
#define __SOFT_I2C_H

#include "stm32f10x.h"

// ================== 【用户可配置区域】 ==================
// 修改此处即可更换 SCL/SDA 引脚
#define I2C_SCL_PORT        GPIOA
#define I2C_SCL_PIN         GPIO_Pin_0

#define I2C_SDA_PORT        GPIOA
#define I2C_SDA_PIN         GPIO_Pin_1

#define I2C_SCL_CLK         RCC_APB2Periph_GPIOA
#define I2C_SDA_CLK         RCC_APB2Periph_GPIOA
// =====================================================

// 时钟使能宏
#define I2C_GPIO_CLK_ENABLE()   do { \
    RCC_APB2PeriphClockCmd(I2C_SCL_CLK | I2C_SDA_CLK, ENABLE); \
} while(0)

// SCL/SDA 输出高/低
#define SCL_HIGH()          GPIO_SetBits(I2C_SCL_PORT, I2C_SCL_PIN)
#define SCL_LOW()           GPIO_ResetBits(I2C_SCL_PORT, I2C_SCL_PIN)
#define SDA_HIGH()          GPIO_SetBits(I2C_SDA_PORT, I2C_SDA_PIN)
#define SDA_LOW()           GPIO_ResetBits(I2C_SDA_PORT, I2C_SDA_PIN)

// SDA 输入读取(需先设为输入模式)
#define SDA_READ()          GPIO_ReadInputDataBit(I2C_SDA_PORT, I2C_SDA_PIN)

// 延时(单位:微秒,可根据系统时钟调整)
#define I2C_DELAY_US(n)     soft_i2c_delay_us(n)

// 函数声明
void soft_i2c_init(void);
void soft_i2c_start(void);
void soft_i2c_stop(void);
uint8_t soft_i2c_send_byte(uint8_t byte);
uint8_t soft_i2c_read_byte(uint8_t ack);
void soft_i2c_sda_input(void);
void soft_i2c_sda_output(void);
void soft_i2c_delay_us(uint32_t us);

#endif

#include "soft_i2c.h"

// 简单的微秒延时(假设系统时钟为 8MHz,HCLK=8MHz)
// 若使用 72MHz,需调整循环次数
void soft_i2c_delay_us(uint32_t us)
{
    uint32_t i;
    for (i = 0; i < us * 2; i++); // 粗略校准,实际可用 SysTick 或 DWT
}

// 初始化 SCL/SDA 为开漏输出(初始高电平)
void soft_i2c_init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;

    I2C_GPIO_CLK_ENABLE();

    // SCL 配置为开漏输出
    GPIO_InitStruct.GPIO_Pin = I2C_SCL_PIN;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(I2C_SCL_PORT, &GPIO_InitStruct);

    // SDA 配置为开漏输出
    GPIO_InitStruct.GPIO_Pin = I2C_SDA_PIN;
    GPIO_Init(I2C_SDA_PORT, &GPIO_InitStruct);

    // 初始释放总线(高电平)
    SCL_HIGH();
    SDA_HIGH();
}

// 产生起始条件:SDA 从高→低,SCL 保持高
void soft_i2c_start(void)
{
    SDA_HIGH();
    SCL_HIGH();
    I2C_DELAY_US(2);
    SDA_LOW();
    I2C_DELAY_US(2);
    SCL_LOW(); // 拉低 SCL,准备发送数据
}

// 产生停止条件:SDA 从低→高,SCL 保持高
void soft_i2c_stop(void)
{
    SCL_LOW();
    SDA_LOW();
    I2C_DELAY_US(2);
    SCL_HIGH();
    I2C_DELAY_US(2);
    SDA_HIGH();
    I2C_DELAY_US(2);
}

// 发送一个字节(MSB first),返回从机 ACK(0=ACK, 1=NACK)
uint8_t soft_i2c_send_byte(uint8_t byte)
{
    uint8_t i;
    uint8_t ack;

    // 设置 SDA 为输出
    soft_i2c_sda_output();

    // 发送 8 位数据
    for (i = 0; i < 8; i++)
    {
        if (byte & 0x80)
            SDA_HIGH();
        else
            SDA_LOW();
        byte <<= 1;

        SCL_HIGH();
        I2C_DELAY_US(2);
        SCL_LOW();
        I2C_DELAY_US(2);
    }

    // 读取 ACK(SDA 输入)
    soft_i2c_sda_input();
    SCL_HIGH();
    I2C_DELAY_US(2);
    ack = SDA_READ();
    SCL_LOW();
    I2C_DELAY_US(2);

    // 恢复 SDA 为输出
    soft_i2c_sda_output();

    return ack; // 0 表示 ACK
}

// 读取一个字节,ack=1 表示发送 ACK,ack=0 表示发送 NACK
uint8_t soft_i2c_read_byte(uint8_t ack)
{
    uint8_t i;
    uint8_t data = 0;

    soft_i2c_sda_input();

    for (i = 0; i < 8; i++)
    {
        data <<= 1;
        SCL_HIGH();
        I2C_DELAY_US(2);
        if (SDA_READ())
            data |= 0x01;
        SCL_LOW();
        I2C_DELAY_US(2);
    }

    // 发送 ACK/NACK
    soft_i2c_sda_output();
    if (ack)
        SDA_LOW();  // ACK
    else
        SDA_HIGH(); // NACK

    SCL_HIGH();
    I2C_DELAY_US(2);
    SCL_LOW();
    I2C_DELAY_US(2);

    return data;
}

// 设置 SDA 为输入模式(用于读取 ACK 或数据)
void soft_i2c_sda_input(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.GPIO_Pin = I2C_SDA_PIN;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 或 IPD/IPU,但 I²C 通常用浮空+外部上拉
    GPIO_Init(I2C_SDA_PORT, &GPIO_InitStruct);
}

// 设置 SDA 为输出模式
void soft_i2c_sda_output(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.GPIO_Pin = I2C_SDA_PIN;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(I2C_SDA_PORT, &GPIO_InitStruct);
}

#include "stm32f10x.h"
#include "soft_i2c.h"

#define EEPROM_ADDR        0xA0  // AT24C02 写地址(A0-A2=0)

void Delay_ms(uint32_t ms);
void EEPROM_ByteWrite(uint8_t dev_addr, uint16_t mem_addr, uint8_t data);
uint8_t EEPROM_ByteRead(uint8_t dev_addr, uint16_t mem_addr);

int main(void)
{
    uint8_t w_data = 0xAA;
    uint8_t r_data = 0;

    // 初始化软件 I2C
    soft_i2c_init();

    // 写入数据到 EEPROM 地址 0x20
    EEPROM_ByteWrite(EEPROM_ADDR, 0x20, w_data);
    Delay_ms(10); // 等待写入完成

    // 读取数据
    r_data = EEPROM_ByteRead(EEPROM_ADDR, 0x20);

    // r_data 应等于 0xAA

    while (1)
    {
        // 可添加 LED 或串口打印调试
    }
}

// 向 EEPROM 写入一个字节(AT24C02,8位地址)
void EEPROM_ByteWrite(uint8_t dev_addr, uint16_t mem_addr, uint8_t data)
{
    soft_i2c_start();
    soft_i2c_send_byte(dev_addr);          // 发送设备地址(写)
    soft_i2c_send_byte((uint8_t)mem_addr); // 发送内存地址
    soft_i2c_send_byte(data);              // 发送数据
    soft_i2c_stop();
}

// 从 EEPROM 读取一个字节
uint8_t EEPROM_ByteRead(uint8_t dev_addr, uint16_t mem_addr)
{
    uint8_t data;

    // 第一步:写入要读的地址
    soft_i2c_start();
    soft_i2c_send_byte(dev_addr);
    soft_i2c_send_byte((uint8_t)mem_addr);
    
    // 第二步:重新起始,切换到读模式
    soft_i2c_start();
    soft_i2c_send_byte(dev_addr | 0x01); // 读地址(最低位为1)

    data = soft_i2c_read_byte(0); // 最后一个字节发 NACK

    soft_i2c_stop();

    return data;
}

// 简单毫秒延时
void Delay_ms(uint32_t ms)
{
    uint32_t i, j;
    for (i = 0; i < ms; i++)
        for (j = 0; j < 8000; j++);
}

mpu6050

  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
#ifndef __I2C_SOFT_H
#define __I2C_SOFT_H

#include "stm32f10x.h"

// 定义 SCL 和 SDA 引脚(可修改)
#define I2C_SCL_PIN        GPIO_Pin_6
#define I2C_SCL_GPIO_PORT  GPIOA
#define I2C_SDA_PIN        GPIO_Pin_7
#define I2C_SDA_GPIO_PORT  GPIOA

void I2C_Soft_Init(void);
void I2C_Soft_Start(void);
void I2C_Soft_Stop(void);
uint8_t I2C_Soft_WriteByte(uint8_t byte);
uint8_t I2C_Soft_ReadByte(uint8_t ack);
uint8_t I2C_Soft_WaitAck(void);
void I2C_Soft_Ack(void);
void I2C_Soft_NAck(void);

// 高层接口
uint8_t I2C_Soft_WriteReg(uint8_t dev_addr, uint8_t reg, uint8_t data);
uint8_t I2C_Soft_ReadReg(uint8_t dev_addr, uint8_t reg);
void I2C_Soft_ReadMultiReg(uint8_t dev_addr, uint8_t reg, uint8_t *buf, uint8_t len);

#endif


#include "i2c_soft.h"
#include "delay.h"  // 需要一个微秒级延时函数(如 Delay_us)

// 设置 SCL 为输出
#define SCL_H()   GPIO_SetBits(I2C_SCL_GPIO_PORT, I2C_SCL_PIN)
#define SCL_L()   GPIO_ResetBits(I2C_SCL_GPIO_PORT, I2C_SCL_PIN)
// 设置 SDA 为输出
#define SDA_H()   GPIO_SetBits(I2C_SDA_GPIO_PORT, I2C_SDA_PIN)
#define SDA_L()   GPIO_ResetBits(I2C_SDA_GPIO_PORT, I2C_SDA_PIN)
// 读取 SDA 电平
#define SDA_READ() GPIO_ReadInputDataBit(I2C_SDA_GPIO_PORT, I2C_SDA_PIN)

// 微秒延时(需实现,例如使用 SysTick)
extern void Delay_us(uint32_t us);

// 初始化软件 I2C GPIO
void I2C_Soft_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    // SCL: 推挽输出
    GPIO_InitStructure.GPIO_Pin = I2C_SCL_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(I2C_SCL_GPIO_PORT, &GPIO_InitStructure);

    // SDA: 开漏输出(但用推挽模拟,需手动控制高电平)
    GPIO_InitStructure.GPIO_Pin = I2C_SDA_PIN;
    GPIO_Init(I2C_SDA_GPIO_PORT, &GPIO_InitStructure);

    SCL_H();
    SDA_H();
    Delay_us(1);
}

// 产生 I2C 起始信号
void I2C_Soft_Start(void)
{
    SDA_H();
    SCL_H();
    Delay_us(1);
    SDA_L();
    Delay_us(1);
    SCL_L(); // 拉低 SCL,准备发送数据
}

// 产生 I2C 停止信号
void I2C_Soft_Stop(void)
{
    SCL_L();
    SDA_L();
    Delay_us(1);
    SCL_H();
    Delay_us(1);
    SDA_H();
    Delay_us(1);
}

// 写一个字节(返回 0 成功,1 失败)
uint8_t I2C_Soft_WriteByte(uint8_t byte)
{
    uint8_t i;
    for(i = 0; i < 8; i++)
    {
        if(byte & 0x80)
            SDA_H();
        else
            SDA_L();
        byte <<= 1;
        Delay_us(1);
        SCL_H();
        Delay_us(2);
        SCL_L();
        Delay_us(1);
    }
    return I2C_Soft_WaitAck();
}

// 等待从机 ACK(0: ACK, 1: NACK)
uint8_t I2C_Soft_WaitAck(void)
{
    uint8_t retry = 0;
    SDA_H(); // 释放 SDA
    Delay_us(1);
    SCL_H();
    Delay_us(1);
    while(SDA_READ())
    {
        retry++;
        if(retry > 250)
        {
            I2C_Soft_Stop();
            return 1; // 超时无 ACK
        }
    }
    SCL_L();
    return 0;
}

// 主机发送 ACK
void I2C_Soft_Ack(void)
{
    SDA_L();
    SCL_H();
    Delay_us(2);
    SCL_L();
    SDA_H();
}

// 主机发送 NACK
void I2C_Soft_NAck(void)
{
    SDA_H();
    SCL_H();
    Delay_us(2);
    SCL_L();
}

// 读一个字节(ack=1 发 ACK,ack=0 发 NACK)
uint8_t I2C_Soft_ReadByte(uint8_t ack)
{
    uint8_t i, data = 0;
    SDA_H(); // 释放 SDA,设为输入(实际仍用推挽,但靠外部上拉)
    for(i = 0; i < 8; i++)
    {
        data <<= 1;
        SCL_H();
        Delay_us(2);
        if(SDA_READ()) data |= 0x01;
        SCL_L();
        Delay_us(1);
    }
    if(ack)
        I2C_Soft_Ack();
    else
        I2C_Soft_NAck();
    return data;
}

// 写寄存器
uint8_t I2C_Soft_WriteReg(uint8_t dev_addr, uint8_t reg, uint8_t data)
{
    I2C_Soft_Start();
    if(I2C_Soft_WriteByte(dev_addr << 1)) // 写地址
    {
        I2C_Soft_Stop();
        return 1;
    }
    if(I2C_Soft_WriteByte(reg))
    {
        I2C_Soft_Stop();
        return 1;
    }
    if(I2C_Soft_WriteByte(data))
    {
        I2C_Soft_Stop();
        return 1;
    }
    I2C_Soft_Stop();
    return 0;
}

// 读一个寄存器
uint8_t I2C_Soft_ReadReg(uint8_t dev_addr, uint8_t reg)
{
    uint8_t data;
    I2C_Soft_Start();
    I2C_Soft_WriteByte(dev_addr << 1);      // 发送写地址
    I2C_Soft_WriteByte(reg);                // 发送寄存器地址
    I2C_Soft_Start();                       // 重复起始
    I2C_Soft_WriteByte((dev_addr << 1) | 1); // 发送读地址
    data = I2C_Soft_ReadByte(0);            // 读一个字节,发 NACK
    I2C_Soft_Stop();
    return data;
}

// 读多个寄存器
void I2C_Soft_ReadMultiReg(uint8_t dev_addr, uint8_t reg, uint8_t *buf, uint8_t len)
{
    uint8_t i;
    I2C_Soft_Start();
    I2C_Soft_WriteByte(dev_addr << 1);
    I2C_Soft_WriteByte(reg);
    I2C_Soft_Start();
    I2C_Soft_WriteByte((dev_addr << 1) | 1);

    for(i = 0; i < len; i++)
    {
        if(i == len - 1)
            buf[i] = I2C_Soft_ReadByte(0); // 最后一个字节 NACK
        else
            buf[i] = I2C_Soft_ReadByte(1); // 中间字节 ACK
    }
    I2C_Soft_Stop();
}

SPI FLASH读写

SS 0时开始通信,1时不通信
NSS、SCK、MOSI信号都由主机控制产生,而MISO的信号由从机产生

 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
typedef struct
{
    uint16_t SPI_Direction;           /*设置SPI的单双向模式 */
    uint16_t SPI_Mode;                /*设置SPI的主/从机端模式 */
    uint16_t SPI_DataSize;            /*设置SPI的数据帧长度,可选8/16位 */
    uint16_t SPI_CPOL;                /*设置时钟极性CPOL,可选高/低电平*/
    uint16_t SPI_CPHA;                /*设置时钟相位,可选奇/偶数边沿采样 */
    uint16_t SPI_NSS;                /*设置NSS引脚由SPI硬件控制还是软件控制*/
    uint16_t SPI_BaudRatePrescaler;  /*设置时钟分频因子,fpclk/分频数=fSCK */
    uint16_t SPI_FirstBit;            /*设置MSB/LSB先行 */
    uint16_t SPI_CRCPolynomial;       /*设置CRC校验的表达式 */
} SPI_InitTypeDef;

uint16_t SPI_Direction;
作用:设置 SPI  数据传输方向(单工/半双工/全双工)。
常用取值(宏定义):
SPI_Direction_2Lines_FullDuplex2线全双工(默认,MOSI + MISO 同时工作)✅
SPI_Direction_2Lines_RxOnly2线只接收(MISO 有效,MOSI 禁用)
SPI_Direction_1Line_Rx1线只接收
SPI_Direction_1Line_Tx1线只发送


uint16_t SPI_Mode;
作用:设置 STM32  SPI 主机(Master)还是从机(Slave)。
取值:
SPI_Mode_Master:主机模式(STM32 控制 SCK  NSS)✅
SPI_Mode_Slave:从机模式(由外部设备控制时钟)


uint16_t SPI_DataSize;
作用:设置 每帧数据的位数。
取值:
SPI_DataSize_8b8 位数据帧(最常用)✅
SPI_DataSize_16b16 位数据帧


uint16_t SPI_CPOL;
作用:设置 时钟极性(Clock Polarity —— SCK 空闲时的电平。
取值:
SPI_CPOL_LowSCK 空闲时为 低电平(0)✅
SPI_CPOL_HighSCK 空闲时为 高电平(1


uint16_t SPI_CPHA;
作用:设置 时钟相位(Clock Phase —— 数据在 SCK  第几个边沿采样。
取值:
SPI_CPHA_1Edge:第一个边沿采样(上升沿或下降沿,取决于 CPOL)✅
SPI_CPHA_2Edge:第二个边沿采样


uint16_t SPI_NSS;
作用:设置 NSS(片选)信号由硬件自动控制,还是软件手动控制。
取值:
SPI_NSS_Soft:软件控制 NSS(推荐!)✅
 你需要自己用 GPIO 拉低/拉高 CS 引脚
SPI_NSS_Hard:硬件自动控制 NSSSTM32  NSS 引脚自动管理)


uint16_t SPI_BaudRatePrescaler;
作用:设置 SCK 时钟分频系数,决定通信速度。
取值(分频因子):
SPI_BaudRatePrescaler_2  f<sub>PCLK</sub> / 2
SPI_BaudRatePrescaler_4  f<sub>PCLK</sub> / 4
SPI_BaudRatePrescaler_8
SPI_BaudRatePrescaler_16
SPI_BaudRatePrescaler_32
SPI_BaudRatePrescaler_64
SPI_BaudRatePrescaler_128
SPI_BaudRatePrescaler_256
 举例: 
STM32F103  APB2 时钟(PCLK2= 72 MHz
若选 SPI_BaudRatePrescaler_8  SCK = 72 / 8 = 9 MHz
W25Q64 最高支持 104 MHz,但 STM32F1 一般不超过 18 MHz(选 /4  /8 安全)
⚠️ 注意:
SPI1 挂在 APB2SPI2/3 挂在 APB1,时钟源不同! 


uint16_t SPI_FirstBit;
作用:设置数据发送时 MSB(高位)先发,还是 LSB(低位)先发。
取值:
SPI_FirstBit_MSB:高位先发(标准 SPI,绝大多数设备)✅
SPI_FirstBit_LSB:低位先发(少数设备,如某些 ADC


uint16_t SPI_CRCPolynomial;
作用:设置 CRC 校验的多项式(用于硬件 CRC 计算)。
说明:
如果你 不使用 CRC 校验(99% 的应用都不用),这个值可以随便设(如 7
要启用 CRC,还需调用 SPI_CalculateCRC(SPIx, ENABLE);
一般项目可忽略此字段

硬件

  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
#ifndef __W25Q64_H
#define __W25Q64_H

#include "stm32f10x.h"

// =============== 【用户可配置区域】 ===============
#define W25Q64_SPI              SPI1
#define W25Q64_SPI_CLK          RCC_APB2Periph_SPI1

#define W25Q64_SPI_SCK_PORT     GPIOA
#define W25Q64_SPI_SCK_PIN      GPIO_Pin_5
#define W25Q64_SPI_MISO_PORT    GPIOA
#define W25Q64_SPI_MISO_PIN     GPIO_Pin_6
#define W25Q64_SPI_MOSI_PORT    GPIOA
#define W25Q64_SPI_MOSI_PIN     GPIO_Pin_7

#define W25Q64_CS_PORT          GPIOA
#define W25Q64_CS_PIN           GPIO_Pin_4
#define W25Q64_CS_CLK           RCC_APB2Periph_GPIOA
// ==============================================

// 片选宏
#define W25Q64_CS_HIGH()        GPIO_SetBits(W25Q64_CS_PORT, W25Q64_CS_PIN)
#define W25Q64_CS_LOW()         GPIO_ResetBits(W25Q64_CS_PORT, W25Q64_CS_PIN)

// W25Q64 指令定义
#define W25Q64_CMD_READ_DATA        0x03
#define W25Q64_CMD_READ_STATUS1     0x05
#define W25Q64_CMD_WRITE_ENABLE     0x06
#define W25Q64_CMD_READ_ID          0x90
#define W25Q64_CMD_READ_JEDEC_ID    0x9F

// 函数声明
void W25Q64_Init(void);
uint8_t W25Q64_ReadStatus(void);
void W25Q64_WaitBusy(void);
uint32_t W25Q64_ReadID(void);
uint32_t W25Q64_ReadJEDECID(void);
void W25Q64_ReadData(uint32_t addr, uint8_t* buf, uint32_t len);

// 底层 SPI 读写
uint8_t W25Q64_SPI_ReadWriteByte(uint8_t byte);

#endif

#include "w25q64.h"

// SPI 初始化(硬件 SPI)
void W25Q64_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    SPI_InitTypeDef SPI_InitStruct;

    // 使能时钟
    RCC_APB2PeriphClockCmd(
        RCC_APB2Periph_GPIOA | 
        W25Q64_CS_CLK | 
        W25Q64_SPI_CLK, ENABLE);

    // 配置 CS 为推挽输出
    GPIO_InitStruct.GPIO_Pin = W25Q64_CS_PIN;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(W25Q64_CS_PORT, &GPIO_InitStruct);
    W25Q64_CS_HIGH(); // 默认不选中

    // 配置 SPI 引脚:SCK, MISO, MOSI
    GPIO_InitStruct.GPIO_Pin = W25Q64_SPI_SCK_PIN | W25Q64_SPI_MOSI_PIN;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(W25Q64_SPI_SCK_PORT, &GPIO_InitStruct);

    GPIO_InitStruct.GPIO_Pin = W25Q64_SPI_MISO_PIN;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 浮空输入
    GPIO_Init(W25Q64_SPI_MISO_PORT, &GPIO_InitStruct);

    // 配置 SPI1
    SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
    SPI_InitStruct.SPI_Mode = SPI_Mode_Master;
    SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b;
    SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low;   // 时钟空闲低
    SPI_InitStruct.SPI_CPHA = SPI_CPHA_1Edge; // 第一个边沿采样
    SPI_InitStruct.SPI_NSS = SPI_NSS_Soft;    // 软件控制 NSS
    SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4; // 例如 72MHz/4 = 18MHz(W25Q64 最高 104MHz,安全)
    SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;
    SPI_InitStruct.SPI_CRCPolynomial = 7;

    SPI_Init(W25Q64_SPI, &SPI_InitStruct);
    SPI_Cmd(W25Q64_SPI, ENABLE);
}

// SPI 发送/接收一个字节
uint8_t W25Q64_SPI_ReadWriteByte(uint8_t byte)
{
    while (SPI_I2S_GetFlagStatus(W25Q64_SPI, SPI_I2S_FLAG_TXE) == RESET);
    SPI_I2S_SendData(W25Q64_SPI, byte);

    while (SPI_I2S_GetFlagStatus(W25Q64_SPI, SPI_I2S_FLAG_RXNE) == RESET);
    return SPI_I2S_ReceiveData(W25Q64_SPI);
}

// 读取状态寄存器1
uint8_t W25Q64_ReadStatus(void)
{
    uint8_t status;
    W25Q64_CS_LOW();
    W25Q64_SPI_ReadWriteByte(W25Q64_CMD_READ_STATUS1);
    status = W25Q64_SPI_ReadWriteByte(0xFF);
    W25Q64_CS_HIGH();
    return status;
}

// 等待 Flash 不忙(BUSY 位 = 0)
void W25Q64_WaitBusy(void)
{
    while ((W25Q64_ReadStatus() & 0x01) == 0x01); // BIT0 = BUSY
}

// 读取 Manufacturer/Device ID(使用 0x90 指令)
uint32_t W25Q64_ReadID(void)
{
    uint32_t id = 0;
    W25Q64_CS_LOW();
    W25Q64_SPI_ReadWriteByte(W25Q64_CMD_READ_ID);
    W25Q64_SPI_ReadWriteByte(0x00); // Dummy
    W25Q64_SPI_ReadWriteByte(0x00); // Dummy
    W25Q64_SPI_ReadWriteByte(0x00); // Dummy
    id = ((uint32_t)W25Q64_SPI_ReadWriteByte(0xFF) << 8) |
          (uint32_t)W25Q64_SPI_ReadWriteByte(0xFF);
    W25Q64_CS_HIGH();
    return id;
}

// 读取 JEDEC ID(更标准,使用 0x9F)
uint32_t W25Q64_ReadJEDECID(void)
{
    uint32_t id = 0;
    W25Q64_CS_LOW();
    W25Q64_SPI_ReadWriteByte(W25Q64_CMD_READ_JEDEC_ID);
    id = ((uint32_t)W25Q64_SPI_ReadWriteByte(0xFF) << 16) |
         ((uint32_t)W25Q64_SPI_ReadWriteByte(0xFF) << 8) |
          (uint32_t)W25Q64_SPI_ReadWriteByte(0xFF);
    W25Q64_CS_HIGH();
    return id;
}

// 从指定地址读取数据
void W25Q64_ReadData(uint32_t addr, uint8_t* buf, uint32_t len)
{
    uint32_t i;
    W25Q64_CS_LOW();
    W25Q64_SPI_ReadWriteByte(W25Q64_CMD_READ_DATA);
    W25Q64_SPI_ReadWriteByte((uint8_t)(addr >> 16)); // 地址高8位
    W25Q64_SPI_ReadWriteByte((uint8_t)(addr >> 8));  // 中8位
    W25Q64_SPI_ReadWriteByte((uint8_t)addr);         // 低8位

    for (i = 0; i < len; i++)
    {
        buf[i] = W25Q64_SPI_ReadWriteByte(0xFF);
    }
    W25Q64_CS_HIGH();
}

#include "stm32f10x.h"
#include "w25q64.h"

void Delay_ms(uint32_t ms);

int main(void)
{
    uint32_t jedec_id = 0;
    uint8_t read_buf[16] = {0};

    // 初始化 SPI 和 W25Q64
    W25Q64_Init();

    // 读取 JEDEC ID(W25Q64 应为 0xEF4017)
    jedec_id = W25Q64_ReadJEDECID();

    // 从地址 0x00 读取 16 字节数据(出厂默认为 0xFF)
    W25Q64_ReadData(0x00, read_buf, 16);

    // 此时 read_buf 全为 0xFF(未写入时)

    while (1)
    {
        // 可通过串口打印 jedec_id 和 read_buf 调试
        // 例如:jedec_id == 0xEF4017 表示 W25Q64
    }
}

void Delay_ms(uint32_t ms)
{
    uint32_t i, j;
    for (i = 0; i < ms; i++)
        for (j = 0; j < 8000; j++);
}

软件

  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
#ifndef __SOFT_SPI_H
#define __SOFT_SPI_H

#include "stm32f10x.h"

// =============== 【用户可配置区域】 ===============
#define SOFT_SPI_CLK_PORT       GPIOA
#define SOFT_SPI_CLK_PIN        GPIO_Pin_5

#define SOFT_SPI_MOSI_PORT      GPIOA
#define SOFT_SPI_MOSI_PIN       GPIO_Pin_7

#define SOFT_SPI_MISO_PORT      GPIOA
#define SOFT_SPI_MISO_PIN       GPIO_Pin_6

#define SOFT_SPI_CS_PORT        GPIOA
#define SOFT_SPI_CS_PIN         GPIO_Pin_4

#define SOFT_SPI_GPIO_CLK       (RCC_APB2Periph_GPIOA)
// ==============================================

// 时钟使能
#define SOFT_SPI_GPIO_CLK_ENABLE() \
    RCC_APB2PeriphClockCmd(SOFT_SPI_GPIO_CLK, ENABLE)

// 引脚控制宏
#define SPI_CLK_HIGH()          GPIO_SetBits(SOFT_SPI_CLK_PORT, SOFT_SPI_CLK_PIN)
#define SPI_CLK_LOW()           GPIO_ResetBits(SOFT_SPI_CLK_PORT, SOFT_SPI_CLK_PIN)
#define SPI_MOSI_HIGH()         GPIO_SetBits(SOFT_SPI_MOSI_PORT, SOFT_SPI_MOSI_PIN)
#define SPI_MOSI_LOW()          GPIO_ResetBits(SOFT_SPI_MOSI_PORT, SOFT_SPI_MOSI_PIN)
#define SPI_CS_HIGH()           GPIO_SetBits(SOFT_SPI_CS_PORT, SOFT_SPI_CS_PIN)
#define SPI_CS_LOW()            GPIO_ResetBits(SOFT_SPI_CS_PORT, SOFT_SPI_CS_PIN)

// 读取 MISO(需先设为输入)
#define SPI_MISO_READ()         GPIO_ReadInputDataBit(SOFT_SPI_MISO_PORT, SOFT_SPI_MISO_PIN)

// 延时(单位:微秒)
#define SOFT_SPI_DELAY_US(n)    soft_spi_delay_us(n)

// 函数声明
void soft_spi_init(void);
uint8_t soft_spi_read_write_byte(uint8_t byte);
void soft_spi_delay_us(uint32_t us);
void soft_spi_miso_input(void);
void soft_spi_mosi_output(void);

#endif

#include "soft_spi.h"

// 微秒延时(假设系统时钟 8MHz,若为 72MHz 需调整)
void soft_spi_delay_us(uint32_t us)
{
    uint32_t i;
    for (i = 0; i < us * 2; i++); // 粗略校准
}

// 初始化 GPIO(推挽输出 + 浮空输入)
void soft_spi_init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;

    SOFT_SPI_GPIO_CLK_ENABLE();

    // CLK 配置为推挽输出
    GPIO_InitStruct.GPIO_Pin = SOFT_SPI_CLK_PIN;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(SOFT_SPI_CLK_PORT, &GPIO_InitStruct);

    // MOSI 配置为推挽输出
    GPIO_InitStruct.GPIO_Pin = SOFT_SPI_MOSI_PIN;
    GPIO_Init(SOFT_SPI_MOSI_PORT, &GPIO_InitStruct);

    // MISO 配置为浮空输入
    GPIO_InitStruct.GPIO_Pin = SOFT_SPI_MISO_PIN;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(SOFT_SPI_MISO_PORT, &GPIO_InitStruct);

    // CS 配置为推挽输出
    GPIO_InitStruct.GPIO_Pin = SOFT_SPI_CS_PIN;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(SOFT_SPI_CS_PORT, &GPIO_InitStruct);

    // 初始状态:CLK=LOW, CS=HIGH(未选中)
    SPI_CLK_LOW();
    SPI_CS_HIGH();
}

// 软件 SPI 读写一个字节(MSB first,CPOL=0, CPHA=0)
uint8_t soft_spi_read_write_byte(uint8_t byte)
{
    uint8_t i;
    uint8_t read_byte = 0;

    for (i = 0; i < 8; i++)
    {
        // MOSI 设置数据(在 CLK 上升沿前)
        if (byte & 0x80)
            SPI_MOSI_HIGH();
        else
            SPI_MOSI_LOW();

        SOFT_SPI_DELAY_US(1);

        // CLK 上升沿(采样由从机在上升沿完成)
        SPI_CLK_HIGH();
        SOFT_SPI_DELAY_US(1);

        // 读取 MISO(在 CLK 高电平时)
        read_byte <<= 1;
        if (SPI_MISO_READ())
            read_byte |= 0x01;

        // CLK 下降沿
        SPI_CLK_LOW();
        SOFT_SPI_DELAY_US(1);

        byte <<= 1;
    }

    return read_byte;
}


#ifndef __W25Q64_SOFT_H
#define __W25Q64_SOFT_H

#include "soft_spi.h"

// 指令定义
#define W25Q64_CMD_READ_DATA        0x03
#define W25Q64_CMD_READ_STATUS1     0x05
#define W25Q64_CMD_WRITE_ENABLE     0x06
#define W25Q64_CMD_READ_JEDEC_ID    0x9F

// 函数声明
void W25Q64_Soft_Init(void);
uint8_t W25Q64_Soft_ReadStatus(void);
void W25Q64_Soft_WaitBusy(void);
uint32_t W25Q64_Soft_ReadJEDECID(void);
void W25Q64_Soft_ReadData(uint32_t addr, uint8_t* buf, uint32_t len);

#endif

#include "w25q64_soft.h"

void W25Q64_Soft_Init(void)
{
    soft_spi_init();
}

uint8_t W25Q64_Soft_ReadStatus(void)
{
    uint8_t status;
    SPI_CS_LOW();
    soft_spi_read_write_byte(W25Q64_CMD_READ_STATUS1);
    status = soft_spi_read_write_byte(0xFF);
    SPI_CS_HIGH();
    return status;
}

void W25Q64_Soft_WaitBusy(void)
{
    while ((W25Q64_Soft_ReadStatus() & 0x01) == 0x01);
}

uint32_t W25Q64_Soft_ReadJEDECID(void)
{
    uint32_t id = 0;
    SPI_CS_LOW();
    soft_spi_read_write_byte(W25Q64_CMD_READ_JEDEC_ID);
    id = ((uint32_t)soft_spi_read_write_byte(0xFF) << 16) |
         ((uint32_t)soft_spi_read_write_byte(0xFF) << 8) |
          (uint32_t)soft_spi_read_write_byte(0xFF);
    SPI_CS_HIGH();
    return id;
}

void W25Q64_Soft_ReadData(uint32_t addr, uint8_t* buf, uint32_t len)
{
    uint32_t i;
    SPI_CS_LOW();
    soft_spi_read_write_byte(W25Q64_CMD_READ_DATA);
    soft_spi_read_write_byte((uint8_t)(addr >> 16));
    soft_spi_read_write_byte((uint8_t)(addr >> 8));
    soft_spi_read_write_byte((uint8_t)addr);

    for (i = 0; i < len; i++)
    {
        buf[i] = soft_spi_read_write_byte(0xFF);
    }
    SPI_CS_HIGH();
}

#include "stm32f10x.h"
#include "w25q64_soft.h"

void Delay_ms(uint32_t ms);

int main(void)
{
    uint32_t jedec_id = 0;
    uint8_t read_buf[16] = {0};

    // 初始化软件 SPI 和 W25Q64
    W25Q64_Soft_Init();

    // 读取 JEDEC ID
    jedec_id = W25Q64_Soft_ReadJEDECID(); // 应为 0xEF4017

    // 读取前 16 字节(默认为 0xFF)
    W25Q64_Soft_ReadData(0x00, read_buf, 16);

    while (1)
    {
        // 可通过 LED 或串口调试
    }
}

void Delay_ms(uint32_t ms)
{
    uint32_t i, j;
    for (i = 0; i < ms; i++)
        for (j = 0; j < 8000; j++);
}

串行FLASH文件系统FatFs

goals:
设备状态获取(disk_status)、设备初始化(disk_initialize)、扇区读取(disk_read)、 扇区写入(disk_write)、其他控制(disk_ioctl)。

  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
/* 为每个设备定义一个物理编号 */
#define ATA         0     // 预留SD卡使用
#define SPI_FLASH   1     // 外部SPI Flash


DSTATUS disk_status (
    BYTE pdrv   /* 物理编号 */
)
{

    DSTATUS status = STA_NOINIT;

    switch (pdrv) {
    case ATA: /* SD CARD */
        break;

    case SPI_FLASH:
        /* SPI Flash状态检测:读取SPI Flash 设备ID */
        if (sFLASH_ID == SPI_FLASH_ReadID()) {
            /* 设备ID读取结果正确 */
            status &= ~STA_NOINIT;
        } else {
            /* 设备ID读取结果错误 */
            status = STA_NOINIT;;
        }
        break;

    default:
        status = STA_NOINIT;
    }
    return status;
}

DSTATUS disk_initialize (
    BYTE pdrv       /* 物理编号 */
)
{
    uint16_t i;
    DSTATUS status = STA_NOINIT;
    switch (pdrv) {
    case ATA:          /* SD CARD */
        break;

    case SPI_FLASH:    /* SPI Flash */
        /* 初始化SPI Flash */
        SPI_FLASH_Init();
        /* 延时一小段时间 */
        i=500;
        while (--i);
        /* 唤醒SPI Flash */
        SPI_Flash_WAKEUP();
        /* 获取SPI Flash芯片状态 */
        status=disk_status(SPI_FLASH);
        break;

    default:
        status = STA_NOINIT;
    }
    return status;
}

DRESULT disk_read (
    BYTE pdrv,    /* 设备物理编号(0..) */
    BYTE *buff,   /* 数据缓存区 */
    DWORD sector, /* 扇区首地址 */
    UINT count    /* 扇区个数(1..128) */
)
{
    DRESULT status = RES_PARERR;
    switch (pdrv) {
    case ATA: /* SD CARD */
        break;

    case SPI_FLASH:
        /* 扇区偏移约2MB,外部Flash文件系统空间放在SPI Flash后面约6MB空间 */
        sector+=514;
        SPI_FLASH_BufferRead(buff, sector <<12, count<<12);
        status = RES_OK;
        break;

    default:
        status = RES_PARERR;
    }
    return status;
}

DRESULT disk_write (
    BYTE pdrv,        /* 设备物理编号(0..) */
    const BYTE *buff, /* 欲写入数据的缓存区 */
    DWORD sector,     /* 扇区首地址 */
    UINT count        /* 扇区个数(1..128) */
)
{
    uint32_t write_addr;
    DRESULT status = RES_PARERR;
    if (!count) {
        return RES_PARERR;    /* Check parameter */
    }

    switch (pdrv) {
    case ATA: /* SD CARD */
        break;

    case SPI_FLASH:
        /* 扇区偏移约2MB,外部Flash文件系统空间放在SPI Flash后面约6MB空间 */
        sector+=514;
        write_addr = sector<<12;
        SPI_FLASH_SectorErase(write_addr);
        SPI_FLASH_BufferWrite((u8 *)buff,write_addr,count<<12);
        status = RES_OK;
        break;

    default:
        status = RES_PARERR;
    }
    return status;
}

DRESULT disk_ioctl (
    BYTE pdrv,    /* 物理编号 */
    BYTE cmd,     /* 控制指令 */
    void *buff    /* 写入或者读取数据地址指针 */
)
{
    DRESULT status = RES_PARERR;
    switch (pdrv) {
    case ATA: /* SD CARD */
        break;

    case SPI_FLASH:
        switch (cmd) {
        /* 扇区数量:1534*4096/1024/1024=约6(MB) */
        case GET_SECTOR_COUNT:
            *(DWORD * )buff = 1534;
            break;
        /* 扇区大小  */
        case GET_SECTOR_SIZE :
            *(WORD * )buff = 4096;
            break;
        /* 同时擦除扇区个数 */
        case GET_BLOCK_SIZE :
            *(DWORD * )buff = 1;
            break;
        }
        status = RES_OK;
        break;

    default:
        status = RES_PARERR;
    }
    return status;
}

__weak DWORD get_fattime(void)
{
    /* 返回当前时间戳 */
    return    ((DWORD)(2015 - 1980) << 25)  /* Year 2015 */
            | ((DWORD)1 << 21)        /* Month 1 */
            | ((DWORD)1 << 16)        /* Mday 1 */
            | ((DWORD)0 << 11)        /* Hour 0 */
            | ((DWORD)0 << 5)         /* Min 0 */
            | ((DWORD)0 >> 1);        /* Sec 0 */
}

裁剪

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#define _USE_MKFS   1
#define _CODE_PAGE  936
#define _USE_LFN    2
#define _VOLUMES    2
#define _MIN_SS     512
#define _MAX_SS     4096
1) _USE_MKFS 格式化功能选择,为使用FatFs格式化功能,需要把它设置为1

2) _CODE_PAGE 语言功能选择,并要求把相关语言文件添加到工程宏。为支持简体中文文件名需要使用“936”, 正如在图 添加FatFS文件到工程 的操作,我们已经把cc936.c文件添加到工程中

3) _USE_LFN 长文件名支持,默认不支持长文件名,这里配置为2,支持长文件名,并指定使用栈空间为缓冲区。

4) _VOLUMES 指定物理设备数量,这里设置为2,包括预留SD卡和SPI Flash芯片

5) _MIN_SS _MAX_SS 指定扇区大小的最小值和最大值。SD卡扇区大小一般都为512字节SPI Flash芯片扇区大小一般设置为4096字节,所以需要把_MAX_SS改为4096

test

  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
#include "stm32f10x.h"
#include "ff.h"
#include <string.h>
#include <stdio.h>

// 简单延时
void Delay_ms(uint32_t ms)
{
    uint32_t i, j;
    for (i = 0; i < ms; i++)
        for (j = 0; j < 8000; j++);
}

// 错误处理宏(可替换为串口打印)
#define CHECK_RES(res) do { if (res != FR_OK) while(1) { Delay_ms(200); } } while(0)

int main(void)
{
    FATFS fs;
    FRESULT res;
    DWORD free_clusters, total_clusters;
    FATFS *pfs;
    UINT bytes_written, bytes_read;
    char buffer[128];

    // 1. 挂载文件系统
    res = f_mount(&fs, "", 1);
    if (res != FR_OK)
    {
        res = f_mkfs("", FM_FAT | FM_SFD, 0, NULL, 0);
        CHECK_RES(res);
        res = f_mount(&fs, "", 1);
        CHECK_RES(res);
    }

    // 2. 获取存储空间信息
    res = f_getfree("", &free_clusters, &pfs);
    CHECK_RES(res);
    total_clusters = (pfs->n_fatent - 2); // 总数据簇数
    DWORD free_sectors = free_clusters * pfs->csize;
    DWORD total_sectors = total_clusters * pfs->csize;
    DWORD sector_size = pfs->ssize;

    // 计算容量(单位:KB)
    DWORD total_kb = total_sectors * sector_size / 1024;
    DWORD free_kb = free_sectors * sector_size / 1024;

    // 此处可串口打印:printf("Total: %lu KB, Free: %lu KB\r\n", total_kb, free_kb);

    // 3. 创建目录
    res = f_mkdir("/DATA");
    CHECK_RES(res);

    // 4. 在目录中创建文件并写入
    FIL fil;
    res = f_open(&fil, "/DATA/log.txt", FA_CREATE_ALWAYS | FA_WRITE);
    CHECK_RES(res);
    const char* header = "=== System Log ===\r\n";
    const char* data1 = "Event 1: Boot success\r\n";
    const char* data2 = "Event 2: SPI Flash OK\r\n";
    f_write(&fil, header, strlen(header), &bytes_written);
    f_write(&fil, data1, strlen(data1), &bytes_written);
    f_write(&fil, data2, strlen(data2), &bytes_written);
    f_close(&fil);

    // 5. 文件指针定位:跳到文件开头,追加新内容
    res = f_open(&fil, "/DATA/log.txt", FA_WRITE | FA_OPEN_EXISTING);
    CHECK_RES(res);
    f_lseek(&fil, f_size(&fil)); // 移动到文件末尾
    const char* append = "Event 3: FatFs test running...\r\n";
    f_write(&fil, append, strlen(append), &bytes_written);
    f_close(&fil);

    // 6. 重命名(也是移动):将文件移到根目录并改名
    res = f_rename("/DATA/log.txt", "/system_log.txt");
    CHECK_RES(res);

    // 7. 获取文件信息
    FILINFO fno;
    res = f_stat("/system_log.txt", &fno);
    CHECK_RES(res);
    // fno.fsize: 文件大小
    // fno.fdate, fno.ftime: 日期时间(若启用时间戳)
    // fno.fname: 文件名(但 f_stat 不填充 fname,需自己知道路径)

    // 8. 读取文件验证内容
    res = f_open(&fil, "/system_log.txt", FA_READ);
    CHECK_RES(res);
    f_read(&fil, buffer, sizeof(buffer) - 1, &bytes_read);
    buffer[bytes_read] = '\0';
    f_close(&fil);

    // 可选:检查是否包含关键字符串
    if (strstr(buffer, "Event 3") == NULL)
    {
        while (1) { Delay_ms(300); } // 内容不完整
    }

    // 9. 【可选】删除目录(需先确保目录为空)
    // res = f_unlink("/DATA"); // 删除空目录
    // CHECK_RES(res);

    // 10. 测试成功!
    while (1)
    {
        Delay_ms(1000); // 慢闪表示成功
    }
}

液晶屏(IL9341)

驱动

  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
237
238
239
#ifndef __ILI9341_SPI_H
#define __ILI9341_SPI_H

#include <stdint.h>

// =============== 【用户配置区】===============
// 修改此处以匹配你的硬件

// SPI 读写函数(由用户实现)
extern uint8_t SPI_ReadWriteByte(uint8_t byte);

// GPIO 控制宏(由用户实现)
#define ILI9341_DC_HIGH()   do { GPIO_SetBits(GPIOA, GPIO_Pin_1); } while(0)
#define ILI9341_DC_LOW()    do { GPIO_ResetBits(GPIOA, GPIO_Pin_1); } while(0)
#define ILI9341_RST_HIGH()  do { GPIO_SetBits(GPIOA, GPIO_Pin_2); } while(0)
#define ILI9341_RST_LOW()   do { GPIO_ResetBits(GPIOA, GPIO_Pin_2); } while(0)

// 屏幕方向(0=竖屏, 1=横屏)
#define ILI9341_USE_HORIZONTAL  1

#if ILI9341_USE_HORIZONTAL
    #define ILI9341_WIDTH   320
    #define ILI9341_HEIGHT  240
#else
    #define ILI9341_WIDTH   240
    #define ILI9341_HEIGHT  320
#endif

// =============== 【函数声明】===============
void ILI9341_Init(void);
void ILI9341_SetWindow(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2);
void ILI9341_WritePixel(uint16_t x, uint16_t y, uint16_t color);
void ILI9341_WriteRAM_Prepare(void);
void ILI9341_WriteRAM(uint16_t color);

#endif


#include "ili9341_spi.h"
#include "stm32f10x.h"

// SPI 片选(假设 CS 接 PA4,由 SPI 外设自动控制)
// 若 CS 为 GPIO,请在此处添加 CS 控制
#define ILI9341_CS_LOW()    do { GPIO_ResetBits(GPIOA, GPIO_Pin_4); } while(0)
#define ILI9341_CS_HIGH()   do { GPIO_SetBits(GPIOA, GPIO_Pin_4); } while(0)

// 内部函数声明
static void ILI9341_WriteCmd(uint8_t cmd);
static void ILI9341_WriteData(uint8_t data);
static void ILI9341_WriteData16(uint16_t data);

// 写命令
static void ILI9341_WriteCmd(uint8_t cmd)
{
    ILI9341_CS_LOW();
    ILI9341_DC_LOW();   // RS = 0 → 命令
    SPI_ReadWriteByte(cmd);
    ILI9341_CS_HIGH();
}

// 写 8 位数据
static void ILI9341_WriteData(uint8_t data)
{
    ILI9341_CS_LOW();
    ILI9341_DC_HIGH();  // RS = 1 → 数据
    SPI_ReadWriteByte(data);
    ILI9341_CS_HIGH();
}

// 写 16 位数据(RGB565)
static void ILI9341_WriteData16(uint16_t data)
{
    ILI9341_CS_LOW();
    ILI9341_DC_HIGH();
    SPI_ReadWriteByte(data >> 8);     // 先发高8位
    SPI_ReadWriteByte(data & 0xFF);   // 再发低8位
    ILI9341_CS_HIGH();
}

// 初始化 ILI9341
void ILI9341_Init(void)
{
    // 硬件复位
    ILI9341_RST_LOW();
    for (volatile int i = 0; i < 10000; i++);
    ILI9341_RST_HIGH();
    for (volatile int i = 0; i < 10000; i++);

    // 软件初始化序列(来自官方例程)
    ILI9341_WriteCmd(0x01); // SW reset
    for (volatile int i = 0; i < 10000; i++);

    ILI9341_WriteCmd(0x28); // Display off

    ILI9341_WriteCmd(0xCF);
    ILI9341_WriteData(0x00);
    ILI9341_WriteData(0x83);
    ILI9341_WriteData(0X30);

    ILI9341_WriteCmd(0xED);
    ILI9341_WriteData(0x64);
    ILI9341_WriteData(0x03);
    ILI9341_WriteData(0X12);
    ILI9341_WriteData(0X81);

    ILI9341_WriteCmd(0xE8);
    ILI9341_WriteData(0x85);
    ILI9341_WriteData(0x01);
    ILI9341_WriteData(0x79);

    ILI9341_WriteCmd(0xCB);
    ILI9341_WriteData(0x39);
    ILI9341_WriteData(0x2C);
    ILI9341_WriteData(0x00);
    ILI9341_WriteData(0x34);
    ILI9341_WriteData(0x02);

    ILI9341_WriteCmd(0xF7);
    ILI9341_WriteData(0x20);

    ILI9341_WriteCmd(0xEA);
    ILI9341_WriteData(0x00);
    ILI9341_WriteData(0x00);

    ILI9341_WriteCmd(0xC0); // Power control
    ILI9341_WriteData(0x26); // VRH[5:0]

    ILI9341_WriteCmd(0xC1); // Power control
    ILI9341_WriteData(0x11); // SAP[2:0];BT[3:0]

    ILI9341_WriteCmd(0xC5); // VCM control
    ILI9341_WriteData(0x35); // 3.8V
    ILI9341_WriteData(0x3E); // 3.8V

    ILI9341_WriteCmd(0xC7); // VCM control2
    ILI9341_WriteData(0xBE);

    ILI9341_WriteCmd(0x36); // Memory Access Control
#if ILI9341_USE_HORIZONTAL
    ILI9341_WriteData(0x28); // MX, MY, RGB mode (横屏)
#else
    ILI9341_WriteData(0x08); // 竖屏
#endif

    ILI9341_WriteCmd(0x3A);
    ILI9341_WriteData(0x55); // 16-bit pixel format

    ILI9341_WriteCmd(0xB1);
    ILI9341_WriteData(0x00);
    ILI9341_WriteData(0x1B);

    ILI9341_WriteCmd(0xF2);
    ILI9341_WriteData(0x08);

    ILI9341_WriteCmd(0x26);
    ILI9341_WriteData(0x01);

    ILI9341_WriteCmd(0xE0); // Gamma curve
    ILI9341_WriteData(0x1F);
    ILI9341_WriteData(0x1A);
    ILI9341_WriteData(0x18);
    ILI9341_WriteData(0x0A);
    ILI9341_WriteData(0x0F);
    ILI9341_WriteData(0x06);
    ILI9341_WriteData(0x45);
    ILI9341_WriteData(0X87);
    ILI9341_WriteData(0x32);
    ILI9341_WriteData(0x0A);
    ILI9341_WriteData(0x07);
    ILI9341_WriteData(0x02);
    ILI9341_WriteData(0x07);
    ILI9341_WriteData(0x05);
    ILI9341_WriteData(0x00);

    ILI9341_WriteCmd(0XE1);
    ILI9341_WriteData(0x00);
    ILI9341_WriteData(0x25);
    ILI9341_WriteData(0x27);
    ILI9341_WriteData(0x05);
    ILI9341_WriteData(0x10);
    ILI9341_WriteData(0x09);
    ILI9341_WriteData(0x3A);
    ILI9341_WriteData(0x78);
    ILI9341_WriteData(0x4D);
    ILI9341_WriteData(0x05);
    ILI9341_WriteData(0x18);
    ILI9341_WriteData(0x0D);
    ILI9341_WriteData(0x38);
    ILI9341_WriteData(0x3A);
    ILI9341_WriteData(0x1F);

    ILI9341_WriteCmd(0x29); // Display on
    ILI9341_WriteCmd(0x2C); // Memory write
}

// 设置显示窗口(用于局部刷新)
void ILI9341_SetWindow(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2)
{
    // 列地址设置
    ILI9341_WriteCmd(0x2A);
    ILI9341_WriteData16(x1);
    ILI9341_WriteData16(x2);

    // 行地址设置
    ILI9341_WriteCmd(0x2B);
    ILI9341_WriteData16(y1);
    ILI9341_WriteData16(y2);

    // 准备写显存
    ILI9341_WriteCmd(0x2C);
}

// 画单个像素(LVGL 可能用到,但效率低)
void ILI9341_WritePixel(uint16_t x, uint16_t y, uint16_t color)
{
    if (x >= ILI9341_WIDTH || y >= ILI9341_HEIGHT) return;
    ILI9341_SetWindow(x, y, x, y);
    ILI9341_WriteRAM(color);
}

// 准备写显存(用于 LVGL flush)
void ILI9341_WriteRAM_Prepare(void)
{
    ILI9341_CS_LOW();
    ILI9341_DC_HIGH();
}

// 写一个像素到显存(需先调用 Prepare)
void ILI9341_WriteRAM(uint16_t color)
{
    SPI_ReadWriteByte(color >> 8);
    SPI_ReadWriteByte(color & 0xFF);
}

// 结束写显存(LVGL flush 结尾调用)
void ILI9341_WriteRAM_Finish(void)
{
    ILI9341_CS_HIGH();
}

触摸驱动(xpt2046)

  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
#ifndef __XPT2046_H
#define __XPT2046_H

#include <stdint.h>

// =============== 【用户配置区】===============
// 修改此处以匹配你的硬件

// SPI 读写函数(由用户实现)
extern uint8_t SPI_ReadWriteByte(uint8_t byte);

// CS 控制(XPT2046 片选)
#define XPT2046_CS_HIGH()   do { GPIO_SetBits(GPIOA, GPIO_Pin_3); } while(0)
#define XPT2046_CS_LOW()    do { GPIO_ResetBits(GPIOA, GPIO_Pin_3); } while(0)

// PENIRQ 引脚(触摸中断,可选)
// 若不用,定义为 1(始终返回未触摸)
#define XPT2046_PENIRQ()    (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == 0) // 低电平有效

// 屏幕分辨率(必须与 ILI9341 一致)
#define XPT2046_HOR_RES     320
#define XPT2046_VER_RES     240

// =============== 【类型定义】===============
typedef struct {
    int16_t x;
    int16_t y;
} xpt2046_point_t;

// =============== 【函数声明】===============
void XPT2046_Init(void);
uint8_t XPT2046_ReadXYZ(int16_t *x, int16_t *y, int16_t *z);
void XPT2046_Calibrate(int16_t x0, int16_t y0, int16_t x1, int16_t y1,
                       int16_t x2, int16_t y2, int16_t x3, int16_t y3);
void XPT2046_GetCalibratedPoint(int16_t raw_x, int16_t raw_y, int16_t *cal_x, int16_t *cal_y);

#endif


#include "xpt2046.h"
#include "stm32f10x.h"

// 校准参数(默认值,需通过校准更新)
static int32_t xpt2046_cal_x0 = 320, xpt2046_cal_x1 = 0;
static int32_t xpt2046_cal_y0 = 240, xpt2046_cal_y1 = 0;

// 读取 12 位 ADC 值
static uint16_t XPT2046_ReadAdc(uint8_t cmd)
{
    uint16_t data = 0;
    XPT2046_CS_LOW();
    SPI_ReadWriteByte(cmd);
    data = SPI_ReadWriteByte(0x00) << 8;
    data |= SPI_ReadWriteByte(0x00);
    XPT2046_CS_HIGH();
    return (data >> 3); // 12 位有效(右移 3 位)
}

// 读取 X, Y, Z(Z 用于判断是否按下)
uint8_t XPT2046_ReadXYZ(int16_t *x, int16_t *y, int16_t *z)
{
    static int16_t last_x = 0, last_y = 0;
    uint16_t x_raw, y_raw, z1, z2;

    // 读取 Y 坐标
    y_raw = XPT2046_ReadAdc(0x90); // Y 命令(差分)
    if (y_raw >= 4095) return 0;   // 无效值

    // 读取 X 坐标
    x_raw = XPT2046_ReadAdc(0xD0); // X 命令(差分)
    if (x_raw >= 4095) return 0;

    // 读取 Z(压力)
    z1 = XPT2046_ReadAdc(0xB0); // Y+
    z2 = XPT2046_ReadAdc(0xC0); // X+
    *z = (int16_t)(z1 - z2);

    // 简单去抖:变化太小视为无效
    if (abs(x_raw - last_x) < 50 && abs(y_raw - last_y) < 50) {
        *x = last_x;
        *y = last_y;
        return 1;
    }

    last_x = x_raw;
    last_y = y_raw;
    *x = x_raw;
    *y = y_raw;
    return 1;
}

// 初始化(可空)
void XPT2046_Init(void)
{
    // 可选:配置 PENIRQ 为输入
    // RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    // GPIO_InitTypeDef gpio;
    // gpio.GPIO_Pin = GPIO_Pin_0;
    // gpio.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    // GPIO_Init(GPIOA, &gpio);
}

// 4 点校准(屏幕四角)
void XPT2046_Calibrate(int16_t x0, int16_t y0, int16_t x1, int16_t y1,
                       int16_t x2, int16_t y2, int16_t x3, int16_t y3)
{
    // 计算 X 轴范围
    if (x0 < x1) { xpt2046_cal_x0 = x0; xpt2046_cal_x1 = x1; }
    else { xpt2046_cal_x0 = x1; xpt2046_cal_x1 = x0; }

    // 计算 Y 轴范围
    if (y2 < y3) { xpt2046_cal_y0 = y2; xpt2046_cal_y1 = y3; }
    else { xpt2046_cal_y0 = y3; xpt2046_cal_y1 = y2; }
}

// 将原始坐标映射到屏幕坐标
void XPT2046_GetCalibratedPoint(int16_t raw_x, int16_t raw_y, int16_t *cal_x, int16_t *cal_y)
{
    // 线性映射:raw → screen
    if (xpt2046_cal_x1 != xpt2046_cal_x0) {
        *cal_x = (int16_t)((((int32_t)raw_x - xpt2046_cal_x0) * XPT2046_HOR_RES) / 
                          (xpt2046_cal_x1 - xpt2046_cal_x0));
    } else {
        *cal_x = 0;
    }

    if (xpt2046_cal_y1 != xpt2046_cal_y0) {
        *cal_y = (int16_t)((((int32_t)raw_y - xpt2046_cal_y0) * XPT2046_VER_RES) / 
                          (xpt2046_cal_y1 - xpt2046_cal_y0));
    } else {
        *cal_y = 0;
    }

    // 边界限制
    if (*cal_x < 0) *cal_x = 0;
    if (*cal_x >= XPT2046_HOR_RES) *cal_x = XPT2046_HOR_RES - 1;
    if (*cal_y < 0) *cal_y = 0;
    if (*cal_y >= XPT2046_VER_RES) *cal_y = XPT2046_VER_RES - 1;
}

移植Lvgl

 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
void disp_flush(lv_disp_drv_t * disp, const lv_area_t * area, lv_color_t * color_p)
{
    // 设置刷新区域
    ILI9341_SetWindow(area->x1, area->y1, area->x2, area->y2);
    
    // 准备写数据
    ILI9341_WriteRAM_Prepare();
    
    // 发送所有像素(LVGL 颜色格式需与 ILI9341 一致:RGB565)
    uint32_t size = (area->x2 - area->x1 + 1) * (area->y2 - area->y1 + 1);
    for (uint32_t i = 0; i < size; i++) {
        ILI9341_WriteRAM(color_p[i].full); // .full 是 uint16_t
    }
    
    ILI9341_WriteRAM_Finish();
    
    lv_disp_flush_ready(disp); // 通知 LVGL 刷新完成
}



#include "xpt2046.h"

static bool xpt2046_read(lv_indev_drv_t * drv, lv_indev_data_t * data)
{
    static int16_t last_x = 0, last_y = 0;
    int16_t x_raw, y_raw, z;

    // 方式 1:使用 PENIRQ(推荐)
    // if (XPT2046_PENIRQ()) {
    //     if (XPT2046_ReadXYZ(&x_raw, &y_raw, &z)) {
    //         XPT2046_GetCalibratedPoint(x_raw, y_raw, &data->point.x, &data->point.y);
    //         data->state = LV_INDEV_STATE_PRESSED;
    //         return false;
    //     }
    // }

    // 方式 2:轮询(无 PENIRQ)
    if (XPT2046_ReadXYZ(&x_raw, &y_raw, &z)) {
        // 判断是否按下(Z > 阈值)
        if (z > 100 && z < 2000) { // 根据实际调整
            XPT2046_GetCalibratedPoint(x_raw, y_raw, &data->point.x, &data->point.y);
            data->state = LV_INDEV_STATE_PRESSED;
            last_x = data->point.x;
            last_y = data->point.y;
            return false;
        }
    }

    // 未触摸
    data->point.x = last_x;
    data->point.y = last_y;
    data->state = LV_INDEV_STATE_RELEASED;
    return false;
}

void lv_port_indev_init(void)
{
    XPT2046_Init();

    // 可选:执行校准(此处用默认值)
    // XPT2046_Calibrate(300, 300, 3800, 300, 300, 3800, 3800, 3800);

    lv_indev_drv_t indev_drv;
    lv_indev_drv_init(&indev_drv);
    indev_drv.type = LV_INDEV_TYPE_POINTER;
    indev_drv.read_cb = xpt2046_read;
    lv_indev_drv_register(&indev_drv);
}

lvgl 读取fatfs上的字库与图片

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
 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
void AD_Init(void)
{
    /*开启时钟*/
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);    //开启ADC1的时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);   //开启GPIOA的时钟
    
    /*设置ADC时钟*/
    RCC_ADCCLKConfig(RCC_PCLK2_Div6);                       //选择时钟6分频,ADCCLK = 72MHz / 6 = 12MHz
    
    /*GPIO初始化*/
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);                  //将PA0引脚初始化为模拟输入
    
    /*规则组通道配置*/
    ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);     //规则组序列1的位置,配置为通道0, 存放在数组中的位置,采样的时间
    
    /*ADC初始化*/
    ADC_InitTypeDef ADC_InitStructure;                      //定义结构体变量
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;      //模式,选择独立模式,即单独使用ADC1
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;  //数据对齐,选择右对齐
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //外部触发,使用软件触发,不需要外部触发
    ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;     //连续转换,失能,每转换一次规则组序列后停止
    ADC_InitStructure.ADC_ScanConvMode = DISABLE;           //扫描模式,失能,只转换规则组的序列1这一个位置
    ADC_InitStructure.ADC_NbrOfChannel = 1;                 //通道数,为1,仅在扫描模式下,才需要指定大于1的数,在非扫描模式下,只能是1
    ADC_Init(ADC1, &ADC_InitStructure);                     //将结构体变量交给ADC_Init,配置ADC1
    
    /*ADC使能*/
    ADC_Cmd(ADC1, ENABLE);                                  //使能ADC1,ADC开始运行
    
    /*ADC校准*/
    ADC_ResetCalibration(ADC1);                             //固定流程,内部有电路会自动执行校准
    while (ADC_GetResetCalibrationStatus(ADC1) == SET);
    ADC_StartCalibration(ADC1);
    while (ADC_GetCalibrationStatus(ADC1) == SET);
}

/**
  * 函    数:获取AD转换的值
  * 参    数:无
  * 返 回 值:AD转换的值,范围:0~4095
  */
uint16_t AD_GetValue(void)
{
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);                 //软件触发AD转换一次
    while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET); //等待EOC标志位,即等待AD转换结束
    return ADC_GetConversionValue(ADC1);                    //读数据寄存器,得到AD转换的结果
}



//多通道
void AD_Init(void)
{
    /*开启时钟*/
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);    //开启ADC1的时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);   //开启GPIOA的时钟
    
    /*设置ADC时钟*/
    RCC_ADCCLKConfig(RCC_PCLK2_Div6);                       //选择时钟6分频,ADCCLK = 72MHz / 6 = 12MHz
    
    /*GPIO初始化*/
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);                  //将PA0、PA1、PA2和PA3引脚初始化为模拟输入
    
    /*不在此处配置规则组序列,而是在每次AD转换前配置,这样可以灵活更改AD转换的通道*/
    
    /*ADC初始化*/
    ADC_InitTypeDef ADC_InitStructure;                      //定义结构体变量
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;      //模式,选择独立模式,即单独使用ADC1
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;  //数据对齐,选择右对齐
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //外部触发,使用软件触发,不需要外部触发
    ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;     //连续转换,失能,每转换一次规则组序列后停止
    ADC_InitStructure.ADC_ScanConvMode = DISABLE;           //扫描模式,失能,只转换规则组的序列1这一个位置
    ADC_InitStructure.ADC_NbrOfChannel = 1;                 //通道数,为1,仅在扫描模式下,才需要指定大于1的数,在非扫描模式下,只能是1
    ADC_Init(ADC1, &ADC_InitStructure);                     //将结构体变量交给ADC_Init,配置ADC1
    
    /*ADC使能*/
    ADC_Cmd(ADC1, ENABLE);                                  //使能ADC1,ADC开始运行
    
    /*ADC校准*/
    ADC_ResetCalibration(ADC1);                             //固定流程,内部有电路会自动执行校准
    while (ADC_GetResetCalibrationStatus(ADC1) == SET);
    ADC_StartCalibration(ADC1);
    while (ADC_GetCalibrationStatus(ADC1) == SET);
}

/**
  * 函    数:获取AD转换的值
  * 参    数:ADC_Channel 指定AD转换的通道,范围:ADC_Channel_x,其中x可以是0/1/2/3
  * 返 回 值:AD转换的值,范围:0~4095
  */
uint16_t AD_GetValue(uint8_t ADC_Channel)
{
    ADC_RegularChannelConfig(ADC1, ADC_Channel, 1, ADC_SampleTime_55Cycles5);   //在每次转换前,根据函数形参灵活更改规则组的通道1
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);                 //软件触发AD转换一次
    while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET); //等待EOC标志位,即等待AD转换结束
    return ADC_GetConversionValue(ADC1);                    //读数据寄存器,得到AD转换的结果
}


//多通道+DMA
#include "stm32f10x.h"                  // Device header

uint16_t AD_Value[4];                   //定义用于存放AD转换结果的全局数组

/**
  * 函    数:AD初始化
  * 参    数:无
  * 返 回 值:无
  */
void AD_Init(void)
{
    /*开启时钟*/
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);    //开启ADC1的时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);   //开启GPIOA的时钟
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);      //开启DMA1的时钟
    
    /*设置ADC时钟*/
    RCC_ADCCLKConfig(RCC_PCLK2_Div6);                       //选择时钟6分频,ADCCLK = 72MHz / 6 = 12MHz
    
    /*GPIO初始化*/
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);                  //将PA0、PA1、PA2和PA3引脚初始化为模拟输入
    
    /*规则组通道配置*/
    ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); //规则组序列1的位置,配置为通道0
    ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5); //规则组序列2的位置,配置为通道1
    ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5); //规则组序列3的位置,配置为通道2
    ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5); //规则组序列4的位置,配置为通道3
    
    /*ADC初始化*/
    ADC_InitTypeDef ADC_InitStructure;                                          //定义结构体变量
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;                          //模式,选择独立模式,即单独使用ADC1
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;                      //数据对齐,选择右对齐
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;         //外部触发,使用软件触发,不需要外部触发
    ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;                          //连续转换,使能,每转换一次规则组序列后立刻开始下一次转换
    ADC_InitStructure.ADC_ScanConvMode = ENABLE;                                //扫描模式,使能,扫描规则组的序列,扫描数量由ADC_NbrOfChannel确定
    ADC_InitStructure.ADC_NbrOfChannel = 4;                                     //通道数,为4,扫描规则组的前4个通道
    ADC_Init(ADC1, &ADC_InitStructure);                                         //将结构体变量交给ADC_Init,配置ADC1
    
    /*DMA初始化*/
    DMA_InitTypeDef DMA_InitStructure;                                          //定义结构体变量
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;             //外设基地址,给定形参AddrA
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //外设数据宽度,选择半字,对应16为的ADC数据寄存器
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;            //外设地址自增,选择失能,始终以ADC数据寄存器为源
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value;                  //存储器基地址,给定存放AD转换结果的全局数组AD_Value
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;         //存储器数据宽度,选择半字,与源数据宽度对应
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;                     //存储器地址自增,选择使能,每次转运后,数组移到下一个位置
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;                          //数据传输方向,选择由外设到存储器,ADC数据寄存器转到数组
    DMA_InitStructure.DMA_BufferSize = 4;                                       //转运的数据大小(转运次数),与ADC通道数一致
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;                             //模式,选择循环模式,与ADC的连续转换一致
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;                                //存储器到存储器,选择失能,数据由ADC外设触发转运到存储器
    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;                       //优先级,选择中等
    DMA_Init(DMA1_Channel1, &DMA_InitStructure);                                //将结构体变量交给DMA_Init,配置DMA1的通道1
    
    /*DMA和ADC使能*/
    DMA_Cmd(DMA1_Channel1, ENABLE);                         //DMA1的通道1使能
    ADC_DMACmd(ADC1, ENABLE);                               //ADC1触发DMA1的信号使能
    ADC_Cmd(ADC1, ENABLE);                                  //ADC1使能
    
    /*ADC校准*/
    ADC_ResetCalibration(ADC1);                             //固定流程,内部有电路会自动执行校准
    while (ADC_GetResetCalibrationStatus(ADC1) == SET);
    ADC_StartCalibration(ADC1);
    while (ADC_GetCalibrationStatus(ADC1) == SET);
    
    /*ADC触发*/
    ADC_SoftwareStartConvCmd(ADC1, ENABLE); //软件触发ADC开始工作,由于ADC处于连续转换模式,故触发一次后ADC就可以一直连续不断地工作
}

TIM

外部时钟-》监测对应ETR引脚的指定边沿,如果出现则cnt++;

 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
void Timer_Init(void)
{
    /*开启时钟*/
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);            //开启TIM2的时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);           //开启GPIOA的时钟
    
    /*GPIO初始化*/
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);                      //将PA0引脚初始化为上拉输入
    
    /*外部时钟配置*/
    TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0x0F);
                                                                //选择外部时钟模式2,时钟从TIM_ETR引脚输入
                                                                //注意TIM2的ETR引脚固定为PA0,无法随意更改
                                                                //最后一个滤波器参数加到最大0x0F,可滤除时钟信号抖动
    
    /*时基单元初始化*/
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;              //定义结构体变量
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
    TIM_TimeBaseInitStructure.TIM_Period = 10 - 1;                  //计数周期,即ARR的值
    TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1;                //预分频器,即PSC的值
    TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;            //重复计数器,高级定时器才会用到
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);             //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元  
    
    /*中断输出配置*/
    TIM_ClearFlag(TIM2, TIM_FLAG_Update);                       //清除定时器更新标志位
                                                                //TIM_TimeBaseInit函数末尾,手动产生了更新事件
                                                                //若不清除此标志位,则开启中断后,会立刻进入一次中断
                                                                //如果不介意此问题,则不清除此标志位也可
                                                                
    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);                  //开启TIM2的更新中断
    
    /*NVIC中断分组*/
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);             //配置NVIC为分组2
                                                                //即抢占优先级范围:0~3,响应优先级范围:0~3
                                                                //此分组配置在整个工程中仅需调用一次
                                                                //若有多个中断,可以把此代码放在main函数内,while循环之前
                                                                //若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
    
    /*NVIC配置*/
    NVIC_InitTypeDef NVIC_InitStructure;                        //定义结构体变量
    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;             //选择配置NVIC的TIM2线
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;             //指定NVIC线路使能
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;   //指定NVIC线路的抢占优先级为2
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;          //指定NVIC线路的响应优先级为1
    NVIC_Init(&NVIC_InitStructure);                             //将结构体变量交给NVIC_Init,配置NVIC外设
    
    /*TIM使能*/
    TIM_Cmd(TIM2, ENABLE);          //使能TIM2,定时器开始运行
}


void TIM2_IRQHandler(void)
{
    if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)        //判断是否是TIM2的更新事件触发的中断
    {
        Num ++;                                             //Num变量自增,用于测试定时中断
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);         //清除TIM2更新事件的中断标志位
                                                            //中断标志位必须清除
                                                            //否则中断将连续不断地触发,导致主程序卡死
    }
}

看门狗

独立看门狗

递减到0结束

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
    IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);   //独立看门狗写使能
    IWDG_SetPrescaler(IWDG_Prescaler_16);           //设置预分频为16
    IWDG_SetReload(2499);                           //设置重装值为2499,独立看门狗的超时时间为1000ms
    IWDG_ReloadCounter();                           //重装计数器,喂狗
    IWDG_Enable();                                  //独立看门狗使能
    
    while (1)
    {
        Key_GetNum();                               //调用阻塞式的按键扫描函数,模拟主循环卡死
        
        IWDG_ReloadCounter();                       //重装计数器,喂狗
        
        OLED_ShowString(4, 1, "FEED");              //OLED闪烁FEED字符串
        Delay_ms(200);                              //喂狗间隔为200+600=800ms
        OLED_ShowString(4, 1, "    ");
        Delay_ms(600);
    }

窗口看门狗

窗口看门狗跟独立看门狗一样,也是一个递减计数器不断的往下递减计数, 当减到一个固定值0X40时还不喂狗的话,产生复位,这个值叫窗口的下限,是固定的值,不能改变。这个是跟独立看门狗类似的地方, 不同的地方是窗口看门狗的计数器的值在减到某一个数之前喂狗的话也会产生复位,这个值叫窗口的上限,上限值由用户独立设置。 窗口看门狗计数器的值必须在上窗口和下窗口之间才可以喂狗

 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
int main(void)
{
    /*模块初始化*/
    OLED_Init();                        //OLED初始化
    Key_Init();                         //按键初始化
    
    /*显示静态字符串*/
    OLED_ShowString(1, 1, "WWDG TEST");
    
    /*判断复位信号来源*/
    if (RCC_GetFlagStatus(RCC_FLAG_WWDGRST) == SET) //如果是窗口看门狗复位
    {
        OLED_ShowString(2, 1, "WWDGRST");           //OLED闪烁WWDGRST字符串
        Delay_ms(500);
        OLED_ShowString(2, 1, "       ");
        Delay_ms(100);
        
        RCC_ClearFlag();                            //清除标志位
    }
    else                                            //否则,即为其他复位
    {
        OLED_ShowString(3, 1, "RST");               //OLED闪烁RST字符串
        Delay_ms(500);
        OLED_ShowString(3, 1, "   ");
        Delay_ms(100);
    }
    
    /*开启时钟*/
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG, ENABLE);    //开启WWDG的时钟
    
    /*WWDG初始化*/
    WWDG_SetPrescaler(WWDG_Prescaler_8);            //设置预分频为8
    WWDG_SetWindowValue(0x40 | 21);                 //设置窗口值,窗口时间为30ms
    WWDG_Enable(0x40 | 54);                         //使能并第一次喂狗,超时时间为50ms
    
    while (1)
    {
        Key_GetNum();                               //调用阻塞式的按键扫描函数,模拟主循环卡死
        
        OLED_ShowString(4, 1, "FEED");              //OLED闪烁FEED字符串
        Delay_ms(20);                               //喂狗间隔为20+20=40ms
        OLED_ShowString(4, 1, "    ");
        Delay_ms(20);
        
        WWDG_SetCounter(0x40 | 54);                 //重装计数器,喂狗
    }
}

DAC

 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
#include "stm32f10x.h"

void DAC_Configuration(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    DAC_InitTypeDef DAC_InitStructure;

    // 1. 使能 GPIOA 和 DAC 时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);

    // 2. 配置 PA4 为模拟输入(DAC 输出模式)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; // 模拟输入模式(用于 DAC 输出)
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    // 3. 配置 DAC 通道 1
    DAC_InitStructure.DAC_Trigger = DAC_Trigger_None;          // 不使用触发,软件直接写
    DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None; // 不使用波形发生
    DAC_InitStructure.DAC_LFSRUnmask_TriangleAmplitude = DAC_TriangleAmplitude_4095; // 无影响
    DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable; // 使能输出缓冲(降低输出阻抗)
    DAC_Init(DAC_Channel_1, &DAC_InitStructure);

    // 4. 使能 DAC 通道 1
    DAC_Cmd(DAC_Channel_1, ENABLE);

    // 5. 可选:关闭 DMA(本例未使用)
    DAC_DMACmd(DAC_Channel_1, DISABLE);
}

int main(void)
{
    // 系统初始化(可选:设置系统时钟等)
    SystemInit();

    // 配置 DAC
    DAC_Configuration();

    // 设置 DAC 输出值(0 ~ 4095)
    // 例如:输出一半电压(Vref = 3.3V 时,输出约 1.65V)
    DAC_SetChannel1Data(DAC_Align_12b_R, 2048);

    // 主循环(DAC 已输出,无需操作)
    while (1)
    {
        // 可在此添加其他逻辑,如改变 DAC 值生成波形等
    }
}

电源管理

图1

睡眠模式

图1

停止模式

图2

待机模式

图1

  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
/** brief  等待中断

    等待中断 是一个暂停执行指令
    暂停至任意中断产生后被唤醒
*/
#define __WFI                             __wfi


/** brief  等待事件

    等待事件 是一个暂停执行指令
    暂停至任意事件产生后被唤醒
*/
#define __WFE                             __wfe



while (1)
{
    OLED_ShowNum(1, 7, CountSensor_Get(), 5);           //OLED不断刷新显示CountSensor_Get的返回值
    
    OLED_ShowString(2, 1, "Running");                   //OLED闪烁Running,指示当前主循环正在运行
    Delay_ms(100);
    OLED_ShowString(2, 1, "       ");
    Delay_ms(100);
    
    PWR_EnterSTOPMode(PWR_Regulator_ON, PWR_STOPEntry_WFI); //STM32进入停止模式,并等待中断唤醒
    SystemInit();                                       //唤醒后,要重新配置时钟
}


int main(void)
{
    /*模块初始化*/
    OLED_Init();        //OLED初始化
    MyRTC_Init();       //RTC初始化
    
    /*开启时钟*/
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);     //开启PWR的时钟
                                                            //停止模式和待机模式一定要记得开启
    
    /*显示静态字符串*/
    OLED_ShowString(1, 1, "CNT :");
    OLED_ShowString(2, 1, "ALR :");
    OLED_ShowString(3, 1, "ALRF:");
    
    /*使能WKUP引脚*/
    PWR_WakeUpPinCmd(ENABLE);                       //使能位于PA0的WKUP引脚,WKUP引脚上升沿唤醒待机模式
    
    /*设定闹钟*/
    uint32_t Alarm = RTC_GetCounter() + 10;         //闹钟为唤醒后当前时间的后10s
    RTC_SetAlarm(Alarm);                            //写入闹钟值到RTC的ALR寄存器
    OLED_ShowNum(2, 6, Alarm, 10);                  //显示闹钟值
    
    while (1)
    {
        OLED_ShowNum(1, 6, RTC_GetCounter(), 10);   //显示32位的秒计数器
        OLED_ShowNum(3, 6, RTC_GetFlagStatus(RTC_FLAG_ALR), 1);     //显示闹钟标志位
        
        OLED_ShowString(4, 1, "Running");           //OLED闪烁Running,指示当前主循环正在运行
        Delay_ms(100);
        OLED_ShowString(4, 1, "       ");
        Delay_ms(100);
        
        OLED_ShowString(4, 9, "STANDBY");           //OLED闪烁STANDBY,指示即将进入待机模式
        Delay_ms(1000);
        OLED_ShowString(4, 9, "       ");
        Delay_ms(100);
        
        OLED_Clear();                               //OLED清屏,模拟关闭外部所有的耗电设备,以达到极度省电
        
        PWR_EnterSTANDBYMode();                     //STM32进入停止模式,并等待指定的唤醒事件(WKUP上升沿或RTC闹钟)
        /*待机模式唤醒后,程序会重头开始运行*/
    }
}
void MyRTC_Init(void)
{
    /*开启时钟*/
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);     //开启PWR的时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);     //开启BKP的时钟
    
    /*备份寄存器访问使能*/
    PWR_BackupAccessCmd(ENABLE);                            //使用PWR开启对备份寄存器的访问
    
    if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5)          //通过写入备份寄存器的标志位,判断RTC是否是第一次配置
                                                            //if成立则执行第一次的RTC配置
    {
        RCC_LSEConfig(RCC_LSE_ON);                          //开启LSE时钟
        while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) != SET);  //等待LSE准备就绪
        
        RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);             //选择RTCCLK来源为LSE
        RCC_RTCCLKCmd(ENABLE);                              //RTCCLK使能
        
        RTC_WaitForSynchro();                               //等待同步
        RTC_WaitForLastTask();                              //等待上一次操作完成
        
        RTC_SetPrescaler(32768 - 1);                        //设置RTC预分频器,预分频后的计数频率为1Hz
        RTC_WaitForLastTask();                              //等待上一次操作完成
        
        MyRTC_SetTime();                                    //设置时间,调用此函数,全局数组里时间值刷新到RTC硬件电路
        
        BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);           //在备份寄存器写入自己规定的标志位,用于判断RTC是不是第一次执行配置
    }
    else                                                    //RTC不是第一次配置
    {
        RTC_WaitForSynchro();                               //等待同步
        RTC_WaitForLastTask();                              //等待上一次操作完成
    }
}

RTC

  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
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
#include "stm32f10x.h"
#include <stdio.h>

// 函数声明
void RCC_Configuration(void);
void RTC_Configuration(void);
void USART1_Config(void);
int fputc(int ch, FILE *f); // 用于 printf 重定向

// 后备寄存器用于标记 RTC 是否已初始化
#define BKP_DR1_RTC_INIT_FLAG   ((uint16_t)0x5AA5)

// 当前时间结构
typedef struct {
    uint8_t hour;
    uint8_t min;
    uint8_t sec;
    uint8_t date;
    uint8_t month;
    uint8_t year; // 00-99
} RTC_Time_t;

void Delay(__IO uint32_t nCount);

int main(void)
{
    RTC_Time_t currentTime;
    uint32_t time_reg, date_reg;

    SystemInit();
    RCC_Configuration();
    USART1_Config(); // 可选:用于打印

    printf("RTC Example Start...\r\n");

    // 检查是否已初始化 RTC
    if (BKP_ReadBackupRegister(BKP_DR1) != BKP_DR1_RTC_INIT_FLAG)
    {
        printf("RTC not initialized. Setting default time...\r\n");
        RTC_Configuration(); // 首次配置 RTC

        // 设置默认时间:2024年6月15日 10:30:00
        // 注意:STM32F1 RTC 年份范围 00-99,2024 对应 24
        RTC_SetCounter((24 << 25) | (6 << 21) | (15 << 16) | (10 << 12) | (30 << 6) | 0);
        // 上述写法仅为示意,实际应使用 BCD 编码或分步设置

        // 更推荐:使用 RTC_SetCounter 设置秒计数器(从 1970 或自定义起点)
        // 但为简化,此处使用直接写寄存器方式(仅演示)

        // 实际推荐方式:设置为 0 秒开始,然后软件维护日期
        RTC_SetCounter(0); // 从 0 开始计数(即 00:00:00)

        // 写入标志,避免重复初始化
        BKP_WriteBackupRegister(BKP_DR1, BKP_DR1_RTC_INIT_FLAG);
    }
    else
    {
        printf("RTC already initialized.\r\n");
    }

    // 等待 RTC 寄存器同步(重要!)
    RTC_WaitForSynchro();

    while (1)
    {
        // 读取 RTC 计数器(单位:秒)
        uint32_t seconds = RTC_GetCounter();

        // 简单显示秒数(实际应用中可转换为时分秒)
        printf("RTC Seconds: %lu\r\n", seconds);

        // 若需显示时分秒,可自行转换(例如 seconds % 86400)
        uint32_t secs = seconds % 60;
        uint32_t mins = (seconds / 60) % 60;
        uint32_t hours = (seconds / 3600) % 24;

        printf("Time: %02lu:%02lu:%02lu\r\n", hours, mins, secs);

        Delay(1000000); // 约 1 秒延迟(非精确)
    }
}

void RCC_Configuration(void)
{
    // 使能 PWR 和 BKP 时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);

    // 允许访问后备区域
    PWR_BackupAccessCmd(ENABLE);

    // 使能 LSI(内部低速时钟)
    RCC_LSICmd(ENABLE);
    while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET); // 等待 LSI 就绪

    // 选择 RTC 时钟源为 LSI
    RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);

    // 使能 RTC 时钟
    RCC_RTCCLKCmd(ENABLE);
}

void RTC_Configuration(void)
{
    // 等待 RTC 寄存器同步
    RTC_WaitForSynchro();

    // 允许 RTC 寄存器配置
    RTC_WaitForLastTask();

    // 设置 RTC 预分频值:LSI ≈ 40kHz,要得到 1Hz,分频 = 40000 - 1
    // 但 LSI 频率不精确(典型值 40kHz),实际需校准
    // 这里使用近似值:39999 → 40000 分频 → 1Hz
    RTC_SetPrescaler(39999);

    RTC_WaitForLastTask();
}

void USART1_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);

    // PA9 - TX
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    // PA10 - RX(本例未使用接收)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    USART_InitStructure.USART_BaudRate = 115200;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Tx;
    USART_Init(USART1, &USART_InitStructure);
    USART_Cmd(USART1, ENABLE);
}

// 重定向 printf 到 USART1
int fputc(int ch, FILE *f)
{
    USART_SendData(USART1, (uint8_t) ch);
    while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
    return ch;
}

void Delay(__IO uint32_t nCount)
{
    for (; nCount != 0; nCount--);
}






#include "stm32f10x.h"                  // Device header
#include <time.h>

uint16_t MyRTC_Time[] = {2023, 1, 1, 23, 59, 55};   //定义全局的时间数组,数组内容分别为年、月、日、时、分、秒

void MyRTC_SetTime(void);               //函数声明

/**
  * 函    数:RTC初始化
  * 参    数:无
  * 返 回 值:无
  */
void MyRTC_Init(void)
{
    /*开启时钟*/
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);     //开启PWR的时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);     //开启BKP的时钟
    
    /*备份寄存器访问使能*/
    PWR_BackupAccessCmd(ENABLE);                            //使用PWR开启对备份寄存器的访问
    
    if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5)          //通过写入备份寄存器的标志位,判断RTC是否是第一次配置
                                                            //if成立则执行第一次的RTC配置
    {
        RCC_LSEConfig(RCC_LSE_ON);                          //开启LSE时钟
        while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) != SET);  //等待LSE准备就绪
        
        RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);             //选择RTCCLK来源为LSE
        RCC_RTCCLKCmd(ENABLE);                              //RTCCLK使能
        
        RTC_WaitForSynchro();                               //等待同步
        RTC_WaitForLastTask();                              //等待上一次操作完成
        
        RTC_SetPrescaler(32768 - 1);                        //设置RTC预分频器,预分频后的计数频率为1Hz
        RTC_WaitForLastTask();                              //等待上一次操作完成
        
        MyRTC_SetTime();                                    //设置时间,调用此函数,全局数组里时间值刷新到RTC硬件电路
        
        BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);           //在备份寄存器写入自己规定的标志位,用于判断RTC是不是第一次执行配置
    }
    else                                                    //RTC不是第一次配置
    {
        RTC_WaitForSynchro();                               //等待同步
        RTC_WaitForLastTask();                              //等待上一次操作完成
    }
}

/**
  * 函    数:RTC设置时间
  * 参    数:无
  * 返 回 值:无
  * 说    明:调用此函数后,全局数组里时间值将刷新到RTC硬件电路
  */
void MyRTC_SetTime(void)
{
    time_t time_cnt;        //定义秒计数器数据类型
    struct tm time_date;    //定义日期时间数据类型
    
    time_date.tm_year = MyRTC_Time[0] - 1900;       //将数组的时间赋值给日期时间结构体
    time_date.tm_mon = MyRTC_Time[1] - 1;
    time_date.tm_mday = MyRTC_Time[2];
    time_date.tm_hour = MyRTC_Time[3];
    time_date.tm_min = MyRTC_Time[4];
    time_date.tm_sec = MyRTC_Time[5];
    
    time_cnt = mktime(&time_date) - 8 * 60 * 60;    //调用mktime函数,将日期时间转换为秒计数器格式
                                                    //- 8 * 60 * 60为东八区的时区调整
    
    RTC_SetCounter(time_cnt);                       //将秒计数器写入到RTC的CNT中
    RTC_WaitForLastTask();                          //等待上一次操作完成
}

/**
  * 函    数:RTC读取时间
  * 参    数:无
  * 返 回 值:无
  * 说    明:调用此函数后,RTC硬件电路里时间值将刷新到全局数组
  */
void MyRTC_ReadTime(void)
{
    time_t time_cnt;        //定义秒计数器数据类型
    struct tm time_date;    //定义日期时间数据类型
    
    time_cnt = RTC_GetCounter() + 8 * 60 * 60;      //读取RTC的CNT,获取当前的秒计数器
                                                    //+ 8 * 60 * 60为东八区的时区调整
    
    time_date = *localtime(&time_cnt);              //使用localtime函数,将秒计数器转换为日期时间格式
    
    MyRTC_Time[0] = time_date.tm_year + 1900;       //将日期时间结构体赋值给数组的时间
    MyRTC_Time[1] = time_date.tm_mon + 1;
    MyRTC_Time[2] = time_date.tm_mday;
    MyRTC_Time[3] = time_date.tm_hour;
    MyRTC_Time[4] = time_date.tm_min;
    MyRTC_Time[5] = time_date.tm_sec;
}

Debug调试

BKP

1
2
3
4
5
6
7
8
9
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);     //开启BKP的时钟
    /*备份寄存器访问使能*/
    PWR_BackupAccessCmd(ENABLE);


    BKP_WriteBackupRegister(BKP_DR1, ArrayWrite[0]);    //写入测试数据到备份寄存器
    BKP_WriteBackupRegister(BKP_DR2, ArrayWrite[1]);
    ArrayRead[0] = BKP_ReadBackupRegister(BKP_DR1);     //读取备份寄存器的数据
    ArrayRead[1] = BKP_ReadBackupRegister(BKP_DR2);

番外

移植CJSON

实现MQTT客户端(paho)

I2S

LORA

写个Bootloader

参考

参考

Licensed under CC BY-NC-SA 4.0