因为项目需要,学习了一下stm32的GPIO,串口,PWM,中断部分,在这里做个小结, 共同学习,所有程序均经过实际测试,输出正确。
将GPIO,串口,PWM(定时器)的配置程序粘贴如下
1、使能外设的时钟:APB1ENR,APB2ENR
2、配置寄存器或者说是控制寄存器。在配置stm32外设时,任何时候都要先使能该外设的时钟!而每个控制寄存器,很有可能包括了,1模式寄存器,2使能寄存器,3才可能是我们认为的数据寄存器或者内容寄存器。
3、使能外设。即使配置好了,没有使能外设,则外设永远不会工作,这一点比较容易遗忘。
寄存器配置,请查看
由于stm32的很多引脚都是复用,所以在配置寄存器配置时,必须设置为复用,复用切记是默认的复用功能为主,参考文档,请查看
http://download.csdn.net/detail/bolvtin/8867933
1、外设时钟寄存器
RCC_APB1ENR(APB1外设时钟使能寄存器)
RCC_APB2ENR(APB2外设时钟使能寄存器)
RCC_APB2RSTR(APB2外设复位寄存器)
RCC_APB1RSTR(APB1外设复位寄存器)
外设使能寄存器与外设复位寄存器存在着对应关系,相应位置对应相应外设的时钟使能与复位。
注意以下几个外设
1,GPIO--GPIOA,GPIOB,GPIOC,GPIOD,GPIOE,GPIOF,GPIOG时钟都在RCC_APB2ENR寄存器
2,串口--USART1时钟都在RCC_APB2ENR寄存器
3,定时器—TIM1,TIM8时钟都在RCC_APB2ENR寄存器
1,串口—USART2,USART3,USART4,USART5时钟都在RCC_APB1ENR寄存器
2,定时器—TIM2,TIM3,TIM4,TIM5,TIM6,TIM7时钟都在RCC_APB1ENR寄存器,TIM2- TIM5可以产生PWM
注意一下定时器的内部时钟信号来源
当APB1的时钟分频数设置为1,通用定时器的时钟就是APB1的时钟;当APB1的时钟分频数设置为2,通用定时器TIMx的时钟是APB1的2倍,即72Mhz。高级定时器1,8的时钟来自于APB2,不是APB1。
程序部分:
led.h
#ifndef __LED_H
#define __LED_H
#include "sys.h"
//Mini STM32开发板
//LED驱动代码
//正点原子@ALIENTEK
//2010/5/27
//LED端口定义
#define LED0 PAout(8)// PA8
#define LED1 PDout(2)// PD2
void LED_Init(void);//初始化
#endif
led.c
#include <stm32f10x_lib.h>
#include "led.h"
//
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//Mini STM32开发板
//LED驱动代码
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//修改日期:2010/5/27
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 正点原子 2009-2019
//All rights reserved
//
//初始化PA8和PD2为输出口.并使能这两个口的时钟
//LED IO初始化
/********************************************************************************/
void LED_Init(void)
{
//总体是3部分
//1、使能GPIO的时钟,这里假设使能了A、B、C、D4个GPIO外设
//2、配置寄存器,是输入还是输出,是默认的定义,还是默认的复用功能
//3、填写默认的数据参数,这里选择了,默认输出高电平
RCC->APB2ENR|=1<<2; //使能PORTA时钟
RCC->APB2ENR|=1<<3; //使能PORTB时钟
RCC->APB2ENR|=1<<4; //使能PORTC时钟
RCC->APB2ENR|=1<<5; //使能PORTD时钟
GPIOA->CRH&=0XFFFFFFF0; //清掉PA8位原来的设置,同时不影响其他位的设置 在mini stm32上 PA8连接了DS0灯
GPIOA->CRH|=0X00000003;//PA8 推挽输出
GPIOA->ODR|=1<<8; //PA8 输出高
GPIOC->CRH&=0XFFF00FFF;
GPIOA->CRH|=0X00038000;//PC11 输入 PC12 输出
GPIOA->ODR=1<<11; //PC11 上拉
GPIOA->ODR|=1<<12; //PC.12 输出高
GPIOD->CRL&=0XFFFFF0FF; //在mini stm32上 PD2连接了DS1灯
GPIOD->CRL|=0X00000300;//PD.2推挽输出
GPIOD->ODR|=1<<2; //PD.2输出高
}
timer.h
#ifndef __TIMER_H
#define __TIMER_H
#include "sys.h"
//
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//Mini STM32开发板
//通用定时器 驱动代码
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//修改日期:2010/12/03
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 正点原子 2009-2019
//All rights reserved
//
//通过改变TIM3->CCR2的值来改变占空比,从而控制LED0的亮度
#define LED0_PWM_VAL TIM3->CCR2
void Timerx_Init(u16 arr,u16 psc);
void PWM_Init(u16 arr,u16 psc);
#endif
usart.h
#ifndef __USART_H
#define __USART_H
#include <stm32f10x_lib.h>
#include "stdio.h"
//
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//Mini STM32开发板
//串口1初始化
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//修改日期:2010/5/27
//版本:V1.3
//版权所有,盗版必究。
//Copyright(C) 正点原子 2009-2019
//All rights reserved
//********************************************************************************
//V1.3修改说明
//支持适应不同频率下的串口波特率设置.
//加入了对printf的支持
//增加了串口接收命令功能.
//修正了printf第一个字符丢失的bug
//
extern u8 USART_RX_BUF[64]; //接收缓冲,最大63个字节.末字节为换行符
extern u8 USART_RX_STA; //接收状态标记
extern char imgCenterX[5];
extern char imgCenterY[5];
extern int imgCenterX0,imgCenterX1;
extern int imgCenterY0,imgCenterY1;
//如果想串口中断接收,请不要注释以下宏定义
#define EN_USART1_RX //使能串口1接收
#define EN_USART2_RX //使能串口2接收
#define EN_USART3_RX //使能串口3接收
void uart_init(u32 pclk2,u32 bound);
#endif
usart.c
#include "sys.h"
#include "usart.h"
//
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//Mini STM32开发板
//串口1初始化
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//修改日期:2010/5/27
//版本:V1.3
//版权所有,盗版必究。
//Copyright(C) 正点原子 2009-2019
//All rights reserved
//********************************************************************************
//V1.3修改说明
//支持适应不同频率下的串口波特率设置.
//加入了对printf的支持
//增加了串口接收命令功能.
//修正了printf第一个字符丢失的bug
//
//
//加入以下代码,支持printf函数,而不需要选择use MicroLIB
#if 1
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{
int handle;
/* Whatever you require here. If the only file you are using is */
/* standard output using printf() for debugging, no file handling */
/* is required. */
};
/* FILE is typedef’ d in stdio.h. */
FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
_sys_exit(int x)
{
x = x;
}
//重定义fputc函数,因为这个串口在ministm32上定义为PA9与PA10 与USB转串口的芯片 连接 因此便于与PC机通讯,方便通过串口助手调试,所以不再重定义为其他串口输出
int fputc(int ch, FILE *f)
{
while((USART1->SR&0X40)==0);//循环发送,直到发送完毕
USART1->DR = (u8) ch;
return ch;
}
自己的重定义fputc函数
//我们还是定义USART1作为printf函数的重定义 输出串口,但是用USART3进行linux的串口输入
//int fputc(int ch, FILE *f)
//{
// while((USART3->SR&0X40)==0);//循环发送,直到发送完毕
// USART3->DR = (u8) ch;
// return ch;
//}
#endif
//在stm32中,串口即用GPIO的复用功能输出 由下面链接可以看出,
//http://download.csdn.net/detail/bolvtin/8867933
//PA9--USART1_TX 输出 PA10--USART1_RX 输入
//PA2--USART2_TX 输出 PA3--USART2_RX 输入
//PB10--USART3_TX 输出;PB11--USART3_RX 输入
/********************************************************************************/
//这里本来是用 串口1既不停地接收另一个PC机B的串口数据,并且在程序里再不断把数据发送给PC机A(上面有串口调试助手,以观察数据)的
//但是发现,串口1在不断接收的同时,如果不停地再往外发,数据就会丢失很多,导致程序错误,
//因此设置为,从串口3接收PC机B的数据,从串口1发送给PC机A的串口调试助手以观察数据
//#ifdef EN_USART1_RX //如果使能了接收
串口1中断服务程序
注意,读取USARTx->SR能避免莫名其妙的错误
//u8 USART_RX_BUF[64]; //接收缓冲,最大64个字节.
接收状态
bit7,接收完成标志
bit6,接收到0x0d
bit5~0,接收到的有效字节数目
//u8 USART_RX_STA=0; //接收状态标记
//
//char imgCenterX[5];
//char imgCenterY[5];
//
//int imgCenterX0=320,imgCenterX1;//对应的图片为宽*高=640*480的
//int imgCenterY0=240,imgCenterY1;
//
//void USART1_IRQHandler(void)
//{
// u8 res;
// if(USART1->SR&(1<<5))//接收到数据
// {
// res=USART1->DR;
// if((USART_RX_STA&0x80)==0)//接收未完成
// {
// //if(USART_RX_STA&0x40)//接收到了0x0d,把USART_RX_STA设置为0100 0000
// if(USART_RX_STA&0x40)//接收到了$美金符号 王博
// {
// //if(res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
// if(res!=0x24)USART_RX_STA=0;//接收错误,重新开始
// else
// {
//
// USART_RX_STA|=0x80; //接收完成了
// imgCenterX[0]=USART_RX_BUF[14]; //第15个字节
// imgCenterX[1]=USART_RX_BUF[15];
// imgCenterX[2]=USART_RX_BUF[16];
// imgCenterX[3]=USART_RX_BUF[17];
//
// imgCenterY[0]=USART_RX_BUF[36];
// imgCenterY[1]=USART_RX_BUF[37];
// imgCenterY[2]=USART_RX_BUF[38];
// imgCenterY[3]=USART_RX_BUF[39];
//
// imgCenterX1=atoi(imgCenterX);
// //printf("%d\n",imgCenterX1);
//
// imgCenterY1=atoi(imgCenterY);
// //printf("%d\n",imgCenterY1);
//
// }
// }else //还没收到0X0D
// {
// //if(res==0x0d)USART_RX_STA|=0x40;
// if(res==0x24)USART_RX_STA|=0x40;
// else
// {
// USART_RX_BUF[USART_RX_STA&0X3F]=res;
// USART_RX_STA++;
// if(USART_RX_STA>63)USART_RX_STA=0;//接收数据错误,重新开始接收
// }
// }
// }
// }
//}
//#endif
/********************************************************************************/
//mini stm32 串口2
#ifdef EN_USART2_RX //如果使能了接收
//串口1中断服务程序
//注意,读取USARTx->SR能避免莫名其妙的错误
u8 USART_RX_BUF[64]; //接收缓冲,最大64个字节.
//接收状态
//bit7,接收完成标志
//bit6,接收到0x0d
//bit5~0,接收到的有效字节数目
u8 USART_RX_STA=0; //接收状态标记
char imgCenterX[5];
char imgCenterY[5];
int imgCenterX0=320,imgCenterX1=320;//对应的图片为宽*高=640*480的
int imgCenterY0=240,imgCenterY1=240;
void USART2_IRQHandler(void)
{
u8 res;
if(USART2->SR&(1<<5))//接收到数据
{
res=USART2->DR;
if((USART_RX_STA&0x80)==0)//接收未完成
{
//if(USART_RX_STA&0x40)//接收到了0x0d,把USART_RX_STA设置为0100 0000
if(USART_RX_STA&0x40)//接收到了$美金符号 tinbo
{
//if(res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
if(res!=0x24)USART_RX_STA=0;//接收错误,重新开始
else
{
USART_RX_STA|=0x80; //接收完成了
imgCenterX[0]=USART_RX_BUF[14]; //第15个字节
imgCenterX[1]=USART_RX_BUF[15];
imgCenterX[2]=USART_RX_BUF[16];
imgCenterX[3]=USART_RX_BUF[17];
imgCenterY[0]=USART_RX_BUF[36];
imgCenterY[1]=USART_RX_BUF[37];
imgCenterY[2]=USART_RX_BUF[38];
imgCenterY[3]=USART_RX_BUF[39];
imgCenterX1=atoi(imgCenterX);
//printf("%d\n",imgCenterX1);
imgCenterY1=atoi(imgCenterY);
//printf("%d\n",imgCenterY1);
}
}else //还没收到0X0D
{
//if(res==0x0d)USART_RX_STA|=0x40;
if(res==0x24)USART_RX_STA|=0x40;
else
{
USART_RX_BUF[USART_RX_STA&0X3F]=res;
USART_RX_STA++;
if(USART_RX_STA>63)USART_RX_STA=0;//接收数据错误,重新开始接收
}
}
}
}
else
{
}
}
#endif
/********************************************************************************/
//mini stm32 串口3
//#ifdef EN_USART3_RX //如果使能了接收
串口1中断服务程序
注意,读取USARTx->SR能避免莫名其妙的错误
//u8 USART_RX_BUF[64]; //接收缓冲,最大64个字节.
接收状态
bit7,接收完成标志
bit6,接收到0x0d
bit5~0,接收到的有效字节数目
//u8 USART_RX_STA=0; //接收状态标记
//
//char imgCenterX[5];
//char imgCenterY[5];
//
//int imgCenterX0=320,imgCenterX1=320;//对应的图片为宽*高=640*480的
//int imgCenterY0=240,imgCenterY1=240;
//
//void USART3_IRQHandler(void)
//{
// u8 res;
// if(USART3->SR&(1<<5))//接收到数据
// {
// res=USART3->DR;
// if((USART_RX_STA&0x80)==0)//接收未完成
// {
// //if(USART_RX_STA&0x40)//接收到了0x0d,把USART_RX_STA设置为0100 0000
// if(USART_RX_STA&0x40)//接收到了$美金符号 王博
// {
// //if(res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
// if(res!=0x24)USART_RX_STA=0;//接收错误,重新开始
// else
// {
//
// USART_RX_STA|=0x80; //接收完成了
// imgCenterX[0]=USART_RX_BUF[14]; //第15个字节
// imgCenterX[1]=USART_RX_BUF[15];
// imgCenterX[2]=USART_RX_BUF[16];
// imgCenterX[3]=USART_RX_BUF[17];
//
// imgCenterY[0]=USART_RX_BUF[36];
// imgCenterY[1]=USART_RX_BUF[37];
// imgCenterY[2]=USART_RX_BUF[38];
// imgCenterY[3]=USART_RX_BUF[39];
//
// imgCenterX1=atoi(imgCenterX);
// //printf("%d\n",imgCenterX1);
//
// imgCenterY1=atoi(imgCenterY);
// //printf("%d\n",imgCenterY1);
//
// }
// }else //还没收到0X0D
// {
// //if(res==0x0d)USART_RX_STA|=0x40;
// if(res==0x24)USART_RX_STA|=0x40;
// else
// {
// USART_RX_BUF[USART_RX_STA&0X3F]=res;
// USART_RX_STA++;
// if(USART_RX_STA>63)USART_RX_STA=0;//接收数据错误,重新开始接收
// }
// }
// }
// }
// else
// {
//
// }
//}
//#endif
/********************************************************************************/
//初始化IO 串口
//pclk2:PCLK2时钟频率(Mhz)
//bound:波特率
//CHECK OK
//091209
void uart_init(u32 pclk2,u32 bound)
{
float temp;
u16 mantissa;
u16 fraction;
float temp2;
u16 mantissa2;
u16 fraction2;
float temp3;
u16 mantissa3;
u16 fraction3;
//总体是3部分
//1、使能串口1时钟。但是因为用到了GPIO口,所以需要使能GPIO的时钟,这里假设使能了A、B、C、D4个GPIO外设,包括了GPIO口的3个步骤
//2、配置寄存器,配置波特率、停止位、校验位等
//3、串口使能,接收缓冲区非空中断使能
/********************************************************************************/
//串口1 的初始化
temp=(float)(pclk2*1000000)/(bound*16);//得到USARTDIV
mantissa=temp; //得到整数部分
fraction=(temp-mantissa)*16; //得到小数部分
mantissa<<=4;
mantissa+=fraction;
RCC->APB2ENR|=1<<2; //使能PORTA口时钟
RCC->APB2ENR|=1<<14; //使能串口1时钟
GPIOA->CRH&=0XFFFFF00F;
GPIOA->CRH|=0X000008B0;//IO状态设置
RCC->APB2RSTR|=1<<14; //复位串口1
RCC->APB2RSTR&=~(1<<14);//停止复位
//波特率设置
USART1->BRR=mantissa; // 波特率设置
USART1->CR1|=0X200C; //1位停止,无校验位.
#ifdef EN_USART1_RX //如果使能了接收
//使能接收中断
USART1->CR1|=1<<8; //PE中断使能
USART1->CR1|=1<<5; //接收缓冲区非空中断使能
MY_NVIC_Init(3,3,USART1_IRQChannel,2);//组2,最低优先级
#endif
/********************************************************************************/
//串口2 的初始化
//PORTA口时钟 //串口2对应的Tx为PA2 RX为PA3
temp2=(float)(pclk2/2*1000000)/(bound*16);//得到USARTDIV
mantissa2=temp2; //得到整数部分
fraction2=(temp2-mantissa2)*16; //得到小数部分
mantissa2<<=4;
mantissa2+=fraction2;
RCC->APB2ENR|=1<<2; //使能PORTA口时钟 //串口2对应的Tx为PA2 RX为PA3
RCC->APB1ENR|=1<<17; //使能串口2时钟
GPIOA->CRL&=0XFFFF00FF; //设置PC10为输出模式 PC11为输入模式
GPIOA->CRL|=0X00008B00;//IO状态设置
RCC->APB1RSTR|=1<<17; //复位串口3
RCC->APB1RSTR&=~(1<<17);//停止复位
//波特率设置
USART2->BRR=mantissa2; // 波特率设置 串口2-5使用的晶振都是36Mhz的 串口2~5使用的是pclk1,为36M,而不是串口1的72M
USART2->CR1|=0X200C; //1位停止,无校验位.
#ifdef EN_USART2_RX //如果使能了接收
//使能接收中断
USART2->CR1|=1<<8; //PE中断使能
USART2->CR1|=1<<5; //接收缓冲区非空中断使能
MY_NVIC_Init(3,3,USART2_IRQChannel,1);//组1,最低优先级
#endif
/********************************************************************************/
//串口3 的初始化
temp3=(float)(pclk2/2*1000000)/(bound*16);//得到USARTDIV
mantissa3=temp3; //得到整数部分
fraction3=(temp3-mantissa3)*16; //得到小数部分
mantissa3<<=4;
mantissa3+=fraction3;
RCC->APB1ENR|=1<<3; //使能PORTC口时钟 //串口3对应的Tx为PB10 RX为PB11
RCC->APB1ENR|=1<<18; //使能串口3时钟
GPIOB->CRH&=0XFFFF00FF; //设置PB10为输出模式 PB11为输入模式
GPIOB->CRH|=0X00008B00;//IO状态设置
RCC->APB1RSTR|=1<<18; //复位串口3
RCC->APB1RSTR&=~(1<<18);//停止复位
//波特率设置
USART3->BRR=mantissa3; // 波特率设置 串口2-5使用的晶振都是36Mhz的 串口2~5使用的是pclk1,为36M,而不是串口1的72M
USART3->CR1|=0X200C; //1位停止,无校验位.
#ifdef EN_USART3_RX //如果使能了接收
//使能接收中断
USART3->CR1|=1<<8; //PE中断使能
USART3->CR1|=1<<5; //接收缓冲区非空中断使能
MY_NVIC_Init(3,3,USART3_IRQChannel,1);//组1,最低优先级
#endif
}
<pre name="code" class="cpp">timer.h
#ifndef __TIMER_H
#define __TIMER_H
#include "sys.h"
//
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//Mini STM32开发板
//通用定时器 驱动代码
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//修改日期:2010/12/03
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 正点原子 2009-2019
//All rights reserved
//
//通过改变TIM3->CCR2的值来改变占空比,从而控制LED0的亮度
#define LED0_PWM_VAL TIM3->CCR2
void Timerx_Init(u16 arr,u16 psc);
void PWM_Init(u16 arr,u16 psc);
#endif
timer.c
#include "timer.h"
#include "led.h"
//
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//Mini STM32开发板
//通用定时器 驱动代码
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//修改日期:2010/12/03
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 正点原子 2009-2019
//All rights reserved
//
//定时器3中断服务程序
void TIM3_IRQHandler(void)
{
if(TIM3->SR&0X0001)//溢出中断
{
LED1=!LED1;
}
TIM3->SR&=~(1<<0);//清除中断标志位
}
//通用定时器中断初始化
//这里时钟选择为APB1的2倍,而APB1为36M
//arr:自动重装值。
//psc:时钟预分频数
//这里使用的是定时器3!
void Timerx_Init(u16 arr,u16 psc)
{
RCC->APB1ENR|=1<<1;//TIM3时钟使能
TIM3->ARR=arr; //设定计数器自动重装值//刚好1ms
TIM3->PSC=psc; //预分频器7200,得到10Khz的计数时钟
//这两个东东要同时设置才可以使用中断
TIM3->DIER|=1<<0; //允许更新中断
TIM3->DIER|=1<<6; //允许触发中断
TIM3->CR1|=0x01; //使能定时器3
MY_NVIC_Init(1,3,TIM3_IRQChannel,2);//抢占1,子优先级3,组2
}
//TIM3 PWM部分
//正点原子@ALIENTEK
//2010/6/2
//PWM输出初始化
//arr:自动重装值
//psc:时钟预分频数
void PWM_Init(u16 arr,u16 psc)
{
/********************************************************************************/
//总体是3部分
//1、使能定时器时钟,因为PWM是由定时器产生的。
// 其实这里用到了GPIOA的引脚,因此也需要使能GPIOA的时钟的,但是在led.c文件中,已经设置过了,所以这里省略了
// 另外这里使用的GPIO输出PWM是复用,所以需要配置GPIO口的默认复用功能,这里请注意,一定是默认的复用功能,可以查看led.c中的3个步骤
//2、配置定时器的配置,TIM3->结构后的,是对定时器3的配置
//3、使能定时器外设,TIM3->CR1|=0x01;
//此部分需手动修改IO口设置
RCC->APB1ENR|=1<<1; //TIM3时钟使能
//下面这两句仅仅是为了,用跳帽把PA7与PA8连接起来,用led灯来观察PWM输出,可以没有的
GPIOA->CRH&=0XFFFFFFF0;//PA8输出
GPIOA->CRH|=0X00000004;//浮空输入 这里的浮空输入是为了防止干扰PA8的输出,因为PA8在ministm32上是连接的led灯
GPIOA->CRL&=0X0FFFFFFF;//PA7输出
GPIOA->CRL|=0XB0000000;//复用功能输出
GPIOA->ODR|=1<<7;//PA7上拉
TIM3->ARR=arr;//设定计数器自动重装值
TIM3->PSC=psc;//预分频器不分频
//TIM3->CCMR1|=7<<12; //CH2 PWM2模式 这个作用在板子上的灯DS0
TIM3->CCMR1|=6<<12; //CH2 PWM2模式 与上面一句的极性相反 如果用这个控制舵机,请使用这个PWM,输出正电平方波
TIM3->CCMR1|=1<<11; //CH2预装载使能
TIM3->CCER|=1<<4; //OC2 输出使能
TIM3->CR1=0x8000; //ARPE使能
TIM3->CR1|=0x01; //使能定时器3
/********************************************************************************/
//通用定时器4 1通道(PB6)和2通道(PB7)
RCC->APB1ENR|=1<<2;//TIM4时钟使能
RCC->APB2ENR|=1<<3;//这里必须有这一个GPIO口的使能,GPIOA已经在led.c中定义过了,可是GPIO 的B口还没有
GPIOB->CRL&=0X00FFFFFF;//清掉PB7 PB6位原来的设置,同时不影响其他位的设置
GPIOB->CRL|=0XBB000000;//复用功能输出 PB7的默认复用功能为 TIM4的2通道
GPIOB->ODR|=1<<7;//PB7
GPIOB->ODR|=1<<6;//PB6
TIM4->ARR=arr;//设定计数器自动重装值
TIM4->PSC=psc;//预分频器不分频
//这里进行说明,CCMR1寄存器是配置模式的,总共可以设置为7中模式,CCMR1控制CH1和CH2 CCMR2控制CH3和CH4
//TIM4->CCMR1|=7<<12; //
TIM4->CCMR1|=6<<12; //PB7的默认复用功能为 TIM4的2通道 CH2 PWM2模式且极性为正电平方波
TIM4->CCMR1|=6<<4; //CH1 PWM2模式
TIM4->CCMR1|=1<<11; //CH2预装载使能
TIM4->CCMR1|=1<<3; //ch1预装载使能 是PB6口
TIM4->CCER|=1<<0; //输出使能 这个寄存器控制着各个输入输出通道的开关,因此如果要有输出必须使能
TIM4->CCER|=1<<4; //输出使能
TIM4->CR1=0x8000; //ARPE使能
TIM4->CR1|=0x01; //使能定时器4
}