一、项目介绍
本项目实现了一个基于树莓派RP2040的温度检测器。通过一个核心板外接其他模块,使用IIC协议的oled和单总线的DS18B20,以及蓝牙模块HC-05,再结合核心板上的WS2812B灯组,从而实现一个简单的温度检测系统。
二、项目设计思路(含设计框图)
(在介绍之前,我原先是想参照本次寒假硬禾的树莓派pico,设计一个核心板和一个扩展板,用WS2812B做一个类似时钟那样的项目,但是因为调试和设计等问题,导致了扩展板报废了报废情况后面会说,突然想到我还有四个灯,于是退而求其次,做了这么一个小系统。)
硬件部分:
- 单片机选用规定的RP2040芯片,56引脚且QFN封装(焊接使用的锡膏,涂在板子上,固定好芯片,热风枪吹化,烙铁拖一下即可)
- 参考了官方的手册,且为了符合核心板需求,没有拉出全部的引脚,如GP24等完全没有进行处理
- 基于核心板添加了一些东西,如reset按键,同时添加了四个ws2812b的灯组
- 屏幕部分选择了外接一个IIC协议的OLED模块,以及一个ds18b20的单总线温度传感器。
- 外加了一个HC05模块,可以把数据实时显示在手机上。
- 核心板实物测试过程比较简单,拿到板子后,从芯片开始焊接,可以拿手机开微距看引脚情况,然后优先芯片等器件,再焊接其他的阻容元件,最后连接typec到电脑看看有没有u盘弹出来就行了,然后去官网下载uf2固件,给他拖到u盘里面,基本上就配置成功。
软件部分:
在项目中,由于功能较为简单,使用的资源并不多,毕竟onewire方式的除了供电接地,只需要一个IO口就可以实现数据传输,然后oled的显示。因为是IIC协议,所以只需要一个SCK和一个SDA就足够了,hc05也比较简单,配置一个新的串口用于传输数据就行了,还有就是ws2812b核心板自带,总结一下,大致思路及操作如下。
- 环境配置:先声明,本人平时用过stm32、ch32、esp32等c语言编程的单片机,以及有过一定的fpga经历,学过python,但是完全没使用过micropython,虽然我知道这个是基于c改的,但是我还是想尝试一下这种对新手比较友好的;首先在官网下载thonny,(配置过程网上随便一大把,很简单,不展示了)
- 准备:首先要导入库,因为github上等很多地方,都有现成的库,所以要先导入两个库,一个是SSD1306的库,用于驱动oled显示的,还有一个是ds18b20的单总线onewire和ds18x20,其中后两个在插件里面就可以直接配置,ssd1306只需要网上下载一下,导入树莓派就可以了。
- 运行流程:首先由DS18B20获取采样的温度,并且将这个温度存储在一个浮点型的变量中,随后对oled的显示操作,使其显示这个变量的值,同时串口向hc05发送温度的值,然后手机端接收到hc05接受到的温度,并且显示在手机上,然后此时对温度进行判断,得到判断结果,最终把结果输出在oled上,并且由ws2812显示出来。
- 单独部分代码解析和总体调试解析(见下面)。
- 补充一下,hc05蓝牙,我的配置是波特率115200,然后这个模块刚开始的时候一般要AT模式重置一下配置(如果你是新买的),如果是自己的,只要改代码的部分就行了,新的一般是长按按键,然后插电(已经连接好一个usbttl了,记得切换38400),接着照着下面的来自己去配置就行了,一般随意一个串口助手就行。
AT指令的波特率38400,命令后面记得加==回车== 发送AT,返回OK表示执行AT指令正常 AT+ROLE=0 //0表示主模式,1表示从模式,2表示回环模式 AT+NAME="BLUENAME" //配置蓝牙模块名称,也可以不打引号,返回OK AT+PSWD="1234" //设置蓝牙配对码 /*这里设置 自动连接工作模式的波特率,也就是通信的当蓝牙接收发送数据模式的波特率 我们现在是AT模式下波特率只能是38400,这个改不了*/ AT+UART=115200,0,0 //波特率115200,一个停止位,不进行校验 AT+RESET //重启模块,可以看到LED等以0.5s间隔闪烁,说明是进入了透传模式 AT+VERSION? //查看版本信息 AT+ORGL //恢复默认状态 AT+ADDR? //查看蓝牙地址 AT+PSWD? //查看配对码 AT+UART? //查看串口参数
- (SSD1306的py文件下载)记得上传到pico里面,不然会报错
main代码;
import machine, onewire, ds18x20, time,utime
from ssd1306 import SSD1306_I2C
from machine import Pin, I2C
from time import sleep
import array
import rp2
#这个部分是HC05的串口部分
uart = machine.UART(0, baudrate=115200, tx=machine.Pin(16), rx=machine.Pin(17))
print(uart)
#这个部分是ws2812b
led_count = 4 # 灯珠数量
PIN_NUM = 23 # 连接灯带的引脚
brightness = 1.0 # 亮度
@rp2.asm_pio(sideset_init=rp2.PIO.OUT_LOW, out_shiftdir=rp2.PIO.SHIFT_LEFT,
autopull=True, pull_thresh=24)
def ws2812():
T1 = 2
T2 = 5
T3 = 3
wrap_target()
label("bitloop")
out(x, 1).side(0)[T3 - 1]
jmp(not_x, "do_zero").side(1)[T1 - 1]
jmp("bitloop").side(1)[T2 - 1]
label("do_zero")
nop().side(0)[T2 - 1]
wrap()
state_machine = rp2.StateMachine(0, ws2812, freq=8_000_000, sideset_base=Pin(PIN_NUM))
state_machine.active(1)
pixel_array = array.array("I", [0 for _ in range(led_count)])
def update_pix(brightness_input=brightness):
dimmer_array = array.array("I", [0 for _ in range(led_count)])
for ii, cc in enumerate(pixel_array):
r = int(((cc >> 8) & 0xFF) * brightness_input)
g = int(((cc >> 16) & 0xFF) * brightness_input)
b = int((cc & 0xFF) * brightness_input)
dimmer_array[ii] = (g << 16) + (r << 8) + b
state_machine.put(dimmer_array, 8)
time.sleep_ms(10)
def set_24bit(ii, color):
pixel_array[ii] = (color[1] << 16) + (color[0] << 8) + color[2]
def green_judge():#温度区间判断
if TEMP>=35 and TEMP<100:
set_24bit(3, (255,0,0)) # 设置当前位置为红色
print('warningA')
oled.text("warning",0,20)
elif TEMP>=30 and TEMP<35:
set_24bit(2, (255,0,255)) # 设置当前位置为紫色
print('warningB')
oled.text("cautious",0,20)
elif TEMP>=25 and TEMP<30:
set_24bit(3, (0,255,0)) # 设置当前位置为绿色
print('warningC')
oled.text("normal",0,20)
else:
# set_24bit(3, (0, 0, 0)) # 其他位置关闭
print('CLOSE')
oled.text("CLOSE",0,20)
update_pix()
time.sleep(0.1) # 等待时间
print('WS2812B OVER')
#这个部分是ds18b20温度传感器配置
ds_pin=machine.Pin(4) #将传感器连接到GO4 这里为:4
ds_sensor = ds18x20.DS18X20(onewire.OneWire(ds_pin)) #创建onewire总线 引脚4(GO4)
roms = ds_sensor.scan() #扫描总线上的设备
print('DS18B20 Loading: ', roms)#加载
#这个部分是IIColed的配置
i2c = I2C(1, scl=Pin(7), sda=Pin(6), freq=200000)#Grove - OLED Display 0.96",六七号引脚
oled = SSD1306_I2C(128, 64, i2c)
print('IIC OVER')
while True:
ds_sensor.convert_temp() #获取采样温度
time.sleep_ms(750)#延时
for rom in roms:
print(ds_sensor.read_temp(rom)) #转换得到温度
TEMP = ds_sensor.read_temp(rom)
time.sleep(2)
oled.fill(0)#clear
oled.text("temp is:"+str(TEMP),0,0)
uart.write(str(TEMP))
set_24bit(3, (0, 0, 0)) # 其他位置关闭
set_24bit(2, (0, 0, 0)) # 其他位置关闭
set_24bit(1, (0, 0, 0)) # 其他位置关闭
set_24bit(0, (0, 0, 0)) # 其他位置关闭
green_judge()
oled.show()
#sleep(0.5)
SSD1306.py:
# MicroPython SSD1306 OLED driver, I2C and SPI interfaces
from micropython import const
import framebuf
# register definitions
SET_CONTRAST = const(0x81)
SET_ENTIRE_ON = const(0xA4)
SET_NORM_INV = const(0xA6)
SET_DISP = const(0xAE)
SET_MEM_ADDR = const(0x20)
SET_COL_ADDR = const(0x21)
SET_PAGE_ADDR = const(0x22)
SET_DISP_START_LINE = const(0x40)
SET_SEG_REMAP = const(0xA0)
SET_MUX_RATIO = const(0xA8)
SET_COM_OUT_DIR = const(0xC0)
SET_DISP_OFFSET = const(0xD3)
SET_COM_PIN_CFG = const(0xDA)
SET_DISP_CLK_DIV = const(0xD5)
SET_PRECHARGE = const(0xD9)
SET_VCOM_DESEL = const(0xDB)
SET_CHARGE_PUMP = const(0x8D)
# Subclassing FrameBuffer provides support for graphics primitives
# http://docs.micropython.org/en/latest/pyboard/library/framebuf.html
class SSD1306(framebuf.FrameBuffer):
def __init__(self, width, height, external_vcc):
self.width = width
self.height = height
self.external_vcc = external_vcc
self.pages = self.height // 8
self.buffer = bytearray(self.pages * self.width)
super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB)
self.init_display()
def init_display(self):
for cmd in (
SET_DISP | 0x00, # off
# address setting
SET_MEM_ADDR,
0x00, # horizontal
# resolution and layout
SET_DISP_START_LINE | 0x00,
SET_SEG_REMAP | 0x01, # column addr 127 mapped to SEG0
SET_MUX_RATIO,
self.height - 1,
SET_COM_OUT_DIR | 0x08, # scan from COM[N] to COM0
SET_DISP_OFFSET,
0x00,
SET_COM_PIN_CFG,
0x02 if self.width > 2 * self.height else 0x12,
# timing and driving scheme
SET_DISP_CLK_DIV,
0x80,
SET_PRECHARGE,
0x22 if self.external_vcc else 0xF1,
SET_VCOM_DESEL,
0x30, # 0.83*Vcc
# display
SET_CONTRAST,
0xFF, # maximum
SET_ENTIRE_ON, # output follows RAM contents
SET_NORM_INV, # not inverted
# charge pump
SET_CHARGE_PUMP,
0x10 if self.external_vcc else 0x14,
SET_DISP | 0x01,
): # on
self.write_cmd(cmd)
self.fill(0)
self.show()
def poweroff(self):
self.write_cmd(SET_DISP | 0x00)
def poweron(self):
self.write_cmd(SET_DISP | 0x01)
def contrast(self, contrast):
self.write_cmd(SET_CONTRAST)
self.write_cmd(contrast)
def invert(self, invert):
self.write_cmd(SET_NORM_INV | (invert & 1))
def show(self):
x0 = 0
x1 = self.width - 1
if self.width == 64:
# displays with width of 64 pixels are shifted by 32
x0 += 32
x1 += 32
self.write_cmd(SET_COL_ADDR)
self.write_cmd(x0)
self.write_cmd(x1)
self.write_cmd(SET_PAGE_ADDR)
self.write_cmd(0)
self.write_cmd(self.pages - 1)
self.write_data(self.buffer)
class SSD1306_I2C(SSD1306):
def __init__(self, width, height, i2c, addr=0x3C, external_vcc=False):
self.i2c = i2c
self.addr = addr
self.temp = bytearray(2)
self.write_list = [b"\x40", None] # Co=0, D/C#=1
super().__init__(width, height, external_vcc)
def write_cmd(self, cmd):
self.temp[0] = 0x80 # Co=1, D/C#=0
self.temp[1] = cmd
self.i2c.writeto(self.addr, self.temp)
def write_data(self, buf):
self.write_list[1] = buf
self.i2c.writevto(self.addr, self.write_list)
class SSD1306_SPI(SSD1306):
def __init__(self, width, height, spi, dc, res, cs, external_vcc=False):
self.rate = 10 * 1024 * 1024
dc.init(dc.OUT, value=0)
res.init(res.OUT, value=0)
cs.init(cs.OUT, value=1)
self.spi = spi
self.dc = dc
self.res = res
self.cs = cs
import time
self.res(1)
time.sleep_ms(1)
self.res(0)
time.sleep_ms(10)
self.res(1)
super().__init__(width, height, external_vcc)
def write_cmd(self, cmd):
self.spi.init(baudrate=self.rate, polarity=0, phase=0)
self.cs(1)
self.dc(0)
self.cs(0)
self.spi.write(bytearray([cmd]))
self.cs(1)
def write_data(self, buf):
self.spi.init(baudrate=self.rate, polarity=0, phase=0)
self.cs(1)
self.dc(1)
self.cs(0)
self.spi.write(buf)
self.cs(1)
三、搜集素材的思路
首先本人没用过树莓派,所以想着自己画一个核心板玩玩,又可以学习pcb设计,还可以不用特意花钱去买一个树莓派,但是看着官方的手册看着看着,想画w,又觉得那个wifi功能没必要,直接外接一个模块就ok了,然后突然想起来寒假的训练营,欸嘿,这个我比较中意,还带更多的灯,没有扩展板的时候也能玩玩(没玩过ws2812),所以就基于这个做了一些改动,因为我想加一下logo,又不想太拥挤,然后就把灯拉到了一边,重新做了一下布局。
四、画原理图、PCB制板过程中遇到的问题,以及解决方法
本人平时全做的软件编程,极少接触pcb设计,干过最多的也是用立创画一些小模块、基础核心板,还有一些ADDA电路,所以本次kicad所碰到的问题还是很多的(大部分都是不熟练导致),尤其是7.0版本兼容问题等等。
- 错误一,7.0新版本的问题KICAD要使用pwr——flag连接
- 错误二,总线问题。也是因为新版本,要给总线标注标签
- 错误三,更新自建库中的pico的GND时,双向改电源输入,然后因为sch原理图没更新pico符号,导致报错(记得更新)
- 错误四,原理图更新PCB的时候,有一些外部导入的库没有给引脚编号。
- 绘制PCB的问题,主要是DRC查出来的,有个花焊盘最小辐条数目,这个如果不是1的话,(或不对GND等软件检测到的地方进行处理,使其为2),会报错,要修改一下
- 第二个,第一次使用KICAD,并不知道嘉立创的机制,嘉立创的钻孔层对KICAD生成的文件有要求,普通的drill是不行的,要生成gerber文件才ok(打板的时候出了问题,是我无知了)。
- 所有的封装库和符号库都是自建的,有很多资料来源于SnapEDA(我的库大多从这下载)
五、实现结果展示(调试过程中遇到什么问题)
除了展示部分,然后我也会在这展示软硬件编程调试的问题。
总体图片+核心板正反面pcb+扩展板
展示部分:
- 温度大于25°且小于30°,实时显示温度,且显示在oled上,该温度下提醒“normal”,且显示绿灯,同时蓝牙手机调试器也可实时接收温度。
- 同1,变动为温度处于30-35时,温度提醒“cautious”,显示紫色灯
- 同1,变动为大于35°,温度提醒“warning”,显示红色灯
- 蓝牙实时接收温度,与oled和串口调试打印结果同步
- 5.然后是四个状态,分别是close,normal,cautious,warning(PS,拍照的时候因为室温25度多,然后我就把值改成27了,小小变一下,影响不大。)
硬件调试问题:
硬件焊接问题,这个问题比较严重,我在焊接结束后,点亮了led,第二天测试的时候,突然识别不到串口了,然后长按boot页弹不出u盘:
以下是测试过程:
- 首先我怀疑晶振没起振,然后用示波器测试了一下看了看波形,发现波形是12Mhz完美正弦波,pass
- 然后我觉得是typec接口焊接问题,然后测试了一下引脚,发现没问题,但是以防万一我重新焊接了一次,然后没解决,pass。
- 接着经过群友提醒,我去测了一下flash,然后发现可能有点问题,因为我这个封装画错了,把芯片引脚折了一下,发现换了一个flash后也没好,pass
- 最后犹豫不决,只能换芯片,芯片换了一下,发现还是没好,然后我就不知道什么情况了。
- 因为晚上实验室关门,就没没调试成功,第二天去调试,结果发现突然连成功了,然后我突然意识到了可能是我那个扩展坞的连接不稳定,然后我把昨天的flash和rp2040换回来了,测试了一下,还真是连接不稳定。
总之,以上就是我的一个调试过程,虽然原因有点搞笑。
第二个(未解决):
我把核心板接到扩展板上面之后,发现两个板的pwr灯都不亮了,而且电脑也连接不到pico了,然后我就开始测电源电路,但是后面测着发现导通不是问题,二极管焊接也不是问题,然后我怀疑是一个三极管的引脚错了,事实证明我的封装画错了,但是我飞线的时候飞失误了,不小心加热太久,焊盘废了,然后就不了了之了,后续有时间会重新焊接一块新的重新调试一下,(感觉可能设计的时候没考虑供电问题?我也不清楚)因为pcb设计我还是小白,还望各位大佬赐教。文件中会上传我的所有kicad设计文件。
软件调试问题:
- 想让oled显示变量的值的时候,因为没有转换类型,报错了,后发现是该函数没注意,只需要对变量加个str使其转换成字符即可。oled.text("temp is:"+str(TEMP),0,0)
- 在温度跳变的过程中,由于没有关闭其他的位置,导致两个灯,即25-30和30-35的灯一起亮了,所以在该函数执行前,添加了这段代码,用于关闭所有位置。后续的灯才可以单独亮
set_24bit(3, (0, 0, 0)) # 其他位置关闭 set_24bit(2, (0, 0, 0)) # 其他位置关闭 set_24bit(1, (0, 0, 0)) # 其他位置关闭 set_24bit(0, (0, 0, 0)) # 其他位置关闭
- oled.show()放在了green_judge()的前面,因为warning提醒放在了green函数里面,所以不显示,这个应该放在后面。(低级错误)
- 3问题的测试方法,只需要在green_judge的if语句加一下中断或者直接printf打印,看串口上的数据就可以测试代码卡在哪里了,从而得到原因。
六、在芯片设计过程中,遇到什么难题以及解决方法,或未来针对这个芯片的扩展项目
- 布局布线,因为不熟悉kicad,导致我刚开始的线宽和通孔的尺寸很大,然后就导致了布线卡住了,只能重新画,后面发现尺寸没改过来,改完尺寸后重新画,然后成功布线。
- 这次我想做的很简单,就是个核心板,加上个扩展板,简单点就是实现一个灯光时钟,未来我肯定是要修好扩展板的,然后用麦克风去采集信号,2040采集到AD信号,做一个FFT,然后把处理完的频谱图显示在屏幕上,实现一个音频的旋律跳动,然后我再带个灯效,实现一个效果。
- 蓝牙部分其实可以使用那个数据结构包,做一个UI界面,只要写一个函数,把数据流头部分尾部分处理一下,然后再把要接收的数据放到14个里面,做一个处理就可以实现一个复杂的UI界面,这个没做,但是要做的话效果肯定会更好。
- ps:3的教程,这个是C和stm32编的,主要可以学习思路,详细教程见蓝牙调试器 接收处理 hc-05蓝牙上传数据
- 基于上述内容,其实完全可以做一个音乐频谱律动的屏幕,不仅仅oled显示,手机也可也显示。
七、芯片的优势与局限
优势:
- 高性价比:RP2040芯片的价格相对较低,适合于各种成本敏感的应用。
- 高性能:RP2040采用双核ARM Cortex-M0+处理器,主频高达133MHz,具有强大的计算和处理能力。
- 大容量内存:RP2040芯片具有264KB的SRAM,足够存储大量的数据和程序代码。
- 多种外设接口:RP2040芯片支持多种常用的外设接口,包括SPI、I2C、UART等,方便与其他设备进行通信和连接。
- 丰富的GPIO:RP2040芯片提供了26个通用IO引脚,可用于连接和控制各种外部设备。
- 低功耗:RP2040芯片在低功耗模式下具有较低的功耗,适合电池供电的应用场景。
局限性:
-
处理能力限制:RP2040芯片是一款低功耗微控制器,其处理能力相对于高性能处理器(如桌面计算机或嵌入式系统)有一定的限制。它适用于较为简单的任务和轻量级应用,对于复杂的计算或高性能需求,可能需要选择更强大的处理器。
-
外围设备限制:RP2040芯片集成了一些常用的外围设备接口,如GPIO、SPI、I2C和UART等,但其数量和功能有限,尤其是对比其他的高级mcu。如果需要更多的外围设备接口或特定功能的支持,可能需要外部扩展模块或使用其他芯片。
-
缺乏操作系统生态系统:尽管RP2040芯片可以运行一些操作系统,但与通用计算平台相比,其操作系统生态系统相对较小。这意味着可能无法获得广泛的操作系统支持、大量的第三方库和开发工具,以及成熟的开发社区。
八、原理图/PCB图(放在项目附件)
包括扩展板的pcb图也有,这个是参照课程画的,可以直接根据课程来
附件里面只能放两张,放了核心板的,还有一个扩展板的详细见文件
这是大概的截图,扩展板图片:
九、可编译下载的代码(放在项目的附件,用于验证)
代码注释都有解释了
全部完整资源见百度网盘链接:
https://pan.baidu.com/s/1oOZWd77pzrMHWMBkDvFWYA?pwd=6666
提取码:6666
链接:https://pan.baidu.com/s/1oOZWd77pzrMHWMBkDvFWYA?pwd=6666 提取码:6666
我不确定压缩完后的放在附件里面的文件是否可以正常使用