做单片机开发久了,发现用的单片机也多,现在每个厂家都提供对应的库以方便加快客户的开发。但是总是有那么一些人,不习惯用官方的库,因为厂家太多,每家写代码的规范都不一样,A厂的单片机是一个规范,B厂的单片机可能又是一个规范,C厂的又会是其他的规范,这样看起来就有点头大了。对于我们这种习惯了使用寄存器开发的老鸟,我有时候就干脆不用官方的头文件,自己定义头文件,统一规范。又由于一般写单片机程序还都是要看寄存器用户手册的,于是干脆自己动手写头文件吧,而且我发现自己定义寄存器相关头文件,可以更加深刻的了解单片机。
下面以MM32L0xx系列单片机的UART模块为例进行介绍。
首先我们根据寄存器用户手册的UART模块的寄存器概况,定义一个UART类型struct UartType,然后定义好模块访问指针pstUart1和pstUart1 如下:
739a7966525c453886fff04842145700~noop.image?_iz=58558&from=article.jpg
MM32L0xx系列单片机UART寄存器概览
/**
  • MM32L0xx串口硬件寄存器定义
  • */
  • struct UartType{
  •   __IO uint32_t vuiTdr;
  •   __I  uint32_t vuiRdr;
  •   __I  uint32_t vuiCsr;
  •   __I  uint32_t vuiIsr;
  •   __IO uint32_t vuiIer;
  •   __O  uint32_t vuiIcr;
  •   __IO uint32_t vuiGcr;
  •   __IO uint32_t vuiCcr;
  •   __IO uint32_t vuiBrr;
  •   __IO uint32_t vuiFra;
  • //  __IO uint32_t vuiRxAddr;
  • //  __IO uint32_t vuiRxMask;
  • //  __IO uint32_t vuiScr;
  • };
  • /** 操作MM32L0xx串口硬件的指针*/
  • #define pstUart1    ((struct UartType *) DE_Uart1BaseAddress)
  • #define pstUart2    ((struct UartType *) DE_Uart2BaseAddress)
  • 复制代码
    df888cb0add740719cad70b3a3dd9a91~noop.image?_iz=58558&from=article.jpg
    UART模块类型定义

    结构体里的vuiTdr就对应手册里的UART发送寄存器UART_TDR,我这里有个习惯就是,寄存器的相关定义我都在变量名前面加字母v,阅读代码的时候就可以很清楚的知道这是一个寄存器。然后后面的ui表示unsigned int,用来表示这是一个32位宽的变量。后面跟的Tdr就是表示寄存器UART_TDR了,我这里是习惯将首字母大写。这也是我的一个习惯,寄存器名首字母大写,查看的时候可以很清楚的知道是什么寄存器,寄存器名前面的vui指出了这是一个32位的寄存器。这样看一个变量名就可以很清楚的知道这个变量的相关属性和含义。
    后面再根据实际的寄存器中的位,写出对应位的宏定义,这里以UART通用控制寄存器(UART_CCR)为例进行介绍。
    2a38599bbc3d4036aa7d4b911d1266a7~noop.image?_iz=58558&from=article.jpg
    MM32L0xx系列单片机UART通用控制寄存器(UART_CCR)

    首先我们看校验使能位PEN,我们可以做这样的定义#define DE_UartCcrPen (1ul),这里又来介绍我的一个习惯,我习惯宏定义的时候在其前面加DE_前缀来表明这是一个宏定义,这个也是方便在查阅代码的时候一眼就发现这是一个宏,对于一个很久之前写的代码做修改,通常只要看到宏的地方肯定是可以修改的地方。DE_后面跟着Uart表示这是一个和UART串口相关的宏定义,后面的Ccr就表示这是UART的通用控制寄存器UART_CCR,再后面的Pen,就表示手册里的校验使能位PEN。最后的(1ul)表示这个位在位0的位置,加ul表示这个位是一个32位寄存器中的位,当然这里也可以写成(1ul<<0)。同样的写法定义好其他位的宏定义如下:
    /** MM32L0xx串口通用控制寄存器位定义*/
  • #define DE_UartCcrPen (1ul)
  • #define DE_UartCcrPsel (1ul << 1)
  • #define DE_UartCcrSpb (1ul << 2)
  • #define DE_UartCcrBrk (1ul << 3)
  • #define DE_UartCcrChar (3ul << 4)
  • 复制代码
    3306c911e5c64203a37668fae6cd945f~noop.image?_iz=58558&from=article.jpg
    UART的通用控制寄存器UART_CCR字段位定义

    这里再说下这个CHAR字段,这个字段表示了UART的数据位宽,占了2个位,这样我们就可以写成(3ul << 4)。对于这种,我们可以配置这2位来指定串口的数据位是5位还是6位,或者7位,8位。这样就相当于一个配置参数,我们怎么配置呢。请看下面:
    /** 串口数据位宽配置参数*/
  • #define DE_UartCcrChar5Bit (0ul)
  • #define DE_UartCcrChar6Bit (1ul << 4)
  • #define DE_UartCcrChar7Bit (2ul << 4)
  • #define DE_UartCcrChar8Bit (3ul << 4)
  • 复制代码
    ab1c7a9d9f084843b53daba6d472e6ec~noop.image?_iz=58558&from=article.jpg
    串口数据位宽配置参数定义

    看到上面的定义是不是很一目了然。
    那么串口用的时候怎么用呢,我就实现了下面的串口配置的方法:
    /**
  •   * @brief  串口配置并使能,先打开串口时钟,复位串口后再配置
  •   * @note   要先配置好使用串口的引脚复用,再调用此函数初始化配置串口,串口才能正常使用,在没有复用串口引脚前,串口引脚数据会乱码
  •   * @param  [in] pstUart 要初始化的串口对应的硬件地址指针
  •   * @arg    pstUart1, pstUart2
  •   * @param  [in] uiBaudRate 要配置的波特率(分配置整数和小数部分)
  •   * @param  [in] ucDataBits 要配置的数据位
  •   * @arg    DE_UartCcrChar5Bit, DE_UartCcrChar6Bit, DE_UartCcrChar7Bit, DE_UartCcrChar8Bit
  •   * @param  [in] ucStopBits 要配置的停止位
  •   * @arg    DE_UartCcrSpb1Bit, DE_UartCcrSpb2Bit
  •   * @param  [in] ucParity 要配置的校验
  •   * @arg    DE_UartCcrPenNone, DE_UartCcrPenOdd, DE_UartCcrPenEven
  •   * @param  [in] uiMode 要配置的串口模式,可以是可选参数的或组合
  •   * @arg    DE_UartGcrDmamode, DE_UartGcrRxen, DE_UartGcrTxen
  •   * @param  [in] ucFlowControl 要配置的硬件流控
  •   * @arg    DE_UartGcrAutoflowerDisable, DE_UartGcrAutoflowerEnable
  •   * @retval None
  •   */
  • void vUartConfig(struct UartType * pstUart,uint32_t uiBaudRate,uint32_t uiDataBits,\
  •         uint32_t uiStopBits,uint32_t uiParity,uint32_t uiMode,uint32_t uiFlowControl);
  • 复制代码
    ef7de7f1cb164c09b0695a7371eaa7f8~noop.image?_iz=58558&from=article.jpg
    串口配置函数功能使用说明

    这里的函数功能使用说明是参考Doxygen注释规范来写的,可以通过软件自动生成帮助手册。然后针对对应的参数,我还加入了可选参数列表到注释中。比如我们要使用串口1进行波特率为115200bps,8位数据位,一个停止位,无校验位,无流控的配置,可以调用下面的代码。
    vUartConfig(pstUart1,(uint32_t)115200ul,DE_UartCcrChar8Bit,DE_UartCcrSpb1Bit,DE_UartCcrPenNone,(DE_UartGcrRxen | DE_UartGcrTxen),DE_UartGcrAutoflowerDisable);
  • 复制代码
    看这个代码就可以很清楚的知道串口的配置,这里函数前加的v表示void,表示这个函数是一个无返回数据类型的一个函数。下面是我个使用规范的一些示例,仅供参考:
    变量定义示例:
  • char cName = ‘a’;
  • int iNum = 0;
  • short sNum = 0;
  • long lNum = 0;
  • unsigned char ucCode = 0xff;
  • unsigned int uiLength = 66666;
  • unsigned short usLength = 300;
  • unsigned long ulLength = 7777777;
  • char * pcName = 0;
  • int * piNum = 0;
  • short * psNum = 0;
  • long * plNum = 0;
  • unsigned char * pucCode = 0;
  • unsigned int * puiLength = 0;
  • unsigned short * pusLength = 0;
  • unsigned long * pulLength = 0;
  • const char ccName = ‘a’;
  • const int ciNum = 0;
  • const short csNum = 0;
  • const long clNum = 0;
  • const unsigned char cucCode = 0xff;
  • const unsigned int cuiLength = 66666;
  • const unsigned short cusLength = 300;
  • const unsigned long culLength = 7777777;
  • const char * cpcName = 0;
  • const int * cpiNum = 0;
  • const short * cpsNum = 0;
  • const long * cplNum = 0;
  • const unsigned char * cpucCode = 0;
  • const unsigned int * cpuiLength = 0;
  • const unsigned short * cpusLength = 0;
  • const unsigned long * cpulLength = 0;
  • char * const pccName = 0;
  • int * const picNum = 0;
  • short * const pscNum = 0;
  • long * const plcNum = 0;
  • unsigned char * const puccCode = 0;
  • unsigned int * const puicLength = 0;
  • unsigned short * const puscLength = 0;
  • unsigned long * const pulcLength = 0;
  • 宏定义示例,所有宏定义前面加DE_
  • #define DE_PictureWide
  • #define DE_OpenIo()
  • 复制代码
    来源:单片机嵌入式爱好者