初始STM32
什么是STM32
1、ST— 意法半导体,是一个公司名,即SOC厂商
2、M— Microelectronics的缩写,表示微控制器,大家注意微控制 器和微处理器的区别
3、32— 32bit的意思,表示这是一个32bit的微控制器
诞生背景:技术更替,市场需求,ST的努力
STM32能做什么
STM32属于一个微控制器,自带了各种常用通信接口,功能非常强大
1、串口—USART,用于跟跟串口接口的设备通信,比如:USB转串口模块、ESP8266 WIFI、GPS模块,GSM 模块,串口屏、指纹识别模块 STM32属于一个微控制器,自带了各种常用通信接口,功能非常强大
2、内部集成电路—I2C(Inter-Integrated Circuit),用于跟I2C接口的设备通信,比如:EEPROM、电容屏、陀螺 仪MPU6050、0.96寸OLED模块
3、串行通信接口—SPI(Serial Peripheral Interface),用于跟SPI接口的设备通信,比如:串行FLASH、以太网 W5500、音频模块VS1053
4、SDIO、FSMC的超级、I2S、SAI、ADC、GPIO
STM32怎么选型
STM32分类
STM32命名方法
选择合适的MCU
1、选择哪种内核的芯片,内核越高意味着功耗也越高
2、选择多少引脚的芯片,引脚多少决定了资源的多少,也影响价格
3、选择多少RAM和FLASH的芯片,FLASH越大,价格越贵
4、还要考虑所选型号采购是否容易,供货是否稳定
分配原理图引脚
数据手册和参考手册:数据手册主要用于芯片选型和设计原理图时参考,参考手册主要用于在 编程的时候查阅
数据手册中对引脚的定义
引脚的功能定义解读
开始分配原理图IO
确定MCU型号,封装形式,在数据手册上找到封装的引脚定义,根据引脚序号,复制整理成excel表。
置位1用或,置位0用与(加取反)。
GPIO=general purpose input output通用输入输出端口
定义的是结构体指针的话,访问里面的元素就用p->结构体成员;或者用(*p).结构体成员`
定义的是结构体的话,访问里面的元素就用p.结构体成员 就可以
用keil编辑STM32点灯—遇到灯常亮不闪烁的问题
方法一:options-c/c++选项 更改优化级别为-O0
方法二:while循环中出现的变量,定义时加上关键字volatile
STM32固件库文件分析
1-汇编编写的启动文件
startup-stm32f10x_hd.s : 设置堆栈指针,设置PC指针、初始化中断向量表、配置系统时钟、对用C库函数_main最终去到c的世界
2-时钟配置文件
system_stm32f10x.c:把外部时钟HSE=8M,经过PLL倍频为72M
3-外设相关的
stm32f10x.h:实现了内核之外的外设寄存器映射
xxx:GPIO、USART、I2C、SPI、FSMC
stm32f10x_XXX.c:片上外设的驱动函数库文件
stm32f10x_XXX.h:存放外设的初始化结构体,外设初始化结构体成员的参数列表,外设固件库函数的声明
4-内核相关的
CMSIS – Cortex微控制器软件接口标准
core_cm3.h:实现了内核里面外设的寄存器映射
core_cm3.c:内核外设的驱动固件库
NVIC(嵌套向量中断控制器)、SysTick(系统滴答定时器)
misc.h
misc.c
5-头文件的配置文件
stm32f10x_conf.h:头文件的头文件
6-专门存放中断服务函数的C文件
stm32f10x_it.h
stm32f10x_it.c
中断服务函数可以随意放在其他地方,并不是一定要放在stm32f10x_it.c
^异或,C语言的一个二进制运算符
与1异或改变,与0异或不变
0x00到0x01是一个字节的变化,字节Byte,比特bit
!GPIO点亮LED灯编程要点
1.使能GPIO端口时钟
2.初始化GPIO目标引脚为推挽输出模式、
3.编写主函数,控制GPIO引脚输出高低电平
!GPIO按键检测编程要点
1.使能GPIO端口时钟
2.初始化GPIO目标引脚为输入模式(浮空输入)
3.编写主函数,检测按键状态,实现按键控制LED灯
启动文件代码做了什么
1.初始化堆栈指针
2.初始化中断向量表
3.执行复位程序
4.中断服务程序
5.用户堆栈初始化
RCC相关头文件和固件库源文件
1.时钟使能配置
2.时钟源相关配置
3.分频系数选择配置
4.外设时钟使能
5.其他外设时钟配置
6.状态参数获取函数
7.RCC中断相关函数
!使用HSE配置时钟编程要点
1.使能HSE/HSI,并等待其稳定
2.设置AHB、APB2、APB1预分频因子
3.设置PLL时钟来源,设置PLL倍频因子
4.使能PLL并等待PLL稳定
5.选择PLL作为系统时钟来源
6.读取时钟切换状态位,确保PLLCLK被选为系统时钟
中断应用概览
优先级的设定,要先分组,再配置主优先级和次优先级
中断源从stm32f10x.h的IRQn_Type结构体里面找
中断服务函数名称的编写,要和启动文件里的中断向量表名称要一样,能确保函数的执行,而不是跳到预先写好的空的中断服务函数一直在循环。
EXTI功能框图
!EXTI外部中断控制实验编程要点
建议对按键和EXTI进行宏定义
初始化结构体和初始化库函数配合使用是标准库精髓所在
1.初始化用来产生中断的GPIO
2.初始化EXTI
3.配置NVIC(嵌套向量中断控制器)
4.编写中断服务函数
!Systick定时实验编程要点
1.设置重装载寄存器的值LOAD
2.清除当前数值寄存器的值VAL
3.配置控制与状态寄存器CTRL
USART功能框图
!串口通信接发实验编程要点
1.初始化串口读写端用到的GPIO配置
2.初始化串口配置,USART_InitTypeDef(波特率,帧数据字长,停止位,校验位等等)
3.串口中断配置(中断优先级配置,接收中断使能)
4.使能串口
5.编写发送和接收函数
6.编写中断服务函数
DMA功能框图
direct memory access直接存储器访问
!DMA——M-M实验编程要点
1.在FLASH中定义好要传输的数据,在SRAM中定义好用来接收FLASH数据的变量
2.初始化DMA,主要是配置DMA初始化结构体
3.编写比较函数
4.编写main函数
!DMA——M-P实验编程要点
1.初始化串口
2.配置DMA初始化结构体
3.编写主函数(开启串口发送DMA请求)
基本存储器种类
random access memory随机存储器
Dynamic RAM
Synchronous DRAM时钟同步DRAM
Double Data Rate SDRAM
Static RAM
read only memory只读存储器
One Time Programable ROM
Erasable Programable ROM
Elecrtically Programable ROM
EEPROM芯片和FLASH芯片区别
I2C实验的EEPROM芯片(型号:AT24C02),容量为2Kb(这里是bit),相当于256B(256个字节)。可以按字节为单位修改数据,无需整个芯片擦除。
SPI实验的FLASH芯片(型号:W25Q64)属于NOR FLASH,有8MB存储容量,分为128个64KB的块,每个块包含16个4KB的扇区。以扇区为最小擦除单位,但是可以基于字节读写(NAND FLASH必须以块为单位读写)
FLASH的存储特性
1.在写入数据之前必须先擦除
2.擦除时会把数据位全重置为1
3.写入数据时只能把为1的数据位改成0
4.擦除时必须按最小单位来擦除(一般为扇区)
I2C物理层
Inter-Integrated Circuit
I2C协议层——基本读写信号
通讯的起始和停止信号
数据有效性
地址及数据方向
响应
I2C架构剖析
!I2C读写EEPROM实验编程要点
1.配置通讯使用的目标引脚为开漏模式
2.编写模拟I2C时序的控制函数
3.编写基本I2C按字节收发的函数
4.编写读写EEPROM存储内容的函数
5.编写测试程序,对读写数据进行校验
SPI物理层
SPI(Serial Peripheral Interface)
SPI协议层
SPI的四种模式
时钟极性:CPOL=0时,SCK在空闲状态为低电平;CPOL=1时,SCK在空闲状态为高电平
时钟相位:CPHA=0时,信号在SCK时钟线的奇数边沿被采样;CPHA=1时,信号在SCK时钟线的偶数边沿被采样
SPI架构剖析
SPI通讯过程
!SPI读写串行FLASH实验编程要点
(1) 初始化通讯使用的目标引脚及端口时钟;
(2) 使能SPI 外设以及引脚相关的时钟,配置并初始化四个引脚;
(3) 配置SPI 外设的模式、地址、速率等参数,并使能SPI 外设;
(4) 编写基本SPI 按字节收发的函数;
(5) 编写对FLASH 擦除及读写操作的的函数;
(6) 编写测试程序,对读写数据进行校验。
备注:(4)等到发送缓冲区为空时,表示可能存在的上一个数据已经发送完毕,可以调用senddata发送数据了;当接收缓冲区非空时,表示上面的数据发送完毕,且接收缓冲器也收到新的数据,可以调用receivedata读取数据了。接收数据函数只需要调用发送函数发送数据Dummy_Byte即可获取返回值,收发同步进行。
ADC功能框图
!ADC电压采集实验编程要点
- 初始ADC用到的GPIO
- 设置ADC的工作参数并初始化
- 设置ADC工作时钟
- 设置ADC转换通道顺序及采样时间
- 配置使能ADC转换完成中断,在中断内读取转换完的数据
- 使能ADC
- 使能软件触发ADC转换
ADC 转换结果数据使用中断方式读取,这里没有使用DMA 进行数据传输。
DAC功能框图
!DAC输出正弦波实验编程要点
- 计算获取正弦波数据表
- 根据正弦波数据表的周期内点数和周期计算定时器触发间隔
- 初始化DAC输出通道,初始化DAC工作模式
- 配置触发DAC用的定时器
- 配置DMA自动转运正弦波数据表
配置完成后,即可在PA4、PA5引脚中检测到信号输出
智能小车项目
硬件
元件选型购买
原理图绘制、PCB布局和走线
焊接PCB
组装各模块
软件
- 点亮小灯——环境搭建、工程模板、GPIO初始化和控制
- 电机控制——GPIO输出、定时器的PWM输出功能
- 舵机控制——定时器PWM输出功能
- 按键-外部中断——GPIO输入、外部中断
- 红外对管-轮询——GPIO输入功能
- 蓝牙控制——蓝牙模块使用、单片机串口接收数据——编写中断服务函数
- 单片机串口发送数据到蓝牙模块——串口发送数据
- OLED显示数据——OLED的使用
- ADC测量电池电压——ADC配置、分压电路
- 超声波测距
- 测距跟随
- 超声波结合舵机避障
- APP和按键选择小车运动模式
程序移植与点灯烧录
ZET6移植到C8T6、更改端口PC13实现点灯烧录
总体设计方案
电机驱动
TB6612——初始化电机驱动对应的GPIO端口
GPIO高低电平控制AIN和BIN(AIN为左轮正反转,BIN为右轮正反转)
PWM控制PWMA和PWMB(左右轮的转速)
1.原理图PWMA和PWMB依次连接PA11和PA8
2.参考手册关于定时器复用功能重映射的介绍
3.初始化外设
- 配置对应引脚功能
- 初始化TIM1
- 初始化TIM1相应通道的PWM模式
- 使能
- 注意输出使能,对于高级定时器必须使用TIM_CtrlPWMOutputs
TIM_CtrlPWMOutputs(TIM_TypeDef* TIMx, FunctionalState NewState)
测试:调用初始化函数,改变占空比。
时钟树
原理
TIM向上计数模式,定时器模式为脉冲宽度调制模式2,TIM输出比较极性高(即有效电平为高电平,有效点平指的是计数值达到CCR之后)
一个周期的频率就为100HZ,占空比指的是:高电平/(高电平+低电平)
小车左右转前进后退函数编写
电机驱动
舵机控制
180度舵机,上电复位至0度,通过脉冲信号控制角度,控制参数如下:
由脉冲周期20ms可得,需要输出50hz的PWM波
舵机端口为PA6,对应定时器复用功能重映像为TIM3_CH1,不需要开启部分重映射
循迹功能——按键与红外对管
按键
两个按键外部引脚KEY1-PA7、KEY2-PA12
通过原理图可以得知,
KEY1-PA7应该配置成下拉输入,上升沿触发
KEY2-PA12应该配置成上拉输入,下降沿触发
配置中断初始化结构体:配置中断线、触发方式、外部中断通道(这里大于4的有特殊表示为EXTI9_5_IRQn)
配置中断服务函数
红外对管
红外对管前面是黑色时,DO引脚为高电平,二极管熄灭;
红外对管前面是红色时,DO引脚为低电平,二极管点亮。
可以把红外对管看成“按键”,当前面有黑色时为高电平,所以为上升沿触发
查看原理图 依次连接PB5、PB4、PB3、PA15
有一些端口在复位之后会改变功能,所以需要关闭JTAG-DP,重映射配置应写为GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
循迹功能
根据实际情况写逻辑,左转和右转,中间两个为1延迟时间短一些,两边为1延迟时间长一些,一侧的两个灯都亮则延迟时间设置最长,从而实现循迹功能。
串口接收发送
STM32串口初始化
由于中断服务函数不要处理大量的运算,故在该服务函数里面只改变标志位,在主程序中根据标志位变化执行任务。
在main中定义标志位
int g_USART1_FLAG1 = 0;//串口控制标志位
在usart.h中声明变量
extern int g_USART1_FLAG1;
测试单片机串口
先测试单片机的串口和电脑通讯没问题之后,再连接其他外设
TTL插入电脑,使用串口助手——选择端口——更改波特率115200——发送数据
现象发送A或B可以使小灯反转,证明通讯成功
配置蓝牙
更改蓝牙波特率
在AT模式下发送AT指令:AT+UART=115200.0.0
测试蓝牙
在115200波特率下可以收发消息
蓝牙控制小灯
通过发送A或者B控制单片机小灯反转,完成了蓝牙的基本控制
初始化串口3,开启串口接受中断,使能串口3
蓝牙控制小车运动
编写USART中断服务函数
main中的逻辑
数据发送给手机
定义函数发送不定参数据
OLED显示
OLED_SCL PC14
OLED_SDA PC15
移植OLED的头文件和库文件——复制相关文件到工程,KEIL中添加文件,添加头文件路径
根据原理图更改初始化函数——IO口初始化
修改OLED_Init()函数,修改oled.h中的宏
主程序
OLED_ShowString显示数字或字符
ADC测量电池电压
电池电压12V,单片机ADC最大测量电压3.3V,需要分压电路分压,通过测量ADC电压就可以计算VBAT_IN的电压
ADC PA4,默认复用功能ADC12_IN4,配置ADC1的通道4
移植程序,把例程的HARDWAER中ADC文件夹拷贝到自己的工程中,KEIL中添加文件,添加头文件路径
超声波测距模块
测距原理
MCU给Trig脚一个触发信号(大于10微秒的高电平脉冲),然后读取Echo脚的高电平信号时间,通过公式:距离 = T*声速/2可以算出距离。
软件方面:高电平脉冲通过GPIO输出实现,信号时间通过定时器的输入捕获来计算
SR04_Trig——PA0
SR04_Echo——PA1 定时器2的通道2
初始化脉冲引脚PA0
配置GPIOA,端口为0
led.h有关宏定义和声明
#define SR04 PAout(0);//PA0
void SR04_GPIO_Init(void);
初始化PA1输入捕获
初始化定时器2 通道2 输入捕获相关功能
配置输入捕获函数 TIM2_Cap_Init()和定时器2中断服务函数
在time.h中声明初始化函数
计算输出距离
在main.c中声明变量
extern u8 TIM5CH1_CAPTURE_STA;//输入捕获状态
exterm u16 TIM5CH1_CAPTURE_VAL;//输入捕获值
定义变量
int Distance = 0;
int time = 0;
调用初始化函数
SR04_GPIO_Init();
TIM2_Cap_Init(0XFFFF,72-1);//以1Mhz的频率计数
完成测距的函数
封装函数方便调用
编写定距离跟随功能
写逻辑,当距离大于20时,往前走;当距离小于15时,往后退。后面将电机口置0
结合舵机完成避障功能
通过舵机旋转不同角度,超声波测量左右是否存在障碍物,控制小车运动
测试舵机的转角,不同占空比小车舵机的角度。
写避障逻辑
TIM_SetCompare1(TIM3,80);//舵机向前,使超声波朝前方
delay_ms(500);
综合——将上述功能缝合
- 小车具有红外对管循迹、蓝牙控制、定距离跟随、避障运动模式
- 可以通过小车按键和APP进行切换小车的运动模式
- APP与OLED显示小车所处模式和超声波测量值、电池电压
编写串口3的中断服务函数和按键的中断服务函数、主函数逻辑
手机APP-蓝牙调试助手设置
串口3中断服务函数
void USART3_IRQHandler(void) //串口1中断服务程序
{
u8 Res;
if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾)
{
Res =USART_ReceiveData(USART3); //读取接收到的数据
if(Res =='A') g_USART3_FLAG =1;
if(Res =='B') g_USART3_FLAG =2;
if(Res =='C') g_USART3_FLAG =3;
if(Res =='D') g_USART3_FLAG =4;
if(Res =='E') g_USART3_FLAG =5;
if(Res == 'F') Mode =1;
if(Res == 'G') Mode =2;
if(Res == 'H') Mode =3;
if(Res == 'I') Mode =4;
if(Res == 'J') Mode =0;
}
}
按键中断处理函数
/**
* @brief 按键KEY1 PA7 中断处理函数
* @param
* @return
*/
void EXTI9_5_IRQHandler(void)
{
delay_ms(10);//消抖
if(KEY1==1) //按键KEY1 PA7
{
if(Mode == 4) Mode =1;
else Mode = Mode +1;
LED=!LED;
}
EXTI_ClearITPendingBit(EXTI_Line7); //清除LINE4上的中断标志位
}
/**
* @brief 按键KEY2 PA12 中断处理函数
* @param
* @return
*/
void EXTI15_10_IRQHandler(void)
{
delay_ms(10);//消抖
if(KEY2==0) //按键KEY2
{
Mode = 0;
LED=!LED;
}
EXTI_ClearITPendingBit(EXTI_Line12); //清除LINE4上的中断标志位
}
main函数中的循环
while(1)
{
//OLED 显示ADC测量
adcx=Get_Adc_Average(ADC_Channel_4,10);
temp=(float)adcx*(3.3/4096);
sprintf((char *)string,"U:%.2f ",(temp*5));
OLED_ShowString(12,0,string,16);
//OLED显示距离功能
sprintf((char *)string,"D:%d ",SR04_Distance());
OLED_ShowString(12,3,string,16);
//OLED显示模式功能
sprintf((char *)string,"Mode:%d ",Mode);
OLED_ShowString(12,6,string,16);
//串口输出
UsartPrintf(USART3,"Mode:%d\r\n",Mode);
UsartPrintf(USART3,"U:%.2f\r\n",(temp*5));
UsartPrintf(USART3,"D:%d \r\n",SR04_Distance());
if(Mode == 0)
{
//停止模式
TIM_SetCompare1(TIM3,80); //舵机向前 使超声波朝前方
delay_ms(200);
AIN1 =0;
AIN2 =0;
BIN1 =0;
BIN2 =0;
}
if(Mode == 3)
{
//超声波避障
TIM_SetCompare1(TIM3,80); //舵机向前 使超声波朝前方
delay_ms(200);
if(SR04_Distance()>25)// 前方无障碍物
{
Forward();
delay_ms(500);
}
if(SR04_Distance()<25) //向前有障碍物
{
TIM_SetCompare1(TIM3,50); //舵机向右边转大约30度
delay_ms(200);
if(SR04_Distance()>25)//右侧无障碍物判断
{
Rightward();
delay_ms(700);
}
else { //右边有障碍物
TIM_SetCompare1(TIM3,110); //舵机向左边转大约30度
delay_ms(200);
if(SR04_Distance()>25)//左侧无障碍物
{
Leftward();
delay_ms(700);
}
else{
Backward();//后退
delay_ms(700);
Rightward(); //右转
delay_ms(700);
}
}
}
}
if(Mode == 1)
{
//定距离跟随
if(SR04_Distance()>20)
{
Forward();
delay_ms(50);
}
if(SR04_Distance()<15)
{
Backward();
delay_ms(50);
}
AIN1 =0;
AIN2 =0;
BIN1 =0;
BIN2 =0;
}
if(Mode == 2)
{
//蓝牙遥控
if(g_USART3_FLAG == 5)//停
{
LED =~LED;
AIN1 =0;
AIN2 =0;
BIN1 =0;
BIN2 =0;
}
if(g_USART3_FLAG == 1)//前
{
LED =~LED;
Forward();
delay_ms(100);
}
if(g_USART3_FLAG == 2)//后
{
LED =~LED;
Backward();
delay_ms(100);
}
if(g_USART3_FLAG == 3)//右
{
LED =~LED;
Rightward();
}
if(g_USART3_FLAG == 4)//左
{
LED =~LED;
Leftward();
delay_ms(100);
}
}
if(Mode == 4)
{
//红外对管循迹
if(HW_1 == 0 && HW_2 == 0 && HW_3 == 0 && HW_4 == 0)
{
Forward();
delay_ms(80);
AIN1 =0;
AIN2 =0;
BIN1 =0;
BIN2 =0;
delay_ms(10);
}
if(HW_1 == 0 && HW_2 == 1 && HW_3 == 0 && HW_4 == 0)
{
Rightward();
delay_ms(100);
}
if(HW_1 == 1 && HW_2 == 0 && HW_3 == 0 && HW_4 == 0)
{
Rightward();
delay_ms(150);
}
if(HW_1 == 1 && HW_2 == 1 && HW_3 == 0 && HW_4 == 0)
{
Rightward();
delay_ms(200);
}
if(HW_1 == 0 && HW_2 == 0 && HW_3 == 1 && HW_4 == 0)
{
Leftward();
delay_ms(100);
}
if(HW_1 == 0 && HW_2 == 0 && HW_3 == 0 && HW_4 == 1)
{
Leftward();
delay_ms(150);
}
if(HW_1 == 0 && HW_2 == 0 && HW_3 == 1 && HW_4 == 1)
{
Leftward();
delay_ms(200);
}
}
}
转自:https://blog.csdn.net/xiangchaoyuchang/article/details/130297353