STM32入门(1-10)
STM32命名规范
板子硬件和烧写介绍
- 这是我入门100步学习用的板子(STM32F103C8T6),价格不菲大概260大洋
-
STM32里有两个时钟:APB1(低速时钟) 和 APB2(高速时钟);通过手册系统架构那章(2.1)可以看到:
APB2 负责AD,I/O(GPIO),串口1,高级定时器TIM
APB1 负责DA,串口2,3,4,5,普通定时器TIM, USB , IIC , CAN
- 根据核心板背后查看是使用 CH340芯片驱动还是 FT232芯片驱动,然后下载对应的驱动程序
- 烧写程序我用的是 FlyMcu 软件,按下面的设置好然后找个点灯程序烧进去测试一下是否烧写正常
- 如果想烧进 RamIsp 的话,需要在FlyMcu 上面把【使用 RamIsp】打勾,然后长按核心板第一个按键,等LED闪两下即可(它的下载速度快很多,但是也有缺陷就是一旦断电后程序就会消失不能保存)
内核和存储器
- SRAM 和 FLASH 特性对比
低功耗 和 ADC(2.3.26)
ADC 接口上的其他逻辑功能:
- 同步的采样和保存
- 交叉的采样和保持
- 单次采样
DMA(2.3.15)
- 7 通道 DMA 控制器
- 支持的外设:定时器,ADC,SPI,I^2^C 和 USART
STM32F103最小系统电路
注:x表示任意电平
- FLASH ISP是从FLASH中启动,运行用户程序(正常启动)
- BootLoader是ST公司制作了一段用于ISP下载的程序,用户不可修改
- RAM ISP是从SRAM中启动,主要用于调试阶段的下载。下载速度快,但断电丢失
固件库的安装和工程建立
首先安装 STM32F10x固件库V3.5.0(在书签第一个)也可以去 ST 官网下载:STSW-STM32054 - STM32F10x标准外设库 - 意法半导体
下载安装解压后可以看到这些文件:
然后就可以开始接下来的步骤了:
- 首先选择一个硬盘目录作为工程建立的地方,在这目录下手动创建4个文件夹
-
打开刚刚解压的固件库文件:
2-1-2-STM32F10x官方固件库
\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\CoreSupport
选择这两个文件
2-1-2-STM32F10x官方固件库
\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x
选择这三个文件
复制到一开始新建的的工程文件的 CMSIS 文件下即可
然后把此目录的八个文件:
2-1-2-STM32F10x官方固件库
\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\arm
复制粘贴到工程目录下新建的 Startup 文件下即可
把此目录下的两个文件夹:
2-1-2-STM32F10x官方固件库
\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\STM32F10x_StdPeriph_Driver
复制到工程目录下新建的 Lib 文件下即可
然后把此目录下的四个文件:
2-1-2-STM32F10x官方固件库
\STM32F10x_StdPeriph_Lib_V3.5.0\Project\STM32F10x_StdPeriph_Template
复制到工程目录下新建的 User 文件下即可
-
Keil 5 的设置
- 新建工程
- 文件配置
-
初始化设置
注:V5.35 将禁用在µVision 5 项目 (*.uvprojx) 的目标对话框选项(Options for Target )中的外部振荡器频率 (Xtal) 配置;如果不是这个版本的则可以修改成 8.0
复制这段粘贴到输入框:
USE_STDPERIPH_DRIVER,STM32F10X_MD
然后点击 User 下的 main.c 把里面内容全部替换成以下代码:
# include "stm32f10x.h"
int main()
{
while(1)
{
}
}
然后点击编译,如果你是keil 5 则可能会报错,这是因为设置里默认是 编译器版本6,而固件库还是支持版本5的,所以你需要改成 编译器版本5;改完再编译就会发现 0错误0警告 了(如果有警告是因为最后一行没有换行,只需把main函数最外的大括号结束位置后换行即可)
-
工程模板的建立
首先在工程目录下新建两个文件夹(一个是放用到的外设操作,一个放通用的(比如延时函数等等))App文件夹和 Public文件夹(名字随便记住就行)
然后添加模板到通用文件夹(Public)里(书签第一个自己找)
App文件夹则放你用到的外设代码比如LED,KEY等等。
最后记得在 Keil 里把这些文件添加进去,头文件也别忘了把路径也添加进去
具体文件代码:
delay.c
# include "delay.h"
# define AHB_INPUT 72 //请按RCC中设置的AHB时钟频率填写到这里(单位MHz)
void delay_us(u32 uS){ //uS微秒级延时程序(参考值即是延时数,72MHz时最大值233015)
SysTick->LOAD=AHB_INPUT*uS; //重装计数初值(当主频是72MHz,72次为1微秒)
SysTick->VAL=0x00; //清空定时器的计数器
SysTick->CTRL=0x00000005;//时钟源HCLK,打开定时器
while(!(SysTick->CTRL&0x00010000)); //等待计数到0
SysTick->CTRL=0x00000004;//关闭定时器
}
void delay_ms(u16 ms){ //mS毫秒级延时程序(参考值即是延时数,最大值65535)
while( ms-- != 0){
delay_us(1000); //调用1000微秒的延时
}
}
void delay_s(u16 s){ //S秒级延时程序(参考值即是延时数,最大值65535)
while( s-- != 0){
delay_ms(1000); //调用1000毫秒的延时
}
}
delay.h
# ifndef __DELAY_H
# define __DELAY_H
# include "sys.h"
void delay_s(u16 s);
void delay_ms(u16 ms);
void delay_us(u32 us);
# endif
sys.c
/*
《修改日志》
1-201708221422 加入RCC时钟的设置函数。
*/
# include "sys.h"
void NVIC_Configuration(void){ //嵌套中断向量控制器 的设置
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
}
void RCC_Configuration(void){ //RCC时钟的设置
ErrorStatus HSEStartUpStatus;
RCC_DeInit(); /* RCC system reset(for debug purpose) RCC寄存器恢复初始化值*/
RCC_HSEConfig(RCC_HSE_ON); /* Enable HSE 使能外部高速晶振*/
HSEStartUpStatus = RCC_WaitForHSEStartUp(); /* Wait till HSE is ready 等待外部高速晶振使能完成*/
if(HSEStartUpStatus == SUCCESS){
/*设置PLL时钟源及倍频系数*/
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9); //RCC_PLLMul_x(枚举2~16)是倍频值。当HSE=8MHZ,RCC_PLLMul_9时PLLCLK=72MHZ
/*设置AHB时钟(HCLK)*/
RCC_HCLKConfig(RCC_SYSCLK_Div1); //RCC_SYSCLK_Div1——AHB时钟 = 系统时钟(SYSCLK) = 72MHZ(外部晶振8HMZ)
/*注意此处的设置,如果使用SYSTICK做延时程序,此时SYSTICK(Cortex System timer)=HCLK/8=9MHZ*/
RCC_PCLK1Config(RCC_HCLK_Div2); //设置低速AHB时钟(PCLK1),RCC_HCLK_Div2——APB1时钟 = HCLK/2 = 36MHZ(外部晶振8HMZ)
RCC_PCLK2Config(RCC_HCLK_Div1); //设置高速AHB时钟(PCLK2),RCC_HCLK_Div1——APB2时钟 = HCLK = 72MHZ(外部晶振8HMZ)
/*注:AHB主要负责外部存储器时钟。APB2负责AD,I/O,高级TIM,串口1。APB1负责DA,USB,SPI,I2C,CAN,串口2,3,4,5,普通TIM */
FLASH_SetLatency(FLASH_Latency_2); //设置FLASH存储器延时时钟周期数
/*FLASH时序延迟几个周期,等待总线同步操作。
推荐按照单片机系统运行频率:
0—24MHz时,取Latency_0;
24—48MHz时,取Latency_1;
48~72MHz时,取Latency_2*/
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable); //选择FLASH预取指缓存的模式,预取指缓存使能
RCC_PLLCmd(ENABLE); //使能PLL
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET); //等待PLL输出稳定
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); //选择SYSCLK时钟源为PLL
while(RCC_GetSYSCLKSource() != 0x08); //等待PLL成为SYSCLK时钟源
}
/*开始使能程序中需要使用的外设时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB |
RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE, ENABLE); //APB2外设时钟使能
// RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); //APB1外设时钟使能
// RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);
// RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
// RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
}
sys.h
# ifndef __SYS_H
# define __SYS_H
# include "stm32f10x.h"
//位带操作,实现51类似的GPIO控制功能
//具体实现思想,参考<<CM3权威指南>>第五章(87页~92页).
//IO口操作宏定义
# 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))
//IO口地址映射
# 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 GPIOD_IDR_Addr (GPIOD_BASE+8) //0x40011408
# define GPIOE_IDR_Addr (GPIOE_BASE+8) //0x40011808
# define GPIOF_IDR_Addr (GPIOF_BASE+8) //0x40011A08
# define GPIOG_IDR_Addr (GPIOG_BASE+8) //0x40011E08
//IO口操作,只对单一的IO口!
//确保n的值小于16!
# 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) //输入
# define PDout(n) BIT_ADDR(GPIOD_ODR_Addr,n) //输出
# define PDin(n) BIT_ADDR(GPIOD_IDR_Addr,n) //输入
# define PEout(n) BIT_ADDR(GPIOE_ODR_Addr,n) //输出
# define PEin(n) BIT_ADDR(GPIOE_IDR_Addr,n) //输入
# define PFout(n) BIT_ADDR(GPIOF_ODR_Addr,n) //输出
# define PFin(n) BIT_ADDR(GPIOF_IDR_Addr,n) //输入
# define PGout(n) BIT_ADDR(GPIOG_ODR_Addr,n) //输出
# define PGin(n) BIT_ADDR(GPIOG_IDR_Addr,n) //输入
void NVIC_Configuration(void); //嵌套中断控制器的设置
void RCC_Configuration(void); //RCC时钟类的设置
# endif
注意
STM32F103固件函数库用户手册(中文):书签第一个
点亮LED灯
本节用到的固件库函数
- GPIO_WriteBit(手册 10.2.11)
- GPIO_Init(手册 10.2.3)
介绍 GPIO 八种模式
STM32操作GPIO的方式跟51单片机相比,要复杂一些,但是功能也会相应的强大一些。功能的强大主要体现在GPIO的输入输出方式上,比如STM32中允许对任意GPIO每一根针单独设置模式,共有八种模式,四种输入模式,四种输出模式(原则上同一个GPIO是不允许同一时间即作输出又作输入的)
其中最常用的输入模式是「下拉输入」、「上拉输入」,这个很好理解,「下拉输入」时常态为低电平,有信号时是高电平;「上拉输入」时常态为高电平,有信号时是低电平。
最常用的输出模式是「开漏输出」和「推挽输出」,这个也很好理解,「开漏输出」就是普通51系列单片机的P0口的输出模式,设置低电平时接地,设置高电平时浮空,外部需要上拉才能正确输出,适用于高负载;「推挽输出」就是普通51系列单片机除P0以外的其他GPIO,同时具有接地和接VCC的能力,不会浮空,初始化时统一置高电平。
-
STM32芯片最拥有GPIOA、GPIOB…GPIOG等7组端口,每组端口最多拥有Pin0、Pin1…Pin15共16个引脚
-
STM32的GPIO模式总结:
1、输入模式 (模拟,浮空,上拉,下拉)下,输出被禁止
2、输出模式(推挽、开漏)下,输入仍然可用
3、复用功能(推挽、开漏)下,输出寄存器(比如ODR)无效,输出源来自其他片内外设,输入仍然可用
-
STM32的GPIO模式习惯用法:
RXD/按键,配置为浮空输入
ADC,配置为模拟输入
蜂鸣器与 LED ,配置为推挽输出
DHT11/模拟I2C,配置为开漏输出
TXD(USART属于片上外设) ,配置为复用推挽输出
操作
led.c
下面的初始化函数 LED_Init 描述的是使能PB0引脚,并将其设置为「推挽输出」模式,要记住,STM32里面使用每一个GPIO引脚之前都需要明确的使能,并设置其工作模式。
# include "led.h"
void LED_Init(void){ //LED灯的接口初始化
GPIO_InitTypeDef GPIO_InitStructure;//定义结构体
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC,ENABLE); //声明对PA,PB,PC的GPIO使用
GPIO_InitStructure.GPIO_Pin = LED1 | LED2; //选择端口号(0~15或all)如果只点LED1则可以把 |LED2删掉还有上面的 RCC_APB2Periph_GPIOA| 和|RCC_APB2Periph_GPIOC
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //选择IO接口工作方式:推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置IO接口速度(2/10/50MHz)
GPIO_Init(LEDPORT, &GPIO_InitStructure);//把结构体名传进去
}
/*
选择IO接口工作方式:
GPIO_Mode_AIN 模拟输入
GPIO_Mode_IN_FLOATING 浮空输入
GPIO_Mode_IPD 下拉输入
GPIO_Mode_IPU 上拉输入
GPIO_Mode_Out_PP 推挽输出
GPIO_Mode_Out_OD 开漏输出
GPIO_Mode_AF_PP 复用推挽输出
GPIO_Mode_AF_OD 复用开漏输出
*/
led.h
- 根据原理图可以知道 LED1 的IO口是 PB0,LED2 是 PB1
# ifndef __LED_H
# define __LED_H
# include "sys.h"
//# define LED1 PBout(0)// PB0
//# define LED2 PBout(1)// PB1
# define LEDPORT GPIOB //定义IO接口
# define LED1 GPIO_Pin_0 //定义IO接口
# define LED2 GPIO_Pin_1 //定义IO接口
void LED_Init(void);//初始化
# endif
main.c
(BitAction)(1) 是枚举类型,可以通过跳转看到
也可以将 main.c 的这个改成
GPIO_WriteBit(LEDPORT,LED1,Bit_SET);//LED1接口输出高电平1
/*********************************************************************************************
说明:
# 本模板加载了STM32F103内部的RCC时钟设置,并加入了利用滴答定时器的延时函数。
# 可根据自己的需要增加或删减。
*********************************************************************************************/
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "led.h"
int main (void){//主程序
RCC_Configuration(); //时钟设置
LED_Init();
while(1)
{
GPIO_WriteBit(LEDPORT,LED1,(BitAction)(1));//LED1接口输出高电平1,灯亮
//GPIO_WriteBit(LEDPORT,LED1,(BitAction)(0)); //LED1接口输出低电平0,灯灭
}
}
/*
【变量定义】
u32 a; //定义32位无符号变量a
u16 a; //定义16位无符号变量a
u8 a; //定义8位无符号变量a
vu32 a; //定义易变的32位无符号变量a
vu16 a; //定义易变的 16位无符号变量a
vu8 a; //定义易变的 8位无符号变量a
uc32 a; //定义只读的32位无符号变量a
uc16 a; //定义只读 的16位无符号变量a
uc8 a; //定义只读 的8位无符号变量a
# define ONE 1 //宏定义
delay_us(1); //延时1微秒
delay_ms(1); //延时1毫秒
delay_s(1); //延时1秒
*/
实验现象
更多点亮LED的方法
本节用到的固件库函数
- GPIO_SetBits(手册 10.2.9)
- GPIO_ResetBits(手册 10.2.10)
- GPIO_Write(手册 10.2.10)
操作
main.c 里添加:
方法2
GPIO_SetBits(LEDPORT,LED1); //LED灯都为高电平(1),灯亮
GPIO_ResetBits(LEDPORT,LED1); //LED灯都为低电平(0),灯灭
GPIO_SetBits(LEDPORT,LED1|LED2);//LED1和LED2同时亮
方法3
//可以点击定义的LED跳转查看它的寄存器地址
GPIO_Write(LEDPORT,0x0001); //直接数值操作将变量值写入LED,灯亮
GPIO_Write(LEDPORT,0x0000); //直接数值操作将变量值写入LED,灯灭
GPIO_Write(LEDPORT,0x0001|0x0002);//LED1和LED2同时亮
方法4
修改led.h
# define LED1 PBout(0)// PB0 //自己定义端口
# define LED2 PBout(1)// PB1
修改led.c
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_Init(GPIOB, &GPIO_InitStructure);
修改main.c
LED1=1;//类似51单片机直接操控管脚
LED闪灯程序
LED 闪烁用到了延时函数,也可以用中断不过等后面学了中断先,直接用回上面的程序只需在 main.c 添加延时函数即可(记得添加头文件:delay.h)
main.c
GPIO_WriteBit(LEDPORT,LED1,(BitAction)(1));//LED1接口输出高电平1,灯亮
delay_us(50000);//延时(也就是0.05秒)
GPIO_WriteBit(LEDPORT,LED1,(BitAction)(0)); //LED1接口输出低电平0,灯灭
delay_us(50000);//延时
三种延时函数
//三种延时函数(换算可以用电脑自带计算器)
delay_us(50000);//延时50000微妙
delay_ms(5000);//延时5000毫秒
delay_s(5);//延时5秒(最小值:1最大值:65535)
实验现象
LED呼吸灯程序
其他文件照搬上面,只需改 main.c :
main.c
/*********************************************************************************************
硬件支持: STM32F103C8 外部晶振8MHz RCC函数设置主频72MHz
说明:
# 本模板加载了STM32F103内部的RCC时钟设置,并加入了利用滴答定时器的延时函数。
# 可根据自己的需要增加或删减。
*********************************************************************************************/
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "led.h"
int main (void){//主程序
//定义需要的变量
u8 MENU;
u16 t,i;
//初始化程序
RCC_Configuration(); //时钟设置
LED_Init();
//设置变量的初始值
MENU = 0;
t = 1;
//主循环
while(1){
//菜单0
if(MENU == 0){ //变亮循环
for(i = 0; i < 10; i++){
GPIO_WriteBit(LEDPORT,LED1,(BitAction)(1)); //LED1接口输出高电平1
delay_us(t); //延时(值越大越亮)
GPIO_WriteBit(LEDPORT,LED1,(BitAction)(0)); //LED1接口输出低电平0
delay_us(501-t); //延时
}
t++;
if(t==500){
MENU = 1;
}
}
//菜单1
if(MENU == 1){ //变暗循环
for(i = 0; i < 10; i++){
GPIO_WriteBit(LEDPORT,LED1,(BitAction)(1)); //LED1接口输出高电平1
delay_us(t); //延时
GPIO_WriteBit(LEDPORT,LED1,(BitAction)(0)); //LED1接口输出低电平0
delay_us(501-t); //延时
}
t--;
if(t==1){
MENU = 0;
}
}
}
}
合并起来写法:
main.c
int main()
{
u8 MENU,flag;
u16 t,i;
RCC_Configuration();
LED_Init();
MENU=0;
t=1;
flag=1;
while(1)
{
for(i=0;i<10;i++)
{
GPIO_SetBits(LEDPORT,LED1);
delay_us(t);
GPIO_ResetBits(LEDPORT,LED1);
delay_us(501-t);
}
if(flag==1)
{
t++;
if(t==500)
flag=0;
}
else if(flag==0)
{
t--;
if(t==1)
flag=1;
}
}
}
实验现象
按键控制LED程序
本节用到的固件库函数
- GPIO_ReadInputDataBit(手册 10.2.5)
注意
led.c,led.h,delay.c,delay.h,sys.c,sys.h 与 7.3相同
key.c
I/O输入的时候不需要速度,所以可以屏蔽
# include "key.h"
void KEY_Init(void){ //微动开关的接口初始化
GPIO_InitTypeDef GPIO_InitStructure; //定义GPIO的初始化枚举结构
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitStructure.GPIO_Pin = KEY1 | KEY2; //选择端口号(0~15或all)
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //选择IO接口工作方式 //上拉电阻
// GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置IO接口速度(2/10/50MHz)
GPIO_Init(KEYPORT,&GPIO_InitStructure);
}
key.h
# ifndef __KEY_H
# define __KEY_H
# include "sys.h"
//# define KEY1 PAin(0)// PA0
//# define KEY2 PAin(1)// PA1
# define KEYPORT GPIOA //定义IO接口组
# define KEY1 GPIO_Pin_0 //定义IO接口
# define KEY2 GPIO_Pin_1 //定义IO接口
void KEY_Init(void);//初始化
# endif
main.c
有锁存表示按键按下后状态锁定只能再按一下才能解除状态,无锁存则按下状态锁定松开则状态解除
/*********************************************************************************************
硬件支持: STM32F103C8 外部晶振8MHz RCC函数设置主频72MHz
说明:
# 本模板加载了STM32F103内部的RCC时钟设置,并加入了利用滴答定时器的延时函数。
# 可根据自己的需要增加或删减。
*********************************************************************************************/
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "led.h"
# include "key.h"
int main (void){//主程序
u8 a; //定义变量
//初始化程序
RCC_Configuration(); //时钟设置
LED_Init();//LED初始化
KEY_Init();//按键初始化
//主循环
while(1){
//示例1:无锁存
if(GPIO_ReadInputDataBit(KEYPORT,KEY1)){ //读按键接口的电平
GPIO_ResetBits(LEDPORT,LED1); //LED灯都为低电平(0)
}else{
GPIO_SetBits(LEDPORT,LED1); //LED灯都为高电平(1)
}
}
}
示例2:无锁存
//示例2:无锁存
GPIO_WriteBit(LEDPORT,LED1,(BitAction)(!GPIO_ReadInputDataBit(KEYPORT,KEY1)));
示例3:有锁存
//示例3:有锁存
if(!GPIO_ReadInputDataBit(KEYPORT,KEY1))//读按键接口的电平
{
delay_ms(20); //延时去抖动
if(!GPIO_ReadInputDataBit(KEYPORT,KEY1))//读按键接口的电平
{
GPIO_WriteBit(LEDPORT,LED1,(BitAction)(1-GPIO_ReadOutputDataBit(LEDPORT,LED1))); //LED取反
while(!GPIO_ReadInputDataBit(KEYPORT,KEY1)); //等待按键松开
}
}
示例4:有锁存
//示例4:有锁存(这个需要LED管脚连续,否则不能通用!!)
if(!GPIO_ReadInputDataBit(KEYPORT,KEY1))//读按键接口的电平
{
delay_ms(20); //延时20ms去抖动
if(!GPIO_ReadInputDataBit(KEYPORT,KEY1))//读按键接口的电平
{
//在2个LED上显示二进制加法
a++; //变量加1
if(a>3)//当变量大于3时清0
{
a=0;
}
GPIO_Write(LEDPORT,a); //直接数值操作将变量值写入LED(LED在GPIOB组的PB0和PB1上)(3的时候同时亮)
while(!GPIO_ReadInputDataBit(KEYPORT,KEY1)); //等待按键松开
}
}
FLASH 读写程序
本节用到的固件库函数
- FLASH_Unlock(手册 9.2.4)
- FLASH_ErasePage(手册 9.2.6)
- FLASH_ProgramHalfWord(手册 9.2.10)
- FLASH_ClearFlag(手册 9.2.21)
- FLASH_Lock(手册 9.2.5)
注意
首先添加 flash.c 和 flash.h 文件到工程,然后确保工程文件 Lib文件 有 stm32f10x_flash.c 文件(本次用到的 key.c,key.h,led.c,led.h,delay.c,delay.h 跟 7.3 相同)
操作一定要先擦后写
- 每页是1024个地址,
起始地址0x08000000
- 擦除操作以
页
为单位,写操作则必须以16位宽度为单位,允许跨页写入
- STM32内置FLASH擦或写时,
必须打开外部/内部高速振荡器
。 - FLASH可多次擦写
10万次
,不可死循环擦写。 - 擦写时要
避开用户程序存储区的区域
,否则会擦掉用户程序导致错误。 - 擦除一页要
10ms
(对于1k大小的一页),比较慢。而且不能单个字节的擦写
。
flash.c
注意
写入的地址不能和用户程序相冲突,我们下载的用户程序是在第0页开始写入的;所以我们一般把临时存放数据放在靠后的地址。
- 关于为什么这里可以直接去解引用地址是因为单片机实际在程序里已经开辟好这些空间了,已经通过内存映射把地址映射到某一个寄存器了(因为一开始不理解所以去vs上测试,结果一直报错原来是直接指向一个未申请开辟的空间是非法的,这跟单片机程序不一样)
# include "flash.h"
//FLASH写入数据
void FLASH_W(u32 add,u16 dat){ //参数1:32位FLASH地址。参数2:16位数据,如果需要写入更多数据,则需要加一个参数:u16 dat2
// RCC_HSICmd(ENABLE); //打开HSI时钟(因为已经开启外部高速时钟,所以不需要开启内部高速时钟)
FLASH_Unlock(); //解锁FLASH编程擦除控制器
FLASH_ClearFlag(FLASH_FLAG_BSY|FLASH_FLAG_EOP|FLASH_FLAG_PGERR|FLASH_FLAG_WRPRTERR);//清除标志位
FLASH_ErasePage(add); //擦除指定地址页
FLASH_ProgramHalfWord(add,dat); //从指定页的addr地址开始写
//FLASH_ProgramHalfWord(add+1,dat2); //写入更多数据则需要这条
FLASH_ClearFlag(FLASH_FLAG_BSY|FLASH_FLAG_EOP|FLASH_FLAG_PGERR|FLASH_FLAG_WRPRTERR);//清除标志位
FLASH_Lock(); //锁定FLASH编程擦除控制器
}
//FLASH读出数据
u16 FLASH_R(u32 add){ //参数1:32位读出FLASH地址。返回值:16位数据
u16 a;
a = *(u16*)(add);//从指定页的addr地址开始读
return a;
}
flash.h
# ifndef __FLASH_H
# define __FLASH_H
# include "sys.h"
void FLASH_W(u32 add,u16 dat);//写入更多数据需要加参数:u16 dat2
u16 FLASH_R(u32 add);
# endif
main.c
/*********************************************************************************************
程序名: FLASH读写LED状态程序
硬件支持: STM32F103C8 外部晶振8MHz RCC函数设置主频72MHz
说明:
# 本模板加载了STM32F103内部的RCC时钟设置,并加入了利用滴答定时器的延时函数。
# 可根据自己的需要增加或删减。
*********************************************************************************************/
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "led.h"
# include "key.h"
# include "flash.h"
# define FLASH_START_ADDR 0x0801f000 //写入的起始地址
int main (void){//主程序
u16 a; //定义变量
//初始化程序
RCC_Configuration(); //时钟设置
LED_Init();//LED初始化
KEY_Init();//按键初始化
a = FLASH_R(FLASH_START_ADDR);//从指定页的地址读FLASH
//断电又插电时就会读取a的值然后传进去
GPIO_Write(LEDPORT,a); //直接数值操作将变量值写入LED(LED在GPIOB组的PB0和PB1上)
//主循环
while(1){
//示例4:有锁存
if(!GPIO_ReadInputDataBit(KEYPORT,KEY1)){ //读按键接口的电平
delay_ms(20); //延时20ms去抖动
if(!GPIO_ReadInputDataBit(KEYPORT,KEY1)){ //读按键接口的电平
//在2个LED上显示二进制加法
a++; //变量加1
if(a>3){ //当变量大于3时清0
a=0;
}
GPIO_Write(LEDPORT,a); //直接数值操作将变量值写入LED(LED在GPIOB组的PB0和PB1上)
FLASH_W(FLASH_START_ADDR,a); //从指定页的地址写入FLASH,写入更多数据需要加变量参数:b
while(!GPIO_ReadInputDataBit(KEYPORT,KEY1)); //等待按键松开
}
}
}
}
实验现象
蜂鸣器驱动程序
注意
首先添加 buzzer.c 和 buzzer.h 文件到工程,然后确保工程文件 Lib文件 有 stm32f10x_flash.c 文件(本次用到的 key.c,key.h,led.c,led.h,delay.c,delay.h ,flash.c,flash.h 跟 9 相同)
buzzer.c
延时函数的值越小,频率越高声调越高,修改 BUZZER_BEEP1 里的for循环的值(200),可以决定发出声音的时间长度,值越大声音越长;注:结束后一定要把蜂鸣器输出0,而且顺序不能调换否则三极管一直导通状态,损坏蜂鸣器
# include "buzzer.h"
# include "delay.h"
void BUZZER_Init(void){ //蜂鸣器的接口初始化
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = BUZZER; //选择端口号
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //选择IO接口工作方式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置IO接口速度(2/10/50MHz)
GPIO_Init(BUZZERPORT, &GPIO_InitStructure);
GPIO_WriteBit(BUZZERPORT,BUZZER,(BitAction)(1)); //蜂鸣器接口输出高电平1
}
void BUZZER_BEEP1(void){ //蜂鸣器响一声
u16 i;
for(i=0;i<200;i++){
GPIO_WriteBit(BUZZERPORT,BUZZER,(BitAction)(0)); //蜂鸣器接口输出0
delay_us(500); //延时
GPIO_WriteBit(BUZZERPORT,BUZZER,(BitAction)(1)); //蜂鸣器接口输出高电平1
delay_us(500); //延时
}
}
buzzer.h
# ifndef __BUZZER_H
# define __BUZZER_H
# include "sys.h"
# define BUZZERPORT GPIOB //定义IO接口(使用PB组)
# define BUZZER GPIO_Pin_5 //定义IO接口(PB5 IO口)
void BUZZER_Init(void);//初始化
void BUZZER_BEEP1(void);//响一声
# endif
main.c
GPIO_Write(LEDPORT,a|0xfffc&GPIO_ReadOutputData(LEDPORT));这个地方是为了:让两个 LED 进行 flash 操作,并且不能影响其他端口所以采用了这个算法
- 按位与(&)运算通常用来对某些位清0或保留某些位
/*********************************************************************************************
程序名: 蜂鸣器驱动程序(在按键控制LED基础上加入提示音)
硬件支持: STM32F103C8 外部晶振8MHz RCC函数设置主频72MHz
说明:
# 本模板加载了STM32F103内部的RCC时钟设置,并加入了利用滴答定时器的延时函数。
# 可根据自己的需要增加或删减。
*********************************************************************************************/
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "led.h"
# include "key.h"
# include "flash.h"
# include "buzzer.h"
# define FLASH_START_ADDR 0x0801f000 //写入的起始地址
int main (void){//主程序
u16 a; //定义变量
//初始化程序
RCC_Configuration(); //时钟设置
LED_Init();//LED初始化
KEY_Init();//按键初始化
BUZZER_Init();//蜂鸣器初始化
BUZZER_BEEP1();//蜂鸣器音1
a = FLASH_R(FLASH_START_ADDR);//从指定页的地址读FLASH
GPIO_Write(LEDPORT,a|0xfffc&GPIO_ReadOutputData(LEDPORT)); //直接数值操作将变量值写入LED(LED在GPIOB组的PB0和PB1上)
//主循环
while(1){
//示例4:有锁存
if(!GPIO_ReadInputDataBit(KEYPORT,KEY1)){ //读按键接口的电平
delay_ms(20); //延时20ms去抖动
if(!GPIO_ReadInputDataBit(KEYPORT,KEY1)){ //读按键接口的电平
//在2个LED上显示二进制加法
a++; //变量加1
if(a>3){ //当变量大于3时清0
a=0;
}
GPIO_Write(LEDPORT,a|0xfffc&GPIO_ReadOutputData(LEDPORT)); //直接数值操作将变量值写入LED(LED在GPIOB组的PB0和PB1上)
BUZZER_BEEP1();//蜂鸣器音1
FLASH_W(FLASH_START_ADDR,a); //从指定页的地址写入FLASH
while(!GPIO_ReadInputDataBit(KEYPORT,KEY1)); //等待按键松开
}
}
}
}
MIDI音乐播放程序
注意
首先添加 buzzer.c 和 buzzer.h 文件到工程,然后确保工程文件 Lib文件 有 stm32f10x_flash.c 文件(本次用到的 key.c,key.h,led.c,led.h,delay.c,delay.h ,flash.c,flash.h 跟 9 相同)
buzzer.c
- delay_us(500000/music1[i*2]),第一次是数组下标为0的也就是330,产生330HZ的频率时,半周期的时间是0.5秒除以330(一个周期是1秒)
- e < music1[ix2] * music1[i x 2+1]/1000;奇位先除以1000,平均每毫秒震动多少次,最后再乘以偶数位得到在一定时间范围内震动次数
# include "buzzer.h"
# include "delay.h"
void BUZZER_Init(void){ //蜂鸣器的接口初始化
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = BUZZER; //选择端口号
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //选择IO接口工作方式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置IO接口速度(2/10/50MHz)
GPIO_Init(BUZZERPORT, &GPIO_InitStructure);
GPIO_WriteBit(BUZZERPORT,BUZZER,(BitAction)(1)); //蜂鸣器接口输出高电平1
}
void BUZZER_BEEP1(void){ //蜂鸣器响一声
u16 i;
for(i=0;i<200;i++){
GPIO_WriteBit(BUZZERPORT,BUZZER,(BitAction)(0)); //蜂鸣器接口输出0
delay_us(500); //延时
GPIO_WriteBit(BUZZERPORT,BUZZER,(BitAction)(1)); //蜂鸣器接口输出高电平1
delay_us(500); //延时
}
}
uc16 music1[78]={ //音乐1的数据表(奇数是音调,偶数是长度)
330,750,
440,375,
494,375,
523,750,
587,375,
659,375,
587,750,
494,375,
392,375,
440,1500,
330,750,
440,375,
494,375,
523,750,
587,375,
659,375,
587,750,
494,375,
392,375,
784,1500,
659,750,
698,375,
784,375,
880,750,
784,375,
698,375,
659,750,
587,750,
659,750,
523,375,
494,375,
440,750,
440,375,
494,375,
523,750,
523,750,
494,750,
392,750,
440,3000
};
void MIDI_PLAY(void){ //MIDI音乐(决定音调的长度)
u16 i,e;
for(i=0;i<39;i++){//播放哪个音调(2个一组一共78/2=39组)
for(e=0;e<music1[i*2]*music1[i*2+1]/1000;e++){//数组右边
GPIO_WriteBit(BUZZERPORT,BUZZER,(BitAction)(0)); //蜂鸣器接口输出0
delay_us(500000/music1[i*2]); //延时
GPIO_WriteBit(BUZZERPORT,BUZZER,(BitAction)(1)); //蜂鸣器接口输出高电平1
delay_us(500000/music1[i*2]); //延时
}
}
}
buzzer.h
# ifndef __BUZZER_H
# define __BUZZER_H
# include "sys.h"
# define BUZZERPORT GPIOB //定义IO接口
# define BUZZER GPIO_Pin_5 //定义IO接口
void BUZZER_Init(void);//初始化
void BUZZER_BEEP1(void);//响一声
void MIDI_PLAY(void);
# endif
main.c
/*********************************************************************************************
硬件支持: STM32F103C8 外部晶振8MHz RCC函数设置主频72MHz
说明:
# 本模板加载了STM32F103内部的RCC时钟设置,并加入了利用滴答定时器的延时函数。
# 可根据自己的需要增加或删减。
*********************************************************************************************/
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "led.h"
# include "key.h"
# include "flash.h"
# include "buzzer.h"
# define FLASH_START_ADDR 0x0801f000 //写入的起始地址
int main (void){//主程序
u16 a; //定义变量
//初始化程序
RCC_Configuration(); //时钟设置
LED_Init();//LED初始化
KEY_Init();//按键初始化
BUZZER_Init();//蜂鸣器初始化
// BUZZER_BEEP1();//蜂鸣器音1
MIDI_PLAY(); //播放MIDI音乐
a = FLASH_R(FLASH_START_ADDR);//从指定页的地址读FLASH
GPIO_Write(LEDPORT,a|0xfffc&GPIO_ReadOutputData(LEDPORT)); //直接数值操作将变量值写入LED(LED在GPIOB组的PB0和PB1上)
//主循环
while(1){
//示例4:有锁存
if(!GPIO_ReadInputDataBit(KEYPORT,KEY1)){ //读按键接口的电平
delay_ms(20); //延时20ms去抖动
if(!GPIO_ReadInputDataBit(KEYPORT,KEY1)){ //读按键接口的电平
//在2个LED上显示二进制加法
a++; //变量加1
if(a>3){ //当变量大于3时清0
a=0;
}
GPIO_Write(LEDPORT,a|0xfffc&GPIO_ReadOutputData(LEDPORT)); //直接数值操作将变量值写入LED(LED在GPIOB组的PB0和PB1上)
BUZZER_BEEP1();//蜂鸣器音1
FLASH_W(FLASH_START_ADDR,a); //从指定页的地址写入FLASH
while(!GPIO_ReadInputDataBit(KEYPORT,KEY1)); //等待按键松开
}
}
}
}