keil实战——新建工程模板
新建项目的时候发现没有32的库,还需要自己添加一下,去官网下载,网址:MDK5 Software Packs
野火视频用的是F1
在网址里找到keil这个标题,往下翻
下载即可
下载完以后双击安装
装了MDK会自动识别,直接点击NEXT,装完以后点击finish
我是装完才截的图,会有警告,没装过的不会有,忽略警告就好
新建步骤如图,视频里讲的很清楚,就不细说了
下图直接关闭即可
添加启动文件,地址如下
A盘(资料盘)\3-STM32官方资料\STM32F103官方固件库与手册(标准库)\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\arm
把startup_stm32f10x_hd.s复制到工程目录下
复制以后在keil中双击Source Group1,添加此文件到工程里
在目录里新建txt文件,把名字改为main.c ,以同样的步骤把main添加进工程里,别忘了添加.h文件
我txt文件无论怎么改名都无法转换成.c文件
解决办法:
这是我在csdn找的http://t.csdn.cn/0DJQy
1、首先你要有C/C++软件,如DEV-C(下载地址如下Dev-C++ download | SourceForge.net)
2、如果你是WIN10系统。首先打开“此电脑”,依次点“查看”-“选项”-“查看”,将“隐藏已知文件类型的扩展名”前面的勾去掉,再确定,让系统显示完整的文件全名。然后.c后缀就能直接改成C文件。
双击选项
去掉选项以后再进行重命名的操作就可以了
新建项目,编译
此时直接编译的话,会出现如下错误:
“ Error: L6218E: Undefined symbol SystemInit (referred from startup_stm32f429_439xx.o)”
错误提示 SystemInit引用但没有定义。
图 2 汇编文件
由图汇编文件可知,运行的时候会先运行SystemInit,初始化系统时钟以后在调用面函数
在main函数外面做一个空实现,即可解决报错
这块我从一开始就出现了意想不到的问题,
然后就是我明明输的和老师一样吧,SystemInit报错no previous prototype for function systeminit
解决办法:
鼠标右键点击Target 1,选择Options for Group‘Source Group 1...’
或者你发现工具栏里有,和编译在一行,直接点击工具栏里的即可
将 6选成5即可
然后还没完呢,现在编译器确实不报错了,但是呢编译不过,会出现以下问题,编译终止
神奇的事情来了,既然Version 5 is not available,我就又换回Version 6了,这次编译页面也没有报错,编译也是直接就过了。
这个问题说一下,我这里装的是keil社区版,不能调试,才会有这种奇怪的现象,如果是破解版的,换成5版本就没有问题了,我后面又装了一个破解版,后续所有例程都是在破解版下进行的了。
1.include<>与include" "区别
include<>表示文件不在当前目录下,在编译器目录下
include" "表示在当前目录下找不到,再去软件目录下找
.hex:通过串口下载的可执行文件
.axf:通过编译器下载的可执行文件
.sct:加载没听清,以后补文件
配置
下载程序
debug里选择DAP
确保Utilities里的use debug driver是勾选上的
然后在返回debug,在选择DAP的位置点击setting,在debug界面确保SWJ是勾选上的,时钟选择5MHz
切换到Flash Download界面,勾选reset and run
下载即可。如果出现这个报错,重新编译下
我实际下载的时候发现与视频中不一样,我的无法勾选SWJ,如图
去问了淘宝客服,说是版本不一样,只需connect处选择under Reset,Reset处选择SYSRESETREQ,,然后再次下载,就能成功了。
2.屏蔽 #if 0
#else
# end if
点亮LED灯
点灯三部曲
// 1.打开时钟 在AHB上
// 2.设置输出模式 GPIOx_CRL
// 3.开灯 用CDR设置指定位置为0
步骤
图 3 指南者原理图
(1)由图可知,PB0控制灯,PB0为0,灯亮
PB0通过GPIOB_ODR第0位控制
复位值为0x0000 0000,所以低十六位全是0
*(unsigned int*) (0x40010C0C) & =~(1<<0);利用上一篇最后的清零操作
这一部相当于
(2)显然光有这一步等还是不会亮,这是由于PB0目前是输入状态,不是输出状态,需改变输入状态
此寄存器四位控制一个IO口 ,CRL思维控制的一个io口,相当于ODR寄存器里的pb0
由下方每一位的解释来选择模式,LED一般用推挽输出,所以CNF选择00,MODE选择01(此位置选择没有固定原因,就是随机选择)
*(unsigned int*)0x40010C00 |=((1)<<(4*0));//最后一位置一
这行刚开始代码我一直理解的不太好,找淘宝客服battle半天,后面发现按我的理解完全可以实现视频里的结果,并且作业中的点亮其他灯也完美实现,所以我觉得我的理解没有问题
这里说一下我的理解,确实是像视频里说的一样四个一组,
*(unsigned int*)0x40010C00 |=((1)<<(4*0));这行代码在我这边是这样的
*(unsigned int*)0x40010C00 |=((0x01)<<(4*0));相当于是0001这个整体左移了4个0;
在CRL寄存器里,四位对因一个PBx,如图
所以在程序里我这行代码是这样写的(*(unsigned int*)(0x40010C00))|= ((0x01)<<(0*(4*1)));
(3)加上这一步以后,灯还是不能点亮,这是因为GPIO时钟被关闭
所以还要将时钟使能
可知0时钟关闭,所以要置一,地址为0x40021018,IOPB在第三位,所以左移三位置一
*(unsigned int*)0x40021018 |=(1<<3);
整体代码如下
作业
作业需要注意的是,灯对应的引脚不一样,其对应的CRL寄存器的位不一样,记得改CRL的值
点灯代码如下
//点亮蓝灯
//记得改CRL输出端口,不然输出端口一直是PB0,亮什么蓝灯啊
(*(unsigned int*)(0x40021018))|=(1<<3);
(*(unsigned int*)(0x40010C00))|= ((0x01)<<(4*1));//配置低寄存器CRL 模式为 0001 PB0的端口
//(*(unsigned int*)(0x40010C0C)) |=(1<<0); 可以不需要,因为CRL输出端口变了
(*(unsigned int*)(0x40010C0C)) &=~(1<<1);
//点亮红灯
//打开时钟 APB2 外设时钟使能寄存器(RCC_APB2ENR)
(*(unsigned int*)(0x40021018))|=(1<<3);
(*(unsigned int*)(0x40010C00))|= ((0x01)<<(5*(4*1)));//配置低寄存器CRL 模式为 0001 PB1的端口
(*(unsigned int*)(0x40010C0C)) &=~(1<<5);
延时函数
原因是函数写道main函数以后,并且没在main前声明
void mydelay(int dy)
{
int i=0;
int j=0;
for(i=0;i<dy;i++)
{
for(j=0;j<1000;j++)
{
}
}
}
蓝灯闪烁
//蓝灯闪烁
(*(unsigned int*)(0x40021018))|=(1<<3);
(*(unsigned int*)(0x40010C00))|= ((0x01)<<(4*1));
while(k<10){
(*(unsigned int*)(0x40010C0C)) &=~(1<<1);//点蓝灯
mydelay(500);
(*(unsigned int*)(0x40010C0C)) |=(1<<1);//关蓝灯
mydelay(500);
k++;
}
绿蓝红依次闪烁
(*(unsigned int*)(0x40021018))|=(1<<3);
while(k<10){
(*(unsigned int*)(0x40010C00))|= ((1)<<(4*0));
(*(unsigned int*)(0x40010C0C))&=~(1<<0);//点绿灯
mydelay(500);
(*(unsigned int*)(0x40010C0C)) |=(1<<0); //关闭绿灯
mydelay(500);
(*(unsigned int*)(0x40010C00))|= ((0x01)<<(4*1));
(*(unsigned int*)(0x40010C0C)) &=~(1<<1);//点蓝灯
mydelay(500);
(*(unsigned int*)(0x40010C0C)) |=(1<<1);//关蓝灯
mydelay(500);
(*(unsigned int*)(0x40010C00))|= ((0x01)<<(5*(4*1)));
(*(unsigned int*)(0x40010C0C)) &=~(1<<5);//点红灯
mydelay(500);
(*(unsigned int*)(0x40010C0C)) |=(1<<5);//关红灯
mydelay(500);
k++;
}
优化版,在h文件中声明好地址
//基地址 #define PERIPH_BASE ((unsigned int)0x40000000) //总线基地址 AHB APB1 APB2 #define APB1PERIHP_BASE PERIPH_BASE //((unsigned int)0x40000000) #define APB2PERIHP_BASE (PERIPH_BASE + 0x10000) //((unsigned int)0x40000000 + 00010000) #define AHBPERIHP_BASE (PERIPH_BASE + 0x20000) //AHB前三位为保留,单独定义,从0x20000开始定义
AHB前两位是保留,所以单独定义这两位,AHB从DAM1开始定义
作业
//外设 perirharl //基地址 #define PERIPH_BASE ((unsigned int)0x40000000) //总线基地址 AHB APB1 APB2 #define APB1PERIHP_BASE PERIPH_BASE //((unsigned int)0x40000000) #define APB2PERIHP_BASE (PERIPH_BASE + 0x10000) //((unsigned int)0x40000000 + 00010000) #define AHBPERIHP_BASE (PERIPH_BASE + 0x20000) //AHB前三位为保留,单独定义,从0x20000开始定义 //时钟复位和恢复控制RCC #define RCC_BASE (AHBPERIHP_BASE + 0x1000) #define RCC_APB2ENR *(unsigned int*)(RCC_BASE + 0x18) //GPIO 外设基地址 ABCDEFGH #define GPIOA_BASE (APB2PERIHP_BASE + 0x0800) #define GPIOB_BASE (APB2PERIHP_BASE + 0x0C00) #define GPIOC_BASE (APB2PERIHP_BASE + 0x1000) #define GPIOD_BASE (APB2PERIHP_BASE + 0x0400) #define GPIOE_BASE (APB2PERIHP_BASE + 0x1800) #define GPIOF_BASE (APB2PERIHP_BASE + 0x1C00) #define GPIOG_BASE (APB2PERIHP_BASE + 0x2000) //寄存器地址 GPIOA #define GPIOA_CRL *(unsigned int*)(GPIOA_BASE) #define GPIOA_CRH *(unsigned int*)(GPIOA_BASE + 0x04) #define GPIOA_IDR *(unsigned int*)(GPIOA_BASE + 0x08) #define GPIOA_ODR *(unsigned int*)(GPIOA_BASE + 0x0C) #define GPIOA_BSRR *(unsigned int*)(GPIOA_BASE + 0x10) #define GPIOA_LCKR *(unsigned int*)(GPIOA_BASE + 0x14) //寄存器地址 GPIOB #define GPIOB_CRL *(unsigned int*)(GPIOB_BASE) #define GPIOB_CRH *(unsigned int*)(GPIOB_BASE + 0x04) //(GPIOB_BASE + 0400) #define GPIOB_IDR *(unsigned int*)(GPIOB_BASE + 0x08) //(GPIOB_BASE + 0800) #define GPIOB_ODR *(unsigned int*)(GPIOB_BASE + 0x0C) //(GPIOB_BASE + 0C00) #define GPIOB_BSRR *(unsigned int*)(GPIOB_BASE + 0x10) //(GPIOB_BASE + 1000) #define GPIOB_LCKR *(unsigned int*)(GPIOB_BASE + 0x14) //(GPIOB_BASE + 1400) //寄存器地址 GPIOC #define GPIOC_CRL *(unsigned int*)(GPIOC_BASE) #define GPIOC_CRH *(unsigned int*)(GPIOC_BASE + 0x04) #define GPIOC_IDR *(unsigned int*)(GPIOC_BASE + 0x08) #define GPIOC_ODR *(unsigned int*)(GPIOC_BASE + 0x0C) #define GPIOC_BSRR *(unsigned int*)(GPIOC_BASE + 0x10) #define GPIOC_LCKR *(unsigned int*)(GPIOC_BASE + 0x14) //寄存器地址 GPIOD #define GPIOD_CRL *(unsigned int*)(GPIOD_BASE) #define GPIOD_CRH *(unsigned int*)(GPIOD_BASE + 0x04) #define GPIOD_IDR *(unsigned int*)(GPIOD_BASE + 0x08) #define GPIOD_ODR *(unsigned int*)(GPIOD_BASE + 0x0C) #define GPIOD_BSRR *(unsigned int*)(GPIOD_BASE + 0x10) #define GPIOD_LCKR *(unsigned int*)(GPIOD_BASE + 0x14)
点灯
//优化 提升代码可读性 //打开时钟 RCC_APB2ENR|=(1<<3); //先将CRL清零 GPIOB_CRL &=~((0x0f)<<(4*0)); //先设置输出模式 配置低寄存器CRL 模式为 0001 GPIOB_CRL |= ((1)<<(4*0)); //ODR设置指定位置为0 GPIOB_ODR &=~(1<<0); //GPIOB_ODR |=(1<<0); //关闭绿灯 //BSRR控制开灯 //绿灯 //打开时钟 RCC_APB2ENR |=(1<<3); //先清零 GPIOB_CRL &=~((0x0f)<<(4*0)); //设置输出模式 GPIOB_CRL |=((0x01<<(4*0))); //配置BSRR BSy使对应PBy为1 关灯 //GPIOB_BSRR |=(1<<0); //BRR BRy使对应PBy为0 点灯 GPIOB_BRR |=(1<<0); //红灯 //打开时钟 RCC_APB2ENR |=(1<<3); //先清零 GPIOB_CRL &=~((0x0f)<<5*(4*1)); //设置输出模式 GPIOB_CRL |=((0x01<<5*(4*1))); //配置BSRR BSy使对应PBy为1 关灯 //GPIOB_BSRR |=(1<<5); //BRR BRy使对应PBy为0 点灯 GPIOB_BRR |=(1<<5); //蓝灯 //打开时钟 RCC_APB2ENR |=(1<<3); //先清零 GPIOB_CRL &=~((0x0f)<<(4*1)); //设置输出模式 GPIOB_CRL |=((0x01<<(4*1))); //配置BSRR BSy使对应PBy为1 关灯 //GPIOB_BSRR |=(1<<1); //BRR BRy使对应PBy为0 点灯 GPIOB_BRR |=(1<<1); //红灯闪烁 RCC_APB2ENR |=(1<<3); GPIOB_CRL |= ((0x01)<<5*(4*1)); while(k<10){ GPIOB_BRR |=(1<<5);//点灯 mydelay(500); GPIOB_BSRR |=(1<<5);//关灯 mydelay(500); k++; } //绿蓝红依次点亮 RCC_APB2ENR |=(1<<3); while(k<10){ GPIOB_CRL|= ((1)<<(4*0)); GPIOB_BRR |=(1<<0);//点绿灯 mydelay(500); GPIOB_BSRR |=(1<<0); //关闭绿灯 mydelay(500); GPIOB_CRL|= ((0x01)<<(4*1)); GPIOB_BRR |=(1<<1);//点蓝灯 mydelay(500); GPIOB_BSRR |=(1<<1);//关蓝灯 mydelay(500); GPIOB_CRL|= ((0x01)<<(5*(4*1))); GPIOB_BRR |=(1<<5);//点红灯 mydelay(500); GPIOB_BSRR |=(1<<5);//关红灯 mydelay(500); k++; }
GPIO功能概述
全称:General purpose input output,软件可控制的引脚
1.GPIO包含在引脚内部
2.查找stm32数据手册
功能框图
按照图中顺序来看,虚线部分是芯片内部,我们无法看到。
保护二极管原理,当I/O口输入电压高于3.3v,上方二极管导通
当电压小于0V,下方二极管导通
下面进入内部输出部分
开漏和推挽都是通过控制输出数据寄存器高低电平来控制的
这里涉及模电MOS管的知识,我比较清楚,有需要的话后面会加进来。
首先是推挽输出
简化电路分析,当输入是1时,经过反相器,变为0,PMOS导通,NMOS截至,输出电压和VDD相同,输出3.3V电压
当输入是0时,经过反相器以后变为去,PMOS截至,NMOS管导通,输出处电压被拉为0V
开漏输出(I2C,MODBus)
当输入一个高电平,经过反相器,NMOS管截止,需加上拉电阻输出为高电平
输入为一个低电平时,经过反相器,NMOS管导通,将out拉为低电平,此低电平是内部电路提供的
可以直接通过ODR控制输出,也可以通过BSRR控制
低16位控制set,置位,让BS0为1,将BS0写为1,
高16位是Reset,清零,让BR0为零,将BR0写为1
同时写1的话,置位起作用
BSR低16位有效,清零,Reset
复用功能输出,有可能是串口
输入IDR,当通过BSRR或者ODR控制输出1的话,可以通过IDR监测到
上拉输入,下拉输入
在CRL寄存器里,但是是通过软件按控制,不直接配置CRL寄存器
当配置成上拉时,向置位寄存器BSRR里写1;配置下拉时,向BSR里写1
施密特触发器起到门限作用,当电压小于1.2v时为低电平0,高于2.V为高电平1