【单片机】STM32 最小板 学习笔记
目录&索引
- 前言
- 第一章 前期工作准备
- 软件获取
- 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 最小板 学习笔记相关推荐
- STM32开发板学习笔记【5】UART 串口 1 数据收发实验
实验目的: 串口的使用对于我们开发调试过程中的作用是非常之大,可以用来查看,打印以及输入相关信息.所 以对串口的调试使用要熟练掌握. 实验内容: 编写串口 1 数据收发程序.调试编译好程序后,将程序下 ...
- 立创EDA入门3 通过51单片机最小板学习PCB设计
立创EDA入门3 通过51单片机最小板学习PCB设计 一.本文目的 二.原理图设计 1. 新建工程,命名为51系统 2. 各模块原理图 3. 一些常用操作 (1)放置普通元器件 (2)封装.标签设置 ...
- STM32 HAL库学习笔记1-HAL库简介
STM32 HAL库学习笔记1-HAL库简介 HAL库 SPL 库 和 HAL 库两者相互独立,互不兼容.几种库的比较如下 目前几种库对不同芯片的支持情况如下 ST 中文官网上有一篇<关于ST库 ...
- 华清远见fs4412开发板学习笔记(五)
fs4412开发板学习笔记(五) 作业1: 输入10个整数,按从小到大的顺序输出(选择排序) 每轮排序在未排序的集合中找到(最小/最大),将找到的数与未排序的 第一个数交换位置. 5 4 3 2 1 ...
- 华清远见fs4412开发板学习笔记(四)
fs4412开发板学习笔记(四) 今天的课程安排 1.复习 1.1 VIM 编辑器 [1] vim + filename 打开 [2] 工作模式 命令模式 编辑模式 底行模式 [3] 模式切换 命令- ...
- PWM控制LED亮度(2-呼吸灯)-STM32电控学习笔记10
PWM控制LED亮度(2-呼吸灯)-STM32电控学习笔记10 day10:2022/9/29 前面学了两三天的PWM了,PWM本身不难理解,至于为啥进度缓慢,还不是这时钟定时器分频值重装载啥的太难理 ...
- 阿里云HaaS100物联网开发板学习笔记(六)做个智能灯---一个完整的开发例子
摘要:本篇文章将前期几个专题综合起来,基于阿里云HaaS100的新固件设计制作一个智能灯.这个智能灯由云平台.手机APP端和设备端组成,基本上涵盖了一个物联网小项目所需的主要步骤. 目录 1.在阿里云 ...
- 阿里云HaaS100物联网开发板学习笔记(二)硬件控制初步--让小灯闪烁起来
摘要:无论是哪种开发板,要想开发特定的功能,必先从GPIO开始,HaaS100开发也是一样.如果仅仅利用HaaS100的联网功能,那简直是太浪费了.HaaS100拥有其他开发板所具备的所有的功能,比如 ...
- 阿里云HaaS100物联网开发板学习笔记(四)轻应用初步--用javascript连接阿里云物联网平台
摘要:本篇文章讲解如何使用JavaScript"轻应用"连接阿里云物联网平台并上报一个数据.仍然延续前几篇文章的结构,从安装软件环境开始讲,以使零基础的同学看了本篇文章之后,也能够 ...
最新文章
- Kafka设计解析(二):Kafka High Availability (上)-转
- html游戏禁止微信浏览器下拉,JavaScript实现禁止微信浏览器下拉回弹效果
- OpenCV哈里斯角落探测器Harris corner detector
- 处理字符串_14_SQL处理IN和合并后字符串案例详解
- 纪中B组模拟赛总结(2019.12.21)
- 每个人都应该学git,最新GitHub上git指南我不信你不会git
- Navicat的使用,连表查询,python代码操作sql语句
- 数据挖掘概念与技术(原书第三版)范明 孟小峰译-----第二章课后习题答案
- 动感歌词制作与转换工具
- Java实现 LeetCode 41 缺失的第一个正数
- 智慧城市是什么,建设智慧城市需要哪些核心技术?
- html5画图程序,基于HTML5的Windows画图程序
- 计算机组装与维护我要自学网,【答疑】3D机械建模软件有哪些,3D机械建模一般用的是哪个软件? - 视频教程线上学...
- EOS智能合约开发系列(18): 狼人杀游戏的`eosio.code`
- MASM的Hello World
- Dew Lab Studio 2020 VCL软件包,很好的RAD(快速软件开发)工具
- 计算机专业PhD申请文书范文,留学文书写作:医学专业PHD个人陈述(PS)英文范文模板...
- ValueError: bad marshal data (unknown type code)
- 浙江大学PAT (Basic Level) Practice (中文)1014福尔摩斯的约会JAVA实现代码及分析
- 关于宽带和窄带的解释