【.Net Micro Framework PortingKit - 08】GPIO驱动

简介: 要点亮LED灯或获得输入IO的状态应该是比较容易的,打开端口时钟,然后读写相关的GPIO寄存器就可以了,但是要实现一个输入中断,就要费些周折了

要点亮LED灯或获得输入IO的状态应该是比较容易的,打开端口时钟,然后读写相关的GPIO寄存器就可以了,但是要实现一个输入中断,就要费些周折了。

对STM32(Cortex-M3)的芯片,要实现一个GPIO中断一般需要如下几步:

1、  配置时钟控制器寄存器(RCC)的APB2RSTR,确保对应的GPIOA ~ GPIOG时钟使能。

2、  对GPIO寄存器的CRL(或CRH)要设置正确的输入模式,如浮空输入模式(对接收IO中断来说,当然要设置成输入模式)。

3、  要通过AFIO寄存器配置中断的输入来源,对STM32芯片来说,具有19路EXTI中断线,其中3路分别连接PVD输出、RTC闹钟事件及USB唤醒事件,剩下的对GPIOA ~ GPIOG 7*16=112个IO点来说,同时只能配置16路IO输入中断。

4、  接下来要配置EXIT寄存器,根据需要来配置是上升沿触发中断、还是下降沿触发中断或两者都触发。

5、  而后比较重要的是, 要配置NVIC的SETENA寄存器,让对应的EXTI0、EXTI1、EXTI2、EXTI3、EXTI4、EXTI9_5或EXTI15_10中断位使能,此外还要配置各中断的优先级(前提是中断优先级分组寄存器已配置完毕)。

6、  最后我们要设置中断向量表(该向量表要重定位到内存中,以便于动态修改),在EXTI0、EXTI1、EXTI2、EXTI3、EXTI4、EXTI9_5或EXTI15_10对应的位置,放入我们的中断函数的入口地址。

 

和PC平台程序开发不同,基本上你每做一步,都可以很直观的看到你的进展和成果,但对嵌入式开发来说,如果上述几步有任何一个环节出了问题,你的进展都是零,有时候你会花上一天的时间去反复核实每个寄存器的值是否正确,以期获得你希望的结果。所以说嵌入式开发是惊喜的型的,要么成,要么不成,一线之隔!

接下来我们说一下GPIO实现的详细步骤,首先在CortexM3.h头文件中添加GPIO相关的寄存器描述:

struct CortexM3_GPIO

{

  static const UINT32 c_Base = 0x40010800;

  static const UINT32 A = 0;

  static const UINT32 B = 1;

  static const UINT32 C = 2;

  static const UINT32 D = 3;

  static const UINT32 E = 4;

  static const UINT32 F = 5;

  static const UINT32 G = 6;

  static const UINT32 GPIO_Mode_NULL = 0x00;

  static const UINT32 GPIO_Mode_Speed_10MHz = 0x01;

  static const UINT32 GPIO_Mode_Speed_2MHz = 0x02;

  static const UINT32 GPIO_Mode_Speed_50MHz = 0x03;

  static const UINT32 GPIO_Mode_IN_FLOATING = 0x04;

    /****/ volatile UINT32   CRL;  //配置寄存器

  /****/ volatile UINT32   CRH;

  /****/ volatile UINT32   IDR;  //数据寄存器

  /****/ volatile UINT32   ODR;

  /****/ volatile UINT32   BSRR; //置位复位寄存器

  /****/ volatile UINT32   BRR;  //复位寄存器

  /****/ volatile UINT32   LCKR; //锁定寄存器

};

struct CortexM3_EXTI

{

  static const UINT32 c_Base = 0x40010400;

  /****/ volatile UINT32  IMR;

  /****/ volatile UINT32  EMR;

  /****/ volatile UINT32  RTSR;

  /****/ volatile UINT32  FTSR;

  /****/ volatile UINT32  SWIER;

  /****/ volatile UINT32  PR;

};

struct CortexM3_AFIO

{

  static const UINT32 c_Base = 0x40010000; 

  /****/ volatile UINT32 EVCR;

  /****/ volatile UINT32 MAPR;

  /****/ volatile UINT32 EXTICR[4];

};

由于NVIC相关的代码我们已经在《NVIC中断处理》说过了,这里就不重复了。

对.Net Micro Framework的架构来说,要实现如下几个接口:

1、CPU_GPIO_Initialize

2、CPU_GPIO_Uninitialize

3、CPU_GPIO_Attributes

4、CPU_GPIO_DisablePin

5、CPU_GPIO_EnableOutputPin

6、CPU_GPIO_EnableInputPin

7、CPU_GPIO_EnableInputPin2

8、CPU_GPIO_GetPinState

9、CPU_GPIO_SetPinState

10、CPU_GPIO_GetPinCount

11、CPU_GPIO_GetPinsMap

12、CPU_GPIO_GetSupportedResistorModes

13、CPU_GPIO_GetSupportedInterruptModes

14、CPU_GPIO_PinIsBusy

15、CPU_GPIO_ReservePin

16、CPU_GPIO_GetDebounce

17、CPU_GPIO_SetDebounce

考虑到难易程度和篇幅,我们只介绍CPU_GPIO_Initialize、CPU_GPIO_EnableOutputPin、 CPU_GPIO_EnableInputPin和EXTI_IRQHandler 中断函数的具体实现。

BOOL GPIO_Driver::Initialize()

{

         CortexM3_AFIO &AFIO = CortexM3::AFIO();

    for(int i=0;i<4;i++)

    {

       AFIO.EXTICR[i]=0x0000;

    }       

CortexM3_EXTI &EXTI= CortexM3::EXTI();   

    EXTI.IMR = 0x00000000;

    EXTI.EMR = 0x00000000;

    EXTI.RTSR = 0x00000000;

    EXTI.FTSR = 0x00000000;

    EXTI.PR = 0x0007FFFF;

    //NVIC

    if(!CPU_INTC_ActivateInterruptEx(CortexM3_NVIC::c_IRQ_Index_EXTI0,(UINT32)(void *)EXTI_IRQHandler ))   return FALSE;

         //略

    return TRUE;

}

其中比较重要的是CPU_INTC_ActivateInterruptEx函数,它可动态设置c_IRQ_Index_EXTI0中断所对应的中断函数的入口地址。

void GPIO_Driver::EnableOutputPin(GPIO_PIN pin, BOOL initialState)

{

    ASSERT(pin < c_MaxPins);       

    UINT32 port = PinToPort(pin);

         UINT32 bit = PinToBit(pin);

    UINT32 pos = (bit % 8)<<2;

         CortexM3_GPIO &GPIO= CortexM3::GPIO(port);   

    //通用推挽输出模式

    if(bit<8)

    {       

       GPIO.CRL = (GPIO.CRL & ~(0x0F << pos)) | (CortexM3_GPIO::GPIO_Mode_Speed_50MHz << pos);

    }

         else

         {

            GPIO.CRH = (GPIO.CRH & ~(0x0F << pos)) | (CortexM3_GPIO::GPIO_Mode_Speed_50MHz << pos);

         }

 

    //初值

    if(initialState) GPIO.BSRR = 0x1 << bit;

    else GPIO.BRR = 0x1 << bit;

}

输出默认为通用推挽输出模式,你也可以根据实际需要进行必要的调整。

BOOL GPIO_Driver::EnableInputPin(GPIO_PIN pin, BOOL GlitchFilterEnable, GPIO_INTERRUPT_SERVICE_ROUTINE ISR, void *pinIsrParam, GPIO_INT_EDGE intEdge, GPIO_RESISTOR resistorState)

{

    ASSERT(pin < c_MaxPins);       

    UINT32 port = PinToPort(pin);

         UINT32 bit = PinToBit(pin);

    UINT32 pos = (bit % 8)<<2;

         CortexM3_GPIO &GPIO= CortexM3::GPIO(port);   

    //浮空输入

    if(bit<8)

    {       

       GPIO.CRL = (GPIO.CRL & ~(0x0F << pos))| (CortexM3_GPIO::GPIO_Mode_IN_FLOATING << pos);

    }

         else

         {

            GPIO.CRH = (GPIO.CRH & ~(0x0F << pos))| (CortexM3_GPIO::GPIO_Mode_IN_FLOATING << pos);

         }

    //中断输入源配置(AFIO)

    CortexM3_AFIO &AFIO = CortexM3::AFIO();

    AFIO.EXTICR[bit >> 2] &= ~(0x0F << (0x04 * (bit & 0x03)));

    AFIO.EXTICR[bit >> 2] |= port << (0x04 * (bit & 0x03));

         CortexM3_EXTI &EXTI=CortexM3::EXTI();

    if(ISR)

    {

        switch(intEdge)

        {

            case GPIO_INT_NONE:   //无中断

                EXTI.IMR &= ~(0x1<<bit);                                   

                return FALSE;

            case GPIO_INT_EDGE_LOW:   //下降沿中断

            case GPIO_INT_LEVEL_LOW:

                EXTI.IMR |= 0x1<<bit;    

                                     EXTI.FTSR |= 0x1<<bit;        //下降沿有效

                                     EXTI.RTSR &= ~(0x1<<bit);  //上升沿无效

                break;

            //略

            default:

                ASSERT(0);

                return FALSE;

        }

    }                

                  

    return TRUE;

}

GPIO输入的实现比较繁琐一些,可以根据需要仅把端口配置成输入模式,而不配置相应的中断参数。这样可以通过不断扫描的方式获得输入信号。

void GPIO_Driver::ISR(void *Param)

{

         CortexM3_EXTI &EXTI=CortexM3::EXTI();

    UINT32 interruptsActive = EXTI.PR;

    UINT32 bitMask  = 0x1, bitIndex = 0;

         while(interruptsActive)

    {

        while((interruptsActive & bitMask) == 0)

        {

            bitMask  <<= 1;

            ++bitIndex;

        }

        CortexM3_AFIO &AFIO = CortexM3::AFIO();

        UINT32 port = (AFIO.EXTICR[bitIndex >> 2]>>(0x04 * (bitIndex & 0x03))) & 0xF;

        GPIO_PIN pin = BitToPin( bitIndex, port);

        PIN_ISR_DESCRIPTOR& pinIsr = g_GPIO_Driver.m_PinIsr[ pin ];

        pinIsr.Fire( (void*)&pinIsr );

        interruptsActive ^= bitMask;

                   EXTI.PR |= bitMask;

    }

}

在中断函数中,根据相关寄存器的值来判断哪一个(或同时哪一些)GPIO发生的中断,并由此执行业已配置好的异步中断处理函数。

好了,写完了GPIO驱动程序,我们就可以漂漂亮亮的在NativeSample中写我们的测试程序了:

void ISR( GPIO_PIN Pin, BOOL PinState, void* Param )

{

    if(PinState)    // released, up

    {

      CPU_GPIO_SetPinState(GPIO_Driver::PF7,0x0);

    }

    else            // pressed, down

    {

       CPU_GPIO_SetPinState(GPIO_Driver::PF7,0x1);

    }

}

 

void ApplicationEntryPoint()

{  

    //LED D1 D2 D3 D4  

    CPU_GPIO_EnableOutputPin(GPIO_Driver::PF7,FALSE);

         CPU_GPIO_EnableOutputPin(GPIO_Driver::PF8,FALSE);

 

         //user按钮 = 0x1

         CPU_GPIO_EnableInputPin(GPIO_Driver::PG8,FALSE,ISR,GPIO_INT_EDGE_BOTH,RESISTOR_PULLDOWN);                   

    while(TRUE)    

    {  

           CPU_GPIO_SetPinState(GPIO_Driver::PF8,!CPU_GPIO_GetPinState(GPIO_Driver::PF8));

           Events_WaitForEvents( 0, 1000 );      

         }

}

上面的程序比我们最初在《调试初步:点亮LED灯》中提到的代码清爽多了,把程序下载到开发板上运行,你会发现D3 LED灯以一秒为周期不断地闪烁,而D2 LED灯则在user按钮按下时才亮,放开时则灭。

只要细心 + 耐心,其实嵌入式开发还是比较容易的,并且功能实现那一刻喜悦的“强度”,是作为PC平台软件开发者所难以企及的。

相关文章
|
2月前
使用的是.NET Framework 4.0,并且需要使用SMTP协议发送电子邮件
使用的是.NET Framework 4.0,并且需要使用SMTP协议发送电子邮件
57 1
|
2月前
|
开发框架 缓存 监控
NET Framework 到 .NET 5/6 的迁移是重大的升级
本文详细介绍了从 .NET Framework 4.8 迁移到 .NET 5/6 的过程,通过具体案例分析了迁移策略与最佳实践,包括技术栈评估、代码迁移、依赖项更新及数据库访问层的调整,强调了分阶段迁移、保持代码可维护性及性能监控的重要性。
62 3
|
2月前
|
机器学习/深度学习 编解码 算法
【小样本图像分割-4】nnU-Net: Self-adapting Framework for U-Net-Based Medical Image Segmentation
《nnU-Net: 自适应框架用于基于U-Net的医学图像分割》是一篇2018年的论文,发表在Nature上。该研究提出了一种自适应的医学图像分割框架nnU-Net,能够自动调整模型的超参数以适应不同的数据集。通过2D和3D U-Net及级联U-Net的组合,nnU-Net在10个医学分割数据集上取得了卓越的性能,无需手动调整。该方法强调数据增强、预处理和训练策略等技巧,为医学图像分割提供了一个强大的解决方案。
103 0
【小样本图像分割-4】nnU-Net: Self-adapting Framework for U-Net-Based Medical Image Segmentation
winform .net6 和 framework 的图表控件,为啥项目中不存在chart控件,该如何解决?
本文讨论了在基于.NET 6和.NET Framework的WinForms项目中添加图表控件的不同方法。由于.NET 6的WinForms项目默认不包含Chart控件,可以通过NuGet包管理器安装如ScottPlot等图表插件。而对于基于.NET Framework的WinForms项目,Chart控件是默认存在的,也可以通过NuGet安装额外的图表插件,例如LiveCharts。文中提供了通过NuGet添加图表控件的步骤和截图说明。
winform .net6 和 framework 的图表控件,为啥项目中不存在chart控件,该如何解决?
|
3月前
|
开发框架 前端开发 JavaScript
ASP.NET MVC 教程
ASP.NET 是一个使用 HTML、CSS、JavaScript 和服务器脚本创建网页和网站的开发框架。
51 7
|
3月前
|
存储 开发框架 前端开发
ASP.NET MVC 迅速集成 SignalR
ASP.NET MVC 迅速集成 SignalR
82 0
|
4月前
|
开发框架 前端开发 .NET
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
60 0
|
4月前
|
开发框架 前端开发 安全
ASP.NET MVC 如何使用 Form Authentication?
ASP.NET MVC 如何使用 Form Authentication?
|
4月前
|
开发框架 .NET
Asp.Net Core 使用X.PagedList.Mvc.Core分页 & 搜索
Asp.Net Core 使用X.PagedList.Mvc.Core分页 & 搜索
151 0
|
7月前
|
开发框架 前端开发 .NET
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
214 0