STM32----杂记

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);  //只在此处开启时钟不能成功初始化外部中断

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
	}

}
定时器中使用到的变量以及功能函数,若是主循环中也使用,应谨防此类错误



猜你喜欢

转载自blog.csdn.net/a3748622/article/details/79425747