Rust no-std嵌入式開發:從零構建裸機韌體完整指南 2026
编程语言
Rust no-std嵌入式開發:從零構建裸機韌體完整指南 2026
在嵌入式開發領域,C語言長期佔據統治地位。但隨著Rust生態的成熟,越來越多的團隊開始用Rust替代C來構建安全可靠的裸機韌體。Rust的所有權系統在編譯期就能消除記憶體安全隱患,而no_std環境讓Rust可以在沒有任何作業系統的裸機硬體上運行。本文將帶你從零開始,完整掌握Rust嵌入式開發的核心模式。
核心概念速覽
| 概念 | 說明 | 適用場景 |
|---|---|---|
no_std |
禁用Rust標準庫,僅使用core和alloc |
裸機/RTOS環境 |
cortex-m-rt |
Cortex-M執行時入口與連結腳本 | ARM Cortex-M系列MCU |
PAC |
週邊存取包,直接映射暫存器 | 底層驅動開發 |
HAL |
硬體抽象層,提供進階API | 應用層開發 |
critical-section |
臨界區抽象,跨平台安全 | 中斷安全程式碼 |
defmt |
零成本日誌框架 | 嵌入式除錯 |
RTIC |
基於中斷的即時並行框架 | 即時系統 |
embassy |
非同步嵌入式執行器 | 高並行週邊操作 |
五大痛點分析
- 環境搭建複雜:Rust嵌入式工具鏈配置繁瑣,交叉編譯、除錯器連接、燒錄工具鏈缺一不可,新手往往在環境配置階段就放棄了
- 裸機啟動流程不透明:從MCU上電到
main()函式執行之間的啟動過程(向量表、堆疊初始化、資料段搬運)對很多開發者來說是個黑盒 - 週邊驅動開發門檻高:暫存器操作、時序控制、DMA配置等底層操作需要同時理解硬體手冊和Rust unsafe語義
- 中斷處理容易出Bug:中斷與主迴圈的資料競爭、優先級配置、巢狀中斷等都是高頻Bug來源
- RTOS整合缺乏最佳實踐:FreeRTOS/RT-Thread與Rust的互操作、記憶體分配策略、任務間通訊等缺少成熟方案
分步實操:5個核心模式
模式一:no_std環境搭建
執行環境:Rust 1.85+ / cortex-m target / probe-rs 0.24+
首先建立no_std專案並配置交叉編譯目標:
# 安裝Rust嵌入式目標
rustup target add thumbv7em-none-eabihf
# 建立no_std專案
cargo new --lib embedded-firmware
cd embedded-firmware
# Cargo.toml
[package]
name = "embedded-firmware"
version = "0.1.0"
edition = "2021"
[dependencies]
cortex-m = "0.7"
cortex-m-rt = "0.7"
panic-halt = "1.0"
defmt = "0.3"
defmt-rtt = "0.4"
[profile.release]
opt-level = "s"
lto = true
codegen-units = 1
[[bin]]
name = "embedded-firmware"
path = "src/main.rs"
// src/main.rs
#![no_main]
#![no_std]
use cortex_m_rt::entry;
use panic_halt as _;
use defmt::info;
#[entry]
fn main() -> ! {
info!("Hello from no_std Rust!");
loop {
cortex_m::asm::delay(8_000_000); // 約1秒延時(8MHz時脈)
}
}
# .cargo/config.toml
[build]
target = "thumbv7em-none-eabihf"
[target.thumbv7em-none-eabihf]
runner = "probe-rs run --chip STM32F411CEUx"
rustflags = [
"-C", "link-arg=-Tlink.x",
"-C", "link-arg=--nmagic",
"-C", "link-arg=-Tdefmt.x",
]
模式二:Cortex-M裸機啟動
理解並自訂啟動流程,掌握向量表和連結腳本:
// src/startup.rs
//! 自訂啟動程式碼 - 理解MCU從上電到main的全過程
use core::arch::global_asm;
// 向量表:MCU上電後第一個讀取的資料結構
// 位於Flash起始位址,包含初始堆疊指標和中斷向量
global_asm!(
".section .vector_table, \"a\"",
".global _vector_table",
"_vector_table:",
" .word _estack // 初始堆疊指標(指向RAM末尾)",
" .word Reset // Reset處理函式",
" .word NMI // NMI處理函式",
" .word HardFault // HardFault處理函式",
" .word 0 // MemManage(Cortex-M7特有)",
" .word 0 // BusFault",
" .word 0 // UsageFault",
" .word 0 // Reserved",
" .word 0 // Reserved",
" .word 0 // Reserved",
" .word 0 // Reserved",
" .word SVCall // SVC呼叫",
" .word DebugMonitor // Debug Monitor",
" .word 0 // Reserved",
" .word PendSV // PendSV(上下文切換)",
" .word SysTick // SysTick計時器",
);
/// Reset處理函式:MCU上電或復位後執行
/// 負責:初始化.data段、清零.bss段、呼叫main
#[no_mangle]
pub unsafe extern "C" fn Reset() -> ! {
// 1. 初始化.data段:從Flash複製初始化資料到RAM
extern "C" {
static mut _sdata: u32;
static mut _edata: u32;
static _sidata: u32;
}
let mut src = &_sidata as *const u32;
let mut dst = &mut _sdata as *mut u32;
while dst < &mut _edata as *mut u32 {
dst.write_volatile(src.read_volatile());
src = src.add(1);
dst = dst.add(1);
}
// 2. 清零.bss段
extern "C" {
static mut _sbss: u32;
static mut _ebss: u32;
}
let mut bss_dst = &mut _sbss as *mut u32;
while bss_dst < &mut _ebss as *mut u32 {
bss_dst.write_volatile(0);
bss_dst = bss_dst.add(1);
}
// 3. 呼叫Rust main函式
extern "Rust" {
fn main() -> !;
}
main()
}
/// HardFault處理 - 嵌入式最關鍵的異常處理
#[no_mangle]
pub unsafe extern "C" fn HardFault() -> ! {
let scb = cortex_m::peripheral::SCB::ptr();
let hfsr = (*scb).hfsr.read();
defmt::error!("HardFault! HFSR = {:08x}", hfsr);
let psp = cortex_m::register::psp::read();
if psp != 0 {
let stack_frame = psp as *const u32;
defmt::error!("R0 = {:08x}", *stack_frame.offset(0));
defmt::error!("R3 = {:08x}", *stack_frame.offset(3));
defmt::error!("R12 = {:08x}", *stack_frame.offset(4));
defmt::error!("LR = {:08x}", *stack_frame.offset(5));
defmt::error!("PC = {:08x}", *stack_frame.offset(6));
}
loop {}
}
fn NMI() {}
fn SVCall() {}
fn DebugMonitor() {}
fn PendSV() {}
fn SysTick() {}
模式三:週邊驅動開發
基於PAC和HAL開發週邊驅動,以GPIO和UART為例:
// src/drivers/led.rs
//! LED驅動 - 基於HAL的GPIO封裝
use embedded_hal::digital::OutputPin;
use cortex_m::delay::Delay;
/// LED驅動結構體
pub struct Led<PIN: OutputPin> {
pin: PIN,
active_low: bool,
}
impl<PIN: OutputPin> Led<PIN> {
/// 建立新的LED實例
/// - `pin`: GPIO輸出引腳
/// - `active_low`: true表示低電位點亮(共陽極LED)
pub fn new(pin: PIN, active_low: bool) -> Self {
let mut led = Self { pin, active_low };
led.off();
led
}
/// 點亮LED
pub fn on(&mut self) {
if self.active_low {
self.pin.set_low().ok();
} else {
self.pin.set_high().ok();
}
}
/// 關閉LED
pub fn off(&mut self) {
if self.active_low {
self.pin.set_high().ok();
} else {
self.pin.set_low().ok();
}
}
/// LED呼吸效果(軟體PWM)
pub fn breathe(&mut self, delay: &mut Delay, cycles: u32) {
const STEPS: u32 = 100;
for _ in 0..cycles {
for i in 0..STEPS {
self.on();
delay.delay_us(i);
self.off();
delay.delay_us(STEPS - i);
}
for i in (0..STEPS).rev() {
self.on();
delay.delay_us(i);
self.off();
delay.delay_us(STEPS - i);
}
}
}
}
// src/drivers/uart.rs
//! UART驅動 - 基於HAL的串列通訊封裝
use embedded_hal::serial::{Read, Write};
use nb::block;
/// UART錯誤類型
#[derive(Debug, defmt::Format)]
pub enum UartError {
Framing,
Noise,
Overrun,
Parity,
BufferFull,
}
/// 環形緩衝區 - 用於中斷接收
pub struct RingBuffer<const N: usize> {
buffer: [u8; N],
head: usize,
tail: usize,
full: bool,
}
impl<const N: usize> RingBuffer<N> {
pub const fn new() -> Self {
Self {
buffer: [0u8; N],
head: 0,
tail: 0,
full: false,
}
}
pub fn push(&mut self, byte: u8) -> bool {
if self.full {
return false;
}
self.buffer[self.head] = byte;
self.head = (self.head + 1) % N;
if self.head == self.tail {
self.full = true;
}
true
}
pub fn pop(&mut self) -> Option<u8> {
if self.is_empty() {
return None;
}
let byte = self.buffer[self.tail];
self.tail = (self.tail + 1) % N;
self.full = false;
Some(byte)
}
pub fn is_empty(&self) -> bool {
self.head == self.tail && !self.full
}
pub fn len(&self) -> usize {
if self.full {
return N;
}
if self.head >= self.tail {
self.head - self.tail
} else {
N - self.tail + self.head
}
}
}
/// UART通訊介面
pub struct UartDriver<SERIAL> {
serial: SERIAL,
rx_buffer: RingBuffer<256>,
}
impl<SERIAL, E> UartDriver<SERIAL>
where
SERIAL: Read<u8, Error = E> + Write<u8, Error = E>,
E: defmt::Format,
{
pub fn new(serial: SERIAL) -> Self {
Self {
serial,
rx_buffer: RingBuffer::new(),
}
}
/// 非阻塞讀取中斷接收的資料
pub fn read_byte(&mut self) -> Option<u8> {
self.rx_buffer.pop()
}
/// 中斷中呼叫:將接收到的位元組存入緩衝區
pub fn on_rx_interrupt(&mut self) {
match self.serial.read() {
Ok(byte) => {
if !self.rx_buffer.push(byte) {
defmt::warn!("UART RX buffer overflow!");
}
}
Err(nb::Error::WouldBlock) => {}
Err(nb::Error::Other(_)) => {
defmt::error!("UART read error");
}
}
}
/// 阻塞發送位元組
pub fn write_byte(&mut self, byte: u8) {
block!(self.serial.write(byte)).ok();
}
/// 阻塞發送字串
pub fn write_str(&mut self, s: &str) {
for byte in s.bytes() {
self.write_byte(byte);
}
}
/// 釋放底層串列埠所有權
pub fn free(self) -> SERIAL {
self.serial
}
}
模式四:中斷處理
安全的中斷處理模式,避免資料競爭:
// src/interrupts.rs
//! 安全的中斷處理 - 使用cortex_m::interrupt和atomic
use core::sync::atomic::{AtomicU32, AtomicBool, Ordering};
use cortex_m::interrupt::{self, Mutex};
use core::cell::RefCell;
use stm32f4xx_hal::pac::{self, interrupt};
/// 方式1:原子操作 - 最簡單的中斷安全方案
static Systick_COUNT: AtomicU32 = AtomicU32::new(0);
static BUTTON_PRESSED: AtomicBool = AtomicBool::new(false);
/// 方式2:Mutex保護的共享資源
type SharedSerial = Mutex<RefCell<Option<pac::USART1>>>;
static SHARED_SERIAL: SharedSerial = Mutex::new(RefCell::new(None));
/// 方式3:cortex_m::singleton! 編譯期保證單例
cortex_m::singleton!(
static SHARED_BUFFER: [u8; 128] = [0; 128];
);
/// SysTick中斷 - 系統節拍
#[interrupt]
fn SysTick() {
Systick_COUNT.fetch_add(1, Ordering::Relaxed);
if Systick_COUNT.load(Ordering::Relaxed) % 1000 == 0 {
defmt::trace!("1 second elapsed");
}
}
/// 外部按鈕中斷 - GPIO中斷
#[interrupt]
fn EXTI0() {
unsafe {
let exti = &*pac::EXTI::ptr();
exti.pr.write(|w| w.pr0().set_bit());
}
BUTTON_PRESSED.store(true, Ordering::Release);
}
/// USART1中斷 - 串列埠接收中斷
#[interrupt]
fn USART1() {
interrupt::free(|cs| {
let mut serial = SHARED_SERIAL.borrow(cs).borrow_mut();
if let Some(ref mut usart1) = serial.deref_mut() {
if usart1.sr.read().rxne().bit_is_set() {
let byte = usart1.dr.read().bits() as u8;
defmt::info!("RX: {:02x}", byte);
}
}
});
}
/// 主迴圈中檢查中斷標誌
pub fn process_interrupt_flags() {
if BUTTON_PRESSED.swap(false, Ordering::AcqRel) {
defmt::info!("Button pressed!");
}
let tick = Systick_COUNT.load(Ordering::Acquire);
if tick > 0 && tick % 5000 == 0 {
defmt::info!("5 seconds elapsed, tick = {}", tick);
}
}
use core::ops::DerefMut;
模式五:RTOS整合
Rust與FreeRTOS/embassy的整合方案:
// src/rtos_embassy.rs
//! Embassy非同步執行器 - Rust原生的"RTOS"方案
use embassy_executor::Spawner;
use embassy_stm32::{
gpio::{Level, Output, Speed},
usart::Uart,
time::Hertz,
Config,
};
use embassy_time::{Duration, Timer};
/// Embassy非同步任務:LED閃爍
#[embassy_executor::task]
async fn blink_led(mut led: Output<'static, embassy_stm32::gpio::PA5>) {
let mut counter = 0u32;
loop {
led.set_high();
Timer::after(Duration::from_millis(500)).await;
led.set_low();
Timer::after(Duration::from_millis(500)).await;
counter += 1;
defmt::info!("Blink #{}", counter);
}
}
/// Embassy非同步任務:串列埠回顯
#[embassy_executor::task]
async fn uart_echo(mut uart: Uart<'static, embassy_stm32::usart::USART2>) {
use embassy_stm32::usart::Error;
loop {
let mut buf = [0u8; 1];
match uart.read(&mut buf).await {
Ok(_) => {
let _ = uart.write(&buf).await;
}
Err(Error::Framing) => defmt::error!("UART framing error"),
Err(_) => defmt::error!("UART error"),
}
}
}
/// Embassy非同步任務:感測器讀取
#[embassy_executor::task]
async fn sensor_reader() {
let mut values = [0u16; 64];
let mut idx = 0;
loop {
Timer::after(Duration::from_secs(1)).await;
let value = read_adc_channel(0);
values[idx % 64] = value;
idx += 1;
if idx % 10 == 0 {
let avg: u16 = values[..10].iter().sum::<u16>() / 10;
defmt::info!("Sensor avg: {}", avg);
}
}
}
fn read_adc_channel(_ch: u8) -> u16 {
42
}
/// 主入口 - Embassy執行時
#[embassy_executor::main]
async fn main(spawner: Spawner) {
let config = Config::default();
let p = embassy_stm32::init(config);
let led = Output::new(p.PA5, Level::Low, Speed::Low);
let uart_config = embassy_stm32::usart::Config::default()
.baudrate(Hertz(115200));
let uart = Uart::new(
p.USART2, p.PA3, p.PA2,
uart_config,
).unwrap();
spawner.spawn(blink_led(led)).ok();
spawner.spawn(uart_echo(uart)).ok();
spawner.spawn(sensor_reader()).ok();
defmt::info!("Embassy RTOS started!");
loop {
Timer::after(Duration::from_secs(5)).await;
defmt::info!("Main loop heartbeat");
}
}
// src/rtos_freertos.rs
//! FreeRTOS與Rust整合 - 使用freertos-rust crate
use freertos_rust::{CurrentTask, Delay, FreeRtosAllocator, Task, TaskPriority};
#[global_allocator]
static ALLOC: FreeRtosAllocator = FreeRtosAllocator;
fn led_task(_pv: *mut core::ffi::c_void) {
loop {
toggle_led();
Delay::new(500);
}
}
fn sensor_task(_pv: *mut core::ffi::c_void) {
loop {
let value = read_sensor();
defmt::info!("Sensor: {}", value);
Delay::new(1000);
}
}
pub fn start_freertos_tasks() {
Task::new()
.name("LED")
.stack_size(256)
.priority(TaskPriority(2))
.start(led_task)
.expect("Failed to create LED task");
Task::new()
.name("Sensor")
.stack_size(512)
.priority(TaskPriority(1))
.start(sensor_task)
.expect("Failed to create Sensor task");
freertos_rust::start_scheduler();
}
fn toggle_led() { /* 硬體相關 */ }
fn read_sensor() -> u16 { 42 }
避坑指南
坑1:忘記#![no_std]導致連結錯誤
// ❌ 錯誤:缺少no_std宣告
use std::println; // 這會拉入整個標準庫!
// ✅ 正確:必須在crate根部宣告
#![no_std]
#![no_main]
原因:Rust預設連結標準庫,而嵌入式目標沒有OS支援標準庫。忘記宣告會導致大量未定義符號的連結錯誤。
坑2:在中斷中呼叫阻塞函式
// ❌ 錯誤:中斷中執行耗時操作
#[interrupt]
fn USART1() {
let data = blocking_read_sensor(); // 可能阻塞數ms!
process_data(data);
}
// ✅ 正確:中斷中只設標誌,主迴圈處理
#[interrupt]
fn USART1() {
DATA_READY.store(true, Ordering::Release);
}
loop {
if DATA_READY.swap(false, Ordering::AcqRel) {
let data = blocking_read_sensor();
process_data(data);
}
}
坑3:共享可變狀態未加保護
// ❌ 錯誤:裸共享可變靜態變數
static mut BUFFER: [u8; 64] = [0; 64];
#[interrupt]
fn DMA1_STREAM0() {
unsafe { BUFFER[0] = 42; } // 資料競爭!
}
// ✅ 正確:使用Mutex或Atomic
use core::sync::atomic::AtomicU8;
static BUFFER_HEAD: AtomicU8 = AtomicU8::new(0);
坑4:PAC暫存器操作忘記unsafe
// ❌ 錯誤:直接存取PAC暫存器而不使用安全API
let gpioa = unsafe { &*pac::GPIOA::ptr() };
gpioa.bsrr.write(|w| w.bits(1)); // 未使用型別安全的API
// ✅ 正確:使用PAC提供的型別安全API
let gpioa = unsafe { &*pac::GPIOA::ptr() };
gpioa.bsrr.write(|w| w.bs5().set_bit()); // 編譯期檢查
坑5:堆疊溢位未偵測
// ❌ 錯誤:大陣列直接放在堆疊上
fn process() {
let buffer = [0u8; 4096]; // STM32F103只有20KB RAM!
}
// ✅ 正確:使用靜態緩衝區或堆分配
static BUFFER: cortex_m::singleton!(Buffer = [0u8; 4096]) = [0; 4096];
use embedded_alloc::Heap;
#[global_allocator]
static HEAP: Heap = Heap::empty();
報錯排查表
| 報錯資訊 | 原因 | 解決方案 |
|---|---|---|
error: language item required, but not found: eh_personality |
缺少no_std執行時支援 |
在main.rs新增#![no_main]和panic-halt依賴 |
error: linking with cc failed |
連結腳本缺失 | 新增-C link-arg=-Tlink.x到rustflags |
error[E0152]: duplicate lang item |
多個crate定義相同lang item | 檢查是否同時引用了std和no_std的panic handler |
region FLASH overflowed |
韌體超出Flash容量 | 開啟LTO、減小codegen-units為1、使用opt-level=s |
region RAM overflowed |
RAM不足 | 減小堆疊/堆大小、使用靜態分配替代堆分配 |
HardFault at 0x08001234 |
空指標解參考或堆疊溢位 | 使用defmt列印故障暫存器、檢查堆疊大小 |
panic at src/main.rs:42 |
執行時panic | 使用panic-probe替代panic-halt取得呼叫堆疊 |
error: cannot find -lprobe_rs |
probe-rs未安裝 | cargo install probe-rs-tools |
OpenOCD connection failed |
除錯器連接失敗 | 檢查ST-Link驅動、USB線、openocd.cfg配置 |
error[E0277]: the trait bound is not satisfied |
HAL trait未實作 | 檢查目標MCU是否被HAL crate支援 |
進階最佳化
1. 使用defmt替代傳統日誌
// defmt在release模式下零開銷
// 編譯期格式化,不佔用執行時資源
defmt::info!("Sensor value: {}", value);
defmt::debug!("Buffer: {[u8; 4]}", &buf[..4]);
defmt::error!("HardFault HFSR={:08x}", hfsr);
2. 使用cortex_m::singleton!管理靜態資源
// 編譯期保證單例,避免執行時檢查
cortex_m::singleton!(
static SHARED_STATE: SharedState = SharedState::new();
);
3. 使用RTIC實現零成本中斷驅動架構
#[rtic::app(device = stm32f4xx_hal::pac)]
mod app {
use stm32f4xx_hal::prelude::*;
#[shared]
struct Shared {
sensor_value: u16,
}
#[local]
struct Local {
led: PA5<Output<PushPull>>,
}
#[init]
fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) {
let mut led = cx.device.GPIOA.split().pa5.into_push_pull_output();
led.set_high();
(
Shared { sensor_value: 0 },
Local { led },
init::Monotonics(),
)
}
#[task(binds = TIM2, shared = [sensor_value], local = [led])]
fn timer_interrupt(cx: timer_interrupt::Context) {
let sensor_value = cx.shared.sensor_value;
let led = cx.local.led;
*sensor_value.lock(|v| *v = read_adc());
led.toggle();
}
}
4. 使用embassy實現非同步並行
#[embassy_executor::task]
async fn task_a() {
loop {
do_something().await;
Timer::after(Duration::from_millis(100)).await;
}
}
#[embassy_executor::task]
async fn task_b() {
loop {
another_thing().await;
Timer::after(Duration::from_secs(1)).await;
}
}
// 每個任務僅需數十位元組的堆疊(協程狀態機)
5. Flash最佳化策略
[profile.release]
opt-level = "z" # 最佳化體積而非速度
lto = true # 連結時最佳化,消除未使用程式碼
codegen-units = 1 # 單編譯單元,最佳最佳化
strip = true # 去除除錯符號
panic = "abort" # abort替代unwind,減小體積
# 典型最佳化效果:
# 最佳化前:128KB
# 最佳化後:32KB(減少75%)
對比分析
| 特性 | Embassy | RTIC | FreeRTOS+Rust | 裸機輪詢 |
|---|---|---|---|---|
| 記憶體開銷 | 極低(協程) | 極低 | 高(每任務獨立堆疊) | 最低 |
| 學習曲線 | 中等 | 中等 | 高 | 低 |
| 並行模型 | async/await | 中斷優先級 | 搶佔式多任務 | 無 |
| 生態成熟度 | ★★★★ | ★★★ | ★★ | ★★★★★ |
| 除錯便利性 | ★★★★ | ★★★★ | ★★★ | ★★★★★ |
| 即時性保證 | 軟即時 | 硬即時 | 硬即時 | 取決於實作 |
| Rust原生 | ✅ | ✅ | ❌(C互操作) | ✅ |
| 適用場景 | IoT/感測器 | 馬達控制 | 複雜多任務 | 簡單控制 |
總結
Rust嵌入式開發正在從「嘗鮮」走向「生產可用」。2026年的今天,no_std生態已經足夠成熟:
- 環境搭建:probe-rs統一了燒錄和除錯體驗,
cargo embed一鍵完成 - 裸機啟動:cortex-m-rt封裝了啟動細節,但理解底層原理仍是排障關鍵
- 週邊驅動:PAC→HAL→應用的三層架構,兼顧安全與效率
- 中斷處理:原子操作、Mutex、RTIC三種方案覆蓋不同複雜度需求
- RTOS整合:Embassy作為Rust原生方案正在成為首選,FreeRTOS適合存量專案遷移
選擇建議:新專案優先考慮Embassy;對即時性要求極高的場景選RTIC;已有FreeRTOS程式碼庫的用freertos-rust漸進遷移。
線上工具推薦
- /zh-TW/json/format - JSON格式化工具,除錯串列通訊協定資料必備
- /zh-TW/dev/curl-to-code - HTTP請求轉程式碼,IoT裝置API除錯利器
- /zh-TW/encode/hash - 雜湊計算工具,韌體校驗和產生
- /zh-TW/text/diff - 文字對比工具,對比不同版本暫存器配置
本站提供瀏覽器本地工具,免註冊即可試用 →
#Rust no-std#嵌入式开发#裸机固件#ARM Cortex#嵌入式Rust#2026#编程语言