普冉(PUYA)单片机开发笔记(1): UART通信

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 位定时器,满足使用需求

普冉(PUYA)单片机开发笔记(1): UART通信_第1张图片

开发环境:硬件连接

网购了一块 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 了。

普冉(PUYA)单片机开发笔记(1): UART通信_第2张图片

普冉(PUYA)单片机开发笔记(1): UART通信_第3张图片

普冉(PUYA)单片机开发笔记(1): UART通信_第4张图片

开发环境:软件配置

使用 USART_IT 例程

购买开发板时带有 PUYA MCU 的资料下载地址,于是从指定的地址下载了 PUYA 的开发包。开发包中有芯片手册和例程。这里选择了 USART_IT 例程,选择这个例程里的 Keil 工程,直接用 Keil uVision 打开,如下图所示。

普冉(PUYA)单片机开发笔记(1): UART通信_第5张图片

查看一下这个工程的 Option,如下图所示。需要注意的是这个工程还包含了和其它例程共用的程序,例如 BSP/py32f003xx_Start_Kit中的 .c 文件,这些文件的存储位置不在例程文件夹里。

普冉(PUYA)单片机开发笔记(1): UART通信_第6张图片

Project Items 的截图如下,默认使用 ARM Compiler:ARMGCC。先不改这些,继续往下走。

普冉(PUYA)单片机开发笔记(1): UART通信_第7张图片

导入 PY32F003 的 DFP Pack

在开始编译工程以前,需要导入 PY MCU 的 DFP pack,这个 pack 包含在了软件包中的 pack 文件夹里,使用 Pack Installer --> Import 进来。Import 完成后,在产品系列中选择 PY32F003x8就好了。Packs Installer 界面右侧的部件显示 DFP 是 “Up to date” 状态。其它的都不修改先,继续!

普冉(PUYA)单片机开发笔记(1): UART通信_第8张图片

普冉(PUYA)单片机开发笔记(1): UART通信_第9张图片

使用魔术棒配置Target Options

确认 Device 正确

普冉(PUYA)单片机开发笔记(1): UART通信_第10张图片

检查 Target 选项

  • 设置 Xtal (外部晶振)的频率为 24MHz,和开发板保持一致
  • ARM Compiler:使用默认的 5.x 版本的编译器
  • Operation system:None
  • 选中 “Use MicroLIB”,这个选项会影响到 printf 的编码
  • 检查和确认 IROM1 和 IRAM1 的范围

普冉(PUYA)单片机开发笔记(1): UART通信_第11张图片

普冉(PUYA)单片机开发笔记(1): UART通信_第12张图片

检查和确认 C/C++ 选项

  • Define PY32F003x8 变量
  • 选中 C99
  • 选中 “One ELF Section per Function”
  • Include Path 和 Compile control string 不要修改

普冉(PUYA)单片机开发笔记(1): UART通信_第13张图片

配置仿真器(Debug)

PY 文档中举例使用的 PY-LINK 仿真器,本例中使用的是 J-LINK,所以仿真器应做相应的修改。其它选项不做修改。

普冉(PUYA)单片机开发笔记(1): UART通信_第14张图片

点击仿真器的 Setting,第一次运行时会得到一个错误对话框,提示 J-Link 不认识这颗 MCU,点击继续,选择 Cortex-M0+,继续,得到 J-Link 对 MCU 的识别参数如下图所示。

  • 把 SWD 的速度设置得低一点:1MHz,避免下载时的不可靠
  • Reset 设置成 Normal
  • 为了确保下载正确,勾选 “Verify Code Downloaded” 和 “Download to Flash”

普冉(PUYA)单片机开发笔记(1): UART通信_第15张图片

  • Flash Download 选项卡选择 “Erase Sectors”(默认是 “Erase Full Chip”)
  • 检查 “RAM for Algorithm” 的值如图所示
  • 确认 “Programming Algorithm” 的值,Start 和 Size 值如图所示

普冉(PUYA)单片机开发笔记(1): UART通信_第16张图片

  • Utility 选项卡中 “Use Debug Driver” 和 “Update Target before Debugging”

普冉(PUYA)单片机开发笔记(1): UART通信_第17张图片

编译工程

点击 “LOAD” 键一键下载。第一次编译是成功的,但是会提示烧写失败,不要紧,再下载一次就成了。例程的功能如下:

  1. 设置 UART2 的参数为 115200/N/8/1
  2. 设置 UART2 为中断式无阻塞收发
  3. 启动后,从 UART2 发送 0x01 到 0x0C
  4. 回送每一次从 UART2 接收到的固定 12 个字节的字节流到对端

普冉(PUYA)单片机开发笔记(1): UART通信_第18张图片

实验证明,运行是成功的。

重定向 printf

  1. 在 main.c 的 #include “main.h” 下一行添加 #include
  2. /* Private user code 一节中增加 fputc 函数(参见后面的 main.c 代码)
  3. 编译时会出现错误,提示 fputc 函数在 py32f003xx_Start_Kit.c 中和 main.c 中重复定义了。打开 py32f003xx_Start_Kit.c 文件,把 fputc 函数注释掉。
  4. 重新编译下载,成功了。在上位机的 XCOM 中实现了预期的功能。

普冉(PUYA)单片机开发笔记(1): UART通信_第19张图片

main.c 的完整代码

#include "main.h"
#include 

/* 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 */

普冉(PUYA)单片机开发笔记(1): UART通信_第20张图片

最终的代码中做了一些小修改,使程序可读性更强些:

  1. 初始化完成后,打印一条 Ready 消息
  2. 把初次发送的二进制数修改为 1,2,3 ... 9,0,回车,换行,共 12 个可打印字符
  3. 上位机的 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 单片机的朋友有所借鉴,谬误之处,不吝指出。

你可能感兴趣的:(PY,MCU,嵌入式硬件,单片机,mcu,物联网)