怎么在极小硬件中运用go语言-mile米乐体育

怎么在极小硬件中运用go语言

这篇文章主要介绍“怎么在极小硬件中运用go语言”,在日常操作中,相信很多人在怎么在极小硬件中运用go语言问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”怎么在极小硬件中运用go语言”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

硬件部分

stm32f030f4p6 给人留下了很深的印象:

  • cpu: cortex m0 48 mhz(最低配置,只有 12000 个逻辑门电路)

  • ram: 4 kb,

  • flash: 16 kb,

  • adc、spi、i2c、usart 和几个定时器

以上这些采用了 tssop20 封装。正如你所见,这是一个很小的 32 位系统。

软件部分

如果你想知道如何在这块开发板上使用 go 编程,你需要反复阅读硬件规范手册。你必须面对这样的真实情况:在 go 编译器中给 cortex-m0 提供支持的可能性很小。而且,这还仅仅只是第一个要解决的问题。

我会使用 emgo,但别担心,之后你会看到,它如何让 go 在如此小的系统上尽可能发挥作用。

在我拿到这块开发板之前,对 stm32/hal 系列下的 f0 mcu 没有任何支持。在简单研究参考手册后,我发现 stm32f0 系列是 stm32f3 削减版,这让在新端口上开发的工作变得容易了一些。

如果你想接着本文的步骤做下去,需要先安装 emgo

cd$homegitclonehttps://github.com/ziutek/emgo/cdemgo/egcgoinstall

然后设置一下环境变量

exportegcc=path_to_arm_gcc#eg./usr/local/arm/bin/arm-none-eabi-gccexportegld=path_to_arm_linker#eg./usr/local/arm/bin/arm-none-eabi-ldexportegar=path_to_arm_archiver#eg./usr/local/arm/bin/arm-none-eabi-arexportegroot=$home/emgo/egrootexportegpath=$home/emgo/egpathexportegarch=cortexm0exportegos=noosexportegtarget=f030x6

更详细的说明可以在 emgo 米乐app官网登录官网上找到。

要确保egc在你的path中。 你可以使用go build来代替go install,然后把egc复制到你的$home/bin/usr/local/bin中。

现在,为你的第一个 emgo 程序创建一个新文件夹,随后把示例中链接器脚本复制过来:

mkdir$home/firstemgocd$home/firstemgocp$egpath/src/stm32/examples/f030-demo-board/blinky/script.ld.

最基本程序

main.go文件中创建一个最基本的程序:

packagemainfuncmain(){}

文件编译没有出现任何问题:

$egc$arm-none-eabi-sizecortexm0.elftextdatabssdechexfilename745217210477281e30cortexm0.elf

第一次编译可能会花点时间。编译后产生的二进制占用了 7624 个字节的 flash 空间(文本 数据)。对于一个什么都没做的程序来说,占用的空间有些大。还剩下 8760 字节,可以用来做些有用的事。

不妨试试传统的 “hello, world!” 程序:

packagemainimport"fmt"funcmain(){fmt.println("hello,world!")}

不幸的是,这次结果有些糟糕:

$egc/usr/local/arm/bin/arm-none-eabi-ld:/home/michal/p/go/src/github.com/ziutek/emgo/egpath/src/stm32/examples/f030-demo-board/blog/cortexm0.elfsection`.text'willnotfitinregion`flash'/usr/local/arm/bin/arm-none-eabi-ld:region`flash'overflowedby10880bytesexitstatus1

“hello, world!” 需要 stm32f030x6 上至少 32kb 的 flash 空间。

fmt包强制包含整个strconvreflect包。这三个包,即使在精简版本中的 emgo 中,占用空间也很大。我们不能使用这个例子了。有很多的应用不需要好看的文本输出。通常,一个或多个 led,或者七段数码管显示就足够了。不过,在第二部分,我会尝试使用strconv包来格式化,并在 uart 上显示一些数字和文本。

闪烁

我们的开发板上有一个与 pa4 引脚和 vcc 相连的 led。这次我们的代码稍稍长了一些:

packagemainimport("delay""stm32/hal/gpio""stm32/hal/system""stm32/hal/system/timer/systick")varledgpio.pinfuncinit(){system.setuppll(8,1,48/8)systick.setup(2e6)gpio.a.enableclock(false)led=gpio.a.pin(4)cfg:=&gpio.config{mode:gpio.out,driver:gpio.opendrain}led.setup(cfg)}funcmain(){for{led.clear()delay.millisec(100)led.set()delay.millisec(900)}}

按照惯例,init函数用来初始化和配置外设。

system.setuppll(8, 1, 48/8)用来配置 rcc,将外部的 8 mhz 振荡器的 pll 作为系统时钟源。pll 分频器设置为 1,倍频数设置为 48/8 =6,这样系统时钟频率为 48mhz。

systick.setup(2e6)将 cortex-m systick 时钟作为系统时钟,每隔 2e6 次纳秒运行一次(每秒钟 500 次)。

gpio.a.enableclock(false)开启了 gpio a 口的时钟。false意味着这一时钟在低功耗模式下会被禁用,但在 stm32f0 系列中并未实现这一功能。

led.setup(cfg)设置 pa4 引脚为开漏输出。

led.clear()将 pa4 引脚设为低,在开漏设置中,打开 led。

led.set()将 pa4 设为高电平状态,关掉led。

编译这个代码:

$egc$arm-none-eabi-sizecortexm0.elftextdatabssdechexfilename9772172168101122780cortexm0.elf

正如你所看到的,这个闪烁程序占用了 2320 字节,比最基本程序占用空间要大。还有 6440 字节的剩余空间。

看看代码是否能运行:

$openocd-d0-finterface/stlink.cfg-ftarget/stm32f0x.cfg-c'init;programcortexm0.elf;resetrun;exit'openon-chipdebugger0.10.0 dev-00319-g8f1f912a(2018-03-07-19:20)licensedundergnugplv2forbugreports,readhttp://openocd.org/doc/doxygen/bugs.htmldebug_level:0adapterspeed:1000khzadapter_nsrst_delay:100noneseparateadapterspeed:950khztargethaltedduetodebug-request,currentmode:threadxpsr:0xc1000000pc:0x0800119cmsp:0x20000da0adapterspeed:4000khz**programmingstarted**autoeraseenabledtargethaltedduetobreakpoint,currentmode:threadxpsr:0x61000000pc:0x2000003amsp:0x20000da0wrote10240bytesfromfilecortexm0.elfin0.817425s(12.234kib/s)**programmingfinished**adapterspeed:950khz

更多的 go 语言编程

如果你不是一个 go 程序员,但你已经听说过一些关于 go 语言的事情,你可能会说:“go 语法很好,但跟 c 比起来,并没有明显的提升。让我看看 go 语言的通道和协程!”

接下来我会一一展示:

import("delay""stm32/hal/gpio""stm32/hal/system""stm32/hal/system/timer/systick")varled1,led2gpio.pinfuncinit(){system.setuppll(8,1,48/8)systick.setup(2e6)gpio.a.enableclock(false)led1=gpio.a.pin(4)led2=gpio.a.pin(5)cfg:=&gpio.config{mode:gpio.out,driver:gpio.opendrain}led1.setup(cfg)led2.setup(cfg)}funcblinky(ledgpio.pin,periodint){for{led.clear()delay.millisec(100)led.set()delay.millisec(period-100)}}funcmain(){goblinky(led1,500)blinky(led2,1000)}

代码改动很小: 添加了第二个 led,上一个例子中的main函数被重命名为blinky并且需要提供两个参数。main在新的协程中先调用blinky,所以两个 led 灯在并行使用。值得一提的是,gpio.pin可以同时访问同一 gpio 口的不同引脚。

emgo 还有很多不足。其中之一就是你需要提前规定goroutines(tasks)的最大执行数量。是时候修改script.ld了:

isrstack=1024;mainstack=1024;taskstack=1024;maxtasks=2;includestm32/f030x4includestm32/loadflashincludenoos-cortexm

栈的大小需要靠猜,现在还不用关心这一点。

$egc$arm-none-eabi-sizecortexm0.elftextdatabssdechexfilename1002017217210364287ccortexm0.elf

另一个 led 和协程一共占用了 248 字节的 flash 空间。

通道

通道是 go 语言中协程之间相互通信的一种推荐方式。emgo 甚至能允许通过中断处理来使用缓冲通道。下一个例子就展示了这种情况。

packagemainimport("delay""rtos""stm32/hal/gpio""stm32/hal/irq""stm32/hal/system""stm32/hal/system/timer/systick""stm32/hal/tim")var(leds[3]gpio.pintimer*tim.periphch=make(chanint,1))funcinit(){system.setuppll(8,1,48/8)systick.setup(2e6)gpio.a.enableclock(false)leds[0]=gpio.a.pin(4)leds[1]=gpio.a.pin(5)leds[2]=gpio.a.pin(9)cfg:=&gpio.config{mode:gpio.out,driver:gpio.opendrain}for_,led:=rangeleds{led.set()led.setup(cfg)}timer=tim.tim3pclk:=timer.bus().clock()ifpclk

与之前例子相比较下的不同:

  1. 鸿蒙官方战略合作共建——harmonyos技术社区

  2. 添加了第三个 led,并连接到 pa9 引脚(uart 头的 txd 引脚)。

  3. 时钟(tim3)作为中断源。

  4. 新函数timerisr用来处理irq.tim3的中断。

  5. 新增容量为 1 的缓冲通道是为了timerisrblinky协程之间的通信。

  6. isrs数组作为中断向量表,是更大的异常向量表的一部分。

  7. blinky中的for语句被替换成range语句。

为了方便起见,所有的 led,或者说它们的引脚,都被放在leds这个数组里。另外,所有引脚在被配置为输出之前,都设置为一种已知的初始状态(高电平状态)。

在这个例子里,我们想让时钟以 1 khz 的频率运行。为了配置 tim3 预分频器,我们需要知道它的输入时钟频率。通过参考手册我们知道,输入时钟频率在apbclk = ahbclk时,与apbclk相同,反之等于 2 倍的apbclk

如果 cnt 寄存器增加 1 khz,那么 arr 寄存器的值等于更新事件(重载事件)在毫秒中的计数周期。 为了让更新事件产生中断,必须要设置 dier 寄存器中的 uie 位。cen 位能启动时钟。

时钟外设在低功耗模式下必须启用,为了自身能在 cpu 处于休眠时保持运行:timer.enableclock(true)。这在 stm32f0 中无关紧要,但对代码可移植性却十分重要。

timerisr函数处理irq.tim3的中断请求。timer.sr.store(0)会清除 sr 寄存器里的所有事件标志,无效化向 nvic 发出的所有中断请求。凭借经验,由于中断请求无效的延时性,需要在程序一开始马上清除所有的中断标志。这避免了无意间再次调用处理。为了确保万无一失,需要先清除标志,再读取,但是在我们的例子中,清除标志就已经足够了。

下面的这几行代码:

select{casech<-0://successdefault:leds[0].clear()}

是 go 语言中,如何在通道上非阻塞地发送消息的方法。中断处理程序无法一直等待通道中的空余空间。如果通道已满,则执行default,开发板上的led就会开启,直到下一次中断。

isrs数组包含了中断向量表。//c:__attribute__((section(".isrs")))会导致链接器将数组插入到.isrs节中。

blinkyfor循环的新写法:

forrangech{led.clear()delay.millisec(100)led.set()delay.millisec(period-100)}

等价于:

for{_,ok:=<-chif!ok{break//channelclosed.}led.clear()delay.millisec(100)led.set()delay.millisec(period-100)}

注意,在这个例子中,我们不在意通道中收到的值,我们只对其接受到的消息感兴趣。我们可以在声明时,将通道元素类型中的int用空结构体struct{}来代替,发送消息时,用struct{}{}结构体的值代替 0,但这部分对新手来说可能会有些陌生。

让我们来编译一下代码:

$egc$arm-none-eabi-sizecortexm0.elftextdatabssdechexfilename11096228188115122cf8cortexm0.elf

新的例子占用了 11324 字节的 flash 空间,比上一个例子多占用了 1132 字节。

采用现在的时序,两个闪烁协程从通道中获取数据的速度,比timerisr发送数据的速度要快。所以它们在同时等待新数据,你还能观察到select的随机性,这也是 go 规范所要求的。

stm32f030f4p6

开发板上的 led 一直没有亮起,说明通道从未出现过溢出。

我们可以加快消息发送的速度,将timer.arr.store(700)改为timer.arr.store(200)。 现在timerisr每秒钟发送 5 条消息,但是两个接收者加起来,每秒也只能接受 4 条消息。

到此,关于“怎么在极小硬件中运用go语言”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注恰卡编程网网站,小编会继续努力为大家带来更多实用的文章!

展开全文
内容来源于互联网和用户投稿,文章中一旦含有米乐app官网登录的联系方式务必识别真假,本站仅做信息展示不承担任何相关责任,如有侵权或涉及法律问题请联系米乐app官网登录删除

最新文章

网站地图