1.添加基础库
stm 官网下载地址:https://doc.embedfire.com/products/link/zh/latest/mcu/stm32/stm32f103c8t6_core_new1.html
项目参数配置:
Preprocessor Symbols Define 设置参数:
USE_STDPERIPH_DRIVER,USE_STM3210C_EVAL
#需要添加的文件:
mcu 配置:startup_stm32f10x_hd.s
core 配置:Libraries\CMSIS\CM3\CoreSupport 所有文件
inc配置:Libraries\STM32F10x_StdPeriph_Driver\inc 文件夹所有文件
src配置:Libraries\STM32F10x_StdPeriph_Driver\src 文件夹所有文件
eval 配置: Utilities\STM32_EVAL\STM3210C_EVAL 文件夹所有文件
fonts 配置:Utilities\STM32_EVAL\Common\fonts.h 文件
项目配置:Project\STM32F10x_StdPeriph_Examples\SysTick\TimeBase 文件夹下 【stm32f10x_conf.h,stm32f10x_it.c,stm32f10x_it.h,system_stm32f10x.c】
源码:
c
#include "stm32f10x.h"
#include "stm32_eval.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_it.h"
#include "stm32_eval.h"
#include "stm32f10x_rcc.h"
//当使用存储器到存储器模式时候,通道可以随便选,没有硬性的规定
#define DMA_CHANNEL DMA1_Channel6
#define DMA_CLOCK RCC_AHBPeriph_DMA1
//传输完成标志
#define DMA_FLAG_TC DMA1_FLAG_TC6
//要发送的数据大小
#define BUFFER_SIZE 32
#define DEBUG_USARTx USART1
#define DEBUG_USART_CLK RCC_APB2Periph_USART1
#define DEBUG_USART_APBxClkCmd RCC_APB2PeriphClockCmd
#define DEBUG_USART_BAUDRATE 115200
//usart gpio 引脚定义
#define DEBUG_USART_GPIO_CLK (RCC_APB2Periph_GPIOA)
#define DEBUG_USART_GPIO_APBxClkCmd RCC_APB2PeriphClockCmd
#define DEBUG_USART_TX_GPIO_PROT GPIOA
#define DEBUG_USART_TX_GPIO_PIN GPIO_Pin_9
#define DEBUG_USART_RX_GPIO_PORT GPIOA
#define DEBUG_USART_RX_GPIO_PIN GPIO_Pin_10
//串口对应的DMA请求通带
#define USART_TX_DMA_CHANNEL DMA1_Channel4
//外设寄存器地址
#define USART_DR_ADDRESS (USART1_BASE+0x04)
//一次发送的数据量
#define SENDBUF_SIZE 5000
//定义DMA传输目标存储器 存储器在内部的SRAM中
uint32_t aDST_BUffer[BUFFER_SIZE];
const uint32_t aSRC_Const_Buffer[BUFFER_SIZE] =
{
0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10,
0x11121314,0x15161718,0x191A1B1C,0x1D1E1F20,
0x21222324,0x25262728,0x292A2B2C,0x2D2E2F30,
0x31323334,0x35363738,0x393A3B3C,0x3D3E3F40,
0x41424344,0x45464748,0x494A4B4C,0x4D4E4F50,
0x51525354,0x55565758,0x595A5B5C,0x5D5E5F60,
0x61626364,0x65666768,0x696A6B6C,0x6D6E6F70,
0x71727374,0x75767778,0x797A7B7C,0x7D7E7F80
};
void DMA_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
//开启DMA时钟
RCC_AHBPeriphClockCmd(DMA_CLOCK,ENABLE);
//源数据地址
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) aSRC_Const_Buffer;
//目标地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t) aDST_BUffer;
//方向:外设到存储器(这里的外设时内部FLASH)
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
//传输大小
DMA_InitStructure.DMA_BufferSize = BUFFER_SIZE;
//外设(内部的Flash) 地址递增
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
//内存地址递增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
//外设数据单位
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
//内存数据单位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
//DMA 模式,一次或者循环模式
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ;
//优先级:高
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
//使能内存到内存的传输
DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;
//配置DMA通道
DMA_Init(DMA_CHANNEL,&DMA_InitStructure);
//使能DMA
DMA_Cmd(DMA_CHANNEL,ENABLE);
}
uint8_t Buffercmp(const uint32_t* pBuffer,uint32_t* pBuffer1,uint16_t BufferLength)
{
//数据长度递减
while(BufferLength --)
{
//判断两个数据源是否对应相等
if(*pBuffer != *pBuffer1)
{
//对应数据源不相等,满上退出函数,返回0
return 0;
}
//递增两个数据源的地址指针
pBuffer ++ ;
pBuffer1 ++;
}
//完成判断并且对应数据相对
return 1;
}
void USARTx_DMA_Config(void)
{
DMA_InitTypeDef DMA_InitStucture;
//开启DMA时钟
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
//设置DMA源地址:串口数据寄存器地址
DMA_InitStucture.DMA_PeripheralBaseAddr = USART_DR_ADDRESS;
//内存地址(要传输的变量的指针)
// DMA_InitStucture.DMA_MemoryBaseAddr = (u32) Send;
}
void Delay(__IO uint32_t nCount) //简单的延时函数
{
for (; nCount != 0; nCount--);
}
/**
* 微妙级延时(适配任意主频,依赖SystemCoreClock 全局变量)
* @parems us 延时的微妙数
*/
void delay_us(uint32_t us)
{
SysTick ->LOAD = (SystemCoreClock / 1000000) * us -1; // 设置重载值,72MHz 下1us 计数72次
SysTick ->VAL = 0x00; //清空当前计数值
SysTick ->CTRL = SysTick_CTRL_ENABLE_Msk | SysTick_CTRL_CLKSOURCE_Msk; //使能SysTick 时
while(!(SysTick ->CTRL & SysTick_CTRL_COUNTFLAG_Msk)); //等待计数完成
SysTick ->CTRL = 0x00; //关闭SysTick
}
void LED_GPIO_Config(void)
{
/*定义一个GPIO_InitTypeDef 类型的结构体*/
GPIO_InitTypeDef GPIO_InitSture;
/*开启LED相关的GPIO外设时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
/*选择要控制的GPIO引脚*/
GPIO_InitSture.GPIO_Pin = GPIO_Pin_1| GPIO_Pin_2 | GPIO_Pin_3;
/*选择引脚模式为通用推挽输出*/
GPIO_InitSture.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitSture.GPIO_Speed = GPIO_Speed_50MHz;
/*设置引脚的速率为50HZ*/
GPIO_Init(GPIOA,&GPIO_InitSture);
GPIO_SetBits(GPIOA,GPIO_Pin_1| GPIO_Pin_2 | GPIO_Pin_3);
}
int main(void)
{
// SystemInit();
//定义存放比较结果变量
uint8_t TransferStatus;
//LED 端口初始化
LED_GPIO_Config();
//设置蓝灯亮
GPIO_ResetBits(GPIOA,GPIO_Pin_3);
//设置延时函数
delay_us(5000000);
//DMA 传输配置
DMA_Config();
//等待DMA传输完成
while(DMA_GetFlagStatus(DMA_FLAG_TC) == RESET) {}
//比较源数据与传输后的数据
TransferStatus = Buffercmp(aSRC_Const_Buffer,aDST_BUffer,BUFFER_SIZE);
//判断源数据与传输后数据比较结果
if(TransferStatus == 0)
{
//源数据与传输后数据不相等的RGB彩色灯显示红色
GPIO_ResetBits(GPIOA,GPIO_Pin_1);
}
else
{
//源数据与传输后数据不相等的RGB彩色灯显示绿色
GPIO_ResetBits(GPIOA,GPIO_Pin_2);
}
while(1){
}
}
参考文档:
https://doc.embedfire.com/mcu/stm32/f103zhinanzhe/std/zh/latest/book/DMA.html
实现光敏电阻采集,OLED 屏幕显示,串口输出当前的光敏电阻值
OLED 显示,参考网站:
https://github.com/WDWD5875/OLED-Mpu6050-DMP-STM32F103C8T6/tree/main
OLED 引脚相关的内容:
| 名称 | 说明 |
|---|---|
| VCC | 供电 3.3V |
| GND | 地线 |
| SCL | IIC时钟引脚 |
| SDA | IIC数据引脚 |
数据手册内容:

光敏电阻引脚:
| 名称 | 说明 |
|---|---|
| VCC | 供电 3.3V |
| GND | 地线 |
| DO | 数字量数据 |
| AO | 模拟量数据 |
c
#include "stm32f10x.h"
#include <stdio.h>
#include <string.h>
#include "oled_ssd1306.h"
#include "mpu6050_dmp.h"
#include "mpu_port_i2c.h"
/*======================= 参数配置 =======================*/
#define ADC_BUF_SIZE 256 // ADC DMA 缓冲区大小(256点取平均,数据更稳)
#define SERIAL_BAUDRATE 115200 // 串口波特率
#define R_DIVIDER 10000.0f// 分压电阻阻值 10KΩ
#define ADC_VREF 3.3f // ADC 参考电压
/*======================= 全局变量 =======================*/
uint16_t adc_dma_buf[ADC_BUF_SIZE]; // ADC DMA 硬件缓冲区
volatile uint8_t adc_new_data = 0; // 新数据就绪标志
volatile uint16_t adc_avg_val = 0; // 滤波后的ADC平均值
/*======================= 简易延时 =======================*/
void delay_us(uint32_t us)
{
uint32_t temp;
SysTick->LOAD = SystemCoreClock / 8 / 1000000 * us - 1;
SysTick->VAL = 0;
SysTick->CTRL |= 0x01;
do {
temp = SysTick->CTRL;
} while((temp & 0x01) && !(temp & (1<<16)));
SysTick->CTRL &= ~0x01;
SysTick->VAL = 0;
}
void delay_ms(uint32_t ms) { while(ms--) delay_us(1000); }
/*======================= 重定向 printf 到串口 =======================*/
// 重写 fputc,让 printf 直接输出到串口
int fputc(int ch, FILE *f)
{
while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
USART_SendData(USART1, (uint8_t)ch);
return ch;
}
/*======================= USART 初始化 =======================*/
void USART_Init_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, 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);
// 串口参数:115200 8N1
USART_InitStructure.USART_BaudRate = SERIAL_BAUDRATE;
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_Mode_Rx;
USART_Init(USART1, &USART_InitStructure);
USART_Cmd(USART1, ENABLE);
}
/*======================= ADC + DMA 初始化 =======================*/
void ADC_DMA_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
uint32_t timeout; // 超时计数,防止死循环
// 开时钟(注意顺序:先开DMA时钟,再开ADC时钟)
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);
delay_ms(10);
// DMA1 通道1 -> ADC1(循环模式)
DMA_DeInit(DMA1_Channel1);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)adc_dma_buf;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = ADC_BUF_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);
// 先关闭所有DMA中断,清除所有标志
DMA_ITConfig(DMA1_Channel1, DMA_IT_TC | DMA_IT_HT | DMA_IT_TE, DISABLE);
DMA_ClearITPendingBit(DMA1_IT_TC1 | DMA1_IT_HT1 | DMA1_IT_TE1);
DMA_Cmd(DMA1_Channel1, ENABLE);
// ADC配置:12位 连续转换
RCC_ADCCLKConfig(RCC_PCLK2_Div6); // ADC时钟 72M/6 = 12MHz
ADC_DeInit(ADC1);
delay_ms(5);
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);
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
// ===== 关键修复2:ADC上电,等待稳定后再校准 =====
ADC_Cmd(ADC1, ENABLE);
delay_ms(5); // 必须等ADC上电稳定(至少tSTAB = 1us,这里给5ms足够)
// ADC校准
ADC_ResetCalibration(ADC1);
timeout = 100000;
while(ADC_GetResetCalibrationStatus(ADC1))
{
timeout--;
if(timeout == 0) break; // 超时直接退出,不再死等
};
// 开始校准
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1))
{
timeout--;
if(timeout == 0) break; // 超时直接退出
}
// ===== 关键修复4:校准完成后,再开启DMA和软件触发 =====
ADC_DMACmd(ADC1, ENABLE);
// 清除所有ADC标志位
ADC_ClearFlag(ADC1, ADC_FLAG_EOC | ADC_FLAG_JEOC | ADC_FLAG_AWD);
// 开DMA传输完成中断
DMA_ITConfig(DMA1_Channel1, DMA_IT_TC, ENABLE);
// ===== 补上这三行 =====
ADC_Cmd(ADC1, ENABLE);
delay_ms(2);
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
// ======================
// DMA中断配置
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
/*======================= DMA 中断:均值滤波 =======================*/
void DMA1_Channel1_IRQHandler(void)
{
uint32_t sum = 0;
uint16_t i;
if(DMA_GetITStatus(DMA1_IT_TE1)) DMA_ClearITPendingBit(DMA1_IT_TE1);
if(DMA_GetITStatus(DMA1_IT_HT1)) DMA_ClearITPendingBit(DMA1_IT_HT1);
if(DMA_GetITStatus(DMA1_IT_TC1)) {
DMA_ClearITPendingBit(DMA1_IT_TC1);
// 256个点取平均,消除工频噪声
for(i = 0; i < ADC_BUF_SIZE; i++) {
sum += adc_dma_buf[i];
}
adc_avg_val = (uint16_t)(sum / ADC_BUF_SIZE);
adc_new_data = 1; // 标记有新数据
}
}
/**
* @brief uint16_t整数转字符串
* @param num: 要转换的数字
* @param str: 输出的字符串缓存
* @retval 字符串长度
*/
uint8_t Num2Str(uint16_t num, char *str)
{
uint8_t i = 0, j = 0;
char tmp[6]; // 最大支持65535,共5位数字
if(num == 0) {
str[0] = '0';
str[1] = '\0';
return 1;
}
// 逐位取数字(倒序)
while(num > 0) {
tmp[i++] = num % 10 + '0';
num /= 10;
}
// 反转成正序
for(j = 0; j < i; j++) {
str[j] = tmp[i - 1 - j];
}
str[i] = '\0'; // 字符串结束符
return i;
}
/**
* @brief float转字符串(保留2位小数,自动四舍五入)
* @param f: 要转换的浮点数(支持0~999范围,足够光敏/电压用)
* @param str: 输出字符串数组,建议长度≥8
*/
void float2str(float f, char *str)
{
uint8_t i = 0;
uint16_t integer_part; // 整数部分
uint16_t decimal_part; // 小数部分
// 1. 拆分整数和小数(+0.5是四舍五入)
integer_part = (uint16_t)f;
decimal_part = (uint16_t)((f - integer_part) * 100 + 0.5f);
// 处理小数进位(比如1.995+0.5=2.00的情况)
if(decimal_part >= 100) {
integer_part += 1;
decimal_part -= 100;
}
// 2. 整数部分转字符串(倒序存,再反转)
char tmp[8];
uint8_t len = 0;
if(integer_part == 0) {
tmp[len++] = '0';
} else {
while(integer_part > 0) {
tmp[len++] = integer_part % 10 + '0';
integer_part /= 10;
}
}
// 反转整数部分到str
for(i = 0; i < len; i++) {
str[i] = tmp[len - 1 - i];
}
str[len++] = '.'; // 加小数点
// 3. 小数部分转字符串(固定2位,不足补0)
str[len++] = decimal_part / 10 + '0'; // 十分位
str[len++] = decimal_part % 10 + '0'; // 百分位
str[len] = '\0'; // 必须加字符串结束符
}
/*======================= 主函数 =======================*/
int main(void)
{
float voltage; // PA0 电压值
float r_light; // 光敏电阻阻值
uint8_t brightness; // 亮度百分比
char str_buf[16]; // 显示用的字符串缓存
SystemInit();
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
USART_Init_Config(); // 初始化串口
ADC_DMA_Init(); // 初始化ADC+DMA
oled_init();
mpu6050_dmp_i2c_init();
oled_clear();
oled_show_string(1, 1, "OLED OK"); // 先验证 OLED 本身没问题
// 开机提示
printf(" STM32 Light Sensor Demo \r\n");
while(1)
{
if(adc_new_data) // 有新的ADC数据
{
adc_new_data = 0;
// 1. 计算电压值
voltage = (float)adc_avg_val * ADC_VREF / 4095.0f;
// 2. 计算光敏电阻阻值(分压公式反推)
// 分压电路:3.3V → R_light → PA0 → R_divider(10K) → GND
// Vadc = 3.3 × R_divider / (R_light + R_divider)
// 反推:R_light = R_divider × (3.3 / Vadc - 1)
if(voltage < 0.01f) {
r_light = 999999.0f; // 电压过低,阻值超大(完全黑暗)
} else {
r_light = R_DIVIDER * (ADC_VREF / voltage - 1.0f);
}
// 3. 计算亮度百分比(0=全黑,100=最亮)
// 以3.3V为满量程估算,实际可根据光敏电阻型号校准
brightness = (uint8_t)((float)adc_avg_val * 100 / 4095);
float2str(r_light, str_buf);
oled_show_string(1, 1, "Light Sendor:");
oled_show_string(2, 1, str_buf);
printf("light: %.1f\r\n",r_light);
// 4. 串口输出(格式化显示)
// printf("ADC value: %4d | voltage: %.3f V | resistance: %.1f KΩ | brightness: %3d %% \r\n",
// adc_avg_val, voltage, r_light / 1000.0f, brightness);
}
delay_us(50); // 1秒输出一次,可自行调整刷新频率
}
}