P3 串口电路一键下载原理分析
上拉电路
三极管
b为基极,c为集电极,e为发射极
作开关使用时,NPN型三极管:b接低电平,则电路截止,b接高电平则电路饱和导通;PNP型三极管:b接高电平,则电路截止,b接低电平则电路饱和导通
GPIO:通用输入输出
P5 初识STM32
PCB打样选择深圳嘉立创公司
引脚顺序:从黑点开始逆时针旋转为正方向
写好的程序编译之后都是一条条指令,存放在 FLASH中,内核要读取这些指令来执行程序就必须通过 ICode 总线
FLASH用来存放程序
SRAM用来存放变量
(unsigned int*)将地址进行强制类型转换
*+地址单元 可以对该地址里的数据进行操作
该代码指1左移10位,其他都置为0,即第10位置1,其他位都置0
|= 或等于,即第10位为1其他位为0的与原来的相或,然后赋值给原来的置,也就是将第10位置为1,其他位不变
同理,&=,或等于,~为取反,该操作为将第10位取反,其他位不变
头文件中<>尖括号表示这个头文件不在当前工程的目录下,而是在编译器的目录下
头文件中“”表示这个头文件在当前工程的目录下,如果在当前工程的目录下找不到再去编译器目录下找。
32位寄存器,每个寄存器有32位
P7点亮一个LED灯
#include"stm32f10x.h"
int main (void)
{
///打开 GPIOB 端口的时钟
*(unsigned int*)0x40021018 |=(1<<3);
//配置IO口为输出
*(unsigned int*)0x40010C00 |= (1<<(4*5));//0x40010C00地址中第一位 置位为1
//控制 ODR 寄存器
*(unsigned int*)0x40010C0C &= ~(1<<0);//不能直接0x40010C0C &= ~(1<<0);编译器会把它当做立即数来处理,0x40010C0C第一位 置位为0
}
//置位 |= , 清0 &=
void SystemInit(void)
{
//函数体为空,目的是为了骗过编译器不要报错
}
P8 GPIO功能框图讲解(重点)
GPIO:general purpose input output,通用输入输出端口
IO端口位的基本结构
第1-4部分为输出,5-7部分为输入
第1部分位保护电路,I/O引脚电压过高时通过上方晶体管进入VDD(3.3V电压),电压过低(负值)时通过下方电路Vss(接地)进行保护
第2部分位推挽输出电路,
为反相器,输入1则输出0,输入0则输出1
开漏输出:只能输出低电平,不能输出高电平,需要外加上拉电路才能输出高电平
开漏电路一般用在I方C或SM bus总线上
第3部分,输出数据寄存器(ODR)能直接控制输出为高电平或低电平,或者通过位设置/清除寄存器(BSRR)来控制输出数据寄存器间接输出高电平或低电平
第4部分, 复用功能输出,使用该功能时第3部分将不起作用,通过片上外设(串口)来控制引脚的输出
第5部分为输入数据寄存器(IDR),能够读取外部引脚的数据,也可以读取ODR中的数据
上拉电路和下拉电路通过端口配置低寄存器(CRL)来设置上拉/下拉输入模式
具体是上拉还是下拉可以通过端口位设置/清除寄存器(BSRR)来通过软件控制
肖特基触发器相当于门禁,输入电平大于2.0V时为高电平,输入电平小鱼1.2V时为低电平
第6部分复用功能输入从片上外设输入,
第7部分模拟输入不经过肖特基触发器处理,直接采集模拟信号
直接对内存进行编程
stm32f10x.h文件中代码
//用来存放STM32寄存器映射的代码
//外设 pheriphral
#define PHERIPH_BASE ((unsigned int)0x40000000)
#define APB1PHERIPH_BASE PHERIPH_BASE
#define APB2PHERIPH_BASE (PHERIPH_BASE + 0x10000)
#define AHBPHERIPH_BASE (PHERIPH_BASE + 0x20000)
#define RCC_BASE (AHBPHERIPH_BASE + 0x1000)
#define GPIOB_BASE (APB2PHERIPH_BASE + 0x0C00)
#define RCC_APB2ENR *(unsigned int*)(RCC_BASE + 0x18)
#define GPIOB_CRL *(unsigned int*)(GPIOB_BASE + 0x00)
#define GPIOB_CRH *(unsigned int*)(GPIOB_BASE + 0x04)
#define GPIOB_ODR *(unsigned int*)(GPIOB_BASE + 0x0C)
#define GPIOB_BSRR *(unsigned int*)(GPIOB_BASE + 0x10)
#define GPIOB_BRR *(unsigned int*)(GPIOB_BASE + 0x14)
main,c文件中的代码
#include "stm32f10x.h"
int main (void)
{
#if 0
///打开 GPIOB 端口的时钟
*(unsigned int*)0x40021018 |=(1<<3);
//配置IO口为输出
*(unsigned int*)0x40010C00 &= ~((0x0f)<<(4*5));//先对该寄存器进行清0
*(unsigned int*)0x40010C00 |= (1<<(4*5));//0x40010C00地址中第一位 置位为1
//控制 ODR 寄存器
*(unsigned int*)0x40010C0C &= ~(1<<5);//不能直接0x40010C0C &= ~(1<<0);编译器会把它当做立即数来处理,0x40010C0C第一位 置位为0
#else
///打开 GPIOB 端口的时钟
RCC_APB2ENR |=(1<<3);
//配置IO口为输出
GPIOB_CRL &= ~((0x0f)<<(4*1));
GPIOB_CRL |= (1<<(4*1));
//控制 ODR 寄存器来直接控制输出
//GPIOB_ODR |= (1<<1);
//控制 BSRR 寄存器来间接控制输出
GPIOB_BSRR &= ~(1<<1);
#endif
}
//置位 |= , 清0 &=
void SystemInit(void)
{
//函数体为空,目的是为了骗过编译器不要报错
}
P12 自己写库——构建库函数
采用结构体的方式来控制寄存器
先定义一个结构体(示例中为GPIO_TypeDef),该结构体的成员的类型和分布顺序都和外设的寄存器(示例程序为GPIOB寄存器)的排列方式一样,然后找到外设的基地址,例如程序中的GPIOB_BASE,然后把该地址强制类型转换为GPIO_TypeDef这种结构体类型的指针,这个指针经过强制类型转换将指向该外设的所有寄存器。用结构体加指针即可指向该外设的寄存器。
stm32f10x.h文件中代码
//用来存放STM32寄存器映射的代码
//外设 pheriphral
#define PHERIPH_BASE ((unsigned int)0x40000000)
#define APB1PHERIPH_BASE PHERIPH_BASE
#define APB2PHERIPH_BASE (PHERIPH_BASE + 0x10000)
#define AHBPHERIPH_BASE (PHERIPH_BASE + 0x20000)
#define RCC_BASE (AHBPHERIPH_BASE + 0x1000)
#define GPIOB_BASE (APB2PHERIPH_BASE + 0x0C00)
#define RCC_APB2ENR *(unsigned int*)(RCC_BASE + 0x18)
//#define GPIOB_CRL *(unsigned int*)(GPIOB_BASE + 0x00)
//#define GPIOB_CRH *(unsigned int*)(GPIOB_BASE + 0x04)
//#define GPIOB_IDR *(unsigned int*)(GPIOB_BASE + 0x08)
//#define GPIOB_ODR *(unsigned int*)(GPIOB_BASE + 0x0C)
//#define GPIOB_BSRR *(unsigned int*)(GPIOB_BASE + 0x10)
//#define GPIOB_BRR *(unsigned int*)(GPIOB_BASE + 0x14)
//#define GPIOB_LCKR *(unsigned int*)(GPIOB_BASE + 0x18)
typedef unsigned int uint32_t;
typedef unsigned short uint16_t;
typedef struct
{
uint32_t CR;
uint32_t CFGR;
uint32_t CIR;
uint32_t APB2RSTR;
uint32_t APB1RSTR;
uint32_t AHBENR;
uint32_t APB2ENR;
uint32_t APB1ENR;
uint32_t BDCR;
uint32_t CSR;
}RCC_TypeDef;
#define RCC ((RCC_TypeDef*)RCC_BASE)//强制类型转换
typedef struct
{
uint32_t CRL;
uint32_t CRH;
uint32_t IDR;
uint32_t ODR;
uint32_t BSRR;
uint32_t BRR;
uint32_t LCKR;
}GPIO_TypeDef;
#define GPIOB ((GPIO_TypeDef*)GPIOB_BASE)//强制类型转换
main,c文件中的代码
#include "stm32f10x.h"
int main (void)
{
///打开 GPIOB 端口的时钟
RCC->APB2ENR |=(1<<3);
//配置IO口为输出
GPIOB->CRL &= ~((0x0f)<<(4*1));
GPIOB->CRL |= (1<<(4*1));
//控制 ODR 寄存器来直接控制输出
//GPIOB_ODR |= (1<<1);
//控制 BSRR 寄存器来间接控制输出
GPIOB->BSRR &= ~(1<<1);
#endif
}
//置位 |= , 清0 &=
void SystemInit(void)
{
//函数体为空,目的是为了骗过编译器不要报错
}
}
为了避免头文件被多次调用产生错误警告,在编写头文件的时候开头和结尾加上几个语句便可解决
开头
#ifndef __STM3210X_H//头文件名称为stm32f10x.h,则此处代码对__STM3210X_H进行宏定义
#define __STM3210X_H//同上
结尾
#endif /*__STM3210X_H*/
定义函数来控制寄存器
增加程序的可读性,定义了端口置位和复位函数来控制端口的输出
#ifndef __STM32F10X_GPIO_H
#define __STM32F10X_GPIO_H
#include "stm32f10x.h"
#define GPIO_Pin_0 ((uint16_t)0x0001) /*!<选择Pin0 */ //(00000000 00000001)b
#define GPIO_Pin_1 ((uint16_t)0x0002) /*!<选择Pin1 */ //(00000000 00000010)b
#define GPIO_Pin_2 ((uint16_t)0x0004) /*!<选择Pin2 */ //(00000000 00000100)b
#define GPIO_Pin_3 ((uint16_t)0x0008) /*!<选择Pin3 */ //(00000000 00001000)b
#define GPIO_Pin_4 ((uint16_t)0x0010) /*!<选择Pin4 */ //(00000000 00010000)b
#define GPIO_Pin_5 ((uint16_t)0x0020) /*!<选择Pin5 */ //(00000000 00100000)b
#define GPIO_Pin_6 ((uint16_t)0x0040) /*!<选择Pin6 */ //(00000000 01000000)b
#define GPIO_Pin_7 ((uint16_t)0x0080) /*!<选择Pin7 */ //(00000000 10000000)b
#define GPIO_Pin_8 ((uint16_t)0x0100) /*!<选择Pin8 */ //(00000001 00000000)b
#define GPIO_Pin_9 ((uint16_t)0x0200) /*!<选择Pin9 */ //(00000010 00000000)b
#define GPIO_Pin_10 ((uint16_t)0x0400) /*!<选择Pin10 */ //(00000100 00000000)b
#define GPIO_Pin_11 ((uint16_t)0x0800) /*!<选择Pin11 */ //(00001000 00000000)b
#define GPIO_Pin_12 ((uint16_t)0x1000) /*!<选择Pin12 */ //(00010000 00000000)b
#define GPIO_Pin_13 ((uint16_t)0x2000) /*!<选择Pin13 */ //(00100000 00000000)b
#define GPIO_Pin_14 ((uint16_t)0x4000) /*!<选择Pin14 */ //(01000000 00000000)b
#define GPIO_Pin_15 ((uint16_t)0x8000) /*!<选择Pin15 */ //(10000000 00000000)b
#define GPIO_Pin_All ((uint16_t)0x0001) /*!<选择全部引脚 */ //(11111111 11111111)b
void GPIO_SetBits(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin);
void GPIO_ResetBits(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin);
#endif /* __STM32F10X_GPIO_H*/
#include "stm32f10x_gpio.h"
void GPIO_SetBits(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin)
{
GPIOx->BSRR |= GPIO_Pin;//控制BSRR寄存器
}
void GPIO_ResetBits(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin)
{
GPIOx->BRR |= GPIO_Pin;//控制BRR寄存器
}
#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
int main (void)
{
///打开 GPIOB 端口的时钟
RCC->APB2ENR |=(1<<3);
//配置IO口为输出
GPIOB->CRL &= ~((0x0f)<<(4*0));
GPIOB->CRL |= (1<<(4*0));
GPIO_SetBits(GPIOB,GPIO_Pin_0);//让PB0端口置位,即调用函数控制将LED关闭
GPIO_ResetBits(GPIOB,GPIO_Pin_0);//让PB0端口复位,即调用函数将LED打开
#endif
}
//置位 |= , 清0 &=
void SystemInit(void)
{
//函数体为空,目的是为了骗过编译器不要报错
}
#ifndef __STM3210X_H
#define __STM3210X_H
//用来存放STM32寄存器映射的代码
//外设 pheriphral
#define PHERIPH_BASE ((unsigned int)0x40000000)
#define APB1PHERIPH_BASE PHERIPH_BASE
#define APB2PHERIPH_BASE (PHERIPH_BASE + 0x10000)
#define AHBPHERIPH_BASE (PHERIPH_BASE + 0x20000)
#define RCC_BASE (AHBPHERIPH_BASE + 0x1000)
#define GPIOB_BASE (APB2PHERIPH_BASE + 0x0C00)
#define RCC_APB2ENR *(unsigned int*)(RCC_BASE + 0x18)
typedef unsigned int uint32_t;
typedef unsigned short uint16_t;
typedef struct
{
uint32_t CR;
uint32_t CFGR;
uint32_t CIR;
uint32_t APB2RSTR;
uint32_t APB1RSTR;
uint32_t AHBENR;
uint32_t APB2ENR;
uint32_t APB1ENR;
uint32_t BDCR;
uint32_t CSR;
}RCC_TypeDef;
#define RCC ((RCC_TypeDef*)RCC_BASE)
typedef struct
{
uint32_t CRL;
uint32_t CRH;
uint32_t IDR;
uint32_t ODR;
uint32_t BSRR;
uint32_t BRR;
uint32_t LCKR;
}GPIO_TypeDef;
#define GPIOB ((GPIO_TypeDef*)GPIOB_BASE)
#endif /*__STM3210X_H*/
利用结构体和初始化函数来进行进行初始化
C语言中变量的定义要放在最开头,不能在程序中间定义,不然会报错
教程中写初始化函数的目的是让程序的可读性更好,不用去查参考手册也能看懂程序要完成什么操作
步骤:定义一个外设的初始化结构体,把能够配置出来的寄存器的所有参数(速度、模式等)全部枚举出来,编程的时候可以把枚举定义的参数写到初始化的结构体里面,最后调用外设的初始化函数,把结构体中配置好的成员写到相应的寄存器里面,实现配置寄存器的工作。
#ifndef __STM32F10X_GPIO_H
#define __STM32F10X_GPIO_H
#include "stm32f10x.h"
#define GPIO_Pin_0 ((uint16_t)0x0001) /*!<选择Pin0 */ //(00000000 00000001)b
#define GPIO_Pin_1 ((uint16_t)0x0002) /*!<选择Pin1 */ //(00000000 00000010)b
#define GPIO_Pin_2 ((uint16_t)0x0004) /*!<选择Pin2 */ //(00000000 00000100)b
#define GPIO_Pin_3 ((uint16_t)0x0008) /*!<选择Pin3 */ //(00000000 00001000)b
#define GPIO_Pin_4 ((uint16_t)0x0010) /*!<选择Pin4 */ //(00000000 00010000)b
#define GPIO_Pin_5 ((uint16_t)0x0020) /*!<选择Pin5 */ //(00000000 00100000)b
#define GPIO_Pin_6 ((uint16_t)0x0040) /*!<选择Pin6 */ //(00000000 01000000)b
#define GPIO_Pin_7 ((uint16_t)0x0080) /*!<选择Pin7 */ //(00000000 10000000)b
#define GPIO_Pin_8 ((uint16_t)0x0100) /*!<选择Pin8 */ //(00000001 00000000)b
#define GPIO_Pin_9 ((uint16_t)0x0200) /*!<选择Pin9 */ //(00000010 00000000)b
#define GPIO_Pin_10 ((uint16_t)0x0400) /*!<选择Pin10 */ //(00000100 00000000)b
#define GPIO_Pin_11 ((uint16_t)0x0800) /*!<选择Pin11 */ //(00001000 00000000)b
#define GPIO_Pin_12 ((uint16_t)0x1000) /*!<选择Pin12 */ //(00010000 00000000)b
#define GPIO_Pin_13 ((uint16_t)0x2000) /*!<选择Pin13 */ //(00100000 00000000)b
#define GPIO_Pin_14 ((uint16_t)0x4000) /*!<选择Pin14 */ //(01000000 00000000)b
#define GPIO_Pin_15 ((uint16_t)0x8000) /*!<选择Pin15 */ //(10000000 00000000)b
#define GPIO_Pin_All ((uint16_t)0x0001) /*!<选择全部引脚 */ //(11111111 11111111)b
typedef enum//枚举
{
GPIO_Speed_10MHZ = 1, //10 MHZ (01)b
GPIO_Speed_2MHZ, //2MHZ (10)b
GPIO_Speed_50MHZ //50MHZ (11)b
}GPIOSpeed_TypeDef;
typedef enum
{
GPIO_Mode_AIN = 0x0, //模拟输入 (0000 0000)b
GPIO_Mode_IN_FLOATING = 0x04, //浮空输入 (0000 0100)b
GPIO_Mode_IPD = 0x28, //下拉输入 (0010 1000)b
GPIO_Mode_IPU = 0x48, //上拉输入 (0100 1000)b
GPIO_Mode_Out_OD = 0x14, //开漏输出 (0001 0100)b
GPIO_Mode_Out_PP = 0x10, //推挽输出 (0001 0000)b
GPIO_Mode_AF_OD = 0x1C, //复用开漏输出 (0001 1100)b
GPIO_Mode_AF_PP = 0x18, //复用推挽输出 (0001 1000)b
}GPIOMode_TypeDef;
typedef struct
{
uint16_t GPIO_Pin;
uint16_t GPIO_Speed;
uint16_t GPIO_Mode;
}GPIO_InitTypeDef;
void GPIO_SetBits(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin);
void GPIO_ResetBits(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin);
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
#endif /* __STM32F10X_GPIO_H*/
#include "stm32f10x_gpio.h"
void GPIO_SetBits(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin)
{
GPIOx->BSRR |= GPIO_Pin;//控制BSRR寄存器
}
void GPIO_ResetBits(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin)
{
GPIOx->BRR |= GPIO_Pin;//控制BRR寄存器
}
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
{
uint32_t currentmode = 0x00, currentpin = 0x00, pinpos = 0x00, pos = 0x00;
uint32_t tmpreg = 0x00, pinmask = 0x00;
/*---------------------- GPIO 模式配置 --------------------------*/
// 把输入参数GPIO_Mode的低四位暂存在currentmode
currentmode = ((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x0F);
// bit4是1表示输出,bit4是0则是输入
// 判断bit4是1还是0,即首选判断是输入还是输出模式
if ((((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00)
{
// 输出模式则要设置输出速度
currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed;
}
/*-------------GPIO CRL 寄存器配置 CRL寄存器控制着低8位IO- -------*/
// 配置端口低8位,即Pin0~Pin7
if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00)
{
// 先备份CRL寄存器的值
tmpreg = GPIOx->CRL;
// 循环,从Pin0开始配对,找出具体的Pin
for (pinpos = 0x00; pinpos < 0x08; pinpos++)
{
// pos的值为1左移pinpos位
pos = ((uint32_t)0x01) << pinpos;
// 令pos与输入参数GPIO_PIN作位与运算,为下面的判断作准备
currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;
//若currentpin=pos,则找到使用的引脚
if (currentpin == pos)
{
// pinpos的值左移两位(乘以4),因为寄存器中4个寄存器位配置一个引脚
pos = pinpos << 2;
//把控制这个引脚的4个寄存器位清零,其它寄存器位不变
pinmask = ((uint32_t)0x0F) << pos;
tmpreg &= ~pinmask;
// 向寄存器写入将要配置的引脚的模式
tmpreg |= (currentmode << pos);
// 判断是否为下拉输入模式
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
{
// 下拉输入模式,引脚默认置0,对BRR寄存器写1可对引脚置0
GPIOx->BRR = (((uint32_t)0x01) << pinpos);
}
else
{
// 判断是否为上拉输入模式
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
{
// 上拉输入模式,引脚默认值为1,对BSRR寄存器写1可对引脚置1
GPIOx->BSRR = (((uint32_t)0x01) << pinpos);
}
}
}
}
// 把前面处理后的暂存值写入到CRL寄存器之中
GPIOx->CRL = tmpreg;
}
/*-------------GPIO CRH 寄存器配置 CRH寄存器控制着高8位IO- -----------*/
// 配置端口高8位,即Pin8~Pin15
if (GPIO_InitStruct->GPIO_Pin > 0x00FF)
{
// // 先备份CRH寄存器的值
tmpreg = GPIOx->CRH;
// 循环,从Pin8开始配对,找出具体的Pin
for (pinpos = 0x00; pinpos < 0x08; pinpos++)
{
pos = (((uint32_t)0x01) << (pinpos + 0x08));
// pos与输入参数GPIO_PIN作位与运算
currentpin = ((GPIO_InitStruct->GPIO_Pin) & pos);
//若currentpin=pos,则找到使用的引脚
if (currentpin == pos)
{
//pinpos的值左移两位(乘以4),因为寄存器中4个寄存器位配置一个引脚
pos = pinpos << 2;
//把控制这个引脚的4个寄存器位清零,其它寄存器位不变
pinmask = ((uint32_t)0x0F) << pos;
tmpreg &= ~pinmask;
// 向寄存器写入将要配置的引脚的模式
tmpreg |= (currentmode << pos);
// 判断是否为下拉输入模式
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
{
// 下拉输入模式,引脚默认置0,对BRR寄存器写1可对引脚置0
GPIOx->BRR = (((uint32_t)0x01) << (pinpos + 0x08));
}
// 判断是否为上拉输入模式
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
{
// 上拉输入模式,引脚默认值为1,对BSRR寄存器写1可对引脚置1
GPIOx->BSRR = (((uint32_t)0x01) << (pinpos + 0x08));
}
}
}
// 把前面处理后的暂存值写入到CRH寄存器之中
GPIOx->CRH = tmpreg;
}
}
#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
int main (void)
{
GPIO_InitTypeDef GPIO_InitStructure;// C语言中变量的定义要放在最开头,不能在程序中间定义,不然会报错
///打开 GPIOB 端口的时钟
RCC->APB2ENR |=(1<<3);
//配置IO口为输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHZ;
GPIO_Init(GPIOB, &GPIO_InitStructure);//&为取地址符,函数定义时此处为结构体的地址,故使用函数时这里调用地址
GPIO_SetBits(GPIOB,GPIO_Pin_0);//调用函数控制将LED关闭
GPIO_ResetBits(GPIOB,GPIO_Pin_0);//调用函数将LED打开
#endif
}
//置位 |= , 清0 &=
void SystemInit(void)
{
//函数体为空,目的是为了骗过编译器不要报错
}
#ifndef __STM3210X_H
#define __STM3210X_H
//用来存放STM32寄存器映射的代码
//外设 pheriphral
#define PHERIPH_BASE ((unsigned int)0x40000000)
#define APB1PHERIPH_BASE PHERIPH_BASE
#define APB2PHERIPH_BASE (PHERIPH_BASE + 0x10000)
#define AHBPHERIPH_BASE (PHERIPH_BASE + 0x20000)
#define RCC_BASE (AHBPHERIPH_BASE + 0x1000)
#define GPIOB_BASE (APB2PHERIPH_BASE + 0x0C00)
#define RCC_APB2ENR *(unsigned int*)(RCC_BASE + 0x18)
//#define GPIOB_CRL *(unsigned int*)(GPIOB_BASE + 0x00)
//#define GPIOB_CRH *(unsigned int*)(GPIOB_BASE + 0x04)
//#define GPIOB_IDR *(unsigned int*)(GPIOB_BASE + 0x08)
//#define GPIOB_ODR *(unsigned int*)(GPIOB_BASE + 0x0C)
//#define GPIOB_BSRR *(unsigned int*)(GPIOB_BASE + 0x10)
//#define GPIOB_BRR *(unsigned int*)(GPIOB_BASE + 0x14)
//#define GPIOB_LCKR *(unsigned int*)(GPIOB_BASE + 0x18)
typedef unsigned int uint32_t;
typedef unsigned short uint16_t;
typedef struct
{
uint32_t CR;
uint32_t CFGR;
uint32_t CIR;
uint32_t APB2RSTR;
uint32_t APB1RSTR;
uint32_t AHBENR;
uint32_t APB2ENR;
uint32_t APB1ENR;
uint32_t BDCR;
uint32_t CSR;
}RCC_TypeDef;
#define RCC ((RCC_TypeDef*)RCC_BASE)
typedef struct
{
uint32_t CRL;
uint32_t CRH;
uint32_t IDR;
uint32_t ODR;
uint32_t BSRR;
uint32_t BRR;
uint32_t LCKR;
}GPIO_TypeDef;
#define GPIOB ((GPIO_TypeDef*)GPIOB_BASE)
#endif /*__STM3210X_H*/
P10 固件库编程
固件库包含哪些文件
1-汇编编写的启动文件
startup_stm32f10x_hd.s:设置堆栈指针、设置PC指针、初始化中断向量表、配置系统时钟、对用C库函数_main最终去到C的世界
2-时钟配置文件
system_stm32f10x.c:把外部时钟HSE=8M,经过PLL倍频为72M。
3-外设相关的
stm32f10x.h:实现了内核之外的外设的寄存器映射
xxx:GPIO、USRAT、I2C、SPI、FSMC
stm32f10x_xx.c:外设的驱动函数库文件
stm32f10x_xx.h:存放外设的初始化结构体,外设初始化结构体成员的参数列表,外设固件库函数的声明
4-内核相关的
CMSIS - Cortex 微控制器软件接口标准
core_cm3.h:实现了内核里面外设的寄存器映射
core_cm3.c:内核外设的驱动固件库
NVIC(嵌套向量中断控制器)、SysTick(系统滴答定时器)
misc.h
misc.c
5-头文件的配置文件
stm32f10x_conf.h:头文件的头文件
//stm32f10x_usart.h
//stm32f10x_i2c.h
//stm32f10x_spi.h
//stm32f10x_adc.h
//stm32f10x_fsmc.h
......
6-专门存放中断服务函数的C文件
stm32f10x_it.c
stm32f10x_it.h
中断服务函数你可以随意放在其他的地方,并不是一定要放在stm32f10x_it.c,当中断较多时单独放在一个文件里可以便于查看中断并分配优先级。
#include "stm32f10x.h" // 相当于51单片机中的 #include <reg51.h>
int main(void)
{
// 来到这里的时候,系统的时钟已经被配置成72M。
}
P11 新建工程
新建工程时如果不用设置好的模板,要先配置一下环境
每新建一个头文件都要指定路径
为了避免头文件被多次调用产生错误警告,在编写头文件的时候开头和结尾加上几个语句便可解决
例如对于头文件
开头
#ifndef _BSP_LED_H//起名为_BSP_LED_H为行业习惯是按照这种格式起名,也可以用来区分自己写的宏,其实起什么名字都可以
#define _BSP_LED_H
结尾
#endif /*_BSP_LED_H*/
这样只有在bsp_led.h文件没有编译的时候才会编译该文件,如果已经编译过了就会跳过不执行
P12 GPIO输出-使用固件库点亮LED
开时钟函数要放在配置函数之前,不然灯亮不了
P13 GPIO输入-按键检测
输入的是上升沿电平
电容来进行消除抖动(按键时有20ms左右的抖动),为硬件消抖,软件上就不需要消抖了
1k电阻为保护电阻,减小电流,防止引脚击穿
P14 位带操作
一个地址对应一个字节,一个字节有8位
P15 启动文件讲解
中断优先级数值越小,优先级越高
如果两个中断设置的软件优先级相同,那么看二者硬件编号哪个小,小的优先级高
写中断服务函数时,函数名一定要和这个向量表表里的函数一样
P16 RCC时钟
可以通过读取MCO时钟输出来检测SYSCLK系统时钟配置是否正确
首先使用HSE(8MHz)(高速外部时钟)来配置系统时钟, 不分频经过PLLXTPRE,然后进入锁相环时钟源配置PLLSRC这个位,然后进入锁相环倍频因子,配置成9倍频,得出锁相环时钟PLLCLK为72MHz,配置SYSCLK系统时钟时面临三个选择:HSI/HSE/PLLCLK,选择锁相环时钟等于系统时钟(72MHz)。
接下来配置三条总线的分频因子,AHB高速总线配置1分频,出来的AHB时钟为72MHz,然后进过APB1和APB2的预分频因子,APB1因为最大为36MHz,所以选择2分频,APB1为高速总线,最大为72MHz,所以配置成1分频。那么总线已经配置好了使用外设时再来配置外设的分频因子。
时钟安全系统CSS
如果没有使能CSS中断的话,系统会在硬件上把时钟切换到HSI上,也就是8MHz,用户需要通过在软件上选择HSI间接控制PLL来产生64MHz(8/2*16)的时钟,有的外设还能使用,但是最保险的还是报警或通知用户有故障产生。
P17 中断应用总结
先比较主优先级,再比较子优先级,谁小谁优先级高,如果都一样则比较硬件中断编号(中断向量表中可查),谁小谁优先级高
NVIC中的中断使能为总开关,外设的中断使能为小开关,只有总开关和使用的外设的开关都打开才能使用该外设的中断。
中断源在stm32f10x.h文件里的IRQn_Type结构体里面找
中断服务函数的名称要和启动文件startup_stm32f10x_hd.s里向量表里定义的中断服务函数名称一样
中断服务函数都放在stm32f10x_it.c里,便于统一管理和分配优先级
P18 EXTI(外部中断/事件控制器)
GPIO要产生中断一定要读取一个电平,电平有上升沿或下降沿,这两种信号通过EXTI产生中断,然后交给内核NVIC。即先到GPIO再到EXTI再到NVIC三步。
NVIC的固件库文件在misc.h中
触发事件: 脉冲发生器在单片机内部,触发后产生一个脉冲信号,可以用来控制ADC开始采集或者定时器开始计数等事件。
P19 SysTick 系统定时器
P20 通信的基本概念
同步与异步:有时钟信号的都叫同步,没有时钟信号的都叫异步
P21 串口通信
从单片机或者芯片中出来的都叫TTL电平
RS232:逻辑1为-15V,逻辑0为+15V
stm32串口功能框图讲解
n表示低电平有效
注意串口1是挂在APB2总线上的,串口2、3、4、5是挂载到APB1总线上的,编程的时候要注意修改时钟
串口是全双工通讯
APB1为低速时钟,为36M
USART_BRR寄存器分为整数部分和小数部分,小数部分为四位,最小分辨率为1/16=0.065.
串口发送都是一个字节一个字节的发,即8位8位的发
多个字节或数组或字符串发送要用循环语句
P22 DMA
M指Memory,即寄存器,P指Peripheral,即外设
DMA2只存在于大容量和互联型的产品中
Memory to Memory模式可以访问DMA1、DMA2的任意通道
P23 存储器
RAM(random access memory)即随机存储器:掉电数据会丢失,但读写速度较快
ROM(Read-Only Memory)即只读存储器:掉电后数据不会丢失,但读写速度较慢
P24 I2C
24.1物理层
当设备空闲时输出高阻态可以防止对其他设备产生干扰
当输出逻辑1时也是高阻态,想当于是将主机直接连接到上拉电阻,利用上拉电阻的电压来产生高电平(如图所示)
从机通过输出低电平来将总线拉成低电平,来表示该从机正在占用总线,其他设备就不要再用了。
开漏输出就是不输出电压,控制输出低电平时引脚接地,控制输出高电平时引脚既不输出高电平,也不输出低电平,为高阻态。如果外接上拉电阻,则在输出高电平时电压会拉到上拉电阻的电源电压。这种方式适合在连接的外设电压比单片机电压低的时候。
24.2协议层
通讯复合格式可以读某个寄存器中的某个位
7位的设备地址指的是从机的地址,8位的设备地址指的是从机地址加上“读/写”的一位
发送端释放SDA控制权时,若数据接收端为高电平,则停止数据传输,若数据接收端为低电平,则继续传输
PCLK1是指APB1的时钟
P25 SPI——读写串行FLASH
就算只是从机读取数据也要让主机给从机发送数据才能执行操作,因为只有主机发送了数据,SCK时钟才会开启
norflash写入时就没有大小的限制了,可以一个字节一个字节的写入 ,不需要一个扇区一个扇区的写入
或等于“|=”想当于相加,与等于“&=”相当于其本身(但可以用来取多字节的某个字节,比如32位数据0xaabbccdd与8位数据0xff进行&操作,可以得到其最低8位的数据,即0xdd)
判断数据是否溢出(超出栈的最大空间),可以通过在stm32f10x_it.c文件中的HardFalut_Handler函数处设置断点进行调试,若程序卡死在这里则表示确实是溢出造成的错误,可通过将数组定义在函数外,即定义为全局变量来解决问题,全局变量使用的不是栈空间,而是内存的空余的空间。