1.0 串口接收数据的几种场景
MCU 只发送命令接收响应
串口接收数据比较的复杂,我们可以给数据包定义一定的格式,数据按照固定的格式进行数据的发送和接收。
1:数据的接收端不知道什么时候会接收到数据
2:数据包的长度是不确定长度的
3:接收到的数据频率很快,有时候来不及处理,引入问题
2.0 串口接收数据实验
收发双方需要协商出一些数据格式
帧头:表示一包数据开始了
数据域的长度:也就是包含的数据,后面有几个数据
功能字:0x06 使用8421包含的含义就是 0x0000 0101
LED编号:00/01/02
开关的状态: 0x00 表示关闭,0x01表示开启
校验:异或校验,和校验,奇数偶数校验
一包数据的格式协商出来
数据帧头:表示数据开始了,有时可以使用两个数据来表示数据帧头
数据域的长度:0x03 表示后面的数据是三个数据帧
功能字: 控制外设发出的动作
LED编号:第几个led灯
开关状态:0 表示的是关,1 表示的是开
校验:有多种的校验方式
数据帧头:一般使用特殊的数值【0x55|0xaa】,最好不要出现在有效的数据域内,后面的是数据的长度03表示后面的几个字节,功能字,编号,开关状态,与校验位。
串口数据的接收需要配合中断的方式进行使用
3.0 初始化串口与GPIO
初始化串口与GPIO,设置波特率为115200
/**
***********************************************************
* @brief USB转串口硬件初始化
* @param
* @return
***********************************************************
*/
void Usb2ComDrvInit(void)
{
Usb2ComGpioInit();
Usb2ComUartInit(115200);
}
#include <stdint.h>
#include <stdio.h>
#include <stdbool.h>
#include "gd32f30x.h"
typedef struct
{
uint32_t uartNo;
rcu_periph_enum rcuUart;
rcu_periph_enum rcuGpio;
uint32_t gpio;
uint32_t txPin;
uint32_t rxPin;
uint8_t irq;
} UartHwInfo_t;
static UartHwInfo_t g_uartHwInfo = {USART0, RCU_USART0, RCU_GPIOA, GPIOA, GPIO_PIN_9, GPIO_PIN_10, USART0_IRQn};
static void Usb2ComGpioInit(void)
{
rcu_periph_clock_enable(g_uartHwInfo.rcuGpio);
gpio_init(g_uartHwInfo.gpio, GPIO_MODE_AF_PP, GPIO_OSPEED_10MHZ, g_uartHwInfo.txPin);
gpio_init(g_uartHwInfo.gpio, GPIO_MODE_IPU, GPIO_OSPEED_10MHZ, g_uartHwInfo.rxPin);
}
static void Usb2ComUartInit(uint32_t baudRate)
{
/* 使能UART时钟;*/
rcu_periph_clock_enable(g_uartHwInfo.rcuUart);
/* 复位UART;*/
usart_deinit (g_uartHwInfo.uartNo);
/* 通过USART_CTL0寄存器的WL设置字长;*/
//usart_word_length_set(g_uartHwInfo.uartNo, USART_WL_8BIT);
/* 通过USART_CTL0寄存器的PCEN设置校验位;*/
//usart_parity_config(g_uartHwInfo.uartNo, USART_PM_NONE);
/* 在USART_CTL1寄存器中写STB[1:0]位来设置停止位的长度;*/
//usart_stop_bit_set(g_uartHwInfo.uartNo, USART_STB_1BIT);
/* 在USART_BAUD寄存器中设置波特率;*/
usart_baudrate_set(g_uartHwInfo.uartNo, baudRate);
/* 在USART_CTL0寄存器中设置TEN位,使能发送功能;*/
usart_transmit_config(g_uartHwInfo.uartNo, USART_TRANSMIT_ENABLE);
/* 在USART_CTL0寄存器中设置TEN位,使能接收功能;*/
usart_receive_config(g_uartHwInfo.uartNo, USART_RECEIVE_ENABLE);
/* 使能串口接收中断;*/
usart_interrupt_enable(g_uartHwInfo.uartNo, USART_INT_RBNE);
/* 使能串口中断;*/
nvic_irq_enable(g_uartHwInfo.irq, 0, 0);
/* 在USART_CTL0寄存器中置位UEN位,使能UART;*/
usart_enable(g_uartHwInfo.uartNo);
}
/**
***********************************************************
* @brief USB转串口硬件初始化
* @param
* @return
***********************************************************
*/
void Usb2ComDrvInit(void)
{
Usb2ComGpioInit();
Usb2ComUartInit(115200);
}
4.0 串口中断服务函数
/**
***********************************************************
* @brief 串口0中断服务函数
* @param
* @return
***********************************************************
*/
void USART0_IRQHandler(void)
{
if (usart_interrupt_flag_get(g_uartHwInfo.uartNo, USART_INT_FLAG_RBNE) != RESET)
{
usart_interrupt_flag_clear(g_uartHwInfo.uartNo, USART_INT_FLAG_RBNE);
uint8_t uData = (uint8_t)usart_data_receive(g_uartHwInfo.uartNo);
pProcUartData(uData);
}
}
判断串口的中断标志位,看串口输入数据寄存器中是否有只,如果串口输入数据寄存器中存在数字将数据发送输出去,调用pProcUartData(),这里下层调用上层的代码使用回调函数的方式间接的进行调用。
// 使用函数指针简介的调用上层的接口函数
static void (*pProcUartData)(uint8_t data);
// 创建注册回调函数的api
void regUsb2ComCb(void (*pFunc)(uint8_t data))
{
// 使用静态全局变量保存回调函数的地址
pProcUartData = pFunc;
}
5.0 输出重定向函数printf实现串口打印输出printf
/**
***********************************************************
* @brief printf函数默认打印输出到显示器,如果要输出到串口,
必须重新实现fputc函数,将输出指向串口,称为重定向
* @param
* @return
***********************************************************
*/
int fputc(int ch, FILE *f)
{
usart_data_transmit(g_uartHwInfo.uartNo, (uint8_t)ch);
while (RESET == usart_flag_get(g_uartHwInfo.uartNo, USART_FLAG_TBE));
return ch;
}
驱动部分完整代码
#include <stdint.h>
#include <stdio.h>
#include <stdbool.h>
#include "gd32f30x.h"
typedef struct
{
uint32_t uartNo;
rcu_periph_enum rcuUart;
rcu_periph_enum rcuGpio;
uint32_t gpio;
uint32_t txPin;
uint32_t rxPin;
uint8_t irq;
} UartHwInfo_t;
static UartHwInfo_t g_uartHwInfo = {USART0, RCU_USART0, RCU_GPIOA, GPIOA, GPIO_PIN_9, GPIO_PIN_10, USART0_IRQn};
static void Usb2ComGpioInit(void)
{
rcu_periph_clock_enable(g_uartHwInfo.rcuGpio);
gpio_init(g_uartHwInfo.gpio, GPIO_MODE_AF_PP, GPIO_OSPEED_10MHZ, g_uartHwInfo.txPin);
gpio_init(g_uartHwInfo.gpio, GPIO_MODE_IPU, GPIO_OSPEED_10MHZ, g_uartHwInfo.rxPin);
}
static void Usb2ComUartInit(uint32_t baudRate)
{
/* 使能UART时钟;*/
rcu_periph_clock_enable(g_uartHwInfo.rcuUart);
/* 复位UART;*/
usart_deinit (g_uartHwInfo.uartNo);
/* 通过USART_CTL0寄存器的WL设置字长;*/
//usart_word_length_set(g_uartHwInfo.uartNo, USART_WL_8BIT);
/* 通过USART_CTL0寄存器的PCEN设置校验位;*/
//usart_parity_config(g_uartHwInfo.uartNo, USART_PM_NONE);
/* 在USART_CTL1寄存器中写STB[1:0]位来设置停止位的长度;*/
//usart_stop_bit_set(g_uartHwInfo.uartNo, USART_STB_1BIT);
/* 在USART_BAUD寄存器中设置波特率;*/
usart_baudrate_set(g_uartHwInfo.uartNo, baudRate);
/* 在USART_CTL0寄存器中设置TEN位,使能发送功能;*/
usart_transmit_config(g_uartHwInfo.uartNo, USART_TRANSMIT_ENABLE);
/* 在USART_CTL0寄存器中设置TEN位,使能接收功能;*/
usart_receive_config(g_uartHwInfo.uartNo, USART_RECEIVE_ENABLE);
/* 使能串口接收中断;*/
usart_interrupt_enable(g_uartHwInfo.uartNo, USART_INT_RBNE);
/* 使能串口中断;*/
nvic_irq_enable(g_uartHwInfo.irq, 0, 0);
/* 在USART_CTL0寄存器中置位UEN位,使能UART;*/
usart_enable(g_uartHwInfo.uartNo);
}
/**
***********************************************************
* @brief USB转串口硬件初始化
* @param
* @return
***********************************************************
*/
void Usb2ComDrvInit(void)
{
Usb2ComGpioInit();
Usb2ComUartInit(115200);
}
// 使用函数指针简介的调用上层的接口函数
static void (*pProcUartData)(uint8_t data);
// 创建注册回调函数的api
void regUsb2ComCb(void (*pFunc)(uint8_t data))
{
// 使用静态全局变量保存回调函数的地址
pProcUartData = pFunc;
}
/**
***********************************************************
* @brief 串口0中断服务函数
* @param
* @return
***********************************************************
*/
void USART0_IRQHandler(void)
{
if (usart_interrupt_flag_get(g_uartHwInfo.uartNo, USART_INT_FLAG_RBNE) != RESET)
{
usart_interrupt_flag_clear(g_uartHwInfo.uartNo, USART_INT_FLAG_RBNE);
uint8_t uData = (uint8_t)usart_data_receive(g_uartHwInfo.uartNo);
pProcUartData(uData);
}
}
/**
***********************************************************
* @brief printf函数默认打印输出到显示器,如果要输出到串口,
必须重新实现fputc函数,将输出指向串口,称为重定向
* @param
* @return
***********************************************************
*/
int fputc(int ch, FILE *f)
{
usart_data_transmit(g_uartHwInfo.uartNo, (uint8_t)ch);
while (RESET == usart_flag_get(g_uartHwInfo.uartNo, USART_FLAG_TBE));
return ch;
}
5.0 应用层代码实现
#include <stdint.h>
#include <stdio.h>
#include <stdbool.h>
#include "usb2com_drv.h"
#include "led_drv.h"
#define FRAME_HEAD_0 0x55
#define FRAME_HEAD_1 0xAA
#define CTRL_DATA_LEN 3 //数据域长度
#define PACKET_DATA_LEN (CTRL_DATA_LEN + 4) //包长度
#define FUNC_DATA_IDX 3 //功能字数组下标
#define LED_CTRL_CODE 0x06 //功能字
#define MAX_BUF_SIZE 20
static uint8_t g_rcvDataBuf[MAX_BUF_SIZE];
static bool g_pktRcvd = false;
typedef struct
{
uint8_t ledNo;
uint8_t ledState;
} LedCtrlInfo_t;
static void ProcUartData(uint8_t data)
{
static uint8_t index = 0;
g_rcvDataBuf[index++] = data;
switch (index)
{
case 1:
if (g_rcvDataBuf[0] != FRAME_HEAD_0)
{
index = 0;
}
break;
case 2:
if (g_rcvDataBuf[1] != FRAME_HEAD_1)
{
index = 0;
}
break;
case PACKET_DATA_LEN:
g_pktRcvd = true;
index = 0;
break;
default:
break;
}
}
/**
***********************************************************
* @brief 对数据进行异或运算
* @param data, 存储数组的首地址
* @param len, 要计算的元素的个数
* @return 异或运算结果
***********************************************************
*/
static uint8_t CalXorSum(const uint8_t *data, uint32_t len)
{
uint8_t xorSum = 0;
for (uint32_t i = 0; i < len; i++)
{
xorSum ^= data[i];
}
return xorSum;
}
/**
***********************************************************
* @brief LED控制处理函数
* @param ctrlData,结构体指针,传入LED的编号和状态
* @return
***********************************************************
*/
static void CtrlLed(LedCtrlInfo_t *ctrlData)
{
ctrlData->ledState != 0 ? TurnOnLed(ctrlData->ledNo) : TurnOffLed(ctrlData->ledNo);
}
/**
***********************************************************
* @brief USB转串口任务处理函数
* @param
* @return
***********************************************************
*/
void Usb2ComTask(void)
{
if (!g_pktRcvd)
{
return;
}
g_pktRcvd = false;
if (CalXorSum(g_rcvDataBuf, PACKET_DATA_LEN - 1) != g_rcvDataBuf[PACKET_DATA_LEN - 1])
{
return;
}
if (g_rcvDataBuf[FUNC_DATA_IDX] == LED_CTRL_CODE)
{
CtrlLed((LedCtrlInfo_t *)(&g_rcvDataBuf[FUNC_DATA_IDX + 1]));
}
}
//
void Usb2ComAppInit(void)
{
regUsb2ComCb(ProcUartData);
}
应用层头文件代码
#ifndef __USB2COM_APP_H_
#define __USB2COM_APP_H_
// 串口应用初始化函数
void Usb2ComAppInit(void);
// 串口任务处理函数
void Usb2ComTask(void);
#endif
main 函数代码测试
#include <stdint.h>
#include <stdio.h>
#include "gd32f30x.h"
#include "led_drv.h"
#include "key_drv.h"
#include "systick.h"
#include "usb2com_drv.h"
#include "usb2com_app.h"
static void DrvInit(void)
{
SystickInit();
LedDrvInit();
KeyDrvInit();
Usb2ComDrvInit();
}
static void AppInit(void)
{
Usb2ComAppInit();
}
int main(void)
{
DrvInit();
AppInit();
while (1)
{
Usb2ComTask();
}
}