一.前言
前面我们完成了手柄的硬件设计与调试,现在来分享详细的软件开发过程。
软件已经开源见:https://github.com/qinyunti/py32f002b-key.git。 本着寓教于乐的精神,尽可能详细分享整个过程,本文也可以作为py32f002b的开发参考。
二.开发环境搭建
我这里基于MDK 5.40,编译器版本6. MDK的安装就不再赘述。
2.1 下载手册和SDK
从以下官网地址https://www.py32.org/mcu/PY32F002Bxx.html下载相关资料
可以从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中在线安装
2.3创建工程
Project->New uVision Project...
选择目录,输入工程名保存
选择芯片型号
选择startup
配置选项
定义宏PY32F002Bx5
File -> New...
Ctrl+S保存
保存为main.c
在Source Group1上点击右键,添加文件
选择刚才添加的main.c
编译
右键点击Target1->Option for Target
选择仿真器,我这里是CMSIS-DAP
然后点击Settings
看到识别到了芯片
确认flash下载算法
如果没有则点击Add添加,算法文件位于D:\Keil_v5\Packs\Puya\PY32F0xx_DFP\1.2.2\Flash
将上述文件复制到
D:\Keil_v5\ARM\Flash
此时点击ADD就可以看到上述算法
点击红色d按钮,下载并进入仿真环境
此时可以仿真调试,说明一切就绪。
2.4添加驱动库
将git上下载的固件库的PY32F002B_HAL_Driver复制到自己的工程,并添加
点击Target右键,添加Group
选中Fn+F2改名为Driver
双击Driver,添加所有c和h文件
并添加头文件包含路径
从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为例介绍。
对应的opt位置如下,需要将该选项字节修改为10,即PC0作为IO,PB6作为SWD
先要添加opt算法,见前面创建工程章节
然后新建
文件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
如下位置勾选则下载opt,否则不下载
实际就是一个条件编译,控制是否有定位在地址0x1FFF0080处的16字节数据
这16字节数据可以参考手册的说明,手动修改,也可以
从官网下载PY32OptionBytesConfig_x64.exe生成
https://py32.org/tool/PY32_OptionBytesConfig.html
按照如下配置,成成bin文件
用16进制编辑工具打开查看该16字节内容,复制到上述代码中去。
以上之后直接点击d就会和程序一起下载。
下载完成之后这里不勾选,下次就不会再下载opt了。
四. 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
代码考虑了可移植性,可以方便的移植。
串口使用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模拟的方式,因为有现成的代码,简单方便。
代码如下
见公众号文章
https://mp.weixin.qq.com/s/ESzWWqxHpQevsWfjV0s2VQ?token=1631959641&lang=zh_CN
Pcf8574驱动,设计为完全独立的模块,只依赖iic操作,无需任何修改完全可移植。
具体操作参考手册即可。
基于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的入门开发参考。