基于RiskV的阿里平头哥MCU开箱文章之软硬件i2C驱动oled 12864
拿到这款国产单片机有一段时间了,今天回头写写入门攻略,就拿一篇自己的iic驱动oled12864工程讲一下。
Risk-V简介
RISC-V读作RISC Five,也即第五代精简指令处理器。RISC-V指令集完全开源,设计简单,易于移植Unix系统,模块化设计,完整工具链,同时有大量的开源实现和流片案例,已在社区得到大力支持。它虽然不是第一个开源的的指令集(ISA),但它是第一个被设计成可以根据具体场景可以选择适合的指令集的指令集架构。基于RISC-V指令集架构可以设计服务器CPU、家用电器CPU、工控CPU和传感器中的CPU。想要了解它先得提一下 RISC。即精简指令集计算机,Reduced Instruction Set Computer-RISC。它和CISC(复杂指令集计算机,Complex Instruction Set Computer-CISC)是CPU的两种架构,区别在于不同的CPU设计理念和方法。早期的CPU全部是CISC架构。重点介绍下RISC。
针对CISC的庞杂的指令系统不但不易实现,而且还可能降低系统性能等弊病,David Patterson教授等人提出了精简指令的设想,即指令系统应当只包含那些使用频率很高的少量指令,并提供一些必要的指令以支持操作系统和高级语言.按照这个原则发展而成的计算机被称为精简指令集计算机(Reduced Instruction Set Computer-RISC)结构。RISC架构要求软件来指定各个操作步骤。这种架构可以降低CPU的复杂性以及允许在同样的工艺水平下生产出功能更强大的CPU,但对于编译器的设计有更高的要求。
国产单片机及开发环境CDK
首先感谢一下深圳A1SEMI矽海半导体公司提供的单片机和下载工具CK-LINK。
平头哥MCU开发环境CDK 下载安装等可以参考下面的文章
https://blog.csdn.net/sqhone/article/details/123015091。
如果你想玩玩这款单片机又没有下载工具的话自己diy一个也不是不可以。链接附上 https://www.bilibili.com/video/BV1L44y1J7Jz?spm_id_from=333.337.search-card.all.click
这款国产单片机确实让我感到震惊,国产真的是赶上了国际水平,在性能使用方面一点也不比STM32单片机差多少,有些方面甚至特别香。比如说同样是基于库函数开发,它在ADC,定时器,IIC等应用上,函数完全给你写的好好的,只需要你改改参数就行了。
当然他也有一些让人头疼的地方,尤其是下载程序。烧录管脚一定不要复用,否则的话,芯片可能就废了,再也烧不进去程序了。
iic简介
IIC(Inter Integrated Circuit,集成电路总线)是一种由 PHILIPS 公司开发的两线式串行总线,用于连接微控制器及其外围设备。它是由数据线 SDA 和时钟 SCL 构成的串行总线,可发送和接收数据。在 CPU (单片机)与IIC模块之间、IIC模块与IIC模块之间进行双向传送。
从上图可以看到时序很简单。SCL 线为高电平期间,SDA 线由高电平向低电平的变化表示起始信号;SCL 线为高电平期间,SDA 线由低电平向高电平的变化表示终止信号。起始信号后跟着是从机地址,共有七位。最后一位是读写位。特别强调地址很重要一旦错误,不可能发送信息的即使发送了也是错误的。一般器件的地址在手册上都会有提到。随后便是应答位,就开始发送数据,这之就看你是停止还是继续收发数据。
根据以上时序图我们很快便可以写出一个软件模拟iIC的程序代码。
以上是一个stm32硬件iIC 时序图。可以看到,它与软件模拟的流程差不多。但又有一些细微差别。那我到底使用软件还是硬件呢。首先你要确定你的板子是否有这个硬件资源,没有的话就没得选择。如果有,具体比较如下
接下来根据参考的文章和自己的理解浅浅的介绍一下软件和硬件iIC。
所谓硬件I2C对应芯片上的I2C外设,有相应I2C驱动电路,其所使用的I2C管脚也是专用的;软件I2C通常是用GPIO管脚,用软件控制管脚状态以模拟I2C通讯波形。
-
硬件IIC用法比较复杂,模拟IIC的流程更清楚一些。
-
硬件IIC速度比模拟快,而且在stm32中能够用DMA
-
模拟IIC能够在任何管脚上,而硬件只能在固定管脚上。
-
硬件iIC比软件模拟更加稳定。
-
代码量上硬件较少,软件更多。
-
软件i2c是使用程序控制SCL,SDA线输出高低电平,模拟i2c协议的时序。硬件iic调用现成的函数或者给某个寄存器赋值来配置对应的功能。
看完这些你应该懂了软硬件直接的区别。首先你要确定你使用硬件还是软件模拟,大方向要确定好不然最后就算是问别人你都说不清楚自己的程序是什么。如果还是不太懂那就在接下来的代码中慢慢体会一下吧。
关于iIC通信的时序和流程我觉得也可以看这篇文章写。链接附上:https://www.elecfans.com/d/1845634.html。
iIC代码移植详细介绍
# stm32 iic:
在网上或者某宝店铺随便找便可以找到一大堆关于iIC驱动oled的代码,不过大多是用软件模拟的。关于软件模拟我就简单点介绍一下。
移植程序的时候不能把所有东西都丢进来一定要先把核心程序放进来。
以我移植oled程序为例(程序源码最后奉上可直接下载)。关键程序就是OLED_Init() ;OLED_Write_Byte();和其他显示函数以及刷新函数。而其余的什么字库,显示图片,反色显示,翻转等等这些花的都不是必要的。甚至可以这样说我只要点亮屏幕就可以了,其余什么函数我都可以先不要。只留下最关键的核心程序也就是你要实现的功能的最简单一步。然后在这个基础之上逐步添加你所需要的功能。以下是具体调试步骤:
第一步:先在你源程序上调试,比如我在stm32f103c8t6上确定我的核心程序是哪些(以硬件iic为例,确保点亮屏幕就可以了)。
int main(void)
{
DelayInit();
I2C_Configuration();
OLED_Init();
while(1)
{
OLED_Fill(0xFF);//全屏点亮
DelayS(2);
OLED_Fill(0x00);//全屏熄灭
DelayS(2);
}
}
然后再去看具体的函数,比如I2C_Configuration();
里面具体是怎么配置的。
void I2C_Configuration(void)
{
I2C_InitTypeDef I2C_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
/*STM32F103C8T6оƬµÄÓ²¼þI2C: PB6 -- SCL; PB7 -- SDA */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;//I2C±ØÐ뿪©Êä³ö
GPIO_Init(GPIOB, &GPIO_InitStructure);
I2C_DeInit(I2C1);//ʹÓÃI2C1
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStructure.I2C_OwnAddress1 = 0x30;//Ö÷»úµÄI2CµØÖ·,Ëæ±ãдµÄ
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStructure.I2C_ClockSpeed = 400000;//400K
I2C_Cmd(I2C1, ENABLE);
I2C_Init(I2C1, &I2C_InitStructure);
}
这里面有管脚配置,时钟速度,模式等等根据你的需要来选择就好。如果是配套的基本上不需要改什么。在这里在强调一下最为关键的一个函数写字节函数。
可以对比一下软硬件iIC的程序
硬件确实有点不好理解,但是回过头看看时序图就明白了还是按照时序来的。而且硬件是直接调用库函数中的函数来写的,只要你按照时序来事实上你根本就不需要写什么代码。
# 向国产单片机进发
按照你所要移植的单片机规则把你的核心程序一步一步移植过去。一定要从硬件入手,确定你有相应的硬件,否则你再怎么调试也不行。比如我使用的单片机就只有一个硬件iic。相应的管脚、时钟使能、速度配置、从机地址、直接移植过来就好。还是以硬件iIC为例(软件iIC你直接复制过来就好如果出现重定义直接把原来的函数或者变量注释掉)
写字节的函数在我要移植的单片机中的库函数也有以一个。这也是我在程序移植的过程中我遇到的最大的一个问题。相似的函数变量不知道如何处理。首先第一点你肯定是想不费功夫的全都copy过来,当然可以,如果你能实现功能的话。不过哦百分之九十九还是不行吧。我在oled移植就遇到了写字节的函数。在我的单片机标准iic库里自己定义了一套写字节的函数,但是在外面32源程序里面也定义了一个写字节的函数,两个函数定义的还不一样。
怎么办呢。首先肯定是如果你要移植的单片机有自己的iIC例程一定要把它的例程看明白。这是第一步。第二步找不同。举个栗子。
我要移植软件iIC。这个可以说是闭着眼睛就能做。为什么呢,把原来自己带的函数都注释了直接移植。以写一个字节函数为例。
void OLED_WR_Byte(u8 dat,u8 mode)
{
I2C_Start();//起始信号
Send_Byte(0x78);//发送从机地址
I2C_WaitAck();//等待应答
if(mode){Send_Byte(0x40);}//写数据
else{Send_Byte(0x00);}//写命令
I2C_WaitAck();//等待应答
Send_Byte(dat);//发送数据
I2C_WaitAck();//iic等待应答
I2C_Stop();//停止信号
}
以上便是我要移植的函数 。若是通过软件模拟iIC 那我直接copy就可以。一个字都不用改动。
但是如果我是用硬件iIC ,那就要好好琢磨一下了。
void I2C_WriteByte(uint8_t addr,uint8_t data)
{
while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));
I2C_GenerateSTART(I2C1, ENABLE);//¿ªÆôI2C1
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));/*EV5,Ö÷ģʽ*/
I2C_Send7bitAddress(I2C1, OLED_ADDRESS, I2C_Direction_Transmitter);//Æ÷¼þµØÖ· -- ĬÈÏ0x78
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
I2C_SendData(I2C1, addr);//¼Ä´æÆ÷µØÖ·
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
I2C_SendData(I2C1, data);//·¢ËÍÊý¾Ý
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
I2C_GenerateSTOP(I2C1, ENABLE);//¹Ø±ÕI2C1×ÜÏß
}
以上是硬件iIC 的stm32 程序。该程序实现写一个字节的功能。看起来比软件复杂多了不是。但是仔细推敲实际上也不是太难。
对比软件函数发现他们基本上是同样的功能时许基本相似。但是一个是对SCL SDA 高低电平进行操作模拟信号,另一个是直接对寄存器操作。废话不多说,开始我的表演。
看懂了这个移植的程序每一步在干嘛之后,对比你要移植的单片机语法规则。起始信号函数,停止信号函数,发送数据函数,地址函数这些函数在你要移植的单片机中是怎么写的,是否都有,你怎么把按照iIC时序把这些组织起来。或者说你要移植的单片机的库函数中自带了iIC 写一个字节的函数,那你就要去跑一下例程看看他是怎么运行的,然后自己是否需要改动。先假设你不需要,那么把其他的函数照搬过来,编译没有错误后烧录看程序是否运行。如果不能说明你的函数有概率有问题需要改动,也有很大可能是你的整个程序哪有问题,比如iIC从机地址不对,引脚连接或者设置错误。或者说是板子上某个跳线帽没连接等等。
以下是我要移植的单片机自带的库函数写字节函数。
void I2C_WRITE_Byte(U8_T write_adds,U8_T i2c_data)
{
U16_T R_EEROR_CONT=0;
I2C0->DATA_CMD = I2C_CMD_WRITE|write_adds ;
I2C0->DATA_CMD = i2c_data |I2C_CMD_STOP;
do
{
if(R_EEROR_CONT++>=10000)
{
R_EEROR_CONT=0;
f_ERROR=1;
I2C_Disable();
I2C_Enable();
break;
}
}
while( (I2C0->STATUS & I2C_BUSY) != I2C_BUSY ); //Wait for FSM working
do
{
if(R_EEROR_CONT++>=10000)
{
R_EEROR_CONT=0;
f_ERROR=1;
I2C_Disable();
I2C_Enable();
break;
}
}
while(((I2C0->STATUS) & I2C_TFE) != I2C_TFE);
}
他是直接对寄存器操作的,一开始我也不是太明白,不过我一上来假设它不需要改动,是完整的。(还假设对了)把其他函数复制过来结果程序就是不运行,屏幕点不亮,最后才排查出来从机地址错误。
如果你的单片机没有这些库函数或者时同款32的单片机建议你移植的时候一定要仔细比对,哪有有,哪些没有,哪些需要改动,怎么改动。建议在纸上写好大致改动思路。
这是我在学习国产单片机遇到的问题希望能对大家有所帮助。最后感谢一下深圳A1SEMI矽海半导体公司提供的技术支持和帮助,我所使用的正是此公司生产的ASM32s003单片机。
拿一个简单的实例请大家看看。
int main(void)
{
float ADC_Buffer[2];
float temp;
float temp1;
char buff[10];
char buff1[10];
ASM32S003_init();//管脚、iic初始化
OLED_Init();
OLED_Fill(0xFF);//全屏点亮
delay_nms(2000);
OLED_Fill(0x00);//全屏熄灭
delay_nms(4000);
while(1)
{
OLED_ShowStr(42,0,“ABC",2)
}
}