知不足而奋进·望远山而前行
目录
前言
在嵌入式软件开发中,封装抽取流程和抽取封装策略是非常重要的技术,能够提高代码的复用性和可维护性。本文将介绍如何在文件系统中创建库目录,并通过keil工程中创建分组管理库的方式,实现串口功能的封装和抽取。通过具体的步骤和代码示例,帮助读者掌握封装抽取流程和策略。
目标
- 掌握封装抽取流程
- 掌握抽取封装策略
内容
开发流程
- 在文件系统中,创建库目录Library
- 在keil工程中,创建分组管理库Library
- 编写中间件逻辑
- 使用中间件
文件目录创建
在工程根目录,创建Library
目录,在这个目录中,创建具体的功能目录,当前是做串口功能,我们新建usart
。
分组创建
- 创建
Library
分组。右键进入Manage Project Items
- 右键创建头文件和c文件
添加include引入
接口定义
初始化及发送功能定义
void USART0_init(void);
// 发送1个byte数据
void USART0_send_byte(uint8_t byte);
// 发送多个byte数据
void USART0_send_data(uint8_t* data, uint32_t len);
// 发送字符串 (结尾标记\0)
void USART0_send_string(char *data);
接收回调定义
// 功能开关配置
#define USART0_RECV_CALLBACK 1
#if USART0_RECV_CALLBACK
// 收到串口0数据,回调函数
extern void USART0_on_recv(uint8_t* data, uint32_t len);
#endif
...
#if USART0_RECV_CALLBACK
USART0_on_recv(g_rx_buffer, g_rx_cnt);
#endif
...
- 通过宏定义做开关
系统printf打印定义
#define USART0_PRINTF 1
#if USART0_PRINTF
#include <stdio.h>
#endif
#if USART0_PRINTF
// 配置printf打印函数
int fputc(int ch, FILE *f) {
USART0_send_byte(ch);
return ch;
}
#endif
完整代码
#ifndef __USART0_H__
#define __USART0_H__
#include "gd32f4xx.h"
// 功能开关配置
#define USART0_RECV_CALLBACK 1
#define USART0_PRINTF 1
#if USART0_PRINTF
#include <stdio.h>
#endif
void USART0_init(void);
// 发送1个byte数据
void USART0_send_byte(uint8_t byte);
// 发送多个byte数据
void USART0_send_data(uint8_t* data, uint32_t len);
// 发送字符串 (结尾标记\0)
void USART0_send_string(char *data);
#if USART0_RECV_CALLBACK
// 收到串口0数据,回调函数
extern void USART0_on_recv(uint8_t* data, uint32_t len);
#endif
#endif
#include "USART0.h"
#include <stdio.h>
void USART0_init(void) {
// GPIO 初始化 ----------------------------------------------------
// 启用GPIO时钟
rcu_periph_clock_enable(RCU_GPIOA);
/* 配置TX PA9和RX PA10引脚 */
gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_9);
gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9);
gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_10);
gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_10);
/* configure the USART0 TX pin and USART0 RX pin */
gpio_af_set(GPIOA, GPIO_AF_7, GPIO_PIN_9);
gpio_af_set(GPIOA, GPIO_AF_7, GPIO_PIN_10);
// 串口 初始化 ----------------------------------------------------
// 启用USART0时钟
rcu_periph_clock_enable(RCU_USART0);
// 重置(可选)
usart_deinit(USART0);
// 配置串口参数:波特率*, 数据位,校验位,停止位, 大小端模式
usart_baudrate_set(USART0, 115200UL); // 波特率:必填
usart_word_length_set(USART0, USART_WL_8BIT); // 数据位:默认8bit
usart_parity_config(USART0, USART_PM_NONE); // 校验位:默认无校验
usart_stop_bit_set(USART0, USART_STB_1BIT); // 停止位:默认1bit
usart_data_first_config(USART0, USART_MSBF_LSB);// 大小端模式:默认小端
// 启用发送功能
usart_transmit_config(USART0, USART_TRANSMIT_ENABLE);
// 启用接收功能
usart_receive_config(USART0, USART_RECEIVE_ENABLE);
// 开启接收中断
nvic_irq_enable(USART0_IRQn, 2, 2);
// 启用RBNE中断,读数据缓冲区不为空中断
usart_interrupt_enable(USART0, USART_INT_RBNE);
// 启用IDLE中断,空闲中断
usart_interrupt_enable(USART0, USART_INT_IDLE);
// 启用USART
usart_enable(USART0);
}
// 发送1个byte数据
void USART0_send_byte(uint8_t byte){
// 从USART0的TX发送一个字节出去
usart_data_transmit(USART0, (uint8_t)byte);
// 等待发送完成 (轮询等待发送数据缓冲区为空)
while(RESET == usart_flag_get(USART0, USART_FLAG_TBE));
}
// 发送多个byte数据
void USART0_send_data(uint8_t* data, uint32_t len){
// 满足:1.data指针不为空 2.长度不为0
while(data && len--){
USART0_send_byte(*data);
data++;
}
}
// 发送字符串 (结尾标记\0)
void USART0_send_string(char *data){
// 满足:1.data指针不为空 2. 数据不能是\0
while(data && *data){
USART0_send_byte((uint8_t)*data);
data++;
}
}
#if USART0_PRINTF
// 配置printf打印函数
int fputc(int ch, FILE *f) {
USART0_send_byte(ch);
return ch;
}
#endif
/************************************
中断函数:收到标记信号,马上执行
1. 触发中断函数的原因(标记)有很多
2. 需要区分是哪个标记触发的中断
RBNE: read data buffer not empty
中断函数名不能随便写,要根据中断向量表复制
*************************************/
#define RX_BUFFER_LEN 1024
uint8_t g_rx_buffer[RX_BUFFER_LEN];
uint32_t g_rx_cnt = 0;
void USART0_IRQHandler(void){
if(SET == usart_interrupt_flag_get(USART0, USART_INT_FLAG_RBNE)){
// 收到数据
// printf(">RBNE<\n");
// 清理标记(避免多次触发中断)
usart_interrupt_flag_clear(USART0, USART_INT_FLAG_RBNE);
// 获取寄存器里的数据
uint8_t data = usart_data_receive(USART0);
// 缓存到buffer中
g_rx_buffer[g_rx_cnt++] = data;
// 避免缓冲区溢出 (可选)
if(g_rx_cnt >= RX_BUFFER_LEN) g_rx_cnt = 0;
// 原样返回 send_byte(data);
}
if(SET == usart_interrupt_flag_get(USART0, USART_INT_FLAG_IDLE)){
// printf(">IDLE<\n"); // 空闲
// 清理标记(无效) usart_interrupt_flag_clear(USART0, USART_INT_FLAG_IDLE);
// 只能使用以下方式清理IDLE标记
usart_data_receive(USART0); // 必须读取一次USART0,读到的结果没有用
// 添加字符串结束标记,避免打印出错
g_rx_buffer[g_rx_cnt] = '\0';
#if USART0_RECV_CALLBACK
// printf("%s", g_rx_buffer);
USART0_on_recv(g_rx_buffer, g_rx_cnt);
#endif
// 把缓冲区[0, g_rx_cnt)设置为0x00 (可选)
// memset(g_rx_buffer, 0x00, g_rx_cnt);
// 重置缓冲区数据个数
g_rx_cnt = 0;
}
}
总结
通过本文的学习,我们深入探讨了如何使用文件系统中的库目录和keil工程中的分组管理库来实现串口功能的封装和抽取。通过编写中间件逻辑和定义相关接口,我们实现了串口初始化、发送数据以及接收数据的功能,并通过宏定义的开关配置来实现功能的灵活控制。同时,我们也介绍了如何配置printf打印函数以及处理串口中断的相关操作。