历史上的今天
返回首页

历史上的今天

今天是:2025年05月31日(星期六)

2019年05月31日 | 从自定义的库函数到STM32官方标准库

2019-05-31 来源:eefocus

  在上一节的基础上,进一步改写代码,再引入官方标注库函数。虽然官方标准库慢慢式微,有一些别的库可能会取代它,但是并不妨碍我们继续拿官方库来写代码,吸取里边好的写法,强化下C语言技能,加深对寄存器的理解也是不错的。

  本文模仿库函数,首先自定义库函数,然后一步一步改写代码,最终引入官方标准库函数。


实现流水灯

void delay(unsigned int a)

{

    while(a--);

}


int main(void)

{

    RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;

    GPIOB->CRH &= ~(0xf<<(0*4)|0xf<<(1*4));

    GPIOB->CRH |= 0x3<<(0*4)|0x3<<(1*4);    

    while(1)

    {   

        GPIOB->ODR &= ~(1<<8);  //PB8 = 0

        GPIOB->ODR |= 1<<9;         //PB9 = 1

        delay(0xfffff);

        GPIOB->ODR &= ~(1<<9);   //PB9 = 0

        GPIOB->ODR |= 1<<8;      //PB8 = 1

        delay(0xfffff);

    }    

}



  主要是增加了延时函数与while(1)的循环。通过寄存器,实现流水灯。这是单片机最基础的实验,意义等同于helloworld。这些代码如果有51基础,是很好理解的。但是存在问题:代码的扩展性与维护性较差,存在较多的复制粘贴,不太容易看懂,如果出现业务变更,如LED的引脚换了,那么修改工作太大。

  接下来我们来自己从零开始,写一个库函数,目的是提高代码的扩展性和可维护性,具体表现是:

1方便移植,比较通用。

2读起来没那么费劲,不用翻着手册来读。


自定义IO初始化函数

  在不考虑时钟的情况下,配置一个IO口(也就是引脚)需要知道以下信息:

  1 PORT PA还是PB

  2 PIN PB1还是PB2

  3 模式 输入还是输出?

  我们可以把这几个信息作为函数的参数,用一个函数来进行IO的初始化。例如初始化PB0位2Mhz的推挽输出:


void easy_IO(GPIO_TypeDef *GPIOx,char pin,char cny,char mode)

{

    uint8_t temp;

    if(mode != shuru)

    {

        GPIOx->CRL &= ~(0xf << pin*4);

        temp =(cny<<2)|(mode);

        GPIOx ->CRL |= temp << (pin*4);

    }

}

easy_IO(GPIOB,0,tuiwan,MHZ2);//调用


  用这种方法可以比较方便地进行引脚的初始化,弊端在于,参数太多,容易出错。有没有更好的传参数的方法?

使用结构体

  首先把需要的信息都放在一个结构体内。


typedef struct

{

    Uint16_t pin;

    uint8_t mode;//输入还是输出,速度

    uint8_t cny;//输出模式

}GPIO_InitST;


  然后定义两个枚举类型,为参数可能的取值起一个好理解的名字:


typedef enum

{

    shuru = 0x00,

    MHZ10,

    MHZ2,

    MHZ50,

}ModeEm;

typedef enum

{

    tuiwan = 0x00,

    kailou,

    futuiwan,

    fukailou,

}CNYEm;


  最后定义一个函数,用于IO的初始化:


//自定义IO初始化函数

void myGPIO_Init(GPIO_TypeDef* GPIOx,GPIO_InitST* st)

{

    if(st->mode != shuru)//暂时先只处理输出的情况

    {

        uint8_t temp;

        if(st->pin<8)//P0-P7用CRL

        {

            GPIOx->CRL &= ~(0xf<pin*4);

            temp = (st->cny<<2)|(st->mode);

            GPIOx->CRL |= temp<pin*4;

        }

        else//P8及以上是CRH

        {

            GPIOx->CRH &= ~(0xf<<(st->pin-8)*4);

            temp = (st->cny<<2)|(st->mode);

            GPIOx->CRH |= temp<<(st->pin-8)*4;              

        }

    }

}


  主函数中IO的初始化可以直接调用函数:


int main(void)

{

    GPIO_InitST myst;

    RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;

        

    myst.pin = 8;

    myst.mode = MHZ50;

    myst.cny = tuiwan;

    myGPIO_Init(GPIOB,&myst);


    myst.pin = 9;

    myGPIO_Init(GPIOB,&myst);

    while(1)

    {   

        GPIOB->ODR &= ~(1<<8);  //PB8 = 0

        GPIOB->ODR |= 1<<9;         //PB9 = 1

        delay(0xfffff);

        GPIOB->ODR &= ~(1<<9);   //PB9 = 0

        GPIOB->ODR |= 1<<8;      //PB8 = 1

        delay(0xfffff);

    }    

}


  编译程序,下载观察现象,跟流水灯一样。


使用独热码操作多个引脚

  有两个不同引脚的话,需要调用两次初始化函数,能不能调用一次初始化的函数就初始化两个引脚?可以,代码仍然有改进的空间。

  使用独热码one-hot code, 直观来说就是有多少个状态就有多少比特,而且只有一个比特为1,其他全为0的一种码制。

  如果引脚0定义为0x01,引脚1定义为0x02,引脚2定义为0x04(而不是0x03),那么0x07就可以代表这三个引脚。想同时初始化三个引脚,只需要传入一个参数,0x07。


//自定义IO初始化函数

void myGPIO_Init(GPIO_TypeDef* GPIOx,GPIO_InitST* st)

{

    uint16_t i=0, j = 0, pflg = 0;

    uint8_t temp;

    if(st->mode != shuru)//暂时先只处理输出的情况

    {

        for(i = 0 ; i < 16 ; i++)

        {

            j = 0x01 << i;

            pflg = st->pin & j;

            if (pflg == j)

            {

                if(i<8)//P0-P7用CRL

                {

                    GPIOx->CRL &= ~(0xf<                    temp = (st->cny<<2)|(st->mode);

                    GPIOx->CRL |= temp<                }

                else//P8及以上是CRH

                {

                    GPIOx->CRH &= ~(0xf<<(i-8)*4);

                    temp = (st->cny<<2)|(st->mode);

                    GPIOx->CRH |= temp<<(i-8)*4;                

                }

            }

        }

    }

}



  以上代码,主要思想是通过一个for循环,将传入的参数按位取出,并判断这一位是0还是1。如果是1,则需要对这一位对应的IO进行操作。

  然后主函数的调用也需要做相应的修改,一次传入PB8与PB9两个引脚。


#define Pin_8                 ((uint16_t)0x0100)  /*!< Pin 8 selected */

#define Pin_9                 ((uint16_t)0x0200)  /*!< Pin 9 selected */

int main(void)

{

    GPIO_InitST myst;

    RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;

        

    myst.pin = Pin_8| Pin_9;

    myst.mode = MHZ50;

    myst.cny = tuiwan;

    myGPIO_Init(GPIOB,&myst);


    while(1)

    {   

        GPIOB->ODR &= ~(1<<8);  //PB8 = 0

        GPIOB->ODR |= 1<<9;         //PB9 = 1

        delay(0xfffff);

        GPIOB->ODR &= ~(1<<9);   //PB9 = 0

        GPIOB->ODR |= 1<<8;      //PB8 = 1

        delay(0xfffff);

    }    

}


改写引脚操作

  在使用ODR寄存器的时候,总是需要关注那些不应该被操作的引脚,怕误操作。现在由51单片机的思维改为STM32的思维。STM32提供了特别丰富,好用的寄存器,例如BSRR(端口位设置/清除)寄存器,对某个位写1代表把对应的IO设置为高电平,其它位写0,则不对其它位产生影响。

  看数据手册

在这里插入图片描述

  跟它类似的有个BRR寄存器,功能是端口位清零。

  借助这两个寄存器,我们可以很方便的写出IO设置为1和IO设置为0的两个函数:


void mySetbits(GPIO_TypeDef* GPIOx,uint16_t pin)//引脚设置1

{

    GPIOx->BSRR = pin;

}

void myResetbits(GPIO_TypeDef* GPIOx, uint16_t pin)//引脚设置0

{

    GPIOx-> BRR= pin;

}


  然后修改主函数内的死循环:


while(1)

    {   

        myResetbits(GPIOB,Pin_8);  //PB8 = 0

        mySetbits(GPIOB,Pin_9);     //PB9 = 1

        delay(0xfffff);

        myResetbits(GPIOB,Pin_9);   //PB9 = 0

        mySetbits(GPIOB,Pin_8);      //PB8 = 1

        delay(0xfffff);

    }   


  最终效果还是一样的,但代码看上去就比较爽心悦目了。事实上,这已经很接近于库函数的代码了。


引入官方库函数

  官方的固件库提供了很多好用的函数,接下来由自定义的固件库转到官方标准固件库。

  官方标准固件库(以后简称官方库)的函数可以查看文档《STM32固件库使用手册的中文翻译版.pdf》

  官方库除了提供了详细的函数说明,还提供了使用的例子。另外,通过函数的跳转,可以看到官方库的源码,这些源码经过千锤百炼,都是学习单片机编程的好榜样。

  与引脚相关的函数都在GPIO章节,读者可以自行查看。新建一个函数,IO_Init()。参考例子,稍作修改就能写完LED引脚的初始化函数。


void IO_Init(void)

{

    GPIO_InitTypeDef GPIO_InitStructure;

     

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);   

  

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_9; 

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;  

    GPIO_Init(GPIOB, &GPIO_InitStructure);

}


int main(void)

{

    IO_Init();

    GPIO_SetBits(GPIOB,GPIO_Pin_8|GPIO_Pin_9);

    while(1)

    {   

        GPIO_ResetBits(GPIOB,GPIO_Pin_8);  //PB8 = 0

        GPIO_SetBits(GPIOB,GPIO_Pin_9);     //PB9 = 1

        delay(0xfffff);

        GPIO_ResetBits(GPIOB,GPIO_Pin_9);   //PB9 = 0

        GPIO_SetBits(GPIOB,GPIO_Pin_8);      //PB8 = 1

        delay(0xfffff);

    }    

}


  我们通过对寄存器的封装,实现了最简单的库函数。使用STM32编程,库函数远比寄存器方便。所谓的库函数,就是封装寄存器,提供接口给用户调用。如果是需要快速开发的时候,可以直接使用库函数,不必纠结于是怎样实现的,要大胆的拿来就用。但是,学习的时候,还是要考虑下库函数的实现方式,库函数是经过千锤百炼的优秀代码。因此我们花费巨大的精力,自己实现了简单的库函数,就是为了让大家明白,库函数不神秘,如果需要,我们自己就能写出来。以后使用其它的平台,也能根据数据手册,操作寄存器,实现功能,并把代码封装,优化,这才是最好的结果。

  项目式开发要避免重复造轮子,要尽快完成目标,而不是把时间浪费在不必要的细节上。库函数与寄存器两种开发方式并不对立,哪个方便用哪个即可。不同类型的库也是,那个方便用哪个即可。


推荐阅读

史海拾趣

DENWIRE公司的发展小趣事

以下是关于DENWIRE公司在电子行业中发展起来的五个相关故事,每个故事都遵循了字数和事实性的要求。

故事一:
DENWIRE公司起初只是一家小型的电线制造商,专注于生产低端音频线缆。然而,随着科技的进步和消费者对于音频质量的追求,公司创始人李先生看到了高品质音频线缆市场的潜力。他带领团队投入大量研发资源,成功开发出了一款具有极低信号损失和出色耐用性的音频线缆,赢得了市场的广泛认可。这款产品不仅让DENWIRE在音频线缆领域崭露头角,也为公司后续的发展奠定了坚实的基础。

故事二:
随着公司规模的扩大,DENWIRE逐渐涉足到了数据传输线缆的生产。在面对激烈市场竞争时,公司坚持创新驱动,注重产品质量。在一次与知名科技公司的合作中,DENWIRE凭借其出色的产品性能和可靠的品质,成功赢得了对方的信任,并签订了长期供货合同。这次合作不仅为DENWIRE带来了可观的收益,也进一步提升了公司在行业内的知名度。

故事三:
在新能源汽车行业蓬勃发展的背景下,DENWIRE敏锐地捕捉到了这一领域的商机。公司迅速调整战略方向,加大了对新能源汽车用线缆的研发和生产投入。经过多次试验和改进,DENWIRE成功开发出了一款符合新能源汽车高标准要求的线缆产品,并在市场上取得了良好的销售业绩。这一成就不仅彰显了DENWIRE的技术实力,也为公司未来的发展开辟了新的道路。

故事四:
面对全球化市场的挑战,DENWIRE积极寻求国际合作与拓展。公司先后与多个国家和地区的知名企业建立了战略合作关系,共同开发新产品、拓展新市场。通过国际合作,DENWIRE不仅学习到了先进的生产技术和管理经验,也进一步提升了自身的品牌影响力和市场竞争力。

故事五:
在环保和可持续发展成为全球共识的背景下,DENWIRE积极响应号召,致力于推动绿色生产和可持续发展。公司投入大量资金引进环保设备和工艺,优化生产流程,减少能源消耗和废弃物排放。同时,DENWIRE还积极参与社会公益活动,回馈社会。这些举措不仅提升了公司的社会形象,也为公司的长期发展注入了新的活力。

Frolyt Condensers & Elements GmbH公司的发展小趣事

面对电子行业的快速变化,Frolyt Condensers & Elements GmbH深知数字化转型的重要性。近年来,公司积极推进数字化转型,引入先进的ERP系统和智能制造技术,实现了生产过程的自动化、智能化和精细化管理。数字化转型不仅提高了公司的生产效率和产品质量,还降低了运营成本,为公司的持续发展奠定了坚实的基础。同时,Frolyt还利用大数据和人工智能技术优化供应链管理,提升了客户服务水平和市场响应速度。

Coherent Inc公司的发展小趣事

2023年,Coherent Inc.在纽交所上市敲钟,标志着公司的发展进入了新的阶段。这一事件不仅彰显了公司的实力和影响力,也为其未来的发展提供了更多的资本支持和发展空间。上市后的公司将继续加大在技术研发和市场拓展方面的投入,推动激光技术的进一步发展。

AMRI Enterprise Co Ltd公司的发展小趣事

AMRI Enterprise Co Ltd注重与高校和研究机构的产学研合作,通过引进先进技术和人才,推动公司的技术创新。公司与多所知名高校建立了紧密的合作关系,共同开展科研项目和技术攻关。这些合作不仅为AMRI带来了前沿的技术成果,还提升了公司的研发能力和创新能力。在产学研合作的推动下,AMRI不断推出具有创新性和竞争力的新产品,为公司的持续发展提供了强有力的支撑。

Avery Dennison公司的发展小趣事

Avery Dennison一直致力于技术创新和产品开发。例如,公司在水胶技术方面取得了显著进展,成功开发出环保且高效的水性乳液聚合物粘胶剂,成为第一家大规模供应这类产品的标签制造商。此外,公司还推出了具有智能功能的RFID标签产品组合,为零售、食品、物流、医疗等行业提供了数字化解决方案。这些创新产品不仅提升了公司的竞争力,也为客户创造了更大的价值。

ERNI Electronics公司的发展小趣事

随着公司规模的扩大和产品线的丰富,ERNI Electronics开始积极拓展全球市场。1980年,公司在美国建立了分公司,为后续成为全球供应商打下了坚实的基础。此后,ERNI在新加坡建立了亚洲区总部,并在中国、台湾、韩国、泰国、马来西亚和印度等地设立了销售办事处。这一系列举措使得ERNI的产品能够覆盖更广泛的市场,满足了全球客户的需求。

问答坊 | AI 解惑

天祥 (protel升级版)Altium Designer 视频教程 5dvd

AD6.9安装程序 AD6.9 Crack.rar AD6.9中文菜单修正文件.rar Altium.Designer.v6.9.12759.rar 免License操作说明.txt Altium Designer官方网站资料 Altium 宣传信息 Altium\'s DEMOcenter Vidios Altium官方资料 Training Manuals ...…

查看全部问答>

【转帖】一个老电子工程师的一些建议

我当电子工程师也是十余年了,不算有出息,环顾四周,也没有看见几个有出息的!回顾工程师生涯,感慨万千,愿意讲几句掏心窝子的话,也算给我们师弟师妹们提个醒,希望他们比我们强!   [1]好好规划自己的路,不要跟着感觉走!   根据个人的 ...…

查看全部问答>

跳槽者的四大“杯具”

跳不?”“危机来袭不能跳!”     “跳吧!”“等形势好些咱在跳!”     “跳啦……”     金融危机的阴霾逐渐散去,职场人越发地按捺不住跳槽的欲望。阳春三月,正值跳槽的黄金期,观望许久的职场人开始行 ...…

查看全部问答>

VC 中结构体定义的一点疑问。

我的定义过程。 1.在1.h文件中第一了一个结构体新的数据类型。 typedef struct {         UINT32 Scope;         UINT32 WaveSpeed;         UINT32 Scale;     &nb ...…

查看全部问答>

请问 bsp for powerpc in vxworks 5.5 在哪里可以下到?

从一家网站的ftp上下的只有12MB, 无法使用。Emule现在也无源。还有哪里可以下到呢? 非常感谢!…

查看全部问答>

带手机充电器的应急灯

充电管理芯片实现充电控制,灯光亮度控制由pwm实现. 单独一个usb接口给手机充电用,电压变换到5V.…

查看全部问答>

如何读懂编译器生成的汇编文件

本帖最后由 dontium 于 2015-1-23 13:40 编辑 想看看编译器到底对程序做了哪些优化,好让自己更好地去改进程序的性能,可是编译器生成的汇编文件里面很多符号和语句确看不懂什么意思。请问该怎么去看呢? …

查看全部问答>

自己做的PCB板调不通 用的是nrf24l01

自己差不多完全按照Nordic公司的24L01模块来做的pCB板,但是做出来后发现根本就收不到,也发送不了,各位支个招啊!急啊!…

查看全部问答>

(转帖)————verilog编码风格

一. 强调Verilog代码编写风格的必要性。 强调Verilog代码编写规范,经常是一个不太受欢迎的话题,但却是非常有必要的。 每个代码编写者都有自己的编写习惯,而且都喜欢按照自己的习惯去编写代码。与自己编写风格相近的代码,阅读起来容易接受和理 ...…

查看全部问答>

LPC1500体验+1.把图搞对之丝印勘误

官网的丝印勘误  ,是不是有让你走弯路的地方…

查看全部问答>