打印
[信息]

STM32学习笔记

[复制链接]
1057|7
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
snake7031|  楼主 | 2015-8-9 10:34 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
Stm32学习笔记—寄存器版本
养成良好的编程能力很重要!!!否则还以后的生活和工作当中会吃很大的亏的!
一、在MDK中进行对stm32的学习中用寄存器进行学习只需要很简单的操作,比起用库函数进行学**降低了学习的难度。。。

更多的资源分享尽在:嵌入式分享群 148124126
1)首先将system文件夹中的delay、sys以及usart三个文件夹复制到工程文件夹中并添加进工程当中去,然后再把一个启动文件复制到工程当中去并添加即可。
二、LED跑马灯的学习:
1)编写LED初始化函数,这其中包括GPIOX时钟的使能,然后就是对应LED的端口的设置,其中包括输入输出模式以及输出的形式(是高电平还是低电平);
2)编写完成之后就是调用LED初始化函数进行试验。代码如下:
void LED_Init(void)
{
        RCC->APB2ENR|=1<<3;    //使能PORTB时钟                    
        RCC->APB2ENR|=1<<6;    //使能PORTE时钟       
                    
        GPIOB->CRL&=0XFF0FFFFF; //输出模式
        GPIOB->CRL|=0X00300000;//PB.5 推挽输出            
    GPIOB->ODR|=1<<5;      //PB.5 输出高
                                                                                          
        GPIOE->CRL&=0XFF0FFFFF;
        GPIOE->CRL|=0X00300000;//PE.5推挽输出
        GPIOE->ODR|=1<<5;      //PE.5输出高
}
3)stm32中的IO口初始化之后默认的形式是下拉的。下面是几种常见的输入形式,最好记住
三、用串口发送和接受数据
1)首先肯定是使能串口时钟和串口所对应的io口时钟,设置io口的输入输出模式,然后复位串口、停止复位,设置波特率和校检位。至此串口已经初始化完毕。串口的波特率是根据以下公式算的以下是初始化串口的函数
void uart_init(u32 pclk2,u32 bound)
{           
        float temp;
        u16 mantissa;
        u16 fraction;          
        temp=(float)(pclk2*1000000)/(bound*16);//得到USARTDIV
        mantissa=temp;                                 //得到整数部分
        fraction=(temp-mantissa)*16; //得到小数部分         
    mantissa<<=4;//将最后的一个16进制位移开再与小数位相加
        mantissa+=fraction;
        RCC->APB2ENR|=1<<2;   //使能PORTA口时钟  
        RCC->APB2ENR|=1<<14;  //使能串口时钟
        GPIOA->CRH&=0XFFFFF00F;//IO状态设置
        GPIOA->CRH|=0X000008B0;//IO状态设置
                  
        RCC->APB2RSTR|=1<<14;   //复位串口1
        RCC->APB2RSTR&=~(1<<14);//停止复位                     
        //波特率设置
        USART1->BRR=mantissa; // 波特率设置         
        USART1->CR1|=0X200C;  //1位起始,无校验位.
#if EN_USART1_RX                  //如果使能了接收
        //使能接收中断
        USART1->CR1|=1<<8;    //PE中断使能
        USART1->CR1|=1<<5;    //接收缓冲区非空中断使能                   
        MY_NVIC_Init(3,3,USART1_IRQn,2);//组2,最低优先级
#endif
}
四、外部中断事件
1)初始化IO口为输入。
2)开启IO口复用时钟,设置IO口与中断线的映射关系。APB2ENR寄存器
3)开启与该IO口相对的线上事件,设置触发事件的条件。EXTI->FTSR下降沿触发发、EXTI->RTSR 上升沿触
4)配置中断分组(NVIC)并使能中断。RCC_CIR寄存器
5)编写中断服务函数。
五、独立看门狗事件(注意,在初始化时钟或者是计数器的时候应该是最后才使能时钟或者是计数器)
1)在键寄存器(IWDG_KR)中写入0xCCCC,开始启用独立看门狗
2)IWDG_PR和IWDG_RLR寄存器具有写保护功能。要修改这两个寄存器的值,必须先向IWDG_KR寄存器中写入0x5555。以不同的值写入这个寄存器将会打乱操作顺序,寄存器将重新被保护。重装载操作(即写入0xAAAA)也会启动写保护功能。
void IWDG_Init(u8 prer,u16 rlr)
{
        IWDG->KR=0X5555;//使能对IWDG->PR和IWDG->RLR的写,因为KR寄存器有写保护功能                                                                                                   
          IWDG->PR=prer;  //设置分频系数   
          IWDG->RLR=rlr;  //从加载寄存器 IWDG->RLR  
        IWDG->KR=0XAAAA;//reload                                                                                          
          IWDG->KR=0XCCCC;//使能看门狗       
}
3)独立看门狗计数器一旦开始后就要不断地对其进行喂狗操作,否则当其被计数到零时就会引起系统复位。喂狗操作就是想KR寄存器中写入0xAAAA,使其自动进行重载操作。
void IWDG_Feed(void)
{
        IWDG->KR=0XAAAA;//reload                                                                                          
}
更多的资源分享尽在:嵌入式分享群 148124126
六、窗口看门狗事件
1)首先使能窗口看门狗时钟,在RCC->APB1ENR中的11位置1
2)控制寄存器的配置(WWDG->CR),这个寄存器只有低7位有效。第7位为看门狗使能位,[6:0]为看门狗计数器的值,当第6位为0时就会引起系统复位。

3)看门狗配置寄存器(WWWDG_CFR),这个寄存器为低10位有效其中[6:0]位为窗口值;值得注意的是只有当计数器的值小于窗口值时并且大于0x40时才能重新写入计数器的值,否则会引起系统复位。不管是提前还是延后都会引起系统复位。具体在stm32中文参考手册的321页
4)窗口看门狗的状态寄存器WWDG_SR只有一位,但是起作用却很重要。当该寄存器的0位置1时为提前唤醒中断标志,即当控制寄存器的中的计数器的值从0x40转变为0x3f时会引起中,可以利用这个中断来进行喂狗。以下是初始化窗口看门狗的初始化函数
//初始化窗口看门狗        
//tr   :T[6:0],计数器值
//wr   :W[6:0],窗口值
//fprer:分频系数(WDGTB),仅最低2位有效
//Fwwdg=PCLK1/(4096*2^fprer).
void WWDG_Init(u8 tr,u8 wr,u8 fprer)
{   
        RCC->APB1ENR|=1<<11;         //使能wwdg时钟
        WWDG_CNT=tr&WWDG_CNT;   //初始化WWDG_CNT.     
        WWDG->CFR|=fprer<<7;    //PCLK1/4096再除2^fprer
        WWDG->CFR&=0XFF80;      
        WWDG->CFR|=wr;                     //设定窗口值      
        WWDG->CR|=WWDG_CNT;         //设定计数器值
        WWDG->CR|=1<<7;                  //开启看门狗      
        MY_NVIC_Init(2,3,WWDG_IRQn,2);//抢占2,子优先级3,组2     
        WWDG->SR=0X00;                         //清除提前唤醒中断标志位
        WWDG->CFR|=1<<9;        //使能提前唤醒中断
}
中断服务函数为
void WWDG_IRQHandler(void)
{      
        WWDG_Set_Counter(WWDG_CNT);//重设窗口看门狗的值!         
        WWDG->SR=0X00;//清除提前唤醒中断标志位
        LED1=!LED1;
        BEEP=0;
}
七、通用计数器的使用
通用计数器的初始化步骤如下
1)使能计数器的时钟RCC->APB1ENRd的第1位
2)设置自动加载寄存器的值,即为计数值
3)设置预分频器的分频值
4)设置计数器的计数防方向,是向下还是向上计数,默认状态下是向上计数的
5)使能计数器
6)设置计数器的中断方式并编写中断服务函数(当计数器溢出时会响应一个中断,中断使能寄存器的0位会置1,但是前提是在DMA/中断使能寄存器中将更新事件的中断使能位置1)
//通用定时器3中断初始化
//这里时钟选择为APB1的2倍,而APB1为36M
//arr:自动重装值。
//psc:时钟预分频数
//这里使用的是定时器3!
void TIM3_Int_Init(u16 arr,u16 psc)
{
        RCC->APB1ENR|=1<<1;        //TIM3时钟使能   
        TIM3->ARR=arr;          //设定计数器自动重装值//刚好1ms   
        TIM3->PSC=psc;          //预分频器7200,得到10Khz的计数时钟                  
        TIM3->DIER|=1<<0;         //允许更新中断
        TIM3->CR1|= 1<<4;        //设置计数器是向下计数,默认状态下是向上计数的            
        TIM3->CR1|=0x01;    //使能定时器3
MY_NVIC_Init(1,3,TIM3_IRQn,2);//抢占1,子优先级3,组2                                                                         
}
//定时器3中断服务程序         
void TIM3_IRQHandler(void)
{                                                                   
        if(TIM3->SR&0X0001)//溢出中断,当一个更新事件到来的时候,状态寄存器的0位就会置位,否则为零
        {
                LED1=!LED1;                                                                                                                       
        }                                  
        TIM3->SR&=~(1<<0);//清除中断标志位 ,0为无更新事件发生            
}
八、用timer3进行PWM输出

1)首先是使能计数器3的时钟,并且要使能相应管脚的时钟
2)设置计数器复用相应管脚的输出模式,一般是设置为推挽输出
3)使能管脚复用时钟
4)设置计数器自动重载值和预分频器的值
5)设置PWM的输出模式
6)使能预装载寄存器
7)进行输出使能和计数器使能
//TIM3 PWM部分初始化
//PWM输出初始化
//arr:自动重装值
//psc:时钟预分频数
void TIM3_PWM_Init(u16 arr,u16 psc)
{                                                          
        //此部分需手动修改IO口设置
        RCC->APB1ENR|=1<<1;         //TIM3时钟使能   
        RCC->APB2ENR|=1<<3;            //使能PORTB时钟       
        GPIOB->CRL&=0XFF0FFFFF;        //PB5输出
        GPIOB->CRL|=0X00B00000;        //复用功能输出                    
          
        RCC->APB2ENR|=1<<0;     //开启辅助时钟          
        AFIO->MAPR&=0XFFFFF3FF; //清除MAPR的[11:10]
        AFIO->MAPR|=1<<11;      //部分重映像,TIM3_CH2->PB5

        TIM3->ARR=arr;                        //设定计数器自动重装值
        TIM3->PSC=psc;                        //预分频器不分频
       
        TIM3->CCMR1|=7<<12;          //CH2 PWM2模式                 
        TIM3->CCMR1|=1<<11;         //CH2预装载使能          
        TIM3->CCER|=1<<4;           //OC2 输出使能          
        TIM3->CR1=0x0080;           //ARPE使能
        TIM3->CR1|=0x01;            //使能定时器3                                                                                           
}
九、输入捕获实验 (以TIMER5为例)
1)开启TIM5时钟,配置PA0为下拉输入
2)设置TIM5的ARR和PSC

3)设置TIM5的CCMR1
4)设置TIM5的CCER,开启输入捕获,并设置为上升沿捕获

5)设置TIM5的DIER,使能捕获和更新中断,并编写中断服务函数

6)设置TIM5的CR1,使能定时器

//定时器5通道1输入捕获配置
//arr:自动重装值
//psc:时钟预分频数
void TIM5_Cap_Init(u16 arr,u16 psc)
{                 
        RCC->APB1ENR|=1<<3;           //TIM5 时钟使能
        RCC->APB2ENR|=1<<2;            //使能PORTA时钟  
         
        GPIOA->CRL&=0XFFFFFFF0;        //PA0 清除之前设置  
        GPIOA->CRL|=0X00000008;        //PA0 输入   
        GPIOA->ODR|=0<<0;                //PA0 下拉
          
        TIM5->ARR=arr;                  //设定计数器自动重装值   
        TIM5->PSC=psc;                  //预分频器

        TIM5->CCMR1|=1<<0;                //CC1S=01         选择输入端 IC1映射到TI1上
        TIM5->CCMR1|=0<<4;                 //IC1F=0000 配置输入滤波器 不滤波
        TIM5->CCMR1|=0<<10;         //IC2PS=00         配置输入分频,不分频

        TIM5->CCER|=0<<1;                 //CC1P=0        上升沿捕获
        TIM5->CCER|=1<<0;                 //CC1E=1         允许捕获计数器的值到捕获寄存器中

        TIM5->DIER|=1<<1;           //允许捕获中断                               
        TIM5->DIER|=1<<0;           //允许更新中断       
        TIM5->CR1|=0x01;            //使能定时器2
        MY_NVIC_Init(2,0,TIM5_IRQn,2);//抢占2,子优先级0,组2          
}

//捕获状态
//[7]:0,没有成功的捕获;1,成功捕获到一次.
//[6]:0,还没捕获到高电平;1,已经捕获到高电平了.
//[5:0]:捕获高电平后溢出的次数
u8  TIM5CH1_CAPTURE_STA=0;        //输入捕获状态,这里的TIM5CH1_CAPTURE_STA当做一个                                                        //寄存器来用                                                   
u16        TIM5CH1_CAPTURE_VAL;        //输入捕获值
//定时器5中断服务程序         
void TIM5_IRQHandler(void)
{                     
        u16 tsr;
        tsr=TIM5->SR;
        if((TIM5CH1_CAPTURE_STA&0X80)==0)//还未成功捕获        ,第7位为捕获完成标志位
        {
                if(tsr&0X01)//溢出
                {            
                        if(TIM5CH1_CAPTURE_STA&0X40)//已经捕获到高电平了
                        {
                                if((TIM5CH1_CAPTURE_STA&0X3F)==0X3F)//高电平太长了,捕获标志位全部为1
                                {
                                        TIM5CH1_CAPTURE_STA|=0X80;//标记成功捕获了一次
                                        TIM5CH1_CAPTURE_VAL=0XFFFF;
                                }else TIM5CH1_CAPTURE_STA++;
                        }         
                }
                if(tsr&0x02)//捕获1发生捕获事件
                {       
                        if(TIM5CH1_CAPTURE_STA&0X40)                //捕获到一个下降沿,由于前面已经有了高电平的标志位了                
                        {                                 
                                TIM5CH1_CAPTURE_STA|=0X80;                //标记成功捕获到一次高电平脉宽
                            TIM5CH1_CAPTURE_VAL=TIM5->CCR1;        //获取当前的捕获值.
                                 TIM5->CCER&=~(1<<1);                        //CC1P=0 设置为上升沿捕获
                        }else                                                                  //还未开始,第一次捕获上升沿
                        {
                                TIM5CH1_CAPTURE_STA=0;                        //清空
                                TIM5CH1_CAPTURE_VAL=0;
                                TIM5CH1_CAPTURE_STA|=0X40;                //标记捕获到了上升沿
                                 TIM5->CNT=0;                                        //计数器清空
                                 TIM5->CCER|=1<<1;                                 //CC1P=1 设置为下降沿捕获
                        }                    
                }                                                                                   
        }
        TIM5->SR=0;//清除中断标志位             
}
更多的资源分享尽在:嵌入式分享群 148124126
十、电容触摸按键实验
1)电容触摸按键的原理是根据电容值不同充放电的时间不同,用定时器对电容的充电时间的计时比较,就可以判断是否有被触摸
Vc=V0*(1-e^(-t/RC))
其中Vc为电容电压,V0为充电电压,R为充电电阻,C为电容容值,e为自然底数,t为充电时间。根据这个公式,我们就可以计算出Cs和Cx。先用开关将Cs(或Cs+Cx)上的电放尽,然后断开开关,让R给Cs(或Cs+Cx)充电,当没有手指触摸的时候,Cs的充电曲线如图中的A曲线。而当有手指触摸的时候,手指和TPAD之间引入了新的电容Cx,此时Cs+Cx的充电曲线如图中的B曲线。从上图可以看出,A、B两种情况下,Vc达到Vth的时间分别为Tcs和Tcs+Tcx。,就已经可以实现触摸检测了,当充电时间在Tcs附近,就可以认为没有触摸,而当充电时间大于Tcs+Tx时,就认为有触摸按下(Tx为检测阀值)。
2)、实验方法如下:本章,我们使用PA1(TIM5_CH2)来检测TPAD是否有触摸,在每次检测之前,我们先配置PA1为推挽输出,将电容Cs(或Cs+Cx)放电,然后配置PA1为浮空输入,利用外部上拉电阻给电容Cs(Cs+Cx)充电,同时开启TIM5_CH2的输入捕获,检测上升沿,当检测到上升沿的时候,就认为电容充电完成了,完成一次捕获检测。
3)、实验原理图如下
//定时器5通道2输入捕获配置         
//arr:自动重装值
//psc:时钟预分频数
void TIM5_CH2_Cap_Init(u16 arr,u16 psc)
{
        //此部分需手动修改 IO口设置  
        RCC->APB1ENR|=1<<3;           //TIM5 时钟使能
        RCC->APB2ENR|=1<<2;            //使能PORTA时钟

        GPIOA->CRL&=0XFFFFFF0F;        //PA1  输入  
        GPIOA->CRL|=0X00000040;        //浮空输入               
          
        TIM5->ARR=arr;                  //设定计数器自动重装值//刚好1ms   
        TIM5->PSC=psc;                  //预分频器,1M的计数频率         

        TIM5->CCMR1|=1<<8;                //CC2S=01         选择输入端 IC2映射到TI2上
        TIM5->CCMR1|=0<<12;         //IC2F=0011 配置输入滤波器 8个定时器时钟周期滤波
        TIM5->CCMR1|=0<<10;         //IC2PS=00         配置输入分频,不分频

        TIM5->CCER|=0<<5;                 //CC2P=0        上升沿捕获
        TIM5->CCER|=1<<4;                 //CC2E=1         允许捕获计数器的值到捕获寄存器中
                                                                                                                                 
        TIM5->CR1|=0x01;            //使能定时器5
}
更多的资源分享尽在:嵌入式分享群 148124126
       
沙发
309030106| | 2015-8-9 11:31 | 只看该作者
控制寄存器的配置(WWDG->CR),这个寄存器只有低7位有效
感谢分享,有些东西再看一遍还是会有收获的

使用特权

评论回复
板凳
松哥无敌| | 2015-8-9 12:00 | 只看该作者
全是寄存器哦.....

使用特权

评论回复
地板
mmuuss586| | 2015-8-9 12:47 | 只看该作者

还分享群啊;

使用特权

评论回复
5
还是库函数好啊...10天就可以玩会很多外设啊,中断也是一个模式...**量小太多啊,寄存器太头疼

使用特权

评论回复
6
小可可儿| | 2015-8-9 22:18 | 只看该作者
操作函数的流程与直接操作寄存器的流程是一样的,这个对操作函数也有借鉴意义

使用特权

评论回复
7
ticomi| | 2015-8-10 08:25 | 只看该作者
使用库函数比较简单,使用寄存器效率高,我是两者结合使用,依据情况确定!

使用特权

评论回复
8
sleaven| | 2015-8-10 08:26 | 只看该作者
我觉得还是库函数方便些。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

34

主题

42

帖子

3

粉丝