IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> 串口数据框架 -> 正文阅读

[嵌入式]串口数据框架

串口数据框架

一、串口初始化配置

(串口任务函数)

unsigned int uart_Task_init(struct tasks* urt)

函数内容:定义了串口名称,分配内存空间。

unsigned int uart_Task_init(struct tasks* urt)
{
#if UART_TASK_FLAG
	
	urt->task_Name = "Task_uart";
	urt->task_Stack_Size = UART_TASK_STACK_SIZE;
	urt->task_Value = NULL;
	urt->task_Prio = UART_TASK_PRIO;
//	log_debug("adc_Task_init uart size=%d\r\n",sizeof(urt));
	InitQueue(rx_queue);
	return 1;
#else
	return 0;
#endif
}

InitQueue函数完成了队列的初始化操作

//初始化队列
void InitQueue(SqQueue *Q)
{
  Q->rear = Q->front = 0;
}

( 串口初始化函数)

void SerialInit(uint32_t MCU_Baudrate, UART_TypeDef *UARTx)

函数内容:用于串口GPIO及波特率等一些参数的设置。

  • 配置了uart0的GPIO输入输出脚
  • 定义了数据位,奇偶校验位,停止位的长度与形式
  • 设置了触发中断时长
#if (THIS_PROJECT_IS_BM01 || THIS_PROJECT_IS_VC002||THIS_PROJECT_IS_VC002B)
		PORT_Init(PORTM, PIN2, FUNMUX0_UART0_RXD, 1); //GPIOA.2配置为UART0输入引脚
		PORT_Init(PORTM, PIN3, FUNMUX1_UART0_TXD, 0); //GPIOA.3配置为UART0输出引脚
		UART_initStruct.DataBits = UART_DATA_8BIT;
		UART_initStruct.Parity = UART_PARITY_NONE;
		UART_initStruct.StopBits = UART_STOP_1BIT;
		UART_initStruct.RXThreshold = 3;
		UART_initStruct.RXThresholdIEn = 1;
		UART_initStruct.TXThreshold = 3;
		UART_initStruct.TXThresholdIEn = 0;
		UART_initStruct.TimeoutTime = 10; //10个字符时间内未接收到新的数据则触发超时中断
		UART_initStruct.TimeoutIEn = 1;				


二、底层数据接收

UART0_Handler(void)

调用EnRxQueue函数接收串口数据

uint32_t chr;
char data;

if (UART_INTRXThresholdStat(UART0) || UART_INTTimeoutStat(UART0))
{
	while (UART_IsRXFIFOEmpty(UART0) == 0)
	{
		if (UART_ReadByte(UART0, &chr) == 0)
		{
			usleep(3);
			EnRxQueue(&rx_queue[MCU_UART0], chr);
		}
	}
}
bool EnRxQueue(SqQueue *Q, ElemType x)
{
  if ((Q->rear + 1) % MaxSize == Q->front)
    return false;
  Q->rear = (Q->rear + 1) % MaxSize;
  Q->data[Q->rear] = x;
  return true;
}

三、出队数据接收

FR_data_read(unsigned char *pDe_buf, unsigned char *pDe_cnt, unsigned char *pOld_buf, unsigned char pOld_cnt, UART_STATUS uart_sta)

函数内容:将串口接收的数据进行出队操作,取出队列数据,此外,为了防止出现数据与数据的重叠,函数体也相应地设置了参数用于判断每帧数据的断点

  • param {unsigned char} *pDe_buf : 接收到的数据buf
  • param {unsigned char} *pDe_cnt : 接收到的数据的长度
  • param {unsigned char} *pOld_buf : 上次接收的没处理完的旧数据。
  • param {unsigned char} pOld_cnt : 旧数据长度。
  • param {UART_STATUS} uart_sta : 串口状态参数,可通过改状态判断串口当前处于哪一个状态。
while (dt_uart_receive_char(0, &ch))
	{
		if (pOld_cnt > 0)
		{
			printf("has old_cnt %d\r\n", pOld_cnt);
		}
		//两次接收超过100ms,认为是新的一帧
		if (now() - pre_uart0_time > 200)
		{
			printf("new uart frame Head is %02x \r\n", (uint8_t)ch);
			if (ch == 0x7e)
			{
				pOld_cnt = 0;
				De_len = 0;
			}
		}
		printf("%02x ", (unsigned char)ch);
		if (0x7d == (unsigned char)ch)
		{
			escape_num++;
		}
		if (0 < pOld_cnt)
		{
			memset(pOld_buf, 0, pOld_cnt);
			pDe_buf[De_len + pOld_cnt] = ch;
			De_len++;
			msleep(3);
			*pDe_cnt = De_len + pOld_cnt;
		}
		else
		{
			pOld_cnt = 0;
			pDe_buf[De_len++] = ch;
			msleep(3);
			*pDe_cnt = De_len;
		}
		pre_uart0_time = now();
	}
	if (De_len > 0 && ((pDe_buf[1] + escape_num) == De_len))
	{
		printf("R[%d][%d]:Uart 0\r\n", pDe_buf[1], De_len);
		De_len = 0;										
		escape_num = 0;
	}

}

主要使用dt_uart_receive_char(…)来调用出队操作函数,即DeRxQueue(…),读取接收

bool DeRxQueue(SqQueue *Q, ElemType *x)
{
  if (Q->rear == Q->front)
    return false;
  Q->front = (Q->front + 1) % MaxSize;
  *x = Q->data[Q->front];
  return true;
}

四、数据解析

int parseData(unsigned char *dst, int *dstLen, unsigned char *src, int srcLen, int *RescapeNum)

数据解析函数。依据串口协议解析数据,即首先去掉帧头帧尾(同时存在帧头帧尾)

int flag = 0; //开始计数
unsigned char checkSum = 0;
int dataLen = 0;
unsigned char tmpDst[100] = {0}; //去掉帧头帧尾后的部分
int tmpDstIdx = 0;				 //无帧头帧尾的长度

寻找帧头

for (i = 0; i < srcLen; i++)
{
	if (0x7E == src[i])
		break;
}
if (0 != i)
{
	log_abort("无效数据:%d\r\n", i);
	return i; //说明有无效数据,要跳过,外面的buffer要做对应的处理。
}

寻找帧尾

for (i = 1; i < srcLen; i++)
{
	if (src[i] != 0x7E && flag == 1)
	{
		tmpDst[tmpDstIdx++] = src[i];
		log_abort(" tmpDst1[%d]: %02X\r\n", tmpDstIdx - 1, tmpDst[tmpDstIdx - 1]);
	}
	else if (src[i] == 0x7E && flag == 1)
	{ //结尾跳出循环
		log_abort(" tmpDst2[%d]: %02X\r\n", tmpDstIdx, tmpDst[tmpDstIdx]);
		flag = 2;
		break;
	}
	log_abort(" tmpDst3[%d]: %02X\r\n", tmpDstIdx - 1, tmpDst[tmpDstIdx - 1]);
}

反转义:dedivertFrame(…)主要将转化后数据中的0x01,0x02还原为原数据的0x7D,0x7E

if (src[srcIdx] == 0x7D)
		{
			if (src[srcIdx + 1] == 0x02)
			{
				dst[dstIdx++] = 0x7E;
				deLen++;
			}
			else if (src[srcIdx + 1] == 0x01)
			{
				dst[dstIdx++] = 0x7D;
				deLen++;
			}

校验和:checkSumFun(…)主要用于计算实际数据的校验和

for (i = 0; i < len; i++)
{
	sum = (unsigned char)((sum + dst[i]) & 0xFF);
}

验证长度:累计计算数据内容长度,使用memmove(…)字符串库函数进行数据数据的读取

if ((*dstLen - 1 - 1) != dataLen)
{
	log_error("Error:dataLenErr:%d\r\n", dataLen);
	return (tmpDstIdx - 1);
}

*dstLen = dataLen;
memmove(dst, dst + 1, dataLen);

五、数据分离

app_uart_Start(…)通过返回的app_uart_recvCmd(…)来判断是协议数据还是OTA数据

int app_uart_Start(unsigned char *Data_buff, int Data_buff_Len, UART_STATUS uart_sta)

协议数据:进行数据解析处理,EventDataParse(…),处理完成数据,入消息队列给UI或其他需要接受数据的位置

OTA数据:

  • CAN_CMD(ota复位升级请求,mcu进入升级状态)
case CAN_CMD: //ota复位升级请求,mcu进入升级状态
#else
case cmdUartVersionInquire:        //ota复位升级请求,mcu进入升级状态
#endif
#if TEST_TASK_FLAG
	State_info.ManufacturerCode[0] = Data_buff[2];
	State_info.ManufacturerCode[1] = Data_buff[3];
	State_info.HardwareVersion[0] = Data_buff[4];
	State_info.HardwareVersion[1] = Data_buff[5];
	State_info.ControlBoardVersion[0] = Data_buff[6];
	State_info.ControlBoardVersion[1] = Data_buff[7];
	State_info.CommunicationProtocolVersion = Data_buff[8];
	State_info.TypeOfEquipment = Data_buff[9];
  • CAN_DATA(ota升级准备,擦除flag固件标志位,flash空间)
case CAN_DATA: //ota升级准备,擦除flag固件标志位,flash空间
	#else
	case cmdUartStateHandshake:    //ota升级准备,擦除flag固件标志位,flash空间
	#endif
		#if TEST_TASK_FLAG
		vers_info.ScreenSwitch = Data_buff[2];
		vers_info.UIshow = Data_buff[3];
		vers_info.WorkingMode = Data_buff[4];
		vers_info.ContinuousWorkingMode = Data_buff[5];
		vers_info.CurrentLanguage = Data_buff[6];
		vers_info.LampRingState = Data_buff[7];
		vers_info.WorkingState = Data_buff[8];
		vers_info.AbnormalInformation = Data_buff[9];
		vers_info.ElectricityValue = Data_buff[10];

六、数据处理

EventDataParse(…)串口数据统一处理框架,通过该函数将解析完的数据统一进行处理,转化为UI框架能够识别的数据格式,统一通过消息队列依次发送出去。

int EventDataParse(char *data, int dstLen, char *src)
  • param {char} *data : 实际处理的数据内容
  • param {int} dstLen : 实际处理数据长度
  • param {char} *src :转化完成后的数据内容。
int mq_send(mqd_t msgid, const char *msg, size_t msg_len, unsigned int msg_prio)

将处理完的数据入到消息队列

q = (mqQueue *)msgid;
q->msg_size = msg_len;
xTicksToWait = (q->oflag & O_NONBLOCK) ? 0 : portMAX_DELAY;
if (xQueueSend(q->xQueue, (const void *)msg, xTicksToWait) != pdTRUE)
{
      return -1;
}

七、下行数据入队

app_uart_Start(…)主要使用下行数据组合函数cmdtomcu(…)和数据组包函数FR_Combination(…)进行入队前的数据处理

int app_uart_Start(unsigned char *Data_buff, int Data_buff_Len, UART_STATUS uart_sta)

arm主动发送指令

int cmdtomcu(unsigned char *preaddata, unsigned char *pCmdData, unsigned char len)
{
	for (int i = 0; i < len; i++)
	{
		pCmdData[i] = preaddata[i];
	}
	return len;
}

下行数据组合:帧头+长度+数据+校验和+帧尾

int FR_Combination(unsigned char Rec_CmdData[], unsigned char nRecCmdCnt) //组合数据
{
	unsigned char pSen_CmdData[nRecCmdCnt + 4];
	unsigned char Sen_CmdData[UART_DATA_BUFF_SIZE];
	unsigned char crc, pSen_datalen = 0;
	unsigned int crcSunm = 0;
	pSen_CmdData[0] = HEAD_END;
	pSen_CmdData[1] = 4 + nRecCmdCnt; //len
	for (unsigned char j = 0; j < nRecCmdCnt; j++)
	{
		pSen_CmdData[2 + j] = Rec_CmdData[j];
	}
	for (unsigned char i = 1; i < 2 + nRecCmdCnt; i++)
	{
		crcSunm += pSen_CmdData[i];
	}
	crc = crcSunm & 0xff; //取校验和低八位
	pSen_CmdData[2 + nRecCmdCnt] = crc;
	pSen_CmdData[3 + nRecCmdCnt] = HEAD_END;

	escape(pSen_CmdData, sizeof(pSen_CmdData), Sen_CmdData, &pSen_datalen);
	for (unsigned char j = 0; j < pSen_datalen; j++)
	{
		bl_TX_com1(MCU_UART0, Sen_CmdData[j]); //入队发送
	}
	return 0;

}

其中escape函数将7E转换成7D02,进行转义操作

for (i = 1; i < rlen - 1; i++, j++)
	{
		if (realACK[i] == 0x7e)
		{
			trunACK[j] = 0x7d;
			j++;
			trunACK[j] = 0x02;
		}
		else if (realACK[i] == 0x7d)
		{
			trunACK[j] = 0x7d;
			j++;
			trunACK[j] = 0x01;
		}

八、发送数据

int ota_data_send_Uart(void)

将写入bl_tx_q_com1循环队列里面的数据通过串口发送出去

unsigned char sen_buff[UART_BUFF_SIZE]; //发送的暂时数据
	unsigned short int sen_cnt = 0;			//发送数据的暂时长度
	ElemType ch;
	while (!isEmpty(&tx_queue[MCU_UART0]))
	{
		DeQueue(&tx_queue[MCU_UART0], &ch);
		sen_buff[sen_cnt] = ch;
		dt_uart_send_char(MCU_UART0,ch);
		sen_cnt++;
		if (sen_cnt > UART_BUFF_SIZE - 1)
		{
			sen_cnt = 0;
		}
	}

使用DeQueue(…)出队操作函数,将数据输出

bool DeQueue(SqQueue *Q, ElemType *x)
{
  if (Q->rear == Q->front)
    return false;
  Q->front = (Q->front + 1) % MaxSize;
  *x = Q->data[Q->front];
  return true;
}
  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2021-10-17 12:09:12  更:2021-10-17 12:09:53 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 7:38:14-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码