在PY32F0系列的封装可以分为两大类, 20PIN及以上的和小于20PIN的.
这篇主要介绍没有BOOT0的情况如何修改Option Bytes, 以及如何在物理管脚上使用不同的PIN
可以看到 SOP8 和 SOP10 存在复用情况
因为PY32F003型号较多, 这里只列出小于20PIN的封装
从上面的管脚配置可以看到, 大部分型号都存在同一物理管脚的复用情况, 有一些是功能脚(PF2/NRST)与普通IO脚的复用.
PF2/NRST这个PIN是比较麻烦的一个功能脚, 因为默认启用了RESET功能, 不受PIN模式的影响, 所以无论你把它设置成INPUT, OUTPUT 还是 ANALOG, RESET永远生效, 和这个PIN同处于同一个物理管脚的PIN就没法正常使用.
要禁用它的RESET功能, 要在芯片的 OB(Option Bytes)里修改. OB 位于地址 0x1FFF 0E80, 占用4个字节, 其中2字节是配置, 另外2字节是这两个字节的反码. 对应 RESET 功能的设置 NRST_MODE 存储于第14位, 0表示仅复位输入, 1表示禁用复位输入,启用 GPIO 功能.
对于正常带 PF4/BOOT0 的型号, 在上电时拉高 BOOT0, 就可以从 system memory 启动 boot loader, 通过 ISP 工具连接后在工具里修改 OB, 但是 SOP8 和 SOP16 这些封装没有 BOOT0, 所以没法使用 ISP 工具修改. 只能通过代码或第三方工具(例如JLink)修改. 以下以LL库为例, 说明在代码中修改OB的方法
在OB中关闭PF2复位输入的方法
static void APP_FlashSetOptionBytes(void) |
|
{ |
|
FLASH_OBProgramInitTypeDef OBInitCfg; |
|
LL_FLASH_Unlock(); |
|
LL_FLASH_OB_Unlock(); |
|
OBInitCfg.OptionType = OPTIONBYTE_USER; |
|
OBInitCfg.USERType = OB_USER_BOR_EN | OB_USER_BOR_LEV | OB_USER_IWDG_SW | OB_USER_WWDG_SW | OB_USER_NRST_MODE | OB_USER_nBOOT1; |
|
/* |
|
* 默认的值: OB_BOR_DISABLE | OB_BOR_LEVEL_3p1_3p2 | OB_IWDG_SW | OB_WWDG_SW | OB_RESET_MODE_RESET | OB_BOOT1_SYSTEM; |
|
*/ |
|
OBInitCfg.USERConfig = OB_BOR_DISABLE | OB_BOR_LEVEL_3p1_3p2 | OB_IWDG_SW | OB_WWDG_SW | OB_RESET_MODE_GPIO | OB_BOOT1_SYSTEM; |
|
LL_FLASH_OBProgram(&OBInitCfg); |
|
LL_FLASH_Lock(); |
|
LL_FLASH_OB_Lock(); |
|
/* 重新载入OB, 这会触发软复位, MCU重启 */ |
|
LL_FLASH_OB_Launch(); |
|
} |
注意, 上面这个方法执行后会重启MCU, 所以在调用前要做个判断, 否则它会一直循环重启下去
/* 检查 PF2 是否已经关闭了复位 */ |
|
if(READ_BIT(FLASH->OPTR, FLASH_OPTR_NRST_MODE) == OB_RESET_MODE_RESET) |
|
{ |
|
/* 如果没关闭则调用 */ |
|
APP_FlashSetOptionBytes(); |
|
} |
|
// 否则继续正常执行 |
这样执行完之后, RESET按钮就失效了, 如果要恢复, 要再将OB改回默认的值
OB_BOR_DISABLE | OB_BOR_LEVEL_3p1_3p2 | OB_IWDG_SW | OB_WWDG_SW | OB_RESET_MODE_RESET | OB_BOOT1_SYSTEM; |
以下以SOP16封装的为例, 启用 PF1, PF0, 禁用对应同一管脚的 PA14 和 PF2
static void APP_GPIO_Config(void) |
|
{ |
|
//... |
|
// PF1 SCL |
|
GPIO_InitStruct.Pin = LL_GPIO_PIN_1; |
|
GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE; |
|
GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_HIGH; |
|
GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_OPENDRAIN; |
|
GPIO_InitStruct.Pull = LL_GPIO_PULL_UP; |
|
GPIO_InitStruct.Alternate = LL_GPIO_AF_12; |
|
LL_GPIO_Init(GPIOF, &GPIO_InitStruct); |
|
// PF0 SDA |
|
GPIO_InitStruct.Pin = LL_GPIO_PIN_0; |
|
GPIO_InitStruct.Alternate = LL_GPIO_AF_12; |
|
LL_GPIO_Init(GPIOF, &GPIO_InitStruct); |
|
/** |
|
* 根据数据手册第20页, 同管脚的其它PIN应当设为 ANALOG. |
|
*/ |
|
// PA14 |
|
LL_GPIO_SetPinMode(GPIOA, LL_GPIO_PIN_14, LL_GPIO_MODE_ANALOG); |
|
// PF2 |
|
LL_GPIO_SetPinMode(GPIOF, LL_GPIO_PIN_2, LL_GPIO_MODE_ANALOG); |
|
//... |
|
} |
管脚复用之后, 一些功能脚带的开关按钮和电阻电容就会对其它PIN造成影响.
例如对于复位键, 如果上面加了电容, 其容量一般是104(100nF), 用于避免按键抖动, 如果将这个脚禁用复位, 改为I2C的输出, 这个电容就会对输出信号造成干扰, 100nF的容量基本能消除掉1KHz以上的频率, 所以要将这样的电容去掉.
因为小封装没有 BOOT0, 所以在 SWD 口烧录失败的情况下, 没法用 ISP 工具救场, 如果你的程序加电后没有预留足够长时间的 delay, 又把 SWD 口的 PA13 PA14 给关掉了, 那下一次烧录就会干瞪眼.
一个好习惯是在设置完时钟之后, 保留一到两秒的延时, 可以在加电后从容不迫地按下烧录按钮.
int main(void) |
|
{ |
|
uint8_t i; |
|
BSP_RCC_HSI_24MConfig(); |
|
/** |
|
* 在SWD口关闭前停留2秒, 保证上电后有足够长的烧录等待时间 |
|
*/ |
|
LL_mDelay(2000); |
|
//... |
以 SOP16 封装的 PY32F003W18S 为例, 依然使用 1602LCD 作为参考.
代码通过禁用 PA14 和 PF2, 将 PF1 和 PF0 设置为 I2C 外设接口, 驱动 1602LCD.
源代码已经提交到 GitHub 仓库, 地址: https://github.com/IOsetting/py32f0-template/tree/main/Examples/LL/I2C/PCF8574_1602LCD_PY32F003W_PF0_PF1
运行示例