0 keil5库函数配置

1、建立工程文件夹,Keil中新建工程,选择型号
2、工程文件夹里建立Start、Library、User等文件夹,复制固件库里面的文件到工程文件夹。

Start文件夹中:

  • 复制 \STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\arm下,8个启动文件之一(stm32f103c8 64kflash,需要后缀是md.s,就复制这一个),程序从启动文件开始执行。
  • 复制\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x 下的3个文件,stm32f10x.h(描述外设寄存器名称和地址,类似regx52.h),system_stm32f10x.c, system_stm32f10x.h(配置时钟用)
  • 复制\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\CoreSupport 下的两个文件 core_cm3.c,core_cm3.h(内核寄存器描述及内核的配置函数)

User文件夹:

  • 建立文件main.c
  • 复制 \STM32F10x_StdPeriph_Lib_V3.5.0\Project\STM32F10x_StdPeriph_Template下的 stm32f10x_conf.h(配置库函数头文件的包含关系及用来函数检查的函数定义),stm32f10x_it.c,stm32f10x_it.h(用来存放中断函数的)

Library文件夹:

  • 复制 \STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\STM32F10x_StdPeriph_Driver\src 下的所有文件 misc.c(内核的库函数),其他的是外设的库函数
  • 复制和src同级文件夹inc中的所有头文件

3、工程里对应建立Start、Library、User等同名称的分组,然后将文件夹内的文件添加到工程分组里(用keil5中的工程管理Manage Project Items)
4、工程选项,C/C++,Include Paths内声明所有包含头文件的文件夹Start、Library、Use以及根据项目需要建立的文件夹
5、工程选项,C/C++,Define内定义USE_STDPERIPH_DRIVER
6、工程选项,Debug,下拉列表选择对应调试器,Settings,Flash Download里勾选Reset and Run

1 GPIO模块

一、库函数点亮LED

led.h

#ifndef __LED_H__
#define __LED_H__

#define LED_GPIO_Port GPIOB
#define LED_GPIO_Pin GPIO_Pin_8
#define LED_GPIO_CLK RCC_APB2Periph_GPIOB

void Led_Init(void);
void LED_GPIO_High(void);
void LED_GPIO_Low(void);
void LED_GPIO_Cpl(void);

#endif

led.c

#include "stm32f10x.h"  
#include "led.h"

void Led_Init(void)
{
	RCC_APB2PeriphClockCmd(LED_GPIO_CLK, ENABLE); 
	
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;
	GPIO_InitStruct.GPIO_Pin=LED_GPIO_Pin;
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(LED_GPIO_Port, &GPIO_InitStruct);
}

void LED_GPIO_High(void)
{
	GPIO_SetBits(LED_GPIO_Port, LED_GPIO_Pin);
}

void LED_GPIO_Low(void)
{
	GPIO_ResetBits(LED_GPIO_Port, LED_GPIO_Pin);
}

void LED_GPIO_Cpl(void)
{
	GPIO_WriteBit(LED_GPIO_Port, LED_GPIO_Pin, \
	(BitAction)(GPIO_ReadOutputDataBit(LED_GPIO_Port, LED_GPIO_Pin)? 0:1));
}

main.c

#include "stm32f10x.h" 
#include "led.h" 
#include "Delay.h"

int main(void)
{
	Led_Init();
	while(1)
	{
		LED_GPIO_High();
		Delay_ms(500);
		LED_GPIO_Low();
		Delay_ms(500);
		LED_GPIO_Cpl();
		Delay_ms(500);
		LED_GPIO_Cpl();
		Delay_ms(500);
	}
}

2 SYSTICK滴答定时器

systick 实现延时程序1(启停定时器影响CLKSOURCE位)

void Delay_us(uint32_t xus)  //最大72*xus不能超过2的24次方
{
	SysTick->LOAD = 72 * xus;				//设置定时器重装值(72Mhz,计数72个为1us)
	SysTick->VAL = 0x00;					//清空当前计数值
	SysTick->CTRL = 0x00000005;	//置位CLKSOURCE,ENABLE,时钟源为72Mhz。如果复位,除以8为9Mhz。
	while(!(SysTick->CTRL & 0x00010000));	//等待计数到0
	SysTick->CTRL = 0x00000004;				//关闭定时器,
}//SysTick等同于((SysTick_Type*)(0xE000E010)) SysTick_Type是结构体

systick 实现延时程序2

void delay_init()
{
	SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //选择外部时钟,HCLK/8为9Mhz。
	fac_us = SystemCoreClock / 8000000; //数值为9
	fac_ms = (u16)fac_us*1000;  //数值为9000
}

void Delay_us(u32 xus)  //void Delay_ms(u16 nms) 
{
	u32 temp;
	SysTick->LOAD = fac_us * xus;			//设置定时器重装值us,LOAD值不能超过2的24次方
	//SysTick->LOAD = (u32)fac_ms * nms;	//设置定时器重装值ms,最大1864ms
	SysTick->VAL = 0x00;					    //清空当前计数值
	SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;	//启动定时器
	do
	{
		temp = SysTick->CTRL;
	}while((temp & 0x01) && !(temp&(1<<16)));  //如果定时器已经启动并且COUNTFLAG为0,等待
	SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; // 停止计数器
	SysTick->VAL = 0x00;                       //清空当前计数值
}

systick 设置计数值,中断处理

SysTick_Config(uint32_t ticks) //时钟源为内部时钟72Mhz,参数最大不能超过0xFFFFFF,大概233ms,设置中断优先级,
void SysTick_Handler(void)  
{
	LED_GPIO_Cpl();
}

3 外部中断编程

外部中断EXTI

void exti_init(void) //PB9 外部中断
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO | RCC_APB2Periph_GPIOB, ENABLE); //EXTI、NVIC时钟不用手动开启
	
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPU;
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_9;
	GPIO_Init(GPIOB, &GPIO_InitStruct);
	
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource9); //afio选择外部中断引脚
	
	EXTI_InitTypeDef EXTI_InitStruct;
	EXTI_InitStruct.EXTI_Mode=EXTI_Mode_Interrupt; //中断模式,别的选项是事件模式
	EXTI_InitStruct.EXTI_Line=EXTI_Line9;
	EXTI_InitStruct.EXTI_Trigger=EXTI_Trigger_Falling; //下降沿触发,别的选项上升沿、上升下降沿
	EXTI_InitStruct.EXTI_LineCmd=ENABLE;
	EXTI_Init(&EXTI_InitStruct);
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	NVIC_InitTypeDef NVIC_InitStruct;
	NVIC_InitStruct.NVIC_IRQChannel=EXTI9_5_IRQn;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=2;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority=2;
	NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
	NVIC_Init(&NVIC_InitStruct);
}

void EXTI9_5_IRQHandler(void)
{
	if(EXTI_GetITStatus(EXTI_Line9)==SET)
	{
		//中断处理代码
		EXTI_ClearITPendingBit(EXTI_Line9);
	}
}

4 定时器编程

void time3_init(u16 arr, u16 psc) //arr为9999,psc为7199,定时周期为10000*7200/(72*1000000)=1s
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //使能time3时钟
	
	TIM_InternalClockConfig(TIM3); //配置time3 选择时钟为内部时钟
	//选择ETR引脚通过外部时钟模式2输入的时钟(对time2为PA0,time3为PD2)
	//TIM_ETRClockMode2Config(TIM3, TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,0x00);	
	//参数:预分频,极性,滤波器(采样频率和采样次数),当然需要对GPIO引脚配置,浮空或者上拉输入
	/*
	TIM_ITRxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);//选择ITRx其它定时器的时钟
	TIM_TIxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_TIxExternalCLKSource,
                                uint16_t TIM_ICPolarity, uint16_t ICFilter); //选择TIx捕获通道的时钟
	TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,
                             uint16_t ExtTRGFilter); //选择ETR通过外部时钟模式1输入的时钟
	TIM_ETRConfig(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,
                   uint16_t ExtTRGFilter);//用来单独配置ETR引脚的预分频器、极性、滤波器这些参数的
	*/	
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;  //对CNT计数器,SetCounter和GetCounter读写
	TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1; //死区及数字滤波采样时钟分频比
	TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up; //向上计数
	TIM_TimeBaseInitStruct.TIM_Period=arr;    //重装载值
	TIM_TimeBaseInitStruct.TIM_Prescaler=psc; //预分频系数
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct); //配置时基单元
	
	TIM_ClearFlag(TIM3, TIM_FLAG_Update);  //解决刚运行就进入中断的问题
	TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE); //定时器中断中使能更新中断
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断分组,可以在main函数中出现一次
	NVIC_InitTypeDef NVIC_InitStruct;
	NVIC_InitStruct.NVIC_IRQChannel=TIM3_IRQn; //中断源为time3
	NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=2; //等待优先级
	NVIC_InitStruct.NVIC_IRQChannelSubPriority=2;        //抢占优先级
	NVIC_Init(&NVIC_InitStruct);
	
	TIM_Cmd(TIM3, ENABLE); //使能定时器
}
void TIM3_IRQHandler(void)
{
	
	if(TIM_GetITStatus(TIM3, TIM_IT_Update)==SET)
	{
		LED_GPIO_Cpl(); //反转led口
		TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
	}
}

5 串口编程模块

状态寄存器重要位:

TXE:发送数据寄存器空(Transmit data register empty)复位为空1

TC:发送完成(Transmission complete)复位为完成1

RXNE:读数据寄存器非空(Read data register not empty)复位为空0

void my_usart1_init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP; //串口1:PA9 TX 发送引脚
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_9;
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStruct);
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IN_FLOATING;  //PA10 RX 接受引脚
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_10;
	GPIO_Init(GPIOA, &GPIO_InitStruct);
	
	USART_InitTypeDef USART_InitStruct;
	USART_InitStruct.USART_BaudRate=9600; //波特率
	USART_InitStruct.USART_Mode=USART_Mode_Rx | USART_Mode_Tx; //发送接收全双工
	USART_InitStruct.USART_Parity=USART_Parity_No;   //奇偶校验位无
	USART_InitStruct.USART_StopBits=USART_StopBits_1;   //停止位1
	USART_InitStruct.USART_WordLength=USART_WordLength_8b; //数据位数
	USART_InitStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None; //硬件数据流控制
	USART_Init(USART1, &USART_InitStruct);
	
	//开启中断并且配置NVIC
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	NVIC_InitTypeDef NVIC_InitStruct;
	NVIC_InitStruct.NVIC_IRQChannel=USART1_IRQn;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=2;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority=2;
	NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
	NVIC_Init(&NVIC_InitStruct);
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
		
	USART_Cmd(USART1, ENABLE);
}
void usart_send_string(USART_TypeDef* USARTx, char *str)
{
	while(*str!='\0')
	{
		USART_SendData(USARTx, (u16)(*str++));
		while(USART_GetFlagStatus(USARTx, USART_FLAG_TXE)==0);
	}
	while(USART_GetFlagStatus(USARTx, USART_FLAG_TC)==0);
}
void USART_SendByte(USART_TypeDef* USARTx, uint8_t Data)
{
	assert_param(IS_USART_ALL_PERIPH(USARTx));
	assert_param(IS_USART_DATA(Data)); 

	USARTx->DR = (Data & (uint16_t)0x01FF);
	while(USART_GetFlagStatus(USART1, USART_FLAG_TXE)==RESET);
}

void USART1_IRQHandler(void)
{
	u16 temp;
	if(USART_GetITStatus(USART1, USART_IT_RXNE)==SET)
	{
		temp = USART_ReceiveData(USART1);
		USART_SendByte(USART1, temp);
//		USART_ClearITPendingBit(USART1, USART_IT_RXNE);
	}
}

使用printf语句,方便把信息通过串口输出到电脑显示,下面是响应的设置

#pragma import(__use_no_semihosting)  //在串口模块输入下面代码

struct __FILE
{
	int handle;
};
FILE __stdout;
void _sys_exit(int x)
{
	x=x;
}

//重定向c库函数printf到串口,重定向后可使用printf函数
int fputc(int ch, FILE *f)
{
	USART_SendData(USART1, (u8)ch);
	while(USART_GetFlagStatus(USART1, USART_FLAG_TXE)==RESET);
	return ch;
}
//重定向c库函数scanf到串口,重定向后可使用scanf、getchar等函数
int fgetc(FILE *f)
{
	while(USART_GetFlagStatus(USART1, USART_FLAG_RXNE)==RESET);
	return (int)USART_ReceiveData(USART1);
}
/* // main.c程序中使用printf,当然 需要include "stdio.h"
	u32 temp=12345;
	float f=12.3478;
	my_usart1_init(); //使用printf可以不开启串口中断
	printf("temp=%d\r\n", temp);
	printf("temp=%f\r\n", f);
*/