基于STM32模拟UART串口通信

发布者:快乐的天使最新更新时间:2024-02-27 来源: elecfans关键字:STM32  模拟UART  串口通信 手机看文章 扫描二维码
随时随地手机看文章

UART工作原理


UART即通用异步收发器,是一种串行通信方式。数据在传输过程中是通过一位一位地进行传输来实现通信的,串行通信方式具有传输线少,成本底等优点,缺点是速度慢。串行通信分为两种类型:同步通信方式和异步通信方式。



但一般多用异步通信方式,主要因为接受和发送的时钟是可以独立的这样有利于增加发送与接收的灵活性。异步通信是一个字符接着一个字符传输,一个字符的信息由起始位、数据位、奇偶校验位和停止位组成。


每一个字符的传输靠起始位来同步,字符的前面一位是起始位,用下降沿通知收方开始传输,紧接着起始位之后的是数据位,传输时低位在前高位在后,字符本身由5~8位数据位组成。


数据位后面是奇偶校验位,最后是停止位,停止位是用高电平来标记一个字符的结束,并为下一个字符的传输做准备。停止位后面是不同长度的空闲位。停止位和空闲位都规定为高电平,这样可以保证起始位有一个下降沿。


UART的帧格式如图:

56a5cda3463d6bdba8056511a0624bd0_wKgZomSYBWaAUq5TAAAdLwoffVc558.png

UART的帧格式包括线路空闲状态(idle,高电平)、起始位(start bit,低电平)、5~8位数据位(data bits)、校验位(parity bit,可选)和停止位(stop bit,位数可为1、1.5、2位)。


往期相关推文:STM32串口通信基本原理


UART模拟原理


UART的模拟方式基本就是定时器+IO口实现。


方案1:只打印不接收


如果在实际使用中只是为了打印log而不接收数据,可以采用DWT加普通IO口的方式;


#define  VCOM_BOUND     115200

#define  VCOM_PIN       GPIO_Pin_11

#define  VCOM_PORT      GPIOA

#define  VCOM_PIN_HIGH  VCOM_PORT->BSRR = VCOM_PIN

#define  VCOM_PIN_LOW   VCOM_PORT->BRR  = VCOM_PIN



#define  BSP_REG_DEM_CR                           (*(volatile unsigned int *)0xE000EDFC) //DEMCR寄存器

#define  BSP_REG_DWT_CR                           (*(volatile unsigned int *)0xE0001000)   //DWT控制寄存器

#define  BSP_REG_DWT_CYCCNT                       (*(volatile unsigned int *)0xE0001004) //DWT时钟计数寄存器 

#define  BSP_REG_DBGMCU_CR                        (*(volatile unsigned int *)0xE0042004)



#define  DEF_BIT_00                               0x01u

#define  DEF_BIT_24                               0x01000000u

#define  BSP_BIT_DEM_CR_TRCENA                    DEF_BIT_24   

#define  BSP_BIT_DWT_CR_CYCCNTENA                 DEF_BIT_00

static unsigned int  sys_clock = 48000000;



inline void dwt_start(void)

{

 BSP_REG_DEM_CR     |= (unsigned int)BSP_BIT_DEM_CR_TRCENA;

    BSP_REG_DWT_CYCCNT  = (unsigned int)0u;            //初始化CYCCNT寄存器

    BSP_REG_DWT_CR     |= (unsigned int)BSP_BIT_DWT_CR_CYCCNTENA;    //开启CYCCNT 

}



inline void dwt_stop(void)

{

 BSP_REG_DWT_CR = 0;

}



void vcom_pin_init(void)

    GPIO_InitTypeDef GPIO_InitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);

    GPIO_InitStructure.GPIO_Pin   = VCOM_PIN;

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;

    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_Out_PP;

    GPIO_Init(VCOM_PORT, &GPIO_InitStructure);

    GPIO_SetBits(VCOM_PORT,VCOM_PIN);

 VCOM_PIN_HIGH;

}



void vcom_put_char(char ch)

{

 int i;

 int dat[8];

 uint32_t sys_clk, bit_width;

 volatile uint32_t time_stamp;

 

    sys_clk = sys_clock/1000000;  

    bit_width = 1000000*sys_clk/VCOM_BOUND;

    for(i=0; i<8; i++)           

    {

        if(ch & 0x01)

            dat[i] = 1;

        else

            dat[i] = 0; 

        ch >>= 1;

    }

    OS_CPU_SR cpu_sr;

    enter_critical();//以下代码进行临界保护,防止被中断打断造成发送误码

    dwt_start();

    VCOM_PIN_LOW; //发送起始位

 time_stamp = BSP_REG_DWT_CYCCNT;

 while(BSP_REG_DWT_CYCCNT < (time_stamp+bit_width));

 for(i=0; i<8; i++)

 {

  if(dat[i])

   VCOM_PIN_HIGH;

  else

   VCOM_PIN_LOW;

  time_stamp = BSP_REG_DWT_CYCCNT;

  while(BSP_REG_DWT_CYCCNT < (time_stamp+bit_width)); //发8bit 数据位

 }

 VCOM_PIN_HIGH;

 time_stamp = BSP_REG_DWT_CYCCNT;

 while(BSP_REG_DWT_CYCCNT < (time_stamp+bit_width));     //发停止位

 dwt_stop();

 exit_critical();

}



void vcom_printf(const char *fmt, ...)

{

    char buf[0x80];

    int  i;

    va_list ap;

 memset(buf, 0x00, sizeof(buf));

    va_start(ap, fmt);

    vsnprintf(buf, sizeof(buf), fmt, ap); 

    va_end(ap); 

 

 i = 0;

 while(buf[i])

 {

  vcom_put_char(buf[i]);

  i++;

 }

}

方案2:半双工UART


实现方式: 普通定时器+普通IO口中断+fifo


/**

*软件串口的实现(IO模拟串口)

* 波特率:9600    1-8-N

* TXD : PC13

* RXD : PB14

* 使用外部中断对RXD的下降沿进行触发,使用定时器4按照9600波特率进行定时数据接收。

* Demo功能: 接收11个数据,然后把接收到的数据发送出去

*/





#define OI_TXD PCout(13)

#define OI_RXD PBin(14)



#define BuadRate_9600 100



u8 len = 0; //接收计数

u8 USART_buf[11];  //接收缓冲区



enum{

 COM_START_BIT,

 COM_D0_BIT,

 COM_D1_BIT,

 COM_D2_BIT,

 COM_D3_BIT,

 COM_D4_BIT,

 COM_D5_BIT,

 COM_D6_BIT,

 COM_D7_BIT,

 COM_STOP_BIT,

};



u8 recvStat = COM_STOP_BIT;

u8 recvData = 0;



void IO_TXD(u8 Data)

{

 u8 i = 0;

 OI_TXD = 0;  

 delay_us(BuadRate_9600);

 for(i = 0; i < 8; i++)

 {

  if(Data&0x01)

   OI_TXD = 1;  

  else

   OI_TXD = 0;  

  

  delay_us(BuadRate_9600);

  Data = Data>>1;

 }

 OI_TXD = 1;

 delay_us(BuadRate_9600);

}

 

void USART_Send(u8 *buf, u8 len)

{

 u8 t;

 for(t = 0; t < len; t++)

 {

  IO_TXD(buf[t]);

 }

}

 

 void IOConfig(void)

 {

 GPIO_InitTypeDef  GPIO_InitStructure;

 NVIC_InitTypeDef NVIC_InitStructure;

 EXTI_InitTypeDef EXTI_InitStruct;

 RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC, ENABLE);  //使能PB,PC端口时钟 

 

 //SoftWare Serial TXD

 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;     

 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;    //推挽输出

 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;   //IO口速度为50MHz  

 GPIO_Init(GPIOC, &GPIO_InitStructure);       

 GPIO_SetBits(GPIOC,GPIO_Pin_13);       

  

  

 //SoftWare Serial RXD

 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;

 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;

 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  

 GPIO_Init(GPIOB, &GPIO_InitStructure);  

 

 GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);

 EXTI_InitStruct.EXTI_Line = EXTI_Line14;

 EXTI_InitStruct.EXTI_Mode=EXTI_Mode_Interrupt;

 EXTI_InitStruct.EXTI_Trigger=EXTI_Trigger_Falling; //下降沿触发中断

 EXTI_InitStruct.EXTI_LineCmd=ENABLE;

 EXTI_Init(&EXTI_InitStruct);



 NVIC_InitStructure.NVIC_IRQChannel= EXTI15_10_IRQn ; 

 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2; 

 NVIC_InitStructure.NVIC_IRQChannelSubPriority =2;  

 NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;  

 NVIC_Init(&NVIC_InitStructure);  

 

}

 

void TIM4_Int_Init(u16 arr,u16 psc)

{

 TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;

 NVIC_InitTypeDef NVIC_InitStructure;

 

 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); //时钟使能

 

 //定时器TIM4初始化

 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(TIM4, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位

 TIM_ClearITPendingBit(TIM4, TIM_FLAG_Update);

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

 

 //中断优先级NVIC设置

 NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;  //TIM4中断

 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;  //先占优先级1级

 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;  //从优先级1级

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

 NVIC_Init(&NVIC_InitStructure);  //初始化NVIC寄存器    

}

 

 

 int main(void)

 {  

 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级

 delay_init();

 IOConfig();

 TIM4_Int_Init(107, 71);  //1M计数频率

 

 while(1)

 {

  if(len > 10)

  {

   len = 0;

   USART_Send(USART_buf,11);

  }

 }

}



void EXTI15_10_IRQHandler(void)

{

 if(EXTI_GetFlagStatus(EXTI_Line14) != RESET)

 {

  if(OI_RXD == 0) 

  {

   if(recvStat == COM_STOP_BIT)

   {

    recvStat = COM_START_BIT;

    TIM_Cmd(TIM4, ENABLE);

   }

  }

  EXTI_ClearITPendingBit(EXTI_Line14);

 }

}



void TIM4_IRQHandler(void)

{  

 if(TIM_GetFlagStatus(TIM4, TIM_FLAG_Update) != RESET)

 {

  TIM_ClearITPendingBit(TIM4, TIM_FLAG_Update); 

  recvStat++;

  if(recvStat == COM_STOP_BIT)

  {

   TIM_Cmd(TIM4, DISABLE);

   USART_buf[len++] = recvData; 

   return;

  }

  if(OI_RXD)

  {

   recvData |= (1 << (recvStat - 1));

  }else{

   recvData &= ~(1 << (recvStat - 1));

  } 

 }  

}


关键字:STM32  模拟UART  串口通信 引用地址:基于STM32模拟UART串口通信

上一篇:STM32 RTC实时时钟(二)
下一篇:STM32F1命名规则及选型

推荐阅读最新更新时间:2024-11-12 21:51

stm32-vref+ vref- vdda vssa
VDDA,VSSA则是模拟部分的电源,这个必须接,否则无法串口下载代码。
[单片机]
stm32 多路ADC+DMA 在内存中各路ADC的数据是如何界定的
#define N 50 //每通道采50次 #define M 7 //为7个通道 uint32_t ADC_ConvertedValue ; 其中 DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&ADC_ConvertedValue; DMA_InitStructure.DMA_BufferSize = N*M; //DMA通道的DMA缓存的大小 这里DMA 知道往 ADC_ConvertedValue 放数据以及缓存的SIZE, 当把七个通道的ADC 数据通过DMA传送完了, DMA 是怎样界定ADC_ConvertedValue 为一个通道的数据?ADC_ConvertedValu
[单片机]
STM32红牛开发板非固件库控制LED
摘要 STM32红牛开发板上的5个LED,接在GPIOF6~10脚,输出低电平时,LED亮。这样我们设置GPIOF的相关寄存器,让其输出低电平就可以让LED亮。因为没有用到ST提供的固件库,所以是直接对寄存器的内存地址读写,即对一个指向该地址的指针变量进行读写。并且该变量必须为易变型的,即用volatile定义,这样是为了告诉编译器不要去优化这个变量,导致其它一些寄存器的数据变化。GPIOx是挂载在APB2高速外设总线上的,最大频率是72MHZ,所以我们除了了打开HSE(外部高速时钟)并关闭内部高速时钟(开机默认选择了HSI)外,还要打开APB2总线的时钟。 一、寄存器地址映射 外设的基址PERI
[单片机]
STM32——定时器TIME模块之输入捕获
STM32的定时器模块有很强大的功能,除了普通的定时功能之外还可以进行输入捕获和输出比较(PWM),PWM已在别的文章中介绍过,现在介绍一下输入捕获。 在这里先说本人在测试时想到的两个疑问: 疑问1:STM32的同一个定时器是否能同时进行输入捕获和输出比较(PWM)? 疑问2:假设疑问1的答案是可以,那输入捕获的范围是不是受限制? 功能实现: 1、既然是输入捕获,肯定需要相关引脚对信号进行检测,所以需要查找开发手册和数据手册来确定是哪几个引脚,要用哪几个引脚,然后给予合适的配置(图1)。 2、要进行输入捕获实验,首先需要配置某个定时器的时基功能,这样我们才能根据这个时基计算我们的捕获信息;其次就是要配置输
[单片机]
<font color='red'>STM32</font>——定时器TIME模块之输入捕获
STM32基于固件库学习笔记(13)ADC读取电压值
ADC简介 STM32f103 系列有 3 个 ADC(STM32F101/102 系列只有 1 个 ADC),精度为 12 位,每个 ADC 最多有 16 个外部通道。其中ADC1和 ADC2 都有 16个外部通道,ADC3根据 CPU 引脚的不同通道数也不同,一般都有8 个外部通道。 ADC 可以独立使用,也可以使用双重模式(提高采样率)。STM32 的 ADC 是 12 位逐次逼近型的模拟数字转换器。它有 18 个通道,可测量 16 个外部和 2 个内部信号源。各通道的 A/D 转换可以单次、连续、扫描或间断模式执行。ADC 的结果可以左对齐或右对齐方式存储在 16 位数据寄存器中。 模拟看门狗特性允许应用程序检测输入电压是
[单片机]
意法半导体STM32软件包,让简单的IoT产品具有亚马逊的Alexa技术
2018年4月2日,中国——意法半导体的X-CUBE-AVS软件包让亚马逊的Alexa语音服务(AVS)能够运行在STM32*微控制器上,使具有云智能功能(自动语音识别和自然语言理解)的高级会话用户界面出现在简单的物联网设备上,例如,智能家电、家庭自动化设备和办公设备。 作为STM32Cube软件平台的扩展包,X-CUBE-AVS包含直接可用的固件库和开放例行程序,这有助于将AVS SDK(软件开发套件)快速移植到微控制器上。此外,该软件包还集成应用代码示例,开发人员无需开发嵌入式设备运行AVS所需的复杂软件层。AVS开发工具通常是为能耗和价格较高的微处理器设计,而X-CUBE-AVS是同类首个微控制器专用软件包,让更多的
[半导体设计/制造]
STM32 printf重定向
STM32串口通信中使用printf发送数据配置方法(开发环境 Keil RVMDK) 在STM32串口通信程序中使用printf发送数据,非常的方便。可在刚开始使用的时候总是遇到问题,常见的是硬件访真时无法进入main主函数,其实只要简单的配置一下就可以了。 下面就说一下使用printf需要做哪些配置。 有两种配置方法: 一、对工程属性进行配置,详细步骤如下 1、首先要在你的main 文件中 包含“stdio.h” (标准输入输出头文件)。 2、在main文件中重定义 fputc 函数 如下: // 发送数据 int fputc(int ch, FILE *f) { USART_SendData(
[单片机]
STM32处理器的AD难点整理
1.STM32的AD转换,可以将转换任务组织为两个组:规则组和注入组。在任意多个通道上以任意顺序进行的一系列转换构成成组转换。例如,可以如下顺序完成转换:通道3、通道8、通道2、通道2、通道0、通道2、通道2、通道15。在执行规则通道组扫描转换时,如有例外处理则可启用注入通道组的转换。可以模糊的将注入组的转换理解为AD转换的中断一样,规则通道组的转换是普通转换,然而注入组的转换条件满足的情况下,注入组的转换会打断规则组的转换。如果规则转换已经在运行,为了在注入转换后确保同步,所有的ADC(主和从)的规则转换被停止,并在注入转换结束时同步恢复。规则转换和注入转换均有外部触发选项,规则通道转换期间有DMA请求产生,而注入转换则无DMA
[单片机]
小广播
设计资源 培训 开发板 精华推荐

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

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

换一换 更多 相关热搜器件
随便看看

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

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