目录&索引

  • 前言
  • 第一章 前期工作准备
    • 软件获取
    • STM32 资料
    • 相关下载
    • 硬件准备
  • 第二章 单片机系统介绍
  • 第三章 库函数工程模板建立
    • 第一步,下载固件库,文件分类
    • 第二步,打开 mdk5 创建工程
    • 第三步,连接 ST-LINK,配置,下载 LED 点亮程序
  • 第四章 启动文件
  • 第五章 时钟
  • 第六章 GPIO 与寄存器方法
  • 第七章 串口下载
  • 第八章 库函数工程模板(led)
  • 第九章 库函数工程模板(button)
  • 第十章 位带操作
  • 第十一章 SysTick 定时器
  • 第十二章 中断
  • 第十三章 定时器
  • 第十四章 串口 USART(一)
  • 第十五章 串口 USART(二)
  • 第十六章 PWM
  • 第十七章 输入捕获
  • 第十八章 ADC 模数转换
  • 第十九章 ADC 模数转换(内部温度传感器)
  • 小结

前言

本篇为 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.864sunsigned 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 bitNVIC_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); // 提出问题,什么情况需要使能 AFIOGPIO_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 bitNVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);delay_init();led_init();timer_init(7200 - 1, 5000 - 1); // 72M / 7200 = 10Kwhile (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 = 72MTIM_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 bitNVIC_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:CPCout(13) = 0;} else {PCout(13) = 1;}}USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
#endif#if 0
// 版本三
void USART1_IRQHandler(void) { // 发送数据 dat + 1 到 USART1unsigned int dat;if (USART_GetITStatus(USART1, USART_IT_RXNE)) {dat = USART_ReceiveData(USART1);if (dat == 67) { // ASCII:CPCout(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 bitNVIC_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:CPCout(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 bitNVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);delay_init();// timer_init(7200 - 1, 5000 - 1); // 72M / 7200 = 10Kpwm_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 = 72MTIM_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 bitNVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);usart_init(115200);tim2_capture_init(72 - 1, 0xffff); // 72M / 72 = 1Mprintf("test:\n");while (1) { // 触发机制好像还是没设置好, 计算过程能被中断, 值得思考if (flag & 0x80) {num = (flag & 0x3f) * 65536; // 更新中断次数统计时间num = num + dat; // 捕获中断统计时间t = num;printf("timer = %ldus \n", t); // 频率 1M, 单位 usflag = 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 = 72MTIM_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 bitNVIC_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 位上限 4096value = 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); // 72MRCC_ADCCLKConfig(RCC_PCLK2_Div6);   // 设置 ADC 分频因子 6, 72M / 6 = 12, ADC 最大频率不能超过 14MGPIO_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); // 复位 ADC1ADC_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); // 使能指定的 ADC1ADC_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 变为 0return 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 中定义报错, 可打勾 C99sum += 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 bitNVIC_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 位上限 4096value = adc_temperature_handle(tmp);printf("temperature: %.2f°C\n", value); // 温度竟然是负值, 怀疑通道 17 参考电压非 3.3Vdelay_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); // 72MRCC_ADCCLKConfig(RCC_PCLK2_Div6);   // 设置 ADC 分频因子 6, 72M / 6 = 12, ADC 最大频率不能超过 14MADC_DeInit(ADC1); // 复位 ADC1ADC_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); // 使能指定的 ADC1ADC_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 变为 0return 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 andAvg_Slope = Average Slope for curve between Temperature vs. VSENSE (given inmV/° C or μV/ °C).Refer to the Electrical characteristics section for the actual values of V25 andAvg_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 中定义报错, 可打勾 C99sum += 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.3Vtmp = (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

小结

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


【单片机】STM32 最小板 学习笔记相关推荐

  1. STM32开发板学习笔记【5】UART 串口 1 数据收发实验

    实验目的: 串口的使用对于我们开发调试过程中的作用是非常之大,可以用来查看,打印以及输入相关信息.所 以对串口的调试使用要熟练掌握. 实验内容: 编写串口 1 数据收发程序.调试编译好程序后,将程序下 ...

  2. 立创EDA入门3 通过51单片机最小板学习PCB设计

    立创EDA入门3 通过51单片机最小板学习PCB设计 一.本文目的 二.原理图设计 1. 新建工程,命名为51系统 2. 各模块原理图 3. 一些常用操作 (1)放置普通元器件 (2)封装.标签设置 ...

  3. STM32 HAL库学习笔记1-HAL库简介

    STM32 HAL库学习笔记1-HAL库简介 HAL库 SPL 库 和 HAL 库两者相互独立,互不兼容.几种库的比较如下 目前几种库对不同芯片的支持情况如下 ST 中文官网上有一篇<关于ST库 ...

  4. 华清远见fs4412开发板学习笔记(五)

    fs4412开发板学习笔记(五) 作业1: 输入10个整数,按从小到大的顺序输出(选择排序) 每轮排序在未排序的集合中找到(最小/最大),将找到的数与未排序的 第一个数交换位置. 5 4 3 2 1 ...

  5. 华清远见fs4412开发板学习笔记(四)

    fs4412开发板学习笔记(四) 今天的课程安排 1.复习 1.1 VIM 编辑器 [1] vim + filename 打开 [2] 工作模式 命令模式 编辑模式 底行模式 [3] 模式切换 命令- ...

  6. PWM控制LED亮度(2-呼吸灯)-STM32电控学习笔记10

    PWM控制LED亮度(2-呼吸灯)-STM32电控学习笔记10 day10:2022/9/29 前面学了两三天的PWM了,PWM本身不难理解,至于为啥进度缓慢,还不是这时钟定时器分频值重装载啥的太难理 ...

  7. 阿里云HaaS100物联网开发板学习笔记(六)做个智能灯---一个完整的开发例子

    摘要:本篇文章将前期几个专题综合起来,基于阿里云HaaS100的新固件设计制作一个智能灯.这个智能灯由云平台.手机APP端和设备端组成,基本上涵盖了一个物联网小项目所需的主要步骤. 目录 1.在阿里云 ...

  8. 阿里云HaaS100物联网开发板学习笔记(二)硬件控制初步--让小灯闪烁起来

    摘要:无论是哪种开发板,要想开发特定的功能,必先从GPIO开始,HaaS100开发也是一样.如果仅仅利用HaaS100的联网功能,那简直是太浪费了.HaaS100拥有其他开发板所具备的所有的功能,比如 ...

  9. 阿里云HaaS100物联网开发板学习笔记(四)轻应用初步--用javascript连接阿里云物联网平台

    摘要:本篇文章讲解如何使用JavaScript"轻应用"连接阿里云物联网平台并上报一个数据.仍然延续前几篇文章的结构,从安装软件环境开始讲,以使零基础的同学看了本篇文章之后,也能够 ...

最新文章

  1. Kafka设计解析(二):Kafka High Availability (上)-转
  2. html游戏禁止微信浏览器下拉,JavaScript实现禁止微信浏览器下拉回弹效果
  3. OpenCV哈里斯角落探测器Harris corner detector
  4. 处理字符串_14_SQL处理IN和合并后字符串案例详解
  5. 纪中B组模拟赛总结(2019.12.21)
  6. 每个人都应该学git,最新GitHub上git指南我不信你不会git
  7. Navicat的使用,连表查询,python代码操作sql语句
  8. 数据挖掘概念与技术(原书第三版)范明 孟小峰译-----第二章课后习题答案
  9. 动感歌词制作与转换工具
  10. Java实现 LeetCode 41 缺失的第一个正数
  11. 智慧城市是什么,建设智慧城市需要哪些核心技术?
  12. html5画图程序,基于HTML5的Windows画图程序
  13. 计算机组装与维护我要自学网,【答疑】3D机械建模软件有哪些,3D机械建模一般用的是哪个软件? - 视频教程线上学...
  14. EOS智能合约开发系列(18): 狼人杀游戏的`eosio.code`
  15. MASM的Hello World
  16. Dew Lab Studio 2020 VCL软件包,很好的RAD(快速软件开发)工具
  17. 计算机专业PhD申请文书范文,留学文书写作:医学专业PHD个人陈述(PS)英文范文模板...
  18. ValueError: bad marshal data (unknown type code)
  19. 浙江大学PAT (Basic Level) Practice (中文)1014福尔摩斯的约会JAVA实现代码及分析
  20. 关于宽带和窄带的解释

热门文章

  1. IPFS计算机存储器,IPFS节点储存
  2. 【从FT到DFT和FFT】(三)从离散傅里叶变换到快速傅里叶变换
  3. JpaRepository查询方法名规范
  4. 简单搭建钓鱼Wifi信号获取用户手机号
  5. 电商工作后台首页的商业价值重构与产品化设计
  6. NVIDIA开启独显
  7. 伽马矫正(Gamma correction)
  8. ReactiveX-Observable
  9. Linux操作系统的主要用途是什么呢
  10. JavaScript初学笔记总汇