一.前言

        前面我们完成了手柄的硬件设计与调试,现在来分享详细的软件开发过程。

软件已经开源见:https://github.com/qinyunti/py32f002b-key.git。 本着寓教于乐的精神,尽可能详细分享整个过程,本文也可以作为py32f002b的开发参考。

二.开发环境搭建

我这里基于MDK 5.40,编译器版本6.  MDK的安装就不再赘述。

2.1 下载手册和SDK

从以下官网地址https://www.py32.org/mcu/PY32F002Bxx.html下载相关资料

225212tkd1jq1p7bzb9y9k

可以从git地址下载固件库, git clone https://github.com/OpenPuya/PY32F002B_Firmware.git

其他资料可以参考

https://pan.baidu.com/s/1GJoXbWn9oOyeqGn6Igg5DA?pwd=6688&_at_=1737128105692#list/path=%2F%E8%B5%84%E6%96%99%E4%B8%8B%E8%BD%BD%2FMCU%E8%B5%84%E6%96%99%2FPY32%E7%B3%BB%E5%88%97%E5%8D%95%E7%89%87%E6%9C%BA

2.2安装MDK支持包

可以如下MDK中在线安装

225212rij5kjaz1nzehahw

2.3创建工程

Project->New uVision Project...

选择目录,输入工程名保存

225213z515nsk05r01x255

选择芯片型号

225213jo9dyvppffzxyfzh

选择startup

225213qce2ysbdekl5demi

配置选项

225213z3rprg8g2vzmmpg7

定义宏PY32F002Bx5

225214uvjlg00pjxajjl20

File -> New...

Ctrl+S保存

保存为main.c

225214lts4oj4k5zdmdttz

在Source Group1上点击右键,添加文件

225214dh36jd858q95qb66

选择刚才添加的main.c

225214md1zxerfx08oce8d

编译

225214cmm2m7xf6t36prq7

右键点击Target1->Option for Target

225214ynl9pryvrpv44s4t

选择仿真器,我这里是CMSIS-DAP

225214pgcoi0510otz08is

然后点击Settings

看到识别到了芯片

225214hcxxxsahhats4jib

确认flash下载算法

225214r3z033ifppfwvhgb

如果没有则点击Add添加,算法文件位于D:\Keil_v5\Packs\Puya\PY32F0xx_DFP\1.2.2\Flash

225214wc8rdaci9zqaiac2

将上述文件复制到

D:\Keil_v5\ARM\Flash

此时点击ADD就可以看到上述算法

225215khrqp6nhkbhgggqb

点击红色d按钮,下载并进入仿真环境

225215ud558v5lu5ldv5u1

此时可以仿真调试,说明一切就绪。

2.4添加驱动库

将git上下载的固件库的PY32F002B_HAL_Driver复制到自己的工程,并添加

点击Target右键,添加Group

225215dykt44wj4w3o32z3

选中Fn+F2改名为Driver

双击Driver,添加所有c和h文件

225215svnkczenozkuon1k

并添加头文件包含路径

225215ke39617whehh387f

从D:\Keil_v5\Packs\Puya\PY32F0xx_DFP\1.2.2\MDK\Templates\PY32F002B\Inc下复制

py32f002b_hal_conf.h到自己的工程,同样添加其所在路径到头文件包含路径。

三.OPT编程方法

硬件上PC0控制了一个LED

默认PC0为复位功能,可以修改为IO功能。

需要修改opt,以下以修改PC0为IO为例介绍。

225216a2mp4mrwrwizlio3

对应的opt位置如下,需要将该选项字节修改为10,即PC0作为IO,PB6作为SWD

225216skd96xp3ckd0066z

先要添加opt算法,见前面创建工程章节

225216naemea8mujlvxtxx

然后新建

文件py32f002b_OPT.s添加到工程,内容为

;/*****************************************************************************/

;/* py32f002b_OPT.S: y32f002b Flash Option Bytes                              */

;/*****************************************************************************/

;/* <<< Use Configuration Wizard in Context Menu >>>                          */

;/*****************************************************************************/

;/* This file is part of the uVision/ARM development tools.                   */

;/* Copyright (c) 2005-2008 Keil Software. All rights reserved.               */

;/* This software may only be used under the terms of a valid, current,       */

;/* end user licence from KEIL for a compatible version of KEIL software      */

;/* development tools. Nothing else gives you the right to use this software. */

;/*****************************************************************************/

;// <e> Flash Option Bytes

FLASH_OPT       EQU     0

;// </e>

                IF      FLASH_OPT <> 0

                AREA    |.ARM.__AT_0x1FFF0080|, CODE, READONLY

                DCB     0xAA,  0xDE,  0x55,  0x21

                DCB     0x0B,  0x00,  0xF4,  0xFF

                DCB     0x00,  0x00,  0xFF,  0xFF   

                DCB     0x3F,  0x00,  0xC0,  0xFF  

                ENDIF

                END

225216unzid9v2rrmmdnsr

如下位置勾选则下载opt,否则不下载

225216snelilz70hlhn1e1

实际就是一个条件编译,控制是否有定位在地址0x1FFF0080处的16字节数据

225216rjb6vgmxjsn5a69m

这16字节数据可以参考手册的说明,手动修改,也可以

从官网下载PY32OptionBytesConfig_x64.exe生成

https://py32.org/tool/PY32_OptionBytesConfig.html

225217jaeju5kjjhkqutx4

按照如下配置,成成bin文件

225217gy5mhtkatm7y7qi0

用16进制编辑工具打开查看该16字节内容,复制到上述代码中去。

以上之后直接点击d就会和程序一起下载。

下载完成之后这里不勾选,下次就不会再下载opt了。

225217dkkkeqe52qvlm2l0

四. LED测试

以上修改opt之后,pc0就变为了IO功能,用于驱动LED

初始化

void led_init(void){

  GPIO_InitTypeDef  GPIO_InitStruct;

  __HAL_RCC_GPIOC_CLK_ENABLE();                          /* Enable GPIOA clock */

  GPIO_InitStruct.Pin = GPIO_PIN_0;

  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;            /* Push-pull output */

  GPIO_InitStruct.Pull = GPIO_PULLUP;                    /* Enable pull-up */

  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;          /* GPIO speed */  

  /* GPIO initialization */

  HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);  

}

然后HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_0);就可以反转控制LED。

五.串口驱动/蓝牙模块控制

我们接收中断+FIFO实现串口接收,发送使用查询方式。

FIFO的实现见公众号文章。

https://mp.weixin.qq.com/s/MvL9eDesyuxD60fnbl1nag?token=1631959641&lang=zh_CN

串口驱动实现见公众号文章

https://mp.weixin.qq.com/s/VEEGGd1L5pJ4a5hIUrYzlw?token=1631959641&lang=zh_CN

代码见uart.c/h,fifo.c/h

代码考虑了可移植性,可以方便的移植。

225217qrjywcziw4pw0p0i

串口使用PA6和PA7(见原理图),初始化如下

void uart_init(uint32_t baud)

{

         UART_HandleTypeDef huart;


                GPIO_InitTypeDef  GPIO_InitStruct;

                __HAL_RCC_GPIOA_CLK_ENABLE();                          /* Enable GPIOA clock */

          __HAL_RCC_USART1_CLK_ENABLE();


                GPIO_InitStruct.Pin = GPIO_PIN_6 | GPIO_PIN_7;

                GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;                /* Push-pull output */

                GPIO_InitStruct.Pull = GPIO_PULLUP;                    /* Enable pull-up */

                GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM;          /* GPIO speed */  

          GPIO_InitStruct.Alternate = GPIO_AF1_USART1;           /* PA6 AF1 USART_TX*/

                /* GPIO initialization */

                HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);  


         //LL_USART_InitTypeDef USART_InitStruct;

         //USART_InitStruct.BaudRate = baud;

         //USART_InitStruct.DataWidth = 16;

         //USART_InitStruct.HardwareFlowControl = LL_USART_HWCONTROL_NONE;

         //USART_InitStruct.OverSampling = LL_USART_OVERSAMPLING_8;

         //USART_InitStruct.Parity = LL_USART_PARITY_NONE;

         //USART_InitStruct.StopBits = LL_USART_STOPBITS_1;

         //USART_InitStruct.TransferDirection = LL_USART_DIRECTION_TX_RX;

         //LL_USART_Init(USART1, &USART_InitStruct);

         //LL_USART_EnableIT_RXNE(USART1);


         huart.Instance = USART1;

         huart.Init.BaudRate = baud;

         huart.Init.WordLength = UART_WORDLENGTH_8B;

         huart.Init.StopBits = UART_STOPBITS_1;

         huart.Init.Parity = UART_PARITY_NONE;

         huart.Init.HwFlowCtl = UART_HWCONTROL_NONE;

         huart.Init.Mode = UART_MODE_TX_RX;

         huart.Init.OverSampling = UART_OVERSAMPLING_8;

         HAL_UART_Init(&huart);


         //24000000/115200 = 208.3333

         //USART1->BRR = (208ul<<4) + 5;

         __HAL_UART_ENABLE_IT(&huart, UART_IT_RXNE);

         HAL_NVIC_SetPriority(USART1_IRQn,1,1);

         HAL_NVIC_EnableIRQ(USART1_IRQn);

}

串口接收中断时数据写入FIFO中

void uart_rx_cb(uint8_t* buff, uint32_t len)

{

        fifo_in(&s_uart_fifo_dev, buff, len);

}

void USART1_IRQHandler(void)

{

        uint8_t ch;

        ch = LL_USART_ReceiveData8(USART1);

        uart_rx_cb(&ch, 1);

}

读数据就是从FIFO取出数据

uint32_t uart_read(uint8_t* buffer, uint32_t len)

{

    uint32_t rlen;

    CriticalAlloc();

    EnterCritical();

    rlen = fifo_out(&s_uart_fifo_dev, buffer, len);

    ExitCritical();

    return rlen;

}

发送采用查询方式,发送其实也可以中断+FIFO方式,这里为了简单就直接查询方式。

uint32_t uart_send(uint8_t* buffer, uint32_t len)

{

    for(uint32_t i=0;i<len;i++)

    {

                        while(LL_USART_IsActiveFlag_TXE(USART1) == 0);

                        LL_USART_TransmitData8(USART1,buffer);

    }

    return len;

}

FIFO实例以及临界段接口

#define CriticalAlloc()

#define EnterCritical()                              __disable_irq()

#define ExitCritical()                              __enable_irq()

uint8_t s_uart_rx_buffer[64];

static fifo_st s_uart_fifo_dev=

{

.in = 0,

.len = 0,

.out = 0,

.buffer = s_uart_rx_buffer,

.buffer_len = sizeof(s_uart_rx_buffer),

};

main.c中初始化串口

                uart_init(115200);

然后调用                                uart_send(s_key_buf,sizeof(s_key_buf));即可发送数据。

蓝牙模块使用AT指令详见模块手册即可。

六.IIC驱动与PCF8574按键采集

IIC直接使用IO模拟的方式,因为有现成的代码,简单方便。

代码如下

225217oyrykxc0f0xlkakl

见公众号文章

https://mp.weixin.qq.com/s/ESzWWqxHpQevsWfjV0s2VQ?token=1631959641&lang=zh_CN

Pcf8574驱动,设计为完全独立的模块,只依赖iic操作,无需任何修改完全可移植。

具体操作参考手册即可。

225217rjyrdkfzr2fmaaax

基于IIC和Pcf8574再封装按键获取接口,见key.c/h

#ifndef KEY_H

#define KEY_H

#ifdef __cplusplus

    extern "C"{

#endif

#include <stdint.h>

void key_init(void);

void key_deinit(void);

int key_write(uint8_t val);

int key_read(uint8_t* val);

#ifdef __cplusplus

    }

#endif

#endif

Key.c实现IIC实例,需要的IO接口

static io_iic_dev_st iic_dev=

{

        .scl_write = io_iic_scl_write_port,

        .sda_write = io_iic_sda_write_port,

        .sda_2read = io_iic_sda_2read_port,

        .sda_read = io_iic_sda_read_port,

        .delay_pf = io_iic_delay_us_port,

        .init = io_iic_init_port,

        .deinit = io_iic_deinit_port,

        .delayus = 1,

};

实现pcf8574实例和需要的接口

pcf8574_dev_st pcf8574_dev=

{

        .start = pcf8574_iic_start_port,

        .stop = pcf8574_iic_stop_port,

        .read = pcf8574_iic_read_port,

        .write = pcf8574_iic_write_port,

        .init = pcf8574_iic_init_port,

        .deinit = pcf8574_iic_deinit_port,

        .addr = 0,

};

详见代码。

在main.c中

初始化

          HAL_Init();

          HAL_Delay(1000);

          led_init();

                key_init();

                uart_init(115200);

蓝牙初始化

#define AT_CONN "AT+CONN=D2BC14D9F46B\r\n"

                uart_send((uint8_t*)AT_CONN,strlen(AT_CONN));

          HAL_Delay(1000);

发送该命令连接从蓝牙,进入透传模式,D2BC14D9F46B为从模块的地址,具体获取参考蓝牙模块的规格书。

按键和LED使用时间控制

                uint32_t led_pre_ts = 0;

                uint32_t led_cur_ts = 0;

                uint32_t key_pre_ts = 0;

                uint32_t key_cur_ts = 0;

LED1S反转一次

          led_pre_ts = HAL_GetTick();

          key_pre_ts = HAL_GetTick();


                while (1){


                        led_cur_ts = HAL_GetTick();

                        key_cur_ts = HAL_GetTick();


                        if(diff_u32(led_pre_ts, led_cur_ts) >= LED_PERIOD){

                                led_pre_ts = led_cur_ts;

                                HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_0);

                        }

按键20ms获取一次并发送

                        if(diff_u32(key_pre_ts, key_cur_ts) >= KEY_PERIOD){

                                key_pre_ts = key_cur_ts;

                                key_read(&key_now);

                                s_key_buf[2] = key_now;

                                s_key_buf[3] = ~key_now;

                                uart_send(s_key_buf,sizeof(s_key_buf));

                        }

七.测试

测试见视频,先蓝牙从模块接电脑,可以看到每一个按键都正确。

八.总结

以上详细分享了py32f002b的开发过程,包括串口,LED控制,按键等模块的驱动。可以作为py32ff002b的入门开发参考。