[牛人杂谈] M451例程讲解之LED

[复制链接]
1428|7
 楼主| zhuomuniao110 发表于 2018-8-24 11:49 | 显示全部楼层 |阅读模式
  1. /**************************************************************************//**
  2. * [url=home.php?mod=space&uid=288409]@file[/url]     main.c
  3. * [url=home.php?mod=space&uid=895143]@version[/url]  V3.00
  4. * $Revision: 3 $
  5. * $Date: 15/09/02 10:03a $
  6. * [url=home.php?mod=space&uid=247401]@brief[/url]    Demonstrate how to set GPIO pin mode and use pin data input/output control.
  7.              演示如何设置GPIO引脚模式并使用引脚数据输入/输出控制。
  8. * @note
  9. * Copyright (C) 2013~2015 Nuvoton Technology Corp. All rights reserved.
  10. *
  11. ******************************************************************************/
  12. #include "stdio.h"
  13. #include "M451Series.h"
  14. #include "NuEdu-Basic01.h"

  15. #define PLL_CLOCK       72000000


  16. void SYS_Init(void)
  17. {
  18.     /*---------------------------------------------------------------------------------------------------------*/
  19.     /* Init System Clock                                                                                       */
  20.     /*---------------------------------------------------------------------------------------------------------*/

  21.     /* Enable HIRC clock (Internal RC 22.1184MHz) */
  22.     CLK_EnableXtalRC(CLK_PWRCTL_HIRCEN_Msk);

  23.     /* Wait for HIRC clock ready */
  24.     CLK_WaitClockReady(CLK_STATUS_HIRCSTB_Msk);

  25.     /* Select HCLK clock source as HIRC and and HCLK clock divider as 1 */
  26.     CLK_SetHCLK(CLK_CLKSEL0_HCLKSEL_HIRC, CLK_CLKDIV0_HCLK(1));

  27.     /* Enable HXT clock (external XTAL 12MHz) */
  28.     CLK_EnableXtalRC(CLK_PWRCTL_HXTEN_Msk);

  29.     /* Wait for HXT clock ready */
  30.     CLK_WaitClockReady(CLK_STATUS_HXTSTB_Msk);

  31.     /* Set core clock as PLL_CLOCK from PLL */
  32.     CLK_SetCoreClock(PLL_CLOCK);

  33.     /* Enable UART module clock */
  34.     CLK_EnableModuleClock(UART0_MODULE);

  35.     /* Select UART module clock source as HXT and UART module clock divider as 1 */
  36.     CLK_SetModuleClock(UART0_MODULE, CLK_CLKSEL1_UARTSEL_HXT, CLK_CLKDIV0_UART(1));

  37.     /*---------------------------------------------------------------------------------------------------------*/
  38.     /* Init I/O Multi-function                                                                                 */
  39.     /*---------------------------------------------------------------------------------------------------------*/

  40.     /* Set PD multi-function pins for UART0 RXD(PD.6) and TXD(PD.1) */
  41.     SYS->GPD_MFPL &= ~(SYS_GPD_MFPL_PD6MFP_Msk | SYS_GPD_MFPL_PD1MFP_Msk);
  42.     SYS->GPD_MFPL |= (SYS_GPD_MFPL_PD6MFP_UART0_RXD | SYS_GPD_MFPL_PD1MFP_UART0_TXD);

  43. }

  44. void UART0_Init()
  45. {
  46.     /*---------------------------------------------------------------------------------------------------------*/
  47.     /* Init UART                                                                                               */
  48.     /*---------------------------------------------------------------------------------------------------------*/
  49.     /* Reset UART module */
  50.     SYS_ResetModule(UART0_RST);

  51.     /* Configure UART0 and set UART0 baud rate */
  52.     UART_Open(UART0, 115200);
  53. }

  54. /*---------------------------------------------------------------------------------------------------------*/
  55. /*  Main Function                                                                                          */
  56. /*---------------------------------------------------------------------------------------------------------*/
  57. int32_t main(void)
  58. {
  59.     int i, j;
  60.     /* Unlock protected registers */
  61.     SYS_UnlockReg();

  62.     /* Init System, peripheral clock and multi-function I/O */
  63.     SYS_Init();

  64.     /* Lock protected registers */
  65.     SYS_LockReg();

  66.     /* Init UART0 for printf */
  67.     UART0_Init();

  68.     printf("\n\nCPU [url=home.php?mod=space&uid=72445]@[/url] %dHz\n",   SystemCoreClock);

  69.     printf("LED test\n\r");

  70.     Initial_LED();

  71.     while(1)
  72.     {
  73.         for(i = 0; i < 9; i++)
  74.         {
  75.             Write_LED_Bar(i);
  76.             for(j = 0; j < 600; j++)
  77.                 CLK_SysTickDelay(1000);
  78.         }
有必要说说LED,正所谓每一个程序刚开始都要写“HELLO WORLD”,而单片机或嵌入式都要点亮小灯,万物从点灯开始,哈哈它的结构很简单
1318049-20180305143952679-35418397.jpg LED,是一种能够将电能转化为可见光的固态的半导体器件,即发光二极管,它可以直接把电转化为光。LED的心脏是一个半导体的晶片,晶片的一端附在一个支架上,一端是负极,
另一端连接电源的正极,使整个晶片被环氧树脂封装起来。半导体晶片由两部分组成,一部分是P型半导体,在它里面空穴占主导地位,另一端是N型半导体,在这边主要是电子。
这两种半导体连接起来的时候,它们之间就形成一个P-N结。当电流通过导线作用于这个晶片的时候,电子就会被推向P区,在P区里电子跟空穴复合,然后就会以光子的形式发出能量,
这就是LED灯发光的原理。而光的波长也就是光的颜色,是由形成P-N结的材料决定的。
单个的LED灯珠只能在低电压(约3V)、低电流(约几毫安)下工作,发出的光线很微弱。需要将许多LED灯珠串联或并联起来;
同时单个LED灯珠是单向导电的,为了充分利用交流电的正负半周电流,这就需要一块集成电路芯片,将交流220V电源转变为电压、电流能与LED集合相匹配的直流电,
以满足LED灯珠集合体的要求,使其能正常发光。
下图是
MCU 外围连接
1318049-20180305144132916-1247791120.jpg
电路图您明白了么?再看软件的相应的库文件
  1. /* Peripheral and SRAM base address */
  2. #define SRAM_BASE            (0x20000000UL)                              /*!< (SRAM      ) Base Address */
  3. #define PERIPH_BASE          (0x40000000UL)                              /*!< (Peripheral) Base Address */
  1. #define AHBPERIPH_BASE       PERIPH_BASE
  2. #define APBPERIPH_BASE       (PERIPH_BASE + 0x00040000)
  1. #define GCR_BASE             (AHBPERIPH_BASE + 0x00000)               
  2. #define CLK_BASE             (AHBPERIPH_BASE + 0x00200)
  3. #define INT_BASE             (AHBPERIPH_BASE + 0x00300)
  4. #define GPIO_BASE            (AHBPERIPH_BASE + 0x04000)
  5. #define GPIOA_BASE           (AHBPERIPH_BASE + 0x04000)
  6. #define GPIOB_BASE           (AHBPERIPH_BASE + 0x04040)
  7. #define GPIOC_BASE           (AHBPERIPH_BASE + 0x04080)
  8. #define GPIOD_BASE           (AHBPERIPH_BASE + 0x040C0)
  9. #define GPIOE_BASE           (AHBPERIPH_BASE + 0x04100)
  10. #define GPIOF_BASE           (AHBPERIPH_BASE + 0x04140)
  11. #define GPIO_DBCTL_BASE      (AHBPERIPH_BASE + 0x04440)
  12. #define GPIO_PIN_DATA_BASE   (AHBPERIPH_BASE + 0x04800)
  13. #define PDMA_BASE            (AHBPERIPH_BASE + 0x08000)
  14. #define USBH_BASE            (AHBPERIPH_BASE + 0x09000)
  15. #define FMC_BASE             (AHBPERIPH_BASE + 0x0C000)
  16. #define EBI_BASE             (AHBPERIPH_BASE + 0x10000)
  17. #define CRC_BASE             (AHBPERIPH_BASE + 0x31000)
  1. #define SYS ((SYS_T *) GCR_BASE)
  2. #define SYSINT ((SYS_INT_T *) INT_BASE)
  3. #define CLK ((CLK_T *) CLK_BASE)
  4. #define PA ((GPIO_T *) GPIOA_BASE)
  5. #define PB ((GPIO_T *) GPIOB_BASE)
  6. #define PC ((GPIO_T *) GPIOC_BASE)
  7. #define PD ((GPIO_T *) GPIOD_BASE)
  8. #define PE ((GPIO_T *) GPIOE_BASE)
  9. #define PF ((GPIO_T *) GPIOF_BASE)
  10. #define GPIO ((GPIO_DBCTL_T *) GPIO_DBCTL_BASE)
  11. #define PDMA ((PDMA_T *) PDMA_BASE)
  12. #define USBH ((USBH_T *) USBH_BASE)
  13. #define FMC ((FMC_T *) FMC_BASE)
  14. #define EBI ((EBI_T *) EBI_BASE)
  15. #define CRC ((CRC_T *) CRC_BASE)

  16. #define GPIO_MODE_INPUT 0x0UL /*!< Input Mode */
  17. #define GPIO_MODE_OUTPUT 0x1UL /*!< Output Mode */
  18. #define GPIO_MODE_OPEN_DRAIN 0x2UL /*!< Open-Drain Mode */
  19. #define GPIO_MODE_QUASI 0x3UL /*!< Quasi-bidirectional Mode */

  20. /* One Bit Mask Definitions */
  21. #define BIT0 0x00000001
  22. #define BIT1 0x00000002
  23. #define BIT2 0x00000004
  24. #define BIT3 0x00000008
  25. #define BIT4 0x00000010
  26. #define BIT5 0x00000020
  27. #define BIT6 0x00000040
  28. #define BIT7 0x00000080
  29. #define BIT8 0x00000100
  30. #define BIT9 0x00000200
  31. #define BIT10 0x00000400
  32. #define BIT11 0x00000800
  33. #define BIT12 0x00001000
  34. #define BIT13 0x00002000
  35. #define BIT14 0x00004000
  36. #define BIT15 0x00008000
  37. #define BIT16 0x00010000
  38. #define BIT17 0x00020000
  39. #define BIT18 0x00040000
  40. #define BIT19 0x00080000
  41. #define BIT20 0x00100000
  42. #define BIT21 0x00200000
  43. #define BIT22 0x00400000
  44. #define BIT23 0x00800000
  45. #define BIT24 0x01000000
  46. #define BIT25 0x02000000
  47. #define BIT26 0x04000000
  48. #define BIT27 0x08000000
  49. #define BIT28 0x10000000
  50. #define BIT29 0x20000000
  51. #define BIT30 0x40000000
  52. #define BIT31 0x80000000

  53. void GPIO_SetMode(GPIO_T *port, uint32_t u32PinMask, uint32_t u32Mode)
  54. {
  55. uint32_t i;

  56. for(i = 0; i < GPIO_PIN_MAX; i++)
  57. {
  58. if(u32PinMask & (1 << i))
  59. {
  60. port->MODE = (port->MODE & ~(0x3 << (i << 1))) | (u32Mode << (i << 1));
  61. }
  62. }
  63. }

  64. void Initial_LED(void)
  65. {
  66. GPIO_SetMode(PB, BIT2, GPIO_MODE_OUTPUT); //LED1
  67. GPIO_SetMode(PB, BIT3, GPIO_MODE_OUTPUT); //LED2
  68. GPIO_SetMode(PC, BIT3, GPIO_MODE_OUTPUT); //LED3
  69. GPIO_SetMode(PC, BIT2, GPIO_MODE_OUTPUT); //LED4
  70. GPIO_SetMode(PA, BIT9, GPIO_MODE_OUTPUT); //LED5
  71. GPIO_SetMode(PB, BIT1, GPIO_MODE_OUTPUT); //LED6
  72. GPIO_SetMode(PC, BIT7, GPIO_MODE_OUTPUT); //LED7
  73. }



 楼主| zhuomuniao110 发表于 2018-8-24 11:50 | 显示全部楼层
关于AHB与APB在我的内核架构一文中有描述,再此不加说明。
你把这几部分放到一个程序中完全可以运行
I/O管脚的I状态可由软件独立地配置为输入,推挽式的输出,开漏或准双向模式。复位之后,所有管脚的模式取决于CIOIN (CONFIG0[10])的设置。
每个I/O管脚有一个阻值为110K~300K的弱上拉电阻接到VDD 上,VDD范围从5.0 V 到2.5 V。
首先我们来看上拉电阻与下拉电阻:
两者共同的作用是:避免电压的“悬浮”,造成电路的不稳定;

一、上拉电阻如图所示
1、概念:将一个不确定的信号,通过一个电阻与电源VCC相连,固定在高电平;
2、上拉是对器件注入电流;灌电流;
3、当一个接有上拉电阻的IO端口设置为输入状态时,它的常态为高电平;
1318049-20180305153600256-369209658.jpg

二、下拉电阻如图所示:
1、 概念:将一个不确定的信号,通过一个电阻与地GND相连,固定在低电平;
2、下拉是从器件输出电流;拉电流;
3、当一个接有下拉电阻的IO端口设置为输入状态时,它的常态为低电平;
1318049-20180305153612277-292191848.jpg

要理解推挽输出,首先要理解好三极管(晶体管)的原理。下面这种三极管有三个端口,分别是基极(Base)、集电极(Collector)和发射极(Emitter)。
下图是NPN型晶体管。
1318049-20180305153951055-1110613396.jpg

这种三极管是电流控制型元器件,注意关键词电流控制。意思就是说,只要基极B有输入(或输出)电流就可以对这个晶体管进行控制了。
下面请允许我换一下概念,把基极B视为控制端,集电极C视为输入端,发射极E视为输出端。这里输入输出是指电流流动的方向。
1318049-20180305154045501-87882732.jpg [url=][/url]

当控制端有电流输入的时候,就会有电流从输入端进入并从输出端流出。

[url=][/url]
而PNP管正好相反,当有电流从控制端流出时,就会有电流从输入端流到输出端。

1318049-20180305154225523-187038413.jpg [url=][/url]

那么推挽电路:
[url=][/url]
1318049-20180305154258675-1080229469.jpg
上面的三极管是N型三极管,下面的三极管是P型三极管,请留意控制端、输入端和输出端。
当Vin电压为V+时,上面的N型三极管控制端有电流输入,Q3导通,于是电流从上往下通过,提供电流给负载。
1318049-20180305154344845-1718830077.jpg
经过上面的N型三极管提供电流给负载(Rload),这就叫「推」
当Vin电压为V-时,下面的三极管有电流流出,Q4导通,有电流从上往下流过。
1318049-20180305154429863-1727639256.jpg
经过下面的P型三极管提供电流给负载(Rload),这就叫「挽」。
以上,这就是推挽(push-pull)电路。
1318049-20180529234017311-1532985995.jpg
1318049-20180529234248668-1617345354.jpg
1318049-20180529234430435-1633282539.jpg
1318049-20180529234518537-2058678920.jpg
1318049-20180529234554555-164753360.jpg
1318049-20180529234630081-1545809897.jpg

那么什么是开漏呢?这个在我答案一开头给出的「网上资料」里讲得很详细了,我这里也简单写一下。
要理解开漏,可以先理解开集。
1318049-20180305154631887-365752199.jpg
如图,开集的意思,就是集电极C一端什么都不接,直接作为输出端口。
如果要用这种电路带一个负载,比如一个LED,必须接一个上拉电阻,就像这样。
1318049-20180305154703042-1973589064.jpg
当Vin没有电流,Q5断开时,LED亮。 当Vin流入电流,Q5导通时,LED灭。
开漏电路,就是把上图中的三极管换成场效应管(MOSFET)。
N型场效应管各个端口的名称:
1318049-20180305154754547-2101111141.jpg
场效应管是电压控制型元器件,只要对栅极施加电压,DS就会导通。
结型场效应管有一个特性就是它的输入阻抗非常大,这意味着:没有电流从控制电路流出,也没有电流进入控制电路。没有电流流入或流出,就不会烧坏控制电路。而双极型晶体管不同,是电流控制性元器件,如果使用开集电路,可能会烧坏控制电路。这大概就是我们总是听到开漏电路而很少听到开集电路的原因吧?因为开集电路被淘汰了。

1、开漏
全部名字是,内部mos管漏极开路(也可理解为晶体管集电极开路)。输出引脚只有对地低阻抗以及高阻态两种模式。
1318049-20180305154909859-572044196.jpg
2、推挽
内部有上下两个mos(或晶体管),输出引脚有对VCC低阻抗以及对地低阻抗两种模式。
1318049-20180305155040834-908480321.jpg
下面付一个无脑版的,用开关代替晶体管或MOS管。等效电路。
1318049-20180305155130921-203854195.jpg
1. 推挽输出能够输出高或者低,而开漏输出只能输出低,或者关闭输出,因此开漏输出总是要配一个上拉电阻使用。
2. 开漏输出的上拉电阻不能太小,太小的话,当开漏输出的下管导通时,电源到地的电压在电阻上会造成很大的功耗,因此这个电阻阻值通常在10k以上,这样开漏输出在从输出低电平切换到高电平时,速度是很慢的。
3. 推挽输出任意时刻的输出要么是高,要么是低,所以不能将多个输出短接,而开漏输出可以将多个输出短接,共用一个上拉,此时这些开漏输出的驱动其实是与非的关系。
4. 推挽输出输出高时,其电压等于推挽电路的电源,通常为一个定值,而开漏输出的高取决于上拉电阻接的电压,不取决于前级电压,所以经常用来做电平转换,用低电压逻辑驱动高电压逻辑,比如3.3v带5v。

单片机的几种IO口配置
在单片机学习、开发和应用中,IO口的配置对功能的实现起着重要的作用,下面介绍常见的四种配置,而现在很多单片机都兼有这四种配置,可供选择。

一.准双向口配置

如下图,当IO输出为高电平时,其驱动能力很弱,外部负载很容易将其拉至低电平。当IO输出为低电平时,其驱动能力很强,可吸收相当大的电流。

准双向口有三个上拉晶体管,一个“极弱上拉”,当端锁存器为逻辑“1”时打开,当端口悬空时,“极弱上拉”将端口上拉至高电平。

第二个上拉晶体管为“弱上拉”,当端口锁存器为逻辑“1”且端口本身也为“1”时打开,此上拉提供的电流,使准双向口输出为“1”。如果此时端口被
外部装置拉到逻辑“0”时,通过施密特触发器,控制“弱上拉”关闭,而“极弱上拉”维持开状态,为了把这个端口拉低,外部装置必须有足够的灌电流能力,使
管脚上的电压,降到门槛电以下。

第三个上拉晶体管为“强上拉”,当端口锁存器由“0”跳变到“1”时,这个上拉用来加快端口由逻辑“0”到逻辑“1”的转换速度。

准双向口做为输入时,通个一个施密特触如器和一个非门,用以干扰和滤波。

20160907095652801.jpg



准双向口用作输入时,可对地接按键,如下图1,当然也可以去掉R1直接接按键,当按键闭合时,端口被拉至低电平,当按键松开时,端口被内部“极弱上
拉”晶体管拉至高电平。当端口作为输出时,不应对地外接LED如图形控制,这样端口的驱动能力很弱,LED只能发很微弱的光,如果要驱动LED,要采用图
3的方法,这样准双向口在输出为低时,可吸收20mA的电流,故能驱动LED。图4的方法也可以,不过LED不发光时,端口要吸收收很大电流。


二.开漏输出配置
这种配置,关闭所有上拉晶体管,只驱动下拉晶体管,下拉与准双向口下拉配置相同,因此只能输出低电平(吸收电流),和高阻状态。不能输出高电平(输也电流)。如果要作为逻辑输出,必须接上拉电阻到VCC。这种配置也可以通过上图3和图4来驱动LED。

20160907095652465.jpg



三.推挽输出配置

这种配置的下拉与准双向口和开漏配置相同,具有较强的拉电流能力,不同的是,具有持续的强上拉。因此可以用上图2的方法来驱动LED。



20160907095653889.jpg

四.仅为输入配置(高阻配置)

这种配置不能输出电流,也不能有收电流,只能作为输入数据使用。

20160907095653424.jpg



以上四种配置各有其特点,在使用中应根据其特点灵活运用。

准双向口的最大特点是既可以作为输入,也可以作为输出,不需要通过控制切换。

推挽输出的特点是,无论输也高电平还是低电平都有较大的驱动能力,在输也高电平时,也能直接点亮LED,这在准双向口中是不能办到的。这种配置不宜作为输入,因为这需要外部设备有很强的拉电流的能胃。

仅为输入配置的特点是端口只能作为输入使用,可以获得很高的输入阻抗,在有模拟比较器或ADC的端口中用得较多。

开漏输出配置与准又向口相似,但内部没有上拉电阻。有很好的电气兼容性,外部接上拉电阻到3V电源,就能和3V逻辑器件连接。外部接上拉电阻到5V电源,就要以和5V器件连接。

需要说明的是以上四种配置均可以作为输入,也就是都可以检测端的逻辑状态,但其特性不同,不是每种配置都可以直接接按键


就讲到这里,其实这就是I/O口的输入输出模式,具体看程序就可以清楚明白了
GPIO_SetMode(PC, BIT7, GPIO_MODE_OUTPUT);//#define GPIO_MODE_OUTPUT 0x0UL /*!< Input Mode */
设置成为了输出模式,之后我们在来看,这个函数的具体功能


 楼主| zhuomuniao110 发表于 2018-8-24 11:51 | 显示全部楼层
  1. while(1)
  2.     {
  3.         for(i = 0; i < 9; i++)
  4.         {
  5.             Write_LED_Bar(i);
  6.             for(j = 0; j < 600; j++)
  7.                 CLK_SysTickDelay(1000);
  8.         }
  9.     }
  1. #define _LED1 PB2
  2. #define _LED2 PB3
  3. #define _LED3 PC3
  4. #define _LED4 PC2
  5. #define _LED5 PA9
  6. #define _LED6 PB1
  7. #define _LED7 PC7


  8. #define _LED_Bar_Count      7
  9. void Write_LED_Bar(uint32_t Number)
  10. {
  11.     uint32_t i;
  12.     volatile uint32_t *ptrLED[_LED_Bar_Count] = {&_LED1, &_LED2, &_LED3, &_LED4, &_LED5, &_LED6, &_LED7};

  13.     for(i = 0; i < _LED_Bar_Count; i++)
  14.     {
  15.         if((Number > i) & 0x01)
  16.             *ptrLED[i] = 0; //LED ON
  17.         else
  18.             *ptrLED[i] = 1; //LED OFF
  19.     }
  20. }
如果我们假设Number为一的话,i为零时,1大于零,为真,为一,遇上0X01之后为真,则进行负值为零(开灯)的操作,它是共阳级的操作。
试试我写的这个
  1. #define _LED1 PB2
  2. #define _LED2 PB3
  3. #define _LED3 PC3
  4. #define _LED4 PC2
  5. #define _LED5 PA9
  6. #define _LED6 PB1
  7. #define _LED7 PC7


  8. volatile uint32_t *ptrLED[_LED_Bar_Count] = {&_LED1, &_LED2, &_LED3, &_LED4, &_LED5, &_LED6, &_LED7};


  9. while(1)
  10.     {
  11.                     
  12.        for(i = 0; i < 7; i++)
  13.         {
  14.           *ptrLED[i]=0;
  15.                     for(j = 0; j < 600; j++)
  16.                     {
  17.               CLK_SysTickDelay(1000);
  18.                     }
  19.                     *ptrLED[i]=1;
  20.                     for(j = 0; j < 600; j++)
  21.                     {
  22.               CLK_SysTickDelay(1000);
  23.                     }
  24.                     if(i==8)
  25.                     {
  26.                     i=0;
  27.                     }
  28.         }
  29.     }


稳稳の幸福 发表于 2018-8-25 21:31 | 显示全部楼层
教程不错,把IO的几种模式和使用讲明白了。
xuanhuanzi 发表于 2018-8-25 22:26 | 显示全部楼层
讲的真好。
玛尼玛尼哄 发表于 2018-8-28 19:23 | 显示全部楼层
貌似我有个M4的板子,忘了是哪个系列的了,几年前用过。
wanduzi 发表于 2018-8-29 20:44 | 显示全部楼层
IO 驱动看似简单,学好要下功夫。
huahuagg 发表于 2018-8-31 23:59 | 显示全部楼层
很全面的介绍。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

233

主题

3529

帖子

11

粉丝
快速回复 在线客服 返回列表 返回顶部