开发板是老师做的一块,芯片是STM32F103ZET6 。
各种例子。一段一段来分析,最后再来个总结。
/* Includes ------------------------------------------------------------------*/
#include "stm32f10x.h"
#include "stm32_eval.h"
#include "stm32f10x_flash.h"
#include
记得加入stm32f10x_flash.h 是因为除了一个错误,具体错误提示忘了。
一段头文件的编译预处理
#ifdef USE_STM3210B_EVAL
#include "stm3210b_eval_lcd.h"
#elif defined USE_STM3210E_EVAL
#include "stm3210e_eval_lcd.h"
#elif defined USE_STM3210C_EVAL
#include "stm3210c_eval_lcd.h"
#endif
其中EVAL 的意思是 evaluation 评估的意思。另外在stm32_eval.h 中有如下一段,
/**
@code
The table below gives an overview of the hardware resources supported by each
STM32 EVAL board.
- LCD: TFT Color LCD (Parallel (FSMC) and Serial (SPI))
- IOE: IO Expander on I2C
- sFLASH: serial SPI FLASH (M25Pxxx)
- sEE: serial I2C EEPROM (M24C08, M24C32, M24C64)
- TSENSOR: Temperature Sensor (LM75)
- SD: SD Card memory (SPI and SDIO (SD Card MODE))
=================================================================================================================+
STM32 EVAL | LED | Buttons | Com Ports | LCD | IOE | sFLASH | sEE | TSENSOR | SD (SPI) | SD(SDIO) |
=================================================================================================================+
STM3210B-EVAL | 4 | 8 | 2 | YES (SPI) | NO | YES | NO | YES | YES | NO |
-----------------------------------------------------------------------------------------------------------------+
STM3210E-EVAL | 4 | 8 | 2 | YES (FSMC)| NO | YES | NO | YES | NO | YES |
-----------------------------------------------------------------------------------------------------------------+
STM3210C-EVAL | 4 | 3 | 1 | YES (SPI) | YES | NO | YES | NO | YES | NO |
-----------------------------------------------------------------------------------------------------------------+
STM32100B-EVAL | 4 | 8 | 2 | YES (SPI) | NO | YES | NO | YES | YES | NO |
=================================================================================================================+
@endcode
*/
应该是STM 提供了不同的评估板,每块板子的外设结构有所不同,于是在下面有这么一段:
/**
* @brief Uncomment the line corresponding to the STMicroelectronics evaluation
* board used in your application.
*
* Tip: To avoid modifying this file each time you need to switch between these
* boards, you can define the board in your toolchain compiler preprocessor.
*/
#if !defined (USE_STM32100B_EVAL) && !defined (USE_STM3210B_EVAL) && !defined (USE_STM3210E_EVAL) && !defined (USE_STM3210C_EVAL)
//#define USE_STM32100B_EVAL
//#define USE_STM3210B_EVAL
#define USE_STM3210E_EVAL
//#define USE_STM3210C_EVAL
#endif
叫做 uncomment the line 来指定相应的评估板。这里可以看见我用的这个项目里面 uncomment 的行是 也就是说老师所做的板子是对应于 E 型的评估板,而根据上面所提供的的 overview ,这种型号的评估板应该包含
* 4 个 led ,这个确实有,而且算上那些指示灯,还不止4 个,不过应该不用算只是灯。
* 8 个按键, 这个没有,只有一个复位键和四个不明按键。
* 串口两个 确实有
* LCD 这个有,2.8寸 TFT 触摸屏
* sFLASH , 这个还不知道有米有 :TAG MARK
* TSENSOR , 温度传感器,这个应该有,不确定。 :TAG MARK
* SD ,这个有。不过SPI 和SDIO 有什么区别? :TAG MARK
继续下面的,上面另外的问题先留着,免得一件事都没整完。继续贴代码
/** @addtogroup Template_Project
* @{
*/
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
#ifdef USE_STM3210B_EVAL
#define MESSAGE1 "STM32 Medium Density"
#define MESSAGE2 " Device running on "
#define MESSAGE3 " STM3210B-EVAL "
#elif defined USE_STM3210E_EVAL
#define MESSAGE1 " STM32 High Density "
#define MESSAGE2 " Device running on "
#define MESSAGE3 " STM3210E-EVAL "
#elif defined USE_STM3210C_EVAL
#define MESSAGE1 " STM32 Connectivity "
#define MESSAGE2 " Line Device running"
#define MESSAGE3 " on STM3210C-EVAL "
#endif
这段说明 E 型开发板用的是 STM32 High Density 的 微控制器,噢,原来如此,不同型的评估板用的微控制型号是不同的。那么 STM32 High Density 是怎样的呢? 查选型手册:没仔细找 STM32 High Density,找到上面的表。
也就是说:频率 72Mhz , 512KB ROM , 64kb RAM,.........
继续贴代码,LED 定义,
/* Private macro -------------------------------------------------------------*/
/*神州III号LED灯相关定义*/
#define RCC_GPIO_LED RCC_APB2Periph_GPIOF /*LED使用的GPIO时钟*/
#define LEDn 4 /*IIILED数量*/
#define GPIO_LED GPIOF /*IIILED灯使用的GPIO组*/
#define DS1_PIN GPIO_Pin_6 /*DS1使用的GPIO管脚*/
#define DS2_PIN GPIO_Pin_7 /*DS2使用的GPIO管脚*/
#define DS3_PIN GPIO_Pin_8 /*DS3使用的GPIO管脚*/
#define DS4_PIN GPIO_Pin_9 /*DS4使用的GPIO管脚*/
#define GPIO_LED_ALL DS1_PIN |DS2_PIN |DS3_PIN |DS4_PIN
老师好似借鉴了很多板子啊。查过原理图,对应引脚是这样的。好理解 。
continue ....
/*神州III号按键相关定义*/
#define RCC_KEY1 RCC_APB2Periph_GPIOD
#define GPIO_KEY1_PORT GPIOD
#define GPIO_KEY1 GPIO_Pin_3
#define RCC_KEY2 RCC_APB2Periph_GPIOA
#define GPIO_KEY2_PORT GPIOA
#define GPIO_KEY2 GPIO_Pin_8
#define RCC_KEY3 RCC_APB2Periph_GPIOC
#define GPIO_KEY3_PORT GPIOC
#define GPIO_KEY3 GPIO_Pin_13
#define RCC_KEY4 RCC_APB2Periph_GPIOA
#define GPIO_KEY4_PORT GPIOA
#define GPIO_KEY4 GPIO_Pin_0
#define GPIO_KEY_ANTI_TAMP GPIO_KEY3
#define GPIO_KEY_WEAK_UP GPIO_KEY4
/* Values magic to the Board keys */
#define NOKEY 0
#define KEY1 1
#define KEY2 2
#define KEY3 3
#define KEY4 4
/* LED status */
#define LED_ON 1
#define LED_OFF 0
由此可得,按键对应的引脚是: D3 、A8、C13、A0
于是问题来了,#define RCC_KEY1 RCC_APB2Periph_GPIOD
RCC 的意思是 复位 时钟控制。好吧 先去吃饭了。回来再继续,
此时看得应该是时钟设置,
三种不同的时钟源可被用来驱动系统时钟(SYSCLK) :
● HSI振荡器时钟 (高速 内部振荡器时钟)
● HSE振荡器时钟 (高速外部振荡器时钟)
● PLL 时钟 (内部PLL 可以用来倍频HSI RC的输出时钟或HSE晶体输出时钟)
这段先到这,后面还有相关。
看LED——config
void LED_config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* Enable GPIOB, GPIOC and AFIO clock */
RCC_APB2PeriphClockCmd(RCC_GPIO_LED | RCC_APB2Periph_AFIO , ENABLE); //RCC_APB2Periph_AFIO
/* LEDs pins configuration */
GPIO_InitStructure.GPIO_Pin = GPIO_LED_ALL;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIO_LED, &GPIO_InitStructure);
}
函数首先定义了一个GPIO_InitTypeDef 类型的变量,其结构如下:
typedef struct
{
uint16_t GPIO_Pin; /*!< Specifies the GPIO pins to be configured.
This parameter can be any value of @ref GPIO_pins_define */
GPIOSpeed_TypeDef GPIO_Speed; /*!< Specifies the speed for the selected pins.
This parameter can be a value of @ref GPIOSpeed_TypeDef */
GPIOMode_TypeDef GPIO_Mode; /*!< Specifies the operating mode for the selected pins.
This parameter can be a value of @ref GPIOMode_TypeDef */
}GPIO_InitTypeDef;
即,引脚, 速度, 和模式。引脚很好理解,速度的可取值有哪些?又有哪些模式可以设置呢?如下:
/**
* @brief Output Maximum frequency selection
*/
typedef enum
{
GPIO_Speed_10MHz = 1,
GPIO_Speed_2MHz,
GPIO_Speed_50MHz
}GPIOSpeed_TypeDef;
#define IS_GPIO_SPEED(SPEED) (((SPEED) == GPIO_Speed_10MHz) || ((SPEED) == GPIO_Speed_2MHz) || \
((SPEED) == GPIO_Speed_50MHz))
/**
* @brief Configuration Mode enumeration
*/
typedef enum
{ GPIO_Mode_AIN = 0x0, // 模拟输入
GPIO_Mode_IN_FLOATING = 0x04, // 输入浮空
GPIO_Mode_IPD = 0x28, // 输入下拉
GPIO_Mode_IPU = 0x48, // 输入上拉
GPIO_Mode_Out_OD = 0x14, // 开漏输出
GPIO_Mode_Out_PP = 0x10, // 推挽式输出
GPIO_Mode_AF_OD = 0x1C, // 开漏复用功能
GPIO_Mode_AF_PP = 0x18 // 推挽式复用功能
}GPIOMode_TypeDef;
#define IS_GPIO_MODE(MODE) (((MODE) == GPIO_Mode_AIN) || ((MODE) == GPIO_Mode_IN_FLOATING) || \
((MODE) == GPIO_Mode_IPD) || ((MODE) == GPIO_Mode_IPU) || \
((MODE) == GPIO_Mode_Out_OD) || ((MODE) == GPIO_Mode_Out_PP) || \
((MODE) == GPIO_Mode_AF_OD) || ((MODE) == GPIO_Mode_AF_PP))
可以看到,GPIO 的速度可以取3档, 10mhz 2mhz , 50mhz 。而模式的有很多种,手册上P105 面:设置的寄存器是 端口配置低寄存器(GPIOx_CRL(/H)) (x=A..E) (p114)
─ 输入浮空 TAG MARK
─ 输入上拉
─ 输入下拉
─ 模拟输入
─ 开漏输出
─ 推挽式输出
─ 推挽式复用功能
─ 开漏复用功能
看第二行设置时钟:
其中有个
RCC_APB2Periph_AFIO
在手册中的APB2 外设时钟使能寄存器了有说明:(P71)
AFIOEN :辅助(翻译的貌似不对,应该是复用)功能IO时钟使能 (Alternate function I/O clock enable)
由软件置’1’ 或清’0’
0:复用功能IO时钟关闭;
1:复用功能IO时钟开启。
那么合适需要使能这个时钟呢?
在EXIT 里找到一段:通过AFIO_EXTICRx配置GPIO线上的外部中断/ 事件,必须先使能AFIO时钟。另外有人说:需要用到外设的重映射功能时才需要使能AFIO的时钟
还有手册里的一段:
***************************************************************************************************************
复用功能I/O 和调试配置(AFIO)
为了优化64脚或100 脚封装的外设数目,可以把一些复用功能重新映射到其他引脚上。设置复用
重映射和调试I/O 配置寄存器(AFIO_MAPR) 实现引脚的重新映射。这时,复用功能不再映射到它
们的原始分配上。
***************************************************************************************************************
还是不太懂 : TAG MARK
看看RCC 使能的函数:
void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState)
{
/* Check the parameters */
assert_param(IS_RCC_APB2_PERIPH(RCC_APB2Periph));
assert_param(IS_FUNCTIONAL_STATE(NewState));
if (NewState != DISABLE)
{
RCC->APB2ENR |= RCC_APB2Periph;
}
else
{
RCC->APB2ENR &= ~RCC_APB2Periph;
}
}
/* Exported macro ------------------------------------------------------------*/
#ifdef USE_FULL_ASSERT
/**
* @brief The assert_param macro is used for function's parameters check.
* @param expr: If expr is false, it calls assert_failed function
* which reports the name of the source file and the source
* line number of the call that failed.
* If expr is true, it returns no value.
* @retval None
*/
#define assert_param(expr) ((expr) ? (void)0 : assert_failed((uint8_t *)__FILE__, __LINE__))
/* Exported functions ------------------------------------------------------- */
void assert_failed(uint8_t* file, uint32_t line);
#else
#define assert_param(expr) ((void)0)
#endif /* USE_FULL_ASSERT */
再就是GPIO_Init的过程了,
首先看看GPIO 有哪些寄存器,
Each of the general-purpose I/O ports has two 32-bit configuration registers (GPIOx_CRL, GPIOx_CRH), two 32-bit data registers (GPIOx_IDR, GPIOx_ODR), a 32-bit set/reset register (GPIOx_BSRR), a 16-bit reset register (GPIOx_BRR) and a 32-bit locking register (GPIOx_LCKR).
即两个configuration register ,两个data register (inout 一个 , output 一个),一个复位置位寄存器,还有一个16bit的复位寄存器。加上一个锁寄存器。
如代码中描述:
typedef struct{
__IO uint32_t CRL; // 控制寄存器低位
__IO uint32_t CRH; // 控制寄存器高位
__IO uint32_t IDR; // 输入数据寄存器
__IO uint32_t ODR; // 输出数据寄存器
__IO uint32_t BSRR; //端口位设置/清除寄存器
__IO uint32_t BRR; // 端口为清除寄存器
__IO uint32_t LCKR; //锁存寄存器
} GPIO_TypeDef;
又到饭点了,应该说都过了饭点了,吃饭了再来继续,刚才又看见不少。
吃了饭回来,写了篇马克思的论文,再蹂躏了几件衣服,就差不多这个时候了,本打算洗洗睡了,没地方洗。就继续看看。
之前我看见,STM32F103ZET6 有 A...G 个GPIO,而每个GPIO 有0...15,号,所以相应的各种寄存器也都有16套。
看GPIO 的初始化
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;
/* Check the parameters */
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
assert_param(IS_GPIO_MODE(GPIO_InitStruct->GPIO_Mode));
assert_param(IS_GPIO_PIN(GPIO_InitStruct->GPIO_Pin));
/*---------------------------- GPIO Mode Configuration -----------------------*/
currentmode = ((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x0F);
if ((((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00)
{
/* Check the parameters */
assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed));
/* Output mode */
currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed;
}
/*---------------------------- GPIO CRL Configuration ------------------------*/
/* Configure the eight low port pins */
if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00)
{
tmpreg = GPIOx->CRL;
for (pinpos = 0x00; pinpos < 0x08; pinpos++)
{
pos = ((uint32_t)0x01) << pinpos;
/* Get the port pins position */
currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;
if (currentpin == pos)
{
pos = pinpos << 2;
/* Clear the corresponding low control register bits */
pinmask = ((uint32_t)0x0F) << pos;
tmpreg &= ~pinmask;
/* Write the mode configuration in the corresponding bits */
tmpreg |= (currentmode << pos);
/* Reset the corresponding ODR bit */
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
{
GPIOx->BRR = (((uint32_t)0x01) << pinpos);
}
else
{
/* Set the corresponding ODR bit */
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
{
GPIOx->BSRR = (((uint32_t)0x01) << pinpos);
}
}
}
}
GPIOx->CRL = tmpreg;
}
/*---------------------------- GPIO CRH Configuration ------------------------*/
/* Configure the eight high port pins */
if (GPIO_InitStruct->GPIO_Pin > 0x00FF)
{
tmpreg = GPIOx->CRH;
for (pinpos = 0x00; pinpos < 0x08; pinpos++)
{
pos = (((uint32_t)0x01) << (pinpos + 0x08));
/* Get the port pins position */
currentpin = ((GPIO_InitStruct->GPIO_Pin) & pos);
if (currentpin == pos)
{
pos = pinpos << 2;
/* Clear the corresponding high control register bits */
pinmask = ((uint32_t)0x0F) << pos;
tmpreg &= ~pinmask;
/* Write the mode configuration in the corresponding bits */
tmpreg |= (currentmode << pos);
/* Reset the corresponding ODR bit */
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
{
GPIOx->BRR = (((uint32_t)0x01) << (pinpos + 0x08));
}
/* Set the corresponding ODR bit */
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
{
GPIOx->BSRR = (((uint32_t)0x01) << (pinpos + 0x08));
}
}
}
GPIOx->CRH = tmpreg;
}
}
真长, 慢慢看吧。其实下午看的时候我就没懂,现在又想起前面写的几个字,就忽然懂了。有时候不一定要“从代码去懂理论”,也可以从“理论去懂代码”
说 有两个32位的控制寄存器,一个叫CRL, 一个叫CRH ,其中CRL中控制的是 0~7号引脚 , CRH 控制的是 8~15号引脚,每一个有四位去控制,四位分为两种,一个叫MODEy[1..0],控制引脚的输入输出状态,输入状态有一种,输出状态则有三种不同的速率,前面已经碰到,而对应于 输入和输出的MODE , 还有另外两位是 CNF,是更加具体的输入输出模式设置,如什么 pp ipd ipu 啥的(这个我不是太懂。)好吧扯得有点远了。回来继续分析:
第一句:currentmode = ((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x0F); 意思就是将mode 设为传进来mode的低四位的值,如上所述,设置一个引脚的状态方式只需要四位,而STM 对mode 的定义是八位,如下:
/**
* @brief Configuration Mode enumeration
*/
typedef enum
{ GPIO_Mode_AIN = 0x0,
GPIO_Mode_IN_FLOATING = 0x04,
GPIO_Mode_IPD = 0x28,
GPIO_Mode_IPU = 0x48,
GPIO_Mode_Out_OD = 0x14,
GPIO_Mode_Out_PP = 0x10,
GPIO_Mode_AF_OD = 0x1C,
GPIO_Mode_AF_PP = 0x18
}GPIOMode_TypeDef;
#define IS_GPIO_MODE(MODE) (((MODE) == GPIO_Mode_AIN) || ((MODE) == GPIO_Mode_IN_FLOATING) || \
((MODE) == GPIO_Mode_IPD) || ((MODE) == GPIO_Mode_IPU) || \
((MODE) == GPIO_Mode_Out_OD) || ((MODE) == GPIO_Mode_Out_PP) || \
((MODE) == GPIO_Mode_AF_OD) || ((MODE) == GPIO_Mode_AF_PP))
那么我肯定就要问了,那么它的 0x28, 0x48是什么意思?看手册知道 输入上拉/下拉 的CNFy[1..0] 设置其实是一起的 ,如下:
在输入模式(MODE[1:0]=00):
00:模拟输入模式
01:浮空输入模式(复位后的状态
10:上拉/ 下拉输入模式
11:保留
那么这个2 和 4 有什么意思呢?就我目前浅薄的见识来看,没什么意思,没什么作用。但是下面的输出状态高位填的1 却是有作用的,看GPIO_Init 的第二句:
if ((((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00)
我在总结前一篇位掩码时想到这种干是判断,判断什么呢?判断传入的8位的MODE的高四位是否为1 ,也就是说 判断 mode是否为输出模式,因为输出模式要设置速度。
在输出模式时,CNF的两位可以设置输出方式(通用推挽(00),通用开漏(01),复用推挽(10),复用开漏(11)),但是MODE的值确是来设定速度的,于是输的mode 的8位的低四位即本来应该对应 CNFy[1..0] 和 MODEy[1..0]的四位只取了CNFy[1..0]的值,而将mode留空,如
通用推挽(00 00), ...
通用开漏(01 00), 即得到GPIO_Mode_Out_OD = 0x14, 的 4
复用推挽(10 00),...
复用开漏(11 00) ...
在通过附加在mode的一个高位 的 1 判断出是输出模式后,再利用 GPIO_InitTypeDef 结构体里面的 GPIO_Speed 去设置输出速率,也就是下面这句话所做的事。
currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed;
接下来就是配置到具体的引脚了,注意 刚才的 currentmode 只是一个变量。还要把它装入到寄存器的相应位中才能设置模式,可是不知道哪个引脚要怎么设呢?于是接下来就开始判断。
好吧后面的一段由于太晚我已经没什么耐心了。反正就是通过各种位操作,判断出是哪个引脚,然后将相应的设置装入的寄存器的相应位。
准备在下面总结下GPIO,不得不提一提之前看得一点关于系统总线的:
系统总线的图示:
(这手册比较老,GPIO少了几个)。注:此图不对应互联型产品。
从图上看得也算清楚,再结合手册说明,总线的组成如下:
四个驱动单元:(也就是接入在总线矩阵左边的)
Cortex-M3 内核驱动DCode总线, System 总线
DMA1 和 DMA2
四个被动单元:也比较清楚。
FLASH
SRAM
FSNC : TAG MARK
AHB 到 APB 总线桥。
其实我要说的就是第四个被动单元:AHB 到 APB 总线桥。因为要说到GPIO。
首先看查到的一些资料,给我的印象就是 AHB 就相当于 微机 里的系统总线,用于连接高速设备,而APB 则相当于用户总线,用以连接低速设备。
查到了 ARM 的AMBA (高级微控制器总线体系),简单的看看:
包括:
1. AHB -- 高级高性能总线
2. ASB -- 高级系统总线
3. APB -- 高级外设总线
APB 一般包装为 AHB 总线的一个外设,如图可见。而APB 一般连接外设,如图中连接的一堆。在APB2上面就连接有 GPIOy 。
上面讲的貌似都是GPIO 先就上面的小结一下吧。
一般的东西要工作,都得给个时钟,时钟是什么了,目前还没有那么尖锐的理解。而 GPIO 是连接在 APB2上的,所以呢,GPIO 的初始化中时钟使能是调用的函数 RCC_APB2PeriphClockCmd,(至于APB连接在AHB 上,那么是不是要先调用AHB 的时总使能呢?)。那么通用 GPIO 的使用过程应该可以总结如下:
1. 调用 RCC_APB2PeriphClockCmd 使能相应引脚时钟
2. 设置引脚模式。若是输出模式,则还需要设置 速率。
3. 调用 GPIO_Init 将设置装入到相应的寄存器。
其中还有问题的地方: TAG MARK
1. 各种输出、输入模式是是什么意思?
2. AFIO