嵌入式技术基础与实践-学习(学号32106200033)

注:如果点击连接GEC时卡住了,记得关闭蓝牙,蓝牙的模拟串口会影响。

第一章作业

嵌入式系统常用术语:

与硬件相关的术语

1.封装

  • 中文名:封装
  • 英语缩写:PKG
  • 英语全称:Packaging

2.印刷电路板

  • 中文名:印刷电路板
  • 英语缩写:PCB
  • 英语全称:Printed Circuit Board

3.模拟量与开关量

  • 中文名:模拟量
  • 英语缩写:AQ
  • 英语全称:Analog Quantity
  • 中文名:开关量
  • 英语缩写:SQ 或 DQ
  • 英语全称:Switch Quantity 或 Digital Quantity

与功能模块相关的术语

1.通用输入/输出GPIO

  • 中文名:通用输入/输出
  • 英语缩写:GPIO
  • 英语全称:General Purpose Input/Output

2.模数转换ADC与数模转换DAC

  • 中文名:模数转换
  • 英语缩写:ADC
  • 英语全称:Analog-to-Digital Converter
  • 中文名:数字模拟转换
  • 英语缩写:DAC
  • 英语全称:Digital-to-Analog Converter

3.脉冲宽度调制器PWM

  • 中文名:脉冲宽度调制
  • 英语缩写:PWM
  • 英语全称:Pulse Width Modulation

4.看门狗

  • 中文名:看门狗
  • 英语缩写:WDT
  • 英语全称:Watchdog Timer

5.液晶显示(LCD)

  • 中文名:液晶显示
  • 英语缩写:LCD
  • 英语全称:Liquid Crystal Display

6.发光二极管(LED)

  • 中文名:发光二极管
  • 英语缩写:LED
  • 英语全称:Light Emitting Diode

7.键盘

  • 中文名:键盘
  • 英语缩写:KBD 
  • 英语全称:Keyboard

与通信相关的术语

1.并行通信(同时发送多位)

  • 中文名:并行通信
  • 英语缩写:Parallel Comm.
  • 英语全称:Parallel Communication

2.串行通信UART(一位一位发送)

  • 中文名:串行通信
  • 英语缩写:UART
  • 英语全称:Universal Asynchronous Receiver

3.串行外设接口SPI

  • 中文名:串行外设接口
  • 英语缩写:SPI
  • 英语全称:Serial Peripheral Interface

4.集成电路互联总线I2C

  • 中文名:集成电路互联总线
  • 英语缩写:I2C
  • 英语全称:Inter-Integrated Circuit

5.通用串行总线USB

  • 中文名:通用串行总线
  • 英语缩写:USB
  • 英语全称:Universal Serial Bus

6.控制器局域网CAN

  • 中文名:控制器局域网
  • 英语缩写:CAN
  • 英语全称:Controller Area Network

7.边界扫描测试协议JTAG(工厂测试芯片)

  • 中文名:边界扫描测试协议
  • 英语缩写:BSTP
  • 英语全称:Boundary Scan Test Protocol

8.串行线调试技术SWD

  • 中文名:串行线调试技术
  • 英语缩写:SWD
  • 英语全称:Serial Wire Debug

运行实例:

编译、下载与运行第一个嵌入式程序

步骤一:硬件连线

步骤二:打开环境,导入工程

步骤三:编译工程

步骤四:连接GEC

步骤五:下载机器码

步骤六:观察运行结果

23e8e7a853454e54a525bfe6136ee03f.png01716323d88046f5b03e3df5b5e6cbd9.png6e809d9df84b42e483fd9ac0d62ae100.png

a45bdc4796ba425d92371f71496f7b25.jpeg8b3cd5813a754c6ab98abe87b28c7b39.jpeg60cf8171780744e697fa6ef2a0905b22.jpeg2429f025063344a5b38e5b5c7421a1e3.jpeg

步骤七: 通过串口观察运行情况

0c2c9ce896114ac5a28411abc29bf217.png

第二章作业

描述:打开04-Software/ch02/CH02-1-20220118工程目录编译下载运行,理解并学习main.s汇编源文件。

写出main.s中94~101行语句的C语言描述

修改main.s源文件,增加以下内容:
1、在第一行显示“广州大学”字样。
2、编写一个1+2+..+10的程序,将求和结果存入名为“sumresult”的内存单元中,并将求和结果用printf显示出来。

结论经过测试和观察,data_format输出格式输出的是R1;  而data_format1输出格式输出的是R1 : R2,且R1适合放内存地址,R2放结果数据。

C语言描述main.s中main_loop里的语句

#include<iostream>
using namespace std;

int* mMainLoopCount = new int(1); //地址
const  int MainLoopNUM = 10; // 常数(循环结束的阻止次数)

int main() {

    int  r2 = MainLoopNUM;  
    int r1 = *mMainLoopCount; // 取数
    // 为达到主循环次数设定值,继续循环
    for (int i = r1; r1 != r2; i++) {
        r1=i;
        *mMainLoopCount = r1;
    }
    // 达到主循环次数设定值
    delete mMainLoopCount; // 释放空间

    return 0;
}

代码

内存单元命名为sumresult

f98fceab1358496492c9c7b50f848c55.png

编写1-10的累加程序

51019ca8b7d54c4293041326404d2012.png

完整代码:

//=====================================================================
//文件名称:main.s
//功能概要:汇编编程调用GPIO构件控制小灯闪烁(利用printf输出提示信息)
//版权所有:SD-ARM(sumcu.suda.edu.cn)
//版本更新:20180810-20191018
//=====================================================================
.include "include.inc"    //头文件中主要定义了程序中需要使用到的一些常量
//(0)数据段与代码段的定义
//(0.1)定义数据存储data段开始,实际数据存储在RAM中
.section .data
//(0.1.1)定义需要输出的字符串,标号即为字符串首地址,\0为字符串结束标志
hello_information:           //字符串标号
    .ascii "广州大学\n"           
    .ascii "32106200033\n sumresult地址和结果:\n\0"
    
data_format:
    .ascii "%d\n\0"                 //printf使用的数据格式控制符

data_format1:
    .ascii "%08x:%02x\n\0"                 //printf使用的数据格式控制符,其中8表示输出位数,
                                         //0表示将输出的前面补上0,直到占满指定列宽为止
light_show1:	
	.ascii "LIGHT_BLUE:ON--\n\0"    //灯亮状态提示   
light_show2:	
	.ascii "LIGHT_BLUE:OFF--\n\0"   //灯暗状态提示
light_show3:
	.ascii "闪烁次数mLightCount=\0"  //闪烁次数提示
//(0.1.2)定义变量
.align 4               //.word格式四字节对齐
mMainLoopCount:		   //定义主循环次数变量
	.word 0
mFlag:				   //定义灯的状态标志,1为亮,0为暗
	.byte 'A'	
.align 4
mLightCount:
    .word 0

// 定义sumresult	
sumresult: 
	.word 0   

//(0.2)定义代码存储text段开始,实际代码存储在Flash中
.section   .text
.syntax unified        //指示下方指令为ARM和thumb通用格式
.thumb                 //Thumb指令集
.type main function    //声明main为函数类型                     
.global main           //将main定义成全局函数,便于芯片初始化之后调用
.align 2               //指令和数据采用2字节对齐,兼容Thumb指令集




//--------------------------------------------------------------------                        
//main.c使用的内部函数声明处

//--------------------------------------------------------------------
//主函数,一般情况下可以认为程序从此开始运行(实际上有启动过程,参见书稿)
main:
//(1)======启动部分(开头)主循环前的初始化工作======================
//(1.1)声明main函数使用的局部变量

//(1.2)【不变】关总中断
	cpsid i   
//(1.3)给主函数使用的局部变量赋初值
	
//(1.4)给全局变量赋初值
	
//(1.5)用户外设模块初始化
//  初始化蓝灯, r0、r1、r2是gpio_init的入口参数
	ldr r0,=LIGHT_BLUE     //r0指明端口和引脚(用=,因常量>=256,需用ldr)
	mov r1,#GPIO_OUTPUT    //r1指明引脚方向为输出
	mov r2,#LIGHT_OFF       //r2指明引脚的初始状态为亮
	bl  gpio_init          //调用gpio初始化函数
//  初始化串口UART_User1
	mov r0,#UART_User       //串口号
	ldr r1,=UART_BAUD       //波特率
	bl uart_init            //调用uart初始化函数
//(1.6)使能模块中断
    mov r0,#UART_User       //串口号
    bl  uart_enable_re_int  //调用uart中断使能函数

//(1.7)【不变】开总中断
	cpsie  i  
//显示hello_information定义的字符串
	ldr r0,=hello_information   //待显示字符串首地址
	bl  printf		            //调用printf显示字符串
	
	
	
   	
	// 循环计数器R1为10	
    MOV R2, #0      
	MOV R0, #10	 

P1: 	// 标记
	// r2=r0+r2
	ADD R2,R2, R0  
	// r0--
    SUB R0,R0, #1   
   	//  如果R1大于0,则跳转回P1继续循环  
   	CMP R0, #0
    BNE P1      	
	// 将sumresult的地址加载到R1
	MOV R1, #sumresult 
	// 存到sumresult
	STR R2, [R1]
	// 输出格式
	ldr R0,=data_format1  // R1 : R2
	// MOV R1 ,#1
	ldr R1,= #sumresult 
	// 输出1+2+...+10的结果
	bl printf
    
	bl .   //在此打桩(.表示当前地址),理解发光二极管为何亮起来了?

	
//(1)======启动部分(结尾)=======================================

//(2)======主循环部分(开头)=====================================
main_loop:                      //主循环标签(开头)
//(2.1)主循环次数变量mMainLoopCount+1
		ldr r2,=mMainLoopCount     //r2←mMainLoopCount的地址
		ldr r1, [r2]
		add r1,#1
		str r1,[r2]	
//(2.2)未达到主循环次数设定值,继续循环
        ldr r2,=MainLoopNUM
		cmp r1,r2
		blO  main_loop     //未达到,继续循环
//(2.3)达到主循环次数设定值,执行下列语句,进行灯的亮暗处理

//测试代码部分[理解机器码存储]
Label:	
	MOV  R0,#0xDE               //立即数范围为0x00~0xFF
	
	ldr r0,=data_format1        //输出格式送r0 
	ldr r1,=Label               //r1中是Label地址
	ldrb r2,[r1]                //r2中是Label地址中的数据
	bl  printf	
	
	
	ldr r0,=data_format1         //输出格式送r0 
	ldr r1,=Label+1              //r1中是Label+1地址
	ldrb r2,[r1]                 //r2中是Label+1地址中的数据
	bl  printf	
	
	ldr r0,=data_format1         //输出格式送r0 
	ldr r1,=Label+2              //r1中是Label+2地址
	ldrb r2,[r1]                 //r2中是Label+2地址中的数据
	bl  printf	
	
	ldr r0,=data_format1         //输出格式送r0 
	ldr r1,=Label+3              //r1中是Label+3地址
	ldrb r2,[r1]                 //r2中是Label+3地址中的数据
	bl  printf	

//(2.3.1)清除循环次数变量
		ldr r2,=mMainLoopCount     //r2←mMainLoopCount的地址
		mov r1,#0
		str r1,[r2]	
//(2.3.2)如灯状态标志mFlag为'L',灯的闪烁次数+1并显示,改变灯状态及标志	
		//判断灯的状态标志
		ldr r2,=mFlag		   
		ldr r6,[r2]
		cmp r6,#'L'			
		bne main_light_off	   //mFlag不等于'L'转
		//mFlag等于'L'情况
		ldr r3,=mLightCount	   //灯的闪烁次数mLightCount+1
		ldr r1,[r3]
		add r1,#1				
		str r1,[r3]
		ldr r0,=light_show3   //显示“灯的闪烁次数mLightCount=”
		bl  printf				
		ldr r0,=data_format    //显示灯的闪烁次数值
		ldr r2,=mLightCount
		ldr r1,[r2]
		bl  printf	
		ldr r2,=mFlag           //灯的状态标志改为'A'
		mov r7,#'A'
		str r7,[r2]             
		ldr r0,=LIGHT_BLUE      //亮灯
		ldr r1,=LIGHT_ON
		bl  gpio_set          
		ldr r0, =light_show1    //显示灯亮提示
		bl  printf	
		//mFlag等于'L'情况处理完毕,转
		b main_exit  
//(2.3.3)如灯状态标志mFlag为'A',改变灯状态及标志
main_light_off:
        ldr r2,=mFlag		   //灯的状态标志改为'L'        
		mov r7,#'L'
		str r7,[r2]   
        ldr r0,=LIGHT_BLUE      //暗灯
		ldr r1,=LIGHT_OFF
		bl  gpio_set  
        ldr r0, =light_show2    //显示灯暗提示
		bl  printf	
main_exit:
	b main_loop                 //继续循环
//(2)======主循环部分(结尾)=====================================
.end     //整个程序结束标志(结尾)

结果

4942455e76804eaea229bc44c5420c97.png

第三章作业

给出所用MCU芯片型号标识所获得的信息。

(对照命名格式)

举例:型号标识:ATMEGA328P-AU

  1. 制造商代码(Manufacturer Code)AT  这通常是制造商的缩写。在这个例子中,AT可能代表Atmel(现为Microchip Technology的一部分),一家知名的微控制器和微处理器制造商。

  2. 系列或产品家族(Series/Family)MEGA   这表示MCU属于特定的产品系列或家族。对于Atmel来说,MEGA系列通常指的是基于AVR架构的高性能微控制器。

  3. 型号(Model)328 - 这个数字标识了MCU的具体型号。在这个例子中,它可能表示这是一款具有特定内存大小、处理能力或其他特性的微控制器。

  4. 封装类型(Package Type)P  这个字母表示MCU的封装类型。P可能代表某种特定的封装,如PDIP(Platinum Dual In-line Package)或其他封装形式。

  5. 版本或修订(Version/Revision)-A  这个后缀可能表示MCU的版本或修订级别。A可能意味着这是原始设计之后的某个改进版本,可能包括修正、性能提升或新特性的添加。

  6. 额外信息(Additional Information):在这个例子中,型号标识中没有提供额外的信息,但有时制造商会添加额外的字母或数字来指示温度范围、RoHS合规性、引脚数量等。

给出所用MCU芯片的RAM及Flash大小、地址范围。

ATMEGA328P-AU的MCU为例:

  1. RAM(随机存取存储器)

    • 大小:ATMEGA328P通常有2KB的RAM。这提供了足够的工作内存来存储变量和临时数据。
    • 地址范围:RAM的地址范围通常是从0x0000到0x07FF。2KB的地址空间
  2. Flash Memory(闪存)

    • 大小:ATMEGA328P通常配备有32KB的闪存。这意味着它有足够的存储空间来存储程序代码和常量数据。
    • 地址范围:闪存的地址范围通常是从0x0000到0x7FFF。这是32KB的地址空间,每个字节都有一个唯一的地址。

第四章作业

1.学习CH04示例程序,包括gpio.c和4个工程中的main.c.
2.给出 gpio_set(LIGHT_RED,LIGHT_OFF); 语句中,LIGHT RED和LIGHT OFF的值是多少?贴出每一步的查找截图。

 GPIO-BlueLight_20230328工程

de9f0816593f461a8f969f0de89052b1.png

4bc2477a75ca4255b5290bc3bd2da26b.png

3d60c4efa8c641beb0fdb2562b014ee6.png

20483ac509a141a4b751d5ec41e3e154.png

GPIO-Output-Component_STM32L431_20200928工程

ad9b1043f7e8473abfebb5e8824f548a.png

fa3e3e9924854b6d9d9b04e2d1f7ce32.png
3.用直接地址编程方式,实现红绿蓝三灯轮流闪烁

修改代码:

377fa20276c24fe5a0a79c6e7510bb42.png

完整代码:

//====================================================================
//文件名称:main.c(应用工程主函数)
//框架提供:SD-Arm(sumcu.suda.edu.cn)
//版本更新:2017.08, 2020.05
//功能描述:见本工程的<01_Doc>文件夹下Readme.txt文件
//====================================================================

#define GLOBLE_VAR
#include "includes.h"      //包含总头文件

//----------------------------------------------------------------------
//声明使用到的内部函数
//main.c使用的内部函数声明处

//----------------------------------------------------------------------
//主函数,一般情况下可以认为程序从此开始运行(实际上有启动过程见书稿)
int main(void)
{
    //(1)======启动部分(开头)==========================================
    //(1.1)声明main函数使用的局部变量
    uint32_t mMainLoopCount;  //主循环使用的记录主循环次数变量
    uint8_t  mFlag;            //主循环使用的临时变量
    
    //(1.2)【不变】关总中断
    DISABLE_INTERRUPTS;

    
    //(1.3)给主函数使用的局部变量赋初值
    mMainLoopCount = 0;     //主循环使用的记录主循环次数变量
    mFlag='B';              //主循环使用的临时变量:蓝灯状态标志
    

    //(1.4)给全局变量赋初值
    
    //(1.5)用户外设模块初始化
    // B口9脚(蓝灯,低电平点亮)
    //(1.5.1)声明变量
    volatile uint32_t* RCC_AHB2;    //GPIO的B口时钟使能寄存器地址
    volatile uint32_t* gpio_ptr;    //GPIO的B口基地址
    volatile uint32_t* gpio_mode;   //引脚模式寄存器地址=口基地址
	volatile uint32_t* gpio_bsrr;   //置位/复位寄存器地址
	volatile uint32_t* gpio_brr;    //GPIO位复位寄存器
	//(1.5.2)变量赋值
    RCC_AHB2=(uint32_t*)0x4002104C;   //GPIO的B口时钟使能寄存器地址
	gpio_ptr=(uint32_t*)0x48000400;   //GPIO的B口基地址
	gpio_mode=gpio_ptr;    //引脚模式寄存器地址=口基地址
    gpio_bsrr=gpio_ptr+6;  //置位/复位寄存器地址
    gpio_brr=gpio_ptr+10;  //GPIO位复位寄存器
    //(1.5.3)GPIO初始化
    //(1.5.3.1)使能相应GPIOB的时钟
    *RCC_AHB2|=(1<<1);       //GPIOB的B口时钟使能
    //(1.5.3.1)定义B口9脚为输出引脚(令D19、D18=01)方法如下:
    *gpio_mode &= ~(3<<18);  //0b11111111111100111111111111111111; 
    *gpio_mode |=(1<<18);    //0b00000000000001000000000000000000;
    //(思考:为什么这样赋值?答案见本文件末尾注①)
    
    //(1.6)使能模块中断
    
    //(1.7)【不变】开总中断
    ENABLE_INTERRUPTS;
    
    printf("-----------------------------------------------------\r\n"); 
    printf("金葫芦提示:直接地址方式进行GPIO输出\r\n"); 
    printf("    这个编程有点难以看懂,使用构件编程就简单多了,\r\n");
    printf("    但是构件制作要经过这一关,因此,我们把构件制作与\r\n");
    printf("    基于构件的编程分成不同过程。学习嵌入式系统,\r\n");
    printf("    以理解GPIO、UART、定时器、Flash、ADC、...\r\n");
    printf("    知识要素为出发点,学会正确运用构件进行应用编程,\r\n");
    printf("    理解和掌握2~3个简单构件的制作方法即可。\r\n");
    printf("----------------------------------------------------\r\n"); 
    
    //for(;;) {  }     //在此打桩,理解蓝色发光二极管为何亮起来了?
    
    //(1)======启动部分(结尾)==========================================
    
    //(2)======主循环部分(开头)=========================================
    
//    gpio_init(LIGHT_RED,GPIO_OUTPUT,LIGHT_ON);

    
    *gpio_bsrr|=(1<<7);     //设置灯“暗”
    *gpio_bsrr|=(1<<8);     //设置灯“暗”
    *gpio_bsrr|=(1<<9);     //设置灯“暗”

    for(;;)     //for(;;)(开头)
    {
        
        //(2.1)主循环次数+1,并判断是否小于特定常数
        mMainLoopCount++;                         //+1
        if (mMainLoopCount<=6556677)  continue;   //如果小于特定常数,继续循环
        //(2.2)主循环次数超过特定常数,灯状态进行切换(这样灯会闪烁)
        mMainLoopCount=0;      //清主循环次数
        //切换灯状态
        

        if (mFlag=='B')   //若灯状态标志为'B'
        {
         	*gpio_bsrr|=(1<<7);     //设置灯“暗”
			*gpio_brr|=(1<<9);     //设置灯“亮”	
        	
            printf("蓝灯:亮\r\n");   //通过调试串口输出灯的状态
            mFlag='G';              //改变状态标志
        
//        	gpio_set(LIGHT_RED,LIGHT_OFF);
//        	gpio_set(LIGHT_BLUE,LIGHT_ON);
			
        }
        
       else  if(mFlag=='G')                //否则,若灯状态标志为'G'    
        {
//        	gpio_set(LIGHT_RED,LIGHT_OFF);
//        	gpio_set(LIGHT_GREEN,LIGHT_ON);

			*gpio_bsrr|=(1<<9);     //设置灯“暗”
			*gpio_brr|=(1<<8);     //设置灯“亮”	

            printf("绿灯:亮\r\n");   //通过调试串口输出灯的状态
            mFlag='R';              //改变状态标志
        }	
       else 
        {
//        	gpio_set(LIGHT_GREEN,LIGHT_OFF);
//        	gpio_set(LIGHT_RED,LIGHT_ON);
        	*gpio_bsrr|=(1<<8);     //设置灯“暗”
			*gpio_brr|=(1<<7);     //设置灯“亮”	
			
            printf("红灯:亮\r\n");  //通过调试串口输出灯的状态
            mFlag='B';             //改变状态标志

        }
        
    }     //for(;;)结尾
    //(2)======主循环部分(结尾)========================================	
}

/*
注① 这样做的目的在于更改了D19、D18两位的值,而不改变其他位的值,不这样的话,	
     可能把不需要改变的位也改变了!
*/
//======以下为主函数调用的子函数存放处=====================================

//======以下为主函数调用的子函数===========================================


//========================================================================
/*
 知识要素:
 (1)main.c是一个模板,该文件所有代码均不涉及具体的硬件和环境,通过调用构件
      实现对硬件的干预。
 (2)本文件中对宏GLOBLE_VAR进行了定义,所以在包含"includes.h"头文件时,会定
      义全局变量,在其他文件中包含"includes.h"头文件时,
      编译时会自动增加extern
 */

结果

6272b913879747c6ada1910f55fd3f3b.jpeg

cc95b38b67264b6c865a81060ff34c1d.jpeg

f8c5c20cdaf4454a96dd3af4caef8c9b.jpeg
4.用调用构件方式,实现红绿蓝的八种组合轮流闪烁

结果:

33d329ecee504ff5bc3e2db42984b873.png

d09122cde6c64ff7ac59ad22111b4b9a.png

d348aafb65f04106a921fe9f1a13a276.jpeg

07e43b7a227d4d839e99e27c58504f68.jpeg

b863cc493ecf4298a1fc9d3ad342a94c.jpeg

c88aaa4d191e4908b12d712808e189f5.jpeg

b232fd44ba2340b6b5fafaf59c095ff9.jpeg

d143b662684247f4a28272b4411ec850.jpeg

080b2eefd2e548ab8ba02c5afa118fb0.jpeg

完整代码

//======================================================================
//文件名称:main.c(应用工程主函数)
//框架提供:SD-Arm(sumcu.suda.edu.cn)
//版本更新:20191108-20200419
//功能描述:见本工程的..\01_Doc\Readme.txt
//移植规则:【固定】
//======================================================================
#define GLOBLE_VAR
#include "includes.h"      //包含总头文件

//----------------------------------------------------------------------
//声明使用到的内部函数
//main.c使用的内部函数声明处

//----------------------------------------------------------------------
//主函数,一般情况下可以认为程序从此开始运行(实际上有启动过程,参见书稿)
int main(void)
{
//(1)======启动部分(开头)==========================================
//(1.1)声明main函数使用的局部变量
	uint32_t mMainLoopCount;  //主循环次数变量
	uint8_t  mFlag;           //灯的状态标志
	uint32_t mLightCount;     //灯的状态切换次数

//(1.2)【不变】关总中断
	DISABLE_INTERRUPTS;

//(1.3)给主函数使用的局部变量赋初值
    mMainLoopCount=0;    //主循环次数变量
	mFlag='B';           //灯的状态标志
	mLightCount=0;       //灯的闪烁次数

//(1.4)给全局变量赋初值
   
//(1.5)用户外设模块初始化
	gpio_init(LIGHT_BLUE,GPIO_OUTPUT,LIGHT_ON);	//初始化红灯
	

   
//(1.6)使能模块中断
    
   
//(1.7)【不变】开总中断
	ENABLE_INTERRUPTS;
	
	printf("------------------------------------------------------\n");   
    printf("金葫芦提示:构件法输出控制小灯亮暗   \n");
    printf("    第一次用构件方法点亮的蓝色发光二极管,\n");
    printf("    这是进行应用编程的第一步,可以在此基础上,\n");
    printf("   “照葫芦画瓢”地继续学习实践。\n");
    printf("    例如:改为绿灯;调整闪烁频率等。\n");
    printf("------------------------------------------------------\n"); 
    
    //asm ("bl .");
    
    //for(;;) {  }     //在此打桩,理解蓝色发光二极管为何亮起来了?
    
        
//(1)======启动部分(结尾)==========================================

//(2)======主循环部分(开头)========================================


	gpio_set(LIGHT_GREEN,LIGHT_OFF);  //灯“暗”
	gpio_set(LIGHT_BLUE,LIGHT_OFF);  //灯“暗”
	gpio_set(LIGHT_RED,LIGHT_OFF);  //灯“暗”
	for(;;)   //for(;;)(开头)
	{
//(2.1)主循环次数变量+1
        mMainLoopCount++;
//(2.2)未达到主循环次数设定值,继续循环
		if (mMainLoopCount<=12888999)  continue;
//(2.3)达到主循环次数设定值,执行下列语句,进行灯的亮暗处理
//(2.3.1)清除循环次数变量
		mMainLoopCount=0; 
//(2.3.2)如灯状态标志mFlag为'R',灯的闪烁次数+1并显示,改变灯状态及标志
		// 红色
		if (mFlag=='R')                    //判断灯的状态标志
		{
			mLightCount++;  
			printf("灯的闪烁次数 mLightCount = %d\n",mLightCount);
			mFlag='G';                       //灯的状态标志
			gpio_set(LIGHT_RED,LIGHT_ON);  //灯“亮”
			printf("标志为R  LIGHT_RED=%d ,LIGHT_ON = %d\n",LIGHT_RED,LIGHT_ON);
			printf(" 红灯:ON--\n\n");   //串口输出灯的状态
		}
//(2.3.3)如灯状态标志mFlag为'G',改变灯状态及标志
		// 绿色
		else if (mFlag=='G')                    //判断灯的状态标志
		{
			mLightCount++;  
			printf("灯的闪烁次数 mLightCount = %d\n",mLightCount);
			mFlag='B';                       //灯的状态标志
			gpio_set(LIGHT_RED,LIGHT_OFF);  //灯“暗”
			gpio_set(LIGHT_GREEN,LIGHT_ON);  //灯“亮”
			printf("标志为G  LIGHT_GREEN=%d ,LIGHT_ON = %d\n",LIGHT_GREEN,LIGHT_ON);
			printf(" 绿灯:ON--\n\n");   //串口输出灯的状态
		}
		// 蓝色
		else if (mFlag=='B')                    //判断灯的状态标志
		{
			mLightCount++;  
			printf("灯的闪烁次数 mLightCount = %d\n",mLightCount);
			mFlag='Y';                       //灯的状态标志
			
			gpio_set(LIGHT_GREEN,LIGHT_OFF);  //灯“暗”
			gpio_set(LIGHT_BLUE,LIGHT_ON);  //灯“亮”
			gpio_set(LIGHT_RED,LIGHT_OFF);  //灯“暗”
			printf("标志为B  LIGHT_BLUE=%d ,LIGHT_ON = %d\n",LIGHT_BLUE,LIGHT_ON);
			printf(" 蓝灯:ON--\n\n");   //串口输出灯的状态
		}
		// 黄色(红绿)
		else if (mFlag=='Y')                    //判断灯的状态标志
		{
			mLightCount++;  
			printf("灯的闪烁次数 mLightCount = %d\n",mLightCount);
			mFlag='P';                       //灯的状态标志
			
			gpio_set(LIGHT_RED,LIGHT_ON);  
			gpio_set(LIGHT_BLUE,LIGHT_OFF);  
			gpio_set(LIGHT_GREEN,LIGHT_ON);  
			printf(" 黄灯:ON--\n\n");   //串口输出灯的状态
		}
		// 紫色(红蓝)
		else if (mFlag=='P')                    //判断灯的状态标志
		{
			mLightCount++;  
			printf("灯的闪烁次数 mLightCount = %d\n",mLightCount);
			mFlag='C';                       //灯的状态标志
			
			gpio_set(LIGHT_RED,LIGHT_ON); 
			gpio_set(LIGHT_GREEN,LIGHT_OFF);
			gpio_set(LIGHT_BLUE,LIGHT_ON);   
			printf(" 紫灯:ON--\n\n");   //串口输出灯的状态
		}
		// 青色(蓝绿)
		else if (mFlag=='C')                    //判断灯的状态标志
		{
			mLightCount++;  
			printf("灯的闪烁次数 mLightCount = %d\n",mLightCount);
			mFlag='W';                       //灯的状态标志
			gpio_set(LIGHT_BLUE,LIGHT_ON);   
			gpio_set(LIGHT_RED,LIGHT_OFF);
			gpio_set(LIGHT_GREEN,LIGHT_ON);
			printf(" 青灯:ON--\n\n");   //串口输出灯的状态
		}
		// 白色(红绿蓝)
		else if (mFlag=='W')                    //判断灯的状态标志
		{
			mLightCount++;  
			printf("灯的闪烁次数 mLightCount = %d\n",mLightCount);
			mFlag='D';                       //灯的状态标志
			gpio_set(LIGHT_BLUE,LIGHT_ON);   
			gpio_set(LIGHT_GREEN,LIGHT_ON);
			gpio_set(LIGHT_RED,LIGHT_ON);  
			printf(" 白灯:ON--\n\n");   //串口输出灯的状态
		}
		// 暗色
		else if (mFlag=='D')                    //判断灯的状态标志
		{
			mLightCount++;  
			printf("灯的闪烁次数 mLightCount = %d\n",mLightCount);
			mFlag='R';                       //灯的状态标志
			gpio_set(LIGHT_GREEN,LIGHT_OFF);
			gpio_set(LIGHT_BLUE,LIGHT_OFF); 
			gpio_set(LIGHT_RED,LIGHT_OFF);  
			printf(" 暗灯:ON--\n\n");   //串口输出灯的状态
		}
		
		
	}  //for(;;)结尾
//(2)======主循环部分(结尾)========================================
}   //main函数(结尾)


//======以下为主函数调用的子函数===========================================


//========================================================================
/*
 知识要素:
 (1)main.c是一个模板,该文件所有代码均不涉及具体的硬件和环境,通过调用构件
      实现对硬件的干预。
 (2)本文件中对宏GLOBLE_VAR进行了定义,所以在包含"includes.h"头文件时,会定
      义全局变量,在其他文件中包含"includes.h"头文件时,
      编译时会自动增加extern
 */

bug: 实践发现红绿蓝三种颜色在程序只能出现其中的2种(比如红绿的组合或者蓝绿的组合)

解决办法:红绿蓝都初始化

第六章作业

1、编写UART_2串口发送程序时,初始化需要设置哪些参数?

  • 波特率:(波特率决定了数据传输的速度,是串口通信中最重要的参数之一。它需要匹配接收设备设置的波特率)   uart_init(UART_User,115200); //初始化串口模块
  • 数据位:(指定每个字符中的数据位数,常见的有7位、8位等)
  • 停止位:(决定每个字符传输结束后的停止位个数,常见的有1位或2位)
  • 奇偶校验位:(设置奇偶校验,用于错误检测。可以设置为无校验、奇校验或偶校验)
  • 流控制:(确定是否使用硬件或软件流控制,如RTS/CTS)
  • UART 端口: (选择用于通信的串口硬件端口)
  • 中断使能: (根据程序设计,决定是否使用中断来管理UART通信)
  • 接收/发送寄存器:(初始化UART的接收和发送寄存器)
  • 时钟源:(设置UART时钟源,这可能会影响到波特率的生成)
  • FIFO :(如果硬件支持,可能需要配置FIFO缓冲区的大小和触发级别)
  • 硬件接口配置:(对于某些系统,可能还需要配置用于UART通信的硬件接口,如GPIO引脚设置为UART功能)

2、假设速度为115200,系统时钟为72MHz,波特率寄存器BRR中的值应该是多少?

  • 对于 OVER8 模式:

    usartdiv = (uint16_t)((SystemCoreClock / 115200) * 2);

  • 对于非 OVER8 模式:

    usartdiv = (uint16_t)(SystemCoreClock / 115200);

波特率寄存器 BRR 中的值应分别为 1250(OVER8 模式)和 625(非 OVER8 模式)


3、中断向量表在哪个文件中?表中有多少项?给出部分截图。

       中断向量表总共98项,前16个为内核中断,后面的为非内核中断


4、以下是中断源使能函数,假设中断源为TIM6,将函数实例化(写出各项具体数值)。

传入参数为IRQn(54)

ISER[(((uint32_t)IRQn) >> 5UL)]

函数内部实现将IRQ号值右移5位,54>>5=1 , 索引值为1

(uint32_t)(1UL << (((uint32_t)IRQn) & 0x1FUL))

54 & 0x1F = 22 , 第22位

54/32=1    54%32=22, 将ISER[1]的第22位设置为1

5、假设将UART 2和TIM6交换其在中断向量表中的位置和IRQ号,UART_2可以正常中断吗?

UART_2 (38)的中断号将变为54,  TIM6 (54)的中断号将变为38

如果 UART_2 新的中断号没有被占用, 且原来可以正常中断, 那么仍然可以正常中断。

中断号的改变只是改变了向量表中中断函数入口地址。

6、实现UART_2串口的接收程序 

当收到字符时:

①在电脑的输出窗口显示下一个字符,如收到A显示B;

②亮灯:收到字符G,亮绿灯;收到字符R,亮红灯;收到字符B,亮蓝灯;收到其他字符,不亮灯。

实现方式:

(1) 用构件调用方式实现;

    //(1.1)声明main函数使用的局部变量	
    uint8_t  mi=0;             //临时变量
	char re;
    //(1.5)用户外设模块初始化
	gpio_init(LIGHT_BLUE,GPIO_OUTPUT,LIGHT_ON);	//初始化蓝灯
	gpio_init(LIGHT_RED,GPIO_OUTPUT,LIGHT_ON);		//初始化红灯
	gpio_init(LIGHT_GREEN,GPIO_OUTPUT,LIGHT_ON);	//初始化绿灯
	uart_init(UART_User,115200);                    //初始化串口模块   
    //(2)======主循环部分(开头)========================================
	for(;;) {  //for(;;)(开头)
		mMainLoopCount++;
		if (mMainLoopCount<=35000000)  continue;
		mMainLoopCount=0;   
		mLightCount++;  
		
		re=(char) uart_re1(UART_User,&mi);
		// 有数据
		if(mi){
			if(re == 'B'){
				gpio_set(LIGHT_BLUE,LIGHT_ON); 
				gpio_set(LIGHT_RED,LIGHT_OFF);  
				gpio_set(LIGHT_GREEN,LIGHT_OFF);
			
	          }else if(re=='R'){
	          	gpio_set(LIGHT_RED,LIGHT_ON);
				gpio_set(LIGHT_GREEN,LIGHT_OFF);
		        gpio_set(LIGHT_BLUE,LIGHT_OFF); 
			  } else if(re == 'G'){
			  	gpio_set(LIGHT_GREEN,LIGHT_ON);
	          	gpio_set(LIGHT_BLUE,LIGHT_OFF); 
				gpio_set(LIGHT_RED,LIGHT_OFF);  
	          }
	          re+=1;
	          uart_send1(UART_User,re);
		}else{
		// 没收到数据
			gpio_set(LIGHT_RED,LIGHT_OFF);  
			gpio_set(LIGHT_GREEN,LIGHT_OFF);
			gpio_set(LIGHT_BLUE,LIGHT_OFF); 
          	re+=1;
          	uart_send1(UART_User,re);
		}
	
     
    
	}  //for(;;)结尾
	

//(2)======主循环部分(结尾)========================================









结果:

(2) UART部分用直接地址方式实现(即不调用uart.c中的函数,其他部分如GPIO、中断设置可调用函数)。

    //(1.1)声明main函数使用的局部变量
    uint8_t receivedChar;
     //(1.5)用户外设模块初始化
    gpio_init(LIGHT_BLUE,GPIO_OUTPUT,LIGHT_ON);	//初始化蓝灯
    gpio_init(LIGHT_RED,GPIO_OUTPUT,LIGHT_ON);	//初始化红灯
    gpio_init(LIGHT_GREEN,GPIO_OUTPUT,LIGHT_ON);	//初始化绿灯
     //(2)======主循环部分(开头)========================================
    for(;;)
    {
         // 接收缓冲区不满,接受字符
    	if (*uart_isr & (0x1UL << 5U)) {
        // 读取接收到的字符
        receivedChar = (uint8_t)(*uart_tdr & 0xFF);

        // 接收到字符时的处理
        switch (receivedChar) {
             case 'B':
                gpio_set(LIGHT_BLUE, LIGHT_ON);   
                gpio_set(LIGHT_RED, LIGHT_OFF);    
                gpio_set(LIGHT_GREEN, LIGHT_OFF);  
                 printf("蓝灯亮\n");
                break;
            case 'R':
                gpio_set(LIGHT_RED, LIGHT_ON);    
                gpio_set(LIGHT_GREEN, LIGHT_OFF);  
                gpio_set(LIGHT_BLUE, LIGHT_OFF);   
                 printf("红灯亮\n");
                break;
            case 'G':
                gpio_set(LIGHT_GREEN, LIGHT_ON);  
                gpio_set(LIGHT_RED, LIGHT_OFF);    
                gpio_set(LIGHT_BLUE, LIGHT_OFF);   
                printf("绿灯亮\n");
                break;
          
            default:
                // 其他字符不亮灯
                gpio_set(LIGHT_RED, LIGHT_OFF);
                gpio_set(LIGHT_GREEN, LIGHT_OFF);
                gpio_set(LIGHT_BLUE, LIGHT_OFF);
                 printf("不亮灯\n");
                break;
            }
             printf("显示:%c\n",receivedChar+1);
        }

    }
    
    
    
    
    
    //(2)======主循环部分(结尾)========================================

结果:

实验要求使用两种不同的实现方式:构件调用方式和直接地址方式。构件调用方式通常指使用库函数简化编程,这种方式易于实现,但可能在性能上有所妥协。直接地址方式则要求直接操作硬件寄存器,这种方式对硬件的控制更为精细,但编程难度较高。

在实现程序时,需要考虑以下几个关键步骤:

  1. 初始化UART:设置正确的波特率和数据格式,使能接收和发送功能。

  2. 中断服务程序:编写中断服务程序以响应UART接收到数据的事件。在中断服务程序中,需要读取接收到的字符,并根据实验要求执行后续动作。

  3. 字符处理:对于显示下一个字符的功能,可以设计一个简单的逻辑或查找表,将接收到的字符映射到下一个字符。

  4. LED灯控制:根据接收到的字符,编写逻辑控制GPIO引脚的电平,以点亮相应的LED灯。例如,接收到'G'时点亮绿灯,'R'时点亮红灯,'B'时点亮蓝灯。

  5. 硬件操作:无论是使用构件调用方式还是直接地址方式,都需要对UART和GPIO的硬件寄存器进行操作。直接地址方式要求对这些寄存器有更深入的了解。

  6. 测试与调试:完成程序后,需要进行充分的测试,确保UART通信稳定,字符处理和LED灯控制逻辑正确无误。

第七章作业

1、利用SysTick定时器编写倒计时程序,如初始设置为2分30秒,每秒在屏幕上输出一次时间,倒计时为0后,红灯亮,停止屏幕输出,并关闭SysTick定时器的中断。

uint8_t  mSec;	        //记当前秒的值
    uint8_t  mMi=2;
uint8_t  mSe=30;	
   uint8_t  sumTime=mMi*60+mSe;
    uint8_t m=0;
    uint8_t d=0;
    for(;;)     //for(;;)(开头)
    {
   		if (gTime[2] == mSec) continue;
   		mSec=gTime[2];
   		
		m=gTime[1]*60+gTime[2];
		d=sumTime-m;
        printf("%d:%d:%d\n",gTime[0],d/60,d%60);
        //切换灯状态
        if(gTime[0]==0&& gTime[1]==mMi && gTime[2]==mSe){
        	gpio_set(LIGHT_RED,LIGHT_ON); 
		   DISABLE_INTERRUPTS;
		   wdog_stop();
        }
    }     //for(;;)结尾

结果:

2、利用RTC显示日期(年月日、时分秒),每秒更新。并设置某个时间的闹钟。闹钟时间到时,屏幕上显示有你的姓名的文字,并点亮绿灯。

isr.c

//======================================================================
//程序名称:RTC_WKUP_IRQHandler
//函数参数:无
//中断类型:RTC闹钟唤醒中断处理函数
//======================================================================
 void RTC_WKUP_IRQHandler(void)
 {
 	 uint8_t hour,min,sec;
 	 uint8_t  year,month,date,week;
 	 
 	 uint8_t  alarm_year;
	 uint8_t  alarm_month;
	 uint8_t  alarm_day;
	 uint8_t  alarm_hour;
	 uint8_t  alarm_minute;
	 uint8_t  alarm_sec;

	  alarm_year=24;
	  alarm_month=5;
	  alarm_day=23;
	  alarm_hour=11;
	  alarm_minute=23;
	  alarm_sec=3;
	  
 	 gpio_init(LIGHT_GREEN,GPIO_OUTPUT,LIGHT_OFF);	//初始化蓝灯
 	 char *p;
	 if(RTC_PeriodWKUP_Get_Int())         //唤醒中断的标志
	 {
	 	 RTC_PeriodWKUP_Clear();           //清除唤醒中断标志
	 	 RTC_Get_Date(&year,&month,&date,&week); //获取RTC记录的日期
		 RTC_Get_Time(&hour,&min,&sec);    //获取RTC记录的时间
		 p=NumToStr("%02d/%02d/%02d %02d:%02d:%02d 星期%d\n",year,month,date,hour,min,sec,week);
		 uart_send_string(UART_User,p);
		 printf("%02d/%02d/%02d %02d:%02d:%02d 星期%d\n",year,month,date,hour,min,sec,week); 
		 if (alarm_year==year && alarm_month==month && alarm_day==date && alarm_hour==hour && alarm_minute==min && alarm_sec ==sec )                    //判断灯的状态标志
		{	
			printf("文锦松");                     
			gpio_set(LIGHT_GREEN,LIGHT_ON);  //灯“亮”	
		
			DISABLE_INTERRUPTS;      //关总中断
		}
		
	 }
 }

结果:

3、利用PWM脉宽调制,交替显示红灯的5个短闪和5个长闪。

uint8_t dFlag=1;
    for(uint8_t i=1;i<=5;i++)     //for(;;)(开头)
    {
        pwm_update(PWM_USER,m_duty);         //调节占空比
        m_duty=m_duty+5.0;                   //占空比每次递增5
        if (m_duty>=90.0) m_duty=1.0;       //当占空比大于90后置为1,重新进行循环。
        for (m_i=0;m_i<3;m_i++)            //m_i<3为了控制未知周期内相同占空比的波形只打印三次
        {
            m_K=0;                        //保证每次输出打印完整的PWM波,再进入下一个循环                 
            do 
            {
                mFlag=gpio_get(PWM_USER);
                if ((mFlag==1)&&(Flag==1))
                {
                  	// 高电平亮
                    Flag=0;
                    m_K++; 
                    //小灯反转
                    gpio_reverse(LIGHT_RED);
                    if(dFlag==1){
                    	Delay_ms(1000);
                    	printf("第%d次长闪\n",i);
                        dFlag=0;  	
                   		continue;
                    }
                    dFlag=1;
                    printf("第%d次短闪\n",i);
                }
                else if ((mFlag==0)&&(Flag==0))
                {
           			// 低电平暗
                    Flag=1;
                    m_K++;
                    gpio_reverse(LIGHT_RED);
                }
            }
            while (m_K<1);
        }
}  //for(;;)结尾

结果:

4、GEC39定义为输出引脚,GEC10定义为输入引脚,用杜邦线将两个引脚相连,验证捕捉实验程序Incapture-Outcmp-20211110,观察输出的时间间隔。

for(;;)     //for(;;)(开头)
    {        
        flag = gpio_get(INCAP_USER);
        //灯状态标志mFlag为'L',改变灯状态及标志
		if (mFlag=='L' && flag == 1)             //判断灯的状态标志
		{
			mFlag='A';                //灯的状态标志
			gpio_set(LIGHT_BLUE,LIGHT_ON);             //灯“亮”
		}
        //如灯状态标志mFlag为'A',改变灯状态及标志
		else if(mFlag=='A' && flag == 0)     //判断灯的状态标志
		{
			mFlag='L';                         //灯的状态标志
			gpio_set(LIGHT_BLUE,LIGHT_OFF);            //灯“暗”
		}
	}

结果:

   451ms -> 451ms -> 401ms-> 401ms -> 351ms -> …

   从时间可以看出,时间间隔在逐渐减小

 总结:

1. 利用SysTick定时器编写倒计时程序

思考总结

首先,我们需要知道SysTick定时器的频率和分辨率,以便准确设置倒计时的时间。

设置SysTick定时器的中断周期,使得每次中断表示1秒的时间。

SysTick中断服务程序中,更新倒计时时间,并在屏幕上显示剩余时间。

当倒计时到0时,控制红灯亮起,关闭SysTick中断,并停止屏幕输出。

2. 利用RTC显示日期和时间,并设置闹钟

思考总结

RTC常具有独立的电源,能够在系统关闭时保持时间和日期。

首先需要初始化RTC并设置当前的日期和时间。

设置闹钟时间时,需要定义一个闹钟中断服务程序,在该程序中执行相应的操作,显示名字和点亮绿灯。

每秒更新屏幕显示的日期和时间

3. 利用PWM脉宽调制交替显示红灯的短闪和长闪

思考总结

PWM允许我们精确控制LED的亮度或占空比,从而实现闪烁效果。

设置PWM的频率和占空比来控制红灯的闪烁。短闪和长闪可以通过改变占空比来实现。

编写程序交替改变PWM的占空比,以达到交替显示短闪和长闪的效果。

4. GEC39定义为输出引脚,GEC10定义为输入引脚,验证捕捉实验程序

思考总结

在这个实验中,GEC39作为输出引脚,需要产生一个特定的信号(如脉冲)。

GEC10作为输入引脚,需要配置为能够捕捉GEC39输出的信号的变化(上升沿或下降沿)。

验证实验程序需要包含信号的输出和输入捕捉的逻辑。

GEC10捕捉到GEC39的输出信号变化时,记录下时间间隔或触发相应的事件。

第八-十章

对于can的驱动函数文件加注释。

// 设置CAN模式为正常模式  

CANMode = CAN_MODE_NORMAL;   

// 计算接收到的CAN消息的长度  
// 从接收FIFO邮箱的RDT寄存器中读取DLC(数据长度码)字段  
// 并将它与某个特定CAN通道(由canNo决定)的FIFO邮箱的RDTR寄存器的相应字段结合  
// 然后右移DLC的位置位,得到消息的长度  

  len = (CAN_RDT0R_DLC & CAN_ARR[canNo-1]->sFIFOMailBox[RxFifo].RDTR) >> CAN_RDT0R_DLC_Pos; 

// 设置CAN通道(由canNo决定)的RF0R寄存器的RFOM0位  
// 用于释放接收FIFO0的邮箱,以便它可以再次接收新的消息  

SET_BIT(CAN_ARR[canNo-1]->RF0R, CAN_RF0R_RFOM0);

// 设置CAN通道(由canNo决定)的IER寄存器的FMPIE0位  
// 用于启用FIFO消息挂起中断,当FIFO中有新的消息时会产生中断 

SET_BIT(CAN_ARR[canNo-1]->IER, CAN_IER_FMPIE0); 

// 启用与CAN接收FIFO(由Can_Rx_FifoNo决定)相关的中断  
// 允许CPU响应接收到的CAN消息的中断  

NVIC_EnableIRQ(table_irq_can[Can_Rx_FifoNo]); 


// 从某个CAN发送邮箱的TSR寄存器中读取CODE字段  

transmit_mailbox = (register_tsr & CAN_TSR_CODE) >> CAN_TSR_CODE_Pos; 


// 检查目标ID(DestID)是否小于或等于0x7FFU  

if(DestID <= 0x7FFU)   

  {        

       // 设置指定CAN通道(由canNo决定)的发送邮箱(由transmit_mailbox决定)的TIR寄存器  
    // TIR寄存器用于配置发送消息的标识符和其他属性  
      
    // 将目标ID左移CAN_TI0R_STID_Pos位,这是标准标识符(STID)在TIR寄存器中的起始位置  
    // CAN_ID_STD是一个宏或常量,用于指示这是一个标准CAN标识符(通常是一个位掩码)  
    // rtr是一个变量或宏,用于指示这是否是一个远程帧(Remote Transmission Request,RTR)  
    // 如果是远程帧,通常rtr会被设置为某个特定的值(比如1),否则为0 

         // 将目标ID、标准标识符标识和RTR位组合在一起  
        // 然后将这个值写入TIR寄存器,配置发送邮箱以发送具有给定标识符和属性的CAN消息  
          
            CAN_ARR[canNo-1]->sTxMailBox[transmit_mailbox].TIR = ((DestID << CAN_TI0R_STID_Pos)|CAN_ID_STD|rtr);  //2024.6
  }

// 将目标ID(DestID)左移CAN_TI0R_STID_Pos位,然后与标准标识符位(CAN_ID_STD)和RTR位(rtr)进行按位或操作  
// 结果值被写入到指定CAN通道(由canNo决定)的发送邮箱(由transmit_mailbox决定)的TIR寄存器  
// 这用于配置一个标准标识符(11位ID)的CAN消息 

CAN_ARR[canNo-1]->sTxMailBox[transmit_mailbox].TIR = ((DestID << CAN_TI0R_STID_Pos)|CAN_ID_STD|rtr); 


// 将目标ID(DestID)左移CAN_TI0R_EXID_Pos位,然后与扩展标识符位(CAN_ID_EXT)和RTR位(rtr)进行按位或操作  
// 结果值被写入到相同CAN通道和发送邮箱的TIR寄存器  
// 这用于配置一个扩展标识符(29位ID)的CAN消息  

CAN_ARR[canNo-1]->sTxMailBox[transmit_mailbox].TIR = ((DestID <<CAN_TI0R_EXID_Pos)|CAN_ID_EXT|rtr);

// 设置发送请求位(TXRQ)在TIR寄存器中  
// CAN控制器准备发送存储在发送邮箱中的数据  

// 写入数据到发送邮箱的数据高位寄存器(TDHR)  
// 注意:这里只提到了写入操作,但没有给出具体的数据值  
// WRITE_REG是一个宏或函数,用于将数据写入到寄存器  
// 这里假设数据已经准备好并存储在某个变量或常量中  

 WRITE_REG(CAN_ARR[canNo-1]->sTxMailBox[transmit_mailbox].TDHR, 

// 使能CAN1的时钟  
// RCC_APB1ENR1是RCC(Reset and Clock Control)寄存器的一部分,用于控制APB1总线上的时钟  
// CAN1EN是CAN1时钟的使能位  

  SET_BIT(CAN_ARR[canNo-1]->sTxMailBox[transmit_mailbox].TIR, CAN_TI0R_TXRQ);  

// 配置GPIOA端口的第11位和第12位为通用推挽输出模式  
// MODER是GPIO端口的模式寄存器  
// GPIO_MODER_MODE11_1和GPIO_MODER_MODE12_1是宏或常量,用于设置特定位的模式
// 意味着GPIOA端口的某些引脚被用作CAN或其他功能的输出  

  RCC->APB1ENR1 |= RCC_APB1ENR1_CAN1EN;

// 设置GPIOA的MODER寄存器,配置引脚11和12为某种模式(具体模式取决于GPIO_MODER_MODE11_1和GPIO_MODER_MODE12_1的定义)  
// 可能是设置为推挽输出或其他某种模式,这取决于具体的GPIO库定义  

GPIOA->MODER |= (GPIO_MODER_MODE11_1|GPIO_MODER_MODE12_1);

// 设置CAN通道(由canNo决定)的发送邮箱)的TIR寄存器  
// 将DestID左移CAN_TI0R_STID_Pos位(即标准标识符的起始位置)  
// 然后与标准标识符的掩码CAN_ID_STD和远程帧标识符rtr进行位或操作  
// 这行代码试图配置发送一个具有标准标识符的CAN消息

CAN_ARR[canNo-1]->sTxMailBox[transmit_mailbox].TIR = ((DestID << CAN_TI0R_STID_Pos)|CAN_ID_STD|rtr); 

// 设置CAN通道(由canNo决定)的发送邮箱)的TIR寄存器  
// 将DestID左移CAN_TI0R_STID_Pos位(即标准标识符的起始位置)  
// 然后与标准标识符的掩码CAN_ID_STD和远程帧标识符rtr进行位或操作  
// 这行代码试图配置发送一个具有标准标识符的CAN消息

CAN_ARR[canNo-1]->sTxMailBox[transmit_mailbox].TIR = ((DestID <<CAN_TI0R_EXID_Pos)|CAN_ID_EXT|rtr);

// 使用WRITE_REG宏(或函数)向发送邮箱的TDHR寄存器写入数据  
// TDHR寄存器通常用于存放数据的高字节

 WRITE_REG(CAN_ARR[canNo-1]->sTxMailBox[transmit_mailbox].TDHR, 

// 使用SET_BIT宏设置发送邮箱的TIR寄存器的TXRQ位
// 这将请求发送一个CAN消息  

  SET_BIT(CAN_ARR[canNo-1]->sTxMailBox[transmit_mailbox].TIR, CAN_TI0R_TXRQ);  

// 启用CAN1的时钟(如果它是APB1总线上的第一个CAN控制器)  
// RCC->APB1ENR1是一个寄存器,用于控制APB1总线上的各种外设的时钟  
// RCC_APB1ENR1_CAN1EN是CAN1时钟使能位  

  RCC->APB1ENR1 |= RCC_APB1ENR1_CAN1EN;

// 设置GPIOA的MODER寄存器,配置引脚11和12的模式  
// 假设GPIO_MODER_MODE11_1和GPIO_MODER_MODE12_1是已经定义好的模式值 

GPIOA->MODER |= (GPIO_MODER_MODE11_1|GPIO_MODER_MODE12_1); 

// 设置CAN通道(由canNo决定)的发送邮箱(由transmit_mailbox决定)的TIR寄存器  
// 使用标准标识符(STID)来发送CAN消息  

CAN_ARR[canNo-1]->sTxMailBox[transmit_mailbox].TIR = ((DestID << CAN_TI0R_STID_Pos)|CAN_ID_STD|rtr); 

// 设置CAN通道(由canNo决定)的发送邮箱(由transmit_mailbox决定)的TIR寄存器  
// 使用标准标识符(STID)来发送CAN消息  

CAN_ARR[canNo-1]->sTxMailBox[transmit_mailbox].TIR = ((DestID <<CAN_TI0R_EXID_Pos)|CAN_ID_EXT|rtr);

// CAN_ARR是一个指向CAN通道结构体的数组,canNo是CAN通道的索引  
// 设置CAN通道canNo的transmit_mailbox邮箱的TDHR寄存器(通常用于发送数据的高字节寄存器)

 WRITE_REG(CAN_ARR[canNo-1]->sTxMailBox[transmit_mailbox].TDHR, 

// 设置CAN通道canNo的transmit_mailbox邮箱的TIR寄存器中的TXRQ位(发送请求位)  
// 这意味着你正在请求发送数据到CAN网络上  
// SET_BIT是一个宏或函数,用于设置特定寄存器中的特定位  

  SET_BIT(CAN_ARR[canNo-1]->sTxMailBox[transmit_mailbox].TIR, CAN_TI0R_TXRQ);  

// 启用CAN1的时钟。APB1ENR1是RCC(重置和时钟控制)寄存器的一部分,用于控制APB1总线上的时钟  
// RCC_APB1ENR1_CAN1EN是APB1ENR1寄存器中用于CAN1的位掩码  

  RCC->APB1ENR1 |= RCC_APB1ENR1_CAN1EN;

// 配置GPIOA端口的MODER寄存器,用于设置PA11和PA12的模式(Mode)  
// GPIO_MODER_MODE11_1和GPIO_MODER_MODE12_1可能是用于设置PA11和PA12为特定模式的位掩码  
// 通常这些位用于设置GPIO为复用模式或通用IO模式  

GPIOA->MODER |= (GPIO_MODER_MODE11_1|GPIO_MODER_MODE12_1); 

// 配置GPIOA端口的AFR[1]寄存器,用于设置PA11和PA12的复用功能(Alternate Function)  
// GPIO_AFRH_AFSEL11_x和GPIO_AFRH_AFSEL12_x是用于设置PA11和PA12的复用功能选择的位掩码  
// 这里x可能是0到3之间的某个值,具体取决于CAN使用的复用功能  

GPIOA->AFR[1] |= (GPIO_AFRH_AFSEL11_0|GPIO_AFRH_AFSEL11_3)|(GPIO_AFRH_AFSEL12_0|GPIO_AFRH_AFSEL12_3); 

// 唤醒CAN控制器(如果它之前处于睡眠模式)  

CLEAR_BIT(CAN_ARR[canNo-1]->MCR, CAN_MCR_SLEEP); 

// 等待CAN控制器退出睡眠模式  
// 注意:这里我添加了一个while循环来等待SLAK位(睡眠激活)清零  
// 假设MSR是状态寄存器,SLAK是其中的睡眠激活位  

while ((CAN_ARR[canNo-1]->MSR & CAN_MSR_SLAK) != 0U) 

// 如果需要初始化CAN控制器(例如,在复位后),可以设置初始化请求位  

SET_BIT(CAN_ARR[canNo-1]->MCR, CAN_MCR_INRQ);  

// 清除CAN_ARR[canNo-1]中MCR寄存器的TTCM位。  
// TTCM是Time Triggered Communication Mode(时间触发通信模式)的位。  
// 清除此位将禁用时间触发通信模式。  

CLEAR_BIT(CAN_ARR[canNo-1]->MCR, CAN_MCR_TTCM);   

// 清除CAN_ARR[canNo-1]中MCR寄存器的ABOM位。  
// ABOM是Automatic Bus-Off Management(自动总线关闭管理)的位。  
// 清除此位将禁用自动总线关闭管理功能。  
    CLEAR_BIT(CAN_ARR[canNo-1]->MCR, CAN_MCR_ABOM);  

// 清除CAN_ARR[canNo-1]中MCR寄存器的AWUM位。  
// AWUM是Automatic Wake-Up Mode(自动唤醒模式)的位。  
// 清除此位将禁用自动唤醒模式。  
    CLEAR_BIT(CAN_ARR[canNo-1]->MCR, CAN_MCR_AWUM);  

// 设置CAN_ARR[canNo-1]中MCR寄存器的NART位。  
// NART是No Automatic Retransmission(无自动重传)的位。  
// 设置此位将禁用自动重传功能。  
    SET_BIT(CAN_ARR[canNo-1]->MCR, CAN_MCR_NART);    

// 清除CAN_ARR[canNo-1]中MCR寄存器的RFLM位。  
// RFLM是Receiver FIFO Locked Mode(接收器FIFO锁定模式)的位。  
// 清除此位将禁用接收器FIFO锁定模式。  
    CLEAR_BIT(CAN_ARR[canNo-1]->MCR, CAN_MCR_RFLM); 

  // 清除CAN_ARR[canNo-1]中MCR寄存器的TXFP位。  
// TXFP是Transmit FIFO Priority(发送FIFO优先级)的位。  
// 清除此位可能会更改发送FIFO的优先级设置(具体行为取决于硬件实现)。  
    CLEAR_BIT(CAN_ARR[canNo-1]->MCR, CAN_MCR_TXFP);    

// 修改CAN_ARR[canNo-1]中BTR寄存器的值。  
// 这里是对CAN波特率寄存器(BTR)的设置,用于配置CAN通信的波特率和其他参数。  
// Prescaler:预分频值,减1后写入BTR寄存器的相关位。  
// CAN_SJW_1TQ:重新同步跳转宽度设置为1个时间量子(TQ)。  
// CAN_BTR_TS1_1 和 CAN_BTR_TS1_0:设置时间段1(Time Segment 1, TS1)的长度。  
// CAN_BTR_TS2_2:设置时间段2(Time Segment 2, TS2)的长度。  
// CANMode:可能是一个用于设置CAN工作模式的值(如Normal Mode, Loopback Mode等)。  
// 通过“|”操作符,这些值被组合在一起并写入BTR寄存器。  

CAN_ARR[canNo-1]->BTR |= ((uint32_t)(Prescaler-1)|CAN_SJW_1TQ|CAN_BTR_TS1_1|CAN_BTR_TS1_0|CAN_BTR_TS2_2|CANMode);  

// 清除位操作:

CLEAR_BIT(CAN_ARR[canNo-1]->MCR, CAN_MCR_INRQ);   

//等待响应

    while ((CAN_ARR[canNo-1]->MSR & CAN_MSR_INAK) != 0U)

 //CAN ID的位操作

if(CanID <= 0x7FFU) CanID = CanID << CAN_TI0R_STID_Pos; 

//提取Filter ID和Mask:

FilterIdHigh = (CanID >> 16) & 0xFFFF;   
    FilterIdLow = (CanID & 0xFFFF);  
    FilterMaskIdHigh = 0xFFE0;    
    FilterMaskIdLow = 0x0000;   

// 计算filternbrbitpos:
    filternbrbitpos = (uint32_t)1 << (FilterBank & 0x1FU);    

// 设置过滤器初始化模式 (FINIT=1),在此模式下可以进行过滤器初始化  
// 这通常是将FMR寄存器中的FINIT位设置为1,以允许对过滤器进行配置      SET_BIT(CAN_ARR[canNo-1]->FMR, CAN_FMR_FINIT);    

// 清除过滤器激活寄存器(FA1R)中对应过滤器的位  
// 这意味着在初始化过程中,该过滤器不会被激活  
    CLEAR_BIT(CAN_ARR[canNo-1]->FA1R, filternbrbitpos);   

// 判断过滤器的尺度是32位还是16位  

if (FilterScale == CAN_FILTERSCALE_32BIT)    

// 如果过滤器尺度是32位,则设置过滤器尺度寄存器(FS1R)中对应过滤器的位  
    // 这告诉CAN控制器该过滤器使用的是32位模式  

      SET_BIT(CAN_ARR[canNo-1]->FS1R, filternbrbitpos);   

   // 设置过滤器的ID寄存器(FR1)  
    // 在32位模式下,FR1包含了过滤器ID的高16位和低16位  
    // 这里将FilterIdHigh和FilterIdLow组合成一个32位的值,并写入FR1  
  
      CAN_ARR[canNo-1]->sFilterRegister[FilterBank].FR1 =     ((0x0000FFFFU & (uint32_t)FilterIdHigh) << 16U) | (0x0000FFFFU & (uint32_t)FilterIdLow);

// 判断过滤器的模式是ID匹配还是ID屏蔽  

if (FilterMode == CAN_FILTERMODE_IDMASK)
    {

   // 如果过滤器模式是ID屏蔽,则清除过滤器模式寄存器(FM1R)中对应过滤器的位  
    // 这告诉CAN控制器该过滤器使用ID屏蔽模式  
    // 在ID屏蔽模式下,过滤器不仅匹配ID,还使用FilterMaskIdHigh和FilterMaskIdLow来定义哪些位应该被忽略
      CLEAR_BIT(CAN_ARR[canNo-1]->FM1R, filternbrbitpos); 
    }

// 判断CAN接收FIFO(First-In-First-Out,先进先出)编号是否为FIFO0  

if (Can_Rx_FifoNo == CAN_FILTER_FIFO0)    
    {

  // 如果是FIFO0,则清除过滤器激活寄存器FFA1R中对应过滤器的位  
    // 这意味着在FIFO0中,该过滤器不会被激活来接收消息 
      CLEAR_BIT(CAN_ARR[canNo-1]->FFA1R, filternbrbitpos);   
    }

// 判断是否要激活过滤器  

    if (IsActivate == 1)    
    {


    // 如果需要激活过滤器,则设置过滤器激活寄存器FA1R中对应过滤器的位  
    // 这意味着该过滤器将被激活,可以接收匹配的消息  
      SET_BIT(CAN_ARR[canNo-1]->FA1R, filternbrbitpos);  

       
    }


  // 退出过滤器初始化模式  
// 将FMR寄存器中的FINIT位设置为0,以退出初始化模式  
// 在此模式下进行的任何过滤器配置都将生效  
// FINIT=0 意味着初始化模式结束,过滤器配置现在可以生效  
    CLEAR_BIT(CAN_ARR[canNo-1]->FMR, CAN_FMR_FINIT);  

实验:

1、2个或以上同学相互连接,利用CAN通信,向对方发送带有本人姓名的信息。连线方式:按基本原理性电路(不带收发器芯片)连接,参考教材图10-1。

源码:

localMsgID = 0x0CU;

   txMsgID = 0x0BU;

 for(;;)   //for(;;)(开头)

       {

        mMainLoopCount++;

              if (mMainLoopCount<=12889000)  continue;

              mMainLoopCount=0;

              if (mFlag=='L')                    //判断灯的状态标志

              {

                     mLightCount++;

                     mFlag='A';                       //灯的状态标志

                     gpio_set(LIGHT_RED,LIGHT_ON);  //

                     //***CAN模块发送一帧数据***

                     if(can_send(CAN_1, txMsgID, 8, (uint8_t*)"文锦松") != 0) printf("failed\r\n");

              }

              else

              {

                     mFlag='L';                       //灯的状态标志

                     gpio_set(LIGHT_RED,LIGHT_OFF); //

              }

   } 

结果:

原理图:

可以看到接收到对方的名字

2、在ADC实验中,结合热敏电阻,分别通过触摸芯片表面和热敏电阻,引起A/D值变化,显示芯片内部温度和当前温度。

源码:

for(;;)   //for(;;)(开头)

{

        //延时1

        Delay_ms(1000);

      

        printf("------------------------\r\n\n");

       

        mcu_temp_AD = adc_read(AD_MCU_TEMP); //读取mcu温度通道AD

        mcu_temp=TempTrans(mcu_temp_AD);    //温度回归

        printf("芯片内部温度为:%6.2f\r\n",mcu_temp);

        temp_AD = adc_read(AD_TEMP);

        temp=TempRegression(temp_AD);

        if(temp==-274)

        {

          printf("热敏电阻温度AD值异常,请检查相关硬件\n");

        }

        else

        {

            printf("热敏电阻温度为:%6.2f\r\n",temp);

        }

        //20201106RJ添加触摸温度传感器闪烁黄灯的功能

        if(preTemp > 0 && (temp > preTemp) && (temp - preTemp >= 1))

        {      

            preTemp = temp;//记录下上次的温度

            printf("触摸温度传感器!!!!!\n");

            //只有当灯原来是亮的情况下才将灯置灭

            //保证灯是灭的

            if ((mCount / 5) % 2)

            gpio_reverse(LIGHT_RED);     

            if ((mCount / 10) % 2)

            gpio_reverse(LIGHT_GREEN);

            if ((mCount / 20) % 2)

            gpio_reverse(LIGHT_BLUE);

            //闪烁黄灯

            printf("指示灯颜色为【黄色】");

            for (i = 0; i <= 2; i++)

            {

                            gpio_reverse(LIGHT_GREEN);

                            gpio_reverse(LIGHT_RED);

                Delay_ms(1000);

                gpio_reverse(LIGHT_GREEN);

                            gpio_reverse(LIGHT_RED);

                Delay_ms(1000);

            }

            //将灯恢复之前的状态

            if ((mCount / 5) % 2)

            gpio_reverse(LIGHT_RED);

            if ((mCount / 10) % 2)

            gpio_reverse(LIGHT_GREEN);

            if ((mCount / 20) % 2)

            gpio_reverse(LIGHT_BLUE);

            continue;

        }

        else

        {

            preTemp = temp;//记录下上次的温度

        }         

        mCount++;

        //当秒数40秒时,重新开始计数

        //避免一直累加

        if (mCount >= 40)

        {

             mCount=0;

                gpio_set(LIGHT_BLUE,LIGHT_OFF);

                gpio_set(LIGHT_RED,LIGHT_OFF);

                   gpio_set(LIGHT_GREEN,LIGHT_OFF);

            printf("指示灯颜色为【暗色】");

        }

        //2.3.2)如灯状态标志mFlag'L',灯的闪烁次数+1并显示,改变灯状态及标志

        if(mCount%5==0)

        {

          gpio_reverse(LIGHT_RED);

          printf(" LIGHT_RED:reverse--\n");   //串口输出灯的状态

          if(mCount/5==1)

          {

            printf("指示灯颜色为【红色】");

           }

           else if(mCount/5==3)

           {

            printf("指示灯颜色为【黄色】");

           }

           else if(mCount/5==5)

           {

            printf("指示灯颜色为【紫色】");

           }

           else if(mCount/5==7)

           {

            printf("指示灯颜色为【白色】");

           }

        }

        if (mCount%10==0)         //判断灯的状态标志

        {

            gpio_reverse(LIGHT_GREEN);

           printf(" LIGHT_GREEN:reverse--\n"); //串口输出灯的状态

            if(mCount/10==1)

            {

             printf("指示灯颜色为【绿色】");

            }

            else if(mCount/10==3)

           {

            printf("指示灯颜色为【青色】");

           }

        }

        if (mCount%20==0)                //判断灯的状态标志

        {

            gpio_reverse(LIGHT_BLUE);                        

            printf(" LIGHT_BLUE:reverse--\n"); //串口输出灯的状态

            if(mCount/20==1)

            {

            printf("指示灯颜色为【蓝色】");

            }

        }

    }  //for(;;)结尾

结果:

3、用实验验证,对于有数据的某扇区,如果没有擦除(Flash_erase),可否写入新数据?注:数据文本中要有姓名。

源码:

// 有擦除

    //擦除第50扇区

       flash_erase(50);  

    //50扇区第0偏移地址开始写64个字节数据

    flash_write(50,0,64,(uint8_t *) "Welcome to Soochow University!文锦松");

       flash_read_logic(mK1,50,0,64); //50扇区读取64个字节到mK1

       printf("逻辑读方式读取50扇区的64字节的内容:  %s\n",mK1);

       //擦除第50扇区

       flash_erase(50);

       //50扇区写64个字节数据

       flash_write_physical(0x8019000,64,flash_test);

       flash_read_physical(mK2,0x8019000,64);      //50扇区读取64个字节到mK2

       printf("物理读方式读取50扇区的64字节的内容:  %s\n",mK2);

       result = flash_isempty(50,MCU_SECTORSIZE); // 判断第50扇区是否为空

       printf("50扇区是否为空,1表示空,0表示不空:%d\n",result);

       // 不擦除

    //50扇区第0偏移地址开始写64个字节数据

    flash_write(50,0,64,(uint8_t *) "Welcome to Soochow University!文锦松");

       flash_read_logic(mK1,50,0,64); //50扇区读取64个字节到mK1

       printf("逻辑读方式读取50扇区的64字节的内容:  %s\n",mK1);

       //50扇区写64个字节数据

       flash_write_physical(0x8019000,64,flash_test);

       flash_read_physical(mK2,0x8019000,64);      //50扇区读取64个字节到mK2

       printf("物理读方式读取50扇区的64字节的内容:  %s\n",mK2);

       result = flash_isempty(50,MCU_SECTORSIZE); // 判断第50扇区是否为空

       printf("50扇区是否为空,1表示空,0表示不空:%d\n",result);

结果:

没擦除

擦除

从实验结果看,没有影响

分析思考

对于CAN通信实验,需要考虑网络的拓扑结构。此外,还需要考虑错误检测和处理机制,以确保数据的可靠传输。

在ADC实验中,热敏电阻的特性需要被准确理解,包括其温度-电阻关系。此外,ADC的精度和采样率也是影响测量结果的重要因素。

Flash存储器写入实验中,需要理解Flash的工作原理和写入限制。实验可以帮助理解为什么在写入新数据前需要擦除,以及这可能对数据的持久性和存储器的寿命产生什么影响。

  • 21
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值