锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

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

时间:2022-09-21 08:30:00 温度传感器99st400700

目录&索引

  • 前言
  • 第一章 前期工作准备
    • 软件获取
    • 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 可下载)、启动电路(跳线帽设置高低电平)、指示灯、稳压芯片(背面)、两个晶振(8)M,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);
    }
}

第七章 串口下载


第八章 库函数工程模板(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

第十二章 中断

中断系统详解,见参考手册。外部按键中断,步骤:<

相关文章