当前位置: 首页 > news >正文

C语言(嵌入式方向)

1. 数据存储与转换


1.1 内存存储方式

(1) 基本单位

  • Bit(二进制位):最小存储单位,值为 01

  • Byte(字节):8 bit

  • Word(字/字长):处理器一次可处理的数据宽度

    • 8位 MCU:1 Word = 8 bit

    • 32位 MCU:1 Word = 32 bit

嵌入式意义

  • MCU 寄存器、RAM、Flash 都是按字节/字访问。

  • Bit 常用于标志位或寄存器控制位。


(2) 字节序(大小端)

  • 大端(Big Endian)

    • 高字节存储在低地址,低字节存储在高地址

    • 例如:int a = 0x12345678;

      地址   数据
      0x00   12
      0x01   34
      0x02   56
      0x03   78

  • 小端(Little Endian)

    • 低字节存储在低地址,高字节存储在高地址

      地址   数据
      0x00   78
      0x01   56
      0x02   34
      0x03   12

嵌入式应用场景

  • 外设寄存器:访问寄存器要按硬件要求处理字节序

  • 通信协议:SPI、I2C、UART、CAN 数据可能需要大小端转换

  • Flash/EEPROM:存储多字节数据时注意读写顺序

C语言示例

#include <stdio.h>int main() {unsigned int a = 0x12345678;unsigned char *p = (unsigned char*)&a;printf("存储顺序:%02x %02x %02x %02x\n", p[0], p[1], p[2], p[3]);return 0;
}

输出结果不同表示系统大小端方式


(3) 对齐与填充

  • 内存对齐:编译器将数据放到特定地址,提升访问效率

  • 规则(以 32 位 MCU 为例):

    • char → 对齐到 1 字节

    • short → 对齐到 2 字节

    • intfloat → 对齐到 4 字节

    • 结构体整体大小也会按最大成员对齐

  • 嵌入式注意

    • 寄存器映射结构体必须 #pragma pack(1)__attribute__((packed)) 避免填充

例子

struct Test { char a; int b; };

  • 不加 packedsizeof(Test) = 8(1字节 + 3字节填充 + 4字节)

  • packedsizeof(Test) = 5


1.2 基本数据类型大小

类型默认大小(32位MCU)说明
char1字节0255 / -128127
short2字节-3276832767 / 065535
int4字节-2^31~2^31-1
long4字节同 int
long long8字节-2^63~2^63-1
float4字节单精度浮点
double8字节双精度浮点(部分 MCU 可优化成4字节)

嵌入式应用提醒

  • 小型 MCU 浮点运算慢,尽量用整数处理

  • uint8_t, int16_t, uint32_t 等标准整型可保证大小一致


1.3 类型转换

(1) 隐式转换

  • 小类型 → 大类型

  • 整数 → 浮点

int a = 10;
float b = a; // int → float

(2) 强制类型转换

  • (类型) 显式指定

  • 可能丢失精度或溢出

float x = 3.14;
int y = (int)x; // y = 3

(3) 嵌入式应用示例

  • ADC 转换为电压

uint16_t adc_val = 2048;
float voltage = (float)adc_val * 3.3f / 4095;
  • 通信协议数据打包/拆包

uint16_t data = 0x1234;
uint8_t low = (uint8_t)data;      // 低字节
uint8_t high = (uint8_t)(data>>8); // 高字节


1.4 未决问题 & 嵌入式注意事项

  1. 溢出

    • 有符号整型溢出行为未定义

    • 无符号整型溢出回绕(模2^n)

  2. 浮点精度

    • 小数运算可能不精确

    • MCU可用定点运算替代

  3. 内存优化

    • 位域、压缩结构体可减少 RAM 占用

2. 代码与基本数据类型


2.1 基本数据类型

(1) 整数类型

  • 有符号(signed):默认,能表示正数、负数

  • 无符号(unsigned):只能表示正数,范围更大

👉 在嵌入式中,无符号类型常用于计数器、定时器、寄存器值

uint16_t timer_cnt = 65535;  // 定时器计数寄存器

(2) 浮点类型

  • float(单精度,4字节)

  • double(双精度,8字节,有些 MCU 仍然按 4 字节处理)

⚠️ 嵌入式注意

  • STM32F1 没有 FPU(硬件浮点单元),浮点运算非常慢

  • 在定时、PWM、滤波中,能用整数计算就尽量用整数

例如:

// 用浮点计算电压
float voltage = (adc_val * 3.3f) / 4096.0f;// 用整数计算(效率更高)
uint32_t voltage_mV = (adc_val * 3300) / 4096;  // 单位:mV

(3) 特殊关键字类型

volatile
  • 告诉编译器:该变量随时可能被 硬件/中断修改,不要优化。

  • 常用于寄存器映射、全局标志位。

volatile uint32_t TIM_FLAG;  // 定时器中断标志void TIM_IRQHandler(void) {TIM_FLAG = 1;   // 中断修改
}int main() {while (!TIM_FLAG);  // 主程序轮询等待
}

如果不用 volatile,编译器可能认为 TIM_FLAG 永远不变,直接优化掉循环。


const
  • 定义只读变量

  • 嵌入式常用于查表、存储常量参数(放在 Flash,而不是 RAM)

const uint8_t sine_table[256] = { /* 正弦波表 */ };


typedef
  • 为类型起别名,提高可读性

typedef unsigned int uint32_t;

2.2 指针与数组

(1) 指针

  • 存储变量地址,可以间接访问数据

  • 嵌入式最重要的用途:寄存器映射

#define GPIOA_ODR  (*(volatile unsigned int*)0x4001080C)
GPIOA_ODR = 0x01;  // 直接操作寄存器

👉 volatile 必须加,否则优化掉寄存器访问。


(2) 数组

  • 一段连续内存存储同类数据

  • 嵌入式常用于 数据缓存(buffer)

uint8_t rx_buffer[128];  // 串口接收缓存

(3) 字符串(C风格)

  • 实际上是 char 数组,以 '\0' 结尾

char msg[] = "Hello STM32";

2.3 结构体与位域

(1) 结构体

  • 把相关数据打包

  • 嵌入式常用于 传感器数据打包、通信数据帧

struct Sensor {int temperature;int humidity;int pressure;
};

(2) 位域(嵌入式重点 ⚡)

  • 可以按位定义变量,节省空间

  • 常用于寄存器操作

struct GPIO_CR {unsigned int MODE : 2;  // 2位unsigned int CNF  : 2;  // 2位unsigned int RES  : 28; // 保留
};

使用方式:

struct GPIO_CR *reg = (struct GPIO_CR*)0x40010800;
reg->MODE = 0x3;   // 设置模式

⚠️ 注意:不同编译器对位域存放规则可能不同,不适合跨平台寄存器映射,常用 宏定义+位操作 更可靠。


2.4 枚举与宏

(1) 枚举(enum)

  • 用于定义状态机、模式切换等

enum State { IDLE, RUNNING, ERROR };
enum State sys_state = IDLE;

(2) 宏(#define)

  • 用于常量、寄存器、位操作

#define LED_PIN  (1<<5)     // PA5
#define LED_ON   (GPIOA_ODR |= LED_PIN)
#define LED_OFF  (GPIOA_ODR &= ~LED_PIN)

📌 小结(代码与基本数据类型)

  1. 基本类型:整数优先、浮点尽量少用

  2. 关键字:volatile(寄存器、中断标志)、const(查表)

  3. 指针:核心在于寄存器映射

  4. 结构体与位域:提高可读性,寄存器操作常用宏更安全

  5. 宏 & 枚举:便于定义寄存器位、系统状态

3. 数据运算符


3.1 算术运算符

(1) 基本运算

  • +:加法

  • -:减法

  • *:乘法

  • /:除法

  • %:取余(整型才有,浮点不能用 %

嵌入式注意点

  1. 整型除法 → 舍弃小数部分:

    int a = 7/3;  // a = 2
    

  2. 除以 0 → 未定义行为,MCU 可能异常。

  3. % 运算效率低,可以用位运算代替(如 %2 可以用 &1)。

优化示例

// 判断奇偶数
if (x % 2 == 0) { }   // 慢
if ((x & 1) == 0) { } // 快

3.2 关系运算符

  • ==:相等

  • !=:不相等

  • > < >= <=

👉 在嵌入式中常用于 传感器判断、标志位检测

if (adc_val > 2000) {// 电压过高
}

3.3 逻辑运算符

  • &&:逻辑与

  • ||:逻辑或

  • !:逻辑非

嵌入式应用

  • 多条件检测(如按键+传感器同时有效)

if (button_pressed && adc_val > 1000) {// 条件同时满足
}

3.4 位运算符(嵌入式重点 ⚡)

(1) 与(&)

  • 保留某些位,其余清零

value &= ~(1<<3); // 清零第3位

(2) 或(|)

  • 设置某些位为 1

value |= (1<<5);  // 置位第5位

(3) 异或(^)

  • 相同为 0,不同为 1 → 用于翻转某些位

value ^= (1<<2);  // 翻转第2位

(4) 取反(~)

  • 0 → 1,1 → 0

uint8_t a = 0b10101010;
a = ~a;  // 结果:01010101

(5) 移位(<< >>)

  • << 左移:乘 2

  • >> 右移:除 2(有符号数右移时,最高位补符号位)

uint8_t a = 1 << 3; // 结果 = 8 (00001000)

(6) 嵌入式应用实例

GPIO 输出控制
#define LED_PIN (1<<5)  GPIOA_ODR |= LED_PIN;   // PA5 置高(LED亮)
GPIOA_ODR &= ~LED_PIN;  // PA5 置低(LED灭)
寄存器配置(置位/清零)
// 使能 TIM2 时钟 (RCC_APB1ENR寄存器第0位)
RCC_APB1ENR |= (1<<0);  // 关闭 TIM2 时钟
RCC_APB1ENR &= ~(1<<0);
翻转引脚状态
GPIOA_ODR ^= (1<<5);  // PA5 电平翻转

3.5 赋值与自增运算符

(1) 复合赋值运算符

  • += -= *= /= &= |= ^= <<= >>=

a += 5;   // 相当于 a = a + 5

(2) 自增自减运算符

  • ++a:先加再用

  • a++:先用再加

int a = 5;
int b = a++; // b=5, a=6
int c = ++a; // c=7, a=7

嵌入式常用场景:循环计数、数组索引、延时函数。


3.6 条件运算符

  • 三目运算符:条件 ? 值1 : 值2

int min = (a < b) ? a : b;

👉 嵌入式里常用于 寄存器快速判断赋值


📌 小结(数据运算符)

  1. 算术运算:注意整型除法和溢出

  2. 逻辑运算:常用于多条件判断

  3. 位运算:嵌入式开发核心,寄存器、GPIO 必备

  4. 赋值/自增:常用于循环和计数

  5. 条件运算:简洁高效,适合快速判断

4. 总结与应用实例


4.1 LED 闪烁程序解析

这是嵌入式 C 语言学习的第一个“Hello World”。

#include "stm32f10x.h"int main(void) {// 1. 开启 GPIOA 时钟 (RCC_APB2ENR bit2 = 1)RCC->APB2ENR |= (1<<2);// 2. 设置 PA5 为推挽输出 (GPIOA_CRL MODE5=11, CNF5=00)GPIOA->CRL &= ~(0xF << (5*4));  // 清零GPIOA->CRL |=  (0x3 << (5*4));  // 输出模式 50MHz// 3. 循环点亮和熄灭 LEDwhile(1) {GPIOA->ODR |=  (1<<5);   // LED ONfor (volatile int i=0; i<500000; i++); // 延时GPIOA->ODR &= ~(1<<5);   // LED OFFfor (volatile int i=0; i<500000; i++); // 延时}
}

知识点应用:

  1. 数据存储

    • volatile 用于延时变量,避免编译器优化掉循环

  2. 数据类型

    • 使用 intuint32_t,保证寄存器访问一致

  3. 运算符

    • |&~ 用于寄存器位操作

    • 移位 << 用于快速定位寄存器位


4.2 定时器配置实例

利用 TIM2 让 LED 每秒闪烁一次。

#include "stm32f10x.h"int main(void) {// 1. 开启 GPIOA、TIM2 时钟RCC->APB2ENR |= (1<<2);  // GPIOARCC->APB1ENR |= (1<<0);  // TIM2// 2. 设置 PA5 为输出GPIOA->CRL &= ~(0xF << (5*4));GPIOA->CRL |=  (0x3 << (5*4));// 3. 配置 TIM2 (1Hz 定时)TIM2->PSC = 7200 - 1;   // 72MHz/7200 = 10kHzTIM2->ARR = 10000 - 1;  // 10kHz/10000 = 1HzTIM2->CR1 |= 1;         // 使能定时器while (1) {if (TIM2->SR & 1) {       // 溢出标志 UIF=1TIM2->SR &= ~1;       // 清除 UIFGPIOA->ODR ^= (1<<5); // 翻转 LED}}
}

知识点应用:

  • 存储与转换PSCARR 用整数计算替代浮点运算

  • 数据类型uint16_t 确保定时器参数不会溢出

  • 运算符

    • & 检查标志位

    • &~ 清除标志位

    • ^ 翻转 LED


4.3 通信协议打包与解析

模拟 UART 发送温度和湿度数据。

#include <stdint.h>typedef struct {uint8_t header;uint16_t temperature;uint16_t humidity;uint8_t checksum;
} DataPacket;uint8_t calc_checksum(DataPacket *pkt) {return (uint8_t)(pkt->header + (pkt->temperature&0xFF) + (pkt->temperature>>8)+ (pkt->humidity&0xFF) + (pkt->humidity>>8));
}int main(void) {DataPacket pkt;pkt.header = 0xAA;pkt.temperature = 253;  // 25.3℃pkt.humidity = 600;     // 60.0%pkt.checksum = calc_checksum(&pkt);// 此处可通过 UART 发送 pktreturn 0;
}

知识点应用:

  1. 数据存储与转换

    • uint16_t 拆分为高低字节

  2. 基本类型

    • typedef struct 用于打包数据帧

    • const 可以用于协议固定帧头

  3. 运算符

    • &>> 提取高低字节

    • + 计算校验和


4.4 小结

  • 数据存储与转换 → 解决了 MCU 的 字节序、寄存器映射、类型大小 问题

  • 基本数据类型 → 通过 volatiletypedefstruct 让代码更可靠和可读

  • 数据运算符 → 位运算是嵌入式的“灵魂”,直接控制硬件

通过 LED、定时器、通信这几个例子,我们可以看到 所有理论知识都能落到寄存器和外设操作上,这就是 C 语言在嵌入式开发中的精髓。


文章转载自:

http://dRMBzrRL.gbrdx.cn
http://PMrMEnXC.gbrdx.cn
http://1x0FqsjY.gbrdx.cn
http://YJz8lSR0.gbrdx.cn
http://qMU16jRh.gbrdx.cn
http://KLwuWTjj.gbrdx.cn
http://Ec8226Oy.gbrdx.cn
http://NLrJTghW.gbrdx.cn
http://QxoxuE6O.gbrdx.cn
http://aASg94zi.gbrdx.cn
http://cZCh4waI.gbrdx.cn
http://iXfWdS6W.gbrdx.cn
http://gumiPpDo.gbrdx.cn
http://O5iNwEoK.gbrdx.cn
http://zgz3zzF2.gbrdx.cn
http://LZ33Ra2b.gbrdx.cn
http://03rLoAnY.gbrdx.cn
http://CEmeZaHZ.gbrdx.cn
http://GfcHsc1L.gbrdx.cn
http://SBhCmQ1Z.gbrdx.cn
http://4xyw2BUj.gbrdx.cn
http://OBEs8tcz.gbrdx.cn
http://0rqtIW2R.gbrdx.cn
http://Y1CLK6XL.gbrdx.cn
http://bLErzJh1.gbrdx.cn
http://BHCEixV4.gbrdx.cn
http://TNuW1UZP.gbrdx.cn
http://sbyi1jDo.gbrdx.cn
http://MNqpe40q.gbrdx.cn
http://NRbwOC6u.gbrdx.cn
http://www.dtcms.com/a/375449.html

相关文章:

  • 【大模型手撕】pytorch实现LayerNorm, RMSNorm
  • 执行计划 RAC 笔记
  • 西嘎嘎学习 - C++ 类 对象 - Day 8
  • 如何把PPT转换成PDF?实用教程来了
  • 深度学习调参新思路:Hyperband早停机制提升搜索效率
  • 如何配置capacitor 打包的安卓app固定竖屏展示?
  • Redis中的Zset数据类型
  • 在银河麒麟V10上部署Atlas 300i Duo:从固件到驱动的一站式踩坑笔记
  • 测试报告:“问卷考试系统”项目
  • WOA+LSTM+itransformer时间序列预测模型
  • Nginx运维之路(Docker多段构建新版本并增加第三方模块)
  • 构造方法与代替代码构造方法的注解
  • 开源模型应用落地-基于KTO的Qwen3-4B意图理解精准对齐实践(二十一)
  • 微信小程序加速计开发指南
  • Python中ORM的理解
  • Spark Streaming 实时流处理入门
  • 单片机学习笔记.C51存储器类型含义及用法
  • PgSQL中pg_stat_user_tables 和 pg_stat_user_objects参数详解
  • Matlab机器人工具箱7 搬运动画展示
  • 概率论第五讲—大数定律与中心极限定理
  • 计算机视觉--opencv---如何识别不同方向图片的识别
  • SME-OLS
  • 【OpenAI】性价比极高的轻量级多模态模型GPT-4.1-mini介绍 + API KEY的使用教程!
  • 机器学习-聚类
  • MyBatis基础到高级实践:全方位指南(中)
  • CLR的GC机制
  • 《投资-48》- 股市有哪些相互矛盾的说法?
  • 传统商业积分的普遍困境与 RWA 的破局可能
  • 稳定币法律困局:游走在创新与监管的钢丝绳上
  • 第三方区块链应用测评:【多签钱包合约安全评估_阈值签名机制与私钥存储安全性测试】