OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
楼主: it_do_just

初学者即将毕业实习,希望利用最后两个月的学习顺利找到工作,占个地方做些笔记O(∩_∩)O哈哈~

    [复制链接]

72

主题

2711

帖子

2

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3505
金钱
3505
注册时间
2014-8-4
在线时间
696 小时
发表于 2015-11-11 17:13:14 | 显示全部楼层
以我资质之鲁钝,当尽平心静气、循序渐进、稳扎稳打之力。
正点原子逻辑分析仪DL16劲爆上市
回复 支持 4 反对 0

使用道具 举报

头像被屏蔽

52

主题

440

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
1237
金钱
1237
注册时间
2013-1-9
在线时间
151 小时
发表于 2015-11-12 00:40:29 | 显示全部楼层
搞电子态度很重要,学历真是浮云.
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-12 07:32:11 | 显示全部楼层
回复【51楼】龙之谷:
---------------------------------
谢谢
回复【52楼】hexboot:
---------------------------------
恩,赞同
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-12 08:33:37 | 显示全部楼层
本帖最后由 1208 于 2019-11-23 16:28 编辑

<p class="MsoNormal">
        <strong><span style="color:#E53333;font-size:16px;">实战:定时器中断</span></strong>
</p>
<p class="MsoNormal" align="left">
        <span><span style="font-size:16px;color:#E53333;"><strong>这章自己写的代码较多!</strong></span><br>
<span style="font-size:16px;"></span></span><span style="font-size:16px;"></span><br>
<span style="font-size:16px;"><span style="line-height:24px;color:#E53333;font-size:16px;">简介:</span></span>
</p>
<p class="MsoNormal" align="left">
        <span style="font-size:16px;">STM32 的定时器功能十分强大,有&nbsp;TIME1&nbsp;和&nbsp;TIME8&nbsp;等高级定时器,也有&nbsp;TIME2~TIME5&nbsp;等通用定时器,还有&nbsp;TIME6&nbsp;和TIME7&nbsp;等基本定时器。</span>
</p>
<p class="MsoNormal" align="left">
        <span style="font-size:16px;">通用定时器是一个通过可编程预分频器驱动的16位自动装载计数器构成。</span><br>
<span style="font-size:16px;"> 它适用于多种场合,包括测量输入信号的脉冲长度(输入捕获)或者产生输出波形(输出比较和PWM)。使用定时器预分频器和RCC时钟控制器预分频器,脉冲长度和波形周期可以在几个微秒到几个毫秒间调整。每个定时器都是完全独立的,没有互相共享任何资源。它们可以一起同步操作。</span>
</p>
<p class="MsoNormal" align="left">
        <span style="font-size:16px;">&nbsp;</span>
</p>
<p class="MsoNormal" align="left">
        <span style="font-size:16px;color:#E53333;">定时器的时钟来源有4个:</span><br>
<span style="font-size:16px;"> 1) 内部时钟(CK_INT)</span><br>
<span style="font-size:16px;"> 2) 外部时钟模式1:外部输入脚(TIx)</span><br>
<span style="font-size:16px;"> 3) 外部时钟模式2:外部触发输入(ETR)</span><br>
<span style="font-size:16px;"> 4) 内部触发输入(ITRx):使用A定时器作为B定时器的预分频器(A为B提供时钟)。</span>
</p>
<p class="MsoNormal" align="left">
        <span style="font-size:16px;">&nbsp;</span>
</p>
<p class="MsoNormal" align="left">
        <span style="font-size:16px;color:#E53333;">几个配置寄存器:</span>
</p>
<p class="MsoNormal" align="left">
        <span style="font-size:16px;">TIMx_CR1:这章只用到了 TIMx_CR1 的最低位(位 0),也就是计数器使能位</span>
</p>
<p class="MsoNormal" align="left">
        <span style="font-size:16px;">TIMx_DIER:DMA/中断使能寄存器,同样仅关心它的最低位,该位是更新中断允许位</span>
</p>
<p class="MsoNormal" align="left">
        <span style="font-size:16px;">TIMx_PSC(位15:0):预分频寄存器</span>
</p>
<p class="MsoNormal" align="left">
        <span style="font-size:16px;">TIMx_ARR:自动重装载寄存器</span>
</p>
<p class="MsoNormal" align="left">
        <span style="font-size:16px;">TIMx_SR:状态寄存器,只用到了最低位,当计数器 CNT 被重新初始化的时候,产生</span><br>
<span style="font-size:16px;"> 更新中断标记,通过这个中断标志位,就可以知道产生中断的类型。</span>
</p>
<p class="MsoNormal" align="left">
        <span style="font-size:16px;">&nbsp;</span>
</p>
<p class="MsoNormal" align="left">
        <span style="font-size:16px;color:#E53333;">配置步奏:</span>
</p>
<p class="MsoNormal" align="left">
        <span style="font-size:16px;">1)TIMx&nbsp;时钟使能。</span>
</p>
<p class="MsoNormal" align="left">
        <span style="font-size:16px;">2) 设置 &nbsp;和&nbsp;TIMx_PSC&nbsp;的值。</span>
</p>
<p class="MsoNormal" align="left">
        <span style="font-size:16px;">3)设置&nbsp;TIMx_DIER&nbsp;允许更新中断。</span>
</p>
<p class="MsoNormal" align="left">
        <span style="font-size:16px;">4)允许&nbsp;TIMx&nbsp;工作。</span>
</p>
<p class="MsoNormal" align="left">
        <span style="font-size:16px;">5)TIMx&nbsp;中断分组设置。</span>
</p>
<p class="MsoNormal" align="left">
        <span style="font-size:16px;">6)编写中断服务函数。</span><br>
<br>
<span style="color:#337FE5;font-size:16px;">高级定时器需要增加的配置:</span><br>
<span style="font-size:16px;"> TIM_CtrlPWMOutputs(TIM8,ENABLE);//高级定时器需要定义的</span><br>
<span style="color:#337FE5;"></span><span style="color:#337FE5;font-size:16px;">ETR</span><span><span style="color:#337FE5;font-size:16px;">需要增加的配置:</span><br>
<span style="font-size:16px;"> TIM_ETRClockMode2Config(TIM3, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0);</span><br>
<span style="color:#337FE5;font-size:16px;">MCO需要增加的配置:</span><span style="color:#337FE5;"></span><br>
<span style="font-size:16px;"> RCC_MCOConfig(RCC_MCO_HSE);</span></span><br>
<br>
<span style="color:#E53333;font-size:16px;">本章遇到的问题:</span><br>
<span style="font-size:16px;">用TIM3来定时的时候发现在main函数一开始执行就先进入了一次定时器中断,导致自己很久都没找到问题,似乎是硬伤。以前也一直体会不到软件仿真和实际硬件上跑的区别,今天一试,发现软件模拟示波器的出来的波形和实际在板子上跑的并不一样。</span><br>
<br>
<span style="color:#E53333;"></span><span style="font-size:16px;line-height:24px;color:#E53333;">接下来讲解下自己写的代码:</span><br>
<span style="font-size:16px;">由于之前窥探了下官方库函数的编程方式,今天自己也模仿他们写了一下自己的库函数,加上之前原子哥写的时钟摘取法自己深有体会,自己也用结构体封装了一个,以定时器1ms为基准写了个delay函数,跟时钟摘取法的原理差不多,在delay期间可以去执行其他的任何函数,对按键延时消抖也挺有帮助的。废话不多说了,上代码</span><br>
<span style="font-size:16px;"> 首先是自己封装的定时器初始化函数的库,主要针对通用定时器写的,如果要用高级定时器和基本定时器还需要修改才能使用</span>
</p>
<div style="background-color:#E8E8E8;">
[mw_shl_code=c,true]timer.h

//首先初始化需要控制哪些寄存器:TIMx_CR1,TIMx_ARR,TIMx_PSC,TIMx_DIER
//根据需要设置的寄存器建立一个结构体(由于寄存器都是16bit,所以类型为u16)
typedef struct{
        uint16_t TIM_Prescaler;   //TIMx_PSC
        uint16_t TIM_CounterMode; //TIMx_CR1 向上计数还是向下计数
        uint16_t TIM_Period;      //TIMx_ARR
}TIM_InitTypeDef;

//宏定义模式值(使用断言机制)
#define TIM_CounterMode_Up        ((uint16_t)0x0000) //向上计数
#define TIM_CounterMode_Down      ((uint16_t)0x0010) //向下计数
#define IS_TIM_COUNTER_MODE(MODE) (((MODE) == TIM_CounterMode_Up) || ((MODE) == TIM_CounterMode_Down))

//宏定义通用定时器的值
#define IS_TIM_GENERAL_PERIPH(PERIPH) (((PERIPH) == TIM2) || \
                                       ((PERIPH) == TIM3) || \
                                       ((PERIPH) == TIM4) || \
                                       ((PERIPH) == TIM5))
//本章只考虑更新中断
#define TIM_IT_Update                  ((uint16_t)0x0001)

//定时器使能位
#define TIM_EN                         ((uint16_t)0x0001)

#ifdef  USE_FULL_ASSERT//支持断言机制
  #define assert_param(expr) ((expr) ? (void)0 : assert_failed((uint8_t *)__FILE__, __LINE__))
  void assert_failed(uint8_t* file, uint32_t line);
#else
  #define assert_param(expr) ((void)0)
#endif


timer.c

/**************************定时器初始化***********************************
* 函数名: TIM_Init
* 输  入: TIMx-选择第几个通用定时器,
*         TIM_TimeBaseInitStruct-模式,定时相关结构体设置
*         IT-通用定时器中断使能
*         TIM_Cmd-通用定时器使能
* 备  注: 这个函数只适用于通用定时器(TIM2~5)
**************************************************************************/
void TIM_Init(TIM_TypeDef* TIMx, TIM_InitTypeDef* TIM_TimeBaseInitStruct, FunctionalState IT, FunctionalState TIM_Cmd)
{
        uint16_t Tim_CR1 = 0;
      
        assert_param(IS_TIM_GENERAL_PERIPH(TIMx));
        assert_param(IS_TIM_COUNTER_MODE(TIM_TimeBaseInitStruct-&gt;TIM_CounterMode));
        assert_param(IS_FUNCTIONAL_STATE(NewState));
      
        Tim_CR1 = TIMx-&gt;CR1;  //读取寄存器值(控制寄存器)
      
        Tim_CR1 &amp;= (uint16_t)(~(uint16_t)TIM_CR1_DIR); //不改变已经设置的值,只重设方向,DIR为方向,这里不考虑对齐方式,本章用不到
        Tim_CR1 |= (uint32_t)TIM_TimeBaseInitStruct-&gt;TIM_CounterMode;//设置向上还是向下计数
      
        TIMx-&gt;CR1 = Tim_CR1;  //把所有的设置赋给CR1
      
        TIMx-&gt;ARR = TIM_TimeBaseInitStruct-&gt;TIM_Period ;//重载值
        TIMx-&gtSC = TIM_TimeBaseInitStruct-&gt;TIM_Prescaler; //时钟预分频数
      
        if (IT != DISABLE)//如果使能了更新中断
&nbsp; &nbsp; &nbsp;&nbsp;&nbsp;&nbsp;{
              TIMx-&gt;DIER |= TIM_IT_Update; &nbsp; &nbsp; &nbsp;//TIMx_DIER的最低位为更新中断使能位
&nbsp; &nbsp;     }
[/mw_shl_code]
[mw_shl_code=c,true]        else
        {
              TIMx-&gt;DIER &amp;= (uint16_t)~TIM_IT_Update;
        }
        if(TIM_Cmd != DISABLE)
        {
              TIMx-&gt;CR1 |= TIM_EN;  //TIMx_CR1的最低位为定时器使能位
        }
        else
        {
              TIMx-&gt;CR1 &amp;= (uint16_t)~TIM_EN;
        }
}

void TIM3_Int_Init(u16 arr,u16 psc)
{
        TIM_InitTypeDef TIM_TimeBaseInitStruct;
      
        RCC-&gt;APB1ENR|=1&lt;&lt;1;    //TIM3时钟使能
      
        TIM_TimeBaseInitStruct.TIM_Prescaler = psc;
        TIM_TimeBaseInitStruct.TIM_Period = arr;
        TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
        TIM_Init(TIM3, &amp;TIM_TimeBaseInitStruct, ENABLE, ENABLE);
      
        MY_NVIC_Init(1,3,TIM3_IRQn,2);//抢占1,子优先级3,组2                                                            
}

void TIM2_Int_Init(u16 arr,u16 psc)
{
        TIM_InitTypeDef TIM_TimeBaseInitStruct;
      
        RCC-&gt;APB1ENR|=1&lt;&lt;0;    //TIM2时钟使能
      
        TIM_TimeBaseInitStruct.TIM_Prescaler = psc;
        TIM_TimeBaseInitStruct.TIM_Period = arr;
        TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
        TIM_Init(TIM2, &amp;TIM_TimeBaseInitStruct, ENABLE, ENABLE);
      
        MY_NVIC_Init(0,3,TIM2_IRQn,2);//抢占1,子优先级2,组2                                                            
}
[/mw_shl_code]
</div>
<span style="font-size:16px;color:#E53333;">接下来是自己写delay函数,跟原子哥的时钟摘取法原理差不多</span><br>
<div style="background-color:#E8E8E8;">
[mw_shl_code=c,true].h文件

typedef struct{
        u32 Time_storage; //时间存放
        bool mark;        //到达指定延时的标志
}_Delay;

void my_delay_ms(u16 ms, _Delay* Mark);

.c文件

u32 _sys_time=0;           //每毫秒加1,延时基准

//定时器2中断服务程序        (1ms中断一次)
void TIM2_IRQHandler(void)
{                
        if(TIM2-&gt;SR&amp;0X0001)//溢出中断
        {
                _sys_time++;
        }
        TIM2-&gt;SR&amp;=~(1&lt;&lt;0);//清除中断标志位             
}

void my_delay_ms(u16 ms, _Delay* Mark)
{
        if(Mark-&gt;mark != 0){    //新的延时任务
                Mark-&gt;Time_storage = _sys_time;
                Mark-&gt;mark = 0;     //延时标志清零
        }
        else
        {
                if(Mark-&gt;Time_storage &lt; _sys_time)
                {
                        if(_sys_time - Mark-&gt;Time_storage &gt;= ms)//延时到了
                        {
                                Mark-&gt;mark = 1;   //置位标志位
                        }
                }
                else if(Mark-&gt;Time_storage &gt; _sys_time)  //考虑到如果_sys_time计时超过了32位重新置0开始往上计数
                {
                        if(_sys_time + (0xffffffff - Mark-&gt;Time_storage) &gt;= ms)//延时到了
                        {
                                Mark-&gt;mark = 1;   //置位标志位
                        }
                }
                else{} //相等不做处理                                
        }
}

//使用方式
#define NEW_DELAY 1 &nbsp; //新的延时任务

int main(void)
{
&nbsp;&nbsp;&nbsp;&nbsp;_Delay Twinkle={0, NEW_DELAY};//定义一个延时变量并初始化

&nbsp;    Stm32_Clock_Init(9);      //系统时钟设置
     LED_Init();               //初始化与LED连接的硬件接口
     LED1 = 0;
     TIM3_Int_Init(4999, 7199);//10Khz的计数频率,计数到5000为500ms &nbsp;
     TIM2_Int_Init(9, 7199); &nbsp; //一个ms进一次中断,提供延时基准时间
&nbsp; &nbsp;  while(1)
     {
          my_delay_ms(500, &amp;Twinkle); &nbsp;
          if(Twinkle.mark == 1)//500ms延时到的标志,执行完后Twinkle.mark会自动清零
          {
              LED0 =! LED0;
          }
      }
}
[/mw_shl_code]
[mw_shl_code=c,true][/mw_shl_code]
</div>
<p>
        <br>
</p>
<p>
        <br>
</p>
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-12 08:52:40 | 显示全部楼层
升级了
回复 支持 反对

使用道具 举报

72

主题

2711

帖子

2

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3505
金钱
3505
注册时间
2014-8-4
在线时间
696 小时
发表于 2015-11-12 12:09:21 | 显示全部楼层
回复【54楼】229382777@qq.com:
---------------------------------
知识掌握很熟练,赞一个
以我资质之鲁钝,当尽平心静气、循序渐进、稳扎稳打之力。
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-12 12:32:44 | 显示全部楼层
回复【56楼】龙之谷:
---------------------------------
谢谢,还在学习中
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

0

主题

1

帖子

0

精华

新手上路

积分
30
金钱
30
注册时间
2015-11-1
在线时间
1 小时
发表于 2015-11-12 18:43:30 | 显示全部楼层
右键 收藏  定时来看
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-13 23:43:35 | 显示全部楼层
实战:PWM

这部分自己写在了另一个帖子,附上地址
http://www.openedv.com/posts/list/62861.htm#356847
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-14 19:51:00 | 显示全部楼层

实战:输入捕获

 

简介:

输入捕获模式可以用来测量脉冲宽度或者测量频率。STM32 的定时器,除了TIM6和TIM7,
其他定时器都有输入捕获功能。STM32的输入捕获,简单的说就是通过检测TIMx_CHx上的
边沿信号,在边沿信号发生跳变(比如上升沿/下降沿)的时候,将当前定时器的值(TIMx_CNT)
存放到对应的通道的捕获/比较寄存器(TIMx_CCRx)里面,完成一次捕获。同时还可以配置
捕获时是否触发中断/DMA等。

 

需要配置的寄存器:

TIMx_ARR:设自动重装载值

TIMx_PSC:时钟分频

TIMx_CCMR1:捕获/比较模式寄存器

TIMx_CCER:捕获/比较使能寄存器

TIMx_DIER:DMA/中断使能寄存器

TIMx_CR1:控制寄存器

TIMx_CCR1:捕获/比较寄存器

 

配置步奏:

1. 开启 TIM2 时钟,配置 PA0 为下拉输入。

2. 设置 TIM2 ARR PSC

3. 设置 TIM2 CCMR1通道方向配置,输入捕获滤波器设置,

4. 设置 TIM2 CCER,开启输入捕获,并设置为上升沿捕获。

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

6. 设置 TIM2 CR1,使能定时器

 

这里贴上两张有关输入捕获的图

 


 
本章寄存器和库函数版本的差别不大,按键输入我用的是库函数实现,PWM输入用的是寄存器实现,也算是巩固下库函数和寄存器的操作方式区别。

原子哥历程中以按键输入来做捕获,用一个8位变量模仿寄存器,第7位用于表示成功捕获一个高电平,第6位用于表示成功捕获到一个上升沿,剩余的0~5位用于保存定时器溢出时间累加,累加一次是计数器从0记到65535,一次是1us  ” TIM2_Cap_Init(0XFFFF,72-1);” ,定时器溢出一次的时间为65535*1us,由于低6位最大只能表示0x3F,所以捕获的最长时间为65536us*63+65535 = 4194303us;实测也是这个值。



为了测量准确性,自己更改了一下代码,加上PWM,利用杜邦线将PWM输出口和捕获输入口进行连接测量输出,下图是代码软件仿真的波形图,高电平为50ms(截图关系没有对齐边沿,对齐可测到为50ms),第二张图为串口的输出值,也为50ms。说明输入捕获值还是挺准确的。





接来下开始学习外设了!!
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

72

主题

2711

帖子

2

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3505
金钱
3505
注册时间
2014-8-4
在线时间
696 小时
发表于 2015-11-15 00:58:03 | 显示全部楼层
回复【60楼】229382777@qq.com:
---------------------------------
临睡前顶一下,加油~~~~~
以我资质之鲁钝,当尽平心静气、循序渐进、稳扎稳打之力。
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-15 09:05:23 | 显示全部楼层
回复【61楼】龙之谷:
---------------------------------
共勉!!!加油
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-16 14:41:57 | 显示全部楼层

实战:OLED

 

简介:

OLED,即有机发光二极管(Organic Light-Emitting Diode),又称为有机电激光显示( OrganicElectroluminesence Display,OELD)。OLED 由于同时具备自发光,不需背光源、对比度高、厚度薄、视角广、反应速度快、可用于挠曲性面板、使用温度范围广、构造及制程较简单等优异之特性,被认为是下一代的平面显示器新兴应用技术。
LCD 都需要背光,而 OLED 不需要,因为它是自发光的。这样同样的显示,OLED效果要来得好一些。以目前的技术,OLED的尺寸还难以大型化,但是分辨率确可以做到很高。


控制器SSD1306通信接口




8080写时序以及对应程序



[mw_shl_code=c,true]对应程序: //向SSD1306写入一个字节。 //dat:要写入的数据/命令 //cmd:数据/命令标志 0,表示命令;1,表示数据; void OLED_WR_Byte(u8 dat,u8 cmd) { OLED_RS=cmd; //数据/命令标志 OLED_CS=0; OLED_WR=0; DATAOUT(dat); OLED_WR=1; OLED_CS=1; OLED_RS=1; } [/mw_shl_code]
4线SPI接口写时序及对应程序


[mw_shl_code=c,true]SPI方式对应程序: //向SSD1306写入一个字节。 //dat:要写入的数据/命令 //cmd:数据/命令标志 0,表示命令;1,表示数据; void OLED_WR_Byte(u8 dat,u8 cmd) { u8 i; OLED_RS=cmd; //写命令 OLED_CS=0; for(i=0;i<8;i++) { OLED_SCLK=0; if(dat&0x80)OLED_SDIN=1; else OLED_SDIN=0; OLED_SCLK=1; dat<<=1; } OLED_CS=1; OLED_RS=1; } [/mw_shl_code]
SSD1306指令集(写入即可获得相关屏幕设置)


控制8080接口读写命令数据



假读命令(Dummy Read),以使得微控制器的操作频率和显存的操作频率相匹配。在读取真正的数据之前,由一个的假读的过程。这里的假读,其实就是第一个读到的字节丢弃不要,从第二个开始,才是我们真正要读的数据。

读显存时序图:



ALIENTEK OLED 模块的 8080 接口方式需要如下一些信号线:



SSD1306初始化框图



显存与屏幕对应关系表:



一直在思考原子哥关于数组的定义,u8 OLED_GRAM[128][8] ,表示128行,8(18bit),而我们第一眼看屏幕的时候明明是64行,128列,为什么数组不定义成OLED_GRAM[8][128]呢,后面看了上面那副图就明白了,如果定义成[8][128]那么每次加1都是横向加的,而如果定义成[128][8]每次加1就是换一页,符合上图的从上往下扫。

 

程序为什么要内建GRAM?原因如下:

SSD1306 的每页包含了 128 个字节,总共 8 页,这样刚好是 128*64 的点阵大小。因为每次写入都是按字节写入的,这就存在一个问题,如果我们使用只写方式操作模块,那么,每次要写 8 个点,这样,我们在画点的时候,就必须把要设置的点所在的字节的每个位都搞清楚当前的状态( 0/1?),否则写入的数据就会覆盖掉之前的状态,结果就是有些不需要显示的点,显示出来了,或者该显示的没有显示了。这个问题在能读的模式下,我们可以先读出来要写入的那个字节,得到当前状况,在修改了要改写的位之后再写进 GRAM,这样就不会影响到之前的状况了。但是这样需要能读 GRAM,对于 3 线或 4 线 SPI 模式,模块是不支持读的,而且读->改->写的方式速度也比较慢。所以我们采用的办法是在 STM32 的内部建立一个 OLED 的 GRAM(共 128*8 个字节),在每次修改的时候,只是修改 STM32 上的 GRAM(实际上就是 SRAM),在修改完了之后,一次性把 STM32 上的 GRAM 写入到 OLED 的 GRAM。当然这个方法也有坏处,就是对于那些 SRAM很小的单片机(比如 51 系列)就比较麻烦了。

函数:发现写好时序并设置好一些启动指令实现和屏的通信后,最主要的就是画点函数,几乎所有的其他函数都是基于画点函数来显示的,发现原子哥写的这些函数思维很严谨,考虑的也很充足,是自己以后需要学习的,自己没有OLED屏,学LCD的时候打算先不看原子哥的代码写一个画点的函数试试。
[mw_shl_code=c,true]//画点 //x:0~127 //y:0~63 //t:1 填充 0,清空 void OLED_DrawPoint(u8 x,u8 y,u8 t) { u8 pos,bx,temp=0; if(x>127||y>63)return;//超出范围了. pos=7-y/8; //计算得出在第几页,page0~7 bx=y%8; //得出在page里面的具体位置 temp=1<<(7-bx); //由于x,y是以左下角为准,这里要转为以page的左上角为准 if(t)OLED_GRAM[x][pos]|=temp; //相应位置1 else OLED_GRAM[x][pos]&=~temp;//相应位置0 } //在指定位置显示一个字符,包括部分字符 //x:0~127 //y:0~63 //mode:0,反白显示;1,正常显示 //size:选择字体 16/12 void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size,u8 mode) { u8 temp,t,t1; u8 y0=y; u8 csize=(size/8+((size%8)?1:0))*(size/2); //得到字体一个字符对应点阵集所占的字节数 chr=chr-' '; //得到偏移后的值 for(t=0;t<csize;t++) //写入多个字节 { if(size==12)temp=asc2_1206[chr][t]; //调用1206字体 else if(size==16)temp=asc2_1608[chr][t]; //调用1608字体 else if(size==24)temp=asc2_2412[chr][t]; //调用2412字体 else return; //没有的字库 for(t1=0;t1<8;t1++) //写入一个字节 { if(temp&0x80)OLED_DrawPoint(x,y,mode); else OLED_DrawPoint(x,y,!mode); temp<<=1; y++; //一个bit一个bit写进去 if((y-y0)==size) //写完一个字符一列的所有bit { y=y0; //下一列,在同一行 x++; //写完一个字的一列了,退出写下一列 break; } } } } //显示字符串 //x,y:起点坐标 //size:字体大小 //*p:字符串起始地址 void OLED_ShowString(u8 x,u8 y,const u8 *p,u8 size) { while((*p<='~')&&(*p>=' ')) //判断是不是非法字符! { if(x>(128-(size/2))){x=0;y+=size;} //宽度超过屏幕的一行,显示到下一行 if(y>(64-size)){y=x=0;OLED_Clear();} //长度到屏幕底部了回到起始,清屏 OLED_ShowChar(x,y,*p,size,1); x+=size/2; p++; } }[/mw_shl_code]
我的博客:http://blog.csdn.net/itdo_just
回复 支持 1 反对 0

使用道具 举报

72

主题

2711

帖子

2

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3505
金钱
3505
注册时间
2014-8-4
在线时间
696 小时
发表于 2015-11-18 11:27:02 | 显示全部楼层
到OLED了,挺快的,哈哈,加油~~~~~
以我资质之鲁钝,当尽平心静气、循序渐进、稳扎稳打之力。
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-18 12:21:38 | 显示全部楼层
回复【64楼】龙之谷:
---------------------------------
LCD昨天没上贴.......信息量有点大,看英文手册看的头晕
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

72

主题

2711

帖子

2

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3505
金钱
3505
注册时间
2014-8-4
在线时间
696 小时
发表于 2015-11-18 14:00:55 | 显示全部楼层
回复【65楼】229382777@qq.com:
---------------------------------
LCD方面原子哥开发指南把常用信息讲得比较清楚了,如果有时间看英文手册仔细研究又能提高阅读英文资料水平那自然是极好的

一分耕耘一分收获,继续努力
以我资质之鲁钝,当尽平心静气、循序渐进、稳扎稳打之力。
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-18 15:04:03 | 显示全部楼层
回复【66楼】龙之谷:
---------------------------------
恩,谢谢!!!!
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

7

主题

333

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
1838
金钱
1838
注册时间
2012-7-16
在线时间
504 小时
发表于 2015-11-18 16:10:54 | 显示全部楼层
好习惯,总结的不错。
回复 支持 反对

使用道具 举报

3

主题

41

帖子

0

精华

初级会员

Rank: 2

积分
102
金钱
102
注册时间
2015-4-18
在线时间
8 小时
发表于 2015-11-18 16:22:29 | 显示全部楼层
写得不错,加油
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-18 17:22:57 | 显示全部楼层
回复【68楼】hyghyg1234:
---------------------------------
回复【69楼】leowuyou:
---------------------------------
谢谢支持!!
我的博客:http://blog.csdn.net/itdo_just
回复 支持 1 反对 0

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-18 21:22:34 | 显示全部楼层

实战:LCD

 

驱动LCD屏的步奏:

首先要弄清楚LCD的驱动芯片,原子哥的2.8LCD驱动芯片为ILI9341,驱动方式采用8080总线,因此需要两个手册,一个是ILI9341的数据手册,一个是8080的时序图。

1.根据8080总线的时序图将时序用IO口模拟出来(103RC没有FSMC)

写时序:分为写指令和写数据(DC=0命令,DC=1数据),先上时序图


[mw_shl_code=c,true]//写寄存器函数 //data:寄存器值 //时序上完全按照线和线之间的顺序去操作 void LCD_WR_REG(unsigned int dat) { LCD_CS_CLR; //#define LCD_CS_CLR GPIO_ResetBits(GPIOC, GPIO_Pin_9); LCD_RS_CLR; //#define LCD_RS_CLR GPIO_ResetBits(GPIOC, GPIO_Pin_8); LCD_WR_CLR; //#define LCD_WR_CLR GPIO_ResetBits(GPIOC, GPIO_Pin_7); GPIOB->ODR = dat; //写入16bit数据 LCD_WR_SET; //#define LCD_WR_SET GPIO_SetBits(GPIOC, GPIO_Pin_7); LCD_CS_SET; //#define LCD_CS_SET GPIO_SetBits(GPIOC, GPIO_Pin_9); } [/mw_shl_code]

至于为什么要用宏定义,而不用位带操作直接输出10,从可读性来考虑的话显示这样写更好,01只是数字并不代表任何意思,从代码效率来考虑的话使用的宏定义既可完成函数调用的功能,又能避免函数的出栈与入栈操作,减少系统开销,提高运行效率;所以宏定义后面你使用寄存器操作GPIO和使用库函数操作没有差别。

 

写数据函数和上面的写寄存器函数一样,只需要把LCD_RS_CLR改成LCD_RS_SET就行了

接来下是读时序

读时序:读时序我们只读数据,先上时序图


[mw_shl_code=c,true]//读LCD数据 //返回值:读到的值 unsigned int LCD_RD_DATA(void) { unsigned int dat=0; GPIOB->CRL = 0x88888888;//上下拉输入模式 GPIOB->CRH = 0x88888888;//上下拉输入模式 GPIOB->ODR = 0x0000; //下拉输入,默认低电平 LCD_CS_CLR; LCD_RS_SET; LCD_RD_CLR; dat = GPIOB->IDR; //读出16bit数据 LCD_RD_SET; LCD_CS_SET; GPIOB->CRL = 0x33333333; //输出模式,最大速度50MHz GPIOB->CRH = 0x33333333; //输出模式,最大速度50MHz GPIOB->ODR = 0xFFFF; //全部输出高 return dat; } [/mw_shl_code]

读数据的函数时序上和上面写的差别不大,也是按着时序图操作就好了,但是比较麻烦的是由于32没有像51那样的双向IO口,所以需要先把GPIO口设置为输入,并且是下拉输入,主要是为了让IO口在没数据进来时能保持低电平,提高稳定性,这里之前自己也有点疑惑,CRH只能设置GPIO口为上下拉输入模式,但具体怎么设置上拉和下拉呢,根据手册其实可以查找到在输入模式下,ODR可以用于设置上下拉,1设置为上拉,0设置为下拉,在设置完GPIO口输入并完成时序的读后,需要把GPIO口设置回输出模式方便下次写入数据

 

在写完读时序和写时序后我们就可以开始试着和ILI9341芯片进行通信试试,可以测试出自己写的时序有无错误,可以先读出芯片的ID试试,读ID的指令可以通过手册查找到,如下图


写好串口的初始化程序和GPIO口的初始化程序,由于原子哥板子JTAG引脚和LCD的引脚共用,所以需要把模式改为SWD并且使能复用时钟,之后根据0XD3这个指令去读取屏幕的ID号,读出来的ID没有任何问题,说明成功建立了通信

LCD_WR_REG(0xd3);

LCD_RD_DATA();          //dummy read         

LCD_RD_DATA();          //读到0X00

id = LCD_RD_DATA();   //读取93                  

id <<= 8;

id |= LCD_RD_DATA();  //读取41

printf("%X\n", id);


[mw_shl_code=c,true]//为了后续方便操作,将写指令写数据和写指令读数据打包成函数 //写寄存器 //LCD_Reg:寄存器编号 //LCD_RegValue:要写入的值 void LCD_WriteReg(u16 LCD_Reg,u16 LCD_RegValue) { LCD_WR_REG(LCD_Reg); LCD_WR_DATAX(LCD_RegValue); } //读寄存器 //LCD_Reg:寄存器编号 //返回值:读到的值 unsigned int LCD_ReadReg(unsigned int Command) { LCD_WR_REG(Command); return LCD_RD_DATA(); //返回指令带的信息 } [/mw_shl_code]

接着就是屏幕的初始化了,一开始对着手册看每个寄存器什么意思,发现原子哥对寄存器写入的数据跟手册的对不上,后来论坛查了一下,才知道是厂商提供的代码,在另一段文件里面可以找到很多初始化话的代码,原子哥的代码出处找到了,但是里面有三个寄存器的控制应该是原子哥自己加上去的:

LCD_WR_REG(0x3A);    //COLMOD: Pixel Format Set (3Ah)

LCD_WR_DATAX(0x55);  // "1 0 1" 16 bits / pixel "1 0 1" 16 bits / pixel

LCD_WR_REG(0x2B);    //Page Address Set (2Bh)

LCD_WR_DATAX(0x00);

LCD_WR_DATAX(0x00);

LCD_WR_DATAX(0x01);

LCD_WR_DATAX(0x3f);   //Y轴默认最大319

LCD_WR_REG(0x2A);    //Column Address Set (2Ah)

LCD_WR_DATAX(0x00);

LCD_WR_DATAX(0x00);

LCD_WR_DATAX(0x00);

LCD_WR_DATAX(0xef);   //X轴默认最大239

 

整段屏幕的初始化主要有电源控制、驱动时序控制、泵比控制(电荷泵)、电压控制、存储器存储控制、显示功能控制、伽玛值(伽玛值(Gamma)表示图象输出值与输入值关系的斜线。)、像素格式、横坐标、纵坐标的设置、退出休眠开启显示。

设置完以上初始化后就可以开始驱动屏幕了,以打点为目的。

 

写打点函数前需要确定显示方向(0x36),在手册上找了下,找到了下面这个指令可以用于设置方向,手册没有找到MY,MX,MV对于扫描方式的说明,但是在原子哥的教程中有说到,也希望原子哥能说下在哪里能找到



[mw_shl_code=c,true]//先将扫描方式的程序写出来 //扫描方向 //dir:0~7,代表8个方向 void LCD_Scan_Dir(unsigned char dir) { unsigned int regval = 0; if(lcddev.id == 0x9341) { switch(dir) //确定方向 { case 0: //从左到右,从上到下 regval |= (0<<7) || (0<<6) || (0<<5); break; case 1://从左到右,从下到上 regval|=(1<<7)|(0<<6)|(0<<5); break; case 2://从右到左,从上到下 regval|=(0<<7)|(1<<6)|(0<<5); break; case 3://从右到左,从下到上 regval|=(1<<7)|(1<<6)|(0<<5); break; case 4://从上到下,从左到右 regval|=(0<<7)|(0<<6)|(1<<5); break; case 5://从上到下,从右到左 regval|=(0<<7)|(1<<6)|(1<<5); break; case 6://从下到上,从左到右 regval|=(1<<7)|(0<<6)|(1<<5); break; case 7://从下到上,从右到左 regval|=(1<<7)|(1<<6)|(1<<5); break; } regval|=0X08; //!!!!!!!!实验发现原子哥的画笔颜色给的BGR模式的,这句一定要加,不然有些颜色会相反 LCD_WriteReg(0x36,regval); //将扫描方式写入0x36指令,其他位默认为0即可 } } [/mw_shl_code]
写好方向以后就可以开始写打点的函数了,需要用到下面三个指令,用于确定打点的位置,以及写入存储器的指令(写入颜色),由于原子哥教程中提到手册的颜色值是错误的,并且自己对颜色的配色不了解,所以直接调用了原子哥的宏定义来定义点的颜色,下面是手册的指令及打点程序





[mw_shl_code=c,true]//根据上面那些资料就画点也就不难了 //x,y轴 //点的颜色 void LCD_Draw_Point(unsigned int x, unsigned int y, unsigned int color) { if(lcddev.id == 0x9341) { LCD_WR_REG(0x2A); //写入X坐标 LCD_WR_DATAX(x>>8); LCD_WR_DATAX(x&0xFF); LCD_WR_REG(0x2B); //写入Y坐标 LCD_WR_DATAX(y>>8); LCD_WR_DATAX(y&0xFF); LCD_WR_REG(0x2C); //写GRAM指令 LCD_WR_DATAX(color);//写入颜色数据 } } [/mw_shl_code]
[mw_shl_code=c,true]//最后就是实现功能了,初始化完以后设置扫描方向开启背光显示然后再用上面代码输出点就好了 int main(void) { u16 x,y; delay_init(); LED_Init(); uart_init(9600); LCD_Init(); LCD_Scan_Dir(0); //扫描方式:从左到右,从上到下 LCD_LED = 1; //开启背光 while(1) { for(x=0; x< 239; x++) for(y=0; y< 159; y++) LCD_Draw_Point(x,y,BLUE);//画一半屏 } } [/mw_shl_code]



下面是原子哥列出的流程,附上图,方便些后面代码



今天完成画点,明天再写些画线,字符,数字等函数,明天上完课还要去“高交会”逛逛,O(∩_∩)O哈哈~
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

74

主题

334

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
436
金钱
436
注册时间
2015-5-28
在线时间
144 小时
发表于 2015-11-19 08:26:02 | 显示全部楼层
加油朋友,我也是三流学校的
最近爱上了stm32
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-19 08:30:15 | 显示全部楼层
回复【72楼】带我足够强大:
---------------------------------
哈哈,谢谢,我学校三流都不是啊,大专证都要自己去自考
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

72

主题

2711

帖子

2

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3505
金钱
3505
注册时间
2014-8-4
在线时间
696 小时
发表于 2015-11-19 08:39:14 | 显示全部楼层
回复【71楼】229382777@qq.com:
---------------------------------
分析地清晰到位,非常好
以我资质之鲁钝,当尽平心静气、循序渐进、稳扎稳打之力。
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-19 08:46:51 | 显示全部楼层
回复【74楼】龙之谷:
---------------------------------
哈哈,谢谢龙哥
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165309
金钱
165309
注册时间
2010-12-1
在线时间
2108 小时
发表于 2015-11-19 23:04:01 | 显示全部楼层
不错啊,继续努力.
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-19 23:09:25 | 显示全部楼层
回复【76楼】正点原子:
---------------------------------
难得原子哥顶,我会继续努力的
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-20 23:46:15 | 显示全部楼层

实战:LCD()

 

上章驱动完屏幕后就开始写些显示字符、数字的函数了,还有些类似清屏,设置坐标等函数,下面是一些简单的设置函数
[mw_shl_code=c,true]//开显示 void LCD_Display_ON(void) { if(lcddev.id == 0x9341) { LCD_WR_REG(0x29); //开启显示指令 } } //关闭显示 void LCD_Display_OFF(void) { if(lcddev.id == 0x9341) { LCD_WR_REG(0x28); //开启显示指令 } } //设置光标位置 void LCD_SetCursor(unsigned int x, unsigned int y) { if(lcddev.id == 0x9341) { LCD_WR_REG(0x2A); //写入X坐标 LCD_WR_DATAX(x>>8); LCD_WR_DATAX(x&0xFF); LCD_WR_REG(0x2B); //写入Y坐标 LCD_WR_DATAX(y>>8); LCD_WR_DATAX(y&0xFF); } } //开始写入GRAM void LCD_Write_GRAM(void) { LCD_WR_REG(0x2C); //写GRAM指令 } // x,y轴,点的颜色,为了加速画点,全部用宏定义实现 void LCD_Draw_Point(unsigned short int x, unsigned short int y, unsigned short int color) { if(lcddev.id == 0x9341) { LCD_WR_QREG(0x2A); //写入X坐标(宏定义快速写入) LCD_WR_DATA(x>>8); LCD_WR_DATA(x&0xFF); LCD_WR_QREG(0x2B); //写入Y坐标 LCD_WR_DATA(y>>8); LCD_WR_DATA(y&0xFF); //写入GRAM指令 LCD_WR_REG(0x2C); //写入数据指令 LCD_WR_DATA(color); } } //横屏、竖屏转换 void LCD_Display_Dir(u8 dir) { if(dir==0) //竖屏 { lcddev.dir = 0; lcddev.height = 320; lcddev.width = 240; } else if(dir==1) //横屏 { lcddev.dir = 1; lcddev.height = 240; lcddev.width = 320; } } //清屏 //color-屏幕的颜色 void LCD_Clear(unsigned short int color) { unsigned int num=0; unsigned int i=0; num = lcddev.width * lcddev.height;//计算总点数 LCD_SetCursor(0x0000, 0x0000);//设置光标位置 LCD_Write_GRAM();//写GRAM指令 for(i=0; i<num; i++) { LCD_WR_DATA(color); } } //扫描方向 //dir:0~7,代表8个方向 void LCD_Scan_Dir(unsigned char dir) { unsigned int regval = 0; u16 temp; if(lcddev.id == 0x9341) { if(lcddev.dir==1) //横屏需要改变扫描方向 { dir = 6; //从下到上,从左到右的扫描方式 } switch(dir) //确定方向 { case 0: //从左到右,从上到下 regval |= (0<<7) || (0<<6) || (0<<5); break; case 1://从左到右,从下到上 regval|=(1<<7)|(0<<6)|(0<<5); break; case 2://从右到左,从上到下 regval|=(0<<7)|(1<<6)|(0<<5); break; case 3://从右到左,从下到上 regval|=(1<<7)|(1<<6)|(0<<5); break; case 4://从上到下,从左到右 regval|=(0<<7)|(0<<6)|(1<<5); break; case 5://从上到下,从右到左 regval|=(0<<7)|(1<<6)|(1<<5); break; case 6://从下到上,从左到右 regval|=(1<<7)|(0<<6)|(1<<5); break; case 7://从下到上,从右到左 regval|=(1<<7)|(1<<6)|(1<<5); break; } regval|=0X08; //RGB格式 LCD_WriteReg(0x36,regval); //将扫描方式写入0x36指令,其他位默认为0即可 if((regval&0X20)||lcddev.dir==1) //横屏 { if(lcddev.width<lcddev.height)//交换X,Y { temp=lcddev.width; lcddev.width=lcddev.height; lcddev.height=temp; } }else //竖屏 { if(lcddev.width>lcddev.height)//交换X,Y { temp=lcddev.width; lcddev.width=lcddev.height; lcddev.height=temp; } } LCD_WR_REG(0X2A); //写入X坐标范围 LCD_WR_DATA(0);LCD_WR_DATA(0); //x其实位置 LCD_WR_DATA((lcddev.width-1)>>8);LCD_WR_DATA((lcddev.width-1)&0XFF); //x的结束位置 LCD_WR_REG(0X2B); //写入Y坐标范围 LCD_WR_DATA(0);LCD_WR_DATA(0); //y其实位置 LCD_WR_DATA((lcddev.height-1)>>8);LCD_WR_DATA((lcddev.height-1)&0XFF); //y的结束位置 } } [/mw_shl_code]

写完上面的基本应用后就可以开始写字符和字符串了,以下是关于字符库的取值方法




字库提取出来后就可以开始写字符函数和字符串函数了,下面是函数

[mw_shl_code=c,true]//显示一个字符 //x,y-坐标 //chr-字符 //size-字符大小 void LCD_Show_Char(u16 x, u16 y, u8 chr, u8 size) { u8 temp, csize, t, t1; u16 y0; y0 = y; //保存y值,达到字的高度开始扫下一列 csize = (size/8 + ((size%8)?1:0)) * size/2;//计算字符占的字节数,纵向是横向的2倍 chr = chr - ' '; //省去前32个字符 for(t=0; t<csize; t++) { if(size==12)temp = asc2_1206[chr][t]; //字符大小设置 else if(size==16)temp = asc2_1608[chr][t]; else if(size==24)temp = asc2_2412[chr][t]; else return; for(t1=0; t1<8 ;t1++) //一个字节数据 { if(temp&0x80)LCD_Draw_Point(x, y, POINT_COLOR); //画点 temp<<=1; //一个字节的遍历 y++; //列扫,y每次加1 if(x>=lcddev.width)return; //超区域了 if((y-y0)==size) //遍历到字体大小值后停止,x加1 { x++; y = y0; //重新从上往下扫 if(x>=lcddev.width)return; //超区域了 break; } } } } //显示字符串 //x,y:起点坐标 //width,height:区域大小 //size:字体大小 //*p:字符串起始地址 void LCD_ShowString(u16 x,u16 y,u16 width,u16 height,u8 size,u8 *p) { u8 x0=x; width += x; //字符串的宽度+x的起始位置 height += y; //字符串的高度+y的其实位置 while((*p<='~')&&(*p>=' '))//判断是不是非法字符! { if(x>=width)//超过宽度了显示到下一行 { x=x0; y+=size; //下一行,一行size个点 } if(y>=height) //超过范围 break;//退出 LCD_Show_Char(x, y, *p, size); x += size/2; //字符的宽度是高度的一半 p++; //下一个字符 } } [/mw_shl_code]
最后是显示效果






我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

69

主题

475

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
1334
金钱
1334
注册时间
2013-12-28
在线时间
197 小时
发表于 2015-11-21 00:13:33 | 显示全部楼层
楼主 ,继续加油,顶起
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-21 08:23:57 | 显示全部楼层
回复【79楼】jiaozhu:
---------------------------------
谢谢支持!!
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

8

主题

48

帖子

0

精华

初级会员

Rank: 2

积分
135
金钱
135
注册时间
2015-11-9
在线时间
23 小时
发表于 2015-11-21 08:48:46 | 显示全部楼层
支持楼主,mark!
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-21 09:01:18 | 显示全部楼层
回复【81楼】我是来捣乱的:
---------------------------------
谢谢......
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

1

主题

374

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
1309
金钱
1309
注册时间
2014-7-23
在线时间
418 小时
发表于 2015-11-21 18:41:24 | 显示全部楼层
LZ加油啊,你是广东哪里的啊
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-21 19:06:12 | 显示全部楼层
回复【83楼】guset_03:
---------------------------------
谢谢支持!
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-22 11:51:32 | 显示全部楼层

实战:RTC

 

简介:

实时时钟是一个独立的定时器。 RTC模块拥有一组连续计数的计数器,在相应软件配置下,可
提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。
RTC模块和时钟配置系统(RCC_BDCR寄存器)处于后备区域,即在系统复位或从待机模式唤醒
后, RTC的设置和时间维持不变。
系统复位后,对后备寄存器和RTC的访问被禁止,这是为了防止对后备区域(BKP)的意外写操
作。执行以下操作将使能对后备寄存器和RTC的访问:
● 设置寄存器RCC_APB1ENR的PWREN和BKPEN位,使能电源和后备接口时钟
● 设置寄存器PWR_CR的DBP位,使能对后备寄存器和RTC

 

主要特性
可编程的预分频系数:分频系数最高为220。
32位的可编程计数器,可用于较长时间段的测量。
2个分离的时用于APB1接口的PCLK1和RTC时钟(RTC时钟的频率必须小于PCLK1时钟频率的四分之一以上)。
可以选择以下三种RTC的时钟源:
─ HSE时钟除以128;
─ LSE振荡器时钟;
─ LSI振荡器时钟
2个独立的复位类型:
─ APB1接口由系统复位;
─ RTC核心(预分频器、闹钟、计数器和分频器)只能由后备域复位(详见6.1.3节)。
3个专门的可屏蔽中断:
─ 闹钟中断,用来产生一个软件可编程的闹钟中断。
─ 秒中断,用来产生一个可编程的周期性中断信号(最长可达1秒)。
─ 溢出中断,指示内部可编程计数器溢出并回转为0

 

功能概述:

RTC由两个主要部分组成(参见下图)。第一部分(APB1接口)用来和APB1总线相连。此单元还包
含一组16位寄存器,可通过APB1总线对其进行读写操作。 APB1接口由APB1总线时钟驱动。

另一部分(RTC核心)由一组可编程计数器组成,分成两个主要模块。第一个模块是RTC的预分频
模块,它可编程产生最长为1秒的RTC时间基准TR_CLK。 RTC的预分频模块包含了一个20位的
可编程分频器(RTC预分频器)。如果在RTC_CR寄存器中设置了相应的允许位,则在每个

TR_CLK周期中RTC产生一个中断(秒中断)。第二个模块是一个32位的可编程计数器,可被初始
化为当前的系统时间。系统时间按TR_CLK周期累加并与存储在RTC_ALR寄存器中的可编程时
间相比较,如果RTC_CR控制寄存器中设置了相应允许位,比较匹配时将产生一个闹钟中断


RTC寄存器
RTC核完全独立于RTC APB1接口。
软件通过APB1接口访问RTC的预分频值、 计数器值和闹钟值。但是,相关的可读寄存器只在与
RTC APB1时钟进行重新同步的RTC时钟的上升沿被更新。 RTC标志也是如此的。
这意味着,如果APB1接口曾经被关闭,而读操作又是在刚刚重新开启APB1之后,则在第一次
的内部寄存器更新之前,从APB1上读出的RTC寄存器数值可能被破坏了(通常读到0)。下述几种
情况下能够发生这种情形:
● 发生系统复位或电源复位
● 系统刚从待机模式唤醒
● 系统刚从停机模式唤醒
所有以上情况中, APB1接口被禁止时(复位、无时钟或断电)RTC核仍保持运行状态。
因此,若在读取RTC寄存器时, RTC的APB1接口曾经处于禁止状态,则软件首先必须等待
RTC_CRL寄存器中的RSF位(寄存器同步标志)被硬件置’1’。
注: RTC APB1 接口不受WFI WFE 等低功耗模式的影响。

 

RTC配置过程:

配置过程:
1. 查询RTOFF位,直到RTOFF的值变为’1’
2. 置CNF值为1,进入配置模式
3. 对一个或多个RTC寄存器进行写操作
4. 清除CNF标志位,退出配置模式
5. 查询RTOFF,直至RTOFF位变为’1’以确认写操作已经完成。
仅当CNF标志位被清除时,写操作才能进行,这个过程至少需要3个RTCCLK周期。

  

RTC几个主要配置寄存器






RTC 预分频器余数寄存器,该寄存器也有2个寄存器组成RTC_DIVH和RTC_DIVL,这两个寄存

器的作用就是用来获得比秒钟更为准确的时钟,比如可以得到0.1秒,或者0.01秒等。该寄存

器的值自减的,用于保存还需要多少时钟周期获得一个秒信号。在一次秒钟更新后,由硬件重新

装载。这两个寄存器和RTC预分频装载寄存器的各位是一样的,

 

RTC计数器寄存器 (RTC_CNTH / RTC_CNTL)

RTC核有一个32位可编程的计数器,可通过两个16位的寄存器访问。计数器以预分频器产生的
TR_CLK时间基准为参考进行计数。 RTC_CNT寄存器用来存放计数器的计数值。他们受RTC_CR

的位RTOFF写保护,仅当RTOFF值为’1’ 时,允许写操作在高或寄存器(RTC_CNTH或RTC_CNTL)

上的写操作,能够直接装载到相应的可编程计数器,并且重新装载RTC预分频器。当进行读操作时,

直接返回计数器内的计数值(系统时间)。

 

RTC闹钟寄存器(RTC_ALRH/RTC_ALRL)

当可编程计数器的值与RTC_ALR中的32位值相等时,即触发一个闹钟事件,并且产生RTC闹钟
中断。此寄存器受RTC_CR寄存器里的RTOFF位写保护,仅当RTOFF值为’1’时,允许写操作。

 

备份寄存器是 42 个 16 位的寄存器( Mini 开发板就是大容量的),可用来存储 84 个字节的
用户应用程序数据。他们处在备份域里,当 VDD 电源被切断,他们仍然由 VBAT 维持供电。
即使系统在待机模式下被唤醒,或系统复位或电源复位时,他们也不会被复位。

 

BKP简介
备份寄存器是42个16位的寄存器,可用来存储84个字节的用户应用程序数据。他们处在备份域
里,当VDD电源被切断,他们仍然由VBAT维持供电。当系统在待机模式下被唤醒,或系统复位或
电源复位时,他们也不会被复位。
此外, BKP控制寄存器用来管理侵入检测和RTC校准功能。
复位后,对备份寄存器和RTC的访问被禁止,并且备份域被保护以防止可能存在的意外的写操
作。执行以下操作可以使能对备份寄存器和RTC的访问。
● 通过设置寄存器RCC_APB1ENR的PWREN和BKPEN位来打开电源和后备接口的时钟
● 电源控制寄存器(PWR_CR)的DBP位来使能对后备寄存器和RTC




RTC 正常工作的一般配置步骤如下:

1) 使能电源时钟和备份区域时钟。

寄存器:RCC->APB1ENR|=1<<28; //使能电源时

RCC->APB1ENR|=1<<27;   //使能备份时钟              

库函数:RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);

 

2) 取消备份区写保护。

寄存器:PWR->CR|=1<<8;    //取消备份区写保护

库函数:PWR_BackupAccessCmd(ENABLE); //使能 RTC 和后备寄存器访问

 

3) 复位备份区域,开启外部低速振荡器。

寄存器:RCC->BDCR|=1<<16;        //备份区域软复位(复位整个备份域)     

             RCC->BDCR&=~(1<<16);  //备份区域软复位结束               

             RCC->BDCR|=1<<0;         //开启外部低速振荡器

库函数:BKP_DeInit();                    //复位备份区域

  RCC_LSEConfig(RCC_LSE_ON);// 开启外部低速振荡器

 

4)选择 RTC 时钟,并使能。

寄存器:RCC->BDCR|=1<<8; //LSI作为RTC时钟          

             RCC->BDCR|=1<<15;//RTC时钟使能   

库函数:RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //选择 LSE 作为 RTC 时钟

             RCC_RTCCLKCmd(ENABLE); //使能 RTC 时钟

 

5)设置 RTC的分频,以及配置RTC时钟。

寄存器:RTC->CRL|=1<<4;        //允许配置  

             RTC->RLH=0X0000;

             RTC->RLL=32767;       //时钟周期设置(有待观察,看是否跑慢了?)理论值:32767   

库函数:RTC_EnterConfigMode(); //允许配置

             RTC_ExitConfigMode();   //退出配置模式,更新配置

  void RTC_SetPrescaler(uint32_t PrescalerValue);// RTC时钟的分频数

 

6)更新配置,设置RTC中断。

寄存器:R RTC->CRL&=~(1<<4);         //配置更新

             while(!(RTC->CRL&(1<<5)));   //等待RTC寄存器操作完成                                                                                                   BKP->DR1=0X5050;                //写入 0X5050 代表我们已经初始化过时钟

  MY_NVIC_Init(0,0,RTC_IRQn,2);//优先级设置   

库函数:RTC_ExitConfigMode();           //退出配置模式,更新配置

             BKP_WriteBackupRegister(BKP_DR1, 0X5050); //写入 0X5050 代表我们已经初始化过时钟了

  RTC_NVIC_Config();             //RCT中断分组设置                 

 

7)编写中断服务函数。         

RTC初始化的过程设置还是比较麻烦的,每次对RTC寄存器的写操作后都要等待写操作完成,在向

RTC_CNTRTC_ALRRTC_PRL写入数据的时候还要允许配置,配置完后还要退出配置,在读取秒钟

数之前还要等待RTC寄存器同步,要注意的细节比较多。

 

下面是时间设置程序,这个程序是LINUX的源码移植过来的,算法简直超神了,不用考虑闰年,月份差距的事,就几行搞定。如果坛友有需求我可以发出来,有带解析,如果没有解析我估计没几个人看得懂呀


[mw_shl_code=c,true]u8 RTC_Set(unsigned int year, unsigned int mon, unsigned int day, unsigned int hour, unsigned int min, unsigned int sec) {[/mw_shl_code] [mw_shl_code=c,true] if(year<1970||year>2099)return 1; if (0 >= (int) (mon -= 2)) { /**//* 1..12 -> 11,12,1..10 */ mon += 12; /**//* Puts Feb last since it has leap day */ year -= 1; } RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); //使能PWR和BKP外设时钟 PWR_BackupAccessCmd(ENABLE); //使能RTC和后备寄存器访问 RTC_SetCounter((((//设置RTC计数器的值 (unsigned long) (year/4 - year/100 + year/400 + 367*mon/12 + day) + year*365 - 719499 )*24 + hour /**//* now have hours */ )*60 + min /**//* now have minutes */ )*60 + sec); /**//* finally seconds */ return 0; } [/mw_shl_code]
获取时间和周期的代码和原子哥的没什么差别就不贴了,LCD显示部分自己写了个彩笔,加了个艺术字的字体上去,下面是效果图


我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-22 22:02:45 | 显示全部楼层

实战:待机唤醒

 

简介:

很多单片机都有低功耗模式,STM32 也不例外。在系统或电源复位以后,微控制器处于运
行状态。运行状态下的 HCLK 为 CPU 提供时钟,内核执行程序代码。当CPU不需继续运行时,
可以利用多个低功耗模式来节省功耗,例如等待某个外部事件时。用户需要根据最低电源消耗,
最快速启动时间和可用的唤醒源等条件,选定一个最佳的低功耗模式。

STM32 的低功耗模式有 3 种:
1)睡眠模式( CM3 内核停止,外设仍然运行)
2)停止模式(所有时钟都停止)
3)待机模式( 1.8V 内核电源关闭)


在这三种低功耗模式中,最低功耗的是待机模式,在此模式下,最低只需要2uA左右的电
流。停机模式是次低功耗的,其典型的电流消耗在20uA左右。最后就是睡眠模式了。可以

根据自己的需求来决定使用哪种低功耗模式。







电源控制寄存器




待机模式唤醒步奏:

1) 设置 SLEEPDEEP 位,系统控制寄存器(SCB_SCR)

SCB->SCR|=1<<2;//使能SLEEPDEEP位 (SYS->CTRL)



2) 使能电源时钟,设置 WK_UP 引脚作为唤醒源。

RCC->APB1ENR|=1<<28;     //使能电源时钟      

PWR->CSR|=1<<8;          //设置WKUP用于唤醒



3) 设置 PDDS 位,执行 WFI 指令,进入待机模式。

PWR->CR|=1<<2;           //清除Wake-up标志

PWR->CR|=1<<1;           //PDDS置位                  

WFI_SET();                     //进入深睡眠,和PDDS配合进入待机模式



4) 最后编写 WK_UP 中断函数。

中断中判断是否超过3s,超过进入待机模式

原子整个实现待机的过程是通过开机检测是否有按下超过3s,有的话退出待机,没超过3s一直在待机状态,退出待机后通过wkup按键触发外部中断,在中断里面再次检测有没有按下超过3s,超过3s则进入待机模式,由于是按键上升沿退出待机所以程序从main的头开始执行下来一直卡在WKUP_Init();这个函数这里,没到3s再次进入待机,原子哥的代码写法也值得自己学习。

我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165309
金钱
165309
注册时间
2010-12-1
在线时间
2108 小时
发表于 2015-11-22 22:57:02 | 显示全部楼层
回复【86楼】229382777@qq.com:
---------------------------------
不错。。。
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-22 23:26:16 | 显示全部楼层
回复【87楼】正点原子:
---------------------------------
多谢原子哥给裤子!!
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

72

主题

2711

帖子

2

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3505
金钱
3505
注册时间
2014-8-4
在线时间
696 小时
发表于 2015-11-22 23:32:22 | 显示全部楼层
回复【85楼】229382777@qq.com:
---------------------------------
很不错,加的艺术字很漂亮,学以致用了~~~~~
以我资质之鲁钝,当尽平心静气、循序渐进、稳扎稳打之力。
回复 支持 反对

使用道具 举报

72

主题

2711

帖子

2

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3505
金钱
3505
注册时间
2014-8-4
在线时间
696 小时
发表于 2015-11-22 23:38:03 | 显示全部楼层
回复【88楼】229382777@qq.com:
---------------------------------
刷新最新回复,看到给cool了,恭喜.....
以我资质之鲁钝,当尽平心静气、循序渐进、稳扎稳打之力。
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-23 10:04:58 | 显示全部楼层
谢谢,后面越学越难,有了cool给了很大动力
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-23 10:08:46 | 显示全部楼层
回复【90楼】龙之谷:
---------------------------------
谢谢!!!!!
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

13

主题

296

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
2066
金钱
2066
注册时间
2012-5-26
在线时间
291 小时
发表于 2015-11-23 14:05:38 | 显示全部楼层
不错呀,赞!!
活着才是王道!健康是一切的前提!
回复 支持 反对

使用道具 举报

2

主题

5

帖子

0

精华

新手上路

积分
40
金钱
40
注册时间
2011-7-13
在线时间
1 小时
发表于 2015-11-23 17:12:43 | 显示全部楼层
精神可嘉  能力可奖,值得学习
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-23 17:15:40 | 显示全部楼层
回复【93楼】lison0103:
---------------------------------
回复【94楼】w2006612:
---------------------------------
谢谢支持
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165309
金钱
165309
注册时间
2010-12-1
在线时间
2108 小时
发表于 2015-11-23 22:14:53 | 显示全部楼层
回复【88楼】229382777@qq.com:
---------------------------------
好东西必须支持啊
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

11

主题

193

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
425
金钱
425
注册时间
2013-3-19
在线时间
20 小时
发表于 2015-11-24 10:30:10 | 显示全部楼层
回复【85楼】229382777@qq.com:
---------------------------------
这个比较屌,共享下看看,O(∩_∩)O谢谢
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-24 10:37:49 | 显示全部楼层
回复【97楼】精神不死6530:
---------------------------------
哈哈,有人观察到了,等下发出来
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-24 11:02:06 | 显示全部楼层
回复【97楼】精神不死6530:
---------------------------------
http://www.openedv.com/posts/list/63389.htm?fromAll=0
算法的解析分享出来了,有需要的可以看下
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-11-24 12:47:46 | 显示全部楼层

实战:ADC


简介:

STM32 拥有 1~3 个 ADC ( STM32F101/102 系列只有 1 个 ADC),这些 ADC 可以独立使用,
也可以使用双重模式(提高采样率)。 STM32 的 ADC 是 12 位逐次逼近型的模拟数字转换器。
它有 18 个通道,可测量 16 个外部和 2 个内部信号源。各通道的A/D 转换可以单次、连续、扫
描或间断模式执行。 ADC 的结果可以左对齐或右对齐方式存储在16位数据寄存器中。模拟看
门狗特性允许应用程序检测输入电压是否超出用户定义的高/低阀值。
STM32F103 系列最少都拥有 2 个 ADC,我们选择的 STM32F103RCT 包含有 3 个 ADC。
STM32 的 ADC 最大的转换速率为 1Mhz,也就是转换时间为 1us(在 ADCCLK=14M,采样周期
为 1.5 个 ADC 时钟下得到),不要让 ADC 的时钟超过 14M,否则将导致结果准确度下降。
STM32 将 ADC 的转换分为 2 个通道组:规则通道组和注入通道组。规则通道相当于你正
常运行的程序,而注入通道呢,就相当于中断。在你程序正常执行的时候,中断是可以打断你

的执行的。同这个类似,注入通道的转换可以打断规则通道的转换,在注入通道被转换完成之
后,规则通道才得以继续转换。

 

AD的主要指标

在选取和使用 A/D 的时候,依靠什么指标来判断很重要。由于 AD 的种类很多,分为积分型、逐次逼近型、并行/串行比较型、Σ-Δ型等多种类型。同时指标也比较多,并且有的指标还有轻微差别,如果和某一确定类型A/D 概念和原理有差别,也不会影响实际应用。

1、 ADC 的位数
一个 n 位的 ADC 表示这个 ADC 共有 2 的 n 次方个刻度。 8 位的 ADC,输出的是从 0~255 一共 256 个数字量,也就是 2 的 8 次方个数据刻度。

2、基准源
基准源,也叫基准电压,是 ADC 的一个重要指标,要想把输入 ADC 的信号测量准确,那么基准源首先要准,基准源的偏差会直接导致转换结果的偏差。比如一根米尺,总长度本应该是 1 米,假定这根米尺被火烤了一下,实际变成了 1.2 米,再用这根米尺测物体长度的话自然就有了较大的偏差。假如我们的基准源应该是 5.10V,但是实际上提供的却是 4.5V,这样误把 4.5V 当成了 5.10V 来处理的话,偏差也会比较大。

3、分辨率
分辨率是数字量变化一个最小刻度时,模拟信号的变化量,定义为满刻度量程与 2n-1 的比值。假定 5.10V 的电压系统,使用 8 位的 ADC 进行测量,那么相当于 0~255 一共 256 个刻度把 5.10V 平均分成了 255 份,那么分辨率就是 5.10/255 = 0.02V。

4、 INL(积分非线性度)和 DNL(差分非线性度)

最容易混淆的两个概念就是“分辨率”和“精度”,认为分辨率越高,则精度越高,而实际上,两者之间是没有必然联系的。分辨率是用来描述刻度划分的,而精度是用来描述准确程度的。同样一根米尺,刻度数相同,分辨率就相当,但是精度却可以相差很大,如图 17-2 所示。

图 17-2 表示的精度一目了然,不需多说。和 ADC 精度关系重大的两个指标是 INL(IntegralNonLiner)和 DNL(Differencial NonLiner)。

INL :指的是 ADC 器件在所有的数值上对应的模拟值,和真实值之间误差最大的那一个点的误差值,是 ADC 最重要的一个精度指标,单位是 LSB。 LSB( Least Significant Bit)是最低有效位的意思,那么它实际上对应的就是 ADC 的分辨率。一个基准为 5.10V 的 8 位 ADC,它的分辨率就是 0.02V,用它去测量一个电压信号,得到的结果是 100,就表示它测到的电压值是 100*0.02V=2V,假定它的 INL 是 1LSB,就表示这个电压信号真实的准确值是在1.98V~2.02V 之间的,按理想情况对应得到的数字应该是 99~101,测量误差是一个最低有效位,即 1LSB。

DNL :表示的是 ADC 相邻两个刻度之间最大的差异,单位也是 LSB。一把分辨率是 1 毫米的尺子,相邻的刻度之间并不都刚好是 1 毫米,而总是会存在或大或小的误差。同理,一个 ADC 的两个刻度线之间也不总是准确的等于分辨率,也是存在误差,这个误差就是 DNL。一个基准为 5.10V 的 8 位 ADC,假定它的 DNL 是 0.5LSB,那么当它的转换结果从 100 增加到 101 时,理想情况下实际电压应该增加 0.02V,但 DNL 为 0.5LSB 的情况下实际电压的增加值是在 0.01~0.03V 之间。值得一提的是 DNL 并非一定小于 1LSB,很多时候它会等于或大于 1LSB,这就相当于是一定程度上的刻度紊乱,当实际电压保持不变时, ADC 得出的结果可能会在几个数值之间跳动,很大程度上就是由于这个原因(但并不完全是,因为还有无时无处不在的干扰的影响)。


5、转换速率

转换速率,是指 ADC 每秒能进行采样转换的最大次数,单位是 sps (或 s/s、sa/s,即 samplesper second),它与 ADC 完成一次从模拟到数字的转换所需要的时间互为倒数关系。 ADC 的种类比较多,其中积分型的 ADC 转换时间是毫秒级的,属于低速 ADC;逐次逼近型 ADC转换时间是微秒级的,属于中速 ADC;并行/串行的 ADC 的转换时间可达到纳秒级,属于高速 ADC。

 

规则通道单次转换几个需要用到的寄存器

时钟配置寄存器(RCC_CFGR)



ADC控制寄存器 1(ADC_CR1)



ADC控制寄存器 2(ADC_CR2)







ADC采样时间寄存器 2(ADC_SMPR2)


ADC规则序列寄存器 1(ADC_SQR1)


ADC规则序列寄存器 3(ADC_SQR3)



ADC规则数据寄存器(ADC_DR)



设置步奏:

1) 开启 PA 口时钟,设置 PA1 为模拟输入。

GPIOA->CRL&=0XFFFFFF0F;//PA1 anolog输入            


2 ) 使能 ADC1 时钟,并设置分频因子。
RCC->CFGR&=~(3<<14);   //分频因子清零    

RCC->CFGR|=2<<14;

3) 设置 ADC1 的工作模式。

ADC1->CR1&=0XF0FFFF;    //工作模式清零

ADC1->CR1|=0<<16;        //独立工作模式 

ADC1->CR1&=~(1<<8);    //非扫描模式          

ADC1->CR2&=~(1<<1);    //单次转换模式

ADC1->CR2&=~(7<<17);  

ADC1->CR2|=7<<17;         //软件控制转换 

ADC1->CR2|=1<<20;         //使用用外部触发(SWSTART)!!!     必须使用一个事件来触发

ADC1->CR2&=~(1<<11);   //右对齐       


4) 设置 ADC1 规则序列的相关信息。

ADC1->SQR1&=~(0XF<<20);

ADC1->SQR1|=0<<20;        //1个转换在规则序列中 也就是只转换规则序列1  

ADC1->SMPR2&=~(7<<3);  //通道1采样时间清空          

ADC1->SMPR2|=7<<3;        //通道1  239.5周期,提高采样时间可以提高精确度         

ADC1->SQR3&=0XFFFFFFE0; //规则序列1 通道ch

ADC1->SQR3|=ch;                                           


5) 开启 AD 转换器,并校准。

ADC1->CR2|=1<<0;          //开启AD转换器       

ADC1->CR2|=1<<3;          //使能复位校准 

while(ADC1->CR2&1<<3); //等待校准结束                          

//该位由软件设置并由硬件清除。在校准寄存器被初始化后该位将被清除。                 

ADC1->CR2|=1<<2;          //开启AD校准      

while(ADC1->CR2&1<<2);  //等待校准结束


6) 读取 ADC 值。

ADC1->CR2|=1<<22;          //启动规则转换通道

while(!(ADC1->SR&1<<1));  //等待转换结束                 

return ADC1->DR;                //返回adc值


发现ADC的模式还真多,也同时发现调用库函数的方便性和可读性,用库函数来配置简单很多,自己用单次转换模式显示出8路的采集,不过明显这种处理方式不太好,ADC也属于常用而已比较有难度的内容,后面打算学了DAC和DMA再试试ADC的其他模式,下面是显示效果,ADC8路采集


我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2024-11-22 07:11

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表