嵌入式基础知识_1——C语言知识与数据结构

C语言篇

关键字使用与说明

1. static:这个可以将变量申明为静态的,限制变量/函数的使用范围(只限于本文件中);延长变量的生命周期使变量和main函数的生命周期一致。

变量申明——延长变量生命周期:

void Test(void)
{
    static uint8_t i = 0;
    i++;
}

函数申明——只能在本文件中使用:

static void Test(void)
{
    uint8_t i = 0;
    i++;
}

2. extern:这个关键字用于申明变量,扩大变量的使用范围(扩大到其他文件)

变量申明:

extern uint8_t code Crc8_Table[];

函数申明:

extern uint8_t Generate_CRC_8bit(const uint8_t *pstr,uint8_t len,const uint8_t *crc_table);

3. const:表示此变量为不可修改

 //变量不可以被修改
const uint8_t code_arr[10] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};

//此指针变量不能被修改,但是指针指向的内容可以修改。这个和数组名称一样
char * const p = 0x00001010; 
*p = 1;

4. typedef:数据类型定义符号,用于定义新的数据类型

typedef struct leds_struct{
    uint16_t pin;
    uint32_t port; 
}LED_STRUCT;//定义LED_STRUCT新类型

typedef unsiged char uint8_t; //定义uin8_t类型

5. volatile:可变的,用于将变量申明为实时可变。这样编译器不会将此关键字申明的变量优化,CPU每次都会从内存中读取变量数据而不是从CPU寄存器中来获取(速度比内存快)。常用于申明中断变量与外设寄存器变量

volatile unsigned char flag = 0; //定义一个实时变量

//中断服务函数
void TIM1_IRQHandler(void)
{
    flag = 1;
}

寄存器操作

宏定义形式:

//使用宏定义,使用volatile申明寄存器地址为实时可变。因为这个是一个寄存器里面的
//内容会被外设随时修改。
#define GPL0CON (*(volatile unsigned int *)0x000D0100)

结构体形式:

typedef struct
{
  __IO uint32_t MODER;    /*!< GPIO port mode register,               Address offset: 0x00      */
  __IO uint32_t OTYPER;   /*!< GPIO port output type register,        Address offset: 0x04      */
  __IO uint32_t OSPEEDR;  /*!< GPIO port output speed register,       Address offset: 0x08      */
  __IO uint32_t PUPDR;    /*!< GPIO port pull-up/pull-down register,  Address offset: 0x0C      */
  __IO uint32_t IDR;      /*!< GPIO port input data register,         Address offset: 0x10      */
  __IO uint32_t ODR;      /*!< GPIO port output data register,        Address offset: 0x14      */
  __IO uint32_t BSRR;     /*!< GPIO port bit set/reset register,      Address offset: 0x18      */
  __IO uint32_t LCKR;     /*!< GPIO port configuration lock register, Address offset: 0x1C      */
  __IO uint32_t AFR[2];   /*!< GPIO alternate function registers,     Address offset: 0x20-0x24 */
} GPIO_TypeDef;

指针应用

1. 指针作为函数形参:可以修改原始变量的值

void HAL_GPIO_TogglePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
  /* Check the parameters */
  assert_param(IS_GPIO_PIN(GPIO_Pin));

  GPIOx->ODR ^= GPIO_Pin;
}

void MyLed_Ctrl(void)
{
    HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_1);//修改GPIOA对应的寄存器值
}

2. 指针变量:操作内存地址

//拷贝一定长度的内存
void My_memcyp(unsigned char *dec,unsigned char *src,unsigned int ssize)
{
    while(ssize--)
    {
        *dec++ = *src++;
    }
}

汇编语言篇

常用汇编指令

ldr:用于将内存中的数据加载到寄存器中(内存→寄存器

ldr r0,#1 ;将1读取到寄存器r0中

小技巧:使用"ldr"伪指令,可以让编译器帮我们自动将数值转换成合法立即数

ldr r0, =0x10010000 ;将地址加载到r0寄存器中

str:用于将寄存器中的数据保存到内存中(寄存器→内存

str r1, [r0] ;将r1寄存器中的数值写入到r0寄存器保存的数值对应的内存地址中去

mov:把立即数存入到寄存器中

mov r1, #1 ;向r1寄存器中写入立即数1

orr:orr{条件}{S}  目的寄存器,操作数1,操作数2

orr r0,r0,#0xD3 
;0xD3=1101 0011
;将r0与0xd3作算数或运算,然后将结果返还给r0,即把r0的bit[7:6]和bit[4]和bit[1:0]置为1

bic: bic{条件}{S}  目的寄存器,操作数1,操作数2

bic  r0,r0,#0x1F ; 0x1F=0001 1111 含义:清除R0的bit[4:0]位

b:跳转,不返回

b . ;跳转到当前地址位置:相当于while(1)死循环

bl:跳转前把返回地址放入lr中,以便返回,以便用于函数调用

bl _delay ;跳转到_delay函数中

完整的代码:

;说明:LED闪烁汇编裸机程序
;作者:Jack.Tang
;时间:2020年5月22日21:15:13

			PRESERVE8 ;8字节对齐

			AREA  LedBlink_4412, CODE, READONLY ;声明以下内容为代码

			;定义寄存器地址,这个和C语言中的#defing宏定义是一样的功能
GPL2CON		EQU     0x11000100
GPK1CON		EQU     0x11000060

GPL2DAT		EQU     0x11000104
GPK1DAT		EQU     0x11000064

			ENTRY ;以下为汇编程序入口
			EXPORT _start ;注意:汇编文件中的符号(symbol)表示的是地址。这个类似于C语言中的函数名称

			;设置GPL2_0为输出模式--GPL2CON寄存器--LED2
_start		LDR r0, =GPL2CON
			LDR r1, =0x00000001
			STR r1, [r0]

			;设置GPK1_1为输出模式--GPK1CON寄存器--LED3
			LDR r0, =GPK1CON
			LDR r1, =0x00000010
			STR r1, [r0] ;将寄存器r1中的值写回到r0寄存器保存的值对应的地址中。这个就和C语言中的指针*p一样的效果

			;设置GPL2DAT寄存器为1,点亮LED2
			LDR r0, =GPL2DAT
			MOV r1, #1
			STR r1, [r0]

			;设置GPK1DAT寄存器为1,点亮LED3
			LDR r0, =GPK1DAT
			MOV r1, #2
			STR r1, [r0]


			BL . ;跳转到当前地址。功能:死循环防止程序跑飞

			END ;程序结束标志

数据结构与算法篇

链表

关键字:结构体指针、结构体

嵌入式基础知识_1——C语言知识与数据结构_第1张图片

typedef struct list_node{
    struct list_node *next;
    uint8_t buf_data;
}LIST_NODE;

注意:next指针指向的是下一个节点的地址。意思就是如果知道当前节点地址p,那么下一个节点的地址就是p->next。记住这一点后面链表的插入时需要使用到。

补充说明:

嵌入式基础知识_1——C语言知识与数据结构_第2张图片

假设已知头节点(第一个节点)的地址为p,那么第二个节点的地址就为p->next,第三个节点的地址为p->next->next。以此类推,剩余其他节点的地址也可以推导出来。记住这一点,这个在后面的链表增加/删除会用到

注意:单向链表为“单向”的,即只能由前面节点的地址来获取后面节点的地址,不能反过来!

节点添加:

1. 通过判断头节点的next指针是否为空来确认是否空链表。空链表直接将head和tail指针指向此节点,并设置head和tail节点的next指针指向NULL

2. 非空链表,新节点添加到尾部。节点“连接”操作是通过节点中的next指针完成的

嵌入式基础知识_1——C语言知识与数据结构_第3张图片

删除尾节点:

“断链”:即将当前节点next指针指向NULL(空地址)处。这样此节点就与后面的节点没有连接了

操作:先将倒数第二个节点与尾部节点“断链”,移动尾部指针tail指向倒数第二个节点使它成为新的尾部节点。最后使用free函数将原先尾部节点所占用的内存释放。

嵌入式基础知识_1——C语言知识与数据结构_第4张图片

插入节点:

操作:先“断链”,再将新的节点连接起来

嵌入式基础知识_1——C语言知识与数据结构_第5张图片

链表完整代码:

说明:此代码中的head和tail只是节点指针,它们不是节点。因为没有分配内存给它们!

国内gitee:https://gitee.com/jack_tang_1994/data_struct.git

百度云:

链接:https://pan.baidu.com/s/1BQ5VCJN88FbCG-YVrHjKLw 
提取码:cceu

你可能感兴趣的:(嵌入式基础知识,数据结构,嵌入式)