配置MM32微控制器引脚复用功能
文章目录
- 配置MM32微控制器引脚复用功能
-
- Introduction
- Algorithm
-
- GPIOx_CR寄存器
- GPIOx_AFR寄存器
- GPIOx_CR & GPIOx_AFR寄存器
-
- TIM
- UART
- SPI_MASTER
- SPI_SLAVE
- I2C
- CAN
- ADC
- FSMC
- QSPI
- DAC
- COMP
- SDIO
- USB
- Practice
- Conclusion
Introduction
使用过NXP(FSL)微控制器的开发者只配置引脚复用功能PORT在模块中,引脚对应PCR寄存器的MUX在字段中选择引脚的复用功能。但是作者最近在用MM32微控制器(MM32F3270)在配置引脚复用功能时遇到了一些麻烦。MM在用户界面的设计上,32微控制器走得很近ST相对于风格NXP将GPIO(一般控制GPIO的外设)和PORT(与具体芯片相关的复用功能)两个模块分开管理,仅用作数字IO端口的信号输入输出和电气特性,MM32的GPIO外设混合了GPIO和PORT的功能,并把PORT引脚异步中断的功能再次分散到EXTI模块和SYSCFG模块(SYSCFG_EXTICRn)中。
如何整理本文MM引脚复用功能配置在32微控制器上。
现在看来,后续需要整理如何使用MM32中断引脚,混合GPIO、EXTI、SYSCFG等三个模块中断引脚。
Algorithm
配置MM32引脚复用功能需要同时配置GPIO相关寄存器模块的电气特性(CR)寄存器和复用功能选择(AFR),为了正常工作,需要根据具体情况设置句柄两部分。
GPIOx_CR寄存器
这里的x指代A、B、… 、H,每个端口都有。CR寄存器分为高8位(CRH)和低8位(CRL),一个端口总共有16个引脚。CRH和CRL合起来的64个bit位,每4个bit对应一个引脚,但在手册中,这四个bit分为两个区域(CNFn和MODEn)进行描述。
本文以更清爽的方式描述:
MODE | Description |
---|---|
0b0000 | 模拟输入模式(Analog Input) |
0b0100 | 浮空输入模式(Floating Input),可用于模拟信号、数字信号 |
0b1000 | 数字上拉/下拉输入模式ODR将寄存器输出到端口,选择上拉或下拉。(Pull-Up or Pull-Down) |
0b1100 | 未使用 |
0b0001 | 数字推拉输出模式(Digital Push-Pull Output) |
0b0101 | 数字泄漏输出模式(Digital Open-Drain Output) |
0b1001 | 复用功能推拉输出模式(Mux Push-Pull Output) |
0b1101 | 复用功能开漏输出模式(Mux Open-Drain Output) |
根据手册中的描述,当选择数字上拉/下拉输入模式时,用户需要通过ODR将电平控制信号写在输出端口上。GPIO_ODR
当指定寄存器时GPIO引脚为GPIO输出时,控制输出引脚的电平。看来这里的上下拉配置其实是需要的GPIO模块自己的输出电路(虽然软件上没有使用)拉动自己的输入信号。这让我想起了多年前51单片机端口电路的使用,51单片机P0上所有的引脚都是泄漏的,没有输入输出配置,需要外部拉电路,默认为输出功能,但当需要使用输入功能时,必须先写1,然后读数,也用自己的输出电路拉输入。
GPIOx_AFR寄存器
GPIOx_AFR
寄存器也寄存器(AFRH
)和低8位(AFRL
),总共64个bit位,每4位对应一个引脚。每个引脚可以配置成16个复用功能中的一个。其中可选的复用功能,就要在datasheet查阅了文档中引脚复用功能表。
这里要注意:
- 如果要使用GPIO功能必须对应引脚
AFRn
写成0xF
,哪怕GPIOx_CR
数字输入输出模式不能在寄存器中配置引脚。下一节将讨论这种映射关系的约束。 - 并非所有的引脚
AFRn
默认值是0xF
。例如JTAG(还包含SWD未使用的引脚),BOOT0和ISP相关功能引脚,上电后的默认值是相应的功能引脚。因此,建议使用它GPIO功能,也例行对应AFRn
值指定为0xF
。
GPIOx_CR & GPIOx_AFR寄存器
前面提到的只有一个引脚CR[MODE]
和AFR[AFRn]
只有按照特定的配置组合才能工作。
关于这些特定的配置组合,请注意,不要主观地认为这是理所当然的AFR[AFRn]
选选为复用功能,复用功能的信号可以正常工作。必须明确复用输出功能模式和输入模式:
- 对于复用功能的输出信号,应指定复用推拉输出模式
- 复用功能的输入信号应指定浮动输入模式。请注意,这种浮动输入模式不仅用于复用功能,也用于复用功能GPIO还将使用功能。
- 复用功能的输入信号也可以指定为数字上拉/下拉输入模式。这种模式应该只支持GPIO功能。
事实上,这也是一个迷惑我,严重影响开发效率的问题。这种映射关系不是通用的,只能通过具体的特例来表现。
TIM
TIM引脚 | TIM功能(AFR[AFRn] | 引脚电气特性(CR[MODE]) |
---|---|---|
TIM_CHn | 输入捕获通道n | 浮空输入模式 |
TIM_CHn | 输出比较通道n | 复用推挽输出模式 |
TIM_CHnN | 互补输出比较通道n | 复用推挽输出模式 |
TIM_BKIN | 刹车输入 | 浮空输入模式 |
TIM_ETR | 输入外部时钟 | 浮空输入模式 |
UART
UART引脚 | UART功能(AFR[AFRn]) | 引脚电气特性(CR[MODE]) |
---|---|---|
UART_TX | 串行发送 | 复用推挽输出模式 |
UART_RX | 串行接收 | 浮空输入模式(或数字上拉输入模式) |
UART_RTS | 硬件流控 | 复用推挽输出模式 |
UART_CTS | 硬件流控 | 浮空输入模式(或数字上拉输入模式) |
SPI_MASTER
SPI引脚 | SPI功能(AFR[AFRn]) | 引脚电气特性(CR[MODE]) |
---|---|---|
SPI_CLK | 发送时钟 | 复用推挽输出模式 |
SPI_MOSI | 串行发送 | 复用推挽输出模式 |
SPI_MISO | 串行接收 | 浮空输入模式(或数字上拉输入模式) |
SPI_NSS | 片选 | 复用推挽输出模式 |
SPI_SLAVE
SPI引脚 | SPI功能(AFR[AFRn]) | 引脚电气特性(CR[MODE]) |
---|---|---|
SPI_CLK | 接收时钟 | 浮动输入模式 |
SPI_MOSI | 串行接收 | 浮空输入模式(或数字上拉输入模式) |
SPI_MISO | 串行发送 | 复用推挽输出模式 |
SPI_NSS | 片选 | 浮空输入模式(或数字上/下拉输入模式) |
I2C
I2C引脚 | I2C功能(AFR[AFRn]) | 引脚电气特性(CR[MODE]) |
---|---|---|
I2C_SCL | 串行时钟 | 开漏复用输出模式 |
I2C_SDA | 串行数据 | 开漏复用输出模式 |
CAN
CAN引脚 | CAN功能(AFR[AFRn]) | 引脚电气特性(CR[MODE]) |
---|---|---|
CAN_TX | CAN发送(对接收发器) | 复用推挽输出模式 |
CAN_RX | CAN接收(对接收发器) | 浮空输入模式(或数字上拉输入模式) |
ADC
ADC引脚 | ADC功能(AFR[AFRn]) | 引脚电气特性(CR[MODE]) |
---|---|---|
ADC_CHn | ADC输入通道n | 模拟输入模式 |
FSMC
所有FSMC信号,无论实际信号的方向,全部设定为“复用推挽输出模式”。
FSMC引脚 | FSMC功能(AFR[AFRn]) | 引脚电气特性(CR[MODE]) |
---|---|---|
FMC_A[0…25] | 数据总线信号 | 复用推挽输出模式 |
FMC_DA[0…15] | 地址总线信号 | 复用推挽输出模式 |
FMC_NCS[0…3] | 输出使能信号 | 复用推挽输出模式 |
FMC_NBS[0…1] | 选高低半字(可为16位字,32位字) | 复用推挽输出模式 |
FMC_NOE | 输出读使能信号 | 复用推挽输出模式 |
FMC_NWE | 输出写使能信号 | 复用推挽输出模式 |
FMC_NADV | 当数据线也能作为地址线时,选择地址有效信号 | 复用推挽输出模式 |
FMC_NWAIT | 地址有效信号 | 复用推挽输出模式 |
QSPI
所有QSPI信号,无论实际信号的方向,全部设定为“复用推挽输出模式”。
QSPI引脚 | QSPI功能(AFR[AFRn]) | 引脚电气特性(CR[MODE]) |
---|---|---|
QSPI_SCK | 串行时钟 | 复用推挽输出模式 |
QSPI_DA[0…3] | 双向数据总线 | 复用推挽输出模式 |
QSPI_NSS | 设备片选线 | 复用推挽输出模式 |
DAC
DAC输出也使用“模拟输入模式”?!!,没错,是的。实际上这里应该将“模拟输入模式”,理解成为“模拟信号模式”,模拟输入和模拟输出都用这个选项。
DAC引脚 | DAC功能(AFR[AFRn]) | 引脚电气特性(CR[MODE]) |
---|---|---|
DAC_OUTn | DAC输出信号总线 | 模拟输入模式 |
COMP
COMP引脚 | DAC功能(AFR[AFRn]) | 引脚电气特性(CR[MODE]) |
---|---|---|
COMP_INP | COMP正极输入端 | 模拟输入模式 |
COMP_INM | COMP负极输入端 | 模拟输入模式 |
SDIO
SDIO所有引脚的功能均需配置成复用推挽输出模式。
SDIO引脚 | SDIO功能(AFR[AFRn]) | 引脚电气特性(CR[MODE]) |
---|---|---|
SDIO_CMD | SDIO命令数据线 | 复用推挽输出模式 |
SDIO_CLK | SDIO同步时钟线 | 复用推挽输出模式 |
SDIO_D[0 … 3] | SDIO数据总线引脚 | 复用推挽输出模式 |
USB
一旦启用USB外设模块,USB相关引脚自动切换为USB功能,此时对应引脚的GPIO配置全部失效。USB功能高于GPIO的配置。例如在MM32F3270芯片上,USB功能的引脚也可以被复用成UART的功能,一旦启用USB功能,原本工作的UART引脚就会失效。
也有常见的做法,是将USB引脚单独拿出来,固定为USB功能,不做复用配置。
Practice
考虑到跟手册尽量保持一致,在SDK的驱动中,对应将复用功能的配置也纳入到gpio驱动中。
typedef enum
{
GPIO_PinMode_In_Analog = 0x00u, /*!< Analog input. */
GPIO_PinMode_In_Floating = 0x04u, /*!< Floating input. */
GPIO_PinMode_In_PullDown = 0x28u, /*!< Pull down input. */
GPIO_PinMode_In_PullUp = 0x48u, /*!< Pull up input. */
GPIO_PinMode_Out_OpenDrain = 0x14u, /*!< Universal open drain output. */
GPIO_PinMode_Out_PushPull = 0x10u, /*!< Universal push-pull output. */
GPIO_PinMode_AF_OpenDrain = 0x1Cu, /*!< Multiplex open drain output. */
GPIO_PinMode_AF_PushPull = 0x18u, /*!< Multiplex push-pull output. */
} GPIO_PinMode_Type;
在SDK的工程结构中,将配置引脚的程序统一放入pin_init.c
文件中的Board_InitPins()
函数中,通过GPIO_Initf()
和GPIO_PinAFConf()
两个API配置一个引脚的复用功能。现在看来,既然这两部分的功能都放在同一个外设模块中,也可以考虑把这两个功能合并成一个API,相当于这两部分的功能,就对应PORT模块配置引脚复用功能。
void BOARD_InitPins(void)
{
GPIO_Init_Type gpio_init;
/* PB6 - UART1_TX. */
gpio_init.Pins = GPIO_PIN_6;
gpio_init.PinMode = GPIO_PinMode_AF_PushPull;
gpio_init.Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &gpio_init);
GPIO_PinAFConf(GPIOB, gpio_init.Pins, GPIO_AF_7);
/* PB7 - UART1_RX. */
gpio_init.Pins = GPIO_PIN_7;
gpio_init.PinMode = GPIO_PinMode_In_Floating;
gpio_init.Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &gpio_init);
GPIO_PinAFConf(GPIOB, gpio_init.Pins, GPIO_AF_7);
/* PC12 - SDIO_CLK. */
gpio_init.Pins = GPIO_PIN_12;
gpio_init.PinMode = GPIO_PinMode_AF_PushPull;
gpio_init.Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &gpio_init);
GPIO_PinAFConf(GPIOC, GPIO_PIN_12, GPIO_AF_12);
/* PD2 - SDIO_CMD. */
gpio_init.Pins = GPIO_PIN_2;
gpio_init.PinMode = GPIO_PinMode_AF_PushPull;
gpio_init.Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOD, &gpio_init);
GPIO_PinAFConf(GPIOD, GPIO_PIN_2, GPIO_AF_12);
/* PC8 - SDIO_DAT0. */
gpio_init.Pins = GPIO_PIN_8;
gpio_init.PinMode = GPIO_PinMode_AF_PushPull;
gpio_init.Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &gpio_init);
GPIO_PinAFConf(GPIOC, GPIO_PIN_8, GPIO_AF_12);
/* PC9 - SDIO_DAT1. */
gpio_init.Pins = GPIO_PIN_9;
gpio_init.PinMode = GPIO_PinMode_AF_PushPull;
gpio_init.Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &gpio_init);
GPIO_PinAFConf(GPIOC, GPIO_PIN_9, GPIO_AF_12);
/* PC10 - SDIO_DAT2. */
gpio_init.Pins = GPIO_PIN_10;
gpio_init.PinMode = GPIO_PinMode_AF_PushPull;
gpio_init.Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &gpio_init);
GPIO_PinAFConf(GPIOC, GPIO_PIN_10, GPIO_AF_12);
/* PC11 - SDIO_DAT3. */
gpio_init.Pins = GPIO_PIN_11;
gpio_init.PinMode = GPIO_PinMode_AF_PushPull;
gpio_init.Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &gpio_init);
GPIO_PinAFConf(GPIOC, GPIO_PIN_11, GPIO_AF_12);
}
Conclusion
。。。