你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

查看: 4283|回复: 11

【JESSE】STM32初学(寄存器版)——GPIO操作[2]

[复制链接]

10

主题

170

回帖

0

蝴蝶豆

金牌会员

最后登录
2020-10-7
发表于 2017-8-22 22:09:33 | 显示全部楼层 |阅读模式


好几个学弟跟我说看不懂我的代码,既然是本着跟初学者一块学习的目的开帖子,那我们就讲的细致一点(小弟献丑,大神勿喷)。

上个帖子上传的附件工程中,我写的C文件主要有两个,一个是“jesse_pin.c”,一个是“jesse_led.c”。
先说说“jesse_pin.c”,这个文件主要是用于操作GPIO,例如初始化,复用设置,写GPIO寄存器和读GPIO寄存器。
  1. /************************************************************************************/
  2. void jesse_gpio_pin_init(uint8_t GPIOx,uint32_t BITx,uint32_t MODE,uint32_t OTYPE,uint32_t OSPEED,uint32_t PUPD)
  3. {  
  4.         GPIO_TypeDef *curgpio=0;
  5.         curgpio = (GPIO_TypeDef*)(AHB1PERIPH_BASE+GPIOx*0x0400U);        

  6.         RCC->AHB1ENR |= 0x01U<<GPIOx;                      //开启相应引脚时钟
  7.         curgpio->MODER&=~(3U<<(BITx*2));                   //先清除原来的设置
  8.         curgpio->MODER|=MODE<<(BITx*2);                    //设置新的模式
  9.         if((MODE==0x01)||(MODE==0x02))                        //如果是输出模式/复用功能模式,则需要设置输出速度和类型
  10.         {  
  11.                 curgpio->OSPEEDR&=~(3U<<(BITx*2));        //清除原来的设置
  12.                 curgpio->OSPEEDR|=(OSPEED<<(BITx*2));   //设置新的速度值  
  13.                 curgpio->OTYPER&=~(1U<<BITx);                 //清除原来的设置
  14.                 curgpio->OTYPER|=OTYPE<<BITx;                 //设置新的输出模式
  15.         }  
  16.         curgpio->PUPDR&=~(3U<<(BITx*2));                     //先清除原来的设置
  17.         curgpio->PUPDR|=PUPD<<(BITx*2);                      //设置新的上下拉
  18. }

  19. /************************************************************************************/
  20. void jesse_device_pin_init(GPIO_PIN_INIT* pin)               
  21. {
  22.         jesse_gpio_pin_init(pin->GPIOx,pin->BITx,pin->MODE,pin->OTYPE,pin->OSPEED,pin->PUPD);
  23. }

  24. /************************************************************************************/
  25. //        设置引脚的复用功能函数
  26. void jesse_gpio_pin_af(uint8_t GPIOx,uint32_t BITx,uint8_t Alternate)
  27. {
  28.         GPIO_TypeDef * curgpio;
  29.         uint8_t regpos,bitpos;
  30.         
  31.         curgpio = (GPIO_TypeDef*)(AHB1PERIPH_BASE+GPIOx*0x0400U);
  32.         regpos  = BITx/8;
  33.         bitpos  = BITx%8;
  34.         curgpio->AFR[regpos] &= ~(0x0f<<bitpos*4);
  35.         curgpio->AFR[regpos] |=  (Alternate<<bitpos*4);
  36. }

  37. /************************************************************************************/
  38. void jesse_device_pin_af(GPIO_PIN_INIT* pin, uint8_t Alternate)
  39. {
  40.         jesse_gpio_pin_af(pin->GPIOx,pin->BITx,Alternate);
  41. }

  42. /************************************************************************************/
  43. //        写引脚函数
  44. void jesse_gpio_pin_write(uint8_t GPIOx,uint32_t BITx,uint32_t Value)
  45. {
  46.         GPIO_TypeDef *curgpio=0;
  47.         curgpio = (GPIO_TypeDef *)(AHB1PERIPH_BASE+GPIOx*0x0400U);
  48.         
  49.         curgpio->ODR &= ~(0x01<<BITx);                                                                                                //清除相应位
  50.         curgpio->ODR |=  Value<<BITx;                                                                                                        //写入相应位
  51. }

  52. /************************************************************************************/
  53. void jesse_device_pin_write(GPIO_PIN_INIT* pin, uint32_t Value)
  54. {
  55.         jesse_gpio_pin_write(pin->GPIOx,pin->BITx,Value);
  56. }

  57. /************************************************************************************/
  58. //        读引脚函数
  59. uint8_t jesse_gpio_pin_read(uint8_t GPIOx,uint32_t BITx)
  60. {
  61.         GPIO_TypeDef *curgpio=0;
  62.         curgpio = (GPIO_TypeDef *)(AHB1PERIPH_BASE+GPIOx*0x0400U);
  63.         
  64.         if((curgpio->IDR&(0x01U<<BITx))==(0x01U<<BITx))                                //读取BITx的状态值
  65.                 return PIN_HIGH;                                                                                                                                                //如果读取的值为1,则返回高                                                                                                                                
  66.         else
  67.                 return PIN_LOW;                                                                                                                                                        //返回低
  68. }

  69. /************************************************************************************/
  70. uint8_t  jesse_device_pin_read(GPIO_PIN_INIT* pin)
  71. {
  72.         return jesse_gpio_pin_read(pin->GPIOx,pin->BITx);
  73. }

  74. /************************************************************************************/
  75. GPIO_PIN_FUN jesse_gpio_pin=
  76. {
  77.         jesse_device_pin_init,
  78.         jesse_device_pin_af,
  79.         jesse_device_pin_write,
  80.         jesse_device_pin_read,
  81. };
  82. /************************************************************************************/
复制代码
有8个函数,其中四个是对另外四个的封装,为什么要添加个四个函数,后面我们可以根据实例讲讲。
我们就拿 jesse_gpio_pin_init 函数讲讲,这个明白了,其他的应该都能明白。
看过正点原子寄存器版本代码的同学应该会觉得眼熟,并不说抄它的,我也是在偶然的机会下才发现我写的这个函数跟他的有那么点像,但是处理机制还是不一样的。

初始化某个管脚,首先要知道的是:这个管脚属于哪个端口,第几个引脚.
1.PNG
我们并没有使用ST文件中关于GPIO的定义,而是根据GPIO_PORT地址的规律使用了自己的查找方式
2.PNG
GPIO的是在AHB1总线外设地址的基础上每隔0x0400往后偏移
3.PNG
如图,我们可以根据“jesse_pin.h”中的宏定义
再结合  curgpio = (GPIO_TypeDef*)(AHB1PERIPH_BASE+GPIOx*0x0400U)  这一句查找到传进来的是哪个端口,为什么这么做,这样子就可以利用移位操作开启该端口的时钟啊,看代码
RCC->AHB1ENR |= 0x01U<<GPIOx;                                                                                //开启相应引脚时钟
再看图
4.PNG
注意这里的GPIOx是上图中的 GPIO_A....等定义(是不是觉得有点取巧,有点绕)
原子使用的则是ST对GPIO的定义,这样子则是在开时钟这一步操作的时候会更麻烦一些。
对于PIN的设置,根据PIN0~15所对应的位在寄存器中的规律进行查找设置。
原子这处理这一步的时候,会比我的简单一些,它的处理可以传入同一个GPIO中的多个PIN,然后查找每个PIN,并对其设置,我的则是不行的。
说到这里,“jesse_pin.c”中的代码基本都能看懂了。
还有就是 jesse_gpio_pin 这个函数指针结构体了,也可以不使用它,直接调用函数。

剩下“jesse_led.c”这个文件,我觉得就初始化那段会让初学者比较懵
  1. /************************************************************************************/
  2. static GPIO_PIN_INIT jesse_led_pin_init[]=
  3. {
  4.         {GPIO_G,PIN6,GPIO_MODE_OUT,GPIO_OTYPE_PP,GPIO_SPEED_2M,GPIO_PUPD_NONE},                        //LED1
  5.         {GPIO_D,PIN4,GPIO_MODE_OUT,GPIO_OTYPE_PP,GPIO_SPEED_2M,GPIO_PUPD_NONE},                        //LED2
  6.         {GPIO_D,PIN5,GPIO_MODE_OUT,GPIO_OTYPE_PP,GPIO_SPEED_2M,GPIO_PUPD_NONE},                        //LED3
  7.         {GPIO_K,PIN3,GPIO_MODE_OUT,GPIO_OTYPE_PP,GPIO_SPEED_2M,GPIO_PUPD_NONE},                        //LED4
  8. };

  9. /************************************************************************************/
  10. #define LED_NUM                (sizeof(jesse_led_pin_init)/sizeof(jesse_led_pin_init[0]))
  11. /************************************************************************************/

  12. const GPIO_PIN_FUN *jesse_led_pin=&jesse_gpio_pin;
  13. /************************************************************************************/
  14. void jesse_LED_Init(void)
  15. {
  16.         uint8_t i=0;
  17.         for(i=0;i<LED_NUM;i++)
  18.         {
  19.                 jesse_led_pin->jesse_device_pin_init(&jesse_led_pin_init[i]);
  20.                 jesse_led_pin->jesse_device_pin_write(&jesse_led_pin_init[i],PIN_HIGH);
  21.         }
  22. }
复制代码
GPIO_PIN_INIT 这个是在头文件中定义的结构体类型
  1. typedef struct
  2. {
  3.         uint8_t  GPIOx;
  4.         uint32_t BITx;
  5.         uint32_t MODE;
  6.         uint32_t OTYPE;
  7.         uint32_t OSPEED;
  8.         uint32_t PUPD;
  9. }GPIO_PIN_INIT;
复制代码
它包含了初始化一个PIN所需要的各个参数(没有引脚复参数)
jesse_led_pin_init[],这是个结构体类型数组,它的每一个元素都是结构体,这样就可以将每个PIN的初始化参数打包访问。
jesse_LED_Init()这个初始化函数则可以将数组中每个PIN进行循环设置,这主要是靠这个宏定义
#define LED_NUM                (sizeof(jesse_led_pin_init)/sizeof(jesse_led_pin_init[0]))
它将计算出数组中PIN的个数,然后以数组下标查找。

还有最后一个问题,不说明白可能会成为一个坑。
这里的每个PIN的MODE我都设置为推挽输出模式,然后我在LED_Toggle函数中读取了寄存器中的内容,PIN一旦设置为推挽输出模式,那么输出的MOS
则有可能会导通,那么PIN将会输出一个稳定的电平,这将影响IDR中的内容。
我们看图
5.PNG
我们这里读取的引脚电平是引脚输出电平,引脚并没有电平信号输入,所以我们设置为推挽输出,如果是IIC等需要读取外部电平的时候则是不能这么设置的,这样读取出来的数据很可能是错的。

解决完遗留的问题,我们还可以讲讲按键。

DIS板卡中有两个按键,一个是复位键,另一个便是用户按键
6.PNG
图中我们可以看到按键引脚进行了下拉(什么是下拉?我们放文末讨论讨论),并且有一个电容消抖(实际上这个电容并没有焊接)。
使用按键一样是配置相应引脚的寄存器,不过这次需要配置的寄存器就少很多了,因为我们是读取按键的状态,所以对输出配置的寄存器我们可以不用管。硬件做了下拉处理,所以上下拉的寄存器也可以不用配置,保持‘00’(No pull-up,pull-down)即可,筛选之后,我们只用配置MODER这个寄存器即可。
GPIOA->MODER &= ~(0x01);
还有就是打开GPIOA的时钟
RCC->AHB1ENR |= 0x01;

做好相应的配置工作之后,我们就可以在while中一直读取按键的状态
  1. if(key_read())
  2.         {
  3.             led_toggle(LED1);
  4.         }
复制代码

Key_read()便是查看按键状态的函数,如果按键按下,这个函数便回返回‘1’,接下来便是执行led_toggle(LED1)这一步。
Key_read()这个函数是怎么样操作的呢。
  1. uint8_t key_read(void)
  2. {
  3.     if((GPIOA->IDR&(0x0001)) == (0x0001))
  4.     {
  5.         key_delay(20000);     //延时消抖
  6.         if((GPIOA->IDR&(0x0001)) == (0x0001))
  7.             return KEY;
  8.     }
  9.     return 0;                                //这一步最好不要省略,如果函数有返回值,
  10.                                                     //无论是何种情况都要返回一个确定的值,
  11.                                                     //否则有可能会出现一个不确定的值,从而影响程序运行
  12. }
复制代码

代码里其实是在不停的查询GPIOAIDR寄存器,(GPIOA->IDR&(0x0001)) == (0x0001)这句的意思便是,读出IDR中的内容,如果按键按下,IDR寄存器中对应Pin0的位(最低位)将会置1(按键按下,为高电平),将寄存器中16位的数据与上0x0001,如果最低位为‘1’,那么条件成立,接着便是执行if中的内容。
key_delay(20000);这个函数就是延时,让单片机做一些空操作,为的就是消耗单片机的时间,避开因为按键按下而带来的电平抖动。
延时之后再一次检测IDR寄存器中的内容,为的是确保按键按下,避免一些误操作。如果按键真的按下,函数将会返回KEY(这是个宏)的值(非零值)。

这里主要接触到两个问题
一:上下拉问题
         我们以开漏模式来探讨一下,开漏模式更容易说明上拉这个问题。
         开漏模式便是漏极开路,以三极管便是集电极开漏输出的结构,如图
7.jpg
图一有两个三极管,前面那个三极管是反向之用,后面那个三极管集电极开路。
对于图一,如果前面的三极管输入为‘0’,那么第一个三极管便会截至,导致后面的三极管导通,使输出直接接地。当前面的三极管输入为‘1’的时候呢,前面导通,后面截止,这时候输出便是一个高阻态。这就相当于图二的模型,开关闭合,输出接地,开关打开,输出便不确定了了。
上拉便是如图三所示,输出接一个电阻到VCC。当开关断开时,输出会被拉到接近VCC的电平,这个时候估计会有人会产生这样的疑惑:VCC通过一个电阻到输出,电阻不就分压了,输出不就应该是个低电平吗?电阻分压的前提是VCC到输出有一定电流,前面我们分析了输出到地的三极管是截至的,这时候输出到地之间接近断路,就算有电流也是极小的,故此时输出便被拉到接近VCC的电平。
我们分析了开漏上拉,那开漏能下拉吗?
我们要注意,开漏模式下如果没有进行上拉,那么输出端口是没有驱动能力的,可以导通到地,但是输出不了一个明确的高电平,所以如果在外部下拉的引脚上开启开漏模式,那么输出就可能会出现问题。
下拉更多情况下是用于得到一个低电平,例如这个程序中的按键,如果不进行下拉(外部或内部),那么在按键没有按下的情况下,这个引脚是浮空的,引脚的电平是不确定的,那读取电平的时候多少会出现问题。

二:按键消抖问题
我们平时用到的开关一般都是机械弹性开关,按键按下的时候并不会马上稳定的接通,会有几毫秒到几十毫秒的抖动时间。在这个程序中,如果不对这种抖动进行处理,那这个按键就会非常不好使。
我们用软件对按键进行消抖一般有两种方案,一是重采样,即程序中所用的方法,延时一段时间之后再进行一次检测;二是持续采样,即多次采样,然后进行处理,最后以处理结果判断按键是否按下。
还有就是进行硬件消抖,即接上原理图中的电容,用电容的充放电特性来对抖动过程中产生的毛刺进行平滑处理。
并没有说那种方式好,这都需要大家亲自去实验。

最后上传两个工程,一个工程是没有使用“jesse_pin.c”文件进行处理的,这个工程可能会比较乱。
另一个则是沿用上一个帖子的工程,不过使用了中断处理




project1.rar

下载

194.2 KB, 下载次数: 5, 下载积分: ST金币 -1

project2.rar

下载

185.97 KB, 下载次数: 4, 下载积分: ST金币 -1

<
回复

使用道具 举报

5

主题

44

回帖

0

蝴蝶豆

金牌会员

最后登录
2019-8-28
发表于 2017-8-23 08:46:59 | 显示全部楼层
手动点赞一个
回复 支持 反对

使用道具 举报

1182

主题

3785

回帖

1

蝴蝶豆

论坛元老

最后登录
2020-3-17
发表于 2017-8-23 09:29:12 | 显示全部楼层
自动点个赞   
回复 支持 反对

使用道具 举报

36

主题

1996

回帖

32

蝴蝶豆

论坛元老

最后登录
2020-12-9
发表于 2017-8-23 10:30:40 | 显示全部楼层
顶一下
回复 支持 反对

使用道具 举报

10

主题

170

回帖

0

蝴蝶豆

金牌会员

最后登录
2020-10-7
 楼主| 发表于 2017-8-23 10:42:47 | 显示全部楼层

谢谢大佬
回复 支持 反对

使用道具 举报

20

主题

1513

回帖

5

蝴蝶豆

论坛元老

最后登录
2020-12-9
发表于 2017-8-23 10:57:29 | 显示全部楼层
把自己的经验分享出来也是对自己所学的一个总结,向楼主学习
回复 支持 反对

使用道具 举报

10

主题

170

回帖

0

蝴蝶豆

金牌会员

最后登录
2020-10-7
 楼主| 发表于 2017-8-23 11:18:49 | 显示全部楼层
子曰好人 发表于 2017-8-23 10:57
把自己的经验分享出来也是对自己所学的一个总结,向楼主学习

一起学习说不定还能让大神指点指点,弥补所缺
回复 支持 反对

使用道具 举报

3

主题

118

回帖

0

蝴蝶豆

高级会员

最后登录
2020-11-28
发表于 2018-1-8 21:00:47 | 显示全部楼层
JESSE 你好:
                  你描述的按鍵是指用戶按鍵?
Image x1.jpg
回复 支持 反对

使用道具 举报

3

主题

118

回帖

0

蝴蝶豆

高级会员

最后登录
2020-11-28
发表于 2018-1-8 23:51:17 | 显示全部楼层
JESSE 你好:
                 我有一個問題,程式規劃成 PC12 OUTPUT, PC13 INPUT ,INPUT 讀取按鍵無法正常讀取
                 剛你下列所講是否會有此問題產生

还有最后一个问题,不说明白可能会成为一个坑。
这里的每个PIN的MODE我都设置为推挽输出模式,然后我在LED_Toggle函数中读取了寄存器中的内容,PIN一旦设置为推挽输出模式,那么输出的MOS
则有可能会导通,那么PIN将会输出一个稳定的电平,这将影响IDR中的内容。
回复 支持 反对

使用道具 举报

10

主题

170

回帖

0

蝴蝶豆

金牌会员

最后登录
2020-10-7
 楼主| 发表于 2018-1-23 08:58:14 | 显示全部楼层
jxchen 发表于 2018-1-8 23:51
JESSE 你好:
                 我有一個問題,程式規劃成 PC12 OUTPUT, PC13 INPUT ,INPUT 讀取按鍵無法正 ...

不好意思,这学期一直在忙毕业设计,没来论坛,所以现在才看到您的点评。
两个引脚设置正确的情况下,至少在我的学习过程中没有遇到过互相干扰的状况。
我在LED_Toggle中读取引脚电平就是为了获取引脚的输出状态,或者说我就是要获取是那个MOS导通来判断LED的亮灭情况,可能在引脚电平翻转的情况下用这种方式有点不合理。
感谢您的点评
回复 支持 反对

使用道具 举报

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版