1、开启引脚复用,引脚外部中断时,需要开启 RCC_APB2Periph_AFIO。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //只在此处开启时钟可顺利初始化外部中断
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource12); //不配置无法启用外部中断
EXTI_InitStruct.EXTI_Line = EXTI_Line12;
EXTI_InitStruct.EXTI_LineCmd = ENABLE;
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_Init(&EXTI_InitStruct);
//RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //只在此处开启时钟不能成功初始化外部中断
对于F2、F4系列,则需要打开
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
“系统配置控制器主要用于管理对可执行代码的存储区的地址重映射。选择以太网PHY接口以及管理GPIO的外部中断连接。”
2、按键处理
按键处理函数TEST_SW(); 每隔50MS在中断处理函数TIM6_IRQHandler();中执行一次。
优点:去抖动检测,不会浪费处理器时间。可用在OS下的按键检测任务。
缺点:非OS下需要定时器定时检测按键。
逻辑:
(1)只要按键松开,SW_Key = 0;
(2)第一次检测到按键按下:SW_Key = 0x80;
第二次检测:SW_Key = 0x01;
(3)通过判断“SW_Key ==SW_ON” 来判断按键是否按下
#define SW_ON 1
#define SW_OFF 0
u8 SW_Key = 0;
void TEST_SW(void)
{
if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_5)==0)
{
SW_Key = SW_Key>0 ? 0x01:0x80;
}
else
{
SW_Key = 0;
}
}
void TIM6_IRQHandler(void)
{
TEST_SW();
TIM_ClearITPendingBit(TIM6, TIM_IT_Update);
}
延伸一下,处理多个按键:
//头文件
//按键的数量
#define KEY_Num 2
//按键名称
#define KEY_0 0
#define KEY_1 1
//按键有效电平的宏定义
#define Active_High 1
#define Active_Low 0
//按键检测的宏定义
#define KeyCheck(GPIO,PIN,FBit,Lev) (FBit = ( GPIO->IDR & PIN ) ^ ( Lev ? PIN : 0 ) ? 0 : FBit&0x81 ? 1 : 0x80)
//检测按键是否单次触发
//检测完毕后,需再次触发才能检测第二次
#define KeySingle(FLAG) (KEY_S.KEY[FLAG]++ == 0x80)
//检测按键长按
#define KeyON(FLAG) (KEY_S.KEY[FLAG] == 0x01)
//检测按键是否松开
#define KeyOFF(FLAG) (KEY_S.KEY[FLAG] == 0)
//按键管理结构体
typedef struct
{
uint8_t KEY[KEY_Num];
}KEY_DEV;
extern KEY_DEV KEY_S;
void ScanKey(void);
//源文件
#include "bsp_key.h"
KEY_DEV KEY_S;
void ScanKey(void)
{
KeyCheck(GPIOA,GPIO_PIN_4, KEY_S.KEY[KEY_0],Active_Low); //PE2为输出引脚,低电平有效
KeyCheck(GPIOA,GPIO_PIN_5, KEY_S.KEY[KEY_1],Active_Low); //PE2为输出引脚,低电平有效
}
//中断函数
//放进中断程序中,50MS执行一次
void TIM3_IRQHandler(void)
{
ScanKey(); //按键检测
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
}
//主函数
#include "bsp_key.h"
int main(void)
{
int ia = 0;
GPIO_Init();
TIM_Init();
while(1)
{
if(KeySingle(KEY_0))
{
ia++;
}
if(KeySingle(KEY_1))
{
ia--;
}
printf("ia = %d, ",ia);
Delay_MS(1000);
}
}
使用的时候,先定义好按键宏定义“KEY_Num”,再定义按键名称宏定义,在“ScanKey();”里面添加对应的按键检测宏定义语句即可。之所以用结构体数组成员而不是直接用数组管理按键标志位,是为了以后添加别的功能的时候好管理。
3、定时器捕获的溢出处理
以下是F103捕获PWM频率的溢出处理,TIM分频系数:71,计数器装载值:9999 最低可
捕获1Hz的PWM,全局变量TIM3_FRE保存捕获到的频率值。
u16 TIM3_IC3 = 0,TIM3_Over = 0;
u32 TIM3_FRE=0;
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3,TIM_IT_Update) == 1 )
{
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
TIM3_Over++;
if( (TIM3_Over&0x7f)>99){TIM3_Over=0; TIM3_FRE = 0;} //循环100次没抓到信号则重新捕获
}
else
{
if( (TIM3_Over&0x80) ==0) //第一次捕获到信号
{
TIM3->CNT = 0;
TIM3_Over = 0x80;
}
else //第二次捕获到信号
{
TIM3_IC3 = TIM_GetCapture3(TIM3);
if( (TIM3_Over&0x7f)>0) //超过一个周期捕获到信号
{
TIM3_FRE = 1000000/(TIM3_IC3+(TIM3_Over&0x7f)*10000);
}
else //同周期捕获到信号
{
TIM3_FRE =1000000/TIM3_IC3;
}
TIM3_Over = 0;
}
TIM_ClearITPendingBit(TIM3, TIM_IT_CC3);
}
}
4、移植F1程序到F2
移植程序到不同系列的MCU其实是挺麻烦的事的。要注意几点:
1、系统时钟频率,包括各AHB总线、APB总线的时钟不同。
2、初始化结构体不同,例如GPIO初始化,F2系列需要设置输出类型、上下拉、模式、引脚号、速度等
几个结构体成员,而F1只需要设置模式、速度、引脚号就能进行初始化。而且,在设置片上外设引脚时,
F2还需要设置引脚复用功能。设置方法类似F4系列。
3、外设结构不同。例如DMA。F1系列是不带FIFO的,但是F2是带的。在使用的时候需注意设置对应
初始化结构体成员。
4、中断函数不同。这点特容易出现难以察觉的错误。例如TIM6,它的中断函数名称在F2是:
void TIM6_DAC_IRQHandler(void)
以及F1中的:
void TIM6_IRQHandler(void)
而且,F1中ADC中断分为ADC1_2以及ADC3两个,但在F2同一为一个ADC中断。
然后是默认初始化的总线分频系数,F1为:
F2是:
如果没留意到这点,在计算外设时钟的时候(ADC或者TIM)就会出现计算错误。
5、STM32 F2标准库函数扇区擦除
当每次仿真运行扇区擦除函数
FLASH_Status FLASH_EraseSector(uint32_t FLASH_Sector, uint8_t VoltageRange);
都会出现错误跳出仿真。当复位MCU时,发现MCU可以正常运行,但是指定的扇区并不能擦除。
仿真跟踪,发现问题出现在如下地方:
/* if the previous operation is completed, proceed to erase the sector */
FLASH->CR &= CR_PSIZE_MASK;
FLASH->CR |= tmp_psize;
FLASH->CR &= SECTOR_MASK;
FLASH->CR |= FLASH_CR_SER | FLASH_Sector;
FLASH->CR |= FLASH_CR_STRT;
/* Wait for last operation to be completed */
status = FLASH_WaitForLastOperation(); //每次运行到该处,仿真弹出
debug下查看寄存器:
CR寄存器的SNB以及PSIZE被清除了,并且PGAERR以及PGPERR这两个标志位置1。
在STM32 的官方文档中对此2标志位描述如下:
可知当PSIZE或者SNB设置错误时,这两个对应的标志位就会置1。
事实上SNB是没有设置错误的,试过设置所有不同的PSIZE还是出现如上错误。
故推测:执行了 FLASH->CR |= FLASH_CR_STRT; 之后
寄存器的SNB以及PSIZE被清零,导致出现以上两个标志位置1。
从手册上并未找出寄存器方面解决的办法,但可以手动添加延时,解决这个问题。
/* Wait for last operation to be completed */
delay_ms(2000);
status = FLASH_WaitForLastOperation();
在添加了2000MS的延时后,仿真不会出错了,扇区也能正常擦除了。
寄存器解决的办法,等研究了STM32CubeMx以及熟悉Hal库后再解决。
6、SPI快速读取FLASH数据
这个方法主要应用在快速读取FLASH数据,然后将数据传输至LCD进行显示。
我使用的FLASH为华邦的W25Q系列,LCD控制器为SSD1963.
u32 i=0, len;
LCD_WR_REG(0x002A);
LCD_WR_DATA((StartX)>>8);
LCD_WR_DATA((StartX)&0x00ff);
LCD_WR_DATA((StartX+width-1)>>8);
LCD_WR_DATA((StartX+width-1)&0x00ff);
LCD_WR_REG(0x002b);
LCD_WR_DATA((StartY)>>8);
LCD_WR_DATA((StartY)&0x00ff);
LCD_WR_DATA((StartY+heigth-1)>>8);
LCD_WR_DATA((StartY+heigth-1)&0x00ff);
LCD_WR_REG(0x002c);
FLASH_L //使能器件
SPI_FLASH_SendByte(0x0B); //发送读取命令
SPI_FLASH_SendByte((u8)((addr)>>16)); //发送24bit地址
SPI_FLASH_SendByte((u8)((addr)>>8));
SPI_FLASH_SendByte((u8)addr);
SPI_FLASH_SendByte(0xff);
SPI1->CR1 |= 0x0800; //更改SPI传输数据长度为16bit
SPI1->CR1 |= SPI_CR1_SPE; //使能SPI外设
SPI1->DR = 0xffff; //启动传输
for(i=0;i<SPIReadWaitTime;i++);
len = width*heigth;
while(len--)
{
SPI1->DR = 0xffff;
for(i=0;i<SPIReadWaitTime;i++);
*(u16 *)0x60020000 = SPI1->DR; //数据发送至LCD
}
SPI1->CR1 &= 0xf7ff; //更改SPI传输数据长度为8bit
FLASH_H
“SPIReadWaitTime”为延迟时间宏定义,根据自己MCU速度进行调整。太快将导致数据读取异常。
主要原理是将SPI数据传输宽度改为16bit,一是为了减少MCU对2个8bit数据进行整合的操作(16bit的LCD)。二是SPI以16bit数据位宽与8bit数据位宽对比,读取同等大小的数据,16bit更快。
实测8bit下,刷屏速度为14帧/每秒,改为16bit后约为20帧/每秒。
7、定时器中断导致的逻辑错误
int t4 = 0;
void TIM4_IRQHandler(void)
{
t4 = 1;
TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
}
int main(void)
{
TIM_Cmd(TIM4, ENABLE); //TIM4 启动后100us进入更新中断
t4 = 0;
Delay_MS(1);
if (t4 == 0) LCD_DIS(LOGO); //此处的LCD_DIS(LOGO);不会被执行,因为定时器中断将t4置为:1
while (1)
{
//code
}
}
定时器中使用到的变量以及功能函数,若是主循环中也使用,应谨防此类错误
8、SPI标准库函数问题
硬件环境是STM32F205VCT6,F2使用标准库。
对SPI FLASH进行读写,WQX256。SPI分频系数为2
u8 SPI1_ReadWriteByte(u8 TxData)
{
u8 retry=0;
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET)
{
retry++;
if(retry>200)return 0;
}
SPI1->DR = TxData;
retry=0;
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET) //这一步,跳出while后,仿真检测到retry为0
{
retry++;
if(retry>200)return 0;
}
return SPI1->DR; //返回通过SPIx最近接收的数据
}
如上SPI读写函数,在进行SPI DUMMY读写的时候,仿真中检测到“retry = 0;”,即SPI库中的标志位检测函数没有检测到正确的
标志位。但后续SPI读写,数据确是正确的。
这就导致了读写前2个字节会出现错误的数据。解决方法是添加DUMMY的功能:
void SPI_RW_DUMMY(void)
{
u8 i;
SPI1->DR = 0xff;
for(i=0;i<50;i++); //这个延时时间可自行定义
i = SPI1->DR;
}