一、C语言入门

程序结构

//file: main.c
#include<stdio.h> 
int main()
{
    printf("Hello World");
    return 0; 
}

一个C程序由若干头文件和函数组成。

#include <stdio.h>是一条预处理命令, 它的作用是通知C语言编译系统,在对C程序进行正式编译之前需做一些预处理工作。函数是实现代码逻辑的一个小的单元。

编程规范

注释

参考STM32CubeMX生成的模版程序

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2023 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "tim.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "driver_tim_pwm.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  if(IS_ENGINEERING_BOOT_MODE())
  {
    /* Configure the system clock */
    SystemClock_Config();
  }

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_TIM2_Init();
  /* USER CODE BEGIN 2 */
  TIM_LED3_PWM_Start();
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSIDivValue = RCC_HSI_DIV1;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
  RCC_OscInitStruct.PLL2.PLLState = RCC_PLL_NONE;
  RCC_OscInitStruct.PLL3.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL3.PLLSource = RCC_PLL3SOURCE_HSI;
  RCC_OscInitStruct.PLL3.PLLM = 4;
  RCC_OscInitStruct.PLL3.PLLN = 26;
  RCC_OscInitStruct.PLL3.PLLP = 2;
  RCC_OscInitStruct.PLL3.PLLQ = 2;
  RCC_OscInitStruct.PLL3.PLLR = 2;
  RCC_OscInitStruct.PLL3.PLLRGE = RCC_PLL3IFRANGE_1;
  RCC_OscInitStruct.PLL3.PLLFRACV = 1024;
  RCC_OscInitStruct.PLL3.PLLMODE = RCC_PLL_FRACTIONAL;
  RCC_OscInitStruct.PLL4.PLLState = RCC_PLL_NONE;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** RCC Clock Config
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_ACLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2
                              |RCC_CLOCKTYPE_PCLK3|RCC_CLOCKTYPE_PCLK4
                              |RCC_CLOCKTYPE_PCLK5;
  RCC_ClkInitStruct.AXISSInit.AXI_Clock = RCC_AXISSOURCE_HSI;
  RCC_ClkInitStruct.AXISSInit.AXI_Div = RCC_AXI_DIV1;
  RCC_ClkInitStruct.MCUInit.MCU_Clock = RCC_MCUSSOURCE_PLL3;
  RCC_ClkInitStruct.MCUInit.MCU_Div = RCC_MCU_DIV1;
  RCC_ClkInitStruct.APB4_Div = RCC_APB4_DIV1;
  RCC_ClkInitStruct.APB5_Div = RCC_APB5_DIV1;
  RCC_ClkInitStruct.APB1_Div = RCC_APB1_DIV2;
  RCC_ClkInitStruct.APB2_Div = RCC_APB2_DIV2;
  RCC_ClkInitStruct.APB3_Div = RCC_APB3_DIV2;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

标识符

C语言规定,标识符可以是字母(A~Z,a~z)、数字(0~9)、下划线_组成的字符串,并且第一个字符必须是字母或下划线。

其他注意点:

  • 标识符的长度最好不要超过8位,因为在某些版本的C中规定标识符前8位有效
  • 标识符是严格区分大小写的
  • 见名思意,不使用中文
  • 不能使用关键字
  • 下划线开头标识符一般定义为特殊含义变量

变量及赋值

使用变量之前必须先定义变量,要区分变量名和变量值是两个不同的概念。

注意:在定义中不允许连续赋值,如int a=b=c=5;是不合法的。

基本数据类型

格式化输出

int a = 10;
float b = 66.6;
char x = 'c';
printf("整数:%d,小数:%f,字符:%c",a ,b, x);

不可改变常量

  • 字面量
    • 整型常量 13、0、-13;
    • 实型常量:13.33、-24.4;
    • 字符常量:‘a’、‘M’
    • 字符串常量:”I love moon!”
  • 符号常量
    • #define 标识符 常量值

类型转换

自动转换发生在不同数据类型运算时,在编译的时候自动完成。

  • 字节小的可以向字节大的自动转换,但字节大的不能向字节小的自动转换。
  • char可以转换为int,int可以转换为double,char可以转换为double。但是不可以反向。

运算符

  • 自增与自减运算符
  • 关系运算符

> 、 >= 、 < 、 <= 、 == 、 !=

关系表达式的值是真和假,在C程序用整数1和0表示。注意:>=, <=, ==, !=这种符号之间不能存在空格。

  • 逻辑运算符

&&、|| 、!

  • 三目运算符

表达式1 ? 表达式2 : 表达式3;

先判断表达式1的值是否为真,如果是真的话执行表达式2;如果是假的话执行表达式3

#include <stdio.h>
int main()
{
    double money =12.0      ; 
    double cost =11.5       ;  
    printf("小编能不能打车回家呢:"); 
    //输出y小编就打车回家了,输出n小编就不能打车回家
    printf("%c\n",money >= cost ? 'y' : 'n' );
    return 0;
}

分支结构

if(表达式)
{
    执行代码块;
}

if(表达式)
{
    执行代码块1;
}
else
{
    执行代码块2;
}
switch(表达式)
{
    case 常量表达式1
        执行代码块1;
        break
    case 常量表达式2
        执行代码块2;
        break
        //...
    default:
        执行代码块n+1;
}

注意:

  • 在case后的各常量表达式的值不能相同,否则会出现错误。
  • 在case子句后如果没有break;会一直往后执行一直到遇到break;才会跳出switch语句。
  • switch后面的表达式语句只能是整型或者字符类型。
  • 在case后,允许有多个语句,可以不用{}括起来。
  • 各case和default子句的先后顺序可以变动,而不会影响程序执行结果。
  • default子句可以省略不用。
#include <stdio.h>

int main() 

{ 
    /* 定义需要计算的日期 */
    int date = 0;
    int year = 2008;
    int month = 8;
    int day = 8;

    switch(month)
    {
        case 12:
            date+=30;
        case 11:
            date+=31;
        case 10:
            date+=30;
        case 9:
            date+=31;
        case 8:
            date+=31;
        case 7:
            date+=30;
        case 6:
            date+=31;
        case 5:
            date+=30;
        case 4:
            date+=31;
        case 3:
            if((year%4==0&&year%100!=0)||year%400==0)
            {
                date+=29;
            }
            else
            {
                date+=28;
            }
        case 2:
            date+=31;
        case 1:
            date += day;
            printf("%d年%d月%d日是该年的第%d天",year,month,day,date);
            break;
        default:
            printf("error");
            break;
    }
    return 0;
}

循环结构

while(表达式)
{
    执行代码块
}

do
{
    执行代码块
}while(表达式);

for(表达式1;表达式2;表达式3)
{
    执行代码块
}

do-while :

先执行循环中的执行代码块,然后再判断while中表达式是否为真,如果为真则继续循环;如果为假,则终止循环。因此,do-while循环至少要执行一次循环语句。

在for循环中:

  • 表达式1是一个或多个赋值语句,它用来控制变量的初始值;
  • 表达式2是一个关系表达式,它决定什么时候退出循环;
  • 表达式3是循环变量的步进值,定义控制循环变量每循环一次后按什么方式变化。

在知道循环次数的情况下更适合使用for循环;在不知道循环次数的情况下适合使用while或者do-while循环:在不知道循环次数的情况下适合使用while或者do-while循环,如果至少循环一次应考虑使用do-while循环。

结束语句之break语句

在没有循环结构的情况下,break不能用在单独的if-else语句中。在多层循环中,一个break语句只跳出当前循环。

结束语句之continue语句

continue语句的作用是结束本次循环开始执行下一次循环。

break是跳出当前整个循环,continue是结束本次循环开始下一次循环。

自创函数

格式:

[数据类型说明] 函数名称 ([参数])
{
    变量定义部分
    语句部分
    return(表达式)
}

自定义函数放在main函数后面的话, 需要在main函数之前先声明自定义函数。声明格式: [数据类型说明] 函数名称 ([参数]);

函数调用

函数名称 ([参数]);

有参与无参

形参与实参

函数的返回值

return 表达式;

return (表达式);

递归函数

局部变量和全局变量

C语言中的变量,按作用域范围可分为两种,即局部变量和全局变量。局部变量也称为内部变量。全局变量也称为外部变量,它是在函数外部定义的变量。

C语言根据变量的生存周期来划分,可以分为静态存储方式和动态存储方式。静态存储方式:是指在程序运行期间分配固定的存储空间的方式。静态存储区中存放了在整个程序执行过程中都存在的变量,如全局变量。动态存储方式:是指在程序运行期间根据需要进行动态的分配存储空间的方式。动态存储区中存放的变量是根据程序运行的需要而建立和释放的,通常包括:函数形式参数;自动变量;函数调用时的现场保护和返回地址等。

1、全局变量在整个工程文件内都有效。

2、静态全局变量只在定义它的文件内有效。

3、静态局部变量只在定义它的函数内有效,且程序仅分配一次内存,函数返回后,该变量不会消失;局部变量在定义它的函数内有效,但是函数返回后失效。

4、全局变量和静态变量如果没有手工初始化,则由编译器初始化为0。局部变量的值不可知。

5、静态局部变量与全局变量共享全局数据区,但静态局部变量只在定义它的函数中可见。静态局部变量与局部变量在存储位置上不同,使得其存在的时限也不同,导致对这两者操作的运行结果也不同。

#include <stdio.h>
void func();//函数声明,主要是因为被调用的函数定义写在了main函数后面了
int n = 1; //全局变量

int  main(void)
{
    static int a; // 静态局部变量,但静态局部变量只在定义它的函数中可见,并且只初始化一次
    int b = -10; // 局部变量
 
    printf("main:   a=%d,   b=%d,   n=%d\n",a,b,n);
 
    b += 4;
 
    func();
 
    printf("main:   a=%d,   b=%d,   n=%d\n",a,b,n);
    n += 10;
 
    func();
    printf("main:   a=%d,   b=%d,   n=%d\n",a,b,n);
 
}

void func()
{
    static int a = 2; // 静态局部变量
    int b = 5; // 局部变量
    a += 2;
    n += 12;
    b += 5;
   printf("func:   a=%d,   b=%d,   n=%d\n",a,b,n);
}

static在文件作用域和代码块作用域的意义是不同的:在文件作用域用于限定函数和变量的外部链接性(能否被其它文件访问), 在代码块作用域则用于将变量分配到静态存储区。

外部变量

程序的编译单位是源程序文件,一个源文件可以包含一个或若干个函数。在函数内定义的变量是局部变量,而在函数之外定义的变量则称为外部变量,外部变量也就是我们所讲的全局变量。它的存储方式为静态存储,其生存周期为整个程序的生存周期。全局变量可以为本文件中的其他函数所共用,它的有效范围为从定义变量的位置开始到本源文件结束1

如果全局变量不在文件的开头定义,有效的作用范围将只限于其定义处到文件结束。如果在定义点之前的函数想引用该全局变量,则应该在引用之前用关键字 extern 对该变量作“外部变量声明”,表示该变量是一个已经定义的外部变量。有了此声明,就可以从“声明”处起,合法地使用该外部变量。

如果整个工程由多个源文件组成,在一个源文件中想引用另外一个源文件中已经定义的外部变量,同样只需在引用变量的文件中用 extern 关键字加以声明即可。

变量存储类别

C语言中存储类别又分为四类:

  • 自动(auto)
  • 静态(static)
  • 寄存器的(register)
  • 外部的(extern)

字符串与数组

c语言获取数组长度

int length = sizeof(arr)/sizeof(arr[0]);

#include <stdio.h>
int main()
{
    double arr[]={1.78, 1.77, 1.82, 1.79, 1.85, 1.75, 1.86, 1.77, 1.81, 1.80};
    int i,j;
    printf("\n************排队前*************\n");
    for(i=0;i<10;i++)
    {
        if(i != 9)   
            printf("%1.2f, ", arr[i]);  //%1.2f表示小数点前一位,小数点后精确到两位
        else
            printf("%1.2f", arr[i]);    //%1.2f表示小数点前一位,小数点后精确到两位
    }
    for(i=8; i>=0; i--)
    {
        for(j=0;j<=i;j++)
        {
            if( arr[j]>arr[j+1])      //当前面的数比后面的数大时
            {
                double temp;    //定义临时变量temp
                temp=arr[j];//将前面的数赋值给temp
                arr[j]=arr[j+1];             //前后之数颠倒位置
                arr[j+1]=temp;//将较大的数放在后面    
            }                 
        }                
    }
    printf("\n************排队后*************\n");
    for(i=0;i<10;i++)
    {
        if(i != 9)   
            printf("%1.2f, ", arr[i]);  //%1.2f表示小数点前一位,小数点后精确到两位     
        else
            printf("%1.2f", arr[i]);    //%1.2f表示小数点前一位,小数点后精确到两位
    }
    return 0;    
}

C语言中,是没有办法直接定义字符串数据类型的,但是我们可以使用数组来定义我们所要的字符串。一般有以下两种格式:

char 字符串名称[长度] = “字符串值”;

char 字符串名称[长度] = {‘字符1’,‘字符2’,…,‘字符n’,'\0'};

采用第2种方式的时候最后一个元素必须是'\0','\0’表示字符串的结束标志;

字符串处理函数

易变变量

关键字 volatile 告诉编译器,它后面所定义的变量为“易失性变量”,随时都有可能被修改,因此编译器必须确保每次使用变量时都要主动从内存中、根据存储变量的地址读取数据,而不是使用寄存器中的缓存值。

宏定义 #define __IO volatile 的作用是将标记为 “__IO” 的变量设置成可读可写的,具有在任何时候均可读写的权限,本质上,它是一个缩写,意味着变量可以在不需要在代码中显式标明 “volatile” 的情况下使用2

typedef unsigned int uint32_t;

#define	 __IO  volatile  /* defines 'read / write' permissions */ 
void Delay(__IO uint32_t nCount)	 //简单的延时函数
{
	for(; nCount != 0; nCount--);
}

二、嵌入式C开发基础

关键字

sizeof

sizeof是C语言的关键字,它用来计算变量(或数据类型)在当前系统中占用内存的字节数3。sizeof不是函数,sizeof有两种写法:

  1. 用于数据类型 sizeof(数据类型);

数据类型必须用括号括住。

printf("字符型变量占用的内存是=%d\n",sizeof(char));   // 输出:字符型变量占用的内存是=1
printf("整型变量占用的内存是=%d\n",sizeof(int));   // 输出:整型变量占用的内存是=4
  1. 用于变量

sizeof(变量名);

sizeof 变量名;

变量名可以不用括号括住,带括号的用法更普遍,大多数程序员采用这种形式。 

sizeof(结构体)

/*
 * 程序名:book90.c,此程序用于演示C语言的结构体占用内存的情况
 * 作者:C语言技术网(www.freecplus.net) 日期:20190525
*/
#include <stdio.h>

// #pragma pack(1)  # 告诉编译器内存按1字节对齐。

struct st_girl
{
  char name[50];     // 姓名
  int  age;          // 年龄
  int  height;       // 身高,单位:厘米cm
  char sc[30];       // 身材,火辣;普通;飞机场。
  char yz[30];       // 颜值,漂亮;一般;歪瓜裂枣。
};

int main()
{
  struct st_girl queen;
  printf("sizeof(struct st_girl) %d\n",sizeof(struct st_girl));
  printf("sizeof(queen) %d\n",sizeof(queen));
}

数据类型

内存管理和存储架构

//main.c
#include <stdio.h>
#include <stdlib.h>

static int st_val;                   //静态全局变量 -- 静态存储区
int ex_val;                           //全局变量 -- 静态存储区
int main(void)
{
   int a = 0;                         //局部变量 -- 栈上申请
   int *ptr = NULL;                   //指针变量
   static int local_st_val = 0;       //静态变量
   local_st_val += 1;
   a = local_st_val;
   ptr = (int *)malloc(sizeof(int)); //从堆上申请空间
   if(ptr != NULL)
   {      
    printf("*p value:%d", *ptr);
    free(ptr);      
    ptr = NULL;      
    //free后需要将ptr置空,否则会导致后续ptr的校验失效,出现野指针   
    }            
}    

指针和数组

结构类型和对齐

typedef enum {
    spring = 1,
    summer,
    autumn,
    winter
}season;

season s1 = summer;

typedef union{     
  char c;     
  short s;     
  int i; 
}UNION_VAL;
 
UNION_VAL val; 

结构体则是将具有共通特征的变量组成的集合,比起C++的类来说,它没有安全访问的限制,不支持直接内部带函数,但通过自定义数据类型,函数指针,仍然能够实现很多类似于类的操作,对于大部分嵌入式项目来说,结构化处理数据对于优化整体架构以及后期维护大有便利。

typedef int (*pfunc)(int, int); 
typedef struct{     
  int num;     
  int profit;     
  pfunc get_total; 
}STRUCT_VAL;
  
int GetTotalProfit(int a, int b)
{     
  return a*b; 
}  

int main(void){     
  STRUCT_VAL Val;     
  STRUCT_VAL *pVal;      
  Val.get_total = GetTotalProfit;     
  Val.num = 1;     
  Val.profit = 10;     
  printf("Total:%d\n",  Val.get_total(Val.num, Val.profit));  //变量访问    
  pVal = &Val;     
  printf("Total:%d\n",  pVal->get_total(pVal->num, pVal->profit)); //指针访问 
} 

C语言的结构体支持指针和变量的方式访问,通过转换可以解析任意内存的数据(如我们之前提到的通过指针强制转换解析协议),另外通过将数据和函数指针打包,在通过指针传递,是实现驱动层实接口切换的重要基础,有着重要的实践意义,另外基于位域,联合体,结构体,可以实现另一种位操作,这对于封装底层硬件寄存器具有重要意义,实践如下:

typedef unsigned char uint8_t; 
union reg
{     
    struct
    {         
    uint8_t bit0:1;         
    uint8_t bit1:1;         
    uint8_t bit2_6:5;         
    uint8_t bit7:1;     
    }bit;     
  uint8_t all; 
}; 

int main(void)
{     
  union reg RegData;     
  RegData.all = 0;      
  RegData.bit.bit0 = 1;     
  RegData.bit.bit7 = 1;     
  printf("0x%x\n", RegData.all);      
  RegData.bit.bit2_6 = 0x3;     
  printf("0x%x\n", RegData.all); 
} 

通过联合体和位域操作,可以实现对数据内bit的访问,这在寄存器以及内存受限的平台,提供了简便且直观的处理方式。

预处理机制

模块化编程

一般项目结构,采用模块化思想组织项目。.c 文件主要负责实现,定义函数;.h 文件主要负责声明,比如函数声明、宏定义等。若采用面向对象编程思想,.h文件存放数据(结构体)和数据操作接口(函数),.c文件负责实现具体的功能函数。

project
	module1
		inc
            header1.h
            header2.h
		src
            func1.c
            func2.c
	module2
		inc
		src
	main.c
    config.xml
    docs

设计自己的库

1、原则

  • 每个库有一个自己的主题
  • 要考虑通用性

2、设计库接口和函数实现

  • 接口/头文件(.h)
//file:random.h
//随机函数库的头文件
#ifndef _random_h
#define _random_h

//函数:RandomInit
//用法:RandomInit()
//作用:此函数初始化随机数发生器,需要用随机数的程序在第一次生成随机数前,必须先调用一次本函数
void RandomInit();

//函数:RandomInteger
//用法:n = RandomInteger(int low, int high);
//作用:此函数返回一个low到hign之间的随机数,包括low和high
int RandomInteger(int low, int high);

//函数:RandomDouble
//用法:n = RandomDouble(low, high)
//作用:此函数返回一个大于等于low,且小于high的随机实数
double RandomDouble(double low,double high);

#endif
  • 实现/源代码(.c)
//file:random.cpp
//该文件实现了random库
#include <cstdlib>
#include <ctime>
#include <random.h>

//函数:RandomInit
//该函数取当前系统时间作为随机数发生器种子
void RandomInit()
{
    srand(time(NULL));
}

//函数:RandomInteger
//该函数将0到RAND_MAX的区间划分成high-low+1个子区间
//当产生的随机数逻在第一个子区间时,则映射成low。当落在最后一个子区间时,
//则映射成high。当落在第i个子区间时(i从0到high-low),则映射到low+1
int RandomInteger(int low, int high)
{
    return (low + (high - low + 1)*rand() / (RAND_MAX +1));
}

//函数:RandomDouble
//该函数将产生的随机数映射为[0,1]之间的实数,然后转换到区间[low,high]
double RandomDouble(double low, double high)
{
    double d = (double rand() / (RAND_MAX + 1));
    return (low + (high - low +1) * d);
}

三、嵌入式系统基础

嵌入式系统基础

通信基础

在嵌入式中,通信将主控芯片与传感器、存储芯片、外围控制芯片等连接了起来,使得功能不再受限于主控本身。主控既从其它设备获取信息,也将自己的信息传递给其它设备。如果通信没处理好,将直接影响整个系统的功能,由此可见通信技术的重要性4

相关概念有:

  • 串行/并行
  • 全双工/半双工/单工
  • 同步/异步
    • 同步通信的做法是加一个时钟信号,发送方和接收方在这个时钟的节拍下传输数据,比如常见的 SPI、I2C。而异步通信的做法是对数据进行封装,在数据开头加上起始信号,在数据结尾加上终止信号,双方就按这个规则传输数据,比如 UART、1-Wire。因此,可以通过是否有时钟信号,初步判断是何种数据同步方式。
    • 对于同步通信,通信速率由时钟信号决定,时钟信号越快,传输速度就越快。对于异步通信,需要收发双方提前统一通信速率,这也就是我们串口调试时,波特率不对显示乱码的原因。
  • 电平标准
    • 说到串口,经常提到 TTL、RS232、RS422、RS485,简单的说,就是为了适应不同的环境条件,使用了不同的电平标准。假如微处理器和板载的蓝牙透传模块通信时,一般就使用 TTL 电平,引脚直接连接即可。假如微处理器在工业现场,需要连接一个几十米外的装置,则应该考虑将 TTL 电平转为 RS232、RS422、RS485。

接口技术


  1. C语言extern关键字用法详解 ↩︎

  2. 宏定义#define __IO volatile 的作用 ↩︎

  3. sizeof运算符 ↩︎

  4. 100ASK_STM32MP157 M4 用户手册 ↩︎