Model Me, Model World
Blayground
STM32F030 光头程序编写
缘起
最近向老师借了一套 STM32 开发板练练手,顺便填充下单片机动手玩这条成就线的最后一个坑。 拿到手评估版上焊接的是 STM32F030F4P6(可惜不是最流行的F103),TSSOP20,万能的山寨仓库一搜,大概 10 元一套,价格十分优惠,但是毕竟 ARM,一看手册该有的功能也不缺。
配置大概是:
| 基地址 | 长度 | 对应设备 |
|---|---|---|
| 0x0800_0000 | 16K | Flash |
| 0x2000_0000 | 4K | SRAM |
| 0x4800_0000 | -- | GPIOA |
同时老师还给了个 SWD 接口(电源两根,时钟数据各一根)的 J-link 调试器。
芯片手册介绍说款单片机采用 cortex-m0,之后说请参考 armv6 的指令手册,加上看了一眼手册里描述的支持的指令列表,发现其只有 thumb 模式。好,看了这么多翻译成下面一句就足矣囊括:
-mcpu=cortex-m0 -march=armv6-m -mthumb
顺便说一句树莓派 BCM2835 SoC 内用的 ARM 核是 ARM11,双模式支持,概括成:
-mcpu-arm1176jzf-s -march=armv6 [-mthumb]
准备工作
如果没理解错的话这次练手应该产出个 Makefile,调用make生成文件可烧写的文件。期间所有过程都使用开源工具。当然,这玩意儿原本应该是用 ARM 提供的商业软件Keil做开发的。不过既然器件支持 ARM 指令集,那么无论用什么工具,吐出来的二进制代码合适正确,烧入芯片里效果都是一样的。
STM32 有特殊管脚,BOOT0 和 BOOT1(PB2)
| BOOT1 | BOOT0 | 启动位置 | 备注 |
|---|---|---|---|
| X | 0 | Flash | 一般就这样启动 |
| 0 | 1 | 系统存储器 | 厂家固化的程序,串口下载 |
| 1 | 1 | SRAM |
不过 F030 没有 BOOT1。
调试器下载
首先保证 A14/A15 不被别的功能占用,然后连上 J-link 就能在线编程。Windows 下使用安装完 Keil 后安装文件夹下 Segger 文件夹下的 J-link 程序。蛮好用的。
但是如果不小心写了程序占用了 A14/A15 这时候就不能在线编程了。用厂家固化程序“救砖”吧。
串口下载
到手的板子拔掉跳线上电后就能上厂家固化的程序。该程序自适应波特率,侦听其特有的协议(似乎是 1F 1F 开头)A9/A10 接到串口上。
可以用官方的 Flash Tool 或者山寨的下载程序。至于协议是什么,可以用逻辑分析仪看一下吧。
使用 Keil
万能仓库中卖的几个 F030 评估板有直接给了例程,因此直接拿来试一试下载功能好不好用。
首先 Keil 里有一个包管理系统,得先更新下(更新之前还没有 F030 的包可安装)然后安装 F030 的支持包。
这块板子上除了电源以外还有别的 LED,从万能仓库中下载到的代码都是亮 GPIOA4 口上的灯,因此当时就觉得这板子也是,结果尝试了好几次都没让板上的 LED 亮起来。实在是无奈,肉眼去看了下那个 LED,居然没有连在 GPIOA4 上,好像连在在 GPIOB1 上,没有万用表也不好验证。实在觉得很尴尬。
抄起逻辑分析仪,一采集,果然 GPIOA4 好好地在蹦蹦跳跳。
光头程序编写
芯片的手册很好找,然后手册里说参考 RM0360 来取得更加详细的信息。 设法找到意法半导体的编号为 RM0360 文档,由于是想亮灯,因此找到文档的 GPIO 部分,看一下怎么设置寄存器。
GPIO port mode register
每两比特决定一个口的状态
初始值 0x28,表明 A14/A13 处在特殊功能状态下。看一下板子,正好发现这两个口子是 SW 调试用的。
现在想设置 0-7 端口变成输出,相应的比特值是 01
BASE + 0x00 |= 0x5555
GPIO port output type register
不用设置了,上电状态是普通,驱个 LED 应该不用设置成开漏的。
GPIO port output speed register
A13 上电是高速,不知道为啥 A14 不是,程序里可设置可不设置
GPIO port pull-up/pull-down register
A14 下拉 A13 上拉,低 16bit 设置成下拉
BASE + 0x0c |= 0x5555
GPIO port input data register
读数据用的,低 16bit 有效
GPIO port output data register
写出数据用的,低 16bit 有效
GPIO port bit set/reset register
清零向高 16bit 写 1,置位向低 16bit 写 1
之后的 C 程序里就用了这两个寄存器
写完 C 程序烧到板子上去看了下,逻辑分析仪在所有的 GPIOA 口上都采集不到数据。可能是有些寄存器忘记设置了,于是乎再进入 Keil,对之前能亮灯的那个程序进行单步调试。Keil 的单步调试实在高级,C 语言和汇编上下对照的。
单步执行下来发现还得设置一个总线使能寄存器(属于 RCC 组),跳转到文档 RCC 相应位置,的确如此,可惜 GPIO 文档那部分没有提醒说得开启总线使能。
添加总线使能后发现输出口还是没有蹦蹦跳跳地输出信号。想起来似乎没有对现在这个 Makefile 用的 C 程序进行过调试,于是打开 J-link 命令行单步执行,程序运行到push指令的时候就跳转到一个hang的死循环里了。思忖良久,懂了原因是可能写错了栈顶地址,导致压栈不成功,跳转到预先定义的中断向量地址中了,那个地址中只有一条跳转的死循环。
打开汇编文件一看,果然栈顶地址写错了,后面少加了一个零。再次编译程序烧写,逻辑分析仪终于能采集到输出口的数据了。
结束
这次用的程序完全来自dwelch67公布的光头程序教程。自己就查了手册,根据手册改了下代码而已。大多数时间都花在熟悉环境上(Keil,J-link,启动跳线,串口下载)。
附:源码
Makefile
#ARMGNU=arm-thumb-elf
ARMGNU=arm-linux-gnueabi
#ARMGNU?=arm-none-eabi
COPS = -Wall -O2 -nostdlib -nostartfiles -ffreestanding -march=armv6-m -mcpu=cortex-m0
bin: blinker.gcc.thumb.bin
vectors.o : vectors.s
$(ARMGNU)-as vectors.s -o vectors.o
blinker.gcc.thumb.o : blinker.c
$(ARMGNU)-gcc $(COPS) -mthumb -c blinker.c -o blinker.gcc.thumb.o
blinker.gcc.thumb.bin : memmap vectors.o blinker.gcc.thumb.o
$(ARMGNU)-ld -o blinker.gcc.thumb.elf -T memmap vectors.o blinker.gcc.thumb.o
$(ARMGNU)-objdump -D blinker.gcc.thumb.elf > blinker.gcc.thumb.list
$(ARMGNU)-objdump -s blinker.gcc.thumb.elf > blinker.gcc.thumb.dump
$(ARMGNU)-objcopy blinker.gcc.thumb.elf blinker.gcc.thumb.bin -O binary
clean:
rm -rf *.bin *.dump *.elf *.list *.omemmap(ld script)
/* memmap */
MEMORY
{
rom : ORIGIN = 0x08000000, LENGTH = 0x10000
ram : ORIGIN = 0x20000000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > rom
.bss : { *(.bss*) } > ram
}vector.s
/*vectors.s*/
.cpu cortex-m0
.thumb
.word 0x20001000 /* stack top address */
.word _start /* 1 Reset */
.word hang /* 2 NMI */
.word hang /* 3 HardFault */
.word hang /* 4 MemManage */
.word hang /* 5 BusFault */
.word hang /* 6 UsageFault */
.word hang /* 7 RESERVED */
.word hang /* 8 RESERVED */
.word hang /* 9 RESERVED*/
.word hang /* 10 RESERVED */
.word hang /* 11 SVCall */
.word hang /* 12 Debug Monitor */
.word hang /* 13 RESERVED */
.word hang /* 14 PendSV */
.word hang /* 15 SysTick */
.word hang /* 16 External Interrupt(0) */
.word hang /* 17 External Interrupt(1) */
.word hang /* 18 External Interrupt(2) */
.word hang /* 19 ... */
.thumb_func
hang: b .
.thumb_func
.globl _start
_start:
bl notmain
b hang
.thumb_func
.globl PUT32
PUT32:
str r1,[r0]
bx lr
.thumb_func
.globl GET32
GET32:
ldr r0,[r0]
bx lr
.thumb_func
.globl dummy
dummy:
bx lrblinker.c
#define RCC_AHBENR (0x40021000+0x14)
#define GPIOA_BASE (0x48000000)
#define GPIO_MODER (GPIOA_BASE+0x00)
#define GPIO_SPEEDR (GPIOA_BASE+0x08)
#define GPIO_PUPDR (GPIOA_BASE+0x0c)
#define GPIO_BSRR (GPIOA_BASE+0x18)
extern void PUT32 ( unsigned int, unsigned int );
extern unsigned int GET32 ( unsigned int );
extern void dummy ( unsigned int );
void dowait ( void )
{
unsigned int ra;
for(ra=0x43210;ra;ra--)
{
dummy(ra);
}
}
void notmain ( void )
{
unsigned int ra,rb, rc;
// enable GPIO A bus clock
ra=0x00020014;
PUT32(RCC_AHBENR,ra);
// change GPIO A0-7 to output mode
ra=GET32(GPIO_MODER);
ra|=0x00005555;
PUT32(GPIO_MODER,ra);
// set speed for GPIO A
//ra=GET32(GPIO_SPEEDR);
//ra|=(uint32_t)0x00005555;
//PUT32(GPIO_SPEEDR,ra);
// pull-up pull-down
//ra=GET32(GPIO_PUPDR);
//ra|=0x00005555;
//PUT32(GPIO_PUPDR,ra);
// set/reset output resiger
ra=GPIO_BSRR;
rb=0x000000ff;
rc=0x00ff0000;
while(1)
{
PUT32(ra,rb);
dowait();
PUT32(ra,rc);
dowait();
}
}