赞
踩
由原理图可以知道PC8-PC15控制LED灯,要通过设置GPIO来点亮灯,首先定义初始化结构体,然后使能对应的时钟(可以在rcc.c/.h文件中寻找相关的时钟函数),之后配置结构体(可以在gpio.h文件中查看每个变量可以填入的值),因为GPIO直接输出控制LED,不存在时钟复用,所以采用推挽输出,然后初始化结构体通过GPIO_SetBits(),GPIO_ResetBits()来控制寄存器置位清零来控制LED亮灭。
注意:要找什么外设的相关函数,就去什么模块对应的c/h文件去找,h文件有函数申明,以及函数的可选参数,c文件有函数的注释,用法等。
#include "stm32f10x.h" #include "lcd.h" #include "stm32f10x_gpio.h" u32 TimingDelay = 0; void LED_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC|RCC_APB2Periph_GPIOD,ENABLE);//配置之前先开时钟 GPIO_InitStructure.GPIO_Pin=GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_11|GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15|GPIO_Pin_All; GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP; GPIO_Init(GPIOC,&GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2; GPIO_Init(GPIOD,&GPIO_InitStructure);//配置LED_NLE才能往里写数据 } void LED_Control(u16 led_ctrl) { GPIO_SetBits(GPIOC,GPIO_Pin_All);//LED为低电平点亮,先将所有LED关闭 GPIO_ResetBits(GPIOC,led_ctrl<<8); //低电平点亮 GPIO_SetBits(GPIOD,GPIO_Pin_2);//开启锁存器可以往里写数据 GPIO_ResetBits(GPIOD,GPIO_Pin_2);//写完数据以后关闭锁存器 } void Delay_Ms(u32 nTime); //Main Body int main(void) { LED_Init(); //刚开始初始化好结构体,全为低电平 LED_Control(0xff); while(1); } void Delay_Ms(u32 nTime) { TimingDelay = nTime; while(TimingDelay != 0); }
为了优化外设数目,可以把一些复用功能映射到其他引脚上;比如:LSE 振荡器关闭时, LSE 振荡器引脚 OSC32_IN/OSC32_OUT 可以分别用做 GPIO 的 PC14/PC15,LSE功能始终优先于通用I/O口的功能,此为端口重映射
对于CT117E的蜂鸣器,控制他的引脚为N Buzz对应GPIO为PB4,PB4的重映射为JNTRST功能,现在只想用PB4用作一般GPIO口,不需要JNTRST功能,可以通过控制SWJ_CFG[2:0]位来改变重映射,而改变重映射不能取消SWJ功能,SWJ在下载程序和仿真时使用,只取消JNTRST,所以设置SWJ_CFG[2:0]为001,使用函数GPIO_PinRemapConfig()设置重映射来关闭JNTRST
注意写配置函数时候先打开时钟以后再配置重映射等其他东西对于蜂鸣器电路图,蜂鸣器引脚给0低电平则触发蜂鸣器,给1高电平则关闭蜂鸣器为了在调试期间可以使用更多GPIOs,通过设置复用重映射和调试I/O配置寄存器(AFIO_MAPR) 的SWJ_CFG[2:0]位,可以改变上述重映像配置。参见下表。
buzzer.c
#include "buzzer.h"
void Buzzer_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO,ENABLE);
//改变引脚映射
GPIO_PinRemapConfig(GPIO_Remap_SWJ_NoJTRST,ENABLE);
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
}
buzzer.h
#ifndef __BUZZER_H
#define __BUZZER_H
#include "stm32f10x.h"
#define Buzzer_ON GPIO_ResetBits(GPIOB,GPIO_Pin_4)
#define Buzzer_OFF GPIO_SetBits(GPIOB,GPIO_Pin_4)
void Buzzer_Init(void);
#endif
main.c #include "stm32f10x.h" #include "lcd.h" #include "buzzer.h" u32 TimingDelay = 0; void Delay_Ms(u32 nTime); //Main Body int main(void) { SysTick_Config(SystemCoreClock/1000); Delay_Ms(200); Buzzer_Init(); Buzzer_ON; while(1); } // void Delay_Ms(u32 nTime) { TimingDelay = nTime; while(TimingDelay != 0); }
通过SysTick_Config(SystemCoreClock/1000);此函数配置SysTick定时器,其中参数为配置重装载计数器的值,用此函数打开计数器之后,函数参数开始减少,每当参数减小到0时即开始一次中断,执行中断然后参数又恢复到原来的值继续递减。 而SystemCoreClock为72 000 000即1s震动72 000 000次,SystemCoreClock除以1000即1ms震动72 000次,从72 000开始递减每次执行这个函数可以减1,减72 000次的时间是1ms,即每产生一次中断的时间是1ms,所以可以让中断事件为一个数值递减的函数,即每次产生中断这个数递减1,那么可以实现定时器延时。
除此之外,还要在stm32f10x_it.c中找到对应的中断函数名,在这个函数里填入中断事件
即通过SysTick_Config设置了震动每1ms中断一次,对应的中断函数名是SysTick_Handler中断事件是TimingDelay减1,通过赋予nTime的值赋予TimingDelay实现延时
led.c #include "led.h" void Led_Init() { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE); GPIO_InitStructure.GPIO_Pin=GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_11|GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15|GPIO_Pin_All; GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP; GPIO_Init(GPIOC,&GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2; GPIO_Init(GPIOD,&GPIO_InitStructure); } void Led_Control(u16 led_ctrl,u16 index) { GPIO_SetBits(GPIOC,GPIO_Pin_All); GPIO_ResetBits(GPIOC,led_ctrl<<(8+index)); GPIO_ResetBits(GPIOD,GPIO_Pin_2); GPIO_SetBits(GPIOD,GPIO_Pin_2); }
led.h
#ifndef __LED_H
#define __LED_H
#include "stm32f10x.h"
void Led_Init();
void Led_Control(u16 led_ctrl,u16 index);
#endif
main.c #include "stm32f10x.h" #include "lcd.h" #include "buzzer.h" #include "led.h" #include "timer.h" u32 TimingDelay = 0; _Bool Buzzer_Flag=0; void Delay_Ms(u32 nTime); //Main Body int main(void) { u16 index; SysTick_Config(SystemCoreClock/1000); Delay_Ms(200); Led_Init(); Buzzer_Init(); while(1) { Buzzer_OFF; for(index=0;index<8;index++) { Led_Control(0x01,index); Delay_Ms(500); } Buzzer_ON; Delay_Ms(500); } } // void Delay_Ms(u32 nTime) { TimingDelay = nTime; while(TimingDelay != 0); }
中断定时器:有基本定时器,通用定时器,高级定时器,TIM1到TIM8,TIM1和TIM8是高级定时器,TIM6和TIM7是基本定时器,其余的是通用定时器使用外部定时器需要和中断向量控制器NVIC配合使用。
使用前先新建一个c文件,用来存放定时器TIM的配置和中断向量控制器NVIC的配置,先配置定时器的结构体。
其中TIM_Prescaler为预分频系数,可以从0到71之间配置,0为不分频或者1分频,都是它本身, 71则是72分频,原来时钟频率为72 000 000,即72MHz,72分频后为1 000 000,即1Mhz,也就是1us。
其中TIM_Period为计数周期,从0开始计数,设置为999,即1000个1us,即1ms;
其中TIM_CounterMode为计数模式,向上计数则为0,1,2,3…999,向下计数则为1000,999…1
其中TIM_ClockDivision也是分频系数,一般配置为0
其中TIM_RepetitionCounter通用定时器没有此功能 然后配置好结构体后调用Init函数初始化结构体,然后TIM_ITConfig()来开启中断,最后使用TIM_Cmd()来打开定时器
NVIC配置:NVIC的函数存放在misc.h中,在配置好结构体之前要先NVIC_PriorityGroupConfig()通过此函数配置中断向量优先级分组,之后配置好结构体
其中NVIC_IRQChannel来设置中断源,中断源名字从stm32f10x.h中寻找
其中NVIC_IRQChannelPreemptionPriority为主优先级其中NVIC_IRQChannelSubPriority为次优先级
其中NVIC_IRQChannelCmd为中断使能,ENABLE和DISABLE
主优先级和次优先级的设置都要和中断向量优先级分组相匹配,然后将NVIC的配置整体放于TIM配置前,数字越小优先级越高
最后写入中断函数,中断函数可以写在stm32f10x_it.c中,也可以写在main.c文件中,中断函数名不可以随便起,要在启动文件中查找,中断函数中要通过TIM_GetITStatus()函数检查是否中断函数标志位置位,即if(TIM_GetITStatus(TIM2,TIM_IT_Update)!=RESET),然后通过TIM_ClearITPendingBit(TIM2,TIM_IT_Update)清除标志位,在清除标志位的同时写入中断函数内容。外部中断的好处是可以在CPU执行别的语句时同时计时产生中断,不浪费时间,SysTick则浪费时间
timer.c #include "time.h" #include "stm32f10x_tim.h" void NVIC_Config(void); void TIM2_Init(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); NVIC_Config(); //设置预分频系数,从0开始计数,0是不分频,即1分频,71则是72分频 TIM_TimeBaseInitStructure.TIM_Prescaler=71;//原来为72 000 000,72分频之后得1 000 000即1Mhz,也就是1us //设置计数模式 TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //设置计数周期 TIM_TimeBaseInitStructure.TIM_Period=1000-1;//也是从0开始计数所以从0记到999,即1000个1us,1ms //也是分频系数,没多大用,一般设置为0 TIM_TimeBaseInitStructure.TIM_ClockDivision=0; //配置重复计算器,通用定时器没有此功能 //配置好定时器 TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); //开启中断 TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); //开启定时器 TIM_Cmd(TIM2, ENABLE); } void NVIC_Config(void) { NVIC_InitTypeDef NVIC_InitStructure; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0); NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0; NVIC_InitStructure.NVIC_IRQChannelSubPriority=1; NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE; NVIC_Init(&NVIC_InitStructure); }
timer.h
#ifndef __TIMER_H
#define __TIMER_H
#include "stm32f10x_tim.h"
void TIM2_Init(void);
void NVIC_Config(void);
#endif
#include "stm32f10x.h" #include "lcd.h" #include "buzzer.h" #include "led.h" #include "timer.h" u32 TimingDelay = 0; _Bool Buzzer_Flag=0; void Delay_Ms(u32 nTime); //Main Body int main(void) { u16 index; SysTick_Config(SystemCoreClock/1000); Delay_Ms(200); Led_Init(); Buzzer_Init(); TIM2_Init(); while(1) { // Buzzer_OFF; // for(index=0;index<8;index++) // { // Led_Control(0x01,index); // Delay_Ms(500); // } // Buzzer_ON; // Delay_Ms(500); if(Buzzer_Flag==1) { Buzzer_ON; } else { Buzzer_OFF; } } } // void Delay_Ms(u32 nTime) { TimingDelay = nTime; while(TimingDelay != 0); } void TIM2_IRQHandler(void) { static u16 Buzzer_Count=0; if(TIM_GetITStatus(TIM2,TIM_IT_Update)!=RESET) { TIM_ClearITPendingBit(TIM2,TIM_IT_Update); Buzzer_Count++; if(Buzzer_Count==500) { Buzzer_Count=0; Buzzer_Flag=!Buzzer_Flag; } } }
前面的和状态机消抖一样,但是需要在.h文件中额外定义两个外部变量Trg和Cont,除此之外还要在.c文件再次定义这两个变量。####切记宏定义的KB值别加分号
^ 为按位异或,即一样为0,不一样为1;&为按位与
当没有按键按下时:KEYPORT=0xff,ReadData=(KEYPORT)^0xff为0,所以在Trg中,0&任何数都得0,所以Trg=0,所以Cont也为0;
当按键KB1按下时:KEYPORT=0xfe,ReadData=(0xfe)^0xff,即 1111 1110 ^ 1111 1111 =0000 0001=0x01,即ReadData=0x01,Trg=0x01 & (0x01 ^ 0),即 0000 0001 & (0000 0001 ^0000 0000),按位运算,所以Trg=0x01;
当按键KB1松开时:KEYPORT=0xff,ReadData=(0xff)^0xff=0,Trg也等于0,Cont也等于0
当按键KB2按下时:KEYPORT=0xfd,ReadData=(0xfd)^0xff,即 1111 1101 ^ 1111 1111 = 0000 0010=0x02,即ReadData=0x02,Trg=0x02 & (0x02 ^ 0),即 0000 0010 & (0000 0010 ^ 0000 0000),按位运算,所以Trg=0x02;
所以可以看出,可以通过查看Trg的值看是否有按键按下,0x01为第一个按键,0x02为第二个,以此类推
###注意ReadData=(KEYPORT)^ 0xff,这个KEYPORT必须带括号,因为KEYPORT =KB1|(KB2<<1)|(KB3<<2)|(KB4<<3)|0xf0 如果不加括号,C语言默认的是直接替换即KB1|(KB2<<1)|(KB3<<2)|(KB4<<3)|0xf0^ 0xff,因为异或的运算等级高,所以会0xf0^0xff先计算,会导致错误
key.c
#include "key.h"
#include "stm32f10x.h"
void KEY_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_8;
//GPIO_InitStructure.GPIO_Speed=GPIO_Speed_10MHz;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1|GPIO_Pin_2;
GPIO_Init(GPIOB,&GPIO_InitStructure);
}
key.h
#ifndef __KEY_H
#define __KEY_H
void KEY_Config(void);
#endif
key_threeline.c
#include "key_threeline.h"
#include "stm32f10x.h"
unsigned char Cont;
unsigned char Trg;
void Key_Threeline(void)
{
unsigned char ReadData=(KEYPORT)^0xff;
Trg=ReadData&(ReadData^Cont);
Cont=ReadData;
}
key_threeline.h
#ifndef __KEY_THREELINE_H
#define __KEY_THREELINE_H
#define KB1 GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0)
#define KB2 GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_8)
#define KB3 GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1)
#define KB4 GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_2)
#define KEYPORT KB1|(KB2<<1)|(KB3<<2)|(KB4<<3)|0xf0
void Key_Threeline(void);
extern unsigned char Cont;
extern unsigned char Trg;
#endif
timer.c #include "timer.h" #include "stm32f10x.h" #include "stm32f10x_tim.h" void NVIC_Config(void) { NVIC_InitTypeDef NVIC_InitStructure; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0); NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0; NVIC_InitStructure.NVIC_IRQChannelSubPriority=1; NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE; NVIC_Init(&NVIC_InitStructure); } void TIM2_Config(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); NVIC_Config(); TIM_TimeBaseInitStructure.TIM_Prescaler=71; TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; TIM_TimeBaseInitStructure.TIM_Period=999; TIM_TimeBaseInitStructure.TIM_ClockDivision=0; //TIM_TimeBaseInitStructure.TIM_RepetitionCounter=; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); TIM_Cmd(TIM2,ENABLE); }
timer.h
#ifndef __TIMER_H
#define __TIMER_H
void NVIC_Config(void);
void TIM2_Config(void);
#endif
main.c #include "stm32f10x.h" #include "lcd.h" #include "buzzer.h" #include "timer.h" #include "key_state.h" #include "key.h" #include "key_threeline.h" extern unsigned char Bool=0; u32 TimingDelay = 0; void Delay_Ms(u32 nTime); //Main Body int main(void) { u8 key_value; SysTick_Config(SystemCoreClock/1000); Delay_Ms(200); Led_Init(); KEY_Config(); TIM2_Config(); while(1) { if(Bool==1) { Bool=0; Key_Threeline(); if(Trg==0x01) { Led_Control(0x01,0); } if(Trg==0x02) { Led_Control(0x01,1); } } } } void TIM2_IRQHandler(void) { static unsigned char count=0; if(TIM_GetITStatus(TIM2, TIM_IT_Update)!=RESET) { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); count++; if(count==5) { count=0; Bool=1; } } } // void Delay_Ms(u32 nTime) { TimingDelay = nTime; while(TimingDelay != 0); }
IIC通信协议:有SCL,SDA两条线,在启动IIC时,要在SCL高电平期间SDA由高电平向低电平转化,关闭IIC时,要在SCL高电平期间把SDA从低拉高
写数据:在写入一个字节的时候先打开IIC,然后写入AT24C02的固定地址和可配置地址还有一位读写的01构成的八位的地址,固定地址为1010,可配置地址为000,写为0,读为1,然后每次发送一个数据就要接收一个应答,接受一个数据就要发送一个应答,所以要接受一个应答,然后写入存储数据的地址,即EEPROM中有从0到255的位置都可以存储数据,看要把数据存放在哪,单片机就可以知道在哪里操作,再接收一个应答,发送要发送的数据再接收一个应答,最后停止IIC
读数据:在写入一个字节的时候先打开IIC,然后写入AT24C02的固定地址和可配置地址还有一位读写的01构成的八位的地址,接受一个应答,写入存储数据的地址,接受一个应答,就可以让单片机找到要在哪里读写数据,然后再打开IIC,然后写入AT24C02的固定地址和可配置地址还有一位读的1,单片机就可以知道是要读数据,接收一个应答,然后读出数据,发送一个应答,结束IIC
每次接收应答的时候默认可以正常接收到应答,只调用一下,不接收返回值,注意传参的时候,写数据要传入存储地址和数据,读数据的时候要传入存储地址。
******不能在中断中用IIc保存数据,应该在中断中用个变量置一,在中断外部保存数据
i2c.c /* 程序说明: CT117E嵌入式竞赛板GPIO模拟I2C总线驱动程序 软件环境: Keil uVision 4.10 硬件环境: CT117E嵌入式竞赛板 日 期: 2011-8-9 */ #include "stm32f10x.h" #include "i2c.h" /** I2C 总线接口 */ #define I2C_PORT GPIOB #define SDA_Pin GPIO_Pin_7 #define SCL_Pin GPIO_Pin_6 #define FAILURE 0 #define SUCCESS 1 #define SLAVE_ADDRESSW 0xA0 #define SLAVE_ADDRESSR 0xA1 //配置SDA信号线为输入模式 void SDA_Input_Mode() { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = SDA_Pin; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; GPIO_Init(I2C_PORT, &GPIO_InitStructure); } //配置SDA信号线为输出模式 void SDA_Output_Mode() { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = SDA_Pin; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(I2C_PORT, &GPIO_InitStructure); } // void SDA_Output( uint16_t val ) { if ( val ) { GPIO_SetBits(I2C_PORT,SDA_Pin); } else { GPIO_ResetBits(I2C_PORT,SDA_Pin); } } // void SCL_Output( uint16_t val ) { if ( val ) { GPIO_SetBits(I2C_PORT,SCL_Pin); } else { GPIO_ResetBits(I2C_PORT,SCL_Pin); } } // uint8_t SDA_Input() { return GPIO_ReadInputDataBit( I2C_PORT, SDA_Pin); } //延时程序 void delay1(unsigned int n) { unsigned int i; for ( i=0;i<n;++i); } //I2C总线启动 void I2CStart(void) { SDA_Output(1);delay1(500); SCL_Output(1);delay1(500); SDA_Output(0);delay1(500); SCL_Output(0);delay1(500); } //I2C总线停止 void I2CStop(void) { SCL_Output(0); delay1(500); SDA_Output(0); delay1(500); SCL_Output(1); delay1(500); SDA_Output(1); delay1(500); } //等待应答 unsigned char I2CWaitAck(void) { unsigned short cErrTime = 5; SDA_Input_Mode(); delay1(500); SCL_Output(1);delay1(500); while(SDA_Input()) { cErrTime--; delay1(500); if (0 == cErrTime) { SDA_Output_Mode(); I2CStop(); return FAILURE; } } SDA_Output_Mode(); SCL_Output(0);delay1(500); return SUCCESS; } //发送应答位 void I2CSendAck(void) { SDA_Output(0);delay1(500); delay1(500); SCL_Output(1); delay1(500); SCL_Output(0); delay1(500); } // void I2CSendNotAck(void) { SDA_Output(1); delay1(500); SCL_Output(1); delay1(500); SCL_Output(0); delay1(500); } //通过I2C总线发送一个字节数据 void I2CSendByte(unsigned char cSendByte) { unsigned char i = 8; while (i--) { SCL_Output(0);delay1(500); SDA_Output(cSendByte & 0x80); delay1(500); cSendByte += cSendByte; delay1(500); SCL_Output(1);delay1(500); } SCL_Output(0);delay1(500); } //从I2C总线接收一个字节数据 unsigned char I2CReceiveByte(void) { unsigned char i = 8; unsigned char cR_Byte = 0; SDA_Input_Mode(); while (i--) { cR_Byte += cR_Byte; SCL_Output(0);delay1(500); delay1(500); SCL_Output(1);delay1(500); cR_Byte |= SDA_Input(); } SCL_Output(0);delay1(500); SDA_Output_Mode(); return cR_Byte; } //I2C总线初始化 void i2c_init() { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitStructure.GPIO_Pin = SDA_Pin | SCL_Pin; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // ** GPIO_Init(I2C_PORT, &GPIO_InitStructure); } //自己写的 unsigned char Write_AT24C02(uint16_t WORD_ADDRESS , uint16_t Data) { I2CStart(); I2CSendByte(SLAVE_ADDRESSW); I2CWaitAck(); I2CSendByte(WORD_ADDRESS); I2CWaitAck(); I2CSendByte(Data); I2CWaitAck(); I2CStop(); } unsigned char Read_AT24C02(uint16_t WORD_ADDRESS) { unsigned char Data; I2CStart(); I2CSendByte(SLAVE_ADDRESSW); I2CWaitAck(); I2CSendByte(WORD_ADDRESS); I2CWaitAck(); I2CStart(); I2CSendByte(SLAVE_ADDRESSR); I2CWaitAck(); Data=I2CReceiveByte(); I2CSendAck(); I2CStop(); return Data; }
i2c.h #ifndef __I2C_H__ #define __I2C_H__ void i2c_init(void); void delay1(unsigned int n); void I2CStart(void); void I2CStop(void); void I2CSendAck(void); void I2CSendNotAck(void); unsigned char I2CWaitAck(void); void I2CSendByte(unsigned char cSendByte); unsigned char I2CReceiveByte(void); #endif
#include "stm32f10x.h" #include "lcd.h" #include "buzzer.h" #include "led.h" u32 TimingDelay = 0; _Bool Buzzer_flag=0; void Delay_Ms(u32 nTime); //Main Body int main(void) { u16 index; SysTick_Config(SystemCoreClock/1000); Delay_Ms(200); Led_Init(); Buzzer_Init(); i2c_init(); Buzzer_flag=Read_AT24C02(0x00); Buzzer_flag=!Buzzer_flag; if(Buzzer_flag) { Buzzer_ON; } else { Buzzer_OFF; } Write_AT24C02(0x00 ,Buzzer_flag); while(1) { } } // void Delay_Ms(u32 nTime) { TimingDelay = nTime; while(TimingDelay != 0); }
至于lcd显示,只需要知道几个常用函数即可:
STM3210B_LCD_Init(void);用来初始化lcd
LCD_SetTextColor(vu16 Color);用来设置从这句代码执行之后的文字颜色
LCD_SetBackColor(vu16 Color);用来设置从这句代码执行之后的背景颜色
LCD_Clear(u16 Color);用来设置以什么颜色清屏
LCD_DisplayStringLine(u8 Line, u8 *ptr);来设置在哪行显示什么字符,通常与sprintf函数搭配使用
void Display_eeprom(uint16_t eeprom)
{
uint8_t temp[20];
sprintf(temp,"eeprom is %d",eeprom);
LCD_DisplayStringLine(Line0,temp);
}
USART串口通信:RBT6有两个USART,一个USART对应一对RX、TX引脚,RBT6上有两对,RX1、TX1对应USART1,RX2、TX2对应USART2,可以用于单片机向电脑发送数据,电脑通过串口助手打印电脑收到的数据,也可以电脑通过串口助手向单片机发送数据,单片机再把收到的数据发向电脑看是否单片机正确接收到了数据。串口也可以再程序的关键点检查错误,如果程序运行到那里,就发送OK
USART只发送数据:查看原理图上需要使用的USARTx对用的TXx和RXx,然后初始化引脚GPIO的结构体,如果使用USART2的话打开时钟时不仅打开GPIO时钟,还要打开AFIO的时钟,在使用USART的情况下,TX对应的引脚要配置成复用功能的推挽输出,RX对应的引脚要配置成浮空输入,然后配置USART的结构体,USART_BaudRate为波特率,根据题目要求配置波特率,只有波特率一样才能在统一频道通信,USART_WordLength为字长,有八位九位字长,一般为八位字长,USART_StopBits为停止位,有一位,半位,两位,一点五位,一般为一位,USART_Mode位通信模式,通过设置通信模式来设置是接收数据还是发送数据,USART_HardwareFlowControl来指定硬件流量控制模式是启用还是禁用,一般是禁用,然后初始化USART结构体,然后使用USART_Cmd(
)来打开USART。
自定义函数发送:发送数据的方式有两种,其中一种是自己书写发送函数,以发送字符串为例,形参设置为字符串的地址,以地址来取字符串,用USART_SendData(USART2, *s++)来一个字母一个字母,一位一位的发送,*s在自增运算符++的左边,表示先用后加,指针默认指向字符串的第一个字母,然后逐渐往后移,USART_GetFlagStatus()来判断中断标志位状态,在发送数据的时候中断标志位设置为检测USART_FLAG_TXE也就是发送数据的空标志位,每次数据发送出去这个标志位就清零,如果清零,就说明这一位数据已发送,该发送下一位数据了,再通过循环发送下一位数据,用while判断数据有没有发送完,如果字符型数据发送完,字符型数据后面有\0,即可跳出循环。主函数调用即可SendStr(“hello”)
usart.c #include "usart.h" #include "stm32f10x.h" void usart_config(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2; GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3; GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); USART_InitStructure.USART_BaudRate=115200; USART_InitStructure.USART_WordLength=USART_WordLength_8b; USART_InitStructure.USART_StopBits=USART_StopBits_1; USART_InitStructure.USART_Parity=USART_Parity_No; USART_InitStructure.USART_Mode=USART_Mode_Tx|USART_Mode_Rx; USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None; USART_Init(USART2, &USART_InitStructure); USART_Cmd(USART2, ENABLE); } void SendStr(char *s) { while(*s) { USART_SendData(USART2, *s++); while(USART_GetFlagStatus(USART2, USART_FLAG_TXE)==RESET) { } } }
usart.h
#ifndef __USART_H
#define __USART_H
void usart_config(void);
void SendStr(char *s);
#endif
main.c #include "stm32f10x.h" #include "lcd.h" #include "buzzer.h" #include "led.h" #include "usart.h" u32 TimingDelay = 0; void Delay_Ms(u32 nTime); //Main Body int main(void) { u16 index; SysTick_Config(SystemCoreClock/1000); Delay_Ms(200); Led_Init(); Buzzer_Init(); usart_config(); SendStr("hello"); while(1) { } } // void Delay_Ms(u32 nTime) { TimingDelay = nTime; while(TimingDelay != 0); }
C语言库发送:在固件库\stm32f10x_stdperiph_lib\STM32F10x_StdPeriph_Lib_V3.5.0\Project\STM32F10x_StdPeriph_Examples\USART\Printf\main.c 文件中复制以int fputc(int ch, FILE *f)为函数名,图示下方语句为函数体的函数,并将原有函数体中参数改变,改为如图所示,切记将中断标志位都换位发送非空中断,即USART_FLAG_TXE,因为原有的C语言的函数不能在keil中直接使用,要添加头文件 #include “stdio.h” ,并且要将魔术棒中Use MicroLIB勾选,使用微库才可以正常使用,然后可以像原有C语言中的printf一样使用
####使用printf必须使用微库,不然会导致代码无法运行
USART接收数据:在初始化USART时,除了要配置上面的GPIO,USART结构体之外,还要配置NVIC来初始化USART串口中断,与定时器中断不太一样,然后在main函数中写入USART中断函数,应为这是接收数据,所以要判断接收数据寄存器非空标志位,每次接收一个数据,非空标志位就置1,用USART_GetITStatus()函数来判断非空标志位是不是RESET,如果不是,说明单片机接收到了数据,,然后要清除这个标志位,用来判断下一位数据是否正常发送,清除数据位之后,用一个数组逐位接收USART接收到的数据,用自己定义的计数变量来推后数组引用,因为这是接收数据,无法通过串口助手直接看到,所以在串口中断中再将接受到的数据返回给电脑,用于检查程序是否错误,这也是USART的一个作用
****在接收数据时,尤其是字符串,接收寄存器不会接收到字符串最后的“\0”,或者说可以接收到,但是“\0”并不占数组的一位
usart.c #include "stdio.h" #include "usart.h" void Usart_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2; GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3; GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); USART_InitStructure.USART_BaudRate=115200; USART_InitStructure.USART_Mode=USART_Mode_Rx|USART_Mode_Tx; USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None; USART_InitStructure.USART_Parity=USART_Parity_No; USART_InitStructure.USART_StopBits=USART_StopBits_1; USART_InitStructure.USART_WordLength=USART_WordLength_8b; USART_Init(USART2, &USART_InitStructure); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0); NVIC_InitStructure.NVIC_IRQChannel=USART2_IRQn; NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0; NVIC_InitStructure.NVIC_IRQChannelSubPriority=1; NVIC_Init(&NVIC_InitStructure); USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); USART_Cmd(USART2, ENABLE); } int fputc(int ch, FILE *f) { /* Place your implementation of fputc here */ /* e.g. write a character to the USART */ USART_SendData(USART2, (uint8_t) ch); /* Loop until the end of transmission */ while (USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET) {} return ch; }
usart.h
#ifndef __USART_H
#define __USART_H
#include "stm32f10x.h"
#include "stdio.h"
void Usart_Config(void);
#endif
main.c #include "stm32f10x.h" #include "lcd.h" #include "buzzer.h" #include "led.h" //#include "stdio.h" #include "usart.h" u32 TimingDelay = 0; void Delay_Ms(u32 nTime); //Main Body int main(void) { u16 index; SysTick_Config(SystemCoreClock/1000); Delay_Ms(200); Led_Init(); Buzzer_Init(); Usart_Config(); printf("hello %d",10); while(1) { } } u8 rx_buf[10]; char rx_count=0; int i=0; void USART2_IRQHandler(void) { if(USART_GetITStatus(USART2, USART_IT_RXNE)!=RESET) { USART_ClearITPendingBit(USART2, USART_IT_RXNE); rx_buf[rx_count++]=USART_ReceiveData(USART2); if((rx_buf[4]!=0)&&i==0) { printf("%s",rx_buf); i++; } } } void Delay_Ms(u32 nTime) { TimingDelay = nTime; while(TimingDelay != 0); }
ADC数模转换:RBT6有ADC1,ADC2两个ADC,每个ADC有16个通道,通过下图所示PB0接滑动变阻器,滑到最上为3.3v,滑到最下为0v,将0到3.3v的电压映射到0到4096,即2的12次方,通过采集到的值反馈当前电压值。
首先初始化GPIO,注意将GPIO模式设置为模拟输入,几乎是ADC专用模式,在输入模式下不需要设置速度,下来配置ADC结构体时候先打开时钟,然后配置结构体:
ADC_Mode为ADC模式,有单双通道模式,即单个ADC还是两个ADC
ADC_ScanConvMode为扫描模式指定是在扫描(多通道)模式还是单通道(单通道)模式下执行转换,一般配置为ENABLE即可
ADC_ContinuousConvMode为是否配置持续扫描模式,一般配置为ENABLE,否则只扫描一次,使能后会持续一直扫描ADC_ExternalTrigConv为定义用于启动常规通道的模数转换的外部触发器,一般配置为没有,即ADC_ExternalTrigConv_None
ADC_DataAlign设置对齐方式设置为右对齐ADC_DataAlign_Right即可
ADC_NbrOfChannel为设置需要使用的ADC通道数,一个ADC有16个通道,需要用几个就配置几个
然后初始化结构体,如果设置了ADC中断,接下来就要用ITConfig来使能中断,
ADC_RegularChannelConfig通过这个函数来配置规则通道的ADC通道和转换顺序和采样时间,准换顺序在只有一个ADC的时候没用,所以配置成1即可,采样时间随便配置一个即可ADC_SampleTime_55Cycles5就行,然后用Cmd使能ADC,然后通过ADC_ResetCalibration来复位校准寄存器,用while函数嵌套ADC_GetResetCalibrationStatus来等待ADC校准寄存器复位完成,再用ADC_StartCalibration来进行ADC校准,同样使用while嵌套ADC_GetCalibrationStatus来等待ADC采样完成,最后用ADC_SoftwareStartConvCmd用软件开始ADC采样。
使用了中断以后,在写中断函数时老套路用ADC_GetITStatus检查ADC_IT_EOC也就是规则通道的中断是不是来了,如果中断来了这个位就会置位成SET,ADC_IT_AWD则是注入通道的中断,然后用ADC_ClearITPendingBit这个函数来清除中断标志位,以便于接收下一次中断,然后可以用ADC_GetConversionValue来读取ADC中接收到的值
adc.c #include "adc.h" #include "stm32f10x.h" void GPIO_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN; GPIO_Init(GPIOB,&GPIO_InitStructure); } void ADC_Config(void) { ADC_InitTypeDef ADC_InitStructure; GPIO_Config(); RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); ADC_InitStructure.ADC_Mode=ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode= DISABLE; ADC_InitStructure.ADC_ContinuousConvMode=ENABLE; ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel=1; ADC_Init(ADC1,&ADC_InitStructure); RCC_ADCCLKConfig(RCC_PCLK2_Div8); ADC_RegularChannelConfig(ADC1, ADC_Channel_8, 1, ADC_SampleTime_55Cycles5); //ADC_ITConfig(ADC1,ADC_IT_EOC,ENABLE);有中断时使用 ADC_DMACmd(ADC1, ENABLE); ADC_Cmd(ADC1,ENABLE); ADC_ResetCalibration(ADC1); while(ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while(ADC_GetCalibrationStatus(ADC1)); ADC_SoftwareStartConvCmd(ADC1,ENABLE); }
adc.h
#ifndef __ADC_H
#define __ADC_H
void GPIO_Config(void);
void ADC_Config(void);
#endif
main.c #include "stm32f10x.h" #include "lcd.h" #include "buzzer.h" #include "led.h" #include "adc.h" u32 TimingDelay = 0; void Delay_Ms(u32 nTime); //Main Body uint32_t value; void LCD_Show(uint32_t value) { char buffer[20]; sprintf(buffer,"value is %d",value); LCD_DisplayStringLine(Line2,buffer); } int main(void) { u16 index; SysTick_Config(SystemCoreClock/1000); Delay_Ms(200); ADC_Config(); STM3210B_LCD_Init(); LCD_Clear(Black); LCD_SetTextColor(Red); LCD_SetBackColor(Black); while(1) { value=ADC_GetConversionValue(ADC1); LCD_Show(value); } } // void Delay_Ms(u32 nTime) { TimingDelay = nTime; while(TimingDelay != 0); }
RTC实时时钟:单片机内部有一块掉电不丢失的寄存器(由额外电池(纽扣电池)供电),RTC模块的一些配置是被一个叫做后备区域保护着(禁止写),所以设置之前要先取消后背区域写保护。RTC和普通定时器不一样的地方:RTC模块和时钟配置系统是在后备区域,即在系统复位或从待机模式唤醒后,RTC的设置和时间维持不变。但在系统复位后会禁止访问后备寄存器和RTC,以防止对后备区域的意外操作,所以在设置RTC之前要去掉备份区域写保护。
RTC配置:先配置好NVIC的RTC中断,然后开始正式配置RTC,首先将RCC_APB1Periph_PWR | RCC_APB1Periph_BKP这两个的时钟配置好,即打开给RTC供电的电源和备份区域的时钟,用PWR_BackupAccessCmd(ENABLE)来使能后备电源,使用BKP_DeInit()来复位备份区域,因为RBT6没有外部晶振,所以只能使用LSI作为时钟来源,用RCC_LSICmd(ENABLE)来打开LSI为时钟,使用while循环来判断LSI低速时钟是否准备就绪,即while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET),再通过RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI)来设置LSI作为RTC时钟来源,上面那个是打开LSI时钟,这个是把LSI作为RTC的时钟,再通过RCC_RTCCLKCmd(ENABLE)来使能RTC时钟,再通过RTC_WaitForLastTask()来等待最近一次对RTC寄存器的写操作完成,通过RTC_WaitForSynchro()来等待RTC寄存器同步,然后使用RCC的ITConfig来使能中断,参数设置为RTC_IT_SEC来设置为秒中断,一秒记一次时,继续通过RTC_WaitForLastTask()来等待最近一次对RTC寄存器的写操作完成,通过RTC_SetPrescaler(40000)来配置预分频系数,继续用上述RTC函数RTC_WaitForLastTask()来等待写完成,使用BKP_TamperPinCmd(DISABLE);来禁用防篡改功能,最受使能NVIC即可
初始化时间值:先让RTC等待上次写操作完成,使用RTC_SetCounter(Tmp_HH3600 + Tmp_MM60 + Tmp_SS)来写入初始化以后的时间,因为是输入参数是秒,所以将其设置为三个形参的函数,分别输入时分秒,然后在括号中将其转换成秒,最后再调用一次等待写操作函数
在LCD显示时间:向函数传入参数,参数应为秒数,先在函数中用RTC_GetCounter()这个函数的返回值来判断是不是等于23:59:59这个时间的秒数的十六进制值即 0x1517F,如果等于的话,将时间再通过RTC_SetCounter(0x0)来重置为0,然后等待写操作完成,然后用除法把输入的形参拆分成时分秒,然后用printf来显示。
RTC的中断配置:老规矩用GetITStatus来判断RTC_IT_SEC秒中断是否已经不是RESET,即中断位已经置一,如果已经置一,则清除中断位以后写入要写的操作,然后再次调用函数等待写操作完成
在主函数中先调用函数将时间初始化,然后再通过调用Display函数把用GetCounter返回的值显示出来,因为中断一直在更新,所以函数返回值也在一直更新,所以时间显示出来也在一直更新
rtc.c #include "rtc.h" #include "stm32f10x.h" #include "stdio.h" void Rtc_Config(void) { NVIC_InitTypeDef NVIC_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP|RCC_APB1Periph_PWR,ENABLE); PWR_BackupAccessCmd(ENABLE); BKP_DeInit(); RCC_LSICmd(ENABLE); while(RCC_GetFlagStatus(RCC_FLAG_LSIRDY)==RESET); RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI); RCC_RTCCLKCmd(ENABLE); RTC_WaitForLastTask(); RTC_WaitForSynchro(); RTC_ITConfig(RTC_IT_SEC, ENABLE); RTC_WaitForLastTask(); RTC_SetPrescaler(40000); RTC_WaitForLastTask(); BKP_TamperPinCmd(DISABLE); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0); NVIC_InitStructure.NVIC_IRQChannel=RTC_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0; NVIC_InitStructure.NVIC_IRQChannelSubPriority=1; NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE; NVIC_Init(&NVIC_InitStructure); } void Time_Adjust(u8 HH,u8 MM,u8 SS) { RTC_WaitForLastTask(); RTC_SetCounter(HH*3600+MM*60+SS); RTC_WaitForLastTask(); } void Time_Display(uint32_t Time) { u8 HH=0,MM=0,SS=0; if(RTC_GetCounter()==0x0001517F) { RTC_SetCounter(0x0); RTC_WaitForLastTask(); } HH=Time/3600; MM=(Time%3600)/60; SS=(Time%3600)%60; printf("Time:%d:%d:%d\n",HH,MM,SS); }
rtc.h
#ifndef __RTC_H
#define __RTC_H
#include "stm32f10x.h"
void Rtc_Config(void);
void Time_Adjust(u8 HH,u8 MM,u8 SS);
void Time_Display(uint32_t Time);
#endif
main.c #include "stm32f10x.h" #include "lcd.h" #include "buzzer.h" #include "led.h" #include "usart.h" u32 TimingDelay = 0; _Bool flag=0; void Delay_Ms(u32 nTime); //Main Body int main(void) { u16 index; SysTick_Config(SystemCoreClock/1000); Delay_Ms(200); Rtc_Config(); Time_Adjust(23,50,50); while(1) { if(flag==1) { Time_Display(RTC_GetCounter()); flag=0; } } } void RTC_IRQHandler(void) { if(RTC_GetITStatus(RTC_IT_SEC)!=RESET) { RTC_ClearITPendingBit(RTC_IT_SEC); flag=1; RTC_WaitForLastTask(); } } // void Delay_Ms(u32 nTime) { TimingDelay = nTime; while(TimingDelay != 0); }
PWM输出与输入捕获:PWM波通过定时器产生,STM32 的定时器除了基本定时器 TIM6 和 TIM7。其他的定时器都可以用来产生 PWM 输出。其中高级定时器 TIM1 和 TIM8 可以同时产生多达 7 路的 PWM 输出。而通用定时器也能同时产生多达 4路的 PWM 输出,这样,STM32 最多可以同时产生 30 路 PWM 输出。以下为STM32所有的定时器的输出PWM的通道对应的端口
PWM输出:首先打开时钟,这里以TIM2的第二个通道为例,即PA1口,因为PA1口对应不仅有TIM2的通道,还有TIM5的通道,所以打开时钟的时候不仅要打开定时器和GPIO的时钟,还要打开AFIO端口复用的时钟,再配置GPIO的结构体时,要将模式设置为复用推挽输出,然后接下来配置TIM的定时器功能的结构体,即TIM_TimeBaseInitTypeDef:
TIM_Prescaler:配置预分频系数Prescaler = (TIM3CLK / TIM3 counter clock) - 1,在官方解释中The TIM3CLK frequency is set to SystemCoreClock (Hz), to get TIM3 counter clock at 24 MHz the Prescaler is computed as following,即TIM3CLK=SystemCoreClock,TIM3 counter clock=24MHz,即可得出预分频系数,
TIM_Period:定时器周期,实际就是设定自动重载寄存器的值,在官方解释中The TIM3 is running at 36 KHz: TIM3 Frequency = TIM3 counter clock/(ARR + 1) = 24 MHz / 666 = 36 KHz,即TIM3应该是36KHz的速度运行,因为TIM3 counter clock即TIM3的时钟为24MHz,要想让TIM3以36KHz的速度运行,就应该配置666为一个周期,又因为从0开始计数,所以设置TIM_Period为665
再配置管理PWM输出的结构体TIM_OCInitTypeDef:
TIM_OCMode:配置输出的模式,常用有TIM_OCMode_PWM1和TIM_OCMode_PWM2:
TIM_OCMode_PWM1:在向上计数时,一旦TIMx_CNT<TIMx_CCR1时通道1为有效电平,否则为 无效电平;在向下计数时,一旦TIMx_CNT>TIMx_CCR1时通道1为无效电平(OC1REF=0),否 则为有效电平(OC1REF=1)。
TIM_OCMode_PWM2:在向上计数时,一旦TIMx_CNT<TIMx_CCR1时通道1为无效电平,否则为 有效电平;在向下计数时,一旦TIMx_CNT>TIMx_CCR1时通道1为有效电平,否则为无效电 平。以PWM2为例,图示斜线为TIM中的计时器,随时间而增长,增长到ARR之后回到0重新计时,当小于CCRx时为无效电平,当大于CCRx为有效电平。
TIM_OutputState:用来让输出状态为使能OC输出。
TIM_OutputNState:用来指定输出比较状态,但是只对TIM1和TIM8适用,一般不用
TIM_Pulse:设置脉冲宽度,左图为计算占空比的方法,即CCRx与ARR的比值,因为在上面已将说到了以CCRx为界来看有效电平和无效电平,所以如果CCR为ARR的一半,刚好占空比就是百分之五十, 而ARR为计数的最高点,TIM_Period为一个周期的值,也就是ARR的值,ARR为666,要想获得一个占空比为百分之五十的波形,那么就要设置TIM_Pulse为333。
TIM_OCPolarity:设置输出比较极性,TIM_OCPolarity_High或者TIM_OCPolarity_Low两种,实际输出的电平是由输出极性和电平有效共同决定的,左图为极性和电平的有效来控制灯的点亮的不同情况,根据所有情况可以知道当高极性对应有效电平可以输出高电平,低极性和无效电平可以输出高电平,其余不是这样对应的都是低电平。
然后调用TIM_OCxInit()来初始化结构体,其中x为要使用的定时器的PWM第x个通道,然后调用TIM_OC2PreloadConfig(),该函数是设置使能寄存器TIM_CCMR1的OC2PE位,该位是开启/禁止TIMx_CCR1寄存器的预装载功能,TIMx_CCRx寄存器能够在任何时候通过软件进行更新以控制波形,这个通过软件写入控制波形的值是立即生效还是在定时器发生下一次更新事件时被更新的,就是由这个函数决定的,然后调用函数TIM_ARRPreloadConfig(),这个函数是允许或禁止在定时器工作时向ARR的缓冲器中写入新值,以便在更新事件发生时载入覆盖以前的值,最后用Cmd函数来使能定时器,主函数调用即可
在主函数中可以调用TIM_SetComparex()函数来设置CRR寄存器的值,CRR即高低电平分界线,若在这里设置为222,根据上面的配置,ARR为666,则占空比为三分之一,若设置为333,则占空比为二分之一。还可以通过TIM_SetAutoreload()来设置ARR的值,即重装载值,TIM_SetComparex()来设置CRR为500,用TIM_SetAutoreload()来设置ARR为1000,那么占空比就是二分之一
PWM输入捕获:首先开启定时器时钟和GPIO时钟,在输入捕获时不管哪个管脚都不需要开启复用时钟,然后初始化GPIO结构体,因为要输入捕获,所以要设置为浮空输入,然后初始化输入捕获的结构体TIM_ICInitTypeDef,
TIM_Channel:设置捕获通道
TIM_ICPolarity:设置上升沿捕获还是下降沿捕获
TIM_ICSelection:用来设置映射关系,设置为直连还是非直连,每个定时器有四个输入通道,IC1,IC2,IC3,IC4,其中IC1和IC2为一组,IC3与IC4一组,在PWM输入捕获中,只用通道一和通道二,即IC1和IC2,而IC3与IC4不用,如果使用通道一,将TI1FP1设置为触发点,那么IC1将捕获周期,IC2将捕获占空比,这就是直连,如果TI1FP2设置为触发点,IC2将捕获周期,IC1将捕获占空比,此为非直连。所以看直连非直连只需要看图中捕获周期的线是直的还是弯的,直的就是直连,弯的就是非直连。
TIM_ICPrescaler:设置输入捕获分频系数,不分频则每次触发信号都捕获,2分频即两次触发信号才捕获
TIM_ICFilter:设置滤波器长度,设置为0x0即可
然后用TIM_PWMIConfig函数来初始化结构体,再用TIM_SelectInputTrigger()来选择输入捕获的触发信号,即告诉定时器上升沿来了开始捕获,始终将触发点设置为捕获周期的那一个,再根据设置的直连非直连具体看是设置哪个,因为这个程序中设置通道二捕获,且为直连,所以TI2FP2将捕获周期,所以出发点设置为TIM_TS_TI2FP2,这个寄存器在捕获的时候会捕获到周期,一旦捕获到周期然后就触发,然后用TIM_SelectSlaveMode()来选定复位模式为从模式,再用TIM_SelectMasterSlaveMode()函数来设置使能主从模式,然后接下来Cmd和ITConfig来配置TIM定时器,注意在调用ITConfig的时候,要将第二个参数设置为TIM使用的捕获通道,使用的第几个通道就是CC几,在用定时器的功能的时候才使用Update,除此之外还要配置NVIC中断。
中断函数:不同于之前定时器,进入中断不再判断是否中断标志位是否置位,直接清除中断标志位,然后要先取出周期值,根据配置的直连非直连和必须先取出周期值的要求决定是用TIM_GetCapture1还是TIM_GetCapture2函数来取出周期值,剩下的那个就用来取出占空比,在这个程序中时通道二采取的是直连,并且TI2FP2存储周期,所以要用TIM_GetCapture2来取出周期,进行计算。
timer.c #include "timer.h" #include "stm32f10x.h" void TIM2_PWM_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; TIM_OCInitTypeDef TIM_OCInitStructure; uint16_t Prescaler=(uint16_t)(SystemCoreClock/24000000)-1; RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1; GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; GPIO_Init(GPIOA, &GPIO_InitStructure); TIM_TimeBaseInitStructure.TIM_Prescaler=Prescaler; TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; TIM_TimeBaseInitStructure.TIM_Period=665; TIM_TimeBaseInitStructure.TIM_ClockDivision=0; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse=333; TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High; TIM_OC2Init(TIM2,&TIM_OCInitStructure); TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable); TIM_ARRPreloadConfig(TIM2, ENABLE); TIM_Cmd(TIM2,ENABLE); } void TIM3_PWM_INPUT_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_ICInitTypeDef TIM_ICInitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0); NVIC_InitStructure.NVIC_IRQChannel=TIM3_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0; NVIC_InitStructure.NVIC_IRQChannelSubPriority=1; NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE; NVIC_Init(&NVIC_InitStructure); GPIO_InitStructure.GPIO_Pin=GPIO_Pin_6; GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); TIM_ICInitStructure.TIM_Channel=TIM_Channel_1; TIM_ICInitStructure.TIM_ICPolarity=TIM_ICPolarity_Rising; TIM_ICInitStructure.TIM_ICSelection=TIM_ICSelection_DirectTI; TIM_ICInitStructure.TIM_ICPrescaler=TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICFilter=0x0; TIM_PWMIConfig(TIM3, &TIM_ICInitStructure); TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1 );// TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset); TIM_SelectMasterSlaveMode(TIM3, TIM_MasterSlaveMode_Enable); TIM_ITConfig(TIM3, TIM_IT_CC1, ENABLE);// TIM_Cmd(TIM3, ENABLE); }
timer.h
#ifndef __TIMER_H
#define __TIMER_H
void TIM2_PWM_Config(void);
void TIM3_PWM_INPUT_Config(void);
#endif
main.c #include "stm32f10x.h" #include "lcd.h" #include "buzzer.h" #include "led.h" u32 TimingDelay = 0; void Delay_Ms(u32 nTime); //Main Body int main(void) { u16 index; SysTick_Config(SystemCoreClock/1000); Delay_Ms(200); TIM2_PWM_Config(); TIM3_PWM_INPUT_Config(); while(1) { } } __IO uint32_t IC2value=0; __IO uint32_t DutyCycle=0; __IO uint32_t Frequency=0; void TIM3_IRQHandler(void) { TIM_ClearITPendingBit(TIM3, TIM_IT_CC1);// IC2value=TIM_GetCapture1(TIM3); if(IC2value!=0) { DutyCycle=(TIM_GetCapture2(TIM3)*100)/IC2value; Frequency=SystemCoreClock/IC2value; } else { DutyCycle=0; Frequency=0; } } // void Delay_Ms(u32 nTime) { TimingDelay = nTime; while(TimingDelay != 0); }
对于输入捕获来说,直接用杜邦线把定时器2的2通道和定时器3的1通道连接然后读取相应值就可以知道周期与占空比。
对于输出模式来说,如果手边有示波器,直接把示波器一脚接IO口,一脚接地即可,如果手边没有示波器,那么可用keil自带的仿真查看波形
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。