怎么说呢,感觉自己之前都白学了,又从头到尾看了一遍。
主要参考厂家给的源码,不过只有STM32的程序,但是大差不差,拿过来改一下就可以了,其次就是仔细查看芯片手册。
好的,最大的收获就是学会了如何翻手册,有问题翻手册!!
想要让水墨屏显示起来,需要利用spi来进行驱动,spi用来发送命令和数据,本质上来说还是设置寄存器。
发送的命令是地址,数据是要设置的值,这跟I2C其实没什么差别。
具体的什么spi的原理,其他文章都说的很全。
我也懒得写....
好,下面就记录一下我做的过程吧。
在做之前,先来看一下需要几个引脚,以及这些引脚都用来干啥。
查手册,以及我们开发板的硬件原理图。
首先这四根线是我们必须要有的。
还有选择是命令/数据,复位以及忙线等。
所以综合来说,我们需要七根线。
MISO、MOSI、CLK、CS、D/C、RES、BUSY。
有了这些,就可以对我们的GPIO进行设置了。
需要进行GPIO初始化的只有后面四根线,别问我怎么知道,我翻的例程里面就是这么写的。
其实对于我们主控芯片来说,BUSY是输入,CS、D/C、RES都是输出。
其中BUSY存在中断,因为在选模式的时候,选的是SPI0的模式,上升沿采样、下降沿移出,所以此时设置BUSY下下降沿的时候触发中断,允许数据变化(这里理解好像有一点点问题)。
下面上代码:
void ds_screen_gpio_init(){gpio_config_t io_conf;//disable interruptio_conf.intr_type = GPIO_PIN_INTR_DISABLE;//set as output modeio_conf.mode = GPIO_MODE_OUTPUT;//bit mask of the pins that you want to set,e.g.GPIO18/19io_conf.pin_bit_mask = SCREEN_GPIO_OUTPUT_CS_SEL;//disable pull-down modeio_conf.pull_down_en = 0;//disable pull-up modeio_conf.pull_up_en = 0;//configure GPIO with the given settingsgpio_config(&io_conf);//初始化片选//bit mask of the pins that you want to set,e.g.GPIO18/19io_conf.pin_bit_mask = SCREEN_GPIO_OUTPUT_DC_SEL;//configure GPIO with the given settingsgpio_config(&io_conf);//初始化D/C//bit mask of the pins that you want to set,e.g.GPIO18/19io_conf.pin_bit_mask = SCREEN_GPIO_OUTPUT_RES_SEL;//configure GPIO with the given settingsgpio_config(&io_conf);//复位io_conf.intr_type = GPIO_INTR_NEGEDGE;//这里为啥要用下降沿来进行中断触发,存疑//bit mask of the pins, use GPIO4/5 hereio_conf.pin_bit_mask = SCREEN_GPIO_INTPUT_BUSY_SEL;//set as input mode io_conf.mode = GPIO_MODE_INPUT;//enable pull-up modeio_conf.pull_up_en = 1;gpio_config(&io_conf);}
本部分参考官方STM32代码,如下图所示。
GPIO基本就设置完了。
下面编写SPI的代码。
同样是参考官方例程。
首先初始化:
void screen_spi_init(void)
{esp_err_t ret;spi_bus_config_t buscfg={.miso_io_num = PIN_NUM_MISO, // MISO信号线.mosi_io_num = PIN_NUM_MOSI, // MOSI信号线.sclk_io_num = PIN_NUM_CLK, // SCLK信号线.quadwp_io_num = -1, // WP信号线,专用于QSPI的D2.quadhd_io_num = -1, // HD信号线,专用于QSPI的D3.max_transfer_sz = 64*8, // 最大传输数据大小};spi_device_interface_config_t devcfg={.clock_speed_hz=15*1000*1000, //Clock out at 26 MHz.mode=0, //SPI mode 0.queue_size=7, //We want to be able to queue 7 transactions at a time// .pre_cb=spi_pre_transfer_callback, //Specify pre-transfer callback to handle D/C line};//Initialize the SPI busret=spi_bus_initialize(HSPI_HOST, &buscfg, 0);ESP_ERROR_CHECK(ret);//Attach the LCD to the SPI busret=spi_bus_add_device(HSPI_HOST, &devcfg, &spi);ESP_ERROR_CHECK(ret);}
这没啥好说的,直接参考手册就可以了。
初始化之后就是编写发送命令和发送数据的函数了,为了后面编写屏幕驱动做准备。
定义一个spi句柄。
spi_device_handle_t spi;
定义一个传输的结构体,并进行初始化。
spi_transaction_t t;
memset(&t, 0, sizeof(t));
struct spi_transaction_t {uint32_t flags; ///< Bitwise OR of SPI_TRANS_* flagsuint16_t cmd; /**< Command data, of which the length is set in the ``command_bits`` of spi_device_interface_config_t.** <b>NOTE: this field, used to be "command" in ESP-IDF 2.1 and before, is re-written to be used in a new way in ESP-IDF 3.0.</b>** Example: write 0x0123 and command_bits=12 to send command 0x12, 0x3_ (in previous version, you may have to write 0x3_12).*/uint64_t addr; /**< Address data, of which the length is set in the ``address_bits`` of spi_device_interface_config_t.** <b>NOTE: this field, used to be "address" in ESP-IDF 2.1 and before, is re-written to be used in a new way in ESP-IDF3.0.</b>** Example: write 0x123400 and address_bits=24 to send address of 0x12, 0x34, 0x00 (in previous version, you may have to write 0x12340000).*/size_t length; ///< Total data length, in bitssize_t rxlength; ///< Total data length received, should be not greater than ``length`` in full-duplex mode (0 defaults this to the value of ``length``).void *user; ///< User-defined variable. Can be used to store eg transaction ID.union {const void *tx_buffer; ///< Pointer to transmit buffer, or NULL for no MOSI phaseuint8_t tx_data[4]; ///< If SPI_TRANS_USE_TXDATA is set, data set here is sent directly from this variable.};union {void *rx_buffer; ///< Pointer to receive buffer, or NULL for no MISO phase. Written by 4 bytes-unit if DMA is used.uint8_t rx_data[4]; ///< If SPI_TRANS_USE_RXDATA is set, data is received directly to this variable};
} ; //the rx data should start from a 32-bit aligned address to get around dma issue.
结构体的定义如上代码所示。
由此,编写我们的代码。
void spi_send_cmd(const uint8_t cmd)
{esp_err_t ret;spi_transaction_t t;ds_gpio_set_screen_dc(0);ds_gpio_set_screen_cs(0);memset(&t, 0, sizeof(t)); //Zero out the transaction// t.flags=SPI_TRANS_USE_TXDATA;t.length=8; //Command is 8 bitst.tx_buffer=&cmd; //The data is the cmd itselft.user=(void*)0; //D/C needs to be set to 0ret=spi_device_polling_transmit(spi, &t); //Transmit!ds_gpio_set_screen_cs(1);assert(ret==ESP_OK); //Should have had no issues.
}
void spi_send_data(const uint8_t data)
{esp_err_t ret;spi_transaction_t t;ds_gpio_set_screen_dc(1);ds_gpio_set_screen_cs(0);memset(&t, 0, sizeof(t)); //Zero out the transactiont.length=8; //Len is in bytes, transaction length is in bits.t.tx_buffer=&data; //Datat.user=(void*)1; //D/C needs to be set to 1ret=spi_device_polling_transmit(spi, &t); //Transmit!ds_gpio_set_screen_cs(1);assert(ret==ESP_OK); //Should have had no issues.
}
到这里SPI的代码基本就写完了。
虽然说起来比较简单,但是其实花时间去看,还是会要点时间的。
最后就是编写驱动了。
到了这里,我们需要去翻手册,和移植代码。
首先看驱动一个屏幕的流程图。
这样其实就很好理解了。
然后再去看给的源码。
这是初始化。
观察一下,发现基本是按照流程图来编写的。
打开芯片手册,我们需要知道这些地址都是些什么,设置了什么,代表什么意思,以便根据我们的需求进行修改。
这一段很好理解,复位。
将地址翻到12,发现仍然是复位。
将芯片手册翻到01和11这两个地址。
发现01都是默认值,我们默认即可。
而11设置的是x方向递增,y方向递减。
这个得记住 ,因为这在后面编写驱动程序的时候非常重要,因为这就意味着我们的y_start是从高地址开始的,而x_start是从低地址开始的。
如果不清楚这个的话,很容易出现图像倒过来的现象。
后面的命令就以此类推,一个个去查询,弄懂是什么意思,自己需要什么就可以了。
代码如下:
static void init_display(){vTaskDelay(10 / portTICK_PERIOD_MS);ds_gpio_set_screen_rst(0); // Module resetvTaskDelay(10 / portTICK_PERIOD_MS);ds_gpio_set_screen_rst(1);vTaskDelay(100 / portTICK_PERIOD_MS);lcd_chkstatus(); spi_send_cmd(0x12); //SWRESETlcd_chkstatus(); spi_send_cmd(0x01); //Driver output control spi_send_data(0xC7);spi_send_data(0x00);spi_send_data(0x01);spi_send_cmd(0x11); //data entry mode spi_send_data(0x01);spi_send_cmd(0x44); //set Ram-X address start/end position spi_send_data(0x00);spi_send_data(0x18); //0x0C-->(18+1)*8=200 改为//0x18 -->(24+1)*8 =200spi_send_cmd(0x45); //set Ram-Y address start/end position spi_send_data(0xC7); //0xC7-->(199+1)=200spi_send_data(0x00);spi_send_data(0x00);spi_send_data(0x00); spi_send_cmd(0x3C); //BorderWavefromspi_send_data(0x05); spi_send_cmd(0x18); //Read built-in temperature sensorspi_send_data(0x80); spi_send_cmd(0x4E); // set RAM x address count to 0;spi_send_data(0x00);spi_send_cmd(0x4F); // set RAM y address count to 0X199; spi_send_data(0xC7);spi_send_data(0x00);vTaskDelay(100 / portTICK_PERIOD_MS);lcd_chkstatus();
}
知道了这些,其他就很好办了。
参照STM32中的代码一个个编写。
void deep_sleep(void) //Enter deep sleep mode
{spi_send_cmd(0x10); //enter deep sleepspi_send_data(0x01); vTaskDelay(100 / portTICK_PERIOD_MS);
}void refresh(void)
{spi_send_cmd(0x22); //Display Update Controlspi_send_data(0xF7); spi_send_cmd(0x20); //Activate Display Update Sequencelcd_chkstatus();
}void refresh_part(void)
{spi_send_cmd(0x22); //Display Update Controlspi_send_data(0xFF); spi_send_cmd(0x20); //Activate Display Update Sequencelcd_chkstatus();
}
其中,屏幕全刷很简单,比较麻烦的是局部刷,需要定位到某个区域。
所以这就跟我们之前设置的x和y地址递减递增的方向很有联系了,知道这个,再去编写局部刷新就简单很多了。
因为我用的屏幕是200*200的。
x方向是8位一个,所以X方向是0-24。
y方向是0-199。
所以算起来一共是25*200,共5000个数据。
这也是我们后面显示图像的数组。
局刷之所以麻烦,是因为每次局部刷都需要重新设置一下X和Y的起始地址和计数地址,其他没有什么。
void ds_screen_partial_display(unsigned int x_start,unsigned int y_start,void partial_new(void),unsigned int PART_COLUMN,unsigned int PART_LINE){unsigned int i; unsigned int x_end,y_start1,y_start2,y_end1,y_end2;x_start=x_start/8;x_end=x_start+PART_LINE/8-1; y_start1=0;y_start2=200 - y_start;//这里是因为上下会翻转嘛if(y_start>=256)//判断一下这里有没有超过256,正常不会超{y_start1=y_start2/256;y_start2=y_start2%256;}y_end1=0;y_end2=y_start2+PART_COLUMN-1;if(y_end2>=256){y_end1=y_end2/256;y_end2=y_end2%256; } // Add hardware reset to prevent background color changeds_gpio_set_screen_rst(0); // Module resetvTaskDelay(10 / portTICK_PERIOD_MS);ds_gpio_set_screen_rst(1);vTaskDelay(10 / portTICK_PERIOD_MS);//Lock the border to prevent flashingspi_send_cmd(0x3C); //BorderWavefrom,spi_send_data(0x80); spi_send_cmd(0x44); // set RAM x address start/end, in page 35spi_send_data(x_start); // RAM x address start at 00h;spi_send_data(x_end); // RAM x address end at 0fh(15+1)*8->128 spi_send_cmd(0x45); // set RAM y address start/end, in page 35spi_send_data(y_start2); // RAM y address start at 0127h;spi_send_data(y_start1); // RAM y address start at 0127h;spi_send_data(y_end2); // RAM y address end at 00h;spi_send_data(y_end1); // ????=0 spi_send_cmd(0x4E); // set RAM x address count to 0;spi_send_data(x_start); spi_send_cmd(0x4F); // set RAM y address count to 0X127; spi_send_data(y_start2);spi_send_data(y_start1);spi_send_cmd(0x24); //Write Black and White image to RAMpartial_new();refresh_part();deep_sleep();
}
基本上就没有什么注意点了。
这里X和Y可以自己调节,根据自己的喜好来,只要图像能够正常显示就行。
OK,到这里基本上就结束了。
最后测试正常。
等之后做好自己的图片,取模显示出来,再放照片!!!
本篇参考ESP32技术参考手册、SSD1681芯片技术手册、ESP32API编写规范,水墨屏厂家STM32源码等...