历史上的今天
返回首页

历史上的今天

今天是:2025年08月06日(星期三)

正在发生

2021年08月06日 | STM32—串口使用总结

2021-08-06 来源:eefocus

一.仅向上位机打印调试信息

单纯利用串口向上位机打印调试信息,程序如下:


void USART1_Init( uint32_t btl )

{

GPIO_InitTypeDef  GPIO_InitStruct;

USART_InitTypeDef USART_InitStruct;

RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA|RCC_APB2Periph_USART1, ENABLE );

GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;

GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;//Tx

GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_Init( GPIOA, &GPIO_InitStruct );

GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;//Rx

GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;

GPIO_Init( GPIOA, &GPIO_InitStruct );

USART_InitStruct.USART_BaudRate = btl;

USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;

USART_InitStruct.USART_Mode = USART_Mode_Tx;

USART_InitStruct.USART_Parity = USART_Parity_No;

USART_InitStruct.USART_StopBits = USART_StopBits_1;

USART_InitStruct.USART_WordLength = USART_WordLength_8b;

USART_Init( USART1, &USART_InitStruct );

USART_Cmd( USART1, ENABLE );

}


//串口重定向,直接利用printf函数输出调试信息

int fputc( int ch, FILE *f )

{

USART_SendData( USART1, ( uint8_t )ch );

while( USART_GetFlagStatus( USART1, USART_FLAG_TXE )!=SET );

return ch;

}


记得包含头文件,勾选Use MicroLIB,以使用printf等函数

在这里插入图片描述

二.与上位机交互信息

相比于上面的单向通信,有时候需要从上位机接收信息,然后进行反馈,这个时候就使用到串口的中断了。

上位机向单片机发送字符串,接收后再发送给上位机:



void USART1_Init( uint32_t btl )

{

/* 结构体声明 */

GPIO_InitTypeDef  GPIO_InitStruct;

USART_InitTypeDef USART_InitStruct;

NVIC_InitTypeDef NVIC_InitStruct;

/* 打开时钟 */

RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE );

RCC_APB2PeriphClockCmd( RCC_APB2Periph_USART1, ENABLE );

/* GPIO配置 */

GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;

GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;//Tx

GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_Init( GPIOA, &GPIO_InitStruct );

GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;//Rx

GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;

GPIO_Init( GPIOA, &GPIO_InitStruct );

/* 串口配置 */

USART_InitStruct.USART_BaudRate = btl;

USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;

USART_InitStruct.USART_Mode = USART_Mode_Tx|USART_Mode_Rx;//收发模式

USART_InitStruct.USART_Parity = USART_Parity_No;

USART_InitStruct.USART_StopBits = USART_StopBits_1;

USART_InitStruct.USART_WordLength = USART_WordLength_8b;

USART_Init( USART1, &USART_InitStruct );

/* 中断配置 */

NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;

NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=3 ;

NVIC_InitStruct.NVIC_IRQChannelSubPriority = 3;

NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;

NVIC_Init(&NVIC_InitStruct);

USART_ITConfig( USART1, USART_IT_RXNE, ENABLE );//接收寄存器非空触发中断

/* 使能串口 */

USART_Cmd( USART1, ENABLE );

}



volatile uint8_t n=0;

//接收缓冲区

uint8_t USART1_Rx_Buf[100];

void USART1_IRQHandler( void )

{

if( USART_GetITStatus( USART1, USART_IT_RXNE )==SET )

{

USART1_Rx_Buf[n]=USART_ReceiveData( USART1 );

n++;

}

USART_ClearFlag( USART1, USART_IT_RXNE );

}


每从上位机中接收一字节的数据,都将数据存储在串口的接收缓冲区USART1_Rx_Buf[100]中。


三.作为驱动接口

一些模块的驱动接口就是串口,这个时候就需要单片机从模块中读取指定格式的数据,比如GPS模块,将定位信息从串口发出,单片机解析串口数据,显示在上位机中。

usart3用来与GPS模块通信,从GPS模块接收数据,认为10ms内的数据属于一次数据,所以就需要定时器来控制时间。

usart3:


//串口1中断服务程序

//注意,读取USARTx->SR能避免莫名其妙的错误   

u8 USART_RX_BUF[USART_REC_LEN];     //接收缓冲,最大USART_REC_LEN个字节.

//接收状态

//bit15, 接收完成标志

//bit14, 接收到0x0d

//bit13~0, 接收到的有效字节数目

u16 USART_RX_STA=0;       //接收状态标记   

  

void uart_init(u32 bound){

  //GPIO端口设置

  GPIO_InitTypeDef GPIO_InitStructure;

USART_InitTypeDef USART_InitStructure;

NVIC_InitTypeDef NVIC_InitStructure;

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

  

//USART1_TX   GPIOA.9

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9

  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出

  GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9

   

  //USART1_RX   GPIOA.10初始化

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入

  GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10  


  //Usart1 NVIC 配置

  NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;

NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3

NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级3

NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能

NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器

  

   //USART 初始化设置


USART_InitStructure.USART_BaudRate = bound;//串口波特率

USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式

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); //初始化串口1

  USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断

  USART_Cmd(USART1, ENABLE);                    //使能串口1 


}


void USART1_IRQHandler(void)                //串口1中断服务程序

{

u8 Res;


if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //接收中断(接收到的数据必须是0x0d 0x0a结尾)

{

Res =USART_ReceiveData(USART1); //读取接收到的数据

if((USART_RX_STA&0x8000)==0)//接收未完成

{

if(USART_RX_STA&0x4000)//接收到了0x0d

{

if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始

else USART_RX_STA|=0x8000; //接收完成了 

}

else //还没收到0X0D

{

if(Res==0x0d)USART_RX_STA|=0x4000;

else

{

USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;

USART_RX_STA++;

if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收   

}  

}

}     

     } 


time7:



extern vu16 USART3_RX_STA;


//定时器7中断服务程序     

void TIM7_IRQHandler(void)

{

if (TIM_GetITStatus(TIM7, TIM_IT_Update) != RESET)//是更新中断

{    

USART3_RX_STA|=1<<15; //标记接收完成

TIM_ClearITPendingBit(TIM7, TIM_IT_Update  );  //清除TIM7更新中断标志    

TIM_Cmd(TIM7, DISABLE);  //关闭TIM7 

}     

}

 

//通用定时器7中断初始化

//这里时钟选择为APB1的2倍,而APB1为42M

//arr:自动重装值。

//psc:时钟预分频数

//定时器溢出时间计算方法:Tout=((arr+1)*(psc+1))/Ft us.

//Ft=定时器工作频率,单位:Mhz 

//通用定时器中断初始化

//这里始终选择为APB1的2倍,而APB1为36M

//arr:自动重装值。

//psc:时钟预分频数  

void TIM7_Int_Init(u16 arr,u16 psc)

{

NVIC_InitTypeDef NVIC_InitStructure;

TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;


RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM7, ENABLE);//TIM7时钟使能    

//定时器TIM7初始化

TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值

TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值

TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim

TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式

TIM_TimeBaseInit(TIM7, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位

 

TIM_ITConfig(TIM7,TIM_IT_Update,ENABLE ); //使能指定的TIM7中断,允许更新中断

TIM_Cmd(TIM7,ENABLE);//开启定时器7

NVIC_InitStructure.NVIC_IRQChannel = TIM7_IRQn;

NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0 ;//抢占优先级0

NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //子优先级2

NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能

NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器

}


四.结合DMA接收数据帧

当单片机从串口上接收数据时,一般我们都是接收一个字节数据,进入一次中断,在中断中处理数据或者做标记,这种方法虽然简单,但是对于大量数据的情况,频繁地进入中断处理数据就占有了CPU的宝贵资源。


这个时候我们可以使用DMA来接收数据,DMA接收数据的好处就是省CPU资源,DMA仅仅在初始化的时候占用一下CPU资源,其他操作都是在DMA的控制器来完成的。


使用背景:单片机从USART2中接收传感器传回的数据帧(12字节),传感器每秒上报一次数据,要求单片机可以收到完整数据并且可以对数据进行处理。


程序框架:以往串口接收数据都是判断数据寄存器非空的,一字节一字节地接收,现在使用DMA,使每一次数据寄存器的值都自动传给内存指定地址(也就是指定的数据缓冲区),当串口接收完一帧数据后,会触发空闲中断,这意味着一帧数据的接收完成,我们只需要在数据缓冲区中对数据进行处理即可。


代码:


uint8_t USART2_Rx_Buf[12];


void USART2_Config( void )

{

/* 声明各结构体 */

GPIO_InitTypeDef GPIO_InitStruct;

USART_InitTypeDef USART_InitStruct;

NVIC_InitTypeDef NVIC_InitStruct;

DMA_InitTypeDef DMA_InitStruct;

/* 打开时钟 */

RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE );

RCC_APB1PeriphClockCmd( RCC_APB1Periph_USART2, ENABLE );

RCC_AHBPeriphClockCmd( RCC_AHBPeriph_DMA1, ENABLE );

/* GPIO配置 */

GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;

GPIO_InitStruct.GPIO_Pin = GPIO_Pin_2;                 //USART2_Tx:PA2

GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_Init( GPIOA, &GPIO_InitStruct );

GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;

GPIO_InitStruct.GPIO_Pin = GPIO_Pin_3;                //USART2_Rx:PA3

GPIO_Init( GPIOA, &GPIO_InitStruct );

/* 串口配置 */

USART_DeInit( USART2 );

USART_InitStruct.USART_BaudRate = 9600;                //波特率:9600

USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;

USART_InitStruct.USART_Mode = USART_Mode_Tx|USART_Mode_Rx;

USART_InitStruct.USART_Parity = USART_Parity_No;

USART_InitStruct.USART_StopBits = USART_StopBits_1;

USART_InitStruct.USART_WordLength = USART_WordLength_8b;

USART_Init( USART2, &USART_InitStruct);

/* 中断配置 */

NVIC_PriorityGroupConfig( NVIC_PriorityGroup_2 );

NVIC_InitStruct.NVIC_IRQChannel = USART2_IRQn;

NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;

NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;

NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;

NVIC_Init( &NVIC_InitStruct );

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

USART_DMACmd( USART2, USART_DMAReq_Rx, ENABLE );   //开启DMA接收

/* 配置DMA传输数据 USART2对应DMA1的通道6 方向:外设到存储器*/

    DMA_InitStruct.DMA_PeripheralBaseAddr = USART2_BASE+0x04;              // 设置DMA源地址:串口数据寄存器地址*/

DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)USART2_Rx_Buf;           // 内存地址(要传输的变量的指针)

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

DMA_InitStruct.DMA_BufferSize = 12;                                    // 传输大小 一帧数据12字节     

推荐阅读

史海拾趣

BELDEN公司的发展小趣事

BELDEN公司的故事始于1902年,当Joseph C. Belden在美国芝加哥创立这家公司时,他怀揣着对电线电缆制造的深厚热情与独特见解。初创时期,公司致力于研发和生产高品质的电线电缆产品,很快便在市场中赢得了良好的声誉。经过多次试验,公司成功发明了“Beldenamel”绝缘材料,这种柔韧的珐琅绝缘材料为公司的成功奠定了坚实的基础。随着产品的不断优化和市场的逐步拓展,BELDEN逐渐在电子行业中崭露头角。

广州奥松公司的发展小趣事

随着公司的不断发展,奥松电子陆续获得了多项荣誉资质,如“国家专精特新‘小巨人’企业”、“国家高新技术企业”等。这些荣誉的获得,不仅证明了公司在电子行业中的实力与地位,也为公司的品牌建设提供了有力的支撑。同时,这些荣誉也为公司的市场拓展和业务发展提供了更多的机遇和可能。

Essentra Components公司的发展小趣事

作为一家有社会责任感的企业,Essentra Components公司积极履行社会责任,关注社会公益事业。公司定期参与各种慈善活动和社会公益活动,为当地社区的发展做出了积极贡献。此外,公司还注重员工的福利和培训发展,为员工提供了良好的工作环境和发展机会。这些举措不仅树立了企业的良好形象,还增强了企业的凝聚力和向心力。

请注意,以上故事是基于对Essentra Components公司业务特性和市场环境的理解而构想出来的,并非实际发生的事件。如有需要,建议直接联系Essentra Components公司或查阅相关资料以获取更准确的信息。

BITECH公司的发展小趣事

面对日益激烈的市场竞争,Essentra Components公司决定实施全球化战略,以拓展更广阔的市场。公司先后在亚洲、欧洲、美洲等多个地区设立了生产基地和销售服务点,实现了产品的全球覆盖。同时,公司还积极与当地企业合作,共同开发适合当地市场需求的产品,进一步巩固了其在全球市场的地位。

Dantona Industries公司的发展小趣事

Dantona Industries公司的创始人,Daniel Tona,在电子工程领域拥有深厚的背景。他看到了电子行业的巨大潜力,决定创立自己的公司。起初,他只有一间小办公室和几个志同道合的伙伴。为了节省成本,他们经常需要亲自采购材料、组装产品,甚至进行市场推广。在这个过程中,他们遇到了资金短缺、技术难题以及市场竞争等多重挑战。然而,凭借对技术的执着和对市场的敏锐洞察力,Daniel Tona带领团队一步步克服困难,逐渐在电子行业站稳了脚跟。

BENCENT公司的发展小趣事

随着国内市场的饱和,BENCENT公司开始将目光投向海外市场。公司制定了详细的国际化战略,包括在海外市场设立分支机构、拓展销售渠道、加强与当地企业的合作等。通过一系列的努力,公司成功打入多个海外市场,实现了业务的多元化和国际化。

问答坊 | AI 解惑

matlab信号处理详解

《Matlab信号处理详解》详细介绍Matlab在数字信号处理中的应用实例,各种数字滤波器的设计,包含源程序…

查看全部问答>

关于S3C2410裸机驱动LCD的问题

/************************************************************** 320×240 16Bpp TFT LCD功能模块初始化 **************************************************************/ void Lcd_Init(void) {         rLCDCON1=( ...…

查看全部问答>

vs2005 用VC++建立的基于对话框的智能设备MFC应用程序,当包含afxinet.h头文件时,出现下面的错误,是什么原因呀?高手进,在线等之

如题:我想在winCE里写一个FTP上传文件的程序,vs2005 用VC++建立的基于对话框的智能设备MFC应用程序,当包含afxinet.h头文件时,出现下面的错误,是什么原因呀?高手进,在线等之 错误        1        ...…

查看全部问答>

nand drive 问题

nand cach write/read 命令是自动连续读写nand 如果用这个命令后遇到坏blcok 怎么办?nand 自己会跳过吗? 谢谢…

查看全部问答>

wince5.0下直接读写内存

想在wince5.0下直接读写内存,往指定的空间读写数据,该怎么实现,忘达人们给点建议!!谢谢!!…

查看全部问答>

分析一个程序错误!

module delay_line(start,stop,up_out,upQ,down_out,downQ,clk,reset);input clk,start,stop,reset;reg [60:0]up_out,down_out,upQ,downQ;integer i,j,m,n;delay_unit du0(start,reset,up_out[0]);delay_unit du2(stop,reset,down_out[0]);always @ ...…

查看全部问答>

大家来说说用DSP的时候你都遇到过什么样无法解决的问题

大家来说说用DSP的时候你都遇到过什么样无法解决的问题?…

查看全部问答>

超级给力DIY钢铁侠MK3方舟反应堆

从《钢铁侠1》到《复仇者联盟》再到《钢铁侠3》,托尼胸前的反应堆,也更新了好几代。高中刚毕业,放假在家,就有了想自己做一个反应堆的想法,然后就开始实践了。 ◆ 第一步:采购原材料(具体如下)      ● 网上定制的亚 ...…

查看全部问答>

用ccr0 ccr1两个中断控制led灯 不能成功

#include                 int main(void) {     WDTCTL = WDTPW | WDTHOLD;        // Stop watchdog timer     DCOCTL=DCO0;   ...…

查看全部问答>

allegro设置底部开窗露铜时,如何能设置挖空过孔呢?

如下图,左边为原来设置好的,右边是我自己画的。怎么设置过孔挖空,也像左边的一样效果呢?求~谢谢 …

查看全部问答>