rust嵌入式开发

这篇具有很好参考价值的文章主要介绍了rust嵌入式开发。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

最近终于打通了rust嵌入式,值得庆贺!在折腾的过程中发现相关的资料不说少,但合用的太少,所以做个总结,希望能帮到有需要的兄弟。

在这个回答中我说了一下为什么想要启用rust嵌入式,不过当时还是有点低估了rust本身的门槛:(

环境

开发环境很简单:vscode+插件Cortex-Debug,但我实在没精力折腾怎么在vscode中进行debug,就是写完代码直接命令行编译。

相关的工具链请参考:安装工具链。我最后使用的芯片是STM32F103C8T6,所以需要安装

rustup target add thumbv7m-none-eabi

芯片刷新使用JTAG的ARM仿真器,淘宝多的是,选个买得人多的就行。刷程序我用的是JFlash,到官网下载安装后直接运行即可。

之前用rtt的时候,debug都已经被集成到IDE中了,而rust必须还得自己折腾,可折腾半天最后发现个问题:STM32F103C8只有64K的flash,刚写了几个功能debug版本就已经70几K的,只能用release版:

cargo build --release

所以干脆就不debug了,反正现在功能还比较简单,出问题了猜都能猜出是哪挂了:) 后面打通了uart串口的收发,直接看串口输出就是了。

这里需要说明的就是,本来打算用GD32的,但相关的库太少,支持的芯片少不说,而且功能不全,最后还是先选了STM32的芯片。

配置

这个都是比较标准的,主要是做个集中记录,避免以后的新项目少折腾。

.cargo/config.toml

主要是设置交叉编译的目标:

[build]
target = "thumbv7m-none-eabi"
memory.x
/* Linker script for the STM32F103C8T6 */
MEMORY
{
  FLASH : ORIGIN = 0x08000000, LENGTH = 64K
  RAM : ORIGIN = 0x20000000, LENGTH = 20K
}

根据自己选的芯片设置flash和内存大小即可。

Cargo.toml

主要是配置依赖。我现在用到的是:

[dependencies]
embedded-hal = "0.2.7"
nb = "1"
cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] }
cortex-m-rt = "0.7.1"
embedded-alloc = "0.5.1"
panic-halt = "0.2.0"
fugit = "0.3.6"
cortex-m-rtic = "1.1.4"

[dependencies.stm32f1xx-hal]
version = "0.10.0"
features = ["rt", "stm32f103", "medium"]

整个项目的框架我使用了rtic,就是上面依赖中的【cortex-m-rtic】,主要是用它来处理中断,说实话,就我目前rust的水平,其实不用rtic直接写更好理解,但多个中断、时间任务之间的协调实在来不及折腾了。

rtic其实比较简单,麻烦的是需要对rust和硬件的理解足够【当然,rtic能支持多个硬件体系和平台,它的价值在这里,但这一点对我反而价值不大】,需要搞清楚哪些需要自己做,哪些rtic帮我们做了,这很头疼。

大家看看上面rtic的官方指南,自己描个架子,我下面主要说一下自己的处理。

内存管理

rust嵌入式用的是core,所以std中的一些东东用不了,但好在基本的core中都有,包括vec、字符串等,但大家需要看一下rust的说明,官方已经指出:想在no_std环境中使用vec等,必须启用alloc。

所以,我们第一步就是做好相应的内存管理:

1、配置embedded-alloc依赖

有些例子给的是cortex-m-alloc,但这个crate自己都已经说自己挂了,请使用embedded-alloc

2、引用

extern crate alloc;
//引用之后,vec啥的就可以引用到了
use alloc::vec;
use alloc::collections::BTreeMap;
use alloc::string::String;

3、创建堆

//我分配了8k的堆空间
const HEAP_SIZE: usize = 8192;
use embedded_alloc::Heap;
#[global_allocator]
static HEAP: Heap = Heap::empty();

4、初始化堆

在入口的第一条指令就执行堆的初始化工作:

use core::mem::MaybeUninit;
static mut HEAP_MEM: [MaybeUninit<u8>; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE];
unsafe { HEAP.init(HEAP_MEM.as_ptr() as usize, HEAP_SIZE) }

入口:如果使用了rtic,就是init函数;否则就是main函数。

时钟

嵌入式编程很多时候我们得自己设时钟:

let mut flash = cx.device.FLASH.constrain();
let rcc = cx.device.RCC.constrain();
let clocks = rcc.cfgr.adcclk(2.MHz()).freeze(&mut flash.acr);

我因为要用到adc,所以这里设了adcclk。

大家在rtic官方的例子中经常看到:

#[monotonic(binds = TIM2, default = true)]
type MicrosecMono = MonoTimerUs<pac::TIM2>;

因为STM32F103只有TIM1,我又要用时钟中断,所以我就去掉了,初始化时只使用:init::Monotonics()。

串口

我用的是USART1,gpio管脚是pa9/pa10,没有使用DMA【被rust折腾惨了,暂时还搞不定】就是中断收发。

let mut afio = cx.device.AFIO.constrain();
let mut gpioa = cx.device.GPIOA.split();
let uart1_pin_tx = gpioa.pa9.into_alternate_push_pull(&mut gpioa.crh);
let uart1_pin_rx = gpioa.pa10;
let uart1 = Serial::new(
    cx.device.USART1,
    (uart1_pin_tx, uart1_pin_rx),
    &mut afio.mapr,
    serial::Config::default()
        //115200,8N1
        .baudrate(115200.bps())
        .stopbits(serial::StopBits::STOP1)
        .wordlength_8bits()
        .parity_none(),
    &clocks,
);
let (mut tx, mut rx) = uart1.split();
//监听数据中断
rx.listen();
//监听空闲中断
rx.listen_idle();
发送

我需要将数据打包后发送,所以我的串口发送程序是这样的:

pub fn sent_collect_uart(tx: &mut serial::Tx<USART1>, c:&mut Collect) {
	let ps: Vec<u8> = c.into_parket();
	let arr: &[u8] = &ps[..];
	tx.bwrite_all(arr).unwrap();
}

即将自己写的数据集先打包成一个u8的缓冲区,然后将这个缓冲区转换成一个u8数组,然后用tx的bwrite_all函数发送即可。

注意:不要flush,会挂

由于串口发送在很多地方都会用到,所以我把tx放到了Shared中:

#[shared]
struct Shared {
	tx_uart1: Tx<USART1>,
	live_random: u32,
}

这样一来,在rtic的任务中就必须以加锁的方式才能使用tx,这就可以保证串口发送是互斥的:

cx.shared.tx_uart1.lock(|tx_uart1| {
	sent_collect_uart(tx_uart1, &mut c);
});
接收

串口的接收需要中断,但硬件中断是不应该嵌套的,所以接收完数据的应用处理应该从中断中分离出来。我们在配置并初始化串口后,调用了两个listen函数,这就是分别监听了数据中断和空闲中断

  • 数据中断:串口接收到了数据
  • 空闲中断:串口超过9bit时间未收到数据信号

组合这两个中断,我们就可以成帧接收数据了。

前面说过,rtic的麻烦是麻烦在需要搞清楚哪些需要我们做,哪些rtic会帮我们做。rtic的例子非常少,文档说的也不是很透彻,需要在新功能上反复尝试才行,好郁闷的:(

串口的数据中断和空闲中断都是绑在USART1号上的,我直接贴代码,然后加注释了:

//用户区的接收处理
fn uart1_recv(buff: vec::Vec<u8>) {
	......
}

//串口的接收缓存区,一次最多只能接受1024个字节
const BUFFER_LEN: usize = 1024;
static mut BUFFER: &mut [u8; BUFFER_LEN] = &mut [0; BUFFER_LEN];
static mut WIDX: usize = 0;

//串口1的中断处理函数
#[task(binds = USART1, priority = 3, local = [rx_uart1], shared = [tx_uart1])]
fn uart1(mut cx: uart1::Context) {
	//指示本次中断是否可以处理接收到的新数据
	let mut b: bool = false;
	//如果有新数据,则将其拷贝到buff中再提供给用户处理程序
	let mut buff: vec::Vec<u8> = vec![];
	//接收端口,不这么做会报move方面的错误,快被折腾疯了:(
	let rx = cx.local.rx_uart1;
	//接收的时候不允许发送,发送的时候也不会接收,这就是强制将USART变成了单工
	cx.shared.tx_uart1.lock(|tx_uart1| {
		if rx.is_rx_not_empty() {
			//是数据中断,则接收到的数据放到接收缓存
			if let Ok(w) = nb::block!(rx.read()) {
				unsafe {
					BUFFER[WIDX] = w;
					WIDX += 1;
					if WIDX >= BUFFER_LEN - 1 {
						//超出的数据丢弃
						//WIDX = 0;
					}
				}
			}
			//可以等待空闲中断了
			rx.listen_idle();
		} else if rx.is_idle() {
			//空闲中断,数据接收完毕
			b = true;
			unsafe {
				//将接收到的数据从接收缓存copy到用户空间,以避免被新数据覆盖
				buff = vec![0; WIDX];
				let to = buff.as_mut_ptr();
				let from = BUFFER.as_mut_ptr();
				ptr::copy(from, to, WIDX);
				WIDX = 0;
			}
			//除非接收到新的数据,否则不等待空闲中断
			rx.unlisten_idle();
		}
	});
	if b {
		//这里应该将其放入空闲任务队列,异步执行以尽快结束中断处理
		//但我现在还没做到这一步,rust太折腾了:(
		uart1_recv(buff);
	}
}

时钟中断

stm32只有TIM1,我设了10ms的tick【哈哈,从rtt学到的】:

let mut timer = cx.device.TIM1.counter_ms(&clocks);
timer.start(TIMER_TICK.millis()).unwrap();
timer.listen(Event::Update);

啊,对了,尽可能的用cx.device而不要再自己引用了,现在还搞不清楚为什么,问题太多了,反正就尽量先这么用吧:(

时钟中断的处理函数:

#[task(binds = TIM1_UP, priority = 5, local = [timer])]
fn tick(cx: tick::Context) {
	unsafe {
		//借鉴rtt,每个时钟中断tick加1
		if sys_tick == u32::MAX {
			sys_tick = 1;
		}else{
			sys_tick += 1;
		}
	}
	//可以继续时钟中断
	cx.local.timer.clear_interrupt(Event::Update);
	
	//用户任务
	//和串口中断一样,应该放到空闲任务队列中异步调度执行
	mytask::spawn().unwrap();
	
	//用板载led做个呼吸灯,直观表示还活着
	live::spawn().unwrap();
}

注意:绑定的中断号是TIM1_UP

我把时钟中断的优先级设成了5。

数据打包

说实话,在c中这根本就不应该值得多写一个字!可在rust中,我写完都得骄傲的跳三跳!!简直是被rust折腾的死去活来的:(

取到一块buff
fn get_buff(len:usize) -> vec::Vec<u8>{
	let vec:vec::Vec<u8> = vec![0; len];
	vec
}
向buff中写入数据

基本函数

fn write_buff(from:*const u8, to:*mut u8, len:usize) -> *mut u8 {
	unsafe {
		ptr::copy(from, to, len);
		to.add(len)
	}
}

有了基本函数,就可以写各种类型的数据了:

//写u16:
fn write_buff_short(to:*mut u8, v:u16) -> *mut u8 {
	let from = &v as *const u16;
	write_buff(from as *const u8, to, 2)
}
//写字节:
fn write_buff_byte(to:*mut u8, v:u8) -> *mut u8 {
	let from = &v as *const u8;
	write_buff(from, to, 1)
}
//写u32:
fn write_buff_int(to:*mut u8, v:u32) -> *mut u8 {
	let from = &v as *const u32;
	write_buff(from as *const u8, to, 4)
}
//写f32:
fn write_buff_float(to:*mut u8, v:f32) -> *mut u8 {
	let from = &v as *const f32;
	write_buff(from as *const u8, to, 4)
}
//写字符串:
fn write_buff_str(to:*mut u8, v:&str) -> *mut u8 {
	let from = v.as_ptr();
	write_buff(from, to, v.len())
}

真被rust的借用、生命周期给折腾的欲死欲仙的:(

打包

我用伪码写一下:

impl <'a> Collect<'a> {
	......其它代码
	//给自己的数据结构打包
	pub fn into_parket(&mut self) -> vec::Vec<u8>{
		//获取缓冲区
		let mut buff = get_buff(self.tl as usize);
		//得到缓冲区的基址
		let base = buff.as_mut_ptr();
		//每写入一个数据,prt就会指向下一个待写入的地址
		let mut ptr: *mut u8 = base;
		unsafe {
			//打入包头
			ptr = write_buff_byte(ptr, b'D');
			//移动指针
			ptr = base.add(OFFSET_DATA_LEN as usize);
			write_buff_short(ptr, self.tl);
			......其它代码
			//开始打入数据
			ptr = base.add(OFFSET_BODY as usize);
			let pbody = ptr;
			......其它代码
			//crc
			ptr = base.add(OFFSET_CRC as usize);
			//crc是数据区的校验和,所以要从包身开始,计算总数去掉包头的长度
			let crc = crc_xor(pbody, (self.tl - 12) as u32);
			write_buff_byte(ptr, crc);
		}
		buff
	}
}

需要注意:虽然写数据是用unsafe封了可以强制读写,但如果自己没算清楚,buff的读写超范围了,出了unsafe就会挂!所以,在打包完成后,调试期间应该立刻写一个print语句,如果没看到相应的提示,那自然就说明指针移动的时候算错了。

杂项

其它adc和gpio,包括业务处理都很简单,各种例子都可以参考,不复赘述。

dispatchers

我现在还没搞懂,rtic的app为什么需要一个dispatchers,还必须是和自己用到的中断都不一样的一个硬件中断源,我就用了官方例子中的SPI1:

#[rtic::app(device = stm32f1xx_hal::pac, dispatchers = [SPI1])]
mod app {
代码布局

这个也很折腾,rtic::app它本身是一个宏!所以我们写的代码,并不是编译器阅读到的最终代码,所以我最终就没用官方例子的:

use ......
mod app {
    use super::*;

即和其它rust程序一样,在代码的开头就引用需要的crate。我是:

#![no_std]
#![no_main]

use panic_halt as _;
extern crate alloc;

mod 自己写的模块;

#[rtic::app(device = stm32f1xx_hal::pac, dispatchers = [SPI1])]
mod app {
	use ......

包括静态数据的定义都是放到app模块里面,这样就不会有问题。

idle

这个很简单:

#[idle]
fn idle(_cx: idle::Context) -> ! {
	loop {
		cortex_m::asm::wfi();
		//官方例子中的也可以
		//rtic::export::wfi()
	}
}

即便不用idle也没问题,但一呢,根据官方的说法,用了idle会比较节省【当然,我们做了一个10ms的时钟】;二呢,就是可以在idle中做我们的用户任务管理。文章来源地址https://www.toymoban.com/news/detail-823444.html

到了这里,关于rust嵌入式开发的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 嵌入式毕设分享 stm32人脸识别快递柜系统(源码+硬件+论文)

    🔥 这两年开始毕业设计和毕业答辩的要求和难度不断提升,传统的毕设题目缺少创新和亮点,往往达不到毕业答辩的要求,这两年不断有学弟学妹告诉学长自己做的项目系统达不到老师的要求。 为了大家能够顺利以及最少的精力通过毕设,学长分享优质毕业设计项目,今天

    2024年04月10日
    浏览(45)
  • 嵌入式项目分享 stm32智能运动计步系统 - 物联网 嵌入式 单片机

    🔥 这两年开始毕业设计和毕业答辩的要求和难度不断提升,传统的毕设题目缺少创新和亮点,往往达不到毕业答辩的要求,这两年不断有学弟学妹告诉学长自己做的项目系统达不到老师的要求。 为了大家能够顺利以及最少的精力通过毕设,学长分享优质毕业设计项目,今天

    2024年02月20日
    浏览(49)
  • 嵌入式毕设分享 基于单片机的智能音响设计与实现 -物联网 嵌入式 stm32

    🔥 这两年开始毕业设计和毕业答辩的要求和难度不断提升,传统的毕设题目缺少创新和亮点,往往达不到毕业答辩的要求,这两年不断有学弟学妹告诉学长自己做的项目系统达不到老师的要求。 为了大家能够顺利以及最少的精力通过毕设,学长分享优质毕业设计项目,今天

    2024年02月22日
    浏览(38)
  • stm32毕设分享 stm32智能运动计步系统 - 物联网 嵌入式 单片机

    🔥 这两年开始毕业设计和毕业答辩的要求和难度不断提升,传统的毕设题目缺少创新和亮点,往往达不到毕业答辩的要求,这两年不断有学弟学妹告诉学长自己做的项目系统达不到老师的要求。 为了大家能够顺利以及最少的精力通过毕设,学长分享优质毕业设计项目,今天

    2024年02月20日
    浏览(45)
  • STM32开发环境搭建&工程创建(嵌入式学习)

    简介 STM32CubeMX是STMicroelectronics公司提供的一款集成开发环境(IDE)工具,用于快速配置和初始化STM32微控制器系列的软件工程。它提供了图形化界面和交互式工具,使开发者能够轻松地生成STM32微控制器的初始化代码和配置文件。 STM32CubeMX具有以下主要功能和特点: 微控制器

    2024年02月11日
    浏览(44)
  • 嵌入式项目分享 Stm32 WIFI智能家居温湿度和烟雾检测系统 - 单片机 物联网 嵌入式

    🔥 这两年开始毕业设计和毕业答辩的要求和难度不断提升,传统的毕设题目缺少创新和亮点,往往达不到毕业答辩的要求,这两年不断有学弟学妹告诉学长自己做的项目系统达不到老师的要求。 为了大家能够顺利以及最少的精力通过毕设,学长分享优质毕业设计项目,今天

    2024年01月16日
    浏览(44)
  • 毕设开源 基于stm32的智能平衡小车 - 单片机 物联网嵌入式

    文章目录 0 前言 1 项目背景 2 设计思路 3 硬件设计 4 软件设计 4.2 直立控制程序设计 4.3 速度控制程序设计 4.4 方向控制程序设计 4.5 关键代码 5 最后 🔥 这两年开始毕业设计和毕业答辩的要求和难度不断提升,传统的毕设题目缺少创新和亮点,往往达不到毕业答辩的要求,这

    2024年02月22日
    浏览(42)
  • 嵌入式物联网STM32实战开发代码例程库

    几百个例程清单,CSDN下载地址: https://download.csdn.net/download/weixin_39804904/88671879 开发例程清单(持续新增中,欢迎点赞关注): 0001基于STM32F103单片机GPIO实现控制LED灯闪烁的程序代码0001.rar 0002基于STM32F103单片机GPIO实现按键KEY的检测程序代码0002.rar 0003基于STM32F103单片机GPIO实现

    2024年02月02日
    浏览(30)
  • STM32毕设分享 - 基于单片机的智能鱼缸系统设计与实现 - 嵌入式 物联网 stm32 51单片机 智能鱼缸

    Hi,大家好,今天向大家介绍一个 单片机项目, 大家可用于 课程设计 或 毕业设计 基于单片机的智能鱼缸系统设计与实现 🔥 项目分享与指导: https://gitee.com/sinonfin/sharing 近年以来,随着我国综合实力飞速飙升,人们对物质和精神生活质量的要求也不断提升,各式各样的智能

    2024年04月11日
    浏览(38)
  • 单片机毕业设计 stm32智能温控风扇设计与实现 - 嵌入式 物联网

    Hi,大家好,学长今天向大家介绍一个 单片机项目 基于stm32的智能温控风扇设计与实现 大家可用于 课程设计 或 毕业设计 随着科技的日新月异,智能家居逐渐走入普通家庭,风扇作为基本的家用电器也将成为智能家居的一部分。这里介绍的是以STM32单片机为控制单元并结合嵌

    2024年02月09日
    浏览(68)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包