1、分析LED1灯电路图
1. 分析电路图的连接关系,操作的外设(比如:LED,KEY)连接到SOC的哪个GPIO引脚。
2. 分析控制外设电路图的工作的原理,比如LED灯的工作的原理。
1.1 分析电路图的连接关系
灯的网络标识,LED1
在根据原理图找到LED1,LED2,LED3连接的GPIO引脚,后面配置的时候就配置相应的组
1.2 分析电路图的工作原理
三极管的分类:
NPN三极管 PNP三极管
三极管的状态:
放大,饱和,截止
NPN : 基极为高电平时,集电极和发射极导通;
基极为低电平时,集电极和发射极截止。
PNP : 基极为高电平时,集电极和发射极截止;
基极为低电平时,集电极和发射极导通。
LED1灯的工作原理:
PE10引脚输出高电平时,NPN三极管导通,LED1灯亮;
PE10引脚输出低电平时,NPN三极管截止,LED1灯灭。
编写驱动程序,控制PE10引脚输出高电平,LED1灯亮;
控制PE10引脚输出低电平,LED1灯灭;
2、分析芯片手册
1. 如何编写驱动程序,控制对应的外设工作。
2. 掌握如何分析芯片手册,以及分析芯片手册的技巧。
2.1 分析GPIO章节
上下拉电阻:
GPIO外设控制器框图:
MOS管的工作原理:
推挽输出和开漏输出:
mos管具有开关的特性
NMOS : 栅极为高电平时,源极和漏极导通
栅极为低电平时,源极和漏极截止
PMOS : 栅极为高电平时,源极和漏极截止
栅极为低电平时,源极和漏极导通
推挽输出:
开漏输出:
编程的思路:以LED1灯为例 ---> PE10引脚
// PE10引脚的初始化操作
配置GPIOE_MODER寄存器,配置PE10引脚为输出模式;
配置GPIOE_OTYPER寄存器,配置PE10引脚为推挽输出;
配置GPIOE_OSPEEDR寄存器,配置PE10引脚的速度;
配置GPIOE_PUPDR寄存器,配置PE10引脚的上下拉电阻;
// 配置PE10引脚输出高低电平
配置GPIOE_ODR寄存器,配置PE10引脚输出高电平
配置GPIOE_ODR寄存器,配置PE10引脚输出低电平
2.1.1 配置GPIOE_MODER寄存器,配置PE10引脚为输出模式;
2.1.2 配置GPIOE_OTYPER寄存器,配置PE10引脚为推挽输出;
2.1.3 配置GPIOE_OSPEEDR寄存器,配置PE10引脚的速度;
2.1.4 配置GPIOE_PUPDR寄存器,配置PE10引脚的上下拉电阻;
2.1.5 配置GPIOE_ODR寄存器,配置PE10引脚输出高低电平
2.2 分析2.5.2章节,确定GPIOE外设接到哪根总线上,以及外设寄存器对应的基地址
2.3 分析RCC章节
3、编写驱动代码
led.c
#include "../include/led.h"
void led_init(void){
//led1初始化
//使能led1时钟
*RCC_MP_AHB4ENSETR &=~(1<<4);
*RCC_MP_AHB4ENSETR |=(1<<4);
//GPIOE MODER;
GPIOE->MODER &=~(0b11<<20);
GPIOE->MODER |=(0b01<<20);
//GPIOE OTYPER;
GPIOE->OTYPER &=~(1<<10);
//GPIOE OSPEEDR;
GPIOE->OSPEEDR &=~(0b00<<20);
//GPIOE PUPDR;
GPIOE->PUPDR &=~(0b00<<20);
//led2初始化
//使能led1时钟
*RCC_MP_AHB4ENSETR &=~(1<<5);
*RCC_MP_AHB4ENSETR |=(1<<5);
//GPIOF MODER;
GPIOF->MODER &=~(0b11<<20);
GPIOF->MODER |=(0b01<<20);
//GPIOF OTYPER;
GPIOF->OTYPER &=~(1<<10);
//GPIOF OSPEEDR;
GPIOF->OSPEEDR &=~(0b00<<20);
//GPIOF PUPDR;
GPIOF->PUPDR &=~(0b00<<20);
//led3初始化
//使能led1时钟
*RCC_MP_AHB4ENSETR &=~(1<<4);
*RCC_MP_AHB4ENSETR |=(1<<4);
//GPIOE MODER;
GPIOE->MODER &=~(0b11<<16);
GPIOE->MODER |=(0b01<<16);
//GPIOE OTYPER;
GPIOE->OTYPER &=~(1<<8);
//GPIOE OSPEEDR;
GPIOE->OSPEEDR &=~(0b00<<16);
//GPIOE PUPDR;
GPIOE->PUPDR &=~(0b00<<16);
}
void led_kaiguan(led_t leds,led_statue_t led_statue){
switch (leds)
{
case LED1:if(led_statue==LED_on){
GPIOE->ODR |=(LED_on<<10);
}else{
GPIOE->ODR &=~(LED_on<<10);
}
break;
case LED2:if(led_statue==LED_on){
GPIOF->ODR |=(LED_on<<10);
}else{
GPIOF->ODR &=~(LED_on<<10);
}
break;
case LED3:if(led_statue==LED_on){
GPIOE->ODR |=(LED_on<<8);
}else{
GPIOE->ODR &=~(LED_on<<8);
}
break;
}
}
main.c
#include "include/led.h"
void delay_ms(unsigned int ms){
int i, j;
for (i = 0; i < ms; i++)
for (j = 0; j < 1800; j++)
;
}
int main(){
led_init();
led_kaiguan(LED1,LED_on);
while (1){
led_kaiguan(LED1,LED_on);
delay_ms(1000);
led_kaiguan(LED1,LED_off);
delay_ms(1000);
led_kaiguan(LED2,LED_on);
delay_ms(1000);
led_kaiguan(LED2,LED_off);
delay_ms(1000);
led_kaiguan(LED3,LED_on);
delay_ms(1000);
led_kaiguan(LED3,LED_off);
delay_ms(1000);
}
}
单片机的代码一般都分文件编程,上面的方式不常用,下面是自己封装固件库的方式
按照不同功能分成不同的文件led.c led.h gpio.c gpio.h main.c
代码要有可读性,为了增加可读性,以及方便使用,采取封装库的方式,使调用者一看便知在干嘛
以hal库为例
思路:
1.gpio.h
将寄存器地址宏定义,这些在我的工程的common/include里建好了,我这里不写了
将所需gpio相关寄存器中的选项用枚举形式列举,后续有外设使用直接调用hal_gpio_init,
把枚举里的选项填进去即可
#ifndef _GPIO_H_
#define _GPIO_H_
#include "../common/include/stm32mp1xx_gpio.h"
#include "../common/include/stm32mp1xx_rcc.h"
typedef unsigned int uint32_t;
#define GPIO_PIN_0 0x0001U
#define GPIO_PIN_1 0x0002U
#define GPIO_PIN_2 0x0004U
#define GPIO_PIN_3 0x0008U
#define GPIO_PIN_4 0x0010U
#define GPIO_PIN_5 0x0020U
#define GPIO_PIN_6 0x0040U
#define GPIO_PIN_7 0x0080U
#define GPIO_PIN_8 0x0100U
#define GPIO_PIN_9 0x0200U
#define GPIO_PIN_10 0x0400U
#define GPIO_PIN_11 0x0800U
#define GPIO_PIN_12 0x1000U
#define GPIO_PIN_13 0x2000U
#define GPIO_PIN_14 0x4000U
#define GPIO_PIN_15 0x8000U
typedef enum mode{
Input_mode=0,
General_purpose_output_mode=0b01,
Alternate_function_mode=0b10,
Analog_mode=0b11,
}mode_t;
typedef enum type{
Output_push_pull=0,
Output_open_drain,
}type_t;
typedef enum speed{
Low_speed=0,
Medium_speed=0b01,
High_speed=0b10,
Very_high_speed=0b11,
}speed_t;
typedef enum pupd{
No_pull_up_down=0,
Pull_up=0b01,
Pull_down=0b10,
}pupd_t;
typedef enum {
GPIO_Reset_Pin = 0,
GPIO_Set_Pin,
} gpio_statu_t;
typedef struct gpio{
uint32_t pins;
mode_t mode;
type_t type;
speed_t speed;
pupd_t pupd;
}gpioinit_t;
//这里要用地址,因为要改变GPIOx中的值,用值传递的话只能在函数内部
//改变,函数结束后外部的值没变
void hal_gpio_init(gpio_t *GPIOx,gpioinit_t *InitStruct);
void hal_gpio_writePin(gpio_t *GPIOx, uint32_t pins, gpio_statu_t statu);
/*
* 功能:gpio输入电平的状态
* 参数:
* @ GPIOx :初始化哪个组的GPIO引脚 , GPIOx(A-K,Z)
* @ pins : 哪个gpio引脚, GPIO_PIN_0 ~ GPIO_PIN_15
* 返回值:
* @ 引脚输入的状态
*/
gpio_statu_t hal_gpio_readPin(gpio_t *GPIOx, uint32_t pins);
/*
* 功能:gpio输出电平状态翻转的状态
* 参数:
* @ GPIOx :初始化哪个组的GPIO引脚 , GPIOx(A-K,Z)
* @ pins : 哪个gpio引脚, GPIO_PIN_0 ~ GPIO_PIN_15
* 返回值:
* 无
*/
void hal_gpio_tooglePin(gpio_t *GPIOx, uint32_t pins);
#endif /*_GPIO_H_*/
2.gpio.c
完成通用的hal_gpio_init函数,在这里写通用的操作寄存器的代码
#include "../include/gpio.h"
void hal_gpio_init(gpio_t *GPIOx,gpioinit_t *InitStruct){
//初始化参数没传引脚,但是初始化需要直到引脚是多少,才能正确偏移
//led初始化灯的时候会传,可以从参数的InitStruct里获取到
uint32_t pinnum=0;
for(;pinnum<15;pinnum++){
if(InitStruct->pins & (1<<pinnum)){
GPIOx->MODER &= ~(0x3 << (pinnum * 2));
GPIOx->MODER |= (InitStruct->mode << (pinnum * 2));
GPIOx->OTYPER &= ~(0x1 << pinnum );
GPIOx->OTYPER |=(InitStruct->type <<pinnum);
GPIOx->OSPEEDR &= ~(0x3 << (pinnum * 2));
GPIOx->OSPEEDR |= (InitStruct->mode << (pinnum * 2));
GPIOx->PUPDR &= ~(0x3 << (pinnum * 2));
GPIOx->PUPDR |= (InitStruct->mode << (pinnum * 2));
}
}
}
void hal_gpio_writePin(gpio_t *GPIOx, uint32_t pins, gpio_statu_t statu){
if(statu==GPIO_Set_Pin){
GPIOx->ODR |=pins;
}else{
GPIOx->ODR &=~pins;
}
}
gpio_statu_t hal_gpio_readPin(gpio_t *GPIOx, uint32_t pins){
gpio_statu_t ret;
if(GPIOx->IDR & pins){//相等说明引脚输出高电平,所以返回1
ret=GPIO_Set_Pin;
}else{
ret=GPIO_Reset_Pin;
}
return ret;
}
void hal_gpio_tooglePin(gpio_t *GPIOx, uint32_t pins){
GPIOx->ODR ^=pins;
}
3.led.h
枚举灯的状态和灯
#ifndef _LED_H_
#define _LED_H_
#include "../include/gpio.h"
typedef enum ledstatue{
led_off,
led_on,
}ledstatue_t;
typedef enum led
{
LED1=1,
LED2,
LED3,
}led_t;
void led_init();
void led_open_off(led_t ledx,ledstatue_t ledstatue);
#endif /*_LED_H_*/
4.led.c
灯的初始化函数,led_init,在里面调用hal_gpio_init
#include "../include/gpio.h"
#include "../include/led.h"
void led_init(){
//使能GPIOE,GPIOF外设的时钟源 RCC_MP_AHB4ENSETR[5][4]
RCC->MP_AHB4ENSETR |=(0b11<<4);
gpioinit_t ledinitS;
ledinitS.pins=GPIO_PIN_10 |GPIO_PIN_8;
ledinitS.mode=General_purpose_output_mode;
ledinitS.type=Output_push_pull;
ledinitS.speed=Low_speed;
ledinitS.pupd=No_pull_up_down;
hal_gpio_init(GPIOE,&ledinitS);
ledinitS.pins=GPIO_PIN_10;//GPIOF只有10引脚需要被初始化,其他不用动,只需改下pins即可
hal_gpio_init(GPIOF,&ledinitS);
}
void led_open_off(led_t ledx,ledstatue_t ledstatue){
switch (ledx)
{
case LED1:
if (ledstatue == led_off)
hal_gpio_writePin(GPIOE, GPIO_PIN_10, GPIO_Reset_Pin);
else
hal_gpio_writePin(GPIOE, GPIO_PIN_10, GPIO_Set_Pin);
break;
case LED2:
if (ledstatue == led_off)
hal_gpio_writePin(GPIOF, GPIO_PIN_10, GPIO_Reset_Pin);
else
hal_gpio_writePin(GPIOF, GPIO_PIN_10, GPIO_Set_Pin);
break;
case LED3:
if (ledstatue == led_off)
hal_gpio_writePin(GPIOE, GPIO_PIN_8, GPIO_Reset_Pin);
else
hal_gpio_writePin(GPIOE, GPIO_PIN_8, GPIO_Set_Pin);
break;
}
}
main.c
#include "include/led.h"
void delay_ms(unsigned int ms){
int i, j;
for (i = 0; i < ms; i++)
for (j = 0; j < 1800; j++)
;
}
int main(){
led_init();
led_open_off(LED1,led_on);
while (1){
led_open_off(LED1,led_on);
delay_ms(1000);
led_open_off(LED1,led_off);
delay_ms(1000);
led_open_off(LED2,led_on);
delay_ms(1000);
led_open_off(LED2,led_off);
delay_ms(1000);
led_open_off(LED3,led_on);
delay_ms(1000);
led_open_off(LED3,led_off);
delay_ms(1000);
}
}
4、编译,下载,调试
编译没问题