STM32F103C8T6 单片机开发指南
STM32F103C8T6 是意法半导体(STMicroelectronics)推出的一款基于 ARM Cortex-M3 内核的 32 位微控制器,属于 STM32F1 系列("增强型" 产品线)。它以高性能、低成本和丰富的外设资源著称,广泛应用于工业控制、消费电子、物联网等领域。
一、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 为例:
-
下载并安装 STM32CubeIDE
- 从 ST 官网下载最新版本的 STM32CubeIDE
- 按照安装向导完成安装
-
创建新项目
- 打开 STM32CubeIDE,选择 File > New > STM32 Project
- 在 Board Selector 中选择对应开发板(如 STM32F103C8Tx)
- 点击 Next,设置项目名称和路径,点击 Finish
-
配置外设
- 打开 *.ioc 文件,进入 Pinout & Configuration 视图
- 在 System Core 中配置时钟、调试接口等
- 配置所需外设(如 GPIO、USART、SPI 等)
-
生成代码
- 点击 Project > Generate Code
- 选择覆盖现有文件(首次生成)或合并更改
-
编写应用代码
- 在生成的代码基础上添加应用逻辑
- 编译并下载程序到开发板
三、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 <stdio.h>
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 <stdio.h>
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 <math.h>
#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 <stdio.h>
#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 应用注意事项
-
参考电压:ADC 和 DAC 的参考电压通常为 VREF+(一般连接到 3.3V),决定了转换的满量程范围。
-
采样率:ADC 的最大采样率为 1MHz,实际应用中需根据系统时钟和预分频配置合理设置。
-
抗干扰:模拟信号输入端建议添加滤波电容,减少噪声干扰。
-
电源管理:模拟电路部分建议使用独立电源或 LC 滤波,避免数字电路噪声影响。
-
DMA 优化:对于大数据量的采集和输出,建议使用 DMA 以减少 CPU 负担。
-
分辨率考虑:12 位分辨率对应 4096 个量化级别,输入信号范围需匹配 ADC 量程。
通过合理配置 ADC 和 DAC,STM32F103C8T6 可以实现各种模拟信号处理应用,如传感器数据采集、波形生成、音频处理等。
更多推荐



所有评论(0)