目录&索引
前言
本篇为 STM32F103 学习,基于秋招已经结束,然后项目涉及 IMU 使用,巩固基础,故趁此机会学习单片机。
历时一个月,查漏补缺,主要内容如下:
- 硬件:ST-LINK、USB-TTL、button、电位器、LED、单片机最小系统、若干杜邦线等
- 准备材料,包括硬件及软件
- 单片机系统介绍
- keil 软件介绍
- 数据手册等芯片手册介绍
- STM32 启动文件介绍
- 库函数工程模板建立
- 按键检测
- 中断
- 定时器定时功能
- 串口发送接收程序
- PWM
- 定时器输入捕获
- ADC 实验
第一章 前期工作准备
软件获取
- 编程:KEIL MDK5 / IAR
- 不建议 proteus 仿真软件,建议使用硬件验证代码
- 下载软件以及驱动
包含 USB 串口下载软件及驱动、ST-LINK 固件升级软件及驱动、MDK5及芯片包
STM32 资料
原理图(重要)、数据手册( 重要)、参考手册(重要)、尺寸图、固件库手册、Cortex-M3 权威指南、ST MCU 选型手册
相关下载
数据手册下载,ST 官网
MDK5 下载,keil 官网
开发者社区,STM32 社区官网
硬件准备
包含 ST-LINK、USB-TTL、button、电位器、LED、单片机最小系统、若干杜邦线等,其中单片机最小系统、ST-LINK、若干杜邦线
第二章 单片机系统介绍
熟悉最小系统 STM32F103C8T6 原理图:ST-LINK 下载电路、USB 供电口(不能用于程序下载,USB 转 TTL 可下载)、启动电路(跳线帽设置高低电平)、指示灯、稳压芯片(背面)、两个晶振(8M,32.768k)
数据手册熟悉,所含设备概述、时钟树、引脚、存储映射等
第三章 库函数工程模板建立
第一步,下载固件库,文件分类
- CORE
- core_cm3.c
- core_cm3.h
- stm32f10x.h
- system_stm32f10x.c
- system_stm32f10x.h
- startup_stm32f10x_md.s(根据 flash 容量,对应启动文件)
- stm32f10x_conf.h(工程中找到)
- STM32F10x_StdPeriph_Driver(标准外设库,含 .c 和 .h)
- USER
- main.c
第二步,打开 mdk5 创建工程
自定义文件夹,这里初步分为:
USER
STM32F10x_StdPeriph_Driver
CORE
startup
添加文件
添加头文件路径
Crete HEX File 打勾,用于串口下载
添加 main 主函数,编译报错,warning: #223-D: function “assert_param”,设置 define USE_STDPERIPH_DRIVER
报错找不到 stm32f10x_conf.h,工程中找到并添加
其他报错,按报错信息逐一解决即可
第三步,连接 ST-LINK,配置,下载 LED 点亮程序
-
根据原理图,板载 LED 在 GPIOC13 引脚,低电平点亮
-
编写程序,GPIOC 使能,初始化 GPIOC 结构体,写入高低电平
-
连接 ST-LINK,配置 ST-LINK 下载器,选择 SW,打勾 Reset and Run
-
编译、链接,下载程序
// main.c 点亮板载 LED 示例
#include "stm32f10x.h"
int main(void) {
GPIO_InitTypeDef GPIO_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStruct);
GPIO_ResetBits(GPIOC, GPIO_Pin_13); // 函数一点亮
// GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_RESET); // 函数二点亮
while(1);
}
第四章 启动文件
启动文件过程:
This module performs:
Set the initial SP
Set the initial PC == Reset_Handler
Set the vector table entries with the exceptions ISR address
Configure the clock system
Branches to __main in the C library (which eventually calls main())
arm 启动文件选择:
flash 容量 <= 32k 选择 ld
64k<= flash 容量 <=128k 选择 md
256<= flash 容量 <=512k 选择 hd
值得注意的是,
启动文件作用之一:建立中断服务入口地址,即把中断向量与中断服务函数链接起来
启动文件作用之二:SystemInit,初始化时钟
对于 STM32 我们定义系统时钟的时候直接在 system_stm3210x.c 文件里修改宏定义即可,而事实上到底是从哪开始执行的呢?system_stm3210x.c 文件里有个 SystemInit() 函数,就是对时钟的设置。
而这个 SystemInit() 在哪调用的呢,就是启动文件先调用了,然后才进入到 mian() 函数。
第五章 时钟
问:“我们一直都说 STM32 有一个非常复杂的时钟系统,然而在原子或者野火的例程中,只要涉及到时钟,我们却只能看到类似的库函数调用,如 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); 这个仅仅只是起到开启挂载在 APB2 线上的 USART1 时钟的作用罢了,APB2 的时钟频率是多少我们并不知道”
答:我们的程序在进入到 main 函数之前,先要执行 SystemInit,跳转到这个函数的定义。里面的代码是对寄存器直接进行操作了,查找了用户手册,寄存器的相关配置说明写在了注释里面。这里涉及到了两个寄存器 RCC_CR,与 RCC_CFGR,分别是时钟控制寄存器与时钟配置寄存器,它们的作用顾名思义,就是起到了控制和配置时钟的作用
SystemInit(设置时钟)详细过程:
1、开启 HSI = 8M 作为系统时钟 SYSCLK
2、开启 HSE = 8M
3、等待 HSE 稳定
4、配置 HCLK(AHB) = SYSCLK,APB1 = HCLK / 2,APB2 = HCLK
5、配置 PLL(将 HSE 输入,同时进行 9 倍频),8M * 9 = 72M
6、等待 PLL 稳定
7、切换系统时钟 SYSCLK = PLL
见数据手册,如下图:
【Clock tree】
这个图说明了STM32的时钟走向,从图的左边开始,从时钟源一步步分配到外设时钟。
从时钟频率来说,又分为高速时钟和低速时钟,高速时钟是提供给芯片主体的主时钟,而低速时钟只是提供给芯片中的RTC(实时时钟)及独立看门狗使用。
从芯片角度来说,时钟源分为内部时钟与外部时钟源 ,内部时钟是在芯片内部RC振荡器产生的,起振较快,所以时钟在芯片刚上电的时候,默认使用内部高速时钟。而外部时钟信号是由外部的晶振输入的,在精度和稳定性上都有很大优势,所以上电之后我们再通过软件配置,转而采用外部时钟信号。
时钟源:
高速外部时钟(HSE):以外部晶振作时钟源,晶振频率可取范围为 4~16MHz,我们一般采用 8MHz 的晶振
高速内部时钟(HSI): 由内部 RC 振荡器产生,频率为 8MHz,但不稳定。
低速外部时钟(LSE):以外部晶振作时钟源,主要提供给实时时钟模块,所以一般采用 32.768KHz
低速内部时钟(LSI):由内部 RC 振荡器产生,也主要提供给实时时钟模块,频率大约为 40KHz
时钟树:
SYSCLK 最大 72M
嘀嗒定时器最大 8M
HCLK(AHB) 最大 72M
APB1 最大 36M
APB2 最大 72M
其中,单独开启某个外设时钟目的:降低功耗
第六章 GPIO 与寄存器方法
见参考手册,
输入:浮空、上拉、下拉、模拟
输出:开漏、推挽、复用开漏、复用推挽
- 端口占用可复用到其他端口
- 使用其他外设时 GPIO 查表进行输入输出配置
- 例如由 BSRR 寄存器控制 ODR 寄存器,而不直接操作 ODR 寄存器,参考手册还是比较清楚的
// main.c 点亮、熄灭板载 LED 示例(寄存器方法)
/*
1,使能 GPIOC 时钟
2,配置 GPIOC 推挽输出
3,操作寄存器进行点亮和熄灭
*/
#include "stm32f10x.h"
void delay(uint32_t i) {
while (--i);
}
int main(void) {
RCC->APB2ENR |= 1 << 4;
GPIOC->CRH = 0x00300000; // 应当与、或运算配置
GPIOC->ODR |= 1 << 13;
while (1) {
GPIOC->BSRR = 0x20000000;
delay(2000000);
GPIOC->BSRR = 0x00002000;
delay(2000000);
}
}
第七章 串口下载
- 连线,TXD->RXD,RXD->TXD
- 打开 FlyMcu 串口下载工具,安装串口驱动
- BOOT0 高,BOOT1 低(更改启动方式,Flash memory 改为 System memory)
- hex 文件下载到开发板
第八章 库函数工程模板(led)
- 代码规范,如 led、button 等放在 APP 文件夹
- MDK 头文件路径添加
- 实现板载 LED 点亮与熄灭
// main.c 点亮、熄灭板载 LED 示例
#include "stm32f10x.h"
#include "led.h"
void delay(uint32_t i) {
while (--i);
}
int main(void) {
led_init();
while (1) {
GPIO_SetBits(GPIOC, GPIO_Pin_13);
delay(1000000);
GPIO_ResetBits(GPIOC, GPIO_Pin_13);
delay(1000000);
}
}
// led.h
#ifndef __led_H
#define __led_H
#include "stm32f10x.h"
void led_init(void);
#endif
// led.c
#include "led.h"
void led_init(void) {
GPIO_InitTypeDef GPIO_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStruct);
GPIO_SetBits(GPIOC, GPIO_Pin_13);
}
第九章 库函数工程模板(button)
- 实现 button 按下,输入低电平 led 亮
// main.c button 按下点亮、松开熄灭板载 LED 示例
#include "stm32f10x.h"
#include "led.h"
#include "button.h"
void delay(uint32_t i) {
while (--i);
}
int main(void) {
led_init();
button_init();
while (1) {
button_query();
}
}
// led.c
#include "led.h"
void led_init(void) {
GPIO_InitTypeDef GPIO_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStruct);
GPIO_SetBits(GPIOC, GPIO_Pin_13);
}
void led_light(void) {
GPIO_ResetBits(GPIOC, GPIO_Pin_13);
}
void led_close(void) {
GPIO_SetBits(GPIOC, GPIO_Pin_13);
}
// led.h
#ifndef __led_H
#define __led_H
#include "stm32f10x.h"
void led_init(void);
void led_light(void);
void led_close(void);
#endif
// button.c
#include "button.h"
#include "led.h"
void button_init(void) {
GPIO_InitTypeDef GPIO_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; // 输入时,速度不起作用
GPIO_Init(GPIOB, &GPIO_InitStruct);
}
int button_flag = 1;
void button_query(void) {
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_13) == Bit_RESET) {
button_flag = 0;
} else {
button_flag = 1;
}
button_operation_led(button_flag);
}
void button_operation_led(int i) {
switch (i) {
case 0:
led_light();
break;
case 1:
led_close();
break;
default:
break;
}
}
// button.h
#ifndef __button_H
#define __button_H
#include "stm32f10x.h"
void button_init(void);
void button_query(void);
void button_operation_led(int);
#endif
第十章 位带操作
位带操作原理
把每个比特膨胀(映射)为一个 32 位的字,当访问这些字的时候就达到了访问比特的目的,比如说 BSRR 寄存器有 32 个位,那么可以映射到 32 个地址上,我们去访问(读-改-写)这 32 个地址就达到访问 32 个比特的目的。而另一种方法若不支持位修改,则需要与、或操作,见第六章实现代码(寄存器方法)。
即如果要改写某个寄存器的某一位,通过改写这一位映射的地址即可。
本质,方便 C 语言编程与避免从寄存器读取、置位、写回(见汇编,位带操作少一步读取,即直接置位、写入),如多任务、中断情况下未写回情况发生冲突。编程中遵循最低位有效原则。
数据手册 Memory mapping 章节、参考手册 Memory and bus architecture 章节
- STM32 内存内容:程序内存、数据内存、寄存器和 I/O 口
// main.c 点亮板载 LED 示例(含位带操作)
#include "stm32f10x.h"
#include "bit_operation.h"
int main(void) {
GPIO_InitTypeDef GPIO_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStruct);
PCout(13) = 0; // 低电平点亮,置高电平熄灭
while (1);
}
// bit_operation.h 手写 GPIO 位带操作头文件
#ifndef __bit_operation_H
#define __bit_operation_H
#include "stm32f10x.h"
#define BITBAND(addr, bitnum) ((addr & 0xF0000000) + 0x2000000 + ((addr & 0xFFFFF) << 5) + (bitnum << 2))
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
#define GPIOA_ODR_Addr (GPIOA_BASE + 12) // 0x4001080C
#define GPIOB_ODR_Addr (GPIOB_BASE + 12) // 0x40010C0C
#define GPIOC_ODR_Addr (GPIOC_BASE + 12) // 0x4001100C
#define GPIOD_ODR_Addr (GPIOD_BASE + 12) // 0x4001140C
#define GPIOE_ODR_Addr (GPIOE_BASE + 12) // 0x4001180C
#define GPIOF_ODR_Addr (GPIOF_BASE + 12) // 0x40011A0C
#define GPIOG_ODR_Addr (GPIOG_BASE + 12) // 0x40011E0C
#define GPIOA_IDR_Addr (GPIOA_BASE + 8) // 0x40010808
#define GPIOB_IDR_Addr (GPIOB_BASE + 8) // 0x40010C08
#define GPIOC_IDR_Addr (GPIOC_BASE + 8) // 0x40011008
#define PAout(n) BIT_ADDR(GPIOA_ODR_Addr, n) // 输出
#define PAin(n) BIT_ADDR(GPIOA_IDR_Addr, n) // 输入
#define PBout(n) BIT_ADDR(GPIOB_ODR_Addr, n) // 输出
#define PBin(n) BIT_ADDR(GPIOB_IDR_Addr, n) // 输入
#define PCout(n) BIT_ADDR(GPIOC_ODR_Addr, n) // 输出
#define PCin(n) BIT_ADDR(GPIOC_IDR_Addr, n) // 输入
#endif
第十一章 SysTick 定时器
SysTick 的相关寄存器
校准寄存器(STK_CALIB)
当前值寄存器(STK_VAL):24 位倒计数定时器
重装载值寄存器(STK_LOAD):计到 0 时,从 RELOAD 寄存器自动装载定时初值
控制及状态寄存器(STK_CTRL):使能位(第 0 位),不清除不停息;计到 0 时,COUNTFLAG 位(第 16 位)置位,两种方法清除——读取该寄存器、往当前值寄存器写任何数据;中断位(第 1 位)
对比,使用意义
软件延时,占用 CPU 资源
STM32 定时器,耗费外设资源
// main.c 点亮、熄灭板载 LED SysTick 示例
#include "stm32f10x.h"
#include "led.h" // led 见第八章
#include "bit_operation.h"
#include "delay.h"
int main(void) {
led_init();
delay_init();
while (1) {
PCout(13) = 0;
delay_ms(500);
PCout(13) = 1;
delay_ms(500);
}
}
// delay.c
#include "delay.h"
static unsigned int fac_us;
static unsigned int fac_ms;
void delay_init(void) {
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); // 24 位计数,8 分频提高定时上限
fac_us = SystemCoreClock / 8000000;
fac_ms = (unsigned int)fac_us * 1000;
}
void delay_us(unsigned int xus) { // 上限约 1.864s
unsigned int temp;
if ((xus <= 0 || (xus > 1864135))) return ;
SysTick->LOAD = (unsigned int)xus * fac_us; // 时间加载
SysTick->VAL = 0x00; // 清空计数器
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk ; // 开始倒数
do {
temp = SysTick->CTRL;
} while((temp & 0x01) && !(temp & (1 << 16))); // 等待时间到达
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; // 关闭计数器
SysTick->VAL = 0X00; // 清空计数器
}
void delay_ms(unsigned int xms) {
unsigned int temp;
if ((xms <= 0 || (xms > 1864))) return ;
SysTick->LOAD = (unsigned int)xms * fac_ms;
SysTick->VAL = 0x00;
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk ;
do {
temp = SysTick->CTRL;
} while((temp & 0x01) && !(temp & (1 << 16)));
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
SysTick->VAL = 0X00;
}
// delay.h
#ifndef __delay_H
#define __delay_H
#include "stm32f10x.h"
void delay_init(void);
void delay_us(unsigned int);
void delay_ms(unsigned int);
#endif
第十二章 中断
中断系统详解,见参考手册。外部按键中断,步骤:
1、中断优先级分组
2、开启相应(示例为 GPIOB、AFIO)时钟
3、初始化 GPIO 结构体
4、配置 IO 口与中断线的映射关系,GPIO 与 EXTI
5、EXTI、NVIC 结构体,线上中断、设置触发条件等
6、编写中断服务函数
// main.c 外部中断示例,按键后亮 1s 熄灭
#include "stm32f10x.h"
#include "led.h" // led 见第八章
#include "button.h"
#include "bit_operation.h"
#include "delay.h"
int main(void) {
// 抢占式优先级 2 bit,响应式优先级 2 bit
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
delay_init();
led_init();
button_init();
while (1);
}
// button.c
#include "button.h"
#include "led.h"
#include "delay.h"
void button_init(void) {
GPIO_InitTypeDef GPIO_InitStruct;
EXTI_InitTypeDef EXTI_InitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); // 提出问题,什么情况需要使能 AFIO
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; // 输入时,速度不起作用
GPIO_Init(GPIOB, &GPIO_InitStruct);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource13);
EXTI_InitStruct.EXTI_Line = EXTI_Line13;
EXTI_InitStruct.EXTI_LineCmd = ENABLE;
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_Init(&EXTI_InitStruct);
NVIC_InitStruct.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;
NVIC_Init(&NVIC_InitStruct);
}
void EXTI15_10_IRQHandler(void) {
if (EXTI_GetITStatus(EXTI_Line13)) {
led_light();
} else {
led_close();
}
EXTI_ClearITPendingBit(EXTI_Line13);
delay_ms(1000);
led_close();
}
// button.h
#ifndef __button_H
#define __button_H
#include "stm32f10x.h"
void button_init(void);
void button_query(void);
void button_operation_led(int);
#endif
第十三章 定时器
能够实现的功能:
基本定时
输入捕获
输出比较
PWM 发生器
单脉冲模式输出:开关作用
中断/DMA 发生:
更新中断,即溢出中断
触发事件
输入捕获
输出比较
定时器单元中的寄存器包括:
计数器寄存器(TIMx_CNT):三种模式 16 位计数,默认向上计数
预分频寄存器(TIMx_PSC)
自动重载寄存器(TIMx_ARR)
定时器 TIM2 间隔 0.5s 点亮、熄灭,步骤:
1、中断优先级分组
2、选择时钟,内部时钟,考虑是否还需要倍频
3、开启 APB1 总线 TIM2 外设时钟
4、定时器结构体,设置自动重载寄存器的值、预分频值
5、NVIC 结构体,设置触发条件、优先级
6、配置更新中断
7、开启定时器中断
// main.c 定时器 TIM2 间隔 0.5s 点亮、熄灭板载 LED 示例
#include "stm32f10x.h"
#include "led.h"
#include "delay.h"
#include "timer.h"
int main(void) {
// 抢占式优先级 2 bit, 响应式优先级 2 bit
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
delay_init();
led_init();
timer_init(7200 - 1, 5000 - 1); // 72M / 7200 = 10K
while (1);
}
// timer.c
#include "stm32f10x.h"
#include "bit_operation.h"
#include "timer.h"
void timer_init(unsigned int pre, unsigned int arr) {
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1; // 根据数据手册时钟树, 36M * 2 = 72M
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period = arr;
TIM_TimeBaseInitStruct.TIM_Prescaler = pre;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStruct);
NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;
NVIC_Init(&NVIC_InitStruct);
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); // 配置更新中断
TIM_Cmd(TIM2, ENABLE); // 开启定时器
}
void TIM2_IRQHandler(void) {
if (TIM_GetITStatus(TIM2, TIM_IT_Update)) {
PCout(13) = !PCout(13);
}
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
// timer.h
#ifndef __timer_H
#define __timer_H
#include "stm32f10x.h"
void timer_init(unsigned int pre, unsigned int arr);
#endif
第十四章 串口 USART(一)
- 开始位、数据位、停止位、奇偶校验位,结合计网,即 frame 帧。并行转串行发送,串行转并行接收
- 波特率:1s 传输 bit 数,如 9600(蓝牙、Wifi),115200。Tx/Rx baud = fCK / (16 * USARTDIV)
- 时序图,持续时间需满足芯片要求
- USART 寄存器,见数据手册
串口接收数据点亮板载 LED,编程步骤:
1、中断优先级分组
2、开启 GPIO、USART1 时钟
3、设置 GPIO 结构体,TXD PA9、RXD PA10
4、设置串口结构体、中断结构体
5、开启串口中断
6、开启串口
7、编写串口中断函数
// main.c 串口接收数据点亮板载 LED 示例
#include "stm32f10x.h"
#include "led.h"
#include "usart.h"
int main() {
// 抢占式优先级 2 bit, 响应式优先级 2 bit
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
led_init();
usart_init(115200);
while (1);
}
// usart.c
#include "stm32f10x.h"
#include "usart.h"
#include "bit_operation.h"
void usart_init(unsigned int baud) {
GPIO_InitTypeDef GPIO_InitStruct;
USART_InitTypeDef USART_InitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 见第十一章, 外部中断需要 AFIO, 此处为什么不需要
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; // 提出问题, 试验后推挽也可以, 为什么(已解决, 见参考手册 GPIOs and AFIOs 章节)
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
USART_InitStruct.USART_BaudRate = baud;
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_InitStruct.USART_Parity = USART_Parity_No;
USART_InitStruct.USART_StopBits = USART_StopBits_1;
USART_InitStruct.USART_WordLength = USART_WordLength_8b;
USART_Init(USART1, &USART_InitStruct);
NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;
NVIC_Init(&NVIC_InitStruct);
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // 开启串口中断
USART_Cmd(USART1, ENABLE); // 开启串口
}
// 版本一
void USART1_IRQHandler(void) {
if (USART_GetITStatus(USART1, USART_IT_RXNE)) {
PCout(13) = 0;
}
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
#if 0
// 版本二
void USART1_IRQHandler(void) { // 接收'C'点亮, 其中 XCOM 发送新行(取消打勾)
unsigned int dat;
if (USART_GetITStatus(USART1, USART_IT_RXNE)) {
dat = USART_ReceiveData(USART1);
if (dat == 67) { // ASCII:C
PCout(13) = 0;
} else {
PCout(13) = 1;
}
}
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
#endif
#if 0
// 版本三
void USART1_IRQHandler(void) { // 发送数据 dat + 1 到 USART1
unsigned int dat;
if (USART_GetITStatus(USART1, USART_IT_RXNE)) {
dat = USART_ReceiveData(USART1);
if (dat == 67) { // ASCII:C
PCout(13) = 0;
} else {
PCout(13) = 1;
}
USART_SendData(USART1, dat + 1); // 回显 dat + 1
}
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
#endif
// usart.h
#ifndef __usart_H
#define __usart_H
#include "stm32f10x.h"
void usart_init(unsigned int);
#endif
第十五章 串口 USART(二)
串口 USART,字符串,结合换行符 0x0d 0x0a
// main.c 见 usart.c 中断函数、 usart.h 定义
#include "stm32f10x.h"
#include "led.h"
#include "usart.h"
int main() {
// 抢占式优先级 2 bit, 响应式优先级 2 bit
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
led_init();
usart_init(115200);
while (1);
}
// usart.c
#include "usart.h"
void usart_init(unsigned int baud) {
GPIO_InitTypeDef GPIO_InitStruct;
USART_InitTypeDef USART_InitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 见第十一章, 外部中断需要 AFIO, 此处为什么不需要
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; // 提出问题, 试验后推挽也可以, 为什么(已解决, 见参考手册 GPIOs and AFIOs 章节)
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
USART_InitStruct.USART_BaudRate = baud;
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_InitStruct.USART_Parity = USART_Parity_No;
USART_InitStruct.USART_StopBits = USART_StopBits_1;
USART_InitStruct.USART_WordLength = USART_WordLength_8b;
USART_Init(USART1, &USART_InitStruct);
NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;
NVIC_Init(&NVIC_InitStruct);
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // 开启串口中断
USART_Cmd(USART1, ENABLE); // 开启串口
}
// 新行, 0x0d 0x0a
void USART1_IRQHandler(void) {
unsigned int dat;
if (USART_GetITStatus(USART1, USART_IT_RXNE)) {
dat = USART_ReceiveData(USART1);
if ((sta & 0x8000) == 0) {
if (sta & 0x4000) {
if (dat == 0x0a) {
sta |= 0x8000;
} else {
sta = 0;
}
} else {
if (dat == 0x0d) {
sta |= 0x4000;
} else {
usart1_data[sta & 0x3fff] = dat;
++sta;
if (sta == LEN) sta = 0;
}
}
USART_SendData(USART1, dat);
}
}
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
#if 0
void USART1_IRQHandler(void) {
unsigned int dat;
if (USART_GetITStatus(USART1, USART_IT_RXNE)) {
dat = USART_ReceiveData(USART1);
if (dat == 67) { // ASCII:C
PCout(13) = 0;
} else {
PCout(13) = 1;
}
USART_SendData(USART1, dat + 1);
}
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
#endif
#if 1
#include <stdio.h>
// include "stm32f10x.h"
#pragma import(__use_no_semihosting)
// 标准库需要的支持函数
struct __FILE {
int handle;
};
FILE __stdout;
// 定义 _sys_exit() 以避免使用半主机模式
_sys_exit(int x) {
x = x;
}
// 重映射 fputc 函数, 此函数为多个输出函数的基础函数
int fputc(int ch, FILE *f) {
while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
USART_SendData(USART1, (uint8_t) ch);
return ch;
}
#endif
// usart.h
#ifndef __usart_H
#define __usart_H
#include "stm32f10x.h"
#define LEN 100 // 接收字符串长度最大值 100
static unsigned int usart1_data[LEN]; // 保存 USART1 接收数据
static unsigned int sta = 0; // 计数和判断换行符
void usart_init(unsigned int);
#endif
第十六章 PWM
- 参考手册,通用定时器章节
- 见数据手册,定时器及通道选择,确定 GPIO 口
- PWM = (高电平持续时间 / 周期)* 100%
- 重点关注 CCR、ARR 寄存器,同其他定时器实现功能
呼吸灯,编程步骤:
1、按照定时器定时功能进行设置
2、配置 GPIO 相应功能
3、配置 PWM 输出
// main.c 板载 LED 呼吸灯(PA6 TIM3_CH1)编程示例
#include "stm32f10x.h"
#include "delay.h"
#include "pwm.h"
int main(void) {
unsigned int flag = 0;
unsigned int i = 0;
// 抢占式优先级 2 bit, 响应式优先级 2 bit
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
delay_init();
// timer_init(7200 - 1, 5000 - 1); // 72M / 7200 = 10K
pwm_init(1 - 1, 200 - 1);
while (1) {
if (flag == 0) {
TIM_SetCompare1(TIM3, ++i); // 配置 PWM1、极性高, 则小于 i 输出高电平
delay_ms(10);
if (i == 199) flag = 1;
} else {
TIM_SetCompare1(TIM3, --i);
delay_ms(10);
if (i == 0) flag = 0;
}
}
}
// pwm.c
#include "pwm.h"
// 定时器 3 定时器输入时钟 72M, 值得思考,见第十三章定时器 time.c 注释
void timer3_init(unsigned int pre, unsigned int arr) {
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1; // 根据数据手册时钟树, 36M * 2 = 72M
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period = arr;
TIM_TimeBaseInitStruct.TIM_Prescaler = pre;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct);
NVIC_InitStruct.NVIC_IRQChannel = TIM3_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;
NVIC_Init(&NVIC_InitStruct);
TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE); // 配置更新中断
TIM_Cmd(TIM3, ENABLE); // 开启定时器
}
void TIM3_IRQHandler(void) {
if (TIM_GetITStatus(TIM3, TIM_IT_Update)) {
// TODO
}
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
}
/*
1、按照定时器定时功能进行设置
2、配置 GPIO 相应功能
3、配置 PWM 输出
*/
// PA6 TIM3_CH1
void pwm_init(unsigned int pre, unsigned int arr) {
GPIO_InitTypeDef GPIO_InitStruct;
TIM_OCInitTypeDef TIM_OCInitStruct;
timer3_init(pre, arr);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
TIM_OC1Init(TIM3, &TIM_OCInitStruct);
TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);
}
// pwm.h
#ifndef __pwm_H
#define __pwm_H
#include "stm32f10x.h"
void timer3_init(unsigned int pre, unsigned int arr);
void pwm_init(unsigned int pre, unsigned int arr);
#endif
第十七章 输入捕获
- 参考手册,通用定时器章节
- 见数据手册,定时器及通道选择,确定 GPIO 口
按键(PA2 TIM2_CH3)输入捕获,步骤:
1、配置 GPIO
2、配置定时器基本定时功能
3、配置输入捕获相关参数
4、配置中断
5、编写中断程序
// main.c 按键(PA2 TIM2_CH3)输入捕获示例
#include "stm32f10x.h"
#include "usart.h"
#include "input_capture.h"
int main(void) {
unsigned long num = 0;
unsigned long t = 0;
// 抢占式优先级 2 bit, 响应式优先级 2 bit
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
usart_init(115200);
tim2_capture_init(72 - 1, 0xffff); // 72M / 72 = 1M
printf("test:\n");
while (1) { // 触发机制好像还是没设置好, 计算过程能被中断, 值得思考
if (flag & 0x80) {
num = (flag & 0x3f) * 65536; // 更新中断次数统计时间
num = num + dat; // 捕获中断统计时间
t = num;
printf("timer = %ldus \n", t); // 频率 1M, 单位 us
flag = 0;
}
}
}
// input_capture.c
/*
PA2 TIM2_CH3
输入捕获:
1、配置 GPIO
2、配置定时器基本定时功能
3、配置输入捕获相关参数
4、配置中断
5、编写中断程序
*/
#include "input_capture.h"
void tim2_capture_init(unsigned int pre, unsigned int arr) {
GPIO_InitTypeDef GPIO_InitStruct;
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
TIM_ICInitTypeDef TIM_ICInitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPD; // 下拉输入, 按下高电平
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1; // 根据数据手册时钟树, 36M * 2 = 72M
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period = arr;
TIM_TimeBaseInitStruct.TIM_Prescaler = pre;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStruct);
NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;
NVIC_Init(&NVIC_InitStruct);
TIM_ICInitStruct.TIM_Channel = TIM_Channel_3;
TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM_ICInitStruct.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStruct.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInitStruct.TIM_ICFilter = 0;
TIM_ICInit(TIM2, &TIM_ICInitStruct);
TIM_ITConfig(TIM2, TIM_IT_Update | TIM_IT_CC3, ENABLE); // 配置更新中断和通道 3 捕获中断
TIM_Cmd(TIM2, ENABLE); // 开启定时器
}
unsigned char flag = 0;
unsigned char dat = 0;
void TIM2_IRQHandler(void) {
if ((flag & 0x80) == 0) { // 最高位下降沿标记,次高位上升沿标记
if (TIM_GetITStatus(TIM2, TIM_IT_Update)) { // 更新中断逻辑
if (flag & 0x40) {
if ((flag & 0x3f) == 0x3f) { // 为假定上限, 超过则分别置位
flag |= 0x80;
dat = 0xff;
} else {
++flag;
}
}
} else { // 捕获中断逻辑
if (flag & 0x40) {
flag |= 0x80;
dat = TIM_GetCapture3(TIM2); // CCR 寄存器
TIM_OC3PolarityConfig(TIM2, TIM_ICPolarity_Rising);
} else {
flag = 0;
dat = 0;
TIM_SetCounter(TIM2, 0); // 简化 CCR 部分计算手段
flag |= 0x40;
TIM_OC3PolarityConfig(TIM2, TIM_ICPolarity_Falling);
}
}
}
TIM_ClearITPendingBit(TIM2, TIM_IT_Update | TIM_IT_CC3);
}
// input_capture.h
#ifndef __input_capture_H
#define __input_capture_H
#include "stm32f10x.h"
extern unsigned char flag; // 语言规则: extern 在头文件声明而非定义
extern unsigned char dat;
void tim2_capture_init(unsigned int pre, unsigned int arr);
#endif
第十八章 ADC 模数转换
- 传感器 -> 模拟电压 -> 放大 -> 滤波 -> ADC
- 16 位寄存器放到 12 位 ADC,通常选择数据右对齐,同时选择规则通道组 DR 寄存器数据即存入数据
- Tconv = Sampling time + 12.5 cycles
- 通常规则通道组中可以安排最多 16 个通道,而注入通道组可以安排最多 4 个通道。规则的就是有顺序的,注入通道类似于中断一样,在规则执行的时候,注入一条通道
ADC 模数转换,测量电压值,步骤:
PA3 ADC1_IN3
编写程序步骤:
1、开启 GPIO 和 ADC 时钟
2、配置 GPIOA
3、复位 ADC
4、配置 ADC, 开启 ADC
5、复位校准,等待复位校准完成
6、校准,等待校准完成
7、编写 AD 采集函数
8、编写滤波函数: 多次采集取平均值
9、将数据通过串口发送显示
// main.c ADC 模数转换, 测量电压值示例
#include "stm32f10x.h"
#include "delay.h"
#include "usart.h"
#include "adc.h"
int main(void) {
unsigned long tmp = 0;
float value = 0.0f;
// 抢占式优先级 2 bit, 响应式优先级 2 bit
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
delay_init();
usart_init(115200);
adc_init();
printf("test:\n");
while (1) {
tmp = adc_filter_average(10);
printf("adc_value_before: %ld\n", tmp); // 12 位上限 4096
value = tmp * 3.3 / 4096;
printf("adc_value: %.2f\n", value); // 用 PA3 口, 分别接 3.3V 和接地测试
delay_ms(1000);
}
}
// adc.c
/*
PA3 ADC1_IN3
编写程序步骤:
1、开启 GPIO 和 ADC 时钟
2、配置 GPIOA
3、复位 ADC
4、配置 ADC, 开启 ADC
5、复位校准,等待复位校准完成
6、校准,等待校准完成
7、编写 AD 采集函数
8、编写滤波函数: 多次采集取平均值
9、将数据通过串口发送显示
*/
#include "adc.h"
#include "delay.h"
void adc_init(void) {
GPIO_InitTypeDef GPIO_InitStruct;
ADC_InitTypeDef ADC_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); // 72M
RCC_ADCCLKConfig(RCC_PCLK2_Div6); // 设置 ADC 分频因子 6, 72M / 6 = 12, ADC 最大频率不能超过 14M
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
ADC_DeInit(ADC1); // 复位 ADC1
ADC_InitStruct.ADC_ContinuousConvMode = DISABLE; // 单次转换模式
ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right; // ADC 数据右对齐
ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // 软件触发而不是外部触发
ADC_InitStruct.ADC_Mode = ADC_Mode_Independent; // ADC1 和 ADC2 工作在独立模式
ADC_InitStruct.ADC_NbrOfChannel = 1; // 顺序进行规则转换的 ADC 通道数目
ADC_InitStruct.ADC_ScanConvMode = DISABLE; // 单通道模式
ADC_Init(ADC1, &ADC_InitStruct);
ADC_Cmd(ADC1, ENABLE); // 使能指定的 ADC1
ADC_ResetCalibration(ADC1); // 校准复位
while (ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1); // 校准
while (ADC_GetCalibrationStatus(ADC1));
}
static unsigned int adc_start_conversion(void) {
ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 1, ADC_SampleTime_239Cycles5); // 239.5 周期
ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 使能软件转换启动
// while (ADC_GetSoftwareStartConvStatus(ADC1)); // 等待软件转换结束
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)); // 等待转换结束, EOC 变为 0
return ADC_GetConversionValue(ADC1); // 返回最近一次 ADC1 规则组转换结果
}
unsigned int adc_filter_average(unsigned int times) {
unsigned int t = 0;
unsigned long sum = 0;
for(; t < times; ++t) { // t 放 for 中定义报错, 可打勾 C99
sum += adc_start_conversion();
delay_ms(5);
}
return sum / times;
}
// adc.h
#ifndef __adc_H
#define __adc_H
#include "stm32f10x.h"
void adc_init(void);
static unsigned int adc_start_conversion(void); // static 和 extern 用法知悉
unsigned int adc_filter_average(unsigned int);
#endif
第十九章 ADC 模数转换(内部温度传感器)
- 参考手册,ADC 章节
// main.c ADC 模数转换,内部温度传感器示例
#include "stm32f10x.h"
#include "delay.h"
#include "usart.h"
#include "temperature.h"
int main(void) {
unsigned int tmp = 0;
float value = 0.0f;
// 抢占式优先级 2 bit, 响应式优先级 2 bit
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
delay_init();
usart_init(115200);
adc_temperature_init();
printf("test:\n");
while (1) {
tmp = adc_temperature_filter_average(1);
printf("adc_value_before: %d\n", tmp); // 12 位上限 4096
value = adc_temperature_handle(tmp);
printf("temperature: %.2f°C\n", value); // 温度竟然是负值, 怀疑通道 17 参考电压非 3.3V
delay_ms(1000);
}
}
// temperature.c
#include "temperature.h"
#include "delay.h"
// 调用采集温度函数前, 需要先调用 adc_temperature_init()
// 采集一次温度
void adc_temperature_init(void) {
ADC_InitTypeDef ADC_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); // 72M
RCC_ADCCLKConfig(RCC_PCLK2_Div6); // 设置 ADC 分频因子 6, 72M / 6 = 12, ADC 最大频率不能超过 14M
ADC_DeInit(ADC1); // 复位 ADC1
ADC_InitStruct.ADC_ContinuousConvMode = DISABLE; // 单次转换模式
ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right; // ADC 数据右对齐
ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // 软件触发而不是外部触发
ADC_InitStruct.ADC_Mode = ADC_Mode_Independent; // ADC1 和 ADC2 工作在独立模式
ADC_InitStruct.ADC_NbrOfChannel = 1; // 顺序进行规则转换的 ADC 通道数目
ADC_InitStruct.ADC_ScanConvMode = DISABLE; // 单通道模式
ADC_Init(ADC1, &ADC_InitStruct);
ADC_TempSensorVrefintCmd(ENABLE); // 注意区别 ADC 其他通道, 需使能温度传感器通道
ADC_Cmd(ADC1, ENABLE); // 使能指定的 ADC1
ADC_ResetCalibration(ADC1); // 校准复位
while (ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1); // 校准
while (ADC_GetCalibrationStatus(ADC1));
}
static unsigned int adc_temperature_get(void) {
ADC_RegularChannelConfig(ADC1, ADC_Channel_16, 1, ADC_SampleTime_239Cycles5); // 239.5 周期
ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 使能软件转换启动
// while (ADC_GetSoftwareStartConvStatus(ADC1)); // 等待软件转换结束
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)); // 等待转换结束, EOC 变为 0
return ADC_GetConversionValue(ADC1); // 返回最近一次 ADC1 规则组转换结果
}
/*
内部温度计算公式:
Obtain the temperature using the following formula:
Temperature (in °C) = {(V25 - VSENSE) / Avg_Slope} + 25.
Where,
V25 = VSENSE value for 25° C and
Avg_Slope = Average Slope for curve between Temperature vs. VSENSE (given in
mV/° C or μV/ °C).
Refer to the Electrical characteristics section for the actual values of V25 and
Avg_Slope.
*/
// 得到温度原始的平均值(电压 V)数字电压亮
unsigned int adc_temperature_filter_average(unsigned int times) {
unsigned int t = 0;
unsigned long sum = 0;
for(; t < times; ++t) { // t 放 for 中定义报错, 可打勾 C99
sum += adc_temperature_get();
delay_ms(5);
}
return sum / times;
}
float adc_temperature_handle(unsigned int dat) {
float tmp;
tmp = (float)dat * 3.3 / 4096; // 模拟电压值, 怀疑通道 17 参考电压非 3.3V
tmp = (1.43 - tmp) / 0.0043 + 25; // 转换为温度
return tmp;
}
// temperature.h
#ifndef __temperature_H
#define __temperature_H
#include "stm32f10x.h"
void adc_temperature_init(void);
static unsigned int adc_temperature_get(void); // static 只在当前文件定义、调用
unsigned int adc_temperature_filter_average(unsigned int times);
float adc_temperature_handle(unsigned int);
#endif
小结
学习笔记,定期回顾,有问题留言。