注:如果点击连接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
步骤五:下载机器码
步骤六:观察运行结果
步骤七: 通过串口观察运行情况
第二章作业
描述:打开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
编写1-10的累加程序
完整代码:
//=====================================================================
//文件名称: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 //整个程序结束标志(结尾)
结果
第三章作业
给出所用MCU芯片型号标识所获得的信息。
(对照命名格式)
举例:型号标识:ATMEGA328P-AU
-
制造商代码(Manufacturer Code):
AT
这通常是制造商的缩写。在这个例子中,AT
可能代表Atmel(现为Microchip Technology的一部分),一家知名的微控制器和微处理器制造商。 -
系列或产品家族(Series/Family):
MEGA
这表示MCU属于特定的产品系列或家族。对于Atmel来说,MEGA
系列通常指的是基于AVR架构的高性能微控制器。 -
型号(Model):
328
- 这个数字标识了MCU的具体型号。在这个例子中,它可能表示这是一款具有特定内存大小、处理能力或其他特性的微控制器。 -
封装类型(Package Type):
P
这个字母表示MCU的封装类型。P
可能代表某种特定的封装,如PDIP(Platinum Dual In-line Package)或其他封装形式。 -
版本或修订(Version/Revision):
-A
这个后缀可能表示MCU的版本或修订级别。A
可能意味着这是原始设计之后的某个改进版本,可能包括修正、性能提升或新特性的添加。 -
额外信息(Additional Information):在这个例子中,型号标识中没有提供额外的信息,但有时制造商会添加额外的字母或数字来指示温度范围、RoHS合规性、引脚数量等。
给出所用MCU芯片的RAM及Flash大小、地址范围。
ATMEGA328P-AU的MCU为例:
-
RAM(随机存取存储器)
- 大小:ATMEGA328P通常有2KB的RAM。这提供了足够的工作内存来存储变量和临时数据。
- 地址范围:RAM的地址范围通常是从0x0000到0x07FF。2KB的地址空间
-
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工程
GPIO-Output-Component_STM32L431_20200928工程
3.用直接地址编程方式,实现红绿蓝三灯轮流闪烁
修改代码:
完整代码:
//====================================================================
//文件名称: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
*/
结果
4.用调用构件方式,实现红绿蓝的八种组合轮流闪烁
结果:
完整代码
//======================================================================
//文件名称: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)======主循环部分(结尾)========================================
结果:
实验要求使用两种不同的实现方式:构件调用方式和直接地址方式。构件调用方式通常指使用库函数简化编程,这种方式易于实现,但可能在性能上有所妥协。直接地址方式则要求直接操作硬件寄存器,这种方式对硬件的控制更为精细,但编程难度较高。
在实现程序时,需要考虑以下几个关键步骤:
-
初始化UART:设置正确的波特率和数据格式,使能接收和发送功能。
-
中断服务程序:编写中断服务程序以响应UART接收到数据的事件。在中断服务程序中,需要读取接收到的字符,并根据实验要求执行后续动作。
-
字符处理:对于显示下一个字符的功能,可以设计一个简单的逻辑或查找表,将接收到的字符映射到下一个字符。
-
LED灯控制:根据接收到的字符,编写逻辑控制GPIO引脚的电平,以点亮相应的LED灯。例如,接收到'G'时点亮绿灯,'R'时点亮红灯,'B'时点亮蓝灯。
-
硬件操作:无论是使用构件调用方式还是直接地址方式,都需要对UART和GPIO的硬件寄存器进行操作。直接地址方式要求对这些寄存器有更深入的了解。
-
测试与调试:完成程序后,需要进行充分的测试,确保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);
}
//【20201106】RJ添加触摸温度传感器闪烁黄灯的功能
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的工作原理和写入限制。实验可以帮助理解为什么在写入新数据前需要擦除,以及这可能对数据的持久性和存储器的寿命产生什么影响。