stm32 固件库之:uart

1. 简介:

自己写此部分的程序即耗时,也不明智,因此,此篇文章是关于移植和分析的文章


2. 移植 uart 程序:

        参照的程序是 红牛开发板/例程-Example/二、基础例程/ 下的有关 usart 部分

在做uart部分之前,需要参照之前章程搭建固件库和FreeRTOS工程,搭建完成之后,将上边目录下的 driver 中的 driver 中的delay.c、delay.h、USART.c、USART.h拷贝到 PROJ\uart 目录下,部分截图如下:

在iar软件中,将新添加的两个 .c 文件放到usr目录下:

在包含进 .c 后还需要添加他们相应的 .h 文件的搜索路径:...PROJ\uart\driver

在程序中我们用到了 printf ,这个函数需要 Full 版本library 的支持:

主函数 main.c

#include <stdio.h>

#include "misc.h"

/* Scheduler includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "USART.h"
#include "delay.h"

#define LED_D1_ON() GPIO_ResetBits(GPIOF, GPIO_Pin_6)
#define LED_D1_OFF() GPIO_SetBits(GPIOF, GPIO_Pin_6)

static void LED_Init(void);
static void LED_D1_Task(void *pvParameters);
static void UART1_Task(void *pvParameters);

int main(void)
{
    LED_Init(); // 初始化 LED 引脚
    USART1_Init();
    Delay_Init();

    Delay_ms(10);
    
    SendMessage();
    printf("write by lip!\n\r");
        
    xTaskCreate(LED_D1_Task, "LED_D1", 1000, NULL, 3, NULL);
    xTaskCreate(UART1_Task, "UART1", 1000, NULL, tskIDLE_PRIORITY + 3, NULL);
    
    /* 启动调度器 */
    vTaskStartScheduler();
    while(1);

    return 0;
}

void LED_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOF, ENABLE); // 使能 GPIOF 的时钟	
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
    GPIO_Init(GPIOF, &GPIO_InitStructure);
}

void LED_D1_Task(void *pvParameters)
{
    while ( 1 )
    {
        LED_D1_ON();
        vTaskDelay(500 / portTICK_RATE_MS);
        LED_D1_OFF();
        vTaskDelay(500 / portTICK_RATE_MS);        
    }
}

void UART1_Task(void *pvParameters)
{
    while(1)
    {
        vTaskDelay(100 / portTICK_RATE_MS);

        if(data_length)
        {
            USART1_SendString(rx_buffer, data_length);   
            data_length = 0;
        }
    }
}

我们还需要在 usr 目录下 stm32f10x_it.c 中实现一下 usart 的中断入口,以便支持用户的输入:

在 void NMI_Handler(void) 函数上边添加:

void USART1_IRQHandler(void)
{
    u8 res;	    
    if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) // 接收到数据
    {	 	
        res = USART_ReceiveData(USART1); // 读取接收到的数据USART1->DR
        if(data_length < DATA_BUF_SIZE)
        {
            rx_buffer[data_length] = res; // 记录接收到的值
            data_length++;
        }
    }
}

当然 .h 中的声明不要忘记:



至此,可以 make、下载了,用串口助手连接上串口一,在开机的瞬间会看到打印一串的数据,并等待用户输入,当用户输入时,会原封不动的打印出来。



【1】为了尊重源作者,打印信息并没有大范围改动


3. 分析固件库中的 delay 延时:

        delay 函数的实现应用的是 m3 核的 systick,是一个硬件部分,一个 24 位的倒计数定时器,当倒计时到0值时会从LOAD寄存器装载新值,还有一点的是当enable不清零的情况,他的运作就不会停息

涉及到固件库中的结构体如下:

#define __IO volatile
typedef struct
{
  __IO uint32_t CTRL;  /* Offset: 0x00  SysTick Control and Status Register */
  __IO uint32_t LOAD;  /* Offset: 0x04  SysTick Reload Value Register       */
  __IO uint32_t VAL;   /* Offset: 0x08  SysTick Current Value Register      */
  __I  uint32_t CALIB; /* Offset: 0x0C  SysTick Calibration Register        */
} SysTick_Type;
用于将首地址强制转化成此结构体,方便用户使用

SysTick寄存器说明如下:

【1】控制和状态寄存器 CTRL :

位段

名称

类型

描述

16

COUNTFLAG

只读

计数到0时置1,读取该位将清0

2

CLKSOURCE

可读可写

时钟来源 0=HCLK/8,1=HCLK

1

TICKINT

可读可写

1=计数到0时产生SysTick异常请求

0

ENABLE

可读可写

定时器的开关,1使能

 

【2】重装在寄存器 LOAD :

位段

名称

类型

描述

23:0

RELOAD

可读可写

当计数到0时将被重装载的值 


【3】当前值寄存器:

位段

名称

类型

描述

23:0

CURRENT

可读可写

读取时返回当前倒计数的值;向该寄存器 写入任意值都可以将其清除变为0。

清0  该寄存器还会导致CTRL寄存器的 COUNTFLAG 位清零


【4】校准寄存器:没见用到


关键函数:(初始化部分)

static	uint8_t  sysclk = 72; //默认系统时钟为72MHz
static  uint8_t  fac_us = 0;  //us延时倍乘数
static  uint16_t fac_ms = 0;  //ms延时倍乘数

/**
  * @brief  基准延时初始化,使用SysTick;
  *         SysTick时钟源由HCLK/8提供,当系统频率为72MHz时
  *         最小计数周期为1/9MHz,计满9次为1us,fac_us = 9作为单位
  * @param  None
  * @retval None
  */
void Delay_Init(void)
{
    SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //选择系统定时器的时钟源
    fac_us = sysclk / 8;
    fac_ms = (u16)fac_us * 1000;
}

(毫秒部分)

/**
  * @brief  毫秒级延时,SysTick->LOAD为24位寄存器,
  *         所以最大延时小于或等于1864ms
  * @param  nms(ms number) 毫秒数 不能超过1864
  * @retval None
  */
void Delay_ms(u16 nms)
{
    u32 temp;
    SysTick->LOAD = (u32)nms*fac_ms; //时间加载(SysTick->LOAD为24bit)
    SysTick->VAL  = 0x00;            //清空计数器
    SysTick->CTRL = 0x01 ;           //开始倒数
    do
    {
        temp = SysTick->CTRL;
    }
    while(temp&0x01 && !(temp&(1<<16))); //等待时间到达
    SysTick->CTRL = 0x00;                 //关闭计数器
    SysTick->VAL  = 0x00;                 //清空计数器
}

(微妙部分)

/**
  * @brief  实现微秒级延时,最大延时1864ms
  *         这两个函数是通过寄存器配置的,没有对应的固件库函数,参考misc.h
  * @param  us(number us) 延时微秒数 最大1864135
  * @retval None
  */
void Delay_us(u32 nus)
{
    u32 temp;
    SysTick->LOAD = nus * fac_us; //时间加载 fac_us=9,走9次就是1us
    SysTick->VAL  = 0x00;         //清空计数器
    SysTick->CTRL = 0x01;         //开始倒数
    do
    {
        temp = SysTick->CTRL;
    }
    while(temp&0x01 && !(temp&(1<<16))); //等待时间到达
    SysTick->CTRL = 0x00;                 //关闭计数器
    SysTick->VAL  = 0x00;                 //清空计数器
}

此部分比较简单,不再分析。


4. 分析 printf  的实现:

        根据 Contex-M3 权威指南中的 《使用UART输出“Hello World”》 章节,更改了开发版提供的源代码,源代码存在以下问题:

a. 不同的串口工具,输出结果不同:经测试 secureCRT、sscom

b. 每次使用printf输出,字符串中必须在字符串的前后都加上 \n\r

#ifdef __GNUC__
/* With GCC/RAISONANCE, small printf (option LD Linker->Libraries->Small printf
   set to 'Yes') calls __io_putchar() */
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */
/*******************************************************************************
* Function Name  : 文本输出的tetargeting代码
* Description    : 把USART输出定向到printf,modified by lip
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
PUTCHAR_PROTOTYPE
{
    if ( ch == '\n')
    {
        while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
        USART_SendData(USART1, '\r'); // 输入附加的CR以使字符串被正常显示
        USART_SendData(USART1, '\n');
    }
    else
    {
        while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET); // 等待发送完成
        USART_SendData(USART1, (u8)ch);
    }

    return ch;
}
【1】要想实现 printf输出到串口上,我们重新实现了 fputc 函数

【2】在 main.c 函数中需要包含 stdio.h 头文件

【3】在工程中还需要配置如下:

【4】我们这里之重新定向了输出,输入并没有实现,因此 scanf 不能使用

【5】\r 的意思是回车,就是将时候的行的位置放在行首,\n 的意思是换行,就是重新开始新的一行


5. usart 的库函数:

【1】我们依旧从初始化开始:

/**
  * @brief  USART1初始化配置 包括GPIO初始化 TX必须配置为复用输出
  * @param  None
  * @retval None
  */
void USART1_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    RCC_APB2PeriphClockCmd( RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA  ,  ENABLE );

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    USART_InitStructure.USART_BaudRate            = 115200  ;
    USART_InitStructure.USART_WordLength          = USART_WordLength_8b;           //8个数据位
    USART_InitStructure.USART_StopBits            = USART_StopBits_1;              //1个停止位
    USART_InitStructure.USART_Parity              = USART_Parity_No ;              //无奇偶校验
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//禁用硬件流控制
    USART_InitStructure.USART_Mode                = USART_Mode_Rx | USART_Mode_Tx; //使能发送/接收
    USART_Init(USART1, &USART_InitStructure);
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);                                  //使能接收中断
    USART_Cmd(USART1, ENABLE);

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);	  //中断分组2
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;	  //USART1接收中断
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //先占优先级0
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;        //次占优先级
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

}
【2】开启时钟:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);

打开 UART1 的时钟,和相应的引脚的时钟

【3】USART 结构体 USART_InitTypeDef

/** 
  * @brief  USART Init Structure definition  
  */ 
  
typedef struct
{
  uint32_t USART_BaudRate;            /* 波特率 */
  uint16_t USART_WordLength;          /* 指定接收和发送的数据的位宽 */
  uint16_t USART_StopBits;            /* 停止位 */
  uint16_t USART_Parity;              /* 奇偶校验 */
  uint16_t USART_Mode;                /* uart的模式,可以是接收或者发送 */
  uint16_t USART_HardwareFlowControl; /* 硬件流控,一般不需要 */
} USART_InitTypeDef;
其中:

USART_BaudRate 可以取值:直接是数字,如果是 115200 ,直接写这个值就行

USART_WordLength 可以取值:USART_WordLength_8b、USART_WordLength_9b

USART_StopBits 可以取值:USART_StopBits_1、USART_StopBits_0_5、USART_StopBits_2、USART_StopBits_1_5

USART_Parity 可以取值:USART_Parity_No(没有奇偶校验位)、USART_Parity_Even(偶校验)、USART_Parity_Odd(奇校验)

USART_Mode 可以取值:USART_Mode_Rx、USART_Mode_Tx

USART_HardwareFlowControl 可以取值:USART_HardwareFlowControl_None(没有硬件流控,一般是这个,其他的不再列出)


USART_Init 函数:

功能是将 USART_InitTypeDef  结构体,相应的值赋值给寄存器中相应的位

USART_ITConfig() 函数:
功能是打开/关闭串口的中断

函数原型:

/**
  * @brief  打开或者关闭指定的 USART 中断.
  * @param  USARTx: Select the USART or the UART peripheral. 
  *   这个可选值如下:
  *   USART1, USART2, USART3, UART4 or UART5.
  * @param  USART_IT: 指定将被打开或关闭的 USART 的中断源.
  *   This parameter can be one of the following values:
  *     @arg USART_IT_CTS:  CTS change interrupt (not available for UART4 and UART5)
  *     @arg USART_IT_LBD:  LIN Break detection interrupt
  *     @arg USART_IT_TXE:  Transmit Data Register empty interrupt
  *     @arg USART_IT_TC:   Transmission complete interrupt
  *     @arg USART_IT_RXNE: Receive Data register not empty interrupt
  *     @arg USART_IT_IDLE: Idle line detection interrupt
  *     @arg USART_IT_PE:   Parity Error interrupt
  *     @arg USART_IT_ERR:  Error interrupt(Frame error, noise error, overrun error)
  * @param  NewState: new state of the specified USARTx interrupts.
  *   This parameter can be: ENABLE or DISABLE.
  * @retval None
  */
void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState)

USART_Cmd() 函数:

功能是打开/关闭串口

/**
  * @brief  打开或关闭 USART 外设.
  * @param  USARTx: Select the USART or the UART peripheral. 
  *         This parameter can be one of the following values:
  *           USART1, USART2, USART3, UART4 or UART5.
  * @param  NewState: new state of the USARTx peripheral.
  *         This parameter can be: ENABLE or DISABLE.
  * @retval None
  */
void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState)


6. 代码的大小

经过将所有的调试信息去掉后,输出的目标文件的大小如下:(设置方法看前边的章节:点击这里

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值