历史上的今天
返回首页

历史上的今天

今天是:2025年01月21日(星期二)

正在发生

2020年01月21日 | STM8S003F使用IO口模拟串口(三)使用中断方式发送和接收数据

2020-01-21 来源:eefocus

在前两篇文章中我们介绍了IO口模拟串口发送数据和接收数据,前两种方法都是使用定时器来进行发送和接收,没有用到中断,优点是逻辑简单,但是缺点很明显,只能进行单个字节的发送和接收,而且不能同时工作。因此在实际工程中没有什么作用,仅供学习使用。使用中断方式我们可以发送和接收多个字节的数据。


1、使用中断方式进行IO口模拟串口发送和接收数据的原理

这篇文章我将使用中断的方式进行发送和接收,同样的,由于原理缺陷,这篇文章介绍的方法无法同时接收和发送,而且由于发送会延时,是一个不太好的方法,仅供学习使用。

注意:这篇文章实现的IO口模拟串口无法同时接收和发送数据!如有需要在实际项目中使用IO口模拟串口工作,请移步:


1.1、发送数据的原理

我们使用定时器更新中断来进行数据的发送,首先在发送函数中开启中断,然后在中断函数中逐位发送,直到发送10位(一个字节,我们暂时没有使用校验位)后关闭更新中断。

1.2、接收数据的原理

我们使用单片机的外部中断(IO中断)来开启比较中断,在比较中断中逐位接收,直到接收了10位后关闭比较中断,并保存接收的有效字节个数。


2、实现过程

同样的,在实现过程中,我们在工程文件夹SimUART中共分了4个文件夹(分别为System:存放系统文件;Project:存放项目文件;User:存放main.c和UserApp.c;My_Lib:存放其它常用的文件)。根据我们将用到的单片机的资源,我们在My_Lib中分了二个文件夹,分别是——IO:存放与IO口相关函数的文件;Time:与定时器和中断相关函数的文件。下面我贴出相关函数的.c文件,而.h文件省略不写,有需要的同学可以根据文章后面的网址下载使用。我的编程环境是IAR,需要自己建立IAR工程。下面详细介绍(Project和System省略不写,其中System只用了stm8s.h)。


2.1、一切从main()函数开始

同样的,我们建立完工程后需要从main()函数开始,为便于理解,我将使用逻辑伪代码,逻辑伪代码如下:


int main( void )

{

单片机时钟初始化;

IO口初始化;

定时器初始化;

中断初始化;

while(1)

{

if( 需要发送的数据数 > 0 )

{

发送;

需要发送的数据数 = 0;

}

}

}


我们首先需要进行初始化的配置All_Config()【在UserApp.c中】,代码如下:


//head file 

#include "UserApp.h"

#include "IO.h"

#include "User.h"

#include "Time.h"

#include "Delay.h"

 

u16 SimUART_SendData = 0xFF;

u8 SimUART_SendData_BitNum = 10;

u8  RxData_ValidNum = 0;

u16 RxDataValue_Temp = 0x0000;

 

//初始化函数

void All_Config( void )

{

    Clock_Config();

    IO_Init();  

    TIM2_Init();

    EXTI_Init();

}


其中User.h是我将自己常用的宏写在了一个文件里面,对应于main.c。在没有接外部时钟的时候,STM8S003F在启动时主时钟默认为HSI RC时钟的8分频,我们这里的初始化仅指定为16MHZ高速内部RC振荡器(HSI),也可以省略不写,Clock_Config()【在UserApp.c中】函数代码如下:


//初始化时钟 选择内部16M晶振

void Clock_Config()

{

    CLK->CKDIVR &= ~( BIT(4) | BIT(3) );

}


我选择单片机的PD2作为我的模拟串口的数据发送口,选择PD3作为我的模拟串口的数据接收口,IO_Init()【在IO.c中】函数代码如下:

//head file

#include "IO.h"

#include "User.h"

 

void IO_Init()

{

    //TXD:TXD位推挽输出  PD2

    SimUART_PORT->ODR |=  SimUART_PIN_TX; //0000 0100

    SimUART_PORT->DDR |=  SimUART_PIN_TX; //0000 0100

    SimUART_PORT->CR1 |=  SimUART_PIN_TX; //0000 0100  

    SimUART_PORT->CR2 &= ~SimUART_PIN_TX; //0000 0100

    

    //RXD:悬浮输入 高电平 PD3

    SimUART_PORT->IDR |=  SimUART_PIN_RX; //0000 1000

    SimUART_PORT->DDR &= ~SimUART_PIN_RX; //0000 1000

    SimUART_PORT->CR1 &= ~SimUART_PIN_RX; //0000 1000

    SimUART_PORT->CR2 &= ~SimUART_PIN_RX; //0000 1000

}

其中在IO.h中的宏定义为:

//宏定义

#define SimUART_PORT GPIOD

#define SimUART_PIN_TX 0X04   //PD2

#define SimUART_PIN_RX 0X08   //PD3

#define SimUART_PIN_RX_0 0X00 //PD3

#define SimUART_PIN_RX_1 0X08 //PD3

定时器的初始化和前面一样,具体操作可以见这里。代码如下:

void TIM2_Init()

{

    CLK->PCKENR1 |= CLK_PCKENR1_TIM2; //使能 TIM2 

    TIM2->PSCR    = 0x04;                //16分频 1MHZ 1us

    TIM2->ARRH    = ARRValue_9600 >> 8;  //自动装载 每52us复位一次TIM2

    TIM2->ARRL    = ARRValue_9600;        //每1us递减1

    TIM2->CNTRH = 0;              //定时器清零

    TIM2->CNTRL = 0; 

  

    TIM2->CR1 |= TIM2_CR1_CEN;    //开启定时器

}

其中Time.h中的宏定义为:

#define ARRValue_9600   104

中断初始化EXTI_Init()【在UserApp.c中】代码如下:

//初始化中断

void Interrupt_Init()

{

    //允许更新中断

    //TIM2->CR1 &= ~TIM2_CR1_UDIS;      //允许更新  可以不管默认为0

    TIM2->IER |= TIM2_IER_UIE;          //更新中断使能

 

    //IO口下降中断 初始化

    SimUART_PORT->CR2 |= 0x08;          //使能外部中断

    EXTI->CR1 = 0x80;                   //仅下降沿触发    

 

    //禁止比较中断

    TIM2->IER &= ~TIM2_IER_CC1IE;       //禁止捕获/比较1

}

根据上面的原理,我们知道:更新中断是在发送函数中打开的,因此更新初始化中使能;IO中断是通过下降沿(串口数据的起始位为低电平)打开的,因此设置成使能和下降沿触发;比较中断实在IO中断中打开的,因此设置成禁止。


2.2、模拟串口发送数据

完成时钟、IO口、定时器、中断的初始化以后我们就可以开始主体程序的设计了,逻辑伪代码如下:


//发送 函数

void SimUART_SendByte(u8 SendData)

{

等待一个字节发送完毕;

第一步:清除 更新更新中断标志位(保证不进入更新中断);

第二步:数据调整(起始位为0,数据位不变,停止位和其它位为1);

第三步: 开启更新中断;

}

 

//定时器更新中断  发送接收到的数据

#pragma vector = 对应向量标志位

__interrupt void SimUART_Update_IRQHandler(void)

{

第一步:清除 更新中断标志位(保证不进入更新中断);

发送一个位计数;

if( 发送位为1 )

{

发送高电平;

}

esle

{

发送低电平;

}

移位,发送下一个位;

//完成了一个位的发送

if( 发送了10个位 )

{

关闭中断;

}

}


进入发送函数,首先应该清除更新中断标志位,然后写功能代码,结束前需要打开更新中断,从而去执行更新中断的代码。我们需要考虑为何需要一个延时来等待一个字节完成发送。在中断函数中我们是让一个字节发送完成以后才关闭中断的,如果不延时,可能发生一个字节还没有发送完成,却进入下一个更新中断的情况,因此需要等待,我们直接用一个标志位就能解决。对应向量标志位通过查芯片手册和头文件可以得到。发送函数在UserApp.c中,更新中断在Time.c中,发送部分代码如下:


void SimUART_SendByte(u8 SendData)

{   

    while( SimUART_SendData_BitNum < 10 );

    

    //清 更新中断标志位

    TIM2->SR1 &= ~TIM2_SR1_UIF;

    

    //0000 0000 0000 0000 保证最低位(起始)为0,除数据位后全部为1

    SimUART_SendData = ( ( SendData << 1 )| (0xFE00) );

    SimUART_SendData_BitNum = 0;

    

    //开启更新中断

    TIM2-> IER |= TIM2_IER_UIE;    

}


//定时器更新中断  发送接收到的数据

#pragma vector = TIM2_Updata_vector

__interrupt void SimUART_Update_IRQHandler(void)

{

    //第一步,清中断标志位

    TIM2->SR1 &= ~TIM2_SR1_UIF;

    

    //发送一个位 计数

    SimUART_SendData_BitNum++;

    

    if( ((SimUART_SendData) & 0X0001) )  //如果是高电平,发高电平

    {

        SimUART_PORT->ODR |= SimUART_PIN_TX;

    }

    else                                        //如果是低电平,发低电平

    { 

        SimUART_PORT->ODR &= ~SimUART_PIN_TX;

    }

    

    //发送一个位 

    SimUART_SendData >>= 1;

        

    if( 10 <= SimUART_SendData_BitNum )

    {      

        TIM2-> IER &= ~TIM2_IER_UIE;

    }

}


2.3、模拟串口接收数据

接收字节也是一个字节一个字节的接收的,由于单片机的始终可能存在误差(这是不可避免的),因此我们需要像个方法来除去这个误差,我们可以增大采样速度(减少采样时间)来排除,比如:我采集5次(发送数据波特率为9600,采集就要是这个的5倍速度),如果有三个是高电平我们就认为该位是高电平。另一种方法是,我们让采样点始终在一个电平的中间,这就用到了IO中断来开启比较中断。具体实现的逻辑伪代码如下:


//接收数据

//判断一个数据的开始  IO外部中断

#pragma vector = 对应向量标志位

__interrupt void SimUART_IO_IRQHandler(void)

{

关闭 IO中断 ;

设置 比较中断 ;

清除 比较中断 中断标志位;

打开 比较中断 ;

}

 

//接收数据  比较中断

#pragma vector = 对应向量标志位

__interrupt void SimUART_Capture_IRQHandler(void)

{

清除 比较中断 标志位;

接收位个数计数;

if( 接收位为1 )

{

将相应的位置1;

}

if( 接收了10个位 )

{

接收位个数清零;

关 比较中断;

清除 IO中断标志位;

开 IO中断;

接收到的有效字节个数计数;

}

}

两个中断函数均在Time.c中,具体实现代码如下:


//判断一个数据的开始  IO外部中断

#pragma vector = EXTI3_PD_vector

__interrupt void SimUART_IO_IRQHandler(void)

{

    //关闭IO中断

    SimUART_PORT->CR2 &= ~0x08;

    

    //设置比较中断

    TIM2-> CCMR1 &= 0x00;       //还是有问题

    

    //TIM2-> CCR1H = 0;

    TIM2-> CCR1L = TIM2->CNTRL + ( ARRValue_9600/2 );

    

    RxDataValue = 0x0000;

    //TIM2->CCER1 |= 0x00;

    TIM2->SR1 &= ~TIM2_SR1_CC1IF;  //清中断标志位    

    TIM2->IER |=  TIM2_IER_CC1IE;  //使能 捕获/比较中断1  

}


//接收数据  比较中断

#pragma vector = TIM2_Capture_vector

__interrupt void SimUART_Capture_IRQHandler(void)

{

    //第一步,清中断标志位(防止始终进入中断)

    TIM2->SR1 &= ~TIM2_SR1_CC1IF;  //清中断标志位

    

    RxDataNum++;

    if( SimUART_PIN_RX_1 == (SimUART_PORT->IDR & SimUART_PIN_RX_1 ) )//其次,接收10个位

    {

        RxDataValue |= ( 0x01 << (RxDataNum) );//该位 置1       

    }

    

    //第二步,读IO输入     

    //首先,判断是否接收了10个位             

    if(10 == RxDataNum)                 //如果是则

    {

        RxDataNum = 0;

        TIM2->IER &=  ~TIM2_IER_CC1IE;    //1、关 比较中断

      

        //STM8S没有外中断标志位,STM8L有标志位,因此暂时不需要清中断标志位

        SimUART_PORT->CR2 |= 0x08;        //2、开 IO中断

        

        //处理有效数据

        if(  (RxDataValue & 0x0402) == 0x0400 )

        {

            RxDataValue_Temp = ( RxDataValue >> 2 );

            RxData_ValidNum++;//接收字有效 节数

        }

    }

}

2.4、补全main()函数

int main( void )

{

    All_Config();       //初始化

    _asm("rim");        //开总中断

    

    while(1)           //发送循环

    {

        if( RxData_ValidNum > 0x00 )

        {

            SimUART_SendByte( RxDataValue_Temp );

            RxData_ValidNum = 0x00;

        }       

    }

    //return 0;

}


3、结束语

至此我们使用中断的方法来进行IO口模拟串口(未使用库函数)收发数据的功能已经实现,在本文章中,为了方便,我使用我的发送数据来验证我接收数据的正确性,因此先写的发送数据,再写的接收数据。正如前面所说,我是一个位一个位的发送和接受,在发送过程中有发送延时,这样的后果是如果是一个字符串的收发是没问题的,但是由于没有使用缓存区(即一个数组),导致我们收发数据不能分布于各个任务中,代码在实际项目中可能会出现一些问题,例如已接受就得发送,否则会出现错误,这回影响单片机在执行任务时产生问题。我将在后面进行介绍我们在实际工程中能够使用的全双工串口程序。值得注意的是,收发应该是单独存在的,我这里是为了方便反而让我的发送程序发送接收到的数据。

推荐阅读

史海拾趣

Acme Electric Corporation公司的发展小趣事

Acme Electric Corporation是一家专注于生产变压器和电源设备的公司。以下是该公司发展的五个相关故事:

  1. 公司成立和初期发展: Acme Electric Corporation成立于1958年,总部位于美国威斯康星州。公司最初是一家小规模的家庭企业,致力于生产各种类型的变压器和电源设备,主要用于工业、商业和农业应用。

  2. 技术创新和产品扩展: 随着市场需求的增长和技术进步,Acme Electric Corporation不断进行技术创新,并扩展了产品线。公司推出了一系列高性能、高效率的变压器和电源设备,包括控制变压器、隔离变压器、电源逆变器等,满足不同客户的需求。

  3. 市场拓展和客户合作: Acme Electric Corporation积极开拓国内外市场,并与各行各业的客户建立了合作关系。公司的产品被广泛应用于制造业、电力行业、建筑业等领域,为客户提供稳定可靠的电源解决方案。同时,Acme Electric Corporation与客户密切合作,根据客户的需求定制产品,提供个性化的服务。

  4. 质量控制和生产管理: Acme Electric Corporation注重产品质量和生产管理,采用先进的生产设备和严格的质量控制体系,确保产品的稳定性和可靠性。公司拥有一支专业的研发团队和生产团队,不断改进工艺流程,提升产品质量和生产效率。

  5. 未来发展展望: Acme Electric Corporation将继续致力于变压器和电源设备领域的研发和生产,不断推出更先进、更可靠的产品和解决方案,以满足客户在各个领域的需求。公司将加强国际市场拓展,提升自身在全球市场的竞争力,为行业的发展做出更大的贡献。

Apex Tool Group公司的发展小趣事

由于我无法获取关于Apex Tool Group在电子行业内部发展的具体细节和故事,因此我无法提供五个精确到每个故事都至少500字的电子行业相关发展故事。但我可以根据已知信息,尝试概括Apex Tool Group的发展概况,以及它如何可能涉及电子行业。

Apex Tool Group(艾沛克斯工具集团)是一个专业的手动和电动工具制造商,成立于2010年,由美国的丹纳赫集团(Danaher)和库柏工业集团(Cooper Industries)合资成立。公司总部位于美国马里兰州的Sparks,并在全球拥有20多家工厂,产品覆盖多个国家和地区。

Apex Tool Group的发展之路充满了变革和机遇。它凭借两大集团的技术和资源优势,迅速崛起为行业内的佼佼者。公司不断推出创新产品,满足不同客户的需求,并在市场上获得了良好的口碑。

在电子行业,Apex Tool Group的产品可能也得到了广泛应用。随着电子行业的快速发展,对高精度、高效率的工具需求日益增长。Apex Tool Group凭借其卓越的技术和品质,为电子行业提供了可靠的工具解决方案。无论是在电子产品的制造过程中,还是在电子设备的维修和维护中,Apex Tool Group的工具都发挥着重要作用。

此外,Apex Tool Group还注重与客户的紧密合作。它根据客户的需求提供定制化服务,帮助客户解决实际问题。这种以客户为中心的经营理念,使得Apex Tool Group在电子行业中赢得了广泛的认可和信任。

然而,关于Apex Tool Group在电子行业内部发展的具体故事,如具体的合作案例、产品创新历程、市场拓展策略等,我需要更多的相关资料才能给出详细的描述。如果您对这方面的信息感兴趣,建议查阅Apex Tool Group的官方网站、行业报告或相关新闻报道,以获取更详细的信息。

希望以上内容能够对您有所帮助,如有更多问题,欢迎继续提问。

Circuit Assembly公司的发展小趣事

由于Circuit Assembly公司的发展故事涉及具体的公司案例和数据,而这些信息往往涉及公司的内部运营、市场策略等敏感内容,且不同公司的发展经历也各有差异,因此我无法直接为您提供5个具体的电子行业里面Circuit Assembly公司的发展起来的相关故事。但我可以根据您提供的背景和要求,给出一些可能的发展趋势和挑战,帮助您理解该行业的整体状况。

在电子行业中,Circuit Assembly公司的发展通常与几个关键因素密切相关,包括技术创新、市场需求、供应链管理以及国际合作等。随着5G、物联网、智能制造等技术的快速发展,Circuit Assembly公司面临着前所未有的机遇和挑战。

一方面,新技术的出现推动了电子产品的小型化、集成化和智能化,对Circuit Assembly公司的工艺水平和生产能力提出了更高的要求。那些能够紧跟技术潮流,不断提升自身技术水平和创新能力的公司,往往能够在市场中脱颖而出,实现快速发展。

另一方面,随着全球市场竞争的加剧,Circuit Assembly公司需要更加注重成本控制和供应链管理。通过建立稳定可靠的供应链体系,优化生产流程和管理,降低生产成本,提高产品质量,这些公司能够更好地满足客户需求,提升市场竞争力。

此外,国际合作也是Circuit Assembly公司发展的重要推动力。通过与国内外同行企业的交流与合作,可以引进先进的技术和管理经验,共同推动行业发展。同时,通过参与国际市场竞争,也能够拓宽公司的业务领域,提高公司的国际影响力。

综上所述,电子行业中的Circuit Assembly公司面临着多方面的机遇和挑战。那些能够抓住机遇、应对挑战的公司,有望在市场中取得更大的成功。但具体的公司发展故事需要根据不同的公司背景和实际情况来具体分析和描述。建议您查阅相关行业报告、公司年报或新闻报道,以获取更具体的信息。

AEC Design公司的发展小趣事
随着公司规模的扩大和市场地位的提升,AEC Design公司开始注重品牌建设。通过精心策划的品牌推广活动、优质的客户服务以及完善的售后服务体系,公司的品牌形象逐渐深入人心。消费者对公司的信任度和忠诚度不断提高,为公司的长期发展奠定了坚实基础。
西安航天民芯公司的发展小趣事

在宇航领域,动力电池管理芯片的性能要求极高。西安航天民芯凭借其在集成电路设计领域的深厚积累,成功研发出全国第一颗宇航级动力电池管理芯片。这一突破不仅打破了国外垄断的局面,也为中国宇航事业的发展做出了重要贡献。

Good Will Instrument Co., Ltd.公司的发展小趣事

除了商业产品的研发外,西安航天民芯还积极承担国家重大科研项目。公司参与了我国工业强基高性能工业DSP芯片等多项国产化项目的研发工作,为提升国家产业核心竞争力做出了贡献。这些项目的成功实施,进一步提升了西安航天民芯在行业内的影响力和地位。

问答坊 | AI 解惑

按键专题活动总结---准备做一个按键学习的开发板

在3月份我们在论坛搞了一个按键专题活动的讨论(https://bbs.eeworld.com.cn/viewthread.php?tid=97931 ),这个活动获得了大家的广泛支持,讨论的很是热烈根据讨论内容,从大家的讨论中,我们学习和了解到了更多的按键方面的知识。 俗话说的好: ...…

查看全部问答>

2440 nboot 与eboot的调试问题

关于nboot和eboot的问题 开发板为飞凌的ok2440,采用64M nandflash(k9f1208)启动,bootloader代码为在网上下载立宇泰的pqoal的5.0bsp,用jsf2440将stepldr.nb1写到block0,eboot.bin写到block2。    目前nboot能够正常起来,通过串口 ...…

查看全部问答>

sdram,nor flash,nand flash

各位大侠,小弟刚接触嵌入式。有些基本的问题还没有搞清楚,请教一下 在一块板子上,有sdram,nor flash,nand flash 哪个是用来存储系统的, 这3个又有什么区别呢?…

查看全部问答>

请教Flash MDD PDD架构的使用

Flash MDD组件在PB里面可以选上,编译后即可生成对应的flashpart.dll和flashmdd.dll, 虽然在BSP中可以看到flashpdd相关的源代码,但pdd相关的dll却不知道该怎么生成? 请各位指教。…

查看全部问答>

wince跑程序老是报错!

C:\\Program Files\\Microsoft eMbedded C++ 4.0\\Common\\EVC\\MyProjects\\2\\2.cpp(4) : fatal error C1083: Cannot open precompiled header file: \'emulatorDbg/2.pch\': No such file or directory 随便什么程序,都是报这个错误,这是为什 ...…

查看全部问答>

RTOS的ABC讨论

学习和应用 RTOS 好多年了。对RTOS的发展和应用有一些粗浅的想法。尤其认识了RAW OS(一款新的RTOS)的作者后,就更多的想法。就写在这里,让大家拍砖吧。 我心里一直对这几个问题耿耿于怀。 1、什么行业在什么情况下应用RTOS? 2、RTOS能解决什么 ...…

查看全部问答>

beaglebone心得一:windows下驱动安装

我收到的就一个SD卡,但带了个SD大卡卡槽。并不是传说中的,两个SD卡。 我破不急等地插到USB口上,当然迷你SD卡也插上,发现硬件。我等着自动装硬件。但显示了一个虚拟的U盘。我又傻等一会儿,还是要驱动。 应是U转串的驱动。 我查型号是FT2232H ...…

查看全部问答>

圆点博士微型四轴飞行器飞行成功

圆点博士微型四轴飞行器飞行成功!!!! (视频由网友js200300953 提供)…

查看全部问答>