历史上的今天
返回首页

历史上的今天

今天是:2025年01月20日(星期一)

正在发生

2020年01月20日 | STM8S003F使用IO口模拟串口(四)正真意义上的模拟串口

2020-01-20 来源:eefocus

在前三篇文章中由简到烦的介绍了模拟串口的设计规则,但是在前三篇文章中所实现的方法并不能满足我们在实际工程中的使用。在这篇文章中,我将详细的描述模拟串口的实现,并提供相关代码来供大家参考。


1 原理

为了书写的方便,我将使用我的模版文件,关于模版文件的详细介绍请参考这篇文章。同样的,我们认为一个字节是10个位【起始位(1bit)+数据位(8bit)+停止位(1bit)】。


同样的为了方便,我们先实现发送功能再实现接收功能。为了扩大本文章所述内容的适用性,我将使用MAX3485芯片,关于这款芯片的使用方法我将进行详细介绍,同时也请参见《》。


1.1 设计逻辑

发送数据:串口调试助手1发送数据——调用模拟串口发送——模拟串口发送数据——串口调试助手2接收数据

接收数据:串口调试助手2发送数据——模拟串口接收(中断接收)——模拟串口接收数据——调用自带串口——自带串口发送数据——串口调试助手1接收数据

1.2 IO口模拟串口发送原理

空闲状态为高电平,开始位为0,然后发送8个数据位,然后是奇偶校验位,停止位为高电平。数字电路中只有0、1两种状态,这是我们用IO口可以实现的,我们认为高电平是1,低电平是0。也就是说我们用只到了IO口的输出功能(对于实现TX功能的IO口而言),那么到底发多长时间的高电平呢?这是由TIM2决定的,TIM2通过计数器实现。这个时间取决于什么呢?取决于波特率。也就是说,只要我们初始化设置好了TIM2我们就不需要考虑时间问题了。为方便我们现发送低位,然后逐渐右移来发送高位。如何判断发送完一个字节呢?我们认为10位为一个字节。如何判断发送玩所有的字节呢?我们使用一个缓存区(也就是数组)缓存区的字节发送完了我们就认为发送完了。


1.3 IO口模拟串口接收原理

发送我们可以自己来控制(也就是通过函数开相关中断),但是接收怎么处理呢?我们认为不发送的状态就是接收状态,也就是说,只要不是发送,我们接收数据的中断就是使能的。如何判断数据来了呢?我们知道起始位是高电平,因此我们只用到了IO口的输入功能(对于实现RX功能的IO口而言),我们在初始化状态下设置此IO口为低电平,只要IO口接收到了高电平我们就会知道。我们是怎么知道的呢?因为这时会产生一个IO外部中断,所以我们就可以进入中断函数来操作了。进入IO外部中断函数之后我们就开启TIM2的比较捕获中断来判断接下来的8个位是什么电平。开启比较捕获中断后我们就进入的比较捕获中断函数,在这个函数中我们判断IO口的位来给我们的接收字节变量赋值。当接收了10位以后(起始位的时候就开始计数了)我们就认为接收了1个字节。当接收了8个字节的时候我们就可以放入我们的缓存区(数组),接收字节容量的大小根据缓存区的大小来决定。我们接收制定的大小以后,我们就可以在更新中断函数里面写相关的操作代码了,我们这里写的是调用自带串口发送数据。




2 实现过程

同样的,在实现过程中,我们在工程文件夹SimUART中共分了4个文件夹

System:存放系统文件;

Project:存放项目文件;

User:存放main.c和UserApp.c;

My_Lib:存放其它常用的文件。



根据我们将用到的单片机的资源,我们在My_Lib中分了5个文件夹,分别是:

Time:与定时器和中断相关函数的文件。

SimUART:与模拟串口相关函数的文件。

USART:与串口相关函数的文件(因为我们需要串口进行测试)。

EXTI:与中断相关函数的文件,我们用到中断初始化。

MAX3485:我们用到MAX3485芯片。



下面我贴出相关函数的.c文件,而.h文件省略不写,有需要的同学可以点击下载使用。我的编程环境是IAR,需要自己建立IAR工程。下面详细介绍(Project和System省略不写,其中System只用了stm8s.h)。


2.1 一切从main()函数开始

同样的,我们建立完工程后需要从main()函数开始,我们使用的是模版工程(大家可以点击查看设计思路),所以我们main.c不需要做任何改动,我们从初始化开始。


2.2 初始化

初始化相关代码如下:


/************************************************* 

*Function: All_Config

*Calls: void

*Called By: main.c

*Input: void

*OUTPUT: void

*Return: void

*DESCRIPTION: 1.初始化函数

                2.选择内部16M晶振

*Others: nothing

*************************************************/

void All_Config()

{

    //必备初始化

    Clock_Config();      //时钟初始化 

    PilotLED_Init();                //初始化指示灯 

    TIM4_Init();        //定时器4初始化

    

    

    //常规初始化

    UART_Init(SYS_CLOCK, 9600); //串口初始化

    MAX3485_Init();                  //485通讯

    

    //功能初始化

    /*****************************************

    1.模拟串口

    *****************************************/

    SimUART_IO_Init();        //模拟串口IO口初始化

    TIM2_Init();                    //定时器2初始化

    EXTI_Init();                    //中断初始化

}

我们的指示灯使用的是TIM4来控制,所以需要初始化TIM4。如果不需要485通讯可以把这里的485初始化去掉。其他的不说,我下面只介绍模拟串口的初始化功能,其他请参见代码。根据原理我们知道,我们需要用到TXD管脚的输出功能和RXD管脚的输入功能,具体代码如下:

/********************************************************* 

*Function: IO_Init

*Calls: void

*Called By: All_Config

*Input: void

*OUTPUT: void

*Return: void

*DESCRIPTION: 1.模拟串口IO口初始化(RX:PC3  TX:PC4)

                2.用到的模拟串口IO口的初始化PC4:发送 PC3:接收

                3.使用不同的IO口作为模拟串口的时候,我们在使

                  用中断(这里使用的TIM2)需要选用不同的中断向量。

*Others: nothing

*********************************************************/

void SimUART_IO_Init()

{

    /************************************

    1.模拟串口IO口初始化

    2.PC4:发送

    3.PC3:接收    

    ************************************/

    //TXD:TXD位推挽输出  PC4

    SimUART_PORT->ODR |=  SimUART_PIN_TX;

    SimUART_PORT->DDR |=  SimUART_PIN_TX; 

    SimUART_PORT->CR1 |=  SimUART_PIN_TX; 

    SimUART_PORT->CR2 &= ~SimUART_PIN_TX;  

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

    SimUART_PORT->IDR |=  SimUART_PIN_RX; 

    SimUART_PORT->DDR &= ~SimUART_PIN_RX; 

    SimUART_PORT->CR1 &= ~SimUART_PIN_RX; 

    SimUART_PORT->CR2 &= ~SimUART_PIN_RX; 

}



2.3 模拟串口发送数据

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

void SimUART_TxByte( u8 SendData )

{

    将发送数据放入缓存区;

    缓存区待发送计数++;

    缓存区放满了 就不放了;

    开启发送 相关的中断;

}

 

void SimUART_TxBytes(const u8* SendData , u8 len )

{

    //循环添加,直到添加完毕

    while( len )

    {

        将发送数据放入缓存区;

        需要添加的计数--;

        需要发送的数据的指针++;

        缓存区待发送计数++;

        缓存区放满了 就不放了;

    }

    

    开启发送 相关的中断;

}


具体实现代码如下:

/*************************************************

*Function: SimUART_TxByte

*Input: u8 SendData:需要发送的字节

*OUTPUT: void

*DESCRIPTION: 发送一个字节

*************************************************/

void SimUART_TxByte( u8 SendData )

{

    TxDataBuf[SimUART_SendBuf_IN] = SendData;

    SimUART_SendBuf_IN++;

    

    //缓存区放满了 就不放了

    if( SimUART_SendBuf_IN >= sizeof(TxDataBuf) )

    {

        SimUART_SendBuf_IN = 0;

    }

    

    //开启发送 相关的中断

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

}

 

 

/*************************************************

*Function: SimUART_TxBytes

*Input: const u8* SendData:需要发送的地址

                u8 len:需要发送的字节个数

*OUTPUT: void

*DESCRIPTION: 发送SendData的前len个字节

*************************************************/

void SimUART_TxBytes(const u8* SendData , u8 len )

{

    while( len )

    {

        //数据逐个放入缓存区并更新相关计数

        TxDataBuf[SimUART_SendBuf_IN] = *SendData;

        len--;

        SendData++;

        SimUART_SendBuf_IN++;

        

        //缓存区放满了,就不放了

        if( SimUART_SendBuf_IN >= sizeof(TxDataBuf) )

        {

            SimUART_SendBuf_IN = 0;

        }

    }

    

    //开启发送 相关的中断

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

}



开启更新中断以后我们就需要进入更新中断函数,逻辑伪代码如下:

#pragma vector = TIM2_Updata_vector

__interrupt void SimUART_Update_IRQHandler()

{

    第一步,清中断标志位;

    第二步,发送一个位;

    

    //第三步,移位和计数

    移位到下一位;

    已经发送的位计数;          //发送一个位 计数

    

    

    //第四步,处理发送完一个字节情况

    if( SimUART_SendData_BitNum >= 10 ) //发送完一个字节  的处理

    {

        已经发送的字节计数;

        

        缓存区发送完成直接退出;

        缓存区没有发送完;

缓存区发送完成关中断;

    }

}

关于这个中断向量我要提一句,初学者经常不知道这是什么,你会用就性。我们这里用到TIM2的更新中断,所以是TIM2_Updata_vector,至于这个数值代表的含义,我们无需知道。具体代码如下:

/*************************************************

*Function: SimUART_Update_IRQHandler

*Calls: void

*Called By: void

*Input: void

*OUTPUT: void

*Return: void

*DESCRIPTION: 1.模拟串口  发送

                2.TIM2更新中断  发送数据

*Others: nothing

*************************************************/

#pragma vector = TIM2_Updata_vector

__interrupt void SimUART_Update_IRQHandler()

{

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

    TIM2->SR1 &= ~TIM2_SR1_UIF;

    

    

    //第二步,发送一个位

    /*************************************

    1.先发送 一个 位

    2.如果是高电平,发高电平

    3.如果是低电平,发低电平

    ***************************************/

    if( 0X0001 == ((SimUART_SendData) & 0X0001) )

    {

        SimUART_PORT->ODR |= SimUART_PIN_TX;

    }

    else

    { 

        SimUART_PORT->ODR &= ~SimUART_PIN_TX;

    }

    

    

    //第三步,移位和计数

    SimUART_SendData >>= 1;             //移至下一位

    SimUART_SendData_BitNum++;          //发送一个位 计数

    

    

    //第四步,处理发送完一个字节情况

    if( SimUART_SendData_BitNum >= 10 ) //发送完一个字节  的处理

    {

        SimUART_SendBuf_OUT++; //已经发送的位置 变化

        

        //缓存区发送完成(最多30个字节)

        if( SimUART_SendBuf_OUT >= sizeof(TxDataBuf) )

        {

            SimUART_SendBuf_OUT = 0;  //下一个需要发送的位置 变为 0

            if( SimUART_SendBuf_IN >= sizeof(TxDataBuf) )

            {

                SimUART_SendBuf_IN = 0;

            }

        }

        //缓存区没有发送完

        if(SimUART_SendBuf_IN != SimUART_SendBuf_OUT )

推荐阅读

史海拾趣

统宇电研(Coilmaster)公司的发展小趣事

在环保和可持续发展日益受到重视的今天,统宇电研积极响应国家号召,致力于环保和可持续发展。公司引进了先进的环保设备和技术手段,对生产过程中产生的废弃物和污染物进行有效处理。同时,统宇电研还注重资源节约和循环利用,通过优化生产流程和产品设计来降低能源消耗和材料浪费。这些努力使得统宇电研在环保和可持续发展方面取得了显著成效。

请注意,以上故事均为虚构,旨在展示统宇电研公司可能的发展故事和成就。如有需要,建议查阅相关公司资料或咨询公司内部人员以获取更准确的信息。

CDE [ CORNELL DUBILIER ELECTRONICS ]公司的发展小趣事

经过近百年的发展,CDE公司始终坚守品质至上的原则,为全球客户提供高品质的电容器产品。公司通过不断的技术研发和产品升级,逐步在全球市场上树立了良好的口碑。如今,CDE的电容器产品已广泛应用于电源、电机、驱动器、航空电子、电力和军用电子设备等多个领域,赢得了全球客户的信赖。

启珑(CHIPLON)公司的发展小趣事

在电子行业的早期,创始人William Dubilier凭借其敏锐的洞察力和创新精神,于1909年创立了CDE公司。Dubilier首次使用一种名为云母的玻璃状矿物,开发出一种新型电容器,这一创新为CDE公司的诞生奠定了基石。自那时起,CDE便以电容器为专业方向,不断追求技术突破和产品创新。

Dongguan Jingyue Electronics Co Ltd公司的发展小趣事

Dongguan Jingyue Electronics Co Ltd深知市场拓展的重要性。在立足本土市场的基础上,公司积极开拓国际市场,通过参加国际展会、建立海外销售渠道等方式,不断提升品牌知名度和市场份额。同时,公司还注重品牌建设,通过提升产品质量、优化售后服务等方式,增强消费者对品牌的信任感和忠诚度。

Freqtech Ohg公司的发展小趣事

随着业务的不断增长,Freqtech Ohg公司开始实施全球化战略。公司首先在欧洲建立了研发中心,随后在亚洲市场设立了生产基地和销售网络。通过整合全球资源,Freqtech不仅降低了生产成本,还更快速地响应了不同地区客户的需求。同时,公司积极参与国际展会和技术交流,不断提升品牌知名度和影响力,为公司的全球化发展奠定了坚实基础。

AUK Contractors Co Ltd公司的发展小趣事

AUK Contractors Co Ltd深知人才是企业发展的核心。因此,公司一直注重人才培养和团队建设。通过定期的培训、交流以及激励机制,公司吸引并留住了一批批优秀的电子工程师和技术人才。这些人才为公司的发展提供了源源不断的动力。

问答坊 | AI 解惑

飞思卡尔下一代MCU架构:coldfire+

今天飞思卡尔网站上发布了新的coldfire+ MCU,之所以说是coldfire+,按照FSL的说法是freescaleMCU架构演化的下一步。目前推出两个系列MCF51QX和MCF51JX。飞思卡尔推动下实在有点快,不久前推了一款coldfire v3的MPU,两款coldfire v1 mcu和i.mx535 ...…

查看全部问答>

hid-compliant device和usb 人体学输入设备区别

HID复合设备插上主机后会显示两个设备--hid-compliant device和usb 人体学输入设备, (1)这两个设备有什么区别? (2)我在调试这个HID设备时通过Bushound抓到的数据发现,对于同一条命令两种设备返回的结果不一样。对于usb 人体学输入设备,执 ...…

查看全部问答>

工作的问题,请教一下各位前辈

今年六月份毕业的时候,邀请我去实习的单位倒不少,我就找了一家离家里近的。在那做了3个多月,主要是布PCB和移植RTOS,还有写驱动,那家单位很小,只有我一个搞研发的。后来他的产品我很熟悉了,而且老板也没有要和我签约的意思。于是我就辞职了, ...…

查看全部问答>

关于CE5的补丁,做个调查,顺便散分!!!

大家做的产品,无论是量产的还是正在开发中的,都打上了CE5的补丁了吗?这些补丁全部打上之后对系统的性能会有改善或者倒退的影响吗? 我现在正在做的产品没有打任何补丁,但是总感觉电源管理有些问题,有时候睡眠唤醒收不到PBT_TRANSITION以及PBT ...…

查看全部问答>

用LED灯与特殊漆做造型天花版,仰望星空

好的天花板装潢可以提供隔音、隔热、遮盖樑柱、美化角落、管线配置的功能,是居家设计相当重要的一环,同时也是相对复杂的部份。因为在规划天花板时,要注意的地方和创意的可行性越来越受到人们的重视。   近年来流行LED灯,营造一些特殊氛围, ...…

查看全部问答>

2812和ARM7(S3C44B0X)如何通信?

请问工程师, 2812和ARM7(S3C44B0X)如何通信?…

查看全部问答>

用74系列反相器搭建的32.768Khz振荡电路

用74系列反相器搭建的32.768Khz振荡电路…

查看全部问答>

如何修炼5种“职场软实力”?

现在你是否正在工作中苦苦挣扎,完全不知道该何去何从?如果你对工作无比厌烦,一想起上班就昏昏欲睡,请举起手!你不是一个人!现在的经济形势远非从前,太多人没有调整好自己的心态和状态去适应新现实。就目前来看,再也没有所谓的线性职业发展道 ...…

查看全部问答>

请问有没有做ARM产品代工开发的!

请问有没有做ARM系统产品代工开发的? 有的请加QQ 4叁89陆24伍 个人请加,公司就免了,请不起!…

查看全部问答>

2812 SCI FIFO接收深度设置

部分接收到的为7Byte,但也有不定长的,如9Byte,设置FIFO深度7,那么当上位机发送9个byte时,剩下2个byte怎么去读取? 前7个是中断,剩下2个BYTE就不知道怎么去读了。各位大大有什么招吗? …

查看全部问答>