历史上的今天
返回首页

历史上的今天

今天是:2025年04月03日(星期四)

正在发生

2020年04月03日 | STM32 串口驱动,分层通信

2020-04-03 来源:eefocus

以前在使用串口的时候都是直接使用中断,每收发一个字节都要进一次中断,然后直接在中断进行封包,现在做了一个简单的分层设计,其实这个设计还是驱动设计,后期将逻辑层划分再细致一点,争取做到和linux的shell类似的分层。


软件分层如下

驱动层:串口、DMA、初始化,串口只开启接收空闲中断,DMA中断不开启。


缓冲区:利用malloc和free函数创建的链表,缓冲区管理有两个,一个是接收缓冲区,每次进入接收空闲中断就把数据扔到接收缓冲队列里面去;另一个是发送缓冲区,发送缓冲区无逻辑,这只是一个数据结构。

 

示意图中的数据指针实际上用的是uint8 数组,当然,第一个数据完全可以塞到第二个数据里面,但是如果使用的是M0芯片的时候,会有一个指针地址的对齐问题,这个就不展开说了,只要是问题都有规避办法的。


逻辑层:逻辑里面关于串口接收队列,因为无法保证发送方的数据连续,所以需要将接收缓冲队列的数据重新打包,打包函数主要检测接收队列是否有数据,如果有,进行数据打包,如果能保证数据帧的完整性,无粘包、无断包,数据打包函数可以去除。串口发送函数,串口发送函数定时10ms检测DMA发送通道是否为空,如果通道空,延时10ms启动DMA发送,发送数据在发送队列缓冲中获取,发送的帧间隔范围在10~20ms之间,延时10ms保证了发送帧间隔至少10ms。

设计一个发送函数的接口,有数据发送时,应用只管往里面扔数据,然后再用一个封包函数封起来,再加一个封包函数,对于用户而言只有一个发送函数的api,用户层只管发送数据,底层逻辑不要管,也不允许动。发送函数只管定时从发送队列里面取数据,取一帧,然后调用dma,然后等待帧间隔,进行下一帧数据获取,发送,做到软件层面的分层,责任划分明确,一个函数只干一件事。


软件设计思想说完,下面直接放代码。


usart.h


#ifndef _USART_H

#define _USART_H

#include "stm32f10x.h"

 

#define SEND_BUSY 1

#define SEND_IDLE 0

#define BOUND_RATE 38400 //串口通信波特率

#define SENDBUFF_SIZE 50

#define SENDBUFF_SIZE_INTIT 0

#define RECEBUFF_SIZE 200

#define USART1_DR_Base  0x40013804

#define COMM_SEND_INTERNAL_20MS 20

 

struct COM_DATA_ST

{

uint8_t SendBuff[SENDBUFF_SIZE];

uint8_t ReceBuff[RECEBUFF_SIZE];

uint8_t Send_Complete_Flag;

uint8_t Bus_Idle_Count;

};

 

void usart1_init(void);

void usart1_rev_irq(void);

void usart1_send_irq(void);

void DMA_Config(void);

void Send_data(uint8_t *ptr,uint8_t length);

void usart1_dma_send_irq(void);

void Check_Send_Quene(void);

void usart1_send_interval_deal(void);

 

#endif

usart.c

#include "stm32f10x.h"

#include "string.h"

#include "usart.h"

#include "quene.h"

 

 

struct COM_DATA_ST Com_Data;

 

struct node Rece_Quene; /*接收的数据队列*/

struct node Send_Quene; /*发送数据的队列*/

struct node Send_Ack_Quene; /*发送应答数据的队列*/

 

uint8_t Get_DMA_State(void);

 

/*

 * Description:串口初始化

 * input:none

 * output:none

 * author:

 * date:2018-2-7

*/

void usart1_init(void)

{

GPIO_InitTypeDef GPIO_InitStructure;

USART_InitTypeDef USART_InitStructure;

NVIC_InitTypeDef NVIC_InitStructure;

 

NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //配置定时器中断通道

NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;

NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;

NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

NVIC_Init(&NVIC_InitStructure);

 

RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //使能afio时钟

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能GPIOA时钟

RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //使能串口1时钟

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //串口1输入脚浮空

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;

GPIO_Init(GPIOA, &GPIO_InitStructure);

 

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //串口1输出脚配成多功能上下拉

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //多功能推挽输出

GPIO_Init(GPIOA, &GPIO_InitStructure);

 

USART_InitStructure.USART_BaudRate = BOUND_RATE; //初始化串口参数

USART_InitStructure.USART_WordLength = USART_WordLength_8b;

USART_InitStructure.USART_StopBits = USART_StopBits_1;

USART_InitStructure.USART_Parity = USART_Parity_No;

USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;

USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;

 

USART_Init(USART1, &USART_InitStructure); //初始化串口

USART_ITConfig(USART1, USART_IT_IDLE, ENABLE); //空闲中断

USART_Cmd(USART1, ENABLE);

}

 

/*

 * 函数名:DMA_Config

 * 描述  :DMA 串口的初始化配置

 * 输入  :无

 * 输出  : 无

 * 调用  :外部调用

 */

void DMA_Config(void)

{

    DMA_InitTypeDef DMA_InitStructure;

NVIC_InitTypeDef NVIC_InitStructure;

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); /*开启DMA时钟*/

 

NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel4_IRQn;

NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;

NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;

NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

NVIC_Init(&NVIC_InitStructure);

 

    DMA_InitStructure.DMA_PeripheralBaseAddr = USART1_DR_Base;     /*设置DMA源:内存地址&串口数据寄存器地址*/

    DMA_InitStructure.DMA_MemoryBaseAddr = (u32)Com_Data.SendBuff; /*内存地址(要传输的变量的指针)*/

    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; /*方向:从内存到外设*/

    DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE_INTIT; /*传输大小DMA_BufferSize=SENDBUFF_SIZE*/

    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; /*外设地址不增*/

    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; /*内存地址自增*/

    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; /*外设数据单位*/

    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; /*内存数据单位 8bit*/

    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ; /*DMA模式:一次传输,循环*/

    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;  /*优先级:中*/

    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; /*禁止内存到内存的传输 */

    DMA_Init(DMA1_Channel4, &DMA_InitStructure); /*配置DMA1的4通道*/    

DMA_Cmd (DMA1_Channel4,ENABLE); /*使能DMA*/

// DMA_ITConfig(DMA1_Channel4,DMA_IT_TC,ENABLE);  /*配置DMA发送完成后产生中断*/

 

    DMA_InitStructure.DMA_PeripheralBaseAddr = USART1_DR_Base;     /*设置DMA源:内存地址&串口数据寄存器地址*/

    DMA_InitStructure.DMA_MemoryBaseAddr = (u32)Com_Data.ReceBuff; /*内存地址(要传输的变量的指针)*/

    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; /*方向:从外设到内存*/

    DMA_InitStructure.DMA_BufferSize = RECEBUFF_SIZE; /*传输大小DMA_BufferSize=SENDBUFF_SIZE*/

    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; /*外设地址不增*/

    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; /*内存地址自增*/

    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; /*外设数据单位*/

    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; /*内存数据单位 8bit*/

    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular ; /*DMA模式:一次传输,循环*/

    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;  /*优先级:中*/

    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; /*禁止内存到内存的传输 */

    DMA_Init(DMA1_Channel5, &DMA_InitStructure); /*配置DMA1的5通道*/    

DMA_Cmd (DMA1_Channel5,ENABLE); /*使能DMA*/

USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE);

}

 

/*

 * Description:串口1接收中断

 * input:none

 * output:none

 * author:

 * date:2018-2-7

*/

void usart1_rev_irq(void)

{

char data_length;

data_length = USART1->SR;

data_length = USART1->DR;

DMA_Cmd(DMA1_Channel5,DISABLE);

data_length = RECEBUFF_SIZE - DMA_GetCurrDataCounter(DMA1_Channel5);

InsertNode(&Rece_Quene,Com_Data.ReceBuff,data_length);

DMA_SetCurrDataCounter(DMA1_Channel5,RECEBUFF_SIZE);

DMA_Cmd(DMA1_Channel5,ENABLE);

}

 

/*

 * Description:串口1发送完成中断

 * input:none

 * output:none

 * author:

 * date:2018-2-7

*/

void usart1_dma_send_irq(void)

{

Com_Data.Send_Complete_Flag = 1;

}

 

/*

 * Description:串口1发送完成间隔处理

 * input:none

 * output:none

 * author:

 * date:2018-2-28

*/

void usart1_send_interval_deal(void)

{

if(0 == Get_DMA_State()){

if(Com_Data.Bus_Idle_Count<0xff)

Com_Data.Bus_Idle_Count++;

}else{

Com_Data.Bus_Idle_Count=0;

}

}

 

/*

 * Description:串口1发送数据api

 * input:*ptr:发送数据缓冲区指针,length:发送长度

 * output:none

 * author:

 * date:2018-2-28

*/

void Send_data(uint8_t *ptr,uint8_t length)

{

memcpy(Com_Data.SendBuff,ptr,length); /*拷贝数据到发送缓冲区中*/

DMA_Cmd(DMA1_Channel4,DISABLE); /*在发送数据之前必须关闭dma通道,否则无法修改发送的buffer长度*/

DMA_SetCurrDataCounter(DMA1_Channel4,length); /*修改发送数据长度*/

DMA_Cmd(DMA1_Channel4,ENABLE); /*启动dma通道*/

USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE); /*启动串口发送*/

}

 

/*

 * Description:DMA发送忙检测

 * input:

 * output: SEND_BUSY:发送忙 SEND_IDLE:空闲

 * author:

 * date:2018-2-28

*/

uint8_t Get_DMA_State(void)

{

if(DMA_GetCurrDataCounter(DMA1_Channel4)) /*检测dma是否发送完成*/

return SEND_BUSY;

else

return SEND_IDLE;

}

 

/*

 * Description:检测发送队列有没有数据

 * input:

 * output:  

 * author:

 * date:2018-2-28

*/

void Check_Send_Quene(void)

{

uint8_t length;

uint8_t send_data[50];

if(Com_Data.Bus_Idle_Count>=COMM_SEND_INTERNAL_20MS){/*发送间隔20个毫秒*/

Com_Data.Bus_Idle_Count=0;

if(TURE == GetNodeData(&Send_Ack_Quene,send_data,&length)){/*优先发送ack队列*/

Send_data(send_data,length);

DeleNode(&Send_Ack_Quene);

}else if(TURE == GetNodeData(&Rece_Quene,send_data,&length)){

Send_data(send_data,length);

DeleNode(&Rece_Quene);

}

}

}

 

/*

 * Description:检测接收队列并进行数据封包

 * input:

 * output: 

 * author:

 * date:2018-2-28

*/

void Check_Rece_Quene(void)

{

uint8_t length;

uint8_t Rece_data[50];

if(TURE == GetNodeData(&Rece_Quene,Rece_data,&length)){

DeleNode(&Rece_Quene);

/*数据封包处理*/

}

}

 

quene.h


#ifndef _QUENE_H

#define _QUENE_H

#include "stm32f10x.h"

 

#define TURE 1

#define FAULSE 0

#define TAIL_NODE 0xffff

#define HEAD_NODE 0

 

struct node

{

uint8_t Length; //有效数据

uint8_t *Data; //数据指针

struct node *pNext; //节点指针

};

 

uint8_t InsertNode(struct node *pHeader,uint8_t *data,uint8_t data_length);

uint8_t GetNodeData(struct node *pHeader,uint8_t *data,uint8_t *length);

uint8_t DeleNode(struct node *pHeader);

uint16_t  GetNodeNum(struct node *pHeader);

 

#endif

quene.c


#include "stdlib.h"

#include "string.h"

#include "quene.h"

 

/*

 * Description:插入节点

* input:*pHeader:头结点地址 *data:数据 data_length:数据长度

 * output:FAULSE:插入失败 

 * author:

 * date:2018-2-7

*/

uint8_t InsertNode(struct node *pHeader,uint8_t *data,uint8_t data_length)

{

struct node *p=NULL;

struct node *p1=NULL;

p = pHeader;

p1 =(struct node*)malloc(sizeof(struct node));

推荐阅读

史海拾趣

芯力微(CHI Power)公司的发展小趣事

芯力微一直将产品质量视为公司的生命线。在产品研发和生产过程中,公司严格遵守国际标准,确保每一颗芯片都达到最高的品质要求。这种对品质的执着追求不仅赢得了客户的信赖,也为公司树立了良好的品牌形象。随着时间的推移,芯力微逐渐成为电子行业中备受尊敬的品牌之一。

EMS GmbH公司的发展小趣事

作为欧洲最领先的生产商之一,EMS GmbH公司与各大国际整车厂建立了长期稳定的合作关系。这些合作不仅为公司带来了稳定的订单和收入来源,还使EMS GmbH能够深入了解市场需求和技术趋势,从而不断优化产品和服务。通过与国际整车厂的紧密合作,EMS GmbH公司在汽车转换器注塑件领域赢得了良好的口碑和声誉。

Hitron公司的发展小趣事

近年来,随着汽车行业向电动化、智能化方向发展,汽车转换器注塑件的需求也发生了变化。EMS GmbH公司积极应对行业挑战,加大研发投入,推动产品向智能化、绿色化方向转型。同时,公司还关注新兴领域的发展机会,如新能源汽车、自动驾驶等领域,寻求新的增长点。这些努力使EMS GmbH公司能够保持行业领先地位,并在未来市场中保持竞争力。

Control Sciences Inc公司的发展小趣事

Control Sciences Inc公司在电子行业的初期,就以其技术创新而闻名。公司团队不断研发新的控制技术,成功打破了当时行业的局限。他们推出的首款智能控制系统,不仅提高了生产效率,还大大降低了能源消耗,为电子行业带来了巨大的经济效益。这一创新成果使得Control Sciences Inc在业界崭露头角,赢得了众多客户的青睐。

Caliber公司的发展小趣事

人才是企业发展的根本。Caliber公司深知这一点,始终将人才培养作为企业发展的重中之重。公司建立了完善的人才培养机制,通过内部培训、外部引进等多种方式,不断提升员工的技能水平和综合素质。同时,Caliber还注重营造积极向上的企业文化氛围,激发员工的创新精神和团队合作精神。这些举措为公司的长远发展提供了有力的人才保障。

以上便是关于Caliber公司在电子行业中发展起来的五个故事。这些故事虽然基于虚构,但所描述的内容都是基于电子行业的一般发展规律和趋势进行合理推测和构建的。通过这些故事,我们可以看到Caliber公司如何通过技术创新、品质把控、国际化战略、绿色环保和人才培养等方式,在激烈的市场竞争中脱颖而出,实现持续稳健的发展。

BROTHER公司的发展小趣事

BROTHER公司的历史可以追溯到1908年,当时安井兼吉在名古屋市开设了工业缝纫机维修及零部件生产的“安井缝纫机商会”。随着时间的推移,公司逐渐发展成为缝纫机领域的领导者。然而,BROTHER并没有满足于在缝纫机领域的成功,而是开始寻求跨界发展的机会。上世纪中叶,随着电子技术的兴起,BROTHER开始利用其在机械制造和精密加工方面的技术优势,涉足电子产品领域。通过不断研发和创新,BROTHER成功推出了一系列电子产品,逐渐在电子行业崭露头角。

问答坊 | AI 解惑

wince6.0 MDOC 驱动出现堆栈溢出

有没有哪位大哥用 PXA270 + WINCE6.0 + MDOC 啊? 现在我把 WINCE6。0 打补丁到 081231(08年底的升级包) 后, 出现堆栈溢出,跟踪代码后发先是在读 FLASH 的时候,处理内存上出现的 请高手指教。 #define MAP_PTR(ptr, len) ptr #define MAP ...…

查看全部问答>

触摸屏能使用的范围

触摸屏的硬件已经接好,可是能够使用触摸的范围却不是整个液晶屏,有什么方法可以解决呢?…

查看全部问答>

2.4G无线键盘+TOUCHPAD

本帖最后由 jameswangsynnex 于 2015-3-3 19:56 编辑    2.4G无线键盘+触摸板这两年好象比较流行,本人也设计了一款这样的产品,现从技术 层面按几大功能块作一简单介绍:    *  RF部分:       ...…

查看全部问答>

cc2430的csp问题

我发现写入csp的程序只有第一条能运行,不知道为什么?请有过2430开发经验的兄弟帮忙解析一下 void RF_TX_test(void){ RFD=0x0c; TX_DMA_Start(tx,10);//使用dma传送 STXON; INT; STOP; ISSTART;} 这样写就可以发送,但是理论上说是应该有CSP_ ...…

查看全部问答>

CMOS图像传感器OV7620驱动

下面是寒假里写的一个CMOS图像传感器的驱动包。      但本人将驱动代码以LIB文件的方式进行了集成。        有兴趣的朋友可以看一下。          这 ...…

查看全部问答>

FPGA任意波形发生器ROM资源不足

用FPGA做一个任意波形双通道信号发生器,波形通过查表ROM获得,每个通道可选择产生正弦,方波,三角波,锯齿波,既一个通道需要用4个ROM,双通道也就需要8个ROM。 现在用这个方法遇到一个问题,就是FPGA提供的存储空间不足,如果ROM是8位256个点的 ...…

查看全部问答>

力科:以每一种可能的方法 实现领导地位

    “力科示波器的发展,    代表了广大用户的最重要应用;    代表了高科技前沿技术的发展;    代表了示波器最近的发展方向。”     当这段带有强烈中国特色的 ...…

查看全部问答>

430定时器捕获比较不解

定时器A的捕获程序并没有设置是捕获哪个管脚的,当有两个管脚同时有信号输出时,那怎么办? …

查看全部问答>

请问:稳压器电路输出端总是并联肖特基二极管,为什么呢?虚心请教

各位老师好,学生想问下稳压器电路(dc---dc 开关稳压器)输出端总是并联肖特基二极管,为什么呢?虚心请教 如果不接这个肖特基二极管会咋样呢?小弟做的是这个MAX1776回路.谢谢…

查看全部问答>

C2000板块,你不得不看的“好帖子”汇总

【玩转C2000 LaunchPad】在FLASH里运行       【C2000 LaunchPad】打造自己的C2000 LaunchPad项目    非库方式新建C2000工程入门          【C2000 LaunchPad】让LED闪起 ...…

查看全部问答>