【复旦微FM33LC046N评测】+ SPI驱动OLED
本帖最后由 chrisrh 于 2021-3-18 19:37 编辑<p>使用复旦微FM33LC046N的自带的硬件SPI1或GPIO口模拟SPI控制0.96'OLED</p>
<ol>
<li>SPI引脚定义</li>
<li>0.96’OLED引脚定义</li>
<li>硬件HW_SPI驱动OLED</li>
<li>软件模拟SPI驱动OLED</li>
<li>一个问题</li>
</ol>
<hr />
<p><strong><span style="color:#c0392b;">1.SPI1引脚</span></strong></p>
<p><span style="background-color:#ffffff;">查阅数据手册可知SPI1的引脚为PB8/9/10/11或PD2/3/4/5,SPI2的引脚为PC7/8/9/10,如下:</span></p>
<p></p>
<hr />
<p><strong><span style="color:#c0392b;">2.0.96’OLED引脚定义</span></strong></p>
<p style="margin-left: 40px;">手中的这块OLED没有CS片选引脚,也就一块,所以在连接MCU的时候,SPI只需要配置好MOSI和SCLK就行,</p>
<p style="margin-left: 40px;">MISO和SSN可不进行配置,其中:</p>
<p style="margin-left: 40px;">VCC,DC供电电压3.3~4.3V,如果使用5V电压,为保险起见串一个100~500欧的电阻 <br />
DC,命令数据选择管脚<br />
RES,模块复位管脚 <br />
D0(SCLK),时钟脚<br />
D1(MOSI),主输出从输入数据脚</p>
<p></p>
<hr />
<p><span style="color:#c0392b;"><strong>3.硬件HW_SPI驱动OLED</strong></span></p>
<p>使用的是SPI1对应的PB<span style="background-color:#ffffff;">8/9/10/11引脚,将其中的PB9/PB11与OLED相连,其中</span></p>
<p><span style="background-color:#ffffff;">PB9----SCLK</span></p>
<p><span style="background-color:#ffffff;">PB11----MOSI</span></p>
<p>控制引脚这里选用PB6/7,其中:<br />
PB6 ---->DC 命令CMD或数据DATA标识控制IO<br />
PB7 ---->RES 复位</p>
<p>SPI1初始化函数如下:</p>
<div aria-label="代码段 小部件" contenteditable="false" role="region" tabindex="-1">
<pre data-widget="codesnippet">
<code class="language-cpp hljs"><span class="hljs-keyword">void</span> MF_SPI1_Init(<span class="hljs-keyword">void</span>)
{
FL_GPIO_InitTypeDef GPIO_InitStruct;
FL_SPI_InitTypeDef defaultInitStruct;
defaultInitStruct.transferMode = FL_SPI_TRANSFER_MODE_FULL_DUPLEX;<span class="hljs-comment">/*! 传输模式 单双工 */</span>
defaultInitStruct.mode = FL_SPI_WORK_MODE_MASTER;<span class="hljs-comment">/*! 主从模式 */</span>
defaultInitStruct.dataWidth = FL_SPI_DATA_WIDTH_8B;<span class="hljs-comment">/*! 数据位宽 */</span>
defaultInitStruct.clockPolarity = FL_SPI_POLARITY_NORMAL;<span class="hljs-comment">/*! 时钟极性 */</span>
defaultInitStruct.clockPhase = FL_SPI_PHASE_EDGE1;<span class="hljs-comment">/*! 时钟相位 */</span>
defaultInitStruct.softControl = DISABLE;
<span class="hljs-comment">/*! NSS 脚使能软件控制,enable时:使用SPI需先拉低片选;disable时为硬件控制*/</span>
defaultInitStruct.baudRate = FL_SPI_BAUDRATE_DIV2;<span class="hljs-comment">/*! 通讯速率 */</span>
defaultInitStruct.bitOrder = FL_SPI_BIT_ORDER_MSB_FIRST;<span class="hljs-comment">/*! Bit方向 */</span>
FL_SPI_Init(SPI1,&defaultInitStruct );
FL_SPI_ClearTXBuff(SPI1);
FL_SPI_ClearRXBuff(SPI1);
GPIO_InitStruct.pin = FL_GPIO_PIN_11|FL_GPIO_PIN_10|FL_GPIO_PIN_9|FL_GPIO_PIN_8;
GPIO_InitStruct.mode = FL_GPIO_MODE_DIGITAL;
<span class="hljs-comment">//数字外设功能,IO 的输入或输出方向由所连接的外设功能决定</span>
GPIO_InitStruct.outputType = FL_GPIO_OUTPUT_PUSHPULL;
GPIO_InitStruct.pull = DISABLE;
GPIO_InitStruct.remapPin = DISABLE;
FL_GPIO_Init( GPIOB, &GPIO_InitStruct );
}</code></pre>
<img src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" /><span style="background: url("https://bbs.eeworld.com.cn/static/editor/plugins/widget/images/handle.png") rgba(220, 220, 220, 0.5); top: -15px; left: 0px; display: block;"><img height="15" role="presentation" src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" title="点击并拖拽以移动" width="15" /></span></div>
<p>其中GPIO初始化,模式选择为FL_GPIO_MODE_DIGITAL,</p>
<p>FL_GPIO_MODE_DIGITAL:即数字外设功能,IO 的输入或输出方向由所连接的外设功能决定;</p>
<p> </p>
<p></p>
<p>配置PB6/7两个控制IO口的初始化其中:<br />
PB6 ---->DC 命令CMD或数据DATA标识控制IO<br />
PB7 ---->RES 复位</p>
<div aria-label="代码段 小部件" contenteditable="false" role="region" tabindex="-1">
<pre data-widget="codesnippet">
<code class="hljs language-cpp"><span class="hljs-comment">//初始化两个控制IO口</span>
<span class="hljs-comment">//PB6 ---->DC 命令CMD或数据DATA标识控制IO</span>
<span class="hljs-comment">//PB7 ---->RES复位</span>
<span class="hljs-keyword">void</span> MF_SPI_GPIOPin_Init(<span class="hljs-keyword">void</span>)
{
FL_GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.pin= FL_GPIO_PIN_6|FL_GPIO_PIN_7;
GPIO_InitStruct.mode = FL_GPIO_MODE_OUTPUT;
GPIO_InitStruct.outputType = FL_GPIO_OUTPUT_PUSHPULL;
GPIO_InitStruct.pull = DISABLE;
GPIO_InitStruct.remapPin = DISABLE;
FL_GPIO_Init( GPIOB, &GPIO_InitStruct );
FL_GPIO_SetOutputPin(GPIOB, FL_GPIO_PIN_6|FL_GPIO_PIN_7);
}</code></pre>
<img src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" /><span style="background: url("https://bbs.eeworld.com.cn/static/editor/plugins/widget/images/handle.png") rgba(220, 220, 220, 0.5); top: -15px; left: 0px; display: block;"><img height="15" role="presentation" src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" title="点击并拖拽以移动" width="15" /></span></div>
<p>通过SPI向OLED中写入命令或数据,当片选SSN配置为软件控制使能时,使用SPI时需先拉低片选,待写完数据后,再拉高SSN;若为硬件控制,可不配置SSN::</p>
<div aria-label="代码段 小部件" contenteditable="false" role="region" tabindex="-1">
<pre data-widget="codesnippet">
<code class="hljs language-cpp"><span class="hljs-comment">//SPI写一个字节</span>
<span class="hljs-keyword">void</span> SPI_WriteByte(uint8_t data)
{
<span class="hljs-comment">//FL_SPI_SetSSNPin(SPI1, FL_SPI_SSN_LOW);</span>
<span class="hljs-keyword">while</span> (!(FL_SPI_IsActiveFlag_TXBuffEmpty(SPI1)));
<span class="hljs-comment">//Get SPI TX Buffer Empty Flag,State of bit (1 or 0)</span>
FL_SPI_WriteTXBuff(SPI1, data);
<span class="hljs-keyword">while</span> (!(FL_SPI_IsActiveFlag_RXBuffFull(SPI1)));
<span class="hljs-comment">//FL_SPI_SetSSNPin(SPI1, FL_SPI_SSN_HIGH);</span>
}
<span class="hljs-comment">//向OLED中写命令或数据</span>
<span class="hljs-keyword">void</span> OLED_WR_Byte(uint8_t dat,uint8_t cmd)
{
<span class="hljs-keyword">if</span>(cmd)
{
SPI_OLED_DC_Set();
}
<span class="hljs-keyword">else</span>
{
SPI_OLED_DC_Clr();
}
SPI_WriteByte(dat);
}</code></pre>
<img src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" /><span style="background: url("https://bbs.eeworld.com.cn/static/editor/plugins/widget/images/handle.png") rgba(220, 220, 220, 0.5); top: -15px; left: 0px; display: block;"><img height="15" role="presentation" src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" title="点击并拖拽以移动" width="15" /></span></div>
<p>配置完驱动函数后,调用FL_SPI_Enable(SPI1)启动SPI;</p>
<p></p>
<p> </p>
<hr />
<p> </p>
<p><span style="color:#c0392b;"><strong>4.软件模拟SPI驱动OLED</strong></span></p>
<p>硬件SPI资源有限,且IO口较为集中,为了更好的利用资源,可以使用软件模拟SPI控制OLED</p>
<p>选用PC5/6/7/8模拟SPI通信控制OLED,其中:</p>
<p>PC7----DC,命令数据选择管脚<br />
PC9----RES,模块复位管脚 <br />
PC6----D0(SCLK),时钟脚<br />
PC5----D1(MOSI),主输出从输入数据脚</p>
<p>宏定义各IO口,以便移植:</p>
<div aria-label="代码段 小部件" contenteditable="false" role="region" tabindex="-1">
<pre data-widget="codesnippet">
<code class="hljs language-cpp"> <span class="hljs-preprocessor">#define OLED_CMD0 <span class="hljs-comment">//写命令</span></span>
<span class="hljs-preprocessor">#define OLED_DATA 1 <span class="hljs-comment">//写数据</span></span>
<span class="hljs-comment">//使用PC5、6、7、9模拟SPI控制OLED</span>
<span class="hljs-comment">//控制引脚DC</span>
<span class="hljs-preprocessor">#define AN_SPI_DC_PIN FL_GPIO_PIN_7</span>
<span class="hljs-preprocessor">#define AN_SPI_DC_GPIOX GPIOC</span>
<span class="hljs-comment">//复位引脚RES</span>
<span class="hljs-preprocessor">#define AN_SPI_RES_PIN FL_GPIO_PIN_9</span>
<span class="hljs-preprocessor">#define AN_SPI_RES_GPIOX GPIOC</span>
<span class="hljs-comment">//时钟SCLK/D0</span>
<span class="hljs-preprocessor">#define AN_SPI_SCLK_PIN FL_GPIO_PIN_6 </span>
<span class="hljs-preprocessor">#define AN_SPI_SCLK_GPIOX GPIOC</span>
<span class="hljs-comment">//数据SDATA/D1</span>
<span class="hljs-preprocessor">#define AN_SPI_SDA_PIN FL_GPIO_PIN_5</span>
<span class="hljs-preprocessor">#define AN_SPI_SDA_GPIOX GPIOC</span>
<span class="hljs-comment">//片选CS:控制单片OLED未使用</span>
<span class="hljs-comment">//#define AN_SPI_CS_PIN FL_GPIO_PIN_8</span>
<span class="hljs-comment">//#define AN_SPI_CS_GPIOX GPIOC</span>
<span class="hljs-preprocessor">#define OLED_RES_Clr() FL_GPIO_ResetOutputPin(AN_SPI_RES_GPIOX, AN_SPI_RES_PIN)</span>
<span class="hljs-comment">//Set pin output 0//低电平复位</span>
<span class="hljs-preprocessor">#define OLED_RES_Set() FL_GPIO_SetOutputPin(AN_SPI_RES_GPIOX, AN_SPI_RES_PIN)</span>
<span class="hljs-comment">//Set pin output 1//保持</span>
<span class="hljs-preprocessor">#define OLED_DC_Clr() FL_GPIO_ResetOutputPin(AN_SPI_DC_GPIOX, AN_SPI_DC_PIN)</span>
<span class="hljs-comment">//Set pin output 0//命令模式</span>
<span class="hljs-preprocessor">#define OLED_DC_Set() FL_GPIO_SetOutputPin(AN_SPI_DC_GPIOX, AN_SPI_DC_PIN)</span>
<span class="hljs-comment">//Set pin output 1//数据模式</span>
<span class="hljs-preprocessor">#define OLED_CS_Clr() FL_GPIO_ResetOutputPin(AN_SPI_CS_GPIOX, AN_SPI_CS_PIN)</span>
<span class="hljs-comment">//Set pin output 0</span>
<span class="hljs-preprocessor">#define OLED_CS_Set() FL_GPIO_SetOutputPin(AN_SPI_CS_GPIOX, AN_SPI_CS_PIN)</span>
<span class="hljs-comment">//Set pin output 1</span>
<span class="hljs-preprocessor">#define OLED_SCLK_Clr()FL_GPIO_ResetOutputPin(AN_SPI_SCLK_GPIOX, AN_SPI_SCLK_PIN)</span>
<span class="hljs-comment">//Set pin output 0</span>
<span class="hljs-preprocessor">#define OLED_SCLK_Set()FL_GPIO_SetOutputPin(AN_SPI_SCLK_GPIOX, AN_SPI_SCLK_PIN)</span>
<span class="hljs-comment">//Set pin output 1</span>
<span class="hljs-preprocessor">#define OLED_SDA_Clr() FL_GPIO_ResetOutputPin(AN_SPI_SDA_GPIOX, AN_SPI_SDA_PIN)</span>
<span class="hljs-comment">//Set pin output 0</span>
<span class="hljs-preprocessor">#define OLED_SDA_Set() FL_GPIO_SetOutputPin(AN_SPI_SDA_GPIOX, AN_SPI_SDA_PIN)</span>
<span class="hljs-comment">//Set pin output 1</span></code></pre>
<img src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" /><span style="background: url("https://bbs.eeworld.com.cn/static/editor/plugins/widget/images/handle.png") rgba(220, 220, 220, 0.5); top: -15px; left: 0px; display: block;"><img height="15" role="presentation" src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" title="点击并拖拽以移动" width="15" /></span></div>
<p>模拟SPI通信写一个字节:</p>
<pre>
<code class="language-cpp">void AN_OLED_WR_Byte(uint8_t dat,uint8_t cmd)
{
uint8_t i;
if(cmd) OLED_DC_Set();
else OLED_DC_Clr();
for(i=0;i<8;i++)
{
OLED_SCLK_Clr();
if(dat&0x80) OLED_SDA_Set();
else OLED_SDA_Clr();
OLED_SCLK_Set();
dat<<=1;
}
OLED_DC_Set();
} </code></pre>
<p>模拟SPI通信写数据和写命令:</p>
<pre>
<code class="language-cpp">void OLED_WrDat(unsigned char data)
{
unsigned char i=8;
OLED_DC_Set();
__NOP();
OLED_SCLK_Clr();
__NOP();
while(i--)
{
if(data&0x80)
{OLED_SDA_Set();}
else
{OLED_SDA_Clr();}
OLED_SCLK_Set();
__NOP();
OLED_SCLK_Clr();
data<<=1;
}
}
void OLED_WrCmd(unsigned char cmd)
{
unsigned char i=8;
OLED_DC_Clr() ;
OLED_SCLK_Clr();
__NOP();
while(i--)
{
if(cmd&0x80)
{OLED_SDA_Set();}
else
{OLED_SDA_Clr();}
OLED_SCLK_Set();
__NOP();
OLED_SCLK_Clr();
cmd<<=1;;
}
}</code></pre>
<p>在main中初始化调用:</p>
<p></p>
<p> </p>
<hr />
<p><strong><span style="color:#c0392b;">5.一个问题</span></strong></p>
<p>和UART+DMA调试的时候一样,设备收到的是正常的,但在Debug中看到的数据不对,</p>
<p>用keil的Debug在线调试SPI收发时,在watch或者memory中看到的数据与实际要收发的数据不一致,</p>
<p>(如SPI发送一组数据到OLED/别的设备,在Debug中看到数组中的数或实时更新的数与要传送的数不同)</p>
<p>不知道是为什么···</p>
<p> </p>
<p><strong><span style="color:#c0392b;">程序见附件,仅供参考······</span></strong></p>
<p>debug调试时,要查看SPI总线寄存器</p>
<p>这个debug有点神奇了。具体描述一下呢。</p>
<p>呼叫 @doudou52098 帮忙</p>
Jacktang 发表于 2021-3-18 22:14
debug调试时,要查看SPI总线寄存器
<p>是看指向APB_bus上的Transmit shift reg吗</p>
freebsder 发表于 2021-3-18 23:21
这个debug有点神奇了。具体描述一下呢。
<p>这是最初用SPI参考例程debug看的</p>
<p>估计是我没有配置好或者没有找对相应的寄存器吧</p>
<p>调试不一致降一下优化等级试试,还有如果是DMA你卡断点的时候收到信数据一般内存的数据还是会变化的。</p>
页:
[1]