无论是学开发什么板子,接触的第一个程序一般都是点亮LED灯。
下面分别从51单片机、STM32单片机(寄存器、库函数、RTOS)、嵌入式Linux五个方面解释,这里我们假设都是低电平点亮。
#include "reg52.h" //此文件中定义了单片机的一些特殊功能寄存器
/* reg52.h 里定义寄存器 sfr P2 = 0xA0*/
sbit led=P2^0; //将单片机的P2.0端口定义为led
void main()
{
while(1)
{
led=0; //P2.0端口设置为低电平
}
}
上图中可以看到,“led = 0;”是最为顶层的代码,他将IO端口配置为低;中间层代码为“led = P2^0;”,他将变量和IO口对应的寄存器做了映射,方便顶层操作;可以看到最底层的代码在reg52.h里,“sfr P2 = 0xA0;”定义了P2IO口对应的首个端口的地址,方便去访问。
综上所述,我们可以知道,从C语言的层面我们需要寄存器地址,最终配置的是寄存器的数值。同时,我们需要引入变量LED增加代码可读性。
/*******************************************************************************
*
* 普中科技
--------------------------------------------------------------------------------
* 实 验 名 : 使用寄存器点亮一个LED
* 实验说明 : 操作寄存器控制D1指示灯闪烁
* 连接方式 :
* 注 意 :
*******************************************************************************/
#include "stm32f4xx.h"
/*
以下头文件stm32f4xx.h内容
#define PERIPH_BASE ((unsigned int)0x40000000)
#define AHB1PERIPH_BASE (PERIPH_BASE + 0x00020000)
#define GPIOF_BASE (AHB1PERIPH_BASE + 0x1400)
#define GPIOF_MODER *(unsigned int*)(GPIOF_BASE+0x00)
#define GPIOF_BSRR *(unsigned int*)(GPIOF_BASE+0x18)
#define RCC_BASE (AHB1PERIPH_BASE + 0x3800)
#define RCC_AHB1ENR *(unsigned int*)(RCC_BASE+0x30)
*/
typedef unsigned int u32; //类型重定义 unsigned int -- u32
void SystemInit()
{
}
//* 函数功能 : 延时函数,通过while循环占用CPU,达到延时功能
void delay(u32 i)
{
while(i--);
}
//* 函数功能 : 主函数
int main()
{
RCC_AHB1ENR |= 1<<5;
GPIOF_MODER = (1<<(2*9));
while(1)
{
GPIOF_BSRR=(1<<(16+9));
delay(0xFFFFF);
GPIOF_BSRR=(1<<(9));
delay(0xFFFFF);
}
}
#define PERIPH_BASE ((unsigned int)0x40000000) 外设基地址(参考手册54页)
#define AHB1PERIPH_BASE (PERIPH_BASE + 0x00020000)
#define GPIOF_BASE (AHB1PERIPH_BASE + 0x1400)
#define GPIOF_MODER *(unsigned int*)(GPIOF_BASE+0x00)
#define GPIOF_BSRR *(unsigned int*)(GPIOF_BASE+0x18)
#define RCC_BASE (AHB1PERIPH_BASE + 0x3800)
#define RCC_AHB1ENR *(unsigned int*)(RCC_BASE+0x30)
我们配置使能时钟: RCC_AHB1ENR |= 1<<5;
我们设置GPIOF9的模式:GPIOF_MODER = (1<<(2*9));这里的1是01
我们设置GPIOF9的电平:GPIOF_BSRR=(1<<(16+9)); (复位点亮) GPIOF_BSRR=(1<<(9));(置位熄灭)
综上所述,相对于51单片机,STM32的单片机复杂度高在了复用功能的增多,导致的寄存器的增多,对于同样可以看到明显的代码分层现象。
/*
标准库
void LED_Init()
{
GPIO_InitTypeDef GPIO_InitStructure; //定义结构体变量
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF,ENABLE); //使能端口F时钟
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_OUT; //输出模式
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;//管脚设置F9
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz;//速度为100M
GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP;//上拉
GPIO_Init(GPIOF,&GPIO_InitStructure); //初始化结构体
GPIO_SetBits(GPIOF,GPIO_Pin_9);
}
*/
#include "stm32f4xx.h"
#include "led.h"
int main()
{
LED_Init();
while(1)
{
/* 复位*/
GPIO_ResetBits(GPIOF,GPIO_Pin_9);//复位F9 点亮D1
}
}
可以看到,将地址define为字符,不需要在访问寄存器,利用给出的字符选项进行控制寄存器里的内容。
如果需要更简单的话,STM32CubeMax图形化界面就行。甚至MATLAB写的流程都可以转化为STM32代码(B站有教程)。
当然,毫无疑问的是编程的安全性(不直接接触地址)和简洁性(字符选项)得到了加强。
这里我们以RT-Thread为例。在我看来RTT相较于裸机程序,主要是在几个方面不同:从while+中断----->线程+中断、将app驱动和设备驱动分离。也就是说我们要更加注重于不同任务的分离、通用型APP的使用。
Drivers驱动文件夹对接硬件底层,将裸机代码里的参数对应到自己的C文件里;DeviceDrivers驱动文件夹对接APP,为开发者提供应用函数,当配置好参数就直接用DeviceDrivers提供的函数编程即可。
#include
#include
#define THREAD_PRIORITY 10
#define THREAD_STACK_SIZE 512
#define THREAD_TIMESLICE 10
#define GPIOF9 89 //device对接底层
//static rt_thread_t tid1 = RT_NULL;
static void led_entry(void *parameter)
{
rt_pin_mode(GPIOF9,PIN_MODE_OUTPUT);
while (1)
{
rt_pin_write(GPIOF9, PIN_HIGH);
rt_thread_mdelay(500);
rt_pin_write(GPIOF9, PIN_LOW);
rt_thread_mdelay(500);
}
}
void led_test(void)
{
rt_thread_t tid1;
/* 创建线程1,名称是thread1,入口是thread1_entry*/
tid1 = rt_thread_create("thread1",
led_entry, RT_NULL,
THREAD_STACK_SIZE,
THREAD_PRIORITY, THREAD_TIMESLICE);
/* 如果获得线程控制块,启动这个线程 */
if (tid1 != RT_NULL)
rt_thread_startup(tid1);
}
可以说除了89对应PF9外(芯片直接不同),其余参数不需要访问Drivers文件夹。分离做的非常好。
APP-->DeviceDrivers-->CPU-->Drivers-->HAL,函数的调用执行基本就是这样一个流程。
Linux的部分以后再做。