STM8、STM8S003F3P6 双串口通信(IO模拟串口)

news2024/11/20 7:26:28

背景


这里为什么要写串口通信,因为实际项目上使用了串口,STM8S003F3P6的串口简单啊,不值得一提。本文写的串口确实简单,因为这里我想先从简单的写起来,慢慢的把难的引出来。这里呢,做个提纲说明,本文涉及的串口,是使用STM8S003F3P6片上的IO模拟串口。由于STM8S003F3P6资源有限,双机通信资源时常不够,下篇文章提出用IO模拟串口的方式进行数据收发。

IO模拟串口还是有一定的难度的,调试起来非常消耗时间,我记得这里我调试了一个多星期。需要对串口时序的理解比较深刻,才可以调试,如果对串口的时序还不清楚,那看代码会一头雾水。

原理图

 

 如上图这里是STM8S003F3P6的串口

当然这里标题说的串口的双机通信,也可以是指别的单片机或者直接对应电脑的串口

有人会说,哎,你这里么有把串口TTL电平转换为RS232或者其他电平。

是的,这里确实没有,这里可以通过上图中的CN2连接 TTL转USB的小模块,接入电脑或者笔记本进行调试。

软件设计

系统主频设置,采用内部时钟16M,即HSI 1分频

这里必须提一下时钟,因为,串口的波特率有个延时,延时时间是和主频有很密切的关联


/************************************************
函数名称 : CLK_Configuration
功    能 : 时钟配置
参    数 : 无
返 回 值 : 无
作    者 : 
*************************************************/
void CLK_Configuration(void)
{
/*
  ErrorStatus clk_return_status;
  CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV8); //HSI = 16M (8分频)=2MHZ
  
  //切换内部低速时钟128khz
  clk_return_status = CLK_ClockSwitchConfig(CLK_SWITCHMODE_AUTO, CLK_SOURCE_LSI, DISABLE, CLK_CURRENTCLOCKSTATE_DISABLE);
  if (clk_return_status == SUCCESS)  //SUCCESS or ERROR
  {
                              
    CLK_ClockSwitchCmd(ENABLE);
    CLK_LSICmd(ENABLE);
    CLK_ClockSwitchCmd(DISABLE);                              
  }*/
 // CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1); //HSI = 16M (1分频)
  //ErrorStatus clk_return_status;
  
    CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1); //HSI = 16M (8分频)=2MHZ
 /* 
  //切换内部低速时钟8M
  clk_return_status = CLK_ClockSwitchConfig(CLK_SWITCHMODE_AUTO, CLK_SOURCE_HSE, DISABLE, CLK_CURRENTCLOCKSTATE_DISABLE);
  if (clk_return_status == SUCCESS)  //SUCCESS or ERROR
  {
                              
    CLK_ClockSwitchCmd(ENABLE);
    CLK_HSECmd(ENABLE);
    CLK_ClockSwitchCmd(DISABLE);                              
  }*/
    
    CLK_DeInit();//设置为默认值
    CLK_HSICmd(ENABLE);//启用HSI
    CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1);//HSI分频
    CLK_SYSCLKConfig(CLK_PRESCALER_CPUDIV1);//CPU分频

}

串口初始化配置

这里配置,如原理图所示

PC4为串口的发送管脚,配置为输出

PC5为串口的接收管脚,配置为输入中断

void IO_UART_init(void)
{
  
   vm_UART_RX_O = 0;
   vm_UART_RX_P = 0;
   memset(vm_UART_RX_BUF, 0 ,sizeof(vm_UART_RX_BUF));

    //VM_UART_TXD_PORT_IN;//发送初始化
    
   //外部中断
    ///EXTI_CR1 &=~(3<<4);//清零///这里源程序针对PC管脚配置外部中断,需要修改PB管脚即可
    ///EXTI_CR1 |=2<<4;//下降沿触发
   
    VM_UART_TXD_PORT_OUT;
    
    VM_UART_RXD_PORT_INT_IN;//接收初始化
    
    EXTI_CR1 &=~(3<<4);//清零//这里需要修改为PC管脚
    //EXTI_CR1 |=3<<2;//上升沿和下降沿触发
    EXTI_CR1 |=2<<4;//下降沿触发
 
  
}

涉及的宏定义如下


#define PC_ODR (GPIOC->ODR)
#define PC_IDR (GPIOC->IDR)
#define PC_DDR (GPIOC->DDR)
#define PC_CR1 (GPIOC->CR1)
#define PC_CR2 (GPIOC->CR2)

#define PB_ODR (GPIOC->ODR)
#define PB_IDR (GPIOC->IDR)
#define PB_DDR (GPIOC->DDR)
#define PB_CR1 (GPIOC->CR1)
#define PB_CR2 (GPIOC->CR2)

#define EXTI_CR1 (EXTI->CR1)

#define TIM2_CR1 (TIM2->CR1)
#define TIM2_IER (TIM2->IER)
#define TIM2_SR1 (TIM2->SR1)
#define TIM2_SR2 (TIM2->SR2)
#define TIM2_EGR (TIM2->EGR)
#define TIM2_CCMR1 (TIM2->CCMR1)
#define TIM2_CCMR2 (TIM2->CCMR2)
#define TIM2_CCMR3 (TIM2->CCMR3)
#define TIM2_CCER1 (TIM2->CCER1)
#define TIM2_CCER2 (TIM2->CCER2)
#define TIM2_CNTRH (TIM2->CNTRH)
#define TIM2_CNTRL (TIM2->CNTRL)
#define TIM2_PSCR (TIM2->PSCR)
#define TIM2_ARRH (TIM2->ARRH)
#define TIM2_ARRL (TIM2->ARRL)
#define TIM2_CCR1H (TIM2->CCR1H)
#define TIM2_CCR1L (TIM2->CCR1L)
#define TIM2_CCR2H (TIM2->CCR2H)
#define TIM2_CCR2L (TIM2->CCR2L)
#define TIM2_CCR3H (TIM2->CCR3H)
#define TIM2_CCR3L (TIM2->CCR3L)

#define TIM1_CR1    (TIM1->CR1)
#define TIM1_CR2    (TIM1->CR2)
#define TIM1_SMCR   (TIM1->SMCR)
#define TIM1_ETR    (TIM1->ETR)
#define TIM1_IER    (TIM1->IER)
#define TIM1_SR1   (TIM1->SR1)
#define TIM1_SR2   (TIM1->SR2)
#define TIM1_EGR   (TIM1->EGR)
#define TIM1_CCMR1   (TIM1->CCMR1)
#define TIM1_CCMR2   (TIM1->CCMR2)
#define TIM1_CCMR3   (TIM1->CCMR3)
#define TIM1_CCMR4   (TIM1->CCMR4)
#define TIM1_CCER1   (TIM1->CCER1)
#define TIM1_CCER2   (TIM1->CCER2)
#define TIM1_CNTRH   (TIM1->CNTRH)
#define TIM1_CNTRL   (TIM1->CNTRL)
#define TIM1_PSCRH   (TIM1->PSCRH)
#define TIM1_PSCRL   (TIM1->PSCRL)
#define TIM1_ARRH   (TIM1->ARRH)
#define TIM1_ARRL   (TIM1->ARRL)
#define TIM1_RCR    (TIM1->RCR)
#define TIM1_CCR1H   (TIM1->CCR1H)
#define TIM1_CCR1L   (TIM1->CCR1L)
#define TIM1_CCR2H   (TIM1->CCR2H)
#define TIM1_CCR2L   (TIM1->CCR2L)
#define TIM1_CCR3H   (TIM1->CCR3H)
#define TIM1_CCR3L   (TIM1->CCR3L)
#define TIM1_CCR4H   (TIM1->CCR4H)
#define TIM1_CCR4L   (TIM1->CCR4L)
#define TIM1_BKR   (TIM1->BKR)
#define TIM1_DTR   (TIM1->DTR)
#define TIM1_OISR   (TIM1->OISR)



#define TIM4_CR1  (TIM4->CR1)
#define TIM4_IER  (TIM4->IER)
#define TIM4_SR1  (TIM4->SR1)
#define TIM4_EGR  (TIM4->EGR)
#define TIM4_CNTR (TIM4->CNTR)
#define TIM4_PSCR (TIM4->PSCR)
#define TIM4_ARR  (TIM4->ARR)


#define	TIM1_timer 1667  //接收定时器
#define	TIM2_timer 1667 //发送定时器
//#define	TIM1_timer 139  //接收定时器
//#define	TIM2_timer 139 //发送定时器
#define vm_UART_RX_BUF_L 32//接收缓存长度


#define	TIM2_START         TIM2_CNTRL = 0;TIM2_CNTRH = 0;TIM2_CR1|= 1<<0 //计数器置零,启动定时器2
#define	TIM2_STOP          TIM2_CR1 &= (~1<<0) //停止定时器


#define	TIM1_START         TIM1_CNTRL = 0;TIM1_CNTRH = 0;TIM1_CR1 |= 1<<0 //计数器置零,启动定时器1
#define	TIM1_STOP          TIM1_CR1 &= ~(1<<0) //停止定时器


#define	VM_UART_TXD_PORT_WriteHigh   PC_ODR|= 1<<4  //高电平
#define	VM_UART_TXD_PORT_WriteLow    PC_ODR &=~(1<<4) //低电平


#define	VM_UART_TXD_PORT_OUT       PC_DDR|=1<<4 ;PC_CR1|=(1<<4 );PC_CR2 &=~(1<<4)//设定为输出
#define	VM_UART_TXD_PORT_IN        PC_DDR&=~(1<<4 );PC_CR1|=(1<<4 );PC_CR2&=~(1<<4 )//设定为输入


#define	VM_UART_RXD_PORT_IN        PB_DDR&=~(1<<5);PB_CR1|=(1<<5);PB_CR2&=~(1<<5) //设置只上拉输入 不中断
#define	VM_UART_RXD_PORT_INT_IN    PB_DDR&=~(1<<5);PB_CR1|=(1<<5);PB_CR2|=(1<<5) //设置只上拉输入 中断


采用定时器4为串口的接收扫描函数

总的来说,串口的发送比较简单,只需要将PC4管脚,按照串口的时序拉高拉低即可

但是串口接收就比较麻烦

这里首先解释一下接收思路

就是开启一个定时器不停的扫描串口接收管脚PC5,然后将扫描的数据进行提取,获取最终的接收数据。

 /* TIM4 configuration:
   - TIM4CLK is set to 16 MHz, the TIM4 Prescaler is equal to 128 so the TIM1 counter
   clock used is 16 MHz / 128 = 125 000 Hz
  - With 125 000 Hz we can generate time base:
      max time base is 2.048 ms if TIM4_PERIOD = 255 --> (255 + 1) / 125000 = 2.048 ms
      min time base is 0.016 ms if TIM4_PERIOD = 1   --> (  1 + 1) / 125000 = 0.016 ms
  - In this example we need to generate a time base equal to 1 ms
   so TIM4_PERIOD = (0.001 * 125000 - 1) = 124 */
void Tim4_Init(void)
{
  TIM4_DeInit();
  //TIM4_TimeBaseInit(TIM4_PRESCALER_32, 52);
  ///TIM4_TimeBaseInit(TIM4_PRESCALER_8, 208);
  TIM4_TimeBaseInit(TIM4_PRESCALER_8, 208);
  ///TIM4_TimeBaseInit(TIM4_PRESCALER_16, 21);//提升5倍采样率,没有用
  //TIM4_TimeBaseInit(TIM4_PRESCALER_32, 208);
  TIM4_ClearFlag(TIM4_FLAG_UPDATE);              //清除标志位
  TIM4_ARRPreloadConfig(ENABLE);

  TIM4_ITConfig(TIM4_IT_UPDATE, ENABLE);         //使能更新UPDATE中断
  /* Enable TIM4 */
  TIM4_Cmd(DISABLE);
}

串口接收数据实现

首先串口接收管脚捕获到中断,启动定时器4

如下图所示,所有接收故事就由此开始

这里首先将PC5管脚配置为输入模式,清空一些变量,启动定时器4

/**
  * @brief External Interrupt PORTC Interrupt routine.
  * @param  None
  * @retval None
  */
INTERRUPT_HANDLER(EXTI_PORTC_IRQHandler, 5)
{
  /* In order to detect unexpected events during development,
     it is recommended to set a breakpoint on the following instruction.
  */
  //外中断一次收一个字节,只识别起始位
  if((PB_IDR &0x20) == 0)
  {
    VM_UART_RXD_PORT_IN; //只上拉输入 不中断
    vm_uart_rx_flag = 1;//开启发送
    vm_UART_RX_byte = 0;

    vm_UART_RX_bit = 0;
    //TIM1_START;//启动定时器
    TIM4_CNTR = 0;//清零计数器
    TIM4_CR1|= 1<<0;//启动定时器
  }
}

定时器4的中断函数函数如下

定时器4的中断函数就是不停的扫描PC5管脚,从中获取串口数据,这里就需要对串口时序有一定的认识

/**
  * @brief Timer4 Update/Overflow Interrupt routine.
  * @param  None
  * @retval None
  */

 INTERRUPT_HANDLER(TIM4_UPD_OVF_IRQHandler, 23)
 {
  /* In order to detect unexpected events during development,
     it is recommended to set a breakpoint on the following instruction.
  */
   TIM4_SR1&=~(1<<0);//清空标志位 
   
    if((PB_IDR &0x20) == 0)
    {
        vm_UART_RX_byte /= 2;
    }
    else
    {
        vm_UART_RX_byte /= 2;
        vm_UART_RX_byte |= 0X80;
    }
    vm_UART_RX_bit++;
    if(vm_UART_RX_bit >= 8)
    {
        //GPIO_WriteReverse(GPIOB, GPIO_PIN_4);//高取反,低取高,必须配置为输出
        ///(uint8_t)PC_ODR_ODR4>0?0:1;//高取反,低取高,必须配置为输出
        //TIM4_STOP;//停止定时器
        TIM4_CR1 &= ~(1<<0);   
        VM_UART_RXD_PORT_INT_IN;
        vm_uart_rx_flag = 0;
        vm_UART_RX_bit = 0;//
        //if(((vm_UART_RX_P + 1) & (vm_UART_RX_BUF_L - 1)) != vm_UART_RX_O)
        {
          vm_UART_RX_BUF[vm_UART_RX_P] = vm_UART_RX_byte; //一个字节时接收完毕
          vm_UART_RX_P++;
          vm_UART_RX_P &= (vm_UART_RX_BUF_L - 1);
        }
    } 
 }

串口发送函数

根据波形做对应延时,拉出来对应的时序即可


//发送一个字节
//以stm8中9600bit/s的波特率计算的过程为例(1秒钟传输9600位)。
//可以计算出传输1位所需要的时间 T1 = 1/9600 约为104us。
void WriteByte( unsigned char sdata ) //波特率9600
{
   //如果数据误码率比较高,可以修改delay_us延时时间
    unsigned char i;
    unsigned char value = 0;
    
    VM_UART_TXD_PORT_OUT;
    //发送起始位
    Send_0();
    delay_us( DELAY_CNT );
    //发送数据位
    for( i = 0; i < 8; i++ )
    {
        value = ( sdata & 0x01 ); //先传低位
        if( value )
        {
            Send_1();
        }
        else
        {
            Send_0();
        }
        delay_us( DELAY_CNT );        //经测试延时100us 数据没有误差 示波器上观察波形时间为104us左右
        sdata = sdata >> 1;
    }
    //停止位
    Send_1();
    delay_us( DELAY_CNT ); 
    
    VM_UART_TXD_PORT_WriteHigh;
    ///VM_UART_TXD_PORT_IN;
      
    
}
//发送字符串
void WriteString( unsigned char *s )
{
    while( *s != 0 )
    {
        WriteByte( *s );
      //vm_UARTsend_byte(*s);
        s++;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/568809.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

VB一款实现图像浏览的ActiveX控件

利用GDI实现浏览图片的ActiveX控件&#xff0c;功能:支持读取PNG格式的图片&#xff0c;支持鼠标飞轮对图片进行缩放&#xff0c;镜像&#xff0c;移动等功能&#xff0c;其中用到了功能强大的GDI&#xff0c;GDI&#xff0c;对初及vb编程爱好者运用GDI-API有很大的研究价值&am…

超级简单的SSM框架(全注解,源码+分析,看一眼就会)

1.什么是SSM&#xff1f; SSM是Spring、SpringMVC、Mybatis的框架整合。 2.什么是Spring? Spring是一个轻量级的控制反转&#xff08;IoC&#xff09;和面向切面&#xff08;AOP&#xff09;的容器框架。 优点&#xff1a; 1.通过Spring的IOC特性&#xff0c;将对象之间的…

基于关系抽取的相似度计算

文章目录 一、面向冶金设备运维履历的知识图谱构建与语义相似性度量研究二、KG中的实体相似度计算研究研究假设研究方法第一步&#xff1a;特征生成第二步&#xff1a;模型选择 三、基于司法案例知识图谱的类案推荐个人解惑 一、面向冶金设备运维履历的知识图谱构建与语义相似性…

叮咚买菜业绩大幅低于预期,2023年前景堪忧

来源l&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 叮咚买菜2023年第一季度业绩低于预期 叮咚买菜&#xff08;DDL&#xff09;于5月12日公布了2023年第一季度财报。 财报显示&#xff0c;叮咚买菜的收入同比下降了-8%&#xff0c;从2022年第一季度的54.44亿元人民币下滑…

搭建Stm32遇到的问题汇总

问题一&#xff1a;右侧watch窗口无法显示数据动态变化 现象&#xff1a;在main循环中加入累加的数值都不变化 现象2&#xff1a;单步执行卡在如下函数 原因&#xff1a;一般是因为没有用微库进入半主机模式 解决措施&#xff1a; 措施一&#xff1a;打开勾选下面的。 措施二…

【HTTP】

目录 &#x1f3a8;1. HTTP 请求 &#x1f3f0;1.1 首行 &#x1f451;1.2 URL &#x1f6a9;1.3 Content-Type 和 Content-Length &#x1f34a;1.4 User-Agent &#x1f33d;1.5 Referer ⚽1.6 Cookie &#x1f369;2. HTTP 响应 &#x1f31e;2.1 HTTP 响应 首行…

软件工程学习1:软件项目项目管理

假设你被指派作为一个软件公司的项目负责人&#xff0c;任务是开发一个管理系统&#xff0c;人员规模大约为8人&#xff0c;人员角色应如何确定&#xff1f;应如何进行项目管理&#xff1f;&#xff08;从软件项目管理估算、软件规模、工作量估算、进度计划安排、软件过程模型等…

入行4年,跳槽2次,我好像摸透了软件测试这一行!

很多测试人在行业中摸爬滚打了很多年&#xff0c;时不时给身边新入职的伙伴们一些好的建议&#xff0c;对一部分刚入职起步的测试小白来说&#xff0c;这些建议都是让你少走弯路的捷径&#xff0c;废话不多说&#xff0c;让我们来了解一下&#xff0c;一位入行4年跳槽2次的老测…

JSONException: illegal identifier : \pos 1 异常报错问题

JSONException: illegal identifier : \pos 1 异常报错问题 1.常见情况&#xff1a;1.1 JSON 字符串格式不正确1.2 JSON 字符串中包含了非法字符1.3 解析 JSON 字符串的方式不正确 2.解决办法&#xff1a;2.1 工具类2.2 StringEscapeUtils.unescapeJava3. JSONObject.parseObje…

黑客入门教程从零基础入门到精通,看完这一篇就够了

学前感言: 1.这是一条坚持的道路,三分钟的热情可以放弃往下看了. 2.多练多想,不要离开了教程什么都不会了.最好看完教程自己独立完成技术方面的开发. 3.有时多google,baidu,我们往往都遇不到好心的大神,谁会无聊天天给你做解答. 4.遇到实在搞不懂的,可以先放放,以后再来解决…

Revit建模|怎么创建轴网标高?

大家好&#xff0c;这里是建模助手&#xff0c;今天给大家讲一讲怎么创建轴网标高。 标高用来定义楼层层高以及生成平面视图&#xff0c;轴网用于为构件定位&#xff0c;在Revit中轴网确定了一个不可见的工作平面&#xff0c;轴网编号以及标高符号样式均可定制修改。目前&…

每日练题---C语言

目录 前言&#xff1a; 一.求最小公倍数 1.1公式法 1.2遍历法 1.3乘除法 二.倒置字符串 前言&#xff1a; 今日份题目有&#xff1a;求两个整数的最小公倍数&#xff0c;求倒置字符串&#xff0c;。 一.求最小公倍数 牛客网链接&#xff1a;OJ链接 百度词条&#xff1a;…

Python自动化测试框架怎么搭建?完整框架源码给到你

目录 前言 搭建过程&#xff1a; 一阶段&#xff0c; 二阶段&#xff0c; 三阶段&#xff0c; 四阶段 下面具体的说一下搭建过程 一阶段&#xff1a; 二阶段&#xff1a; 三阶段&#xff1a; 四阶段 前言 背景&#xff1a;公司需要每一个项目组都搭建自己的一套自动…

Vue企业级项目开发思路,附带源码

项目的技术栈展示 以及项目的核心重点部分 项目搭建使用element实现首页布局 顶部导航菜单及与左侧导航联动的面包屑实现 封装一个ECharts组件 封装一个Form表单组件和Table表格组件 企业开发之权限管理思路讲解 项目搭建使用element实现首页布局 顶部导航菜单及与左侧导…

技术分享 | OB 慢查询排查思路

本文汇总了项目实践中前辈的经验和笔者的理解&#xff0c;旨在帮助初学 OceanBase&#xff08;以下简称 OB&#xff09;的工程师&#xff0c;快速解决 SQL 执行缓慢等性能问题。当遇到性能问题时&#xff0c;很多工程师可能会感到无从下手&#xff0c;本文将根据关键日志提供多…

14_Uboot图形化配置

目录 U-Boot图形化配置体验 make menuconfig过程分析 Kconfig语法简介 Mainmenu menu/endmenu条目 config条目 depends on和select choice/endchoice Menuconfig Comment Source 添加自定义菜单 U-Boot图形化配置体验 uboot或Linux内核可以通过输入"make menu…

计算机组成原理-存储系统-外部存储虚拟存储器

目录 一、外部存储 1.1磁盘组成 1.2性能指标 1.3磁盘地址 1.4硬盘的工作原理 1.5磁盘阵列 二、 固态硬盘SSD 三、虚拟存储器(存储系统详细知识点) 3.1 页式存储器 逻辑地址-》主存(物理)地址 加入块表(TLB)的转换过程 3.2 段式存储器 3.3 段页式存储器 一、外部存储 又称…

一个非系统工程师所关心的——Android开机流程

一、Loader层 1. Boot ROM: 上电后&#xff0c;BootRom会被激活&#xff0c;引导芯片代码开始从预定义的地方&#xff08;固化在ROM&#xff09;开始执行&#xff0c;然后加载引导程序到RAM。 2. Boot Loader引导程序 Android是基于Linux系统的&#xff0c;它没有BIO…

路由器端口映射-原理+图解

文章目录 1. 前言2. 内部服务器3. 内网IP3.1 含义3.2 查询内网IP方法3.3 直观法判断内网IP 4. 内部端口5. 外部端口6. 远程桌面连接7. 端口映射原理图8. 欢迎纠正~ 1. 前言 端口映射就是可将N台主机的内网IP地址映射成一个公网IP地址&#xff0c;从而让外网可以访问到局域网内…

Linux 学习笔记(九):基于 TSS 的进程切换

一、TSS 基本概念 1、什么是 TSS &#xff1f; TSS&#xff08;Task State Segment&#xff09;即任务状态段。具体的说&#xff0c;在设计 “Intel 架构”&#xff08;即 x86 系统结构&#xff09;时&#xff0c;每个任务&#xff08;进程or线程&#xff09;都对应有一个独立…