学习精英版的学习笔记

2019-07-21 02:25发布

本帖最后由 xiatianyun 于 2018-6-4 09:42 编辑

刚看到有前人发布自己学习中的持续更新贴作为鞭策自己不断学习的方法,我也做个学习笔记,把过程中的点点滴滴写下来。
其实写笔记我一直使用复制粘贴保存在电脑里,但最近我喜欢上了用笔写在笔记本上,觉得更有价值更有效率。
--------------------------
先来谈谈STM32的常识。
ARM公司其实不生产芯片,靠什么赚钱呢?靠授权。ARM研发内核然后授权给各个芯片厂家,靠授权费用赚钱,这个是个好注意。毕竟研发和生产在环节上很不同,一家科技公司很难占据产品的各个环节,占据了也就作茧自缚了,就像昔日的诺基亚。
但毕竟内核只是芯片的一部分,内核之外叫做片上外设,意思就是芯片上的相对于内核而言的外部设备,比如GPIO。
芯片厂家根据内核自己设计相关的片上外设成为单片机,如果没有统一的标准那么各个厂家的芯片就不能通用。
意法半导体公司就是其中一家授权生产ARM的公司,当然意法的产品不只ARM芯片。
芯片内核也是升级换代的,我以前知道的ARM其实是比较早的内核产品。精英版的STM32是比较新的内核,叫Cortex-M3,手机上的叫Core-A,A7、A8之类,是很先进的东西。
微控制器芯片Cortex-M3、M4、M7,一个比一个先进。
-------------------------------
事实上使用汇编编写程序已经变得很难了,51时代就有使用C来设计51程序了。学校里面曾看到高年级同学使用手操器来下载程序,那种是在纸上写好汇编程序然后自己手动通过手操器(不知道叫什么合适)一条条下载到试验机里面的,连个电脑软件都没有。想来那种手操器兼具汇编和烧制的功能。我是学PLC的,我以前也是这么学习PLC的。C语言是比较底层的语言,用来编写单片机程序就理所当然了。单片机的C语言采用的编译器和普通的C语言不同,可以把单片机的C语言看作是C的一个子集,并不是所有C的特性均支持。
要想用C来写单片机程序当然要做些底层封装,可以自己封装,事实上芯片公司提供了标准封装,ST公司提供的STM32库函数就是这样的封装。
------------------------------
Keil公司是另外一家公司,是软件公司,提供开发单片机的工具,从51到ARM均有。
ST公司也有自己的开发工具,为什么我们都用Keil呢?业有所专嘛。
Keil 提供的STM32开发工具似乎换了个名字,叫MDK,不过装好后还是叫Keil。
Keil不是免费的,以前很难找到破解的Keil,现在我们使用Keil,所以我们都懂了。
Keil是个IDE工具,除了编辑程序外还提供编译器,并且通过简单选择就配置好了开发链路,不用自己配置,类似与其他专有语言开发工具,比如微软的VS。
换句话说如果开发者要自己配置开发链路的话也是可以的,只要够专业。通过查看,安装好的Keil在目录下的ARMARMCCin里就是编译工具,其他 目录里面提供单片机的include、lib。
为什么还要从ST官网下载库呢?官网的比较新嘛。Keil提供的库我想也是ST提供的,不过可能不会同步更新。
Keil的编辑器不好用,不过keil提供从其他编辑器来操作的可定制菜单,看来Keil还是知道自己的短板的。
现在比较不错的编辑器有gvim/sublime text/vs code,不过这些都是普通编辑器,如果要在开发中实现跳转、自动完成的话需要插件,这些都是学习成本,就看值不值了。
补充:
其实开发STM32除了在windows平台外还有Linux平台和Mac平台也可以,这对于极客来说是个好消息。具体看ST公司提供的开发套件,从我了解的信息看是集成在Eclipse的开发环境。
----------------------------------
学习STM32除了软件外其实很重要的还有硬件,电子工程这类。我学习STM32纯属偶然,我在PLC设计中需要使用一块带Modbus RTU通讯的中继板来做扩展,这块中继板设置通讯奇偶校验时只能使用固定的无校验,这让我很郁闷,系统中其他站都要设置成无校验才行。所以有了我是不是可以修改下的冲动。电子工程没弄过,这个是个拦路虎,我想我以后如果中断学习了可能是因为这个。









友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。
该问题目前已经被作者或者管理员关闭, 无法添加新回复
50条回答
xiatianyun
1楼-- · 2019-07-26 04:15
本帖最后由 xiatianyun 于 2018-6-23 20:01 编辑

谢谢 warship 的指点!
今天得空分析整理了这段时间对key/SysTick的学习,来个综合训练。我把SysTick做成系统共享的定时器脉冲生成器,用来产生1ms脉冲信号,需要用到定时器时使用计数方式累积1ms脉冲直至达到预置时间脉冲数时定时到达。
这种定时方式的误差在1ms之内,正负可以达到0.5ms。我认为在大多数工业场合已经比较精确了,可以用于大多数普通应用。如果需要高精度定时当然要使用系统提供的其他类型的定时中断。
首先定义SysTick的装载值RELOAD:
[mw_shl_code=c,true]// 500us装载值。
// 500us装载值是1ms装载值的一半。
// 72000000/2000=36000.
#define RELOAD_500US    SystemCoreClock/2000U
[/mw_shl_code]
然后是设置SysTick:
[mw_shl_code=c,true]// 如果采用1ms脉冲方式,则以500us设置SysTick.
void delay_Init(void)
{
    // SysTick配置,等待完成。
    if (SysTick_Config(RELOAD_500US))
    {
        while(1);
    }
}
[/mw_shl_code]
定义配合SysTick使用的定时计数结构,其中bPlus_ms是1ms脉冲信号,而uTimer_ms是我另一种定时方式使用的变量,这里无用。
[mw_shl_code=c,true]// 延时器,配合SysTick使用。
static struct {
    u32 uTimer_ms;  // ms延时器,用于查询延时。
    bool bPlus_ms;  // ms脉冲信号,0.5msON,0.5msOFF.
}Timer;
[/mw_shl_code]
设计SysTick中断服务函数,这个函数在库文件中提供了一个空函数,可以直接在库文件中修改,我采用注释掉库函数然后在delay.c中定义。
[mw_shl_code=c,true]// SysTick定时器中断
void SysTick_Handler(void)
{
    // 500us定时中断到,使Timer.bPlus_ms翻转一次。
    Timer.bPlus_ms = !Timer.bPlus_ms;
}
[/mw_shl_code]这样,当初始化SysTick后Timer.bPlus_ms会自动生成1ms等宽脉冲信号。
-------------------------------------------------
接着使用这个脉冲来定时:
定义计数型定时器使用到的数据结构类型,用来定义static的定时器。
[mw_shl_code=c,true]// 脉冲型定时器结构
typedef struct {
    u32  uEt; // 定时计数当前值。
    bool bTemp; //脉冲暂存信号。
    bool bQ;    //定时时间到标识。
}TimerType;
[/mw_shl_code]
之后可以用来声明定时器,比如定义两个定时器:
[mw_shl_code=c,true]// 定时两个定时器。
static TimerType Timer1, Timer2;
[/mw_shl_code]
设计操作定时器的函数,比如需要延时ON定时器:
[mw_shl_code=c,true]// 延时接通定时器
// 当bEnb为TRUE时开始定时,定时单位为1ms。
// 当bEnb为FALSE时复位定时器。
// 当定时到达后如果没有复位定时器则定时器当前计数值uEt保持不变。
bool timeON(bool bEnb, u32 uPt, TimerType *timer)
{
    if(!bEnb){
        timer->uEt = 0U;
        timer->bTemp = FALSE;
        timer->bQ = FALSE;
        return FALSE;
    }
    else{
        if((timer->uEt < uPt) && (Timer.bPlus_ms) && (!timer->bTemp))
            timer->uEt = timer->uEt + 1;
        timer->bTemp = Timer.bPlus_ms;

        if((timer->uEt >= uPt)){
            timer->bQ = TRUE;
            return TRUE;
        }
        else{
            timer->bQ = FALSE;
            return FALSE;
        }
    }
}[/mw_shl_code]比如使定时器Timer1定时15s:
[mw_shl_code=c,true]// 定义定时器。
static TimerType Timer1;
bool bT1_Enb;
....
timeON(bT1_Enb, 15000U, &Timer1);
//通过控制bT1_Enb来使Timer1开始定时及复位。[/mw_shl_code]注意,定时值不超过32位无符号整数范围,也即4294967s又295ms。





xiatianyun
2楼-- · 2019-07-26 07:20
本帖最后由 xiatianyun 于 2018-6-23 20:11 编辑

示例:
使用timeON延时闭合定时器实现LED0的500ms亮1000ms灭,如此循环。
[mw_shl_code=c,true]/*********************************************************
  功能: 内核SysTick试验
  描述: 精英版STM32的SysTick定时器试验。
  设计: azjiao
  版本: 0.1
  日期: 2018年06月21日
*********************************************************/

/* Includes ------------------------------------------------------------------*/
#include <stdio.h>
#include "stm32f10x.h"
#include "template.h"
#include "key.h"
#include "led.h"
#include "beep.h"
#include "delay.h"
void assert_failed(uint8_t* file, uint32_t line)
{
    //printf("Param Error! in file name: xxx, in line %d ",line);
    //while(1);
}

// 定义两个定时器。
static TimerType Timer1, Timer2;

int main(void)
{
    //bool bSwitch = TRUE;
    bool bT1_Enb = FALSE, bT2_Enb = FALSE;  //定时器使能。
    bool bLED0Round = FALSE; // LED0 定时亮灭开关。

    //板载Key初始化。
    key_Init();

    //板载Led初始化。
    led_Init();

    //板载beep初始化。
    beep_Init();

    // SysTick初始化,开始定时。
    delay_Init();

    /* Infinite loop */
    while (1)
    {
        bool bIsKey0;
        bool bIsKey1;
        bool bIsWKUP;

        bIsKey0 = key0_Scan(TRUE);
        bIsKey1 = key1_Scan(TRUE);
        bIsWKUP = WKUP_Scan(FALSE);
        if(bIsWKUP)
            bLED0Round = !bLED0Round;

        if (bLED0Round){
            LED1_ON;
        }
        else
            LED1_OFF;

        //BEEP发声、禁声。
        if( bIsKey0 || bIsKey1 )
        {
            BEEP_ON;
        }
        else
        {
            BEEP_OFF;
        }

        timeON(bT1_Enb, 500U, &Timer1);
        timeON(bT2_Enb, 1000U, &Timer2);

        if(bLED0Round){
            if(!Timer1.bQ)
            {
                bT1_Enb = TRUE; //Timer1开始定时。
                LED0_ON;
            }
            if(Timer1.bQ && !Timer2.bQ){
                bT2_Enb = TRUE;
                LED0_OFF;
            }
            // 当Timer2定时到时复位两个定时器。
            if(Timer2.bQ){
                bT1_Enb = FALSE;
                bT2_Enb = FALSE;
            }
        }
        else {
            bT1_Enb = FALSE;
            bT2_Enb = FALSE;
            LED0_OFF;
        }
    }
}
[/mw_shl_code]
这段程序定时器单独运行,不影响其他操作,不过key没有做防抖动处理,感觉需要优化。
xiatianyun
3楼-- · 2019-07-26 07:55
按键防抖动处理:
我在教程基础上进行了一些优化,key1/key2/wkup可以独立同时操作,互不影响,而且防抖动更合理了。以WKUP处理为例(三个按键独立检测):
[mw_shl_code=c,true]//WKUP 扫描程序
//bSusKey==TRUE:连续按下可以扫描到连续多个值。
//bSusKey==FALSE:连续按下只扫描到一个按键值。
bool WKUP_Scan( bool bSusKey )
{
        //按键上升沿检测暂存信号。
    static bool bsKey_uPTmp = FALSE;
    //防抖动检测定时器。
    static TimerType timer;
    //经过防抖处理后检测到按键压下。
    bool bKeyPress;
    //返回值。
    bool bRet = FALSE;

        assert_param(Is_BOOL(bSusKey));

    // 防抖处理。
    bKeyPress = timeON(WKUP_CODE, 150U, &timer);

    if(bKeyPress) //如果按键按下。
    {
        if( !bsKey_uPTmp ) //检测到是上升沿
        {
            bRet = TRUE;
            if ( !bSusKey )  //按键状态暂存.
                bsKey_uPTmp = TRUE;
        }
    }
    else  //如果按键没有按下
        {
        bsKey_uPTmp = FALSE;
        }

    return bRet;
}
[/mw_shl_code]
防抖动的代码是:[mw_shl_code=c,true]// 防抖处理。
bKeyPress = timeON(WKUP_CODE, 150U, &timer);[/mw_shl_code]
当WKUP_CODE按下时返回键值,键值非0。如果没有按下则返回0。
如果按键持续按下超过150ms则认为按键有效,如果在150ms内发生按键释放则认为按键抖动,按下无效。
经过实测,150ms除了可以防止抖动外还能让按下比较顺滑。如果防抖定时比较长则按键需要按下较长时间,体验不好。

xiatianyun
4楼-- · 2019-07-26 08:23
本帖最后由 xiatianyun 于 2018-6-26 09:37 编辑

学习中遇到了各种C语言的一些语法需要学习,很长时间没有使用C语言了,这些是遇到的坑。
1、keil C居然不支持在for中声明循环变量:
for(int i = 0; i < 100; i++){}
必须先声明i:
int i;
for(i = 0; i<100; i++){}
2、如果中途局部块只有一句,则不能用使用;结尾。
if(...)
   i++ --->写成i++;就错误了。
else
   data=...;
或者可以用{}包围,如果只有一句则显得比较花。
-------------------------------------
后续:
第2个问题其实不存在,是我的错误导致的。原因是这条单独的语句是宏,我在宏定义中错误地添加了一个结束符';'。
warship
5楼-- · 2019-07-26 14:14
 精彩回答 2  元偷偷看……
xiatianyun
6楼-- · 2019-07-26 19:14
 精彩回答 2  元偷偷看……

一周热门 更多>