STM32裸机开发(8) — 在gcc环境下编写uart串口打印程序
一、构建程序目录
新建src
文件夹,用来存放c源代码
新建inc
文件夹,用来存放头文件
新建build
文件夹,用来存放编译输出文件
二、编写源文件
在src
文件夹下新建uart.c
文件如下所示
#include "uart.h"
typedef unsigned int uint32_t;
typedef struct
{
volatile uint32_t SR; /*!< USART Status register, Address offset: 0x00 */
volatile uint32_t DR; /*!< USART Data register, Address offset: 0x04 */
volatile uint32_t BRR; /*!< USART Baud rate register, Address offset: 0x08 */
volatile uint32_t CR1; /*!< USART Control register 1, Address offset: 0x0C */
volatile uint32_t CR2; /*!< USART Control register 2, Address offset: 0x10 */
volatile uint32_t CR3; /*!< USART Control register 3, Address offset: 0x14 */
volatile uint32_t GTPR; /*!< USART Guard time and prescaler register, Address offset: 0x18 */
} USART_TypeDef;
void uart_init(void)
{
USART_TypeDef *usart1 = (USART_TypeDef *)0x40013800;
volatile unsigned int *pReg;
/* 使能GPIOA/USART1模块 */
/* RCC_APB2ENR */
pReg = (volatile unsigned int *)(0x40021000 + 0x18);
*pReg |= (1<<2) | (1<<14);
/* 配置引脚功能: PA9(USART1_TX), PA10(USART1_RX)
* GPIOA_CRH = 0x40010800 + 0x04
*/
pReg = (volatile unsigned int *)(0x40010800 + 0x04);
/* PA9(USART1_TX) */
*pReg &= ~((3<<4) | (3<<6));
*pReg |= (1<<4) | (2<<6); /* Output mode, max speed 10 MHz; Alternate function output Push-pull */
/* PA10(USART1_RX) */
*pReg &= ~((3<<8) | (3<<10));
*pReg |= (0<<8) | (1<<10); /* Input mode (reset state); Floating input (reset state) */
/* 设置波特率
* 115200 = 8000000/16/USARTDIV
* USARTDIV = 4.34
* DIV_Mantissa = 4
* DIV_Fraction / 16 = 0.34
* DIV_Fraction = 16*0.34 = 5
* 真实波特率:
* DIV_Fraction / 16 = 5/16=0.3125
* USARTDIV = DIV_Mantissa + DIV_Fraction / 16 = 4.3125
* baudrate = 8000000/16/4.3125 = 115942
*/
#define DIV_Mantissa 4
#define DIV_Fraction 5
usart1->BRR = (DIV_Mantissa<<4) | (DIV_Fraction);
/* 设置数据格式: 8n1 */
usart1->CR1 = (1<<13) | (0<<12) | (0<<10) | (1<<3) | (1<<2);
usart1->CR2 &= ~(3<<12);
/* 使能USART1 */
}
int getchar(void)
{
USART_TypeDef *usart1 = (USART_TypeDef *)0x40013800;
while ((usart1->SR & (1<<5)) == 0);
return usart1->DR;
}
int putchar(char c)
{
USART_TypeDef *usart1 = (USART_TypeDef *)0x40013800;
while ((usart1->SR & (1<<7)) == 0);
usart1->DR = c;
return c;
}
在inc
文件夹下新建uart.h
内容如下
#ifndef _UART_H
#define _UART_H
void uart_init(void);
int getchar(void);
int putchar(char c);
#endif
在src文件夹下新建led.c
文件如下所示
在这里插入代码片#include "led.h"
int delay(int ndelay)
{
volatile int n = ndelay;
while(n--);
return 0;
}
void led_init(void)
{
unsigned int *pReg;
/* 1、使能GPIOB */
pReg = (unsigned int *)(0x40021000 + 0x18);
*pReg |= (1<<3);
/* 2、设置GPIOB5为输出引脚 */
pReg = (unsigned int *)(0x40010C00 + 0x00);
*pReg |= (1<<20);
pReg = (unsigned int *)(0x40010C00 + 0x0C);
*pReg &= ~(1<<5);
}
void led_on(void)
{
unsigned int *pReg = (unsigned int *)(0x40010C00 + 0x0C);
/* 设置GPIOB5输出0 */
*pReg &= ~(1<<5);
}
void led_off(void)
{
unsigned int *pReg = (unsigned int *)(0x40010C00 + 0x0C);
/* 设置GPIOB5输出1 */
*pReg |= (1<<5);
}
在inc文件夹下新建led.h
内容如下
#ifndef __LED_H
#define __LED_H
int delay(int ndelay);
void led_init(void);
void led_on(void);
void led_off(void);
#endif
三、修改主函数
将了原来的led.c
修改为main.c
如下所示
#include "uart.h"
#include "led.h"
int main(void)
{
uart_init();
led_init();
putchar('s');
putchar('t');
putchar('m');
putchar('3');
putchar('2');
putchar('f');
putchar('1');
putchar('0');
putchar('3');
putchar('\r');
putchar('\n');
while(1)
{
led_on();
delay(1000000);
led_off();
delay(1000000);
}
}
四、编写汇编程序
汇编程序也做相应修改
.syntax unified /* 指明当前汇编文件的指令是ARM和THUMB通用格式 */
.cpu cortex-m3 /* 指明cpu核为cortex-m3 */
.fpu softvfp /* 软浮点 */
.thumb /* thumb指令 */
.global _start /* .global表示Reset_Handler是一个全局符号 */
.word 0x00000000 /* 当前地址写入一个字(32bit)数据,值为0x00000000,实际上应为栈顶地址 */
.word _start+1 /* 当前地址写入一个字(32bit)数据, 值为_reset标号代表的地址+1,即程序入口地址*/
_start: /* 标签_start,汇编程序的默认入口是_start */
/* 1、设置栈 */
LDR SP, =(0x20000000+0x400)
/* 2、跳转到led函数 */
BL led
/* 3、原地循环 */
B .
五、编写Makefile
编写Makefile如下所示,注意OBJECTS为我们所要链接的所有文件,链接时默认是按照其排列顺序来连接的,所以我们要将start.o
文件放在最前面,如下所示;另外vpath
用来指定该依赖文件的查找路径,还有关于-fno-builtin
参数,表示不使用C语言的内建函数,因为我们编写的putchar()
函数是C语言的内建函数,使用了-fno-builtin
参数我们的函数名就可以和内建函数同名了。
TARGET = uart
C_SOURCES = src/main.c \
src/uart.c \
src/led.c
C_INCLUDES = -Iinc
ASM_SOURCES = start.s
CPU = -mcpu=cortex-m3
MCU = $(CPU) -mthumb
CFLAGS = -c -g $(MCU) -Wall $(C_INCLUDES) -fno-builtin
PREFIX = arm-none-eabi-
CC = $(PREFIX)gcc
AS = $(PREFIX)gcc -x assembler-with-cpp
LD=$(PREFIX)ld
CP = $(PREFIX)objcopy
SZ = $(PREFIX)size
OBJDUMP=$(PREFIX)objdump
HEX = $(CP) -O ihex
BIN = $(CP) -O binary -S
BUILD_DIR = build
OBJECTS = $(addprefix $(BUILD_DIR)/,$(notdir $(ASM_SOURCES:.s=.o)))
vpath %.s $(sort $(dir $(ASM_SOURCES)))
OBJECTS += $(addprefix $(BUILD_DIR)/,$(notdir $(C_SOURCES:.c=.o)))
vpath %.c $(sort $(dir $(C_SOURCES)))
all : $(BUILD_DIR)/$(TARGET).elf $(BUILD_DIR)/$(TARGET).hex $(BUILD_DIR)/$(TARGET).bin
$(SZ) $<
$(BUILD_DIR)/%.o: %.c | $(BUILD_DIR)
$(CC) $(CFLAGS) $< -o $@ -MMD -MP -MF"$(@:%.o=%.d)"
$(BUILD_DIR)/%.o: %.s | $(BUILD_DIR)
$(AS) $(CFLAGS) $< -o $@
$(BUILD_DIR)/$(TARGET).elf: $(OBJECTS) | $(BUILD_DIR)
$(LD) -g $(OBJECTS) -Ttext 0X8000000 -o $@
$(BUILD_DIR)/%.hex: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
$(HEX) $< $@
$(BUILD_DIR)/%.bin: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
$(BIN) $< $@
$(BUILD_DIR)/.dis: %.elf
$(OBJDUMP) -D -m cortex-m3 $< > $@
$(BUILD_DIR):
mkdir $@
-include $(wildcard $(BUILD_DIR)/*.d)
clean:
rm -rf $(BUILD_DIR)
然后make命令执行编译
然后烧录运行可以看到其打印的信息
六、优化
接下来我们在uart.c
中添加如下两个函数用来打印字符串和16进制数
int putstring(const char *s)
{
while (*s)
{
putchar(*s);
s++;
}
return 0;
}
void puthex(unsigned int val)
{
/* 0x76543210 */
int i, j;
//puts("0x");
putchar('0');
putchar('x');
for (i = 7; i >= 0; i--)
{
j = (val >> (i*4)) & 0xf;
if ((j >= 0) && (j <= 9))
putchar('0' + j);
else
putchar('A' + j - 0xA);
}
}
修改main函数如下所示
编译烧录运行可以看到其串口打印如下
七、附录
上一篇:STM32裸机开发(7) — 复制data段和清除BSS段(ZI段)
下一篇:STM32裸机开发(9) — 使用链接脚本链接代码
代码存放:https://gitee.com/william_william/stm32f103_noos/tree/master/make-gcc/02_uart