历史上的今天

今天是:2024年11月05日(星期二)

正在发生

2021年11月05日 | STM32F103ZET6 — USART

发布者:SereneNature7 来源: eefocus关键字:STM32F103ZET6  USART  串口通信 手机看文章 扫描二维码
随时随地手机看文章

串口通信介绍

UART串口通信,使用三线即可进行最基本的数据收发传送:

在数据线上的 Timing 遵循标准的串口通信协议,由起始位,数据,校验位,停止位组成,数据传输 LSB -> MSB:

板载 USART 资源介绍

当然,由于电平不一样,使用 RS232 标准进行串口数据传送,需要增加 MAX3232 进行电平转换,再接PC:

单板上的 T1IN 和 R1OUT 接到了 STM32 芯片的 USART1 的 TXD/RXD 管脚,故单板上使用了 USART1 来作为 RS232 和 PC 机进行数据传送:

USART 初始化配置

既然确定了使用了芯片上的 USART1,要正确使用该功能,需要进行如下配置:


1. 开启 USART1 时钟源,开启 GPIOA 组时钟源(因为使用 UASRT1之前,对 PA9/PA10 需要对管脚进行配置)


2. 复位 USART1  模块(使用之前,应当首先对该模块进行复位)


3. 配置管脚功能的 Remap


4. 配置 PA9/PA10 管脚,PA9 为 TXD,配置成为推挽输出,PA10 配置成为浮空输入

5. 配置串口的波特率(9600),数据长度(8bit),停止位(1bit),校验位(无),以及是否开启流控(无)


注意:波特率的配置,遵循一组计算公式(公式复杂),详见 STM32 的芯片手册


6. 配置 NVIC 控制器


7. 配置 USART1 的中断类型(即,工作过程中,会来些什么中断)


8. 使能 USART1 功能


void SK_UartInit(void)

{

    GPIO_InitTypeDef stGpioInit;

    USART_InitTypeDef stUsartInit;

    NVIC_InitTypeDef stNVIC;

 

    /* Step1: Open USART1 And GPIOA Clock */

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);

 

    USART_DeInit(USART1);

 

    /* Step2: Config The Pin Remap */

    // According to the hardware diagram, just use UART1 On PA9 And PA10 in default

    GPIO_PinRemapConfig(GPIO_Remap_USART1, DISABLE);

 

    /* Step3: Config RXD/TXD Mode */

    // PA9 As TXD

    stGpioInit.GPIO_Pin = GPIO_Pin_9;

    stGpioInit.GPIO_Speed = GPIO_Speed_50MHz;

    stGpioInit.GPIO_Mode = GPIO_Mode_AF_PP; 

    GPIO_Init(GPIOA, &stGpioInit);

   

    // PA10 As RXD

    stGpioInit.GPIO_Pin = GPIO_Pin_10;

    stGpioInit.GPIO_Mode = GPIO_Mode_IN_FLOATING;

    GPIO_Init(GPIOA, &stGpioInit);

 

    /* Step4: Reset USART1 before use it */

    USART_DeInit(USART1);

 

    /* Step5: Configure the UART Basic Settings */

    stUsartInit.USART_BaudRate = 9600;

    stUsartInit.USART_WordLength = USART_WordLength_8b;

    stUsartInit.USART_StopBits = USART_StopBits_1;

    stUsartInit.USART_Parity = USART_Parity_No;

    stUsartInit.USART_HardwareFlowControl = USART_HardwareFlowControl_None;

    stUsartInit.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;

 

    USART_Init(USART1, &stUsartInit);

 

    // Configure RX Interrupt

    stNVIC.NVIC_IRQChannel = USART1_IRQn;

    stNVIC.NVIC_IRQChannelPreemptionPriority= 3 ;

    stNVIC.NVIC_IRQChannelSubPriority = 3;

    stNVIC.NVIC_IRQChannelCmd = ENABLE;

    NVIC_Init(&stNVIC);

 

#ifndef USART_USE_DMA

    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);

#endif

    USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE);

    USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);

 

    // Enable Usart1

    USART_Cmd(USART1, ENABLE);

}

在配置过程中,只打开了 RX 接收中断


TX 发送数据时刻,只需 polling 是否发送完成,在进行下一次的数据发送即可。


值得注意的是,数据收发都是使用了同一个DR寄存器,不同时刻,由硬件来进行区分。


USART 数据 TX 发送(Polling)

STM32 USART 数据发送是通过往 USART 的 DR 寄存器写值完成的,DR 寄存器支持每次写 1 Byte 的数据,每次写完数据后,硬件会将 DR 寄存器的值送到移位寄存器中,将数据发送出去。软件需要 polling 硬件的 TC (Transfer Complete)标志位,待 1 Byte 数据发送完成后,再次进行下一个数据的发送:


void SK_UsartSendChar(uint8_t ch)

{

    while(USART_GetFlagStatus(USART1,USART_FLAG_TC) == RESET);

    USART_SendData(USART1, ch);

}

 

void SK_UsartSendData(uint8_t *buf, uint32_t len)

{

    uint32_t i = 0;

 

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

        SK_UsartSendChar(buf[i]);

}

 

USART 数据 RX 接收(IRQ)

对于数据接收,使用 Polling 显然不是一种好办法,用中断的方式进行数据接收,接收的数据带简单的自定义格式:帧头+长度+Data的方式:


typedef struct {

    volatile uint32_t hdr_found;

    volatile uint32_t data_len;

    volatile uint32_t data_ready;

}SK_USART_RX_CTL_t;

 

SK_USART_RX_CTL_t g_stUsartRxCtl;

 

uint8_t g_SK_UsartRxDataBuf[SK_USART_RX_BUF_LEN] = {0};

 

/*

 * Protocol: 1st. Frame start at 0x5A

 *           2nd. Second is data length

 *           3rd. Data

*/

/*

void USART1_IRQHandler(void)

{

    uint8_t rx_data = 0;

    static uint8_t cnt = 0;

    if (USART_GetITStatus(USART1, USART_IT_RXNE))

    {

        rx_data = USART_ReceiveData(USART1);

    }

    if (SK_USART_RX_FRM_HEADER == rx_data && !g_stUsartRxCtl.hdr_found)

    {

        g_stUsartRxCtl.hdr_found++;

        return;

    }

    if (g_stUsartRxCtl.hdr_found && g_stUsartRxCtl.data_len == 0)

    {

        g_stUsartRxCtl.data_len = rx_data;

        if (g_stUsartRxCtl.data_len > SK_USART_RX_BUF_LEN)

            g_stUsartRxCtl.data_len = SK_USART_RX_BUF_LEN;

        return;

    }

    if (cnt < g_stUsartRxCtl.data_len)

    {

        g_SK_UsartRxDataBuf[cnt++] = rx_data;

        if (g_stUsartRxCtl.data_len == cnt)

        {

            g_stUsartRxCtl.data_ready = 1;

            cnt = 0;

            SK_SetLedStatus(SK_LED_1, SK_LED_ON);

            delay_ms(2000);

            SK_SetLedStatus(SK_LED_1, SK_LED_OFF);

            SK_UsartSendData(g_SK_UsartRxDataBuf, g_stUsartRxCtl.data_len);

        }

    }

}

 

USART 数据 RX 接收(IRQ + DMA)

虽然可以使用 IRQ 的方式进行一个 Byte 一个 Byte 的数据接收(中断),但您不觉得这会让 CPU 累死么?看着都费劲。


好嘛,STM32 UASRT 数据接收又不带 FIFO,不过没关系,用 DMA 放飞 CPU 吧!!


STM32 的 DMA1 支持 7个通道:

如上图所示:Ch4 用作 USART1_TX,Ch5 用作 USART1_RX。让 CPU 在歇一会。uploading.4e448015.gif转存失败重新上传取消


USART DMA 配置


DMA 指的是 (Direct Memory Access)直接内存存取,不经过 CPU。STM32 的 DMA 支持外设到内存,内存到外设,以及内存到内存。只要咱们告诉 DMA 控制器,从什么地方去取外设数据,数据有多少,数据宽度是多少,以及将数据放置到内存的什么地方,它便可以带你飞。


当然,DMA 也支持中断的配置,能够配置成为数据传送一半的时候来中断,or,数据传送完来中断,从各方面解决了您的烦恼。让您无忧无虑进行数据传送。


回到正题上来,配置 USART DMA要有入下几个步骤:


1. 开启 DMA1 时钟(这不废话么)


2. 配置 NVIC,并使能(也是废话)


3. 复位 DMA1 的 Ch4/Ch5


4. 设定外设地址为 USART1 的 DR 寄存器,即数据寄存器


5. 设置接收数据的内存地址(本地的一个缓存 RX BUF指针)


6. 设置数据方向为 USART1 的 DR 寄存器 ----> 内存


7. 设置 DMA 传输的数据大小(最大 65536)


8. 设置关闭外设地址自动增加


9. 设置启用缓存 BUF 地址自动增加 (数据传来后,自动存在本地 RX BUF 并指针递增)


10. 配置外设传输数据宽度为 8bit (USART DR 寄存器就 8bit)


11. 配置本地缓存数据宽度为 8bit


12. 配置 DMA 传输模式为 one shot(即传输完一次后,就停止了,也可以配置成为循环模式)


13. 设置 DMA CH15 的优先级


14. 关闭 memory to memory(废话,使用的是外设到内存的数据传输)


15. 开启 DMA1 CH15


16. 使能数据传输完成的 DMA1 中断


17. 在 USART 寄存器中,开启 RX DMA 请求(此项是在 USART 寄存器中进行配置)


好啦,此刻配置基本完成:


void SK_UsartDmaInit(void)

{

    DMA_InitTypeDef stDMA_InitStructCh4;

    DMA_InitTypeDef stDMA_InitStructCh5;

 

    /// Step 1 : Open the DMA1 Clock

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

 

    // NVIC Config

    DMA_NVIC_Config();

 

    /// Step 2 : Reset DMA1_CH4(For USART1 TX) and DMA1_CH5(For USART1 RX)

    DMA_DeInit(DMA1_Channel4);

 

    // Configure the USART1 TX DMA Transfer for DMA CH4

    // Configure the Peripheral address

    stDMA_InitStructCh4.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;

    // Configure the TX Data Buffer address

    stDMA_InitStructCh4.DMA_MemoryBaseAddr     = (uint32_t)g_SK_UsartTxDataBuf;

    // Configure the data direct: Memory to Peripheral

    stDMA_InitStructCh4.DMA_DIR                = DMA_DIR_PeripheralDST;

    // Configure the data Len

    stDMA_InitStructCh4.DMA_BufferSize         = SK_USART_TX_BUF_LEN;

    // Configure the Peripheral Address auto add (disable)

    stDMA_InitStructCh4.DMA_PeripheralInc      = DMA_PeripheralInc_Disable;

    // Configure the Memory Address auto add (enable)

    stDMA_InitStructCh4.DMA_MemoryInc          = DMA_MemoryInc_Enable;

    // Configure the Peripheral Data Size = 1 byte

    stDMA_InitStructCh4.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;

    // Configure the Memory Data Size = 1 byte

    stDMA_InitStructCh4.DMA_MemoryDataSize     = DMA_MemoryDataSize_Byte;

    // Configure Normal mode

    stDMA_InitStructCh4.DMA_Mode               = DMA_Mode_Normal;

    // Configure Priority as medium

    stDMA_InitStructCh4.DMA_Priority           = DMA_Priority_Medium;

    // Disable memory to memory

    stDMA_InitStructCh4.DMA_M2M                = DMA_M2M_Disable;

    // Config DMA1 CH4

    DMA_Init(DMA1_Channel4, &stDMA_InitStructCh4);

    // Enable Ch4

    DMA_Cmd(DMA1_Channel4, ENABLE);

    // Enable IRQ when transfer finished

    DMA_ITConfig(DMA1_Channel4, DMA_IT_TC, ENABLE);

 

    /// Step 3: Reset DMA1_CH5(For USART1 RX)

    DMA_DeInit(DMA1_Channel5);

    // Configure the USART1 RX DMA for DMA CH5

    // Configure the Peripheral address

    stDMA_InitStructCh5.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;

    // Configure the TX Data Buffer address

    stDMA_InitStructCh5.DMA_MemoryBaseAddr     = (uint32_t)g_SK_UsartRxDataBuf;

    // Configure the data direct: Memory to Peripheral

    stDMA_InitStructCh5.DMA_DIR                = DMA_DIR_PeripheralSRC;

    // Configure the data Len

    stDMA_InitStructCh5.DMA_BufferSize         = SK_USART_RX_BUF_LEN;

    // Configure the Peripheral Address auto add (disable)

    stDMA_InitStructCh5.DMA_PeripheralInc      = DMA_PeripheralInc_Disable;

[1] [2]
关键字:STM32F103ZET6  USART  串口通信 引用地址:STM32F103ZET6 — USART

上一篇:STM32F103ZET6 启动模式
下一篇:STM32F103ZET6 — ADC

推荐阅读

在半导体领域,日本厂商一贯以来都是以深厚的基础积累,前瞻的技术研究在世界上闻名。为了深入了解他们对未来半导体技术的规划和看法,半导体行业观察记者日前前往日本千叶县国际会展中心参加“CEATEC JAPAN 2018”。通过对稻盛和夫先生创办的京瓷集团的产品布局的了解,我们大体看到了日本企业关注的新趋势。 汽车电子是重中之重 最近几年,随着智能汽...
1.引言地面伽玛能谱测量是利用便携式伽玛能谱仪直接在现场测定土壤、岩石中钾、铀、钍含量的一种核地球物理方法。在解决地球科学、环境科学等领域具有重要作用[4].地面伽玛能谱仪的校准通常采用饱和模型法,目前,仪器校准系数的计算方法大多采用离线工计算,工作繁琐,易出错。为此,笔者针对地面伽玛能谱仪校准的校准系数、灵敏度系数、准确度的计算特点...
今天,realme副总裁徐起宣布,realme X7 Pro尝鲜Android 11,这是首款联发科天玑1000+手机适配Android 11系统。  据悉,伴随着Android 11底层的升级,realme X7 Pro也更新至realme UI 2.0版本。  在2.0版本上,realme UI支持图标字体编辑、个性化息屏显示等,用户可以高度自由定制自己喜欢的息屏界面,支持手绘创作、个性签名、图...
上市公司的业绩客观地反映了市场行情,2021年受芯片等原材料紧缺影响,手机概念股业绩低于预期,作为手机中的核心部件,摄像头市场景气度是否如传言一般呢?截至10月29日,各大光学上市公司们陆续披露了第三季度报,在这一份报告中意味着它们前三季度的业绩均已出炉。据笔者粗略统计,目前除港股舜宇光学、丘钛科技、瑞声科技及台股大立光外,同兴达、联创...

史海拾趣

问答坊 | AI 解惑

常用电阻标称值

设计电路时计算出来的电阻值经常会与电阻的标称值不相符,有时候需要根据标称值来修正电路的计算。下面列出了常用的5%和1%精度电阻的标称值,供大家设计时参考。??精度为5%的碳膜电阻,以欧姆为单位的标称值:1.0 5.6 33 160 820 3.9K 20K 100K ...…

查看全部问答∨

Protel资料总结

protel学习资料,希望对大家有帮助……

查看全部问答∨

求高手帮忙看下程序哪里有问题?基于arm3000

此为俄罗斯方块,运行无错误 但是文本输出没有显示,方块在落下后,立即消失,无法呈现俄罗斯方块应有的游戏效果 …

查看全部问答∨

串口接收不定长字符处理问题

单片机接收不定的长度.我想PC机到一次就当一次转行.不管长度多少.或者我发送一次协议. 也当一次命令.…

查看全部问答∨

问ioctl各个参数怎么对应的?

模块的ioctl段的定义函数是: int camif_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) {         camif_cfg_t *cfg = file->private_data;         struct ...…

查看全部问答∨

wince PB安装问题

wincepb50-051231-product-update-rollup-armv4i wincepb50-061231-product-update-rollup-armv4i wincepb50-071231-product-update-rollup-armv4i 这3个补丁依次包含吗? 是不是打07的以后就不用打上面2个了…

查看全部问答∨

用EVC5.0做绘制实时数据曲线(每秒采集一次数据),为何不能自动刷新?

用EVC5.0做绘制实时数据曲线(每秒采集一次数据),运行时为何不能自动刷新? 而在VC下编译、运行却没有任何问题。…

查看全部问答∨

请教一个VHDL问题!

请问一下有没有正在使用VHDL语言的吗?我想请教一下问题,用VHDL编写一个程序后,再运行,会生成一个项目符号 , 在图形输入文件中如何才能调用它啊? 有谁知道的给指点一下,谢谢!!…

查看全部问答∨

如何使用OLED,学生写的,还比较详细哦!

从一个初学者的角度写得,没一条都很清楚。。。   …

查看全部问答∨

第9章 进程关系

9.1   引言 1809.2   终端登录 1809.2.1   4.3+BSD终端登录 1809.2.2   SVR4终端登录 1829.3   网络登录 1829.3.1   4.3+BSD网络登录 1829.3.2 & ...…

查看全部问答∨
小广播
设计资源 培训 开发板 精华推荐

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

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

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

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

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