十四、ARM体系结构与接口技术(下)

1. UART驱动编程实验

1.1 基本概念

  • 串口
    串口,官方名称叫做UART(Universal Asynchronous Receiver/Transmitter),即串行异步
    收发器。按电气标准及协议来分包括RS-232、RS-422、RS-485等。


    1.jpeg
  • 串行通信 并行通信


  • 单工通信 双工通信


  • 同步通信 异步通信
    同步通信,一般情况下同步通信指的是通信双方根据同步时钟信号进行通信的方式。
    比如通信双方有一个共同的时钟信号,大家根据时钟信号的变化进行通信。
    异步通信,是指数据传输速度匹配依赖于通信双方有自己独立的系统时钟,大家约定好通信的速度。
    异步通信不需要同步信号,但是并不是说通信的过程不同步。


  • 起始位,空闲状态为高电平,由空闲状态开始的一个周期的低电平规定为起始位(start信号)
  • 数据位, 5~8位的数据位,先低后高(LSB)。通常为8位
  • 校验位,奇校验/偶校验/无校验
    奇校验, 如果传输的数据位为8位,校验位1位, 那么这9个bit 1的个数保证为奇数个
    偶校验,如果传输的数据位为8位,校验位1位, 那么这9个bit 1的个数保证为偶数个
    无校验, 无校验位, 数据位后面直接就是停止位
  • 1~2bit的停止位。即可以是1/1.5/2个周期的高电平
  • 波特率, 单位bps(bit per second),即每秒钟发送的bit位个数。
    发送01 和 0011如何区分?
    各自用各自的表计时,肯定有误差,累积误差。

可以看到数据位,校验位,停止位,波特率是可变的,通信前双方要协商一致。

  • 电气特性
    通信过程中传输的应该是数据, 而导线中实际存在的是电信号。
    电子工业协会规定,RS232通信中电信号和逻辑0、逻辑1的对应关系。
    逻辑1, -3V ~ -15V之间的电平
    逻辑0, +3V ~ +15V之间的电平
    称作EIA电平。
    题外话,TTL电平。 >2.4V即为逻辑1, <0.8V即为逻辑0。


该电气特性,也导致了RS232存在以下不足:
1)接口的信号电平值较高,易损坏接口电路的芯片
2)传输速率较低
3)共地传输容易产生共模干扰,所以抗噪声干扰性弱
4)传输距离短,一般不超过10米
RS422是RS232的改进型,而RS485是RS422的改进型。
RS485一般用于工业环境中,数据最高传输速率为10Mbps,最大的通信距离超过1000米。

1.2 电路原理图分析


1.3 datasheet分析

1.3.1 使能控制器时钟
1.3.2 配置管脚为UART收发功能
1.3.3 配置uart控制器完成数据收发

1.3.3.1 总体概述

  • uart总体框架


STM32MP157中的uart控制器不但可以通过配置作为简单串口控制器功能使用,通过配置还支持红外收发、同步半双工、智能卡通信协议等。

  • 数据发送过程


  • 根据波特率计算第二次分频的分频系数


1.3.3.2 寄存器配置

  • USART_BRR


  • USART_RDR


1.3.4 驱动编程

实现5个函数:
1)初始化uart4
2)发送1个字符
3)发送字符串
4)接收1个字符
5)接收字符串。

// include/stm32mp157_gpio.h
#ifndef __STM32MP157_GPIO__
#define __STM32MP157_GPIO__
typedef struct {
volatile unsigned int MODER;   // 0x00
volatile unsigned int OTYPER;  // 0x04
volatile unsigned int OSPEEDR; // 0x08
volatile unsigned int PUPDR;   // 0x0C
volatile unsigned int IDR;     // 0x10
volatile unsigned int ODR;     // 0x14
volatile unsigned int BSRR;    // 0x18
volatile unsigned int LCKR;    // 0x1C 
volatile unsigned int AFRL;    // 0x20 
volatile unsigned int AFRH;    // 0x24
volatile unsigned int BRR;     // 0x28
volatile unsigned int res;
volatile unsigned int SECCFGR; // 0x30
}gpio_t;
#define  GPIOA   ((gpio_t *)0x50002000)
#define  GPIOB   ((gpio_t *)0x50003000)
#define  GPIOC   ((gpio_t *)0x50004000)
#define  GPIOD   ((gpio_t *)0x50005000)
#define  GPIOE   ((gpio_t *)0x50006000)
#define  GPIOF   ((gpio_t *)0x50007000)
#define  GPIOG   ((gpio_t *)0x50008000)
#define  GPIOH   ((gpio_t *)0x50009000)
#define  GPIOI   ((gpio_t *)0x5000A000)
#define  GPIOJ   ((gpio_t *)0x5000B000)
#define  GPIOK   ((gpio_t *)0x5000C000)
#define  GPIOZ   ((gpio_t *)0x54004000)
#endif
// include/stm32mp157_rcc.h
#ifndef __STM32MP157_RCC__
#define __STM32MP157_RCC__
typedef struct {
    volatile unsigned int TZCR;   // 0x000  
    volatile unsigned int res1[2];     // 0x004-0x008 
    volatile unsigned int OCENSETR;     // 0x00C
    volatile unsigned int OCENCLRR;     // 0x010
    volatile unsigned int res2[1];      // 0x014
    volatile unsigned int HSICFGR;      // 0x018
    volatile unsigned int CSICFGR;      // 0x01C
    volatile unsigned int MPCKSELR;     // 0x020
    volatile unsigned int ASSCKSELR;    // 0x024
    volatile unsigned int PCK12SELR;    // 0x028
    volatile unsigned int MPCKDIVR;     // 0x02C
    volatile unsigned int AXIDIVR;      // 0x030
    volatile unsigned int res3[2];      
    volatile unsigned int APB4DIVR;     // 0x03C
    volatile unsigned int APB5DIVR;     // 0x040
    volatile unsigned int RTCDIVR;      // 0x044
    volatile unsigned int MSSCKSELR;    // 0x048
    volatile unsigned int res4[13];
    volatile unsigned int PLL1CR;       // 0x080
    volatile unsigned int PLL1CFGR1;    // 0x084
    volatile unsigned int PLL1CFGR2;    // 0x088
    volatile unsigned int PLL1FRACR;    // 0x08C
    volatile unsigned int PLL1CSGR;     // 0x090
    volatile unsigned int PLL2CR;       // 0x094
    volatile unsigned int PLL2CFGR1;    // 0x098
    volatile unsigned int PLL2CFGR2;    // 0x09C
    volatile unsigned int PLL2FRACR;    // 0x0A0
    volatile unsigned int PLL2CSGR;     // 0x0A4
    volatile unsigned int res5[6];
    volatile unsigned int I2C46CKSELR;  // 0x0C0
    volatile unsigned int SPI6CKSELR;   // 0x0C4
    volatile unsigned int UART1CKSELR;  // 0x0C8
    volatile unsigned int RNG1CKSELR;   // 0x0CC
    volatile unsigned int CPERCKSELR;   // 0x0D0
    volatile unsigned int STGENCKSELR;  // 0x0D4
    volatile unsigned int DDRITFCR;     // 0x0D8
    volatile unsigned int res6[9];
    volatile unsigned int MP_BOOTCR;    // 0x100
    volatile unsigned int MP_SREQSETR;  // 0x104
    volatile unsigned int MP_SREQCLRR;  // 0x108
    volatile unsigned int MP_GCR;       // 0x10C
    volatile unsigned int MP_APRSTCR;   // 0x110 
    volatile unsigned int MP_APRSTSR;   // 0x114
    volatile unsigned int res7[10];
    volatile unsigned int BDCR;         // 0x140
    volatile unsigned int RDLSICR;      // 0x144
    volatile unsigned int res8[14];
    volatile unsigned int APB4RSTSETR;  // 0x180
    volatile unsigned int APB4RSTCLRR;  // 0x184
    volatile unsigned int APB5RSTSETR;  // 0x188
    volatile unsigned int APB5RSTCLRR;  // 0x18C
    volatile unsigned int AHB5RSTSETR;  // 0x190
    volatile unsigned int AHB5RSTCLRR;  // 0x194
    volatile unsigned int AHB6RSTSETR;  // 0x198
    volatile unsigned int AHB6RSTCLRR;  // 0x19C
    volatile unsigned int TZAHB6RSTSELR;// 0x1A0
    volatile unsigned int TZAHB6RSTCLRR;// 0x1A4
    volatile unsigned int res9[22];
    volatile unsigned int MP_APB4ENSETR;// 0x200
    volatile unsigned int MP_APB4ENCLRR;// 0x204
    volatile unsigned int MP_APB5ENSETR;// 0x208
    volatile unsigned int MP_APB5ENCLRR;// 0x20C
    volatile unsigned int MP_AHB5ENSETR;// 0x210
    volatile unsigned int MP_AHB5ENCLRR;// 0x214
    volatile unsigned int MP_AHB6ENSETR;// 0x218
    volatile unsigned int MP_AHB6ENCLRR;// 0x21C
    volatile unsigned int MP_TZAHB6ENSELR;// 0x220
    volatile unsigned int MP_TZAHB6ENCLRR;// 0x224
    volatile unsigned int res10[22];
    volatile unsigned int MC_APB4ENSETR; // 0x280
    volatile unsigned int MC_APB4ENCLRR; // 0x284
    volatile unsigned int MC_APB5ENSETR; // 0x288
    volatile unsigned int MC_APB5ENCLRR; // 0x28C
    volatile unsigned int MC_AHB5ENSETR; // 0x290
    volatile unsigned int MC_AHB5ENCLRR; // 0x294
    volatile unsigned int MC_AHB6ENSETR; // 0x298
    volatile unsigned int MC_AHB6ENCLRR; // 0x29C
    volatile unsigned int res11[24];
    volatile unsigned int MP_APB4LPENSETR; // 0x300
    volatile unsigned int MP_APB4LPENCLRR; // 0x304
    volatile unsigned int MP_APB5LPENSETR; // 0x308
    volatile unsigned int MP_APB5LPENCLRR; // 0x30C
    volatile unsigned int MP_AHB5LPENSETR; // 0x310
    volatile unsigned int MP_AHB5LPENCLRR; // 0x314
    volatile unsigned int MP_AHB6LPENSETR; // 0x318
    volatile unsigned int MP_AHB6LPENCLRR; // 0x31C
    volatile unsigned int MP_TZAHB6LPENSETR; // 0x320
    volatile unsigned int MP_TZAHB6LPENCLRR; // 0x324
    volatile unsigned int res12[22];
    volatile unsigned int MC_APB4LPENSETR; // 0x380
    volatile unsigned int MC_APB4LPENCLRR; // 0x384
    volatile unsigned int MC_APB5LPENSETR; // 0x388
    volatile unsigned int MC_APB5LPENCLRR; // 0x38C
    volatile unsigned int MC_AHB5LPENSETR; // 0x390
    volatile unsigned int MC_AHB5LPENCLRR; // 0x394
    volatile unsigned int MC_AHB6LPENSETR; // 0x398
    volatile unsigned int MC_AHB6LPENCLRR; // 0x39C
    volatile unsigned int res13[24];
    volatile unsigned int BR_RSTSCLRR;      // 0x400
    volatile unsigned int MP_GRSTCSETR;     // 0x404
    volatile unsigned int MP_RSTSR;         // 0x408 
    volatile unsigned int MP_IWDGFZSETR;    // 0x40C
    volatile unsigned int MP_IWDGFZCLRR;    // 0x410
    volatile unsigned int MP_CIER;          // 0x414
    volatile unsigned int MP_CIFR;          // 0x418
    volatile unsigned int PWRLPDLYCR;       // 0x41C
    volatile unsigned int MP_RSTSS;         // 0x420
    volatile unsigned int res14[247];
    volatile unsigned int MCO1CFGR;         // 0x800
    volatile unsigned int MCO2CFGR;         // 0x804 
    volatile unsigned int OCRDYR;           // 0x808
    volatile unsigned int DBGCFGR;          // 0x80C
    volatile unsigned int res15[4];
    volatile unsigned int RCK3SELR;         // 0x820
    volatile unsigned int RCK4SELR;         // 0x824
    volatile unsigned int TIMG1PRER;        // 0x828
    volatile unsigned int TIMG2PRER;        // 0x82C
    volatile unsigned int MCUDIVR;          // 0x830
    volatile unsigned int APB1DIVR;         // 0x834
    volatile unsigned int APB2DIVR;         // 0x838
    volatile unsigned int APB3DIVR;         // 0x83C
    volatile unsigned int res16[16];
    volatile unsigned int PLL3CR;           // 0x880
    volatile unsigned int PLL3CFGR1;        // 0x884
    volatile unsigned int PLL3CFGR2;        // 0x888
    volatile unsigned int PLL3FRACR;        // 0x88C
    volatile unsigned int PLL3CSGR;         // 0x890
    volatile unsigned int PLL4CR;           // 0x894
    volatile unsigned int PLL4CFGR1;        // 0x898
    volatile unsigned int PLL4CFGR2;        // 0x89C
    volatile unsigned int PLL4FRACR;        // 0x8A0
    volatile unsigned int PLL4CSGR;         // 0x8A4
    volatile unsigned int res17[6];
    volatile unsigned int I2C12CKSELR;      // 0x8C0
    volatile unsigned int I2C35CKSELR;      // 0x8C4
    volatile unsigned int SAI1CKSELR;       // 0x8C8
    volatile unsigned int SAI2CKSELR;       // 0x8CC
    volatile unsigned int SAI3CKSELR;       // 0x8D0
    volatile unsigned int SAI4CKSELR;       // 0x8D4
    volatile unsigned int SPI2S1CKSELR;     // 0x8D8
    volatile unsigned int SPI2S23CKSELR;    // 0x8DC
    volatile unsigned int SPI45CKSELR;      // 0x8E0
    volatile unsigned int UART6CKSELR;      // 0x8E4
    volatile unsigned int UART24CKSELR;     // 0x8E8
    volatile unsigned int UART35CKSELR;     // 0x8EC
    volatile unsigned int UART78CKSELR;     // 0x8F0
    volatile unsigned int SDMMC12CKSELR;    // 0x8F4
    volatile unsigned int SDMMC3CKSELR;     // 0x8F8
    volatile unsigned int ETHCKSELR;        // 0x8FC
    volatile unsigned int QSPICKSELR;       // 0x900
    volatile unsigned int FMCCKSELR;        // 0x904
    volatile unsigned int res18[1];
    volatile unsigned int FDCANCKSELR;      // 0x90C
    volatile unsigned int res19[1];
    volatile unsigned int SPDIFCKSELR;      // 0x914
    volatile unsigned int CECCKSELR;        // 0x918
    volatile unsigned int USBCKSELR;        // 0x91C
    volatile unsigned int RNG2CKSELR;       // 0x920
    volatile unsigned int DSICKSELR;        // 0x924
    volatile unsigned int ADCCKSELR;        // 0x928
    volatile unsigned int LPTIM45CKSELR;    // 0x92C
    volatile unsigned int LPTIM23CKSELR;    // 0x930
    volatile unsigned int LPTIM1CKSELR;     // 0x934
    volatile unsigned int res20[18];
    volatile unsigned int APB1RSTSETR;      // 0x980
    volatile unsigned int APB1RSTCLRR;      // 0x984
    volatile unsigned int APB2RSTSETR;      // 0x988
    volatile unsigned int APB2RSTCLRR;      // 0x98C
    volatile unsigned int APB3RSTSETR;      // 0x990
    volatile unsigned int APB3RSTCLRR;      // 0x994
    volatile unsigned int AHB2RSTSETR;      // 0x998
    volatile unsigned int AHB2RSTCLRR;      // 0x99C
    volatile unsigned int AHB3RSTSETR;      // 0x9A0
    volatile unsigned int AHB3RSTCLRR;      // 0x9A4
    volatile unsigned int AHB4RSTSETR;      // 0x9A8
    volatile unsigned int AHB4RSTCLRR;      // 0x9AC
    volatile unsigned int res21[20];
    volatile unsigned int MP_APB1ENSETR;    // 0xA00
    volatile unsigned int MP_APB1ENCLRR;    // 0xA04
    volatile unsigned int MP_APB2ENSETR;    // 0xA08
    volatile unsigned int MP_APB2ENCLRR;    // 0xA0C
    volatile unsigned int MP_APB3ENSETR;    // 0xA10
    volatile unsigned int MP_APB3ENCLRR;    // 0xA14
    volatile unsigned int MP_AHB2ENSETR;    // 0xA18
    volatile unsigned int MP_AHB2ENCLRR;    // 0xA1C
    volatile unsigned int MP_AHB3ENSETR;    // 0xA20
    volatile unsigned int MP_AHB3ENCLRR;    // 0xA24
    volatile unsigned int MP_AHB4ENSETR;    // 0xA28
    volatile unsigned int MP_AHB4ENCLRR;    // 0xA2C
    volatile unsigned int res22[2];
    volatile unsigned int MP_MLAHBENSETR;   // 0xA38
    volatile unsigned int MP_MLAHBENCLRR;   // 0xA3C
    volatile unsigned int res23[16];
    volatile unsigned int MC_APB1ENSETR;    // 0xA80
    volatile unsigned int MC_APB1ENCLRR;    // 0xA84
    volatile unsigned int MC_APB2ENSETR;    // 0xA88
    volatile unsigned int MC_APB2ENCLRR;    // 0xA8C
    volatile unsigned int MC_APB3ENSETR;    // 0xA90
    volatile unsigned int MC_APB3ENCLRR;    // 0xA94
    volatile unsigned int MC_AHB2ENSETR;    // 0xA98
    volatile unsigned int MC_AHB2ENCLRR;    // 0xA9C
    volatile unsigned int MC_AHB3ENSETR;    // 0xAA0
    volatile unsigned int MC_AHB3ENCLRR;    // 0xAA4
    volatile unsigned int MC_AHB4ENSETR;    // 0xAA8
    volatile unsigned int MC_AHB4ENCLRR;    // 0xAAC
    volatile unsigned int MC_AXIMENSETR;    // 0xAB0
    volatile unsigned int MC_AXIMENCLRR;    // 0xAB4
    volatile unsigned int MC_MLAHBENSETR;   // 0xAB8
    volatile unsigned int MC_MLAHBENCLRR;   // 0xABC
    volatile unsigned int res24[16];
    volatile unsigned int MP_APB1LPENSETR;  // 0xB00
    volatile unsigned int MP_APB1LPENCLRR;  // 0xB04
    volatile unsigned int MP_APB2LPENSETR;  // 0xB08
    volatile unsigned int MP_APB2LPENCLRR;  // 0xB0C
    volatile unsigned int MP_APB3LPENSETR;  // 0xB10
    volatile unsigned int MP_APB3LPENCLRR;  // 0xB14
    volatile unsigned int MP_AHB2LPENSETR;  // 0xB18
    volatile unsigned int MP_AHB2LPENCLRR;  // 0xB1C
    volatile unsigned int MP_AHB3LPENSETR;  // 0xB20
    volatile unsigned int MP_AHB3LPENCLRR;  // 0xB24
    volatile unsigned int MP_AHB4LPENSETR;  // 0xB28
    volatile unsigned int MP_AHB4LPENCLRR;  // 0xB2C
    volatile unsigned int MP_AXIMLPENSETR;  // 0xB30
    volatile unsigned int MP_AXIMLPENCLRR;  // 0xB34
    volatile unsigned int MP_MLAHBLPENSETR; // 0xB38
    volatile unsigned int MP_MLAHBLPENCLRR; // 0xB3C
    volatile unsigned int res25[16];
    volatile unsigned int MC_APB1LPENSETR;  // 0xB80
    volatile unsigned int MC_APB1LPENCLRR;  // 0xB84
    volatile unsigned int MC_APB2LPENSETR;  // 0xB88
    volatile unsigned int MC_APB2LPENCLRR;  // 0xB8C
    volatile unsigned int MC_APB3LPENSETR;  // 0xB90 
    volatile unsigned int MC_APB3LPENCLRR;  // 0xB94
    volatile unsigned int MC_AHB2LPENSETR;  // 0xB98
    volatile unsigned int MC_AHB2LPENCLRR;  // 0xB9C
    volatile unsigned int MC_AHB3LPENSETR;  // 0xBA0 
    volatile unsigned int MC_AHB3LPENCLRR;  // 0xBA4
    volatile unsigned int MC_AHB4LPENSETR;  // 0xBA8
    volatile unsigned int MC_AHB4LPENCLRR;  // 0xBAC
    volatile unsigned int MC_AXIMLPENSETR;  // 0xBB0
    volatile unsigned int MC_AXIMLPENCLRR;  // 0xBB4
    volatile unsigned int MC_MLAHBLPENSETR; // 0xBB8
    volatile unsigned int MC_MLAHBLPENCLRR; // 0xBBC
    volatile unsigned int res26[16];// 0xC00
    volatile unsigned int MC_RSTSCLRR;      // 0xC14
    volatile unsigned int res27[4];// 0xC18

    volatile unsigned int MC_CIER;       
    volatile unsigned int MC_CIFR;        
    volatile unsigned int res28[246];
    volatile unsigned int VERR;            // 0xFF4    
    volatile unsigned int IDR;           // 0xFF8     
    volatile unsigned int SIDR;     // 0xFF        
}rcc_t;

#define RCC   ((rcc_t *)0x50000000)

#endif
// include/stm32mp157_uart.h
#ifndef __STM32MP157_UART_H__
#define __STM32MP157_UART_H__
typedef struct {
volatile unsigned int CR1;
volatile unsigned int CR2;
volatile unsigned int CR3;
volatile unsigned int BRR;
volatile unsigned int GTPR;
volatile unsigned int RTOR;
volatile unsigned int RQR;
volatile unsigned int ISR;
volatile unsigned int ICR;
volatile unsigned int RDR;
volatile unsigned int TDR;
volatile unsigned int PRESC;
}uart_t;
#define USART1  ((uart_t *)0x5C000000)
#define USART2  ((uart_t *)0x4000E000)
#define USART3  ((uart_t *)0x4000F000)
#define USART4  ((uart_t *)0x40010000)
#define USART5  ((uart_t *)0x40011000)
#define USART6  ((uart_t *)0x44003000)
#define USART7  ((uart_t *)0x40018000)
#define USART8  ((uart_t *)0x40019000)
#endif
//uart.h
#ifndef __UART_H__
#define __UART_H__
void uart_init(void);
void uart_puts(char *str);
int uart_gets(char *, int);
#endif
//uart.h
#include"../include/stm32mp157_gpio.h"
#include"../include/stm32mp157_rcc.h"
#include"../include/stm32mp157_uart.h"

void uart_init(){
    /*使能GPIOB GPIOG UART控制器时钟*/
    RCC->MP_AHB4ENSETR |= (1<<1 | 1<<6);
    RCC->MP_APB1ENSETR |= (1<<16);
    /*配置GPIOB GPIOG 设置对应管脚为uart收发功能*/
    GPIOB->MODER &= ~(0x3<<4);
    GPIOB->MODER |= (0x2<<4);
    GPIOG->MODER &= ~(0x3<<22);
    GPIOG->MODER |= (0x2<<22);
    GPIOB->AFRL  &= ~(0x0f<<8);
    GPIOB->AFRL  |= (0x08<<8);
    GPIOG->AFRH  &= ~(0x0f<<12);
    GPIOG->AFRH  |= (0x06<<12);
    /*禁止串口控制器*/
    USART4->CR1 &= ~(1<<0);
    /*禁用FIFO*/
    USART4->CR1 &= ~(1<<29);
    /*8bit数据*/
    USART4->CR1 &= ~(1<<28 | 1<<12);
    /*16倍过采样*/
    USART4->CR1 &= ~(1<<15);
    /*无校验*/
    USART4->CR1 &= ~(1<<10);
    /*1bit 停止位*/
    USART4->CR2 &= ~(3<<12);
    /*设置第一次分频系数: 1分频*/
    USART4->PRESC &= ~(0x0f<<0);
    /*设置第二次分频系数,满足115200bps需求*/
    USART4->BRR = 0x22b;
    /*使能串口发送器*/
    USART4->CR1 |= (1<<3);
    /*使能串口接收器*/
    USART4->CR1 |= (1<<2);
    /*使能串口控制器*/
    USART4->CR1 |=  (1<<0);
}
void uart_putc(char ch){
    /*轮询发送寄存器是否为空*/
    while(!(USART4->ISR & (1<<7))) ;
    USART4->TDR = ch;
    if(ch == '\n')
        uart_putc('\r');
}

void uart_puts(char *str){
    while(*str){
        uart_putc(*str);
        str++;
    }
}

char uart_getc(void){
    /*轮询接收寄存器是否有数据*/
    while(!(USART4->ISR & (1<<5))) ;

    return (char)USART4->RDR;
}

int uart_gets(char *buf, int len){
    int i = 0;
    char ch = 0;
    while(i<(len-1))
    {
        ch = uart_getc();
        /*回显*/
        uart_putc(ch);
        buf[i] = ch;
        if(ch == '\r')
            break;
        i++;
    }
    /*添加字符串结束标志*/
buf[i] = '\0';
return i;
}

#include "../include/uart.h"
int main(void){
    uart_init();
    while(1){
        uart_puts("hello world\n");
    }
    return 0;
}
  • 编译生成bin文件
    arm-linux-gnueabihf-gcc -c main.c -o main.o -marm
    arm-linux-gnueabihf-gcc -c uart.c -o uart.o -marm
    arm-linux-gnueabihf-ld main.o uart.o -o uart -Ttext=0xc0008000 -emain
    arm-linux-gnueabihf-objcopy -O binary uart uart.bin
  • 运行结果


1.3.4.1 遗留的问题
  • 链接时的问题
    为什么一定要把main.o放在所有的.o最前面?
    为了让main函数的逻辑出现整个代码段的开始位置,这样下载后,main函数的逻辑出现在内存0xc0008000开始的位置。

  • 关于\r\n的问题

windows平台下,回车换行是两个字符:
\n, 0x0a/10 去到下一行
\r, 0x0d/13 去到行首
Linux平台下, 回车换行是一个字符:
\n,

2. shell框架

2.1 增加Makefile

NAME=shell
OBJS=$(patsubst %.c, %.o, $(wildcard src/*.c))
CFLAGS += -marm
CC = arm-linux-gnueabihf-gcc
 LDFLAGS= map.lds
$(NAME).bin:$(NAME).elf
    arm-linux-gnueabihf-objcopy -O binary   $< $@
    cp $@ /mnt/hgfs/share/lvl16/D12/
$(NAME).elf: $(OBJS)
    arm-linux-gnueabihf-ld $(OBJS) -o $@  $(LDFLAGS)

%.o: %.c
    $(CC) -c $^ -o $@ $(CFLAGS) 


clean:
    rm $(OBJS) -rf
    rm $(NAME).* -rf

2.2 增加链接脚本

/*
   规定链接动作
 */
/* -e main*/
ENTRY(main)
    SECTIONS{
        . = 0xc0008000;
        .text :
        {
            src/main.o(.text)
                *(.text)
        }
        .data :
        {
            *(.data)
        }
        .bss :
        {
            *(.bss)
        }
    }

2.3 实现shell基本功能

2.3.1 strcmp功能函数
strcmp.c
int my_strcmp(const char *s1, const char *s2){
    while(*s1){
       if(*s1 == *s2){
           s1++;
           s2++;
           continue;
       }
       return *s1 - *s2;
    }
    return *s1 - *s2;
}

strcmp.h
#ifndef __STRCMP_H__
#define __STRCMP_H__
int my_strcmp(const char *, const char *);
#endif
2.3.2 增加led驱动函数
led.c
#include "../include/stm32mp157_gpio.h"
#include "../include/stm32mp157_rcc.h"

void led_init(){
    // 1. 使能GPIOE外设的时钟源 RCC_MP_AHB4ENSETR[4] = 0b1
    RCC->MP_AHB4ENSETR |= (0x1 << 4);
    // 2. 设置为输出模式 GPIOE_MODER[21:20] = 0b01
    GPIOE->MODER &= (~(0x3 << 20));
    GPIOE->MODER |= (0x1 << 20);
    //3. 设置PE10引脚为推挽输出  GPIOE_OTYPER[10] = 0b0
    GPIOE->OTYPER &= (~(0x1 << 10));
    //4. 设置PE10引脚为低速模式  GPIOE_OSPEEDR[21:20] = 0b00
    GPIOE->OSPEEDR &= (~(0x3 << 20));
    //5. 设置PE10引脚禁止上下拉电阻  GPIOE_PUPDR[21:20] = 0b00
    GPIOE->PUPDR &= (~(0x3 << 20));
}

void led1_on(){
  GPIOE->ODR |= (1 << 10);
}

void led1_off(){
    GPIOE->ODR &= ~(1 << 10);
}

led.h
#ifndef __LED_H__
#define __LED_H__
void led_init();
void led1_on();
void led1_off();
#endif
2.3.3 增加命令匹配执行
cmd.c
#include "../include/cmd.h"
#include "../include/strcmp.h"
#include "../include/led.h"

cmd_t cmd_list[] = {
    {"led1on", led1_on},
    {"led1off", led1_off}
};

cmd_t *find_cmd(const char* user_input){
    int num = sizeof(cmd_list)/sizeof(cmd_list[0]);
    for(int i = 0; i < num; i++){
        cmd_t* cmd = cmd_list+i;
        if(my_strcmp(user_input, cmd->name) == 0){
            return cmd;
        }
    }

    return (cmd_t*)0;
}

cmd.h
#ifndef __CMD_H__
#define __CMD_H__
typedef struct
{
    char *name;
    void (*cmd_func)(void);
}cmd_t;
extern  cmd_t *find_cmd(const char *);
#endif

main.c
#include "../include/uart.h"
#include "../include/led.h"
#include "../include/cmd.h"

#define MAX 32
char buf[MAX];
int main(void){
    uart_init();
    led_init();
    cmd_t* ptr = (cmd_t*)0;

    while(1){
        uart_puts("\nroot#");
           /*等待接收用户输入的命令*/
        uart_gets(buf, MAX);
        /*匹配命令*/
        ptr = find_cmd(buf);
        if(ptr) //匹配成功
        {
            ptr->cmd_func();
        }
        else
        {
            uart_puts("\nunkonw commad");
        }
    }
    return 0;
}

2.4 退格键BUG的解决

int uart_gets(char *buf, int len){
    int i = 0;
    char ch = 0;
    while(i<(len-1))
    {
        ch = uart_getc();
        if(i == 0 && ch == 0x08)
            continue;
        /*回显*/
        uart_putc(ch);
        buf[i] = ch;
        if(ch == '\r')
            break;
        if(ch == 0x08){
            uart_putc(' ');
            uart_putc(0x08);
            i--;
            continue;
        }
        i++;
    }
    /*添加字符串结束标志*/
buf[i] = '\0';
return i;
}

2.5 在shell框架中增加beepon / beepoff的命令。

beep.c
#include "../include/stm32mp157_gpio.h"
#include "../include/stm32mp157_rcc.h"
void beep_init(void){
    // 1. 使能GPIOB外设的时钟源 RCC_MP_AHB4ENSETR[1] = 0b1
    RCC->MP_AHB4ENSETR |= (0x1 << 1);
    // 2. 设置为输出模式 GPIOB_MODER[13:12] = 0b01
    GPIOB->MODER &= (~(0x3 << 12));
    GPIOB->MODER |= (0x1 << 12);
    //3. 设置PB6引脚为推挽输出  GPIOB_OTYPER[6] = 0b0
    GPIOB->OTYPER &= (~(0x1 << 6));
    //4. 设置PB6引脚为低速模式  GPIOE_OSPEEDR[13:12] = 0b00
    GPIOB->OSPEEDR &= (~(0x3 << 12));
    //5. 设置PB6引脚禁止上下拉电阻  GPIOE_PUPDR[13:12] = 0b00
    GPIOB->PUPDR &= (~(0x3 << 12));
}
void beep_on(void){
    GPIOB->ODR |= (1 << 6);
}
void beep_off(void){
    GPIOB->ODR &= ~(1 << 6);
}

beep.h
#ifndef __BEEP_H__
#define __BEEP_H__
void beep_init(void);
void beep_on(void);
void beep_off(void);
#endif

3. PWM

3.1 基本概念


是不是可以用我们前面学过的GPIO输出功能 + delay延时就可以了?
确实可以,但耗费了大量的CPU资源。
如果有多个引脚需要控制输出类似波形呢?
现实中更多使用了PWM技术来解决该问题。
PWM(Pulse-width modulation),即脉冲宽度调制。这是对脉冲的宽度进行调制的一种技术。
脉冲:高低电平变化的方波信号。(脉搏跳动的冲击信号)
周期:单位(s), 一个方波信号的时间
频率:单位(Hz), 1秒钟可以产生多少个方波信号。周期和频率成倒数关系: T = 1 / F (s) F = 1 / T (Hz)
占空比:一个方波信号中,高电平占整个方波信号周期的百分比。
PWM的使用场景:电机调速、屏幕背光调节、驱动无源蜂鸣器。

3.2 电路原理图分析


有源蜂鸣器:内部有一个震荡源,当给有源蜂鸣器供电时,有源蜂鸣器内部的震荡源就会按照一定的频率震荡产生方波信号,驱动蜂鸣器发声。因此有源蜂鸣器使用高低电平驱动即可。
无源蜂鸣器:内部没有震荡源,因此要想驱动无源蜂鸣器发声,需要提高一个一定频率变化的方波信号,驱动蜂鸣器的发声,通过改变方波信号的频率可以驱动无源蜂鸣器发出不同的声音,因此无源蜂鸣器一般使用PWM信号进行驱动。
我们板子上使用的是有源蜂鸣器,但也可以使用PWM来驱动。
通过电路原理图可知, 标号为TIM4_CH1的导线连接到了STM32MP157的PB6引脚。

3.3 数据手册分析

PWM的硬件基础就是定时器。


3.3.1 首先确定 RCC、TIM4、GPIOB地址
3.3.2 使能TIM4、GPIOB时钟
  • TIM4


  • GPIOB


3.3.3 配置PB6管脚功能
  • 配置为复用功能


  • 配置为复用功能中的AF2

3.3.4 TIM4分析

3.3.4.1 综述

  • TIM4 有输出PWM的功能


  • 计数模式
    自增(边沿对齐)


  • 时钟选择


  • PWM模式


3.3.5 寄存器
  • TIMx_CR1


  • TIMx_CCMR1


  • TIMx_CCER


  • TIMx_PSC


  • TIMx_ARR
    自动加载寄存器


  • TIMx_CCR1


  • TIM4_EGR


3.4 蜂鸣器驱动编程

beep_pwm.c
#include "../include/stm32mp157_gpio.h"
#include "../include/stm32mp157_rcc.h"
#include "../include/stm32mp157_tim.h"

void beep_pwm_init(){
    /*1使能GPIOB TIMER4控制器时钟*/
    RCC->MP_AHB4ENSETR |= (1<<1);
    RCC->MP_APB1ENSETR |= (1<<2);

    /*2 设置PB6 为 TIM4_CH1功能*/
    GPIOB->MODER &= ~(0x03 << 12);
    GPIOB->MODER |= (0x02 << 12);
    GPIOB->AFRL &= ~(0x0f << 24);
    GPIOB->AFRL |= (0x02 << 24);

    /*3 设置分频系数*/
    TIM4->PSC = 209 - 1;
    /*4 设置PWM的周期*/
    TIM4->ARR = 1000;
    /*5 设置占空比*/
    TIM4->CCR1 = 700;
    /*6 设置TIM4_CH1为PWM1模式*/
    TIM4->CCMR1 &= ~(0x01 << 16 | 0x07 << 4);
    TIM4->CCMR1 |= (0x06 << 4);
    /*7 CC1配置为输出模式*/
    TIM4->CCMR1 &= ~(0x03);
    /*8配置ACTIVE状态为高电平 */
    TIM4->CCER &= ~(0x01 << 1);
    /*9 输出使能*/
    TIM4->CCER |= 0x01;
    /*10 向上计数*/
    TIM4->CR1 &= (~(0x3 << 5));
    TIM4->CR1 &= (~(0x1 << 4));
}

void beep_pwm_on(){
    beep_pwm_init();
    TIM4->CR1 |= 0x01;
}

void beep_pwm_off(){
    beep_pwm_init();
    TIM4->CR1 &= ~0x01;
}
beep_pwm.h
#ifndef __BEEP_PWM_H__
#define __BEEP_PWM_H__
void beep_pwm_init();
void beep_pwm_on();
void beep_pwm_off();
#endif

3.5 下载调试

由于配置为了PWM1模式: CNT < CCR(800), 输出为Active状态由于CCER &= (~(0x1 << 1)),配置了Active状态为高电平状态 故,其占空比为80%。


3.6 风扇和马达的驱动

  • 风扇


fan_pwm.c
/* 参考代码 */
#include "../include/stm32mp157_gpio.h"
#include "../include/stm32mp157_rcc.h"
#include "../include/stm32mp157_tim.h"
void fan_pwm_init(void){
    // 1. 设置GPIOE,TIM1外设的时钟使能   RCC_MP_AHB4ENSETR[4]  RCC_MP_APB2ENSETR[0]
    RCC->MP_AHB4ENSETR |= (0x1 << 4);
    RCC->MP_APB2ENSETR |= (0x1 << 0);
    // 2. 设置PE9引脚为复用功能模式
    GPIOE->MODER &= (~(0x3 << 18));
    GPIOE->MODER |= (0x2 << 18);
    GPIOE->AFRH &= (~(0xF << 4));
    GPIOE->AFRH |= (0x1 << 4);
    // 3. 设置预分频寄存器,TIM1_PSC[15:0] = 208
    // 分频前时钟:CK_PSC = 209MHz, 提供给TIM1的时钟源的频率是209MHz
    // 分频后的时钟 CK_CNT = 209 000 000Hz / (208 + 1) = 1 000 000Hz
    TIM1->PSC = 209 - 1;
    // 4. 设置PWM方波的最终的周期  TIM1_ARR[16:0] = 1000
    //      
    //得到一个1000-2000Hz的方波
    // PWM方波的频率 = CK_PSC / PSC / ARR
    TIM1->ARR = 1000;
    // 5. 设置PWM方波的占空比   TIM1_CCR1[16:0] = 700
    TIM1->CCR1 = 900;
    // 6. 设置TIM1_CH1通道为PWM1模式
    // TIM1_CCMR1[16] = 0b0  TIM1_CCMR1[6:4] = 0b110
//  pwm模式1  = 0b0110
        TIM1->CCMR1 &= (~(0x1 << 16 | 0x7 << 4));
    TIM1->CCMR1 |= (0x6 << 4);
    // 7. 设置TIM1_CH1的OC1通道配置为输出
    //      TIM1_CCMR1[1:0] = 0x0
    TIM1->CCMR1 &= (~(0x3 << 0));
    // 8. 设置TIM1_CH1通道输出PWM方波的极性,
    //    TIM1_CCER[1] = 0x1 or 0x0
#if 0
    TIM1->CCER |= (0x1 << 1);   // CCR1值越小,占空比越大
#else
    TIM1->CCER &= (~(0x1 << 1)); // CCR1值越大,占空比越大
#endif
    // 9. 设置TIM1_CH1通道的输出使能位,通过GPIO引脚输出PWM方波
    // TIM1_CCER[0] = 0x1
    TIM1->CCER |= (0x1 << 0);
    // 10. 设置定时器的计数方式,边沿对齐,向上计数
    //TIM1_CR1[6:5] = 0x0
    TIM1->CR1 &= (~(0x3 << 5));
    //  TIM1_CR1[4] = 0x0
    TIM1->CR1 &= (~(0x1 << 4));
}
void fan_pwm_on(void){
    fan_pwm_init();
    // 11. 使能TIM1_CH1计数器
    //      TIM1_CR1[0] = 0x1
    TIM1->CR1 |= (0x1 << 0);
    // 12. 设置主输出使能
    TIM1->BDTR |= (0x1 << 15);
}
void fan_pwm_off(void){
    fan_pwm_init();
    /* 停止计数*/
    TIM1->CR1 &= ~(0x1 << 0);
    TIM1->BDTR &= ~(1<<15);
}
fan_pwm.h
#ifndef __FAN_PWM_H__
#define __FAN_PWM_H__

void fan_pwm_init();
void fan_pwm_on();
void fan_pwm_off();
#endif
  • 马达


motor_pwm.c
/*参考代码*/
#include "../include/stm32mp157_gpio.h"
#include "../include/stm32mp157_rcc.h"
#include "../include/stm32mp157_tim.h"
void motor_pwm_init(void){
    // 1. 设置GPIOF,TIM16外设的时钟使能   RCC_MP_AHB4ENSETR[5]  RCC_MP_APB2ENSETR[3]
    RCC->MP_AHB4ENSETR |= (0x1 << 5);
    RCC->MP_APB2ENSETR |= (0x1 << 3);
    // 2. 设置PF6引脚为复用功能模式
    GPIOF->MODER &= (~(0x3 << 12));
    GPIOF->MODER |= (0x2 << 12);
    GPIOF->AFRL &= (~(0xF << 24));
    GPIOF->AFRL |= (0x1 << 24);
    // 3. 设置预分频寄存器,TIM16_PSC[15:0] = 208
    // 分频前时钟:CK_PSC = 209MHz, 提供给TIM16的时钟源的频率是209MHz
    // 分频后的时钟 CK_CNT = 209 000 000Hz / (208 + 1) = 1 000 000Hz
    TIM16->PSC = 209 - 1;
    // 4. 设置PWM方波的最终的周期  TIM16_ARR[16:0] = 1000
    //      得到一个1000-2000Hz的方波
        // PWM方波的频率 = CK_PSC / PSC / ARR
        TIM16->ARR = 1000;
    // 5. 设置PWM方波的占空比   TIM16_CCR1[16:0] = 700
    TIM16->CCR1 = 700;
    // 6. 设置TIM16_CH1通道为PWM1模式
    //      TIM16_CCMR1[16] = 0b0  TIM16_CCMR1[6:4] = 0b110
    //  pwm模式1  = 0b0110
        TIM16->CCMR1 &= (~(0x1 << 16 | 0x7 << 4));
    TIM16->CCMR1 |= (0x6 << 4);
    // 7. 设置TIM16_CH1的OC1通道配置为输出
    //      TIM16_CCMR1[1:0] = 0x0
        TIM16->CCMR1 &= (~(0x3 << 0));
    // 8. 设置TIM16_CH1通道输出PWM方波的极性,
    //      TIM16_CCER[1] = 0x1 or 0x0
#if 0
        TIM16->CCER |= (0x1 << 1);   // CCR1值越小,占空比越大
#else
    TIM16->CCER &= (~(0x1 << 1)); // CCR1值越大,占空比越大
#endif
    // 9. 设置TIM16_CH1通道的输出使能位,通过GPIO引脚输出PWM方波
    //          TIM16_CCER[0] = 0x1
        TIM16->CCER |= (0x1 << 0);
}
void motor_pwm_on(void){
    motor_pwm_init();
    // 10. 使能TIM16_CH1计数器
    //          TIM16_CR1[0] = 0x1
        TIM16->CR1 |= (0x1 << 0);
    // 11. 设置主输出使能
    TIM16->BDTR |= (0x1 << 15);
}
void motor_pwm_off(void){
    motor_pwm_init();
    /* 停止计数*/
    TIM16->CR1 &= ~(0x1 << 0);
    TIM16->BDTR &= ~(1<<15);
}

motor_pwm.h
#ifndef __MOTOR_PWM_H__
#define __MOTOR_PWM_H__
void motor_pwm_init();
void motor_pwm_on();
void motor_pwm_off();
#endif

4. 软中断异常处理

4.1 需要的知识点

  • 七种工作模式中的异常模式


  • 寄存器组织结构


  • 七种异常会导致ARM进入五种异常工作模式


  • 为了实现异常处理流程,ARM硬件做的事:
    ARM硬件上收到异常信号,会执行啥操作呢?归纳起来就是:四大、三小

  • 拷贝CPSR中的内容到对应异常模式下的SPSR_<mode>
  • 修改CPSR
    按照需要CPSR.I=1 CPSR.F =1
    CPSR.T=0 (异常处理代码必须为ARM 指令,不能是Thumb指令)
    修改CPSR.mode, 使其进入对应的异常工作模式
  • 保存返回地址到对应异常模式下的LR_<mode>
  • 给PC寄存器赋值。

4.2 软件上需要做的事情

  1. 建立异常向量表
  2. 实现处理各种异常的逻辑
.text
.global _start

_start:
    b reset
    b undef_handler
    b swi_handler
    b pref_handler
    b data_handler
    b .
    b irq_handler
    b fiq_handler
    
reset:
    ldr sp, =svc_stack
    add sp, sp, #256
    
    mrs r0, cpsr
    bic r0, r0, #0x1f
    orr r0, r0, #0x10
    msr cpsr, r0
    
    ldr sp, =irq_stack
    add sp, sp, #256
    mov r0, #3
    mov r1, #4
    
    swi  #2  
    add r2, r0, r1  
    b stop
    
undef_handler:
    b stop
swi_handler:
    stmfd sp!, {r0-r1, lr}    
    mov r0, #5
    mov r1, #6
    ldmfd sp!, {r0-r1, pc}^ 
pref_handler:
    b stop
data_handler:
    b stop
irq_handler:
    b stop
fiq_handler:
    b stop
stop:   
    b stop 
.data
svc_stack:
    .space 256
        
irq_stack:
        .space 256
.end

这个实验的目的:主要是让大家观察异常产生后硬件自动做的那件事是真实发生的。

4.3 异常向量表的表现形式

.text
.global _start

_start:
    ldr pc, =reset
    ldr pc, =undef_handler
    ldr pc, =swi_handler
    ldr pc, =pref_handler
    ldr pc, =data_handler
    b .
    ldr pc, =irq_handler
    ldr pc, =fiq_handler
    
reset:
    ldr sp, =svc_stack
    add sp, sp, #256
    
    mrs r0, cpsr
    bic r0, r0, #0x1f
    orr r0, r0, #0x10
    msr cpsr, r0
    
    ldr sp, =irq_stack
    add sp, sp, #256
    mov r0, #3
    mov r1, #4
    
    swi  #2  
    add r2, r0, r1  
    b stop
    
undef_handler:
    b stop
swi_handler:
    stmfd sp!, {r0-r1, lr}    
    mov r0, #5
    mov r1, #6
    ldmfd sp!, {r0-r1, pc}^ 
pref_handler:
    b stop
data_handler:
    b stop
irq_handler:
    b stop
fiq_handler:
    b stop
stop:   
    b stop 
.data
svc_stack:
    .space 256
        
irq_stack:
        .space 256
.end
.text
.global _start

_start:
    ldr pc, =reset
    ldr pc, _undef_handler
    ldr pc, _swi_handler
    ldr pc, _pref_handler
    ldr pc, _data_handler
    b .
    ldr pc, _irq_handler
    ldr pc, _fiq_handler
    
reset:
    ldr sp, =svc_stack
    add sp, sp, #256
    
    mrs r0, cpsr
    bic r0, r0, #0x1f
    orr r0, r0, #0x10
    msr cpsr, r0
    
    ldr sp, =irq_stack
    add sp, sp, #256
    mov r0, #3
    mov r1, #4
    
    swi  #2  
    add r2, r0, r1  
    b stop
    
_undef_handler:
.word undef_handler
_swi_handler:
.word swi_handler
_pref_handler:
.word pref_handler
_data_handler:
.word data_handler
_irq_handler:
.word irq_handler
_fiq_handler:
.word fiq_handler
    
undef_handler:
    b stop
swi_handler:
    stmfd sp!, {r0-r1, lr}    
    mov r0, #5
    mov r1, #6
    ldmfd sp!, {r0-r1, pc}^ 
pref_handler:
    b stop
data_handler:
    b stop
irq_handler:
    b stop
fiq_handler:
    b stop
stop:   
    b stop 
.data
svc_stack:
    .space 256
        
irq_stack:
        .space 256
.end

4.4 系统调用号

5. 按键中断

5.1 建立IRQ异常处理框架

5.1.1 增加start.s
/* src/start.s */
.text
.global _start
_start:
@ 异常向量表
b reset
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
_undefined_instruction:
.word undefined_instruction
_software_interrupt:
.word software_interrupt
_prefetch_abort:
.word prefetch_abort
_data_abort:
.word data_abort
_not_used:
.word not_used
_irq:
.word irq
_fiq:
.word fiq
/* The actual reset code */
reset:
@ 重新映射异常向量表的入口地址
/* Set Vector Base Address Register */
    mrc p15, 0, r0, c1, c0, 0
bic r0, #(1<<13)
    mcr p15, 0, r0, c1, c0, 0
    ldr r0,=0xc0008000
    mcr p15,0,r0,c12,c0,0       @ Vector Base Address Register
    /* Set the cpu to svc32 mode */
    mrs r0, cpsr
    bic r0, r0, #0x1f
    orr r0, r0, #0xd3
    msr cpsr, r0
    /* Enable NEON/VFP unit */
    mrc p15, #0, r1, c1, c0, #2
orr r1, r1, #(0xf << 20)
    mcr p15, #0, r1, c1, c0, #2
    mov r1, #0
    mcr p15, #0, r1, c7, c5, #4
    mov r0, #0x40000000
    fmxr fpexc, r0
    /* Cache init */
    mrc p15, 0, r0, c0, c0, 0
    and r1, r0, #0x00f00000
    and r2, r0, #0x0000000f
    orr r2, r2, r1, lsr #20-4
    cmp r2, #0x30
    mrceq p15, 0, r0, c1, c0, 1
    orreq r0, r0, #0x6
    mcreq p15, 0, r0, c1, c0, 1
    /* Invalidate L1 I/D */
    mov r0, #0
    mcr p15, 0, r0, c8, c7, 0
    mcr p15, 0, r0, c7, c5, 0
    /* Disable mmu stuff and caches */
    mrc p15, 0, r0, c1, c0, 0
    bic r0, r0, #0x00002000
    bic r0, r0, #0x00000007
    orr r0, r0, #0x00001000
    orr r0, r0, #0x00000002
    orr r0, r0, #0x00000800
    mcr p15, 0, r0, c1, c0, 0
    /* Initialize stacks */
    @ 初始化各种模式下的占空间
init_stack:
    ldr r0, stacktop        /*get stack top pointer*/
    /********svc mode stack********/
    mov sp, r0
    sub r0, #128*4          /*512 byte  for irq mode of stack*/
    /********irq mode stack********/
    msr cpsr, #0xd2
    mov sp, r0
    sub r0, #128*4          /*512 byte  for fiq mode of stack*/
    /********fiq mode stack********/
    msr cpsr, #0xd1
    mov sp, r0
    sub r0, #0
    /********abort mode stack******/
    msr cpsr, #0xd7
    mov sp, r0
    sub r0, #0
    /********undefine mode stack**/
    msr cpsr, #0xdb
    mov sp, r0
    sub r0, #0
    /***sys mode and usr mode stack***/
    msr cpsr, #0x10
    mov sp, r0             /*1024 byte  for user mode of stack*/
    /******clear bss section********/
    @ 清除BSS段
    ldr r0, =__bss_start    /* this is auto-relocated! */
    ldr r1, =__bss_end__    /* this is auto-relocated! */
    mov r2, #0x00000000     /* prepare zero to clear BSS */
clbss_l: 
    cmp r0, r1          /* while not at end of BSS */
    strlo r2, [r0]          /* clear 32-bit BSS word */
    addlo r0, r0, #4        /* move to next */
    blo clbss_l
    /* Call _main */
    ldr pc, =main     @ 汇编调用C  跳转到main.c文件的main函数中
    /*
     * Exception handlers
     */
    .align 5  @ 2^5,32字节对齐
undefined_instruction:
    b   .
    .align 5
software_interrupt:
    b   .
    .align 5
prefetch_abort:
    b   .
    .align 5
data_abort:
    b   .
    .align 5
not_used:
    b .
    .align 5
    .global irq
irq:
    sub  lr, lr, #4
    stmfd sp!, {r0-r12, lr}
    bl do_irq
    ldmfd sp!, {r0-r12, pc}^
    .align 5
    .global fiq

fiq:
    b .
stacktop:
    .word stack + 4 * 512
    .data
    .align 5
stack:
    .space  4 * 512
51.2 修改Makefile
NAME=shell
OBJS=$(patsubst %.c, %.o, $(wildcard src/*.c))
OBJS+=$(patsubst %.s, %.o, $(wildcard src/*.s))
CFLAGS += -marm
CC = arm-linux-gnueabihf-gcc
 LDFLAGS= -Tmap.lds
$(NAME).bin:$(NAME).elf
    arm-linux-gnueabihf-objcopy -O binary   $< $@
    cp $@ /mnt/hgfs/share/lvl16/D16/
$(NAME).elf: $(OBJS)
    arm-linux-gnueabihf-ld $(OBJS) -o $@  $(LDFLAGS)

%.o: %.c
    $(CC) -c $^ -o $@ $(CFLAGS) 

%.o: %.s
    $(CC) -c $^ -o $@ $(CFLAGS) 

clean:
    rm $(OBJS) -rf
    rm $(NAME).* -rf
5.1.3 修改map.lds
/*
   规定链接动作
 */
/* -e main*/
ENTRY(main)
    SECTIONS{
        . = 0xc0008000;
        .text :
        {
            src/start.o(.text)
                *(.text)
        }
        .data :
        {
            *(.data)
        }
        __bss_start = .;
        .bss :
        {
            *(.bss)
        }
        __bss_end__ = .;
    }
5.1.4 增加do_irq.c do_irq.h
do_irq.c
void do_irq(void){
}

do_irq.h
#ifndef __DO_IRQ_H__
#define __DO_IRQ_H__
void do_irq(void);
#endif

5.2 电路原理图

去除抖动的方式有两种:

  1. 硬件去抖, 通常使用0.1μF的电容,利用其充放电特性,过滤掉毛刺信号


  2. 软件延时去抖

5.3 datasheet

中断相关的硬件框架结构:


5.3.1 确定总线
5.3.2 使能时钟
5.3.3 配置管脚功能
5.3.4 配置EXTI
5.3.5 配置GIC

5.3.5.1 配置GICD

5.3.5.2 配置GICC

key_irq.c
#include "../include/stm32mp157_gpio.h"
#include "../include/stm32mp157_gic.h"
#include "../include/stm32mp157_rcc.h"
#include "../include/stm32mp157_exti.h"
#include "../include/uart.h"

void key_irq_init(){
    /*1 使能GPIOF控制器时钟*/
    RCC->MP_AHB4ENSETR |= (1<<5);
    //2 设置PF9为输入模式
    GPIOF->MODER &= ~(3<<18);
    //3 设置PF9连接到EXTI9
    EXTI->EXTICR3 &= ~(0xff<<8);
    EXTI->EXTICR3 = (5<<8);
    //4 设置下降沿触发中断
    EXTI->FTSR1 |= (1<<9);
    //5使能中断
    EXTI->IMR1 |= (1<<9);

    /*GICD*/
    //6 使能中断
    GICD->ISENABLER[3] |= (0x1 << 3);
    //7 设置中断优先级
    GICD->IPRIORITYR[24] &= (~(0x1F << 27));   // EXTI9 --> 99
    GICD->IPRIORITYR[24] |= (0x6 << 27);
    //8 分配中断信号给CPU0
    GICD->ITARGETSR[24] &= (~(0x3 << 24));    
    GICD->ITARGETSR[24] |= (0x1 << 24);
    //9 GICD层的全局使能位
    GICD->CTRL |= 0x1;

    /*GICC*/
    //10 阈值
    GICC->PMR |= (0x1F << 3);
    //11 GICC层全局使能位
    GICC->CTRL |= 0x1;
}

void key1_irq_handler(void){
    uart_puts("key1 is pressed!\n");
    /*清除EXTI层的中断挂起标志位*/
    EXTI->FPR1 |= (1<<9);
    /* 清除GICD层的中断挂起标志位*/
    GICD->ICPENDR[3] |= (0x1 << 3);
}

key_irq.h
#ifndef __KEY_IRQ_H__
#define __KEY_IRQ_H__
void key_irq_init(void);
void key1_irq_handler(void);
#endif

do_irq.c
#include "../include/uart.h"
#include "../include/stm32mp157_gic.h"
#include "../include/stm32mp157_exti.h"
#include "../include/key_irq.h"
void do_irq(void){
    int irq_num = GICC->IAR & 0x3ff;
    switch(irq_num){
    case 99:
        key1_irq_handler();
        break;
    default:
        break;
    }
    /*清除GICC层的中断号*/
    GICC->EOIR = irq_num;
}

6. 光电开关、火焰传感器编程实践

6.1 光电开关

实物图:


photoelectric_switch.c
#include "../include/stm32mp157_gpio.h"
#include "../include/stm32mp157_gic.h"
#include "../include/stm32mp157_rcc.h"
#include "../include/stm32mp157_exti.h"
#include "../include/uart.h"

void photoelectric_switch_init(){
    /*1 使能GPIOE控制器时钟*/
    RCC->MP_AHB4ENSETR |= (1<<4);
    //2 设置PE15为输入模式
    GPIOE->MODER &= ~(3<<30);
    //3 设置PE15连接到EXTI15
    EXTI->EXTICR4 &= ~(0xff<<24);
    EXTI->EXTICR4 = (4<<24);
    //4 设置下降沿触发中断
    EXTI->FTSR1 |= (1<<15);
    EXTI->RTSR1 |= (1<<15); //上升沿触发中断
    //5使能中断
    EXTI->IMR1 |= (1<<15);

    /*GICD*/
    //6 使能中断
    GICD->ISENABLER[4] |= (0x1 << 31);
    //7 设置中断优先级
    GICD->IPRIORITYR[39] &= (~(0x1F << 27));   // EXTI15 --> 159
    GICD->IPRIORITYR[39] |= (0x6 << 27);
    //8 分配中断信号给CPU0
    GICD->ITARGETSR[39] &= (~(0x3 << 24));    
    GICD->ITARGETSR[39] |= (0x1 << 24);
    //9 GICD层的全局使能位
    GICD->CTRL |= 0x1;

    /*GICC*/
    //10 阈值
    GICC->PMR |= (0x1F << 3);
    //11 GICC层全局使能位
    GICC->CTRL |= 0x1;
}

void photoelectric_switch_handler(void){
    if(GPIOE->IDR & (1<<15)){
        uart_puts("photoelectric switch  is released!\n");
    }
    if(!(GPIOE->IDR & (1<<15))){
        uart_puts("photoelectric switch  is pressed!\n");
    }
    
    /*清除EXTI层的中断挂起标志位*/
    EXTI->RPR1 |= (1<<15);
    EXTI->FPR1 |= (1<<15);
    /* 清除GICD层的中断挂起标志位*/
    GICD->ICPENDR[4] |= (0x1 << 31);
}


photoelectric_switch.h
#ifndef __PHOTOELECTRIC_SWITCH_H__
#define __PHOTOELECTRIC_SWITCH_H__
void photoelectric_switch_init(void);
void photoelectric_switch_handler(void);
#endif

6.2 火焰传感器

实物图:


6.3 人体红外传感器

实物图:


7. 定时器中断

前面使用PWM时,使用了定时器。定时器不但可以产生不同占空比的PWM方波信号,也可以产生定时中断,实现精确计时。


  • 使能中断


  • 中断pending


  • GIC分析


timer_irq.c
#include "../include/stm32mp157_rcc.h"
#include "../include/stm32mp157_tim.h"
#include "../include/stm32mp157_gic.h"
#include "../include/uart.h"

void timer_irq_init(void){
    /*使能时钟*/
    RCC->MP_APB1ENSETR |= (1<<1);
    /*3 设置分频系数*/
    TIM3->PSC = 20900 -1;
    /*4 设置PWM的周期*/
    TIM3->ARR = 10000;
    /*10 向上计数*/
    TIM3->CR1 &= (~(0x3 << 5));
    TIM3->CR1 &= (~(0x1 << 4));
    /*使能中断*/
    TIM3->DIER |= 1;
    /*GICD*/
    GICD->ISENABLER[1] |= (1<<29);
    GICD->IPRIORITYR[15] &= ~(0x0f<<11);
    GICD->IPRIORITYR[15] |= (0x06<<11);
    GICD->ITARGETSR[15] &= ~(3<<8);
    GICD->ITARGETSR[15] |= (1<<8);
    /*计数使能*/
    TIM3->CR1 |= (0x1 << 0);

}

void timer3_irq_handler(void){
    uart_puts("1s delay\n");
    TIM3->SR &= ~(1<<0);
    GICD->ICPENDR[1] |= (1<<29);
}

timer_irq.h
#ifndef __TIMER_IRQ_H__
#define __TIMER_IRQ_H__
void timer_irq_init(void);
void timer3_irq_handler(void);
#endif

8. I2C设备驱动

接下来,我们来实现温湿度传感器的驱动程序。


  • 逻辑框图

8.1 I2C总线

8.1.1 定义

I2C总线是PHLIPS公司在八十年代初推出的一种两线式串行同步总线,用于板级芯片之间的相互通信。
通信时只用了SCL(时钟线)和SDA(数据线)。
通信速率一般介于100Kbps~400Kbps。


8.1.2 特点

1)布线简单,硬件互联成本低
2)I2C总线上可以有多个设备,设备之间有主从之分
3)通信的发起、结束一定是主设备来控制
4)每个从设备都有特定的从设备地址
5)I2C总线支持总线冲突仲裁

8.1.3 时序
  • 空闲状态,SCL、SDA为高电平(飞利浦公司规定的,硬件上接了上拉电阻实现的)。

  • START
    SCL处于高电平期间,SDA上出现一个下降沿。

  • STOP
    SCL处于高电平期间,SDA上出现一个上升沿。


  • 低送高取+MSB
    SCL为低电平时,发送方去调整SDA上的电压值
    SCL为高电平时,接收方去采样SDA上的电压值

  • ACK
    第九个周期,接收方在SCL为低电平时将SDA拉成低电平,发送方在SCL为高电平时采样SDA为低电平即为ACK

  • NAK
    第九个周期, 发送方在SCL为高电平时采样SDA为高电平即为NAK

8.1.4 总线通信协议

8.2 I2C总线通信协议的实现

8.2.1软件模拟方式
  • 确定RCC、GPIOF控制器的基地址


  • 使能GPIOF控制器时钟


  • 输出时开漏输出


  • 配置为高速输出


  • 禁止内部上拉下拉


  • 输入时读取管脚电平状态


  • 输出时设置管脚电平状态


8.3 设备驱动

8.3.1 SI7006数据手册
  • 总体框架


  • 温湿度转换需要的时间


  • 从设备地址


  • 测量温湿度的时序图,并读取测量的温湿度的结果


  • 用户寄存器说明


si7006.c
#include "../include/iic.h"
#include "../include/itoa.h"
#include "../include/uart.h"
#include "../include/delay.h"

// SI7006的从机地址
#define SI7006_SLAVE 0x40
// 写用户寄存器的命令码
#define WRITE_USER_REG_CMD 0xE6
// 12位湿度,14位温度, 禁止加热器  0011 1010
#define WRITE_USER_REG_VALUE 0x3A
// 测量湿度的命令码
#define MEASURE_HUM_CMD 0xE5
// 测量温度的命令码
#define MEASURE_TEMP_CMD 0xE3

/*
* 函数名:si7006_init
* 函数功能:SI7006芯片的初始化
* 函数参数:无
* 函数返回值:无
*/
void si7006_init(void){
    i2c_init();
     // 写用户寄存器,12位的温度,14位湿度,禁止加热器
    i2c_start();
    i2c_write_byte(SI7006_SLAVE << 1);
    i2c_wait_ack();
    i2c_write_byte(WRITE_USER_REG_CMD);
    i2c_wait_ack();
    i2c_write_byte(WRITE_USER_REG_VALUE);
    i2c_wait_ack();
    i2c_stop();
}

/*
 * 函数名:si7006_read_hum_data
 * 函数功能:读取SI7006的湿度转换结果
 * 函数参数:
 *     slave_addr : 从机地址
 *     cmd_code : 命令码
 * 函数返回值:湿度测量的数字量
 */
void si7006_read_hum_data(void){
    unsigned short hum;
    unsigned char hum_h, hum_l;
    char itoa_buf[11] ;

    i2c_start();
    i2c_write_byte(SI7006_SLAVE << 1);
    i2c_wait_ack();
    i2c_write_byte(MEASURE_HUM_CMD);
    i2c_wait_ack();
    i2c_start();
    i2c_write_byte((SI7006_SLAVE << 1) | 1);
    i2c_wait_ack();
    delay_ms(100);
    hum_h = i2c_read_byte(0);
    hum_l = i2c_read_byte(1);
    i2c_stop();

    hum = hum_h << 8 | hum_l;
    hum = 100 * (125.0 * hum / 65536 - 6);

    uart_puts("\n");
    itoa(itoa_buf, hum/100);
    uart_puts("current hum: ");
    uart_puts(itoa_buf);
    uart_puts(".");
    itoa(itoa_buf, hum%100);
    uart_puts(itoa_buf+8);
    uart_puts("\n");
}

/*
 * 函数名:si7006_read_temp_data
 * 函数功能:读取SI7006的温度转换结果
 * 函数参数:
 *     slave_addr : 从机地址
 *     cmd_code : 命令码
 * 函数返回值:温度测量的数字量
 */

void si7006_read_temp_data(){
    short temp;
    unsigned char temp_h, temp_l;
    char itoa_buf[11];

    i2c_start();
    i2c_write_byte(SI7006_SLAVE << 1);
    i2c_wait_ack();
    i2c_write_byte(MEASURE_TEMP_CMD);
    i2c_wait_ack();
    i2c_start();
    i2c_write_byte((SI7006_SLAVE << 1) | 1);
    i2c_wait_ack();
    delay_ms(100);
    temp_h = i2c_read_byte(0);
    temp_l = i2c_read_byte(1);
    i2c_stop();

    temp = temp_h;
    temp = (temp << 8) | temp_l;
    temp = 100 * (175.72 * temp / 65535 - 46.85);
    uart_puts("\n");
    uart_puts("current temp: ");
    itoa(itoa_buf, temp/100);
    uart_puts(itoa_buf);
    uart_puts(".");
    itoa(itoa_buf, temp%100);
    uart_puts(itoa_buf+8);
    uart_puts("\n");
}

si7006.h
#ifndef __SI7006_HT_H__
#define __SI7006_HT_H__
void si7006_init(void);
void si7006_read_hum_data(void);
void si7006_read_temp_data(void);
#endif

9. SPI设备驱动

9.1数码管简介

数码管介绍相关的网址:https://m.elecfans.com/article/435699.html
数码管也称LED数码管,不同行业人士对 数码管的称呼不一样,其实都是同样的产品。数码管按段数可分为七段数码管和8段数 码管,八段数码管比七段数码管多一个发光二 极管单元,也就是多一个小数点(DP)这个小 数点可以更精确的表示数码管想要显示的内 容;按能显示多少个(8)可分为1位、2位、3 位、4位、5位、6位、7位等数码管。按发光二极管单元连接方式可分为共阳极数码管和共阴极数码管。共阳数码管是指在应用时应将公共极COM接到高电平,当某一字段发光二 极管的阴极为低电平时,相应字段就点亮,当 某一字段的阴极为高电平时,相应字段就不 亮。阴数码管是指将所有发光二极管的阴极 接到一起形成公共阴极(COM)的数码管, 共阴数码管在应用时应将公共极COM接到地 线GND上,当某一字段发光二极管的阳极为高 电平时,相应字段就点亮,当某一字段的阳极 为低电平时,相应字段就不亮。

  • 共阳数码管:


  • 共阴数码管:


9.2 电路原理图

9.2.1数码管
9.2.2 M74HC595

M74HC595是一个8位串行输入、并行输出的位移缓存器。


9.3 SPI通信协议

9.3.1 概述
  • SPI 是串行外设接口(Serial Peripheral Interface)的缩写。是 Motorola 公司推出的一种同步串行接口技术,是一种高速的,全双工,同步的通信总线。
  • 采用主从模式(Master-Slave)架构,支持多slave模式应用,一般仅支持单Master。
  • SPI接口有2根单向数据线,为全双工通信,
  • SPI总线被广泛地使用在FLASH、 ADC、 LCD等设备与MCU间,要求通讯速率较高的场合



    SPI接口共有4根信号线:
    设备选择线(片选线:主机用于选择和哪个从设备通信的):CS(chip select) SS(slave select) NCS NSS
    时钟线(同步时钟信号线,由主机产生):SCK SCL SCLK
    串行输出数据线(主机输出,从机输入):MOSI, master output slave input
    串行输入数据线(主机输入,从机输出):MISO, master input slave output

  • 优点:
    1)全双工串行通信;
    2)高速数据传输速率。
    3)简单的软件配置

  • 缺点:
    1)没有硬件从机应答信号(主机可能在不知情的情况下无处发送);
    2)通常仅支持一个主设备;
    3)需要更多的引脚(与I2C不同);
    4)没有定义硬件级别的错误检查协议;
    5)与RS-232和CAN总线相比,只能支持非常短的距离;

9.3.2 通信协议

  • 在SPI通信中,最重要的两项设置就是时钟极性(CPOL)和时钟相位(CPHA)这两项。
    时钟极性CPOL:设置时钟空闲时的电平
    当CPOL=0, SCK引脚在空闲状态保持低电平
    当CPOL=1, SCK引脚在空闲状态保持高电平
  • 时钟相位CPHA: 设置数据采集时的时钟沿
    当CPHA=0时, MOSI或者MISO数据线上的信号将会在SCK时钟的奇数边沿(前沿)被采样。
    当CPHA=1时, MOSI或者MISO数据线上的信号将会在SCK时钟的偶数边沿(后沿)被采样。

通信有4种模式:CPOL=0/1(时钟极性, 空闲时SCLK为低/高电平) CPHA=0/1(时钟相位, 前/后沿采样)


9.4 SPI总线通信协议的实现

9.4.1 软件模拟方式
  • 确定RCC、GPIOE控制器的基地址


  • 配置PE11、PE12、PE13、PE14的输入输出功能


  • 配置为高速输出


  • 输入时读取管脚电平状态


spi.c
#include "../include/stm32mp157_rcc.h"
#include "../include/delay.h"
#include "../include/spi.h"

/* SPI4_NSS     ---->   PE11
 * SPI4_SCK     ---->   PE12
 * SPI4_MOSI    ---->   PE14
 * SPI4_MISO    ---->   PE13
 *
 */

void spi_init(){
    RCC->MP_AHB4ENSETR |= (0x1 << 4);
    // MOSI    PE14
    GPIOE->MODER &= (~(0x3 << 28));
    GPIOE->MODER |= (0x1 << 28);
    GPIOE->OTYPER &= (~(0x1 << 14));
    GPIOE->OSPEEDR &= (~(0x3 << 28));
    GPIOE->OSPEEDR |= (0x2 << 28);
    GPIOE->PUPDR &= (~(0x3 << 28));
    // MISO    PE13
    GPIOE->MODER &= (~(0x3 << 26));
    GPIOE->OSPEEDR &= (~(0x3 << 26));
    GPIOE->OSPEEDR |= (0x2 << 26);
    GPIOE->PUPDR &= (~(0x3 << 26));
     // SCK     PE12
    GPIOE->MODER &= (~(0x3 << 24));
    GPIOE->MODER |= (0x1 << 24);
    GPIOE->OTYPER &= (~(0x1 << 12));
    GPIOE->OSPEEDR &= (~(0x3 << 24));
    GPIOE->OSPEEDR |= (0x2 << 24);
    GPIOE->PUPDR &= (~(0x3 << 24));
    // NSS     PE11
    GPIOE->MODER &= (~(0x3 << 22));
    GPIOE->MODER |= (0x1 << 22);
    GPIOE->OTYPER &= (~(0x1 << 11));
    GPIOE->OSPEEDR &= (~(0x3 << 22));
    GPIOE->OSPEEDR |= (0x2 << 22);
    GPIOE->PUPDR &= (~(0x3 << 22));
    NSS_OUTPUT_L;    // 595芯片的锁存引脚拉低
    SCK_OUTPUT_L;    // SPI的时钟线拉低
}

void spi_write(unsigned char dat){
    unsigned char i;
    for(i = 0; i<8; i++){
        if(dat & 0x80){
            MOSI_OUTPUT_H;
        }else{
            MOSI_OUTPUT_L;
        }
        dat <<= 1;
        // 时钟线从低电平到高电平的变化时,MOSI数据线上的数据
        // 被写到595芯片的移位寄存器中
        SCK_OUTPUT_L;   // SCK拉低
        delay_us(10);
        SCK_OUTPUT_H;
        delay_us(10);
    }
}


spi.h
#ifndef __SPI_H__
#define __SPI_H__
#include "stm32mp157_gpio.h"

#define NSS_OUTPUT_L  GPIOE->ODR &= ~(0x01 << 11)
#define NSS_OUTPUT_H  GPIOE->ODR |= (0x01 << 11)
#define SCK_OUTPUT_L  GPIOE->ODR &= ~(0x01 << 12)
#define SCK_OUTPUT_H  GPIOE->ODR |= (0x01 << 12)
#define MOSI_OUTPUT_L  GPIOE->ODR &= ~(0x01 << 14)
#define MOSI_OUTPUT_H  GPIOE->ODR |= (0x01 << 14)
void spi_init(void);
void spi_write(unsigned char dat);
#endif

9.5 设备驱动编程

digital_tube.c
#include "../include/spi.h"
#include "../include/delay.h"
#include "../include/uart.h"

unsigned char code[] = {
    0x3f, //0
    0x06, //1
    0x5b, //2
    0x4f, //3
    0x66, //4
    0x6d, //5
    0x7d, //6
    0x07, //7
    0x7f, //8
    0x6f, //9
};
unsigned char which[] = {
    0x1, //sg0
    0x2, //sg1
    0x4, //sg2
    0x8, //sg3
};

void show_num(void){
    for(int i = 0; i < 10; i++){
        spi_write(0x0f);
        spi_write(code[i]);
        NSS_OUTPUT_L;
        delay_ms(1);
        NSS_OUTPUT_H;
        delay_ms(1000);
    }
}

void show_diff_num(void){
    int i = 0, j = 3000;
    while(j--){
        for(; i< 4; i++){
            spi_write(which[i]);
            spi_write(code[i]);
            NSS_OUTPUT_L;
            delay_ms(1);
            NSS_OUTPUT_H;

        }
        i=0;
    }
}


digital_tube.h
#ifndef __DIGITAL_TUBE_H__
#define __DIGITAL_TUBE_H__
void show_num(void);
void show_diff_num(void);
#endif
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容