STM32F103C8T6 单片机开发指南

一、STM32F103C8T6 简介

1.1 概述

STM32F103C8T6 是意法半导体(STMicroelectronics)推出的一款基于 ARM Cortex-M3 内核的 32 位微控制器,属于 STM32F1 系列("增强型" 产品线)。它以高性能、低成本和丰富的外设资源著称,广泛应用于工业控制、消费电子、物联网等领域。

1.2 主要特性

  • 内核与性能

    • ARM Cortex-M3 内核,最高 72MHz 主频
    • 1.25DMIPS/MHz 的处理能力
    • 单周期乘法和硬件除法
  • 存储器

    • 64KB 或 128KB 闪存(Flash)
    • 20KB 静态随机存储器(SRAM)
    • 支持程序代码和数据的存储
  • 外设资源

    • 3 个 12 位 ADC(最多 16 个通道),采样率可达 1MHz
    • 2 个 12 位 DAC
    • 3 个通用 16 位定时器 + 1 个高级定时器
    • 2 个 I2C 接口
    • 3 个 SPI 接口
    • 2 个 I2S 接口
    • 5 个 USART 接口
    • USB 2.0 全速接口
    • CAN 接口
    • 80 个快速 I/O 端口(大部分支持 5V 容忍)
  • 工作条件

    • 2.0V 至 3.6V 供电
    • -40°C 至 + 85°C 或 + 105°C 工作温度范围
    • 低功耗模式:睡眠、停机和待机模式

1.3 应用场景

  • 工业自动化控制
  • 智能传感器节点
  • 消费电子产品
  • 电机控制
  • 便携式设备
  • 物联网终端设备

二、开发环境搭建

2.1 硬件准备

  • 开发板:STM32F103C8T6 最小系统板或 Nucleo-F103RB 等开发板
  • 调试工具:ST-Link/V2、J-Link 或 USB 转串口模块
  • 电源:USB 线或外部电源
  • 辅助设备:LED、按键、传感器等(根据实验需求)

2.2 软件准备

2.2.1 集成开发环境 (IDE)
  • STM32CubeIDE:ST 官方提供的一站式开发环境,基于 Eclipse,集成了编辑器、编译器、调试器和图形化配置工具。
  • Keil MDK:ARM 官方开发工具,支持 ARM Cortex-M 系列,提供高效的编译器和调试功能。
  • GCC + OpenOCD:开源工具链,配合 VS Code 或 Eclipse 使用,适合跨平台开发。
  • PlatformIO:基于 VS Code 的跨平台开发框架,支持多种 MCU 和开发板。
2.2.2 固件库和工具
  • STM32Cube HAL/LL 库:ST 提供的硬件抽象层库,简化外设配置。
  • STM32CubeMX:图形化配置工具,生成初始化代码。
  • ST-Link Utility:用于烧录和调试 STM32 芯片。

2.3 开发环境配置步骤

以 STM32CubeIDE 为例:

  1. 下载并安装 STM32CubeIDE

    • 从 ST 官网下载最新版本的 STM32CubeIDE
    • 按照安装向导完成安装
  2. 创建新项目

    • 打开 STM32CubeIDE,选择 File > New > STM32 Project
    • 在 Board Selector 中选择对应开发板(如 STM32F103C8Tx)
    • 点击 Next,设置项目名称和路径,点击 Finish
  3. 配置外设

    • 打开 *.ioc 文件,进入 Pinout & Configuration 视图
    • 在 System Core 中配置时钟、调试接口等
    • 配置所需外设(如 GPIO、USART、SPI 等)
  4. 生成代码

    • 点击 Project > Generate Code
    • 选择覆盖现有文件(首次生成)或合并更改
  5. 编写应用代码

    • 在生成的代码基础上添加应用逻辑
    • 编译并下载程序到开发板

三、C 语言基础与 STM32 开发

3.1 STM32 的 C 语言开发特点

  • 直接操作硬件:通过指针访问内存映射的外设寄存器
  • 位操作:频繁使用位运算(&, |, ^, ~, <<,>>)配置寄存器
  • 内存管理:合理分配栈、堆和全局变量空间
  • 中断处理:使用中断服务函数 (ISR) 响应外部事件

3.2 寄存器编程与固件库编程

3.2.1 寄存器编程

直接操作外设寄存器,代码效率高但开发难度大。

示例:使用寄存器点亮 LED

// 使能GPIOA时钟
*(unsigned int*)0x40021018 |= (1 << 2);

// 配置PA8为推挽输出,最大速度50MHz
*(unsigned int*)0x40010800 &= ~(0xF << 0);  // 清除PA8配置
*(unsigned int*)0x40010800 |= (0x3 << 0);   // 配置为输出模式
*(unsigned int*)0x40010800 |= (0x1 << 2);   // 配置为50MHz速度

// 点亮LED (PA8输出高电平)
*(unsigned int*)0x4001080C |= (1 << 8);
3.2.2 固件库编程

使用 ST 提供的标准外设库或 HAL 库,简化开发过程。

示例:使用标准外设库点亮 LED

#include "stm32f10x.h"

int main(void) {
    // 使能GPIOA时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    // 配置GPIOA.8为推挽输出
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    // 点亮LED
    GPIO_SetBits(GPIOA, GPIO_Pin_8);
    
    while (1) {
        // 主循环
    }
}

3.3 HAL 库与 LL 库

3.3.1 HAL 库 (硬件抽象层)
  • 高度抽象的 API,屏蔽硬件细节
  • 代码移植性好,跨系列 STM32 兼容性高
  • 适合快速开发和初学者
3.3.2 LL 库 (低级别库)
  • 接近寄存器操作的轻量级 API
  • 性能优于 HAL 库,代码体积更小
  • 适合对性能要求高的应用

示例:使用 HAL 库点亮 LED

#include "main.h"

GPIO_InitTypeDef GPIO_InitStruct = {0};

void SystemClock_Config(void);
static void MX_GPIO_Init(void);

int main(void) {
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();

    while (1) {
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_SET);
        HAL_Delay(500);
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_RESET);
        HAL_Delay(500);
    }
}

void SystemClock_Config(void) {
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

    /** 初始化RCC振荡器 
     */
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
    RCC_OscInitStruct.HSIState = RCC_HSI_ON;
    RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
    if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
        Error_Handler();
    }
    
    /** 初始化RCC时钟 
     */
    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                                  |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

    if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK) {
        Error_Handler();
    }
}

static void MX_GPIO_Init(void) {
    __HAL_RCC_GPIOA_CLK_ENABLE();

    GPIO_InitStruct.Pin = GPIO_PIN_8;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}

四、GPIO 编程实例

4.1 GPIO 输入输出配置

GPIO (通用输入输出) 是 STM32 最基本的外设,可以配置为多种模式:

  • 输入模式:浮空输入、上拉输入、下拉输入、模拟输入
  • 输出模式:推挽输出、开漏输出
  • 复用功能模式:用于外设(如 USART、SPI 等)
  • 模拟模式:用于 ADC/DAC

示例:配置 GPIO 为输出模式

#include "stm32f10x.h"

void GPIO_Configuration(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    
    // 使能GPIOA和GPIOC时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOC, ENABLE);
    
    // 配置PA8为推挽输出,用于控制LED
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    // 配置PC13为上拉输入,用于读取按键
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;  // 上拉输入
    GPIO_Init(GPIOC, &GPIO_InitStructure);
}

4.2 LED 闪烁实验

示例代码

#include "stm32f10x.h"

void Delay(__IO uint32_t nCount);

int main(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    
    // 使能GPIOA时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    // 配置PA8为推挽输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    while (1) {
        // 点亮LED
        GPIO_SetBits(GPIOA, GPIO_Pin_8);
        Delay(0xFFFFF);
        
        // 熄灭LED
        GPIO_ResetBits(GPIOA, GPIO_Pin_8);
        Delay(0xFFFFF);
    }
}

// 简单延时函数
void Delay(__IO uint32_t nCount) {
    for (; nCount != 0; nCount--);
}

4.3 按键检测实验

示例代码

#include "stm32f10x.h"

void Delay(__IO uint32_t nCount);

int main(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    
    // 使能GPIOA和GPIOC时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOC, ENABLE);
    
    // 配置PA8为推挽输出 (LED)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    // 配置PC13为上拉输入 (按键)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(GPIOC, &GPIO_InitStructure);
    
    while (1) {
        // 检测按键是否按下 (PC13为低电平)
        if (GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_13) == 0) {
            Delay(0x20000);  // 消抖
            if (GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_13) == 0) {
                // 按键确实按下,切换LED状态
                GPIO_WriteBit(GPIOA, GPIO_Pin_8, 
                    (BitAction)(1 - GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_8)));
                
                // 等待按键释放
                while (GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_13) == 0);
            }
        }
    }
}

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

五、定时器编程

5.1 STM32 定时器概述

STM32F103C8T6 包含多个定时器:

  • 高级定时器 (TIM1):16 位,具有 PWM 输出、互补输出、死区时间生成等功能
  • 通用定时器 (TIM2, TIM3, TIM4):16 位,具有输入捕获、输出比较、PWM 输出等功能
  • 基本定时器 (TIM6, TIM7):16 位,主要用于定时计数

5.2 定时器基本配置

示例:配置 TIM3 产生 1ms 定时中断

#include "stm32f10x.h"

void TIM3_Configuration(void) {
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    
    // 使能TIM3时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
    
    // TIM3配置
    TIM_TimeBaseStructure.TIM_Period = 7199;           // 自动重载值
    TIM_TimeBaseStructure.TIM_Prescaler = 9;          // 预分频值
    TIM_TimeBaseStructure.TIM_ClockDivision = 0;      // 时钟分割
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  // 向上计数模式
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
    
    // 使能TIM3中断
    TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
    
    // 配置NVIC
    NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    
    // 使能TIM3
    TIM_Cmd(TIM3, ENABLE);
}

// TIM3中断服务函数
void TIM3_IRQHandler(void) {
    if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) {
        TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
        
        // 在这里添加定时处理代码
    }
}

5.3 PWM 输出实验

PWM (脉冲宽度调制) 常用于电机控制、LED 亮度调节等场景。

示例:配置 TIM3 CH1 产生 PWM 信号控制 LED 亮度

#include "stm32f10x.h"

void TIM3_PWM_Configuration(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_OCInitTypeDef TIM_OCInitStructure;
    
    // 使能GPIOA和TIM3时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
    
    // 配置PA6为复用推挽输出 (TIM3 CH1)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    // TIM3基本配置
    TIM_TimeBaseStructure.TIM_Period = 999;            // PWM周期
    TIM_TimeBaseStructure.TIM_Prescaler = 71;          // 预分频值
    TIM_TimeBaseStructure.TIM_ClockDivision = 0;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
    
    // TIM3 PWM模式配置
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;         // PWM模式1
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;  // 输出使能
    TIM_OCInitStructure.TIM_Pulse = 500;                      // 初始占空比
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; // 输出极性高
    TIM_OC1Init(TIM3, &TIM_OCInitStructure);
    
    // 使能TIM3
    TIM_Cmd(TIM3, ENABLE);
}

int main(void) {
    uint16_t pulse = 0;
    uint8_t direction = 1;
    
    TIM3_PWM_Configuration();
    
    while (1) {
        // 渐变LED亮度
        if (direction) {
            pulse++;
            if (pulse >= 999) direction = 0;
        } else {
            pulse--;
            if (pulse <= 0) direction = 1;
        }
        
        // 更新PWM脉冲宽度
        TIM_SetCompare1(TIM3, pulse);
        
        // 延时
        for (int i = 0; i < 10000; i++);
    }
}

六、串口通信

6.1 UART 基础

UART (通用异步收发传输器) 是一种串行通信协议,常用于设备间的数据传输。STM32F103C8T6 包含 5 个 USART 接口,支持多种通信模式。

6.2 串口配置与数据收发

示例:配置 USART1 实现基本的串口通信

#include "stm32f10x.h"
#include 

void USART1_Configuration(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    
    // 使能GPIOA和USART1时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);
    
    // 配置PA9为复用推挽输出 (USART1 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为浮空输入 (USART1 RX)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    // USART1配置
    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_Rx | USART_Mode_Tx;
    USART_Init(USART1, &USART_InitStructure);
    
    // 使能USART1接收中断
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
    
    // 配置NVIC
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    
    // 使能USART1
    USART_Cmd(USART1, ENABLE);
}

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

// USART1中断服务函数
void USART1_IRQHandler(void) {
    uint8_t ch;
    
    if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
        // 读取接收到的数据
        ch = USART_ReceiveData(USART1);
        
        // 回显数据
        USART_SendData(USART1, ch);
        
        // 清除中断标志
        USART_ClearITPendingBit(USART1, USART_IT_RXNE);
    }
}

int main(void) {
    USART1_Configuration();
    
    printf("Hello, STM32F103!\r\n");
    
    while (1) {
        // 主循环可以处理其他任务
    }
}

6.3 串口通信实例:控制 LED

下面是一个通过串口命令控制 LED 的示例:

#include "stm32f10x.h"
#include 

void GPIO_Configuration(void);
void USART1_Configuration(void);
void Delay(__IO uint32_t nCount);

// 全局变量
uint8_t led_status = 0;  // 0: LED熄灭, 1: LED点亮

int main(void) {
    GPIO_Configuration();
    USART1_Configuration();
    
    printf("LED Control Demo\r\n");
    printf("Type 'on' to turn LED on, 'off' to turn LED off\r\n");
    
    while (1) {
        // 主循环可以处理其他任务
    }
}

// USART1中断服务函数
void USART1_IRQHandler(void) {
    static uint8_t rx_buffer[10] = {0};
    static uint8_t rx_index = 0;
    uint8_t ch;
    
    if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
        // 读取接收到的数据
        ch = USART_ReceiveData(USART1);
        
        // 回显数据
        USART_SendData(USART1, ch);
        
        // 处理命令
        if (ch == '\r' || ch == '\n' || rx_index >= 9) {
            rx_buffer[rx_index] = '\0';
            rx_index = 0;
            
            // 处理命令
            if (strcmp((char*)rx_buffer, "on") == 0) {
                GPIO_SetBits(GPIOA, GPIO_Pin_8);
                led_status = 1;
                printf("LED turned ON\r\n");
            } else if (strcmp((char*)rx_buffer, "off") == 0) {
                GPIO_ResetBits(GPIOA, GPIO_Pin_8);
                led_status = 0;
                printf("LED turned OFF\r\n");
            } else {
                printf("Unknown command: %s\r\n", rx_buffer);
                printf("Valid commands: on, off\r\n");
            }
        } else {
            rx_buffer[rx_index++] = ch;
        }
        
        // 清除中断标志
        USART_ClearITPendingBit(USART1, USART_IT_RXNE);
    }
}

void GPIO_Configuration(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    
    // 使能GPIOA时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    // 配置PA8为推挽输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
}

void USART1_Configuration(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    
    // 使能GPIOA和USART1时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);
    
    // 配置PA9为复用推挽输出 (USART1 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为浮空输入 (USART1 RX)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    // USART1配置
    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_Rx | USART_Mode_Tx;
    USART_Init(USART1, &USART_InitStructure);
    
    // 使能USART1接收中断
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
    
    // 配置NVIC
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    
    // 使能USART1
    USART_Cmd(USART1, ENABLE);
}

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

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

七、ADC 与 DAC 应用

7.1 ADC (模拟 - to - 数字转换器)

STM32F103C8T6 内置 3 个 12 位 ADC(ADC1、ADC2 和 ADC3),支持多达 16 个外部通道,可测量 0-3.6V 范围内的模拟信号。ADC 具有单次、连续、扫描或间断模式,可通过定时器或外部触发启动转换。

7.1.1 ADC 基本配置

以下是配置 ADC1 通道 0(PA0)进行单次转换的代码:

#include "stm32f10x.h"

void ADC1_Configuration(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    ADC_InitTypeDef ADC_InitStructure;
    
    // 使能GPIOA和ADC1时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE);
    
    // 配置PA0为模拟输入模式
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    // ADC1配置
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;               // 独立模式
    ADC_InitStructure.ADC_ScanConvMode = DISABLE;                    // 单通道模式
    ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;              // 单次转换模式
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // 软件触发
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;           // 右对齐
    ADC_InitStructure.ADC_NbrOfChannel = 1;                           // 转换通道数
    ADC_Init(ADC1, &ADC_InitStructure);
    
    // 配置ADC1通道0的采样时间
    ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_28Cycles5);
    
    // 使能ADC1
    ADC_Cmd(ADC1, ENABLE);
    
    // 校准ADC
    ADC_ResetCalibration(ADC1);
    while (ADC_GetResetCalibrationStatus(ADC1));
    
    ADC_StartCalibration(ADC1);
    while (ADC_GetCalibrationStatus(ADC1));
}

uint16_t Get_ADC1_Value(void) {
    // 启动软件转换
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);
    
    // 等待转换完成
    while (!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
    
    // 读取ADC值并返回
    return ADC_GetConversionValue(ADC1);
}
7.1.2 ADC 连续转换模式

配置 ADC1 为连续转换模式,配合 DMA 实现高效数据采集:

#include "stm32f10x.h"

#define ADC_BUFFER_SIZE  100

uint16_t ADC_Buffer[ADC_BUFFER_SIZE];

void ADC1_Configuration(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    ADC_InitTypeDef ADC_InitStructure;
    DMA_InitTypeDef DMA_InitStructure;
    
    // 使能GPIOA、ADC1和DMA1时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
    
    // 配置PA0为模拟输入模式
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    // 配置DMA1通道1
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)ADC_Buffer;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    DMA_InitStructure.DMA_BufferSize = ADC_BUFFER_SIZE;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(DMA1_Channel1, &DMA_InitStructure);
    
    // 使能DMA1通道1
    DMA_Cmd(DMA1_Channel1, ENABLE);
    
    // ADC1配置
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
    ADC_InitStructure.ADC_ScanConvMode = DISABLE;
    ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;  // 连续转换模式
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
    ADC_InitStructure.ADC_NbrOfChannel = 1;
    ADC_Init(ADC1, &ADC_InitStructure);
    
    // 配置ADC1通道0的采样时间
    ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_28Cycles5);
    
    // 使能ADC1的DMA请求
    ADC_DMACmd(ADC1, ENABLE);
    
    // 使能ADC1
    ADC_Cmd(ADC1, ENABLE);
    
    // 校准ADC
    ADC_ResetCalibration(ADC1);
    while (ADC_GetResetCalibrationStatus(ADC1));
    
    ADC_StartCalibration(ADC1);
    while (ADC_GetCalibrationStatus(ADC1));
    
    // 启动连续转换
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}

7.2 DAC (数字 - to - 模拟转换器)

STM32F103C8T6 内置 2 个 12 位 DAC(DAC1 和 DAC2),可输出 0-3.3V 范围内的模拟电压。DAC 支持 8 位或 12 位模式,可通过软件触发或定时器触发。

7.2.1 DAC 基本配置

以下是配置 DAC1(PA4)输出固定电压的代码:

#include "stm32f10x.h"

void DAC_Configuration(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    DAC_InitTypeDef DAC_InitStructure;
    
    // 使能GPIOA和DAC时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);
    
    // 配置PA4为模拟输入模式(DAC1输出)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    // DAC1配置
    DAC_InitStructure.DAC_Trigger = DAC_Trigger_None;               // 无触发,软件控制
    DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None;  // 不生成波形
    DAC_InitStructure.DAC_LFSRUnmask_TriangleAmplitude = DAC_LFSRUnmask_Bits11_0;
    DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable;   // 使能输出缓冲
    DAC_Init(DAC_Channel_1, &DAC_InitStructure);
    
    // 使能DAC1
    DAC_Cmd(DAC_Channel_1, ENABLE);
}

void Set_DAC1_Value(uint16_t value) {
    // 设置DAC1输出值(0-4095)
    DAC_SetChannel1Data(DAC_Align_12b_R, value);
}
7.2.2 DAC 波形生成

使用 DAC 和定时器生成正弦波:

#include "stm32f10x.h"
#include 

#define SINE_POINTS  32     // 正弦波采样点数
#define PI           3.14159265358979f

uint16_t Sine_Table[SINE_POINTS];

void DAC_Configuration(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    DAC_InitTypeDef DAC_InitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    DMA_InitTypeDef DMA_InitStructure;
    
    // 使能GPIOA、DAC和TIM6时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC | RCC_APB1Periph_TIM6, ENABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
    
    // 配置PA4为模拟输入模式(DAC1输出)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    // 生成正弦波数据表
    for (int i = 0; i < SINE_POINTS; i++) {
        // 生成0-3.3V范围内的正弦波值(0-4095)
        Sine_Table[i] = (uint16_t)(2047 + 2047 * sin(2.0f * PI * i / SINE_POINTS));
    }
    
    // 配置TIM6(用于触发DAC)
    TIM_TimeBaseStructure.TIM_Period = 900;            // 调整波形频率
    TIM_TimeBaseStructure.TIM_Prescaler = 0;
    TIM_TimeBaseStructure.TIM_ClockDivision = 0;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeInit(TIM6, &TIM_TimeBaseStructure);
    
    // 设置TIM6为触发模式
    TIM_SelectOutputTrigger(TIM6, TIM_TRGOSource_Update);
    
    // 配置DMA1通道2
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&DAC->DHR12R1;
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)Sine_Table;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
    DMA_InitStructure.DMA_BufferSize = SINE_POINTS;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(DMA1_Channel2, &DMA_InitStructure);
    
    // 使能DMA1通道2
    DMA_Cmd(DMA1_Channel2, ENABLE);
    
    // DAC1配置
    DAC_InitStructure.DAC_Trigger = DAC_Trigger_T6_TRGO;  // TIM6触发
    DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None;
    DAC_InitStructure.DAC_LFSRUnmask_TriangleAmplitude = DAC_LFSRUnmask_Bits11_0;
    DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable;
    DAC_Init(DAC_Channel_1, &DAC_InitStructure);
    
    // 使能DAC1的DMA
    DAC_DMACmd(DAC_Channel_1, ENABLE);
    
    // 使能DAC1
    DAC_Cmd(DAC_Channel_1, ENABLE);
    
    // 启动TIM6
    TIM_Cmd(TIM6, ENABLE);
}

7.3 ADC 与 DAC 综合应用:音频信号采集与回放

下面是一个结合 ADC 和 DAC 的实例,实现简单的音频信号采集与回放系统:

#include "stm32f10x.h"
#include 

#define BUFFER_SIZE  1024   // 缓冲区大小
#define SAMPLE_RATE  8000   // 采样率 (Hz)

uint16_t ADC_Buffer[BUFFER_SIZE];  // ADC采样缓冲区
uint16_t DAC_Buffer[BUFFER_SIZE];  // DAC输出缓冲区
uint8_t Buffer_Index = 0;          // 缓冲区索引
uint8_t Is_Recording = 0;          // 录音状态
uint8_t Is_Playing = 0;            // 播放状态

void SystemInit(void);
void GPIO_Configuration(void);
void ADC_Configuration(void);
void DAC_Configuration(void);
void TIM_Configuration(void);
void DMA_Configuration(void);
void USART_Configuration(void);
void NVIC_Configuration(void);

int main(void) {
    // 系统初始化
    SystemInit();
    
    // 外设配置
    GPIO_Configuration();
    USART_Configuration();
    NVIC_Configuration();
    TIM_Configuration();
    ADC_Configuration();
    DAC_Configuration();
    DMA_Configuration();
    
    // 启动定时器(触发ADC采样)
    TIM_Cmd(TIM2, ENABLE);
    
    printf("Audio Capture & Playback System\r\n");
    printf("Type 'r' to start recording\r\n");
    printf("Type 's' to stop recording\r\n");
    printf("Type 'p' to start playback\r\n");
    
    while (1) {
        // 主循环处理用户命令
        if (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET) {
            char cmd = USART_ReceiveData(USART1);
            
            switch (cmd) {
                case 'r':  // 开始录音
                    Is_Recording = 1;
                    Is_Playing = 0;
                    Buffer_Index = 0;
                    printf("Recording started...\r\n");
                    break;
                    
                case 's':  // 停止录音
                    Is_Recording = 0;
                    printf("Recording stopped.\r\n");
                    break;
                    
                case 'p':  // 开始播放
                    if (!Is_Recording) {
                        Is_Playing = 1;
                        Buffer_Index = 0;
                        printf("Playback started...\r\n");
                        
                        // 启动DAC DMA
                        DMA_Cmd(DMA1_Channel3, ENABLE);
                    } else {
                        printf("Cannot play while recording!\r\n");
                    }
                    break;
                    
                default:
                    printf("Unknown command: %c\r\n", cmd);
                    printf("Valid commands: r, s, p\r\n");
                    break;
            }
        }
    }
}

// TIM2中断处理函数(ADC采样触发)
void TIM2_IRQHandler(void) {
    if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
        
        // 如果正在录音,将ADC值存入缓冲区
        if (Is_Recording) {
            ADC_Buffer[Buffer_Index] = ADC_GetConversionValue(ADC1);
            
            // 简单音量调整(除以2)
            DAC_Buffer[Buffer_Index] = ADC_Buffer[Buffer_Index] / 2;
            
            Buffer_Index++;
            if (Buffer_Index >= BUFFER_SIZE) {
                Buffer_Index = 0;
                printf("Buffer full, looping...\r\n");
            }
        }
    }
}

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

7.4 应用注意事项

  1. 参考电压:ADC 和 DAC 的参考电压通常为 VREF+(一般连接到 3.3V),决定了转换的满量程范围。

  2. 采样率:ADC 的最大采样率为 1MHz,实际应用中需根据系统时钟和预分频配置合理设置。

  3. 抗干扰:模拟信号输入端建议添加滤波电容,减少噪声干扰。

  4. 电源管理:模拟电路部分建议使用独立电源或 LC 滤波,避免数字电路噪声影响。

  5. DMA 优化:对于大数据量的采集和输出,建议使用 DMA 以减少 CPU 负担。

  6. 分辨率考虑:12 位分辨率对应 4096 个量化级别,输入信号范围需匹配 ADC 量程。

通过合理配置 ADC 和 DAC,STM32F103C8T6 可以实现各种模拟信号处理应用,如传感器数据采集、波形生成、音频处理等。

你可能感兴趣的:(C,单片机,stm32,嵌入式硬件)