STM32 串口驱动,分层通信

发布者:码农闲散人最新更新时间:2020-04-03 来源: eefocus关键字:STM32  串口驱动  分层通信 手机看文章 扫描二维码
随时随地手机看文章

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

[1] [2]
关键字:STM32  串口驱动  分层通信 引用地址:STM32 串口驱动,分层通信

上一篇:STM32同时开启两个定时器,其一个定时器不能设置断点的原因
下一篇:STM32一直死在r1,[r0,#0x808]

推荐阅读最新更新时间:2024-11-05 16:52

STM32ADC单次转换DMA读取
DMA读取方式很适合高频率的ADC采样信号。 ADC的DMA读取方式,其实和上一篇的中断读取方式差不多,初始化代码更是相似。初始化代码如下: static void ADC_GPIO_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //ʹÄÜPB,PE¶Ë¿ÚʱÖÓ GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN; GPIO_Init(
[单片机]
STM32串口——5个串口的使用方法
串口是我们常用的一个数据传输接口,STM32F103系列单片机共有5个串口,其中1-3是通用同步/异步串行接口USART(Universal Synchronous/Asynchronous Receiver/Transmitter),4,、5是通用异步串行接口UART(Universal Asynchronous Receiver/Transmitter)。 配置串口包括三部分内容: 1. I/O口配置:TXD配置为复用推挽输出(GPIO_Mode_AF_PP),RXD配置为浮空输入(GPIO_Mode_IN_FLOATING); 2. 串口配置:波特率等; 3. 中断向量配置:一般用中断方式接收数据。 注
[单片机]
<font color='red'>STM32</font>串口——5个串口的使用方法
STM32 中断使用
STM32中断有时候用多了容易乱,特此记录一下,因为之前一直是M3 M0交叉用,固件库有些区别容易弄混,这里说一下M3外部中断的配置 步骤: 1.将对应的IO配置为输入 2.将IO对应的中断的优先等级用NVIC配置好 3.设置好对应IO的中断模式和触发方式 4.将IO所属的中断线设为中断输入源 1---------以PB5为例子 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; GPIO_Init(G
[单片机]
一个简单逆向stm32固件程序的实例分享
本文主要跟大家分享一个简单逆向stm32固件程序的实例,为了让大家在一款成熟的产品中去考虑加密这一块的技术,不然分分钟被别人copy! 1、情景再现 咬金,你们公司固件程序有加密处理吗 ? 额~,算了吧,我们公司的单片机程序炒鸡简单的,还加啥子密。 你这想法不对,假如产品卖得很好,如果没有任何加密措施,那岂不人家随便复制售卖。 没关系吧,反正他们没源码,应该也没那么容易复制吧 一点加密都没有,盗取还是比较简单的。 我才不信~~ 那行,把你的板子给我,不用你的源码,跟你把波特率改了! 直接读取固件 这里以stm32单片机进行演示,如果MCU没有做flash读取或者熔断保护,则可以通过jlink等烧写工具直接读取其Flash上的固
[单片机]
一个简单逆向<font color='red'>stm32</font>固件程序的实例分享
STM32的PWM输出及频率和脉宽(占空比)的计算
一、stm32的pwm输出引脚是使用的IO口的复用功能。 二、T2~T5这4个通用定时器均可输出4路PWM CH1~CH4。 三、我们以tim3的CH1路pwm输出为例来进行图文讲解(其它类似),并在最后给出tim3的ch1和ch2两路pwm输出的c代码(已在STM32F103RBT6上测试成功,大家放心使用!)。 四、给出了PWM频率和占空比的计算公式。 步骤如下: 1、使能TIM3时钟 RCC- APB1ENR |= 1 1; 2、配置对应引脚(PA6)的复用输出功能 GPIOA- CRL &= 0XF0FFFFFF;//PA6清0 GPIOA- CRL |= 0X0B000000;//复用功能输出(推挽
[单片机]
STM32 GPIO 寄存器配置
一.CRH和CRL的使用:fficeffice / CRH和CRL的使用基本相同,CRH用于控制GPIOX(X表示A---G)的高8位(Pin15---Pin8),而CRL用于控制GPIOX(X表示A---G)的低8位(Pin7----Pin0)。 二.ODR的使用: 1. RCC- APB2ENR|=1 2; //使能PORTA时钟 GPIOA- CRH&=0XFFFFFFF0;//清除该位原来的设置 GPIOA- CRH|=0X00000003;//PA8 推挽输出 GPIOA- ODR|=
[单片机]
STM32 大小端模式 与 堆栈及其增长方向分析
栈增长和大端/小端问题是和CPU相关的两个问题. 1,首先来看:栈(STACK)的问题. 函数的局部变量,都是存放在 栈 里面,栈的英文是:STACK.STACK的大小,我们可以在stm32的启动文件里面设置,以 战舰 stm32 开发板 为例,在startup_stm32f10x_hd.s里面,开头就有: Stack_Size EQU 0x00000800 表示栈大小是0X800,也就是2048字节.这样,CPU处理任务的时候,函数局部变量做多可占用的大小就是:2048字节,注意:是所有在处理的函数,包括函数嵌套,递归,等等,都是从这个 栈 里面,来分配的. 所以,如果一个函数的局部变量过多,比如在函数里面定义一个u8
[单片机]
<font color='red'>STM32</font> 大小端模式 与 堆栈及其增长方向分析
STM32软件运行过程,如何查看全局变量的实时数据?
下面是一个单片机STM32RCT6的PA8,PA9,PA10引脚输出PWM波形的仿真步骤,此外还展示了软件运行过程,如何查看全局变量的实时数据。每一步我都做了截图,大家照着一步步来,请大家放心参考! 1.点target图标,如下: 2.选择好单片机芯片的型号:我选的STM32RCT6型号,大家可以根据自己手上stm32开发板的型号来选择 3.外部晶振频率的选择:8Mhz(因为大部分单片机的外部晶振是8Mhz),为了使仿真更贴近实际,通常情况下都是选8Mhz 4.进入Debug页面进行设计,特别要注意第四点parameter,注意选正确好芯片的型号,我的是RC系列,所以写了RC,如果是RB系列,要后面改为RB 5
[单片机]
<font color='red'>STM32</font>软件运行过程,如何查看全局变量的实时数据?
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved