本节有两个目标:

  1. 板子启动
  2. 基本驱动,printf重定向

1. 板子启动

  一般来讲,MCU启动之后运行的第一段程序叫做BootRom,是厂商固化在芯片内部的,主要功能是检测启动方式和做一些必要的准备工作。大多数MCU都支持从多种存储器中启动,如NorFlash、NandFlash、EMMC等。对于STM32F1来说,可以选择3种存储器作为启动源分别是:flash,系统存储,片上RAM。flash的大小是512KB;片上RAM的大小是64KB;系统存储区存储内嵌的自举程序,由ST在生产线上写入,用于通过可用的串口对flash进行编程。我们的代码最终要放在flash上运行,但是前期调试阶段可以先放在RAM上跑,这样下载比较快速,方便调试,在RAM上跑的方法可以参考我写的分散加载相关的文章。

  BootROM之后就是编程人员可以控制的部分了,程序会从地址0开始执行,但并不是执行0地址处的指令,而是取出0地址处的值赋给SP,然后取4地址处的值(其实是复位处理函数的地址)赋给PC,接着就会运行复位处理函数进行系统初始化,调用main()等一系列操作。4地址往后是一系列的异常服务函数的地址。这点跟以前的ARM7系列有所不同,ARM7系列的开头虽然也是中断向量表,但是每个地址存放的都是一条跳转指令,而Cortex-M系列存放的是服务函数的地址。以上功能一般以汇编语言编写,存放在一个单独的文件中,称为启动文件。ST官方已经准备好了一个启动文件的模板供我们使用,即startup_stm32f10x_hd.s,我们目前只需要修改一个地方,就是屏蔽SystemInit函数,这个函数是用来初始化时钟的,我们把这个步骤放到main()函数里去做。 完整的启动文件代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320

Stack_Size EQU 0x00000400

AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp

; <h> Heap Configuration
; <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>

Heap_Size EQU 0x00000200

AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit

PRESERVE8
THUMB


; Vector Table Mapped to Address 0 at Reset
AREA RESET, DATA, READONLY
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size

__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD MemManage_Handler ; MPU Fault Handler
DCD BusFault_Handler ; Bus Fault Handler
DCD UsageFault_Handler ; Usage Fault Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD SVC_Handler ; SVCall Handler
DCD DebugMon_Handler ; Debug Monitor Handler
DCD 0 ; Reserved
DCD PendSV_Handler ; PendSV Handler
DCD SysTick_Handler ; SysTick Handler

; External Interrupts
DCD WWDG_IRQHandler ; Window Watchdog
DCD PVD_IRQHandler ; PVD through EXTI Line detect
DCD TAMPER_IRQHandler ; Tamper
DCD RTC_IRQHandler ; RTC
DCD FLASH_IRQHandler ; Flash
DCD RCC_IRQHandler ; RCC
DCD EXTI0_IRQHandler ; EXTI Line 0
DCD EXTI1_IRQHandler ; EXTI Line 1
DCD EXTI2_IRQHandler ; EXTI Line 2
DCD EXTI3_IRQHandler ; EXTI Line 3
DCD EXTI4_IRQHandler ; EXTI Line 4
DCD DMA1_Channel1_IRQHandler ; DMA1 Channel 1
DCD DMA1_Channel2_IRQHandler ; DMA1 Channel 2
DCD DMA1_Channel3_IRQHandler ; DMA1 Channel 3
DCD DMA1_Channel4_IRQHandler ; DMA1 Channel 4
DCD DMA1_Channel5_IRQHandler ; DMA1 Channel 5
DCD DMA1_Channel6_IRQHandler ; DMA1 Channel 6
DCD DMA1_Channel7_IRQHandler ; DMA1 Channel 7
DCD ADC1_2_IRQHandler ; ADC1 & ADC2
DCD USB_HP_CAN1_TX_IRQHandler ; USB High Priority or CAN1 TX
DCD USB_LP_CAN1_RX0_IRQHandler ; USB Low Priority or CAN1 RX0
DCD CAN1_RX1_IRQHandler ; CAN1 RX1
DCD CAN1_SCE_IRQHandler ; CAN1 SCE
DCD EXTI9_5_IRQHandler ; EXTI Line 9..5
DCD TIM1_BRK_IRQHandler ; TIM1 Break
DCD TIM1_UP_IRQHandler ; TIM1 Update
DCD TIM1_TRG_COM_IRQHandler ; TIM1 Trigger and Commutation
DCD TIM1_CC_IRQHandler ; TIM1 Capture Compare
DCD TIM2_IRQHandler ; TIM2
DCD TIM3_IRQHandler ; TIM3
DCD TIM4_IRQHandler ; TIM4
DCD I2C1_EV_IRQHandler ; I2C1 Event
DCD I2C1_ER_IRQHandler ; I2C1 Error
DCD I2C2_EV_IRQHandler ; I2C2 Event
DCD I2C2_ER_IRQHandler ; I2C2 Error
DCD SPI1_IRQHandler ; SPI1
DCD SPI2_IRQHandler ; SPI2
DCD USART1_IRQHandler ; USART1
DCD USART2_IRQHandler ; USART2
DCD USART3_IRQHandler ; USART3
DCD EXTI15_10_IRQHandler ; EXTI Line 15..10
DCD RTCAlarm_IRQHandler ; RTC Alarm through EXTI Line
DCD USBWakeUp_IRQHandler ; USB Wakeup from suspend
DCD TIM8_BRK_IRQHandler ; TIM8 Break
DCD TIM8_UP_IRQHandler ; TIM8 Update
DCD TIM8_TRG_COM_IRQHandler ; TIM8 Trigger and Commutation
DCD TIM8_CC_IRQHandler ; TIM8 Capture Compare
DCD ADC3_IRQHandler ; ADC3
DCD FSMC_IRQHandler ; FSMC
DCD SDIO_IRQHandler ; SDIO
DCD TIM5_IRQHandler ; TIM5
DCD SPI3_IRQHandler ; SPI3
DCD UART4_IRQHandler ; UART4
DCD UART5_IRQHandler ; UART5
DCD TIM6_IRQHandler ; TIM6
DCD TIM7_IRQHandler ; TIM7
DCD DMA2_Channel1_IRQHandler ; DMA2 Channel1
DCD DMA2_Channel2_IRQHandler ; DMA2 Channel2
DCD DMA2_Channel3_IRQHandler ; DMA2 Channel3
DCD DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & Channel5
__Vectors_End

__Vectors_Size EQU __Vectors_End - __Vectors

AREA |.text|, CODE, READONLY

; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
LDR R0, =__main
BX R0
ENDP

; Dummy Exception Handlers (infinite loops which can be modified)

NMI_Handler PROC
EXPORT NMI_Handler [WEAK]
B .
ENDP
HardFault_Handler\
PROC
EXPORT HardFault_Handler [WEAK]
B .
ENDP
MemManage_Handler\
PROC
EXPORT MemManage_Handler [WEAK]
B .
ENDP
BusFault_Handler\
PROC
EXPORT BusFault_Handler [WEAK]
B .
ENDP
UsageFault_Handler\
PROC
EXPORT UsageFault_Handler [WEAK]
B .
ENDP
SVC_Handler PROC
EXPORT SVC_Handler [WEAK]
B .
ENDP
DebugMon_Handler\
PROC
EXPORT DebugMon_Handler [WEAK]
B .
ENDP
PendSV_Handler PROC
EXPORT PendSV_Handler [WEAK]
B .
ENDP
SysTick_Handler PROC
EXPORT SysTick_Handler [WEAK]
B .
ENDP

Default_Handler PROC

EXPORT WWDG_IRQHandler [WEAK]
EXPORT PVD_IRQHandler [WEAK]
EXPORT TAMPER_IRQHandler [WEAK]
EXPORT RTC_IRQHandler [WEAK]
EXPORT FLASH_IRQHandler [WEAK]
EXPORT RCC_IRQHandler [WEAK]
EXPORT EXTI0_IRQHandler [WEAK]
EXPORT EXTI1_IRQHandler [WEAK]
EXPORT EXTI2_IRQHandler [WEAK]
EXPORT EXTI3_IRQHandler [WEAK]
EXPORT EXTI4_IRQHandler [WEAK]
EXPORT DMA1_Channel1_IRQHandler [WEAK]
EXPORT DMA1_Channel2_IRQHandler [WEAK]
EXPORT DMA1_Channel3_IRQHandler [WEAK]
EXPORT DMA1_Channel4_IRQHandler [WEAK]
EXPORT DMA1_Channel5_IRQHandler [WEAK]
EXPORT DMA1_Channel6_IRQHandler [WEAK]
EXPORT DMA1_Channel7_IRQHandler [WEAK]
EXPORT ADC1_2_IRQHandler [WEAK]
EXPORT USB_HP_CAN1_TX_IRQHandler [WEAK]
EXPORT USB_LP_CAN1_RX0_IRQHandler [WEAK]
EXPORT CAN1_RX1_IRQHandler [WEAK]
EXPORT CAN1_SCE_IRQHandler [WEAK]
EXPORT EXTI9_5_IRQHandler [WEAK]
EXPORT TIM1_BRK_IRQHandler [WEAK]
EXPORT TIM1_UP_IRQHandler [WEAK]
EXPORT TIM1_TRG_COM_IRQHandler [WEAK]
EXPORT TIM1_CC_IRQHandler [WEAK]
EXPORT TIM2_IRQHandler [WEAK]
EXPORT TIM3_IRQHandler [WEAK]
EXPORT TIM4_IRQHandler [WEAK]
EXPORT I2C1_EV_IRQHandler [WEAK]
EXPORT I2C1_ER_IRQHandler [WEAK]
EXPORT I2C2_EV_IRQHandler [WEAK]
EXPORT I2C2_ER_IRQHandler [WEAK]
EXPORT SPI1_IRQHandler [WEAK]
EXPORT SPI2_IRQHandler [WEAK]
EXPORT USART1_IRQHandler [WEAK]
EXPORT USART2_IRQHandler [WEAK]
EXPORT USART3_IRQHandler [WEAK]
EXPORT EXTI15_10_IRQHandler [WEAK]
EXPORT RTCAlarm_IRQHandler [WEAK]
EXPORT USBWakeUp_IRQHandler [WEAK]
EXPORT TIM8_BRK_IRQHandler [WEAK]
EXPORT TIM8_UP_IRQHandler [WEAK]
EXPORT TIM8_TRG_COM_IRQHandler [WEAK]
EXPORT TIM8_CC_IRQHandler [WEAK]
EXPORT ADC3_IRQHandler [WEAK]
EXPORT FSMC_IRQHandler [WEAK]
EXPORT SDIO_IRQHandler [WEAK]
EXPORT TIM5_IRQHandler [WEAK]
EXPORT SPI3_IRQHandler [WEAK]
EXPORT UART4_IRQHandler [WEAK]
EXPORT UART5_IRQHandler [WEAK]
EXPORT TIM6_IRQHandler [WEAK]
EXPORT TIM7_IRQHandler [WEAK]
EXPORT DMA2_Channel1_IRQHandler [WEAK]
EXPORT DMA2_Channel2_IRQHandler [WEAK]
EXPORT DMA2_Channel3_IRQHandler [WEAK]
EXPORT DMA2_Channel4_5_IRQHandler [WEAK]

WWDG_IRQHandler
PVD_IRQHandler
TAMPER_IRQHandler
RTC_IRQHandler
FLASH_IRQHandler
RCC_IRQHandler
EXTI0_IRQHandler
EXTI1_IRQHandler
EXTI2_IRQHandler
EXTI3_IRQHandler
EXTI4_IRQHandler
DMA1_Channel1_IRQHandler
DMA1_Channel2_IRQHandler
DMA1_Channel3_IRQHandler
DMA1_Channel4_IRQHandler
DMA1_Channel5_IRQHandler
DMA1_Channel6_IRQHandler
DMA1_Channel7_IRQHandler
ADC1_2_IRQHandler
USB_HP_CAN1_TX_IRQHandler
USB_LP_CAN1_RX0_IRQHandler
CAN1_RX1_IRQHandler
CAN1_SCE_IRQHandler
EXTI9_5_IRQHandler
TIM1_BRK_IRQHandler
TIM1_UP_IRQHandler
TIM1_TRG_COM_IRQHandler
TIM1_CC_IRQHandler
TIM2_IRQHandler
TIM3_IRQHandler
TIM4_IRQHandler
I2C1_EV_IRQHandler
I2C1_ER_IRQHandler
I2C2_EV_IRQHandler
I2C2_ER_IRQHandler
SPI1_IRQHandler
SPI2_IRQHandler
USART1_IRQHandler
USART2_IRQHandler
USART3_IRQHandler
EXTI15_10_IRQHandler
RTCAlarm_IRQHandler
USBWakeUp_IRQHandler
TIM8_BRK_IRQHandler
TIM8_UP_IRQHandler
TIM8_TRG_COM_IRQHandler
TIM8_CC_IRQHandler
ADC3_IRQHandler
FSMC_IRQHandler
SDIO_IRQHandler
TIM5_IRQHandler
SPI3_IRQHandler
UART4_IRQHandler
UART5_IRQHandler
TIM6_IRQHandler
TIM7_IRQHandler
DMA2_Channel1_IRQHandler
DMA2_Channel2_IRQHandler
DMA2_Channel3_IRQHandler
DMA2_Channel4_5_IRQHandler
B .

ENDP

ALIGN

;*******************************************************************************
; User Stack and Heap initialization
;*******************************************************************************
IF :DEF:__MICROLIB

EXPORT __initial_sp
EXPORT __heap_base
EXPORT __heap_limit

ELSE

IMPORT __use_two_region_memory
EXPORT __user_initial_stackheap

__user_initial_stackheap

LDR R0, = Heap_Mem
LDR R1, =(Stack_Mem + Stack_Size)
LDR R2, = (Heap_Mem + Heap_Size)
LDR R3, = Stack_Mem
BX LR

ALIGN

ENDIF

END

  接下来把ARM和ST官方提供的寄存器定义等文件放到hyyos/Arch/stm32f10x/目录中,此时目录中内容如下:

1
2
3
4
5
6
|-stm32f10x # STM32F10X系列相关的代码
|-startup_stm32f10x_hd.s # 启动文件
|-core_cm3.h
|-core_cmFunc.h
|-core_cmInstr.h
|-stm32f10x.h

  接下本来应该是helloworld亮相了,我们计划把它输出到USART1上面,通过PC上的串口调试助手进行显示和交互。所以先要把USART1读写功能做出来,而USART1是挂在APB2上面的,所以要先配置一下对应的时钟。我们在hw目录下新增两个文件USART1.c和sys.c,分别用来放USART1相关和时钟初始化代码。然后依次编写实现以下功能的代码:

  1. 配置系统时钟。参考我的往期文章《STM32F1学习(一)时钟》
  2. USART1端口配置,中断配置。参考我的往期文章《STM32F1学习(三)USART》
  3. 编写USART1接收中断服务函数USART1_IRQHandler此函数名在启动文件中定义。这里我们使用了一个256字节大小的接收缓冲,由于只是用来跟PC通信,因此通信协议非常简单,把\r\n作为每次接收结束标志。
  4. 编写USART1发送函数int Usart1SendByte(int data)
  5. 重定向printf到USART1。printf调用了C库中的fputc()函数进行输出,因此我们只需要定义一下fputc()即可。在sys.c中定义int fputc (int c, File *fp)并在其中调用Usart1SendByte()函数实现串口输出

hw/sys.c文件如下:

1
2
3
4
5
6
// 用于printf重定向
int fputc(int ch, FILE *f)
{
Usart1SendByte(ch);
return ch;
}

hw/usart1.c文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#define USART1_RCV_BUF_MAX  256
static unsigned usart1RcvLen;
char *usart1RcvBuf[USART1_RCV_BUF_MAX] = {0};

void Usart1Init(void)
{
unsigned tmp;
SETUP_BIT(RCC->APB2ENR, 14); // USART1时钟使能
SETUP_BIT(RCC->APB2RSTR, 14); // USART1复位

SET_BITS(GPIOA->CRH, 5, 4, 0x3); // PA9 50MHz速率
SET_BITS(GPIOA->CRH, 7, 6, 0x2); // 推挽复用输出
SET_BITS(GPIOA->CRH, 9, 8, 0); // PA10 输入模式
SET_BITS(GPIOA->CRH, 11, 10, 0x1); // 浮空输入

// TXRX配置
// 1. 通过在USART_CR1寄存器上置位UE位来激活USART
SETUP_BIT(USART1->CR1, 13);

// 2. 编程USART_CR1的M位来定义字长。
CLR_BIT(USART1->CR1, 12); // 8个数据位

// 3. 在USART_CR2中编程停止位的位数。
SET_BITS(USART1->CR2, 13, 12, 0); // 1个停止位

// 4. 如果采用多缓冲器通信,配置USART_CR3中的DMA使能位(DMAT)。按多缓冲器通信中的描述配置DMA寄存器。
//5. 利用USART_BRR寄存器选择要求的波特率。
// Tx / Rx 波特率 = FCK / (16 * USARTDIV)
// 这里的fCK是给外设的时钟(PCLK1用于USART2、3、4、5,PCLK2用于USART1)
// 假设BRR寄存器中DIV_Mantissa = 27,DIV_Fraction = 12 (USART_BRR=0x1BC),
// Mantissa (USARTDIV) = 27,Fraction (USARTDIV) = 12/16 = 0.75,所以 USARTDIV = 27.75
// 本例中PCLK2 = 72M,我们要得到115200的波特率,则USARTDIV = 72M / (16*115200) = 39.0625
// 因此DIV_Mantissa = 39,DIV_Fraction = 1,BRR = 0x271
USART1->BRR = 0x271; // 配置波特率115200
// 6. 设置USART_CR1中的TE位,发送一个空闲帧作为第一次数据发送,即发送使能。
SETUP_BIT(USART1->CR1, 3);
// 6. 设置USART_CR1的RE位。激活接收器,使它开始寻找起始位,即接收使能。
SETUP_BIT(USART1->CR1, 2);

// 配置中断
SETUP_BIT(USART1->CR1, 5); // 接收中断
// 以下NVIC相关的配置下一章节会详细讲解
tmp = SCB->AIRCR;
SET_BITS(tmp, 31, 16, 0x05FA);
SET_BITS(tmp, 10, 8, 0x5); // 优先级分组类型:2:2
SCB-->AIRCR=temp; //设置分组

SET_BITS(NVIC->IP[37], 7, 6, 0); // 抢占优先级,最低
SET_BITS(NVIC->IP[37], 5, 4, 0); // 响应优先级,最低
NVIC->ISER[1] = (1 << 5); // 中断使能
}

void USART1_IRQHandler(void)
{
while (GET_BIT(USART1->SR, 5)) {
usart1RcvBuf[usart1RcvLen++] = (char)USART1->DR;
if (usart1RcvLen == USART1_RCV_BUF_MAX) { // 接收缓存满
usart1RcvLen = 0;
memset(usart1RcvBuf, 0, sizeof(usart1RcvBuf));
}
}
}

int Usart1Send(const char *data, unsigned len)
{
unsigned i = 0;
while (i < len) {
while (!GET_BIT(USART1->SR, 7)) ; // 等待上一个数据被转移到移位寄存器
USART1->DR = data[i];
i++;
}
while (!GET_BIT(USART1->SR, 6)) ; // 等待发送完成(从移位寄存器中发送出去了)

return 0;
}

int Usart1SendByte(char data)
{
while (!GET_BIT(USART1->SR, 7)) ; // 等待上一个数据被转移到移位寄存器
USART1->DR = data[i];
return 0;
}

  至此,准备工作终于做完了,helloworld亮相!

1
2
3
4
5
6
7
8
9
// main.c
int32_t main(void)
{
Stm32_Clock_Init();
Usart1Init();
printf("Hello world!\r\n");

return 0;
}