目录
1,GPIO数据手册
1.1,端口配置寄存器
1.2,端口输入数据寄存器和端口输出数据寄存器
1.3,端口位设置/清除寄存器
1.4,端口位清除寄存器
2,原理图分析和MDK工程
3,写代码通过GPIO点亮LED
4,STM32时钟设置函数移植
5,STM32外设编程经验总结
1,GPIO数据手册
每个GPI/O端口有两个32位配置寄存器(GPIOx_CRL,GPIOx_CRH),两个32位数据寄存器(GPIOx_IDR和GPIOx_ODR),一个32位置位/复位寄存器(GPIOx_BSRR),一个16位复位寄存器(GPIOx_BRR)和一个32位锁定寄存器(GPIOx_LCKR)。
GPIOx_CRL,GPIOx_CRH中的x代表端口编号,CR代表control register,L代表Low,H代表High;
GPIOx_IDR和GPIOx_ODR中的IDR代表input data register,ODR代表output data register;
GPIOx_LCKR是锁定寄存器,可以将输出锁定,提升安全性,也是STM32与51单片机的区别,STM32用于工业控制的一个特点;
GPIO端口的每个位可以由软件分别配置成多种模式:
─ 输入浮空
─ 输入上拉
─ 输入下拉
─ 模拟输入
─ 开漏输出
─ 推挽式输出
─ 推挽式复用功能
─ 开漏复用功能
前四种是输入模式,其中前三个是数字输入,第四个是模拟输入。
下图为IO端口的基本结构:
通过设置可将端口设置为输入或输出端口,图中上半部分为输入,下半部分为输出,输入模式是分为模拟输入以及通过TTL肖特基触发器转化为数字输入。输出模式中的P-MOS和N-MOS起到将输出增强的作用,在软件编程中重点是对图中寄存器的设置。
输出模式位代表着输出端口可设置的信号输出频率。
1.1,端口配置寄存器
端口配置低寄存器(GPIOx_CRL) (x=A..E)对应端口0-7和端口配置高寄存器(GPIOx_CRH) (x=A..E)对应端口8-18。
每个端口占四位,分别为CNFy[1:0]和MODEy[1:0],一个寄存器可以配置8个IO。
如果一个端口中的IO超过8个就需要用到配置高寄存器(GPIOx_CRH)。
1.2,端口输入数据寄存器和端口输出数据寄存器
端口输入数据寄存器(GPIOx_IDR) (x=A..E):
端口输出数据寄存器(GPIOx_ODR) (x=A..E):
1.3,端口位设置/清除寄存器
端口位设置/清除寄存器(GPIOx_BSRR) (x=A..E):
注:如果同时设置了BSy和BRy的对应位,BSy位起作用。也就是此时置位起作用。
1.4,端口位清除寄存器
端口位清除寄存器(GPIOx_BRR) (x=A..E):
其他寄存器不再一一列举,具体参考数据手册。
2,原理图分析和MDK工程
需要分析STM32核心板和转接插座的对应端口,以及通过转接插座的端口连接LED的插接口。
选择PB8-PB15控制LED,也就是STM32的Port B。
起始代码:
不同的CPU的起始代码一般是不同的;
起始代码是用汇编写的,一般不需要看懂,知道点就行了。
3,写代码通过GPIO点亮LED
第一步,寄存器信息确认,确认使用端口地址,Port B,0X4001 0C00 - 0x4001 0FFF。
第二步,找到Port B对应要操作的寄存器,即第一节中介绍的几个寄存器。
有可能涉及到的GPIO的寄存器地址:
寄存器名 偏移量 寄存器地址
GPIOB_CRL 0x00 0x40010C00
GPIOB_CRH 0x04 0x40010C04
GPIOB_IDR 0x08 0x40010C08
GPIOB_ODR 0x0C 0x40010C0C
GPIOB_BSRR 0x10 0x40010C10
GPIOB_BRR 0x14 0x40010C14
第三步,通过C语言编程操作寄存器。
需要注意的几点:
(1)ARM是内存与IO统一编址的,所以ARM中的所有外设都是通过寄存器的方式来操作的
(2)每个寄存器都有地址,C语言通过这些地址来操作这些寄存器位,用到的C语言技巧主要是C语言的位操作和C语言指针。
(3)常见面试题:用C语言向内存地址0x30000004写入16
*(unsigned int *)0x30000004 = 16; 或者:
unsigned int *p = (unsigned int *)0x30000004; *p = 16;
下边的代码中直接通过操作寄存器的地址控制LED:
#define GPIOB_CRH 0x40010C04
#define GPIOB_ODR 0x40010C0C
void main()
{
//将 port B端口的8-15设置为输出模式,最大速度50MHz,通用推挽输出模式
*((unsigned int *)GPIOB_CRH) = 0x3333 3333;
//输出1将8个LED灯全部点亮
*((unsigned int *)GPIOB_ODR) = 0x0000 ffff;
while(1)
}
4,STM32时钟设置函数移植
需要配置时钟的时钟控制寄存器(RCC_CR),时钟配置寄存器(RCC_CFGR)
第一步,时钟控制寄存器(RCC_CR)打开HSE ON(使用外部晶振),检测是否ready(HSE RDY)。
第二部,时钟配置寄存器(RCC_CFGR)配置HPRE(AHB)、PPRE1(APB1)、PPRE2(APB2)、PLLSRC、PLLXTPRE、PLLMUL。
第三步,配置时钟控制寄存器(RCC_CR) ,PLL ON,并检测PLL RDY。
第四步,配置时钟配置寄存器(RCC_CFGR)中的SW,选择时钟源,并检测SWS状态。
时钟设置时需注意对flash相关寄存器的操作 。
另外需要注意各端口时钟的使能是独立的:
时钟函数代码:
头文件:
#ifndef __CLOCK_H__
#define __CLOCK_H__
#include "gpio.h"
// 寄存器宏定义
// RCC寄存器基地址0x40021000
#define RCC_BASE 0x40021000 // RCC部分寄存器的基地址·
#define RCC_CR (RCC_BASE + 0x00) // RCC_CR的地址·
#define RCC_CFGR (RCC_BASE + 0x04)
#define FLASH_ACR 0x40022000
// 用C语言来访问寄存器的宏定义
#define rRCC_CR (*((volatile unsigned int *)RCC_CR))
#define rRCC_CFGR (*((volatile unsigned int *)RCC_CFGR))
#define rFLASH_ACR (*((volatile unsigned int *)FLASH_ACR))
// 时钟源切换到HSE并使能PLL,将主频设置为12MHz
void Set_SysClockTo72M(void);
#endif
源文件:
#include "clock.h"
void Set_SysClockTo72M(void)
{
unsigned int rccCrHserdy = 0;
unsigned int rccCrPllrdy = 0;
unsigned int rccCfrSwsPll = 0;
unsigned int faultTime = 0;
rRCC_CR = 0x00000083;
rRCC_CR &= ~(1<<16); // 关闭HSEON
rRCC_CR |= (1<<16); //打开HSEON,让HSEON工作
do
{
rccCrHserdy = rRCC_CR & (1<<17); //检测第17位是否为1
faultTime++;//检测时间
}
while ((faultTime<0x0FFFFFFF) && (rccCrHserdy==0));
if ((rRCC_CR & (1<<17)) != 0)
{
rFLASH_ACR |= 0x10;
rFLASH_ACR &= (~0x03);
rFLASH_ACR |= (0x02);
// HSE ready,下面配置PLL并且等待ready
rRCC_CFGR &= (~((0x0f<<4) | (0x07<<8) | (0x07<<11)));
//rRCC_CFGR &= (~(0x3ff<<4));
// AHB和APB2未分频,APB1被2分频,所以,AHB和APB2都是72MHz,APB是36MHz
rRCC_CFGR |= ((0x0<<4) | (0x04<<8) | (0x0<<11));
// 选择HSE作为PLL输入并且HSE不分频,所以PLL输入为8MHz
rRCC_CFGR &= (~((1<<16) | (1<<17))); // 清零bit17和bit16
rRCC_CFGR |= ((1<<16) | (0<<17)); //bit16置1
// 设置PLL倍频系数为9
rRCC_CFGR &= (~(0x0f<<18)); // 清零bit18-21
rRCC_CFGR |= (0x07<<18); // 9倍频
// 打开PLL开关
rRCC_CR |= (1<<24);
// do while 循环等待PLL时钟稳定
faultTime = 0;
do
{
rccCrPllrdy = rRCC_CR & (1<<25); //检测25为是否为1
faultTime++;//检测时间
}
while ((faultTime<0x0FFFFFFF) && (rccCrPllrdy==0));
//while (rccCrPllrdy==0);
if ((rRCC_CR & (1<<25)) == (1<<25))
{
// 到这里说明PLL已经稳定了,可以用了,下面就可以切了
// 切换PLL输出为SYSCLK
rRCC_CFGR &= (~(0x03<<0));
rRCC_CFGR |= (0x02<<0);
faultTime = 0;
do
{
rccCfrSwsPll = rRCC_CFGR & (0x03<<2); //检测第25位是否为1
faultTime++;//检测时间
}
while ((faultTime<0x0FFFFFFF) && (rccCfrSwsPll!=(0x02<<2)));
if ((rRCC_CFGR & (0x03<<2))== (0x02<<2))
{
//到这里时钟就设置好了
}
else
{
// 到这里说明PLL输出作为SYSCLK不成功
while (1);
}
}
else
{
// 到这里说明PLL启动时出错了,PLL不能稳定工作
while (1);
}
}
else
{
// HSE配置超时,说明HSE不可用,一般硬件存在问题
while (1);
}
}
5,STM32外设编程经验总结
STM32和51或其他简单单片机的相同:
(1)开发环境都是Keil;
(2)都是看原理图和数据手册;
(3)都是用C语言;
STM32和51或其他简单单片机的不同:
(1)工程会更复杂,会用到Keil的一些高级设置;
(2)原理图和数据手册比简单单片机更复杂(复杂不是难);
(3)STM32会用到C语言的更多高级特性 ;