前提:文件创建
创建顺序:
keil新建文件夹里面起工程名“Project”,保存。
选择芯片:STM32F103C8
在文件管理给刚刚建立的工程夹把模板Start、Library、User文件粘贴进来
点击魔术棒右边的三个箱子图案(文件管理按钮)
把默认组(SourceGroup1)叉掉,点叉左边的按钮新建三个组:Start、Libaray、User
Start里添加文件:startup_stm32f10x_md.s
core_cm3.c
core_cm3.h
stm32f10x.h
system_stm32f10x.c
system_stm32f10x.h
Libaray里添加文件:全部Libaray文件
User里添加文件:全部User文件
点击魔术棒 ——> c/c++ ——>Include Paths:添加Start、Library、User;然后Define:USE_STDPERIPH_DRIVER(USE使用、STD标准、PERIPH外设、DRIVER驱动)——>
Debug:调试器(右上角):STLINK,点击右边settings->Flash Download把Reset and run
1、点亮LED加闪烁
主要用RCC和GPIO口两个外设
使用GPIO口有三个步骤:
(1)使用RCC开启GPIO的时钟
(2)使用GPIO_Init函数初始化GPIO
(3)使用输出或者输入的函数控制GPIO口
了解:
#1:打开Library下面的stm32f10_rcc.h(.h文件里面的最下面一般都是库函数所有函数的声明)
最常用的三个函数()为:
692|void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState);
693|void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);
694|void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);
点击RCC_AHBPeriphClockCmd跳转看到函数的介绍,这个AHB外设时钟控制的函数就是使能或失能AHB外设时钟的,第一个参数选择外设,第二个参数选择使能或失能,下方列表(1039行以下@arg)有外设连接到哪个总线。
1
/**
* @brief Enables or disables the AHB peripheral clock.
* @param RCC_AHBPeriph: specifies the AHB peripheral to gates its clock.
*
* For @b STM32_Connectivity_line_devices, this parameter can be any combination
* of the following values:
* @arg RCC_AHBPeriph_DMA1
* @arg RCC_AHBPeriph_DMA2
* @arg RCC_AHBPeriph_SRAM
* @arg RCC_AHBPeriph_FLITF
* @arg RCC_AHBPeriph_CRC
* @arg RCC_AHBPeriph_OTG_FS
* @arg RCC_AHBPeriph_ETH_MAC
* @arg RCC_AHBPeriph_ETH_MAC_Tx
* @arg RCC_AHBPeriph_ETH_MAC_Rx
*
* For @b other_STM32_devices, this parameter can be any combination of the
* following values:
* @arg RCC_AHBPeriph_DMA1
* @arg RCC_AHBPeriph_DMA2
* @arg RCC_AHBPeriph_SRAM
* @arg RCC_AHBPeriph_FLITF
* @arg RCC_AHBPeriph_CRC
* @arg RCC_AHBPeriph_FSMC
* @arg RCC_AHBPeriph_SDIO
*
* @note SRAM and FLITF clock can be disabled only during sleep mode.
* @param NewState: new state of the specified peripheral clock.
* This parameter can be: ENABLE or DISABLE.
* @retval None
*/
void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState)
{
/* Check the parameters */
assert_param(IS_RCC_AHB_PERIPH(RCC_AHBPeriph));
assert_param(IS_FUNCTIONAL_STATE(NewState));
if (NewState != DISABLE)
{
RCC->AHBENR |= RCC_AHBPeriph;
}
else
{
RCC->AHBENR &= ~RCC_AHBPeriph;
}
}
2
/**
* @brief Enables or disables the High Speed APB (APB2) peripheral clock.
* @param RCC_APB2Periph: specifies the APB2 peripheral to gates its clock.
* This parameter can be any combination of the following values:
* @arg RCC_APB2Periph_AFIO, RCC_APB2Periph_GPIOA, RCC_APB2Periph_GPIOB,
* RCC_APB2Periph_GPIOC, RCC_APB2Periph_GPIOD, RCC_APB2Periph_GPIOE,
* RCC_APB2Periph_GPIOF, RCC_APB2Periph_GPIOG, RCC_APB2Periph_ADC1,
* RCC_APB2Periph_ADC2, RCC_APB2Periph_TIM1, RCC_APB2Periph_SPI1,
* RCC_APB2Periph_TIM8, RCC_APB2Periph_USART1, RCC_APB2Periph_ADC3,
* RCC_APB2Periph_TIM15, RCC_APB2Periph_TIM16, RCC_APB2Periph_TIM17,
* RCC_APB2Periph_TIM9, RCC_APB2Periph_TIM10, RCC_APB2Periph_TIM11
* @param NewState: new state of the specified peripheral clock.
* This parameter can be: ENABLE or DISABLE.
* @retval None
*/
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;
}
}
3
/**
* @brief Enables or disables the Low Speed APB (APB1) peripheral clock.
* @param RCC_APB1Periph: specifies the APB1 peripheral to gates its clock.
* This parameter can be any combination of the following values:
* @arg RCC_APB1Periph_TIM2, RCC_APB1Periph_TIM3, RCC_APB1Periph_TIM4,
* RCC_APB1Periph_TIM5, RCC_APB1Periph_TIM6, RCC_APB1Periph_TIM7,
* RCC_APB1Periph_WWDG, RCC_APB1Periph_SPI2, RCC_APB1Periph_SPI3,
* RCC_APB1Periph_USART2, RCC_APB1Periph_USART3, RCC_APB1Periph_USART4,
* RCC_APB1Periph_USART5, RCC_APB1Periph_I2C1, RCC_APB1Periph_I2C2,
* RCC_APB1Periph_USB, RCC_APB1Periph_CAN1, RCC_APB1Periph_BKP,
* RCC_APB1Periph_PWR, RCC_APB1Periph_DAC, RCC_APB1Periph_CEC,
* RCC_APB1Periph_TIM12, RCC_APB1Periph_TIM13, RCC_APB1Periph_TIM14
* @param NewState: new state of the specified peripheral clock.
* This parameter can be: ENABLE or DISABLE.
* @retval None
*/
void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState)
{
/* Check the parameters */
assert_param(IS_RCC_APB1_PERIPH(RCC_APB1Periph));
assert_param(IS_FUNCTIONAL_STATE(NewState));
if (NewState != DISABLE)
{
RCC->APB1ENR |= RCC_APB1Periph;
}
else
{
RCC->APB1ENR &= ~RCC_APB1Periph;
}
}
#2:打开stm32f10x_gpio.h文件,下面的“四void+四uint+四void”这个12个文件为常用的函数:
349|void GPIO_DeInit(GPIO_TypeDef* GPIOx);//调用后GPIO外设就会被复位
350|void GPIO_AFIODeInit(void);//可以复位AFIO外设
351|void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
//第三个非常重要,用结构体的参数来初始化GPIO口。需要先定义一个结构体变量,然后再给结构体赋值,
//最后调用这个函数,这个函数内部就会自动读取结构体的值,然后自动把外设的各个参数配置好,这种
//Init函数在stm32基本所有的外设都有,一般初始化外设都是使用这个Init函数来完成的
352|void GPIO_StructInit(GPIO_InitTypeDef* GPIO_InitStruct);//第四个可以把结构体变量赋一个默认
//值
//接下来四个uint就是GPIO的读取函数,再下面四个void就是GPIO的写入函数
353|uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
354|uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);
355|uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
356|uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);
357|void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
358|void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
359|void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);
360|void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);
//上面8个函数就可以实现读写GPIO口的功能
//第一个,调用后GPIO外设就会被复位
//第二个,可以复位AFIO外设
//第三个非常重要,用结构体的参数来初始化GPIO口。需要先定义一个结构体变量,然后再给结构体赋值,最后调用这个函数,这个函数内部就会自动读取结构体的值,然后自动把外设的各个参数配置好,这种Init函数在stm32基本所有的外设都有,一般初始化外设都是使用这个Init函数来完成的。
//第四个可以把结构体变量赋一个默认值
//接下来四个uint就是GPIO的读取函数,再下面四个void就是GPIO的写入函数,上面8个函数就可以实现读写GPIO口的功能
点亮LED:
第一种方式,用(第三个后void)GPIO_WriteBit,设置第三个参数Bit_RESET为低电平,Bit_SET为高电平
#include "stm32f10x.h"
#include "Delay.h"
int main(){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启时钟(外设,状态)=(GPIO A口,
//使能)
GPIO_InitTypeDef GPIO_InitStructure;//建立一个结构体
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//状态:推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;//引脚
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//输出速度
GPIO_Init(GPIOA,&GPIO_InitStructure);
//GPIO_ResetBits(GPIOA,GPIO_Pin_0);//设置低电平
//GPIO_SetBits(GPIOA,GPIO_Pin_0);//设置高电平
//GPIO_Write(GPIOA, PortVal);
while(1){
GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_RESET);
Delay_ms(500);
GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_SET);
Delay_ms(500);
}
}
如果我们非要给一个数,1是高电平,0是低电平。我们可以将Bit_SET改成1,Bit_RESET改成0,但如果直接这样编译的话会有两个警告,说的是枚举类型中混进了其他类型的变量,所以如果想直接写1和0的话,需要在这里加上强制类型转换,即改成(BitAction)0和(BitAction)1,将1和0类型转换为BitAction的枚举类型。代码如下:
GPIO_WriteBit(GPIOA, GPIO_Pin_0, (BitAction)0);
Delay_ms(500);
GPIO_WriteBit(GPIOA, GPIO_Pin_0, (BitAction)1);
Delay_ms(500);
第二种方式用第一个后void函数和第二个后void函数,即用GPIO_SetBits和GPIO_ResetBits两个函数来设置
GPIO_SetBits(GPIOA,GPIO_Pin_0);//设置高电平
GPIO_ResetBits(GPIOA,GPIO_Pin_0);//设置低电平
在这里可以讨论以下推挽输出和开漏输出的驱动问题:
将LED灯引脚拔掉,两个引脚互换位置插到面包板,此时LED就是高电平点亮的方式,可以看到LED正常闪烁,说明在推挽模式下,高低电平都有驱动能力。
然后我们把这个端口的模式换成Out_OD,开漏输出模式,再编译下载,此时LED还是高电平点亮方式,但LED不亮,说明开漏输出的模式下高电平是没有驱动能力的,再把LED引脚插入位置改回(即低电平点亮方式),LED亮,说明开漏模式的低电平是有驱动能力的。
这就印证的推挽输出和开漏输出的特性,推挽输出高低电平都有驱动能力,开漏输出高电平相当于高阻态,没有驱动能力,低电平有驱动能力。一般输出用推挽模式就行了,特殊的地方才会用到开漏模式。
LED流水灯
当GPIO要引用多个引脚时,可以用或来连接引脚定义: GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3; (可以这样子的原因:打开定义看,Pin0对应的数据是0x0001,然后Pin1、Pin2、Pin3依次为0x0002、0x0004、0x0008,如果把这个16进制换成二进制,就是0000 0000 0000 0001,然后是0010、0100、1000,在这里每个端口对应一个位,如果把他们进行按位或的操作,比如Pin0、Pin1和Pin2按位或,那结果就是0111,这样就相当于同时选中了3个端口,这就是按位或的操作逻辑,最后看到有个GPIO_Pin_All,它对应的数据就是0xFFFF,也就是所有位都为1,这样就相当于选中了所有引脚,在这里除了这个GPIO_Pin可以按位或的操作方式外,时钟控钟RCC_APB2PeriphResetCmd的这一项,也是可以用按位或的方式来选择多个外设,它的定义和GPIO参数的一样,数据的规律是每一位对应一个外设,还有GPIO_SetBits)
GPIO_Pin:
#define GPIO_Pin_0 ((uint16_t)0x0001) /*!< Pin 0 selected */
#define GPIO_Pin_1 ((uint16_t)0x0002) /*!< Pin 1 selected */
#define GPIO_Pin_2 ((uint16_t)0x0004) /*!< Pin 2 selected */
#define GPIO_Pin_3 ((uint16_t)0x0008) /*!< Pin 3 selected */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启时钟(外设,状态)=(GPIO A口,使能)点击第一个参数(外设)
#define RCC_APB2Periph_AFIO ((uint32_t)0x00000001)
#define RCC_APB2Periph_GPIOA ((uint32_t)0x00000004)
#define RCC_APB2Periph_GPIOB ((uint32_t)0x00000008)
#define RCC_APB2Periph_GPIOC ((uint32_t)0x00000010)
#define RCC_APB2Periph_GPIOD ((uint32_t)0x00000020)
#define RCC_APB2Periph_GPIOE ((uint32_t)0x00000040)
现在为了同时控制16个端口,我们就可以使用GPIO_Write这个函数,第一个参数是GPIOx,我们直接写GPIOA,第二个参数转到定义看到:
414|* @param PortVal: specifies the value to be written to the port output data register.
这里写的是,指定写到输出数据寄存器的值,下面看到:GPIOx->ODR = PortVal;这第二个参数就是直接写到GPIO的ODR寄存器里的,所以我们可以直接写0x0001,对应二进制就是0000 0000 0000 0001;(c语言不支持直接写二进制,所以这里只能转换为十六进制来写),这十六个二进制分别对应PA0到PA15总共16个端口,最低位对应PA0,然后往上依次是PA1,PA2,一直到PA15,因为是低电平点亮,所以前面再加一个按位取反的符号~。
while(1){
GPIO_Write(GPIOA,~0x0001);//0000 0000 0000 0001
Delay_ms(500);
GPIO_Write(GPIOA,~0x0002);//0000 0000 0000 0010
Delay_ms(500);
GPIO_Write(GPIOA,~0x0004);//0000 0000 0000 0100
Delay_ms(500);
GPIO_Write(GPIOA,~0x0008);//0000 0000 0000 1000
Delay_ms(500);
GPIO_Write(GPIOA,~0x0010);//0000 0000 0001 0000
Delay_ms(500);
GPIO_Write(GPIOA,~0x0020);//0000 0000 0010 0000
Delay_ms(500);
GPIO_Write(GPIOA,~0x0040);//0000 0000 0100 0000
Delay_ms(500);
GPIO_Write(GPIOA,~0x0080);//0000 0000 1000 0000
Delay_ms(500);
}
2、蜂鸣器
#include "stm32f10x.h"
#include "Delay.h"
int main(){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//使能时钟,开启外设时钟
//(选择外设,选择新的状态)
GPIO_InitTypeDef GPIO_InitStructure;//定义结构体
GPIO_InitStructure.GPIO_Mode= GPIO_Mode_Out_PP;//GPIO模式=通用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;//GPIO端口=(跳转定义时选member)
//这里随便选一个口,但A15\B3\B4有特殊定义,这三个口默认是TTAG的调试端口,
//如果要用作普通端口的话,需要进行配置
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//GPIO速度
GPIO_Init(GPIOB,&GPIO_InitStructure);//配置端口模式(选择GPIO,选择参数的结构体)
while(1){
GPIO_ResetBits(GPIOB,GPIO_Pin_12);
Delay_ms(100);
GPIO_SetBits(GPIOB,GPIO_Pin_12);
Delay_ms(100);
GPIO_ResetBits(GPIOB,GPIO_Pin_12);
Delay_ms(100);
GPIO_SetBits(GPIOB,GPIO_Pin_12);
Delay_ms(100);
}
}