PY32F003 系列 MCU
国产 32 位 MCU 日渐风行。在新做的项目中,为了 Cost-down,考虑要用国产 MCU 替代进口货,如果可行,在单片机这一块,BOM 可以降低一块钱。近日在考虑普冉(PUYA)的32位MCU。由于板子上 MCU 所需功能较为单一,因此考虑使用入门级的一款 MCU 进行替代。最终选择了 PY32F003F18P。
- 这个型号采用 TSSOP20 封装,PCB 占用面积比较小
- 虽然配备的内核是 Cortex-M0+,但其工作频率高达 48MHz(下图简介中的 32MHz 工作频率好像是笔误),单周期乘除法,软浮点运算,估计运算速度是够用的
- 存储器容量也较大,64KByte Flash 和 8KByte SRAM,代码足够装得下
- UART 外设有两个,满足使用需求
- 1个 ADC,具有10个通道,满足使用需求
- 3个 DMA 通道,满足使用需求
- 至少5个可用的 16 位定时器,满足使用需求
开发环境:硬件连接
网购了一块 PY32F003F18P 芯片组的开发板(很便宜,良心价),买回来后自己焊接了管脚排针和 SWD 排针,然后用上了 J-Link 仿真器,为 J-Link 仿真器配备了一个 JTAG-SWD 转接板,用四芯杜邦线连接开发板。开发板配备了两个跳线器,一个跳线器连接了 3V3_IN 和 3V3,这样子接可以使用 SWD 的 3.3V 电源,省去了从 Type-C 供电。另一个跳线器把 BOOT0 对地短路,使 MCU 可以从内部 FLASH 启动。BOOT0 是 PA14 管脚,很好找。为了做实验,还另外连接了开发板的 UART2 (PA1:TX,PA0:RX)接到 USB-UART 转接模块上,再连接到上位机。
开发板上的 SWD 排针是5芯的,JTAG-SWD 接了 4 根线,多出来的是 RST 信号线。估摸着这个 RST 线应该是不用接的,于是就不接先。至此,硬件连接就 OK 了。
开发环境:软件配置
使用 USART_IT 例程
购买开发板时带有 PUYA MCU 的资料下载地址,于是从指定的地址下载了 PUYA 的开发包。开发包中有芯片手册和例程。这里选择了 USART_IT 例程,选择这个例程里的 Keil 工程,直接用 Keil uVision 打开,如下图所示。
查看一下这个工程的 Option,如下图所示。需要注意的是这个工程还包含了和其它例程共用的程序,例如 BSP/py32f003xx_Start_Kit中的 .c 文件,这些文件的存储位置不在例程文件夹里。
Project Items 的截图如下,默认使用 ARM Compiler:ARMGCC。先不改这些,继续往下走。
导入 PY32F003 的 DFP Pack
在开始编译工程以前,需要导入 PY MCU 的 DFP pack,这个 pack 包含在了软件包中的 pack 文件夹里,使用 Pack Installer --> Import 进来。Import 完成后,在产品系列中选择 PY32F003x8就好了。Packs Installer 界面右侧的部件显示 DFP 是 “Up to date” 状态。其它的都不修改先,继续!
使用魔术棒配置Target Options
确认 Device 正确
检查 Target 选项
- 设置 Xtal (外部晶振)的频率为 24MHz,和开发板保持一致
- ARM Compiler:使用默认的 5.x 版本的编译器
- Operation system:None
- 选中 “Use MicroLIB”,这个选项会影响到 printf 的编码
- 检查和确认 IROM1 和 IRAM1 的范围
检查和确认 C/C++ 选项
- Define PY32F003x8 变量
- 选中 C99
- 选中 “One ELF Section per Function”
- Include Path 和 Compile control string 不要修改
配置仿真器(Debug)
PY 文档中举例使用的 PY-LINK 仿真器,本例中使用的是 J-LINK,所以仿真器应做相应的修改。其它选项不做修改。
点击仿真器的 Setting,第一次运行时会得到一个错误对话框,提示 J-Link 不认识这颗 MCU,点击继续,选择 Cortex-M0+,继续,得到 J-Link 对 MCU 的识别参数如下图所示。
- 把 SWD 的速度设置得低一点:1MHz,避免下载时的不可靠
- Reset 设置成 Normal
- 为了确保下载正确,勾选 “Verify Code Downloaded” 和 “Download to Flash”
- Flash Download 选项卡选择 “Erase Sectors”(默认是 “Erase Full Chip”)
- 检查 “RAM for Algorithm” 的值如图所示
- 确认 “Programming Algorithm” 的值,Start 和 Size 值如图所示
- Utility 选项卡中 “Use Debug Driver” 和 “Update Target before Debugging”
编译工程
点击 “LOAD” 键一键下载。第一次编译是成功的,但是会提示烧写失败,不要紧,再下载一次就成了。例程的功能如下:
- 设置 UART2 的参数为 115200/N/8/1
- 设置 UART2 为中断式无阻塞收发
- 启动后,从 UART2 发送 0x01 到 0x0C
- 回送每一次从 UART2 接收到的固定 12 个字节的字节流到对端
实验证明,运行是成功的。
重定向 printf
- 在 main.c 的 #include “main.h” 下一行添加 #include <stdio.h>
- /* Private user code 一节中增加 fputc 函数(参见后面的 main.c 代码)
- 编译时会出现错误,提示 fputc 函数在 py32f003xx_Start_Kit.c 中和 main.c 中重复定义了。打开 py32f003xx_Start_Kit.c 文件,把 fputc 函数注释掉。
- 重新编译下载,成功了。在上位机的 XCOM 中实现了预期的功能。
main.c 的完整代码
#include "main.h"
#include <stdio.h>
/* Private define ------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
UART_HandleTypeDef UartHandle;
uint8_t aTxBuffer[12] = {'1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', '\r', '\n'};
uint8_t aRxBuffer[12] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
/* Private function prototypes -----------------------------------------------*/
/* Private user code ---------------------------------------------------------*/
/*
* Redirect printf to Uart2 (UartHandle refers to USART2)
*/
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&UartHandle, (uint8_t *)(&ch), 1, 0xffff);
return ch;
}
/* Private macro -------------------------------------------------------------*/
/* Private function prototypes -----------------------------------------------*/
void USART_Config(void);
void Error_Handler(void);
/********************************************************************************************************
**函数信息 :void main(void)
**功能描述 :main函数
**输入参数 :
**输出参数 :
** 备注 :
********************************************************************************************************/
int main(void)
{
HAL_Init(); // systick初始化
USART_Config(); // USART初始化
printf("PY32F003F18P is ready.\r\n");
HAL_Delay(500);
/*通过中断方式发送数据*/
if (HAL_UART_Transmit_IT(&UartHandle, (uint8_t *)aTxBuffer, 12) != HAL_OK)
{
Error_Handler();
}
while (1)
{
}
}
/********************************************************************************************************
**函数信息 :void USART_Config(void)
**功能描述 :USART初始化
**输入参数 :
**输出参数 :
** 备注 :
********************************************************************************************************/
void USART_Config(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
//====================
// USART2初始化
//====================
__HAL_RCC_USART2_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
UartHandle.Instance = USART2;
UartHandle.Init.BaudRate = 115200;
UartHandle.Init.WordLength = UART_WORDLENGTH_8B;
UartHandle.Init.StopBits = UART_STOPBITS_1;
UartHandle.Init.Parity = UART_PARITY_NONE;
UartHandle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
UartHandle.Init.Mode = UART_MODE_TX_RX;
if (HAL_UART_Init(&UartHandle) != HAL_OK)
{
Error_Handler();
}
/**USART2 GPIO Configuration
PA0 ------> USART2_TX
PA1 ------> USART2_RX
*/
GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF9_USART2;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* 使能NVIC */
HAL_NVIC_SetPriority(USART2_IRQn, 0, 1);
HAL_NVIC_EnableIRQ(USART2_IRQn);
}
/********************************************************************************************************
**函数信息 :Error_Handler(void)
**功能描述 :错误执行函数
**输入参数 :
**输出参数 :
** 备注 :
********************************************************************************************************/
void Error_Handler(void)
{
while (1)
{
}
}
/********************************************************************************************************
**函数信息 :void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
**功能描述 :USART错误回调执行函数,输出错误代码
**输入参数 :
**输出参数 :
** 备注 :
********************************************************************************************************/
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
printf("Uart Error, ErrorCode = %d\r\n", huart->ErrorCode);
}
/********************************************************************************************************
**函数信息 :void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
**功能描述 :USART发送回调执行函数
**输入参数 :
**输出参数 :
** 备注 :
********************************************************************************************************/
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *UartHandle)
{
if (HAL_UART_Receive_IT(UartHandle, (uint8_t *)aRxBuffer, 12) != HAL_OK)
{
Error_Handler();
}
}
/********************************************************************************************************
**函数信息 :void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
**功能描述 :USART接收回调执行函数
**输入参数 :
**输出参数 :
** 备注 :
********************************************************************************************************/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *UartHandle)
{
/*通过中断方式接收数据*/
if (HAL_UART_Transmit_IT(UartHandle, (uint8_t *)aRxBuffer, 12) != HAL_OK)
{
Error_Handler();
}
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* User can add his own implementation to report the file name and line number,
tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
}
#endif /* USE_FULL_ASSERT */
最终的代码中做了一些小修改,使程序可读性更强些:
- 初始化完成后,打印一条 Ready 消息
- 把初次发送的二进制数修改为 1,2,3 ... 9,0,回车,换行,共 12 个可打印字符
- 上位机的 XCOM 发送 10 个数字,选中“发送新行”,也就是加入回车和换行符,凑够 12 个字符
总结
- 导入 DFP 的 pack 以后,使用 Keil MDK 可以正确配置 PY32F003x8
- 空芯片的初次烧写会失败,但是后续烧写会成功。怀疑是不是初次烧写执行了类似 “Unlock Flash” 的操作,就像 J-Link 的 ARM-Flash 程序一样的功能?
- 使用 1MHz 的 SWD 烧写速度,会出现小概率的烧写失败的情况,这时重新烧写一般都会成功。实验中没有出现反复多次烧写不成功的情况。后续再尝试降低一些 SWD 的速率看看
- J-Link V8 不识别 PY 单片机,只能用 Cortex-M0+ 系列产品代替,代替后可以正常烧写
- 本例中用到的 HAL 库函数和 STM32 系列的相同,GPIO_InitStruct 的定义和操作方法和 STM32 系列也完全相同
- PY32F003x8 的 HAL_Receive_IT 函数和 HAL_Transmit_IT 函数的语法和作用,至少从功能上和 STM32 的相同。HAL_NVIC_xxx 函数接口和 STM32 也相同。也难怪,都是 Cortex-M0+ 的构架,代码通用也挺好的
- PY32F003x8 的 UART 运行在 115200 波特率没问题。
- 开发板上的 SWD RST 可以不接
初次尝试,可能对打算应用 PUYA 单片机的朋友有所借鉴,谬误之处,不吝指出。