首页 > 其他分享 >从自定义的库函数到STM32官方标准库

从自定义的库函数到STM32官方标准库

时间:2023-01-11 12:36:23浏览次数:47  
标签:GPIOB Pin 自定义 引脚 GPIOx STM32 GPIO 库函数


  在​​上一节​​的基础上,进一步改写代码,再引入官方标注库函数。虽然官方标准库慢慢式微,有一些别的库可能会取代它,但是并不妨碍我们继续拿官方库来写代码,吸取里边好的写法,强化下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<<st->pin*4);
temp = (st->cny<<2)|(st->mode);
GPIOx->CRL |= temp<<st->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<<i*4);
temp = (st->cny<<2)|(st->mode);
GPIOx->CRL |= temp<<i*4;
}
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,则不对其它位产生影响。

  看数据手册

从自定义的库函数到STM32官方标准库_库函数


  跟它类似的有个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编程,库函数远比寄存器方便。所谓的库函数,就是封装寄存器,提供接口给用户调用。如果是需要快速开发的时候,可以直接使用库函数,不必纠结于是怎样实现的,要大胆的拿来就用。但是,学习的时候,还是要考虑下库函数的实现方式,库函数是经过千锤百炼的优秀代码。因此我们花费巨大的精力,自己实现了简单的库函数,就是为了让大家明白,库函数不神秘,如果需要,我们自己就能写出来。以后使用其它的平台,也能根据数据手册,操作寄存器,实现功能,并把代码封装,优化,这才是最好的结果。
  项目式开发要避免重复造轮子,要尽快完成目标,而不是把时间浪费在不必要的细节上。库函数与寄存器两种开发方式并不对立,哪个方便用哪个即可。不同类型的库也是,那个方便用哪个即可。


标签:GPIOB,Pin,自定义,引脚,GPIOx,STM32,GPIO,库函数
From: https://blog.51cto.com/u_12001544/6002119

相关文章

  • STM32掌机教程2,掌机的原理
    打地鼠的逻辑  打地鼠的逻辑很简单:我点亮某个LED,然后开始计时。如果在规定的时间内按下了LED对应的按键,那么加分,点亮下一个LED。按错按键,或者规定时间内没有按下,则减命,点......
  • STM32掌机教程3,工程模板与带灯按键测试
    我们需要“脚手架”  关于代码,我想体现出这么一个过程:我是如何一步一步修改代码的。我认为,从学习的角度来考虑,直接看最终的代码没有什么意义。写代码就像工人盖房子,盖房......
  • STM32自带GB2312字库显示汉字
      本文介绍如何把字库文件写入单片机的flash中,然后无需再提取字模,即可实现单片机显示中文字符的功能。下载字库到单片机flash中的指定位置  在​​上一篇博客​​中,最后......
  • STM32HAL库定时器中断关闭的方法
    本文可以用于解决这个问题:定时器中断上电后莫名其妙开启,或者首先触发一次。STM32HAL库在定时器初始化并开启以后,也是会默认开启中断。如不需要默认就开启中断可以用一下代......
  • HAL库教程2:使用STM32CubeMX新建一个工程
    安装STM32CubeMX  安装STM32CubeMX之前,电脑中要有java运行时环境(JRE),否则会报错:  双击JavaSetup8u201.exe即可安装JRE。在安装过程中,需要在线下载一些资源,所以应当保持......
  • HAL库教程1:STM32Cube的介绍
      使用STM32HAL库已经有了一段时间,觉得相比于标准库,好用了不少。加上STM32CubeMX图形化配置工具的加持,个人认为可以极大提升开发效率。其实关于HAL库的教程已经很多了,关于......
  • HAL库教程7:STM32的时钟系统
      STM32的时钟系统比较复杂,不像51单片机,可能只有一个时钟。原因:1、外设多,不同的外设有不同的时钟要求;2、功耗原因,速度越快,功耗越大,抗电磁干扰能力越弱。此处以STM32F405......
  • Axure 自定义元件库
    点击文件->新建元件库可以添加多个元件,并将期重命名保存元件库新建页面添加元件,选择自建的元件库导入后就会发现我的原件库这样就可以使用我们自定义的元件......
  • 进阶阶段——STM32学习笔记(一)
    进阶阶段——STM32学习笔记(1)前言由于套件放在学校,待等假期结束后才能做实验0STM32简介注意:STM32的标准工作电压为3.3V,若用5V供电,需要用(电平转换电路)稳压芯片降压至3.3......
  • STM32 - 中断
    1.中断定义(Event)中断(Interrupt):指当出现需要时,CPU暂时暂停当前程序的执行,转而执行处理新情况的程序的处理过程。即在程序运行过程中,系统出现了一个必须由CPU立即处理......