【单片机】STM32 最小板 学习笔记


前言

本篇为 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

小结

学习笔记,定期回顾,有问题留言。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

idiot5liev

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值